///|
/// The squares a piece on `square` attacks in this position.
pub fn Position::attacks_from(self : Position, square : Square) -> Bitboard {
match self.piece_at(square) {
Some(piece) =>
Bitboard::attacks_of(
piece.kind(),
piece.color(),
square,
self.occupancy(),
)
None => Bitboard::empty()
}
}
///|
/// Every piece of `color` that attacks `square`, as a square set.
pub fn Position::attackers_to(
self : Position,
square : Square,
color : Color,
) -> Bitboard {
let occupancy = self.occupancy()
let mut result = Bitboard::empty()
result = result.union(
self
.pieces_of(color, Pawn)
.intersect(Bitboard::pawn_attacks(color.opposite(), square)),
)
result = result.union(
self.pieces_of(color, Knight).intersect(Bitboard::knight_attacks(square)),
)
result = result.union(
self.pieces_of(color, King).intersect(Bitboard::king_attacks(square)),
)
result = result.union(
self
.pieces_of(color, Bishop)
.intersect(Bitboard::bishop_attacks(square, occupancy)),
)
result = result.union(
self
.pieces_of(color, Rook)
.intersect(Bitboard::rook_attacks(square, occupancy)),
)
result = result.union(
self
.pieces_of(color, Queen)
.intersect(Bitboard::queen_attacks(square, occupancy)),
)
result
}
///|
/// Whether `color` attacks `square`.
pub fn Position::is_attacked(
self : Position,
square : Square,
color : Color,
) -> Bool {
!self.attackers_to(square, color).is_empty()
}
///|
/// Whether `color`'s king is attacked.
pub fn Position::in_check(self : Position, color : Color) -> Bool {
match self.king_square(color) {
Some(king) => self.is_attacked(king, color.opposite())
None => false
}
}
///|
/// Whether a wing is cleared for castling: the right is held, both pieces
/// stand on their home squares and the squares between them are empty.
///
/// King safety is not considered here, so a position with a king in check or
/// a piece attacking the king's path still answers `true`.
pub fn Position::castling_is_pseudo_legal(
self : Position,
color : Color,
kingside : Bool,
) -> Bool {
let allowed = if kingside {
self.castling.has_kingside(color)
} else {
self.castling.has_queenside(color)
}
if !allowed {
return false
}
let home_rank = if color == White { 0 } else { 7 }
let king_home = Square::make(4, home_rank)
if self.piece_at(king_home) != Some(Piece::new(color, King)) {
return false
}
let rook_home = Square::make(if kingside { 7 } else { 0 }, home_rank)
if self.piece_at(rook_home) != Some(Piece::new(color, Rook)) {
return false
}
let files : Array[Int32] = if kingside { [5, 6] } else { [1, 2, 3] }
for file in files {
if !self.is_empty_square(Square::make(file, home_rank)) {
return false
}
}
true
}
///|
fn add_targets(
moves : Array[Move],
from : Square,
targets : Bitboard,
own : Bitboard,
) -> Unit {
for target in targets.difference(own).squares() {
moves.push(Move::new(from, target))
}
}
///|
fn push_promotions(moves : Array[Move], from : Square, to : Square) -> Unit {
for kind in [Queen, Rook, Bishop, Knight] {
moves.push(Move::promotion(from, to, kind))
}
}
///|
fn Position::add_pawn_moves(
self : Position,
moves : Array[Move],
color : Color,
enemy : Bitboard,
) -> Unit {
let rank_delta = if color == White { 1 } else { -1 }
let start_rank = if color == White { 1 } else { 6 }
let last_rank = if color == White { 7 } else { 0 }
for square in self.pieces_of(color, Pawn).squares() {
match square.offset(0, rank_delta) {
Some(one) =>
if self.is_empty_square(one) {
if one.rank() == last_rank {
push_promotions(moves, square, one)
} else {
moves.push(Move::new(square, one))
if square.rank() == start_rank {
match square.offset(0, rank_delta * 2) {
Some(two) =>
if self.is_empty_square(two) {
moves.push(Move::double_push(square, two))
}
None => ()
}
}
}
}
None => ()
}
for file_delta in [-1, 1] {
match square.offset(file_delta, rank_delta) {
Some(target) => {
if enemy.contains(target) {
if target.rank() == last_rank {
push_promotions(moves, square, target)
} else {
moves.push(Move::new(square, target))
}
}
match self.ep_square {
Some(ep) =>
if ep == target {
match target.offset(0, -rank_delta) {
Some(captured) =>
if self.piece_at(captured) ==
Some(Piece::new(color.opposite(), Pawn)) {
moves.push(Move::en_passant(square, target))
}
None => ()
}
}
None => ()
}
}
None => ()
}
}
}
}
///|
fn Position::add_piece_moves(
self : Position,
moves : Array[Move],
color : Color,
own : Bitboard,
kind : PieceKind,
) -> Unit {
let occupancy = self.occupancy()
for square in self.pieces_of(color, kind).squares() {
add_targets(
moves,
square,
Bitboard::attacks_of(kind, color, square, occupancy),
own,
)
}
}
///|
fn Position::add_king_moves(
self : Position,
moves : Array[Move],
color : Color,
own : Bitboard,
) -> Unit {
for square in self.pieces_of(color, King).squares() {
add_targets(moves, square, Bitboard::king_attacks(square), own)
for kingside in [true, false] {
if self.castling_is_pseudo_legal(color, kingside) {
moves.push(Move::castle(color, kingside))
}
}
}
}
///|
/// Every move of the side to move that ignores king safety.
///
/// A position with a pinned piece therefore reports moves that would leave
/// its own king in check, and castling is reported whenever the right and
/// the empty path allow it. Filtering through `is_legal` is the task of the
/// next increment; this list is what perft-style bookkeeping starts from.
pub fn Position::pseudo_legal_moves(self : Position) -> Array[Move] {
let moves : Array[Move] = []
let us = self.turn
let own = self.color_occupancy(us)
let enemy = self.color_occupancy(us.opposite())
self.add_pawn_moves(moves, us, enemy)
self.add_piece_moves(moves, us, own, Knight)
self.add_piece_moves(moves, us, own, Bishop)
self.add_piece_moves(moves, us, own, Rook)
self.add_piece_moves(moves, us, own, Queen)
self.add_king_moves(moves, us, own)
moves
}
///|
/// The pseudo-legal moves that start on a square.
pub fn Position::pseudo_legal_moves_from(
self : Position,
square : Square,
) -> Array[Move] {
let out : Array[Move] = []
for candidate in self.pseudo_legal_moves() {
if candidate.from == square {
out.push(candidate)
}
}
out
}