cleanroom implementation of a chess engine. doesn't work :)
at trunk 11 kB view raw
1use bitvec::prelude::*; 2#[derive(Debug, Clone, Copy, PartialEq, Eq)] 3enum Piece { 4 Pawn, 5 Knight, 6 Bishop, 7 Rook, 8 Queen, 9 King, 10} 11 12impl Piece { 13 fn from_char(ch: char) -> Option<Self> { 14 Some(match ch { 15 'N' | '♘' | '♞' => Self::Knight, 16 'B' | '♗' | '♝' => Self::Bishop, 17 'R' | '♖' | '♜' => Self::Rook, 18 'Q' | '♕' | '♛' => Self::Queen, 19 'K' | '♔' | '♚' => Self::King, 20 'P' | '♙' | '♟' => Self::Pawn, 21 _ => return None, 22 }) 23 } 24 fn value(self) -> i32 { 25 match self { 26 Piece::Pawn => 1, 27 Piece::Knight | Piece::Bishop => 3, 28 Piece::Rook => 5, 29 Piece::Queen => 9, 30 Piece::King => 0, 31 } 32 } 33} 34 35#[derive(Clone, Copy, PartialEq, Eq, Default)] 36struct BitBoard { 37 backing: u64, 38} 39impl BitBoard { 40 fn get_square(&self, file: u8, rank: u8) -> bool { 41 self.backing & (rank * 8 + file) as u64 != 0 42 } 43 fn set_square(&mut self, file: u8, rank: u8) { 44 self.backing &= (rank * 8 + file) as u64 45 } 46 fn del_square(&mut self, file: u8, rank: u8) { 47 self.backing &= !((rank * 8 + file) as u64) 48 } 49 fn toggle_square(&mut self, file: u8, rank: u8) { 50 self.backing ^= (rank * 8 + file) as u64 51 } 52 fn value(&self) -> u32 { 53 self.backing.count_ones() 54 } 55 fn or(self, other: Self) -> Self { 56 Self { 57 backing: self.backing | other.backing, 58 } 59 } 60} 61 62struct Move { 63 source_rank: u8, 64 dest_rank: u8, 65 source_file: u8, 66 dest_file: u8, 67} 68 69pub struct Board { 70 white_to_move: bool, 71 72 white_pawns: BitBoard, 73 white_knights: BitBoard, 74 white_bishops: BitBoard, 75 white_rooks: BitBoard, 76 white_queens: BitBoard, 77 white_king: BitBoard, 78 79 black_pawns: BitBoard, 80 black_knights: BitBoard, 81 black_bishops: BitBoard, 82 black_rooks: BitBoard, 83 black_queens: BitBoard, 84 black_king: BitBoard, 85} 86 87impl Board { 88 pub fn new() -> Self { 89 let white_pawns = BitBoard { 90 backing: 0xff << 1 * 8, 91 }; 92 let black_pawns = BitBoard { 93 backing: 0xff << 6 * 8, 94 }; 95 let white_knights = BitBoard { 96 backing: 0b01000010, 97 }; 98 let black_knights = BitBoard { 99 backing: 0b01000010 << 7 * 8, 100 }; 101 let white_bishops = BitBoard { 102 backing: 0b00100100, 103 }; 104 let black_bishops = BitBoard { 105 backing: 0b00100100 << 7 * 8, 106 }; 107 let white_rooks = BitBoard { 108 backing: 0b10000001, 109 }; 110 let black_rooks = BitBoard { 111 backing: 0b10000001 << 7 * 8, 112 }; 113 let white_queens = BitBoard { 114 backing: 0b00001000, 115 }; 116 let black_queens = BitBoard { 117 backing: 0b00001000 << 7 * 8, 118 }; 119 let white_king = BitBoard { 120 backing: 0b00010000, 121 }; 122 let black_king = BitBoard { 123 backing: 0b00010000 << 7 * 8, 124 }; 125 Self { 126 white_to_move: true, 127 white_pawns, 128 white_knights, 129 white_bishops, 130 white_rooks, 131 white_queens, 132 white_king, 133 134 black_pawns, 135 black_knights, 136 black_bishops, 137 black_rooks, 138 black_queens, 139 black_king, 140 } 141 } 142 fn material(&self) -> i32 { 143 let white_value = self.white_pawns.value() 144 + self.white_knights.value() * 3 145 + self.white_bishops.value() * 3 146 + self.white_rooks.value() * 5 147 + self.white_queens.value() * 9; 148 let black_value = self.black_pawns.value() 149 + self.black_knights.value() * 3 150 + self.black_bishops.value() * 3 151 + self.black_rooks.value() * 5 152 + self.black_queens.value() * 9; 153 white_value as i32 - black_value as i32 154 } 155 fn white_pieces(&self) -> BitBoard { 156 self.white_pawns 157 .or(self.white_knights) 158 .or(self.white_bishops) 159 .or(self.white_rooks) 160 .or(self.white_queens) 161 .or(self.white_king) 162 } 163 164 fn black_pieces(&self) -> BitBoard { 165 self.black_pawns 166 .or(self.black_knights) 167 .or(self.black_bishops) 168 .or(self.black_rooks) 169 .or(self.black_queens) 170 .or(self.black_king) 171 } /* 172 pub fn make_move(&mut self, algebra: &str) { 173 let (mover, non_mover) = self.movers_or_not(); 174 if algebra == "O-O" || algebra == "0-0" { 175 mover 176 .iter_mut() 177 .find(|lp| lp.piece == Piece::King) 178 .unwrap() 179 .file = 6; //castles kingside 180 mover 181 .iter_mut() 182 .find(|lp| lp.piece == Piece::Rook && lp.file == 7) 183 .unwrap() 184 .file = 5; 185 } else if algebra == "O-O-O" || algebra == "0-0-0" { 186 mover 187 .iter_mut() 188 .find(|lp| lp.piece == Piece::King) 189 .unwrap() 190 .file = 2; //castles queenside 191 mover 192 .iter_mut() 193 .find(|lp| lp.piece == Piece::Rook && lp.file == 0) 194 .unwrap() 195 .file = 3; 196 } else { 197 let mut move_segments = vec![]; 198 let mut iter = algebra.chars(); 199 while let Some(ch) = iter.next() { 200 if let Some(piece) = Piece::from_char(ch) { 201 move_segments.push(MoveSegment::Piece(piece)); 202 } else if let Some(rank) = ch.to_digit(10) { 203 move_segments.push(MoveSegment::File(rank as usize)); 204 } else if ch >= 'a' && ch <= 'h' { 205 move_segments.push(MoveSegment::File(ch as usize - 'a' as usize)); 206 } else if ch == 'x' { 207 move_segments.push(MoveSegment::Captures) 208 } else if ch == '=' 209 && let Some(next) = iter.next() // if given bad notation this breaks, but the notation was unparseable anyway 210 && let Some(piece) = Piece::from_char(next) 211 { 212 move_segments.push(MoveSegment::Promotion(piece)); 213 } 214 } 215 if move_segments.len() == 2 216 && let MoveSegment::File(file) = move_segments[0] 217 && let MoveSegment::Rank(rank) = move_segments[1] 218 { 219 self.move_unambiguous(Piece::Pawn, None, None, rank, file); 220 } 221 } 222 } 223 fn move_unambiguous( 224 &mut self, 225 piece: Piece, 226 source_rank: Option<usize>, 227 source_file: Option<usize>, 228 dest_rank: usize, 229 dest_file: usize, 230 ) { 231 let piece_to_move = (if self.white_to_move { 232 &mut self.white 233 } else { 234 &mut self.black 235 }) 236 .iter_mut() 237 .filter(|lp| lp.piece == piece) 238 .filter(|lp| { 239 source_rank.is_none_or(|rank| rank == lp.rank) 240 && source_file.is_none_or(|file| file == lp.file) 241 }) 242 .find(|lp| { 243 self.valid_moves(lp.clone()) 244 .contains(&(dest_file, dest_rank)) 245 }) 246 .unwrap(); 247 248 self.capture_if_possible(dest_file, dest_rank); 249 piece_to_move.rank = dest_rank; 250 piece_to_move.file = dest_file; 251 }*/ 252 fn pawn_moves(&self, file: u8, rank: u8) -> Vec<(u8, u8)> { 253 let white = self.white_pieces(); 254 let black = self.black_pieces(); 255 let white_moving = self.white_pawns.get_square(file, rank); 256 let either = white.or(black); 257 let mut moves = vec![]; 258 if white_moving { 259 let (open_squares, _) = self.open_squares(file, rank, Direction::North); 260 if (open_squares) > 0 { 261 moves.push((file, rank + 1)); 262 } 263 if rank == 1 && open_squares > 1 { 264 moves.push((file, rank + 2)); 265 } 266 if let (0, true) = self.open_squares(file, rank, Direction::Northeast) { 267 moves.push((file + 1, rank + 1)) 268 } 269 if let (0, true) = self.open_squares(file, rank, Direction::Northwest) { 270 moves.push((file - 1, rank + 1)) 271 } 272 } else { 273 let (open_squares, _) = self.open_squares(file, rank, Direction::South); 274 if open_squares > 0 { 275 moves.push((file, rank - 1)) 276 } 277 if rank == 6 && open_squares > 1 { 278 moves.push((file, rank - 2)) 279 } 280 281 if let (0, true) = self.open_squares(file, rank, Direction::Southeast) { 282 moves.push((file + 1, rank - 1)) 283 } 284 if let (0, true) = self.open_squares(file, rank, Direction::Southwest) { 285 moves.push((file - 1, rank - 1)) 286 } 287 } 288 moves 289 } 290 /// returns number of squares and whether that's a capture 291 fn open_squares(&self, mut file: u8, mut rank: u8, direction: Direction) -> (u8, bool) { 292 let white = self.white_pieces(); 293 let black = self.black_pieces(); 294 let white_playing = white.get_square(file, rank); 295 let mut count = 0; 296 loop { 297 match direction { 298 Direction::North => { 299 if rank == 7 { 300 return (count, false); 301 } else { 302 rank += 1 303 } 304 } 305 Direction::Northeast => { 306 if rank == 7 || file == 7 { 307 return (count, false); 308 } else { 309 rank += 1; 310 file += 1; 311 } 312 } 313 Direction::East => todo!(), 314 Direction::Southeast => todo!(), 315 Direction::South => todo!(), 316 Direction::Southwest => todo!(), 317 Direction::West => todo!(), 318 Direction::Northwest => todo!(), 319 } 320 if white.get_square(file, rank) { 321 return (count, !white_playing); 322 } else if black.get_square(file, rank) { 323 return (count, white_playing); 324 } else { 325 count += 1; 326 } 327 } 328 } 329 fn valid_moves(&self, lp: &LocatedPiece) -> Vec<(usize, usize)> { 330 let mut moves = vec![]; 331 match lp.piece { 332 Piece::Pawn => todo!(), //todo: en passant (requires a list of moves to keep track of whether the previous 333 //move was one that is en passantable?) 334 Piece::Knight => todo!(), 335 Piece::Bishop => todo!(), 336 Piece::Rook => todo!(), 337 Piece::Queen => todo!(), 338 Piece::King => todo!(), 339 } 340 moves.retain(|(file, rank)| !self.mover_is_in_check(file, rank)); 341 moves 342 } 343} 344 345#[derive(Clone, Copy, PartialEq, Eq)] 346enum Direction { 347 North, 348 Northeast, 349 East, 350 Southeast, 351 South, 352 Southwest, 353 West, 354 Northwest, 355} 356 357#[derive(Clone, Copy, PartialEq, Eq)] 358enum MoveSegment { 359 Piece(Piece), 360 Rank(usize), 361 File(usize), 362 Captures, 363 Promotion(Piece), 364} 365 366#[cfg(test)] 367mod tests { 368 use super::*; 369 370 #[test] 371 fn new_board_zero_material() { 372 let board = Board::new(); 373 assert_eq!(board.material(), 0) 374 } 375 376 #[test] 377 fn white_pieces_material_correct() { 378 let mut board = Board::new(); 379 board.black = vec![]; 380 assert_eq!(board.material(), 8 + 5 * 2 + 3 * 4 + 9) 381 } 382}