鸿蒙元服务实战-笑笑五子棋(5)

鸿蒙元服务实战-笑笑五子棋(5)

来到最后一章了,这一章节讲两个部分。一是笑笑五子棋的卡片制作,二就是发布上架。

卡片介绍

Form Kit(卡片开发框架)提供了一种在桌面、锁屏等系统入口嵌入显示应用信息的开发框架和 API,可以将应用内用户关注的重要

信息或常用操作抽取到服务卡片(以下简称"卡片")上,通过将卡片添加到桌面上,以达到信息展示、服务直达的便捷体验效果。

新建卡片

卡片类型分为两种:

  1. 静态卡片 功能稍弱
  2. 动态卡片 功能强一些
  1. 选择卡片的属性

  2. 然后你就得到了以下文件

卡片文件解释

EntryFormAbility.ets

entry/src/main/ets/entryformability/EntryFormAbility.ets

该文件可以定义卡片的生命周期,传递数据给卡片等

WidgetCard

entry/src/main/ets/widget/pages/WidgetCard.ets

该文件是卡片的主要展示和业务功能页面。 卡片外观、功能主要由它来提供

form_config.json

entry/src/main/resources/base/profile/form_config.json

该文件是卡片的配置文件,比如卡片的图标、卡片的名字、卡片的种类等等都可以在这配置

获取卡片宽度

卡片的 api 和元服务的 api 稍有区别,所以在开发的需要额外注意

这里在 entry/src/main/ets/entryformability/EntryFormAbility.ets 内,可以设置卡片创建的时获取卡片的宽度

因为卡片有不同的规格尺寸,所以可以动态来获取。

jsx 复制代码
  onAddForm(want: Want) {
    let formData: Record<string, number> = {
      "canwidth": px2vp((want.parameters?.[formInfo.FormParam.WIDTH_KEY] as number) * 2),
    };
    return formBindingData.createFormBindingData(formData);
  }

卡片中是无法使用 AppStorage,所以需要使用 Localstorage 来代替,进行数据传递

卡片中接收

jsx 复制代码
@Entry
@Component
struct WidgetCard {
  @LocalStorageProp("canwidth")
  canwidth: number = 0
  @LocalStorageProp("canwidth")
  canheight: number = 0

完成卡片下棋逻辑

因为卡片的下棋逻辑和宿主-元服务本身几乎一致。因此在实际开发中,可以将它们共同的逻辑抽离出来方便管理。这里就 cv 复用了。

jsx 复制代码
@Entry
@Component
struct WidgetCard {
  @LocalStorageProp("canwidth")
  canwidth: number = 0
  @LocalStorageProp("canwidth")
  canheight: number = 0
  settings: RenderingContextSettings = new RenderingContextSettings(true);
  ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  // 棋盘参数
  gridSize: number = 15;
  cellSize: number = this.canwidth / this.gridSize;
  radius: number = this.cellSize / 2 - 5; // 棋子的半径
  // 棋盘数据
  board: number[][] = []
  currentPlayer: number = 1; // 当前玩家 (1: 黑子, 2: 白子)
  gameOver: boolean = false;
  @State
  textContent: string = ""
  // 处理玩家落子
  handleClick = async (event: ClickEvent) => {
    if (this.gameOver) {
      return;
    }

    const x = event.x;
    const y = event.y;

    const col = Math.floor(x / this.cellSize);
    const row = Math.floor(y / this.cellSize);

    if (this.board[row] && this.board[row][col] === 0) {
      this.board[row][col] = this.currentPlayer;
      this.drawBoard();

      if (this.checkWin(row, col)) {
        this.textContent = this.currentPlayer === 1 ? '黑子胜利!' : '白子胜利!';
        this.gameOver = true;
        // AlertDialog.show({ message: this.textContent })


      } else {
        this.currentPlayer = this.currentPlayer === 1 ? 2 : 1;
        this.textContent = this.currentPlayer === 1 ? '轮到黑子落子' : '轮到白子落子';
      }
    } else {
      // promptAction.showToast({ message: `请点击中棋盘对位位置` })
    }
  }

  aboutToAppear(): void {

  }

  // 绘制棋盘
  drawBoard = () => {
    this.ctx.clearRect(0, 0, this.canwidth, this.canwidth);

    // 绘制网格
    this.ctx.strokeStyle = "#000";
    this.ctx.lineWidth = 1;
    for (let i = 0; i < this.gridSize; i++) {
      this.ctx.beginPath();
      this.ctx.moveTo(this.cellSize * i, 0);
      this.ctx.lineTo(this.cellSize * i, this.canwidth);
      this.ctx.stroke();

      this.ctx.beginPath();
      this.ctx.moveTo(0, this.cellSize * i);
      this.ctx.lineTo(this.canwidth, this.cellSize * i);
      this.ctx.stroke();
    }

    // 绘制已落的棋子
    for (let row = 0; row < this.gridSize; row++) {
      for (let col = 0; col < this.gridSize; col++) {
        if (this.board[row][col] !== 0) {
          this.ctx.beginPath();
          this.ctx.arc(col * this.cellSize + this.cellSize / 2, row * this.cellSize + this.cellSize / 2, this.radius, 0,
            2 * Math.PI);
          this.ctx.fillStyle = this.board[row][col] === 1 ? 'black' : 'white';
          this.ctx.fill();
          this.ctx.stroke();
        }
      }
    }
  }
  // 判断是否有五子连珠
  checkWin = (row: number, col: number) => {
    interface abc {
      dr: number
      dc: number
    }

    const directions: abc[] = [
      { dr: 0, dc: 1 }, // 水平
      { dr: 1, dc: 0 }, // 垂直
      { dr: 1, dc: 1 }, // 主对角线
      { dr: 1, dc: -1 }// 副对角线
    ];

    for (let i = 0; i < directions.length; i++) {
      const dr = directions[i].dr
      const dc = directions[i].dc
      let count = 1;

      // 向一个方向检查
      for (let i = 1; i < 5; i++) {
        let r = row + dr * i;
        let c = col + dc * i;
        if (r >= 0 && r < this.gridSize && c >= 0 && c < this.gridSize && this.board[r][c] === this.currentPlayer) {
          count++;
        } else {
          break;
        }
      }

      // 向另一个方向检查
      for (let i = 1; i < 5; i++) {
        let r = row - dr * i;
        let c = col - dc * i;
        if (r >= 0 && r < this.gridSize && c >= 0 && c < this.gridSize && this.board[r][c] === this.currentPlayer) {
          count++;
        } else {
          break;
        }
      }

      // 如果连续五个相同的棋子,则胜利
      if (count >= 5) {
        return true;
      }
    }

    return false;
  }
  // 初始化游戏
  initGame = () => {
    this.board = []
    for (let index = 0; index < this.gridSize; index++) {
      const arr: number[] = []
      for (let index2 = 0; index2 < this.gridSize; index2++) {
        arr.push(0)

      }
      this.board.push(arr)

    }
    this.currentPlayer = 1;
    this.gameOver = false;
    this.textContent = '轮到黑子落子';
    this.drawBoard();
  }

  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      Canvas(this.ctx)
        .width(this.canwidth)
        .height(this.canwidth)
        .backgroundColor(Color.Orange)
        .onReady(() => {
          this.cellSize = this.canwidth / this.gridSize;
          this.radius = this.cellSize / 2 - 5; // 棋子的半径
          this.initGame()
        })
        .onClick(
          this.handleClick
        )

      Text(this.textContent)
        .fontSize(14)
        .padding(5)
        .fontColor(Color.White)
        .fontWeight(700)

    }
    .width("100%")
    .height("100%")
  }
}

调整卡片的图标和名字

主要业务开发完毕了,可以调整卡片的展示信息

这部分信息在 entry/src/main/resources/base/profile/form_config.json中配置:

  1. displayName 标题
  2. description 简介
jsx 复制代码
{
  "forms": [
    {
      "name": "widget",
      "displayName": "$string:widget_display_name",
      "description": "$string:widget_desc",
      "src": "./ets/widget/pages/WidgetCard.ets",
      "uiSyntax": "arkts",
      "window": {
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "colorMode": "auto",
      "isDynamic": true,
      "isDefault": true,
      "updateEnabled": false,
      "scheduledUpdateTime": "10:30",
      "updateDuration": 1,
      "defaultDimension": "4*4",
      "supportDimensions": [
        "2*2",
        "4*4"
      ]
    }
  ]
}

发布上架

最后,如果要将卡片发布上架,还需要做一些小处理

  1. 设置你的元服务的展示图标
  2. 配置证书
  3. 打包成 Hap
  4. 在 AGC 平台上发布上架等等
  5. 具体流程可以参考底部的文章

参考链接

  1. 卡片开发
  2. HarmonyOS Next 实战卡片开发 01
  3. HarmonyOS Next 实战卡片开发 02
  4. HarmonyOS Next 实战卡片开发 03
  5. HarmonyOS Next 最新 元服务新建到上架全流程

代码仓库

bash 复制代码
https://gitee.com/ukSir/laughing-at-gomoku

总结

至此,笑笑五子棋的开发上架流程已经完毕。

如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯,欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。

相关推荐
2401_897579653 分钟前
AI赋能房地产:利用AI前端技术提升VR/AR浏览体验
前端·人工智能·vr
小馋喵知识杂货铺12 分钟前
XPath语法详解及案例讲解
java·前端·javascript
komo莫莫da14 分钟前
第5章——与HTTP协作的Web服务器
服务器·前端·http
华科云商xiao徐22 分钟前
Python 2.7.3中使用eval和list来求解数学表达式
前端
一个单纯的少年39 分钟前
Chrome 查看 session 信息
前端·chrome·功能测试·ui·交互·ux
棋丶1 小时前
VUE2和VUE3的区别
开发语言·前端·javascript
screct_demo2 小时前
详细讲一下Vue的路由Vue Router的安装,配置,基础用法和详细用法以及实践中应用
前端·javascript·vue.js
林涧泣2 小时前
【Uniapp-Vue3】使用ref定义响应式数据变量
前端·vue.js·uni-app
HelloZheQ2 小时前
CSS 变量:让你的样式更灵活、更易维护
前端·css·tensorflow
Lu_Ca2 小时前
鸿蒙APP之从开发到发布的一点心得
华为·harmonyos·鸿蒙