1use crate::errors::*;
2use crate::moves::position_move::{
3 Direction, Position, PositionMove, DIRECTION_OFFSETS, KNIGHT_DIRECTION_OFFSETS,
4};
5use crate::piece::{piece_type::*, Piece};
6use crate::piece_color::PieceColor;
7use crate::uci_move::{UciMove, UciMoveType, NON_PAWN_SYMBOLS};
8use anyhow::{anyhow, Result};
9use arrayvec::ArrayVec;
10use std::borrow::BorrowMut;
11use std::cmp::min;
12use std::fmt::{Debug, Formatter};
13use std::ops::{Deref, DerefMut, Sub};
14
15const RANKS: [char; 8] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
16const FILES: [char; 8] = ['1', '2', '3', '4', '5', '6', '7', '8'];
17
18#[derive(Clone, Copy)]
19pub struct BoardMap {
20 squares: [[Piece; 8]; 8],
21 active_color: PieceColor,
22 black_king_moved: bool,
23 white_king_moved: bool,
24 black_queenside_rook_moved: bool,
25 black_kingside_rook_moved: bool,
26 white_queenside_rook_moved: bool,
27 white_kingside_rook_moved: bool,
28 white_king_pos: Position,
29 black_king_pos: Position,
30}
31
32impl Default for BoardMap {
33 fn default() -> Self {
34 let squares = [[Piece(0); 8]; 8];
35
36 Self {
37 squares,
38 active_color: PieceColor::White,
39 black_king_moved: false,
40 white_king_moved: false,
41 black_queenside_rook_moved: false,
42 black_kingside_rook_moved: false,
43 white_queenside_rook_moved: false,
44 white_kingside_rook_moved: false,
45 white_king_pos: [0, 0],
46 black_king_pos: [0, 0],
47 }
48 }
49}
50
51impl BoardMap {
52 pub fn empty() -> Self {
53 BoardMap::default()
54 }
55 pub fn starting() -> Self {
57 BoardMap::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
58 }
59 pub fn from_fen(fen: impl Into<String>) -> Self {
60 let fen = fen.into();
61 let mut board = Self::default();
62 let sections = fen.split_whitespace().collect::<Vec<_>>();
63 let placement = sections[0].split('/').collect::<Vec<_>>();
64
65 let mut index = 0;
66 placement.iter().for_each(|x| {
67 for mut x in x.chars() {
68 let color = if x.is_uppercase() { WHITE } else { BLACK };
69
70 if !x.is_numeric() {
71 x.make_ascii_lowercase();
72 let rank = match x {
73 'p' => PAWN,
74 'r' => ROOK,
75 'b' => BISHOP,
76 'q' => QUEEN,
77 'k' => KING,
78 'n' => KNIGHT,
79 _ => 0,
80 };
81 let pos = [index / 8, index % 8];
82 board.squares[pos[0]][pos[1]] = Piece(color | rank);
83
84 if rank == KING {
86 if color == WHITE {
87 board.white_king_pos = pos;
88 } else {
89 board.black_king_pos = pos;
90 }
91 }
92
93 index += 1;
94 } else {
95 index += x.to_digit(10).unwrap() as usize;
96 }
97 }
98 });
99 board.active_color = if let Some(string) = sections.get(1) {
100 if let Some(c) = string.chars().next() {
101 match c {
102 'w' => PieceColor::White,
103 'b' => PieceColor::Black,
104 _ => unreachable!("FEN incorrect"),
105 }
106 } else {
107 PieceColor::White
108 }
109 } else {
110 PieceColor::White
111 };
112
113 board
114 }
115 pub fn get_fen(&self) -> String {
116 let mut fen = String::new();
117 let squares = self.squares;
118 for row in squares {
119 let mut space = 0;
120 for col in row {
121 if let Some(piece_type) = col.get_type() {
122 if space != 0 {
123 fen.push_str(space.to_string().as_str());
124 space = 0;
125 }
126
127 let mut piece_character = match piece_type {
128 PieceType::Rook => 'r',
129 PieceType::Pawn(_) => 'p',
130 PieceType::King => 'k',
131 PieceType::Queen => 'q',
132 PieceType::Bishop => 'b',
133 PieceType::Knight => 'n',
134 };
135
136 if col.get_color() == PieceColor::White {
137 piece_character = piece_character.to_ascii_uppercase();
138 }
139
140 fen.push(piece_character);
141 } else {
142 space += 1;
143 }
144 }
145 if space != 0 {
146 fen.push_str(space.to_string().as_str());
147 }
148 fen.push('/');
149 }
150
151 fen.pop();
152 fen
153 }
154 pub fn parse_uci_to_move(&mut self, mut uci: &str) -> Result<UciMove> {
155 let mate = uci.ends_with('#');
156 if mate {
157 let mut chars = uci.chars();
158 chars.next_back();
159 uci = chars.as_str();
160 }
161
162 let check = uci.ends_with('+');
163 if check {
164 let mut chars = uci.chars();
165 chars.next_back();
166 uci = chars.as_str();
167 }
168
169 let uci_move_type = if uci == "O-O" {
170 UciMoveType::CastleShort {
171 piece_color: self.active_color,
172 take: false,
173 check,
174 }
175 } else if uci == "O-O-O" {
176 UciMoveType::CastleLong {
177 piece_color: self.active_color,
178 take: false,
179 check,
180 }
181 } else if uci.len() == 2 {
182 UciMoveType::Pawn {
183 take: false,
184 check,
185 promotion: None, }
187 } else if RANKS.contains(&uci.chars().next().expect("Couldnt get next char of uci")) {
188 let specified_file =
189 FILES.contains(&uci.chars().nth(1).expect("Couldnt get second char of uci"));
190 let take = (!specified_file && uci.chars().nth(1) == Some('x'))
191 || (specified_file && uci.chars().nth(2) == Some('x'));
192
193 let promotion_position = uci.len() - 2;
194 let promotion = if uci.chars().nth(promotion_position) == Some('=') {
195 let piece_type = match uci
196 .chars()
197 .next_back()
198 .expect("Couldnt get last char of uci")
199 {
200 'K' => PieceType::King,
201 'N' => PieceType::Knight,
202 'Q' => PieceType::Queen,
203 'R' => PieceType::Rook,
204 'B' => PieceType::Bishop,
205 _ => return Err(PieceError::SymbolNotFound.into()),
206 };
207
208 Some(piece_type)
209 } else {
210 None
211 };
212
213 UciMoveType::Pawn {
214 take,
215 check,
216 promotion,
217 }
218 } else if uci.len() >= 3
219 && NON_PAWN_SYMBOLS.contains(&uci.chars().next().expect("Couldnt get next char of uci"))
220 {
221 let specified_rank = uci.len() > 3
233 && RANKS.contains(&uci.chars().nth(1).expect("couldnt get first char of uci"))
234 && (RANKS.contains(&uci.chars().nth(2).expect("couldnt get second char of uci"))
235 || RANKS.contains(&uci.chars().nth(3).expect("couldnt get third char of uci"))
236 || (uci.len() > 4
237 && RANKS.contains(
238 &uci.chars().nth(4).expect("couldnt get third char of uci"),
239 )));
240 let specified_file = uci.len() > 3
241 && (FILES.contains(&uci.chars().nth(1).expect("couldnt get first char of uci"))
242 || (RANKS
243 .contains(&uci.chars().nth(1).expect("couldnt get second char of uci"))
244 && FILES.contains(
245 &uci.chars().nth(2).expect("couldnt get first char of uci"),
246 )));
247 let take = uci.chars().nth(1) == Some('x')
248 || uci.chars().nth(2) == Some('x')
249 || uci.chars().nth(3) == Some('x');
250 let piece_type = match uci.chars().next().expect("Couldnt get next char of uci") {
251 'K' => PieceType::King,
252 'N' => PieceType::Knight,
253 'Q' => PieceType::Queen,
254 'R' => PieceType::Rook,
255 'B' => PieceType::Bishop,
256 _ => return Err(PieceError::SymbolNotFound.into()),
257 };
258
259 UciMoveType::Default {
260 specified_rank,
261 specified_file,
262 piece_type,
263 take,
264 check,
265 }
266 } else {
267 return Err(UciMoveError::InvalidUciMoveType.into());
268 };
269
270 let to: Position = match uci_move_type {
271 UciMoveType::Pawn { take, .. } => {
272 let to = if take {
273 uci.chars().skip(2).take(2).collect::<String>()
274 } else {
275 uci.chars().take(2).collect::<String>()
276 };
277 self.parse_uci_position_to_file_rank(to)?
278 }
279 UciMoveType::Default {
280 specified_rank,
281 specified_file,
282 take,
283 ..
284 } => {
285 let offset = specified_rank as usize + specified_file as usize + take as usize;
286 let to = uci.chars().skip(offset + 1).take(2).collect::<String>();
287 self.parse_uci_position_to_file_rank(to)?
288 }
289 UciMoveType::CastleLong { .. } => {
290 if self.get_active_color() == &PieceColor::White {
291 [7, 2]
292 } else {
293 [0, 2]
294 }
295 }
296 UciMoveType::CastleShort { .. } => {
297 if self.get_active_color() == &PieceColor::White {
298 [7, 6]
299 } else {
300 [0, 6]
301 }
302 }
303 };
304
305 let from: Position = match uci_move_type {
306 UciMoveType::Pawn { take, .. } => {
307 if take {
308 let rank = (uci.chars().next().ok_or(anyhow!("can't parse"))? as usize).sub(97);
309
310 let shift = match self.get_active_color() {
311 PieceColor::Black => 1,
312 PieceColor::White => -1,
313 };
314 let pos1 = [
317 ((to[0] as i32) - shift) as usize,
318 ((to[1] as i32) + 1) as usize,
319 ];
320 let pos2 = [
323 ((to[0] as i32) - shift) as usize,
324 ((to[1] as i32) - 1) as usize,
325 ];
326 let pos3 = [
329 ((to[0] as i32) - shift) as usize,
330 ((to[1] as i32) + 1) as usize,
331 ];
332 let pos4 = [
335 ((to[0] as i32) - shift) as usize,
336 ((to[1] as i32) - 1) as usize,
337 ];
338
339 self.verify_any_own_position(vec![pos1, pos2, pos3, pos4], Some(rank))?
340 } else {
341 let shift = match self.get_active_color() {
342 PieceColor::Black => 1,
343 PieceColor::White => -1,
344 };
345 let pos1 = [((to[0] as i32) - shift) as usize, to[1]];
346 let pos2 = [((to[0] as i32) - shift * 2) as usize, to[1]];
347 self.verify_any_own_position(vec![pos1, pos2], None)?
348 }
349 }
350 UciMoveType::Default {
351 piece_type,
352 specified_rank,
353 specified_file,
354 ..
355 } => {
356 let mut possible_positions = self.get_positions_from_type(&piece_type);
357
358 if specified_rank {
359 let specified_rank =
360 (uci.chars().nth(1).ok_or(anyhow!("Can't parse rank"))? as usize).sub(97);
361 possible_positions = possible_positions
362 .iter()
363 .filter_map(|&x| {
364 if x[1] == specified_rank {
365 return Some(x);
366 }
367 None
368 })
369 .collect::<Vec<_>>();
370 }
371
372 if specified_file {
373 let shift = if specified_rank { 2 } else { 1 };
374
375 let specified_file = 7 - uci
376 .chars()
377 .nth(shift)
378 .ok_or(anyhow!("can't pop last char of uci"))?
379 .to_string()
380 .parse::<usize>()?
381 .sub(1);
382 possible_positions = possible_positions
383 .iter()
384 .filter_map(|&x| {
385 if x[0] == specified_file {
386 return Some(x);
387 }
388 None
389 })
390 .collect::<Vec<_>>();
391 }
392
393 let mut found_position = None;
394 for position in possible_positions.iter() {
395 let moves = self.gen_legal_positions(*position);
396 if moves.contains(&to) {
397 found_position = Some(*position);
398 break;
399 }
400 }
401 if let Some(position) = found_position {
402 position
403 } else {
404 return Err(anyhow!(
405 "Couldn't find [from] position for {:?} with [to] {:?}",
406 uci,
407 to
408 ));
409 }
410 }
411 UciMoveType::CastleShort { .. } | UciMoveType::CastleLong { .. } => {
412 if self.get_active_color() == &PieceColor::White {
413 [7, 4]
414 } else {
415 [0, 4]
416 }
417 }
418 };
419
420 let en_passant = self.is_en_passant(from, to);
421
422 Ok((
423 uci_move_type,
424 PositionMove {
425 from,
426 to,
427 en_passant,
428 promotion: false,
429 ..Default::default()
430 },
431 ))
432 }
433 pub fn get_piece(&self, pos: Position) -> Piece {
434 self.squares[pos[0]][pos[1]]
435 }
436 pub fn find_piece(&self, piece_color: PieceColor, piece_type: PieceType) -> Vec<Position> {
437 let mut vec = vec![];
438 for (y, row) in self.squares.iter().enumerate() {
439 for (x, p) in row.iter().enumerate() {
440 if let Some(t) = p.get_type() {
441 if t == piece_type && p.get_color() == piece_color {
442 vec.push([y, x]);
443 }
444 }
445 }
446 }
447 vec
448 }
449 pub fn find_king(&self, piece_color: PieceColor) -> Option<Position> {
451 let pos = match piece_color {
452 PieceColor::White => self.white_king_pos,
453 PieceColor::Black => self.black_king_pos,
454 };
455 Some(pos)
456 }
457 pub fn get_piece_mut(&mut self, pos: Position) -> &mut Piece {
458 self.squares[pos[0]][pos[1]].borrow_mut()
459 }
460 pub fn get_active_color(&self) -> &PieceColor {
461 &self.active_color
462 }
463 pub fn get_active_pieces(&self) -> Vec<Position> {
464 let mut pieces = vec![];
465 for (i, row) in self.squares.iter().enumerate() {
466 for (j, piece) in row.iter().enumerate() {
467 if piece.get_color() == *self.get_active_color() {
468 pieces.push([i, j]);
469 }
470 }
471 }
472 pieces
473 }
474 pub fn set_piece(&mut self, on: Position, value: u32) {
475 self.squares[on[0]][on[1]] = Piece(value);
476 }
477 pub fn uci_move_turn(&mut self, uci_move: UciMove) -> Result<()> {
481 if let UciMoveType::CastleShort { .. } = uci_move.0 {
482 self.make_move(uci_move.1);
484 } else if let UciMoveType::CastleLong { .. } = uci_move.0 {
485 self.make_move(uci_move.1);
487 } else {
488 let position_move = uci_move.1;
489
490 let PositionMove { .. } = position_move;
491 self.is_valid_move(position_move)?;
492 self.make_move(position_move);
493
494 match uci_move.0 {
495 UciMoveType::Pawn { promotion, .. } => {
496 self.handle_convert_to_en_passantable(position_move);
497
498 if let Some(piece_type) = promotion {
499 let value = piece_type.to_value() | self.get_active_color().to_value();
500 self.set_piece(position_move.to, value);
501 }
502 }
503 UciMoveType::Default { piece_type, .. } => {
504 if piece_type == PieceType::King {
505 if self.get_active_color() == &PieceColor::White {
506 self.white_king_moved = false;
507 } else {
508 self.black_king_moved = false;
509 }
510 }
511 }
512 _ => {}
513 }
514 }
515
516 self.switch_active_color();
517 Ok(())
518 }
519 pub fn single_move_turn(&mut self, position_move: PositionMove) -> Result<()> {
523 let PositionMove { to, .. } = position_move;
524
525 self.is_valid_move(position_move)?;
526
527 self.make_move(position_move);
528
529 let piece_to = &mut self.get_piece(to);
530
531 let castle = false; if castle {
533 if self.active_color == PieceColor::White {
534 self.make_move(PositionMove::new([7, 0], [7, 3]));
535 } else {
536 self.make_move(PositionMove::new([0, 0], [0, 3]));
537 }
538 }
539
540 if let Some(PieceType::Pawn(_)) = piece_to.get_type() {
541 self.handle_convert_to_en_passantable(position_move);
542 }
543
544 self.switch_active_color();
545
546 Ok(())
547 }
548 pub fn is_valid_move(&self, piece_move: PositionMove) -> Result<()> {
550 let PositionMove { from, to, .. } = piece_move;
551 let piece_from = self.squares[from[0]][from[1]];
552 let piece_to = self.squares[to[0]][to[1]];
553
554 if !piece_from.is_piece() {
555 return Err(anyhow!(PieceMoveError::EmptySquare));
556 }
557
558 if piece_from.get_color() != self.active_color {
559 return Err(anyhow!(PieceMoveError::NotYourPiece(piece_from, from)));
560 }
561
562 if piece_to.is_piece() && piece_to.get_color() == self.active_color {
563 return Err(anyhow!(PieceMoveError::NotYourPiece(piece_to, to)));
564 }
565
566 let moves = self.gen_legal_positions(from);
567
568 if !moves.contains(&to) {
569 return Err(anyhow!(PieceMoveError::MoveNotFound));
570 }
571
572 Ok(())
573 }
574 pub fn is_hit(&self, pos: Position) -> bool {
575 let piece_on = self.get_piece(pos);
576 piece_on.is_piece() && piece_on.get_color() != self.active_color
577 }
578 pub fn gen_legal_positions(&self, from: Position) -> Vec<Position> {
580 let mut temp_board = *self;
581 let positions = temp_board.gen_to_positions(from);
582 let mut legal_positions = vec![];
583
584 for to in positions.into_iter() {
585 let en_passant = self.is_en_passant(from, to);
586 let promotion = self.is_promotion(from, to);
587 let last_piece = temp_board.squares[to[0]][to[1]].0;
588
589 let position_move = PositionMove {
590 from,
591 to,
592 en_passant,
593 promotion,
594 ..Default::default()
595 };
596 temp_board.make_move(position_move);
597 let next_moves = temp_board.gen_all_opponent_positions();
598 if !next_moves.iter().any(|m| {
599 let next_piece = temp_board.squares[m[0]][m[1]];
600 next_piece.is_piece()
601 && next_piece.get_color() == temp_board.active_color
602 && Some(PieceType::King) == next_piece.get_type()
603 }) {
604 legal_positions.push(to);
605 }
606
607 temp_board.undo_move(position_move, last_piece);
608 }
609 legal_positions
610 }
611 pub fn gen_to_positions(&self, from: Position) -> ArrayVec<Position, 32> {
613 let piece_from = self.squares[from[0]][from[1]];
614 if let Some(piece_type) = piece_from.get_type() {
615 return match piece_type {
616 PieceType::Bishop | PieceType::Rook | PieceType::Queen => {
617 self.gen_sliding(from, piece_type)
618 }
619 PieceType::Pawn(_) => self.gen_pawn(from),
620 PieceType::King => self.gen_king(from),
621 PieceType::Knight => self.gen_knight(from),
622 };
623 }
624
625 ArrayVec::new()
626 }
627 pub fn gen_sliding(&self, from: Position, piece_type: PieceType) -> ArrayVec<Position, 32> {
628 let piece_from = self.squares[from[0]][from[1]];
629 let piece_color = piece_from.get_color();
630 let mut positions = ArrayVec::new();
631 let start = if piece_type == PieceType::Bishop {
632 4
633 } else {
634 0
635 };
636 let end = if piece_type == PieceType::Rook { 4 } else { 8 };
637 for (direction, offset) in DIRECTION_OFFSETS.iter().enumerate().take(end).skip(start) {
638 for n in 0..self.len_to_edge(from, Direction::from(direction)) {
639 let index = from[0] * 8 + from[1];
640 let target_index = (index as i32 + offset * (n + 1) as i32).clamp(0, 63) as usize;
641 let target_position = [target_index / 8, target_index % 8];
642 let target_piece = self.squares[target_position[0]][target_position[1]];
643
644 if target_piece.is_piece() && target_piece.get_color() == piece_color {
645 break;
647 }
648 positions.push(target_position);
649 if target_piece.is_piece() && target_piece.get_color() != piece_color {
652 break;
654 }
655 }
656 }
657 positions
658 }
659 pub fn gen_king(&self, from: Position) -> ArrayVec<Position, 32> {
660 let piece_from = self.squares[from[0]][from[1]];
661 let piece_color = piece_from.get_color();
662 let mut positions = ArrayVec::new();
663 for (direction, offset) in DIRECTION_OFFSETS.iter().enumerate() {
664 let index = from[0] * 8 + from[1];
665 let target_index = index as i32 + offset;
666 if !(0..=63).contains(&target_index)
667 || self.len_to_edge(from, Direction::from(direction)) == 0
668 {
669 continue;
670 }
671 let target_move = [target_index as usize / 8, target_index as usize % 8];
672 let target_piece = self.squares[target_move[0]][target_move[1]];
673
674 if target_piece.is_piece() && target_piece.get_color() == piece_color {
675 continue;
677 }
678 positions.push(target_move);
679
680 if target_piece.is_piece() && target_piece.get_color() != piece_color {
681 continue;
683 }
684 }
685
686 if piece_color == PieceColor::Black {
688 if self.black_can_short_castle() {
689 positions.push([0, 6]);
690 }
691 if self.black_can_long_castle() {
692 positions.push([0, 2]);
693 }
694 } else {
695 if self.white_can_short_castle() {
696 positions.push([7, 6]);
697 }
698 if self.white_can_long_castle() {
699 positions.push([7, 2]);
700 }
701 }
702
703 positions
704 }
705 pub fn gen_pawn(&self, from: Position) -> ArrayVec<Position, 32> {
706 let piece_from = self.squares[from[0]][from[1]];
707 let mut moves = ArrayVec::new();
708 let piece_color = piece_from.get_color();
709 let is_black = piece_color == PieceColor::Black;
710 let shift = if is_black { 1 } else { -1 };
711
712 let vertical = (from[0] as i32 + shift) as usize;
714 if vertical < 8 {
715 let is_blocking = self.squares[vertical][from[1]].is_piece();
716 if !is_blocking {
717 moves.push([(from[0] as i32 + shift) as usize, from[1]]);
718 }
719
720 let vertical = (from[0] as i32 + shift * 2) as usize;
722 if vertical < 8 {
723 let is_blocking = is_blocking || self.squares[vertical][from[1]].is_piece();
724 if ((is_black && from[0] == 1) || (!is_black && from[0] == 6)) && !is_blocking {
725 moves.push([vertical, from[1]]);
726 }
727 }
728 }
729
730 if from[1] > 0 && from[1] < 8 {
734 let to_top_left_pos = [(from[0] as i32 + shift) as usize, from[1] - 1];
735 if to_top_left_pos[0] < 8 {
736 let to_top_left = self.get_piece(to_top_left_pos);
737 if to_top_left.is_piece() && to_top_left.get_color() != piece_color {
738 moves.push(to_top_left_pos);
739 }
740
741 let to_left = self.squares[from[0]][from[1] - 1];
745 if let Some(PieceType::Pawn(en_passantable)) = to_left.get_type() {
746 if en_passantable && to_left.get_color() != piece_color {
747 let to_en_passant = [(from[0] as i32 + shift) as usize, from[1] - 1];
748 moves.push(to_en_passant);
749 }
750 }
751 }
752 }
753
754 if from[1] < 7 {
758 let to_top_right_pos = [(from[0] as i32 + shift) as usize, from[1] + 1];
759 if to_top_right_pos[0] < 8 {
760 let to_top_right = self.squares[to_top_right_pos[0]][to_top_right_pos[1]];
761 if to_top_right.is_piece() && to_top_right.get_color() != piece_color {
762 moves.push(to_top_right_pos);
763 }
764
765 let to_right = self.squares[from[0]][from[1] + 1];
769 if let Some(PieceType::Pawn(en_passantable)) = to_right.get_type() {
770 if en_passantable && to_right.get_color() != piece_color {
771 let to_en_passant = [(from[0] as i32 + shift) as usize, from[1] + 1];
772 moves.push(to_en_passant);
773 }
774 }
775 }
776 }
777 moves
778 }
779 pub fn gen_knight(&self, from: Position) -> ArrayVec<Position, 32> {
780 let piece_from = self.squares[from[0]][from[1]];
781 let piece_color = piece_from.get_color();
782 KNIGHT_DIRECTION_OFFSETS
783 .iter()
784 .filter_map(|direction| {
785 let new_pos = [
786 (direction[0] + from[0] as i32) as usize,
787 (direction[1] + from[1] as i32) as usize,
788 ];
789 if new_pos[0] < 8 && new_pos[1] < 8 {
790 let target_piece = self.squares[new_pos[0]][new_pos[1]];
791 if !(target_piece.is_piece() && target_piece.get_color() == piece_color) {
792 return Some(new_pos);
793 }
794 }
795 None
796 })
797 .collect()
798 }
799 fn len_to_edge(&self, pos: Position, direction: Direction) -> usize {
800 let (rank, file) = (pos[0], pos[1]);
801 let north = 7 - rank;
802 let south = rank;
803 let west = file;
804 let east = 7 - file;
805
806 match direction {
807 Direction::North => north,
808 Direction::NorthEast => min(north, east),
809 Direction::East => east,
810 Direction::SouthEast => min(south, east),
811 Direction::South => south,
812 Direction::SouthWest => min(south, west),
813 Direction::West => west,
814 Direction::NorthWest => min(north, west),
815 }
816 }
817 pub fn make_move(&mut self, position_move: PositionMove) {
819 let PositionMove {
820 from,
821 to,
822 en_passant,
823 promotion,
824 ..
825 } = position_move;
826
827 let piece = self.get_piece(from);
828 let is_white = piece.get_color() == PieceColor::White;
829 let target_piece = self.get_piece(to);
830
831 if en_passant {
832 let shift = if is_white { -1 } else { 1 };
833 let to_step = [(to[0] as isize - shift) as usize, to[1]];
834 self.set_piece(to_step, 0);
835 }
836
837 if let Some(piece_type) = piece.get_type() {
840 match piece_type {
841 PieceType::King => {
842 if is_white {
843 self.white_king_moved = true;
844 self.white_king_pos = to;
845 } else {
846 self.black_king_moved = true;
847 self.black_king_pos = to;
848 }
849 }
850 PieceType::Rook => {
851 if is_white {
853 if from == [7, 0] {
854 self.white_queenside_rook_moved = true;
855 } else if from == [7, 7] {
856 self.white_kingside_rook_moved = true;
857 }
858 } else if from == [0, 0] {
859 self.black_queenside_rook_moved = true;
860 } else if from == [0, 7] {
861 self.black_kingside_rook_moved = true;
862 }
863 }
864 _ => {}
865 }
866 }
867
868 if target_piece.is_piece() {
870 if let Some(PieceType::Rook) = target_piece.get_type() {
871 match to {
872 [7, 0] => self.white_queenside_rook_moved = true,
873 [7, 7] => self.white_kingside_rook_moved = true,
874 [0, 0] => self.black_queenside_rook_moved = true,
875 [0, 7] => self.black_kingside_rook_moved = true,
876 _ => {}
877 }
878 }
879 }
880
881 if let Some(PieceType::King) = piece.get_type() {
883 if from[1] == 4 && to[1] == 6 {
885 let rook_from = [from[0], 7];
887 let rook_to = [from[0], 5];
888 let rook_piece = self.get_piece(rook_from).0;
889 self.set_piece(rook_to, rook_piece);
890 self.set_piece(rook_from, 0);
891 } else if from[1] == 4 && to[1] == 2 {
892 let rook_from = [from[0], 0];
894 let rook_to = [from[0], 3];
895 let rook_piece = self.get_piece(rook_from).0;
896 self.set_piece(rook_to, rook_piece);
897 self.set_piece(rook_from, 0);
898 }
899 }
900
901 if promotion {
902 let color = if is_white { WHITE } else { BLACK };
903 self.set_piece(to, position_move.promotion_piece | color);
904 } else {
905 self.set_piece(to, piece.0);
906 }
907 self.set_piece(from, 0);
908
909 for pos in self.get_piece_positions_by_type(PieceType::Pawn(true)) {
912 if to == pos {
913 continue;
914 }
915 self.get_piece_mut(pos).0 %= 32;
916 }
917
918 self.handle_convert_to_en_passantable(position_move);
920 }
921 pub fn undo_move(&mut self, piece_move: PositionMove, last_piece: u32) {
922 let PositionMove {
923 from,
924 to,
925 en_passant,
926 ..
927 } = piece_move;
928
929 let piece = self.get_piece(to);
930 let is_white = piece.get_color() == PieceColor::White;
931
932 if let Some(PieceType::King) = piece.get_type() {
934 if from[1] == 4 && to[1] == 6 {
935 let rook_from = [from[0], 5];
937 let rook_to = [from[0], 7];
938 let rook_piece = self.get_piece(rook_from).0;
939 self.set_piece(rook_to, rook_piece);
940 self.set_piece(rook_from, 0);
941 } else if from[1] == 4 && to[1] == 2 {
942 let rook_from = [from[0], 3];
944 let rook_to = [from[0], 0];
945 let rook_piece = self.get_piece(rook_from).0;
946 self.set_piece(rook_to, rook_piece);
947 self.set_piece(rook_from, 0);
948 }
949 }
950
951 self.set_piece(from, piece.0);
952 self.set_piece(to, last_piece);
953
954 if en_passant {
956 let shift = if is_white { -1 } else { 1 };
957 let captured_pawn_pos = [(to[0] as isize - shift) as usize, to[1]];
958 let opponent_color = if is_white { BLACK } else { WHITE };
960 self.set_piece(captured_pawn_pos, PAWN | opponent_color | 32);
961 }
962 }
963 pub fn gen_all_legal_moves(&self) -> Vec<PositionMove> {
965 let mut legal_moves = vec![];
966 for rank in 0..8 {
967 for file in 0..8 {
968 let piece = self.squares[rank][file];
969 if piece.is_piece() && piece.get_color() == self.active_color {
970 let from_move = [rank, file];
971 let to_moves = self.gen_legal_positions([rank, file]);
972 let moves = to_moves
973 .iter()
974 .map(|&to| PositionMove::new(from_move, to))
975 .collect::<Vec<_>>();
976 legal_moves.extend(moves);
977 }
978 }
979 }
980 legal_moves
981 }
982 pub fn gen_all_opponent_positions(&self) -> Vec<Position> {
983 let mut opponent_positions = vec![];
984 for rank in 0..8 {
985 for file in 0..8 {
986 let piece = self.squares[rank][file];
987 if piece.is_piece() && piece.get_color() != self.active_color {
988 let positions = self.gen_to_positions([rank, file]);
989 opponent_positions.extend(positions);
990 }
991 }
992 }
993 opponent_positions
994 }
995 pub fn is_en_passant(&self, from: Position, to: Position) -> bool {
996 if self.get_piece(to).is_piece() {
998 return false;
999 }
1000 let piece = self.get_piece(from);
1001 if let Some(PieceType::Pawn(_)) = piece.get_type() {
1002 let shift = match piece.get_color() {
1003 PieceColor::Black => 1,
1004 PieceColor::White => -1,
1005 };
1006 let step_pos = [(to[0] as isize - shift) as usize, to[1]];
1007 let step_piece = self.get_piece(step_pos);
1008 if step_piece.is_piece() && step_piece.get_color() != piece.get_color() {
1009 if let Some(PieceType::Pawn(_)) = step_piece.get_type() {
1010 return true;
1011 }
1012 }
1013 }
1014 false
1015 }
1016 pub fn is_promotion(&self, from: Position, to: Position) -> bool {
1017 let piece = self.get_piece(from);
1018 if let Some(piece_type) = piece.get_type() {
1019 return (piece_type == PieceType::Pawn(false) || piece_type == PieceType::Pawn(true))
1020 && (to[0] == 7 || to[0] == 0);
1021 }
1022
1023 false
1024 }
1025
1026 pub fn is_square_attacked_by(&self, square: Position, attacking_color: PieceColor) -> bool {
1028 let index = square[0] * 8 + square[1];
1029
1030 let pawn_direction = if attacking_color == PieceColor::White {
1032 8
1033 } else {
1034 -8
1035 };
1036 for file_offset in [-1, 1] {
1037 let pawn_index = index as i32 + pawn_direction + file_offset;
1038 if (0..64).contains(&pawn_index) {
1039 let pawn_pos = [pawn_index as usize / 8, pawn_index as usize % 8];
1040
1041 if (square[1] as i32 + file_offset) >= 0 && (square[1] as i32 + file_offset) < 8 {
1043 let attacker = self.get_piece(pawn_pos);
1044 if attacker.is_piece()
1045 && attacker.get_color() == attacking_color
1046 && matches!(attacker.get_type(), Some(PieceType::Pawn(_)))
1047 {
1048 return true;
1049 }
1050 }
1051 }
1052 }
1053
1054 for &offset in KNIGHT_DIRECTION_OFFSETS.iter() {
1056 let knight_rank = square[0] as i32 + offset[0];
1057 let knight_file = square[1] as i32 + offset[1];
1058 if (0..8).contains(&knight_rank) && (0..8).contains(&knight_file) {
1059 let attacker_pos = [knight_rank as usize, knight_file as usize];
1060 let attacker = self.get_piece(attacker_pos);
1061 if attacker.is_piece()
1062 && attacker.get_color() == attacking_color
1063 && attacker.get_type() == Some(PieceType::Knight)
1064 {
1065 return true;
1066 }
1067 }
1068 }
1069
1070 for (dir_idx, &offset) in DIRECTION_OFFSETS.iter().enumerate() {
1072 let direction = Direction::from(dir_idx);
1073 let is_diagonal = dir_idx >= 4; let max_distance = self.len_to_edge(square, direction);
1075
1076 for n in 1..=max_distance {
1077 let target_index = index as i32 + offset * n as i32;
1078 if !(0..64).contains(&target_index) {
1079 break;
1080 }
1081
1082 let check_pos = [target_index as usize / 8, target_index as usize % 8];
1083 let piece = self.get_piece(check_pos);
1084
1085 if piece.is_piece() {
1086 if piece.get_color() == attacking_color {
1087 match piece.get_type() {
1088 Some(PieceType::Queen) => return true,
1089 Some(PieceType::Bishop) if is_diagonal => return true,
1090 Some(PieceType::Rook) if !is_diagonal => return true,
1091 Some(PieceType::King) if n == 1 => return true,
1092 _ => {}
1093 }
1094 }
1095 break; }
1097 }
1098 }
1099
1100 false
1101 }
1102
1103 pub fn get_material_weight(&self) -> i32 {
1114 let mut res = 0;
1115 for row in self.squares.iter() {
1116 for piece in row.iter() {
1117 let mut value = piece.0;
1118 if value >= 32 {
1119 value %= 32;
1120 } let piece_value =
1122 if self.active_color == PieceColor::White && value > WHITE && value < BLACK {
1123 value - WHITE
1124 } else if self.active_color == PieceColor::Black && value > BLACK {
1125 value - BLACK
1126 } else {
1127 continue;
1128 };
1129
1130 let mut piece_weight = match piece_value {
1131 PAWN => 1,
1132 KNIGHT | BISHOP => 3,
1133 ROOK => 5,
1134 QUEEN => 9,
1135 KING => 200,
1136 _ => unimplemented!("{} is not a valid piece value", piece.0),
1137 };
1138 if self.active_color != piece.get_color() {
1139 piece_weight *= -1;
1140 }
1141 res += piece_weight;
1142 }
1143 }
1144 res
1145 }
1146 pub fn get_num_white_pieces(&self) -> i32 {
1147 self.squares.iter().fold(0, |res, row| {
1148 res + row
1149 .iter()
1150 .filter(|&&item| item.0 > WHITE && item.0 < BLACK)
1151 .count()
1152 }) as i32
1153 }
1154 pub fn get_num_black_pieces(&self) -> i32 {
1155 self.squares.iter().fold(0, |res, row| {
1156 res + row.iter().filter(|&&item| item.0 > BLACK).count()
1157 }) as i32
1158 }
1159 fn get_positions_from_type(&self, piece_type: &PieceType) -> Vec<Position> {
1160 let mut possible_positions = vec![];
1161 for (i, row) in self.squares.iter().enumerate() {
1162 for (j, position) in row.iter().enumerate() {
1163 if position.is_piece() {
1164 if let Some(pt) = position.get_type() {
1165 if piece_type == &pt && position.get_color() == self.active_color {
1166 possible_positions.push([i, j]);
1167 }
1168 }
1169 }
1170 }
1171 }
1172 possible_positions
1173 }
1174 pub fn switch_active_color(&mut self) {
1175 self.active_color = if self.active_color == PieceColor::Black {
1176 PieceColor::White
1177 } else {
1178 PieceColor::Black
1179 };
1180 }
1181 fn move_should_enable_en_passant(&self, piece_move: PositionMove) -> bool {
1182 let PositionMove { from, to, .. } = piece_move;
1183 let piece = self.get_piece(to);
1184 if let Some(PieceType::Pawn(_)) = piece.get_type() {
1185 if *self.get_active_color() == piece.get_color() {
1186 return match piece.get_color() {
1187 PieceColor::White => from[0] == 6 && to[0] == 4,
1188 PieceColor::Black => from[0] == 1 && to[0] == 3,
1189 };
1190 }
1191 }
1192 false
1193 }
1194
1195 fn parse_uci_position_to_file_rank(&self, mut position: String) -> Result<Position> {
1196 let file = 7 - position
1197 .pop()
1198 .ok_or(anyhow!("can't pop last char of uci"))?
1199 .to_string()
1200 .parse::<usize>()?
1201 .sub(1);
1202
1203 let rank = (position
1204 .pop()
1205 .ok_or(anyhow!("can't pop last char of uci"))? as usize)
1206 .sub(97);
1207
1208 Ok([file, rank])
1209 }
1210
1211 fn verify_any_own_position(
1212 &self,
1213 positions: Vec<Position>,
1214 rank: Option<usize>,
1215 ) -> Result<Position> {
1216 for pos in positions.iter() {
1217 if let Some(rank) = rank {
1218 if pos[1] != rank {
1219 continue;
1220 }
1221 }
1222 if self.own_position(pos) {
1223 return Ok(*pos);
1224 }
1225 }
1226 Err(anyhow!("Couldn't find [from] position for {:?}", positions))
1227 }
1228
1229 fn own_position(&self, pos: &Position) -> bool {
1230 let piece = self.get_piece(*pos);
1231 if piece.is_piece() && piece.get_color() == *self.get_active_color() {
1232 if let Some(PieceType::Pawn(_)) = piece.get_type() {
1233 return true;
1234 }
1235 }
1236 false
1237 }
1238
1239 fn handle_convert_to_en_passantable(&mut self, position_move: PositionMove) {
1240 let PositionMove { to, .. } = position_move;
1241 let should_enable_en_passant = self.move_should_enable_en_passant(position_move);
1242
1243 if should_enable_en_passant && self.get_piece(to).0 < 32 {
1244 self.get_piece_mut(to).0 += 32;
1245 }
1246 }
1247
1248 fn get_piece_positions_by_type(&self, piece_type: PieceType) -> Vec<Position> {
1249 let mut positions = Vec::with_capacity(64);
1250 for (y, row) in self.squares.iter().enumerate() {
1251 for (x, piece) in row.iter().enumerate() {
1252 if piece.get_type() == Some(piece_type) {
1253 positions.push([y, x]);
1254 }
1255 }
1256 }
1257 positions
1258 }
1259
1260 fn black_can_long_castle(&self) -> bool {
1261 if self.black_king_moved || self.black_queenside_rook_moved {
1262 return false;
1263 }
1264 let possible_king = self.get_piece([0, 4]);
1266 if possible_king.is_piece()
1267 && possible_king.is_black()
1268 && possible_king.get_type().unwrap() == PieceType::King
1269 {
1270 let row = self.squares[0];
1271 if !row[1..4].iter().all(|p| !p.is_piece()) {
1272 return false;
1273 }
1274 if self.is_square_attacked_by([0, 4], PieceColor::White)
1277 || self.is_square_attacked_by([0, 3], PieceColor::White)
1278 || self.is_square_attacked_by([0, 2], PieceColor::White)
1279 {
1280 return false;
1281 }
1282 return true;
1283 }
1284 false
1285 }
1286
1287 fn black_can_short_castle(&self) -> bool {
1288 if self.black_king_moved || self.black_kingside_rook_moved {
1289 return false;
1290 }
1291 let possible_king = self.get_piece([0, 4]);
1293 if possible_king.is_piece()
1294 && possible_king.is_black()
1295 && possible_king.get_type().unwrap() == PieceType::King
1296 {
1297 let row = self.squares[0];
1298 if !row[5..7].iter().all(|p| !p.is_piece()) {
1299 return false;
1300 }
1301 if self.is_square_attacked_by([0, 4], PieceColor::White)
1304 || self.is_square_attacked_by([0, 5], PieceColor::White)
1305 || self.is_square_attacked_by([0, 6], PieceColor::White)
1306 {
1307 return false;
1308 }
1309 return true;
1310 }
1311 false
1312 }
1313
1314 fn white_can_long_castle(&self) -> bool {
1315 if self.white_king_moved || self.white_queenside_rook_moved {
1316 return false;
1317 }
1318 let possible_king = self.get_piece([7, 4]);
1320 if possible_king.is_piece()
1321 && possible_king.is_white()
1322 && possible_king.get_type().unwrap() == PieceType::King
1323 {
1324 let row = self.squares[7];
1325 if !row[1..4].iter().all(|p| !p.is_piece()) {
1326 return false;
1327 }
1328 if self.is_square_attacked_by([7, 4], PieceColor::Black)
1331 || self.is_square_attacked_by([7, 3], PieceColor::Black)
1332 || self.is_square_attacked_by([7, 2], PieceColor::Black)
1333 {
1334 return false;
1335 }
1336 return true;
1337 }
1338 false
1339 }
1340
1341 fn white_can_short_castle(&self) -> bool {
1342 if self.white_king_moved || self.white_kingside_rook_moved {
1343 return false;
1344 }
1345 let possible_king = self.get_piece([7, 4]);
1347 if possible_king.is_piece()
1348 && possible_king.is_white()
1349 && possible_king.get_type().unwrap() == PieceType::King
1350 {
1351 let row = self.squares[7];
1352 if !row[5..7].iter().all(|p| !p.is_piece()) {
1353 return false;
1354 }
1355 if self.is_square_attacked_by([7, 4], PieceColor::Black)
1358 || self.is_square_attacked_by([7, 5], PieceColor::Black)
1359 || self.is_square_attacked_by([7, 6], PieceColor::Black)
1360 {
1361 return false;
1362 }
1363 return true;
1364 }
1365 false
1366 }
1367}
1368
1369impl Debug for BoardMap {
1370 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1371 for i in 0..8 {
1372 writeln!(f, "{} {:?}", 8 - i, self.squares[i]).unwrap();
1373 }
1374 writeln!(f, " a b c d e f g h").unwrap();
1375 writeln!(f, "fen: {}", self.get_fen()).unwrap();
1376 writeln!(
1377 f,
1378 "{}'s turn",
1379 match self.active_color {
1380 PieceColor::Black => "black",
1381 PieceColor::White => "white",
1382 }
1383 )
1384 .unwrap();
1385
1386 Ok(())
1387 }
1388}
1389
1390impl Deref for BoardMap {
1391 type Target = [[Piece; 8]; 8];
1392
1393 fn deref(&self) -> &Self::Target {
1394 &self.squares
1395 }
1396}
1397
1398impl DerefMut for BoardMap {
1399 fn deref_mut(&mut self) -> &mut Self::Target {
1400 &mut self.squares
1401 }
1402}