stub: proper make/unmake
fails tests
This commit is contained in:
parent
17bec263d9
commit
611cdd4d51
22
src/fen.rs
22
src/fen.rs
@ -1,4 +1,4 @@
|
||||
use crate::{BoardState, CastleRights, ColPiece, Color, Square, BOARD_HEIGHT, BOARD_WIDTH};
|
||||
use crate::{Board, CastleRights, ColPiece, Color, Square, BOARD_HEIGHT, BOARD_WIDTH};
|
||||
|
||||
pub const START_POSITION: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
|
||||
@ -34,9 +34,9 @@ pub enum FenError {
|
||||
InternalError(usize),
|
||||
}
|
||||
|
||||
impl FromFen for BoardState {
|
||||
impl FromFen for Board {
|
||||
type Error = FenError;
|
||||
fn from_fen(fen: &str) -> Result<BoardState, FenError> {
|
||||
fn from_fen(fen: &str) -> Result<Board, FenError> {
|
||||
//! Parse FEN string into position.
|
||||
|
||||
/// Parser state machine.
|
||||
@ -60,7 +60,7 @@ impl FromFen for BoardState {
|
||||
FullMove,
|
||||
}
|
||||
|
||||
let mut pos = BoardState::default();
|
||||
let mut pos = Board::default();
|
||||
|
||||
let mut parser_state = FenState::Piece(0, 0);
|
||||
let mut next_state = FenState::Space;
|
||||
@ -198,7 +198,7 @@ impl FromFen for BoardState {
|
||||
}
|
||||
FenState::HalfMove => {
|
||||
if let Some(digit) = c.to_digit(10) {
|
||||
if pos.half_moves > BoardState::MAX_MOVES {
|
||||
if pos.half_moves > Board::MAX_MOVES {
|
||||
return Err(FenError::TooManyMoves);
|
||||
}
|
||||
pos.half_moves *= 10;
|
||||
@ -211,7 +211,7 @@ impl FromFen for BoardState {
|
||||
}
|
||||
FenState::FullMove => {
|
||||
if let Some(digit) = c.to_digit(10) {
|
||||
if pos.half_moves > BoardState::MAX_MOVES {
|
||||
if pos.half_moves > Board::MAX_MOVES {
|
||||
return Err(FenError::TooManyMoves);
|
||||
}
|
||||
pos.full_moves *= 10;
|
||||
@ -233,7 +233,7 @@ impl FromFen for BoardState {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFen for BoardState {
|
||||
impl ToFen for Board {
|
||||
fn to_fen(&self) -> String {
|
||||
let pieces_str = (0..BOARD_HEIGHT)
|
||||
.rev()
|
||||
@ -286,7 +286,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fen_pieces() {
|
||||
let fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1";
|
||||
let board = BoardState::from_fen(fen.into()).unwrap();
|
||||
let board = Board::from_fen(fen.into()).unwrap();
|
||||
assert_eq!(
|
||||
(0..N_SQUARES)
|
||||
.map(Square)
|
||||
@ -388,7 +388,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_fen_half_move_counter() {
|
||||
for i in 0..=BoardState::MAX_MOVES {
|
||||
for i in 0..=Board::MAX_MOVES {
|
||||
let board = make_board!("8/8/8/8/8/8/8/8 w - - {i} 0");
|
||||
assert_eq!(board.half_moves, i);
|
||||
assert_eq!(board.full_moves, 0);
|
||||
@ -397,7 +397,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_fen_move_counter() {
|
||||
for i in 0..=BoardState::MAX_MOVES {
|
||||
for i in 0..=Board::MAX_MOVES {
|
||||
let board = make_board!("8/8/8/8/8/8/8/8 w - - 0 {i}");
|
||||
assert_eq!(board.half_moves, 0);
|
||||
assert_eq!(board.full_moves, i);
|
||||
@ -437,7 +437,7 @@ mod tests {
|
||||
|
||||
for fen1 in test_cases {
|
||||
println!("fen1: {fen1:?}");
|
||||
let fen2 = BoardState::from_fen(fen1).unwrap().to_fen();
|
||||
let fen2 = Board::from_fen(fen1).unwrap().to_fen();
|
||||
|
||||
assert_eq!(fen1.to_string(), fen2, "FEN not equivalent")
|
||||
}
|
||||
|
50
src/lib.rs
50
src/lib.rs
@ -6,6 +6,8 @@ use std::str::FromStr;
|
||||
pub mod fen;
|
||||
pub mod movegen;
|
||||
|
||||
use crate::fen::{FromFen, START_POSITION};
|
||||
|
||||
const BOARD_WIDTH: usize = 8;
|
||||
const BOARD_HEIGHT: usize = 8;
|
||||
const N_SQUARES: usize = BOARD_WIDTH * BOARD_HEIGHT;
|
||||
@ -401,7 +403,7 @@ impl ToString for CastleRights {
|
||||
///
|
||||
/// Default is empty.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BoardState {
|
||||
pub struct Board {
|
||||
/// Player bitboards
|
||||
players: [Player; N_COLORS],
|
||||
|
||||
@ -426,11 +428,12 @@ pub struct BoardState {
|
||||
turn: Color,
|
||||
}
|
||||
|
||||
/// Piece missing where there should be one.
|
||||
#[derive(Debug)]
|
||||
struct NoPieceError;
|
||||
impl Board {
|
||||
/// Default chess position.
|
||||
pub fn starting_pos() -> Self {
|
||||
Board::from_fen(START_POSITION).unwrap()
|
||||
}
|
||||
|
||||
impl BoardState {
|
||||
/// Get mutable reference to a player.
|
||||
fn pl_mut(&mut self, col: Color) -> &mut Player {
|
||||
&mut self.players[col as usize]
|
||||
@ -456,42 +459,41 @@ impl BoardState {
|
||||
(0..N_SQUARES).map(Square::try_from).map(|x| x.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new piece in a location.
|
||||
fn set_piece(&mut self, idx: Square, pc: ColPiece) {
|
||||
let _ = self.del_piece(idx);
|
||||
/// Create a new piece in a location, and pop any existing piece in the destination.
|
||||
fn set_piece(&mut self, idx: Square, pc: ColPiece) -> Option<ColPiece> {
|
||||
let dest_pc = self.del_piece(idx);
|
||||
let pl = self.pl_mut(pc.col);
|
||||
pl.board_mut(pc.into()).on_sq(idx);
|
||||
*self.mail.sq_mut(idx) = Some(pc);
|
||||
dest_pc
|
||||
}
|
||||
|
||||
/// Set the piece (or no piece) in a square.
|
||||
fn set_square(&mut self, idx: Square, pc: Option<ColPiece>) {
|
||||
/// Set the piece (or no piece) in a square, and return ("pop") the existing piece.
|
||||
fn set_square(&mut self, idx: Square, pc: Option<ColPiece>) -> Option<ColPiece> {
|
||||
match pc {
|
||||
Some(pc) => self.set_piece(idx, pc),
|
||||
Some(pc) => {self.set_piece(idx, pc)},
|
||||
None => {
|
||||
let _ = self.del_piece(idx);
|
||||
self.del_piece(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the piece in a location, and return ("pop") that piece.
|
||||
///
|
||||
/// Returns an error if there is no piece in the location.
|
||||
fn del_piece(&mut self, idx: Square) -> Result<ColPiece, NoPieceError> {
|
||||
fn del_piece(&mut self, idx: Square) -> Option<ColPiece> {
|
||||
if let Some(pc) = *self.mail.sq_mut(idx) {
|
||||
let pl = self.pl_mut(pc.col);
|
||||
pl.board_mut(pc.into()).off_sq(idx);
|
||||
*self.mail.sq_mut(idx) = None;
|
||||
Ok(pc)
|
||||
Some(pc)
|
||||
} else {
|
||||
Err(NoPieceError)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn move_piece(&mut self, src: Square, dest: Square) {
|
||||
let pc = self
|
||||
.del_piece(src)
|
||||
.unwrap_or_else(|_| panic!("move ({src} -> {dest}) should have piece at source"));
|
||||
.unwrap_or_else(|| panic!("move ({src} -> {dest}) should have piece at source"));
|
||||
self.set_piece(dest, pc);
|
||||
}
|
||||
|
||||
@ -516,12 +518,12 @@ impl BoardState {
|
||||
|
||||
new_board.castle.0.reverse();
|
||||
|
||||
for sq in BoardState::squares() {
|
||||
for sq in Board::squares() {
|
||||
let opt_pc = self.get_piece(sq.mirror_vert()).map(|pc| ColPiece {
|
||||
col: pc.col.flip(),
|
||||
pc: pc.pc,
|
||||
});
|
||||
new_board.set_square(sq, opt_pc)
|
||||
new_board.set_square(sq, opt_pc);
|
||||
}
|
||||
new_board
|
||||
}
|
||||
@ -530,7 +532,7 @@ impl BoardState {
|
||||
const MAX_MOVES: usize = 9_999;
|
||||
}
|
||||
|
||||
impl core::fmt::Display for BoardState {
|
||||
impl core::fmt::Display for Board {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut str = String::with_capacity(N_SQUARES + BOARD_HEIGHT);
|
||||
for row in (0..BOARD_HEIGHT).rev() {
|
||||
@ -619,7 +621,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let board = BoardState::from_fen("8/4p3/1q1Q1p2/4p3/1p1r4/8/8/8 w - - 0 1").unwrap();
|
||||
let board = Board::from_fen("8/4p3/1q1Q1p2/4p3/1p1r4/8/8/8 w - - 0 1").unwrap();
|
||||
let white_queens = board
|
||||
.pl(Color::White)
|
||||
.board(Piece::Queen)
|
||||
@ -654,8 +656,8 @@ mod tests {
|
||||
),
|
||||
];
|
||||
for (tc, expect) in test_cases {
|
||||
let tc = BoardState::from_fen(tc).unwrap();
|
||||
let expect = BoardState::from_fen(expect).unwrap();
|
||||
let tc = Board::from_fen(tc).unwrap();
|
||||
let expect = Board::from_fen(expect).unwrap();
|
||||
assert_eq!(tc.flip_colors(), expect);
|
||||
}
|
||||
}
|
||||
|
315
src/movegen.rs
315
src/movegen.rs
@ -2,46 +2,11 @@
|
||||
|
||||
use crate::fen::{FromFen, ToFen, START_POSITION};
|
||||
use crate::{
|
||||
BoardState, ColPiece, Color, Piece, Square, SquareError, BOARD_HEIGHT, BOARD_WIDTH, N_SQUARES,
|
||||
Board, CastleRights, ColPiece, Color, Piece, Square, SquareError, BOARD_HEIGHT,
|
||||
BOARD_WIDTH, N_SQUARES,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Game tree node.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Node {
|
||||
/// Immutable position data.
|
||||
pos: BoardState,
|
||||
/// Backlink to previous node.
|
||||
prev: Option<Rc<Node>>,
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Node::new(BoardState::from_fen(START_POSITION).expect("Starting FEN should be valid"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
/// Undo move.
|
||||
///
|
||||
/// Intended usage is to always keep an Rc to the current node, and overwrite it with the
|
||||
/// result of unmake.
|
||||
pub fn unmake(&self) -> Rc<Node> {
|
||||
if let Some(prev) = &self.prev {
|
||||
Rc::clone(prev)
|
||||
} else {
|
||||
panic!("unmake should not be called on root node");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(board: BoardState) -> Self {
|
||||
Node {
|
||||
pos: board,
|
||||
prev: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Piece enum specifically for promotions.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum PromotePiece {
|
||||
@ -62,6 +27,91 @@ impl From<PromotePiece> for Piece {
|
||||
}
|
||||
}
|
||||
|
||||
enum AntiMoveType {
|
||||
Normal,
|
||||
/// En passant.
|
||||
EnPassant {
|
||||
cap: Square,
|
||||
},
|
||||
/// Pawn promotion.
|
||||
Promotion,
|
||||
/// King-rook castle. The king is the one considered to move.
|
||||
Castle {
|
||||
rook_src: Square,
|
||||
rook_dest: Square,
|
||||
},
|
||||
}
|
||||
|
||||
/// Information used to reverse (unmake) a move.
|
||||
pub struct AntiMove {
|
||||
dest: Square,
|
||||
src: Square,
|
||||
/// Captured piece, always assumed to be of enemy color.
|
||||
cap: Option<Piece>,
|
||||
move_type: AntiMoveType,
|
||||
/// Half-move counter prior to this move
|
||||
half_moves: usize,
|
||||
/// Castling rights prior to this move.
|
||||
castle: CastleRights,
|
||||
/// En passant target square prior to this move.
|
||||
ep_square: Option<Square>
|
||||
}
|
||||
|
||||
impl AntiMove {
|
||||
/// Undo the move.
|
||||
fn unmake(self, pos: &mut Board) {
|
||||
pos.move_piece(self.dest, self.src);
|
||||
pos.half_moves = self.half_moves;
|
||||
pos.castle = self.castle;
|
||||
pos.ep_square = self.ep_square;
|
||||
|
||||
/// Restore captured piece at a given square.
|
||||
macro_rules! cap_sq {
|
||||
($sq: expr) => {
|
||||
if let Some(cap_pc) = self.cap {
|
||||
pos.set_piece(
|
||||
$sq,
|
||||
ColPiece {
|
||||
pc: cap_pc,
|
||||
col: pos.turn.flip(),
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pos.turn = pos.turn.flip();
|
||||
if pos.turn == Color::Black {
|
||||
pos.full_moves -= 1;
|
||||
}
|
||||
|
||||
match self.move_type {
|
||||
AntiMoveType::Normal => {
|
||||
cap_sq!(self.dest)
|
||||
}
|
||||
AntiMoveType::EnPassant { cap } => {
|
||||
cap_sq!(cap);
|
||||
}
|
||||
AntiMoveType::Promotion => {
|
||||
cap_sq!(self.dest);
|
||||
pos.set_piece(
|
||||
self.src,
|
||||
ColPiece {
|
||||
pc: Piece::Pawn,
|
||||
col: pos.turn,
|
||||
},
|
||||
);
|
||||
}
|
||||
AntiMoveType::Castle {
|
||||
rook_src,
|
||||
rook_dest,
|
||||
} => {
|
||||
pos.move_piece(rook_dest, rook_src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
enum MoveType {
|
||||
/// Pawn promotes to another piece.
|
||||
@ -80,24 +130,30 @@ pub struct Move {
|
||||
}
|
||||
|
||||
impl Move {
|
||||
/// Make move, without setting up the backlink for unmake.
|
||||
///
|
||||
/// Call this directly when making new positions that are dead ends (won't be used further).
|
||||
fn make_unlinked(self, old_pos: &BoardState) -> BoardState {
|
||||
let mut new_pos = *old_pos;
|
||||
/// Apply move to a position.
|
||||
fn make(self, pos: &mut Board) -> AntiMove {
|
||||
let mut anti_move = AntiMove {
|
||||
dest: self.dest,
|
||||
src: self.src,
|
||||
cap: None,
|
||||
move_type: AntiMoveType::Normal,
|
||||
half_moves: pos.half_moves,
|
||||
castle: pos.castle,
|
||||
ep_square: pos.ep_square,
|
||||
};
|
||||
|
||||
// reset en passant
|
||||
new_pos.ep_square = None;
|
||||
let ep_square = pos.ep_square;
|
||||
pos.ep_square = None;
|
||||
|
||||
if old_pos.turn == Color::Black {
|
||||
new_pos.full_moves += 1;
|
||||
if pos.turn == Color::Black {
|
||||
pos.full_moves += 1;
|
||||
}
|
||||
|
||||
/// Get the piece at the source square.
|
||||
macro_rules! pc_src {
|
||||
($data: ident) => {
|
||||
new_pos
|
||||
.get_piece($data.src)
|
||||
pos.get_piece($data.src)
|
||||
.expect("Move source should have a piece")
|
||||
};
|
||||
}
|
||||
@ -106,11 +162,11 @@ impl Move {
|
||||
($pc_src: ident) => {
|
||||
debug_assert_eq!(
|
||||
$pc_src.col,
|
||||
new_pos.turn,
|
||||
pos.turn,
|
||||
"Moving piece on wrong turn. Move {} -> {} on board '{}'",
|
||||
self.src,
|
||||
self.dest,
|
||||
old_pos.to_fen()
|
||||
pos.to_fen()
|
||||
);
|
||||
debug_assert_ne!(self.src, self.dest, "Moving piece to itself.");
|
||||
};
|
||||
@ -122,27 +178,32 @@ impl Move {
|
||||
pc_asserts!(pc_src);
|
||||
debug_assert_eq!(pc_src.pc, Piece::Pawn);
|
||||
|
||||
let _ = new_pos.del_piece(self.src);
|
||||
new_pos.set_piece(
|
||||
pos.half_moves = 0;
|
||||
|
||||
anti_move.move_type = AntiMoveType::Promotion;
|
||||
|
||||
pos.del_piece(self.src);
|
||||
pos.set_piece(
|
||||
self.dest,
|
||||
ColPiece {
|
||||
pc: Piece::from(to_piece),
|
||||
col: pc_src.col,
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
MoveType::Normal => {
|
||||
let pc_src = pc_src!(self);
|
||||
pc_asserts!(pc_src);
|
||||
|
||||
let pc_dest: Option<ColPiece> = new_pos.get_piece(self.dest);
|
||||
let pc_dest: Option<ColPiece> = pos.get_piece(self.dest);
|
||||
anti_move.cap = pc_dest.map(|pc| pc.pc);
|
||||
|
||||
let (src_row, src_col) = self.src.to_row_col_signed();
|
||||
let (dest_row, dest_col) = self.dest.to_row_col_signed();
|
||||
|
||||
if matches!(pc_src.pc, Piece::Pawn) {
|
||||
// pawn moves are irreversible
|
||||
new_pos.half_moves = 0;
|
||||
pos.half_moves = 0;
|
||||
|
||||
// set en-passant target square
|
||||
if src_row.abs_diff(dest_row) == 2 {
|
||||
@ -155,31 +216,46 @@ impl Move {
|
||||
};
|
||||
let ep_targ = Square::from_row_col_signed(ep_row, ep_col)
|
||||
.expect("En-passant target should be valid.");
|
||||
new_pos.ep_square = Some(ep_targ)
|
||||
pos.ep_square = Some(ep_targ)
|
||||
} else if pc_dest.is_none() && src_col != dest_col {
|
||||
// we took en passant
|
||||
debug_assert!(src_row.abs_diff(dest_row) == 1);
|
||||
debug_assert_eq!(self.dest, old_pos.ep_square.unwrap());
|
||||
assert_eq!(self.dest, ep_square.expect("ep target should exist if taking ep"));
|
||||
// square to actually capture at
|
||||
let ep_capture = Square::try_from(match pc_src.col {
|
||||
Color::White => self.dest.0 - BOARD_WIDTH,
|
||||
Color::Black => self.dest.0 + BOARD_WIDTH,
|
||||
})
|
||||
.expect("En-passant capture square should be valid");
|
||||
new_pos.del_piece(ep_capture).unwrap_or_else(|_| {
|
||||
panic!("En-passant capture square should have piece. Position '{}', move {:?}", old_pos.to_fen(), self)
|
||||
});
|
||||
|
||||
anti_move.move_type = AntiMoveType::EnPassant{cap: ep_capture};
|
||||
if let Some(pc_cap) = pos.del_piece(ep_capture) {
|
||||
debug_assert_eq!(
|
||||
pc_cap.col,
|
||||
pos.turn.flip(),
|
||||
"attempt to en passant wrong color, pos '{}', move {:?}",
|
||||
pos.to_fen(),
|
||||
self
|
||||
);
|
||||
anti_move.cap = Some(pc_cap.pc);
|
||||
} else {
|
||||
panic!(
|
||||
"En-passant capture square should have piece. Position '{}', move {:?}",
|
||||
pos.to_fen(),
|
||||
self
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_pos.half_moves += 1;
|
||||
pos.half_moves += 1;
|
||||
}
|
||||
|
||||
if pc_dest.is_some() {
|
||||
// captures are irreversible
|
||||
new_pos.half_moves = 0;
|
||||
pos.half_moves = 0;
|
||||
}
|
||||
|
||||
let castle = &mut new_pos.pl_castle_mut(pc_src.col);
|
||||
let castle = &mut pos.pl_castle_mut(pc_src.col);
|
||||
if matches!(pc_src.pc, Piece::King) {
|
||||
// forfeit castling rights
|
||||
castle.k = false;
|
||||
@ -203,8 +279,12 @@ impl Move {
|
||||
.expect("rook castling src square should be valid");
|
||||
let rook_dest = Square::from_row_col_signed(rook_row, rook_dest_col)
|
||||
.expect("rook castling dest square should be valid");
|
||||
debug_assert!(new_pos.get_piece(rook_src).is_some(), "rook castling src square has no rook (move: {rook_src} -> {rook_dest})");
|
||||
new_pos.move_piece(rook_src, rook_dest);
|
||||
debug_assert!(pos.get_piece(rook_src).is_some(), "rook castling src square has no rook (move: {rook_src} -> {rook_dest})");
|
||||
anti_move.move_type = AntiMoveType::Castle {
|
||||
rook_src,
|
||||
rook_dest,
|
||||
};
|
||||
pos.move_piece(rook_src, rook_dest);
|
||||
}
|
||||
debug_assert!(
|
||||
(0..=2).contains(&horiz_diff),
|
||||
@ -231,26 +311,13 @@ impl Move {
|
||||
}
|
||||
}
|
||||
|
||||
new_pos.move_piece(self.src, self.dest);
|
||||
pos.move_piece(self.src, self.dest);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new_pos.turn = new_pos.turn.flip();
|
||||
pos.turn = pos.turn.flip();
|
||||
|
||||
new_pos
|
||||
}
|
||||
|
||||
/// Make move and return new position.
|
||||
///
|
||||
/// Old position is saved in a backlink.
|
||||
/// No checking is done to verify even pseudo-legality of the move.
|
||||
pub fn make(self, old_node: &Rc<Node>) -> Rc<Node> {
|
||||
let pos = self.make_unlinked(&old_node.pos);
|
||||
Node {
|
||||
prev: Some(Rc::clone(old_node)),
|
||||
pos,
|
||||
}
|
||||
.into()
|
||||
anti_move
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,7 +437,7 @@ enum SliderDirection {
|
||||
/// * `slide_type`: Directions the piece is allowed to go in.
|
||||
/// * `keep_going`: Allow sliding more than one square (true for everything except king).
|
||||
fn move_slider(
|
||||
board: &BoardState,
|
||||
board: &Board,
|
||||
src: Square,
|
||||
move_list: &mut Vec<Move>,
|
||||
slide_type: SliderDirection,
|
||||
@ -414,7 +481,7 @@ fn move_slider(
|
||||
}
|
||||
}
|
||||
|
||||
impl PseudoMoveGen for BoardState {
|
||||
impl PseudoMoveGen for Board {
|
||||
fn gen_pseudo_moves(&self) -> impl IntoIterator<Item = Move> {
|
||||
let mut ret = Vec::new();
|
||||
let pl = self.pl(self.turn);
|
||||
@ -607,7 +674,7 @@ pub trait LegalMoveGen {
|
||||
}
|
||||
|
||||
/// Is a given player in check?
|
||||
fn is_check(board: &BoardState, pl: Color) -> bool {
|
||||
fn is_check(board: &Board, pl: Color) -> bool {
|
||||
for src in board.pl(pl).board(Piece::King).into_iter() {
|
||||
macro_rules! detect_checker {
|
||||
($dirs: ident, $pc: pat, $keep_going: expr) => {
|
||||
@ -653,42 +720,47 @@ fn is_check(board: &BoardState, pl: Color) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
impl LegalMoveGen for Node {
|
||||
impl LegalMoveGen for Board {
|
||||
// mut required for check checking
|
||||
fn gen_moves(&self) -> impl IntoIterator<Item = Move> {
|
||||
self.pos
|
||||
.gen_pseudo_moves()
|
||||
let mut pos = self.clone();
|
||||
|
||||
pos.gen_pseudo_moves()
|
||||
.into_iter()
|
||||
.filter(|mv| {
|
||||
// disallow friendly fire
|
||||
let src_pc = self
|
||||
.pos
|
||||
.get_piece(mv.src)
|
||||
.expect("move source should have piece");
|
||||
if let Some(dest_pc) = self.pos.get_piece(mv.dest) {
|
||||
if let Some(dest_pc) = self.get_piece(mv.dest) {
|
||||
return dest_pc.col != src_pc.col;
|
||||
}
|
||||
true
|
||||
})
|
||||
.filter(|mv| {
|
||||
.filter(move |mv| {
|
||||
// disallow moving into check
|
||||
let new_pos = mv.make_unlinked(&self.pos);
|
||||
!is_check(&new_pos, self.pos.turn)
|
||||
let anti_move = mv.make(&mut pos);
|
||||
let ret = !is_check(&pos, self.turn);
|
||||
anti_move.unmake(&mut pos);
|
||||
ret
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// How many nodes at depth N can be reached from this position.
|
||||
pub fn perft(depth: usize, node: &Rc<Node>) -> usize {
|
||||
pub fn perft(depth: usize, pos: &mut Board) -> usize {
|
||||
if depth == 0 {
|
||||
return 1;
|
||||
};
|
||||
|
||||
let mut ans = 0;
|
||||
|
||||
let moves = node.gen_moves();
|
||||
let moves: Vec<Move> = pos.gen_moves().into_iter().collect();
|
||||
for mv in moves {
|
||||
let new_node = mv.make(node);
|
||||
ans += perft(depth - 1, &new_node);
|
||||
let anti_move = mv.make(pos);
|
||||
ans += perft(depth - 1, pos);
|
||||
anti_move.unmake(pos);
|
||||
}
|
||||
|
||||
ans
|
||||
@ -702,15 +774,15 @@ mod tests {
|
||||
#[test]
|
||||
/// Ensure that bitboard properly reflects captures.
|
||||
fn test_bitboard_capture() {
|
||||
let board = BoardState::from_fen("8/8/8/8/8/8/r7/R7 w - - 0 1").unwrap();
|
||||
let mut pos = Board::from_fen("8/8/8/8/8/8/r7/R7 w - - 0 1").unwrap();
|
||||
let mv = Move::from_uci_algebraic("a1a2").unwrap();
|
||||
let new_pos = mv.make_unlinked(&board);
|
||||
let anti_move = mv.make(&mut pos);
|
||||
|
||||
use std::collections::hash_set::HashSet;
|
||||
use Piece::*;
|
||||
for pc in [Rook, Bishop, Knight, Queen, King, Pawn] {
|
||||
let white: HashSet<_> = new_pos.pl(Color::White).board(pc).into_iter().collect();
|
||||
let black: HashSet<_> = new_pos.pl(Color::Black).board(pc).into_iter().collect();
|
||||
let white: HashSet<_> = pos.pl(Color::White).board(pc).into_iter().collect();
|
||||
let black: HashSet<_> = pos.pl(Color::Black).board(pc).into_iter().collect();
|
||||
let intersect = white.intersection(&black).collect::<Vec<_>>();
|
||||
assert!(
|
||||
intersect.is_empty(),
|
||||
@ -723,9 +795,9 @@ mod tests {
|
||||
/// Helper to produce test cases.
|
||||
fn decondense_moves(
|
||||
test_case: (&str, Vec<(&str, Vec<&str>, MoveType)>),
|
||||
) -> (BoardState, Vec<Move>) {
|
||||
) -> (Board, Vec<Move>) {
|
||||
let (fen, expected) = test_case;
|
||||
let board = BoardState::from_fen(fen).unwrap();
|
||||
let board = Board::from_fen(fen).unwrap();
|
||||
|
||||
let mut expected_moves = expected
|
||||
.iter()
|
||||
@ -749,7 +821,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Generate new test cases by flipping colors on existing ones.
|
||||
fn flip_test_case(board: BoardState, moves: &Vec<Move>) -> (BoardState, Vec<Move>) {
|
||||
fn flip_test_case(board: Board, moves: &Vec<Move>) -> (Board, Vec<Move>) {
|
||||
let mut move_vec = moves
|
||||
.iter()
|
||||
.map(|mv| Move {
|
||||
@ -1044,7 +1116,7 @@ mod tests {
|
||||
|
||||
let all_cases = check_cases.iter().chain(¬_check_cases);
|
||||
for (fen, expected) in all_cases {
|
||||
let board = BoardState::from_fen(fen).unwrap();
|
||||
let board = Board::from_fen(fen).unwrap();
|
||||
assert_eq!(
|
||||
is_check(&board, Color::White),
|
||||
*expected,
|
||||
@ -1163,14 +1235,12 @@ mod tests {
|
||||
|
||||
let all_cases = [augmented_test_cases, test_cases].concat();
|
||||
|
||||
for (board, mut expected_moves) in all_cases {
|
||||
for (mut board, mut expected_moves) in all_cases {
|
||||
eprintln!("on test '{}'", board.to_fen());
|
||||
expected_moves.sort_unstable();
|
||||
let expected_moves = expected_moves;
|
||||
|
||||
let node = Node::new(board);
|
||||
|
||||
let mut moves: Vec<Move> = node.gen_moves().into_iter().collect();
|
||||
let mut moves: Vec<Move> = board.gen_moves().into_iter().collect();
|
||||
moves.sort_unstable();
|
||||
let moves = moves;
|
||||
|
||||
@ -1290,24 +1360,19 @@ mod tests {
|
||||
for (i, test_case) in test_cases.iter().enumerate() {
|
||||
let (start_pos, moves) = test_case;
|
||||
|
||||
// make move
|
||||
eprintln!("Starting test case {i}, make move.");
|
||||
let mut node = Rc::new(Node::new(BoardState::from_fen(start_pos).unwrap()));
|
||||
let mut pos = Board::from_fen(start_pos).unwrap();
|
||||
for (move_str, expect_fen) in moves {
|
||||
let prior_fen = pos.to_fen();
|
||||
let mv = Move::from_uci_algebraic(move_str).unwrap();
|
||||
eprintln!("Moving {move_str}.");
|
||||
node = mv.make(&node);
|
||||
assert_eq!(node.pos.to_fen(), expect_fen.to_string())
|
||||
}
|
||||
|
||||
// unmake move
|
||||
eprintln!("Starting test case {i}, unmake move.");
|
||||
for (_, expect_fen) in moves.iter().rev().chain([("", *start_pos)].iter()) {
|
||||
eprintln!("{}", expect_fen);
|
||||
assert_eq!(*node.pos.to_fen(), expect_fen.to_string());
|
||||
if *expect_fen != *start_pos {
|
||||
node = node.unmake();
|
||||
}
|
||||
eprintln!("Moving {move_str} on {}", prior_fen);
|
||||
let anti_move = mv.make(&mut pos);
|
||||
eprintln!("Unmaking {move_str} on {}.", pos.to_fen());
|
||||
anti_move.unmake(&mut pos);
|
||||
assert_eq!(pos.to_fen(), prior_fen.to_string());
|
||||
eprintln!("Remaking {move_str}.");
|
||||
let anti_move = mv.make(&mut pos);
|
||||
assert_eq!(pos.to_fen(), expect_fen.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1354,7 +1419,7 @@ mod tests {
|
||||
),
|
||||
];
|
||||
for (fen, expected_values, _debug_limit_depth) in test_cases {
|
||||
let root_node = Rc::new(Node::new(BoardState::from_fen(fen).unwrap()));
|
||||
let mut pos = Board::from_fen(fen).unwrap();
|
||||
|
||||
for (depth, expected) in expected_values.iter().enumerate() {
|
||||
eprintln!("running perft depth {depth} on position '{fen}'");
|
||||
@ -1364,7 +1429,7 @@ mod tests {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(perft(depth, &root_node), *expected,);
|
||||
assert_eq!(perft(depth, &mut pos), *expected,);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user