From e7578dd0f01594fe279cfcb8c6e16e8de8725c4a Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 13 Nov 2025 15:47:32 +0100 Subject: [PATCH] cleanup and restructure --- src/board.rs | 499 +++++-------------------------------------------- src/display.rs | 195 +++++++++++++++++++ src/lib.rs | 4 +- src/move.rs | 58 +----- src/parsing.rs | 237 +++++++++++++++++++++++ 5 files changed, 487 insertions(+), 506 deletions(-) create mode 100644 src/display.rs create mode 100644 src/parsing.rs diff --git a/src/board.rs b/src/board.rs index 6018337..1d7b4f3 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,6 +1,5 @@ use crate::r#move::*; -use crate::square::{Square, SQUARES}; -use std::mem; +use crate::square::Square; use std::ops::Not; pub const CASTLING_WK_FLAG: u8 = 1; @@ -80,418 +79,29 @@ pub struct Board { } impl Board { - /// Creates a new Board instance from a FEN string. - /// Assumes the FEN string is valid. - pub fn from_fen(fen: &str) -> Self { - let mut parts = fen.split_whitespace(); - - // Initialisiere die Arrays - let mut pieces = [[0u64; 2]; 6]; - let mut occupied = [0u64; 2]; - let mut pieces_on_squares = [None; 64]; // <-- ADDED - - // Part 1: Piece placement - let placement = parts.next().unwrap_or(""); - let mut rank = 7; - let mut file = 0; - - for c in placement.chars() { - if c.is_digit(10) { - file += c.to_digit(10).unwrap_or(0) as usize; - } else if c == '/' { - rank -= 1; - file = 0; - } else if c.is_alphabetic() { - let sq = (rank * 8 + file) as u8; - let mask = 1u64 << sq; - - if c.is_uppercase() { - let color_idx = Color::White as usize; - occupied[color_idx] |= mask; - match c { - 'P' => { - pieces[PieceType::Pawn as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Pawn); // <-- ADDED - } - 'N' => { - pieces[PieceType::Knight as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Knight); // <-- ADDED - } - 'B' => { - pieces[PieceType::Bishop as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Bishop); // <-- ADDED - } - 'R' => { - pieces[PieceType::Rook as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Rook); // <-- ADDED - } - 'Q' => { - pieces[PieceType::Queen as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Queen); // <-- ADDED - } - 'K' => { - pieces[PieceType::King as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::King); // <-- ADDED - } - _ => {} - } - } else { - let color_idx = Color::Black as usize; - occupied[color_idx] |= mask; - match c { - 'p' => { - pieces[PieceType::Pawn as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Pawn); // <-- ADDED - } - 'n' => { - pieces[PieceType::Knight as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Knight); // <-- ADDED - } - 'b' => { - pieces[PieceType::Bishop as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Bishop); // <-- ADDED - } - 'r' => { - pieces[PieceType::Rook as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Rook); // <-- ADDED - } - 'q' => { - pieces[PieceType::Queen as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::Queen); // <-- ADDED - } - 'k' => { - pieces[PieceType::King as usize][color_idx] |= mask; - pieces_on_squares[sq as usize] = Some(PieceType::King); // <-- ADDED - } - _ => {} - } - } - file += 1; - } - } - - // Part 2: Active color - let side_to_move = match parts.next().unwrap_or("w") { - "b" => Color::Black, - _ => Color::White, - }; - - // Part 3: Castling rights - let mut castling_rights = 0u8; - if let Some(castle_str) = parts.next() { - if castle_str.contains('K') { - castling_rights |= CASTLING_WK_FLAG; - } - if castle_str.contains('Q') { - castling_rights |= CASTLING_WQ_FLAG; - } - if castle_str.contains('k') { - castling_rights |= CASTLING_BK_FLAG; - } - if castle_str.contains('q') { - castling_rights |= CASTLING_BQ_FLAG; - } - } - - // Part 4: En passant target - let en_passant_target = match parts.next().unwrap_or("-") { - "-" => None, - sq_str => { - let chars: Vec = sq_str.chars().collect(); - let file = (chars[0] as u8 - b'a') as u8; - let rank = (chars[1] as u8 - b'1') as u8; - let sq_index = rank * 8 + file; - // This is unsafe, but assumes the FEN is valid - Some(unsafe { mem::transmute::(sq_index) }) - } - }; - - // Part 5: Halfmove clock - let halfmove_clock = parts.next().unwrap_or("0").parse::().unwrap_or(0); - - // Part 6: Fullmove number - let fullmove_number = parts.next().unwrap_or("1").parse::().unwrap_or(1); - - let all_occupied = occupied[Color::White as usize] | occupied[Color::Black as usize]; - let empty_squares = !all_occupied; - - Board { - side_to_move, - pieces, - pieces_on_squares, // <-- ADDED - occupied, - all_occupied, - empty_squares, - castling_rights, - en_passant_target, - halfmove_clock, - fullmove_number, - } - } - - /// Converts the current board state into a FEN string. - pub fn to_fen(&self) -> String { - let mut fen = String::with_capacity(90); - - // Part 1: Piece placement - let mut empty_count = 0; - for rank in (0..=7).rev() { - for file in 0..=7 { - let sq = (rank * 8 + file) as u8; - let mask = 1u64 << sq; - - if let Some(piece) = self.get_piece_at(mask) { - if empty_count > 0 { - fen.push((b'0' + empty_count) as char); - empty_count = 0; - } - fen.push(piece); - } else { - empty_count += 1; - } - } - if empty_count > 0 { - fen.push((b'0' + empty_count) as char); - empty_count = 0; - } - if rank > 0 { - fen.push('/'); - } - } - - // Part 2: Active color - fen.push(' '); - fen.push(if self.side_to_move == Color::White { - 'w' - } else { - 'b' - }); - - // Part 3: Castling rights - fen.push(' '); - let mut castle_str = String::new(); - if (self.castling_rights & CASTLING_WK_FLAG) != 0 { - castle_str.push('K'); - } - if (self.castling_rights & CASTLING_WQ_FLAG) != 0 { - castle_str.push('Q'); - } - if (self.castling_rights & CASTLING_BK_FLAG) != 0 { - castle_str.push('k'); - } - if (self.castling_rights & CASTLING_BQ_FLAG) != 0 { - castle_str.push('q'); - } - - if castle_str.is_empty() { - fen.push('-'); - } else { - fen.push_str(&castle_str); - } - - // Part 4: En passant target - fen.push(' '); - if let Some(sq) = self.en_passant_target { - let sq_index = sq as u8; - let file = (sq_index % 8) as u8; - let rank = (sq_index / 8) as u8; - fen.push((b'a' + file) as char); - fen.push((b'1' + rank) as char); - } else { - fen.push('-'); - } - - // Part 5: Halfmove clock - fen.push(' '); - fen.push_str(&self.halfmove_clock.to_string()); - - // Part 6: Fullmove number - fen.push(' '); - fen.push_str(&self.fullmove_number.to_string()); - - fen - } - - /// Helper function to find which piece (as a char) is on a given square mask. - fn get_piece_at(&self, sq_mask: u64) -> Option { - let white = Color::White as usize; - let black = Color::Black as usize; - - if (self.pieces[PieceType::Pawn as usize][white] & sq_mask) != 0 { - return Some('P'); - } - if (self.pieces[PieceType::Pawn as usize][black] & sq_mask) != 0 { - return Some('p'); - } - if (self.pieces[PieceType::Knight as usize][white] & sq_mask) != 0 { - return Some('N'); - } - if (self.pieces[PieceType::Knight as usize][black] & sq_mask) != 0 { - return Some('n'); - } - if (self.pieces[PieceType::Bishop as usize][white] & sq_mask) != 0 { - return Some('B'); - } - if (self.pieces[PieceType::Bishop as usize][black] & sq_mask) != 0 { - return Some('b'); - } - if (self.pieces[PieceType::Rook as usize][white] & sq_mask) != 0 { - return Some('R'); - } - if (self.pieces[PieceType::Rook as usize][black] & sq_mask) != 0 { - return Some('r'); - } - if (self.pieces[PieceType::Queen as usize][white] & sq_mask) != 0 { - return Some('Q'); - } - if (self.pieces[PieceType::Queen as usize][black] & sq_mask) != 0 { - return Some('q'); - } - if (self.pieces[PieceType::King as usize][white] & sq_mask) != 0 { - return Some('K'); - } - if (self.pieces[PieceType::King as usize][black] & sq_mask) != 0 { - return Some('k'); - } - - None - } - - /// Prints the board as a human-readable ASCII grid. - pub fn pretty_print_ascii(&self) { - println!("\n a b c d e f g h"); - for rank in (0..=7).rev() { - print!("{} ", rank + 1); // Rank annotation - for file in 0..=7 { - let sq = (rank * 8 + file) as u8; - let mask = 1u64 << sq; - - if let Some(piece) = self.get_piece_at(mask) { - print!("{} ", piece); - } else { - print!(". "); - } - } - println!(" {}", rank + 1); // Rank annotation - } - println!(" a b c d e f g h\n"); - } - - /// Prints a single bitboard (u64) as an 8x8 grid for debugging. - fn print_bitboard(&self, name: &str, bitboard: u64) { - println!("--- {} ---", name); - println!(" a b c d e f g h"); - for rank in (0..=7).rev() { - print!("{} ", rank + 1); - for file in 0..=7 { - let sq_index = rank * 8 + file; - let mask = 1u64 << sq_index; - - if (bitboard & mask) != 0 { - print!("1 "); - } else { - print!(". "); - } - } - println!(); - } - println!("RAW VALUE: {}", bitboard); - println!(); - } - - /// Prints all internal bitboards for debugging purposes. - pub fn pretty_print_internals(&self) { - println!("\n========= BOARD INTERNAL BITBOARDS ========="); - - let white = Color::White as usize; - let black = Color::Black as usize; - - self.print_bitboard("White Pawns", self.pieces[PieceType::Pawn as usize][white]); - self.print_bitboard("Black Pawns", self.pieces[PieceType::Pawn as usize][black]); - - self.print_bitboard( - "White Knights", - self.pieces[PieceType::Knight as usize][white], - ); - self.print_bitboard( - "Black Knights", - self.pieces[PieceType::Knight as usize][black], - ); - - self.print_bitboard( - "White Bishops", - self.pieces[PieceType::Bishop as usize][white], - ); - self.print_bitboard( - "Black Bishops", - self.pieces[PieceType::Bishop as usize][black], - ); - - self.print_bitboard("White Rooks", self.pieces[PieceType::Rook as usize][white]); - self.print_bitboard("Black Rooks", self.pieces[PieceType::Rook as usize][black]); - - self.print_bitboard( - "White Queens", - self.pieces[PieceType::Queen as usize][white], - ); - self.print_bitboard( - "Black Queens", - self.pieces[PieceType::Queen as usize][black], - ); - - self.print_bitboard("White King", self.pieces[PieceType::King as usize][white]); - self.print_bitboard("Black King", self.pieces[PieceType::King as usize][black]); - - println!("--- Aggregate Bitboards ---"); - self.print_bitboard("All White Pieces", self.occupied[white]); - self.print_bitboard("All Black Pieces", self.occupied[black]); - self.print_bitboard("All Occupied", self.all_occupied); - self.print_bitboard("Empty Squares", self.empty_squares); - - println!("============================================\n"); - } - - fn clear_square(&mut self, target_square: Square, color: Color) -> PieceType { - let target_square_bitboard = target_square.to_bitboard(); - - // update occupancy helper bitboards - self.occupied[color as usize] ^= target_square_bitboard; - self.all_occupied ^= target_square_bitboard; - self.empty_squares |= target_square_bitboard; - - self.pieces_on_squares[target_square as usize] = None; // <-- ADDED - - for piece_type in PIECE_TYPES { - if self.pieces[piece_type as usize][color as usize] & target_square_bitboard > 0 { - self.pieces[piece_type as usize][color as usize] ^= target_square_bitboard; - return piece_type; - } - } - panic!("fn 'clear_square' failed: no piece found"); - } - - fn remove_specific_piece( + fn rm_piece( &mut self, target_square: Square, color: Color, - piece_type: PieceType, - ) { + ) -> PieceType { let target_square_bitboard = target_square.to_bitboard(); - self.pieces[piece_type as usize][color as usize] ^= target_square_bitboard; + let piece_type = self.pieces_on_squares[target_square as usize].unwrap(); - self.pieces_on_squares[target_square as usize] = None; // <-- ADDED + self.pieces[piece_type as usize][color as usize] ^= target_square_bitboard; + self.pieces_on_squares[target_square as usize] = None; // update occupancy helper bitboards self.occupied[color as usize] ^= target_square_bitboard; self.all_occupied ^= target_square_bitboard; self.empty_squares |= target_square_bitboard; + + piece_type } fn put_piece(&mut self, target_square: Square, color: Color, piece_type: PieceType) { let target_square_bitboard = target_square.to_bitboard(); self.pieces[piece_type as usize][color as usize] |= target_square_bitboard; - - self.pieces_on_squares[target_square as usize] = Some(piece_type); // <-- ADDED + self.pieces_on_squares[target_square as usize] = Some(piece_type); // update occupancy helper bitboards self.occupied[color as usize] |= target_square_bitboard; @@ -499,113 +109,105 @@ impl Board { self.empty_squares ^= target_square_bitboard; } + fn move_piece(&mut self, from: Square, to: Square, color: Color) { + let piece_type = self.rm_piece(from, color); + self.put_piece(to, color, piece_type); + } + pub fn make_move(&mut self, mv: Move) -> UndoMove { + // 1. Extract parts from move let from = mv.get_from(); let to = mv.get_to(); let flags = mv.get_flags(); + // 2. Save old state for UndoMove object let old_en_passant_target: Option = self.en_passant_target; let old_castling_rights: u8 = self.castling_rights; let old_halfmove_clock: u8 = self.halfmove_clock; - // needed for half move tracking + // 3. Save pawns and total pieces for half move tracking let old_friendly_pawns = self.pieces[PieceType::Pawn as usize][self.side_to_move as usize]; let old_total_pieces = self.all_occupied.count_ones(); let mut opt_captured_piece: Option = None; let mut opt_en_passant_target: Option = None; + // 4. Make the actual moves on the bitboard based on flag type match flags { MOVE_FLAG_QUIET => { - let piece_type_from = self.clear_square(from, self.side_to_move); - self.put_piece(to, self.side_to_move, piece_type_from); + self.move_piece(from, to, self.side_to_move); } MOVE_FLAG_CAPTURE => { - let piece_type_from = self.clear_square(from, self.side_to_move); - opt_captured_piece = Some(self.clear_square(to, !self.side_to_move)); - self.put_piece(to, self.side_to_move, piece_type_from); + opt_captured_piece = Some(self.rm_piece(to, !self.side_to_move)); + self.move_piece(from, to, self.side_to_move); } MOVE_FLAG_DOUBLE_PAWN => { - let piece_type_from = self.clear_square(from, self.side_to_move); - self.put_piece(to, self.side_to_move, piece_type_from); + self.move_piece(from, to, self.side_to_move); opt_en_passant_target = Some(to + (self.side_to_move as i8 * 16 - 8)); } MOVE_FLAG_PROMO_Q => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); + self.rm_piece(from, self.side_to_move); self.put_piece(to, self.side_to_move, PieceType::Queen); } MOVE_FLAG_PROMO_N => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); + self.rm_piece(from, self.side_to_move); self.put_piece(to, self.side_to_move, PieceType::Knight); } MOVE_FLAG_PROMO_B => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); + self.rm_piece(from, self.side_to_move); self.put_piece(to, self.side_to_move, PieceType::Bishop); } MOVE_FLAG_PROMO_R => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); + self.rm_piece(from, self.side_to_move); self.put_piece(to, self.side_to_move, PieceType::Rook); } MOVE_FLAG_PROMO_Q_CAP => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); - opt_captured_piece = Some(self.clear_square(to, !self.side_to_move)); + self.rm_piece(from, self.side_to_move); + opt_captured_piece = Some(self.rm_piece(to, !self.side_to_move)); self.put_piece(to, self.side_to_move, PieceType::Queen); } MOVE_FLAG_PROMO_N_CAP => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); - opt_captured_piece = Some(self.clear_square(to, !self.side_to_move)); + self.rm_piece(from, self.side_to_move); + opt_captured_piece = Some(self.rm_piece(to, !self.side_to_move)); self.put_piece(to, self.side_to_move, PieceType::Knight); } MOVE_FLAG_PROMO_B_CAP => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); - opt_captured_piece = Some(self.clear_square(to, !self.side_to_move)); + self.rm_piece(from, self.side_to_move); + opt_captured_piece = Some(self.rm_piece(to, !self.side_to_move)); self.put_piece(to, self.side_to_move, PieceType::Bishop); } MOVE_FLAG_PROMO_R_CAP => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); - opt_captured_piece = Some(self.clear_square(to, !self.side_to_move)); + self.rm_piece(from, self.side_to_move); + opt_captured_piece = Some(self.rm_piece(to, !self.side_to_move)); self.put_piece(to, self.side_to_move, PieceType::Rook); } MOVE_FLAG_WK_CASTLE => { - self.remove_specific_piece(Square::E1, Color::White, PieceType::King); - self.remove_specific_piece(Square::H1, Color::White, PieceType::Rook); - self.put_piece(Square::G1, Color::White, PieceType::King); - self.put_piece(Square::F1, Color::White, PieceType::Rook); + self.move_piece(Square::E1, Square::G1, self.side_to_move); + self.move_piece(Square::H1, Square::F1, self.side_to_move); } MOVE_FLAG_BK_CASTLE => { - self.remove_specific_piece(Square::E8, Color::Black, PieceType::King); - self.remove_specific_piece(Square::H8, Color::Black, PieceType::Rook); - self.put_piece(Square::G8, Color::Black, PieceType::King); - self.put_piece(Square::F8, Color::Black, PieceType::Rook); + self.move_piece(Square::E8, Square::G8, self.side_to_move); + self.move_piece(Square::H8, Square::F8, self.side_to_move); } MOVE_FLAG_WQ_CASTLE => { - self.remove_specific_piece(Square::E1, Color::White, PieceType::King); - self.remove_specific_piece(Square::A1, Color::White, PieceType::Rook); - self.put_piece(Square::C1, Color::White, PieceType::King); - self.put_piece(Square::D1, Color::White, PieceType::Rook); + self.move_piece(Square::E1, Square::C1, self.side_to_move); + self.move_piece(Square::A1, Square::D1, self.side_to_move); } MOVE_FLAG_BQ_CASTLE => { - self.remove_specific_piece(Square::E8, Color::Black, PieceType::King); - self.remove_specific_piece(Square::A8, Color::Black, PieceType::Rook); - self.put_piece(Square::C8, Color::Black, PieceType::King); - self.put_piece(Square::D8, Color::Black, PieceType::Rook); + self.move_piece(Square::E8, Square::C8, self.side_to_move); + self.move_piece(Square::A8, Square::D8, self.side_to_move); } MOVE_FLAG_EN_PASSANT => { - self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn); - if self.side_to_move == Color::White { - self.remove_specific_piece(to - 8_u8, !self.side_to_move, PieceType::Pawn); - } else { - self.remove_specific_piece(to + 8_u8, !self.side_to_move, PieceType::Pawn); - } + self.move_piece(from, to, self.side_to_move); + self.rm_piece(to + (self.side_to_move as i8 * 16 - 8), !self.side_to_move); opt_captured_piece = Some(PieceType::Pawn); - self.put_piece(to, self.side_to_move, PieceType::Pawn); } _ => { panic!("unable to make_move: invalid flags: {}", flags); } } - // set castle rights + // 5. Update the castling rights let wk = self.pieces[PieceType::King as usize][Color::White as usize]; let wr = self.pieces[PieceType::Rook as usize][Color::White as usize]; let bk = self.pieces[PieceType::King as usize][Color::Black as usize]; @@ -616,12 +218,12 @@ impl Board { let castling_right_bq = (((bk & CASTLING_BQ_K_POS_MASK) > 0 && (br & CASTLING_BQ_R_POS_MASK) > 0) as u8) << 3; let new_castling_rights = castling_right_wk | castling_right_wq | castling_right_bk | castling_right_bq; - self.castling_rights = self.castling_rights & new_castling_rights; // & operator makes sure castling rights cant be gained back + self.castling_rights = self.castling_rights & new_castling_rights; // & operator makes sure castling rights can not be gained back - // set new en passant target + // 6. Update the en passant target square self.en_passant_target = opt_en_passant_target; - // increase halfmove clock by 1 if no pawn was pushed and now piece captured + // 7. Update the halfmove clock let new_friendly_pawns = self.pieces[PieceType::Pawn as usize][self.side_to_move as usize]; let new_total_pieces = self.all_occupied.count_ones(); let pawns_changed = old_friendly_pawns ^ new_friendly_pawns; @@ -629,12 +231,13 @@ impl Board { let increase_halfmove_clock = ((pawns_changed + piece_captured) == 0) as u8; self.halfmove_clock = increase_halfmove_clock * (self.halfmove_clock + 1); - // increase full move number by 1 when black made a move + // 8. Increase the fullmove clock self.fullmove_number += self.side_to_move as u16; - // flip the side to move + // 9. Flip the side to move self.side_to_move = !self.side_to_move; + // 10. Create and return UndoMove object UndoMove::new( mv, opt_captured_piece, diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..34aea07 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,195 @@ +use std::fmt; +use crate::board::{Board, Color, PieceType}; +use crate::r#move::{Move, MoveList, MOVE_FLAG_BK_CASTLE, MOVE_FLAG_BQ_CASTLE, MOVE_FLAG_PROMO_B, MOVE_FLAG_PROMO_B_CAP, MOVE_FLAG_PROMO_N, MOVE_FLAG_PROMO_N_CAP, MOVE_FLAG_PROMO_Q, MOVE_FLAG_PROMO_Q_CAP, MOVE_FLAG_PROMO_R, MOVE_FLAG_PROMO_R_CAP, MOVE_FLAG_WK_CASTLE, MOVE_FLAG_WQ_CASTLE, MOVE_FROM_MASK, MOVE_TO_MASK}; + +impl Board { + /// Prints the board as a human-readable ASCII grid. + pub fn pretty_print_ascii(&self) { + println!("\n a b c d e f g h"); + for rank in (0..=7).rev() { + print!("{} ", rank + 1); // Rank annotation + for file in 0..=7 { + let sq = (rank * 8 + file) as u8; + let mask = 1u64 << sq; + + if let Some(piece) = self.get_piece_at(mask) { + print!("{} ", piece); + } else { + print!(". "); + } + } + println!(" {}", rank + 1); // Rank annotation + } + println!(" a b c d e f g h\n"); + } + + /// Prints a single bitboard (u64) as an 8x8 grid for debugging. + pub fn print_bitboard(&self, name: &str, bitboard: u64) { + println!("--- {} ---", name); + println!(" a b c d e f g h"); + for rank in (0..=7).rev() { + print!("{} ", rank + 1); + for file in 0..=7 { + let sq_index = rank * 8 + file; + let mask = 1u64 << sq_index; + + if (bitboard & mask) != 0 { + print!("1 "); + } else { + print!(". "); + } + } + println!(); + } + println!("RAW VALUE: {}", bitboard); + println!(); + } + + /// Helper function to find which piece (as a char) is on a given square mask. + pub fn get_piece_at(&self, sq_mask: u64) -> Option { + let white = Color::White as usize; + let black = Color::Black as usize; + + if (self.pieces[PieceType::Pawn as usize][white] & sq_mask) != 0 { + return Some('P'); + } + if (self.pieces[PieceType::Pawn as usize][black] & sq_mask) != 0 { + return Some('p'); + } + if (self.pieces[PieceType::Knight as usize][white] & sq_mask) != 0 { + return Some('N'); + } + if (self.pieces[PieceType::Knight as usize][black] & sq_mask) != 0 { + return Some('n'); + } + if (self.pieces[PieceType::Bishop as usize][white] & sq_mask) != 0 { + return Some('B'); + } + if (self.pieces[PieceType::Bishop as usize][black] & sq_mask) != 0 { + return Some('b'); + } + if (self.pieces[PieceType::Rook as usize][white] & sq_mask) != 0 { + return Some('R'); + } + if (self.pieces[PieceType::Rook as usize][black] & sq_mask) != 0 { + return Some('r'); + } + if (self.pieces[PieceType::Queen as usize][white] & sq_mask) != 0 { + return Some('Q'); + } + if (self.pieces[PieceType::Queen as usize][black] & sq_mask) != 0 { + return Some('q'); + } + if (self.pieces[PieceType::King as usize][white] & sq_mask) != 0 { + return Some('K'); + } + if (self.pieces[PieceType::King as usize][black] & sq_mask) != 0 { + return Some('k'); + } + + None + } + + /// Prints all internal bitboards for debugging purposes. + pub fn pretty_print_internals(&self) { + println!("\n========= BOARD INTERNAL BITBOARDS ========="); + + let white = Color::White as usize; + let black = Color::Black as usize; + + self.print_bitboard("White Pawns", self.pieces[PieceType::Pawn as usize][white]); + self.print_bitboard("Black Pawns", self.pieces[PieceType::Pawn as usize][black]); + + self.print_bitboard( + "White Knights", + self.pieces[PieceType::Knight as usize][white], + ); + self.print_bitboard( + "Black Knights", + self.pieces[PieceType::Knight as usize][black], + ); + + self.print_bitboard( + "White Bishops", + self.pieces[PieceType::Bishop as usize][white], + ); + self.print_bitboard( + "Black Bishops", + self.pieces[PieceType::Bishop as usize][black], + ); + + self.print_bitboard("White Rooks", self.pieces[PieceType::Rook as usize][white]); + self.print_bitboard("Black Rooks", self.pieces[PieceType::Rook as usize][black]); + + self.print_bitboard( + "White Queens", + self.pieces[PieceType::Queen as usize][white], + ); + self.print_bitboard( + "Black Queens", + self.pieces[PieceType::Queen as usize][black], + ); + + self.print_bitboard("White King", self.pieces[PieceType::King as usize][white]); + self.print_bitboard("Black King", self.pieces[PieceType::King as usize][black]); + + println!("--- Aggregate Bitboards ---"); + self.print_bitboard("All White Pieces", self.occupied[white]); + self.print_bitboard("All Black Pieces", self.occupied[black]); + self.print_bitboard("All Occupied", self.all_occupied); + self.print_bitboard("Empty Squares", self.empty_squares); + + println!("============================================\n"); + } +} + +impl Move { + /// Converts a square index (0-63) to algebraic notation (e.g., 0 -> "a1", 63 -> "h8"). + fn square_val_to_alg(val: u16) -> String { + let file = (b'a' + (val % 8) as u8) as char; + let rank = (b'1' + (val / 8) as u8) as char; + format!("{}{}", file, rank) + } + + /// Converts the move to coordinate notation (e.g., "e2e4", "e7e8q", "e1g1"). + pub fn to_algebraic(&self) -> String { + let flags = self.get_flags(); + + // Handle castling first. In this new format, the "to" square is + // the *king's* destination square (g1/c1 or g8/c8). + // Your old implementation reading the file is still fine. + if (flags == MOVE_FLAG_WK_CASTLE) || (flags == MOVE_FLAG_BK_CASTLE) { + return "O-O".to_string(); + } + if (flags == MOVE_FLAG_WQ_CASTLE) || (flags == MOVE_FLAG_BQ_CASTLE) { + return "O-O-O".to_string(); + } + + let from_val = self.0 & MOVE_FROM_MASK; + let to_val = (self.0 & MOVE_TO_MASK) >> 6; + + let from_str = Self::square_val_to_alg(from_val); + let to_str = Self::square_val_to_alg(to_val); + + // Check if it's any promotion type (1xxx) + if (flags & 0b1000_0000_0000_0000) != 0 { + let promo_char = match flags { + MOVE_FLAG_PROMO_N | MOVE_FLAG_PROMO_N_CAP => 'n', + MOVE_FLAG_PROMO_B | MOVE_FLAG_PROMO_B_CAP => 'b', + MOVE_FLAG_PROMO_R | MOVE_FLAG_PROMO_R_CAP => 'r', + MOVE_FLAG_PROMO_Q | MOVE_FLAG_PROMO_Q_CAP => 'q', + _ => '?', // Should not happen + }; + format!("{}{}{}", from_str, to_str, promo_char) + } else { + // This covers Quiet, DoublePawn, Capture, EnPassant + format!("{}{}", from_str, to_str) + } + } +} + +impl fmt::Display for MoveList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.iter().map(|mv| mv.to_algebraic()).collect::>().join(" ")) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 19ad6c3..ef7ca28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ pub mod board; pub mod r#move; pub mod square; -pub mod movegen; \ No newline at end of file +pub mod movegen; +mod display; +mod parsing; \ No newline at end of file diff --git a/src/move.rs b/src/move.rs index 8c78ff6..04b1d7c 100644 --- a/src/move.rs +++ b/src/move.rs @@ -42,7 +42,7 @@ pub const MOVE_FLAG_PROMO_R_CAP: u16 = 0b1101_0000_0000_0000; pub const MOVE_FLAG_PROMO_Q_CAP: u16 = 0b1111_0000_0000_0000; #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Move(u16); +pub struct Move(pub(crate) u16); impl Move { pub fn new(from: Square, to: Square, flags: u16) -> Move { @@ -51,7 +51,6 @@ impl Move { from as u16) } - #[inline(always)] pub fn get_flags(&self) -> u16 { self.0 & MOVE_FLAG_MASK @@ -64,58 +63,10 @@ impl Move { #[inline(always)] pub fn get_to(&self) -> Square { - // --- KORREKTUR HIER --- - // Die Klammern um (self.0 & MOVE_TO_MASK) sind entscheidend SQUARES[((self.0 & MOVE_TO_MASK) >> 6) as usize] } - - - /// Converts a square index (0-63) to algebraic notation (e.g., 0 -> "a1", 63 -> "h8"). - fn square_val_to_alg(val: u16) -> String { - let file = (b'a' + (val % 8) as u8) as char; - let rank = (b'1' + (val / 8) as u8) as char; - format!("{}{}", file, rank) - } - - /// Converts the move to coordinate notation (e.g., "e2e4", "e7e8q", "e1g1"). - pub fn to_algebraic(&self) -> String { - let flags = self.get_flags(); - - // Handle castling first. In this new format, the "to" square is - // the *king's* destination square (g1/c1 or g8/c8). - // Your old implementation reading the file is still fine. - if (flags == MOVE_FLAG_WK_CASTLE) || (flags == MOVE_FLAG_BK_CASTLE) { - return "O-O".to_string(); - } - if (flags == MOVE_FLAG_WQ_CASTLE) || (flags == MOVE_FLAG_BQ_CASTLE) { - return "O-O-O".to_string(); - } - - let from_val = self.0 & MOVE_FROM_MASK; - let to_val = (self.0 & MOVE_TO_MASK) >> 6; - - let from_str = Self::square_val_to_alg(from_val); - let to_str = Self::square_val_to_alg(to_val); - - // Check if it's any promotion type (1xxx) - if (flags & 0b1000_0000_0000_0000) != 0 { - let promo_char = match flags { - MOVE_FLAG_PROMO_N | MOVE_FLAG_PROMO_N_CAP => 'n', - MOVE_FLAG_PROMO_B | MOVE_FLAG_PROMO_B_CAP => 'b', - MOVE_FLAG_PROMO_R | MOVE_FLAG_PROMO_R_CAP => 'r', - MOVE_FLAG_PROMO_Q | MOVE_FLAG_PROMO_Q_CAP => 'q', - _ => '?', // Should not happen - }; - format!("{}{}{}", from_str, to_str, promo_char) - } else { - // This covers Quiet, DoublePawn, Capture, EnPassant - format!("{}{}", from_str, to_str) - } - } } -// ... Rest des MoveList-Codes bleibt exakt gleich ... -// (MoveList, new, push, len, is_empty, iter, impl fmt::Display) pub struct MoveList { moves: [Move; 256], count: usize, @@ -153,13 +104,6 @@ impl MoveList { } } -impl fmt::Display for MoveList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.iter().map(|mv| mv.to_algebraic()).collect::>().join(" ")) - } -} - - pub struct UndoMove { mv: Move, captured_piece: Option, diff --git a/src/parsing.rs b/src/parsing.rs new file mode 100644 index 0000000..55352bf --- /dev/null +++ b/src/parsing.rs @@ -0,0 +1,237 @@ +use std::mem; +use crate::board::{Board, Color, PieceType, CASTLING_BK_FLAG, CASTLING_BQ_FLAG, CASTLING_WK_FLAG, CASTLING_WQ_FLAG}; +use crate::square::Square; + +impl Board { + /// Creates a new Board instance from a FEN string. + /// Assumes the FEN string is valid. + pub fn from_fen(fen: &str) -> Self { + let mut parts = fen.split_whitespace(); + + // Initialisiere die Arrays + let mut pieces = [[0u64; 2]; 6]; + let mut occupied = [0u64; 2]; + let mut pieces_on_squares = [None; 64]; // <-- ADDED + + // Part 1: Piece placement + let placement = parts.next().unwrap_or(""); + let mut rank = 7; + let mut file = 0; + + for c in placement.chars() { + if c.is_digit(10) { + file += c.to_digit(10).unwrap_or(0) as usize; + } else if c == '/' { + rank -= 1; + file = 0; + } else if c.is_alphabetic() { + let sq = (rank * 8 + file) as u8; + let mask = 1u64 << sq; + + if c.is_uppercase() { + let color_idx = Color::White as usize; + occupied[color_idx] |= mask; + match c { + 'P' => { + pieces[PieceType::Pawn as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Pawn); // <-- ADDED + } + 'N' => { + pieces[PieceType::Knight as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Knight); // <-- ADDED + } + 'B' => { + pieces[PieceType::Bishop as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Bishop); // <-- ADDED + } + 'R' => { + pieces[PieceType::Rook as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Rook); // <-- ADDED + } + 'Q' => { + pieces[PieceType::Queen as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Queen); // <-- ADDED + } + 'K' => { + pieces[PieceType::King as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::King); // <-- ADDED + } + _ => {} + } + } else { + let color_idx = Color::Black as usize; + occupied[color_idx] |= mask; + match c { + 'p' => { + pieces[PieceType::Pawn as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Pawn); // <-- ADDED + } + 'n' => { + pieces[PieceType::Knight as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Knight); // <-- ADDED + } + 'b' => { + pieces[PieceType::Bishop as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Bishop); // <-- ADDED + } + 'r' => { + pieces[PieceType::Rook as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Rook); // <-- ADDED + } + 'q' => { + pieces[PieceType::Queen as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::Queen); // <-- ADDED + } + 'k' => { + pieces[PieceType::King as usize][color_idx] |= mask; + pieces_on_squares[sq as usize] = Some(PieceType::King); // <-- ADDED + } + _ => {} + } + } + file += 1; + } + } + + // Part 2: Active color + let side_to_move = match parts.next().unwrap_or("w") { + "b" => Color::Black, + _ => Color::White, + }; + + // Part 3: Castling rights + let mut castling_rights = 0u8; + if let Some(castle_str) = parts.next() { + if castle_str.contains('K') { + castling_rights |= CASTLING_WK_FLAG; + } + if castle_str.contains('Q') { + castling_rights |= CASTLING_WQ_FLAG; + } + if castle_str.contains('k') { + castling_rights |= CASTLING_BK_FLAG; + } + if castle_str.contains('q') { + castling_rights |= CASTLING_BQ_FLAG; + } + } + + // Part 4: En passant target + let en_passant_target = match parts.next().unwrap_or("-") { + "-" => None, + sq_str => { + let chars: Vec = sq_str.chars().collect(); + let file = (chars[0] as u8 - b'a') as u8; + let rank = (chars[1] as u8 - b'1') as u8; + let sq_index = rank * 8 + file; + // This is unsafe, but assumes the FEN is valid + Some(unsafe { mem::transmute::(sq_index) }) + } + }; + + // Part 5: Halfmove clock + let halfmove_clock = parts.next().unwrap_or("0").parse::().unwrap_or(0); + + // Part 6: Fullmove number + let fullmove_number = parts.next().unwrap_or("1").parse::().unwrap_or(1); + + let all_occupied = occupied[Color::White as usize] | occupied[Color::Black as usize]; + let empty_squares = !all_occupied; + + Board { + side_to_move, + pieces, + pieces_on_squares, // <-- ADDED + occupied, + all_occupied, + empty_squares, + castling_rights, + en_passant_target, + halfmove_clock, + fullmove_number, + } + } + + /// Converts the current board state into a FEN string. + pub fn to_fen(&self) -> String { + let mut fen = String::with_capacity(90); + + // Part 1: Piece placement + let mut empty_count = 0; + for rank in (0..=7).rev() { + for file in 0..=7 { + let sq = (rank * 8 + file) as u8; + let mask = 1u64 << sq; + + if let Some(piece) = self.get_piece_at(mask) { + if empty_count > 0 { + fen.push((b'0' + empty_count) as char); + empty_count = 0; + } + fen.push(piece); + } else { + empty_count += 1; + } + } + if empty_count > 0 { + fen.push((b'0' + empty_count) as char); + empty_count = 0; + } + if rank > 0 { + fen.push('/'); + } + } + + // Part 2: Active color + fen.push(' '); + fen.push(if self.side_to_move == Color::White { + 'w' + } else { + 'b' + }); + + // Part 3: Castling rights + fen.push(' '); + let mut castle_str = String::new(); + if (self.castling_rights & CASTLING_WK_FLAG) != 0 { + castle_str.push('K'); + } + if (self.castling_rights & CASTLING_WQ_FLAG) != 0 { + castle_str.push('Q'); + } + if (self.castling_rights & CASTLING_BK_FLAG) != 0 { + castle_str.push('k'); + } + if (self.castling_rights & CASTLING_BQ_FLAG) != 0 { + castle_str.push('q'); + } + + if castle_str.is_empty() { + fen.push('-'); + } else { + fen.push_str(&castle_str); + } + + // Part 4: En passant target + fen.push(' '); + if let Some(sq) = self.en_passant_target { + let sq_index = sq as u8; + let file = (sq_index % 8) as u8; + let rank = (sq_index / 8) as u8; + fen.push((b'a' + file) as char); + fen.push((b'1' + rank) as char); + } else { + fen.push('-'); + } + + // Part 5: Halfmove clock + fen.push(' '); + fen.push_str(&self.halfmove_clock.to_string()); + + // Part 6: Fullmove number + fen.push(' '); + fen.push_str(&self.fullmove_number.to_string()); + + fen + } +} \ No newline at end of file