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::r#move::*;
|
||||||
use crate::square::{Square, SQUARES};
|
use crate::square::Square;
|
||||||
use std::mem;
|
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
|
|
||||||
pub const CASTLING_WK_FLAG: u8 = 1;
|
pub const CASTLING_WK_FLAG: u8 = 1;
|
||||||
|
|
@ -80,418 +79,29 @@ pub struct Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
/// Creates a new Board instance from a FEN string.
|
fn rm_piece(
|
||||||
/// 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(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
target_square: Square,
|
target_square: Square,
|
||||||
color: Color,
|
color: Color,
|
||||||
piece_type: PieceType,
|
) -> PieceType {
|
||||||
) {
|
|
||||||
let target_square_bitboard = target_square.to_bitboard();
|
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
|
// update occupancy helper bitboards
|
||||||
self.occupied[color as usize] ^= target_square_bitboard;
|
self.occupied[color as usize] ^= target_square_bitboard;
|
||||||
self.all_occupied ^= target_square_bitboard;
|
self.all_occupied ^= target_square_bitboard;
|
||||||
self.empty_squares |= target_square_bitboard;
|
self.empty_squares |= target_square_bitboard;
|
||||||
|
|
||||||
|
piece_type
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put_piece(&mut self, target_square: Square, color: Color, piece_type: PieceType) {
|
fn put_piece(&mut self, target_square: Square, color: Color, piece_type: PieceType) {
|
||||||
let target_square_bitboard = target_square.to_bitboard();
|
let target_square_bitboard = target_square.to_bitboard();
|
||||||
self.pieces[piece_type as usize][color as usize] |= target_square_bitboard;
|
self.pieces[piece_type as usize][color as usize] |= target_square_bitboard;
|
||||||
|
self.pieces_on_squares[target_square as usize] = Some(piece_type);
|
||||||
self.pieces_on_squares[target_square as usize] = Some(piece_type); // <-- ADDED
|
|
||||||
|
|
||||||
// update occupancy helper bitboards
|
// update occupancy helper bitboards
|
||||||
self.occupied[color as usize] |= target_square_bitboard;
|
self.occupied[color as usize] |= target_square_bitboard;
|
||||||
|
|
@ -499,113 +109,105 @@ impl Board {
|
||||||
self.empty_squares ^= target_square_bitboard;
|
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 {
|
pub fn make_move(&mut self, mv: Move) -> UndoMove {
|
||||||
|
// 1. Extract parts from move
|
||||||
let from = mv.get_from();
|
let from = mv.get_from();
|
||||||
let to = mv.get_to();
|
let to = mv.get_to();
|
||||||
let flags = mv.get_flags();
|
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_en_passant_target: Option<Square> = self.en_passant_target;
|
||||||
let old_castling_rights: u8 = self.castling_rights;
|
let old_castling_rights: u8 = self.castling_rights;
|
||||||
let old_halfmove_clock: u8 = self.halfmove_clock;
|
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_friendly_pawns = self.pieces[PieceType::Pawn as usize][self.side_to_move as usize];
|
||||||
let old_total_pieces = self.all_occupied.count_ones();
|
let old_total_pieces = self.all_occupied.count_ones();
|
||||||
|
|
||||||
let mut opt_captured_piece: Option<PieceType> = None;
|
let mut opt_captured_piece: Option<PieceType> = None;
|
||||||
let mut opt_en_passant_target: Option<Square> = None;
|
let mut opt_en_passant_target: Option<Square> = None;
|
||||||
|
|
||||||
|
// 4. Make the actual moves on the bitboard based on flag type
|
||||||
match flags {
|
match flags {
|
||||||
MOVE_FLAG_QUIET => {
|
MOVE_FLAG_QUIET => {
|
||||||
let piece_type_from = self.clear_square(from, self.side_to_move);
|
self.move_piece(from, to, self.side_to_move);
|
||||||
self.put_piece(to, self.side_to_move, piece_type_from);
|
|
||||||
}
|
}
|
||||||
MOVE_FLAG_CAPTURE => {
|
MOVE_FLAG_CAPTURE => {
|
||||||
let piece_type_from = self.clear_square(from, self.side_to_move);
|
opt_captured_piece = Some(self.rm_piece(to, !self.side_to_move));
|
||||||
opt_captured_piece = Some(self.clear_square(to, !self.side_to_move));
|
self.move_piece(from, to, self.side_to_move);
|
||||||
self.put_piece(to, self.side_to_move, piece_type_from);
|
|
||||||
}
|
}
|
||||||
MOVE_FLAG_DOUBLE_PAWN => {
|
MOVE_FLAG_DOUBLE_PAWN => {
|
||||||
let piece_type_from = self.clear_square(from, self.side_to_move);
|
self.move_piece(from, to, self.side_to_move);
|
||||||
self.put_piece(to, self.side_to_move, piece_type_from);
|
|
||||||
opt_en_passant_target = Some(to + (self.side_to_move as i8 * 16 - 8));
|
opt_en_passant_target = Some(to + (self.side_to_move as i8 * 16 - 8));
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_Q => {
|
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);
|
self.put_piece(to, self.side_to_move, PieceType::Queen);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_N => {
|
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);
|
self.put_piece(to, self.side_to_move, PieceType::Knight);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_B => {
|
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);
|
self.put_piece(to, self.side_to_move, PieceType::Bishop);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_R => {
|
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);
|
self.put_piece(to, self.side_to_move, PieceType::Rook);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_Q_CAP => {
|
MOVE_FLAG_PROMO_Q_CAP => {
|
||||||
self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn);
|
self.rm_piece(from, self.side_to_move);
|
||||||
opt_captured_piece = Some(self.clear_square(to, !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);
|
self.put_piece(to, self.side_to_move, PieceType::Queen);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_N_CAP => {
|
MOVE_FLAG_PROMO_N_CAP => {
|
||||||
self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn);
|
self.rm_piece(from, self.side_to_move);
|
||||||
opt_captured_piece = Some(self.clear_square(to, !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);
|
self.put_piece(to, self.side_to_move, PieceType::Knight);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_B_CAP => {
|
MOVE_FLAG_PROMO_B_CAP => {
|
||||||
self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn);
|
self.rm_piece(from, self.side_to_move);
|
||||||
opt_captured_piece = Some(self.clear_square(to, !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);
|
self.put_piece(to, self.side_to_move, PieceType::Bishop);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_PROMO_R_CAP => {
|
MOVE_FLAG_PROMO_R_CAP => {
|
||||||
self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn);
|
self.rm_piece(from, self.side_to_move);
|
||||||
opt_captured_piece = Some(self.clear_square(to, !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);
|
self.put_piece(to, self.side_to_move, PieceType::Rook);
|
||||||
}
|
}
|
||||||
MOVE_FLAG_WK_CASTLE => {
|
MOVE_FLAG_WK_CASTLE => {
|
||||||
self.remove_specific_piece(Square::E1, Color::White, PieceType::King);
|
self.move_piece(Square::E1, Square::G1, self.side_to_move);
|
||||||
self.remove_specific_piece(Square::H1, Color::White, PieceType::Rook);
|
self.move_piece(Square::H1, Square::F1, self.side_to_move);
|
||||||
self.put_piece(Square::G1, Color::White, PieceType::King);
|
|
||||||
self.put_piece(Square::F1, Color::White, PieceType::Rook);
|
|
||||||
}
|
}
|
||||||
MOVE_FLAG_BK_CASTLE => {
|
MOVE_FLAG_BK_CASTLE => {
|
||||||
self.remove_specific_piece(Square::E8, Color::Black, PieceType::King);
|
self.move_piece(Square::E8, Square::G8, self.side_to_move);
|
||||||
self.remove_specific_piece(Square::H8, Color::Black, PieceType::Rook);
|
self.move_piece(Square::H8, Square::F8, self.side_to_move);
|
||||||
self.put_piece(Square::G8, Color::Black, PieceType::King);
|
|
||||||
self.put_piece(Square::F8, Color::Black, PieceType::Rook);
|
|
||||||
}
|
}
|
||||||
MOVE_FLAG_WQ_CASTLE => {
|
MOVE_FLAG_WQ_CASTLE => {
|
||||||
self.remove_specific_piece(Square::E1, Color::White, PieceType::King);
|
self.move_piece(Square::E1, Square::C1, self.side_to_move);
|
||||||
self.remove_specific_piece(Square::A1, Color::White, PieceType::Rook);
|
self.move_piece(Square::A1, Square::D1, self.side_to_move);
|
||||||
self.put_piece(Square::C1, Color::White, PieceType::King);
|
|
||||||
self.put_piece(Square::D1, Color::White, PieceType::Rook);
|
|
||||||
}
|
}
|
||||||
MOVE_FLAG_BQ_CASTLE => {
|
MOVE_FLAG_BQ_CASTLE => {
|
||||||
self.remove_specific_piece(Square::E8, Color::Black, PieceType::King);
|
self.move_piece(Square::E8, Square::C8, self.side_to_move);
|
||||||
self.remove_specific_piece(Square::A8, Color::Black, PieceType::Rook);
|
self.move_piece(Square::A8, Square::D8, self.side_to_move);
|
||||||
self.put_piece(Square::C8, Color::Black, PieceType::King);
|
|
||||||
self.put_piece(Square::D8, Color::Black, PieceType::Rook);
|
|
||||||
}
|
}
|
||||||
MOVE_FLAG_EN_PASSANT => {
|
MOVE_FLAG_EN_PASSANT => {
|
||||||
self.remove_specific_piece(from, self.side_to_move, PieceType::Pawn);
|
self.move_piece(from, to, self.side_to_move);
|
||||||
if self.side_to_move == Color::White {
|
self.rm_piece(to + (self.side_to_move as i8 * 16 - 8), !self.side_to_move);
|
||||||
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);
|
|
||||||
}
|
|
||||||
opt_captured_piece = Some(PieceType::Pawn);
|
opt_captured_piece = Some(PieceType::Pawn);
|
||||||
self.put_piece(to, self.side_to_move, PieceType::Pawn);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("unable to make_move: invalid flags: {}", flags);
|
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 wk = self.pieces[PieceType::King as usize][Color::White as usize];
|
||||||
let wr = self.pieces[PieceType::Rook 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];
|
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 castling_right_bq = (((bk & CASTLING_BQ_K_POS_MASK) > 0 && (br & CASTLING_BQ_R_POS_MASK) > 0) as u8) << 3;
|
||||||
let new_castling_rights =
|
let new_castling_rights =
|
||||||
castling_right_wk | castling_right_wq | castling_right_bk | castling_right_bq;
|
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;
|
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_friendly_pawns = self.pieces[PieceType::Pawn as usize][self.side_to_move as usize];
|
||||||
let new_total_pieces = self.all_occupied.count_ones();
|
let new_total_pieces = self.all_occupied.count_ones();
|
||||||
let pawns_changed = old_friendly_pawns ^ new_friendly_pawns;
|
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;
|
let increase_halfmove_clock = ((pawns_changed + piece_captured) == 0) as u8;
|
||||||
self.halfmove_clock = increase_halfmove_clock * (self.halfmove_clock + 1);
|
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;
|
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;
|
self.side_to_move = !self.side_to_move;
|
||||||
|
|
||||||
|
// 10. Create and return UndoMove object
|
||||||
UndoMove::new(
|
UndoMove::new(
|
||||||
mv,
|
mv,
|
||||||
opt_captured_piece,
|
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(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod r#move;
|
pub mod r#move;
|
||||||
pub mod square;
|
pub mod square;
|
||||||
pub mod movegen;
|
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;
|
pub const MOVE_FLAG_PROMO_Q_CAP: u16 = 0b1111_0000_0000_0000;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub struct Move(u16);
|
pub struct Move(pub(crate) u16);
|
||||||
|
|
||||||
impl Move {
|
impl Move {
|
||||||
pub fn new(from: Square, to: Square, flags: u16) -> Move {
|
pub fn new(from: Square, to: Square, flags: u16) -> Move {
|
||||||
|
|
@ -51,7 +51,6 @@ impl Move {
|
||||||
from as u16)
|
from as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_flags(&self) -> u16 {
|
pub fn get_flags(&self) -> u16 {
|
||||||
self.0 & MOVE_FLAG_MASK
|
self.0 & MOVE_FLAG_MASK
|
||||||
|
|
@ -64,58 +63,10 @@ impl Move {
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_to(&self) -> Square {
|
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]
|
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 {
|
pub struct MoveList {
|
||||||
moves: [Move; 256],
|
moves: [Move; 256],
|
||||||
count: usize,
|
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 {
|
pub struct UndoMove {
|
||||||
mv: Move,
|
mv: Move,
|
||||||
captured_piece: Option<PieceType>,
|
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