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 println!("{:?}", self);
381 return Err(anyhow!(
382 "Couldn't find [from] position for {:?} with [to] {:?}",
383 uci,
384 to
385 ));
386 }
387 }
388 UciMoveType::CastleShort { .. } | UciMoveType::CastleLong { .. } => {
389 if self.get_active_color() == &PieceColor::White {
390 [7, 4]
391 } else {
392 [0, 4]
393 }
394 }
395 };
396
397 let en_passant = self.is_en_passant(from, to);
398
399 Ok((
400 uci_move_type,
401 PositionMove {
402 from,
403 to,
404 en_passant,
405 promotion: false,
406 },
407 ))
408 }
409 pub fn get_piece(&self, pos: Position) -> Piece {
410 self.squares[pos[0]][pos[1]]
411 }
412 pub fn find_piece(&self, piece_color: PieceColor, piece_type: PieceType) -> Vec<Position> {
413 let mut vec = vec![];
414 for (y, row) in self.squares.iter().enumerate() {
415 for (x, p) in row.iter().enumerate() {
416 if let Some(t) = p.get_type() {
417 if t == piece_type && p.get_color() == piece_color {
418 vec.push([y, x]);
419 }
420 }
421 }
422 }
423 vec
424 }
425 pub fn get_piece_mut(&mut self, pos: Position) -> &mut Piece {
426 self.squares[pos[0]][pos[1]].borrow_mut()
427 }
428 pub fn get_active_color(&self) -> &PieceColor {
429 &self.active_color
430 }
431 pub fn get_active_pieces(&self) -> Vec<Position> {
432 let mut pieces = vec![];
433 for (i, row) in self.squares.iter().enumerate() {
434 for (j, piece) in row.iter().enumerate() {
435 if piece.get_color() == *self.get_active_color() {
436 pieces.push([i, j]);
437 }
438 }
439 }
440 pieces
441 }
442 pub fn set_piece(&mut self, on: Position, value: u32) {
443 self.squares[on[0]][on[1]] = Piece(value);
444 }
445 pub fn uci_move_turn(&mut self, uci_move: UciMove) -> Result<()> {
449 if let UciMoveType::CastleShort { piece_color, .. } = uci_move.0 {
450 self.make_move(uci_move.1);
451 if piece_color == PieceColor::White {
452 self.make_move(PositionMove::new([7, 7], [7, 5]));
453 } else {
454 self.make_move(PositionMove::new([0, 7], [0, 5]));
455 }
456 } else if let UciMoveType::CastleLong { piece_color, .. } = uci_move.0 {
457 self.make_move(uci_move.1);
458 if piece_color == PieceColor::White {
459 self.make_move(PositionMove::new([7, 0], [7, 3]));
460 } else {
461 self.make_move(PositionMove::new([0, 0], [0, 3]));
462 }
463 } else {
464 let position_move = uci_move.1;
465
466 let PositionMove { .. } = position_move;
467 self.is_valid_move(position_move)?;
468 self.make_move(position_move);
469
470 match uci_move.0 {
471 UciMoveType::Pawn { promotion, .. } => {
472 self.handle_convert_to_en_passantable(position_move);
473
474 if let Some(piece_type) = promotion {
475 let value = piece_type.to_value() | self.get_active_color().to_value();
476 self.set_piece(position_move.to, value);
477 }
478 }
479 UciMoveType::Default { piece_type, .. } => {
480 if piece_type == PieceType::King {
481 if self.get_active_color() == &PieceColor::White {
482 self.white_king_moved = false;
483 } else {
484 self.black_king_moved = false;
485 }
486 }
487 }
488 _ => {}
489 }
490 }
491
492 self.switch_active_color();
493 Ok(())
494 }
495 pub fn single_move_turn(&mut self, position_move: PositionMove) -> Result<()> {
499 let PositionMove { to, .. } = position_move;
500
501 self.is_valid_move(position_move)?;
502
503 self.make_move(position_move);
504
505 let piece_to = &mut self.get_piece(to);
506
507 let castle = false; if castle {
509 if self.active_color == PieceColor::White {
510 self.make_move(PositionMove::new([7, 0], [7, 3]));
511 } else {
512 self.make_move(PositionMove::new([0, 0], [0, 3]));
513 }
514 }
515
516 if let Some(PieceType::Pawn(_)) = piece_to.get_type() {
517 self.handle_convert_to_en_passantable(position_move);
518 }
519
520 self.switch_active_color();
521
522 Ok(())
523 }
524 pub fn is_valid_move(&self, piece_move: PositionMove) -> Result<()> {
526 let PositionMove { from, to, .. } = piece_move;
527 let piece_from = self.squares[from[0]][from[1]];
528 let piece_to = self.squares[to[0]][to[1]];
529
530 if !piece_from.is_piece() {
531 return Err(anyhow!(PieceMoveError::EmptySquare));
532 }
533
534 if piece_from.get_color() != self.active_color {
535 return Err(anyhow!(PieceMoveError::NotYourPiece(piece_from, from)));
536 }
537
538 if piece_to.is_piece() && piece_to.get_color() == self.active_color {
539 return Err(anyhow!(PieceMoveError::NotYourPiece(piece_to, to)));
540 }
541
542 let moves = self.gen_legal_positions(from);
543
544 if !moves.contains(&to) {
545 return Err(anyhow!(PieceMoveError::MoveNotFound));
546 }
547
548 Ok(())
549 }
550 pub fn is_hit(&self, pos: Position) -> bool {
551 let piece_on = self.get_piece(pos);
552 piece_on.is_piece() && piece_on.get_color() != self.active_color
553 }
554 pub fn gen_legal_positions(&self, from: Position) -> Vec<Position> {
556 let mut temp_board = *self;
557 let positions = temp_board.gen_to_positions(from);
558 let mut legal_positions = vec![];
559
560 for to in positions.into_iter() {
561 let en_passant = self.is_en_passant(from, to);
562 let promotion = self.is_promotion(from, to);
563 let last_piece = temp_board.squares[to[0]][to[1]].0;
564
565 let position_move = PositionMove {
566 from,
567 to,
568 en_passant,
569 promotion,
570 };
571 temp_board.make_move(position_move);
572 let next_moves = temp_board.gen_all_opponent_positions();
573 if !next_moves.iter().any(|m| {
574 let next_piece = temp_board.squares[m[0]][m[1]];
575 next_piece.is_piece()
576 && next_piece.get_color() == temp_board.active_color
577 && Some(PieceType::King) == next_piece.get_type()
578 }) {
579 legal_positions.push(to);
580 }
581
582 temp_board.undo_move(position_move, last_piece);
583 }
584 legal_positions
586 }
587 pub fn gen_to_positions(&self, from: Position) -> Vec<Position> {
589 let piece_from = self.squares[from[0]][from[1]];
590 if let Some(piece_type) = piece_from.get_type() {
591 return match piece_type {
592 PieceType::Bishop | PieceType::Rook | PieceType::Queen => {
593 self.gen_sliding(from, piece_type)
594 }
595 PieceType::Pawn(_) => self.gen_pawn(from),
596 PieceType::King => self.gen_king(from),
597 PieceType::Knight => self.gen_knight(from),
598 };
599 }
600 vec![]
601 }
602 pub fn gen_sliding(&self, from: Position, piece_type: PieceType) -> Vec<Position> {
603 let piece_from = self.squares[from[0]][from[1]];
604 let mut positions = vec![];
605 let start = if piece_type == PieceType::Bishop {
606 4
607 } else {
608 0
609 };
610 let end = if piece_type == PieceType::Rook { 4 } else { 8 };
611 for (direction, offset) in DIRECTION_OFFSETS.iter().enumerate().take(end).skip(start) {
612 for n in 0..self.len_to_edge(from, Direction::from(direction)) {
613 let index = from[0] * 8 + from[1];
614 let target_index = (index as i32 + offset * (n + 1) as i32).clamp(0, 63) as usize;
615 let target_position = [target_index / 8, target_index % 8];
616 let target_piece = self.squares[target_position[0]][target_position[1]];
617
618 if target_piece.is_piece() && target_piece.get_color() == piece_from.get_color() {
619 break;
622 }
623 positions.push(target_position);
624 if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
627 break;
630 }
631 }
632 }
633 positions
634 }
635 pub fn gen_king(&self, from: Position) -> Vec<Position> {
636 let piece_from = self.squares[from[0]][from[1]];
637 let mut positions = vec![];
638 for (direction, offset) in DIRECTION_OFFSETS.iter().enumerate() {
639 let index = from[0] * 8 + from[1];
640 let target_index = index as i32 + offset;
641 if !(0..=63).contains(&target_index)
642 || self.len_to_edge(from, Direction::from(direction)) == 0
643 {
644 continue;
645 }
646 let target_move = [target_index as usize / 8, target_index as usize % 8];
647 let target_piece = self.squares[target_move[0]][target_move[1]];
648
649 if target_piece.is_piece() && target_piece.get_color() == piece_from.get_color() {
650 continue;
653 }
654 positions.push(target_move);
655 if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
658 continue;
661 }
662 }
663
664 if self.get_active_color() == &PieceColor::Black {
666 if self.black_can_short_castle() {
667 positions.push([0, 6]);
668 }
669 if self.black_can_long_castle() {
670 positions.push([0, 2]);
671 }
672 } else if self.get_active_color() == &PieceColor::White {
673 if self.white_can_short_castle() {
674 positions.push([7, 6]);
675 }
676 if self.white_can_long_castle() {
677 positions.push([7, 2]);
678 }
679 }
680
681 positions
682 }
683 pub fn gen_pawn(&self, from: Position) -> Vec<Position> {
684 let piece_from = self.squares[from[0]][from[1]];
685 let mut moves = vec![];
686 let shift = match piece_from.get_color() {
687 PieceColor::Black => 1,
688 PieceColor::White => -1,
689 };
690
691 let vertical = (from[0] as i32 + shift) as usize;
693 if vertical < 8 {
694 let is_blocking = self.squares[vertical][from[1]].is_piece();
695 if !is_blocking {
696 moves.push([(from[0] as i32 + shift) as usize, from[1]]);
697 }
698
699 let vertical = (from[0] as i32 + shift * 2) as usize;
701 if vertical < 8 {
702 let is_blocking = is_blocking || self.squares[vertical][from[1]].is_piece();
703 if ((piece_from.is_black() && from[0] == 1)
704 || (piece_from.is_white() && from[0] == 6))
705 && !is_blocking
706 {
707 moves.push([vertical, from[1]]);
708 }
709 }
710 }
711
712 if from[1] > 0 && from[1] < 8 {
716 let to_top_left_pos = [(from[0] as i32 + shift) as usize, from[1] - 1];
717 if to_top_left_pos[0] < 8 {
718 let to_top_left = self.get_piece(to_top_left_pos);
719 if to_top_left.is_piece() && to_top_left.get_color() != piece_from.get_color() {
720 moves.push(to_top_left_pos);
721 }
722
723 let to_left = self.squares[from[0]][from[1] - 1];
727 if let Some(PieceType::Pawn(en_passantable)) = to_left.get_type() {
728 if en_passantable && to_left.get_color() != piece_from.get_color() {
729 let to_en_passant = [(from[0] as i32 + shift) as usize, from[1] - 1];
731 moves.push(to_en_passant);
732 }
733 }
734 }
735 }
736
737 if from[1] < 7 {
741 let to_top_right_pos = [(from[0] as i32 + shift) as usize, from[1] + 1];
742 if to_top_right_pos[0] < 8 {
743 let to_top_right = self.squares[to_top_right_pos[0]][to_top_right_pos[1]];
744 if to_top_right.is_piece() && to_top_right.get_color() != piece_from.get_color() {
745 moves.push(to_top_right_pos);
746 }
747
748 let to_right = self.squares[from[0]][from[1] + 1];
752 if let Some(PieceType::Pawn(en_passantable)) = to_right.get_type() {
753 if en_passantable && to_right.get_color() != piece_from.get_color() {
754 let to_en_passant = [(from[0] as i32 + shift) as usize, from[1] + 1];
756 moves.push(to_en_passant);
757 }
758 }
759 }
760 }
761 moves
762 }
763 pub fn gen_knight(&self, from: Position) -> Vec<Position> {
764 let piece_from = self.squares[from[0]][from[1]];
765 KNIGHT_DIRECTION_OFFSETS
766 .iter()
767 .filter_map(|direction| {
768 let new_pos = [
769 (direction[0] + from[0] as i32) as usize,
770 (direction[1] + from[1] as i32) as usize,
771 ];
772 if new_pos[0] < 8 && new_pos[1] < 8 {
773 let target_piece = self.squares[new_pos[0]][new_pos[1]];
774 if !(target_piece.is_piece()
775 && target_piece.get_color() == piece_from.get_color())
776 {
777 return Some(new_pos);
778 }
779 }
780 None
781 })
782 .collect()
783 }
784 fn len_to_edge(&self, pos: Position, direction: Direction) -> usize {
785 let (rank, file) = (pos[0], pos[1]);
786 let north = 7 - rank;
787 let south = rank;
788 let west = file;
789 let east = 7 - file;
790
791 match direction {
792 Direction::North => north,
793 Direction::NorthEast => min(north, east),
794 Direction::East => east,
795 Direction::SouthEast => min(south, east),
796 Direction::South => south,
797 Direction::SouthWest => min(south, west),
798 Direction::West => west,
799 Direction::NorthWest => min(north, west),
800 }
801 }
802 pub fn make_move(&mut self, position_move: PositionMove) {
804 let PositionMove {
805 from,
806 to,
807 en_passant,
808 promotion,
809 } = position_move;
810 if en_passant {
811 let shift = if self.get_piece(from).get_color() == PieceColor::Black {
813 1
814 } else {
815 -1
816 };
817 let to_step = [(to[0] as isize - shift) as usize, to[1]];
818 self.set_piece(to_step, 0);
820 }
822 if promotion {
823 let color = match self.get_piece(from).get_color() {
824 PieceColor::Black => BLACK,
825 PieceColor::White => WHITE,
826 };
827 self.set_piece(to, QUEEN | color);
828 } else {
829 self.set_piece(to, self.get_piece(from).0);
830 }
831 self.set_piece(from, 0);
832
833 for pos in self.get_piece_positions_by_type(PieceType::Pawn(true)) {
836 if to == pos {
837 continue;
838 }
839 self.get_piece_mut(pos).0 %= 32;
840 }
841 }
842 pub fn undo_move(&mut self, piece_move: PositionMove, last_piece: u32) {
843 let PositionMove { from, to, .. } = piece_move;
844 self.set_piece(from, self.get_piece(to).0);
845 self.set_piece(to, last_piece);
846 }
847 pub fn gen_all_legal_moves(&self) -> Vec<PositionMove> {
849 let mut legal_moves = vec![];
850 for rank in 0..8 {
851 for file in 0..8 {
852 let piece = self.squares[rank][file];
853 if piece.is_piece() && piece.get_color() == self.active_color {
854 let from_move = [rank, file];
855 let to_moves = self.gen_legal_positions([rank, file]);
856 let moves = to_moves
857 .iter()
858 .map(|&to| PositionMove::new(from_move, to))
859 .collect::<Vec<_>>();
860 legal_moves.extend(moves);
861 }
862 }
863 }
864 legal_moves
865 }
866 pub fn gen_all_opponent_positions(&self) -> Vec<Position> {
867 let mut opponent_positions = vec![];
868 for rank in 0..8 {
869 for file in 0..8 {
870 let piece = self.squares[rank][file];
871 if piece.is_piece() && piece.get_color() != self.active_color {
872 let positions = self.gen_to_positions([rank, file]);
874 opponent_positions.extend(positions);
875 }
876 }
877 }
878 opponent_positions
879 }
880 pub fn is_en_passant(&self, from: Position, to: Position) -> bool {
881 if self.get_piece(to).is_piece() {
883 return false;
884 }
885 let piece = self.get_piece(from);
886 if let Some(PieceType::Pawn(_)) = piece.get_type() {
887 let shift = match piece.get_color() {
888 PieceColor::Black => 1,
889 PieceColor::White => -1,
890 };
891 let step_pos = [(to[0] as isize - shift) as usize, to[1]];
892 let step_piece = self.get_piece(step_pos);
893 if step_piece.is_piece() && step_piece.get_color() != piece.get_color() {
894 if let Some(PieceType::Pawn(_)) = step_piece.get_type() {
895 return true;
896 }
897 }
898 }
899 false
900 }
901 pub fn is_promotion(&self, from: Position, to: Position) -> bool {
902 let piece = self.get_piece(from);
903 if let Some(piece_type) = piece.get_type() {
904 return (piece_type == PieceType::Pawn(false) || piece_type == PieceType::Pawn(true))
905 && (to[0] == 7 || to[0] == 0);
906 }
907
908 false
909 }
910 pub fn get_material_weight(&self) -> i32 {
921 let mut res = 0;
922 for row in self.squares.iter() {
923 for piece in row.iter() {
924 let mut value = piece.0;
925 if value >= 32 {
926 value %= 32;
927 } let piece_value =
929 if self.active_color == PieceColor::White && value > WHITE && value < BLACK {
930 value - WHITE
931 } else if self.active_color == PieceColor::Black && value > BLACK {
932 value - BLACK
933 } else {
934 continue;
935 };
936
937 let mut piece_weight = match piece_value {
938 PAWN => 1,
939 KNIGHT | BISHOP => 3,
940 ROOK => 5,
941 QUEEN => 9,
942 KING => 200,
943 _ => unimplemented!("{} is not a valid piece value", piece.0),
944 };
945 if self.active_color != piece.get_color() {
946 piece_weight *= -1;
947 }
948 res += piece_weight;
949 }
950 }
951 res
952 }
953 pub fn get_num_white_pieces(&self) -> i32 {
954 self.squares.iter().fold(0, |res, row| {
955 res + row
956 .iter()
957 .filter(|&&item| item.0 > WHITE && item.0 < BLACK)
958 .count()
959 }) as i32
960 }
961 pub fn get_num_black_pieces(&self) -> i32 {
962 self.squares.iter().fold(0, |res, row| {
963 res + row.iter().filter(|&&item| item.0 > BLACK).count()
964 }) as i32
965 }
966 fn get_positions_from_type(&self, piece_type: &PieceType) -> Vec<Position> {
967 let mut possible_positions = vec![];
968 for (i, row) in self.squares.iter().enumerate() {
969 for (j, position) in row.iter().enumerate() {
970 if position.is_piece() {
971 if let Some(pt) = position.get_type() {
972 if piece_type == &pt && position.get_color() == self.active_color {
973 possible_positions.push([i, j]);
974 }
975 }
976 }
977 }
978 }
979 possible_positions
980 }
981 pub fn switch_active_color(&mut self) {
982 self.active_color = if self.active_color == PieceColor::Black {
983 PieceColor::White
984 } else {
985 PieceColor::Black
986 };
987 }
988 fn move_should_enable_en_passant(&self, piece_move: PositionMove) -> bool {
989 let PositionMove { from, to, .. } = piece_move;
990 let piece = self.get_piece(to);
991 if let Some(PieceType::Pawn(_)) = piece.get_type() {
992 if *self.get_active_color() == piece.get_color() {
993 return match piece.get_color() {
994 PieceColor::White => from[0] == 6 && to[0] == 4,
995 PieceColor::Black => from[0] == 1 && to[0] == 3,
996 };
997 }
998 }
999 false
1000 }
1001
1002 fn parse_uci_position_to_file_rank(&self, mut position: String) -> Result<Position> {
1003 let file = 7 - position
1004 .pop()
1005 .ok_or(anyhow!("can't pop last char of uci"))?
1006 .to_string()
1007 .parse::<usize>()?
1008 .sub(1);
1009
1010 let rank = (position
1011 .pop()
1012 .ok_or(anyhow!("can't pop last char of uci"))? as usize)
1013 .sub(97);
1014
1015 Ok([file, rank])
1016 }
1017
1018 fn verify_any_own_position(
1019 &self,
1020 positions: Vec<Position>,
1021 rank: Option<usize>,
1022 ) -> Result<Position> {
1023 for pos in positions.iter() {
1024 if let Some(rank) = rank {
1025 if pos[1] != rank {
1026 continue;
1027 }
1028 }
1029 if self.own_position(pos) {
1030 return Ok(*pos);
1031 }
1032 }
1033 Err(anyhow!("Couldn't find [from] position for {:?}", positions))
1034 }
1035
1036 fn own_position(&self, pos: &Position) -> bool {
1037 let piece = self.get_piece(*pos);
1038 if piece.is_piece() && piece.get_color() == *self.get_active_color() {
1039 if let Some(PieceType::Pawn(_)) = piece.get_type() {
1040 return true;
1041 }
1042 }
1043 false
1044 }
1045
1046 fn handle_convert_to_en_passantable(&mut self, position_move: PositionMove) {
1047 let PositionMove { to, .. } = position_move;
1048 let should_enable_en_passant = self.move_should_enable_en_passant(position_move);
1049
1050 if should_enable_en_passant {
1051 if self.get_piece(to).0 < 32 {
1053 self.get_piece_mut(to).0 += 32;
1054 }
1055 }
1056 }
1057
1058 fn get_piece_positions_by_type(&self, piece_type: PieceType) -> Vec<Position> {
1059 let mut positions = Vec::with_capacity(64);
1060 for (y, row) in self.squares.iter().enumerate() {
1061 for (x, piece) in row.iter().enumerate() {
1062 if piece.get_type() == Some(piece_type) {
1063 positions.push([y, x]);
1064 }
1065 }
1066 }
1067 positions
1068 }
1069
1070 fn black_can_long_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[1..4].iter().all(|p| !p.is_piece());
1082 }
1083 false
1084 }
1085
1086 fn black_can_short_castle(&self) -> bool {
1087 if self.black_king_moved {
1088 return false;
1089 }
1090 let possible_king = self.get_piece([0, 4]);
1092 if possible_king.is_piece()
1093 && possible_king.is_black()
1094 && possible_king.get_type().unwrap() == PieceType::King
1095 {
1096 let row = self.squares[0];
1097 return row[5..7].iter().all(|p| !p.is_piece());
1098 }
1099 false
1100 }
1101
1102 fn white_can_long_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[1..4].iter().all(|p| !p.is_piece());
1114 }
1115 false
1116 }
1117
1118 fn white_can_short_castle(&self) -> bool {
1119 if self.white_king_moved {
1120 return false;
1121 }
1122 let possible_king = self.get_piece([7, 4]);
1124 if possible_king.is_piece()
1125 && possible_king.is_white()
1126 && possible_king.get_type().unwrap() == PieceType::King
1127 {
1128 let row = self.squares[7];
1129 return row[5..7].iter().all(|p| !p.is_piece());
1130 }
1131 false
1132 }
1133}
1134
1135impl Debug for BoardMap {
1136 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1137 for i in 0..8 {
1138 writeln!(f, "{} {:?}", 8 - i, self.squares[i]).unwrap();
1139 }
1140 writeln!(f, " a b c d e f g h").unwrap();
1141 writeln!(f, "fen: {}", self.get_fen()).unwrap();
1142 writeln!(
1143 f,
1144 "{}'s turn",
1145 match self.active_color {
1146 PieceColor::Black => "black",
1147 PieceColor::White => "white",
1148 }
1149 )
1150 .unwrap();
1151
1152 Ok(())
1153 }
1154}
1155
1156impl Deref for BoardMap {
1157 type Target = [[Piece; 8]; 8];
1158
1159 fn deref(&self) -> &Self::Target {
1160 &self.squares
1161 }
1162}
1163
1164impl DerefMut for BoardMap {
1165 fn deref_mut(&mut self) -> &mut Self::Target {
1166 &mut self.squares
1167 }
1168}