HarmonyOS7应用《拼棋派对》实战:双人象棋的棋盘绘制与选子交互
双人象棋的第一步,是让玩家能够准确地选中棋子,并看见下一步可以走到哪里。《拼棋派对》把棋盘、棋子、选中框和落点提示叠在同一个坐标系里。手机上两人面对面操作时,黑方棋子的文字朝向对方;棋盘数据仍保持同一套行列索引。
这一篇从90个交叉点出发,完成数据到画面的映射,再拆解选子交互。合法走法和悔棋留到下一篇。示例对应主应用1.0.19,运行环境是API26的HarmonyOS7手机模拟器。
本文目录
用数组表示交叉点
中国象棋有10行、9列交叉点,共90个位置。这里的“点”很关键:棋子放在线的交叉处,而不是像拼图一样放在方格中心。如果直接套用普通格子布局,再单独绘制背景线,很容易出现棋子和线错半格的问题。
GameLogic.ets 的 createChineseChessBoard 创建盘面,空点用空字符串表示,红兵用 R兵 表示。第一字符描述阵营,后面的文字描述角色。显示时再由 chineseChessPieceDisplayLabel 处理红相与黑象、红帅与黑将等名称。逻辑身份与显示文字分开后,规则判断不必依赖页面上的字体或旋转方向。
下面是该盘面的索引关系,零起点行列用于内部计算:
const index = row * 9 + col;
const row = Math.floor(index / 9);
const col = index % 9;
例如中央红兵位于第6行、第4列,索引是58;向上前进一步到第5行、第4列,索引是49。前进是行减一,不是屏幕坐标加一。黑方棋子朝下前进,因此规则函数需要同时知道阵营和行列。
初始盘面中,黑方底线占据0—8,红方底线占据81—89。炮和兵分别放在对应行上,页面只遍历这90个索引绘制。初始化数据是规则层的职责,页面负责呈现当前数组。
从索引计算绘制位置
项目给棋盘四周保留边距,把交叉点分布在6%到94%之间。9列产生8个横向间隔,10行产生9个纵向间隔,不能把间隔数直接写成列数或行数。
Index.ets 的位置计算采用以下公式,数值来自当前源码:
private chineseChessPointXPercentByCol(col: number): number {
return this.chineseChessBoardStartPercent() +
col * (this.chineseChessBoardSpanPercent() / 8);
}
private chineseChessPointYPercentByRow(row: number): number {
return this.chineseChessBoardStartPercent() +
row * (this.chineseChessBoardSpanPercent() / 9);
}
起点函数返回6,跨度函数返回88。横线、竖线、九宫线和棋子都引用这些坐标函数,窗口变宽时不需要分别修正每个元素。河界把内部竖线分成上下两段,边线则贯穿整个盘面。河界文字也从相邻行的位置推导,避免写死像素偏移。
这里使用百分比解决位置映射,棋盘外层仍需要根据可用空间确定宽高。百分比不会自动保证圆形棋子、合理字号和足够大的触摸面积,这些需要组件自身的宽高和样式一起配合。
棋子、热区与提示分层
ChineseChessPoint 采用Stack容纳选中框、落点提示和棋子。每个点都保留点击热区,包括没有棋子的空点。这样玩家既可以选择棋子,也能直接点击合法空点走棋。
下面是组件尾部的实际代码:
.width(`${this.chineseChessPointTouchPercent()}%`)
.aspectRatio(1)
.position({
x: `${this.chineseChessPointLeftPercent(index)}%`,
y: `${this.chineseChessPointTopPercent(index)}%`
})
.onClick(() => this.handleChineseChessTap(index))
位置函数以交叉点为中心,减去热区宽度的一半,把左上角转换为组件位置。当前宽屏热区宽度为10.2%,其他布局为11.2%。这组值属于项目的视觉参数,不能脱离棋盘容器直接照搬到所有设备。
选中棋子使用绿色边框,合法空点使用绿色圆点,有敌方棋子的合法落点使用较大的外圈。提示不覆盖棋子文字,玩家能同时读到棋子身份和操作状态。将军状态另外标注,避免把危险状态与普通可走状态混成同一种颜色。
面对面布局仅在棋子显示层旋转黑方文字。事件仍传入原始index,规则函数也仍读取同一数组。若把显示旋转同时写入数组顺序,选中的棋子和规则检查的棋子就可能不再是同一个。
用选择状态驱动交互
chessSelectedIndex 为负数表示没有选子。玩家点击己方棋子时记录索引,点击同一棋子取消,点击另一枚己方棋子则切换选择。点击空点或对方棋子,才进入走法校验。
| 当前状态 | 点击对象 | 页面结果 |
|---|---|---|
| 没有选子 | 己方棋子 | 记录索引、绘制选中框 |
| 没有选子 | 空点或对方棋子 | 提示先选自己的棋子 |
| 已有选子 | 同一枚棋子 | 取消选择 |
| 已有选子 | 另一枚己方棋子 | 切换选择 |
| 已有选子 | 合法目标 | 提交移动并清除选择 |
| 已有选子 | 非法目标 | 保留盘面、提示原因 |
这套分支顺序可以避免“点自己的另一个棋子被解释成吃子”。落点提示调用与实际提交一致的合法性判断,因此提示和走棋没有两套不同规则。
模拟器里的选子与落点
从对局页进入“棋类游戏”,选择“双人象棋”。初始界面为红方回合、0手。点击中央红兵后,选中框出现在兵的位置;前方空点成为可走目标。动图依次展示初始盘面、红兵选中和前进一步。
继续点击前方落点,中央兵从58移动到49,原位置空出,状态变为黑方回合、1手。这个结果同时验证了点击命中、索引计算和提交后的状态刷新。
容易踩到的布局问题
棋子偏离交叉点时,先检查是否混用了组件左上角和交叉点中心;首尾位置不对时,再检查间隔数是否写成9和10。不要先加一串像素补偿,这会让当前屏幕看起来正确,却在窗口变化时再次错位。
还有一个常见问题是只给棋子绑定事件。空点没有组件,就无法直接落子。保留90个点位热区,再把有棋子的点绘制成棋子,是本项目能同时处理选子与落子的基础。
这篇完成了从盘面数组到可操作棋盘的连接。下一篇沿用同一盘面,进一步检查阻挡、自陷将军、走棋历史和结束条件。
源码入口:entry/src/main/ets/common/GameLogic.ets 的 createChineseChessBoard;entry/src/main/ets/pages/Index.ets 的 ChineseChessPoint、handleChineseChessTap 和坐标函数。ArkUI组件用法可结合HarmonyOS官方文档查阅。
更多推荐

所有评论(0)