1use std::error::Error;
2use std::fmt::{Display, Formatter};
3use std::ops::{Add, Div, Mul, Neg, RangeFrom, RangeInclusive};
4
5use num_traits::{Pow, Signed};
6
7#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
9#[repr(transparent)]
10pub struct Han(i32);
11
12impl Han {
13 #[inline]
23 #[must_use]
24 pub const fn new(value: i32) -> Self {
25 Self(value)
26 }
27
28 #[inline]
38 #[must_use]
39 pub const fn get(&self) -> i32 {
40 self.0
41 }
42}
43
44impl<T: Into<i32>> From<T> for Han {
45 fn from(value: T) -> Self {
46 Self::new(value.into())
47 }
48}
49
50impl Display for Han {
51 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
52 write!(f, "{} han", self.0)
53 }
54}
55
56#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
58#[repr(transparent)]
59pub struct Fu(i32);
60
61impl Fu {
62 #[inline]
72 #[must_use]
73 pub const fn new(value: i32) -> Self {
74 Self(value)
75 }
76
77 #[inline]
87 #[must_use]
88 pub const fn get(&self) -> i32 {
89 self.0
90 }
91}
92
93impl<T: Into<i32>> From<T> for Fu {
94 fn from(value: T) -> Self {
95 Self::new(value.into())
96 }
97}
98
99impl Display for Fu {
100 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101 write!(f, "{} fu", self.0)
102 }
103}
104
105#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
107#[repr(transparent)]
108pub struct Honbas(i32);
109
110impl Honbas {
111 pub const ZERO: Honbas = Honbas::new(0);
122
123 #[inline]
133 #[must_use]
134 pub const fn new(value: i32) -> Self {
135 Self(value)
136 }
137
138 #[inline]
148 #[must_use]
149 pub const fn get(&self) -> i32 {
150 self.0
151 }
152}
153
154impl<T: Into<i32>> From<T> for Honbas {
155 fn from(value: T) -> Self {
156 Self::new(value.into())
157 }
158}
159
160impl Display for Honbas {
161 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
162 write!(f, "{} honbas", self.0)
163 }
164}
165
166impl Default for Honbas {
167 fn default() -> Self {
168 Self::new(0)
169 }
170}
171
172#[derive(Clone, Debug, Eq, PartialEq, Hash)]
173enum PointsMode {
174 Calculated { has_tsumo: bool, has_ron: bool },
175 Limited,
176}
177
178impl PointsMode {
179 #[inline]
180 #[must_use]
181 const fn has_tsumo(&self) -> bool {
182 match self {
183 PointsMode::Calculated { has_tsumo, .. } => *has_tsumo,
184 PointsMode::Limited => true,
185 }
186 }
187
188 #[inline]
189 #[must_use]
190 const fn has_ron(&self) -> bool {
191 match self {
192 PointsMode::Calculated { has_ron, .. } => *has_ron,
193 PointsMode::Limited => true,
194 }
195 }
196}
197
198pub type Points = PointsCustom<i32>;
216
217#[derive(Clone, Debug, Eq, PartialEq, Hash)]
226pub struct PointsCustom<T> {
227 base_points: T,
228 honbas: Honbas,
229 mode: PointsMode,
230}
231
232impl<T> PointsCustom<T>
233where
234 T: Clone,
235 T: Signed,
236 T: From<i32>,
237 T: PartialOrd<T>,
238 T: Add<i32, Output = T>,
239 T: Mul<i32, Output = T>,
240 T: Div<i32, Output = T>,
241 T: Pow<u32, Output = T>,
242{
243 pub fn from_calculated(
278 calculation_mode: PointsCalculationMode,
279 han: Han,
280 fu: Fu,
281 honbas: Honbas,
282 ) -> Result<Self, PointCalculationError> {
283 if calculation_mode == PointsCalculationMode::Default {
284 if han < Han::new(1) {
285 return Err(PointCalculationError::InvalidHan(han));
286 }
287 if !VALID_FU.contains(&fu) {
288 return Err(PointCalculationError::InvalidFu(fu));
289 }
290 if honbas < Honbas::ZERO {
291 return Err(PointCalculationError::InvalidHonbas(honbas));
292 }
293 }
294
295 if calculation_mode != PointsCalculationMode::Unlimited {
296 if MANGAN_HAN_RANGE.contains(&han) {
297 return Ok(Self::mangan(honbas));
298 } else if HANEMAN_HAN_RANGE.contains(&han) {
299 return Ok(Self::haneman(honbas));
300 } else if BAIMAN_HAN_RANGE.contains(&han) {
301 return Ok(Self::baiman(honbas));
302 } else if SANBAIMAN_HAN_RANGE.contains(&han) {
303 return Ok(Self::sanbaiman(honbas));
304 } else if KAZOE_YAKUMAN_HAN_RANGE.contains(&han) {
305 return Ok(Self::yakuman(honbas));
306 }
307 }
308
309 let power = han.0 + 2;
310 const MIN_USABLE_HAN: i32 = -(i32::BITS as i32);
311 let points_base = if power.is_positive() {
312 T::from(2i32).pow(power as u32) * fu.0
313 } else {
314 let power = power.max(MIN_USABLE_HAN).neg() as u32;
317 let multiplier = 2i64.pow(power);
318 let value = if fu.0.is_positive() {
319 (fu.0 as i64 + multiplier - 1) / multiplier
320 } else {
321 fu.0 as i64 / multiplier
322 };
323 T::from(value as i32)
324 };
325 if calculation_mode != PointsCalculationMode::Unlimited && points_base >= T::from(7900 / 4)
326 {
327 Ok(Self::mangan(honbas))
328 } else {
329 let val_has_tsumo =
330 calculation_mode != PointsCalculationMode::Default || has_tsumo(han, fu);
331 let val_has_ron =
332 calculation_mode != PointsCalculationMode::Default || has_ron(han, fu);
333
334 let value = Self::new_calculated(points_base, val_has_tsumo, val_has_ron, honbas);
335 Ok(value)
336 }
337 }
338}
339
340impl<T> PointsCustom<T>
341where
342 T: Clone,
343 T: Signed,
344 T: From<i32>,
345 T: Add<i32, Output = T>,
346 T: Mul<i32, Output = T>,
347 T: Div<i32, Output = T>,
348{
349 #[inline]
361 #[must_use]
362 pub const fn new_limited(base_points: T, honbas: Honbas) -> Self {
363 Self {
364 base_points,
365 mode: PointsMode::Limited,
366 honbas,
367 }
368 }
369
370 #[inline]
382 #[must_use]
383 pub fn mangan(honbas: Honbas) -> Self {
384 Self::new_limited(2000.into(), honbas)
385 }
386
387 #[inline]
399 #[must_use]
400 pub fn haneman(honbas: Honbas) -> Self {
401 Self::new_limited(3000.into(), honbas)
402 }
403
404 #[inline]
416 #[must_use]
417 pub fn baiman(honbas: Honbas) -> Self {
418 Self::new_limited(4000.into(), honbas)
419 }
420
421 #[inline]
433 #[must_use]
434 pub fn sanbaiman(honbas: Honbas) -> Self {
435 Self::new_limited(6000.into(), honbas)
436 }
437
438 #[inline]
450 #[must_use]
451 pub fn yakuman(honbas: Honbas) -> Self {
452 Self::new_limited(8000.into(), honbas)
453 }
454
455 #[inline]
473 #[must_use]
474 pub const fn new_calculated(
475 base_points: T,
476 has_tsumo: bool,
477 has_ron: bool,
478 honbas: Honbas,
479 ) -> Self {
480 Self {
481 base_points,
482 mode: PointsMode::Calculated { has_tsumo, has_ron },
483 honbas,
484 }
485 }
486
487 #[inline]
499 #[must_use]
500 pub const fn is_limited(&self) -> bool {
501 match self.mode {
502 PointsMode::Calculated { .. } => false,
503 PointsMode::Limited => true,
504 }
505 }
506
507 #[inline]
519 #[must_use]
520 pub const fn is_calculated(&self) -> bool {
521 match self.mode {
522 PointsMode::Calculated { .. } => true,
523 PointsMode::Limited => false,
524 }
525 }
526
527 #[inline]
537 #[must_use]
538 pub fn oya_tsumo(&self) -> Option<T> {
539 if self.mode.has_tsumo() {
540 let value = round_up_points(self.base_points.clone() * 2) + self.tsumo_honba_points();
541 Some(value)
542 } else {
543 None
544 }
545 }
546
547 #[inline]
557 #[must_use]
558 pub fn oya_ron(&self) -> Option<T> {
559 if self.mode.has_ron() {
560 let value = round_up_points(self.base_points.clone() * 6) + self.ron_honba_points();
561 Some(value)
562 } else {
563 None
564 }
565 }
566
567 #[inline]
579 #[must_use]
580 pub fn ko_tsumo(&self) -> Option<(T, T)> {
581 if self.mode.has_tsumo() {
582 let honba_points = self.tsumo_honba_points();
583 let value_ko = round_up_points(self.base_points.clone()) + honba_points;
584 let value_oya = round_up_points(self.base_points.clone() * 2) + honba_points;
585 Some((value_ko, value_oya))
586 } else {
587 None
588 }
589 }
590
591 #[inline]
601 #[must_use]
602 pub fn ko_ron(&self) -> Option<T> {
603 if self.mode.has_ron() {
604 let value = round_up_points(self.base_points.clone() * 4) + self.ron_honba_points();
605 Some(value)
606 } else {
607 None
608 }
609 }
610
611 #[inline]
622 #[must_use]
623 pub fn honbas(&self) -> Honbas {
624 self.honbas
625 }
626
627 #[inline]
628 #[must_use]
629 fn tsumo_honba_points(&self) -> i32 {
630 self.honbas.get() * 100
631 }
632
633 #[inline]
634 #[must_use]
635 fn ron_honba_points(&self) -> i32 {
636 self.honbas.get() * 300
637 }
638}
639
640#[inline]
641#[must_use]
642fn round_up_points<T>(num: T) -> T
643where
644 T: Signed,
645 T: Add<i32, Output = T>,
646 T: Mul<i32, Output = T>,
647 T: Div<i32, Output = T>,
648{
649 round_up_to(num, 100)
650}
651
652#[inline]
653#[must_use]
654fn round_up_to<T>(num: T, divisor: i32) -> T
655where
656 T: Signed,
657 T: Add<i32, Output = T>,
658 T: Mul<i32, Output = T>,
659 T: Div<i32, Output = T>,
660{
661 if num.is_positive() {
662 (num + (divisor - 1)) / divisor * divisor
663 } else {
664 num / divisor * divisor
665 }
666}
667
668pub const MANGAN_HAN_RANGE: RangeInclusive<Han> = Han::new(5)..=Han::new(5);
671pub const HANEMAN_HAN_RANGE: RangeInclusive<Han> = Han::new(6)..=Han::new(7);
673pub const BAIMAN_HAN_RANGE: RangeInclusive<Han> = Han::new(8)..=Han::new(10);
675pub const SANBAIMAN_HAN_RANGE: RangeInclusive<Han> = Han::new(11)..=Han::new(12);
677pub const KAZOE_YAKUMAN_HAN_RANGE: RangeFrom<Han> = Han::new(13)..;
679
680#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
682pub enum PointsCalculationMode {
683 Default,
687 Loose,
691 Unlimited,
698}
699
700impl Default for PointsCalculationMode {
701 fn default() -> Self {
702 Self::Default
703 }
704}
705
706const VALID_FU: [Fu; 11] = [
707 Fu::new(20),
708 Fu::new(25),
709 Fu::new(30),
710 Fu::new(40),
711 Fu::new(50),
712 Fu::new(60),
713 Fu::new(70),
714 Fu::new(80),
715 Fu::new(90),
716 Fu::new(100),
717 Fu::new(110),
718];
719
720const NO_TSUMO: [(Han, Fu); 3] = [
721 (Han::new(1), Fu::new(20)),
722 (Han::new(1), Fu::new(25)),
723 (Han::new(2), Fu::new(25)),
724];
725#[inline]
726#[must_use]
727fn has_tsumo(han: Han, fu: Fu) -> bool {
728 !NO_TSUMO.contains(&(han, fu))
729}
730
731const NO_RON: [(Han, Fu); 5] = [
732 (Han::new(1), Fu::new(20)),
733 (Han::new(1), Fu::new(25)),
734 (Han::new(2), Fu::new(20)),
735 (Han::new(3), Fu::new(20)),
736 (Han::new(4), Fu::new(20)),
737];
738#[inline]
739#[must_use]
740fn has_ron(han: Han, fu: Fu) -> bool {
741 !NO_RON.contains(&(han, fu))
742}
743
744#[derive(Debug, Copy, Clone)]
747pub enum PointCalculationError {
748 InvalidHan(Han),
751 InvalidFu(Fu),
754 InvalidHonbas(Honbas),
757}
758
759impl Display for PointCalculationError {
760 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
761 match self {
762 PointCalculationError::InvalidHan(han) => {
763 write!(f, "Han cannot be less than 1: {}", han)
764 }
765 PointCalculationError::InvalidFu(fu) => {
766 write!(f, "Invalid fu value: {}", fu)
767 }
768 PointCalculationError::InvalidHonbas(honbas) => {
769 write!(f, "Invalid honba count: {}", honbas)
770 }
771 }
772 }
773}
774
775impl Error for PointCalculationError {}
776
777#[cfg(test)]
778mod tests {
779 use num_bigint::BigInt;
780
781 use crate::points::{Fu, Han, Honbas, Points, PointsCalculationMode, PointsCustom};
782
783 #[derive(Debug, serde::Deserialize)]
784 struct PointsRecord {
785 han: i32,
786 fu: i32,
787 ko_tsumo_1: i32,
788 ko_tsumo_2: i32,
789 ko_ron: i32,
790 oya_ron: i32,
791 }
792
793 #[test]
794 fn should_fail_for_invalid_fu() {
795 let calculation_mode = PointsCalculationMode::Default;
797 let han = Han::new(1);
798 let fu = Fu::new(20);
799 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
800 let calculation_mode = PointsCalculationMode::Default;
801 let han = Han::new(2);
802 let fu = Fu::new(110);
803 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
804
805 let calculation_mode = PointsCalculationMode::Loose;
807 let han = Han::new(1);
808 let fu = Fu::new(13);
809 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
810 let calculation_mode = PointsCalculationMode::Loose;
811 let han = Han::new(1);
812 let fu = Fu::new(35);
813 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
814 let calculation_mode = PointsCalculationMode::Loose;
815 let han = Han::new(1);
816 let fu = Fu::new(150);
817 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
818 let calculation_mode = PointsCalculationMode::Loose;
819 let han = Han::new(1);
820 let fu = Fu::new(10);
821 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
822
823 let calculation_mode = PointsCalculationMode::Unlimited;
825 let han = Han::new(1);
826 let fu = Fu::new(13);
827 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
828 let calculation_mode = PointsCalculationMode::Unlimited;
829 let han = Han::new(1);
830 let fu = Fu::new(35);
831 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
832 let calculation_mode = PointsCalculationMode::Unlimited;
833 let han = Han::new(1);
834 let fu = Fu::new(150);
835 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
836 let calculation_mode = PointsCalculationMode::Unlimited;
837 let han = Han::new(1);
838 let fu = Fu::new(10);
839 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_ok());
840
841 let calculation_mode = PointsCalculationMode::Default;
843 let han = Han::new(1);
844 let fu = Fu::new(13);
845 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_err());
846 let calculation_mode = PointsCalculationMode::Default;
847 let han = Han::new(1);
848 let fu = Fu::new(35);
849 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_err());
850 let calculation_mode = PointsCalculationMode::Default;
851 let han = Han::new(1);
852 let fu = Fu::new(150);
853 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_err());
854 let calculation_mode = PointsCalculationMode::Default;
855 let han = Han::new(1);
856 let fu = Fu::new(10);
857 assert!(Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).is_err());
858 }
859
860 #[test]
861 fn should_display_invalid_fu_error() {
862 let calculation_mode = PointsCalculationMode::Default;
863 let han = Han::new(1);
864 let fu = Fu::new(35);
865 let invalid_fu = Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO);
866 let invalid_fu_error = invalid_fu.unwrap_err();
867 assert_eq!(invalid_fu_error.to_string(), "Invalid fu value: 35 fu");
868 }
869
870 #[test]
871 fn should_display_invalid_han_error() {
872 let calculation_mode = PointsCalculationMode::Default;
873 let han = Han::new(-5);
874 let fu = Fu::new(30);
875 let invalid_han = Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO);
876 let invalid_han_error = invalid_han.unwrap_err();
877 assert_eq!(
878 invalid_han_error.to_string(),
879 "Han cannot be less than 1: -5 han"
880 );
881 }
882
883 #[test]
884 fn should_display_invalid_honbas_error() {
885 let calculation_mode = PointsCalculationMode::Default;
886 let han = Han::new(3);
887 let fu = Fu::new(30);
888 let honbas = Honbas::new(-1);
889 let invalid_honbas = Points::from_calculated(calculation_mode, han, fu, honbas);
890 let invalid_honbas_error = invalid_honbas.unwrap_err();
891 assert_eq!(
892 invalid_honbas_error.to_string(),
893 "Invalid honba count: -1 honbas"
894 );
895 }
896
897 #[test]
898 fn should_return_limited() {
899 let mangan = (2000, 4000, 8000, 12000);
900 check_points_default_limited(5, 40, mangan);
901 check_points_default_limited(4, 40, mangan);
902 check_points_default_limited(4, 60, mangan);
903 check_points_default_limited(3, 100, mangan);
904 check_points_default_limited(3, 110, mangan);
905
906 let haneman = (3000, 6000, 12000, 18000);
907 check_points_default_limited(6, 30, haneman);
908 check_points_default_limited(7, 30, haneman);
909
910 let baiman = (4000, 8000, 16000, 24000);
911 check_points_default_limited(8, 30, baiman);
912 check_points_default_limited(9, 30, baiman);
913 check_points_default_limited(10, 30, baiman);
914
915 let sanbaiman = (6000, 12000, 24000, 36000);
916 check_points_default_limited(11, 30, sanbaiman);
917 check_points_default_limited(12, 30, sanbaiman);
918
919 let kazoe_yakuman = (8000, 16000, 32000, 48000);
920 check_points_default_limited(13, 30, kazoe_yakuman);
921 check_points_default_limited(14, 30, kazoe_yakuman);
922 check_points_default_limited(17, 30, kazoe_yakuman);
923 check_points_default_limited(25, 30, kazoe_yakuman);
924 check_points_default_limited(100, 30, kazoe_yakuman);
925 }
926
927 #[test]
928 fn should_handle_honbas() {
929 check_points_loose_with_honbas(1, 30, 1, (400, 600, 1300, 1800));
930 check_points_loose_with_honbas(1, 30, 5, (800, 1000, 2500, 3000));
931 check_points_loose_with_honbas(1, 30, -3, (0, 200, 100, 600));
932 check_points_loose_with_honbas(1, 30, -5, (-200, 0, -500, 0));
933 check_points_loose_with_honbas(3, 30, 10, (2000, 3000, 6900, 8800));
934 check_points_loose_with_honbas(5, 30, 1, (2100, 4100, 8300, 12300));
935 }
936
937 #[test]
938 fn should_return_calculated() {
939 let points_table = include_bytes!("points/points_table.csv");
940 let mut csv_reader = csv::Reader::from_reader(&points_table[..]);
941 for result in csv_reader.deserialize() {
942 let record: PointsRecord = result.unwrap();
943 let han = Han::new(record.han);
944 let fu = Fu::new(record.fu);
945
946 let calculation_mode = PointsCalculationMode::Default;
947 let points = Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).unwrap();
948 let ko_tsumo = points.ko_tsumo().unwrap_or_default();
949 let ko_ron = points.ko_ron().unwrap_or_default();
950 let oya_ron = points.oya_ron().unwrap_or_default();
951
952 let actual = (ko_tsumo.0, ko_tsumo.1, ko_ron, oya_ron);
953 let expected = (
954 record.ko_tsumo_1,
955 record.ko_tsumo_2,
956 record.ko_ron,
957 record.oya_ron,
958 );
959
960 assert_eq!(
961 actual, expected,
962 "Points for {} and {} are different",
963 han, fu
964 );
965 }
966 }
967
968 #[test]
969 fn should_work_with_loose_mode() {
970 check_points_loose(1, 1, (100, 100, 100, 100));
971 check_points_loose(1, 13, (200, 300, 500, 700));
972 check_points_loose(1, 150, (1200, 2400, 4800, 7200));
973 check_points_loose(3, 150, (2000, 4000, 8000, 12000));
974 check_points_loose(15, 150, (8000, 16000, 32000, 48000));
975 }
976
977 #[test]
978 fn should_work_with_unlimited_mode() {
979 check_points_unlimited(1, 1, (100, 100, 100, 100));
980 check_points_unlimited(1, 13, (200, 300, 500, 700));
981 check_points_unlimited(1, 150, (1200, 2400, 4800, 7200));
982 check_points_unlimited(3, 150, (4800, 9600, 19200, 28800));
983 check_points_unlimited(15, 150, (19660800, 39321600, 78643200, 117964800));
984 check_points_unlimited(4, 1500, (96000, 192000, 384000, 576000));
985 check_points_unlimited(20, 40, (167772200, 335544400, 671088700, 1006633000));
986 }
987
988 #[test]
989 fn should_work_with_non_positive_numbers() {
990 check_points_unlimited(0, 30, (200, 300, 500, 800));
991 check_points_unlimited(-1, 30, (100, 200, 300, 400));
992 check_points_unlimited(-1, 70, (200, 300, 600, 900));
993 check_points_unlimited(-2, 30, (100, 100, 200, 200));
994 check_points_unlimited(-5, 30, (100, 100, 100, 100));
995 check_points_unlimited(-10, 30, (100, 100, 100, 100));
996 check_points_unlimited(4, -30, (-1900, -3800, -7600, -11500));
997 check_points_unlimited(4, -50, (-3200, -6400, -12800, -19200));
998 check_points_unlimited(-4, -100, (0, 0, -100, -100));
999 check_points_unlimited(-10000, i32::MAX, (100, 100, 100, 100));
1000 check_points_unlimited(-6, i32::MAX, (134217800, 268435500, 536871000, 805306400));
1001 }
1002
1003 #[test]
1004 fn should_work_with_bigints_and_unlimited_mode() {
1005 check_points_unlimited_bigint(
1006 20,
1007 40,
1008 ("167772200", "335544400", "671088700", "1006633000"),
1009 );
1010 check_points_unlimited_bigint(
1011 160,
1012 250,
1013 (
1014 "1461501637330902918203684832716283019655932542976000",
1015 "2923003274661805836407369665432566039311865085952000",
1016 "5846006549323611672814739330865132078623730171904000",
1017 "8769009823985417509222108996297698117935595257856000",
1018 ),
1019 );
1020 check_points_unlimited_bigint(
1022 105,
1023 140,
1024 (
1025 "22716298756089870874820921440338000",
1026 "45432597512179741749641842880675900",
1027 "90865195024359483499283685761351700",
1028 "136297792536539225248925528642027600",
1029 ),
1030 );
1031 }
1032
1033 fn check_points_default_limited(han: i32, fu: i32, expected_points: (i32, i32, i32, i32)) {
1034 let han = Han::new(han);
1035 let fu = Fu::new(fu);
1036 let calculation_mode = PointsCalculationMode::Default;
1037 let points = Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).unwrap();
1038 assert!(points.is_limited());
1039 assert!(!points.is_calculated());
1040
1041 check_points(&points, han, fu, &expected_points);
1042 }
1043
1044 fn check_points_loose(han: i32, fu: i32, expected_points: (i32, i32, i32, i32)) {
1045 check_points_loose_with_honbas(han, fu, 0, expected_points);
1046 }
1047
1048 fn check_points_loose_with_honbas(
1049 han: i32,
1050 fu: i32,
1051 honbas: i32,
1052 expected_points: (i32, i32, i32, i32),
1053 ) {
1054 let han = Han::new(han);
1055 let fu = Fu::new(fu);
1056 let honbas = Honbas::new(honbas);
1057 let calculation_mode = PointsCalculationMode::Loose;
1058 let points = Points::from_calculated(calculation_mode, han, fu, honbas).unwrap();
1059 check_points(&points, han, fu, &expected_points);
1060 }
1061
1062 fn check_points_unlimited(han: i32, fu: i32, expected_points: (i32, i32, i32, i32)) {
1063 let han = Han::new(han);
1064 let fu = Fu::new(fu);
1065 let calculation_mode = PointsCalculationMode::Unlimited;
1066 let points = Points::from_calculated(calculation_mode, han, fu, Honbas::ZERO).unwrap();
1067 check_points(&points, han, fu, &expected_points);
1068 }
1069
1070 fn check_points_unlimited_bigint(han: i32, fu: i32, expected_points: (&str, &str, &str, &str)) {
1071 let han = Han::new(han);
1072 let fu = Fu::new(fu);
1073 let calculation_mode = PointsCalculationMode::Unlimited;
1074 let points =
1075 PointsCustom::from_calculated(calculation_mode, han, fu, Honbas::ZERO).unwrap();
1076 check_points_bigint(&points, han, fu, &expected_points);
1077 }
1078
1079 fn check_points(points: &Points, han: Han, fu: Fu, expected_points: &(i32, i32, i32, i32)) {
1080 let ko_tsumo = points.ko_tsumo().unwrap_or_default();
1081 let ko_ron = points.ko_ron().unwrap_or_default();
1082 let oya_tsumo = points.oya_tsumo().unwrap_or_default();
1083 let oya_ron = points.oya_ron().unwrap_or_default();
1084
1085 let actual_points = (ko_tsumo.0, ko_tsumo.1, ko_ron, oya_ron);
1086
1087 assert!(points.is_limited() ^ points.is_calculated());
1088 assert_eq!(ko_tsumo.1, oya_tsumo);
1089 assert_eq!(
1090 actual_points, *expected_points,
1091 "Points for {} and {} are different",
1092 han, fu
1093 );
1094 }
1095
1096 fn check_points_bigint(
1097 points: &PointsCustom<BigInt>,
1098 han: Han,
1099 fu: Fu,
1100 expected_points: &(&str, &str, &str, &str),
1101 ) {
1102 let ko_tsumo = points.ko_tsumo().unwrap_or_default();
1103 let ko_ron = points.ko_ron().unwrap_or_default();
1104 let oya_tsumo = points.oya_tsumo().unwrap_or_default();
1105 let oya_ron = points.oya_ron().unwrap_or_default();
1106
1107 let actual_points = (
1108 ko_tsumo.0.to_string(),
1109 ko_tsumo.1.to_string(),
1110 ko_ron.to_string(),
1111 oya_ron.to_string(),
1112 );
1113 let actual_points_ref = (
1114 actual_points.0.as_ref(),
1115 actual_points.1.as_ref(),
1116 actual_points.2.as_ref(),
1117 actual_points.3.as_ref(),
1118 );
1119
1120 assert_eq!(ko_tsumo.1, oya_tsumo);
1121 assert_eq!(
1122 actual_points_ref, *expected_points,
1123 "Points for {} and {} are different",
1124 han, fu
1125 );
1126 }
1127}