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}