use bitvec::prelude::*; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Piece { Pawn, Knight, Bishop, Rook, Queen, King, } impl Piece { fn from_char(ch: char) -> Option { Some(match ch { 'N' | '♘' | '♞' => Self::Knight, 'B' | '♗' | '♝' => Self::Bishop, 'R' | '♖' | '♜' => Self::Rook, 'Q' | '♕' | '♛' => Self::Queen, 'K' | '♔' | '♚' => Self::King, 'P' | '♙' | '♟' => Self::Pawn, _ => return None, }) } fn value(self) -> i32 { match self { Piece::Pawn => 1, Piece::Knight | Piece::Bishop => 3, Piece::Rook => 5, Piece::Queen => 9, Piece::King => 0, } } } #[derive(Clone, Copy, PartialEq, Eq, Default)] struct BitBoard { backing: u64, } impl BitBoard { fn get_square(&self, file: u8, rank: u8) -> bool { self.backing & (rank * 8 + file) as u64 != 0 } fn set_square(&mut self, file: u8, rank: u8) { self.backing &= (rank * 8 + file) as u64 } fn del_square(&mut self, file: u8, rank: u8) { self.backing &= !((rank * 8 + file) as u64) } fn toggle_square(&mut self, file: u8, rank: u8) { self.backing ^= (rank * 8 + file) as u64 } fn value(&self) -> u32 { self.backing.count_ones() } fn or(self, other: Self) -> Self { Self { backing: self.backing | other.backing, } } } struct Move { source_rank: u8, dest_rank: u8, source_file: u8, dest_file: u8, } pub struct Board { white_to_move: bool, white_pawns: BitBoard, white_knights: BitBoard, white_bishops: BitBoard, white_rooks: BitBoard, white_queens: BitBoard, white_king: BitBoard, black_pawns: BitBoard, black_knights: BitBoard, black_bishops: BitBoard, black_rooks: BitBoard, black_queens: BitBoard, black_king: BitBoard, } impl Board { pub fn new() -> Self { let white_pawns = BitBoard { backing: 0xff << 1 * 8, }; let black_pawns = BitBoard { backing: 0xff << 6 * 8, }; let white_knights = BitBoard { backing: 0b01000010, }; let black_knights = BitBoard { backing: 0b01000010 << 7 * 8, }; let white_bishops = BitBoard { backing: 0b00100100, }; let black_bishops = BitBoard { backing: 0b00100100 << 7 * 8, }; let white_rooks = BitBoard { backing: 0b10000001, }; let black_rooks = BitBoard { backing: 0b10000001 << 7 * 8, }; let white_queens = BitBoard { backing: 0b00001000, }; let black_queens = BitBoard { backing: 0b00001000 << 7 * 8, }; let white_king = BitBoard { backing: 0b00010000, }; let black_king = BitBoard { backing: 0b00010000 << 7 * 8, }; Self { white_to_move: true, white_pawns, white_knights, white_bishops, white_rooks, white_queens, white_king, black_pawns, black_knights, black_bishops, black_rooks, black_queens, black_king, } } fn material(&self) -> i32 { let white_value = self.white_pawns.value() + self.white_knights.value() * 3 + self.white_bishops.value() * 3 + self.white_rooks.value() * 5 + self.white_queens.value() * 9; let black_value = self.black_pawns.value() + self.black_knights.value() * 3 + self.black_bishops.value() * 3 + self.black_rooks.value() * 5 + self.black_queens.value() * 9; white_value as i32 - black_value as i32 } fn white_pieces(&self) -> BitBoard { self.white_pawns .or(self.white_knights) .or(self.white_bishops) .or(self.white_rooks) .or(self.white_queens) .or(self.white_king) } fn black_pieces(&self) -> BitBoard { self.black_pawns .or(self.black_knights) .or(self.black_bishops) .or(self.black_rooks) .or(self.black_queens) .or(self.black_king) } /* pub fn make_move(&mut self, algebra: &str) { let (mover, non_mover) = self.movers_or_not(); if algebra == "O-O" || algebra == "0-0" { mover .iter_mut() .find(|lp| lp.piece == Piece::King) .unwrap() .file = 6; //castles kingside mover .iter_mut() .find(|lp| lp.piece == Piece::Rook && lp.file == 7) .unwrap() .file = 5; } else if algebra == "O-O-O" || algebra == "0-0-0" { mover .iter_mut() .find(|lp| lp.piece == Piece::King) .unwrap() .file = 2; //castles queenside mover .iter_mut() .find(|lp| lp.piece == Piece::Rook && lp.file == 0) .unwrap() .file = 3; } else { let mut move_segments = vec![]; let mut iter = algebra.chars(); while let Some(ch) = iter.next() { if let Some(piece) = Piece::from_char(ch) { move_segments.push(MoveSegment::Piece(piece)); } else if let Some(rank) = ch.to_digit(10) { move_segments.push(MoveSegment::File(rank as usize)); } else if ch >= 'a' && ch <= 'h' { move_segments.push(MoveSegment::File(ch as usize - 'a' as usize)); } else if ch == 'x' { move_segments.push(MoveSegment::Captures) } else if ch == '=' && let Some(next) = iter.next() // if given bad notation this breaks, but the notation was unparseable anyway && let Some(piece) = Piece::from_char(next) { move_segments.push(MoveSegment::Promotion(piece)); } } if move_segments.len() == 2 && let MoveSegment::File(file) = move_segments[0] && let MoveSegment::Rank(rank) = move_segments[1] { self.move_unambiguous(Piece::Pawn, None, None, rank, file); } } } fn move_unambiguous( &mut self, piece: Piece, source_rank: Option, source_file: Option, dest_rank: usize, dest_file: usize, ) { let piece_to_move = (if self.white_to_move { &mut self.white } else { &mut self.black }) .iter_mut() .filter(|lp| lp.piece == piece) .filter(|lp| { source_rank.is_none_or(|rank| rank == lp.rank) && source_file.is_none_or(|file| file == lp.file) }) .find(|lp| { self.valid_moves(lp.clone()) .contains(&(dest_file, dest_rank)) }) .unwrap(); self.capture_if_possible(dest_file, dest_rank); piece_to_move.rank = dest_rank; piece_to_move.file = dest_file; }*/ fn pawn_moves(&self, file: u8, rank: u8) -> Vec<(u8, u8)> { let white = self.white_pieces(); let black = self.black_pieces(); let white_moving = self.white_pawns.get_square(file, rank); let either = white.or(black); let mut moves = vec![]; if white_moving { let (open_squares, _) = self.open_squares(file, rank, Direction::North); if (open_squares) > 0 { moves.push((file, rank + 1)); } if rank == 1 && open_squares > 1 { moves.push((file, rank + 2)); } if let (0, true) = self.open_squares(file, rank, Direction::Northeast) { moves.push((file + 1, rank + 1)) } if let (0, true) = self.open_squares(file, rank, Direction::Northwest) { moves.push((file - 1, rank + 1)) } } else { let (open_squares, _) = self.open_squares(file, rank, Direction::South); if open_squares > 0 { moves.push((file, rank - 1)) } if rank == 6 && open_squares > 1 { moves.push((file, rank - 2)) } if let (0, true) = self.open_squares(file, rank, Direction::Southeast) { moves.push((file + 1, rank - 1)) } if let (0, true) = self.open_squares(file, rank, Direction::Southwest) { moves.push((file - 1, rank - 1)) } } moves } /// returns number of squares and whether that's a capture fn open_squares(&self, mut file: u8, mut rank: u8, direction: Direction) -> (u8, bool) { let white = self.white_pieces(); let black = self.black_pieces(); let white_playing = white.get_square(file, rank); let mut count = 0; loop { match direction { Direction::North => { if rank == 7 { return (count, false); } else { rank += 1 } } Direction::Northeast => { if rank == 7 || file == 7 { return (count, false); } else { rank += 1; file += 1; } } Direction::East => todo!(), Direction::Southeast => todo!(), Direction::South => todo!(), Direction::Southwest => todo!(), Direction::West => todo!(), Direction::Northwest => todo!(), } if white.get_square(file, rank) { return (count, !white_playing); } else if black.get_square(file, rank) { return (count, white_playing); } else { count += 1; } } } fn valid_moves(&self, lp: &LocatedPiece) -> Vec<(usize, usize)> { let mut moves = vec![]; match lp.piece { Piece::Pawn => todo!(), //todo: en passant (requires a list of moves to keep track of whether the previous //move was one that is en passantable?) Piece::Knight => todo!(), Piece::Bishop => todo!(), Piece::Rook => todo!(), Piece::Queen => todo!(), Piece::King => todo!(), } moves.retain(|(file, rank)| !self.mover_is_in_check(file, rank)); moves } } #[derive(Clone, Copy, PartialEq, Eq)] enum Direction { North, Northeast, East, Southeast, South, Southwest, West, Northwest, } #[derive(Clone, Copy, PartialEq, Eq)] enum MoveSegment { Piece(Piece), Rank(usize), File(usize), Captures, Promotion(Piece), } #[cfg(test)] mod tests { use super::*; #[test] fn new_board_zero_material() { let board = Board::new(); assert_eq!(board.material(), 0) } #[test] fn white_pieces_material_correct() { let mut board = Board::new(); board.black = vec![]; assert_eq!(board.material(), 8 + 5 * 2 + 3 * 4 + 9) } }