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 std::borrow::BorrowMut;
10use std::cmp::min;
11use std::fmt::{Debug, Formatter};
12use std::ops::{Deref, DerefMut, Sub};
13
14const RANKS: [char; 8] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
15const FILES: [char; 8] = ['1', '2', '3', '4', '5', '6', '7', '8'];
16
17#[derive(Clone, Copy)]
18pub struct BoardMap {
19 squares: [[Piece; 8]; 8],
20 active_color: PieceColor,
21 black_king_moved: bool,
22 white_king_moved: bool,
23}
24
25impl Default for BoardMap {
26 fn default() -> Self {
27 let squares = [[Piece(0); 8]; 8];
28
29 Self {
30 squares,
31 active_color: PieceColor::White,
32 black_king_moved: false,
33 white_king_moved: false,
34 }
35 }
36}
37
38impl BoardMap {
39 pub fn empty() -> Self {
40 BoardMap::default()
41 }
42 pub fn starting() -> Self {
44 BoardMap::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
45 }
46 pub fn from_fen(fen: impl Into<String>) -> Self {
47 let fen = fen.into();
48 let mut board = Self::default();
49 let sections = fen.split_whitespace().collect::<Vec<_>>();
50 let placement = sections[0].split('/').collect::<Vec<_>>();
51
52 let mut index = 0;
53 placement.iter().for_each(|x| {
54 for mut x in x.chars() {
55 let color = if x.is_uppercase() { WHITE } else { BLACK };
56
57 if !x.is_numeric() {
58 x.make_ascii_lowercase();
59 let rank = match x {
60 'p' => PAWN,
61 'r' => ROOK,
62 'b' => BISHOP,
63 'q' => QUEEN,
64 'k' => KING,
65 'n' => KNIGHT,
66 _ => 0,
67 };
68 board.squares[index / 8][index % 8] = Piece(color | rank);
69 index += 1;
70 } else {
71 index += x.to_digit(10).unwrap() as usize;
72 }
73 }
74 });
75 board.active_color = if let Some(string) = sections.get(1) {
76 if let Some(c) = string.chars().next() {
77 match c {
78 'w' => PieceColor::White,
79 'b' => PieceColor::Black,
80 _ => unreachable!("FEN incorrect"),
81 }
82 } else {
83 PieceColor::White
84 }
85 } else {
86 PieceColor::White
87 };
88
89 board
90 }
91 pub fn get_fen(&self) -> String {
92 let mut fen = String::new();
93 let squares = self.squares;
94 for row in squares {
95 let mut space = 0;
96 for col in row {
97 if let Some(piece_type) = col.get_type() {
98 if space != 0 {
99 fen.push_str(space.to_string().as_str());
100 space = 0;
101 }
102
103 let mut piece_character = match piece_type {
104 PieceType::Rook => 'r',
105 PieceType::Pawn(_) => 'p',
106 PieceType::King => 'k',
107 PieceType::Queen => 'q',
108 PieceType::Bishop => 'b',
109 PieceType::Knight => 'n',
110 };
111
112 if col.get_color() == PieceColor::White {
113 piece_character = piece_character.to_ascii_uppercase();
114 }
115
116 fen.push(piece_character);
117 } else {
118 space += 1;
119 }
120 }
121 if space != 0 {
122 fen.push_str(space.to_string().as_str());
123 }
124 fen.push('/');
125 }
126
127 fen.pop();
128 fen
129 }
130 pub fn parse_uci_to_move(&mut self, mut uci: &str) -> Result<UciMove> {
131 let mate = uci.ends_with('#');
132 if mate {
133 let mut chars = uci.chars();
134 chars.next_back();
135 uci = chars.as_str();
136 }
137
138 let check = uci.ends_with('+');
139 if check {
140 let mut chars = uci.chars();
141 chars.next_back();
142 uci = chars.as_str();
143 }
144
145 let uci_move_type = if uci == "O-O" {
146 UciMoveType::CastleShort {
147 piece_color: self.active_color,
148 take: false,
149 check,
150 }
151 } else if uci == "O-O-O" {
152 UciMoveType::CastleLong {
153 piece_color: self.active_color,
154 take: false,
155 check,
156 }
157 } else if uci.len() == 2 {
158 UciMoveType::Pawn {
159 take: false,
160 check,
161 promotion: None, }
163 } else if RANKS.contains(&uci.chars().next().expect("Couldnt get next char of uci")) {
164 let specified_file =
165 FILES.contains(&uci.chars().nth(1).expect("Couldnt get second char of uci"));
166 let take = (!specified_file && uci.chars().nth(1) == Some('x'))
167 || (specified_file && uci.chars().nth(2) == Some('x'));
168
169 let promotion_position = uci.len() - 2;
170 let promotion = if uci.chars().nth(promotion_position) == Some('=') {
171 let piece_type = match uci
172 .chars()
173 .next_back()
174 .expect("Couldnt get last char of uci")
175 {
176 'K' => PieceType::King,
177 'N' => PieceType::Knight,
178 'Q' => PieceType::Queen,
179 'R' => PieceType::Rook,
180 'B' => PieceType::Bishop,
181 _ => return Err(PieceError::SymbolNotFound.into()),
182 };
183
184 Some(piece_type)
185 } else {
186 None
187 };
188
189 UciMoveType::Pawn {
190 take,
191 check,
192 promotion,
193 }
194 } else if uci.len() >= 3
195 && NON_PAWN_SYMBOLS.contains(&uci.chars().next().expect("Couldnt get next char of uci"))
196 {
197 let specified_rank = uci.len() > 3
209 && RANKS.contains(&uci.chars().nth(1).expect("couldnt get first char of uci"))
210 && (RANKS.contains(&uci.chars().nth(2).expect("couldnt get second char of uci"))
211 || RANKS.contains(&uci.chars().nth(3).expect("couldnt get third char of uci"))
212 || (uci.len() > 4
213 && RANKS.contains(
214 &uci.chars().nth(4).expect("couldnt get third char of uci"),
215 )));
216 let specified_file = uci.len() > 3
217 && (FILES.contains(&uci.chars().nth(1).expect("couldnt get first char of uci"))
218 || (RANKS
219 .contains(&uci.chars().nth(1).expect("couldnt get second char of uci"))
220 && FILES.contains(
221 &uci.chars().nth(2).expect("couldnt get first char of uci"),
222 )));
223 let take = uci.chars().nth(1) == Some('x')
224 || uci.chars().nth(2) == Some('x')
225 || uci.chars().nth(3) == Some('x');
226 let piece_type = match uci.chars().next().expect("Couldnt get next char of uci") {
227 'K' => PieceType::King,
228 'N' => PieceType::Knight,
229 'Q' => PieceType::Queen,
230 'R' => PieceType::Rook,
231 'B' => PieceType::Bishop,
232 _ => return Err(PieceError::SymbolNotFound.into()),
233 };
234
235 UciMoveType::Default {
236 specified_rank,
237 specified_file,
238 piece_type,
239 take,
240 check,
241 }
242 } else {
243 return Err(UciMoveError::InvalidUciMoveType.into());
244 };
245
246 let to: Position = match uci_move_type {
247 UciMoveType::Pawn { take, .. } => {
248 let to = if take {
249 uci.chars().skip(2).take(2).collect::<String>()
250 } else {
251 uci.chars().take(2).collect::<String>()
252 };
253 self.parse_uci_position_to_file_rank(to)?
254 }
255 UciMoveType::Default {
256 specified_rank,
257 specified_file,
258 take,
259 ..
260 } => {
261 let offset = specified_rank as usize + specified_file as usize + take as usize;
262 let to = uci.chars().skip(offset + 1).take(2).collect::<String>();
263 self.parse_uci_position_to_file_rank(to)?
264 }
265 UciMoveType::CastleLong { .. } => {
266 if self.get_active_color() == &PieceColor::White {
267 [7, 2]
268 } else {
269 [0, 2]
270 }
271 }
272 UciMoveType::CastleShort { .. } => {
273 if self.get_active_color() == &PieceColor::White {
274 [7, 6]
275 } else {
276 [0, 6]
277 }
278 }
279 };
280
281 let from: Position = match uci_move_type {
282 UciMoveType::Pawn { take, .. } => {
283 if take {
284 let rank = (uci.chars().next().ok_or(anyhow!("can't parse"))? as usize).sub(97);
285
286 let shift = match self.get_active_color() {
287 PieceColor::Black => 1,
288 PieceColor::White => -1,
289 };
290 let pos1 = [
293 ((to[0] as i32) - shift) as usize,
294 ((to[1] as i32) + 1) as usize,
295 ];
296 let pos2 = [
299 ((to[0] as i32) - shift) as usize,
300 ((to[1] as i32) - 1) as usize,
301 ];
302 let pos3 = [
305 ((to[0] as i32) - shift) as usize,
306 ((to[1] as i32) + 1) as usize,
307 ];
308 let pos4 = [
311 ((to[0] as i32) - shift) as usize,
312 ((to[1] as i32) - 1) as usize,
313 ];
314
315 self.verify_any_own_position(vec![pos1, pos2, pos3, pos4], Some(rank))?
316 } else {
317 let shift = match self.get_active_color() {
318 PieceColor::Black => 1,
319 PieceColor::White => -1,
320 };
321 let pos1 = [((to[0] as i32) - shift) as usize, to[1]];
322 let pos2 = [((to[0] as i32) - shift * 2) as usize, to[1]];
323 self.verify_any_own_position(vec![pos1, pos2], None)?
324 }
325 }
326 UciMoveType::Default {
327 piece_type,
328 specified_rank,
329 specified_file,
330 ..
331 } => {
332 let mut possible_positions = self.get_positions_from_type(&piece_type);
333
334 if specified_rank {
335 let specified_rank =
336 (uci.chars().nth(1).ok_or(anyhow!("Can't parse rank"))? as usize).sub(97);
337 possible_positions = possible_positions
338 .iter()
339 .filter_map(|&x| {
340 if x[1] == specified_rank {
341 return Some(x);
342 }
343 None
344 })
345 .collect::<Vec<_>>();
346 }
347
348 if specified_file {
349 let shift = if specified_rank { 2 } else { 1 };
350
351 let specified_file = 7 - uci
352 .chars()
353 .nth(shift)
354 .ok_or(anyhow!("can't pop last char of uci"))?
355 .to_string()
356 .parse::<usize>()?
357 .sub(1);
358 possible_positions = possible_positions
359 .iter()
360 .filter_map(|&x| {
361 if x[0] == specified_file {
362 return Some(x);
363 }
364 None
365 })
366 .collect::<Vec<_>>();
367 }
368
369 let mut found_position = None;
370 for position in possible_positions.iter() {
371 let moves = self.gen_legal_positions(*position);
372 if moves.contains(&to) {
373 found_position = Some(*position);
374 break;
375 }
376 }
377 if let Some(position) = found_position {
378 position
379 } else {
380 return Err(anyhow!(
381 "Couldn't find [from] position for {:?} with [to] {:?}",
382 uci,
383 to
384 ));
385 }
386 }
387 UciMoveType::CastleShort { .. } | UciMoveType::CastleLong { .. } => {
388 if self.get_active_color() == &PieceColor::White {
389 [7, 4]
390 } else {
391 [0, 4]
392 }
393 }
394 };
395
396 let en_passant = self.is_en_passant(from, to);
397
398 Ok((
399 uci_move_type,
400 PositionMove {
401 from,
402 to,
403 en_passant,
404 promotion: false,
405 },
406 ))
407 }
408 pub fn get_piece(&self, pos: Position) -> Piece {
409 self.squares[pos[0]][pos[1]]
410 }
411 pub fn find_piece(&self, piece_color: PieceColor, piece_type: PieceType) -> Vec<Position> {
412 let mut vec = vec![];
413 for (y, row) in self.squares.iter().enumerate() {
414 for (x, p) in row.iter().enumerate() {
415 if let Some(t) = p.get_type() {
416 if t == piece_type && p.get_color() == piece_color {
417 vec.push([y, x]);
418 }
419 }
420 }
421 }
422 vec
423 }
424 pub fn get_piece_mut(&mut self, pos: Position) -> &mut Piece {
425 self.squares[pos[0]][pos[1]].borrow_mut()
426 }
427 pub fn get_active_color(&self) -> &PieceColor {
428 &self.active_color
429 }
430 pub fn get_active_pieces(&self) -> Vec<Position> {
431 let mut pieces = vec![];
432 for (i, row) in self.squares.iter().enumerate() {
433 for (j, piece) in row.iter().enumerate() {
434 if piece.get_color() == *self.get_active_color() {
435 pieces.push([i, j]);
436 }
437 }
438 }
439 pieces
440 }
441 pub fn set_piece(&mut self, on: Position, value: u32) {
442 self.squares[on[0]][on[1]] = Piece(value);
443 }
444 pub fn uci_move_turn(&mut self, uci_move: UciMove) -> Result<()> {
448 if let UciMoveType::CastleShort { piece_color, .. } = uci_move.0 {
449 self.make_move(uci_move.1);
450 if piece_color == PieceColor::White {
451 self.make_move(PositionMove::new([7, 7], [7, 5]));
452 } else {
453 self.make_move(PositionMove::new([0, 7], [0, 5]));
454 }
455 } else if let UciMoveType::CastleLong { piece_color, .. } = uci_move.0 {
456 self.make_move(uci_move.1);
457 if piece_color == PieceColor::White {
458 self.make_move(PositionMove::new([7, 0], [7, 3]));
459 } else {
460 self.make_move(PositionMove::new([0, 0], [0, 3]));
461 }
462 } else {
463 let position_move = uci_move.1;
464
465 let PositionMove { .. } = position_move;
466 self.is_valid_move(position_move)?;
467 self.make_move(position_move);
468
469 match uci_move.0 {
470 UciMoveType::Pawn { promotion, .. } => {
471 self.handle_convert_to_en_passantable(position_move);
472
473 if let Some(piece_type) = promotion {
474 let value = piece_type.to_value() | self.get_active_color().to_value();
475 self.set_piece(position_move.to, value);
476 }
477 }
478 UciMoveType::Default { piece_type, .. } => {
479 if piece_type == PieceType::King {
480 if self.get_active_color() == &PieceColor::White {
481 self.white_king_moved = false;
482 } else {
483 self.black_king_moved = false;
484 }
485 }
486 }
487 _ => {}
488 }
489 }
490
491 self.switch_active_color();
492 Ok(())
493 }
494 pub fn single_move_turn(&mut self, position_move: PositionMove) -> Result<()> {
498 let PositionMove { to, .. } = position_move;
499
500 self.is_valid_move(position_move)?;
501
502 self.make_move(position_move);
503
504 let piece_to = &mut self.get_piece(to);
505
506 let castle = false; if castle {
508 if self.active_color == PieceColor::White {
509 self.make_move(PositionMove::new([7, 0], [7, 3]));
510 } else {
511 self.make_move(PositionMove::new([0, 0], [0, 3]));
512 }
513 }
514
515 if let Some(PieceType::Pawn(_)) = piece_to.get_type() {
516 self.handle_convert_to_en_passantable(position_move);
517 }
518
519 self.switch_active_color();
520
521 Ok(())
522 }
523 pub fn is_valid_move(&self, piece_move: PositionMove) -> Result<()> {
525 let PositionMove { from, to, .. } = piece_move;
526 let piece_from = self.squares[from[0]][from[1]];
527 let piece_to = self.squares[to[0]][to[1]];
528
529 if !piece_from.is_piece() {
530 return Err(anyhow!(PieceMoveError::EmptySquare));
531 }
532
533 if piece_from.get_color() != self.active_color {
534 return Err(anyhow!(PieceMoveError::NotYourPiece(piece_from, from)));
535 }
536
537 if piece_to.is_piece() && piece_to.get_color() == self.active_color {
538 return Err(anyhow!(PieceMoveError::NotYourPiece(piece_to, to)));
539 }
540
541 let moves = self.gen_legal_positions(from);
542
543 if !moves.contains(&to) {
544 return Err(anyhow!(PieceMoveError::MoveNotFound));
545 }
546
547 Ok(())
548 }
549 pub fn is_hit(&self, pos: Position) -> bool {
550 let piece_on = self.get_piece(pos);
551 piece_on.is_piece() && piece_on.get_color() != self.active_color
552 }
553 pub fn gen_legal_positions(&self, from: Position) -> Vec<Position> {
555 let mut temp_board = *self;
556 let positions = temp_board.gen_to_positions(from);
557 let mut legal_positions = vec![];
558
559 for to in positions.into_iter() {
560 let en_passant = self.is_en_passant(from, to);
561 let promotion = self.is_promotion(from, to);
562 let last_piece = temp_board.squares[to[0]][to[1]].0;
563
564 let position_move = PositionMove {
565 from,
566 to,
567 en_passant,
568 promotion,
569 };
570 temp_board.make_move(position_move);
571 let next_moves = temp_board.gen_all_opponent_positions();
572 if !next_moves.iter().any(|m| {
573 let next_piece = temp_board.squares[m[0]][m[1]];
574 next_piece.is_piece()
575 && next_piece.get_color() == temp_board.active_color
576 && Some(PieceType::King) == next_piece.get_type()
577 }) {
578 legal_positions.push(to);
579 }
580
581 temp_board.undo_move(position_move, last_piece);
582 }
583 legal_positions
584 }
585 pub fn gen_to_positions(&self, from: Position) -> Vec<Position> {
587 let piece_from = self.squares[from[0]][from[1]];
588 if let Some(piece_type) = piece_from.get_type() {
589 return match piece_type {
590 PieceType::Bishop | PieceType::Rook | PieceType::Queen => {
591 self.gen_sliding(from, piece_type)
592 }
593 PieceType::Pawn(_) => self.gen_pawn(from),
594 PieceType::King => self.gen_king(from),
595 PieceType::Knight => self.gen_knight(from),
596 };
597 }
598 vec![]
599 }
600 pub fn gen_sliding(&self, from: Position, piece_type: PieceType) -> Vec<Position> {
601 let piece_from = self.squares[from[0]][from[1]];
602 let mut positions = vec![];
603 let start = if piece_type == PieceType::Bishop {
604 4
605 } else {
606 0
607 };
608 let end = if piece_type == PieceType::Rook { 4 } else { 8 };
609 for (direction, offset) in DIRECTION_OFFSETS.iter().enumerate().take(end).skip(start) {
610 for n in 0..self.len_to_edge(from, Direction::from(direction)) {
611 let index = from[0] * 8 + from[1];
612 let target_index = (index as i32 + offset * (n + 1) as i32).clamp(0, 63) as usize;
613 let target_position = [target_index / 8, target_index % 8];
614 let target_piece = self.squares[target_position[0]][target_position[1]];
615
616 if target_piece.is_piece() && target_piece.get_color() == piece_from.get_color() {
617 break;
619 }
620 positions.push(target_position);
621 if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
624 break;
626 }
627 }
628 }
629 positions
630 }
631 pub fn gen_king(&self, from: Position) -> Vec<Position> {
632 let piece_from = self.squares[from[0]][from[1]];
633 let mut positions = vec![];
634 for (direction, offset) in DIRECTION_OFFSETS.iter().enumerate() {
635 let index = from[0] * 8 + from[1];
636 let target_index = index as i32 + offset;
637 if !(0..=63).contains(&target_index)
638 || self.len_to_edge(from, Direction::from(direction)) == 0
639 {
640 continue;
641 }
642 let target_move = [target_index as usize / 8, target_index as usize % 8];
643 let target_piece = self.squares[target_move[0]][target_move[1]];
644
645 if target_piece.is_piece() && target_piece.get_color() == piece_from.get_color() {
646 continue;
648 }
649 positions.push(target_move);
650
651 if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
652 continue;
654 }
655 }
656
657 if self.get_active_color() == &PieceColor::Black {
659 if self.black_can_short_castle() {
660 positions.push([0, 6]);
661 }
662 if self.black_can_long_castle() {
663 positions.push([0, 2]);
664 }
665 } else if self.get_active_color() == &PieceColor::White {
666 if self.white_can_short_castle() {
667 positions.push([7, 6]);
668 }
669 if self.white_can_long_castle() {
670 positions.push([7, 2]);
671 }
672 }
673
674 positions
675 }
676 pub fn gen_pawn(&self, from: Position) -> Vec<Position> {
677 let piece_from = self.squares[from[0]][from[1]];
678 let mut moves = vec![];
679 let shift = match piece_from.get_color() {
680 PieceColor::Black => 1,
681 PieceColor::White => -1,
682 };
683
684 let vertical = (from[0] as i32 + shift) as usize;
686 if vertical < 8 {
687 let is_blocking = self.squares[vertical][from[1]].is_piece();
688 if !is_blocking {
689 moves.push([(from[0] as i32 + shift) as usize, from[1]]);
690 }
691
692 let vertical = (from[0] as i32 + shift * 2) as usize;
694 if vertical < 8 {
695 let is_blocking = is_blocking || self.squares[vertical][from[1]].is_piece();
696 if ((piece_from.is_black() && from[0] == 1)
697 || (piece_from.is_white() && from[0] == 6))
698 && !is_blocking
699 {
700 moves.push([vertical, from[1]]);
701 }
702 }
703 }
704
705 if from[1] > 0 && from[1] < 8 {
709 let to_top_left_pos = [(from[0] as i32 + shift) as usize, from[1] - 1];
710 if to_top_left_pos[0] < 8 {
711 let to_top_left = self.get_piece(to_top_left_pos);
712 if to_top_left.is_piece() && to_top_left.get_color() != piece_from.get_color() {
713 moves.push(to_top_left_pos);
714 }
715
716 let to_left = self.squares[from[0]][from[1] - 1];
720 if let Some(PieceType::Pawn(en_passantable)) = to_left.get_type() {
721 if en_passantable && to_left.get_color() != piece_from.get_color() {
722 let to_en_passant = [(from[0] as i32 + shift) as usize, from[1] - 1];
723 moves.push(to_en_passant);
724 }
725 }
726 }
727 }
728
729 if from[1] < 7 {
733 let to_top_right_pos = [(from[0] as i32 + shift) as usize, from[1] + 1];
734 if to_top_right_pos[0] < 8 {
735 let to_top_right = self.squares[to_top_right_pos[0]][to_top_right_pos[1]];
736 if to_top_right.is_piece() && to_top_right.get_color() != piece_from.get_color() {
737 moves.push(to_top_right_pos);
738 }
739
740 let to_right = self.squares[from[0]][from[1] + 1];
744 if let Some(PieceType::Pawn(en_passantable)) = to_right.get_type() {
745 if en_passantable && to_right.get_color() != piece_from.get_color() {
746 let to_en_passant = [(from[0] as i32 + shift) as usize, from[1] + 1];
747 moves.push(to_en_passant);
748 }
749 }
750 }
751 }
752 moves
753 }
754 pub fn gen_knight(&self, from: Position) -> Vec<Position> {
755 let piece_from = self.squares[from[0]][from[1]];
756 KNIGHT_DIRECTION_OFFSETS
757 .iter()
758 .filter_map(|direction| {
759 let new_pos = [
760 (direction[0] + from[0] as i32) as usize,
761 (direction[1] + from[1] as i32) as usize,
762 ];
763 if new_pos[0] < 8 && new_pos[1] < 8 {
764 let target_piece = self.squares[new_pos[0]][new_pos[1]];
765 if !(target_piece.is_piece()
766 && target_piece.get_color() == piece_from.get_color())
767 {
768 return Some(new_pos);
769 }
770 }
771 None
772 })
773 .collect()
774 }
775 fn len_to_edge(&self, pos: Position, direction: Direction) -> usize {
776 let (rank, file) = (pos[0], pos[1]);
777 let north = 7 - rank;
778 let south = rank;
779 let west = file;
780 let east = 7 - file;
781
782 match direction {
783 Direction::North => north,
784 Direction::NorthEast => min(north, east),
785 Direction::East => east,
786 Direction::SouthEast => min(south, east),
787 Direction::South => south,
788 Direction::SouthWest => min(south, west),
789 Direction::West => west,
790 Direction::NorthWest => min(north, west),
791 }
792 }
793 pub fn make_move(&mut self, position_move: PositionMove) {
795 let PositionMove {
796 from,
797 to,
798 en_passant,
799 promotion,
800 } = position_move;
801 if en_passant {
802 let shift = if self.get_piece(from).get_color() == PieceColor::Black {
803 1
804 } else {
805 -1
806 };
807 let to_step = [(to[0] as isize - shift) as usize, to[1]];
808 self.set_piece(to_step, 0);
809 }
810 if promotion {
811 let color = match self.get_piece(from).get_color() {
812 PieceColor::Black => BLACK,
813 PieceColor::White => WHITE,
814 };
815 self.set_piece(to, QUEEN | color);
816 } else {
817 self.set_piece(to, self.get_piece(from).0);
818 }
819 self.set_piece(from, 0);
820
821 for pos in self.get_piece_positions_by_type(PieceType::Pawn(true)) {
824 if to == pos {
825 continue;
826 }
827 self.get_piece_mut(pos).0 %= 32;
828 }
829 }
830 pub fn undo_move(&mut self, piece_move: PositionMove, last_piece: u32) {
831 let PositionMove { from, to, .. } = piece_move;
832 self.set_piece(from, self.get_piece(to).0);
833 self.set_piece(to, last_piece);
834 }
835 pub fn gen_all_legal_moves(&self) -> Vec<PositionMove> {
837 let mut legal_moves = vec![];
838 for rank in 0..8 {
839 for file in 0..8 {
840 let piece = self.squares[rank][file];
841 if piece.is_piece() && piece.get_color() == self.active_color {
842 let from_move = [rank, file];
843 let to_moves = self.gen_legal_positions([rank, file]);
844 let moves = to_moves
845 .iter()
846 .map(|&to| PositionMove::new(from_move, to))
847 .collect::<Vec<_>>();
848 legal_moves.extend(moves);
849 }
850 }
851 }
852 legal_moves
853 }
854 pub fn gen_all_opponent_positions(&self) -> Vec<Position> {
855 let mut opponent_positions = vec![];
856 for rank in 0..8 {
857 for file in 0..8 {
858 let piece = self.squares[rank][file];
859 if piece.is_piece() && piece.get_color() != self.active_color {
860 let positions = self.gen_to_positions([rank, file]);
861 opponent_positions.extend(positions);
862 }
863 }
864 }
865 opponent_positions
866 }
867 pub fn is_en_passant(&self, from: Position, to: Position) -> bool {
868 if self.get_piece(to).is_piece() {
870 return false;
871 }
872 let piece = self.get_piece(from);
873 if let Some(PieceType::Pawn(_)) = piece.get_type() {
874 let shift = match piece.get_color() {
875 PieceColor::Black => 1,
876 PieceColor::White => -1,
877 };
878 let step_pos = [(to[0] as isize - shift) as usize, to[1]];
879 let step_piece = self.get_piece(step_pos);
880 if step_piece.is_piece() && step_piece.get_color() != piece.get_color() {
881 if let Some(PieceType::Pawn(_)) = step_piece.get_type() {
882 return true;
883 }
884 }
885 }
886 false
887 }
888 pub fn is_promotion(&self, from: Position, to: Position) -> bool {
889 let piece = self.get_piece(from);
890 if let Some(piece_type) = piece.get_type() {
891 return (piece_type == PieceType::Pawn(false) || piece_type == PieceType::Pawn(true))
892 && (to[0] == 7 || to[0] == 0);
893 }
894
895 false
896 }
897 pub fn get_material_weight(&self) -> i32 {
908 let mut res = 0;
909 for row in self.squares.iter() {
910 for piece in row.iter() {
911 let mut value = piece.0;
912 if value >= 32 {
913 value %= 32;
914 } let piece_value =
916 if self.active_color == PieceColor::White && value > WHITE && value < BLACK {
917 value - WHITE
918 } else if self.active_color == PieceColor::Black && value > BLACK {
919 value - BLACK
920 } else {
921 continue;
922 };
923
924 let mut piece_weight = match piece_value {
925 PAWN => 1,
926 KNIGHT | BISHOP => 3,
927 ROOK => 5,
928 QUEEN => 9,
929 KING => 200,
930 _ => unimplemented!("{} is not a valid piece value", piece.0),
931 };
932 if self.active_color != piece.get_color() {
933 piece_weight *= -1;
934 }
935 res += piece_weight;
936 }
937 }
938 res
939 }
940 pub fn get_num_white_pieces(&self) -> i32 {
941 self.squares.iter().fold(0, |res, row| {
942 res + row
943 .iter()
944 .filter(|&&item| item.0 > WHITE && item.0 < BLACK)
945 .count()
946 }) as i32
947 }
948 pub fn get_num_black_pieces(&self) -> i32 {
949 self.squares.iter().fold(0, |res, row| {
950 res + row.iter().filter(|&&item| item.0 > BLACK).count()
951 }) as i32
952 }
953 fn get_positions_from_type(&self, piece_type: &PieceType) -> Vec<Position> {
954 let mut possible_positions = vec![];
955 for (i, row) in self.squares.iter().enumerate() {
956 for (j, position) in row.iter().enumerate() {
957 if position.is_piece() {
958 if let Some(pt) = position.get_type() {
959 if piece_type == &pt && position.get_color() == self.active_color {
960 possible_positions.push([i, j]);
961 }
962 }
963 }
964 }
965 }
966 possible_positions
967 }
968 pub fn switch_active_color(&mut self) {
969 self.active_color = if self.active_color == PieceColor::Black {
970 PieceColor::White
971 } else {
972 PieceColor::Black
973 };
974 }
975 fn move_should_enable_en_passant(&self, piece_move: PositionMove) -> bool {
976 let PositionMove { from, to, .. } = piece_move;
977 let piece = self.get_piece(to);
978 if let Some(PieceType::Pawn(_)) = piece.get_type() {
979 if *self.get_active_color() == piece.get_color() {
980 return match piece.get_color() {
981 PieceColor::White => from[0] == 6 && to[0] == 4,
982 PieceColor::Black => from[0] == 1 && to[0] == 3,
983 };
984 }
985 }
986 false
987 }
988
989 fn parse_uci_position_to_file_rank(&self, mut position: String) -> Result<Position> {
990 let file = 7 - position
991 .pop()
992 .ok_or(anyhow!("can't pop last char of uci"))?
993 .to_string()
994 .parse::<usize>()?
995 .sub(1);
996
997 let rank = (position
998 .pop()
999 .ok_or(anyhow!("can't pop last char of uci"))? as usize)
1000 .sub(97);
1001
1002 Ok([file, rank])
1003 }
1004
1005 fn verify_any_own_position(
1006 &self,
1007 positions: Vec<Position>,
1008 rank: Option<usize>,
1009 ) -> Result<Position> {
1010 for pos in positions.iter() {
1011 if let Some(rank) = rank {
1012 if pos[1] != rank {
1013 continue;
1014 }
1015 }
1016 if self.own_position(pos) {
1017 return Ok(*pos);
1018 }
1019 }
1020 Err(anyhow!("Couldn't find [from] position for {:?}", positions))
1021 }
1022
1023 fn own_position(&self, pos: &Position) -> bool {
1024 let piece = self.get_piece(*pos);
1025 if piece.is_piece() && piece.get_color() == *self.get_active_color() {
1026 if let Some(PieceType::Pawn(_)) = piece.get_type() {
1027 return true;
1028 }
1029 }
1030 false
1031 }
1032
1033 fn handle_convert_to_en_passantable(&mut self, position_move: PositionMove) {
1034 let PositionMove { to, .. } = position_move;
1035 let should_enable_en_passant = self.move_should_enable_en_passant(position_move);
1036
1037 if should_enable_en_passant && self.get_piece(to).0 < 32 {
1038 self.get_piece_mut(to).0 += 32;
1039 }
1040 }
1041
1042 fn get_piece_positions_by_type(&self, piece_type: PieceType) -> Vec<Position> {
1043 let mut positions = Vec::with_capacity(64);
1044 for (y, row) in self.squares.iter().enumerate() {
1045 for (x, piece) in row.iter().enumerate() {
1046 if piece.get_type() == Some(piece_type) {
1047 positions.push([y, x]);
1048 }
1049 }
1050 }
1051 positions
1052 }
1053
1054 fn black_can_long_castle(&self) -> bool {
1055 if self.black_king_moved {
1056 return false;
1057 }
1058 let possible_king = self.get_piece([0, 4]);
1060 if possible_king.is_piece()
1061 && possible_king.is_black()
1062 && possible_king.get_type().unwrap() == PieceType::King
1063 {
1064 let row = self.squares[0];
1065 return row[1..4].iter().all(|p| !p.is_piece());
1066 }
1067 false
1068 }
1069
1070 fn black_can_short_castle(&self) -> bool {
1071 if self.black_king_moved {
1072 return false;
1073 }
1074 let possible_king = self.get_piece([0, 4]);
1076 if possible_king.is_piece()
1077 && possible_king.is_black()
1078 && possible_king.get_type().unwrap() == PieceType::King
1079 {
1080 let row = self.squares[0];
1081 return row[5..7].iter().all(|p| !p.is_piece());
1082 }
1083 false
1084 }
1085
1086 fn white_can_long_castle(&self) -> bool {
1087 if self.white_king_moved {
1088 return false;
1089 }
1090 let possible_king = self.get_piece([7, 4]);
1092 if possible_king.is_piece()
1093 && possible_king.is_white()
1094 && possible_king.get_type().unwrap() == PieceType::King
1095 {
1096 let row = self.squares[7];
1097 return row[1..4].iter().all(|p| !p.is_piece());
1098 }
1099 false
1100 }
1101
1102 fn white_can_short_castle(&self) -> bool {
1103 if self.white_king_moved {
1104 return false;
1105 }
1106 let possible_king = self.get_piece([7, 4]);
1108 if possible_king.is_piece()
1109 && possible_king.is_white()
1110 && possible_king.get_type().unwrap() == PieceType::King
1111 {
1112 let row = self.squares[7];
1113 return row[5..7].iter().all(|p| !p.is_piece());
1114 }
1115 false
1116 }
1117}
1118
1119impl Debug for BoardMap {
1120 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1121 for i in 0..8 {
1122 writeln!(f, "{} {:?}", 8 - i, self.squares[i]).unwrap();
1123 }
1124 writeln!(f, " a b c d e f g h").unwrap();
1125 writeln!(f, "fen: {}", self.get_fen()).unwrap();
1126 writeln!(
1127 f,
1128 "{}'s turn",
1129 match self.active_color {
1130 PieceColor::Black => "black",
1131 PieceColor::White => "white",
1132 }
1133 )
1134 .unwrap();
1135
1136 Ok(())
1137 }
1138}
1139
1140impl Deref for BoardMap {
1141 type Target = [[Piece; 8]; 8];
1142
1143 fn deref(&self) -> &Self::Target {
1144 &self.squares
1145 }
1146}
1147
1148impl DerefMut for BoardMap {
1149 fn deref_mut(&mut self) -> &mut Self::Target {
1150 &mut self.squares
1151 }
1152}