check_buddy_core/
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                    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    /// makes a move with uci info
446    ///
447    /// returns true if move was successful
448    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    /// makes a single move with check
496    ///
497    /// returns true if move was successful
498    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; //TODO!
508        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    /// check if move is valid
525    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    /// generate only legal move positions for piece
555    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        // eprintln!("legal positions {:?} {:?}", &from, &legal_positions);
585        legal_positions
586    }
587    /// generate all possible move position for piece
588    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                    // your own color is in the way
620                    // eprintln!("Piece is yours! {:?}",target_move);
621                    break;
622                }
623                positions.push(target_position);
624                // self.squares[target_move[0]][target_move[1]] = Piece(100);
625
626                if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
627                    // Enemy piece and capturable
628                    // eprintln!("Piece is not yours, but you should still break the loop!");
629                    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                // your own color is in the way
651                // eprintlnln!("Piece is yours!");
652                continue;
653            }
654            positions.push(target_move);
655            // self.squares[target_move[0]][target_move[1]] = Piece(100);
656
657            if target_piece.is_piece() && target_piece.get_color() != piece_from.get_color() {
658                // Enemy piece and capturable
659                // eprintln!("A piece that's yours is blocking any other moves");
660                continue;
661            }
662        }
663
664        // castling
665        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        // piece blocking
692        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            // hasn't moved yet
700            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        // takeable pieces on [+1,-1]
713        // x  .  .
714        // .  p  .
715        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                // en passant
724                // x  .  .
725                // _  p  .
726                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                        // eprintln!("piece on left ({:?}) is en passantable!", [from[0], from[1] - 1]);
730                        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        // takeable pieces on [+1,+1]
738        // .  .  x
739        // .  p  .
740        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                // en passant
749                // .  .  x
750                // .  p  _
751                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                        // eprintln!("piece on right ({:?}) is en passantable!", [from[0], from[1] + 1]);
755                        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    /// make a move (without check)
803    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            // eprintln!("move is an en passant! {from:?} {to:?}");
812            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            // if to_step[0] != 0 && to_step[1] != 0  && to_step[0] != 8 {
819            self.set_piece(to_step, 0);
820            // }
821        }
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        //TODO instead of downgrading all pawns EVERY MOVE, just keep track of en passants per "next move"
834        // and clear every move, you can only have one en passant per move anyway.
835        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    /// generates all moves based on active color.
848    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                    // eprintln!("found enemy piece! {:?}", moves);
873                    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        // only en passant moves can be moved diagonally on an empty square
882        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    // f(p) = 200(K-K')
911    //        + 9(Q-Q')
912    //        + 5(R-R')
913    //        + 3(B-B' + N-N')
914    //        + 1(P-P')
915    //        - 0.5(D-D' + S-S' + I-I')
916    //        + 0.1(M-M') + ...
917    // KQRBNP = number of kings, queens, rooks, bishops, knights and pawns
918    //  D,S,I = doubled, blocked and isolated pawns
919    //  M = Mobility (the number of legal moves)
920    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                } // en passantable pawns
928                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            // eprintln!("Piece became en passantable! ({},{})", to[0], to[1]);
1052            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        //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[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        //TODO can be removed if single move turn supports castling
1091        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        //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[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        //TODO can be removed if single move turn supports castling
1123        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}