1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! # Ranks
//!
//! A [`Rank`] is one of the eight horizontal rows on a chess board, numbered
//! `1` through `8` from White's perspective. Ranks are stored as a `u8`
//! discriminant (`First = 0`, `Eighth = 7`) and can be converted to a
//! [`Bitboard`](crate::bitboard::Bitboard) mask via [`Rank::MASKS`] or
//! `Bitboard::from(rank)`.
/// One of the eight ranks on a chess board, numbered from White's perspective.
///
/// The discriminant is the 0-based rank index: `Rank::First as u8 == 0`,
/// `Rank::Eighth as u8 == 7`.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
pub enum Rank {
/// Rank 1 — White's back rank.
First = 0,
/// Rank 2 — White's pawn rank.
Second,
/// Rank 3.
Third,
/// Rank 4.
Fourth,
/// Rank 5.
Fifth,
/// Rank 6.
Sixth,
/// Rank 7 — Black's pawn rank.
Seventh,
/// Rank 8 — Black's back rank.
Eighth,
}
impl Rank {
/// Constructs a [`Rank`] from a 0-based index.
///
/// # Panics
/// Panics if `idx >= 8`.
///
/// # Example
/// ```
/// # use ruchess::rank::Rank;
/// assert_eq!(Rank::new(0), Rank::First);
/// assert_eq!(Rank::new(7), Rank::Eighth);
/// ```
#[inline]
pub const fn new(idx: u32) -> Rank {
assert!(idx < 8);
unsafe { Rank::new_unchecked(idx) }
}
/// Constructs a [`Rank`] from a 0-based index without bounds checking.
///
/// # Safety
///
/// Function must be called with an index < 8.
#[inline]
pub const unsafe fn new_unchecked(index: u32) -> Rank {
debug_assert!(index < 8);
unsafe { std::mem::transmute(index as u8) }
}
/// Bitboard masks indexed by rank. `MASKS[rank as usize]` is a `u64`
/// with every square on that rank set.
pub const MASKS: [u64; 8] = [
0x0000_0000_0000_00FF, // Rank 1
0x0000_0000_0000_FF00, // Rank 2
0x0000_0000_00FF_0000, // Rank 3
0x0000_0000_FF00_0000, // Rank 4
0x0000_00FF_0000_0000, // Rank 5
0x0000_FF00_0000_0000, // Rank 6
0x00FF_0000_0000_0000, // Rank 7
0xFF00_0000_0000_0000, // Rank 8
];
/// Returns the underlying 0-based rank index.
///
/// # Example
/// ```
/// # use ruchess::rank::Rank;
/// assert_eq!(Rank::First.as_u8(), 0);
/// assert_eq!(Rank::Eighth.as_u8(), 7);
/// ```
#[inline]
pub const fn as_u8(self) -> u8 {
// Safety: self is repr u8
unsafe { std::mem::transmute(self) }
}
/// Returns the ASCII digit (`'1'`–`'8'`) for this rank.
///
/// # Example
/// ```
/// # use ruchess::rank::Rank;
/// assert_eq!(Rank::First.as_char(), '1');
/// assert_eq!(Rank::Eighth.as_char(), '8');
/// ```
pub fn as_char(&self) -> char {
match self {
Rank::First => '1',
Rank::Second => '2',
Rank::Third => '3',
Rank::Fourth => '4',
Rank::Fifth => '5',
Rank::Sixth => '6',
Rank::Seventh => '7',
Rank::Eighth => '8',
}
}
/// Returns the ASCII digit for this rank as a string slice.
///
/// # Example
/// ```
/// # use ruchess::rank::Rank;
/// assert_eq!(Rank::First.as_str(), "1");
/// ```
pub fn as_str(&self) -> &'static str {
match self {
Rank::First => "1",
Rank::Second => "2",
Rank::Third => "3",
Rank::Fourth => "4",
Rank::Fifth => "5",
Rank::Sixth => "6",
Rank::Seventh => "7",
Rank::Eighth => "8",
}
}
/// Parses a rank from a single ASCII byte in the range `'1'..='8'`.
///
/// Returns `None` if `value` is not a digit between `'1'` and `'8'`.
///
/// # Example
/// ```
/// # use ruchess::rank::Rank;
/// assert_eq!(Rank::from_byte(b'1'), Some(Rank::First));
/// assert_eq!(Rank::from_byte(b'8'), Some(Rank::Eighth));
/// assert_eq!(Rank::from_byte(b'9'), None);
/// ```
pub fn from_byte(value: u8) -> Option<Self> {
match value {
b'1' => Some(Rank::First),
b'2' => Some(Rank::Second),
b'3' => Some(Rank::Third),
b'4' => Some(Rank::Fourth),
b'5' => Some(Rank::Fifth),
b'6' => Some(Rank::Sixth),
b'7' => Some(Rank::Seventh),
b'8' => Some(Rank::Eighth),
_ => None,
}
}
}
impl std::fmt::Debug for Rank {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Rank={}", self.as_str())
}
}
impl std::fmt::Display for Rank {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Rank={}", self.as_str())
}
}
/// Error returned when a string cannot be parsed as a [`Rank`].
///
/// The wrapped `String` is the offending input, preserved verbatim for
/// diagnostics.
#[derive(Debug, PartialEq)]
pub struct ParseRankError(pub String);
impl std::fmt::Display for ParseRankError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "invalid rank: `{}`", self.0)
}
}
impl std::error::Error for ParseRankError {}
impl std::str::FromStr for Rank {
type Err = ParseRankError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let p = s.as_bytes();
if p.len() != 1 {
return Err(ParseRankError(s.to_string()));
}
Self::from_byte(p[0]).ok_or(ParseRankError(s.to_string()))
}
}