///|
/// Generate all pseudo-legal moves for the piece at `from`.
/// A pseudo-legal move follows the piece's movement pattern and does
/// not land on a friendly piece; it may leave the mover's own king in
/// check. Use `legal_moves` for fully validated moves.
pub fn Board::pseudo_moves_from(self : Board, from : Pos) -> Array[Move] {
let moves = []
match self.get(from) {
Some(piece) => {
match piece.kind {
Rook => gen_rook(self, from, piece, moves)
Cannon => gen_cannon(self, from, piece, moves)
Horse => gen_horse(self, from, piece, moves)
Elephant => gen_elephant(self, from, piece, moves)
Advisor => gen_advisor(self, from, piece, moves)
King => gen_king(self, from, piece, moves)
Pawn => gen_pawn(self, from, piece, moves)
}
moves
}
None => moves
}
}
///|
/// Generate all pseudo-legal moves for the side to move.
pub fn Board::pseudo_moves(self : Board) -> Array[Move] {
let moves = []
for pos in self.positions_of_side(self.side_to_move) {
for mv in self.pseudo_moves_from(pos) {
moves.push(mv)
}
}
moves
}
///|
fn forward_step(side : Side) -> Int {
if side.is_red() {
1
} else {
-1
}
}
///|
fn gen_rook(
board : Board,
from : Pos,
piece : Piece,
moves : Array[Move],
) -> Unit {
let dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dir in dirs {
let mut f = from.file + dir.0
let mut r = from.rank + dir.1
while Pos::new(f, r).is_valid() {
let to = Pos::new(f, r)
match board.get(to) {
None => moves.push(Move::new(from, to))
Some(target) => {
if target.side != piece.side {
moves.push(Move::new(from, to))
}
break
}
}
f = f + dir.0
r = r + dir.1
}
}
}
///|
/// A cannon moves like a rook when not capturing, but to capture it
/// must jump over exactly one piece (the screen) on the ray.
fn gen_cannon(
board : Board,
from : Pos,
piece : Piece,
moves : Array[Move],
) -> Unit {
let dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dir in dirs {
let mut f = from.file + dir.0
let mut r = from.rank + dir.1
let mut screen_seen = false
while Pos::new(f, r).is_valid() {
let to = Pos::new(f, r)
match board.get(to) {
None => if !screen_seen { moves.push(Move::new(from, to)) }
Some(target) =>
if !screen_seen {
screen_seen = true
} else {
if target.side != piece.side {
moves.push(Move::new(from, to))
}
break
}
}
f = f + dir.0
r = r + dir.1
}
}
}
///|
/// Horse move offsets paired with their blocking-leg offsets.
/// The leg is the orthogonal square the horse must pass through.
fn horse_offsets() -> Array[((Int, Int), (Int, Int))] {
[
((1, 2), (0, 1)),
((-1, 2), (0, 1)),
((1, -2), (0, -1)),
((-1, -2), (0, -1)),
((2, 1), (1, 0)),
((2, -1), (1, 0)),
((-2, 1), (-1, 0)),
((-2, -1), (-1, 0)),
]
}
///|
fn gen_horse(
board : Board,
from : Pos,
piece : Piece,
moves : Array[Move],
) -> Unit {
for pair in horse_offsets() {
let (offset, leg) = pair
let to = Pos::new(from.file + offset.0, from.rank + offset.1)
if to.is_valid() {
let leg_pos = Pos::new(from.file + leg.0, from.rank + leg.1)
// 蹩马腿: the leg square must be empty.
if board.get(leg_pos) is None {
match board.get(to) {
None => moves.push(Move::new(from, to))
Some(target) =>
if target.side != piece.side {
moves.push(Move::new(from, to))
}
}
}
}
}
}
///|
/// Elephant (bishop) moves two points diagonally and cannot cross the
/// river. The "eye" at the diagonal midpoint must be empty (塞象眼).
fn gen_elephant(
board : Board,
from : Pos,
piece : Piece,
moves : Array[Move],
) -> Unit {
let offsets = [(2, 2), (2, -2), (-2, 2), (-2, -2)]
for offset in offsets {
let to = Pos::new(from.file + offset.0, from.rank + offset.1)
if to.is_valid() && to.own_half(piece.side) {
let eye = Pos::new(from.file + offset.0 / 2, from.rank + offset.1 / 2)
if board.get(eye) is None {
match board.get(to) {
None => moves.push(Move::new(from, to))
Some(target) =>
if target.side != piece.side {
moves.push(Move::new(from, to))
}
}
}
}
}
}
///|
/// Advisor moves one point diagonally and must stay inside the palace.
fn gen_advisor(
board : Board,
from : Pos,
piece : Piece,
moves : Array[Move],
) -> Unit {
let offsets = [(1, 1), (1, -1), (-1, 1), (-1, -1)]
for offset in offsets {
let to = Pos::new(from.file + offset.0, from.rank + offset.1)
if to.is_valid() && to.in_palace(piece.side) {
match board.get(to) {
None => moves.push(Move::new(from, to))
Some(target) =>
if target.side != piece.side {
moves.push(Move::new(from, to))
}
}
}
}
}
///|
/// King moves one point orthogonally inside the palace. The flying
/// general rule (kings facing each other on an open file) is handled
/// during legality filtering, not here.
fn gen_king(
board : Board,
from : Pos,
piece : Piece,
moves : Array[Move],
) -> Unit {
let offsets = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for offset in offsets {
let to = Pos::new(from.file + offset.0, from.rank + offset.1)
if to.is_valid() && to.in_palace(piece.side) {
match board.get(to) {
None => moves.push(Move::new(from, to))
Some(target) =>
if target.side != piece.side {
moves.push(Move::new(from, to))
}
}
}
}
}
///|
/// Pawn moves one point forward; after crossing the river it may also
/// move sideways, but never backwards.
fn gen_pawn(
board : Board,
from : Pos,
piece : Piece,
moves : Array[Move],
) -> Unit {
let targets = []
let forward = Pos::new(from.file, from.rank + forward_step(piece.side))
targets.push(forward)
if !from.own_half(piece.side) {
targets.push(Pos::new(from.file - 1, from.rank))
targets.push(Pos::new(from.file + 1, from.rank))
}
for to in targets {
if to.is_valid() {
match board.get(to) {
None => moves.push(Move::new(from, to))
Some(target) =>
if target.side != piece.side {
moves.push(Move::new(from, to))
}
}
}
}
}