HarmonyOS7应用《拼棋派对》实战:五子棋落子、连线判断与悔棋
五子棋看起来只需要点一下棋盘,但手机上的小棋格容易误触。《拼棋派对》把落子分为“选择空位”和“本人确认”两步:先看清位置,再提交棋子。确认后只检查最新棋子经过的四条线,就能判断是否五连。
这一篇同时记录实操发现的悔棋问题。正常落子与五连获胜已经在API26模拟器上验证;当前1.0.19的悔棋仍有顺序错误,后文给出复现与改进方案。通过这个案例,可以看清棋盘位置与操作历史为什么必须分开保存。
本文目录
15×15盘面与待确认位置
createEmptyGomoku 创建225个空字符串,A、B表示双方棋子。索引转换与象棋类似,只是每行15个位置:row = Math.floor(index / 15),col = index % 15。页面上的列字母和行号供玩家阅读,内部继续使用零起点索引。
页面至少需要区分盘面、当前玩家、选择位置、已落手数和胜者。gomokuSelectedIndex 不等于已经落下的棋子,只有确认后才写入 gomokuCells。把这两个状态混在一起,撤销选择就会变成删除正式棋子,计分和胜负也可能被提前触发。
selectGomokuCell 会拒绝已占用位置和结束后的操作;选中空点时记录索引,并更新“已选,玩家A确认”提示。再次选其他空点可以调整落点,玩家不需要先悔棋才能纠正尚未确认的位置。
本人确认后才提交落子
confirmGomokuForPlayer 检查调用方是不是当前玩家,也检查比赛是否结束。通过后才进入提交函数。手机竖屏显示当前方的“A确认”或“B确认”,其他布局可以把双方确认入口放在各自区域,但事件仍携带玩家身份。
下面摘录提交函数的关键状态更新:
const cells = this.gomokuCells.slice();
cells[this.gomokuSelectedIndex] = this.gomokuCurrent;
this.gomokuCells = cells;
this.gomokuMoves += 1;
if (isGomokuWin(cells, this.gomokuSelectedIndex, this.gomokuCurrent)) {
const winner = this.gomokuCurrent === 'A' ? '玩家A' : '玩家B';
this.gomokuWinner = winner;
this.gomokuSelectedIndex = -1;
return;
}
this.gomokuCurrent = this.gomokuCurrent === 'A' ? 'B' : 'A';
this.gomokuSelectedIndex = -1;
片段省略了获胜声音、提示与战绩写入。核心顺序是复制盘面、落子、累加手数、判胜、未胜时换人。赢棋后提前返回,避免胜者刚显示出来又被切回正常回合。
沿四个方向检查连续棋子
最新落子之外的盘面没有变化,因此新形成的五连必定经过这枚棋子。项目检查横线、竖线和两条斜线,每条线分别向正反两个方向计数。两个方向都包括起点,所以总数需要减一。
以横线为例,实际规则是:
countGomokuLine(cells, row, col, 0, 1, player) +
countGomokuLine(cells, row, col, 0, -1, player) - 1 >= 5
计数函数每次推进一格,遇到空点、对方棋子或边界立即停止。它使用行列边界,而不是仅检查数组索引是否落在0—224之间:第一行最后一格与第二行第一格虽然索引连续,却不是一条横线。
while (currentRow >= 0 && currentRow < 15 &&
currentCol >= 0 && currentCol < 15) {
const index = currentRow * 15 + currentCol;
if (cells[index] !== player) {
break;
}
count += 1;
currentRow += rowStep;
currentCol += colStep;
}
当前使用“连续五枚及以上获胜”的休闲规则,没有三三、四四和长连禁手。这与含禁手的专业规则不同。介绍玩法时应写清规则范围,不能只因棋盘是15×15就宣称符合所有竞赛规则。
九手五连的实际效果
在空盘按以下索引交替落子,每一次都先选择再确认:A为110、111、112、113、114;B为140、142、144、146。A的五枚位于同一行相邻位置,B的四枚分散,避免提前形成五连。
第八手后页面仍处于玩家A待落子状态;第九手确认后显示玩家A获胜,随后“再来一局”恢复第1手。图中可对照A的横向连线与结束面板。
这里验证了横向五连、结束提示和重开。纵线与两条斜线使用相同计数函数,但本次截图没有逐条覆盖它们;补充规则测试时还应加入被空点截断、被对方截断和边界跨行的情况。
复现悔棋顺序错误
实操中发现,当前 undoGomoku 从数组尾部向前查找第一枚非空棋子,然后删除。这找到的是“位置索引最大的一枚”,不是“最后一步”。只沿索引递增的位置落子,会掩盖这个问题。
复现只需要两步:A先落200,即第14行第6列;B后落10,即第1行第11列。此时点击悔棋,预期删除最后落下的B棋子,保留A棋子。实际结果相反:A棋子消失,B棋子留下。动图先显示两枚棋子均在盘上,再显示悔棋后的错误盘面。
出错代码的核心是:
for (let index = cells.length - 1; index >= 0; index--) {
if (cells[index].length > 0) {
cells[index] = '';
// 更新盘面、手数与行动方
return;
}
}
盘面数组只表达“哪里有谁的棋子”,没有保存落子时间。仅从最终位置无法还原操作先后,这个问题不能靠换一种数组遍历方向解决。
用落子历史正确回退
下面是建议加入的历史方案,尚未写入本轮主应用,也不把前面的错误截图称为修复后效果。定义一条记录保存index与player,确认落子时追加记录,悔棋时pop最后一条。
interface GomokuMove {
index: number;
player: string;
}
@State gomokuHistory: GomokuMove[] = [];
确认时,在清除选择和切换玩家之前记录本次落子:
this.gomokuHistory = [...this.gomokuHistory, {
index: this.gomokuSelectedIndex,
player: this.gomokuCurrent
}];
回退主体可以改成以下形式,外围继续保留当前“不允许结束后悔棋”的产品规则:
const history = this.gomokuHistory.slice();
if (history.length === 0) {
return;
}
const last = history.pop() as GomokuMove;
const cells = this.gomokuCells.slice();
cells[last.index] = '';
this.gomokuCells = cells;
this.gomokuHistory = history;
this.gomokuCurrent = last.player;
this.gomokuSelectedIndex = -1;
this.gomokuMoves = Math.max(0, this.gomokuMoves - 1);
重开时还要清空history。如果对局持久化,需要盘面与历史一起保存、一起恢复,否则重新进入页面后又失去先后顺序。接入修改后,至少复测200→10、连续两次悔棋、无历史悔棋和重开后悔棋,再执行构建和模拟器验收。
这次五子棋实操得到两项清楚的结果:确认落子与横向五连已跑通;悔棋暴露出数据模型缺少时间顺序的问题。记录问题和修复路径,比把错误行为包装成完整功能更能帮助后续开发。
源码入口:GameLogic.ets 的 isGomokuWin、countGomokuLine;Index.ets 的 selectGomokuCell、confirmGomokuMove、undoGomoku。ArkTS状态管理参考HarmonyOS官方文档。
更多推荐


所有评论(0)