拼棋派对开发实战系列封面

双人象棋的第一步,是让玩家能够准确地选中棋子,并看见下一步可以走到哪里。《拼棋派对》把棋盘、棋子、选中框和落点提示叠在同一个坐标系里。手机上两人面对面操作时,黑方棋子的文字朝向对方;棋盘数据仍保持同一套行列索引。

这一篇从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官方文档查阅。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐