use super::Position;
use crate::bitboard::{
Bitboard, Direct, between_bb, bishop_effect, direct_of, gold_effect, king_effect,
knight_effect, lance_effect, pawn_effect, ray_effect, rook_effect, silver_effect,
};
use crate::movegen::{ExtMoveBuffer, GenType, generate_evasions, generate_with_type};
use crate::types::{Color, Move, Piece, PieceType, Square, Value};
impl Position {
pub fn pseudo_legal(&self, m: Move) -> bool {
if m.is_none() {
return false;
}
if m.is_pass() {
return self.can_pass();
}
let us = self.side_to_move();
let to = m.to();
if m.is_drop() {
let pt = m.drop_piece_type();
if !self.hand(us).has(pt) {
return false;
}
if self.piece_on(to).is_some() {
return false;
}
if self.in_check() {
let checkers = self.checkers();
debug_assert!(
!checkers.is_empty(),
"checkers should not be empty when in_check() is true"
);
let checker_sq = checkers.lsb().unwrap();
if checkers.count() > 1 {
return false;
}
let king_sq = self.king_square(us);
if !between_bb(checker_sq, king_sq).contains(to) {
return false;
}
}
if pt == PieceType::Pawn {
let file_mask = crate::bitboard::FILE_BB[to.file().index()];
if !(self.pieces(us, PieceType::Pawn) & file_mask).is_empty() {
return false;
}
}
true
} else {
let from = m.from();
let pc = self.piece_on(from);
if pc.is_none() || pc.color() != us {
return false;
}
let pt = pc.piece_type();
let occupied = self.occupied();
if m.is_promote() {
if !pt.can_promote() {
return false;
}
let in_enemy_zone = if us == Color::Black {
from.rank().index() <= 2 || to.rank().index() <= 2
} else {
from.rank().index() >= 6 || to.rank().index() >= 6
};
if !in_enemy_zone {
return false;
}
}
let attacks = match pt {
PieceType::Pawn => pawn_effect(us, from),
PieceType::Lance => lance_effect(us, from, occupied),
PieceType::Knight => knight_effect(us, from),
PieceType::Silver => silver_effect(us, from),
PieceType::Gold
| PieceType::ProPawn
| PieceType::ProLance
| PieceType::ProKnight
| PieceType::ProSilver => gold_effect(us, from),
PieceType::Bishop => bishop_effect(from, occupied),
PieceType::Rook => rook_effect(from, occupied),
PieceType::Horse => bishop_effect(from, occupied) | king_effect(from),
PieceType::Dragon => rook_effect(from, occupied) | king_effect(from),
PieceType::King => king_effect(from),
};
if !attacks.contains(to) {
return false;
}
let to_pc = self.piece_on(to);
if to_pc.is_some() && to_pc.color() == us {
return false;
}
if m.is_promotion() && !pt.can_promote() {
return false;
}
let checkers = self.checkers();
if !checkers.is_empty() {
if pt != PieceType::King {
if checkers.count() > 1 {
return false;
}
debug_assert!(
!checkers.is_empty(),
"checkers should not be empty in this branch"
);
let checker_sq = checkers.lsb().unwrap();
let king_sq = self.king_square(us);
let valid_targets = between_bb(checker_sq, king_sq) | checkers;
if !valid_targets.contains(to) {
return false;
}
}
}
true
}
}
#[inline]
pub fn moved_piece(&self, m: Move) -> Piece {
if m.is_pass() {
Piece::NONE
} else if m.is_drop() {
Piece::make(self.side_to_move(), m.drop_piece_type())
} else {
self.piece_on(m.from())
}
}
#[inline]
pub fn capture_stage(&self, m: Move) -> bool {
!m.is_pass() && !m.is_drop() && self.piece_on(m.to()).is_some()
}
#[inline]
pub fn pseudo_legal_with_all(&self, m: Move, _generate_all_legal_moves: bool) -> bool {
self.pseudo_legal(m)
}
#[inline]
pub fn is_capture(&self, m: Move) -> bool {
if m.is_pass() || m.is_drop() {
false
} else {
self.piece_on(m.to()).is_some()
}
}
#[inline]
pub fn moved_piece_after_move(&self, m: Move) -> Piece {
debug_assert!(m.has_piece_info(), "Move must carry piece info");
m.moved_piece_after()
}
#[inline]
pub fn is_capture_or_promotion(&self, m: Move) -> bool {
self.is_capture(m) || m.is_promotion()
}
#[inline]
pub fn capture_or_pawn_promotion(&self, m: Move) -> bool {
self.is_capture(m)
|| (m.is_promotion() && self.moved_piece(m).piece_type() == PieceType::Pawn)
}
pub fn pawn_history_index(&self) -> usize {
(self.pawn_key() as usize) & (crate::search::PAWN_HISTORY_SIZE - 1)
}
pub fn generate_captures(&self, moves: &mut ExtMoveBuffer) -> usize {
if self.in_check() {
generate_evasions(self, moves);
moves.retain(|m| self.is_capture(m));
} else {
generate_with_type(self, GenType::CapturesProPlus, moves, None);
}
moves.len()
}
pub fn generate_quiets(&self, moves: &mut ExtMoveBuffer, offset: usize) -> usize {
if self.in_check() {
return 0;
}
debug_assert_eq!(
offset,
moves.len(),
"offset should equal buffer length: offset={offset}, len={}",
moves.len()
);
let start_len = moves.len();
generate_with_type(self, GenType::Quiets, moves, None);
moves.len() - start_len
}
pub fn generate_evasions_ext(&self, moves: &mut ExtMoveBuffer) -> usize {
debug_assert!(self.in_check());
generate_with_type(self, GenType::Evasions, moves, None);
moves.len()
}
pub fn see_ge(&self, m: Move, threshold: Value) -> bool {
if m.is_pass() {
return threshold.raw() <= 0;
}
let is_drop = m.is_drop();
let to = m.to();
let captured_value = if is_drop {
0
} else {
let captured = self.piece_on(to);
if captured.is_some() {
see_piece_value(captured.piece_type())
} else {
0
}
};
let mut swap = captured_value - threshold.raw();
if swap < 0 {
return false;
}
let from_value = if is_drop {
see_piece_value(m.drop_piece_type())
} else {
see_piece_value(self.piece_on(m.from()).piece_type())
};
swap = from_value - swap;
if swap <= 0 {
return true;
}
let mut occupied = if is_drop {
self.occupied() ^ Bitboard::from_square(to)
} else {
self.occupied() ^ Bitboard::from_square(m.from()) ^ Bitboard::from_square(to)
};
let mut stm = self.side_to_move();
let mut attackers = self.attackers_to_occ(to, occupied);
let mut res = 1i32;
loop {
stm = !stm;
attackers &= occupied;
let mut stm_attackers = attackers & self.pieces_c(stm);
if stm_attackers.is_empty() {
break;
}
if !(self.state().pinners[stm.index()] & occupied).is_empty() {
stm_attackers &= !self.blockers_for_king(stm);
if stm_attackers.is_empty() {
break;
}
}
res ^= 1;
let (attacker_sq, attacker_value) =
self.least_valuable_attacker(stm_attackers, stm, to, occupied);
swap = attacker_value - swap;
if swap < res {
break;
}
if attacker_value == see_piece_value(PieceType::King) {
return if !(attackers & self.pieces_c(!stm)).is_empty() {
res ^ 1 != 0
} else {
res != 0
};
}
occupied ^= Bitboard::from_square(attacker_sq);
if let Some(dir) = direct_of(to, attacker_sq) {
let ray = ray_effect(dir, to, occupied);
let extras = match dir {
Direct::RU | Direct::RD | Direct::LU | Direct::LD => {
ray & (self.pieces_pt(PieceType::Bishop) | self.pieces_pt(PieceType::Horse))
}
Direct::U => {
let rookers =
self.pieces_pt(PieceType::Rook) | self.pieces_pt(PieceType::Dragon);
let lance = self.pieces(Color::White, PieceType::Lance);
ray & (rookers | lance)
}
Direct::D => {
let rookers =
self.pieces_pt(PieceType::Rook) | self.pieces_pt(PieceType::Dragon);
let lance = self.pieces(Color::Black, PieceType::Lance);
ray & (rookers | lance)
}
Direct::L | Direct::R => {
ray & (self.pieces_pt(PieceType::Rook) | self.pieces_pt(PieceType::Dragon))
}
};
attackers |= extras;
}
}
res != 0
}
fn least_valuable_attacker(
&self,
attackers: Bitboard,
stm: Color,
_to: Square,
_occupied: Bitboard,
) -> (Square, i32) {
let bb = attackers & self.pieces(stm, PieceType::Pawn);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Pawn));
}
let bb = attackers & self.pieces(stm, PieceType::Lance);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Lance));
}
let bb = attackers & self.pieces(stm, PieceType::Knight);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Knight));
}
let bb = attackers & self.pieces(stm, PieceType::Silver);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Silver));
}
for pt in [
PieceType::Gold,
PieceType::ProPawn,
PieceType::ProLance,
PieceType::ProKnight,
PieceType::ProSilver,
] {
let bb = attackers & self.pieces(stm, pt);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Gold));
}
}
let bb = attackers & self.pieces(stm, PieceType::Bishop);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Bishop));
}
let bb = attackers & self.pieces(stm, PieceType::Rook);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Rook));
}
let bb = attackers & self.pieces(stm, PieceType::Horse);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Horse));
}
let bb = attackers & self.pieces(stm, PieceType::Dragon);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::Dragon));
}
let bb = attackers & self.pieces(stm, PieceType::King);
if !bb.is_empty() {
return (bb.lsb().unwrap(), see_piece_value(PieceType::King));
}
unreachable!(
"least_valuable_attacker should always find an attacker when attackers is non-empty"
);
}
}
fn see_piece_value(pt: PieceType) -> i32 {
use PieceType::*;
match pt {
Pawn => 90,
Lance => 315,
Knight => 405,
Silver => 495,
Gold | ProPawn | ProLance | ProKnight | ProSilver => 540,
Bishop => 855,
Horse => 945,
Rook => 990,
Dragon => 1395,
King => 15000,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{File, Rank};
#[test]
fn test_moved_piece() {
let mut pos = Position::new();
pos.set_hirate();
let m = Move::from_usi("7g7f").unwrap();
let pc = pos.moved_piece(m);
assert_eq!(pc, Piece::B_PAWN);
let drop = Move::new_drop(PieceType::Pawn, Square::new(File::File5, Rank::Rank5));
pos.hand[Color::Black.index()] = pos.hand[Color::Black.index()].add(PieceType::Pawn);
let pc_drop = pos.moved_piece(drop);
assert_eq!(pc_drop, Piece::B_PAWN);
}
#[test]
fn test_is_capture() {
let mut pos = Position::new();
let sq76 = Square::new(File::File7, Rank::Rank6);
let sq75 = Square::new(File::File7, Rank::Rank5);
let sq77 = Square::new(File::File7, Rank::Rank7);
let sq59 = Square::new(File::File5, Rank::Rank9);
let sq51 = Square::new(File::File5, Rank::Rank1);
pos.put_piece(Piece::B_PAWN, sq77);
pos.put_piece(Piece::W_PAWN, sq75);
pos.put_piece(Piece::B_KING, sq59);
pos.put_piece(Piece::W_KING, sq51);
pos.king_square[Color::Black.index()] = sq59;
pos.king_square[Color::White.index()] = sq51;
let m1 = Move::new_move(sq77, sq76, false);
assert!(!pos.is_capture(m1));
pos.board[sq77.index()] = Piece::NONE;
pos.put_piece(Piece::B_PAWN, sq76);
let m2 = Move::new_move(sq76, sq75, false);
assert!(pos.is_capture(m2));
pos.hand[Color::Black.index()] = pos.hand[Color::Black.index()].add(PieceType::Gold);
let drop = Move::new_drop(PieceType::Gold, Square::new(File::File5, Rank::Rank5));
assert!(!pos.is_capture(drop));
}
#[test]
fn test_pseudo_legal_basic() {
let mut pos = Position::new();
pos.set_hirate();
let m1 = Move::from_usi("7g7f").unwrap();
assert!(pos.pseudo_legal(m1));
let m2 = Move::from_usi("7g7e").unwrap();
assert!(!pos.pseudo_legal(m2));
let sq55 = Square::new(File::File5, Rank::Rank5);
let sq54 = Square::new(File::File5, Rank::Rank4);
let m3 = Move::new_move(sq55, sq54, false);
assert!(!pos.pseudo_legal(m3));
}
#[test]
fn test_see_ge_simple_capture() {
let mut pos = Position::new();
let sq55 = Square::new(File::File5, Rank::Rank5);
let sq54 = Square::new(File::File5, Rank::Rank4);
let sq59 = Square::new(File::File5, Rank::Rank9);
let sq51 = Square::new(File::File5, Rank::Rank1);
pos.put_piece(Piece::B_PAWN, sq55);
pos.put_piece(Piece::W_GOLD, sq54);
pos.put_piece(Piece::B_KING, sq59);
pos.put_piece(Piece::W_KING, sq51);
pos.king_square[Color::Black.index()] = sq59;
pos.king_square[Color::White.index()] = sq51;
let m = Move::new_move(sq55, sq54, false);
assert!(pos.see_ge(m, Value::new(0)));
assert!(pos.see_ge(m, Value::new(400))); }
#[test]
fn test_pawn_history_index() {
let mut pos = Position::new();
pos.set_hirate();
let idx = pos.pawn_history_index();
assert!(idx < crate::search::PAWN_HISTORY_SIZE);
}
#[test]
fn test_see_xray_attack() {
let mut pos = Position::new();
let from = Square::new(File::File5, Rank::Rank4);
let to = Square::new(File::File5, Rank::Rank5);
let rook_sq = Square::new(File::File5, Rank::Rank8);
let b_king = Square::new(File::File1, Rank::Rank9);
let w_king = Square::new(File::File9, Rank::Rank1);
pos.put_piece(Piece::B_PAWN, from);
pos.put_piece(Piece::W_PAWN, to);
pos.put_piece(Piece::W_ROOK, rook_sq);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let m = Move::new_move(from, to, false);
assert!(!pos.see_ge(m, Value::new(80)), "X-ray rook should make the capture unfavorable");
}
#[test]
fn test_see_xray_attack_diagonal() {
let mut pos = Position::new();
let from = Square::new(File::File3, Rank::Rank3);
let to = Square::new(File::File4, Rank::Rank4);
let bishop_sq = Square::new(File::File7, Rank::Rank7);
let b_king = Square::new(File::File1, Rank::Rank9);
let w_king = Square::new(File::File9, Rank::Rank1);
pos.put_piece(Piece::B_PAWN, from);
pos.put_piece(Piece::W_PAWN, to);
pos.put_piece(Piece::W_BISHOP, bishop_sq);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let m = Move::new_move(from, to, false);
assert!(
!pos.see_ge(m, Value::new(80)),
"Diagonal x-ray should make the capture unfavorable"
);
}
#[test]
fn test_pseudo_legal_rejects_invalid_promote_on_gold() {
let mut pos = Position::new();
let from = Square::new(File::File5, Rank::Rank9);
let to = Square::new(File::File5, Rank::Rank8);
let king_sq = Square::new(File::File1, Rank::Rank9);
let enemy_king = Square::new(File::File1, Rank::Rank1);
pos.put_piece(Piece::B_GOLD, from);
pos.put_piece(Piece::B_KING, king_sq);
pos.put_piece(Piece::W_KING, enemy_king);
pos.king_square[Color::Black.index()] = king_sq;
pos.king_square[Color::White.index()] = enemy_king;
let invalid_promote = Move::new_move(from, to, true);
assert!(!pos.pseudo_legal(invalid_promote), "Gold with promote flag should be rejected");
let valid_move = Move::new_move(from, to, false);
assert!(pos.pseudo_legal(valid_move), "Gold normal move should be allowed");
}
#[test]
fn test_pseudo_legal_rejects_invalid_promote_on_promoted_piece() {
let mut pos = Position::new();
let from = Square::new(File::File5, Rank::Rank5);
let to = Square::new(File::File5, Rank::Rank4);
let king_sq = Square::new(File::File1, Rank::Rank9);
let enemy_king = Square::new(File::File1, Rank::Rank1);
pos.put_piece(Piece::B_DRAGON, from);
pos.put_piece(Piece::B_KING, king_sq);
pos.put_piece(Piece::W_KING, enemy_king);
pos.king_square[Color::Black.index()] = king_sq;
pos.king_square[Color::White.index()] = enemy_king;
let invalid_promote = Move::new_move(from, to, true);
assert!(
!pos.pseudo_legal(invalid_promote),
"Dragon with promote flag should be rejected"
);
}
#[test]
fn test_pseudo_legal_rejects_promote_outside_enemy_zone() {
let mut pos = Position::new();
let from = Square::new(File::File5, Rank::Rank5);
let to = Square::new(File::File5, Rank::Rank4); let king_sq = Square::new(File::File1, Rank::Rank9);
let enemy_king = Square::new(File::File1, Rank::Rank1);
pos.put_piece(Piece::B_SILVER, from);
pos.put_piece(Piece::B_KING, king_sq);
pos.put_piece(Piece::W_KING, enemy_king);
pos.king_square[Color::Black.index()] = king_sq;
pos.king_square[Color::White.index()] = enemy_king;
let invalid_promote = Move::new_move(from, to, true);
assert!(
!pos.pseudo_legal(invalid_promote),
"Promote outside enemy zone should be rejected"
);
}
#[test]
fn test_pseudo_legal_allows_promote_in_enemy_zone() {
let mut pos = Position::new();
let from = Square::new(File::File3, Rank::Rank4);
let to = Square::new(File::File3, Rank::Rank3); let king_sq = Square::new(File::File1, Rank::Rank9);
let enemy_king = Square::new(File::File1, Rank::Rank1);
pos.put_piece(Piece::B_SILVER, from);
pos.put_piece(Piece::B_KING, king_sq);
pos.put_piece(Piece::W_KING, enemy_king);
pos.king_square[Color::Black.index()] = king_sq;
pos.king_square[Color::White.index()] = enemy_king;
let valid_promote = Move::new_move(from, to, true);
assert!(pos.pseudo_legal(valid_promote), "Promote into enemy zone should be allowed");
}
#[test]
fn test_pseudo_legal_drop_double_check() {
let mut pos = Position::new();
let b_king = Square::new(File::File5, Rank::Rank9);
let w_king = Square::new(File::File5, Rank::Rank1);
let w_rook = Square::new(File::File5, Rank::Rank5);
let w_bishop = Square::new(File::File7, Rank::Rank7);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.put_piece(Piece::W_ROOK, w_rook);
pos.put_piece(Piece::W_BISHOP, w_bishop);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let king_sq = pos.king_square(Color::Black);
let checkers = pos.attackers_to(king_sq) & pos.pieces_c(Color::White);
pos.state_stack.last_mut().unwrap().checkers = checkers;
pos.hand[Color::Black.index()] = pos.hand[Color::Black.index()].add(PieceType::Gold);
let drop_56 = Move::new_drop(PieceType::Gold, Square::new(File::File5, Rank::Rank6));
let drop_68 = Move::new_drop(PieceType::Gold, Square::new(File::File6, Rank::Rank8));
assert!(!pos.pseudo_legal(drop_56), "Drop should be illegal during double check");
assert!(!pos.pseudo_legal(drop_68), "Drop should be illegal during double check");
}
#[test]
fn test_pseudo_legal_drop_interpose_rook() {
let mut pos = Position::new();
let b_king = Square::new(File::File5, Rank::Rank9);
let w_king = Square::new(File::File5, Rank::Rank1);
let w_rook = Square::new(File::File5, Rank::Rank5);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.put_piece(Piece::W_ROOK, w_rook);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let king_sq = pos.king_square(Color::Black);
let checkers = pos.attackers_to(king_sq) & pos.pieces_c(Color::White);
pos.state_stack.last_mut().unwrap().checkers = checkers;
pos.hand[Color::Black.index()] = pos.hand[Color::Black.index()].add(PieceType::Gold);
let drop_56 = Move::new_drop(PieceType::Gold, Square::new(File::File5, Rank::Rank6));
let drop_57 = Move::new_drop(PieceType::Gold, Square::new(File::File5, Rank::Rank7));
let drop_58 = Move::new_drop(PieceType::Gold, Square::new(File::File5, Rank::Rank8));
assert!(pos.pseudo_legal(drop_56), "Drop at 56 should be legal (interpose)");
assert!(pos.pseudo_legal(drop_57), "Drop at 57 should be legal (interpose)");
assert!(pos.pseudo_legal(drop_58), "Drop at 58 should be legal (interpose)");
let drop_45 = Move::new_drop(PieceType::Gold, Square::new(File::File4, Rank::Rank5));
assert!(!pos.pseudo_legal(drop_45), "Drop at 45 should be illegal (not interposing)");
}
#[test]
fn test_pseudo_legal_drop_knight_check() {
let mut pos = Position::new();
let b_king = Square::new(File::File5, Rank::Rank9);
let w_king = Square::new(File::File5, Rank::Rank1);
let w_knight = Square::new(File::File4, Rank::Rank7);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.put_piece(Piece::W_KNIGHT, w_knight);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let king_sq = pos.king_square(Color::Black);
let checkers = pos.attackers_to(king_sq) & pos.pieces_c(Color::White);
pos.state_stack.last_mut().unwrap().checkers = checkers;
pos.hand[Color::Black.index()] = pos.hand[Color::Black.index()].add(PieceType::Gold);
let drop_48 = Move::new_drop(PieceType::Gold, Square::new(File::File4, Rank::Rank8));
let drop_58 = Move::new_drop(PieceType::Gold, Square::new(File::File5, Rank::Rank8));
assert!(
!pos.pseudo_legal(drop_48),
"Drop should be illegal (knight check, no interpose)"
);
assert!(
!pos.pseudo_legal(drop_58),
"Drop should be illegal (knight check, no interpose)"
);
}
#[test]
fn test_pseudo_legal_move_double_check() {
let mut pos = Position::new();
let b_king = Square::new(File::File5, Rank::Rank9);
let w_king = Square::new(File::File5, Rank::Rank1);
let w_rook = Square::new(File::File5, Rank::Rank5);
let w_bishop = Square::new(File::File6, Rank::Rank8); let b_gold = Square::new(File::File7, Rank::Rank8);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.put_piece(Piece::W_ROOK, w_rook);
pos.put_piece(Piece::W_BISHOP, w_bishop);
pos.put_piece(Piece::B_GOLD, b_gold);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let king_sq = pos.king_square(Color::Black);
let checkers = pos.attackers_to(king_sq) & pos.pieces_c(Color::White);
pos.state_stack.last_mut().unwrap().checkers = checkers;
assert!(
checkers.count() >= 2,
"Should be double check: checkers count = {}",
checkers.count()
);
let gold_move = Move::new_move(b_gold, Square::new(File::File6, Rank::Rank9), false);
assert!(
!pos.pseudo_legal(gold_move),
"Non-king move should be illegal during double check"
);
}
#[test]
fn test_pseudo_legal_move_interpose() {
let mut pos = Position::new();
let b_king = Square::new(File::File5, Rank::Rank9);
let w_king = Square::new(File::File5, Rank::Rank1);
let w_rook = Square::new(File::File5, Rank::Rank5);
let b_gold = Square::new(File::File6, Rank::Rank8);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.put_piece(Piece::W_ROOK, w_rook);
pos.put_piece(Piece::B_GOLD, b_gold);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let king_sq = pos.king_square(Color::Black);
let checkers = pos.attackers_to(king_sq) & pos.pieces_c(Color::White);
pos.state_stack.last_mut().unwrap().checkers = checkers;
let gold_interpose = Move::new_move(b_gold, Square::new(File::File5, Rank::Rank8), false);
assert!(pos.pseudo_legal(gold_interpose), "Gold move to interpose should be legal");
let gold_not_interpose =
Move::new_move(b_gold, Square::new(File::File6, Rank::Rank7), false);
assert!(
!pos.pseudo_legal(gold_not_interpose),
"Gold move not interposing should be illegal"
);
}
#[test]
fn test_pseudo_legal_move_capture_checker() {
let mut pos = Position::new();
let b_king = Square::new(File::File5, Rank::Rank9);
let w_king = Square::new(File::File5, Rank::Rank1);
let w_rook = Square::new(File::File5, Rank::Rank5);
let b_gold = Square::new(File::File5, Rank::Rank6);
pos.put_piece(Piece::B_KING, b_king);
pos.put_piece(Piece::W_KING, w_king);
pos.put_piece(Piece::W_ROOK, w_rook);
pos.put_piece(Piece::B_GOLD, b_gold);
pos.king_square[Color::Black.index()] = b_king;
pos.king_square[Color::White.index()] = w_king;
pos.side_to_move = Color::Black;
let king_sq = pos.king_square(Color::Black);
let checkers = pos.attackers_to(king_sq) & pos.pieces_c(Color::White);
pos.state_stack.last_mut().unwrap().checkers = checkers;
let gold_capture = Move::new_move(b_gold, w_rook, false);
assert!(pos.pseudo_legal(gold_capture), "Gold capturing the checker should be legal");
}
}