1use derive_more::Display;
4use std::{fmt, hint, str::FromStr};
5use thiserror::Error;
6
7#[derive(Error, Debug, Clone, PartialEq, Eq)]
9pub enum CoordParseError {
10 #[error("unexpected file char {0:?}")]
12 UnexpectedFileChar(char),
13 #[error("unexpected rank char {0:?}")]
15 UnexpectedRankChar(char),
16 #[error("invalid string length")]
18 BadLength,
19}
20
21#[derive(Error, Debug, Clone, PartialEq, Eq)]
23pub enum CellParseError {
24 #[error("unexpected cell char {0:?}")]
26 UnexpectedChar(char),
27 #[error("invalid string length")]
29 BadLength,
30}
31
32#[derive(Error, Debug, Clone, PartialEq, Eq)]
34pub enum ColorParseError {
35 #[error("unexpected color char {0:?}")]
37 UnexpectedChar(char),
38 #[error("invalid string length")]
40 BadLength,
41}
42
43#[derive(Error, Debug, Clone, PartialEq, Eq)]
45pub enum CastlingRightsParseError {
46 #[error("unexpected char {0:?}")]
48 UnexpectedChar(char),
49 #[error("duplicate char {0:?}")]
51 DuplicateChar(char),
52 #[error("the string is empty")]
54 EmptyString,
55}
56
57#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
59#[repr(u8)]
60pub enum File {
61 A = 0,
62 B = 1,
63 C = 2,
64 D = 3,
65 E = 4,
66 F = 5,
67 G = 6,
68 H = 7,
69}
70
71impl File {
72 #[inline]
76 pub const fn index(&self) -> usize {
77 *self as u8 as usize
78 }
79
80 #[inline]
86 pub const unsafe fn from_index_unchecked(val: usize) -> Self {
87 match val {
88 0 => File::A,
89 1 => File::B,
90 2 => File::C,
91 3 => File::D,
92 4 => File::E,
93 5 => File::F,
94 6 => File::G,
95 7 => File::H,
96 _ => hint::unreachable_unchecked(),
97 }
98 }
99
100 #[inline]
106 pub const fn from_index(val: usize) -> Self {
107 assert!(val < 8, "file index must be between 0 and 7");
108 unsafe { Self::from_index_unchecked(val) }
109 }
110
111 #[inline]
113 pub fn iter() -> impl Iterator<Item = Self> {
114 (0..8).map(|x| unsafe { Self::from_index_unchecked(x) })
115 }
116
117 #[inline]
118 unsafe fn from_char_unchecked(c: char) -> Self {
119 File::from_index_unchecked((u32::from(c) - u32::from('a')) as usize)
120 }
121
122 #[inline]
141 pub fn from_char(c: char) -> Option<Self> {
142 match c {
143 'a'..='h' => Some(unsafe { Self::from_char_unchecked(c) }),
144 _ => None,
145 }
146 }
147
148 #[inline]
159 pub fn as_char(&self) -> char {
160 (b'a' + *self as u8) as char
161 }
162}
163
164impl fmt::Display for File {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
166 write!(f, "{}", self.as_char())
167 }
168}
169
170#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
172#[repr(u8)]
173pub enum Rank {
174 R8 = 0,
175 R7 = 1,
176 R6 = 2,
177 R5 = 3,
178 R4 = 4,
179 R3 = 5,
180 R2 = 6,
181 R1 = 7,
182}
183
184impl Rank {
185 #[inline]
189 pub const fn index(&self) -> usize {
190 *self as u8 as usize
191 }
192
193 #[inline]
199 pub const unsafe fn from_index_unchecked(val: usize) -> Self {
200 match val {
201 0 => Rank::R8,
202 1 => Rank::R7,
203 2 => Rank::R6,
204 3 => Rank::R5,
205 4 => Rank::R4,
206 5 => Rank::R3,
207 6 => Rank::R2,
208 7 => Rank::R1,
209 _ => hint::unreachable_unchecked(),
210 }
211 }
212
213 #[inline]
219 pub const fn from_index(val: usize) -> Self {
220 assert!(val < 8, "rank index must be between 0 and 7");
221 unsafe { Self::from_index_unchecked(val) }
222 }
223
224 #[inline]
226 pub fn iter() -> impl Iterator<Item = Self> {
227 (0..8).map(|x| unsafe { Self::from_index_unchecked(x) })
228 }
229
230 #[inline]
231 unsafe fn from_char_unchecked(c: char) -> Self {
232 Rank::from_index_unchecked((u32::from('8') - u32::from(c)) as usize)
233 }
234
235 #[inline]
251 pub fn from_char(c: char) -> Option<Self> {
252 match c {
253 '1'..='8' => Some(unsafe { Self::from_char_unchecked(c) }),
254 _ => None,
255 }
256 }
257 #[inline]
268 pub fn as_char(&self) -> char {
269 (b'8' - *self as u8) as char
270 }
271}
272
273impl fmt::Display for Rank {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
275 write!(f, "{}", self.as_char())
276 }
277}
278
279#[derive(Copy, Clone, PartialEq, Eq, Hash)]
281pub struct Coord(u8);
282
283impl Coord {
284 #[inline]
292 pub const fn from_index(val: usize) -> Coord {
293 assert!(val < 64, "coord must be between 0 and 63");
294 Coord(val as u8)
295 }
296
297 #[inline]
305 pub const unsafe fn from_index_unchecked(val: usize) -> Coord {
306 Coord(val as u8)
307 }
308
309 #[inline]
311 pub const fn from_parts(file: File, rank: Rank) -> Coord {
312 Coord(((rank as u8) << 3) | file as u8)
313 }
314
315 #[inline]
317 pub const fn file(&self) -> File {
318 unsafe { File::from_index_unchecked((self.0 & 7) as usize) }
319 }
320
321 #[inline]
323 pub const fn rank(&self) -> Rank {
324 unsafe { Rank::from_index_unchecked((self.0 >> 3) as usize) }
325 }
326
327 #[inline]
344 pub const fn index(&self) -> usize {
345 self.0 as usize
346 }
347
348 #[inline]
361 pub const fn flipped_rank(self) -> Coord {
362 Coord(self.0 ^ 56)
363 }
364
365 #[inline]
378 pub const fn flipped_file(self) -> Coord {
379 Coord(self.0 ^ 7)
380 }
381
382 #[inline]
387 pub const fn diag(&self) -> usize {
388 self.file().index() + self.rank().index()
389 }
390
391 #[inline]
396 pub const fn antidiag(&self) -> usize {
397 7 - self.rank().index() + self.file().index()
398 }
399
400 #[inline]
407 pub const fn add(self, delta: isize) -> Coord {
408 Coord::from_index(self.index().wrapping_add(delta as usize))
409 }
410
411 #[inline]
418 pub const unsafe fn add_unchecked(self, delta: isize) -> Coord {
419 Coord::from_index_unchecked(self.index().wrapping_add(delta as usize))
420 }
421
422 #[inline]
437 pub fn shift(self, delta_file: isize, delta_rank: isize) -> Option<Coord> {
438 let new_file = self.file().index().wrapping_add(delta_file as usize);
439 let new_rank = self.rank().index().wrapping_add(delta_rank as usize);
440 if new_file >= 8 || new_rank >= 8 {
441 return None;
442 }
443 unsafe {
444 Some(Coord::from_parts(
445 File::from_index_unchecked(new_file),
446 Rank::from_index_unchecked(new_rank),
447 ))
448 }
449 }
450
451 #[inline]
453 pub fn iter() -> impl Iterator<Item = Self> {
454 (0_u8..64_u8).map(Coord)
455 }
456}
457
458impl fmt::Debug for Coord {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
460 if self.0 < 64 {
461 return write!(f, "Coord({})", self);
462 }
463 write!(f, "Coord(?{:?})", self.0)
464 }
465}
466
467impl fmt::Display for Coord {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
469 write!(f, "{}{}", self.file().as_char(), self.rank().as_char())
470 }
471}
472
473impl FromStr for Coord {
474 type Err = CoordParseError;
475
476 fn from_str(s: &str) -> Result<Self, Self::Err> {
477 if s.len() != 2 {
478 return Err(CoordParseError::BadLength);
479 }
480 let bytes = s.as_bytes();
481 let (file_ch, rank_ch) = (bytes[0] as char, bytes[1] as char);
482 Ok(Coord::from_parts(
483 File::from_char(file_ch).ok_or(CoordParseError::UnexpectedFileChar(file_ch))?,
484 Rank::from_char(rank_ch).ok_or(CoordParseError::UnexpectedRankChar(rank_ch))?,
485 ))
486 }
487}
488
489#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
491#[repr(u8)]
492pub enum Color {
493 White = 0,
494 Black = 1,
495}
496
497impl Color {
498 #[inline]
500 pub const fn inv(&self) -> Color {
501 match *self {
502 Color::White => Color::Black,
503 Color::Black => Color::White,
504 }
505 }
506
507 #[inline]
511 pub fn as_char(&self) -> char {
512 match *self {
513 Color::White => 'w',
514 Color::Black => 'b',
515 }
516 }
517
518 #[inline]
522 pub fn from_char(c: char) -> Option<Color> {
523 match c {
524 'w' => Some(Color::White),
525 'b' => Some(Color::Black),
526 _ => None,
527 }
528 }
529
530 #[inline]
532 pub fn as_long_str(&self) -> &'static str {
533 match *self {
534 Color::White => "white",
535 Color::Black => "black",
536 }
537 }
538}
539
540impl fmt::Display for Color {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
542 write!(f, "{}", self.as_char())
543 }
544}
545
546impl FromStr for Color {
547 type Err = ColorParseError;
548
549 fn from_str(s: &str) -> Result<Self, Self::Err> {
550 if s.len() != 1 {
551 return Err(ColorParseError::BadLength);
552 }
553 let ch = s.as_bytes()[0] as char;
554 Color::from_char(s.as_bytes()[0] as char).ok_or(ColorParseError::UnexpectedChar(ch))
555 }
556}
557
558#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
560#[repr(u8)]
561pub enum Piece {
562 Pawn = 0,
563 King = 1,
564 Knight = 2,
565 Bishop = 3,
566 Rook = 4,
567 Queen = 5,
568}
569
570impl Piece {
571 pub const COUNT: usize = 6;
575
576 #[inline]
578 pub const fn index(&self) -> usize {
579 *self as u8 as usize
580 }
581
582 #[inline]
588 pub const unsafe fn from_index_unchecked(val: usize) -> Self {
589 match val {
590 0 => Self::Pawn,
591 1 => Self::King,
592 2 => Self::Knight,
593 3 => Self::Bishop,
594 4 => Self::Rook,
595 5 => Self::Queen,
596 _ => hint::unreachable_unchecked(),
597 }
598 }
599
600 #[inline]
606 pub const fn from_index(val: usize) -> Self {
607 assert!(val < Self::COUNT, "piece index must be between 0 and 5");
608 unsafe { Self::from_index_unchecked(val) }
609 }
610
611 #[inline]
613 pub fn iter() -> impl Iterator<Item = Self> {
614 (0..Self::COUNT).map(|x| unsafe { Self::from_index_unchecked(x) })
615 }
616}
617
618#[derive(Default, Copy, Clone, PartialEq, Eq, Hash)]
625pub struct Cell(u8);
626
627impl Cell {
628 pub const EMPTY: Cell = Cell(0);
630
631 pub const COUNT: usize = 13;
635
636 #[inline]
638 pub const fn is_free(&self) -> bool {
639 self.0 == 0
640 }
641
642 #[inline]
644 pub const fn is_occupied(&self) -> bool {
645 self.0 != 0
646 }
647
648 #[inline]
654 pub const unsafe fn from_index_unchecked(val: usize) -> Cell {
655 Cell(val as u8)
656 }
657
658 #[inline]
664 pub const fn from_index(val: usize) -> Cell {
665 assert!(val < Self::COUNT, "index too large");
666 Cell(val as u8)
667 }
668
669 #[inline]
674 pub const fn index(&self) -> usize {
675 self.0 as usize
676 }
677
678 #[inline]
680 pub const fn from_parts(c: Color, p: Piece) -> Cell {
681 Cell(match c {
682 Color::White => 1 + p as u8,
683 Color::Black => 7 + p as u8,
684 })
685 }
686
687 #[inline]
691 pub const fn color(&self) -> Option<Color> {
692 match self.0 {
693 0 => None,
694 1..=6 => Some(Color::White),
695 _ => Some(Color::Black),
696 }
697 }
698
699 #[inline]
703 pub const fn piece(&self) -> Option<Piece> {
704 match self.0 {
705 0 => None,
706 1 | 7 => Some(Piece::Pawn),
707 2 | 8 => Some(Piece::King),
708 3 | 9 => Some(Piece::Knight),
709 4 | 10 => Some(Piece::Bishop),
710 5 | 11 => Some(Piece::Rook),
711 6 | 12 => Some(Piece::Queen),
712 _ => unsafe { hint::unreachable_unchecked() },
713 }
714 }
715
716 #[inline]
718 pub fn iter() -> impl Iterator<Item = Self> {
719 (0..Self::COUNT).map(|x| unsafe { Self::from_index_unchecked(x) })
720 }
721
722 #[inline]
726 pub fn as_char(&self) -> char {
727 b".PKNBRQpknbrq"[self.0 as usize] as char
728 }
729
730 #[inline]
732 pub fn as_utf8_char(&self) -> char {
733 [
734 '.', '♙', '♔', '♘', '♗', '♖', '♕', '♟', '♚', '♞', '♝', '♜', '♛',
735 ][self.0 as usize]
736 }
737
738 #[inline]
743 pub fn from_char(c: char) -> Option<Self> {
744 if c == '.' {
745 return Some(Cell::EMPTY);
746 }
747 let color = if c.is_ascii_uppercase() {
748 Color::White
749 } else {
750 Color::Black
751 };
752 let piece = match c.to_ascii_lowercase() {
753 'p' => Piece::Pawn,
754 'k' => Piece::King,
755 'n' => Piece::Knight,
756 'b' => Piece::Bishop,
757 'r' => Piece::Rook,
758 'q' => Piece::Queen,
759 _ => return None,
760 };
761 Some(Cell::from_parts(color, piece))
762 }
763}
764
765impl fmt::Debug for Cell {
766 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
767 if (self.0 as usize) < Self::COUNT {
768 return write!(f, "Cell({})", self.as_char());
769 }
770 write!(f, "Cell(?{:?})", self.0)
771 }
772}
773
774impl fmt::Display for Cell {
775 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
776 write!(f, "{}", self.as_char())
777 }
778}
779
780impl FromStr for Cell {
781 type Err = CellParseError;
782
783 fn from_str(s: &str) -> Result<Self, Self::Err> {
784 if s.len() != 1 {
785 return Err(CellParseError::BadLength);
786 }
787 let ch = s.as_bytes()[0] as char;
788 Cell::from_char(s.as_bytes()[0] as char).ok_or(CellParseError::UnexpectedChar(ch))
789 }
790}
791
792#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
794#[repr(u8)]
795pub enum CastlingSide {
796 Queen = 0,
798 King = 1,
800}
801
802#[derive(Default, Copy, Clone, PartialEq, Eq, Hash)]
804pub struct CastlingRights(u8);
805
806impl CastlingRights {
807 #[inline]
808 const fn to_index(c: Color, s: CastlingSide) -> u8 {
809 ((c as u8) << 1) | s as u8
810 }
811
812 #[inline]
813 const fn to_color_mask(c: Color) -> u8 {
814 3 << ((c as u8) << 1)
815 }
816
817 pub const EMPTY: CastlingRights = CastlingRights(0);
819
820 pub const FULL: CastlingRights = CastlingRights(15);
822
823 #[inline]
825 pub const fn has(&self, c: Color, s: CastlingSide) -> bool {
826 ((self.0 >> Self::to_index(c, s)) & 1) != 0
827 }
828
829 #[inline]
832 pub const fn has_color(&self, c: Color) -> bool {
833 (self.0 & Self::to_color_mask(c)) != 0
834 }
835
836 #[inline]
838 pub const fn with(self, c: Color, s: CastlingSide) -> CastlingRights {
839 CastlingRights(self.0 | (1_u8 << Self::to_index(c, s)))
840 }
841
842 #[inline]
844 pub const fn without(self, c: Color, s: CastlingSide) -> CastlingRights {
845 CastlingRights(self.0 & !(1_u8 << Self::to_index(c, s)))
846 }
847
848 #[inline]
853 pub fn set(&mut self, c: Color, s: CastlingSide) {
854 *self = self.with(c, s)
855 }
856
857 #[inline]
862 pub fn unset(&mut self, c: Color, s: CastlingSide) {
863 *self = self.without(c, s)
864 }
865
866 #[inline]
868 pub fn unset_color(&mut self, c: Color) {
869 self.unset(c, CastlingSide::King);
870 self.unset(c, CastlingSide::Queen);
871 }
872
873 #[inline]
879 pub const fn from_index(val: usize) -> CastlingRights {
880 assert!(val < 16, "raw castling rights must be between 0 and 15");
881 CastlingRights(val as u8)
882 }
883
884 #[inline]
889 pub const fn index(&self) -> usize {
890 self.0 as usize
891 }
892}
893
894impl fmt::Debug for CastlingRights {
895 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
896 if self.0 < 16 {
897 return write!(f, "CastlingRights({})", self);
898 }
899 write!(f, "CastlingRights(?{:?})", self.0)
900 }
901}
902
903impl fmt::Display for CastlingRights {
904 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
905 if *self == Self::EMPTY {
906 return write!(f, "-");
907 }
908 if self.has(Color::White, CastlingSide::King) {
909 write!(f, "K")?;
910 }
911 if self.has(Color::White, CastlingSide::Queen) {
912 write!(f, "Q")?;
913 }
914 if self.has(Color::Black, CastlingSide::King) {
915 write!(f, "k")?;
916 }
917 if self.has(Color::Black, CastlingSide::Queen) {
918 write!(f, "q")?;
919 }
920 Ok(())
921 }
922}
923
924impl FromStr for CastlingRights {
925 type Err = CastlingRightsParseError;
926
927 fn from_str(s: &str) -> Result<CastlingRights, Self::Err> {
928 type Error = CastlingRightsParseError;
929 if s == "-" {
930 return Ok(CastlingRights::EMPTY);
931 }
932 if s.is_empty() {
933 return Err(Error::EmptyString);
934 }
935 let mut res = CastlingRights::EMPTY;
936 for b in s.bytes() {
937 let (color, side) = match b {
938 b'K' => (Color::White, CastlingSide::King),
939 b'Q' => (Color::White, CastlingSide::Queen),
940 b'k' => (Color::Black, CastlingSide::King),
941 b'q' => (Color::Black, CastlingSide::Queen),
942 _ => return Err(Error::UnexpectedChar(b as char)),
943 };
944 if res.has(color, side) {
945 return Err(Error::DuplicateChar(b as char));
946 }
947 res.set(color, side);
948 }
949 Ok(res)
950 }
951}
952
953#[non_exhaustive]
955#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, Hash)]
956pub enum DrawReason {
957 #[display(fmt = "stalemate")]
959 Stalemate,
960 #[display(fmt = "insufficient material")]
962 InsufficientMaterial,
963 #[display(fmt = "75 move rule")]
967 Moves75,
968 #[display(fmt = "fivefold repetition")]
972 Repeat5,
973 #[display(fmt = "50 move rule")]
978 Moves50,
979 #[display(fmt = "threefold repetition")]
983 Repeat3,
984 #[display(fmt = "draw by agreement")]
986 Agreement,
987 #[display(fmt = "draw by unknown reason")]
989 Unknown,
990}
991
992#[non_exhaustive]
994#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, Hash)]
995pub enum WinReason {
996 #[display(fmt = "checkmate")]
998 Checkmate,
999 #[display(fmt = "opponent forfeits on time")]
1001 TimeForfeit,
1002 #[display(fmt = "opponent made an invalid move")]
1004 InvalidMove,
1005 #[display(fmt = "opponent is a buggy chess engine")]
1007 EngineError,
1008 #[display(fmt = "opponent resigns")]
1010 Resign,
1011 #[display(fmt = "opponent abandons the game")]
1013 Abandon,
1014 #[display(fmt = "unknown reason")]
1016 Unknown,
1017}
1018
1019#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1021pub enum Outcome {
1022 Win {
1024 side: Color,
1026 reason: WinReason,
1028 },
1029 Draw(DrawReason),
1031}
1032
1033#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1035pub enum OutcomeFilter {
1036 Force,
1038 Strict,
1047 Relaxed,
1054}
1055
1056impl Outcome {
1057 #[inline]
1061 pub fn winner(&self) -> Option<Color> {
1062 match self {
1063 Self::Win { side, .. } => Some(*side),
1064 Self::Draw(_) => None,
1065 }
1066 }
1067
1068 #[inline]
1072 pub fn is_force(&self) -> bool {
1073 matches!(
1074 *self,
1075 Self::Win {
1076 reason: WinReason::Checkmate,
1077 ..
1078 } | Self::Draw(DrawReason::Stalemate)
1079 )
1080 }
1081
1082 #[inline]
1086 pub fn passes(&self, filter: OutcomeFilter) -> bool {
1087 if self.is_force() {
1088 return true;
1089 }
1090 if matches!(filter, OutcomeFilter::Strict | OutcomeFilter::Relaxed)
1091 && matches!(
1092 *self,
1093 Self::Draw(
1094 DrawReason::InsufficientMaterial | DrawReason::Moves75 | DrawReason::Repeat5
1095 )
1096 )
1097 {
1098 return true;
1099 }
1100 matches!(filter, OutcomeFilter::Relaxed)
1101 && matches!(*self, Self::Draw(DrawReason::Moves50 | DrawReason::Repeat3))
1102 }
1103}
1104
1105impl fmt::Display for Outcome {
1106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1107 match self {
1108 Self::Draw(reason) => reason.fmt(f),
1109 Self::Win { side, reason } => match reason {
1110 WinReason::Checkmate => write!(f, "{} checkmates", side.as_long_str()),
1111 WinReason::TimeForfeit => {
1112 write!(f, "{} forfeits on time", side.inv().as_long_str())
1113 }
1114 WinReason::InvalidMove => {
1115 write!(f, "{} made an invalid move", side.inv().as_long_str())
1116 }
1117 WinReason::EngineError => {
1118 write!(f, "{} is a buggy chess engine", side.inv().as_long_str())
1119 }
1120 WinReason::Resign => write!(f, "{} resigns", side.inv().as_long_str()),
1121 WinReason::Abandon => write!(f, "{} abandons the game", side.inv().as_long_str()),
1122 WinReason::Unknown => write!(f, "{} wins by unknown reason", side.as_long_str()),
1123 },
1124 }
1125 }
1126}
1127
1128#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, Hash)]
1130pub enum GameStatus {
1131 #[display(fmt = "1-0")]
1133 White,
1134 #[display(fmt = "0-1")]
1136 Black,
1137 #[display(fmt = "1/2-1/2")]
1139 Draw,
1140 #[display(fmt = "*")]
1142 Running,
1143}
1144
1145impl From<Option<Outcome>> for GameStatus {
1146 #[inline]
1147 fn from(src: Option<Outcome>) -> Self {
1148 match src {
1149 Some(Outcome::Win {
1150 side: Color::White, ..
1151 }) => Self::White,
1152 Some(Outcome::Win {
1153 side: Color::Black, ..
1154 }) => Self::Black,
1155 Some(Outcome::Draw(_)) => Self::Draw,
1156 None => Self::Running,
1157 }
1158 }
1159}
1160
1161impl From<Outcome> for GameStatus {
1162 #[inline]
1163 fn from(src: Outcome) -> Self {
1164 Self::from(Some(src))
1165 }
1166}
1167
1168impl From<&Outcome> for GameStatus {
1169 #[inline]
1170 fn from(src: &Outcome) -> Self {
1171 Self::from(Some(*src))
1172 }
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177 use super::*;
1178
1179 #[test]
1180 fn test_file() {
1181 for (idx, file) in File::iter().enumerate() {
1182 assert_eq!(file.index(), idx);
1183 assert_eq!(File::from_index(idx), file);
1184 }
1185 }
1186
1187 #[test]
1188 fn test_rank() {
1189 for (idx, rank) in Rank::iter().enumerate() {
1190 assert_eq!(rank.index(), idx);
1191 assert_eq!(Rank::from_index(idx), rank);
1192 }
1193 }
1194
1195 #[test]
1196 fn test_piece() {
1197 for (idx, piece) in Piece::iter().enumerate() {
1198 assert_eq!(piece.index(), idx);
1199 assert_eq!(Piece::from_index(idx), piece);
1200 }
1201 }
1202
1203 #[test]
1204 fn test_coord() {
1205 let mut coords = Vec::new();
1206 for rank in Rank::iter() {
1207 for file in File::iter() {
1208 let coord = Coord::from_parts(file, rank);
1209 assert_eq!(coord.file(), file);
1210 assert_eq!(coord.rank(), rank);
1211 coords.push(coord);
1212 }
1213 }
1214 assert_eq!(coords, Coord::iter().collect::<Vec<_>>());
1215 }
1216
1217 #[test]
1218 fn test_cell() {
1219 assert_eq!(Cell::EMPTY.color(), None);
1220 assert_eq!(Cell::EMPTY.piece(), None);
1221 let mut cells = vec![Cell::EMPTY];
1222 for color in [Color::White, Color::Black] {
1223 for piece in [
1224 Piece::Pawn,
1225 Piece::King,
1226 Piece::Knight,
1227 Piece::Bishop,
1228 Piece::Rook,
1229 Piece::Queen,
1230 ] {
1231 let cell = Cell::from_parts(color, piece);
1232 assert_eq!(cell.color(), Some(color));
1233 assert_eq!(cell.piece(), Some(piece));
1234 cells.push(cell);
1235 }
1236 }
1237 assert_eq!(cells, Cell::iter().collect::<Vec<_>>());
1238 }
1239
1240 #[test]
1241 fn test_castling() {
1242 let empty = CastlingRights::EMPTY;
1243 assert!(!empty.has(Color::White, CastlingSide::Queen));
1244 assert!(!empty.has(Color::White, CastlingSide::King));
1245 assert!(!empty.has_color(Color::White));
1246 assert!(!empty.has(Color::Black, CastlingSide::Queen));
1247 assert!(!empty.has(Color::Black, CastlingSide::King));
1248 assert!(!empty.has_color(Color::Black));
1249 assert_eq!(empty.to_string(), "-");
1250 assert_eq!(CastlingRights::from_str("-"), Ok(empty));
1251
1252 let full = CastlingRights::FULL;
1253 assert!(full.has(Color::White, CastlingSide::Queen));
1254 assert!(full.has(Color::White, CastlingSide::King));
1255 assert!(full.has_color(Color::White));
1256 assert!(full.has(Color::Black, CastlingSide::Queen));
1257 assert!(full.has(Color::Black, CastlingSide::King));
1258 assert!(full.has_color(Color::Black));
1259 assert_eq!(full.to_string(), "KQkq");
1260 assert_eq!(CastlingRights::from_str("KQkq"), Ok(full));
1261
1262 let mut rights = CastlingRights::EMPTY;
1263 rights.set(Color::White, CastlingSide::King);
1264 assert!(!rights.has(Color::White, CastlingSide::Queen));
1265 assert!(rights.has(Color::White, CastlingSide::King));
1266 assert!(rights.has_color(Color::White));
1267 assert!(!rights.has(Color::Black, CastlingSide::Queen));
1268 assert!(!rights.has(Color::Black, CastlingSide::King));
1269 assert!(!rights.has_color(Color::Black));
1270 assert_eq!(rights.to_string(), "K");
1271 assert_eq!(CastlingRights::from_str("K"), Ok(rights));
1272
1273 rights.unset(Color::White, CastlingSide::King);
1274 rights.set(Color::Black, CastlingSide::Queen);
1275 assert!(!rights.has(Color::White, CastlingSide::Queen));
1276 assert!(!rights.has(Color::White, CastlingSide::King));
1277 assert!(!rights.has_color(Color::White));
1278 assert!(rights.has(Color::Black, CastlingSide::Queen));
1279 assert!(!rights.has(Color::Black, CastlingSide::King));
1280 assert!(rights.has_color(Color::Black));
1281 assert_eq!(rights.to_string(), "q");
1282 assert_eq!(CastlingRights::from_str("q"), Ok(rights));
1283 }
1284
1285 #[test]
1286 fn test_coord_str() {
1287 assert_eq!(
1288 Coord::from_parts(File::B, Rank::R4).to_string(),
1289 "b4".to_string()
1290 );
1291 assert_eq!(
1292 Coord::from_parts(File::A, Rank::R1).to_string(),
1293 "a1".to_string()
1294 );
1295 assert_eq!(
1296 Coord::from_str("a1"),
1297 Ok(Coord::from_parts(File::A, Rank::R1))
1298 );
1299 assert_eq!(
1300 Coord::from_str("b4"),
1301 Ok(Coord::from_parts(File::B, Rank::R4))
1302 );
1303 assert!(Coord::from_str("h9").is_err());
1304 assert!(Coord::from_str("i4").is_err());
1305 }
1306
1307 #[test]
1308 fn test_cell_str() {
1309 for cell in Cell::iter() {
1310 let s = cell.to_string();
1311 assert_eq!(Cell::from_str(&s), Ok(cell));
1312 }
1313 }
1314}