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

总结

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

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

相关推荐
孤水寒月1 分钟前
基于HTML的悬窗可拖动记事本
前端·css·html
祝余呀10 分钟前
html初学者第一天
前端·html
耶啵奶膘3 小时前
uniapp+firstUI——上传视频组件fui-upload-video
前端·javascript·uni-app
视频砖家3 小时前
移动端Html5播放器按钮变小的问题解决方法
前端·javascript·viewport功能
lyj1689974 小时前
vue-i18n+vscode+vue 多语言使用
前端·vue.js·vscode
小白变怪兽5 小时前
一、react18+项目初始化(vite)
前端·react.js
ai小鬼头5 小时前
AIStarter如何快速部署Stable Diffusion?**新手也能轻松上手的AI绘图
前端·后端·github
墨菲安全6 小时前
NPM组件 betsson 等窃取主机敏感信息
前端·npm·node.js·软件供应链安全·主机信息窃取·npm组件投毒
GISer_Jing6 小时前
Monorepo+Pnpm+Turborepo
前端·javascript·ecmascript
天涯学馆6 小时前
前端开发也能用 WebAssembly?这些场景超实用!
前端·javascript·面试