Directly rename board -> position
This commit is contained in:
		
							parent
							
								
									569693bda9
								
							
						
					
					
						commit
						220da08727
					
				
					 25 changed files with 0 additions and 0 deletions
				
			
		
							
								
								
									
										7
									
								
								position/Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								position/Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "board"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
							
								
								
									
										10
									
								
								position/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								position/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "chessfriend_position"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
chessfriend_core = { path = "../core" }
 | 
			
		||||
chessfriend_bitboard = { path = "../bitboard" }
 | 
			
		||||
							
								
								
									
										15
									
								
								position/src/display.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								position/src/display.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use std::fmt;
 | 
			
		||||
 | 
			
		||||
pub(crate) trait ASCIIDisplay {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) trait UnicodeDisplay {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) trait FENDisplay {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										136
									
								
								position/src/fen.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								position/src/fen.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,136 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use crate::{r#move::Castle, Position};
 | 
			
		||||
use chessfriend_core::{Color, File, Piece, PlacedPiece, Rank, Square};
 | 
			
		||||
use std::fmt::Write;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 | 
			
		||||
pub enum FenError {
 | 
			
		||||
    FmtError(std::fmt::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait ToFen {
 | 
			
		||||
    fn to_fen(&self) -> Result<String, FenError>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToFen for Position {
 | 
			
		||||
    fn to_fen(&self) -> Result<String, FenError> {
 | 
			
		||||
        let mut fen_string = String::new();
 | 
			
		||||
 | 
			
		||||
        let mut empty_squares: u8 = 0;
 | 
			
		||||
        for rank in Rank::ALL.iter().rev() {
 | 
			
		||||
            for file in File::ALL.iter() {
 | 
			
		||||
                let square = Square::from_file_rank(*file, *rank);
 | 
			
		||||
                match self.piece_on_square(square) {
 | 
			
		||||
                    Some(piece) => {
 | 
			
		||||
                        if empty_squares > 0 {
 | 
			
		||||
                            write!(fen_string, "{}", empty_squares)
 | 
			
		||||
                                .map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
                            empty_squares = 0;
 | 
			
		||||
                        }
 | 
			
		||||
                        write!(fen_string, "{}", piece.to_fen()?)
 | 
			
		||||
                            .map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
                    }
 | 
			
		||||
                    None => empty_squares += 1,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if empty_squares > 0 {
 | 
			
		||||
                write!(fen_string, "{}", empty_squares).map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
                empty_squares = 0;
 | 
			
		||||
            }
 | 
			
		||||
            if rank != &Rank::ONE {
 | 
			
		||||
                write!(fen_string, "/").map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        write!(fen_string, " {}", self.player_to_move().to_fen()?)
 | 
			
		||||
            .map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
 | 
			
		||||
        let castling = [
 | 
			
		||||
            (Color::White, Castle::KingSide),
 | 
			
		||||
            (Color::White, Castle::QueenSide),
 | 
			
		||||
            (Color::Black, Castle::KingSide),
 | 
			
		||||
            (Color::Black, Castle::QueenSide),
 | 
			
		||||
        ]
 | 
			
		||||
        .map(|(color, castle)| {
 | 
			
		||||
            let can_castle = self.player_has_right_to_castle(color, castle);
 | 
			
		||||
            if !can_castle {
 | 
			
		||||
                ""
 | 
			
		||||
            } else {
 | 
			
		||||
                match (color, castle) {
 | 
			
		||||
                    (Color::White, Castle::KingSide) => "K",
 | 
			
		||||
                    (Color::White, Castle::QueenSide) => "Q",
 | 
			
		||||
                    (Color::Black, Castle::KingSide) => "k",
 | 
			
		||||
                    (Color::Black, Castle::QueenSide) => "q",
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .concat();
 | 
			
		||||
 | 
			
		||||
        write!(
 | 
			
		||||
            fen_string,
 | 
			
		||||
            " {}",
 | 
			
		||||
            if castling.len() > 0 { &castling } else { "-" }
 | 
			
		||||
        )
 | 
			
		||||
        .map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
 | 
			
		||||
        write!(
 | 
			
		||||
            fen_string,
 | 
			
		||||
            " {}",
 | 
			
		||||
            if let Some(en_passant_square) = self.en_passant_square() {
 | 
			
		||||
                en_passant_square.to_string()
 | 
			
		||||
            } else {
 | 
			
		||||
                "-".to_string()
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        .map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
 | 
			
		||||
        write!(fen_string, " {}", self.ply_counter()).map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
        write!(fen_string, " {}", self.move_number()).map_err(|err| FenError::FmtError(err))?;
 | 
			
		||||
 | 
			
		||||
        Ok(fen_string)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToFen for Color {
 | 
			
		||||
    fn to_fen(&self) -> Result<String, FenError> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Color::White => Ok("w".to_string()),
 | 
			
		||||
            Color::Black => Ok("b".to_string()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToFen for Piece {
 | 
			
		||||
    fn to_fen(&self) -> Result<String, FenError> {
 | 
			
		||||
        let ascii: char = self.to_ascii();
 | 
			
		||||
        Ok(String::from(match self.color() {
 | 
			
		||||
            Color::White => ascii.to_ascii_uppercase(),
 | 
			
		||||
            Color::Black => ascii.to_ascii_lowercase(),
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToFen for PlacedPiece {
 | 
			
		||||
    fn to_fen(&self) -> Result<String, FenError> {
 | 
			
		||||
        Ok(self.piece().to_fen()?)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn starting_position() {
 | 
			
		||||
        let pos = Position::starting();
 | 
			
		||||
        println!("{pos:#?}");
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            pos.to_fen(),
 | 
			
		||||
            Ok(String::from(
 | 
			
		||||
                "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
 | 
			
		||||
            ))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								position/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								position/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
pub mod fen;
 | 
			
		||||
 | 
			
		||||
mod display;
 | 
			
		||||
mod r#move;
 | 
			
		||||
mod move_generator;
 | 
			
		||||
mod position;
 | 
			
		||||
mod sight;
 | 
			
		||||
 | 
			
		||||
#[macro_use]
 | 
			
		||||
mod macros;
 | 
			
		||||
 | 
			
		||||
pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
 | 
			
		||||
pub use r#move::{Castle, MakeMoveError, Move, MoveBuilder};
 | 
			
		||||
							
								
								
									
										45
									
								
								position/src/macros.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								position/src/macros.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! position {
 | 
			
		||||
    [$($color:ident $shape:ident on $square:ident),* $(,)?] => {
 | 
			
		||||
        $crate::PositionBuilder::new()
 | 
			
		||||
            $(.place_piece(
 | 
			
		||||
                chessfriend_core::PlacedPiece::new(
 | 
			
		||||
                    chessfriend_core::Piece::new(
 | 
			
		||||
                        chessfriend_core::Color::$color,
 | 
			
		||||
                        chessfriend_core::Shape::$shape),
 | 
			
		||||
                    chessfriend_core::Square::$square
 | 
			
		||||
                )
 | 
			
		||||
            ))*
 | 
			
		||||
            .build()
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! test_position {
 | 
			
		||||
    [$($color:ident $shape:ident on $square:ident),* $(,)?] => {
 | 
			
		||||
        {
 | 
			
		||||
            let pos = $crate::PositionBuilder::new()
 | 
			
		||||
                $(.place_piece(piece!($color $shape on $square)))*
 | 
			
		||||
                .build();
 | 
			
		||||
            println!("{pos}");
 | 
			
		||||
            pos
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    (empty) => {
 | 
			
		||||
        {
 | 
			
		||||
            let pos = Position::empty();
 | 
			
		||||
            println!("{pos}");
 | 
			
		||||
            pos
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    (starting) => {
 | 
			
		||||
        {
 | 
			
		||||
            let pos = Position::starting();
 | 
			
		||||
            println!("{pos}");
 | 
			
		||||
            pos
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										565
									
								
								position/src/move.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										565
									
								
								position/src/move.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,565 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use chessfriend_core::{Piece, PlacedPiece, Rank, Shape, Square};
 | 
			
		||||
use std::fmt;
 | 
			
		||||
 | 
			
		||||
pub use castle::Castle;
 | 
			
		||||
pub(crate) use move_formatter::AlgebraicMoveFormatter;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 | 
			
		||||
pub enum MakeMoveError {
 | 
			
		||||
    PlayerOutOfTurn,
 | 
			
		||||
    NoPiece,
 | 
			
		||||
    NoCapturedPiece,
 | 
			
		||||
    IllegalCastle,
 | 
			
		||||
    IllegalSquare(Square),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod castle {
 | 
			
		||||
    use chessfriend_core::{Color, Square};
 | 
			
		||||
 | 
			
		||||
    #[repr(u16)]
 | 
			
		||||
    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
    pub enum Castle {
 | 
			
		||||
        KingSide = 0b10,
 | 
			
		||||
        QueenSide = 0b11,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) struct Squares {
 | 
			
		||||
        pub king: Square,
 | 
			
		||||
        pub rook: Square,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Castle {
 | 
			
		||||
        const STARTING_SQUARES: [[Squares; 2]; 2] = [
 | 
			
		||||
            [
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::E1,
 | 
			
		||||
                    rook: Square::H1,
 | 
			
		||||
                },
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::E1,
 | 
			
		||||
                    rook: Square::A1,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            [
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::E8,
 | 
			
		||||
                    rook: Square::H8,
 | 
			
		||||
                },
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::E8,
 | 
			
		||||
                    rook: Square::A8,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const TARGET_SQUARES: [[Squares; 2]; 2] = [
 | 
			
		||||
            [
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::G1,
 | 
			
		||||
                    rook: Square::F1,
 | 
			
		||||
                },
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::C1,
 | 
			
		||||
                    rook: Square::D1,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            [
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::G8,
 | 
			
		||||
                    rook: Square::F8,
 | 
			
		||||
                },
 | 
			
		||||
                Squares {
 | 
			
		||||
                    king: Square::C8,
 | 
			
		||||
                    rook: Square::D8,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn starting_squares(&self, color: Color) -> &'static Squares {
 | 
			
		||||
            &Castle::STARTING_SQUARES[color as usize][self.into_index()]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn target_squares(&self, color: Color) -> &'static Squares {
 | 
			
		||||
            &Castle::TARGET_SQUARES[color as usize][self.into_index()]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn into_index(&self) -> usize {
 | 
			
		||||
            match self {
 | 
			
		||||
                Castle::KingSide => 0,
 | 
			
		||||
                Castle::QueenSide => 1,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(u16)]
 | 
			
		||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub enum PromotableShape {
 | 
			
		||||
    Knight = 0b00,
 | 
			
		||||
    Bishop = 0b01,
 | 
			
		||||
    Rook = 0b10,
 | 
			
		||||
    Queen = 0b11,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TryFrom<Shape> for PromotableShape {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    fn try_from(value: Shape) -> Result<Self, Self::Error> {
 | 
			
		||||
        match value {
 | 
			
		||||
            Shape::Knight => Ok(PromotableShape::Knight),
 | 
			
		||||
            Shape::Bishop => Ok(PromotableShape::Bishop),
 | 
			
		||||
            Shape::Rook => Ok(PromotableShape::Rook),
 | 
			
		||||
            Shape::Queen => Ok(PromotableShape::Queen),
 | 
			
		||||
            _ => Err(()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(u16)]
 | 
			
		||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
enum Kind {
 | 
			
		||||
    Quiet = 0b00,
 | 
			
		||||
    DoublePush = 0b01,
 | 
			
		||||
    Castle(Castle),
 | 
			
		||||
    Capture(PlacedPiece) = 0b0100,
 | 
			
		||||
    EnPassantCapture(PlacedPiece) = 0b0101,
 | 
			
		||||
    Promotion(PromotableShape) = 0b1000,
 | 
			
		||||
    CapturePromotion(PlacedPiece, PromotableShape) = 0b1100,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Kind {
 | 
			
		||||
    fn bits(&self) -> u16 {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Promotion(shape) => self.discriminant() | *shape as u16,
 | 
			
		||||
            Self::CapturePromotion(_, shape) => self.discriminant() | *shape as u16,
 | 
			
		||||
            Self::Castle(castle) => *castle as u16,
 | 
			
		||||
            _ => self.discriminant(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Return the discriminant value. This implementation is copied from the Rust docs.
 | 
			
		||||
    /// See https://doc.rust-lang.org/std/mem/fn.discriminant.html
 | 
			
		||||
    fn discriminant(&self) -> u16 {
 | 
			
		||||
        unsafe { *<*const _>::from(self).cast::<u16>() }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Kind {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::Quiet
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A single player's move. In chess parlance, this is a "ply".
 | 
			
		||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
 | 
			
		||||
pub struct Move(u16);
 | 
			
		||||
 | 
			
		||||
impl Move {
 | 
			
		||||
    pub fn from_square(&self) -> Square {
 | 
			
		||||
        ((self.0 >> 4) & 0b111111).try_into().unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn to_square(&self) -> Square {
 | 
			
		||||
        (self.0 >> 10).try_into().unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_quiet(&self) -> bool {
 | 
			
		||||
        self.flags() == Kind::Quiet.discriminant()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_double_push(&self) -> bool {
 | 
			
		||||
        self.flags() == Kind::DoublePush.discriminant()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_castle(&self) -> bool {
 | 
			
		||||
        self.castle().is_some()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn castle(&self) -> Option<Castle> {
 | 
			
		||||
        match self.flags() {
 | 
			
		||||
            0b0010 => Some(Castle::KingSide),
 | 
			
		||||
            0b0011 => Some(Castle::QueenSide),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_capture(&self) -> bool {
 | 
			
		||||
        (self.0 & 0b0100) != 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_en_passant(&self) -> bool {
 | 
			
		||||
        self.flags() == 0b0101
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_promotion(&self) -> bool {
 | 
			
		||||
        (self.0 & 0b1000) != 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn promotion(&self) -> Option<Shape> {
 | 
			
		||||
        if !self.is_promotion() {
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Some(match self.special() {
 | 
			
		||||
            0b00 => Shape::Knight,
 | 
			
		||||
            0b01 => Shape::Bishop,
 | 
			
		||||
            0b10 => Shape::Rook,
 | 
			
		||||
            0b11 => Shape::Queen,
 | 
			
		||||
            _ => unreachable!(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn flags(&self) -> u16 {
 | 
			
		||||
        self.0 & 0b1111
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn special(&self) -> u16 {
 | 
			
		||||
        self.0 & 0b11
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Debug for Move {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        f.debug_tuple("Move")
 | 
			
		||||
            .field(&format_args!("{:08b}", &self.0))
 | 
			
		||||
            .finish()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
pub struct MoveBuilder {
 | 
			
		||||
    piece: Piece,
 | 
			
		||||
    from: Square,
 | 
			
		||||
    to: Square,
 | 
			
		||||
    kind: Kind,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MoveBuilder {
 | 
			
		||||
    pub fn new(piece: Piece, from: Square, to: Square) -> Self {
 | 
			
		||||
        let kind = match piece.shape() {
 | 
			
		||||
            Shape::Pawn => {
 | 
			
		||||
                let from_rank = from.rank();
 | 
			
		||||
                let to_rank = to.rank();
 | 
			
		||||
                let is_white_double_push = from_rank == Rank::TWO && to_rank == Rank::FOUR;
 | 
			
		||||
                let is_black_double_push = from_rank == Rank::SEVEN && to_rank == Rank::FIVE;
 | 
			
		||||
                if is_white_double_push || is_black_double_push {
 | 
			
		||||
                    Kind::DoublePush
 | 
			
		||||
                } else {
 | 
			
		||||
                    Kind::Quiet
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => Kind::Quiet,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            piece,
 | 
			
		||||
            from,
 | 
			
		||||
            to,
 | 
			
		||||
            kind,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn castle(mut self, castle: Castle) -> Self {
 | 
			
		||||
        self.kind = Kind::Castle(castle);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn capturing(mut self, captured_piece: PlacedPiece) -> Self {
 | 
			
		||||
        self.kind = match self.kind {
 | 
			
		||||
            Kind::Promotion(shape) => Kind::CapturePromotion(captured_piece, shape),
 | 
			
		||||
            _ => Kind::Capture(captured_piece),
 | 
			
		||||
        };
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn capturing_en_passant(mut self, captured_piece: PlacedPiece) -> Self {
 | 
			
		||||
        self.kind = Kind::EnPassantCapture(captured_piece);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn promoting_to(mut self, shape: Shape) -> Self {
 | 
			
		||||
        if let Some(shape) = PromotableShape::try_from(shape).ok() {
 | 
			
		||||
            self.kind = match self.kind {
 | 
			
		||||
                Kind::Capture(piece) => Kind::CapturePromotion(piece, shape),
 | 
			
		||||
                Kind::CapturePromotion(piece, _) => Kind::CapturePromotion(piece, shape),
 | 
			
		||||
                _ => Kind::Promotion(shape),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn build(&self) -> Move {
 | 
			
		||||
        Move(
 | 
			
		||||
            self.kind.bits()
 | 
			
		||||
                | ((self.from as u16 & 0b111111) << 4)
 | 
			
		||||
                | ((self.to as u16 & 0b111111) << 10),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod move_formatter {
 | 
			
		||||
    use super::{Castle, Move};
 | 
			
		||||
    use crate::Position;
 | 
			
		||||
    use chessfriend_core::Shape;
 | 
			
		||||
    use std::fmt;
 | 
			
		||||
 | 
			
		||||
    enum Style {
 | 
			
		||||
        Short,
 | 
			
		||||
        Long,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for Style {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Style::Long
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) struct AlgebraicMoveFormatter<'m, 'pos> {
 | 
			
		||||
        position: &'pos Position,
 | 
			
		||||
        r#move: &'m Move,
 | 
			
		||||
        style: Style,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl<'pos, 'm> AlgebraicMoveFormatter<'m, 'pos> {
 | 
			
		||||
        pub(crate) fn new(
 | 
			
		||||
            mv: &'m Move,
 | 
			
		||||
            position: &'pos Position,
 | 
			
		||||
        ) -> AlgebraicMoveFormatter<'m, 'pos> {
 | 
			
		||||
            AlgebraicMoveFormatter {
 | 
			
		||||
                position,
 | 
			
		||||
                r#move: mv,
 | 
			
		||||
                style: Style::default(),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn style(mut self, style: Style) -> Self {
 | 
			
		||||
            self.style = style;
 | 
			
		||||
            self
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn fmt_kingside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
            write!(f, "0-0")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn fmt_queenside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
            write!(f, "0-0-0")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn fmt_short(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
            unimplemented!()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn fmt_long(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
            // TODO: Figure out how to write the short algebraic form, where a
 | 
			
		||||
            // disambiguating coordiate is specified when two of the same piece
 | 
			
		||||
            // cam move to the same square.
 | 
			
		||||
 | 
			
		||||
            // TODO: Write better pawn moves.
 | 
			
		||||
 | 
			
		||||
            let mv = self.r#move;
 | 
			
		||||
            let from_square = mv.from_square();
 | 
			
		||||
            let to_square = mv.to_square();
 | 
			
		||||
 | 
			
		||||
            let piece = self
 | 
			
		||||
                .position
 | 
			
		||||
                .piece_on_square(from_square)
 | 
			
		||||
                .expect(&format!("No piece on {}", from_square));
 | 
			
		||||
            if piece.shape() != Shape::Pawn {
 | 
			
		||||
                write!(f, "{}", piece.shape())?;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{}{}{}",
 | 
			
		||||
                from_square,
 | 
			
		||||
                if mv.is_capture() { 'x' } else { '-' },
 | 
			
		||||
                to_square,
 | 
			
		||||
            )?;
 | 
			
		||||
 | 
			
		||||
            if let Some(promotion) = mv.promotion() {
 | 
			
		||||
                write!(f, "={}", promotion)?;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // TODO: Write check (+) and checkmate (#) symbols
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl<'pos, 'mv> fmt::Display for AlgebraicMoveFormatter<'mv, 'pos> {
 | 
			
		||||
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
            let mv = self.r#move;
 | 
			
		||||
            match mv.castle() {
 | 
			
		||||
                Some(Castle::KingSide) => return self.fmt_kingside_castle(f),
 | 
			
		||||
                Some(Castle::QueenSide) => return self.fmt_queenside_castle(f),
 | 
			
		||||
                _ => {}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            match self.style {
 | 
			
		||||
                Style::Short => self.fmt_short(f),
 | 
			
		||||
                Style::Long => self.fmt_long(f),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(test)]
 | 
			
		||||
    mod tests {
 | 
			
		||||
        use super::{AlgebraicMoveFormatter, Style};
 | 
			
		||||
        use crate::position;
 | 
			
		||||
        use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
        macro_rules! chess_move {
 | 
			
		||||
            ($color:ident $shape:ident $from_square:ident - $to_square:ident) => {
 | 
			
		||||
                $crate::MoveBuilder::new(
 | 
			
		||||
                    chessfriend_core::Piece::new(
 | 
			
		||||
                        chessfriend_core::Color::$color,
 | 
			
		||||
                        chessfriend_core::Shape::$shape,
 | 
			
		||||
                    ),
 | 
			
		||||
                    chessfriend_core::Square::$from_square,
 | 
			
		||||
                    chessfriend_core::Square::$to_square,
 | 
			
		||||
                )
 | 
			
		||||
                .build()
 | 
			
		||||
            };
 | 
			
		||||
            ($color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident) => {
 | 
			
		||||
                $crate::MoveBuilder::new(
 | 
			
		||||
                    chessfriend_core::Piece::new(
 | 
			
		||||
                        chessfriend_core::Color::$color,
 | 
			
		||||
                        chessfriend_core::Shape::$shape,
 | 
			
		||||
                    ),
 | 
			
		||||
                    chessfriend_core::Square::$from_square,
 | 
			
		||||
                    chessfriend_core::Square::$to_square,
 | 
			
		||||
                )
 | 
			
		||||
                .capturing(chessfriend_core::PlacedPiece::new(
 | 
			
		||||
                    chessfriend_core::Piece::new(
 | 
			
		||||
                        chessfriend_core::Color::$captured_color,
 | 
			
		||||
                        chessfriend_core::Shape::$captured_shape,
 | 
			
		||||
                    ),
 | 
			
		||||
                    chessfriend_core::Square::$to_square,
 | 
			
		||||
                ))
 | 
			
		||||
                .build()
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        macro_rules! test_algebraic_formatter {
 | 
			
		||||
            ($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident, $output:expr) => {
 | 
			
		||||
                #[test]
 | 
			
		||||
                fn $test_name() {
 | 
			
		||||
                    let pos = position![
 | 
			
		||||
                        $color $shape on $from_square,
 | 
			
		||||
                        $captured_color $captured_shape on $to_square,
 | 
			
		||||
                    ];
 | 
			
		||||
                    let mv = chess_move!(
 | 
			
		||||
                        $color $shape $from_square x $to_square,
 | 
			
		||||
                        $captured_color $captured_shape
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    println!("{:?}", &mv);
 | 
			
		||||
 | 
			
		||||
                    let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style);
 | 
			
		||||
                    assert_eq!(format!("{}", formatter), $output);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            ($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident - $to_square:ident, $output:expr) => {
 | 
			
		||||
                #[test]
 | 
			
		||||
                fn $test_name() {
 | 
			
		||||
                    let pos = position![
 | 
			
		||||
                        $color $shape on $from_square,
 | 
			
		||||
                    ];
 | 
			
		||||
 | 
			
		||||
                    let mv = chess_move!($color $shape $from_square-$to_square);
 | 
			
		||||
                    println!("{:?}", &mv);
 | 
			
		||||
 | 
			
		||||
                    let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style);
 | 
			
		||||
                    assert_eq!(format!("{}", formatter), $output);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        test_algebraic_formatter!(long_pawn_move, Long, White Pawn E4 - E5, "e4-e5");
 | 
			
		||||
        test_algebraic_formatter!(long_bishop_move, Long, White Bishop A4 - D7, "Ba4-d7");
 | 
			
		||||
        test_algebraic_formatter!(long_bishop_capture, Long, White Bishop A2 x E6, Black Knight, "Ba2xe6");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
    macro_rules! assert_flag {
 | 
			
		||||
        ($move:expr, $left:expr, $right:expr) => {
 | 
			
		||||
            assert_eq!($left, $right, "{:?}", $move)
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_flags_quiet() {
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::A5).build();
 | 
			
		||||
        assert_flags!(mv, true, false, false, false, false, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_flags_double_push() {
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::C2, Square::C4).build();
 | 
			
		||||
        assert_flags!(mv, false, true, false, false, false, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_flags_capture() {
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::B5)
 | 
			
		||||
            .capturing(piece!(Black Bishop on B5))
 | 
			
		||||
            .build();
 | 
			
		||||
        assert_flags!(mv, false, false, false, true, false, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_flags_en_passant_capture() {
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::A5, Square::B6)
 | 
			
		||||
            .capturing_en_passant(piece!(Black Pawn on B5))
 | 
			
		||||
            .build();
 | 
			
		||||
        assert_flags!(mv, false, false, true, true, false, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_flags_promotion() {
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::H8)
 | 
			
		||||
            .promoting_to(Shape::Queen)
 | 
			
		||||
            .build();
 | 
			
		||||
        assert_flags!(mv, false, false, false, false, false, true);
 | 
			
		||||
        assert_eq!(mv.promotion(), Some(Shape::Queen));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_flags_capture_promotion() {
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::G8)
 | 
			
		||||
            .capturing(piece!(Black Knight on G8))
 | 
			
		||||
            .promoting_to(Shape::Queen)
 | 
			
		||||
            .build();
 | 
			
		||||
        assert_flags!(mv, false, false, false, true, false, true);
 | 
			
		||||
        assert_eq!(mv.promotion(), Some(Shape::Queen));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_flags_castle() {
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(Black King), Square::E8, Square::G8)
 | 
			
		||||
            .castle(Castle::KingSide)
 | 
			
		||||
            .build();
 | 
			
		||||
        assert_flags!(mv, false, false, false, false, true, false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										184
									
								
								position/src/move_generator.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								position/src/move_generator.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,184 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
mod bishop;
 | 
			
		||||
mod king;
 | 
			
		||||
mod knight;
 | 
			
		||||
mod move_set;
 | 
			
		||||
mod pawn;
 | 
			
		||||
mod queen;
 | 
			
		||||
mod rook;
 | 
			
		||||
 | 
			
		||||
pub(crate) use move_set::MoveSet;
 | 
			
		||||
 | 
			
		||||
use self::{
 | 
			
		||||
    bishop::ClassicalMoveGenerator as BishopMoveGenerator, king::KingMoveGenerator,
 | 
			
		||||
    knight::KnightMoveGenerator, pawn::PawnMoveGenerator,
 | 
			
		||||
    queen::ClassicalMoveGenerator as QueenMoveGenerator,
 | 
			
		||||
    rook::ClassicalMoveGenerator as RookMoveGenerator,
 | 
			
		||||
};
 | 
			
		||||
use crate::{Move, Position};
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
 | 
			
		||||
use std::collections::BTreeMap;
 | 
			
		||||
 | 
			
		||||
trait MoveGenerator {
 | 
			
		||||
    fn iter(&self) -> dyn Iterator<Item = Move>;
 | 
			
		||||
    fn moves(&self, color: Color) -> dyn Iterator<Item = Move>;
 | 
			
		||||
    fn attacks(&self, color: Color) -> dyn Iterator<Item = Move>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! move_generator_declaration {
 | 
			
		||||
    ($name:ident) => {
 | 
			
		||||
        move_generator_declaration!($name, struct);
 | 
			
		||||
        move_generator_declaration!($name, new);
 | 
			
		||||
        move_generator_declaration!($name, getters);
 | 
			
		||||
    };
 | 
			
		||||
    ($name:ident, struct) => {
 | 
			
		||||
        #[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
        pub(super) struct $name {
 | 
			
		||||
            color: chessfriend_core::Color,
 | 
			
		||||
            move_sets: std::collections::BTreeMap<
 | 
			
		||||
                chessfriend_core::Square,
 | 
			
		||||
                $crate::move_generator::MoveSet,
 | 
			
		||||
            >,
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    ($name:ident, new) => {
 | 
			
		||||
        impl $name {
 | 
			
		||||
            pub(super) fn new(
 | 
			
		||||
                position: &$crate::Position,
 | 
			
		||||
                color: chessfriend_core::Color,
 | 
			
		||||
            ) -> $name {
 | 
			
		||||
                $name {
 | 
			
		||||
                    color,
 | 
			
		||||
                    move_sets: Self::move_sets(position, color),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    ($name:ident, getters) => {
 | 
			
		||||
        impl $name {
 | 
			
		||||
            pub(super) fn iter(&self) -> impl Iterator<Item = &$crate::Move> + '_ {
 | 
			
		||||
                self.move_sets.values().map(|set| set.moves()).flatten()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            pub(crate) fn moves_for_piece(
 | 
			
		||||
                &self,
 | 
			
		||||
                piece: &chessfriend_core::PlacedPiece,
 | 
			
		||||
            ) -> Option<&$crate::move_generator::move_set::MoveSet> {
 | 
			
		||||
                self.move_sets.get(&piece.square())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fn bitboard(&self) -> chessfriend_bitboard::BitBoard {
 | 
			
		||||
                self.move_sets.values().fold(
 | 
			
		||||
                    chessfriend_bitboard::BitBoard::empty(),
 | 
			
		||||
                    |partial, mv_set| partial | mv_set.bitboard(),
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(self) use move_generator_declaration;
 | 
			
		||||
 | 
			
		||||
trait MoveGeneratorInternal {
 | 
			
		||||
    fn piece(color: Color) -> Piece;
 | 
			
		||||
 | 
			
		||||
    fn move_sets(position: &Position, color: Color) -> BTreeMap<Square, MoveSet> {
 | 
			
		||||
        let piece = Self::piece(color);
 | 
			
		||||
        BTreeMap::from_iter(
 | 
			
		||||
            position
 | 
			
		||||
                .bitboard_for_piece(piece)
 | 
			
		||||
                .occupied_squares()
 | 
			
		||||
                .map(|sq| {
 | 
			
		||||
                    let placed_piece = PlacedPiece::new(piece, sq);
 | 
			
		||||
                    let move_set = Self::move_set_for_piece(position, placed_piece);
 | 
			
		||||
                    (sq, move_set)
 | 
			
		||||
                }),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub struct Moves {
 | 
			
		||||
    pawn_moves: PawnMoveGenerator,
 | 
			
		||||
    knight_moves: KnightMoveGenerator,
 | 
			
		||||
    bishop_moves: BishopMoveGenerator,
 | 
			
		||||
    rook_moves: RookMoveGenerator,
 | 
			
		||||
    queen_moves: QueenMoveGenerator,
 | 
			
		||||
    king_moves: KingMoveGenerator,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Moves {
 | 
			
		||||
    pub fn new(position: &Position, color: Color) -> Moves {
 | 
			
		||||
        Moves {
 | 
			
		||||
            pawn_moves: PawnMoveGenerator::new(position, color),
 | 
			
		||||
            knight_moves: KnightMoveGenerator::new(position, color),
 | 
			
		||||
            bishop_moves: BishopMoveGenerator::new(position, color),
 | 
			
		||||
            rook_moves: RookMoveGenerator::new(position, color),
 | 
			
		||||
            queen_moves: QueenMoveGenerator::new(position, color),
 | 
			
		||||
            king_moves: KingMoveGenerator::new(position, color),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn moves_for_piece(&self, piece: &PlacedPiece) -> Option<&MoveSet> {
 | 
			
		||||
        match piece.shape() {
 | 
			
		||||
            Shape::Pawn => self.pawn_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Knight => self.knight_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Bishop => self.bishop_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Rook => self.rook_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Queen => self.queen_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::King => self.king_moves.moves_for_piece(piece),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn iter(&self) -> impl Iterator<Item = Move> + '_ {
 | 
			
		||||
        self.pawn_moves
 | 
			
		||||
            .iter()
 | 
			
		||||
            .chain(self.knight_moves.iter())
 | 
			
		||||
            .chain(self.bishop_moves.iter())
 | 
			
		||||
            .chain(self.rook_moves.iter())
 | 
			
		||||
            .chain(self.queen_moves.iter())
 | 
			
		||||
            .chain(self.king_moves.iter())
 | 
			
		||||
            .cloned()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::{position, r#move::AlgebraicMoveFormatter, Move, MoveBuilder};
 | 
			
		||||
    use chessfriend_core::{piece, Square};
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_king() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White King on D3,
 | 
			
		||||
            Black King on H6,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::D4).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::E4).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::E3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::E2).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::D2).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::C2).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::C3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::D3, Square::C4).build(),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = pos.moves().iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generated_moves,
 | 
			
		||||
            expected_moves,
 | 
			
		||||
            "{:?}",
 | 
			
		||||
            generated_moves
 | 
			
		||||
                .symmetric_difference(&expected_moves)
 | 
			
		||||
                .map(|m| format!("{}", AlgebraicMoveFormatter::new(&m, &pos)))
 | 
			
		||||
                .collect::<Vec<String>>()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								position/src/move_generator/bishop.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								position/src/move_generator/bishop.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,139 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use crate::{MoveBuilder, Position};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Direction, Piece, PlacedPiece};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(ClassicalMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for ClassicalMoveGenerator {
 | 
			
		||||
    fn piece(color: Color) -> Piece {
 | 
			
		||||
        Piece::bishop(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        let piece = placed_piece.piece();
 | 
			
		||||
        let square = placed_piece.square();
 | 
			
		||||
 | 
			
		||||
        let blockers = position.occupied_squares();
 | 
			
		||||
        let empty_squares = !blockers;
 | 
			
		||||
        let (friendly_pieces, opposing_pieces) = position.all_pieces();
 | 
			
		||||
 | 
			
		||||
        let mut all_moves = BitBoard::empty();
 | 
			
		||||
 | 
			
		||||
        macro_rules! update_moves_with_ray {
 | 
			
		||||
            ($direction:ident, $occupied_squares:tt) => {
 | 
			
		||||
                let ray = BitBoard::ray(square, Direction::$direction);
 | 
			
		||||
                if let Some(first_occupied_square) =
 | 
			
		||||
                    BitBoard::$occupied_squares(&(ray & blockers)).next()
 | 
			
		||||
                {
 | 
			
		||||
                    let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
 | 
			
		||||
                    let attack_ray = ray & !remainder;
 | 
			
		||||
                    all_moves |= attack_ray;
 | 
			
		||||
                } else {
 | 
			
		||||
                    all_moves |= ray;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        update_moves_with_ray!(NorthEast, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(NorthWest, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(SouthEast, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(SouthWest, occupied_squares);
 | 
			
		||||
 | 
			
		||||
        let quiet_moves_bb = all_moves & (empty_squares | !friendly_pieces);
 | 
			
		||||
        let capture_moves_bb = all_moves & opposing_pieces;
 | 
			
		||||
 | 
			
		||||
        let map_to_move = |sq| MoveBuilder::new(*piece, square, sq).build();
 | 
			
		||||
        let quiet_moves = quiet_moves_bb.occupied_squares().map(map_to_move);
 | 
			
		||||
        let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves_bb, quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves_bb, capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, position::DiagramFormatter};
 | 
			
		||||
    use chessfriend_bitboard::BitBoard;
 | 
			
		||||
    use chessfriend_core::Color;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_bishop_bitboard() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Bishop on A1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            BitBoard::new(
 | 
			
		||||
                0b10000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that a bishop can move up to, but not onto, a friendly piece.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_bishop_with_same_color_blocker_bitboard() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Bishop on A1,
 | 
			
		||||
            White Knight on E5,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            BitBoard::new(
 | 
			
		||||
                0b00000000_00000000_00000000_00000000_00001000_00000100_00000010_00000000
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that a rook can move up to, and then capture, an enemy piece.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_bishop_with_opposing_color_blocker_bitboard() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Bishop on A1,
 | 
			
		||||
            Black Knight on C3,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            BitBoard::new(
 | 
			
		||||
                0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_bishop_in_center() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Bishop on E4,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
        let bitboard = generator.bitboard();
 | 
			
		||||
        let expected = BitBoard::new(
 | 
			
		||||
            0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bitboard, expected,
 | 
			
		||||
            "actual:\n{}\nexpected:\n{}",
 | 
			
		||||
            bitboard, expected
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										193
									
								
								position/src/move_generator/king.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								position/src/move_generator/king.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,193 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
//! Declares the KingMoveGenerator type. This struct is responsible for
 | 
			
		||||
//! generating the possible moves for the king in the given position.
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use crate::{r#move::Castle, Move, MoveBuilder, Position};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(KingMoveGenerator, struct);
 | 
			
		||||
move_generator_declaration!(KingMoveGenerator, new);
 | 
			
		||||
move_generator_declaration!(KingMoveGenerator, getters);
 | 
			
		||||
 | 
			
		||||
impl KingMoveGenerator {
 | 
			
		||||
    #[allow(unused_variables)]
 | 
			
		||||
    fn king_side_castle(position: &Position, color: Color) -> Option<Move> {
 | 
			
		||||
        if !position.player_has_right_to_castle(color, Castle::KingSide) {
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Implement king side castle.
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(unused_variables)]
 | 
			
		||||
    fn queen_side_castle(position: &Position, color: Color) -> Option<Move> {
 | 
			
		||||
        if !position.player_has_right_to_castle(color, Castle::QueenSide) {
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Implement queen side castle.
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for KingMoveGenerator {
 | 
			
		||||
    fn piece(color: Color) -> Piece {
 | 
			
		||||
        Piece::king(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        let piece = placed_piece.piece();
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = placed_piece.square();
 | 
			
		||||
 | 
			
		||||
        let empty_squares = position.empty_squares();
 | 
			
		||||
        let opposing_pieces = position.bitboard_for_color(color.other());
 | 
			
		||||
 | 
			
		||||
        let all_moves = BitBoard::king_moves(square);
 | 
			
		||||
        let quiet_moves_bb = all_moves & empty_squares;
 | 
			
		||||
        let capture_moves_bb = all_moves & opposing_pieces;
 | 
			
		||||
 | 
			
		||||
        // TODO: Handle checks. Prevent moving a king to a square attacked by a
 | 
			
		||||
        // piece of the opposite color.
 | 
			
		||||
 | 
			
		||||
        let map_to_move = |sq| MoveBuilder::new(*piece, square, sq).build();
 | 
			
		||||
 | 
			
		||||
        let king_side_castle = Self::king_side_castle(position, color);
 | 
			
		||||
        let queen_side_castle = Self::queen_side_castle(position, color);
 | 
			
		||||
        let quiet_moves = quiet_moves_bb
 | 
			
		||||
            .occupied_squares()
 | 
			
		||||
            .map(map_to_move)
 | 
			
		||||
            .chain(king_side_castle.iter().cloned())
 | 
			
		||||
            .chain(queen_side_castle.iter().cloned());
 | 
			
		||||
        let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves_bb, quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves_bb, capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, r#move::AlgebraicMoveFormatter, PositionBuilder};
 | 
			
		||||
    use chessfriend_bitboard::bitboard;
 | 
			
		||||
    use chessfriend_core::{piece, Square};
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_king() {
 | 
			
		||||
        let pos = position![White King on E4];
 | 
			
		||||
 | 
			
		||||
        let generator = KingMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            bitboard![E5, F5, F4, F3, E3, D3, D4, D5]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let expected_moves = [
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::D5).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::E5).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::F5).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::F4).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::F3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::E3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::D3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::E4, Square::D4).build(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let mut generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        for ex_move in expected_moves {
 | 
			
		||||
            assert!(
 | 
			
		||||
                generated_moves.remove(&ex_move),
 | 
			
		||||
                "{:#?} was not generated",
 | 
			
		||||
                &ex_move
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert!(
 | 
			
		||||
            generated_moves.is_empty(),
 | 
			
		||||
            "Moves unexpectedly present: {:#?}",
 | 
			
		||||
            generated_moves
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_king_corner() {
 | 
			
		||||
        let pos = position![White King on A1];
 | 
			
		||||
 | 
			
		||||
        let generator = KingMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        let generated_bitboard = generator.bitboard();
 | 
			
		||||
        let expected_bitboard = bitboard![A2, B2, B1];
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            bitboard![A2, B2, B1],
 | 
			
		||||
            "Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let expected_moves = [
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::A1, Square::A2).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::A1, Square::B1).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White King), Square::A1, Square::B2).build(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let mut generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        for ex_move in expected_moves {
 | 
			
		||||
            assert!(
 | 
			
		||||
                generated_moves.remove(&ex_move),
 | 
			
		||||
                "{:#?} was not generated",
 | 
			
		||||
                &ex_move
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert!(
 | 
			
		||||
            generated_moves.is_empty(),
 | 
			
		||||
            "Moves unexpectedly present: {:#?}",
 | 
			
		||||
            generated_moves
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn black_king_in_check_by_rook() {
 | 
			
		||||
        let pos = PositionBuilder::new()
 | 
			
		||||
            .place_piece(piece!(White King on E1))
 | 
			
		||||
            .place_piece(piece!(White Rook on E4))
 | 
			
		||||
            .place_piece(piece!(Black King on E7))
 | 
			
		||||
            .to_move(Color::Black)
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
        assert!(pos.is_king_in_check());
 | 
			
		||||
 | 
			
		||||
        let generator = KingMoveGenerator::new(&pos, Color::Black);
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        let king = piece!(Black King);
 | 
			
		||||
        let from_square = Square::E7;
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            MoveBuilder::new(king, from_square, Square::D6).build(),
 | 
			
		||||
            MoveBuilder::new(king, from_square, Square::D7).build(),
 | 
			
		||||
            MoveBuilder::new(king, from_square, Square::D8).build(),
 | 
			
		||||
            MoveBuilder::new(king, from_square, Square::F6).build(),
 | 
			
		||||
            MoveBuilder::new(king, from_square, Square::F7).build(),
 | 
			
		||||
            MoveBuilder::new(king, from_square, Square::F8).build(),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generated_moves,
 | 
			
		||||
            expected_moves,
 | 
			
		||||
            "Difference: {:?}",
 | 
			
		||||
            generated_moves
 | 
			
		||||
                .symmetric_difference(&expected_moves)
 | 
			
		||||
                .map(|mv| format!("{}", AlgebraicMoveFormatter::new(mv, &pos)))
 | 
			
		||||
                .collect::<Vec<_>>()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								position/src/move_generator/knight.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								position/src/move_generator/knight.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,91 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use crate::{MoveBuilder, Position};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(KnightMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for KnightMoveGenerator {
 | 
			
		||||
    fn piece(color: Color) -> Piece {
 | 
			
		||||
        Piece::knight(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        let opposing_pieces = position.bitboard_for_color(placed_piece.piece().color().other());
 | 
			
		||||
        let empty_squares = position.empty_squares();
 | 
			
		||||
        let knight_moves = BitBoard::knight_moves(placed_piece.square());
 | 
			
		||||
 | 
			
		||||
        let quiet_moves_bb = knight_moves & empty_squares;
 | 
			
		||||
        let capture_moves_bb = knight_moves & opposing_pieces;
 | 
			
		||||
 | 
			
		||||
        let quiet_moves = quiet_moves_bb.occupied_squares().map(|to_sq| {
 | 
			
		||||
            MoveBuilder::new(*placed_piece.piece(), placed_piece.square(), to_sq).build()
 | 
			
		||||
        });
 | 
			
		||||
        let capture_moves = capture_moves_bb.occupied_squares().map(|to_sq| {
 | 
			
		||||
            let captured_piece = position.piece_on_square(to_sq).unwrap();
 | 
			
		||||
            MoveBuilder::new(*placed_piece.piece(), placed_piece.square(), to_sq)
 | 
			
		||||
                .capturing(captured_piece)
 | 
			
		||||
                .build()
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves_bb, quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves_bb, capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, Move};
 | 
			
		||||
    use chessfriend_core::{piece, Square};
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_knight() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Knight on E4,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator = KnightMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
        let bb = generator.bitboard();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bb,
 | 
			
		||||
            BitBoard::new(
 | 
			
		||||
                0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        */
 | 
			
		||||
 | 
			
		||||
        let expected_moves = [
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::C3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::D2).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::F2).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::G3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::C5).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::D6).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::G5).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Knight), Square::E4, Square::F6).build(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let mut generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        for ex_move in expected_moves {
 | 
			
		||||
            assert!(
 | 
			
		||||
                generated_moves.remove(&ex_move),
 | 
			
		||||
                "{:#?} was not generated",
 | 
			
		||||
                &ex_move
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert!(
 | 
			
		||||
            generated_moves.is_empty(),
 | 
			
		||||
            "Moves unexpectedly present: {:#?}",
 | 
			
		||||
            generated_moves
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								position/src/move_generator/move_set.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								position/src/move_generator/move_set.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,81 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use crate::Move;
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::PlacedPiece;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
struct BitBoardSet {
 | 
			
		||||
    quiet: BitBoard,
 | 
			
		||||
    captures: BitBoard,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
struct MoveListSet {
 | 
			
		||||
    quiet: Vec<Move>,
 | 
			
		||||
    captures: Vec<Move>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MoveListSet {
 | 
			
		||||
    pub fn contains(&self, mv: &Move) -> bool {
 | 
			
		||||
        self.quiet.contains(mv) || self.captures.contains(mv)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A set of moves for a piece on the board.
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub(crate) struct MoveSet {
 | 
			
		||||
    piece: PlacedPiece,
 | 
			
		||||
    bitboards: BitBoardSet,
 | 
			
		||||
    move_lists: MoveListSet,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MoveSet {
 | 
			
		||||
    pub(super) fn new(piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        MoveSet {
 | 
			
		||||
            piece,
 | 
			
		||||
            bitboards: BitBoardSet {
 | 
			
		||||
                quiet: BitBoard::empty(),
 | 
			
		||||
                captures: BitBoard::empty(),
 | 
			
		||||
            },
 | 
			
		||||
            move_lists: MoveListSet {
 | 
			
		||||
                quiet: Vec::new(),
 | 
			
		||||
                captures: Vec::new(),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn quiet_moves(
 | 
			
		||||
        mut self,
 | 
			
		||||
        bitboard: BitBoard,
 | 
			
		||||
        move_list: impl Iterator<Item = Move>,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        self.bitboards.quiet = bitboard;
 | 
			
		||||
        self.move_lists.quiet = move_list.collect();
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn capture_moves(
 | 
			
		||||
        mut self,
 | 
			
		||||
        bitboard: BitBoard,
 | 
			
		||||
        move_list: impl Iterator<Item = Move>,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        self.bitboards.captures = bitboard;
 | 
			
		||||
        self.move_lists.captures = move_list.collect();
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Return a BitBoard representing all possible moves.
 | 
			
		||||
    pub(super) fn bitboard(&self) -> BitBoard {
 | 
			
		||||
        self.bitboards.captures | self.bitboards.quiet
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn moves(&self) -> impl Iterator<Item = &Move> {
 | 
			
		||||
        self.move_lists
 | 
			
		||||
            .captures
 | 
			
		||||
            .iter()
 | 
			
		||||
            .chain(self.move_lists.quiet.iter())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										240
									
								
								position/src/move_generator/pawn.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								position/src/move_generator/pawn.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,240 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use crate::{Move, MoveBuilder, Position};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
 | 
			
		||||
 | 
			
		||||
enum MoveList {
 | 
			
		||||
    Quiet = 0,
 | 
			
		||||
    Promotions = 1,
 | 
			
		||||
    Captures = 2,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct MoveIterator(usize, usize);
 | 
			
		||||
 | 
			
		||||
struct MoveGenerationParameters {
 | 
			
		||||
    starting_rank: BitBoard,
 | 
			
		||||
    promotion_rank: BitBoard,
 | 
			
		||||
    push_shift: fn(&BitBoard) -> BitBoard,
 | 
			
		||||
    left_capture_shift: fn(&BitBoard) -> BitBoard,
 | 
			
		||||
    right_capture_shift: fn(&BitBoard) -> BitBoard,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(PawnMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for PawnMoveGenerator {
 | 
			
		||||
    fn piece(color: Color) -> Piece {
 | 
			
		||||
        Piece::pawn(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        let from_square = placed_piece.square();
 | 
			
		||||
        let parameters = Self::move_generation_parameters(placed_piece.color());
 | 
			
		||||
 | 
			
		||||
        let opposing_pieces = position.opposing_pieces();
 | 
			
		||||
 | 
			
		||||
        let captures_bitboard = Self::attacks(position, placed_piece, ¶meters);
 | 
			
		||||
        let quiet_moves_bitboard = Self::pushes(position, placed_piece, ¶meters);
 | 
			
		||||
 | 
			
		||||
        let quiet_moves = quiet_moves_bitboard.occupied_squares().map(|to_square| {
 | 
			
		||||
            MoveBuilder::new(*placed_piece.piece(), from_square, to_square).build()
 | 
			
		||||
        });
 | 
			
		||||
        let capture_moves = captures_bitboard.occupied_squares().map(|to_square| {
 | 
			
		||||
            let captured_piece = position.piece_on_square(to_square).unwrap();
 | 
			
		||||
            MoveBuilder::new(*placed_piece.piece(), from_square, to_square)
 | 
			
		||||
                .capturing(captured_piece)
 | 
			
		||||
                .build()
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves_bitboard, quiet_moves)
 | 
			
		||||
            .capture_moves(captures_bitboard, capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PawnMoveGenerator {
 | 
			
		||||
    fn move_generation_parameters(color: Color) -> MoveGenerationParameters {
 | 
			
		||||
        match color {
 | 
			
		||||
            Color::White => MoveGenerationParameters {
 | 
			
		||||
                starting_rank: BitBoard::rank(1),
 | 
			
		||||
                promotion_rank: BitBoard::rank(7),
 | 
			
		||||
                push_shift: BitBoard::shift_north_one,
 | 
			
		||||
                left_capture_shift: BitBoard::shift_north_west_one,
 | 
			
		||||
                right_capture_shift: BitBoard::shift_north_east_one,
 | 
			
		||||
            },
 | 
			
		||||
            Color::Black => MoveGenerationParameters {
 | 
			
		||||
                starting_rank: BitBoard::rank(6),
 | 
			
		||||
                promotion_rank: BitBoard::rank(0),
 | 
			
		||||
                push_shift: BitBoard::shift_south_one,
 | 
			
		||||
                left_capture_shift: BitBoard::shift_south_east_one,
 | 
			
		||||
                right_capture_shift: BitBoard::shift_south_west_one,
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn pushes(
 | 
			
		||||
        position: &Position,
 | 
			
		||||
        piece: PlacedPiece,
 | 
			
		||||
        parameters: &MoveGenerationParameters,
 | 
			
		||||
    ) -> BitBoard {
 | 
			
		||||
        let empty_squares = position.empty_squares();
 | 
			
		||||
        let from_square: BitBoard = piece.square().into();
 | 
			
		||||
 | 
			
		||||
        (parameters.push_shift)(&from_square) & empty_squares
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn attacks(
 | 
			
		||||
        position: &Position,
 | 
			
		||||
        piece: PlacedPiece,
 | 
			
		||||
        parameters: &MoveGenerationParameters,
 | 
			
		||||
    ) -> BitBoard {
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
 | 
			
		||||
        let opponent_pieces = position.bitboard_for_color(color.other());
 | 
			
		||||
        let en_passant_square = position
 | 
			
		||||
            .en_passant_square()
 | 
			
		||||
            .map(|square| <Square as Into<BitBoard>>::into(square))
 | 
			
		||||
            .unwrap_or(BitBoard::empty());
 | 
			
		||||
 | 
			
		||||
        let from_square: BitBoard = piece.square().into();
 | 
			
		||||
 | 
			
		||||
        ((parameters.left_capture_shift)(&from_square)
 | 
			
		||||
            | (parameters.right_capture_shift)(&from_square))
 | 
			
		||||
            & (opponent_pieces | en_passant_square)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, position::DiagramFormatter};
 | 
			
		||||
    use chessfriend_core::{piece, Square};
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_2square_push() {
 | 
			
		||||
        let pos = position![White Pawn on E2];
 | 
			
		||||
 | 
			
		||||
        let generator = PawnMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(generated_moves, expected_moves);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_1square_push() {
 | 
			
		||||
        let mut pos = position![White Pawn on E3];
 | 
			
		||||
 | 
			
		||||
        let generator = PawnMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([MoveBuilder::new(
 | 
			
		||||
            Piece::pawn(Color::White),
 | 
			
		||||
            Square::E3,
 | 
			
		||||
            Square::E4,
 | 
			
		||||
        )
 | 
			
		||||
        .build()]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(generated_moves, expected_moves);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_obstructed_2square_push() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Pawn on E2,
 | 
			
		||||
            White Knight on E4,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = PawnMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([MoveBuilder::new(
 | 
			
		||||
            Piece::pawn(Color::White),
 | 
			
		||||
            Square::E2,
 | 
			
		||||
            Square::E3,
 | 
			
		||||
        )
 | 
			
		||||
        .build()]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(generated_moves, expected_moves);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_obstructed_1square_push() {
 | 
			
		||||
        let mut pos = position![
 | 
			
		||||
            White Pawn on E2,
 | 
			
		||||
            White Knight on E3,
 | 
			
		||||
        ];
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = PawnMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(generated_moves, HashSet::new());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_attack() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Pawn on E4,
 | 
			
		||||
            White Bishop on E5,
 | 
			
		||||
            Black Knight on D5,
 | 
			
		||||
        ];
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = PawnMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        let expected_moves =
 | 
			
		||||
            HashSet::from_iter(
 | 
			
		||||
                [MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5)
 | 
			
		||||
                    .capturing(piece!(Black Knight on D5))
 | 
			
		||||
                    .build()],
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(generated_moves, expected_moves);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_double_attack() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Pawn on E4,
 | 
			
		||||
            White Bishop on E5,
 | 
			
		||||
            Black Knight on D5,
 | 
			
		||||
            Black Queen on F5,
 | 
			
		||||
        ];
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = PawnMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5)
 | 
			
		||||
                .capturing(piece!(Black Knight on D5))
 | 
			
		||||
                .build(),
 | 
			
		||||
            MoveBuilder::new(piece!(White Pawn), Square::E4, Square::F5)
 | 
			
		||||
                .capturing(piece!(Black Queen on F5))
 | 
			
		||||
                .build(),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generated_moves, expected_moves,
 | 
			
		||||
            "generated: {:#?}\nexpected: {:#?}",
 | 
			
		||||
            generated_moves, expected_moves
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										153
									
								
								position/src/move_generator/queen.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								position/src/move_generator/queen.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,153 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use crate::{MoveBuilder, Position};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Direction, Piece, PlacedPiece};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(ClassicalMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for ClassicalMoveGenerator {
 | 
			
		||||
    fn piece(color: Color) -> Piece {
 | 
			
		||||
        Piece::queen(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        let piece = placed_piece.piece();
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = placed_piece.square();
 | 
			
		||||
 | 
			
		||||
        let blockers = position.occupied_squares();
 | 
			
		||||
        let empty_squares = !blockers;
 | 
			
		||||
        let friendly_pieces = position.bitboard_for_color(color);
 | 
			
		||||
        let opposing_pieces = position.bitboard_for_color(color.other());
 | 
			
		||||
 | 
			
		||||
        let mut all_moves = BitBoard::empty();
 | 
			
		||||
 | 
			
		||||
        macro_rules! update_moves_with_ray {
 | 
			
		||||
            ($direction:ident, $occupied_squares:tt) => {
 | 
			
		||||
                let ray = BitBoard::ray(square, Direction::$direction);
 | 
			
		||||
                if let Some(first_occupied_square) =
 | 
			
		||||
                    BitBoard::$occupied_squares(&(ray & blockers)).next()
 | 
			
		||||
                {
 | 
			
		||||
                    let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
 | 
			
		||||
                    let attack_ray = ray & !remainder;
 | 
			
		||||
                    all_moves |= attack_ray;
 | 
			
		||||
                } else {
 | 
			
		||||
                    all_moves |= ray;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        update_moves_with_ray!(NorthWest, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(North, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(NorthEast, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(East, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(SouthEast, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(South, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(SouthWest, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(West, occupied_squares);
 | 
			
		||||
 | 
			
		||||
        let quiet_moves_bb = all_moves & (empty_squares | !friendly_pieces);
 | 
			
		||||
        let capture_moves_bb = all_moves & opposing_pieces;
 | 
			
		||||
 | 
			
		||||
        let map_to_move = |sq| MoveBuilder::new(*piece, square, sq).build();
 | 
			
		||||
        let quiet_moves = quiet_moves_bb.occupied_squares().map(map_to_move);
 | 
			
		||||
        let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves_bb, quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves_bb, capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, position::DiagramFormatter};
 | 
			
		||||
    use chessfriend_bitboard::{bitboard, BitBoard};
 | 
			
		||||
    use chessfriend_core::Color;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_queen_bitboard() {
 | 
			
		||||
        let pos = position![White Queen on B2];
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
        let bitboard = generator.bitboard();
 | 
			
		||||
        let expected = bitboard![
 | 
			
		||||
            A2, C2, D2, E2, F2, G2, H2, // Rank
 | 
			
		||||
            B1, B3, B4, B5, B6, B7, B8, // File
 | 
			
		||||
            A1, C3, D4, E5, F6, G7, H8, // Diagonal
 | 
			
		||||
            C1, A3 // Anti-diagonal
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bitboard, expected,
 | 
			
		||||
            "actual:\n{}\nexpected:\n{}",
 | 
			
		||||
            bitboard, expected
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that a rook can move up to, but not onto, a friendly piece.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_queen_with_same_color_blocker_bitboard() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Queen on A1,
 | 
			
		||||
            White Knight on E1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
        let bitboard = generator.bitboard();
 | 
			
		||||
        let expected = BitBoard::new(
 | 
			
		||||
            0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_00001110,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bitboard, expected,
 | 
			
		||||
            "actual:\n{}\nexpected:\n{}",
 | 
			
		||||
            bitboard, expected
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that a rook can move up to, and then capture, an enemy piece.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_queen_with_opposing_color_blocker_bitboard() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Queen on B2,
 | 
			
		||||
            Black Knight on E5,
 | 
			
		||||
        ];
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            bitboard![
 | 
			
		||||
                A2, C2, D2, E2, F2, G2, H2, // Rank
 | 
			
		||||
                B1, B3, B4, B5, B6, B7, B8, // File
 | 
			
		||||
                A1, C3, D4, E5, // Diagonal
 | 
			
		||||
                C1, A3 // Anti-diagonal
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_queen_in_center() {
 | 
			
		||||
        let pos = position![White Queen on D3];
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            bitboard![
 | 
			
		||||
                A3, B3, C3, E3, F3, G3, H3, // Rank
 | 
			
		||||
                D1, D2, D4, D5, D6, D7, D8, // File
 | 
			
		||||
                B1, C2, E4, F5, G6, H7, // Diagonal
 | 
			
		||||
                F1, E2, C4, B5, A6, // Anti-diagonal
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								position/src/move_generator/rook.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								position/src/move_generator/rook.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use crate::{MoveBuilder, Position};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Direction, Piece, PlacedPiece};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(ClassicalMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for ClassicalMoveGenerator {
 | 
			
		||||
    fn piece(color: Color) -> Piece {
 | 
			
		||||
        Piece::rook(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        let piece = placed_piece.piece();
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = placed_piece.square();
 | 
			
		||||
 | 
			
		||||
        let blockers = position.occupied_squares();
 | 
			
		||||
        let empty_squares = !blockers;
 | 
			
		||||
        let friendly_pieces = position.bitboard_for_color(color);
 | 
			
		||||
        let opposing_pieces = position.bitboard_for_color(color.other());
 | 
			
		||||
 | 
			
		||||
        let mut all_moves = BitBoard::empty();
 | 
			
		||||
 | 
			
		||||
        macro_rules! update_moves_with_ray {
 | 
			
		||||
            ($direction:ident, $occupied_squares:tt) => {
 | 
			
		||||
                let ray = BitBoard::ray(square, Direction::$direction);
 | 
			
		||||
                if let Some(first_occupied_square) =
 | 
			
		||||
                    BitBoard::$occupied_squares(&(ray & blockers)).next()
 | 
			
		||||
                {
 | 
			
		||||
                    let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
 | 
			
		||||
                    let attack_ray = ray & !remainder;
 | 
			
		||||
                    all_moves |= attack_ray;
 | 
			
		||||
                } else {
 | 
			
		||||
                    all_moves |= ray;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        update_moves_with_ray!(North, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(East, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(South, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(West, occupied_squares);
 | 
			
		||||
 | 
			
		||||
        let quiet_moves_bb = all_moves & (empty_squares | !friendly_pieces);
 | 
			
		||||
        let capture_moves_bb = all_moves & opposing_pieces;
 | 
			
		||||
 | 
			
		||||
        let map_to_move = |sq| MoveBuilder::new(*piece, square, sq).build();
 | 
			
		||||
        let quiet_moves = quiet_moves_bb.occupied_squares().map(map_to_move);
 | 
			
		||||
        let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves_bb, quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves_bb, capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, position::DiagramFormatter};
 | 
			
		||||
    use chessfriend_bitboard::{bitboard, BitBoard};
 | 
			
		||||
    use chessfriend_core::Color;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_rook_bitboard() {
 | 
			
		||||
        let pos = position![White Rook on A2];
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            bitboard![A1, A3, A4, A5, A6, A7, A8, B2, C2, D2, E2, F2, G2, H2]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that a rook can move up to, but not onto, a friendly piece.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_rook_with_same_color_blocker_bitboard() {
 | 
			
		||||
        let mut pos = position![
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
            White Knight on E1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            BitBoard::new(
 | 
			
		||||
                0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00001110
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that a rook can move up to, and then capture, an enemy piece.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_rook_with_opposing_color_blocker_bitboard() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
            Black Knight on E1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            bitboard![A2, A3, A4, A5, A6, A7, A8, B1, C1, D1, E1]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_rook_in_center() {
 | 
			
		||||
        let pos = position![White Rook on D4];
 | 
			
		||||
 | 
			
		||||
        let generator = ClassicalMoveGenerator::new(&pos, Color::White);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator.bitboard(),
 | 
			
		||||
            bitboard![A4, B4, C4, E4, F4, G4, H4, D1, D2, D3, D5, D6, D7, D8]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								position/src/position/builders/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								position/src/position/builders/mod.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
mod move_builder;
 | 
			
		||||
mod position_builder;
 | 
			
		||||
 | 
			
		||||
pub use move_builder::Builder as MoveBuilder;
 | 
			
		||||
pub use position_builder::Builder as PositionBuilder;
 | 
			
		||||
							
								
								
									
										358
									
								
								position/src/position/builders/move_builder.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								position/src/position/builders/move_builder.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,358 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    position::flags::Flags, r#move::Castle, sight::SightExt, MakeMoveError, Move, Position,
 | 
			
		||||
};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square};
 | 
			
		||||
 | 
			
		||||
/// A position builder that builds a new position by making a move.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Builder<'p, M: MoveToMake> {
 | 
			
		||||
    position: &'p Position,
 | 
			
		||||
    move_to_make: M,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait MoveToMake {}
 | 
			
		||||
 | 
			
		||||
pub struct NoMove;
 | 
			
		||||
 | 
			
		||||
pub enum ValidatedMove {
 | 
			
		||||
    RegularMove {
 | 
			
		||||
        from_square: Square,
 | 
			
		||||
        to_square: Square,
 | 
			
		||||
        moving_piece: PlacedPiece,
 | 
			
		||||
        captured_piece: Option<PlacedPiece>,
 | 
			
		||||
        promotion: Option<Shape>,
 | 
			
		||||
        flags: Flags,
 | 
			
		||||
        en_passant_square: Option<Square>,
 | 
			
		||||
        increment_ply: bool,
 | 
			
		||||
    },
 | 
			
		||||
    Castle {
 | 
			
		||||
        castle: Castle,
 | 
			
		||||
        king: PlacedPiece,
 | 
			
		||||
        rook: PlacedPiece,
 | 
			
		||||
        flags: Flags,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MoveToMake for NoMove {}
 | 
			
		||||
impl MoveToMake for ValidatedMove {}
 | 
			
		||||
 | 
			
		||||
impl<'p> Builder<'p, NoMove> {
 | 
			
		||||
    pub fn new(position: &'p Position) -> Builder<'p, NoMove> {
 | 
			
		||||
        Builder {
 | 
			
		||||
            position,
 | 
			
		||||
            move_to_make: NoMove,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'p, M> Builder<'p, M>
 | 
			
		||||
where
 | 
			
		||||
    M: MoveToMake,
 | 
			
		||||
{
 | 
			
		||||
    pub fn make(self, mv: &Move) -> Result<Builder<'p, ValidatedMove>, MakeMoveError> {
 | 
			
		||||
        let from_square = mv.from_square();
 | 
			
		||||
 | 
			
		||||
        let piece = self
 | 
			
		||||
            .position
 | 
			
		||||
            .piece_on_square(from_square)
 | 
			
		||||
            .ok_or(MakeMoveError::NoPiece)?;
 | 
			
		||||
 | 
			
		||||
        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 player = self.position.player_to_move();
 | 
			
		||||
 | 
			
		||||
        let captured_piece: Option<PlacedPiece> = if mv.is_en_passant() {
 | 
			
		||||
            // En passant captures the pawn directly ahead (in the player's direction) of the en passant square.
 | 
			
		||||
            let capture_square = match player {
 | 
			
		||||
                Color::White => to_square.neighbor(Direction::South),
 | 
			
		||||
                Color::Black => to_square.neighbor(Direction::North),
 | 
			
		||||
            }
 | 
			
		||||
            .ok_or(MakeMoveError::NoCapturedPiece)?;
 | 
			
		||||
 | 
			
		||||
            Some(
 | 
			
		||||
                self.position
 | 
			
		||||
                    .piece_on_square(capture_square)
 | 
			
		||||
                    .ok_or(MakeMoveError::NoCapturedPiece)?,
 | 
			
		||||
            )
 | 
			
		||||
        } else if mv.is_capture() {
 | 
			
		||||
            Some(
 | 
			
		||||
                self.position
 | 
			
		||||
                    .piece_on_square(to_square)
 | 
			
		||||
                    .ok_or(MakeMoveError::NoCapturedPiece)?,
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // TODO: Check whether the move is legal.
 | 
			
		||||
 | 
			
		||||
        let piece_is_king = piece.is_king();
 | 
			
		||||
        let mut flags = self.position.flags().clone();
 | 
			
		||||
 | 
			
		||||
        if piece_is_king {
 | 
			
		||||
            flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide);
 | 
			
		||||
            flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide);
 | 
			
		||||
        } else if piece.is_kingside_rook() {
 | 
			
		||||
            flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide);
 | 
			
		||||
        } else if piece.is_queenside_rook() {
 | 
			
		||||
            flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(castle) = mv.castle() {
 | 
			
		||||
            println!("piece is king: {}", piece_is_king);
 | 
			
		||||
            if !piece_is_king || !self.position.player_can_castle(player, castle) {
 | 
			
		||||
                return Err(MakeMoveError::IllegalCastle);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let rook = self
 | 
			
		||||
                .position
 | 
			
		||||
                .rook_for_castle(player, castle)
 | 
			
		||||
                .ok_or(MakeMoveError::NoPiece)?;
 | 
			
		||||
 | 
			
		||||
            Ok(Builder {
 | 
			
		||||
                position: self.position,
 | 
			
		||||
                move_to_make: ValidatedMove::Castle {
 | 
			
		||||
                    castle,
 | 
			
		||||
                    king: piece,
 | 
			
		||||
                    rook,
 | 
			
		||||
                    flags,
 | 
			
		||||
                },
 | 
			
		||||
            })
 | 
			
		||||
        } else {
 | 
			
		||||
            let en_passant_square: Option<Square> = if mv.is_double_push() {
 | 
			
		||||
                match piece.color() {
 | 
			
		||||
                    Color::White => to_square.neighbor(Direction::South),
 | 
			
		||||
                    Color::Black => to_square.neighbor(Direction::North),
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Ok(Builder {
 | 
			
		||||
                position: self.position,
 | 
			
		||||
                move_to_make: ValidatedMove::RegularMove {
 | 
			
		||||
                    from_square,
 | 
			
		||||
                    to_square,
 | 
			
		||||
                    moving_piece: piece,
 | 
			
		||||
                    captured_piece,
 | 
			
		||||
                    promotion: mv.promotion(),
 | 
			
		||||
                    flags,
 | 
			
		||||
                    en_passant_square,
 | 
			
		||||
                    increment_ply: !(mv.is_capture() || piece.is_pawn()),
 | 
			
		||||
                },
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'p> Builder<'p, ValidatedMove> {
 | 
			
		||||
    pub fn build(&self) -> Position {
 | 
			
		||||
        let player = self.position.player_to_move();
 | 
			
		||||
 | 
			
		||||
        let updated_move_number =
 | 
			
		||||
            self.position.move_number() + if player == Color::Black { 1 } else { 0 };
 | 
			
		||||
 | 
			
		||||
        match self.move_to_make {
 | 
			
		||||
            ValidatedMove::RegularMove {
 | 
			
		||||
                from_square,
 | 
			
		||||
                to_square,
 | 
			
		||||
                moving_piece,
 | 
			
		||||
                captured_piece,
 | 
			
		||||
                promotion,
 | 
			
		||||
                flags,
 | 
			
		||||
                en_passant_square,
 | 
			
		||||
                increment_ply,
 | 
			
		||||
            } => {
 | 
			
		||||
                let mut pieces = self.position.piece_bitboards().clone();
 | 
			
		||||
 | 
			
		||||
                if let Some(captured_piece) = captured_piece {
 | 
			
		||||
                    pieces.remove_piece(&captured_piece);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if let Some(promotion) = promotion {
 | 
			
		||||
                    pieces.remove_piece(&moving_piece);
 | 
			
		||||
                    let _ = pieces
 | 
			
		||||
                        .place_piece(&PlacedPiece::new(Piece::new(player, promotion), to_square));
 | 
			
		||||
                } else {
 | 
			
		||||
                    pieces.move_piece(moving_piece.piece(), from_square, to_square);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let ply = if increment_ply {
 | 
			
		||||
                    self.position.ply_counter() + 1
 | 
			
		||||
                } else {
 | 
			
		||||
                    0
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                Position::new(
 | 
			
		||||
                    self.position.player_to_move().other(),
 | 
			
		||||
                    flags,
 | 
			
		||||
                    pieces,
 | 
			
		||||
                    en_passant_square,
 | 
			
		||||
                    ply,
 | 
			
		||||
                    updated_move_number,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            ValidatedMove::Castle {
 | 
			
		||||
                castle,
 | 
			
		||||
                king,
 | 
			
		||||
                rook,
 | 
			
		||||
                flags,
 | 
			
		||||
            } => {
 | 
			
		||||
                let mut pieces = self.position.piece_bitboards().clone();
 | 
			
		||||
 | 
			
		||||
                let target_squares = castle.target_squares(player);
 | 
			
		||||
 | 
			
		||||
                let king_from: BitBoard = king.square().into();
 | 
			
		||||
                let king_to: BitBoard = target_squares.king.into();
 | 
			
		||||
                *pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to;
 | 
			
		||||
 | 
			
		||||
                let rook_from: BitBoard = rook.square().into();
 | 
			
		||||
                let rook_to: BitBoard = target_squares.rook.into();
 | 
			
		||||
                *pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to;
 | 
			
		||||
 | 
			
		||||
                *pieces.bitboard_for_color_mut(player) &=
 | 
			
		||||
                    !(king_from | rook_from) | (king_to | rook_to);
 | 
			
		||||
 | 
			
		||||
                Position::new(
 | 
			
		||||
                    player.other(),
 | 
			
		||||
                    flags,
 | 
			
		||||
                    pieces,
 | 
			
		||||
                    None,
 | 
			
		||||
                    self.position.ply_counter() + 1,
 | 
			
		||||
                    updated_move_number,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'p> From<&'p Position> for Builder<'p, NoMove> {
 | 
			
		||||
    fn from(position: &'p Position) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            position,
 | 
			
		||||
            move_to_make: NoMove,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder};
 | 
			
		||||
    use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_white_pawn_one_square() -> Result<(), MakeMoveError> {
 | 
			
		||||
        let pos = position![White Pawn on E2];
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build();
 | 
			
		||||
 | 
			
		||||
        let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
 | 
			
		||||
        println!("{}", &new_position);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            new_position.piece_on_square(Square::E3),
 | 
			
		||||
            Some(piece!(White Pawn on E3))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> {
 | 
			
		||||
        let pos = position![White Pawn on E2];
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build();
 | 
			
		||||
 | 
			
		||||
        let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
 | 
			
		||||
        println!("{}", &new_position);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            new_position.piece_on_square(Square::E4),
 | 
			
		||||
            Some(piece!(White Pawn on E4))
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(new_position.en_passant_square(), Some(Square::E3));
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn white_kingside_castle() -> Result<(), MakeMoveError> {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White King on E1,
 | 
			
		||||
            White Rook on H1,
 | 
			
		||||
            White Pawn on E2,
 | 
			
		||||
            White Pawn on F2,
 | 
			
		||||
            White Pawn on G2,
 | 
			
		||||
            White Pawn on H2
 | 
			
		||||
        ];
 | 
			
		||||
        println!("{}", &pos);
 | 
			
		||||
 | 
			
		||||
        let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1)
 | 
			
		||||
            .castle(Castle::KingSide)
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
        let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
 | 
			
		||||
        println!("{}", &new_position);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            new_position.piece_on_square(Square::G1),
 | 
			
		||||
            Some(piece!(White King on G1))
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            new_position.piece_on_square(Square::F1),
 | 
			
		||||
            Some(piece!(White Rook on F1))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn en_passant_capture() -> Result<(), MakeMoveError> {
 | 
			
		||||
        let pos = PositionBuilder::new()
 | 
			
		||||
            .place_piece(piece!(White Pawn on B5))
 | 
			
		||||
            .place_piece(piece!(Black Pawn on A7))
 | 
			
		||||
            .to_move(Color::Black)
 | 
			
		||||
            .build();
 | 
			
		||||
        println!("{pos}");
 | 
			
		||||
 | 
			
		||||
        let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build();
 | 
			
		||||
        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();
 | 
			
		||||
        println!("{en_passant_position}");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            en_passant_position.piece_on_square(Square::A5),
 | 
			
		||||
            Some(piece!(Black Pawn on A5))
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            en_passant_position.piece_on_square(Square::B5),
 | 
			
		||||
            Some(piece!(White Pawn on B5))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6)
 | 
			
		||||
            .capturing_en_passant(piece!(Black Pawn on A5))
 | 
			
		||||
            .build();
 | 
			
		||||
        let en_passant_capture = Builder::<NoMove>::new(&en_passant_position)
 | 
			
		||||
            .make(&white_pawn_capture)?
 | 
			
		||||
            .build();
 | 
			
		||||
        println!("{en_passant_capture}");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(en_passant_capture.piece_on_square(Square::A5), None);
 | 
			
		||||
        assert_eq!(en_passant_capture.piece_on_square(Square::B5), None);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            en_passant_capture.piece_on_square(Square::A6),
 | 
			
		||||
            Some(piece!(White Pawn on A6))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								position/src/position/builders/position_builder.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								position/src/position/builders/position_builder.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,140 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    position::{flags::Flags, piece_sets::PieceBitBoards},
 | 
			
		||||
    r#move::Castle,
 | 
			
		||||
    MakeMoveError, Move, Position,
 | 
			
		||||
};
 | 
			
		||||
use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square};
 | 
			
		||||
use std::collections::BTreeMap;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Builder {
 | 
			
		||||
    player_to_move: Color,
 | 
			
		||||
    flags: Flags,
 | 
			
		||||
    pieces: BTreeMap<Square, Piece>,
 | 
			
		||||
    kings: [Square; 2],
 | 
			
		||||
    ply_counter: u16,
 | 
			
		||||
    move_number: u16,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Builder {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self::default()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn from_position(position: &Position) -> Self {
 | 
			
		||||
        let pieces = BTreeMap::from_iter(
 | 
			
		||||
            position
 | 
			
		||||
                .pieces(Color::White)
 | 
			
		||||
                .chain(position.pieces(Color::Black))
 | 
			
		||||
                .map(|placed_piece| (placed_piece.square(), *placed_piece.piece())),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let white_king = position.king_square(Color::White);
 | 
			
		||||
        let black_king = position.king_square(Color::Black);
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            player_to_move: position.player_to_move(),
 | 
			
		||||
            flags: position.flags(),
 | 
			
		||||
            pieces,
 | 
			
		||||
            kings: [white_king, black_king],
 | 
			
		||||
            ply_counter: position.ply_counter(),
 | 
			
		||||
            move_number: position.move_number(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn to_move(&mut self, player: Color) -> &mut Self {
 | 
			
		||||
        self.player_to_move = player;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn ply_counter(&mut self, num: u16) -> &mut Self {
 | 
			
		||||
        self.ply_counter = num;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn move_number(&mut self, num: u16) -> &mut Self {
 | 
			
		||||
        self.move_number = num;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn place_piece(&mut self, piece: PlacedPiece) -> &mut Self {
 | 
			
		||||
        let square = piece.square();
 | 
			
		||||
 | 
			
		||||
        if piece.shape() == Shape::King {
 | 
			
		||||
            let color_index: usize = piece.color() as usize;
 | 
			
		||||
            self.pieces.remove(&self.kings[color_index]);
 | 
			
		||||
            self.kings[color_index] = square;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.pieces.insert(square, *piece.piece());
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn build(&self) -> Position {
 | 
			
		||||
        let pieces = PieceBitBoards::from_iter(
 | 
			
		||||
            self.pieces
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(PlacedPiece::from)
 | 
			
		||||
                .filter(Self::is_piece_placement_valid),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Position::new(
 | 
			
		||||
            self.player_to_move,
 | 
			
		||||
            self.flags,
 | 
			
		||||
            pieces,
 | 
			
		||||
            None,
 | 
			
		||||
            self.ply_counter,
 | 
			
		||||
            self.move_number,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Builder {
 | 
			
		||||
    fn is_piece_placement_valid(piece: &PlacedPiece) -> bool {
 | 
			
		||||
        if piece.shape() == Shape::Pawn {
 | 
			
		||||
            // Pawns cannot be placed on the first (back) rank of their side,
 | 
			
		||||
            // and cannot be placed on the final rank without a promotion.
 | 
			
		||||
            let rank = piece.square().rank();
 | 
			
		||||
            return rank != Rank::ONE && rank != Rank::EIGHT;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Builder {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        let white_king_square = Square::E1;
 | 
			
		||||
        let black_king_square = Square::E8;
 | 
			
		||||
 | 
			
		||||
        let pieces = BTreeMap::from_iter([
 | 
			
		||||
            (white_king_square, piece!(White King)),
 | 
			
		||||
            (black_king_square, piece!(Black King)),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            player_to_move: Color::White,
 | 
			
		||||
            flags: Flags::default(),
 | 
			
		||||
            pieces: pieces,
 | 
			
		||||
            kings: [white_king_square, black_king_square],
 | 
			
		||||
            ply_counter: 0,
 | 
			
		||||
            move_number: 1,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::PositionBuilder;
 | 
			
		||||
    use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn place_piece() {
 | 
			
		||||
        let piece = piece!(White Queen on E4);
 | 
			
		||||
        let builder = PositionBuilder::new().place_piece(piece).build();
 | 
			
		||||
        assert_eq!(builder.piece_on_square(piece.square()), Some(piece));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								position/src/position/diagram_formatter.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								position/src/position/diagram_formatter.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use crate::Position;
 | 
			
		||||
use chessfriend_core::{File, Rank, Square};
 | 
			
		||||
use std::fmt;
 | 
			
		||||
 | 
			
		||||
pub struct DiagramFormatter<'a>(&'a Position);
 | 
			
		||||
 | 
			
		||||
impl<'a> DiagramFormatter<'a> {
 | 
			
		||||
    pub fn new(position: &'a Position) -> DiagramFormatter {
 | 
			
		||||
        DiagramFormatter(position)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> fmt::Display for DiagramFormatter<'a> {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        write!(f, "  +-----------------+\n")?;
 | 
			
		||||
 | 
			
		||||
        for rank in Rank::ALL.iter().rev() {
 | 
			
		||||
            write!(f, "{rank} | ")?;
 | 
			
		||||
 | 
			
		||||
            for file in File::ALL.iter() {
 | 
			
		||||
                let square = Square::from_file_rank(*file, *rank);
 | 
			
		||||
                match self.0.piece_on_square(square) {
 | 
			
		||||
                    Some(placed_piece) => write!(f, "{} ", placed_piece.piece())?,
 | 
			
		||||
                    None => write!(f, ". ")?,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            write!(f, "|\n")?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        write!(f, "  +-----------------+\n")?;
 | 
			
		||||
        write!(f, "    a b c d e f g h\n")?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{position, Position};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[ignore]
 | 
			
		||||
    fn empty() {
 | 
			
		||||
        let pos = Position::empty();
 | 
			
		||||
        let diagram = DiagramFormatter(&pos);
 | 
			
		||||
        println!("{}", diagram);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[ignore]
 | 
			
		||||
    fn one_king() {
 | 
			
		||||
        let pos = position![Black King on H3];
 | 
			
		||||
        let diagram = DiagramFormatter(&pos);
 | 
			
		||||
        println!("{}", diagram);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[ignore]
 | 
			
		||||
    fn starting() {
 | 
			
		||||
        let pos = Position::starting();
 | 
			
		||||
        let diagram = DiagramFormatter(&pos);
 | 
			
		||||
        println!("{}", diagram);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								position/src/position/flags.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								position/src/position/flags.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use crate::r#move::Castle;
 | 
			
		||||
use chessfriend_core::Color;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
 | 
			
		||||
pub(super) struct Flags(u8);
 | 
			
		||||
 | 
			
		||||
impl Flags {
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub(super) fn player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize {
 | 
			
		||||
        ((color as usize) << 1) + castle.into_index()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool {
 | 
			
		||||
        (self.0 & (1 << Self::player_has_right_to_castle_flag_offset(color, castle))) != 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn set_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) {
 | 
			
		||||
        self.0 |= 1 << Self::player_has_right_to_castle_flag_offset(color, castle);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn clear_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) {
 | 
			
		||||
        self.0 &= !(1 << Self::player_has_right_to_castle_flag_offset(color, castle));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Debug for Flags {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        write!(f, "Flags({:08b})", self.0)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Flags {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Flags(0b00001111)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::r#move::Castle;
 | 
			
		||||
    use chessfriend_core::Color;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn castle_flags() {
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            Flags::player_has_right_to_castle_flag_offset(Color::White, Castle::KingSide),
 | 
			
		||||
            0
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            Flags::player_has_right_to_castle_flag_offset(Color::White, Castle::QueenSide),
 | 
			
		||||
            1
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            Flags::player_has_right_to_castle_flag_offset(Color::Black, Castle::KingSide),
 | 
			
		||||
            2
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            Flags::player_has_right_to_castle_flag_offset(Color::Black, Castle::QueenSide),
 | 
			
		||||
            3
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn defaults() {
 | 
			
		||||
        let mut flags: Flags = Default::default();
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::White, Castle::KingSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::White, Castle::QueenSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::Black, Castle::KingSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::Black, Castle::QueenSide));
 | 
			
		||||
 | 
			
		||||
        flags.clear_player_has_right_to_castle_flag(Color::White, Castle::QueenSide);
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::White, Castle::KingSide));
 | 
			
		||||
        assert!(!flags.player_has_right_to_castle(Color::White, Castle::QueenSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::Black, Castle::KingSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::Black, Castle::QueenSide));
 | 
			
		||||
 | 
			
		||||
        flags.set_player_has_right_to_castle_flag(Color::White, Castle::QueenSide);
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::White, Castle::KingSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::White, Castle::QueenSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::Black, Castle::KingSide));
 | 
			
		||||
        assert!(flags.player_has_right_to_castle(Color::Black, Castle::QueenSide));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								position/src/position/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								position/src/position/mod.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
pub mod piece_sets;
 | 
			
		||||
 | 
			
		||||
mod builders;
 | 
			
		||||
mod diagram_formatter;
 | 
			
		||||
mod flags;
 | 
			
		||||
mod pieces;
 | 
			
		||||
mod position;
 | 
			
		||||
 | 
			
		||||
pub use {
 | 
			
		||||
    builders::{MoveBuilder, PositionBuilder},
 | 
			
		||||
    diagram_formatter::DiagramFormatter,
 | 
			
		||||
    pieces::Pieces,
 | 
			
		||||
    position::Position,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										181
									
								
								position/src/position/piece_sets.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								position/src/position/piece_sets.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,181 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Eq, PartialEq)]
 | 
			
		||||
pub enum PlacePieceStrategy {
 | 
			
		||||
    Replace,
 | 
			
		||||
    PreserveExisting,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Eq, PartialEq)]
 | 
			
		||||
pub enum PlacePieceError {
 | 
			
		||||
    ExisitingPiece,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for PlacePieceStrategy {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::Replace
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
 | 
			
		||||
pub(crate) struct PieceBitBoards {
 | 
			
		||||
    by_color: ByColor,
 | 
			
		||||
    by_color_and_shape: ByColorAndShape,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
 | 
			
		||||
struct ByColor(BitBoard, [BitBoard; 2]);
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
 | 
			
		||||
struct ByColorAndShape([[BitBoard; 6]; 2]);
 | 
			
		||||
 | 
			
		||||
impl PieceBitBoards {
 | 
			
		||||
    pub(super) fn new(pieces: [[BitBoard; 6]; 2]) -> Self {
 | 
			
		||||
        use std::ops::BitOr;
 | 
			
		||||
 | 
			
		||||
        let white_pieces = pieces[Color::White as usize]
 | 
			
		||||
            .iter()
 | 
			
		||||
            .fold(BitBoard::empty(), BitOr::bitor);
 | 
			
		||||
        let black_pieces = pieces[Color::Black as usize]
 | 
			
		||||
            .iter()
 | 
			
		||||
            .fold(BitBoard::empty(), BitOr::bitor);
 | 
			
		||||
 | 
			
		||||
        let all_pieces = white_pieces | black_pieces;
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            by_color: ByColor(all_pieces, [white_pieces, black_pieces]),
 | 
			
		||||
            by_color_and_shape: ByColorAndShape(pieces),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn king(&self, color: Color) -> &BitBoard {
 | 
			
		||||
        self.by_color_and_shape
 | 
			
		||||
            .bitboard_for_piece(&Piece::new(color, Shape::King))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn all_pieces(&self) -> &BitBoard {
 | 
			
		||||
        self.by_color.all()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn empty_squares(&self) -> BitBoard {
 | 
			
		||||
        !self.by_color.all()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn all_pieces_of_color(&self, color: Color) -> &BitBoard {
 | 
			
		||||
        self.by_color.bitboard(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn bitboard_for_color(&self, color: Color) -> &BitBoard {
 | 
			
		||||
        self.by_color.bitboard(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn bitboard_for_color_mut(&mut self, color: Color) -> &mut BitBoard {
 | 
			
		||||
        self.by_color.bitboard_mut(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn bitboard_for_piece(&self, piece: &Piece) -> &BitBoard {
 | 
			
		||||
        self.by_color_and_shape.bitboard_for_piece(piece)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn bitboard_for_piece_mut(&mut self, piece: &Piece) -> &mut BitBoard {
 | 
			
		||||
        self.by_color_and_shape.bitboard_for_piece_mut(piece)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn place_piece(&mut self, piece: &PlacedPiece) -> Result<(), PlacePieceError> {
 | 
			
		||||
        self.place_piece_with_strategy(piece, Default::default())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn place_piece_with_strategy(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        piece: &PlacedPiece,
 | 
			
		||||
        strategy: PlacePieceStrategy,
 | 
			
		||||
    ) -> Result<(), PlacePieceError> {
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = piece.square();
 | 
			
		||||
 | 
			
		||||
        if strategy == PlacePieceStrategy::PreserveExisting
 | 
			
		||||
            && self.by_color.bitboard(color).is_set(piece.square())
 | 
			
		||||
        {
 | 
			
		||||
            return Err(PlacePieceError::ExisitingPiece);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.by_color_and_shape.set_square(square, piece.piece());
 | 
			
		||||
        self.by_color.set_square(square, color);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn remove_piece(&mut self, piece: &PlacedPiece) {
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = piece.square();
 | 
			
		||||
 | 
			
		||||
        self.by_color_and_shape.clear_square(square, piece.piece());
 | 
			
		||||
        self.by_color.clear_square(square, color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn move_piece(&mut self, piece: &Piece, from_square: Square, to_square: Square) {
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
 | 
			
		||||
        self.by_color_and_shape.clear_square(from_square, piece);
 | 
			
		||||
        self.by_color.clear_square(from_square, color);
 | 
			
		||||
        self.by_color_and_shape.set_square(to_square, piece);
 | 
			
		||||
        self.by_color.set_square(to_square, color);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromIterator<PlacedPiece> for PieceBitBoards {
 | 
			
		||||
    fn from_iter<T: IntoIterator<Item = PlacedPiece>>(iter: T) -> Self {
 | 
			
		||||
        let mut pieces: Self = Default::default();
 | 
			
		||||
 | 
			
		||||
        for piece in iter {
 | 
			
		||||
            let _ = pieces.place_piece(&piece);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pieces
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ByColor {
 | 
			
		||||
    fn all(&self) -> &BitBoard {
 | 
			
		||||
        &self.0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn bitboard(&self, color: Color) -> &BitBoard {
 | 
			
		||||
        &self.1[color as usize]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn bitboard_mut(&mut self, color: Color) -> &mut BitBoard {
 | 
			
		||||
        &mut self.1[color as usize]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_square(&mut self, square: Square, color: Color) {
 | 
			
		||||
        self.0.set_square(square);
 | 
			
		||||
        self.1[color as usize].set_square(square)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clear_square(&mut self, square: Square, color: Color) {
 | 
			
		||||
        self.0.clear_square(square);
 | 
			
		||||
        self.1[color as usize].clear_square(square);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ByColorAndShape {
 | 
			
		||||
    fn bitboard_for_piece(&self, piece: &Piece) -> &BitBoard {
 | 
			
		||||
        &self.0[piece.color() as usize][piece.shape() as usize]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn bitboard_for_piece_mut(&mut self, piece: &Piece) -> &mut BitBoard {
 | 
			
		||||
        &mut self.0[piece.color() as usize][piece.shape() as usize]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_square(&mut self, square: Square, piece: &Piece) {
 | 
			
		||||
        self.bitboard_for_piece_mut(piece).set_square(square);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clear_square(&mut self, square: Square, piece: &Piece) {
 | 
			
		||||
        self.bitboard_for_piece_mut(piece).clear_square(square);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										130
									
								
								position/src/position/pieces.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								position/src/position/pieces.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,130 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::Position;
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
 | 
			
		||||
 | 
			
		||||
pub struct Pieces<'a> {
 | 
			
		||||
    color: Color,
 | 
			
		||||
    position: &'a Position,
 | 
			
		||||
 | 
			
		||||
    current_shape: Option<Shape>,
 | 
			
		||||
 | 
			
		||||
    shape_iterator: Box<dyn Iterator<Item = &'static Shape>>,
 | 
			
		||||
    square_iterator: Option<Box<dyn Iterator<Item = Square>>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Pieces<'a> {
 | 
			
		||||
    pub(crate) fn new(position: &Position, color: Color) -> Pieces {
 | 
			
		||||
        Pieces {
 | 
			
		||||
            color,
 | 
			
		||||
            position,
 | 
			
		||||
            current_shape: None,
 | 
			
		||||
            shape_iterator: Box::new(Shape::iter()),
 | 
			
		||||
            square_iterator: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Iterator for Pieces<'a> {
 | 
			
		||||
    type Item = PlacedPiece;
 | 
			
		||||
 | 
			
		||||
    fn next(&mut self) -> Option<Self::Item> {
 | 
			
		||||
        if let Some(square_iterator) = &mut self.square_iterator {
 | 
			
		||||
            if let (Some(square), Some(shape)) = (square_iterator.next(), self.current_shape) {
 | 
			
		||||
                return Some(PlacedPiece::new(Piece::new(self.color, shape), square));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut current_shape: Option<Shape> = None;
 | 
			
		||||
        let mut next_nonempty_bitboard: Option<&BitBoard> = None;
 | 
			
		||||
 | 
			
		||||
        while let Some(shape) = self.shape_iterator.next() {
 | 
			
		||||
            let piece = Piece::new(self.color, *shape);
 | 
			
		||||
 | 
			
		||||
            let bitboard = self.position.bitboard_for_piece(piece);
 | 
			
		||||
            if bitboard.is_empty() {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            next_nonempty_bitboard = Some(bitboard);
 | 
			
		||||
            current_shape = Some(*shape);
 | 
			
		||||
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let (Some(bitboard), Some(shape)) = (next_nonempty_bitboard, current_shape) {
 | 
			
		||||
            let mut square_iterator = bitboard.occupied_squares();
 | 
			
		||||
 | 
			
		||||
            let mut next_placed_piece: Option<PlacedPiece> = None;
 | 
			
		||||
            if let Some(square) = square_iterator.next() {
 | 
			
		||||
                next_placed_piece = Some(PlacedPiece::new(Piece::new(self.color, shape), square));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            self.square_iterator = Some(Box::new(square_iterator));
 | 
			
		||||
            self.current_shape = Some(shape);
 | 
			
		||||
 | 
			
		||||
            return next_placed_piece;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::{Position, PositionBuilder};
 | 
			
		||||
    use chessfriend_core::{piece, Color};
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn empty() {
 | 
			
		||||
        let pos = Position::empty();
 | 
			
		||||
        let mut pieces = pos.pieces(Color::White);
 | 
			
		||||
        assert_eq!(pieces.next(), None);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one() {
 | 
			
		||||
        let pos = PositionBuilder::new()
 | 
			
		||||
            .place_piece(piece!(White Queen on E4))
 | 
			
		||||
            .build();
 | 
			
		||||
        println!("{:#?}", &pos);
 | 
			
		||||
 | 
			
		||||
        let mut pieces = pos.pieces(Color::White);
 | 
			
		||||
        assert_eq!(pieces.next(), Some(piece!(White Queen on E4)));
 | 
			
		||||
        assert_eq!(pieces.next(), Some(piece!(White King on E1)));
 | 
			
		||||
        assert_eq!(pieces.next(), None);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn multiple_pieces() {
 | 
			
		||||
        let pos = PositionBuilder::new()
 | 
			
		||||
            .place_piece(piece!(White Queen on E4))
 | 
			
		||||
            .place_piece(piece!(White King on A1))
 | 
			
		||||
            .place_piece(piece!(White Pawn on B2))
 | 
			
		||||
            .place_piece(piece!(White Pawn on C2))
 | 
			
		||||
            .build();
 | 
			
		||||
        println!("{}", crate::position::DiagramFormatter::new(&pos));
 | 
			
		||||
 | 
			
		||||
        let expected_placed_pieces = HashSet::from([
 | 
			
		||||
            piece!(White Queen on E4),
 | 
			
		||||
            piece!(White King on A1),
 | 
			
		||||
            piece!(White Pawn on B2),
 | 
			
		||||
            piece!(White Pawn on C2),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let placed_pieces = HashSet::from_iter(pos.pieces(Color::White));
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            placed_pieces,
 | 
			
		||||
            expected_placed_pieces,
 | 
			
		||||
            "{:#?}",
 | 
			
		||||
            placed_pieces
 | 
			
		||||
                .symmetric_difference(&expected_placed_pieces)
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .map(|pp| format!("{}", pp))
 | 
			
		||||
                .collect::<Vec<String>>()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										402
									
								
								position/src/position/position.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								position/src/position/position.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,402 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{flags::Flags, piece_sets::PieceBitBoards, Pieces};
 | 
			
		||||
use crate::{
 | 
			
		||||
    move_generator::{MoveSet, Moves},
 | 
			
		||||
    position::DiagramFormatter,
 | 
			
		||||
    r#move::Castle,
 | 
			
		||||
    sight::SightExt,
 | 
			
		||||
    Move,
 | 
			
		||||
};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
 | 
			
		||||
use std::{cell::OnceCell, fmt};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq)]
 | 
			
		||||
pub struct Position {
 | 
			
		||||
    color_to_move: Color,
 | 
			
		||||
    flags: Flags,
 | 
			
		||||
    pieces: PieceBitBoards,
 | 
			
		||||
    en_passant_square: Option<Square>,
 | 
			
		||||
    sight: [OnceCell<BitBoard>; 2],
 | 
			
		||||
    moves: OnceCell<Moves>,
 | 
			
		||||
    half_move_counter: u16,
 | 
			
		||||
    full_move_number: u16,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Position {
 | 
			
		||||
    pub fn empty() -> Position {
 | 
			
		||||
        Position {
 | 
			
		||||
            color_to_move: Color::White,
 | 
			
		||||
            flags: Default::default(),
 | 
			
		||||
            pieces: PieceBitBoards::default(),
 | 
			
		||||
            en_passant_square: None,
 | 
			
		||||
            sight: [OnceCell::new(), OnceCell::new()],
 | 
			
		||||
            moves: OnceCell::new(),
 | 
			
		||||
            half_move_counter: 0,
 | 
			
		||||
            full_move_number: 1,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Return a starting position.
 | 
			
		||||
    pub fn starting() -> Self {
 | 
			
		||||
        let black_pieces = [
 | 
			
		||||
            BitBoard::new(0b0000000011111111 << 48),
 | 
			
		||||
            BitBoard::new(0b0100001000000000 << 48),
 | 
			
		||||
            BitBoard::new(0b0010010000000000 << 48),
 | 
			
		||||
            BitBoard::new(0b1000000100000000 << 48),
 | 
			
		||||
            BitBoard::new(0b0000100000000000 << 48),
 | 
			
		||||
            BitBoard::new(0b0001000000000000 << 48),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let white_pieces = [
 | 
			
		||||
            BitBoard::new(0b1111111100000000),
 | 
			
		||||
            BitBoard::new(0b0000000001000010),
 | 
			
		||||
            BitBoard::new(0b0000000000100100),
 | 
			
		||||
            BitBoard::new(0b0000000010000001),
 | 
			
		||||
            BitBoard::new(0b0000000000001000),
 | 
			
		||||
            BitBoard::new(0b0000000000010000),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            color_to_move: Color::White,
 | 
			
		||||
            flags: Flags::default(),
 | 
			
		||||
            pieces: PieceBitBoards::new([white_pieces, black_pieces]),
 | 
			
		||||
            en_passant_square: None,
 | 
			
		||||
            sight: [OnceCell::new(), OnceCell::new()],
 | 
			
		||||
            moves: OnceCell::new(),
 | 
			
		||||
            half_move_counter: 0,
 | 
			
		||||
            full_move_number: 1,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn player_to_move(&self) -> Color {
 | 
			
		||||
        self.color_to_move
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn move_number(&self) -> u16 {
 | 
			
		||||
        self.full_move_number
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn ply_counter(&self) -> u16 {
 | 
			
		||||
        self.half_move_counter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns true if the player has the right to castle on the given side of
 | 
			
		||||
    /// the board.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The right to castle on a particular side of the board is retained as
 | 
			
		||||
    /// long as the player has not moved their king, or the rook on that side of
 | 
			
		||||
    /// the board.
 | 
			
		||||
    pub(crate) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool {
 | 
			
		||||
        self.flags.player_has_right_to_castle(color, castle)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns `true` if the player is able to castle on the given side of the board.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The following requirements must be met:
 | 
			
		||||
    ///
 | 
			
		||||
    /// 1. The player must still have the right to castle on that side of the
 | 
			
		||||
    ///    board. The king and rook involved in the castle must not have moved.
 | 
			
		||||
    /// 1. The spaces between the king and rook must be clear
 | 
			
		||||
    /// 2. The king must not be in check
 | 
			
		||||
    /// 3. In the course of castling on that side, the king must not pass
 | 
			
		||||
    ///    through a square that an enemy piece can see
 | 
			
		||||
    pub(crate) fn player_can_castle(&self, player: Color, castle: Castle) -> bool {
 | 
			
		||||
        if !self.player_has_right_to_castle(player, castle.into()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Perform a real check that the player can castle.
 | 
			
		||||
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Return a PlacedPiece representing the rook to use for a castling move.
 | 
			
		||||
    pub(crate) fn rook_for_castle(&self, player: Color, castle: Castle) -> Option<PlacedPiece> {
 | 
			
		||||
        let square = match (player, castle) {
 | 
			
		||||
            (Color::White, Castle::KingSide) => Square::H1,
 | 
			
		||||
            (Color::White, Castle::QueenSide) => Square::A1,
 | 
			
		||||
            (Color::Black, Castle::KingSide) => Square::H8,
 | 
			
		||||
            (Color::Black, Castle::QueenSide) => Square::A8,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.piece_on_square(square)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn moves(&self) -> &Moves {
 | 
			
		||||
        self.moves
 | 
			
		||||
            .get_or_init(|| Moves::new(self, self.color_to_move))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Return a BitBoard representing the set of squares containing a piece.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub(crate) fn occupied_squares(&self) -> &BitBoard {
 | 
			
		||||
        &self.pieces.all_pieces()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub(crate) fn friendly_pieces(&self) -> &BitBoard {
 | 
			
		||||
        self.pieces.all_pieces_of_color(self.color_to_move)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub(crate) fn opposing_pieces(&self) -> &BitBoard {
 | 
			
		||||
        self.pieces.all_pieces_of_color(self.color_to_move.other())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn all_pieces(&self) -> (&BitBoard, &BitBoard) {
 | 
			
		||||
        (self.friendly_pieces(), self.opposing_pieces())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Return a BitBoard representing the set of squares containing a piece.
 | 
			
		||||
    /// This set is the inverse of `occupied_squares`.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub(crate) fn empty_squares(&self) -> BitBoard {
 | 
			
		||||
        !self.occupied_squares()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn piece_on_square(&self, sq: Square) -> Option<PlacedPiece> {
 | 
			
		||||
        for color in Color::iter() {
 | 
			
		||||
            for shape in Shape::iter() {
 | 
			
		||||
                let piece = Piece::new(color, *shape);
 | 
			
		||||
                if self.pieces.bitboard_for_piece(&piece).is_set(sq) {
 | 
			
		||||
                    return Some(PlacedPiece::new(piece, sq));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn pieces(&self, color: Color) -> Pieces {
 | 
			
		||||
        Pieces::new(&self, color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn en_passant_square(&self) -> Option<Square> {
 | 
			
		||||
        self.en_passant_square
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// A bitboard representing the squares the pieces of the given color can
 | 
			
		||||
    /// see. This is synonymous with the squares attacked by the player's
 | 
			
		||||
    /// pieces.
 | 
			
		||||
    pub(crate) fn sight_of_player(&self, color: Color) -> BitBoard {
 | 
			
		||||
        *self.sight[color as usize].get_or_init(|| self._sight_of_player(color, &self.pieces))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard {
 | 
			
		||||
        let en_passant_square = self.en_passant_square;
 | 
			
		||||
 | 
			
		||||
        Shape::ALL
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter_map(|&shape| {
 | 
			
		||||
                let piece = Piece::new(player, shape);
 | 
			
		||||
                let bitboard = pieces.bitboard_for_piece(&piece);
 | 
			
		||||
                if !bitboard.is_empty() {
 | 
			
		||||
                    Some((piece, bitboard))
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .flat_map(|(piece, &bitboard)| {
 | 
			
		||||
                bitboard.occupied_squares().map(move |square| {
 | 
			
		||||
                    PlacedPiece::new(piece, square).sight(pieces, en_passant_square)
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
            .fold(BitBoard::empty(), |acc, sight| acc | sight)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard {
 | 
			
		||||
        piece.sight(&self.pieces, self.en_passant_square)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// A bitboard representing the squares where a king of the given color will
 | 
			
		||||
    /// be in danger. The king cannot move to these squares.
 | 
			
		||||
    pub(crate) fn king_danger(&self) -> BitBoard {
 | 
			
		||||
        let pieces_without_king = {
 | 
			
		||||
            let mut cloned_pieces = self.pieces.clone();
 | 
			
		||||
            let placed_king = PlacedPiece::new(
 | 
			
		||||
                Piece::king(self.color_to_move),
 | 
			
		||||
                self.king_square(self.color_to_move),
 | 
			
		||||
            );
 | 
			
		||||
            cloned_pieces.remove_piece(&placed_king);
 | 
			
		||||
 | 
			
		||||
            cloned_pieces
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self._sight_of_player(self.color_to_move.other(), &pieces_without_king)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn is_king_in_check(&self) -> bool {
 | 
			
		||||
        let sight_of_opposing_player = self.sight_of_player(self.color_to_move.other());
 | 
			
		||||
        sight_of_opposing_player.is_set(self.king_square(self.color_to_move))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn king_square(&self, player: Color) -> Square {
 | 
			
		||||
        self.pieces
 | 
			
		||||
            .bitboard_for_piece(&Piece::king(player))
 | 
			
		||||
            .occupied_squares()
 | 
			
		||||
            .next()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn move_is_legal(&self, mv: Move) -> bool {
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// crate::position methods
 | 
			
		||||
impl Position {
 | 
			
		||||
    pub(super) fn new(
 | 
			
		||||
        player_to_move: Color,
 | 
			
		||||
        flags: Flags,
 | 
			
		||||
        pieces: PieceBitBoards,
 | 
			
		||||
        en_passant_square: Option<Square>,
 | 
			
		||||
        half_move_counter: u16,
 | 
			
		||||
        full_move_number: u16,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            color_to_move: player_to_move,
 | 
			
		||||
            flags,
 | 
			
		||||
            en_passant_square,
 | 
			
		||||
            pieces,
 | 
			
		||||
            sight: [OnceCell::new(), OnceCell::new()],
 | 
			
		||||
            moves: OnceCell::new(),
 | 
			
		||||
            half_move_counter: 0,
 | 
			
		||||
            full_move_number: 1,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn flags(&self) -> Flags {
 | 
			
		||||
        self.flags
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn piece_bitboards(&self) -> &PieceBitBoards {
 | 
			
		||||
        &self.pieces
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// crate methods
 | 
			
		||||
impl Position {
 | 
			
		||||
    pub(crate) fn bitboard_for_color(&self, color: Color) -> &BitBoard {
 | 
			
		||||
        self.pieces.bitboard_for_color(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> &BitBoard {
 | 
			
		||||
        self.pieces.bitboard_for_piece(&piece)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
impl Position {
 | 
			
		||||
    pub(crate) fn test_set_en_passant_square(&mut self, square: Square) {
 | 
			
		||||
        self.en_passant_square = Some(square);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Position {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::empty()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialEq for Position {
 | 
			
		||||
    fn eq(&self, other: &Self) -> bool {
 | 
			
		||||
        self.pieces == other.pieces
 | 
			
		||||
            && self.color_to_move == other.color_to_move
 | 
			
		||||
            && self.flags == other.flags
 | 
			
		||||
            && self.en_passant_square == other.en_passant_square
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for Position {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        write!(f, "{}", DiagramFormatter::new(self))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::{position, test_position, Castle, Position, PositionBuilder};
 | 
			
		||||
    use chessfriend_bitboard::bitboard;
 | 
			
		||||
    use chessfriend_core::{piece, Color, Square};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn piece_on_square() {
 | 
			
		||||
        let pos = test_position![
 | 
			
		||||
            Black Bishop on F7,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let piece = pos.piece_on_square(Square::F7);
 | 
			
		||||
        assert_eq!(piece, Some(piece!(Black Bishop on F7)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn piece_in_starting_position() {
 | 
			
		||||
        let pos = test_position!(starting);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            pos.piece_on_square(Square::H1),
 | 
			
		||||
            Some(piece!(White Rook on H1))
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            pos.piece_on_square(Square::A8),
 | 
			
		||||
            Some(piece!(Black Rook on A8))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn king_is_in_check() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White King on E1,
 | 
			
		||||
            Black Rook on E8,
 | 
			
		||||
        ];
 | 
			
		||||
        assert!(pos.is_king_in_check());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn king_is_not_in_check() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White King on F1,
 | 
			
		||||
            Black Rook on E8,
 | 
			
		||||
        ];
 | 
			
		||||
        assert!(!pos.is_king_in_check());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn rook_for_castle() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White King on E1,
 | 
			
		||||
            White Rook on H1,
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            pos.rook_for_castle(Color::White, Castle::KingSide),
 | 
			
		||||
            Some(piece!(White Rook on H1))
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            pos.rook_for_castle(Color::White, Castle::QueenSide),
 | 
			
		||||
            Some(piece!(White Rook on A1))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn danger_squares() {
 | 
			
		||||
        let pos = PositionBuilder::new()
 | 
			
		||||
            .place_piece(piece!(White King on E1))
 | 
			
		||||
            .place_piece(piece!(Black King on E7))
 | 
			
		||||
            .place_piece(piece!(White Rook on E4))
 | 
			
		||||
            .to_move(Color::Black)
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
        let danger_squares = pos.king_danger();
 | 
			
		||||
        let expected =
 | 
			
		||||
            bitboard![D1, F1, D2, E2, F2, E3, A4, B4, C4, D4, F4, G4, H4, E5, E6, E7, E8];
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            danger_squares, expected,
 | 
			
		||||
            "Actual:\n{}\n\nExpected:\n{}",
 | 
			
		||||
            danger_squares, expected
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										319
									
								
								position/src/sight.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								position/src/sight.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,319 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use crate::position::piece_sets::PieceBitBoards;
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_core::{Color, Direction, PlacedPiece, Shape, Square};
 | 
			
		||||
 | 
			
		||||
pub(crate) trait SightExt {
 | 
			
		||||
    fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> BitBoard;
 | 
			
		||||
 | 
			
		||||
    fn white_pawn_sight(
 | 
			
		||||
        &self,
 | 
			
		||||
        pieces: &PieceBitBoards,
 | 
			
		||||
        en_passant_square: Option<Square>,
 | 
			
		||||
    ) -> BitBoard;
 | 
			
		||||
 | 
			
		||||
    fn black_pawn_sight(
 | 
			
		||||
        &self,
 | 
			
		||||
        pieces: &PieceBitBoards,
 | 
			
		||||
        en_passant_square: Option<Square>,
 | 
			
		||||
    ) -> BitBoard;
 | 
			
		||||
 | 
			
		||||
    fn knight_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
 | 
			
		||||
    fn bishop_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
 | 
			
		||||
    fn rook_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
 | 
			
		||||
    fn queen_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
 | 
			
		||||
    fn king_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SightExt for PlacedPiece {
 | 
			
		||||
    fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> BitBoard {
 | 
			
		||||
        match self.shape() {
 | 
			
		||||
            Shape::Pawn => match self.color() {
 | 
			
		||||
                Color::White => self.white_pawn_sight(pieces, en_passant_square),
 | 
			
		||||
                Color::Black => self.black_pawn_sight(pieces, en_passant_square),
 | 
			
		||||
            },
 | 
			
		||||
            Shape::Knight => self.knight_sight(pieces),
 | 
			
		||||
            Shape::Bishop => self.bishop_sight(pieces),
 | 
			
		||||
            Shape::Rook => self.rook_sight(pieces),
 | 
			
		||||
            Shape::Queen => self.queen_sight(pieces),
 | 
			
		||||
            Shape::King => self.king_sight(pieces),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn white_pawn_sight(
 | 
			
		||||
        &self,
 | 
			
		||||
        pieces: &PieceBitBoards,
 | 
			
		||||
        en_passant_square: Option<Square>,
 | 
			
		||||
    ) -> BitBoard {
 | 
			
		||||
        let opponent = self.color().other();
 | 
			
		||||
        let pawn: BitBoard = self.square().into();
 | 
			
		||||
        let pawn = pawn.shift_north_west_one() | pawn.shift_north_east_one();
 | 
			
		||||
 | 
			
		||||
        let mut possible_squares = pieces.empty_squares() | pieces.all_pieces_of_color(opponent);
 | 
			
		||||
        if let Some(en_passant) = en_passant_square {
 | 
			
		||||
            let en_passant_bitboard: BitBoard = en_passant.into();
 | 
			
		||||
            possible_squares |= en_passant_bitboard;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pawn & possible_squares
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn black_pawn_sight(
 | 
			
		||||
        &self,
 | 
			
		||||
        pieces: &PieceBitBoards,
 | 
			
		||||
        en_passant_square: Option<Square>,
 | 
			
		||||
    ) -> BitBoard {
 | 
			
		||||
        let opponent = self.color().other();
 | 
			
		||||
 | 
			
		||||
        let pawn: BitBoard = self.square().into();
 | 
			
		||||
        let pawn = pawn.shift_south_west_one() | pawn.shift_south_east_one();
 | 
			
		||||
 | 
			
		||||
        let mut possible_squares = pieces.empty_squares() | pieces.all_pieces_of_color(opponent);
 | 
			
		||||
        if let Some(en_passant) = en_passant_square {
 | 
			
		||||
            possible_squares |= &en_passant.into();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pawn & possible_squares
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn knight_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
 | 
			
		||||
        BitBoard::knight_moves(self.square()) & !pieces.all_pieces_of_color(self.color())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn bishop_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
 | 
			
		||||
        let square = self.square();
 | 
			
		||||
 | 
			
		||||
        let mut sight = BitBoard::empty();
 | 
			
		||||
 | 
			
		||||
        let blockers = pieces.all_pieces();
 | 
			
		||||
 | 
			
		||||
        macro_rules! update_moves_with_ray {
 | 
			
		||||
            ($direction:ident, $occupied_squares:tt) => {
 | 
			
		||||
                let ray = BitBoard::ray(square, Direction::$direction);
 | 
			
		||||
                if let Some(first_occupied_square) =
 | 
			
		||||
                    BitBoard::$occupied_squares(&(ray & blockers)).next()
 | 
			
		||||
                {
 | 
			
		||||
                    let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
 | 
			
		||||
                    let attack_ray = ray & !remainder;
 | 
			
		||||
                    sight |= attack_ray;
 | 
			
		||||
                } else {
 | 
			
		||||
                    sight |= ray;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        update_moves_with_ray!(NorthEast, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(NorthWest, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(SouthEast, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(SouthWest, occupied_squares);
 | 
			
		||||
 | 
			
		||||
        let friendly_pieces = pieces.all_pieces_of_color(self.color());
 | 
			
		||||
        sight & !friendly_pieces
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn rook_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
 | 
			
		||||
        let square = self.square();
 | 
			
		||||
 | 
			
		||||
        let mut sight = BitBoard::empty();
 | 
			
		||||
 | 
			
		||||
        let blockers = pieces.all_pieces();
 | 
			
		||||
 | 
			
		||||
        macro_rules! update_moves_with_ray {
 | 
			
		||||
            ($direction:ident, $occupied_squares:tt) => {
 | 
			
		||||
                let ray = BitBoard::ray(square, Direction::$direction);
 | 
			
		||||
                if let Some(first_occupied_square) =
 | 
			
		||||
                    BitBoard::$occupied_squares(&(ray & blockers)).next()
 | 
			
		||||
                {
 | 
			
		||||
                    let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
 | 
			
		||||
                    let attack_ray = ray & !remainder;
 | 
			
		||||
                    sight |= attack_ray;
 | 
			
		||||
                } else {
 | 
			
		||||
                    sight |= ray;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        update_moves_with_ray!(North, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(East, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(South, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(West, occupied_squares);
 | 
			
		||||
 | 
			
		||||
        let friendly_pieces = pieces.all_pieces_of_color(self.color());
 | 
			
		||||
        sight & !friendly_pieces
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn queen_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
 | 
			
		||||
        let square = self.square();
 | 
			
		||||
 | 
			
		||||
        let mut sight = BitBoard::empty();
 | 
			
		||||
 | 
			
		||||
        let blockers = pieces.all_pieces();
 | 
			
		||||
 | 
			
		||||
        macro_rules! update_moves_with_ray {
 | 
			
		||||
            ($direction:ident, $occupied_squares:tt) => {
 | 
			
		||||
                let ray = BitBoard::ray(square, Direction::$direction);
 | 
			
		||||
                if let Some(first_occupied_square) =
 | 
			
		||||
                    BitBoard::$occupied_squares(&(ray & blockers)).next()
 | 
			
		||||
                {
 | 
			
		||||
                    let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
 | 
			
		||||
                    let attack_ray = ray & !remainder;
 | 
			
		||||
                    sight |= attack_ray;
 | 
			
		||||
                } else {
 | 
			
		||||
                    sight |= ray;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        update_moves_with_ray!(NorthWest, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(North, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(NorthEast, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(East, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(SouthEast, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(South, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(SouthWest, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(West, occupied_squares);
 | 
			
		||||
 | 
			
		||||
        let friendly_pieces = pieces.all_pieces_of_color(self.color());
 | 
			
		||||
        sight & !friendly_pieces
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn king_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
 | 
			
		||||
        BitBoard::king_moves(self.square()) & !pieces.all_pieces_of_color(self.color())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    macro_rules! sight_test {
 | 
			
		||||
        ($test_name:ident, $position:expr, $piece:expr, $bitboard:expr) => {
 | 
			
		||||
            #[test]
 | 
			
		||||
            fn $test_name() {
 | 
			
		||||
                let pos = $position;
 | 
			
		||||
                let piece = $piece;
 | 
			
		||||
                let sight = pos.sight_of_piece(&piece);
 | 
			
		||||
 | 
			
		||||
                assert_eq!(sight, $bitboard);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        ($test_name:ident, $piece:expr, $bitboard:expr) => {
 | 
			
		||||
            sight_test! {$test_name, $crate::Position::empty(), $piece, $bitboard}
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod pawn {
 | 
			
		||||
        use crate::test_position;
 | 
			
		||||
        use chessfriend_bitboard::{bitboard, BitBoard};
 | 
			
		||||
        use chessfriend_core::{piece, Square};
 | 
			
		||||
 | 
			
		||||
        sight_test!(e4_pawn, piece!(White Pawn on E4), bitboard!(D5, F5));
 | 
			
		||||
 | 
			
		||||
        sight_test!(
 | 
			
		||||
            e4_pawn_one_blocker,
 | 
			
		||||
            test_position![
 | 
			
		||||
                White Bishop on D5,
 | 
			
		||||
                White Pawn on E4,
 | 
			
		||||
            ],
 | 
			
		||||
            piece!(White Pawn on E4),
 | 
			
		||||
            bitboard!(F5)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn e4_pawn_two_blocker() {
 | 
			
		||||
            let pos = test_position!(
 | 
			
		||||
                White Bishop on D5,
 | 
			
		||||
                White Queen on F5,
 | 
			
		||||
                White Pawn on E4,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            let piece = piece!(White Pawn on E4);
 | 
			
		||||
            let sight = pos.sight_of_piece(&piece);
 | 
			
		||||
 | 
			
		||||
            assert_eq!(sight, BitBoard::empty());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn e4_pawn_capturable() {
 | 
			
		||||
            let pos = test_position!(
 | 
			
		||||
                Black Bishop on D5,
 | 
			
		||||
                White Queen on F5,
 | 
			
		||||
                White Pawn on E4,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            let piece = piece!(White Pawn on E4);
 | 
			
		||||
            let sight = pos.sight_of_piece(&piece);
 | 
			
		||||
 | 
			
		||||
            assert_eq!(sight, bitboard!(D5));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn e5_en_passant() {
 | 
			
		||||
            let mut pos = test_position!(
 | 
			
		||||
                White Pawn on E5,
 | 
			
		||||
                Black Pawn on D5,
 | 
			
		||||
            );
 | 
			
		||||
            pos.test_set_en_passant_square(Square::D6);
 | 
			
		||||
            let piece = piece!(White Pawn on E5);
 | 
			
		||||
            let sight = pos.sight_of_piece(&piece);
 | 
			
		||||
 | 
			
		||||
            assert_eq!(sight, bitboard!(D6, F6));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[macro_use]
 | 
			
		||||
    mod knight {
 | 
			
		||||
        use chessfriend_bitboard::bitboard;
 | 
			
		||||
        use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
        sight_test!(
 | 
			
		||||
            f6_knight,
 | 
			
		||||
            piece!(Black Knight on F6),
 | 
			
		||||
            bitboard!(H7, G8, E8, D7, D5, E4, G4, H5)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod bishop {
 | 
			
		||||
        use chessfriend_bitboard::bitboard;
 | 
			
		||||
        use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
        sight_test!(
 | 
			
		||||
            c2_bishop,
 | 
			
		||||
            piece!(Black Bishop on C2),
 | 
			
		||||
            bitboard!(D1, B3, A4, B1, D3, E4, F5, G6, H7)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod rook {
 | 
			
		||||
        use crate::test_position;
 | 
			
		||||
        use chessfriend_bitboard::bitboard;
 | 
			
		||||
        use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
        sight_test!(
 | 
			
		||||
            g3_rook,
 | 
			
		||||
            piece!(White Rook on G3),
 | 
			
		||||
            bitboard!(G1, G2, G4, G5, G6, G7, G8, A3, B3, C3, D3, E3, F3, H3)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        sight_test!(
 | 
			
		||||
            e4_rook_with_e1_white_king_e7_black_king,
 | 
			
		||||
            test_position![
 | 
			
		||||
                White Rook on E4,
 | 
			
		||||
                White King on E1,
 | 
			
		||||
                Black King on E7,
 | 
			
		||||
            ],
 | 
			
		||||
            piece!(White Rook on E4),
 | 
			
		||||
            bitboard!(A4, B4, C4, D4, F4, G4, H4, E2, E3, E5, E6, E7)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod king {
 | 
			
		||||
        use chessfriend_bitboard::bitboard;
 | 
			
		||||
        use chessfriend_core::piece;
 | 
			
		||||
 | 
			
		||||
        sight_test!(
 | 
			
		||||
            e1_king,
 | 
			
		||||
            piece!(White King on E1),
 | 
			
		||||
            bitboard![D1, D2, E2, F2, F1]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue