1use core::{
2 error,
3 fmt::{self, Write as _},
4 mem,
5 num::TryFromIntError,
6 ops::Sub,
7 str,
8};
9
10use crate::util::{out_of_range_error, AppendAscii};
11
12macro_rules! try_from_int_impl {
13 ($type:ty, $lower:expr, $upper:expr, $($t:ty)+) => {
14 $(impl core::convert::TryFrom<$t> for $type {
15 type Error = TryFromIntError;
16
17 #[inline]
18 #[allow(unused_comparisons)]
19 fn try_from(value: $t) -> Result<$type, Self::Error> {
20 if ($lower..$upper).contains(&value) {
21 Ok(<$type>::new(value as u32))
22 } else {
23 Err(out_of_range_error())
24 }
25 }
26 })+
27 }
28}
29
30#[allow(missing_docs)]
32#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
33#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
34#[repr(u8)]
35pub enum File {
36 A = 0,
37 B,
38 C,
39 D,
40 E,
41 F,
42 G,
43 H,
44}
45
46impl File {
47 #[track_caller]
53 #[inline]
54 pub const fn new(index: u32) -> File {
55 assert!(index < 8);
56 unsafe { File::new_unchecked(index) }
57 }
58
59 #[inline]
66 pub const unsafe fn new_unchecked(index: u32) -> File {
67 debug_assert!(index < 8);
68 unsafe { mem::transmute(index as u8) }
69 }
70
71 #[inline]
72 pub const fn from_char(ch: char) -> Option<File> {
73 match ch {
74 'a' => Some(File::A),
75 'b' => Some(File::B),
76 'c' => Some(File::C),
77 'd' => Some(File::D),
78 'e' => Some(File::E),
79 'f' => Some(File::F),
80 'g' => Some(File::G),
81 'h' => Some(File::H),
82 _ => None,
83 }
84 }
85
86 #[inline]
87 pub const fn char(self) -> char {
88 match self {
89 File::A => 'a',
90 File::B => 'b',
91 File::C => 'c',
92 File::D => 'd',
93 File::E => 'e',
94 File::F => 'f',
95 File::G => 'g',
96 File::H => 'h',
97 }
98 }
99
100 #[inline]
101 pub const fn upper_char(self) -> char {
102 match self {
103 File::A => 'A',
104 File::B => 'B',
105 File::C => 'C',
106 File::D => 'D',
107 File::E => 'E',
108 File::F => 'F',
109 File::G => 'G',
110 File::H => 'H',
111 }
112 }
113
114 #[must_use]
115 #[inline]
116 pub fn offset(self, delta: i32) -> Option<File> {
117 self.to_u32()
118 .checked_add_signed(delta)
119 .and_then(|index| index.try_into().ok())
120 }
121
122 #[inline]
123 pub const fn distance(self, other: File) -> u32 {
124 self.to_u32().abs_diff(other.to_u32())
125 }
126
127 #[must_use]
128 #[inline]
129 pub const fn flip_horizontal(self) -> File {
130 File::new(7 - self.to_u32())
131 }
132
133 #[must_use]
134 #[inline]
135 pub const fn flip_diagonal(self) -> Rank {
136 Rank::new(self.to_u32())
137 }
138
139 #[must_use]
140 #[inline]
141 pub const fn flip_anti_diagonal(self) -> Rank {
142 Rank::new(7 - self.to_u32())
143 }
144
145 #[must_use]
146 #[inline]
147 pub const fn to_u32(self) -> u32 {
148 self as u32
149 }
150
151 #[must_use]
152 #[inline(always)]
153 pub const fn to_usize(self) -> usize {
154 self as usize
155 }
156
157 pub const ALL: [File; 8] = [
159 File::A,
160 File::B,
161 File::C,
162 File::D,
163 File::E,
164 File::F,
165 File::G,
166 File::H,
167 ];
168}
169
170impl Sub for File {
171 type Output = i32;
172
173 #[inline]
174 fn sub(self, other: File) -> i32 {
175 i32::from(self) - i32::from(other)
176 }
177}
178
179impl fmt::Display for File {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 f.write_char(self.char())
182 }
183}
184
185from_enum_as_int_impl! { File, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
186try_from_int_impl! { File, 0, 8, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
187
188#[allow(missing_docs)]
190#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
191#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
192#[repr(u8)]
193pub enum Rank {
194 First = 0,
195 Second,
196 Third,
197 Fourth,
198 Fifth,
199 Sixth,
200 Seventh,
201 Eighth,
202}
203
204impl Rank {
205 #[track_caller]
211 #[inline]
212 pub const fn new(index: u32) -> Rank {
213 assert!(index < 8);
214 unsafe { Rank::new_unchecked(index) }
215 }
216
217 #[inline]
224 pub const unsafe fn new_unchecked(index: u32) -> Rank {
225 debug_assert!(index < 8);
226 unsafe { mem::transmute(index as u8) }
227 }
228
229 #[inline]
230 pub const fn from_char(ch: char) -> Option<Rank> {
231 match ch {
232 '1' => Some(Rank::First),
233 '2' => Some(Rank::Second),
234 '3' => Some(Rank::Third),
235 '4' => Some(Rank::Fourth),
236 '5' => Some(Rank::Fifth),
237 '6' => Some(Rank::Sixth),
238 '7' => Some(Rank::Seventh),
239 '8' => Some(Rank::Eighth),
240 _ => None,
241 }
242 }
243
244 #[inline]
245 pub const fn char(self) -> char {
246 match self {
247 Rank::First => '1',
248 Rank::Second => '2',
249 Rank::Third => '3',
250 Rank::Fourth => '4',
251 Rank::Fifth => '5',
252 Rank::Sixth => '6',
253 Rank::Seventh => '7',
254 Rank::Eighth => '8',
255 }
256 }
257
258 #[must_use]
259 #[inline]
260 pub fn offset(self, delta: i32) -> Option<Rank> {
261 self.to_u32()
262 .checked_add_signed(delta)
263 .and_then(|index| index.try_into().ok())
264 }
265
266 #[inline]
267 pub const fn distance(self, other: Rank) -> u32 {
268 self.to_u32().abs_diff(other.to_u32())
269 }
270
271 #[must_use]
272 #[inline]
273 pub const fn flip_vertical(self) -> Rank {
274 Rank::new(7 - self.to_u32())
275 }
276
277 #[must_use]
278 #[inline]
279 pub const fn flip_diagonal(self) -> File {
280 File::new(self.to_u32())
281 }
282
283 #[must_use]
284 #[inline]
285 pub const fn flip_anti_diagonal(self) -> File {
286 File::new(7 - self.to_u32())
287 }
288
289 #[must_use]
290 #[inline]
291 pub const fn to_u32(self) -> u32 {
292 self as u32
293 }
294
295 #[must_use]
296 #[inline]
297 pub const fn to_usize(self) -> usize {
298 self as usize
299 }
300
301 pub const ALL: [Rank; 8] = [
303 Rank::First,
304 Rank::Second,
305 Rank::Third,
306 Rank::Fourth,
307 Rank::Fifth,
308 Rank::Sixth,
309 Rank::Seventh,
310 Rank::Eighth,
311 ];
312}
313
314impl Sub for Rank {
315 type Output = i32;
316
317 #[inline]
318 fn sub(self, other: Rank) -> i32 {
319 i32::from(self) - i32::from(other)
320 }
321}
322
323impl fmt::Display for Rank {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 f.write_char(self.char())
326 }
327}
328
329from_enum_as_int_impl! { Rank, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
330try_from_int_impl! { Rank, 0, 8, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
331
332#[derive(Clone, Debug)]
334pub struct ParseSquareError;
335
336impl fmt::Display for ParseSquareError {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 f.write_str("invalid square name")
339 }
340}
341
342impl error::Error for ParseSquareError {}
343
344#[rustfmt::skip]
346#[allow(missing_docs)]
347#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
348#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
349#[repr(u8)]
350pub enum Square {
351 A1 = 0, B1, C1, D1, E1, F1, G1, H1,
352 A2, B2, C2, D2, E2, F2, G2, H2,
353 A3, B3, C3, D3, E3, F3, G3, H3,
354 A4, B4, C4, D4, E4, F4, G4, H4,
355 A5, B5, C5, D5, E5, F5, G5, H5,
356 A6, B6, C6, D6, E6, F6, G6, H6,
357 A7, B7, C7, D7, E7, F7, G7, H7,
358 A8, B8, C8, D8, E8, F8, G8, H8,
359}
360
361impl Square {
362 #[track_caller]
377 #[inline]
378 pub const fn new(index: u32) -> Square {
379 assert!(index < 64);
380 unsafe { Square::new_unchecked(index) }
381 }
382
383 #[inline]
389 pub const unsafe fn new_unchecked(index: u32) -> Square {
390 debug_assert!(index < 64);
391 unsafe { mem::transmute(index as u8) }
392 }
393
394 #[inline]
404 pub const fn from_coords(file: File, rank: Rank) -> Square {
405 unsafe { Square::new_unchecked(file.to_u32() | (rank.to_u32() << 3)) }
408 }
409
410 #[inline]
426 pub const fn from_ascii(s: &[u8]) -> Result<Square, ParseSquareError> {
427 if s.len() == 2 {
428 match (File::from_char(s[0] as char), Rank::from_char(s[1] as char)) {
429 (Some(file), Some(rank)) => Ok(Square::from_coords(file, rank)),
430 _ => Err(ParseSquareError),
431 }
432 } else {
433 Err(ParseSquareError)
434 }
435 }
436
437 #[inline]
448 pub const fn file(self) -> File {
449 File::new(self.to_u32() & 7)
450 }
451
452 #[inline]
463 pub const fn rank(self) -> Rank {
464 Rank::new(self.to_u32() >> 3)
465 }
466
467 #[inline]
478 pub const fn coords(self) -> (File, Rank) {
479 (self.file(), self.rank())
480 }
481
482 #[must_use]
495 #[inline]
496 pub fn offset(self, delta: i32) -> Option<Square> {
497 self.to_u32()
498 .checked_add_signed(delta)
499 .and_then(|index| index.try_into().ok())
500 }
501
502 #[must_use]
510 #[inline]
511 pub const unsafe fn offset_unchecked(self, delta: i32) -> Square {
512 debug_assert!(-64 < delta && delta < 64);
513 unsafe { Square::new_unchecked(self.to_u32().wrapping_add_signed(delta)) }
514 }
515
516 #[must_use]
519 #[inline]
520 pub const fn xor(self, other: Square) -> Square {
521 unsafe { Square::new_unchecked(self.to_u32() ^ other.to_u32()) }
523 }
524
525 #[must_use]
534 #[inline]
535 pub const fn flip_horizontal(self) -> Square {
536 self.xor(Square::H1)
537 }
538
539 #[must_use]
548 #[inline]
549 pub const fn flip_vertical(self) -> Square {
550 self.xor(Square::A8)
551 }
552
553 #[must_use]
562 #[inline]
563 pub const fn flip_diagonal(self) -> Square {
564 unsafe { Square::new_unchecked(self.to_u32().wrapping_mul(0x2080_0000) >> 26) }
568 }
569
570 #[must_use]
579 #[inline]
580 pub const fn flip_anti_diagonal(self) -> Square {
581 self.flip_diagonal().rotate_180()
582 }
583
584 #[must_use]
593 #[inline]
594 pub const fn rotate_90(self) -> Square {
595 self.flip_diagonal().flip_vertical()
596 }
597
598 #[must_use]
607 #[inline]
608 pub const fn rotate_180(self) -> Square {
609 self.xor(Square::H8)
610 }
611
612 #[must_use]
621 #[inline]
622 pub const fn rotate_270(self) -> Square {
623 self.flip_diagonal().flip_horizontal()
624 }
625
626 #[inline]
635 pub const fn is_light(self) -> bool {
636 (self.rank().to_u32() + self.file().to_u32()) % 2 == 1
637 }
638
639 #[inline]
648 pub const fn is_dark(self) -> bool {
649 (self.rank().to_u32() + self.file().to_u32()) % 2 == 0
650 }
651
652 pub const fn distance(self, other: Square) -> u32 {
661 let file_distance = self.file().distance(other.file());
662 let rank_distance = self.rank().distance(other.rank());
663
664 if file_distance > rank_distance {
665 file_distance
666 } else {
667 rank_distance
668 }
669 }
670
671 #[must_use]
672 #[inline]
673 pub const fn to_u32(self) -> u32 {
674 self as u32
675 }
676
677 #[must_use]
678 #[inline]
679 pub const fn to_usize(self) -> usize {
680 self as usize
681 }
682
683 pub(crate) fn append_to<W: AppendAscii>(self, f: &mut W) -> Result<(), W::Error> {
684 f.append_ascii(self.file().char())?;
685 f.append_ascii(self.rank().char())
686 }
687
688 #[cfg(feature = "alloc")]
689 pub fn append_to_string(&self, s: &mut alloc::string::String) {
690 let _ = self.append_to(s);
691 }
692
693 #[cfg(feature = "alloc")]
694 pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec<u8>) {
695 let _ = self.append_to(buf);
696 }
697}
698
699mod all_squares {
700 use super::Square::{self, *};
701 impl Square {
702 #[rustfmt::skip]
704 pub const ALL: [Square; 64] = [
705 A1, B1, C1, D1, E1, F1, G1, H1,
706 A2, B2, C2, D2, E2, F2, G2, H2,
707 A3, B3, C3, D3, E3, F3, G3, H3,
708 A4, B4, C4, D4, E4, F4, G4, H4,
709 A5, B5, C5, D5, E5, F5, G5, H5,
710 A6, B6, C6, D6, E6, F6, G6, H6,
711 A7, B7, C7, D7, E7, F7, G7, H7,
712 A8, B8, C8, D8, E8, F8, G8, H8,
713 ];
714 }
715}
716
717from_enum_as_int_impl! { Square, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
718try_from_int_impl! { Square, 0, 64, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
719
720impl Sub for Square {
721 type Output = i32;
722
723 #[inline]
724 fn sub(self, other: Square) -> i32 {
725 i32::from(self) - i32::from(other)
726 }
727}
728
729impl From<(File, Rank)> for Square {
730 #[inline]
731 fn from((file, rank): (File, Rank)) -> Square {
732 Square::from_coords(file, rank)
733 }
734}
735
736impl str::FromStr for Square {
737 type Err = ParseSquareError;
738
739 fn from_str(s: &str) -> Result<Square, ParseSquareError> {
740 Square::from_ascii(s.as_bytes())
741 }
742}
743
744impl fmt::Display for Square {
745 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746 self.append_to(f)
747 }
748}
749
750impl fmt::Debug for Square {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 f.write_char(self.file().upper_char())?;
753 f.write_char(self.rank().char())
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760
761 #[test]
762 fn test_square() {
763 for file in (0..8).map(File::new) {
764 for rank in (0..8).map(Rank::new) {
765 let square = Square::from_coords(file, rank);
766 assert_eq!(square.file(), file);
767 assert_eq!(square.rank(), rank);
768 }
769 }
770 }
771
772 #[cfg(feature = "nohash-hasher")]
773 #[test]
774 fn test_nohash_hasher() {
775 use core::hash::{Hash, Hasher};
776
777 let mut hasher = nohash_hasher::NoHashHasher::<Square>::default();
778 Square::H1.hash(&mut hasher);
779 assert_eq!(hasher.finish(), 7);
780 }
781}