Coverage for project/game/ai/defence/yaku_analyzer/chinitsu.py : 93%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from game.ai.defence.yaku_analyzer.honitsu_analyzer_base import HonitsuAnalyzerBase
2from mahjong.tile import TilesConverter
3from mahjong.utils import count_tiles_by_suits, is_honor
6class ChinitsuAnalyzer(HonitsuAnalyzerBase):
7 id = "chinitsu"
9 MIN_DISCARD = 5
10 MIN_DISCARD_FOR_LESS_SUIT = 10
11 MAX_MELDS = 3
12 EARLY_DISCARD_DIVISOR = 3
13 LESS_SUIT_PERCENTAGE_BORDER = 30
15 def is_yaku_active(self):
16 # TODO: in some distant future we may want to analyze menchin as well
17 if not self.enemy.melds:
18 return False
20 total_melds = len(self.enemy.melds)
21 total_discards = len(self.enemy.discards)
23 # let's check if there is too little info to analyze
24 if total_discards < ChinitsuAnalyzer.MIN_DISCARD and total_melds < ChinitsuAnalyzer.MAX_MELDS:
25 return False
27 # first of all - check melds, they must be all from one suit
28 current_suit = None
29 for meld in self.enemy.melds:
30 tile = meld.tiles[0]
31 tile_34 = tile // 4
33 if is_honor(tile_34):
34 return False
36 suit = self._get_tile_suit(tile)
37 if not current_suit:
38 current_suit = suit
39 elif suit["name"] != current_suit["name"]:
40 return False
42 assert current_suit
44 if not self._check_discard_order(current_suit, int(total_discards / ChinitsuAnalyzer.EARLY_DISCARD_DIVISOR)):
45 return False
47 # finally let's check if discard is not too full of chosen suit
49 discards = [x.value for x in self.enemy.discards]
50 discards_34 = TilesConverter.to_34_array(discards)
51 result = count_tiles_by_suits(discards_34)
53 suits = [x for x in result if x["name"] != "honor"]
54 suits = sorted(suits, key=lambda x: x["count"], reverse=False)
56 less_suits = [x for x in suits if x["count"] == suits[0]["count"]]
57 assert len(less_suits) != 0
59 current_suit_is_less_suit = False
60 for less_suit in less_suits:
61 if less_suit["name"] == current_suit["name"]:
62 current_suit_is_less_suit = True
64 if not current_suit_is_less_suit:
65 return False
67 less_suit = suits[0]
68 less_suit_tiles = less_suit["count"]
70 if total_discards >= ChinitsuAnalyzer.MIN_DISCARD_FOR_LESS_SUIT:
71 percentage_of_less_suit = (less_suit_tiles / total_discards) * 100
72 if percentage_of_less_suit > ChinitsuAnalyzer.LESS_SUIT_PERCENTAGE_BORDER:
73 return False
74 else:
75 if len(self.enemy.melds) < 2:
76 return False
78 if less_suit_tiles > 1:
79 return False
81 self.chosen_suit = current_suit["function"]
82 return True
84 def melds_han(self):
85 return self.enemy.is_open_hand and 5 or 6
87 def get_safe_tiles_34(self):
88 if not self.chosen_suit:
89 return []
91 safe_tiles = []
92 for x in range(0, 34):
93 if not self.chosen_suit(x):
94 safe_tiles.append(x)
96 return safe_tiles
98 @staticmethod
99 # FIXME: remove this method and use proper one from mahjong lib
100 def _get_tile_suit(tile_136):
101 suits = sorted(
102 count_tiles_by_suits(TilesConverter.to_34_array([tile_136])), key=lambda x: x["count"], reverse=True
103 )
104 suit = suits[0]
105 assert suit["count"] == 1
106 return suit