[position] Fix all the unit tests
The pawn move generator only generated pushes for white pawns. The is_king_in_check returned an inverted flag. MoveSet needed a couple more validation methods: can_move_to_square and can_castle. The MakeMoveBuilder also needed a little more move validation using the above methods.
This commit is contained in:
parent
63758a2edd
commit
4a601c2b81
6 changed files with 82 additions and 31 deletions
|
@ -11,6 +11,7 @@ pub enum MakeMoveError {
|
|||
PlayerOutOfTurn,
|
||||
NoPiece,
|
||||
NoCapturedPiece,
|
||||
NoLegalMoves,
|
||||
IllegalCastle,
|
||||
IllegalSquare(Square),
|
||||
}
|
||||
|
@ -523,20 +524,24 @@ mod tests {
|
|||
use chessfriend_core::piece;
|
||||
|
||||
macro_rules! assert_flag {
|
||||
($move:expr, $left:expr, $right:expr) => {
|
||||
assert_eq!($left, $right, "{:?}", $move)
|
||||
($move:expr, $left:expr, $right:expr, $desc:expr) => {
|
||||
assert_eq!($left, $right, "{:?} -> {}", $move, stringify!($desc))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_flags {
|
||||
($move:expr, $quiet:expr, $double_push:expr, $en_passant:expr, $capture:expr, $castle:expr, $promotion:expr) => {
|
||||
assert_flag!($move, $move.is_quiet(), $quiet);
|
||||
assert_flag!($move, $move.is_quiet(), $quiet);
|
||||
assert_flag!($move, $move.is_double_push(), $double_push);
|
||||
assert_flag!($move, $move.is_en_passant(), $en_passant);
|
||||
assert_flag!($move, $move.is_capture(), $capture);
|
||||
assert_flag!($move, $move.is_castle(), $castle);
|
||||
assert_flag!($move, $move.is_promotion(), $promotion);
|
||||
assert_flag!($move, $move.is_quiet(), $quiet, "is_quiet");
|
||||
assert_flag!(
|
||||
$move,
|
||||
$move.is_double_push(),
|
||||
$double_push,
|
||||
"is_double_push"
|
||||
);
|
||||
assert_flag!($move, $move.is_en_passant(), $en_passant, "is_en_passant");
|
||||
assert_flag!($move, $move.is_capture(), $capture, "is_capture");
|
||||
assert_flag!($move, $move.is_castle(), $castle, "is_castle");
|
||||
assert_flag!($move, $move.is_promotion(), $promotion, "is_promotion");
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ macro_rules! move_generator_declaration {
|
|||
($name:ident, getters) => {
|
||||
impl $name {
|
||||
pub(super) fn iter(&self) -> impl Iterator<Item = $crate::Move> + '_ {
|
||||
self.move_sets.values().map(|set| set.moves()).flatten()
|
||||
self.move_sets.values().flat_map(|set| set.moves())
|
||||
}
|
||||
|
||||
pub(crate) fn moves_for_piece(
|
||||
|
@ -161,7 +161,7 @@ impl Moves {
|
|||
}
|
||||
}
|
||||
|
||||
fn iter(&self) -> impl Iterator<Item = Move> + '_ {
|
||||
pub fn iter(&self) -> impl Iterator<Item = Move> + '_ {
|
||||
self.pawn_moves
|
||||
.iter()
|
||||
.chain(self.knight_moves.iter())
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
|
||||
use crate::{r#move::Castle, Move, MoveBuilder};
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape};
|
||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||
|
||||
/// A set of bitboards defining the moves for a single piece on the board.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
struct BitBoardSet {
|
||||
quiet: BitBoard,
|
||||
captures: BitBoard,
|
||||
}
|
||||
|
||||
/// A set of moves for a piece on the board.
|
||||
/// A set of moves for a single piece on the board.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct MoveSet {
|
||||
piece: PlacedPiece,
|
||||
|
@ -27,6 +28,17 @@ impl MoveSet {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn can_move_to_square(&self, to: Square) -> bool {
|
||||
self.bitboard().is_set(to)
|
||||
}
|
||||
|
||||
pub(crate) fn can_castle(&self, castle: Castle) -> bool {
|
||||
match castle {
|
||||
Castle::KingSide => (self.special & 0b1) != 0,
|
||||
Castle::QueenSide => (self.special & 0b10) != 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn quiet_moves(mut self, bitboard: BitBoard) -> MoveSet {
|
||||
self.bitboards.quiet = bitboard;
|
||||
self
|
||||
|
@ -52,7 +64,7 @@ impl MoveSet {
|
|||
self.bitboards.captures | self.bitboards.quiet
|
||||
}
|
||||
|
||||
pub(super) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
|
||||
pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
|
||||
let piece = self.piece.piece();
|
||||
let from_square = self.piece.square();
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||
use crate::Position;
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Piece, PlacedPiece, Rank, Shape, Square};
|
||||
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MoveIterator(usize, usize);
|
||||
|
@ -32,18 +32,31 @@ impl MoveGeneratorInternal for PawnMoveGenerator {
|
|||
|
||||
impl PawnMoveGenerator {
|
||||
fn pushes(position: &Position, piece: PlacedPiece) -> BitBoard {
|
||||
let color = piece.color();
|
||||
let square = piece.square();
|
||||
let bitboard: BitBoard = square.into();
|
||||
|
||||
let starting_rank = Rank::PAWN_STARTING_RANKS[piece.color() as usize];
|
||||
|
||||
let starting_rank = Rank::PAWN_STARTING_RANKS[color as usize];
|
||||
let empty_squares = position.empty_squares();
|
||||
let mut moves = bitboard.shift_north_one() & empty_squares;
|
||||
if !(bitboard & BitBoard::rank(starting_rank.as_index())).is_empty() {
|
||||
moves |= moves.shift_north_one() & empty_squares;
|
||||
}
|
||||
|
||||
moves
|
||||
match color {
|
||||
Color::White => {
|
||||
let mut moves = bitboard.shift_north_one() & empty_squares;
|
||||
if !(bitboard & BitBoard::rank(starting_rank.as_index())).is_empty() {
|
||||
moves |= moves.shift_north_one() & empty_squares;
|
||||
}
|
||||
|
||||
moves
|
||||
}
|
||||
Color::Black => {
|
||||
let mut moves = bitboard.shift_south_one() & empty_squares;
|
||||
if !(bitboard & BitBoard::rank(starting_rank.as_index())).is_empty() {
|
||||
moves |= moves.shift_south_one() & empty_squares;
|
||||
}
|
||||
|
||||
moves
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attacks(position: &Position, piece: PlacedPiece) -> BitBoard {
|
||||
|
@ -63,7 +76,7 @@ impl PawnMoveGenerator {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_move_list, position::DiagramFormatter, test_position, Move, MoveBuilder};
|
||||
use chessfriend_core::{piece, Color, Square};
|
||||
use chessfriend_core::{piece, Color, Piece, Square};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{position::flags::Flags, r#move::Castle, MakeMoveError, Move, Position};
|
||||
use crate::{
|
||||
position::flags::Flags,
|
||||
r#move::{AlgebraicMoveFormatter, Castle},
|
||||
MakeMoveError, Move, Position,
|
||||
};
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square};
|
||||
|
||||
|
@ -60,9 +64,22 @@ where
|
|||
|
||||
let to_square = mv.to_square();
|
||||
|
||||
let sight = self.position.sight_of_piece(&piece);
|
||||
if !sight.is_set(to_square) {
|
||||
return Err(MakeMoveError::IllegalSquare(to_square));
|
||||
let moves = self
|
||||
.position
|
||||
.moves_for_piece(&piece)
|
||||
.ok_or(MakeMoveError::NoLegalMoves)?;
|
||||
|
||||
match mv.castle() {
|
||||
Some(castle) => {
|
||||
if !moves.can_castle(castle) {
|
||||
return Err(MakeMoveError::IllegalCastle);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if !moves.can_move_to_square(to_square) {
|
||||
return Err(MakeMoveError::IllegalSquare(to_square));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let player = self.position.player_to_move();
|
||||
|
@ -324,7 +341,7 @@ mod tests {
|
|||
assert!(black_pawn_move.is_double_push());
|
||||
assert!(!black_pawn_move.is_en_passant());
|
||||
|
||||
let en_passant_position = Builder::<NoMove>::new(&pos).make(&black_pawn_move)?.build();
|
||||
let en_passant_position = Builder::new(&pos).make(&black_pawn_move)?.build();
|
||||
println!("{en_passant_position}");
|
||||
|
||||
assert_eq!(
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use super::{flags::Flags, piece_sets::PieceBitBoards, Pieces};
|
||||
use crate::{
|
||||
check::{self, CheckingPieces},
|
||||
move_generator::Moves,
|
||||
check::CheckingPieces,
|
||||
move_generator::{MoveSet, Moves},
|
||||
position::DiagramFormatter,
|
||||
r#move::Castle,
|
||||
sight::SightExt,
|
||||
|
@ -216,6 +216,10 @@ impl Position {
|
|||
.fold(BitBoard::empty(), |acc, sight| acc | sight)
|
||||
}
|
||||
|
||||
pub(crate) fn moves_for_piece(&self, piece: &PlacedPiece) -> Option<&MoveSet> {
|
||||
self.moves().moves_for_piece(piece)
|
||||
}
|
||||
|
||||
pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard {
|
||||
piece.sight(&self.pieces, self.en_passant_square)
|
||||
}
|
||||
|
@ -236,7 +240,7 @@ impl Position {
|
|||
|
||||
pub(crate) fn is_king_in_check(&self) -> bool {
|
||||
let danger_squares = self.king_danger(self.color_to_move);
|
||||
(danger_squares & self.king_bitboard(self.color_to_move)).is_empty()
|
||||
!(danger_squares & self.king_bitboard(self.color_to_move)).is_empty()
|
||||
}
|
||||
|
||||
fn king_bitboard(&self, player: Color) -> &BitBoard {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue