鸿蒙元服务实战-笑笑五子棋(5)
来到最后一章了,这一章节讲两个部分。一是笑笑五子棋的卡片制作,二就是发布上架。
卡片介绍
Form Kit(卡片开发框架)提供了一种在桌面、锁屏等系统入口嵌入显示应用信息的开发框架和 API,可以将应用内用户关注的重要
信息或常用操作抽取到服务卡片(以下简称“卡片”)上,通过将卡片添加到桌面上,以达到信息展示、服务直达的便捷体验效果。
新建卡片
卡片类型分为两种:
静态卡片 功能稍弱
动态卡片 功能强一些
选择卡片的属性
然后你就得到了以下文件
卡片文件解释
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); }
复制代码
卡片中是无法使用 AppStorage,所以需要使用 Localstorage 来代替,进行数据传递
卡片中接收
@Entry@Componentstruct WidgetCard { @LocalStorageProp("canwidth") canwidth: number = 0 @LocalStorageProp("canwidth") canheight: number = 0
复制代码
完成卡片下棋逻辑
因为卡片的下棋逻辑和宿主-元服务本身几乎一致。因此在实际开发中,可以将它们共同的逻辑抽离出来方便管理。这里就 cv 复用了。
@Entry@Componentstruct 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中配置:
displayName 标题
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" ] } ]}
复制代码
发布上架
最后,如果要将卡片发布上架,还需要做一些小处理
设置你的元服务的展示图标
配置证书
打包成 Hap
在 AGC 平台上发布上架等等
具体流程可以参考底部的文章
参考链接
卡片开发
HarmonyOS Next 实战卡片开发 01
HarmonyOS Next 实战卡片开发 02
HarmonyOS Next 实战卡片开发 03
HarmonyOS Next 最新 元服务新建到上架全流程
代码仓库
https://gitee.com/ukSir/laughing-at-gomoku
复制代码
总结
至此,笑笑五子棋的开发上架流程已经完毕。
如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯,欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。
评论