4. 走法规则与判定
2026/4/13大约 17 分钟
4. 走法规则与判定:让棋子按规矩动起来
4.1 走法验证的整体思路
每种棋子都有自己的走法规则。验证一步棋是否合法,就像查字典一样——先看"谁"在走(棋子类型),再查"怎么走"(走法规则)。
走法验证流程:
点击棋子 → 判断棋子类型 → 生成所有可能的目标位置
↓
逐一检查每个目标位置
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
边界检查 碰撞检查 特殊规则
(出界了吗?) (有自己人吗?) (蹩腿?塞眼?)
↓ ↓ ↓
└──────────────┼──────────────┘
↓
合法走法列表
↓
再过滤"送将"的走法最容易忽略的规则
走完一步之后,自己的将/帅不能处于被攻击状态(不能"送将")。很多人写象棋程序时忘记了这个检查。
4.2 棋子走法详解
4.2.1 帅/将(King)—— 老板
走法:在九宫格内,每次直走一格(上下左右)。
限制:
- 只能在九宫格里走(红方:col 3-5, row 7-9;黑方:col 3-5, row 0-2)
- 不能走到有自己棋子的位置
- 不能和对方的将/帅在同一列"面对面"(中间无棋子阻隔)
C#
/// <summary>
/// 获取帅/将的所有合法走法
/// </summary>
private List<Vector2I> GetKingMoves(int col, int row, Piece3D.PieceColor color, Piece3D[,] board)
{
var moves = new List<Vector2I>();
// 四个方向:上、下、左、右
int[][] directions = {
new[] { 0, -1 }, // 上
new[] { 0, 1 }, // 下
new[] { -1, 0 }, // 左
new[] { 1, 0 } // 右
};
// 九宫格范围
int minCol = 3, maxCol = 5;
int minRow, maxRow;
if (color == Piece3D.PieceColor.Red)
{
minRow = 7; maxRow = 9; // 红方九宫
}
else
{
minRow = 0; maxRow = 2; // 黑方九宫
}
foreach (var dir in directions)
{
int newCol = col + dir[0];
int newRow = row + dir[1];
// 检查是否在九宫格内
if (newCol < minCol || newCol > maxCol ||
newRow < minRow || newRow > maxRow)
continue;
// 检查目标位置是否有自己的棋子
var target = board[newCol, newRow];
if (target != null && target.Color == color)
continue;
moves.Add(new Vector2I(newCol, newRow));
}
return moves;
}GDScript
## 获取帅/将的所有合法走法
func get_king_moves(col: int, row: int, color: Piece3D.PieceColor, board: Array) -> Array[Vector2i]:
var moves: Array[Vector2i] = []
# 四个方向:上、下、左、右
var directions := [
Vector2i(0, -1), # 上
Vector2i(0, 1), # 下
Vector2i(-1, 0), # 左
Vector2i(1, 0) # 右
]
# 九宫格范围
var min_col: int = 3
var max_col: int = 5
var min_row: int
var max_row: int
if color == Piece3D.PieceColor.RED:
min_row = 7
max_row = 9 # 红方九宫
else:
min_row = 0
max_row = 2 # 黑方九宫
for dir in directions:
var new_col: int = col + dir.x
var new_row: int = row + dir.y
# 检查是否在九宫格内
if new_col < min_col or new_col > max_col or new_row < min_row or new_row > max_row:
continue
# 检查目标位置是否有自己的棋子
var target: Piece3D = board[new_col][new_row]
if target != null and target.piece_color == color:
continue
moves.append(Vector2i(new_col, new_row))
return moves4.2.2 仕/士(Advisor)—— 贴身保镖
走法:在九宫格内,每次斜走一格。
限制:
- 只能在九宫格里走
- 不能走到有自己棋子的位置
C#
/// <summary>
/// 获取仕/士的所有合法走法
/// </summary>
private List<Vector2I> GetAdvisorMoves(int col, int row, Piece3D.PieceColor color, Piece3D[,] board)
{
var moves = new List<Vector2I>();
// 四个斜方向
int[][] directions = {
new[] { -1, -1 }, // 左上
new[] { -1, 1 }, // 左下
new[] { 1, -1 }, // 右上
new[] { 1, 1 } // 右下
};
int minCol = 3, maxCol = 5;
int minRow, maxRow;
if (color == Piece3D.PieceColor.Red)
{
minRow = 7; maxRow = 9;
}
else
{
minRow = 0; maxRow = 2;
}
foreach (var dir in directions)
{
int newCol = col + dir[0];
int newRow = row + dir[1];
if (newCol < minCol || newCol > maxCol ||
newRow < minRow || newRow > maxRow)
continue;
var target = board[newCol, newRow];
if (target != null && target.Color == color)
continue;
moves.Add(new Vector2I(newCol, newRow));
}
return moves;
}GDScript
## 获取仕/士的所有合法走法
func get_advisor_moves(col: int, row: int, color: Piece3D.PieceColor, board: Array) -> Array[Vector2i]:
var moves: Array[Vector2i] = []
# 四个斜方向
var directions := [
Vector2i(-1, -1), # 左上
Vector2i(-1, 1), # 左下
Vector2i(1, -1), # 右上
Vector2i(1, 1) # 右下
]
var min_col: int = 3
var max_col: int = 5
var min_row: int
var max_row: int
if color == Piece3D.PieceColor.RED:
min_row = 7
max_row = 9
else:
min_row = 0
max_row = 2
for dir in directions:
var new_col: int = col + dir.x
var new_row: int = row + dir.y
if new_col < min_col or new_col > max_col or new_row < min_row or new_row > max_row:
continue
var target: Piece3D = board[new_col][new_row]
if target != null and target.piece_color == color:
continue
moves.append(Vector2i(new_col, new_row))
return moves4.2.3 相/象(Elephant)—— 田字飞将
走法:走"田"字对角,即每次斜走两格。
限制:
- 不能过河(红方:row 5-9,黑方:row 0-4)
- "塞象眼":田字中心有棋子就不能走
- 不能走到有自己棋子的位置
象在 A,想飞到 D(走田字):
A _ B
_ C _ ← 如果 C 处有棋子,就是"塞象眼",飞不了
_ _ DC#
/// <summary>
/// 获取相/象的所有合法走法
/// </summary>
private List<Vector2I> GetElephantMoves(int col, int row, Piece3D.PieceColor color, Piece3D[,] board)
{
var moves = new List<Vector2I>();
// 四个田字方向:目标偏移 + 眼位偏移
var directions = new (int dCol, int dRow, int eyeCol, int eyeRow)[]
{
(-2, -2, -1, -1), // 左上田
(-2, 2, -1, 1), // 左下田
(2, -2, 1, -1), // 右上田
(2, 2, 1, 1) // 右下田
};
foreach (var (dCol, dRow, eyeCol, eyeRow) in directions)
{
int newCol = col + dCol;
int newRow = row + dRow;
// 边界检查
if (newCol < 0 || newCol > 8 || newRow < 0 || newRow > 9)
continue;
// 不能过河检查
if (color == Piece3D.PieceColor.Red && newRow < 5)
continue;
if (color == Piece3D.PieceColor.Black && newRow > 4)
continue;
// 塞象眼检查:田字中心有棋子
int eyePosX = col + eyeCol;
int eyePosY = row + eyeRow;
if (board[eyePosX, eyePosY] != null)
continue;
// 目标位置有自己人
var target = board[newCol, newRow];
if (target != null && target.Color == color)
continue;
moves.Add(new Vector2I(newCol, newRow));
}
return moves;
}GDScript
## 获取相/象的所有合法走法
func get_elephant_moves(col: int, row: int, color: Piece3D.PieceColor, board: Array) -> Array[Vector2i]:
var moves: Array[Vector2i] = []
# 四个田字方向:[目标偏移col, 目标偏移row, 眼位偏移col, 眼位偏移row]
var directions := [
[-2, -2, -1, -1], # 左上田
[-2, 2, -1, 1], # 左下田
[2, -2, 1, -1], # 右上田
[2, 2, 1, 1] # 右下田
]
for dir in directions:
var new_col: int = col + dir[0]
var new_row: int = row + dir[1]
# 边界检查
if new_col < 0 or new_col > 8 or new_row < 0 or new_row > 9:
continue
# 不能过河检查
if color == Piece3D.PieceColor.RED and new_row < 5:
continue
if color == Piece3D.PieceColor.BLACK and new_row > 4:
continue
# 塞象眼检查:田字中心有棋子
var eye_pos_x: int = col + dir[2]
var eye_pos_y: int = row + dir[3]
if board[eye_pos_x][eye_pos_y] != null:
continue
# 目标位置有自己人
var target: Piece3D = board[new_col][new_row]
if target != null and target.piece_color == color:
continue
moves.append(Vector2i(new_col, new_row))
return moves4.2.4 马(Horse)—— 日字跳跃
走法:走"日"字,先直走一格,再斜走一格。
限制:
- "蹩马腿":如果直走的那一格有棋子挡着,就不能跳
- 不能走到有自己棋子的位置
马在 A,想跳到 D(走日字):
A _ _
| B _ ← 如果 B 处有棋子,就是"蹩马腿",跳不了
_ _ D
马有两个方向可以跳:
先向上一步 → 然后可以跳到左上或右上
先向左一步 → 然后可以跳到左上或左下
... 共8个可能的目标C#
/// <summary>
/// 获取马的所有合法走法
/// </summary>
private List<Vector2I> GetHorseMoves(int col, int row, Piece3D.PieceColor color, Piece3D[,] board)
{
var moves = new List<Vector2I>();
// 马的8个跳跃方向:[目标偏移col, 目标偏移row, 蹩腿检查col, 蹩腿检查row]
var jumps = new (int tCol, int tRow, int bCol, int bRow)[]
{
(-1, -2, 0, -1), // 向上跳,到左上
(1, -2, 0, -1), // 向上跳,到右上
(-2, -1, -1, 0), // 向左跳,到左上
(-2, 1, -1, 0), // 向左跳,到左下
(-1, 2, 0, 1), // 向下跳,到左下
(1, 2, 0, 1), // 向下跳,到右下
(2, -1, 1, 0), // 向右跳,到右上
(2, 1, 1, 0) // 向右跳,到右下
};
foreach (var (tCol, tRow, bCol, bRow) in jumps)
{
int newCol = col + tCol;
int newRow = row + tRow;
// 边界检查
if (newCol < 0 || newCol > 8 || newRow < 0 || newRow > 9)
continue;
// 蹩马腿检查
int blockCol = col + bCol;
int blockRow = row + bRow;
if (board[blockCol, blockRow] != null)
continue;
// 目标位置有自己人
var target = board[newCol, newRow];
if (target != null && target.Color == color)
continue;
moves.Add(new Vector2I(newCol, newRow));
}
return moves;
}GDScript
## 获取马的所有合法走法
func get_horse_moves(col: int, int row, color: Piece3D.PieceColor, board: Array) -> Array[Vector2i]:
var moves: Array[Vector2i] = []
# 马的8个跳跃方向:[目标偏移col, 目标偏移row, 蹩腿检查col, 蹩腿检查row]
var jumps := [
[-1, -2, 0, -1], # 向上跳,到左上
[1, -2, 0, -1], # 向上跳,到右上
[-2, -1, -1, 0], # 向左跳,到左上
[-2, 1, -1, 0], # 向左跳,到左下
[-1, 2, 0, 1], # 向下跳,到左下
[1, 2, 0, 1], # 向下跳,到右下
[2, -1, 1, 0], # 向右跳,到右上
[2, 1, 1, 0] # 向右跳,到右下
]
for jump in jumps:
var new_col: int = col + jump[0]
var new_row: int = row + jump[1]
# 边界检查
if new_col < 0 or new_col > 8 or new_row < 0 or new_row > 9:
continue
# 蹩马腿检查
var block_col: int = col + jump[2]
var block_row: int = row + jump[3]
if board[block_col][block_row] != null:
continue
# 目标位置有自己人
var target: Piece3D = board[new_col][new_row]
if target != null and target.piece_color == color:
continue
moves.append(Vector2i(new_col, new_row))
return moves4.2.5 车(Chariot)—— 横冲直撞
走法:沿直线(横或竖)走任意距离,直到碰到棋子或边界。
限制:
- 不能越过其他棋子
- 如果碰到对方的棋子,可以吃掉(停在那个位置)
- 如果碰到自己的棋子,停在前一格
C#
/// <summary>
/// 获取车的所有合法走法
/// </summary>
private List<Vector2I> GetChariotMoves(int col, int row, Piece3D.PieceColor color, Piece3D[,] board)
{
var moves = new List<Vector2I>();
// 四个方向:上、下、左、右
int[][] directions = {
new[] { 0, -1 }, // 上
new[] { 0, 1 }, // 下
new[] { -1, 0 }, // 左
new[] { 1, 0 } // 右
};
foreach (var dir in directions)
{
// 沿一个方向一直走,直到碰壁
int newCol = col + dir[0];
int newRow = row + dir[1];
while (newCol >= 0 && newCol <= 8 && newRow >= 0 && newRow <= 9)
{
var target = board[newCol, newRow];
if (target == null)
{
// 空位,可以走
moves.Add(new Vector2I(newCol, newRow));
}
else if (target.Color != color)
{
// 对方棋子,可以吃,然后停止
moves.Add(new Vector2I(newCol, newRow));
break;
}
else
{
// 自己的棋子,不能走,停止
break;
}
newCol += dir[0];
newRow += dir[1];
}
}
return moves;
}GDScript
## 获取车的所有合法走法
func get_chariot_moves(col: int, row: int, color: Piece3D.PieceColor, board: Array) -> Array[Vector2i]:
var moves: Array[Vector2i] = []
# 四个方向:上、下、左、右
var directions := [
Vector2i(0, -1), # 上
Vector2i(0, 1), # 下
Vector2i(-1, 0), # 左
Vector2i(1, 0) # 右
]
for dir in directions:
# 沿一个方向一直走,直到碰壁
var new_col: int = col + dir.x
var new_row: int = row + dir.y
while new_col >= 0 and new_col <= 8 and new_row >= 0 and new_row <= 9:
var target: Piece3D = board[new_col][new_row]
if target == null:
# 空位,可以走
moves.append(Vector2i(new_col, new_row))
elif target.piece_color != color:
# 对方棋子,可以吃,然后停止
moves.append(Vector2i(new_col, new_row))
break
else:
# 自己的棋子,不能走,停止
break
new_col += dir.x
new_row += dir.y
return moves4.2.6 炮(Cannon)—— 翻山越岭
走法:
- 移动时:和车一样,沿直线走,不能越过任何棋子
- 吃子时:必须隔一个棋子(叫"炮架")才能吃。翻过炮架后面的第一个对方棋子可以被吃
炮是最独特的棋子
你可以把炮想象成一个"弹弓":移动时正常走路(像车),但吃子时需要把另一个棋子当"弹弓架",炮弹飞过弹弓架才能击中目标。
C#
/// <summary>
/// 获取炮的所有合法走法
/// </summary>
private List<Vector2I> GetCannonMoves(int col, int row, Piece3D.PieceColor color, Piece3D[,] board)
{
var moves = new List<Vector2I>();
int[][] directions = {
new[] { 0, -1 }, new[] { 0, 1 },
new[] { -1, 0 }, new[] { 1, 0 }
};
foreach (var dir in directions)
{
int newCol = col + dir[0];
int newRow = row + dir[1];
bool jumped = false; // 是否已经翻过一个棋子
while (newCol >= 0 && newCol <= 8 && newRow >= 0 && newRow <= 9)
{
var target = board[newCol, newRow];
if (!jumped)
{
// 还没翻过炮架
if (target == null)
{
// 空位,可以移动到这里
moves.Add(new Vector2I(newCol, newRow));
}
else
{
// 碰到棋子,这个棋子就是炮架
jumped = true;
}
}
else
{
// 已经翻过炮架了
if (target != null)
{
// 碰到棋子
if (target.Color != color)
{
// 对方棋子,可以吃
moves.Add(new Vector2I(newCol, newRow));
}
// 不管能不能吃,遇到棋子就停止
break;
}
// 空位,继续飞
}
newCol += dir[0];
newRow += dir[1];
}
}
return moves;
}GDScript
## 获取炮的所有合法走法
func get_cannon_moves(col: int, row: int, color: Piece3D.PieceColor, board: Array) -> Array[Vector2i]:
var moves: Array[Vector2i] = []
var directions := [
Vector2i(0, -1), Vector2i(0, 1),
Vector2i(-1, 0), Vector2i(1, 0)
]
for dir in directions:
var new_col: int = col + dir.x
var new_row: int = row + dir.y
var jumped: bool = false # 是否已经翻过一个棋子
while new_col >= 0 and new_col <= 8 and new_row >= 0 and new_row <= 9:
var target: Piece3D = board[new_col][new_row]
if not jumped:
# 还没翻过炮架
if target == null:
# 空位,可以移动到这里
moves.append(Vector2i(new_col, new_row))
else:
# 碰到棋子,这个棋子就是炮架
jumped = true
else:
# 已经翻过炮架了
if target != null:
# 碰到棋子
if target.piece_color != color:
# 对方棋子,可以吃
moves.append(Vector2i(new_col, new_row))
# 不管能不能吃,遇到棋子就停止
break
# 空位,继续飞
new_col += dir.x
new_row += dir.y
return moves4.2.7 兵/卒(Pawn)—— 过河小卒
走法:
- 过河前:只能向前走一格
- 过河后:可以向前走一格,或向左/右走一格
- 不能后退
"过河"是什么意思?
楚河汉界把棋盘分成两半。兵/卒跨过中线(红方兵从 row 6 走到 row 0-4,黑方卒从 row 3 走到 row 5-9),就叫"过河"。过河后兵的战斗力变强了。
C#
/// <summary>
/// 获取兵/卒的所有合法走法
/// </summary>
private List<Vector2I> GetPawnMoves(int col, int row, Piece3D.PieceColor color, Piece3D[,] board)
{
var moves = new List<Vector2I>();
if (color == Piece3D.PieceColor.Red)
{
// 红方兵:向上走(row 减小)
// 过河 = row <= 4
bool crossed = row <= 4;
// 前进(向上)
TryAddMove(col, row - 1, color, board, moves);
// 过河后可以左右走
if (crossed)
{
TryAddMove(col - 1, row, color, board, moves);
TryAddMove(col + 1, row, color, board, moves);
}
}
else
{
// 黑方卒:向下走(row 增大)
// 过河 = row >= 5
bool crossed = row >= 5;
// 前进(向下)
TryAddMove(col, row + 1, color, board, moves);
// 过河后可以左右走
if (crossed)
{
TryAddMove(col - 1, row, color, board, moves);
TryAddMove(col + 1, row, color, board, moves);
}
}
return moves;
}
private void TryAddMove(int col, int row, Piece3D.PieceColor color,
Piece3D[,] board, List<Vector2I> moves)
{
if (col < 0 || col > 8 || row < 0 || row > 9)
return;
var target = board[col, row];
if (target != null && target.Color == color)
return;
moves.Add(new Vector2I(col, row));
}GDScript
## 获取兵/卒的所有合法走法
func get_pawn_moves(col: int, row: int, color: Piece3D.PieceColor, board: Array) -> Array[Vector2i]:
var moves: Array[Vector2i] = []
if color == Piece3D.PieceColor.RED:
# 红方兵:向上走(row 减小)
# 过河 = row <= 4
var crossed: bool = row <= 4
# 前进(向上)
try_add_move(col, row - 1, color, board, moves)
# 过河后可以左右走
if crossed:
try_add_move(col - 1, row, color, board, moves)
try_add_move(col + 1, row, color, board, moves)
else:
# 黑方卒:向下走(row 增大)
# 过河 = row >= 5
var crossed: bool = row >= 5
# 前进(向下)
try_add_move(col, row + 1, color, board, moves)
# 过河后可以左右走
if crossed:
try_add_move(col - 1, row, color, board, moves)
try_add_move(col + 1, row, color, board, moves)
return moves
func try_add_move(col: int, row: int, color: Piece3D.PieceColor,
board: Array, moves: Array[Vector2i]):
if col < 0 or col > 8 or row < 0 or row > 9:
return
var target: Piece3D = board[col][row]
if target != null and target.piece_color == color:
return
moves.append(Vector2i(col, row))4.3 将军检测
"将军"就是对方的将/帅正在被我方棋子攻击。检测将军是判断游戏是否结束的关键。
一句话理解"将军"
"将军"就好比有人拿刀指着你的老板——你必须立刻想办法保护老板,不能不管。
C#
/// <summary>
/// 检查指定颜色的将/帅是否正在被将军
/// </summary>
public bool IsInCheck(Piece3D.PieceColor color, Piece3D[,] board)
{
// 找到这个颜色的将/帅位置
Vector2I kingPos = FindKing(color, board);
if (kingPos == new Vector2I(-1, -1))
return false; // 找不到将/帅(不应该发生)
// 检查对方所有棋子是否能攻击到这个位置
Piece3D.PieceColor opponent = (color == Piece3D.PieceColor.Red)
? Piece3D.PieceColor.Black
: Piece3D.PieceColor.Red;
for (int col = 0; col < 9; col++)
{
for (int row = 0; row < 10; row++)
{
var piece = board[col, row];
if (piece != null && piece.Color == opponent)
{
var moves = GetRawMoves(piece, board);
if (moves.Contains(kingPos))
return true; // 被将军了!
}
}
}
// 检查将帅对面(飞将)
return IsKingsFacing(board);
}
/// <summary>
/// 找到指定颜色的将/帅位置
/// </summary>
private Vector2I FindKing(Piece3D.PieceColor color, Piece3D[,] board)
{
for (int col = 0; col < 9; col++)
{
for (int row = 0; row < 10; row++)
{
var piece = board[col, row];
if (piece != null &&
piece.Type == Piece3D.PieceType.King &&
piece.Color == color)
{
return new Vector2I(col, row);
}
}
}
return new Vector2I(-1, -1);
}
/// <summary>
/// 检查将帅是否在同一列上"面对面"(中间无棋子)
/// </summary>
private bool IsKingsFacing(Piece3D[,] board)
{
var redKing = FindKing(Piece3D.PieceColor.Red, board);
var blackKing = FindKing(Piece3D.PieceColor.Black, board);
// 不在同一列,没有面对面
if (redKing.X != blackKing.X)
return false;
// 检查中间有没有棋子
int minRow = Mathf.Min(redKing.Y, blackKing.Y);
int maxRow = Mathf.Max(redKing.Y, blackKing.Y);
for (int row = minRow + 1; row < maxRow; row++)
{
if (board[redKing.X, row] != null)
return false; // 中间有棋子,没面对面
}
return true; // 将帅面对面了!这是不允许的
}GDScript
## 检查指定颜色的将/帅是否正在被将军
func is_in_check(color: Piece3D.PieceColor, board: Array) -> bool:
# 找到这个颜色的将/帅位置
var king_pos: Vector2i = find_king(color, board)
if king_pos == Vector2i(-1, -1):
return false # 找不到将/帅
# 检查对方所有棋子是否能攻击到这个位置
var opponent: Piece3D.PieceColor = Piece3D.PieceColor.BLACK if color == Piece3D.PieceColor.RED else Piece3D.PieceColor.RED
for col in range(9):
for row in range(10):
var piece: Piece3D = board[col][row]
if piece != null and piece.piece_color == opponent:
var moves := get_raw_moves(piece, board)
if king_pos in moves:
return true # 被将军了!
# 检查将帅对面(飞将)
return is_kings_facing(board)
## 找到指定颜色的将/帅位置
func find_king(color: Piece3D.PieceColor, board: Array) -> Vector2i:
for col in range(9):
for row in range(10):
var piece: Piece3D = board[col][row]
if piece != null and piece.type == Piece3D.PieceType.KING and piece.piece_color == color:
return Vector2i(col, row)
return Vector2i(-1, -1)
## 检查将帅是否在同一列上"面对面"
func is_kings_facing(board: Array) -> bool:
var red_king := find_king(Piece3D.PieceColor.RED, board)
var black_king := find_king(Piece3D.PieceColor.BLACK, board)
if red_king.x != black_king.x:
return false
var min_row: int = mini(red_king.y, black_king.y)
var max_row: int = maxi(red_king.y, black_king.y)
for row in range(min_row + 1, max_row):
if board[red_king.x][row] != null:
return false
return true4.4 将死判定
"将死"就是被将军且无法解除——无论怎么走,将/帅都处于被攻击状态。
C#
/// <summary>
/// 检查指定颜色是否被将死
/// </summary>
public bool IsCheckmate(Piece3D.PieceColor color, Piece3D[,] board)
{
// 如果没被将军,肯定不是将死
if (!IsInCheck(color, board))
return false;
// 尝试该颜色的每一枚棋子的每一种走法
// 如果有任何一种走法能解除将军,就不是将死
for (int col = 0; col < 9; col++)
{
for (int row = 0; row < 10; row++)
{
var piece = board[col, row];
if (piece == null || piece.Color != color)
continue;
var moves = GetRawMoves(piece, board);
foreach (var move in moves)
{
// 模拟走这一步
if (IsMoveSafe(piece, move, board))
return false; // 找到一步能解除将军的走法
}
}
}
// 所有走法都不能解除将军 → 将死
return true;
}
/// <summary>
/// 检查一步棋走完后,自己的将/帅是否安全
/// </summary>
private bool IsMoveSafe(Piece3D piece, Vector2I target, Piece3D[,] board)
{
// 临时移动棋子
var originalPos = new Vector2I(piece.Col, piece.Row);
var capturedPiece = board[target.X, target.Y];
board[originalPos.X, originalPos.Y] = null;
board[target.X, target.Y] = piece;
piece.Col = target.X;
piece.Row = target.Y;
// 检查是否仍然被将军
bool stillInCheck = IsInCheck(piece.Color, board);
// 恢复原来的状态
board[originalPos.X, originalPos.Y] = piece;
board[target.X, target.Y] = capturedPiece;
piece.Col = originalPos.X;
piece.Row = originalPos.Y;
return !stillInCheck;
}GDScript
## 检查指定颜色是否被将死
func is_checkmate(color: Piece3D.PieceColor, board: Array) -> bool:
# 如果没被将军,肯定不是将死
if not is_in_check(color, board):
return false
# 尝试该颜色的每一枚棋子的每一种走法
for col in range(9):
for row in range(10):
var piece: Piece3D = board[col][row]
if piece == null or piece.piece_color != color:
continue
var moves := get_raw_moves(piece, board)
for move in moves:
# 模拟走这一步
if is_move_safe(piece, move, board):
return false # 找到一步能解除将军的走法
# 所有走法都不能解除将军 → 将死
return true
## 检查一步棋走完后,自己的将/帅是否安全
func is_move_safe(piece: Piece3D, target: Vector2i, board: Array) -> bool:
var original_pos := Vector2i(piece.col, piece.row)
var captured_piece: Piece3D = board[target.x][target.y]
board[original_pos.x][original_pos.y] = null
board[target.x][target.y] = piece
piece.col = target.x
piece.row = target.y
var still_in_check: bool = is_in_check(piece.piece_color, board)
# 恢复原来的状态
board[original_pos.x][original_pos.y] = piece
board[target.x][target.y] = captured_piece
piece.col = original_pos.x
piece.row = original_pos.y
return not still_in_check4.5 合法走法生成器(统一入口)
把所有棋子的走法生成统一到一个函数中。
C#
/// <summary>
/// 获取一枚棋子的所有合法走法(已排除送将)
/// </summary>
public List<Vector2I> GetLegalMoves(Piece3D piece, Piece3D[,] board)
{
// 先获取"原始走法"(不考虑送将)
var rawMoves = GetRawMoves(piece, board);
// 过滤掉会导致自己被将军的走法
var legalMoves = new List<Vector2I>();
foreach (var move in rawMoves)
{
if (IsMoveSafe(piece, move, board))
{
legalMoves.Add(move);
}
}
return legalMoves;
}
/// <summary>
/// 获取一枚棋子的原始走法(不考虑送将)
/// </summary>
private List<Vector2I> GetRawMoves(Piece3D piece, Piece3D[,] board)
{
return piece.Type switch
{
Piece3D.PieceType.King => GetKingMoves(piece.Col, piece.Row, piece.Color, board),
Piece3D.PieceType.Advisor => GetAdvisorMoves(piece.Col, piece.Row, piece.Color, board),
Piece3D.PieceType.Elephant => GetElephantMoves(piece.Col, piece.Row, piece.Color, board),
Piece3D.PieceType.Horse => GetHorseMoves(piece.Col, piece.Row, piece.Color, board),
Piece3D.PieceType.Chariot => GetChariotMoves(piece.Col, piece.Row, piece.Color, board),
Piece3D.PieceType.Cannon => GetCannonMoves(piece.Col, piece.Row, piece.Color, board),
Piece3D.PieceType.Pawn => GetPawnMoves(piece.Col, piece.Row, piece.Color, board),
_ => new List<Vector2I>()
};
}GDScript
## 获取一枚棋子的所有合法走法(已排除送将)
func get_legal_moves(piece: Piece3D, board: Array) -> Array[Vector2i]:
# 先获取"原始走法"(不考虑送将)
var raw_moves := get_raw_moves(piece, board)
# 过滤掉会导致自己被将军的走法
var legal_moves: Array[Vector2i] = []
for move in raw_moves:
if is_move_safe(piece, move, board):
legal_moves.append(move)
return legal_moves
## 获取一枚棋子的原始走法(不考虑送将)
func get_raw_moves(piece: Piece3D, board: Array) -> Array[Vector2i]:
match piece.type:
Piece3D.PieceType.KING:
return get_king_moves(piece.col, piece.row, piece.piece_color, board)
Piece3D.PieceType.ADVISOR:
return get_advisor_moves(piece.col, piece.row, piece.piece_color, board)
Piece3D.PieceType.ELEPHANT:
return get_elephant_moves(piece.col, piece.row, piece.piece_color, board)
Piece3D.PieceType.HORSE:
return get_horse_moves(piece.col, piece.row, piece.piece_color, board)
Piece3D.PieceType.CHARIOT:
return get_chariot_moves(piece.col, piece.row, piece.piece_color, board)
Piece3D.PieceType.CANNON:
return get_cannon_moves(piece.col, piece.row, piece.piece_color, board)
Piece3D.PieceType.PAWN:
return get_pawn_moves(piece.col, piece.row, piece.piece_color, board)
_:
return []4.6 走法规则速查表
| 棋子 | 走法 | 特殊限制 | 每步最多几格 |
|---|---|---|---|
| 帅/将 | 上下左右直走 | 限九宫,不对面 | 1 格 |
| 仕/士 | 斜走 | 限九宫 | 1 格 |
| 相/象 | 走田字(斜2格) | 不过河,塞象眼 | 2 格(斜) |
| 马 | 走日字 | 蹩马腿 | L形(1+1斜) |
| 车 | 直线任意距离 | 不能越子 | 无限制 |
| 炮 | 直线任意距离 | 吃子必须翻炮架 | 无限制 |
| 兵/卒 | 前进,过河后可左右 | 不能后退 | 1 格 |
本章小结
| 要点 | 说明 |
|---|---|
| 走法验证 | 根据棋子类型生成候选走法,再过滤非法走法 |
| 特殊规则 | 蹩马腿、塞象眼、炮架、将帅不对面 |
| 将军检测 | 检查将/帅是否被对方棋子攻击 |
| 将死判定 | 被将军且无合法走法可解除 |
| 合法走法 | 原始走法 - 送将走法 = 合法走法 |
