1use std::fmt::Display;
2
3use crate::{
4 core::{piece_movement, Color, Piece, PieceType, Position},
5 errors::{
6 FenError, PositionBetweenError, PositionEmptyError, PositionOccupiedError,
7 UnalignedPositionsError,
8 },
9 parsing::fen::parse_minified_fen,
10 utils::movements::{diagonal_movement, linear_movement},
11};
12
13#[derive(Debug, Clone)]
17pub struct Board {
18 wpawns: u64,
20 bpawns: u64,
22 wknights: u64,
24 bknights: u64,
26 wbishops: u64,
28 bbishops: u64,
30 wrooks: u64,
32 brooks: u64,
34 wqueens: u64,
36 bqueens: u64,
38 wkings: u64,
40 bkings: u64,
42}
43
44impl Default for Board {
45 fn default() -> Board {
51 Board {
52 wpawns: 0x000000000000FF00,
53 bpawns: 0x00FF000000000000,
54 wknights: 0x0000000000000042,
55 bknights: 0x4200000000000000,
56 wbishops: 0x0000000000000024,
57 bbishops: 0x2400000000000000,
58 wrooks: 0x0000000000000081,
59 brooks: 0x8100000000000000,
60 wqueens: 0x0000000000000008,
61 bqueens: 0x0800000000000000,
62 wkings: 0x0000000000000010,
63 bkings: 0x1000000000000000,
64 }
65 }
66}
67
68impl Board {
69 pub fn new(fen: &str) -> Result<Board, FenError> {
79 Board::from_fen(fen)
80 }
81
82 pub fn empty() -> Board {
88 Board {
89 wpawns: 0,
90 bpawns: 0,
91 wknights: 0,
92 bknights: 0,
93 wbishops: 0,
94 bbishops: 0,
95 wrooks: 0,
96 brooks: 0,
97 wqueens: 0,
98 bqueens: 0,
99 wkings: 0,
100 bkings: 0,
101 }
102 }
103
104 pub fn from_fen(fen: &str) -> Result<Board, FenError> {
115 parse_minified_fen(fen)
116 }
117
118 pub fn is_ocupied(&self, pos: &Position) -> bool {
127 let bit = pos.to_bitboard();
128 (self.wpawns
129 | self.bpawns
130 | self.wknights
131 | self.bknights
132 | self.wbishops
133 | self.bbishops
134 | self.wrooks
135 | self.brooks
136 | self.wqueens
137 | self.bqueens
138 | self.wkings
139 | self.bkings)
140 & bit
141 != 0
142 }
143
144 pub fn get_piece(&self, pos: &Position) -> Option<Piece> {
153 let bit = pos.to_bitboard();
154
155 let piece_data = [
156 (self.wpawns, Color::White, PieceType::Pawn),
157 (self.bpawns, Color::Black, PieceType::Pawn),
158 (self.wknights, Color::White, PieceType::Knight),
159 (self.bknights, Color::Black, PieceType::Knight),
160 (self.wbishops, Color::White, PieceType::Bishop),
161 (self.bbishops, Color::Black, PieceType::Bishop),
162 (self.wrooks, Color::White, PieceType::Rook),
163 (self.brooks, Color::Black, PieceType::Rook),
164 (self.wqueens, Color::White, PieceType::Queen),
165 (self.bqueens, Color::Black, PieceType::Queen),
166 (self.wkings, Color::White, PieceType::King),
167 (self.bkings, Color::Black, PieceType::King),
168 ];
169
170 match piece_data.iter().find(|(board, _, _)| *board & bit != 0) {
171 Some((_, color, piece_type)) => Some(Piece::new(*color, *piece_type)),
172 None => None,
173 }
174 }
175
176 pub fn set_piece(&mut self, piece: Piece, pos: &Position) -> Result<(), PositionOccupiedError> {
187 if self.is_ocupied(pos) {
188 return Err(PositionOccupiedError::new(pos.clone()));
189 }
190 let bit = pos.to_bitboard();
191 match piece.piece_type {
192 PieceType::Pawn => match piece.color {
193 Color::White => self.wpawns |= bit,
194 Color::Black => self.bpawns |= bit,
195 },
196 PieceType::Knight => match piece.color {
197 Color::White => self.wknights |= bit,
198 Color::Black => self.bknights |= bit,
199 },
200 PieceType::Bishop => match piece.color {
201 Color::White => self.wbishops |= bit,
202 Color::Black => self.bbishops |= bit,
203 },
204 PieceType::Rook => match piece.color {
205 Color::White => self.wrooks |= bit,
206 Color::Black => self.brooks |= bit,
207 },
208 PieceType::Queen => match piece.color {
209 Color::White => self.wqueens |= bit,
210 Color::Black => self.bqueens |= bit,
211 },
212 PieceType::King => match piece.color {
213 Color::White => self.wkings |= bit,
214 Color::Black => self.bkings |= bit,
215 },
216 }
217 Ok(())
218 }
219
220 pub fn delete_piece(&mut self, pos: &Position) -> Result<Piece, PositionEmptyError> {
230 let piece = match self.get_piece(&pos) {
231 Some(piece) => piece,
232 None => return Err(PositionEmptyError::new(pos.clone())),
233 };
234
235 let bit = pos.to_bitboard();
236 match piece.piece_type {
237 PieceType::Pawn => match piece.color {
238 Color::White => self.wpawns &= !bit,
239 Color::Black => self.bpawns &= !bit,
240 },
241 PieceType::Knight => match piece.color {
242 Color::White => self.wknights &= !bit,
243 Color::Black => self.bknights &= !bit,
244 },
245 PieceType::Bishop => match piece.color {
246 Color::White => self.wbishops &= !bit,
247 Color::Black => self.bbishops &= !bit,
248 },
249 PieceType::Rook => match piece.color {
250 Color::White => self.wrooks &= !bit,
251 Color::Black => self.brooks &= !bit,
252 },
253 PieceType::Queen => match piece.color {
254 Color::White => self.wqueens &= !bit,
255 Color::Black => self.bqueens &= !bit,
256 },
257 PieceType::King => match piece.color {
258 Color::White => self.wkings &= !bit,
259 Color::Black => self.bkings &= !bit,
260 },
261 }
262 Ok(piece)
263 }
264
265 pub fn find(&self, piece_type: PieceType, color: Color) -> Vec<Position> {
275 let bitboard;
276 match piece_type {
277 PieceType::Pawn => match color {
278 Color::White => bitboard = self.wpawns,
279 Color::Black => bitboard = self.bpawns,
280 },
281 PieceType::Knight => match color {
282 Color::White => bitboard = self.wknights,
283 Color::Black => bitboard = self.bknights,
284 },
285 PieceType::Bishop => match color {
286 Color::White => bitboard = self.wbishops,
287 Color::Black => bitboard = self.bbishops,
288 },
289 PieceType::Rook => match color {
290 Color::White => bitboard = self.wrooks,
291 Color::Black => bitboard = self.brooks,
292 },
293 PieceType::Queen => match color {
294 Color::White => bitboard = self.wqueens,
295 Color::Black => bitboard = self.bqueens,
296 },
297 PieceType::King => match color {
298 Color::White => bitboard = self.wkings,
299 Color::Black => bitboard = self.bkings,
300 },
301 }
302 Position::from_bitboard(bitboard)
303 }
304
305 pub fn find_all(&self, color: Color) -> Vec<Position> {
314 let mut pieces = Vec::new();
315 pieces.append(&mut self.find(PieceType::Pawn, color));
316 pieces.append(&mut self.find(PieceType::Knight, color));
317 pieces.append(&mut self.find(PieceType::Bishop, color));
318 pieces.append(&mut self.find(PieceType::Rook, color));
319 pieces.append(&mut self.find(PieceType::Queen, color));
320 pieces.append(&mut self.find(PieceType::King, color));
321 pieces
322 }
323
324 pub fn move_piece(&mut self, from: &Position, to: &Position) -> Result<(), PositionEmptyError> {
335 let piece = self.delete_piece(from)?;
336
337 self.delete_piece(to).ok();
338
339 self.set_piece(piece, to).unwrap(); Ok(())
341 }
342
343 pub fn is_attacked(&self, pos: Position, color: Color) -> bool {
353 let pieces = self.find_all(color);
354 for piece in pieces {
355 if self.is_attacking(&piece, &pos).unwrap() {
356 return true;
358 }
359 }
360 false
361 }
362
363 pub fn can_move(
374 &self,
375 start_pos: &Position,
376 end_pos: &Position,
377 ) -> Result<bool, PositionEmptyError> {
378 let piece = self
379 .get_piece(start_pos)
380 .ok_or(PositionEmptyError::new(start_pos.clone()))?;
381
382 let captured_piece = self.get_piece(end_pos);
383 match captured_piece {
384 Some(captured_piece) => {
385 if captured_piece.color == piece.color {
386 return Ok(false);
387 }
388 }
389 None => (),
390 }
391
392 if piece_movement(&piece, start_pos, end_pos) {
393 return match piece.piece_type {
394 PieceType::Pawn => {
395 if start_pos.col == end_pos.col {
396 return Ok(!self.piece_between(start_pos, end_pos).unwrap()
397 && captured_piece.is_none()); }
399 Ok(captured_piece.is_some())
400 }
401 PieceType::Knight | PieceType::King => Ok(true),
402 PieceType::Bishop | PieceType::Rook | PieceType::Queen => {
403 Ok(!self.piece_between(start_pos, end_pos).unwrap()) }
405 };
406 }
407 Ok(false)
408 }
409
410 pub fn is_attacking(
423 &self,
424 start_pos: &Position,
425 end_pos: &Position,
426 ) -> Result<bool, PositionEmptyError> {
427 let piece = self
428 .get_piece(start_pos)
429 .ok_or(PositionEmptyError::new(start_pos.clone()))?;
430
431 if piece_movement(&piece, start_pos, end_pos) {
432 return match piece.piece_type {
433 PieceType::Pawn => Ok(diagonal_movement(start_pos, end_pos)),
434 PieceType::Knight | PieceType::King => Ok(true),
435 PieceType::Bishop | PieceType::Rook | PieceType::Queen => {
436 Ok(!self.piece_between(start_pos, end_pos).unwrap()) }
438 };
439 }
440 Ok(false)
441 }
442
443 pub fn piece_between(
454 &self,
455 from: &Position,
456 to: &Position,
457 ) -> Result<bool, PositionBetweenError> {
458 if !linear_movement(from, to) && !diagonal_movement(from, to) {
459 return Err(PositionBetweenError::from(UnalignedPositionsError::new(
460 from.clone(),
461 to.clone(),
462 )));
463 }
464
465 let direction = from.direction(to);
466 let mut pos = from.to_owned();
467
468 for _ in 0..7 {
469 pos = &pos + direction;
470 if pos == *to {
471 break;
472 }
473 if self.is_ocupied(&pos) {
474 return Ok(true);
475 }
476 }
477 Ok(false)
478 }
479}
480
481impl Display for Board {
482 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
483 let mut board = String::new();
484 for row in (0..8).rev() {
485 let mut empty = 0;
486 for col in 0..8 {
487 let pos = Position::new(col, row).unwrap(); let piece = self.get_piece(&pos);
489 match piece {
490 None => {
491 empty += 1;
492 }
493 Some(piece) => {
494 if empty > 0 {
495 board.push_str(&empty.to_string());
496 empty = 0;
497 }
498 board.push_str(&piece.to_string());
499 }
500 }
501 }
502 if empty > 0 {
503 board.push_str(&empty.to_string());
504 }
505 if row > 0 {
506 board.push('/');
507 }
508 }
509 write!(f, "{}", board)
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use crate::core::{Color, Piece, PieceType, Position};
517
518 #[test]
519 fn test_default() {
520 let board = Board::default();
521 assert_eq!(
522 board.to_string(),
523 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
524 );
525 }
526
527 #[test]
528 fn test_from_fen() {
529 let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR").unwrap();
530 assert_eq!(
531 board.to_string(),
532 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
533 );
534 }
535
536 #[test]
537 fn test_to_fen() {
538 let board = Board::default();
539 assert_eq!(
540 board.to_string(),
541 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
542 );
543 }
544
545 #[test]
546 fn test_set_piece() {
547 let mut board = Board::default();
548 let pos = Position::new(4, 2).unwrap();
549 let piece = Piece::new(Color::White, PieceType::Pawn);
550 board.set_piece(piece, &pos).unwrap();
551 assert_eq!(
552 board.to_string(),
553 "rnbqkbnr/pppppppp/8/8/8/4P3/PPPPPPPP/RNBQKBNR"
554 );
555 }
556
557 #[test]
558 fn test_set_piece_occupied() {
559 let mut board = Board::default();
560 let pos = Position::new(0, 1).unwrap();
561 let piece = Piece::new(Color::White, PieceType::Pawn);
562 let result = board.set_piece(piece, &pos);
563 assert!(result.is_err());
564 }
565
566 #[test]
567 fn test_delete_piece() {
568 let mut board = Board::default();
569 let pos = Position::new(0, 0).unwrap();
570 board.delete_piece(&pos).unwrap();
571 assert_eq!(
572 board.to_string(),
573 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/1NBQKBNR"
574 );
575 }
576
577 #[test]
578 fn test_get_piece() {
579 let board = Board::default();
580 let pos = Position::new(0, 0).unwrap();
581 let piece = board.get_piece(&pos).unwrap();
582 assert_eq!(piece.to_string(), "R");
583 }
584
585 #[test]
586 fn test_is_ocupied() {
587 let board = Board::default();
588 let pos = Position::new(0, 0).unwrap();
589 assert!(board.is_ocupied(&pos));
590
591 let pos = Position::new(0, 2).unwrap();
592 assert!(!board.is_ocupied(&pos));
593 }
594
595 #[test]
596 fn test_find() {
597 let board = Board::default();
598 let pieces = board.find(PieceType::Pawn, Color::White);
599 assert_eq!(pieces.len(), 8);
600 }
601
602 #[test]
603 fn test_find_all() {
604 let board = Board::default();
605 let pieces = board.find_all(Color::White);
606 assert_eq!(pieces.len(), 16);
607 }
608
609 #[test]
610 fn test_move_piece() {
611 let mut board = Board::default();
612 let from = Position::new(4, 1).unwrap();
613 let to = Position::new(4, 3).unwrap();
614 board.move_piece(&from, &to).unwrap();
615 assert_eq!(
616 board.to_string(),
617 "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR"
618 );
619 }
620
621 #[test]
622 fn test_is_attacked() {
623 let board = Board::default();
624
625 let pos = Position::from_string("e3").unwrap();
626 assert!(board.is_attacked(pos, Color::White));
627
628 let pos = Position::from_string("e4").unwrap();
629 assert!(!board.is_attacked(pos, Color::White));
630 }
631
632 #[test]
633 fn test_piece_between() {
634 let board = Board::default();
635 let from = Position::new(0, 0).unwrap();
636 let to = Position::new(0, 6).unwrap();
637 assert!(board.piece_between(&from, &to).unwrap());
638 }
639}