chess/square.rs
1use std::cmp::max;
2use std::fmt;
3use std::str::FromStr;
4
5use crate::{
6 Color, Direction, Error, File, Rank, BOARD_CELL_PX_SIZE, BOARD_SIZE, NUM_FILES, NUM_RANKS,
7};
8
9/// Represent a square on the chess board.
10#[rustfmt::skip]
11#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
12#[repr(u8)]
13pub enum Square {
14 A1, B1, C1, D1, E1, F1, G1, H1,
15 A2, B2, C2, D2, E2, F2, G2, H2,
16 A3, B3, C3, D3, E3, F3, G3, H3,
17 A4, B4, C4, D4, E4, F4, G4, H4,
18 A5, B5, C5, D5, E5, F5, G5, H5,
19 A6, B6, C6, D6, E6, F6, G6, H6,
20 A7, B7, C7, D7, E7, F7, G7, H7,
21 A8, B8, C8, D8, E8, F8, G8, H8,
22}
23
24/// Numbers of [`Square`].
25pub const NUM_SQUARES: usize = (BOARD_SIZE.0 * BOARD_SIZE.1) as usize;
26
27/// Enumerate all [`Square`].
28#[rustfmt::skip]
29pub const ALL_SQUARES: [Square; NUM_SQUARES] = [
30 Square::A1, Square::B1, Square::C1, Square::D1, Square::E1, Square::F1, Square::G1, Square::H1,
31 Square::A2, Square::B2, Square::C2, Square::D2, Square::E2, Square::F2, Square::G2, Square::H2,
32 Square::A3, Square::B3, Square::C3, Square::D3, Square::E3, Square::F3, Square::G3, Square::H3,
33 Square::A4, Square::B4, Square::C4, Square::D4, Square::E4, Square::F4, Square::G4, Square::H4,
34 Square::A5, Square::B5, Square::C5, Square::D5, Square::E5, Square::F5, Square::G5, Square::H5,
35 Square::A6, Square::B6, Square::C6, Square::D6, Square::E6, Square::F6, Square::G6, Square::H6,
36 Square::A7, Square::B7, Square::C7, Square::D7, Square::E7, Square::F7, Square::G7, Square::H7,
37 Square::A8, Square::B8, Square::C8, Square::D8, Square::E8, Square::F8, Square::G8, Square::H8,
38];
39
40impl Square {
41 /// Create a new [`Square`], from an index.
42 ///
43 /// # Panics
44 ///
45 /// Panic if the index is not in the range 0..=63.
46 ///
47 /// # Examples
48 ///
49 /// ```
50 /// use chess::Square;
51 ///
52 /// assert_eq!(Square::new(0), Square::A1);
53 /// assert_eq!(Square::new(63), Square::H8);
54 /// ```
55 #[inline]
56 pub fn new(index: usize) -> Self {
57 ALL_SQUARES[index]
58 }
59
60 /// Convert this [`Square`] into a [`usize`].
61 #[inline]
62 pub fn to_index(&self) -> usize {
63 *self as usize
64 }
65
66 /// Make a square from [`File`] and [`Rank`].
67 ///
68 /// # Examples
69 ///
70 /// ```
71 /// use chess::{File, Rank, Square};
72 ///
73 /// // Make the A1 square
74 /// let square = Square::make_square(File::A, Rank::First);
75 /// ```
76 #[inline]
77 pub fn make_square(file: File, rank: Rank) -> Square {
78 Square::new(file.to_index() + rank.to_index() * BOARD_SIZE.0 as usize)
79 }
80
81 /// Transform a screen coordinate into a [`Square`].
82 ///
83 /// > **Reciprocal**: see [`Square::to_screen`].
84 ///
85 /// The result depend of:
86 /// - [`BOARD_SIZE`]
87 /// - [`BOARD_CELL_PX_SIZE`]
88 #[inline]
89 pub fn from_screen(x: f32, y: f32) -> Square {
90 // Transpose to grid space
91 let x = x / BOARD_CELL_PX_SIZE.0;
92 let y = y / BOARD_CELL_PX_SIZE.1;
93
94 // transpose to Square (return the y-axis)
95 let y = BOARD_SIZE.1 - y as i16 - 1;
96 Square::make_square(File::new(x as usize), Rank::new(y as usize))
97 }
98
99 /// Transform a [`Square`] into a screen coordinate.
100 ///
101 /// > **Reciprocal**: see [`Square::from_screen`].
102 ///
103 /// The result depend of:
104 /// - [`BOARD_SIZE`]
105 /// - [`BOARD_CELL_PX_SIZE`]
106 #[inline]
107 pub fn to_screen(&self) -> (f32, f32) {
108 // transpose to grid space (return the y-axis)
109 let x = self.file().to_index() as f32;
110 let y = (BOARD_SIZE.1 as usize - self.rank().to_index() - 1) as f32;
111
112 // Transpose to screen space
113 let x = x * BOARD_CELL_PX_SIZE.0;
114 let y = y * BOARD_CELL_PX_SIZE.1;
115 (x, y)
116 }
117
118 /// Return the [`File`] of this square.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use chess::{File, Rank, Square};
124 ///
125 /// let square = Square::make_square(File::D, Rank::Seventh);
126 ///
127 /// assert_eq!(square.file(), File::D);
128 /// ```
129 #[inline]
130 pub fn file(&self) -> File {
131 File::new(self.to_index() % NUM_FILES)
132 }
133
134 /// Return the "relative" [`File`] of this square according the side.
135 ///
136 /// # Examples
137 ///
138 /// ```
139 /// use chess::{Color, File, Square};
140 ///
141 /// assert_eq!(Square::A1.file_for(Color::White), File::A);
142 /// assert_eq!(Square::A1.file_for(Color::Black), File::H);
143 /// ```
144 #[inline]
145 pub fn file_for(&self, color: Color) -> File {
146 let file = self.file();
147 match color {
148 Color::White => file,
149 Color::Black => File::new(NUM_FILES - file.to_index() - 1),
150 }
151 }
152
153 /// Return the [`Rank`] of this square.
154 ///
155 /// # Examples
156 ///
157 /// ```
158 /// use chess::{File, Rank, Square};
159 ///
160 /// let square = Square::make_square(File::D, Rank::Seventh);
161 ///
162 /// assert_eq!(square.rank(), Rank::Seventh);
163 /// ```
164 #[inline]
165 pub fn rank(&self) -> Rank {
166 Rank::new(self.to_index() / NUM_RANKS)
167 }
168
169 /// Return the "relative" [`Rank`] of this square according the side.
170 /// (i.e. return ranks for black)
171 ///
172 /// # Examples
173 ///
174 /// ```
175 /// use chess::{Color, Rank, Square};
176 ///
177 /// assert_eq!(Square::E1.rank_for(Color::White), Rank::First);
178 /// assert_eq!(Square::E8.rank_for(Color::White), Rank::Eighth);
179 /// assert_eq!(Square::E2.rank_for(Color::Black), Rank::Seventh);
180 /// assert_eq!(Square::E7.rank_for(Color::Black), Rank::Second);
181 /// ```
182 #[inline]
183 pub fn rank_for(&self, color: Color) -> Rank {
184 let rank = self.rank();
185 match color {
186 Color::White => rank,
187 Color::Black => Rank::new(NUM_RANKS - rank.to_index() - 1),
188 }
189 }
190
191 /// Go one [`Rank`] up.
192 ///
193 /// # Examples
194 ///
195 /// ```
196 /// use chess::Square;
197 ///
198 /// assert_eq!(Square::B2.up(), Square::B3);
199 /// ```
200 #[inline]
201 pub fn up(&self) -> Self {
202 Square::make_square(self.file(), self.rank().up())
203 }
204
205 /// Go *n* [`Rank`] up.
206 ///
207 /// # Examples
208 ///
209 /// ```
210 /// use chess::Square;
211 ///
212 /// assert_eq!(Square::B2.n_up(3), Square::B5);
213 /// ```
214 #[inline]
215 pub fn n_up(&self, n: usize) -> Self {
216 let mut square = *self;
217 for _ in 0..n {
218 square = square.up();
219 }
220 square
221 }
222
223 /// Go one [`Rank`] forward according to the side.
224 ///
225 /// # Examples
226 ///
227 /// ```
228 /// use chess::{Color, Square};
229 ///
230 /// assert_eq!(Square::B2.forward(Color::White), Square::B3);
231 /// assert_eq!(Square::B2.forward(Color::Black), Square::B1);
232 /// ```
233 #[inline]
234 pub fn forward(&self, color: Color) -> Self {
235 match color {
236 Color::White => Square::make_square(self.file(), self.rank().up()),
237 Color::Black => Square::make_square(self.file(), self.rank().down()),
238 }
239 }
240
241 /// Go *n* [`Rank`] forward according to the side.
242 ///
243 /// # Examples
244 ///
245 /// ```
246 /// use chess::{Color, Square};
247 ///
248 /// assert_eq!(Square::B2.n_forward(Color::White, 2), Square::B4);
249 /// assert_eq!(Square::B8.n_forward(Color::Black, 5), Square::B3);
250 /// ```
251 #[inline]
252 pub fn n_forward(&self, color: Color, n: usize) -> Self {
253 let mut square = *self;
254 for _ in 0..n {
255 square = square.forward(color);
256 }
257 square
258 }
259
260 /// Go one [`Rank`] down.
261 ///
262 /// # Examples
263 ///
264 /// ```
265 /// use chess::Square;
266 ///
267 /// assert_eq!(Square::B2.down(), Square::B1);
268 /// ```
269 #[inline]
270 pub fn down(&self) -> Self {
271 Square::make_square(self.file(), self.rank().down())
272 }
273
274 /// Go *n* [`Rank`] down.
275 ///
276 /// # Examples
277 ///
278 /// ```
279 /// use chess::{Color, Square};
280 ///
281 /// assert_eq!(Square::B4.n_down(2), Square::B2);
282 /// ```
283 #[inline]
284 pub fn n_down(&self, n: usize) -> Self {
285 let mut square = *self;
286 for _ in 0..n {
287 square = square.down();
288 }
289 square
290 }
291
292 /// Go one [`Rank`] backward according to the side.
293 ///
294 /// # Examples
295 ///
296 /// ```
297 /// use chess::{Color, Square};
298 ///
299 /// assert_eq!(Square::B2.backward(Color::White), Square::B1);
300 /// assert_eq!(Square::B2.backward(Color::Black), Square::B3);
301 /// ```
302 #[inline]
303 pub fn backward(&self, color: Color) -> Self {
304 match color {
305 Color::White => Square::make_square(self.file(), self.rank().down()),
306 Color::Black => Square::make_square(self.file(), self.rank().up()),
307 }
308 }
309
310 /// Go *n* [`Rank`] backward according to the side.
311 ///
312 /// # Examples
313 ///
314 /// ```
315 /// use chess::{Color, Square};
316 ///
317 /// assert_eq!(Square::B4.n_backward(Color::White, 2), Square::B2);
318 /// assert_eq!(Square::B3.n_backward(Color::Black, 5), Square::B8);
319 /// ```
320 #[inline]
321 pub fn n_backward(&self, color: Color, n: usize) -> Self {
322 let mut square = *self;
323 for _ in 0..n {
324 square = square.backward(color);
325 }
326 square
327 }
328
329 /// Go one [`File`] to the right.
330 ///
331 /// # Examples
332 ///
333 /// ```
334 /// use chess::Square;
335 ///
336 /// assert_eq!(Square::B2.right(), Square::C2);
337 /// ```
338 #[inline]
339 pub fn right(&self) -> Self {
340 Square::make_square(self.file().right(), self.rank())
341 }
342
343 /// Go *n* [`File`] to the right.
344 ///
345 /// # Examples
346 ///
347 /// ```
348 /// use chess::{Color, Square};
349 ///
350 /// assert_eq!(Square::A4.n_right(3), Square::D4);
351 /// ```
352 #[inline]
353 pub fn n_right(&self, n: usize) -> Self {
354 let mut square = *self;
355 for _ in 0..n {
356 square = square.right();
357 }
358 square
359 }
360
361 /// Go one [`File`] right according to the side.
362 ///
363 /// # Examples
364 ///
365 /// ```
366 /// use chess::{Color, Square};
367 ///
368 /// assert_eq!(Square::B2.right_for(Color::White), Square::C2);
369 /// assert_eq!(Square::B2.right_for(Color::Black), Square::A2);
370 /// ```
371 #[inline]
372 pub fn right_for(&self, color: Color) -> Self {
373 match color {
374 Color::White => Square::make_square(self.file().right(), self.rank()),
375 Color::Black => Square::make_square(self.file().left(), self.rank()),
376 }
377 }
378
379 /// Go *n* [`File`] to the right according to the side.
380 ///
381 /// # Examples
382 ///
383 /// ```
384 /// use chess::{Color, Square};
385 ///
386 /// assert_eq!(Square::A4.n_right_for(Color::White, 3), Square::D4);
387 /// assert_eq!(Square::D4.n_right_for(Color::Black, 3), Square::A4);
388 /// ```
389 #[inline]
390 pub fn n_right_for(&self, color: Color, n: usize) -> Self {
391 let mut square = *self;
392 for _ in 0..n {
393 square = square.right_for(color);
394 }
395 square
396 }
397
398 /// Go one [`File`] to the left.
399 ///
400 /// # Examples
401 ///
402 /// ```
403 /// use chess::Square;
404 ///
405 /// assert_eq!(Square::B2.left(), Square::A2);
406 /// ```
407 #[inline]
408 pub fn left(&self) -> Self {
409 Square::make_square(self.file().left(), self.rank())
410 }
411
412 /// Go *n* [`File`] to the left.
413 ///
414 /// # Examples
415 ///
416 /// ```
417 /// use chess::{Color, Square};
418 ///
419 /// assert_eq!(Square::D4.n_left(3), Square::A4);
420 /// ```
421 #[inline]
422 pub fn n_left(&self, n: usize) -> Self {
423 let mut square = *self;
424 for _ in 0..n {
425 square = square.left();
426 }
427 square
428 }
429
430 /// Go one [`File`] left according to the side.
431 ///
432 /// # Examples
433 ///
434 /// ```
435 /// use chess::{Color, Square};
436 ///
437 /// assert_eq!(Square::B2.left_for(Color::White), Square::A2);
438 /// assert_eq!(Square::B2.left_for(Color::Black), Square::C2);
439 /// ```
440 #[inline]
441 pub fn left_for(&self, color: Color) -> Self {
442 match color {
443 Color::White => Square::make_square(self.file().left(), self.rank()),
444 Color::Black => Square::make_square(self.file().right(), self.rank()),
445 }
446 }
447
448 /// Go *n* [`File`] to the left according to the side.
449 ///
450 /// # Examples
451 ///
452 /// ```
453 /// use chess::{Color, Square};
454 ///
455 /// assert_eq!(Square::D4.n_left_for(Color::White, 3), Square::A4);
456 /// assert_eq!(Square::A4.n_left_for(Color::Black, 3), Square::D4);
457 /// ```
458 #[inline]
459 pub fn n_left_for(&self, color: Color, n: usize) -> Self {
460 let mut square = *self;
461 for _ in 0..n {
462 square = square.left_for(color);
463 }
464 square
465 }
466
467 /// Go one [`Square`] in the given direction.
468 ///
469 /// # Examples
470 ///
471 /// ```
472 /// use chess::{Direction, Square};
473 ///
474 /// assert_eq!(Square::B2.follow_direction(Direction::Up), Square::B3);
475 /// assert_eq!(Square::B2.follow_direction(Direction::DownRight), Square::C1);
476 /// ```
477 #[inline]
478 pub fn follow_direction(&self, direction: Direction) -> Self {
479 match direction {
480 Direction::Up => self.up(),
481 Direction::UpRight => self.up().right(),
482 Direction::Right => self.right(),
483 Direction::DownRight => self.down().right(),
484 Direction::Down => self.down(),
485 Direction::DownLeft => self.down().left(),
486 Direction::Left => self.left(),
487 Direction::UpLeft => self.up().left(),
488 }
489 }
490
491 /// The distance between the two squares, i.e. the number of king steps
492 /// to get from one square to the other.
493 ///
494 /// # Examples
495 ///
496 /// ```
497 /// use chess::Square;
498 ///
499 /// assert_eq!(Square::A2.distance(Square::B5), 3);
500 /// ```
501 pub fn distance(&self, other: Square) -> u32 {
502 max(
503 self.file().distance(other.file()),
504 self.rank().distance(other.rank()),
505 )
506 }
507}
508
509impl fmt::Display for Square {
510 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
511 write!(
512 f,
513 "{}{}",
514 (b'a' + (self.file() as u8)) as char,
515 (b'1' + (self.rank() as u8)) as char
516 )
517 }
518}
519
520impl FromStr for Square {
521 type Err = Error;
522
523 fn from_str(s: &str) -> Result<Self, Self::Err> {
524 if s.len() < 2 {
525 return Err(Error::InvalidSquare);
526 }
527 let ch: Vec<char> = s.chars().collect();
528 match ch[0] {
529 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {}
530 _ => return Err(Error::InvalidSquare),
531 }
532 match ch[1] {
533 '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {}
534 _ => return Err(Error::InvalidSquare),
535 }
536 Ok(Square::make_square(
537 File::new((ch[0] as usize) - ('a' as usize)),
538 Rank::new((ch[1] as usize) - ('1' as usize)),
539 ))
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546
547 #[test]
548 fn test_square() {
549 for file in (0..8).map(File::new) {
550 for rank in (0..8).map(Rank::new) {
551 let square = Square::make_square(file, rank);
552 assert_eq!(square.file(), file);
553 assert_eq!(square.rank(), rank);
554 }
555 }
556 }
557}