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

棋盘能点击以后,真正决定象棋体验的是规则:马不能穿过蹩腿位置,炮吃子需要炮架,挡在将帅之间的棋子也不能随意移开。《拼棋派对》把规则判断放到 GameLogic.ets,页面通过这些函数决定提示、移动和结算。

这一篇接着上一篇的中央红兵操作,讲清合法性判断的两层结构、移动历史,以及当前悔棋实际恢复哪些状态。运行与截图对应API26模拟器上的主应用1.0.19。

本文目录

先检查棋子走法

isChineseChessMoveShapeLegal 检查棋子的移动形状与阻挡。它不能独自代表完整合法性,因为某一步看起来符合棋子的走法,仍可能让己方将帅暴露在攻击线上。

棋子几何条件额外条件
车同行或同列路径中没有棋子
马一边两格、一边一格对应的马腿位置为空
相、象斜走两格象眼为空且不过河
仕、士斜走一格位于己方九宫
帅、将九宫内正交一格包含飞将相关判断
炮同行或同列移动零阻挡,吃子恰一炮架
兵、卒向前一格过河后可横走,不可后退

判断路径时只统计起点和终点之间的棋子。终点是否为敌方棋子另外判断,不能把目标本身算作炮架。马腿与象眼也应从行列差确定,避免用屏幕方向猜测。

模拟移动,检查己方将帅

isChineseChessMoveLegal 先执行形状检查,通过后复制盘面,把棋子放到目标并清空原点,再检查己方是否被将军。下面是算法流程的等价精简表示,省略了参数保护与具体函数内部实现:

if (!isChineseChessMoveShapeLegal(cells, from, to, owner)) {
  return false;
}
const next = cells.slice();
next[to] = next[from];
next[from] = '';
return !isChineseChessOwnerInCheck(next, owner);

模拟盘面不会赋值到页面状态。否则玩家只是查看某枚棋子的可走位置,盘面就可能被判断函数修改。纯规则函数返回结果,页面确认合法后再提交,是防止这类副作用的关键。

当己方正在被将军时,同样的检查可以过滤掉无法解将的目标。页面提示也会变成“只能走解将的棋”。不必为被将军状态另写一套棋子走法,只需要继续验证移动后的局面。

getChineseChessLegalMoves 枚举90个目标调用完整判断,既用于显示合法落点,也参与“是否还有合法步”的检查。这样视觉提示和结束判断共享同一规则来源。

提交一步棋需要保存什么

页面 commitChineseChessMove 保存起点、终点、移动棋子、被吃棋子和行动阵营。记录被吃棋子很重要,只有from和to无法在悔棋时恢复吃子前的局面。

const move: ChineseChessMove = {
  from,
  to,
  piece: movingPiece,
  captured: capturedPiece,
  player: this.chessCurrent
};
this.chessCells = nextCells;
this.chessHistory = [...this.chessHistory, move].slice(-80);
this.chessMoves += 1;
this.chessSelectedIndex = -1;

当前历史最多保留80手,因此不能把它描述成无限回退。页面展示的最近吃子也只保留一小段列表。它们服务于轻量本地对局,不等同于完整棋谱档案。

提交后先检查胜负,再决定是否切换行动方。已经结束的局面不需要再出现“轮到黑方”之类的正常回合提示;这一顺序可以避免结算文字被回合文字覆盖。

悔棋如何恢复盘面

象棋使用历史数组末尾表示最近一步。先复制历史并pop,再恢复两个位置:

const history = this.chessHistory.slice();
const last = history.pop() as ChineseChessMove;
const cells = this.chessCells.slice();
cells[last.from] = last.piece;
cells[last.to] = last.captured;
this.chessCells = cells;
this.chessHistory = history;
this.chessCurrent = last.player;
this.chessSelectedIndex = -1;
this.chessWinner = '';
this.chessMoves = Math.max(0, this.chessMoves - 1);

恢复行动方应取历史中的player,这比单纯切换红黑更直接。选中状态需要清掉,否则旧落点提示可能还留在新的盘面上。吃子展示也随最后一步撤回。

这里的回退范围有明确边界:当前实现恢复盘面、行动方、手数与相关吃子展示,不恢复双方用时,也不会删除已经写入的战绩。若产品需要比赛级别的完整回退,就需要把时间快照和战绩处理纳入同一次操作,不能仅凭盘面回来了就认为所有数据都已回滚。

区分将军、将死与认输

getChineseChessWinner 检查帅将是否仍然存在。页面还会检查下一方是否有合法走法:没有合法步且被将军,属于将死;没有合法步但未被将军,按当前实现判困毙,由走棋方获胜。

const nextOwner = this.chessCurrent === 'R' ? 'B' : 'R';
const nextInCheck = isChineseChessOwnerInCheck(nextCells, nextOwner);
const nextHasMove = hasChineseChessLegalMove(nextCells, nextOwner);
if (!nextHasMove) {
  this.finishChineseChess(this.chessCurrent,
    nextInCheck ? '将死' : '困毙');
  return;
}

将军本身只是状态提示,仍可继续解将。认输则直接把胜方设置为对方,经过统一结束函数停止计时、播放反馈并写入战绩。当前规则没有在这里实现长将、重复局面和自然限着等完整竞赛裁定,文章中的“合法走法”应理解为本项目已实现的这些移动与自将检查。

实际操作与验收边界

先移动中央红兵58→49,界面显示黑方回合、1手;点击“悔棋”,兵回到58,49重新为空,恢复红方回合、0手。动图展示走兵后的盘面如何通过悔棋恢复。

悔棋恢复中央红兵、红方回合与零手状态

接着在红方回合点击“认输”,显示黑方获胜;再点击“重开”,恢复初始棋子、红方先行、0手和清空的用时。认输和重开的效果如下。

红方认输,显示黑方获胜,再重开初始棋局

本次操作验证的是正常走兵、盘面悔棋和认输重开。马腿、象眼、炮架、自陷将军与将死条件是源码分析内容,本轮没有逐项实测。扩展规则时,应把这些边界局面独立列成用例,分别检查盘面是否变化、行动方是否变化和提示是否正确。

源码入口:GameLogic.ets 的 isChineseChessMoveLegal、hasChineseChessLegalMove;Index.ets 的 commitChineseChessMove、undoChineseChess、finishChineseChess。相关开发资料见HarmonyOS官方文档。

Logo

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

更多推荐