1use std::fmt;
2
3pub struct InvalidRankedScoreError;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum ScoreRank {
7 Normal { fu: u8, han: u8 },
8 Mangan,
9 Haneman,
10 Baiman,
11 Sanbaiman,
12 Yakuman, }
14
15impl Default for ScoreRank {
16 fn default() -> Self {
17 ScoreRank::Normal { fu: 0, han: 0 }
18 }
19}
20
21const RANKS: [(&str, ScoreRank); 5] = [("満貫", ScoreRank::Mangan), ("跳満", ScoreRank::Haneman), ("倍満", ScoreRank::Baiman), ("三倍満", ScoreRank::Sanbaiman), ("役満", ScoreRank::Yakuman)];
22
23#[derive(Debug, PartialEq)]
24pub enum Score {
25 OyaTsumo(i32),
26 KoTsumo(i32, i32), Ron(i32),
28}
29
30impl Default for Score {
31 fn default() -> Self {
32 Score::Ron(0)
33 }
34}
35
36#[derive(Debug, PartialEq, Default)]
37pub struct RankedScore {
38 pub rank: ScoreRank,
39 pub score: Score,
40}
41
42impl fmt::Display for ScoreRank {
43 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44 match self {
45 ScoreRank::Normal { fu, han } => write!(f, "{}符{}飜", fu, han),
46 ScoreRank::Mangan => write!(f, "満貫"),
47 ScoreRank::Haneman => write!(f, "跳満"),
48 ScoreRank::Baiman => write!(f, "倍満"),
49 ScoreRank::Sanbaiman => write!(f, "三倍満"),
50 ScoreRank::Yakuman => write!(f, "役満"),
51 }
52 }
53}
54
55impl fmt::Display for Score {
56 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57 match self {
58 Score::OyaTsumo(x) => write!(f, "{}点∀", x),
59 Score::KoTsumo(ko, oya) => write!(f, "{}-{}点", ko, oya),
60 Score::Ron(x) => write!(f, "{}点", x),
61 }
62 }
63}
64
65impl fmt::Display for RankedScore {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 write!(f, "{}{}", self.rank, self.score)
68 }
69}
70
71impl std::str::FromStr for RankedScore {
72 type Err = InvalidRankedScoreError;
73
74 fn from_str(s: &str) -> Result<Self, Self::Err> {
75 parse_exact_ranked_score(s).ok_or(InvalidRankedScoreError)
76 }
77}
78
79fn parse_number<T: std::str::FromStr>(it: &mut std::str::Chars) -> Option<T> {
80 let mut num = String::new();
81 while let Some(ch) = it.clone().next() {
82 if ch.is_ascii_digit() {
83 num.push(ch);
84 it.next(); } else {
86 break;
87 }
88 }
89 num.parse().ok()
90}
91
92fn parse_symbol(it: &mut std::str::Chars, symbol: &str) -> bool {
93 let mut tmp = it.clone();
94 for expected in symbol.chars() {
95 if tmp.next() != Some(expected) {
96 return false;
97 }
98 }
99 *it = tmp; true
101}
102
103fn parse_rank_normal(it: &mut std::str::Chars) -> Option<ScoreRank> {
104 let mut tmp = it.clone();
105
106 let fu = parse_number(&mut tmp)?;
107 if !parse_symbol(&mut tmp, "符") {
108 return None;
109 }
110
111 let han = parse_number(&mut tmp)?;
112 if !parse_symbol(&mut tmp, "飜") {
113 return None;
114 }
115
116 *it = tmp; Some(ScoreRank::Normal { fu, han })
118}
119
120fn parse_rank_mangan(it: &mut std::str::Chars) -> Option<ScoreRank> {
121 let mut tmp = it.clone();
122
123 for (rank_str, rank) in &RANKS {
124 if parse_symbol(&mut tmp, rank_str) {
125 *it = tmp; return Some(*rank);
127 }
128 }
129 None
130}
131
132fn parse_rank(it: &mut std::str::Chars) -> Option<ScoreRank> {
133 let mut tmp = it.clone();
134
135 if let Some(x) = parse_rank_normal(&mut tmp) {
136 *it = tmp; return Some(x);
138 }
139
140 if let Some(x) = parse_rank_mangan(&mut tmp) {
141 *it = tmp; return Some(x);
143 }
144
145 None
146}
147
148fn parse_score(it: &mut std::str::Chars) -> Option<Score> {
149 let mut tmp = it.clone();
150
151 let num = parse_number(&mut tmp)?;
152 if parse_symbol(&mut tmp, "-") {
153 let num2 = parse_number(&mut tmp)?;
155 if !parse_symbol(&mut tmp, "点") {
156 return None;
157 }
158
159 *it = tmp; Some(Score::KoTsumo(num, num2))
161 } else if parse_symbol(&mut tmp, "点") {
162 if parse_symbol(&mut tmp, "∀") {
163 *it = tmp; Some(Score::OyaTsumo(num))
166 } else {
167 *it = tmp; Some(Score::Ron(num))
170 }
171 } else {
172 None
173 }
174}
175
176fn parse_ranked_score(it: &mut std::str::Chars) -> Option<RankedScore> {
177 Some(RankedScore {
178 rank: parse_rank(it)?,
179 score: parse_score(it)?,
180 })
181}
182
183fn parse_exact_ranked_score(s: &str) -> Option<RankedScore> {
184 let mut it = s.chars();
185 let ret = parse_ranked_score(&mut it)?;
186 if it.next().is_none() {
187 Some(ret)
188 } else {
189 None
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use crate::score::*;
196
197 #[test]
198 fn test_parse_ron() {
199 assert_eq!(
200 parse_exact_ranked_score("40符3飜7700点"),
201 Some(RankedScore {
202 rank: ScoreRank::Normal { fu: 40, han: 3 },
203 score: Score::Ron(7700)
204 })
205 );
206 assert_eq!(
207 parse_exact_ranked_score("満貫8000点"),
208 Some(RankedScore {
209 rank: ScoreRank::Mangan,
210 score: Score::Ron(8000)
211 })
212 );
213 assert_eq!(parse_exact_ranked_score("40符3飜7700点 "), None);
214 }
215
216 #[test]
217 fn test_parse_ko_tsumo() {
218 assert_eq!(
219 parse_exact_ranked_score("30符3飜1000-2000点"),
220 Some(RankedScore {
221 rank: ScoreRank::Normal { fu: 30, han: 3 },
222 score: Score::KoTsumo(1000, 2000)
223 })
224 );
225 assert_eq!(
226 parse_exact_ranked_score("跳満3000-6000点"),
227 Some(RankedScore {
228 rank: ScoreRank::Haneman,
229 score: Score::KoTsumo(3000, 6000)
230 })
231 );
232 assert_eq!(parse_exact_ranked_score("30符3飜1000-2000点 "), None);
233 }
234
235 #[test]
236 fn test_parse_oya_tsumo() {
237 assert_eq!(
238 parse_exact_ranked_score("30符3飜2000点∀"),
239 Some(RankedScore {
240 rank: ScoreRank::Normal { fu: 30, han: 3 },
241 score: Score::OyaTsumo(2000)
242 })
243 );
244 assert_eq!(
245 parse_exact_ranked_score("満貫4000点∀"),
246 Some(RankedScore {
247 rank: ScoreRank::Mangan,
248 score: Score::OyaTsumo(4000)
249 })
250 );
251 assert_eq!(parse_exact_ranked_score("30符3飜2000点∀ "), None);
252 }
253}