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}