chess/
chess_move.rs

1use std::fmt;
2
3use crate::Square;
4
5/// Represent a ChessMove.
6#[derive(Clone, Copy, Eq, PartialEq, Debug)]
7pub struct ChessMove {
8    /// The [`Square`] where the [`Piece`] comes from.
9    pub from: Square,
10    /// The [`Square`] where the [`Piece`] is going to.
11    pub to: Square,
12}
13
14impl ChessMove {
15    /// Create a new chess move.
16    #[inline]
17    pub fn new(from: Square, to: Square) -> Self {
18        ChessMove { from, to }
19    }
20
21    /*
22    /// Convert a SAN (Standard Algebraic Notation) move into a [`ChessMove`].
23    ///
24    /// TODO
25    ///
26    /// ```
27    /// use chess::{Board, ChessMove, Square};
28    ///
29    /// let board = Board::default();
30    /// assert_eq!(
31    ///     ChessMove::from_san(&board, "e4").expect("e4 is valid in the initial position"),
32    ///     ChessMove::new(Square::E2, Square::E4)
33    /// );
34    /// ```
35    pub fn from_san(board: &Board, move_text: &str) -> Result<ChessMove, Error> {
36        // Castles first...
37        if move_text == "O-O" || move_text == "O-O-O" {
38            let rank = match board.side_to_move() {
39                Color::White => Rank::First,
40                Color::Black => Rank::Eighth,
41            };
42            let source_file = File::E;
43            let dest_file = if move_text == "O-O" { File::G } else { File::C };
44
45            let m = ChessMove::new(
46                Square::make_square(source_file, rank),
47                Square::make_square(dest_file, rank),
48            );
49            return if board.is_legal(m) {
50                Ok(m)
51            } else {
52                Err(Error::InvalidSanMove)
53            };
54        }
55
56        // forms of SAN moves
57        // a4 (Pawn moves to a4)
58        // exd4 (Pawn on e file takes on d4)
59        // xd4 (Illegal, source file must be specified)
60        // 1xd4 (Illegal, source file (not rank) must be specified)
61        // Nc3 (Knight (or any piece) on *some square* to c3
62        // Nb1c3 (Knight (or any piece) on b1 to c3
63        // Nbc3 (Knight on b file to c3)
64        // N1c3 (Knight on first rank to c3)
65        // Nb1xc3 (Knight on b1 takes on c3)
66        // Nbxc3 (Knight on b file takes on c3)
67        // N1xc3 (Knight on first rank takes on c3)
68        // Nc3+ (Knight moves to c3 with check)
69        // Nc3# (Knight moves to c3 with checkmate)
70
71        let error = Error::InvalidSanMove;
72        let mut cur_index: usize = 0;
73        let moving_piece = match move_text
74            .get(cur_index..(cur_index + 1))
75            .ok_or(error.clone())?
76        {
77            "N" => {
78                cur_index += 1;
79                Piece::Knight
80            }
81            "B" => {
82                cur_index += 1;
83                Piece::Bishop
84            }
85            "Q" => {
86                cur_index += 1;
87                Piece::Queen
88            }
89            "R" => {
90                cur_index += 1;
91                Piece::Rook
92            }
93            "K" => {
94                cur_index += 1;
95                Piece::King
96            }
97            _ => Piece::Pawn,
98        };
99
100        let mut source_file = match move_text
101            .get(cur_index..(cur_index + 1))
102            .ok_or(error.clone())?
103        {
104            "a" => {
105                cur_index += 1;
106                Some(File::A)
107            }
108            "b" => {
109                cur_index += 1;
110                Some(File::B)
111            }
112            "c" => {
113                cur_index += 1;
114                Some(File::C)
115            }
116            "d" => {
117                cur_index += 1;
118                Some(File::D)
119            }
120            "e" => {
121                cur_index += 1;
122                Some(File::E)
123            }
124            "f" => {
125                cur_index += 1;
126                Some(File::F)
127            }
128            "g" => {
129                cur_index += 1;
130                Some(File::G)
131            }
132            "h" => {
133                cur_index += 1;
134                Some(File::H)
135            }
136            _ => None,
137        };
138
139        let mut source_rank = match move_text
140            .get(cur_index..(cur_index + 1))
141            .ok_or(error.clone())?
142        {
143            "1" => {
144                cur_index += 1;
145                Some(Rank::First)
146            }
147            "2" => {
148                cur_index += 1;
149                Some(Rank::Second)
150            }
151            "3" => {
152                cur_index += 1;
153                Some(Rank::Third)
154            }
155            "4" => {
156                cur_index += 1;
157                Some(Rank::Fourth)
158            }
159            "5" => {
160                cur_index += 1;
161                Some(Rank::Fifth)
162            }
163            "6" => {
164                cur_index += 1;
165                Some(Rank::Sixth)
166            }
167            "7" => {
168                cur_index += 1;
169                Some(Rank::Seventh)
170            }
171            "8" => {
172                cur_index += 1;
173                Some(Rank::Eighth)
174            }
175            _ => None,
176        };
177
178        let takes = if let Some(s) = move_text.get(cur_index..(cur_index + 1)) {
179            match s {
180                "x" => {
181                    cur_index += 1;
182                    true
183                }
184                _ => false,
185            }
186        } else {
187            false
188        };
189
190        let dest = if let Some(s) = move_text.get(cur_index..(cur_index + 2)) {
191            if let Ok(q) = Square::from_str(s) {
192                cur_index += 2;
193                q
194            } else {
195                let sq = Square::make_square(
196                    source_file.ok_or(error.clone())?,
197                    source_rank.ok_or(error.clone())?,
198                );
199                source_rank = None;
200                source_file = None;
201                sq
202            }
203        } else {
204            let sq = Square::make_square(
205                source_file.ok_or(error.clone())?,
206                source_rank.ok_or(error.clone())?,
207            );
208            source_rank = None;
209            source_file = None;
210            sq
211        };
212
213        let promotion = if let Some(s) = move_text.get(cur_index..(cur_index + 1)) {
214            match s {
215                "N" => {
216                    cur_index += 1;
217                    Some(Piece::Knight)
218                }
219                "B" => {
220                    cur_index += 1;
221                    Some(Piece::Bishop)
222                }
223                "R" => {
224                    cur_index += 1;
225                    Some(Piece::Rook)
226                }
227                "Q" => {
228                    cur_index += 1;
229                    Some(Piece::Queen)
230                }
231                _ => None,
232            }
233        } else {
234            None
235        };
236
237        if let Some(s) = move_text.get(cur_index..(cur_index + 1)) {
238            let _maybe_check_or_mate = match s {
239                "+" => {
240                    cur_index += 1;
241                    Some(false)
242                }
243                "#" => {
244                    cur_index += 1;
245                    Some(true)
246                }
247                _ => None,
248            };
249        }
250
251        let ep = if let Some(s) = move_text.get(cur_index..) {
252            s == " e.p."
253        } else {
254            false
255        };
256
257        //if ep {
258        //    cur_index += 5;
259        //}
260
261        // Ok, now we have all the data from the SAN move, in the following structures
262        // moveing_piece, source_rank, source_file, taks, dest, promotion, maybe_check_or_mate, and
263        // ep
264
265        todo!()
266        /*
267        let mut found_move: Option<ChessMove> = None;
268        for m in &mut MoveGen::new_legal(board) {
269            // check that the move has the properties specified
270            if board.piece_on(m.get_source()) != Some(moving_piece) {
271                continue;
272            }
273
274            if let Some(rank) = source_rank {
275                if m.get_source().get_rank() != rank {
276                    continue;
277                }
278            }
279
280            if let Some(file) = source_file {
281                if m.get_source().get_file() != file {
282                    continue;
283                }
284            }
285
286            if m.get_dest() != dest {
287                continue;
288            }
289
290            if m.get_promotion() != promotion {
291                continue;
292            }
293
294            if found_move.is_some() {
295                return Err(error);
296            }
297
298            // takes is complicated, because of e.p.
299            if !takes {
300                if board.piece_on(m.get_dest()).is_some() {
301                    continue;
302                }
303            }
304
305            if !ep && takes {
306                if board.piece_on(m.get_dest()).is_none() {
307                    continue;
308                }
309            }
310
311            found_move = Some(m);
312        }
313
314        found_move.ok_or(error.clone())
315
316        */
317    } */
318
319    /// The distance between the two [`Square`] of the move.
320    ///
321    /// ```
322    /// use chess::{ChessMove, Square};
323    ///
324    /// let m = ChessMove::new(Square::A1, Square::H8);
325    ///
326    /// assert_eq!(m.distance(), 7);
327    /// ```
328    pub fn distance(&self) -> u32 {
329        self.from.distance(self.to)
330    }
331}
332
333impl fmt::Display for ChessMove {
334    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
335        write!(f, "{}{}", self.from, self.to)
336    }
337}