check_buddy/
board.rs

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    /// starting position: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
43    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, //TODO PROMOTION
162            }
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            // specified rank is always position 1
198            // take can be 1 or 2
199            // to position differs
200            //
201            //         f | r | t | p
202            // Re8     x | x | x | 1
203            // Rxe8    x | x | 1 | 2
204            // R1e8    1 | x | x | 2
205            // Rbe8    x | 1 | x | 2
206            // Rbxe8   x | 1 | 2 | 3
207            // R1xe8   1 | x | 2 | 3
208            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                    // t . .
291                    // o f .
292                    let pos1 = [
293                        ((to[0] as i32) - shift) as usize,
294                        ((to[1] as i32) + 1) as usize,
295                    ];
296                    // . . t
297                    // . f o
298                    let pos2 = [
299                        ((to[0] as i32) - shift) as usize,
300                        ((to[1] as i32) - 1) as usize,
301                    ];
302                    // t . .
303                    // . f .
304                    let pos3 = [
305                        ((to[0] as i32) - shift) as usize,
306                        ((to[1] as i32) + 1) as usize,
307                    ];
308                    // . . t
309                    // . f .
310                    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    /// makes a move with uci info
445    ///
446    /// returns true if move was successful
447    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    /// makes a single move with check
495    ///
496    /// returns true if move was successful
497    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; //TODO!
507        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    /// check if move is valid
524    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    /// generate only legal move positions for piece
554    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    /// generate all possible move position for piece
586    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                    // your own color is in the way
618                    break;
619                }
620                positions.push(target_position);
621                // self.squares[target_move[0]][target_move[1]] = Piece(100);
622
623                if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
624                    // Enemy piece and capturable
625                    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                // your own color is in the way
647                continue;
648            }
649            positions.push(target_move);
650
651            if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
652                // Enemy piece and capturable
653                continue;
654            }
655        }
656
657        // castling
658        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        // piece blocking
685        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            // hasn't moved yet
693            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        // takeable pieces on [+1,-1]
706        // x  .  .
707        // .  p  .
708        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                // en passant
717                // x  .  .
718                // _  p  .
719                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        // takeable pieces on [+1,+1]
730        // .  .  x
731        // .  p  .
732        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                // en passant
741                // .  .  x
742                // .  p  _
743                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    /// make a move (without check)
794    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        //TODO instead of downgrading all pawns EVERY MOVE, just keep track of en passants per "next move"
822        // and clear every move, you can only have one en passant per move anyway.
823        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    /// generates all moves based on active color.
836    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        // only en passant moves can be moved diagonally on an empty square
869        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    // f(p) = 200(K-K')
898    //        + 9(Q-Q')
899    //        + 5(R-R')
900    //        + 3(B-B' + N-N')
901    //        + 1(P-P')
902    //        - 0.5(D-D' + S-S' + I-I')
903    //        + 0.1(M-M') + ...
904    // KQRBNP = number of kings, queens, rooks, bishops, knights and pawns
905    //  D,S,I = doubled, blocked and isolated pawns
906    //  M = Mobility (the number of legal moves)
907    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                } // en passantable pawns
915                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        //TODO can be removed if single move turn supports castling
1059        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        //TODO can be removed if single move turn supports castling
1075        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        //TODO can be removed if single move turn supports castling
1091        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        //TODO can be removed if single move turn supports castling
1107        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}