鸿蒙元服务实战-笑笑五子棋(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

总结

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

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

相关推荐
牛奶7 小时前
AI辅助开发最佳实践:2026年新方法
前端·aigc·ai编程
C澒8 小时前
微前端容器标准化:公共能力标准化
前端·架构
Setsuna_F_Seiei8 小时前
AI 对话应用之 JS 的流式接口数据处理
前端·javascript·ai编程
青柠代码录8 小时前
【Vue3】Vue Router 4 路由全解
前端·vue.js
无限大69 小时前
《AI观,观AI》:专栏总结+答疑|吃透核心,解决你用AI的所有困惑
前端·后端
蜡台9 小时前
element-ui 2 el-tree 内容超长滚动条不显示问题
前端·vue.js·elementui·el-tree·v-deep
小小小小宇11 小时前
软键盘常见问题(二)
前端
小小小小宇11 小时前
软键盘常见问题
前端
国医中兴11 小时前
Flutter 三方库 stack_blur 鸿蒙适配指南 - 实现工业级高性能模糊滤镜、在 OpenHarmony 上打造极致视觉质感实战
flutter·华为·harmonyos
小小小小宇11 小时前
富文本编辑器知识体系(三)
前端