1use sashite_epin::{Identifier, Side, State};
20
21use crate::error::ParseError;
22use crate::token::epin_token;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct HandItem {
27 piece: Identifier,
28 count: u32,
29}
30
31impl HandItem {
32 #[must_use]
34 pub const fn piece(self) -> Identifier {
35 self.piece
36 }
37
38 #[must_use]
40 pub const fn count(self) -> u32 {
41 self.count
42 }
43}
44
45#[derive(Debug)]
50pub struct HandIter<'a> {
51 bytes: &'a [u8],
52 pos: usize,
53}
54
55impl<'a> HandIter<'a> {
56 pub(crate) const fn new(bytes: &'a [u8]) -> Self {
58 Self { bytes, pos: 0 }
59 }
60}
61
62impl Iterator for HandIter<'_> {
63 type Item = HandItem;
64
65 fn next(&mut self) -> Option<HandItem> {
66 if self.pos >= self.bytes.len() {
67 return None;
68 }
69 let Ok((next, count, piece)) = read_item(self.bytes, self.pos) else {
70 self.pos = self.bytes.len();
73 return None;
74 };
75 self.pos = next;
76 Some(HandItem { piece, count })
77 }
78}
79
80pub(crate) fn validate(field: &[u8]) -> Result<u32, ParseError> {
82 let mut delimiter = None;
84 let mut slashes = 0usize;
85 for (idx, &b) in field.iter().enumerate() {
86 if b == b'/' {
87 slashes += 1;
88 delimiter = Some(idx);
89 }
90 }
91 let (1, Some(at)) = (slashes, delimiter) else {
92 return Err(ParseError::InvalidHandsDelimiter);
93 };
94
95 let first = validate_one_hand(&field[..at])?;
96 let second = validate_one_hand(&field[at + 1..])?;
97 Ok(first.saturating_add(second))
98}
99
100fn validate_one_hand(bytes: &[u8]) -> Result<u32, ParseError> {
102 let mut seen = [0u64; 10];
104 let mut prev: Option<(u32, u8, u8, u8, u8, u8)> = None;
105 let mut total: u32 = 0;
106
107 let len = bytes.len();
108 let mut i = 0;
109 while i < len {
110 let (next, count, id) = read_item(bytes, i)?;
111 i = next;
112
113 let (letter, case, state, terminal, derived) = token_key(id);
114
115 let index = usize::from(letter) * 24
117 + usize::from(case) * 12
118 + usize::from(state) * 4
119 + usize::from(terminal) * 2
120 + usize::from(derived);
121 let bit = 1u64 << (index % 64);
122 let word = index / 64;
123 if seen[word] & bit != 0 {
124 return Err(ParseError::HandNotAggregated);
125 }
126 seen[word] |= bit;
127
128 let cur = (u32::MAX - count, letter, case, state, terminal, derived);
131 if let Some(p) = prev {
132 if cur < p {
133 return Err(ParseError::HandNotCanonical);
134 }
135 }
138 prev = Some(cur);
139
140 total = total.saturating_add(count);
141 }
142
143 Ok(total)
144}
145
146fn read_item(bytes: &[u8], start: usize) -> Result<(usize, u32, Identifier), ParseError> {
150 let len = bytes.len();
151 let mut i = start;
152
153 let count = if i < len && bytes[i].is_ascii_digit() {
154 if bytes[i] == b'0' {
155 return Err(ParseError::InvalidHandCount); }
157 let mut value: u32 = 0;
158 while i < len && bytes[i].is_ascii_digit() {
159 value = value
160 .saturating_mul(10)
161 .saturating_add(u32::from(bytes[i] - b'0'));
162 i += 1;
163 }
164 if value < 2 {
165 return Err(ParseError::InvalidHandCount); }
167 value
168 } else {
169 1
170 };
171
172 match epin_token(&bytes[i..]) {
173 Some((tok_len, id)) => Ok((i + tok_len, count, id)),
174 None => Err(ParseError::InvalidPieceToken),
175 }
176}
177
178pub(crate) fn token_key(id: Identifier) -> (u8, u8, u8, u8, u8) {
184 let letter = id.letter().as_ascii() - b'A';
185 let case = match id.side() {
186 Side::First => 0,
187 Side::Second => 1,
188 };
189 let state = match id.state() {
190 State::Diminished => 0,
191 State::Enhanced => 1,
192 State::Normal => 2,
193 };
194 (
195 letter,
196 case,
197 state,
198 u8::from(id.is_terminal()),
199 u8::from(id.is_derived()),
200 )
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::error::ParseError;
207
208 fn ok(field: &str) -> u32 {
209 validate(field.as_bytes()).expect("valid hands")
210 }
211 fn err(field: &str) -> ParseError {
212 validate(field.as_bytes()).unwrap_err()
213 }
214
215 #[test]
217 fn both_empty() {
218 assert_eq!(ok("/"), 0);
219 }
220 #[test]
221 fn first_only() {
222 assert_eq!(ok("P/"), 1);
223 }
224 #[test]
225 fn second_only() {
226 assert_eq!(ok("/p"), 1);
227 }
228 #[test]
229 fn multiplicity() {
230 assert_eq!(ok("3P2B/"), 5); }
232 #[test]
233 fn case_order() {
234 assert_eq!(ok("BbPp/"), 4); }
236 #[test]
237 fn cross_hands() {
238 assert_eq!(ok("3P2B/3p2b"), 10);
239 }
240 #[test]
241 fn shogi_like_mixed() {
242 assert_eq!(ok("2P/p"), 3);
243 }
244 #[test]
245 fn state_terminal_derivation_keys() {
246 assert_eq!(ok("-K+KKK^K^'/"), 5);
249 }
250
251 #[test]
253 fn iterator_round_trip() {
254 let encoded: Vec<(char, u32)> = HandIter::new(b"3P2Bp")
255 .map(|it| (it.piece().letter().as_char(), it.count()))
256 .collect();
257 assert_eq!(encoded, vec![('P', 3), ('B', 2), ('P', 1)]);
258 }
259
260 #[test]
262 fn no_delimiter() {
263 assert_eq!(err("PP"), ParseError::InvalidHandsDelimiter);
264 }
265 #[test]
266 fn two_delimiters() {
267 assert_eq!(err("P/p/"), ParseError::InvalidHandsDelimiter);
268 }
269
270 #[test]
272 fn explicit_count_one() {
273 assert_eq!(err("1P/"), ParseError::InvalidHandCount);
274 }
275 #[test]
276 fn leading_zero_count() {
277 assert_eq!(err("02P/"), ParseError::InvalidHandCount);
278 }
279 #[test]
280 fn dangling_count() {
281 assert_eq!(err("2/"), ParseError::InvalidPieceToken);
282 }
283
284 #[test]
286 fn bad_piece() {
287 assert_eq!(err("P$/"), ParseError::InvalidPieceToken);
288 }
289
290 #[test]
292 fn adjacent_duplicate() {
293 assert_eq!(err("PP/"), ParseError::HandNotAggregated); }
295 #[test]
296 fn nonadjacent_duplicate_different_counts() {
297 assert_eq!(err("3P3Q2P/"), ParseError::HandNotAggregated);
300 }
301
302 #[test]
304 fn wrong_letter_order() {
305 assert_eq!(err("QP/"), ParseError::HandNotCanonical); }
307 #[test]
308 fn wrong_multiplicity_order() {
309 assert_eq!(err("2P3Q/"), ParseError::HandNotCanonical); }
311 #[test]
312 fn wrong_case_order() {
313 assert_eq!(err("pP/"), ParseError::HandNotCanonical); }
315}