use crate::{bitboard::{Bitboard, serialize_bitboard}, moves::Move, attacks::Attacks}; #[derive(Debug)] pub struct Board { pub pieces: [Bitboard; 12], pub occupancy: Bitboard, pub ply: u16, attacks: Attacks, } #[derive(Debug, num_enum::FromPrimitive)] #[repr(usize)] pub enum PieceType { #[default] Pawn, Knight, Bishop, Rook, Queen, King, PawnBlack, KnightBlack, BishopBlack, RookBlack, QueenBlack, KingBlack, } const PIECE_CHARS: [&str; 12] = [ "♟︎", "♞", "♝", "♜", "♛", "♚", "♙", "♘", "♗", "♖", "♕", "♔", ]; #[allow(unused)] impl Board { #[allow(non_snake_case)] pub fn from_FEN(fen: String) -> Self { let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let mut rank = 7; let mut file = 0i32; for character in fen.chars() { let index = rank * 8 + file; let position = 1 << index.clamp(0, 63); if character.is_numeric() { let digit = match character.to_digit(10) { None => todo!("What to do here?"), Some(digit) => digit, }; if digit > 0 && digit <= 8 { file += digit as i32; } } else { match character { 'P' => pieces[PieceType::Pawn as usize] |= position, 'N' => pieces[PieceType::Knight as usize] |= position, 'B' => pieces[PieceType::Bishop as usize] |= position, 'R' => pieces[PieceType::Rook as usize] |= position, 'Q' => pieces[PieceType::Queen as usize] |= position, 'K' => pieces[PieceType::King as usize] |= position, 'p' => pieces[PieceType::PawnBlack as usize] |= position, 'n' => pieces[PieceType::KnightBlack as usize] |= position, 'b' => pieces[PieceType::BishopBlack as usize] |= position, 'r' => pieces[PieceType::RookBlack as usize] |= position, 'q' => pieces[PieceType::QueenBlack as usize] |= position, 'k' => pieces[PieceType::KingBlack as usize] |= position, '/' => { rank -= 1; file = -1; // So it becomes 0 }, ' ' => { break }, // TODO: break for now, parse everything else later '-' => {}, // TODO 'w' => {}, // TODO _ => todo!("Unexpected character!"), } file += 1; } } let mut board = Self { pieces, occupancy: 0, ply: 0, attacks: Attacks::new() }; board.update_occupancy(); board } fn update_occupancy(&mut self) { self.occupancy = 0; // TODO: reduce for piece in self.pieces { self.occupancy |= piece; } } fn empty(&self) -> Bitboard { !self.occupancy } fn pieces_by_color(&self, color: Color) -> [Bitboard; 6] { let mut pieces = [0; 6]; for piece_type in 0..6 { pieces[piece_type] = self.pieces[piece_type + 6 * color as usize]; } pieces } fn color_occupancy(&self, color: Color) -> Bitboard { let mut occupancy = 0; for piece in self.pieces_by_color(color) { occupancy |= piece; } occupancy } pub fn print(&self) { println!(); for rank in (0..8).rev() { print!("{}|", rank + 1); for file in 0..8 { let index = rank * 8 + file; let position: Bitboard = 1 << index; let mut found = false; for (piece_type, piece_bitboard) in self.pieces.iter().enumerate() { if (piece_bitboard & position) > 0 { found = true; print!("{} ", PIECE_CHARS[piece_type]); } } if !found { print!(". "); } } println!(); } println!(" a b c d e f g h"); } pub fn generate_moves(&self, color: Color) -> Vec { let mut moves = Vec::with_capacity(1024); let available_targets = self.color_occupancy(Color::from(1 - color as u8)) | self.empty(); for (piece_type, piece) in self.pieces_by_color(color).iter().enumerate() { match PieceType::from(piece_type) { PieceType::Pawn => { for from in serialize_bitboard(*piece) { for to in serialize_bitboard(self.attacks.pawn_pushes[color as usize][from as usize] & available_targets) { moves.push(Move { from, to }); }; } } PieceType::King => { for from in serialize_bitboard(*piece) { for to in serialize_bitboard(self.attacks.king[from as usize] & available_targets) { moves.push(Move { from, to }); }; } } PieceType::Knight => { for from in serialize_bitboard(*piece) { for to in serialize_bitboard(self.attacks.knight[from as usize] & available_targets) { moves.push(Move { from, to }); }; } } PieceType::Bishop => { for from in serialize_bitboard(*piece) { for to in serialize_bitboard(self.attacks.bishop(self.occupancy, from) & available_targets) { moves.push(Move { from, to }); }; } } PieceType::Rook => { for from in serialize_bitboard(*piece) { for to in serialize_bitboard(self.attacks.rook(self.occupancy, from) & available_targets) { moves.push(Move { from, to }); }; } } PieceType::Queen => { for from in serialize_bitboard(*piece) { for to in serialize_bitboard(self.attacks.queen(self.occupancy, from) & available_targets) { moves.push(Move { from, to }); }; } } _ => todo!("Incorrect piece type") } } moves } } #[derive(Clone, Copy, PartialEq, num_enum::FromPrimitive)] #[repr(u8)] pub enum Color { #[default] White, Black, } /// Aliases to board square indexes #[allow(dead_code)] #[derive(Debug)] pub enum Square { A1, B1, C1, D1, E1, F1, G1, H1, A2, B2, C2, D2, E2, F2, G2, H2, A3, B3, C3, D3, E3, F3, G3, H3, A4, B4, C4, D4, E4, F4, G4, H4, A5, B5, C5, D5, E5, F5, G5, H5, A6, B6, C6, D6, E6, F6, G6, H6, A7, B7, C7, D7, E7, F7, G7, H7, A8, B8, C8, D8, E8, F8, G8, H8, } #[cfg(test)] mod tests { use super::*; use crate::bitboard::{pop_count, bitscan, print}; const DEFAULT_FEN: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; #[test] fn test_square_enum() { assert_eq!(Square::A1 as u8, 0); assert_eq!(Square::F1 as u8, 5); assert_eq!(Square::H8 as u8, 63); } #[test] fn test_from_fen() { let fen = String::from(DEFAULT_FEN); let board = Board::from_FEN(fen); board.print(); print(board.empty(), "Empty squares"); assert_eq!(pop_count(board.pieces[PieceType::Pawn as usize]), 8); assert_eq!(pop_count(board.pieces[PieceType::Knight as usize]), 2); assert_eq!(pop_count(board.pieces[PieceType::Bishop as usize]), 2); assert_eq!(pop_count(board.pieces[PieceType::Rook as usize]), 2); assert_eq!(pop_count(board.pieces[PieceType::Queen as usize]), 1); assert_eq!(pop_count(board.pieces[PieceType::King as usize]), 1); assert_eq!(pop_count(board.pieces[PieceType::PawnBlack as usize]), 8); assert_eq!(pop_count(board.pieces[PieceType::KnightBlack as usize]), 2); assert_eq!(pop_count(board.pieces[PieceType::BishopBlack as usize]), 2); assert_eq!(pop_count(board.pieces[PieceType::RookBlack as usize]), 2); assert_eq!(pop_count(board.pieces[PieceType::QueenBlack as usize]), 1); assert_eq!(pop_count(board.pieces[PieceType::KingBlack as usize]), 1); assert_eq!(bitscan(board.pieces[PieceType::King as usize]), Square::E1 as u8); assert_eq!(bitscan(board.pieces[PieceType::QueenBlack as usize]), Square::D8 as u8); assert_eq!(pop_count(board.occupancy), 32); assert_eq!(pop_count(board.empty()), 32); assert_eq!(pop_count(board.color_occupancy(Color::White)), 16); assert_eq!(pop_count(board.color_occupancy(Color::Black)), 16); } #[test] fn test_generate_moves_starting_position() { let fen = String::from(DEFAULT_FEN); let board = Board::from_FEN(fen); let moves = board.generate_moves(Color::White); let black_moves = board.generate_moves(Color::Black); assert_eq!(moves.len(), 20); assert_eq!(black_moves.len(), 20); for mov in moves { mov.print(); } } }