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

万少skr
发布于 2025-1-7 12:06
浏览
0收藏

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

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

卡片介绍

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

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

鸿蒙元服务实战-笑笑五子棋(5)-鸿蒙开发者社区

新建卡片

卡片类型分为两种:

  1. 静态卡片 功能稍弱
  2. 动态卡片 功能强一些

鸿蒙元服务实战-笑笑五子棋(5)-鸿蒙开发者社区

  1. 选择卡片的属性

    鸿蒙元服务实战-笑笑五子棋(5)-鸿蒙开发者社区

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

    鸿蒙元服务实战-笑笑五子棋(5)-鸿蒙开发者社区

卡片文件解释

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 内,可以设置卡片创建的时获取卡片的宽度

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

  onAddForm(want: Want) {
    let formData: Record<string, number> = {
      "canwidth": px2vp((want.parameters?.[formInfo.FormParam.WIDTH_KEY] as number) * 2),
    };
    return formBindingData.createFormBindingData(formData);
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

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

卡片中接收

@Entry
@Component
struct WidgetCard {
  @LocalStorageProp("canwidth")
  canwidth: number = 0
  @LocalStorageProp("canwidth")
  canheight: number = 0
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

完成卡片下棋逻辑

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

@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%")
  }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.

调整卡片的图标和名字

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

鸿蒙元服务实战-笑笑五子棋(5)-鸿蒙开发者社区

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

  1. displayName 标题
  2. description 简介
{
  "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.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

发布上架

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

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

参考链接

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

代码仓库

https://gitee.com/ukSir/laughing-at-gomoku
  • 1.

总结

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

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

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
收藏
回复
举报
回复
    相关推荐