chess_move_gen/
mv.rs

1use crate::castle::{Castle, KING_SIDE, QUEEN_SIDE};
2use crate::piece::*;
3use crate::square::{Square, SquareInternal};
4use std::fmt;
5
6/*
7    FLAGS:
8    0001    1: double pawn push
9    0101    4: capture
10    0101    5: ep capture
11    1XXX     : promotion
12
13    LAYOUT
14    lower, upper
15    00 01 -> double pawn push
16    01 00 -> capture
17    01 01 -> ep capture
18    10 XX -> promotion (XX is piece to promote to)
19    11 XX -> promo-capture (XX is piece to promote to)
20    00 1X -> castle (X is castle type)
21*/
22
23pub type Internal = u8;
24const CASTLE_FLAG: Internal = 128;
25const CAPTURE_FLAG: Internal = 64;
26const PROMOTION_FLAG: Internal = 128;
27const EP_CAPTURE_FLAG: Internal = 64;
28#[allow(dead_code)]
29pub const NULL_MOVE: Move = Move { upper: 0, lower: 0 };
30
31#[allow(dead_code)]
32pub const QUEEN_SIDE_CASTLE: Move = Move {
33    lower: 0,
34    upper: CASTLE_FLAG | (QUEEN_SIDE.to_u8() << 6),
35};
36
37#[allow(dead_code)]
38pub const KING_SIDE_CASTLE: Move = Move {
39    lower: 0,
40    upper: CASTLE_FLAG | (KING_SIDE.to_u8() << 6),
41};
42
43/// Represents a move on the chess position. Uses a compact 16 bit representation
44#[derive(Copy, Clone, PartialEq, Eq, Hash)]
45#[repr(packed(2))] // packed since often stored in transposition tables
46pub struct Move {
47    lower: Internal, // holds from square and castle and pawn flags
48    upper: Internal, // holds to square and promotion and capture flags
49}
50
51impl Default for Move {
52    fn default() -> Self {
53        NULL_MOVE
54    }
55}
56
57impl Move {
58    pub fn from(self) -> Square {
59        Square::new((self.lower & 63) as SquareInternal)
60    }
61
62    pub fn to(self) -> Square {
63        Square::new((self.upper & 63) as SquareInternal)
64    }
65
66    pub fn promote_to(self) -> Kind {
67        debug_assert!(!self.is_castle());
68        debug_assert!(self.is_promotion());
69        Kind((self.upper & (!63)) >> 6)
70    }
71
72    /// Returns the absolute distance moved. Eg for a push from square 8 to square 24: |24 - 8| = 16
73    pub fn distance(self) -> i32 {
74        debug_assert!(!self.is_castle());
75        (self.from().to_i32() - self.to().to_i32()).abs()
76    }
77
78    pub fn is_castle(self) -> bool {
79        ((self.upper & CASTLE_FLAG) != 0) && ((self.lower & (!63)) == 0)
80    }
81
82    pub fn is_capture(self) -> bool {
83        (self.lower & CAPTURE_FLAG) != 0
84    }
85
86    pub fn is_ep_capture(self) -> bool {
87        ((self.lower & (!63)) == CAPTURE_FLAG) && ((self.upper & (!63)) == EP_CAPTURE_FLAG)
88    }
89
90    pub fn is_promotion(self) -> bool {
91        (self.lower & PROMOTION_FLAG) != 0
92    }
93
94    pub fn castle(self) -> Castle {
95        debug_assert!(self.is_castle());
96
97        Castle::new(((self.upper & 64) >> 6) as usize)
98    }
99
100    pub fn new_move(from: Square, to: Square, is_capture: bool) -> Move {
101        Move {
102            lower: from.to_u8() | if is_capture { CAPTURE_FLAG } else { 0 },
103            upper: to.to_u8(),
104        }
105    }
106
107    pub fn new_push(from: Square, to: Square) -> Move {
108        Move {
109            lower: from.to_u8(),
110            upper: to.to_u8(),
111        }
112    }
113
114    pub fn new_capture(from: Square, to: Square) -> Move {
115        Move {
116            lower: from.to_u8() | CAPTURE_FLAG,
117            upper: to.to_u8(),
118        }
119    }
120
121    pub fn new_castle(castle: Castle) -> Move {
122        Move {
123            lower: 0,
124            upper: CASTLE_FLAG | (castle.to_u8() << 6),
125        }
126    }
127
128    pub fn new_promotion(from: Square, to: Square, promote_to: Kind) -> Move {
129        Move {
130            lower: from.to_u8() | PROMOTION_FLAG,
131            upper: to.to_u8() | (promote_to.to_u8() << 6),
132        }
133    }
134
135    pub fn new_capture_promotion(from: Square, to: Square, promote_to: Kind) -> Move {
136        Move {
137            lower: from.to_u8() | PROMOTION_FLAG | CAPTURE_FLAG,
138            upper: to.to_u8() | (promote_to.to_u8() << 6),
139        }
140    }
141
142    pub fn new_ep_capture(from: Square, to: Square) -> Move {
143        Move {
144            lower: from.to_u8() | CAPTURE_FLAG,
145            upper: to.to_u8() | EP_CAPTURE_FLAG,
146        }
147    }
148}
149
150impl fmt::Display for Move {
151    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152        if self.is_castle() {
153            return write!(f, "{}", self.castle().pgn_string());
154        }
155
156        let mut s = String::new();
157
158        s += &self.from().to_string();
159
160        if self.is_capture() {
161            s.push('x');
162        }
163
164        s += &self.to().to_string();
165
166        if self.is_promotion() {
167            s.push('=');
168            s.push(self.promote_to().to_char());
169        }
170
171        if self.is_ep_capture() {
172            s += "e.p."
173        }
174
175        write!(f, "{}", &s)
176    }
177}
178
179impl fmt::Debug for Move {
180    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181        write!(f, "{}", self.to_string())
182    }
183}
184
185/// MoveScore encodes a move and the piece-square score change
186/// that the move creates
187#[derive(Clone, Copy)]
188pub struct MoveScore(Move, i16);
189
190impl MoveScore {
191    #[allow(dead_code)]
192    pub fn mv(self) -> Move {
193        self.0
194    }
195
196    #[allow(dead_code)]
197    pub fn score(self) -> i16 {
198        self.1
199    }
200
201    pub const fn new(mv: Move, score: i16) -> MoveScore {
202        MoveScore(mv, score)
203    }
204}
205
206impl fmt::Display for MoveScore {
207    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
208        write!(f, "{} ({})", self.0, self.1)
209    }
210}
211
212impl fmt::Debug for MoveScore {
213    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214        write!(f, "{} ({})", self.0, self.1)
215    }
216}
217
218#[cfg(test)]
219mod test {
220    use super::*;
221    use crate::square::*;
222    use std::mem;
223
224    #[test]
225    fn test_packed() {
226        assert_eq!(2, mem::size_of::<Move>());
227    }
228
229    #[test]
230    fn push() {
231        let mv = Move::new_push(B2, B3);
232        assert_eq!(mv.from(), B2);
233        assert_eq!(mv.to(), B3);
234        assert_eq!(mv.is_capture(), false);
235        assert_eq!(mv.is_castle(), false);
236        assert_eq!(mv.is_promotion(), false);
237        assert_eq!(mv.is_ep_capture(), false);
238    }
239
240    #[test]
241    fn capture() {
242        let mv = Move::new_capture(B2, B5);
243        assert_eq!(mv.from(), B2);
244        assert_eq!(mv.to(), B5);
245        assert_eq!(mv.is_capture(), true);
246        assert_eq!(mv.is_castle(), false);
247        assert_eq!(mv.is_promotion(), false);
248        assert_eq!(mv.is_ep_capture(), false);
249    }
250
251    #[test]
252    fn promotion() {
253        let mv = Move::new_promotion(B7, B8, KNIGHT);
254        assert_eq!(mv.from(), B7);
255        assert_eq!(mv.to(), B8);
256        assert_eq!(mv.promote_to(), KNIGHT);
257        assert_eq!(mv.is_capture(), false);
258        assert_eq!(mv.is_castle(), false);
259        assert_eq!(mv.is_promotion(), true);
260        assert_eq!(mv.is_ep_capture(), false);
261    }
262
263    #[test]
264    fn capture_promotion() {
265        let mv = Move::new_capture_promotion(B7, B8, QUEEN);
266        assert_eq!(mv.from(), B7);
267        assert_eq!(mv.to(), B8);
268        assert_eq!(mv.promote_to(), QUEEN);
269        assert_eq!(mv.is_capture(), true);
270        assert_eq!(mv.is_castle(), false);
271        assert_eq!(mv.is_promotion(), true);
272        assert_eq!(mv.is_ep_capture(), false);
273    }
274
275    #[test]
276    fn castle_queen_side() {
277        let mv = Move::new_castle(QUEEN_SIDE);
278        assert_eq!(mv.is_castle(), true);
279        assert_eq!(mv.castle(), QUEEN_SIDE);
280        assert_eq!(mv.is_capture(), false);
281        assert_eq!(mv.is_promotion(), false);
282        assert_eq!(mv.is_ep_capture(), false);
283    }
284
285    #[test]
286    fn castle_king_side() {
287        let mv = Move::new_castle(KING_SIDE);
288        assert_eq!(mv.is_castle(), true);
289        assert_eq!(mv.castle(), KING_SIDE);
290        assert_eq!(mv.is_capture(), false);
291        assert_eq!(mv.is_promotion(), false);
292        assert_eq!(mv.is_ep_capture(), false);
293    }
294
295    #[test]
296    fn new_ep_capture() {
297        let mv = Move::new_ep_capture(D4, C3);
298        assert_eq!(mv.from(), D4);
299        assert_eq!(mv.to(), C3);
300        assert_eq!(mv.is_capture(), true);
301        assert_eq!(mv.is_castle(), false);
302        assert_eq!(mv.is_promotion(), false);
303        assert_eq!(mv.is_ep_capture(), true);
304    }
305
306    #[test]
307    fn to_string() {
308        assert_eq!(Move::new_castle(KING_SIDE).to_string(), "O-O");
309        assert_eq!(Move::new_castle(QUEEN_SIDE).to_string(), "O-O-O");
310
311        assert_eq!(Move::new_push(B2, B3).to_string(), "b2b3");
312        assert_eq!(Move::new_push(B2, D5).to_string(), "b2d5");
313        assert_eq!(Move::new_capture(B2, B5).to_string(), "b2xb5");
314        assert_eq!(Move::new_capture(B2, B3).to_string(), "b2xb3");
315
316        assert_eq!(Move::new_promotion(B7, B8, QUEEN).to_string(), "b7b8=Q");
317        assert_eq!(
318            Move::new_capture_promotion(C7, B8, QUEEN).to_string(),
319            "c7xb8=Q"
320        );
321
322        assert_eq!(Move::new_ep_capture(D4, C3).to_string(), "d4xc3e.p.");
323    }
324}