diff --git a/position/src/move.rs b/position/src/move.rs index bf6dc3c..32bc399 100644 --- a/position/src/move.rs +++ b/position/src/move.rs @@ -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"); }; } diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index 7f7d481..d2b7920 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -66,7 +66,7 @@ macro_rules! move_generator_declaration { ($name:ident, getters) => { impl $name { pub(super) fn iter(&self) -> impl Iterator + '_ { - 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 + '_ { + pub fn iter(&self) -> impl Iterator + '_ { self.pawn_moves .iter() .chain(self.knight_moves.iter()) diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs index f8f3ca1..6cefb7a 100644 --- a/position/src/move_generator/move_set.rs +++ b/position/src/move_generator/move_set.rs @@ -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 + '_ { + pub(crate) fn moves(&self) -> impl Iterator + '_ { let piece = self.piece.piece(); let from_square = self.piece.square(); diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index c8b2e6c..8fef802 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -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] diff --git a/position/src/position/builders/move_builder.rs b/position/src/position/builders/move_builder.rs index 85310d8..c926c08 100644 --- a/position/src/position/builders/move_builder.rs +++ b/position/src/position/builders/move_builder.rs @@ -1,6 +1,10 @@ // Eryn Wells -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::::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!( diff --git a/position/src/position/position.rs b/position/src/position/position.rs index f0f8414..1785c04 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -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 {