1use crate::board::Board;
2use crate::error::Error;
3use crate::file::File;
4use crate::movegen::MoveGen;
5use crate::piece::Piece;
6use crate::rank::Rank;
7use crate::square::Square;
8
9use std::cmp::Ordering;
10use std::fmt;
11use std::str::FromStr;
12
13#[derive(Clone, Copy, Eq, PartialEq, Default, Debug, Hash)]
15pub struct ChessMove {
16 source: Square,
17 dest: Square,
18 promotion: Option<Piece>,
19}
20
21impl ChessMove {
22 #[inline]
25 pub fn new(source: Square, dest: Square, promotion: Option<Piece>) -> ChessMove {
26 ChessMove { source, dest, promotion }
27 }
28
29 #[inline]
31 pub fn get_source(&self) -> Square {
32 self.source
33 }
34
35 #[inline]
37 pub fn get_dest(&self) -> Square {
38 self.dest
39 }
40
41 #[inline]
43 pub fn get_promotion(&self) -> Option<Piece> {
44 self.promotion
45 }
46 pub fn from_san(board: &Board, move_text: &str) -> Result<ChessMove, Error> {
58 if move_text == "O-O" || move_text == "O-O-O" {
60 let rank = board.side_to_move().to_my_backrank();
61 let source_file = File::E;
62 let dest_file = if move_text == "O-O" { File::G } else { File::C };
63
64 let m = ChessMove::new(Square::make_square(rank, source_file), Square::make_square(rank, dest_file), None);
65 if MoveGen::new_legal(board).any(|l| l == m) {
66 return Ok(m);
67 } else {
68 return Err(Error::InvalidSanMove);
69 }
70 }
71
72 let error = Error::InvalidSanMove;
123 let mut cur_index: usize = 0;
124 let moving_piece = match move_text.get(cur_index..(cur_index + 1)).ok_or(error.clone())? {
125 "N" => {
126 cur_index += 1;
127 Piece::Knight
128 }
129 "B" => {
130 cur_index += 1;
131 Piece::Bishop
132 }
133 "Q" => {
134 cur_index += 1;
135 Piece::Queen
136 }
137 "R" => {
138 cur_index += 1;
139 Piece::Rook
140 }
141 "K" => {
142 cur_index += 1;
143 Piece::King
144 }
145 _ => Piece::Pawn,
146 };
147
148 let mut source_file = match move_text.get(cur_index..(cur_index + 1)).ok_or(error.clone())? {
149 "a" => {
150 cur_index += 1;
151 Some(File::A)
152 }
153 "b" => {
154 cur_index += 1;
155 Some(File::B)
156 }
157 "c" => {
158 cur_index += 1;
159 Some(File::C)
160 }
161 "d" => {
162 cur_index += 1;
163 Some(File::D)
164 }
165 "e" => {
166 cur_index += 1;
167 Some(File::E)
168 }
169 "f" => {
170 cur_index += 1;
171 Some(File::F)
172 }
173 "g" => {
174 cur_index += 1;
175 Some(File::G)
176 }
177 "h" => {
178 cur_index += 1;
179 Some(File::H)
180 }
181 _ => None,
182 };
183
184 let mut source_rank = match move_text.get(cur_index..(cur_index + 1)).ok_or(error.clone())? {
185 "1" => {
186 cur_index += 1;
187 Some(Rank::First)
188 }
189 "2" => {
190 cur_index += 1;
191 Some(Rank::Second)
192 }
193 "3" => {
194 cur_index += 1;
195 Some(Rank::Third)
196 }
197 "4" => {
198 cur_index += 1;
199 Some(Rank::Fourth)
200 }
201 "5" => {
202 cur_index += 1;
203 Some(Rank::Fifth)
204 }
205 "6" => {
206 cur_index += 1;
207 Some(Rank::Sixth)
208 }
209 "7" => {
210 cur_index += 1;
211 Some(Rank::Seventh)
212 }
213 "8" => {
214 cur_index += 1;
215 Some(Rank::Eighth)
216 }
217 _ => None,
218 };
219
220 let takes = move_text.get(cur_index..cur_index + 1).map(|s| s == "x").unwrap_or(false);
221 if takes {
222 cur_index += 1;
223 }
224
225 let dest = if let Some(s) = move_text.get(cur_index..(cur_index + 2)) {
226 if let Ok(q) = Square::from_str(s) {
227 cur_index += 2;
228 q
229 } else {
230 let sq = Square::make_square(source_rank.ok_or(error.clone())?, source_file.ok_or(error.clone())?);
231 source_rank = None;
232 source_file = None;
233 sq
234 }
235 } else {
236 let sq = Square::make_square(source_rank.ok_or(error.clone())?, source_file.ok_or(error.clone())?);
237 source_rank = None;
238 source_file = None;
239 sq
240 };
241
242 let promotion = if let Some(s) = move_text.get(cur_index..(cur_index + 1)) {
243 match s {
244 "N" => {
245 cur_index += 1;
246 Some(Piece::Knight)
247 }
248 "B" => {
249 cur_index += 1;
250 Some(Piece::Bishop)
251 }
252 "R" => {
253 cur_index += 1;
254 Some(Piece::Rook)
255 }
256 "Q" => {
257 cur_index += 1;
258 Some(Piece::Queen)
259 }
260 _ => None,
261 }
262 } else {
263 None
264 };
265
266 if let Some(s) = move_text.get(cur_index..(cur_index + 1)) {
267 let _maybe_check_or_mate = match s {
268 "+" => {
269 cur_index += 1;
270 Some(false)
271 }
272 "#" => {
273 cur_index += 1;
274 Some(true)
275 }
276 _ => None,
277 };
278 }
279
280 let ep = if let Some(s) = move_text.get(cur_index..) { s == " e.p." } else { false };
281
282 let mut found_move: Option<ChessMove> = None;
291 for m in &mut MoveGen::new_legal(board) {
292 if board.piece_on(m.get_source()) != Some(moving_piece) {
294 continue;
295 }
296
297 if let Some(rank) = source_rank
298 && m.get_source().get_rank() != rank
299 {
300 continue;
301 }
302
303 if let Some(file) = source_file
304 && m.get_source().get_file() != file
305 {
306 continue;
307 }
308
309 if m.get_dest() != dest {
310 continue;
311 }
312
313 if m.get_promotion() != promotion {
314 continue;
315 }
316
317 if found_move.is_some() {
318 return Err(error);
319 }
320
321 if !takes && board.piece_on(m.get_dest()).is_some() {
323 continue;
324 }
325
326 if !ep && takes && board.piece_on(m.get_dest()).is_none() {
327 continue;
328 }
329
330 found_move = Some(m);
331 }
332
333 found_move.ok_or(error.clone())
334 }
335}
336
337impl fmt::Display for ChessMove {
338 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
339 match self.promotion {
340 None => write!(f, "{}{}", self.source, self.dest),
341 Some(x) => write!(f, "{}{}{}", self.source, self.dest, x),
342 }
343 }
344}
345
346impl PartialOrd for ChessMove {
347 fn partial_cmp(&self, other: &ChessMove) -> Option<Ordering> {
348 Some(self.cmp(other))
349 }
350}
351
352impl Ord for ChessMove {
353 fn cmp(&self, other: &ChessMove) -> Ordering {
354 if self.source != other.source {
355 self.source.cmp(&other.source)
356 } else if self.dest != other.dest {
357 self.dest.cmp(&other.dest)
358 } else if self.promotion != other.promotion {
359 match self.promotion {
360 None => Ordering::Less,
361 Some(x) => match other.promotion {
362 None => Ordering::Greater,
363 Some(y) => x.cmp(&y),
364 },
365 }
366 } else {
367 Ordering::Equal
368 }
369 }
370}
371
372impl FromStr for ChessMove {
382 type Err = Error;
383
384 fn from_str(s: &str) -> Result<Self, Self::Err> {
385 let source = Square::from_str(s.get(0..2).ok_or(Error::InvalidUciMove)?)?;
386 let dest = Square::from_str(s.get(2..4).ok_or(Error::InvalidUciMove)?)?;
387
388 let mut promo = None;
389 if s.len() == 5 {
390 promo = Some(match s.chars().last().ok_or(Error::InvalidUciMove)? {
391 'q' => Piece::Queen,
392 'r' => Piece::Rook,
393 'n' => Piece::Knight,
394 'b' => Piece::Bishop,
395 _ => return Err(Error::InvalidUciMove),
396 });
397 }
398
399 Ok(ChessMove::new(source, dest, promo))
400 }
401}
402
403#[test]
404fn test_basic_moves() {
405 let board = Board::default();
406 assert_eq!(
407 ChessMove::from_san(&board, "e4").expect("e4 is valid in the initial position"),
408 ChessMove::new(Square::E2, Square::E4, None)
409 );
410}