1use num_derive::FromPrimitive;
10use num_traits::FromPrimitive;
11use serde_derive::{Serialize, Deserialize};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
16pub enum ParseError {
17 #[error("Invalid hai number")]
18 InvalidHaiNumber,
19 #[error("Invalid player number")]
20 InvalidPlayerNumber,
21 #[error("Invalid tenhou rank")]
22 InvalidTenhouRank,
23 #[error("Invalid extra ryuukyoku reason")]
24 InvalidExtraRyuukyokuReason,
25}
26
27#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
37pub struct Hai(u8);
38
39#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
41pub struct Player(u8);
42
43pub type GamePoint = i32;
45
46#[repr(u8)]
48#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
49pub enum Direction {
50 #[default]
52 SelfSeat,
53 Shimocha,
55 Toimen,
57 Kamicha,
59}
60
61#[repr(u8)]
63#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
64pub enum TenhouRoom {
65 #[default]
67 Ippan,
68 Joukyu,
70 Tokujou,
72 Houou,
74}
75
76#[repr(u8)]
78#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
79pub enum TenhouRank {
80 #[default]
81 Newcomer,
82 Kyu9,
83 Kyu8,
84 Kyu7,
85 Kyu6,
86 Kyu5,
87 Kyu4,
88 Kyu3,
89 Kyu2,
90 Kyu1,
91 Dan1,
92 Dan2,
93 Dan3,
94 Dan4,
95 Dan5,
96 Dan6,
97 Dan7,
98 Dan8,
99 Dan9,
100 Dan10,
101 Tenhou,
102}
103
104#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
106pub struct GameSettings {
107 pub vs_human: bool,
108 pub no_red: bool,
109 pub no_kuitan: bool,
110 pub hanchan: bool,
111 pub sanma: bool,
112 pub soku: bool,
113 pub room: TenhouRoom,
114}
115
116#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
118pub struct InitSeed {
119 pub kyoku: u8,
120 pub honba: u8,
121 pub kyoutaku: u8,
122 pub dice: (u8, u8),
123 pub dora_hyouji: Hai,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub enum Meld {
129 Chii {
130 combination: (Hai, Hai, Hai),
131 called_position: u8,
133 },
134 Pon {
135 dir: Direction,
136 combination: (Hai, Hai, Hai),
137 called: Hai,
138 unused: Hai,
139 },
140 Kakan {
142 dir: Direction,
143 combination: (Hai, Hai, Hai),
144 called: Hai,
145 added: Hai,
146 },
147 Daiminkan {
148 dir: Direction,
149 hai: Hai,
150 },
151 Ankan {
152 hai: Hai,
153 },
154}
155
156#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
158pub enum ExtraRyuukyokuReason {
159 #[default]
161 KyuusyuKyuuhai,
162 SuuchaRiichi,
164 SanchaHoura,
166 SuukanSanra,
168 SuufuuRenda,
170 NagashiMangan,
172}
173
174#[repr(u8)]
176#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
177pub enum ScoreRank {
178 #[default]
179 Normal,
180 Mangan,
182 Haneman,
184 Baiman,
186 Sanbaiman,
188 Yakuman,
190}
191
192#[repr(u8)]
194#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, FromPrimitive)]
195pub enum Yaku {
196 #[default]
197 MenzenTsumo,
198 Riichi,
199 Ippatsu,
200 Chankan,
201 Rinshankaihou,
202 HaiteiTsumo,
203 HouteiRon,
204 Pinfu,
205 Tanyao,
206 Iipeikou,
207 PlayerWindTon,
208 PlayerWindNan,
209 PlayerWindSha,
210 PlayerWindPei,
211 FieldWindTon,
212 FieldWindNan,
213 FieldWindSha,
214 FieldWindPei,
215 YakuhaiHaku,
216 YakuhaiHatsu,
217 YakuhaiChun,
218 DoubleRiichi,
219 Chiitoitsu,
220 Chanta,
221 Ikkitsuukan,
222 SansyokuDoujun,
223 SanshokuDoukou,
224 Sankantsu,
225 Toitoi,
226 Sanannkou,
227 Shousangen,
228 Honroutou,
229 Ryanpeikou,
230 Junchan,
231 Honiisou,
232 Chiniisou,
233 Renhou,
234 Tenhou,
235 Chiihou,
236 Daisangen,
237 Suuankou,
238 SuuankouTanki,
239 Tsuuiisou,
240 Ryuuiisou,
241 Chinroutou,
242 Tyuurenpoutou,
243 Tyuurenpoutou9,
244 Kokushimusou,
245 Kokushimusou13,
246 Daisuushii,
247 Syousuushii,
248 Suukantsu,
249 Dora,
250 UraDora,
251 AkaDora,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct ActionAGARI {
257 pub honba: u8,
259
260 pub kyoutaku: u8,
262
263 pub hai: Vec<Hai>,
265
266 pub m: Vec<Meld>,
268
269 pub machi: Hai,
271
272 pub fu: u8,
274
275 pub net_score: u32,
277
278 pub score_rank: ScoreRank,
280
281 pub yaku: Vec<(Yaku, u8)>,
285
286 pub yakuman: Vec<Yaku>,
290
291 pub dora_hai: Vec<Hai>,
293
294 pub dora_hai_ura: Vec<Hai>,
300
301 pub who: Player,
303
304 pub from_who: Player,
308
309 pub pao_who: Option<Player>,
313
314 pub before_points: Vec<GamePoint>,
316
317 pub delta_points: Vec<GamePoint>,
319
320 pub owari: Option<(Vec<GamePoint>, Vec<f64>)>,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct ActionRYUUKYOKU {
329 pub honba: u8,
331
332 pub kyoutaku: u8,
334
335 pub before_points: Vec<GamePoint>,
337
338 pub delta_points: Vec<GamePoint>,
340
341 pub hai0: Option<Vec<Hai>>,
343
344 pub hai1: Option<Vec<Hai>>,
346
347 pub hai2: Option<Vec<Hai>>,
349
350 pub hai3: Option<Vec<Hai>>,
352
353 pub reason: Option<ExtraRyuukyokuReason>,
357
358 pub owari: Option<(Vec<GamePoint>, Vec<f64>)>,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct ActionSHUFFLE {
367 pub seed: String,
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
372pub struct ActionGO {
373 pub settings: GameSettings,
375 pub lobby: u32,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct ActionUN1 {
384 pub names: Vec<String>,
385 pub dan: Vec<TenhouRank>,
386 pub rate: Vec<f64>,
387 pub sx: Vec<String>,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct ActionUN2 {
395 pub who: Player,
396 pub name: String,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct ActionBYE {
402 pub who: Player,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct ActionTAIKYOKU {
408 pub oya: Player,
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct ActionINIT {
414 pub seed: InitSeed,
415 pub ten: Vec<GamePoint>,
416 pub oya: Player,
417 pub hai: Vec<Vec<Hai>>,
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
427pub struct ActionREACH1 {
428 pub who: Player,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct ActionREACH2 {
434 pub who: Player,
435 pub ten: Vec<GamePoint>,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct ActionN {
441 pub who: Player,
442 pub m: Meld,
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize)]
447pub struct ActionDORA {
448 pub hai: Hai,
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize)]
456pub struct ActionDRAW {
457 pub who: Player,
458 pub hai: Hai,
459}
460
461#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct ActionDISCARD {
467 pub who: Player,
468 pub hai: Hai,
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize)]
473pub enum Action {
474 SHUFFLE(ActionSHUFFLE),
475 GO(ActionGO),
476 UN1(ActionUN1),
477 UN2(ActionUN2),
478 BYE(ActionBYE),
479 TAIKYOKU(ActionTAIKYOKU),
480 INIT(ActionINIT),
481 REACH1(ActionREACH1),
482 REACH2(ActionREACH2),
483 N(ActionN),
484 DORA(ActionDORA),
485 AGARI(ActionAGARI),
486 RYUUKYOKU(ActionRYUUKYOKU),
487 DRAW(ActionDRAW),
488 DISCARD(ActionDISCARD),
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize, Default)]
493pub struct Mjlog {
494 pub ver: f64,
495 pub actions: Vec<Action>,
496}
497
498impl Hai {
499 pub fn new(x: u8) -> Hai {
500 Hai(x)
501 }
502
503 pub fn to_u8(&self) -> u8 {
504 self.0
505 }
506
507 pub fn is_number5(&self) -> bool {
508 let pict_index = self.0 / 4;
510 let pict_type = pict_index / 9;
512 let number = (pict_index % 9) + 1;
513 pict_type <= 2 && number == 5
514 }
515}
516
517impl Player {
518 pub fn new(x: u8) -> Self {
519 Player(x)
520 }
521
522 pub fn to_u8(&self) -> u8 {
523 self.0
524 }
525}
526
527impl ActionAGARI {
528 pub fn is_tsumo(&self) -> bool {
529 self.who == self.from_who
530 }
531}
532
533impl Action {
534 pub fn as_shuffle(&self) -> Option<&ActionSHUFFLE> {
535 match self {
536 Action::SHUFFLE(x) => Some(x),
537 _ => None,
538 }
539 }
540
541 pub fn as_go(&self) -> Option<&ActionGO> {
542 match self {
543 Action::GO(x) => Some(x),
544 _ => None,
545 }
546 }
547
548 pub fn as_un1(&self) -> Option<&ActionUN1> {
549 match self {
550 Action::UN1(x) => Some(x),
551 _ => None,
552 }
553 }
554
555 pub fn as_un2(&self) -> Option<&ActionUN2> {
556 match self {
557 Action::UN2(x) => Some(x),
558 _ => None,
559 }
560 }
561
562 pub fn as_bye(&self) -> Option<&ActionBYE> {
563 match self {
564 Action::BYE(x) => Some(x),
565 _ => None,
566 }
567 }
568
569 pub fn as_taikyoku(&self) -> Option<&ActionTAIKYOKU> {
570 match self {
571 Action::TAIKYOKU(x) => Some(x),
572 _ => None,
573 }
574 }
575
576 pub fn as_init(&self) -> Option<&ActionINIT> {
577 match self {
578 Action::INIT(x) => Some(x),
579 _ => None,
580 }
581 }
582
583 pub fn as_reach1(&self) -> Option<&ActionREACH1> {
584 match self {
585 Action::REACH1(x) => Some(x),
586 _ => None,
587 }
588 }
589
590 pub fn as_reach2(&self) -> Option<&ActionREACH2> {
591 match self {
592 Action::REACH2(x) => Some(x),
593 _ => None,
594 }
595 }
596
597 pub fn as_n(&self) -> Option<&ActionN> {
598 match self {
599 Action::N(x) => Some(x),
600 _ => None,
601 }
602 }
603
604 pub fn as_dora(&self) -> Option<&ActionDORA> {
605 match self {
606 Action::DORA(x) => Some(x),
607 _ => None,
608 }
609 }
610
611 pub fn as_agari(&self) -> Option<&ActionAGARI> {
612 match self {
613 Action::AGARI(x) => Some(x),
614 _ => None,
615 }
616 }
617
618 pub fn as_ryuukyoku(&self) -> Option<&ActionRYUUKYOKU> {
619 match self {
620 Action::RYUUKYOKU(x) => Some(x),
621 _ => None,
622 }
623 }
624
625 pub fn as_draw(&self) -> Option<&ActionDRAW> {
626 match self {
627 Action::DRAW(x) => Some(x),
628 _ => None,
629 }
630 }
631
632 pub fn as_discard(&self) -> Option<&ActionDISCARD> {
633 match self {
634 Action::DISCARD(x) => Some(x),
635 _ => None,
636 }
637 }
638
639 pub fn is_shuffle(&self) -> bool {
640 self.as_shuffle().is_some()
641 }
642
643 pub fn is_go(&self) -> bool {
644 self.as_go().is_some()
645 }
646
647 pub fn is_un1(&self) -> bool {
648 self.as_un1().is_some()
649 }
650
651 pub fn is_un2(&self) -> bool {
652 self.as_un2().is_some()
653 }
654
655 pub fn is_bye(&self) -> bool {
656 self.as_bye().is_some()
657 }
658
659 pub fn is_taikyoku(&self) -> bool {
660 self.as_taikyoku().is_some()
661 }
662
663 pub fn is_init(&self) -> bool {
664 self.as_init().is_some()
665 }
666
667 pub fn is_reach1(&self) -> bool {
668 self.as_reach1().is_some()
669 }
670
671 pub fn is_reach2(&self) -> bool {
672 self.as_reach2().is_some()
673 }
674
675 pub fn is_n(&self) -> bool {
676 self.as_n().is_some()
677 }
678
679 pub fn is_dora(&self) -> bool {
680 self.as_dora().is_some()
681 }
682
683 pub fn is_agari(&self) -> bool {
684 self.as_agari().is_some()
685 }
686
687 pub fn is_ryuukyoku(&self) -> bool {
688 self.as_ryuukyoku().is_some()
689 }
690
691 pub fn is_draw(&self) -> bool {
692 self.as_draw().is_some()
693 }
694
695 pub fn is_discard(&self) -> bool {
696 self.as_discard().is_some()
697 }
698}
699
700impl std::str::FromStr for Hai {
701 type Err = ParseError;
702
703 fn from_str(s: &str) -> Result<Self, Self::Err> {
704 s.parse::<u8>().map(Hai).map_err(|_| ParseError::InvalidHaiNumber)
705 }
706}
707
708impl std::str::FromStr for Player {
709 type Err = ParseError;
710
711 fn from_str(s: &str) -> Result<Self, Self::Err> {
712 s.parse::<u8>().map(Player).map_err(|_| ParseError::InvalidPlayerNumber)
713 }
714}
715
716impl std::str::FromStr for TenhouRank {
717 type Err = ParseError;
718
719 fn from_str(s: &str) -> Result<Self, Self::Err> {
720 let rank = s.parse::<u8>().map_err(|_| ParseError::InvalidTenhouRank)?;
721 TenhouRank::from_u8(rank).ok_or(ParseError::InvalidTenhouRank)
722 }
723}
724
725impl std::str::FromStr for ExtraRyuukyokuReason {
726 type Err = ParseError;
727
728 fn from_str(s: &str) -> Result<Self, Self::Err> {
729 Ok(match s {
730 "yao9" => ExtraRyuukyokuReason::KyuusyuKyuuhai,
731 "reach4" => ExtraRyuukyokuReason::SuuchaRiichi,
732 "ron3" => ExtraRyuukyokuReason::SanchaHoura,
733 "kan4" => ExtraRyuukyokuReason::SuukanSanra,
734 "kaze4" => ExtraRyuukyokuReason::SuufuuRenda,
735 "nm" => ExtraRyuukyokuReason::NagashiMangan,
736 _ => return Err(ParseError::InvalidExtraRyuukyokuReason),
737 })
738 }
739}
740
741impl Default for Meld {
742 fn default() -> Self {
743 Meld::Ankan { hai: Hai::default() }
744 }
745}