use crate::{
constants::{A1, A8, D1, D8, F1, F8, H1, H8},
legal::attack::is_square_attacked,
model::piecemove::{PieceMove, PromotionType},
};
use super::bitboard::BitBoard;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PieceType {
Pawn,
Knight,
Bishop,
Rook,
Queen,
King,
}
#[derive(Clone, Copy, Debug)]
pub struct GameBoard {
pub pawns: BitBoard,
pub knights: BitBoard,
pub bishops: BitBoard,
pub rooks: BitBoard,
pub queens: BitBoard,
pub kings: BitBoard,
pub colour: BitBoard, pub castling: u8,
pub en_passant: PieceMove,
pub playing: bool, }
impl Default for GameBoard {
fn default() -> Self {
GameBoard {
pawns: BitBoard::EMPTY,
knights: BitBoard::EMPTY,
bishops: BitBoard::EMPTY,
rooks: BitBoard::EMPTY,
queens: BitBoard::EMPTY,
kings: BitBoard::EMPTY,
colour: BitBoard::EMPTY,
castling: 0,
en_passant: PieceMove::NULL,
playing: true,
}
}
}
impl GameBoard {
pub fn new() -> Self {
GameBoard::default()
}
pub fn reset(&mut self) {
*self = GameBoard::new();
}
pub fn combined(&self) -> BitBoard {
self.pawns | self.knights | self.bishops | self.rooks | self.queens | self.kings
}
pub fn combined_coloured(&self, desired: bool) -> BitBoard {
self.combined() & (self.colour ^ desired)
}
pub fn casling_right_white(&self) -> (bool, bool) {
(
(self.castling & 0b0001) != 0, (self.castling & 0b0010) != 0, )
}
pub fn casling_right_black(&self) -> (bool, bool) {
(
(self.castling & 0b0100) != 0, (self.castling & 0b1000) != 0, )
}
pub(crate) fn find_king(&self, is_white: bool) -> Option<u8> {
let king_board = if is_white {
self.kings & self.colour
} else {
self.kings & !self.colour
};
if king_board.raw() != BitBoard::EMPTY.raw() {
Some(king_board.raw().trailing_zeros() as u8)
} else {
None
}
}
fn is_path_clear(&self, from: u8, to: u8) -> bool {
let from_rank = (from / 8) as i8;
let from_file = (from % 8) as i8;
let to_rank = (to / 8) as i8;
let to_file = (to % 8) as i8;
let dr = (to_rank - from_rank).signum();
let df = (to_file - from_file).signum();
let mut r = from_rank + dr;
let mut f = from_file + df;
while r != to_rank || f != to_file {
let sq = (r * 8 + f) as u8;
if self.combined().get_bit(sq).unwrap_or(false) {
return false;
}
r += dr;
f += df;
}
true
}
pub fn is_move_legal(&self, piece_move: &PieceMove) -> bool {
if !self.is_correct_turn_piece(piece_move) {
#[cfg(feature = "std")]
eprintln!("Failed: is_correct_turn_piece");
return false;
}
if !self.is_piece_move_valid(piece_move) {
#[cfg(feature = "std")]
eprintln!("Failed: is_piece_move_valid");
return false;
}
if !self.is_destination_valid(piece_move) {
#[cfg(feature = "std")]
eprintln!("Failed: is_destination_valid");
return false;
}
if !self.are_special_moves_valid(piece_move) {
#[cfg(feature = "std")]
eprintln!("Failed: are_special_moves_valid");
return false;
}
if !self.does_not_leave_king_in_check(piece_move) {
#[cfg(feature = "std")]
eprintln!("Failed: does_not_leave_king_in_check");
return false;
}
true
}
fn is_correct_turn_piece(&self, piece_move: &PieceMove) -> bool {
self
.colour
.get_bit(piece_move.from_square())
.is_some_and(|f| f == self.playing)
}
fn is_destination_valid(&self, piece_move: &PieceMove) -> bool {
let to = piece_move.to_square();
if let Some(_) = self.get_piece(to)
&& self.colour.get_bit(to).is_some_and(|f| f == self.playing)
{
return false;
}
if let Some(PieceType::King) = self.get_piece(to) {
return false;
}
true
}
fn is_piece_move_valid(&self, piece_move: &PieceMove) -> bool {
let from = piece_move.from_square();
let to = piece_move.to_square();
let piece_type = match self.get_piece(from) {
Some(pt) => pt,
None => return false,
};
match piece_type {
PieceType::Pawn => self.is_pawn_move_valid(piece_move),
PieceType::Knight => self.is_knight_move_valid(from, to),
PieceType::Bishop => self.is_bishop_move_valid(from, to),
PieceType::Rook => self.is_rook_move_valid(from, to),
PieceType::Queen => self.is_queen_move_valid(from, to),
PieceType::King => self.is_king_move_valid(piece_move),
}
}
fn is_pawn_move_valid(&self, piece_move: &PieceMove) -> bool {
let from = piece_move.from_square();
let to = piece_move.to_square();
let from_rank = from / 8;
let to_rank = to / 8;
let from_file = from % 8;
let to_file = to % 8;
let is_forward = (self.playing && to > from) || (!self.playing && from > to);
let is_capture =
self.get_piece(to).is_some() && self.colour.get_bit(to).is_some_and(|f| f != self.playing);
let is_en_passant = piece_move.is_en_passant();
let is_promotion = piece_move.is_promotion();
if !is_forward {
return false;
}
if from_file == to_file {
return self.is_pawn_forward_move_valid(from, to, from_rank, to_rank, is_promotion);
}
else if (from_file as i8 - to_file as i8).abs() == 1 {
return self.is_pawn_diagonal_move_valid(piece_move, is_capture, is_en_passant, to_rank);
}
false
}
fn is_pawn_forward_move_valid(
&self,
from: u8,
to: u8,
from_rank: u8,
to_rank: u8,
is_promotion: bool,
) -> bool {
let diff = to.abs_diff(from);
if diff == 8 {
if self.get_piece(to).is_some() {
return false; }
} else if diff == 16 && ((from_rank == 1 && self.playing) || (from_rank == 6 && !self.playing))
{
let mid = if self.playing { from + 8 } else { from - 8 };
if self.get_piece(to).is_some() || self.get_piece(mid).is_some() {
return false; }
} else {
return false; }
self.is_pawn_promotion_valid(to_rank, is_promotion)
}
fn is_pawn_diagonal_move_valid(
&self,
piece_move: &PieceMove,
is_capture: bool,
is_en_passant: bool,
to_rank: u8,
) -> bool {
if !(is_capture || is_en_passant) {
return false; }
self.is_pawn_promotion_valid(to_rank, piece_move.is_promotion())
}
fn is_pawn_promotion_valid(&self, to_rank: u8, is_promotion: bool) -> bool {
let should_promote = (to_rank == 7 && self.playing) || (to_rank == 0 && !self.playing);
if should_promote && !is_promotion {
return false; }
if !should_promote && is_promotion {
return false; }
true
}
fn is_knight_move_valid(&self, from: u8, to: u8) -> bool {
let dr = (from / 8) as i8 - (to / 8) as i8;
let df = (from % 8) as i8 - (to % 8) as i8;
(dr.abs() == 2 && df.abs() == 1) || (dr.abs() == 1 && df.abs() == 2)
}
fn is_bishop_move_valid(&self, from: u8, to: u8) -> bool {
let dr = (from / 8) as i8 - (to / 8) as i8;
let df = (from % 8) as i8 - (to % 8) as i8;
if dr.abs() != df.abs() {
return false; }
self.is_path_clear(from, to)
}
fn is_rook_move_valid(&self, from: u8, to: u8) -> bool {
let dr = (from / 8) as i8 - (to / 8) as i8;
let df = (from % 8) as i8 - (to % 8) as i8;
if dr != 0 && df != 0 {
return false; }
self.is_path_clear(from, to)
}
fn is_queen_move_valid(&self, from: u8, to: u8) -> bool {
let dr = (from / 8) as i8 - (to / 8) as i8;
let df = (from % 8) as i8 - (to % 8) as i8;
let is_diagonal = dr.abs() == df.abs();
let is_straight = dr == 0 || df == 0;
if !(is_diagonal || is_straight) {
return false; }
self.is_path_clear(from, to)
}
fn is_king_move_valid(&self, piece_move: &PieceMove) -> bool {
let from = piece_move.from_square();
let to = piece_move.to_square();
let dr = (from / 8) as i8 - (to / 8) as i8;
let df = (from % 8) as i8 - (to % 8) as i8;
if dr.abs() <= 1 && df.abs() <= 1 {
return true;
}
if dr == 0 && df.abs() == 2 {
return self.is_castling_valid(piece_move);
}
false
}
fn is_castling_valid(&self, piece_move: &PieceMove) -> bool {
let from = piece_move.from_square();
let to = piece_move.to_square();
let is_kingside = to == from + 2;
let (can_k, can_q) = if self.playing {
self.casling_right_white()
} else {
self.casling_right_black()
};
if (is_kingside && !can_k) || (!is_kingside && !can_q) {
return false;
}
if !self.are_castling_squares_clear(from, is_kingside) {
return false;
}
self.is_castling_path_safe(from, is_kingside)
}
fn are_castling_squares_clear(&self, from: u8, is_kingside: bool) -> bool {
if is_kingside {
for sq in [from + 1, from + 2] {
if self.combined().get_bit(sq).unwrap_or(false) {
return false;
}
}
} else {
for sq in [from - 1, from - 2, from - 3] {
if self.combined().get_bit(sq).unwrap_or(false) {
return false;
}
}
}
true
}
fn is_castling_path_safe(&self, from: u8, is_kingside: bool) -> bool {
let path = if is_kingside {
[from, from + 1, from + 2]
} else {
[from, from - 1, from - 2]
};
for &sq in &path {
if is_square_attacked(self, sq) {
return false;
}
}
true
}
fn are_special_moves_valid(&self, piece_move: &PieceMove) -> bool {
if piece_move.is_en_passant() && self.get_piece(piece_move.to_square()).is_none() {
return self.is_en_passant_valid(piece_move);
}
true
}
fn is_en_passant_valid(&self, piece_move: &PieceMove) -> bool {
let from = piece_move.from_square();
let to = piece_move.to_square();
let ep_square = self.en_passant.to_square();
if ep_square != to {
return false;
}
let from_file = from % 8;
let to_file = to % 8;
let from_rank = from / 8;
let to_rank = to / 8;
let correct_forward = if self.playing {
to_rank == from_rank + 1
} else {
to_rank + 1 == from_rank
};
if (from_file as i8 - to_file as i8).abs() != 1 || !correct_forward {
return false;
}
if self.get_piece(to).is_some() {
return false;
}
let captured_pawn_square = if self.playing { to - 8 } else { to + 8 };
if self.get_piece(captured_pawn_square) != Some(PieceType::Pawn)
|| self
.colour
.get_bit(captured_pawn_square)
.is_some_and(|f| f == self.playing)
{
return false;
}
true
}
fn does_not_leave_king_in_check(&self, piece_move: &PieceMove) -> bool {
let mut new_board = *self;
new_board.apply_move_unchecked(piece_move);
if let Some(king_square) = new_board.find_king(self.playing) {
!is_square_attacked(&new_board, king_square)
} else {
false }
}
fn apply_move_unchecked(&mut self, piece_move: &PieceMove) {
let from_square = piece_move.from_square();
let to_square = piece_move.to_square();
let mover_white = self.playing;
let piece = self
.get_piece(from_square)
.expect("No piece at from_square");
self.clear_square(from_square);
if piece == PieceType::King {
if mover_white {
self.castling &= !0b0011; } else {
self.castling &= !0b1100; }
} else if piece == PieceType::Rook {
let home_ks = if mover_white { H1 } else { H8 }; let home_qs = if mover_white { A1 } else { A8 }; if from_square == home_ks {
if mover_white {
self.castling &= !0b0001; } else {
self.castling &= !0b0100; }
} else if from_square == home_qs {
if mover_white {
self.castling &= !0b0010; } else {
self.castling &= !0b1000; }
}
}
if piece == PieceType::King && (to_square as i32 - from_square as i32).abs() == 2 {
let is_kingside = to_square > from_square;
let rook_from = if mover_white {
if is_kingside { H1 } else { A1 }
} else if is_kingside {
H8
} else {
A8
};
let rook_to = if mover_white {
if is_kingside { F1 } else { D1 }
} else if is_kingside {
F8
} else {
D8
};
self.clear_square(rook_from);
self.set_square(rook_to, PieceType::Rook, mover_white);
}
let captured_opt = self.get_piece(to_square);
if let Some(captured) = captured_opt {
self.clear_square(to_square);
if captured == PieceType::Rook {
let opp_white = !mover_white;
let opp_home_ks = if opp_white { H1 } else { H8 };
let opp_home_qs = if opp_white { A1 } else { A8 };
if to_square == opp_home_ks {
if opp_white {
self.castling &= !0b0001; } else {
self.castling &= !0b0100; }
} else if to_square == opp_home_qs {
if opp_white {
self.castling &= !0b0010; } else {
self.castling &= !0b1000; }
}
}
}
if piece == PieceType::Pawn {
let from_file = from_square % 8;
let to_file = to_square % 8;
let from_rank = from_square / 8;
let to_rank = to_square / 8;
if (from_file as i32 - to_file as i32).abs() == 1
&& (from_rank as i32 - to_rank as i32).abs() == 1
{
if captured_opt.is_none() {
let captured_pawn_square = if mover_white {
to_square - 8
} else {
to_square + 8
};
self.clear_square(captured_pawn_square);
}
}
}
if piece_move.is_promotion() {
let promotion_type = piece_move.promotion_type().expect("Promotion type not set");
self.set_square(
to_square,
match promotion_type {
PromotionType::Queen => PieceType::Queen,
PromotionType::Rook => PieceType::Rook,
PromotionType::Bishop => PieceType::Bishop,
PromotionType::Knight => PieceType::Knight,
},
mover_white,
);
} else {
self.set_square(to_square, piece, mover_white);
}
self.en_passant = PieceMove::NULL;
if piece == PieceType::Pawn
&& from_square % 8 == to_square % 8
&& (to_square as i32 - from_square as i32).abs() == 16
{
let skipped_square = if mover_white {
to_square - 8
} else {
to_square + 8
};
self.en_passant = PieceMove::new(to_square, skipped_square, false, None);
}
}
pub fn get_piece(&self, square: u8) -> Option<PieceType> {
if self.pawns.get_bit(square)? {
return Some(PieceType::Pawn);
}
if self.knights.get_bit_unchecked(square) {
return Some(PieceType::Knight);
}
if self.bishops.get_bit_unchecked(square) {
return Some(PieceType::Bishop);
}
if self.rooks.get_bit_unchecked(square) {
return Some(PieceType::Rook);
}
if self.queens.get_bit_unchecked(square) {
return Some(PieceType::Queen);
}
if self.kings.get_bit_unchecked(square) {
return Some(PieceType::King);
}
None
}
pub fn clear_square(&mut self, square: u8) -> Option<()> {
let _ = self.pawns.unset_bit_unchecked(square);
let _ = self.knights.unset_bit_unchecked(square);
let _ = self.bishops.unset_bit_unchecked(square);
let _ = self.rooks.unset_bit_unchecked(square);
let _ = self.queens.unset_bit_unchecked(square);
let _ = self.kings.unset_bit_unchecked(square);
let _ = self.colour.unset_bit_unchecked(square);
Some(())
}
pub fn set_square(&mut self, square: u8, piece_type: PieceType, is_white: bool) -> Option<()> {
self.clear_square(square)?;
let bitboard = match piece_type {
PieceType::Pawn => &mut self.pawns,
PieceType::Knight => &mut self.knights,
PieceType::Bishop => &mut self.bishops,
PieceType::Rook => &mut self.rooks,
PieceType::Queen => &mut self.queens,
PieceType::King => &mut self.kings,
};
bitboard.set_bit_unchecked(square);
self.colour.update_bit(square, is_white).map(|_f| ())
}
pub fn move_piece(&mut self, piece_move: &PieceMove) -> Option<()> {
if !self.is_move_legal(piece_move) {
return None;
}
self.apply_move_unchecked(piece_move);
self.playing = !self.playing; Some(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::*;
use crate::model::{gamedata::GameData, piecemove::PromotionType};
fn board_from_fen(fen: &str) -> GameBoard {
GameData::from_fen(fen).unwrap().board
}
fn simple_move(from: u8, to: u8) -> PieceMove {
PieceMove::new(from, to, false, None)
}
fn capture_move(from: u8, to: u8) -> PieceMove {
PieceMove::new(from, to, true, None)
}
fn promotion_move(from: u8, to: u8, promotion: PromotionType) -> PieceMove {
PieceMove::new(from, to, false, Some(promotion))
}
fn promotion_capture_move(from: u8, to: u8, promotion: PromotionType) -> PieceMove {
PieceMove::new(from, to, true, Some(promotion))
}
fn en_passant_move(from: u8, to: u8) -> PieceMove {
PieceMove::new_en_passant(from, to)
}
fn castling_move(from: u8, to: u8) -> PieceMove {
PieceMove::new_castling(from, to)
}
#[test]
fn test_wrong_color_piece() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let black_pawn_move = simple_move(A7, A6);
assert!(!board.is_move_legal(&black_pawn_move));
}
#[test]
fn test_move_to_own_piece() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let move_to_own = simple_move(A2, B2);
assert!(!board.is_move_legal(&move_to_own));
}
#[test]
fn test_no_piece_to_move() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let empty_square_move = simple_move(E4, E5);
assert!(!board.is_move_legal(&empty_square_move));
}
#[test]
fn test_cannot_capture_king() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/4Q3/PPPPPPPP/RNB1KBNR w KQkq - 0 1");
let capture_king = capture_move(E3, E8);
assert!(!board.is_move_legal(&capture_king));
}
#[test]
fn test_pawn_single_step() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let pawn_move = simple_move(E2, E3);
assert!(board.is_move_legal(&pawn_move));
}
#[test]
fn test_pawn_double_step() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let pawn_double = simple_move(E2, E4);
assert!(board.is_move_legal(&pawn_double));
}
#[test]
fn test_pawn_double_step_blocked() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1");
let blocked_double = simple_move(E7, E5);
assert!(board.is_move_legal(&blocked_double));
}
#[test]
fn test_pawn_invalid_double_step_from_wrong_rank() {
let board = board_from_fen("rnbqkbnr/pppp1ppp/4p3/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let invalid_double = simple_move(E6, E4);
assert!(!board.is_move_legal(&invalid_double));
}
#[test]
fn test_pawn_capture_diagonal() {
let board = board_from_fen("rnbqkbnr/pppp1ppp/8/8/3p4/4P3/PPPP1PPP/RNBQKBNR w KQkq - 0 1");
let pawn_capture = capture_move(E3, D4);
assert!(board.is_move_legal(&pawn_capture));
}
#[test]
fn test_pawn_cannot_capture_forward() {
let board = board_from_fen("rnbqkbnr/pppp1ppp/8/8/4p3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1");
let pawn_forward_capture = capture_move(E2, E4);
assert!(!board.is_move_legal(&pawn_forward_capture));
}
#[test]
fn test_pawn_cannot_move_diagonal_without_capture() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let diagonal_move = simple_move(E2, D3);
assert!(!board.is_move_legal(&diagonal_move));
}
#[test]
fn test_pawn_backward_move_illegal() {
let board = board_from_fen("rnbqkbnr/pppp1ppp/8/4p3/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let backward_move = simple_move(E5, E6);
assert!(!board.is_move_legal(&backward_move));
}
#[test]
fn test_pawn_promotion_to_queen() {
let board = board_from_fen("k7/4P3/8/8/8/8/8/7K w - - 0 1");
let promotion = promotion_move(E7, E8, PromotionType::Queen);
assert!(board.is_move_legal(&promotion));
}
#[test]
fn test_pawn_promotion_all_pieces() {
let board = board_from_fen("k7/4P3/8/8/8/8/8/K7 w - - 0 1");
assert!(board.is_move_legal(&promotion_move(E7, E8, PromotionType::Queen)));
assert!(board.is_move_legal(&promotion_move(E7, E8, PromotionType::Rook)));
assert!(board.is_move_legal(&promotion_move(E7, E8, PromotionType::Bishop)));
assert!(board.is_move_legal(&promotion_move(E7, E8, PromotionType::Knight)));
}
#[test]
fn test_pawn_promotion_capture() {
let board = board_from_fen("k2n4/4P3/8/8/8/8/8/K7 w - - 0 1");
let promotion_capture = promotion_capture_move(E7, D8, PromotionType::Queen);
assert!(board.is_move_legal(&promotion_capture));
}
#[test]
fn test_pawn_promotion_wrong_rank() {
let board = board_from_fen("8/8/4P3/8/8/8/8/8 w - - 0 1");
let wrong_rank_promotion = promotion_move(E6, E7, PromotionType::Queen);
assert!(!board.is_move_legal(&wrong_rank_promotion));
}
#[test]
fn test_black_pawn_promotion() {
let board = board_from_fen("K6k/8/8/8/8/8/4p3/8 b - - 0 1");
let black_promotion = promotion_move(E2, E1, PromotionType::Queen);
assert!(board.is_move_legal(&black_promotion));
}
#[test]
fn test_en_passant_basic() {
let mut board = board_from_fen("rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 1");
board.en_passant = PieceMove::new(D5, D6, false, None); let en_passant = en_passant_move(E5, D6);
assert!(board.is_move_legal(&en_passant));
}
#[test]
fn test_en_passant_wrong_target() {
let mut board = board_from_fen("rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1");
board.en_passant = PieceMove::new(D5, C6, false, None); let en_passant = en_passant_move(E5, D6); assert!(!board.is_move_legal(&en_passant));
}
#[test]
fn test_knight_l_shape_moves() {
let board = board_from_fen("k7/8/8/8/3N4/8/8/K7 w - - 0 1");
let knight_moves = [
simple_move(D4, B3),
simple_move(D4, B5),
simple_move(D4, C2),
simple_move(D4, C6),
simple_move(D4, E2),
simple_move(D4, E6),
simple_move(D4, F3),
simple_move(D4, F5),
];
for &knight_move in &knight_moves {
assert!(
board.is_move_legal(&knight_move),
"Knight move {:?} should be legal",
knight_move
);
}
}
#[test]
fn test_knight_invalid_move() {
let board = board_from_fen("8/8/8/8/3N4/8/8/8 w - - 0 1");
let invalid_knight = simple_move(D4, E5); assert!(!board.is_move_legal(&invalid_knight));
}
#[test]
fn test_knight_blocked_by_own_piece() {
let board = board_from_fen("8/8/8/2P5/3N4/8/8/8 w - - 0 1");
let blocked_knight = simple_move(D4, C6);
assert!(!board.is_move_legal(&blocked_knight));
}
#[test]
fn test_knight_capture() {
let board = board_from_fen("8/k7/8/2p5/3N4/8/2K5/8 w - - 0 1");
let knight_capture = capture_move(D4, C6);
assert!(board.is_move_legal(&knight_capture));
}
#[test]
fn test_bishop_diagonal_moves() {
let board = board_from_fen("k7/8/8/8/3B4/8/8/7K w - - 0 1");
let bishop_moves = [
simple_move(D4, A1),
simple_move(D4, B2),
simple_move(D4, C3),
simple_move(D4, E5),
simple_move(D4, F6),
simple_move(D4, G7),
simple_move(D4, H8),
simple_move(D4, A7),
simple_move(D4, B6),
simple_move(D4, C5),
simple_move(D4, E3),
simple_move(D4, F2),
simple_move(D4, G1),
];
for &bishop_move in &bishop_moves {
assert!(
board.is_move_legal(&bishop_move),
"Bishop move {:?} should be legal",
bishop_move
);
}
}
#[test]
fn test_bishop_non_diagonal_illegal() {
let board = board_from_fen("8/8/8/8/3B4/8/8/8 w - - 0 1");
let non_diagonal = simple_move(D4, D6); assert!(!board.is_move_legal(&non_diagonal));
}
#[test]
fn test_bishop_blocked_path() {
let board = board_from_fen("8/8/8/2P5/3B4/8/8/8 w - - 0 1");
let blocked_bishop = simple_move(D4, A7);
assert!(!board.is_move_legal(&blocked_bishop)); }
#[test]
fn test_rook_straight_moves() {
let board = board_from_fen("k7/8/8/8/3R4/8/8/7K w - - 0 1");
let rook_moves = [
simple_move(D4, D1),
simple_move(D4, D2),
simple_move(D4, D3),
simple_move(D4, D5),
simple_move(D4, D6),
simple_move(D4, D7),
simple_move(D4, D8),
simple_move(D4, A4),
simple_move(D4, B4),
simple_move(D4, C4),
simple_move(D4, E4),
simple_move(D4, F4),
simple_move(D4, G4),
simple_move(D4, H4),
];
for &rook_move in &rook_moves {
assert!(
board.is_move_legal(&rook_move),
"Rook move {:?} should be legal",
rook_move
);
}
}
#[test]
fn test_rook_diagonal_illegal() {
let board = board_from_fen("8/8/8/8/3R4/8/8/8 w - - 0 1");
let diagonal_move = simple_move(D4, E5);
assert!(!board.is_move_legal(&diagonal_move));
}
#[test]
fn test_rook_blocked_path() {
let board = board_from_fen("8/8/8/8/2PR4/8/8/8 w - - 0 1");
let blocked_rook = simple_move(D4, A4);
assert!(!board.is_move_legal(&blocked_rook)); }
#[test]
fn test_queen_combined_moves() {
let board = board_from_fen("k7/8/8/8/3Q4/8/8/7K w - - 0 1");
let queen_moves = [
simple_move(D4, D1),
simple_move(D4, A4), simple_move(D4, A1),
simple_move(D4, G7), ];
for &queen_move in &queen_moves {
assert!(
board.is_move_legal(&queen_move),
"Queen move {:?} should be legal",
queen_move
);
}
}
#[test]
fn test_queen_knight_move_illegal() {
let board = board_from_fen("8/8/8/8/3Q4/8/8/8 w - - 0 1");
let knight_like = simple_move(D4, F3); assert!(!board.is_move_legal(&knight_like));
}
#[test]
fn test_king_adjacent_moves() {
let board = board_from_fen("8/8/8/8/3K4/8/8/8 w - - 0 1");
let king_moves = [
simple_move(D4, C3),
simple_move(D4, D3),
simple_move(D4, E3),
simple_move(D4, C4),
simple_move(D4, E4),
simple_move(D4, C5),
simple_move(D4, D5),
simple_move(D4, E5),
];
for &king_move in &king_moves {
assert!(board.is_move_legal(&king_move));
}
}
#[test]
fn test_king_long_move_illegal() {
let board = board_from_fen("8/8/8/8/3K4/8/8/8 w - - 0 1");
let long_move = simple_move(D4, D6); assert!(!board.is_move_legal(&long_move));
}
#[test]
fn test_king_move_into_check() {
let board = board_from_fen("8/8/8/8/3K4/8/8/3r4 w - - 0 1");
let into_check = simple_move(D4, D3); assert!(!board.is_move_legal(&into_check));
}
#[test]
fn test_kingside_castling_legal() {
let board = board_from_fen("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1");
let kingside_castle = castling_move(E1, G1);
assert!(board.is_move_legal(&kingside_castle));
}
#[test]
fn test_queenside_castling_legal() {
let board = board_from_fen("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1");
let queenside_castle = castling_move(E1, C1);
assert!(board.is_move_legal(&queenside_castle));
}
#[test]
fn test_castling_king_in_check() {
let board = board_from_fen("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R2rK2R w KQkq - 0 1");
let castle_in_check = castling_move(E1, G1); assert!(!board.is_move_legal(&castle_in_check));
}
#[test]
fn test_castling_path_blocked() {
let board = board_from_fen("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R2QK2R w KQkq - 0 1");
let castle_blocked = castling_move(E1, C1); assert!(!board.is_move_legal(&castle_blocked));
}
#[test]
fn test_castling_through_check() {
let board = board_from_fen("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K1rR w KQkq - 0 1");
let castle_through_check = castling_move(E1, G1); assert!(!board.is_move_legal(&castle_through_check));
}
#[test]
fn test_castling_no_rights() {
let board = board_from_fen("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w - - 0 1");
let castle_no_rights = castling_move(E1, G1);
assert!(!board.is_move_legal(&castle_no_rights));
}
#[test]
fn test_must_escape_check() {
let board = board_from_fen("8/8/8/8/8/8/4r3/4K3 w - - 0 1");
let non_escape = simple_move(E1, D1); assert!(board.is_move_legal(&non_escape));
}
#[test]
fn test_block_check() {
let board = board_from_fen("7k/8/8/8/8/8/8/4K2r w - - 0 1");
assert!(!board.is_move_legal(&simple_move(E1, D1)));
assert!(!board.is_move_legal(&simple_move(E1, F1))); assert!(board.is_move_legal(&simple_move(E1, E2))); }
#[test]
fn test_capture_checking_piece() {
let board = board_from_fen("k7/8/8/8/8/8/4Q3/4K2r w - - 0 1");
let capture_checker = capture_move(E2, H2); assert!(!board.is_move_legal(&capture_checker));
}
#[test]
fn test_pinned_piece_cannot_move() {
let board = board_from_fen("8/8/8/8/8/8/4B3/4K2r w - - 0 1");
let pinned_move = simple_move(E2, D3); assert!(!board.is_move_legal(&pinned_move));
}
#[test]
fn test_pinned_piece_can_move_along_pin() {
let board = board_from_fen("8/8/8/8/8/8/4B3/4K2r w - - 0 1");
let along_pin = simple_move(E2, F1); assert!(board.is_move_legal(&along_pin));
}
#[test]
fn test_pinned_piece_can_capture_attacker() {
let board = board_from_fen("8/8/8/8/4B3/8/8/4K2r w - - 0 1");
let capture_attacker = capture_move(E4, H1); assert!(board.is_move_legal(&capture_attacker));
}
#[test]
fn test_initial_position_legal_moves() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
assert!(board.is_move_legal(&simple_move(E2, E4)));
assert!(board.is_move_legal(&simple_move(D2, D4)));
assert!(board.is_move_legal(&simple_move(G1, F3)));
assert!(board.is_move_legal(&simple_move(B1, C3)));
}
#[test]
fn test_endgame_position() {
let board = board_from_fen("8/8/8/8/8/8/6k1/6K1 b - - 0 1");
assert!(board.is_move_legal(&simple_move(G2, F3)));
assert!(board.is_move_legal(&simple_move(G2, H3)));
assert!(!board.is_move_legal(&simple_move(G2, H1))); }
#[test]
fn test_null_move_illegal() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let null_move = PieceMove::NULL;
assert!(!board.is_move_legal(&null_move));
}
#[test]
#[should_panic]
fn test_same_square_move_illegal() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let same_square = simple_move(E2, E2);
assert!(!board.is_move_legal(&same_square));
}
#[test]
fn test_discovered_check_legal() {
let board = board_from_fen("k7/8/8/8/3B4/8/8/1K1r4 w - - 0 1");
let discovered_check = simple_move(D4, E5);
assert!(!board.is_move_legal(&discovered_check));
}
#[test]
fn test_discovered_check_self_illegal() {
let board = board_from_fen("K7/8/8/8/3b4/8/8/1k1R4 b - - 0 1");
let self_discovered = simple_move(D4, E5);
assert!(!board.is_move_legal(&self_discovered));
}
#[test]
fn test_promotion_required() {
let board = board_from_fen("8/4P3/8/8/8/8/8/8 w - - 0 1");
let no_promotion = simple_move(E7, E8);
assert!(!board.is_move_legal(&no_promotion));
}
#[test]
fn test_en_passant_removes_correct_pawn() {
let mut board = board_from_fen("rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 1");
board.en_passant = PieceMove::new(D5, D6, false, None);
assert_eq!(board.get_piece(D5), Some(PieceType::Pawn));
assert!(!board.colour.get_bit(D5).unwrap());
let en_passant = en_passant_move(E5, D6);
assert!(board.is_move_legal(&en_passant));
}
}