chess_engine/
piece.rs

1use super::{Board, Color, Move, Position};
2use alloc::vec::Vec;
3
4/// A piece on a board.
5///
6/// Every piece has both a color and a position.
7/// These, combined with the type of piece it is,
8/// determine things like
9/// 1. The validity of legal moves
10/// 2. The validity of legal attacks
11/// 3. Move generation
12/// 4. Material and positional value
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
14pub enum Piece {
15    King(Color, Position),
16    Queen(Color, Position),
17    Rook(Color, Position),
18    Bishop(Color, Position),
19    Knight(Color, Position),
20    Pawn(Color, Position),
21}
22
23const WHITE_KING_POSITION_WEIGHTS: [[f64; 8]; 8] = [
24    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
25    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
26    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
27    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
28    [-2.0, -3.0, -3.0, -4.0, -4.0, -3.0, -3.0, -2.0],
29    [-1.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -1.0],
30    [2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 2.0, 2.0],
31    [2.0, 3.0, 1.0, 0.0, 0.0, 1.0, 3.0, 2.0],
32];
33
34const BLACK_KING_POSITION_WEIGHTS: [[f64; 8]; 8] = [
35    [2.0, 3.0, 1.0, 0.0, 0.0, 1.0, 3.0, 2.0],
36    [2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 2.0, 2.0],
37    [-1.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -1.0],
38    [-2.0, -3.0, -3.0, -4.0, -4.0, -3.0, -3.0, -2.0],
39    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
40    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
41    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
42    [-3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
43];
44
45const WHITE_QUEEN_POSITION_WEIGHTS: [[f64; 8]; 8] = [
46    [-2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0],
47    [-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0],
48    [-1.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0],
49    [-0.5, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5],
50    [0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5],
51    [-1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0],
52    [-1.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, -1.0],
53    [-1.0, -0.0, -1.0, -0.5, -0.5, -0.5, -1.0, -2.0],
54];
55const BLACK_QUEEN_POSITION_WEIGHTS: [[f64; 8]; 8] = [
56    [-1.0, -0.0, -1.0, -0.5, -0.5, -0.5, -1.0, -2.0],
57    [-1.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, -1.0],
58    [-1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0],
59    [0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5],
60    [-0.5, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5],
61    [-1.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0],
62    [-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0],
63    [-2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0],
64];
65
66const WHITE_ROOK_POSITION_WEIGHTS: [[f64; 8]; 8] = [
67    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
68    [0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5],
69    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
70    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
71    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
72    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
73    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
74    [0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0],
75];
76
77const BLACK_ROOK_POSITION_WEIGHTS: [[f64; 8]; 8] = [
78    [0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0],
79    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
80    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
81    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
82    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
83    [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
84    [0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5],
85    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
86];
87
88const WHITE_BISHOP_POSITION_WEIGHTS: [[f64; 8]; 8] = [
89    [-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0],
90    [-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0],
91    [-1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0, -1.0],
92    [-1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, -1.0],
93    [-1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0],
94    [-1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0],
95    [-1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, -1.0],
96    [-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0],
97];
98
99const BLACK_BISHOP_POSITION_WEIGHTS: [[f64; 8]; 8] = [
100    [-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0],
101    [-1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, -1.0],
102    [-1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0],
103    [-1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0],
104    [-1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, -1.0],
105    [-1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0, -1.0],
106    [-1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0],
107    [-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0],
108];
109
110const WHITE_KNIGHT_POSITION_WEIGHTS: [[f64; 8]; 8] = [
111    [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0],
112    [-4.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0, -4.0],
113    [-3.0, 0.0, 1.0, 1.5, 1.5, 1.0, 0.0, -3.0],
114    [-3.0, 0.5, 1.5, 2.0, 2.0, 1.5, 0.5, -3.0],
115    [-3.0, 0.0, 1.5, 2.0, 2.0, 1.5, 0.0, -3.0],
116    [-3.0, 0.5, 1.0, 1.5, 1.5, 1.0, 0.5, -3.0],
117    [-4.0, -2.0, 0.0, 0.5, 0.5, 0.0, -2.0, -4.0],
118    [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0],
119];
120
121const BLACK_KNIGHT_POSITION_WEIGHTS: [[f64; 8]; 8] = [
122    [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0],
123    [-4.0, -2.0, 0.0, 0.5, 0.5, 0.0, -2.0, -4.0],
124    [-3.0, 0.5, 1.0, 1.5, 1.5, 1.0, 0.5, -3.0],
125    [-3.0, 0.0, 1.5, 2.0, 2.0, 1.5, 0.0, -3.0],
126    [-3.0, 0.5, 1.5, 2.0, 2.0, 1.5, 0.5, -3.0],
127    [-3.0, 0.0, 1.0, 1.5, 1.5, 1.0, 0.0, -3.0],
128    [-4.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0, -4.0],
129    [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0],
130];
131
132const WHITE_PAWN_POSITION_WEIGHTS: [[f64; 8]; 8] = [
133    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
134    [5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
135    [1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0],
136    [0.5, 0.5, 1.0, 2.5, 2.5, 1.0, 0.5, 0.5],
137    [0.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0, 0.0],
138    [0.5, -0.5, -1.0, 0.0, 0.0, -1.0, -0.5, 0.5],
139    [0.5, 1.5, -1.0, -2.0, -2.0, 1.0, 1.5, 0.5],
140    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
141];
142
143const BLACK_PAWN_POSITION_WEIGHTS: [[f64; 8]; 8] = [
144    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
145    [0.5, 1.5, -1.0, -2.0, -2.0, 1.0, 1.5, 0.5],
146    [0.5, -0.5, -1.0, 0.0, 0.0, -1.0, -0.5, 0.5],
147    [0.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0, 0.0],
148    [0.5, 0.5, 1.0, 2.5, 2.5, 1.0, 0.5, 0.5],
149    [1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0],
150    [5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
151    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
152];
153
154impl core::fmt::Display for Piece {
155    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
156        write!(
157            f,
158            "{}",
159            match self.get_color() {
160                Color::Black => match self {
161                    Self::King(_, _) => "♔",
162                    Self::Queen(_, _) => "♕",
163                    Self::Rook(_, _) => "♖",
164                    Self::Knight(_, _) => "♘",
165                    Self::Bishop(_, _) => "♗",
166                    Self::Pawn(_, _) => "♙",
167                },
168                Color::White => match self {
169                    Self::King(_, _) => "♚",
170                    Self::Queen(_, _) => "♛",
171                    Self::Rook(_, _) => "♜",
172                    Self::Knight(_, _) => "♞",
173                    Self::Bishop(_, _) => "♝",
174                    Self::Pawn(_, _) => "♟︎",
175                },
176            }
177        )
178    }
179}
180
181impl Piece {
182    /// Get the name of the piece such as `"pawn"` or `"king"`.
183    /// All names are lowercase.
184    #[inline]
185    pub fn get_name(&self) -> &'static str {
186        match self {
187            Self::King(_, _) => "king",
188            Self::Queen(_, _) => "queen",
189            Self::Rook(_, _) => "rook",
190            Self::Bishop(_, _) => "bishop",
191            Self::Knight(_, _) => "knight",
192            Self::Pawn(_, _) => "pawn",
193        }
194    }
195
196    /// Get the material value for a piece.
197    /// | Name | Value |
198    /// |-|-|
199    /// | King | 99999 |
200    /// | Queen | 9 |
201    /// | Rook | 5 |
202    /// | Bishop | 3 |
203    /// | Knight | 3 |
204    /// | Pawn | 1 |
205    #[inline]
206    pub fn get_material_value(&self) -> i32 {
207        match self {
208            Self::King(_, _) => 99999,
209            Self::Queen(_, _) => 9,
210            Self::Rook(_, _) => 5,
211            Self::Bishop(_, _) => 3,
212            Self::Knight(_, _) => 3,
213            Self::Pawn(_, _) => 1,
214        }
215    }
216
217    /// Get the weighted value of a piece. This simply factors in position
218    /// to the pieces value. For example, a knight that is in the center is
219    /// more favorable than a knight on the side of the board. Similarly,
220    /// a king in the center of the board is highly unfavorable compared to
221    /// a king its respective side.
222    ///
223    /// Additionally, the weighted value of the piece is 10 times greater than
224    /// its material value, plus or minus a weight ranging between 5.0 and -5.0.
225    #[inline]
226    pub fn get_weighted_value(&self) -> f64 {
227        let weights = match self {
228            Self::King(c, _) => match c {
229                Color::White => WHITE_KING_POSITION_WEIGHTS,
230                Color::Black => BLACK_KING_POSITION_WEIGHTS,
231            },
232            Self::Queen(c, _) => match c {
233                Color::White => WHITE_QUEEN_POSITION_WEIGHTS,
234                Color::Black => BLACK_QUEEN_POSITION_WEIGHTS,
235            },
236            Self::Rook(c, _) => match c {
237                Color::White => WHITE_ROOK_POSITION_WEIGHTS,
238                Color::Black => BLACK_ROOK_POSITION_WEIGHTS,
239            },
240            Self::Bishop(c, _) => match c {
241                Color::White => WHITE_BISHOP_POSITION_WEIGHTS,
242                Color::Black => BLACK_BISHOP_POSITION_WEIGHTS,
243            },
244            Self::Knight(c, _) => match c {
245                Color::White => WHITE_KNIGHT_POSITION_WEIGHTS,
246                Color::Black => BLACK_KNIGHT_POSITION_WEIGHTS,
247            },
248            Self::Pawn(c, _) => match c {
249                Color::White => WHITE_PAWN_POSITION_WEIGHTS,
250                Color::Black => BLACK_PAWN_POSITION_WEIGHTS,
251            },
252        };
253        weights[(7 - self.get_pos().get_row()) as usize][self.get_pos().get_col() as usize]
254            + (self.get_material_value() * 10) as f64
255    }
256
257    /// Get the color of a given piece.
258    #[inline]
259    pub fn with_color(&self, color: Color) -> Self {
260        match *self {
261            Self::King(_, pos) => Self::King(color, pos),
262            Self::Queen(_, pos) => Self::Queen(color, pos),
263            Self::Rook(_, pos) => Self::Rook(color, pos),
264            Self::Bishop(_, pos) => Self::Bishop(color, pos),
265            Self::Knight(_, pos) => Self::Knight(color, pos),
266            Self::Pawn(_, pos) => Self::Pawn(color, pos),
267        }
268    }
269
270    /// Get the color of a given piece.
271    #[inline]
272    pub fn get_color(&self) -> Color {
273        match self {
274            Self::King(c, _)
275            | Self::Queen(c, _)
276            | Self::Rook(c, _)
277            | Self::Bishop(c, _)
278            | Self::Knight(c, _)
279            | Self::Pawn(c, _) => *c,
280        }
281    }
282
283    /// Get the position of a piece.
284    #[inline]
285    pub fn get_pos(&self) -> Position {
286        match self {
287            Self::King(_, p)
288            | Self::Queen(_, p)
289            | Self::Rook(_, p)
290            | Self::Bishop(_, p)
291            | Self::Knight(_, p)
292            | Self::Pawn(_, p) => *p,
293        }
294    }
295
296    /// Is this piece a king?
297    #[inline]
298    pub fn is_king(&self) -> bool {
299        matches!(self, Self::King(_, _))
300    }
301
302    /// Is this piece a queen?
303    #[inline]
304    pub fn is_queen(&self) -> bool {
305        matches!(self, Self::Queen(_, _))
306    }
307
308    /// Is this piece a rook?
309    #[inline]
310    pub fn is_rook(&self) -> bool {
311        matches!(self, Self::Rook(_, _))
312    }
313
314    /// Is this piece a bishop?
315    #[inline]
316    pub fn is_bishop(&self) -> bool {
317        matches!(self, Self::Bishop(_, _))
318    }
319
320    /// Is this piece a knight?
321    #[inline]
322    pub fn is_knight(&self) -> bool {
323        matches!(self, Self::Knight(_, _))
324    }
325
326    /// Is this piece a pawn?
327    #[inline]
328    pub fn is_pawn(&self) -> bool {
329        matches!(self, Self::Pawn(_, _))
330    }
331
332    /// Is this piece a starting pawn?
333    ///
334    /// A starting pawn is a pawn that has not been pushed
335    /// yet whatsoever.
336    #[inline]
337    pub fn is_starting_pawn(&self) -> bool {
338        if let Self::Pawn(c, pos) = self {
339            pos.is_starting_pawn(*c)
340        } else {
341            false
342        }
343    }
344
345    /// Is this piece in the starting position for the queenside rook?
346    ///
347    /// This method will only return true for rooks that are in the position
348    /// of the queenside rook, not for any particular rook.
349    #[inline]
350    pub fn is_queenside_rook(&self) -> bool {
351        if let Self::Rook(_, pos) = self {
352            pos.is_queenside_rook()
353        } else {
354            false
355        }
356    }
357
358    /// Is this piece in the starting position for the kingside rook?
359    ///
360    /// This method will only return true for rooks that are in the position
361    /// of the kingside rook, not for any particular rook.
362    #[inline]
363    pub fn is_kingside_rook(&self) -> bool {
364        if let Self::Rook(_, pos) = self {
365            pos.is_kingside_rook()
366        } else {
367            false
368        }
369    }
370
371    /// Change the position of this piece to a new position.
372    ///
373    /// For example, `Pawn(Color::White, E4).move_to(E5)` will result in
374    /// `Pawn(Color::White, E5)`. This does not check for move legality,
375    /// it merely creates a new piece with the same color and type, but
376    /// with a new position.
377    #[inline]
378    pub fn move_to(&self, new_pos: Position) -> Self {
379        match *self {
380            Self::King(c, _) => Self::King(c, new_pos),
381            Self::Queen(c, _) => Self::Queen(c, new_pos),
382            Self::Rook(c, _) => Self::Rook(c, new_pos),
383            Self::Bishop(c, _) => Self::Bishop(c, new_pos),
384            Self::Knight(c, _) => Self::Knight(c, new_pos),
385            Self::Pawn(c, _) => Self::Pawn(c, new_pos),
386        }
387    }
388
389    /// Get the exhaustive list of legal moves for a given piece.
390    ///
391    /// This is used for move generation.
392    #[inline]
393    pub(crate) fn get_legal_moves(&self, board: &Board) -> Vec<Move> {
394        let mut result = Vec::new();
395        match *self {
396            Self::Pawn(ally_color, pos) => {
397                let up = pos.pawn_up(ally_color);
398                let next_up = up.pawn_up(ally_color);
399                let up_left = up.next_left();
400                let up_right = up.next_right();
401
402                if let Some(en_passant) = board.get_en_passant() {
403                    if en_passant == up_left || en_passant == up_right {
404                        result.push(Move::Piece(pos, en_passant));
405                    }
406                }
407
408                if next_up.is_on_board()
409                    && self.is_starting_pawn()
410                    && board.has_no_piece(up)
411                    && board.has_no_piece(next_up)
412                {
413                    result.push(Move::Piece(pos, next_up))
414                }
415
416                if up.is_on_board() && board.has_no_piece(up) {
417                    result.push(Move::Piece(pos, up))
418                }
419
420                if up_left.is_on_board() && board.has_enemy_piece(up_left, ally_color) {
421                    result.push(Move::Piece(pos, up.next_left()))
422                } else if up_right.is_on_board()
423                    && board.has_enemy_piece(up.next_right(), ally_color)
424                {
425                    result.push(Move::Piece(pos, up.next_right()))
426                }
427            }
428
429            Self::King(ally_color, pos) => {
430                for p in &[
431                    pos.next_left(),
432                    pos.next_right(),
433                    pos.next_above(),
434                    pos.next_below(),
435                    pos.next_left().next_above(),
436                    pos.next_left().next_below(),
437                    pos.next_right().next_above(),
438                    pos.next_right().next_below(),
439                ] {
440                    if p.is_on_board() && !board.has_ally_piece(*p, ally_color) {
441                        result.push(Move::Piece(pos, *p))
442                    }
443                }
444                if board.can_kingside_castle(ally_color) {
445                    result.push(Move::KingSideCastle);
446                } else if board.can_queenside_castle(ally_color) {
447                    result.push(Move::QueenSideCastle);
448                }
449            }
450            Self::Queen(ally_color, pos) => {
451                for row in 0..8 {
452                    let new_pos = Position::new(row, pos.get_col());
453                    if new_pos != pos
454                        && !board.has_ally_piece(new_pos, ally_color)
455                        && new_pos.is_orthogonal_to(pos)
456                    {
457                        result.push(Move::Piece(pos, new_pos));
458                    }
459                }
460                for col in 0..8 {
461                    let new_pos = Position::new(pos.get_row(), col);
462                    if new_pos != pos
463                        && !board.has_ally_piece(new_pos, ally_color)
464                        && new_pos.is_orthogonal_to(pos)
465                    {
466                        result.push(Move::Piece(pos, new_pos));
467                    }
468                }
469
470                for row in 0..8 {
471                    for col in 0..8 {
472                        let new_pos = Position::new(row, col);
473                        if new_pos != pos
474                            && !board.has_ally_piece(new_pos, ally_color)
475                            && new_pos.is_diagonal_to(pos)
476                        {
477                            result.push(Move::Piece(pos, new_pos));
478                        }
479                    }
480                }
481            }
482
483            Self::Rook(ally_color, pos) => {
484                for row in 0..8 {
485                    let new_pos = Position::new(row, pos.get_col());
486                    if new_pos != pos
487                        && !board.has_ally_piece(new_pos, ally_color)
488                        && new_pos.is_orthogonal_to(pos)
489                    {
490                        result.push(Move::Piece(pos, new_pos));
491                    }
492                }
493                for col in 0..8 {
494                    let new_pos = Position::new(pos.get_row(), col);
495                    if new_pos != pos
496                        && !board.has_ally_piece(new_pos, ally_color)
497                        && new_pos.is_orthogonal_to(pos)
498                    {
499                        result.push(Move::Piece(pos, new_pos));
500                    }
501                }
502            }
503
504            Self::Bishop(ally_color, pos) => {
505                for row in 0..8 {
506                    for col in 0..8 {
507                        let new_pos = Position::new(row, col);
508                        if new_pos != pos
509                            && !board.has_ally_piece(new_pos, ally_color)
510                            && new_pos.is_diagonal_to(pos)
511                        {
512                            result.push(Move::Piece(pos, new_pos));
513                        }
514                    }
515                }
516            }
517            Self::Knight(ally_color, pos) => {
518                for p in &[
519                    pos.next_left().next_left().next_above(),
520                    pos.next_left().next_above().next_above(),
521                    pos.next_left().next_left().next_below(),
522                    pos.next_left().next_below().next_below(),
523                    pos.next_right().next_right().next_above(),
524                    pos.next_right().next_above().next_above(),
525                    pos.next_right().next_right().next_below(),
526                    pos.next_right().next_below().next_below(),
527                ] {
528                    if p.is_on_board() && !board.has_ally_piece(*p, ally_color) {
529                        result.push(Move::Piece(pos, *p))
530                    }
531                }
532            }
533        }
534
535        let color = self.get_color();
536        result
537            .into_iter()
538            .filter(|x| match x {
539                Move::Piece(from, to) => {
540                    if from.is_on_board() && to.is_on_board() {
541                        board.is_legal_move(*x, color)
542                    } else {
543                        false
544                    }
545                }
546                _ => board.is_legal_move(*x, color),
547            })
548            .collect::<Vec<Move>>()
549    }
550
551    /// Verify that moving to a new position is a legal move.
552    #[inline]
553    pub(crate) fn is_legal_move(&self, new_pos: Position, board: &Board) -> bool {
554        if board.has_ally_piece(new_pos, self.get_color()) || new_pos.is_off_board() {
555            return false;
556        }
557
558        match *self {
559            Self::Pawn(ally_color, pos) => {
560                let up = pos.pawn_up(ally_color);
561                let up_left = up.next_left();
562                let up_right = up.next_right();
563
564                (if let Some(en_passant) = board.get_en_passant() {
565                    (en_passant == up_left || en_passant == up_right) && (new_pos == en_passant)
566                } else {
567                    false
568                }) || (self.is_starting_pawn()
569                    && board.has_no_piece(new_pos)
570                    && board.has_no_piece(up)
571                    && new_pos == up.pawn_up(ally_color))
572                    || (board.has_enemy_piece(new_pos, ally_color) && new_pos == up_left)
573                    || (board.has_enemy_piece(new_pos, ally_color) && new_pos == up_right)
574                    || (board.has_no_piece(new_pos) && new_pos == up)
575            }
576
577            Self::King(_, pos) => pos.is_adjacent_to(new_pos),
578
579            Self::Queen(_, pos) => {
580                if pos.is_orthogonal_to(new_pos) {
581                    let mut traveling = pos.orthogonals_to(new_pos);
582                    traveling.pop();
583
584                    for pos in traveling {
585                        if board.has_piece(pos) {
586                            return false;
587                        }
588                    }
589                    true
590                } else if pos.is_diagonal_to(new_pos) {
591                    let mut traveling = pos.diagonals_to(new_pos);
592                    traveling.pop();
593
594                    for pos in traveling {
595                        if board.has_piece(pos) {
596                            return false;
597                        }
598                    }
599                    true
600                } else {
601                    false
602                }
603            }
604
605            Self::Rook(_, pos) => {
606                if pos.is_orthogonal_to(new_pos) {
607                    let mut traveling = pos.orthogonals_to(new_pos);
608                    traveling.pop();
609
610                    for pos in traveling {
611                        if board.has_piece(pos) {
612                            return false;
613                        }
614                    }
615                    true
616                } else {
617                    false
618                }
619            }
620
621            Self::Bishop(_, pos) => {
622                if pos.is_diagonal_to(new_pos) {
623                    let mut traveling = pos.diagonals_to(new_pos);
624                    traveling.pop();
625
626                    for pos in traveling {
627                        if board.has_piece(pos) {
628                            return false;
629                        }
630                    }
631                    true
632                } else {
633                    false
634                }
635            }
636
637            Self::Knight(_, pos) => pos.is_knight_move(new_pos),
638        }
639    }
640
641    /// Verify that attacking a given square is a legal move.
642    #[inline]
643    pub(crate) fn is_legal_attack(&self, new_pos: Position, board: &Board) -> bool {
644        if board.has_ally_piece(new_pos, self.get_color()) || new_pos.is_off_board() {
645            return false;
646        }
647
648        match *self {
649            Self::Pawn(ally_color, pos) => {
650                let up = pos.pawn_up(ally_color);
651                (if let Some(en_passant) = board.get_en_passant() {
652                    (en_passant == up.next_left() || en_passant == up.next_right())
653                        && (new_pos == en_passant)
654                } else {
655                    false
656                }) || new_pos == up.next_left()
657                    || new_pos == up.next_right()
658            }
659
660            Self::King(_, pos) => pos.is_adjacent_to(new_pos),
661
662            Self::Queen(_, pos) => {
663                if pos.is_orthogonal_to(new_pos) {
664                    let mut traveling = pos.orthogonals_to(new_pos);
665                    traveling.pop();
666
667                    for pos in traveling {
668                        if board.has_piece(pos) {
669                            return false;
670                        }
671                    }
672                    true
673                } else if pos.is_diagonal_to(new_pos) {
674                    let mut traveling = pos.diagonals_to(new_pos);
675                    traveling.pop();
676
677                    for pos in traveling {
678                        if board.has_piece(pos) {
679                            return false;
680                        }
681                    }
682                    true
683                } else {
684                    false
685                }
686            }
687
688            Self::Rook(_, pos) => {
689                if pos.is_orthogonal_to(new_pos) {
690                    let mut traveling = pos.orthogonals_to(new_pos);
691                    traveling.pop();
692
693                    for pos in traveling {
694                        if board.has_piece(pos) {
695                            return false;
696                        }
697                    }
698                    true
699                } else {
700                    false
701                }
702            }
703
704            Self::Bishop(_, pos) => {
705                if pos.is_diagonal_to(new_pos) {
706                    let mut traveling = pos.diagonals_to(new_pos);
707                    traveling.pop();
708
709                    for pos in traveling {
710                        if board.has_piece(pos) {
711                            return false;
712                        }
713                    }
714                    true
715                } else {
716                    false
717                }
718            }
719
720            Self::Knight(_, pos) => pos.is_knight_move(new_pos),
721        }
722    }
723}