鸿蒙元服务实战-笑笑五子棋(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
内,可以设置卡片创建的时获取卡片的宽度
因为卡片有不同的规格尺寸,所以可以动态来获取。
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
中配置:
displayName
标题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"
]
}
]
}
发布上架
最后,如果要将卡片发布上架,还需要做一些小处理
- 设置你的元服务的展示图标
- 配置证书
- 打包成 Hap
- 在 AGC 平台上发布上架等等
- 具体流程可以参考底部的文章
参考链接
- 卡片开发
- HarmonyOS Next 实战卡片开发 01
- HarmonyOS Next 实战卡片开发 02
- HarmonyOS Next 实战卡片开发 03
- HarmonyOS Next 最新 元服务新建到上架全流程
代码仓库
bash
https://gitee.com/ukSir/laughing-at-gomoku
总结
至此,笑笑五子棋的开发上架流程已经完毕。
如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯,欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。