cleanup and restructure
This commit is contained in:
parent
5e19c1e494
commit
e7578dd0f0
5 changed files with 487 additions and 506 deletions
499
src/board.rs
499
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<char> = 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::<u8, Square>(sq_index) })
|
||||
}
|
||||
};
|
||||
|
||||
// Part 5: Halfmove clock
|
||||
let halfmove_clock = parts.next().unwrap_or("0").parse::<u8>().unwrap_or(0);
|
||||
|
||||
// Part 6: Fullmove number
|
||||
let fullmove_number = parts.next().unwrap_or("1").parse::<u16>().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<char> {
|
||||
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<Square> = 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<PieceType> = None;
|
||||
let mut opt_en_passant_target: Option<Square> = 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,
|
||||
|
|
|
|||
195
src/display.rs
Normal file
195
src/display.rs
Normal file
|
|
@ -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<char> {
|
||||
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::<Vec<String>>().join(" "))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,3 +2,5 @@ pub mod board;
|
|||
pub mod r#move;
|
||||
pub mod square;
|
||||
pub mod movegen;
|
||||
mod display;
|
||||
mod parsing;
|
||||
58
src/move.rs
58
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::<Vec<String>>().join(" "))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct UndoMove {
|
||||
mv: Move,
|
||||
captured_piece: Option<PieceType>,
|
||||
|
|
|
|||
237
src/parsing.rs
Normal file
237
src/parsing.rs
Normal file
|
|
@ -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<char> = 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::<u8, Square>(sq_index) })
|
||||
}
|
||||
};
|
||||
|
||||
// Part 5: Halfmove clock
|
||||
let halfmove_clock = parts.next().unwrap_or("0").parse::<u8>().unwrap_or(0);
|
||||
|
||||
// Part 6: Fullmove number
|
||||
let fullmove_number = parts.next().unwrap_or("1").parse::<u16>().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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue