riichi_calc/parser/
pi_input.rs

1mod hand_creator;
2
3use crate::constants::hand::{Hand, Mentsu};
4use crate::constants::tiles::{Tile, TileType};
5use crate::parser::pi_input::hand_creator::create_hand;
6use crate::parser::ValidationError::{InvalidHand, InvalidTileNumber};
7use crate::parser::{InputBase, PiInput, ValidationError};
8
9#[derive(Clone, Debug, Default)]
10struct PiHandColor {
11    dragon: Vec<u8>,
12    manzu: Vec<u8>,
13    pinzu: Vec<u8>,
14    souzu: Vec<u8>,
15    wind: Vec<u8>,
16}
17
18// TODO convert mentsu to hand to check no tile has more than 4 pieces?
19// TODO check the dora and ura dora as well-to-do this
20impl InputBase for PiInput {
21    fn validate(&self) -> Result<(), ValidationError> {
22        if self.naki.len() * 3 + self.hand.len() != 13 {
23            return Err(InvalidHand("Using too many tiles".to_string()));
24        }
25
26        for tile in &self.hand {
27            if tile.number < 1 {
28                return Err(InvalidTileNumber(
29                    "Tile number should be greater than or equal to 1".to_string(),
30                    tile.number,
31                ));
32            }
33            match tile.tile_type {
34                TileType::Wind => {
35                    if tile.number > 4 {
36                        return Err(InvalidTileNumber(
37                            "Invalid wind number".to_string(),
38                            tile.number,
39                        ));
40                    }
41                }
42                TileType::Dragon => {
43                    if tile.number > 3 {
44                        return Err(InvalidTileNumber(
45                            "Invalid dragon number".to_string(),
46                            tile.number,
47                        ));
48                    }
49                }
50                _ => {
51                    if tile.number > 10 {
52                        return Err(InvalidTileNumber(
53                            "Invalid number tile number".to_string(),
54                            tile.number,
55                        ));
56                    }
57                }
58            }
59        }
60
61        if self.naki.len() > 4 {
62            return Err(InvalidHand("Too many naki".to_string()));
63        }
64
65        for furo in &self.naki {
66            match furo {
67                Mentsu::Koutsu(_, x) | Mentsu::Shuntsu(_, x) => {
68                    if !x {
69                        return Err(InvalidHand(
70                            "Unopened koutsu or shuntsu in naki".to_string(),
71                        ));
72                    }
73                }
74                Mentsu::Janto(_) => {
75                    return Err(InvalidHand("Janto cannot be in naki".to_string()));
76                }
77                Mentsu::Kantsu(_, _) => continue,
78            }
79        }
80
81        Ok(())
82    }
83}
84
85impl PiInput {
86    pub fn to_mentsu(&self) -> Option<(Vec<Hand>, u8)> {
87        let (colors, red_count) = self.to_hand_color();
88        let head_candidate = self.find_toitu();
89        let mut menzen_hand: Vec<Hand> = Vec::new();
90        for head in head_candidate.iter() {
91            let hand = create_hand(&mut colors.clone(), head, &self.naki);
92            if hand.is_some() {
93                menzen_hand.push(hand.unwrap());
94            }
95        }
96
97        if menzen_hand.is_empty() {
98            None
99        } else {
100            Some((menzen_hand, red_count))
101        }
102    }
103
104    fn to_hand_color(&self) -> (PiHandColor, u8) {
105        let mut hand = PiHandColor::default();
106        let mut red_count = 0;
107
108        for &pi in self.hand.iter() {
109            let pi = if pi.number == 10 {
110                red_count += 1;
111                Tile {
112                    number: 5,
113                    tile_type: pi.tile_type.clone(),
114                }
115            } else {
116                pi
117            };
118
119            match pi.tile_type.clone() {
120                TileType::Dragon => hand.dragon.push(pi.number),
121                TileType::Manzu => hand.manzu.push(pi.number),
122                TileType::Pinzu => hand.pinzu.push(pi.number),
123                TileType::Souzu => hand.souzu.push(pi.number),
124                TileType::Wind => hand.wind.push(pi.number),
125            }
126        }
127
128        match self.hora.tile_type {
129            TileType::Dragon => hand.dragon.push(self.hora.number),
130            TileType::Manzu => hand.manzu.push(self.hora.number),
131            TileType::Pinzu => hand.pinzu.push(self.hora.number),
132            TileType::Souzu => hand.souzu.push(self.hora.number),
133            TileType::Wind => hand.wind.push(self.hora.number),
134        }
135
136        (hand, red_count)
137    }
138
139    fn find_toitu(&self) -> Vec<Tile> {
140        let mut seen = Vec::new();
141        let mut duplicates = Vec::new();
142
143        for tile in &self.hand {
144            if seen.contains(tile) {
145                if !duplicates.contains(tile) {
146                    duplicates.push(tile.clone());
147                }
148            } else {
149                seen.push(tile.clone());
150            }
151        }
152
153        if seen.contains(&self.hora) {
154            duplicates.push(self.hora.clone());
155        }
156
157        duplicates
158    }
159}
160
161#[cfg(test)]
162mod validation_test {
163    use crate::constants::hand::Mentsu;
164    use crate::constants::tiles::{Tile, TileType};
165    use crate::parser::pi_input::PiInput;
166    use crate::parser::InputBase;
167    use crate::parser::ValidationError::{InvalidHand, InvalidTileNumber};
168
169    #[test]
170    fn menzen_input() {
171        let input = PiInput {
172            hand: vec![
173                Tile {
174                    number: 1,
175                    tile_type: TileType::Manzu,
176                },
177                Tile {
178                    number: 1,
179                    tile_type: TileType::Manzu,
180                },
181                Tile {
182                    number: 1,
183                    tile_type: TileType::Manzu,
184                },
185                Tile {
186                    number: 3,
187                    tile_type: TileType::Manzu,
188                },
189                Tile {
190                    number: 3,
191                    tile_type: TileType::Manzu,
192                },
193                Tile {
194                    number: 3,
195                    tile_type: TileType::Manzu,
196                },
197                Tile {
198                    number: 5,
199                    tile_type: TileType::Manzu,
200                },
201                Tile {
202                    number: 5,
203                    tile_type: TileType::Manzu,
204                },
205                Tile {
206                    number: 5,
207                    tile_type: TileType::Manzu,
208                },
209                Tile {
210                    number: 7,
211                    tile_type: TileType::Manzu,
212                },
213                Tile {
214                    number: 7,
215                    tile_type: TileType::Manzu,
216                },
217                Tile {
218                    number: 7,
219                    tile_type: TileType::Manzu,
220                },
221                Tile {
222                    number: 9,
223                    tile_type: TileType::Manzu,
224                },
225            ],
226            naki: vec![],
227            hora: Tile {
228                number: 9,
229                tile_type: TileType::Manzu,
230            },
231        };
232
233        assert_eq!(input.validate(), Ok(()));
234    }
235
236    #[test]
237    fn furo_input() {
238        let input = PiInput {
239            hand: vec![
240                Tile {
241                    number: 1,
242                    tile_type: TileType::Manzu,
243                },
244                Tile {
245                    number: 1,
246                    tile_type: TileType::Manzu,
247                },
248                Tile {
249                    number: 1,
250                    tile_type: TileType::Manzu,
251                },
252                Tile {
253                    number: 3,
254                    tile_type: TileType::Manzu,
255                },
256                Tile {
257                    number: 3,
258                    tile_type: TileType::Manzu,
259                },
260                Tile {
261                    number: 3,
262                    tile_type: TileType::Manzu,
263                },
264                Tile {
265                    number: 5,
266                    tile_type: TileType::Manzu,
267                },
268                Tile {
269                    number: 5,
270                    tile_type: TileType::Manzu,
271                },
272                Tile {
273                    number: 5,
274                    tile_type: TileType::Manzu,
275                },
276                Tile {
277                    number: 9,
278                    tile_type: TileType::Manzu,
279                },
280            ],
281            naki: vec![Mentsu::Koutsu(
282                Tile {
283                    number: 7,
284                    tile_type: TileType::Manzu,
285                },
286                true,
287            )],
288            hora: Tile {
289                number: 9,
290                tile_type: TileType::Manzu,
291            },
292        };
293
294        assert_eq!(input.validate(), Ok(()));
295    }
296
297    #[test]
298    fn invalid_pi() {
299        let too_big_suhai_input = PiInput {
300            hand: vec![
301                Tile {
302                    number: 1,
303                    tile_type: TileType::Manzu,
304                },
305                Tile {
306                    number: 1,
307                    tile_type: TileType::Manzu,
308                },
309                Tile {
310                    number: 1,
311                    tile_type: TileType::Manzu,
312                },
313                Tile {
314                    number: 3,
315                    tile_type: TileType::Manzu,
316                },
317                Tile {
318                    number: 3,
319                    tile_type: TileType::Manzu,
320                },
321                Tile {
322                    number: 3,
323                    tile_type: TileType::Manzu,
324                },
325                Tile {
326                    number: 5,
327                    tile_type: TileType::Manzu,
328                },
329                Tile {
330                    number: 5,
331                    tile_type: TileType::Manzu,
332                },
333                Tile {
334                    number: 5,
335                    tile_type: TileType::Manzu,
336                },
337                Tile {
338                    number: 90,
339                    tile_type: TileType::Manzu,
340                },
341            ],
342            naki: vec![Mentsu::Koutsu(
343                Tile {
344                    number: 7,
345                    tile_type: TileType::Manzu,
346                },
347                true,
348            )],
349            hora: Tile {
350                number: 9,
351                tile_type: TileType::Manzu,
352            },
353        };
354
355        assert_eq!(
356            too_big_suhai_input.validate(),
357            Err(InvalidTileNumber(
358                "Invalid number tile number".to_string(),
359                90
360            ))
361        );
362
363        let too_big_wind_input = PiInput {
364            hand: vec![
365                Tile {
366                    number: 5,
367                    tile_type: TileType::Wind,
368                },
369                Tile {
370                    number: 1,
371                    tile_type: TileType::Manzu,
372                },
373                Tile {
374                    number: 1,
375                    tile_type: TileType::Manzu,
376                },
377                Tile {
378                    number: 3,
379                    tile_type: TileType::Manzu,
380                },
381                Tile {
382                    number: 3,
383                    tile_type: TileType::Manzu,
384                },
385                Tile {
386                    number: 3,
387                    tile_type: TileType::Manzu,
388                },
389                Tile {
390                    number: 5,
391                    tile_type: TileType::Manzu,
392                },
393                Tile {
394                    number: 5,
395                    tile_type: TileType::Manzu,
396                },
397                Tile {
398                    number: 5,
399                    tile_type: TileType::Manzu,
400                },
401                Tile {
402                    number: 9,
403                    tile_type: TileType::Manzu,
404                },
405            ],
406            naki: vec![Mentsu::Koutsu(
407                Tile {
408                    number: 7,
409                    tile_type: TileType::Manzu,
410                },
411                true,
412            )],
413            hora: Tile {
414                number: 9,
415                tile_type: TileType::Manzu,
416            },
417        };
418
419        assert_eq!(
420            too_big_wind_input.validate(),
421            Err(InvalidTileNumber("Invalid wind number".to_string(), 5))
422        );
423
424        let too_big_dragon_input = PiInput {
425            hand: vec![
426                Tile {
427                    number: 4,
428                    tile_type: TileType::Dragon,
429                },
430                Tile {
431                    number: 1,
432                    tile_type: TileType::Manzu,
433                },
434                Tile {
435                    number: 1,
436                    tile_type: TileType::Manzu,
437                },
438                Tile {
439                    number: 3,
440                    tile_type: TileType::Manzu,
441                },
442                Tile {
443                    number: 3,
444                    tile_type: TileType::Manzu,
445                },
446                Tile {
447                    number: 3,
448                    tile_type: TileType::Manzu,
449                },
450                Tile {
451                    number: 5,
452                    tile_type: TileType::Manzu,
453                },
454                Tile {
455                    number: 5,
456                    tile_type: TileType::Manzu,
457                },
458                Tile {
459                    number: 5,
460                    tile_type: TileType::Manzu,
461                },
462                Tile {
463                    number: 9,
464                    tile_type: TileType::Manzu,
465                },
466            ],
467            naki: vec![Mentsu::Koutsu(
468                Tile {
469                    number: 7,
470                    tile_type: TileType::Manzu,
471                },
472                true,
473            )],
474            hora: Tile {
475                number: 9,
476                tile_type: TileType::Manzu,
477            },
478        };
479
480        assert_eq!(
481            too_big_dragon_input.validate(),
482            Err(InvalidTileNumber("Invalid dragon number".to_string(), 4))
483        );
484
485        let too_small_suhai_input = PiInput {
486            hand: vec![
487                Tile {
488                    number: 0,
489                    tile_type: TileType::Manzu,
490                },
491                Tile {
492                    number: 1,
493                    tile_type: TileType::Manzu,
494                },
495                Tile {
496                    number: 1,
497                    tile_type: TileType::Manzu,
498                },
499                Tile {
500                    number: 3,
501                    tile_type: TileType::Manzu,
502                },
503                Tile {
504                    number: 3,
505                    tile_type: TileType::Manzu,
506                },
507                Tile {
508                    number: 3,
509                    tile_type: TileType::Manzu,
510                },
511                Tile {
512                    number: 5,
513                    tile_type: TileType::Manzu,
514                },
515                Tile {
516                    number: 5,
517                    tile_type: TileType::Manzu,
518                },
519                Tile {
520                    number: 5,
521                    tile_type: TileType::Manzu,
522                },
523                Tile {
524                    number: 9,
525                    tile_type: TileType::Manzu,
526                },
527            ],
528            naki: vec![Mentsu::Koutsu(
529                Tile {
530                    number: 7,
531                    tile_type: TileType::Manzu,
532                },
533                true,
534            )],
535            hora: Tile {
536                number: 9,
537                tile_type: TileType::Manzu,
538            },
539        };
540
541        assert_eq!(
542            too_small_suhai_input.validate(),
543            Err(InvalidTileNumber(
544                "Tile number should be greater than or equal to 1".to_string(),
545                0
546            ))
547        );
548    }
549
550    #[test]
551    fn menzen_naki() {
552        let naki_anko_input = PiInput {
553            hand: vec![
554                Tile {
555                    number: 1,
556                    tile_type: TileType::Manzu,
557                },
558                Tile {
559                    number: 1,
560                    tile_type: TileType::Manzu,
561                },
562                Tile {
563                    number: 1,
564                    tile_type: TileType::Manzu,
565                },
566                Tile {
567                    number: 3,
568                    tile_type: TileType::Manzu,
569                },
570                Tile {
571                    number: 3,
572                    tile_type: TileType::Manzu,
573                },
574                Tile {
575                    number: 3,
576                    tile_type: TileType::Manzu,
577                },
578                Tile {
579                    number: 5,
580                    tile_type: TileType::Manzu,
581                },
582                Tile {
583                    number: 5,
584                    tile_type: TileType::Manzu,
585                },
586                Tile {
587                    number: 5,
588                    tile_type: TileType::Manzu,
589                },
590                Tile {
591                    number: 9,
592                    tile_type: TileType::Manzu,
593                },
594            ],
595            naki: vec![Mentsu::Koutsu(
596                Tile {
597                    number: 7,
598                    tile_type: TileType::Manzu,
599                },
600                false,
601            )],
602            hora: Tile {
603                number: 9,
604                tile_type: TileType::Manzu,
605            },
606        };
607
608        assert_eq!(
609            naki_anko_input.validate(),
610            Err(InvalidHand(
611                "Unopened koutsu or shuntsu in naki".to_string()
612            ))
613        );
614
615        let naki_janto_input = PiInput {
616            hand: vec![
617                Tile {
618                    number: 1,
619                    tile_type: TileType::Manzu,
620                },
621                Tile {
622                    number: 1,
623                    tile_type: TileType::Manzu,
624                },
625                Tile {
626                    number: 1,
627                    tile_type: TileType::Manzu,
628                },
629                Tile {
630                    number: 3,
631                    tile_type: TileType::Manzu,
632                },
633                Tile {
634                    number: 3,
635                    tile_type: TileType::Manzu,
636                },
637                Tile {
638                    number: 3,
639                    tile_type: TileType::Manzu,
640                },
641                Tile {
642                    number: 5,
643                    tile_type: TileType::Manzu,
644                },
645                Tile {
646                    number: 5,
647                    tile_type: TileType::Manzu,
648                },
649                Tile {
650                    number: 5,
651                    tile_type: TileType::Manzu,
652                },
653                Tile {
654                    number: 9,
655                    tile_type: TileType::Manzu,
656                },
657            ],
658            naki: vec![Mentsu::Janto(Tile {
659                number: 7,
660                tile_type: TileType::Manzu,
661            })],
662            hora: Tile {
663                number: 9,
664                tile_type: TileType::Manzu,
665            },
666        };
667
668        assert_eq!(
669            naki_janto_input.validate(),
670            Err(InvalidHand("Janto cannot be in naki".to_string()))
671        );
672    }
673}
674
675#[cfg(test)]
676mod convertor_test {
677    use crate::constants::hand::Mentsu::{Janto, Shuntsu};
678    use crate::constants::tiles::{Tile, TileType};
679    use crate::parser::pi_input::PiInput;
680    use crate::parser::InputBase;
681
682    #[test]
683    fn all_shuntsu_pinfu_iipeco() {
684        let input = PiInput {
685            hand: vec![
686                Tile {
687                    number: 1,
688                    tile_type: TileType::Manzu,
689                },
690                Tile {
691                    number: 2,
692                    tile_type: TileType::Manzu,
693                },
694                Tile {
695                    number: 3,
696                    tile_type: TileType::Manzu,
697                },
698                Tile {
699                    number: 1,
700                    tile_type: TileType::Manzu,
701                },
702                Tile {
703                    number: 2,
704                    tile_type: TileType::Manzu,
705                },
706                Tile {
707                    number: 3,
708                    tile_type: TileType::Manzu,
709                },
710                Tile {
711                    number: 4,
712                    tile_type: TileType::Pinzu,
713                },
714                Tile {
715                    number: 5,
716                    tile_type: TileType::Pinzu,
717                },
718                Tile {
719                    number: 6,
720                    tile_type: TileType::Pinzu,
721                },
722                Tile {
723                    number: 9,
724                    tile_type: TileType::Pinzu,
725                },
726                Tile {
727                    number: 6,
728                    tile_type: TileType::Souzu,
729                },
730                Tile {
731                    number: 7,
732                    tile_type: TileType::Souzu,
733                },
734                Tile {
735                    number: 8,
736                    tile_type: TileType::Souzu,
737                },
738            ],
739            naki: vec![],
740            hora: Tile {
741                number: 9,
742                tile_type: TileType::Pinzu,
743            },
744        };
745        let hand = vec![[
746            Janto(Tile {
747                number: 9,
748                tile_type: TileType::Pinzu,
749            }),
750            Shuntsu(
751                Tile {
752                    number: 4,
753                    tile_type: TileType::Pinzu,
754                },
755                false,
756            ),
757            Shuntsu(
758                Tile {
759                    number: 1,
760                    tile_type: TileType::Manzu,
761                },
762                false,
763            ),
764            Shuntsu(
765                Tile {
766                    number: 1,
767                    tile_type: TileType::Manzu,
768                },
769                false,
770            ),
771            Shuntsu(
772                Tile {
773                    number: 6,
774                    tile_type: TileType::Souzu,
775                },
776                false,
777            ),
778        ]];
779        assert!(input.validate().is_ok());
780        assert_eq!(input.to_mentsu(), Some((hand, 0)))
781    }
782
783    #[test]
784    fn all_shuntsu_pinfu_iipeco_red() {
785        let input = PiInput {
786            hand: vec![
787                Tile {
788                    number: 1,
789                    tile_type: TileType::Manzu,
790                },
791                Tile {
792                    number: 2,
793                    tile_type: TileType::Manzu,
794                },
795                Tile {
796                    number: 3,
797                    tile_type: TileType::Manzu,
798                },
799                Tile {
800                    number: 1,
801                    tile_type: TileType::Manzu,
802                },
803                Tile {
804                    number: 2,
805                    tile_type: TileType::Manzu,
806                },
807                Tile {
808                    number: 3,
809                    tile_type: TileType::Manzu,
810                },
811                Tile {
812                    number: 4,
813                    tile_type: TileType::Pinzu,
814                },
815                Tile {
816                    number: 10,
817                    tile_type: TileType::Pinzu,
818                },
819                Tile {
820                    number: 6,
821                    tile_type: TileType::Pinzu,
822                },
823                Tile {
824                    number: 9,
825                    tile_type: TileType::Pinzu,
826                },
827                Tile {
828                    number: 6,
829                    tile_type: TileType::Souzu,
830                },
831                Tile {
832                    number: 7,
833                    tile_type: TileType::Souzu,
834                },
835                Tile {
836                    number: 8,
837                    tile_type: TileType::Souzu,
838                },
839            ],
840            naki: vec![],
841            hora: Tile {
842                number: 9,
843                tile_type: TileType::Pinzu,
844            },
845        };
846        let hand = vec![[
847            Janto(Tile {
848                number: 9,
849                tile_type: TileType::Pinzu,
850            }),
851            Shuntsu(
852                Tile {
853                    number: 4,
854                    tile_type: TileType::Pinzu,
855                },
856                false,
857            ),
858            Shuntsu(
859                Tile {
860                    number: 1,
861                    tile_type: TileType::Manzu,
862                },
863                false,
864            ),
865            Shuntsu(
866                Tile {
867                    number: 1,
868                    tile_type: TileType::Manzu,
869                },
870                false,
871            ),
872            Shuntsu(
873                Tile {
874                    number: 6,
875                    tile_type: TileType::Souzu,
876                },
877                false,
878            ),
879        ]];
880        assert!(input.validate().is_ok());
881        assert_eq!(input.to_mentsu(), Some((hand, 1)))
882    }
883}