Coverage for project/game/ai/strategies/chinitsu.py : 89%

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.strategies.honitsu import HonitsuStrategy
2from game.ai.strategies.main import BaseStrategy
3from mahjong.tile import TilesConverter
4from mahjong.utils import count_tiles_by_suits, is_honor, is_man, is_pin, is_sou, is_tile_strictly_isolated, plus_dora
7class ChinitsuStrategy(BaseStrategy):
8 min_shanten = 4
10 chosen_suit = None
12 dora_count_suitable = 0
13 dora_count_not_suitable = 0
15 def get_open_hand_han(self):
16 return 5
18 def should_activate_strategy(self, tiles_136, meld_tile=None):
19 """
20 We can go for chinitsu strategy if we have prevalence of one suit
21 """
23 result = super(ChinitsuStrategy, self).should_activate_strategy(tiles_136)
24 if not result:
25 return False
27 # when making decisions about chinitsu, we should consider
28 # the state of our own hand,
29 tiles_34 = TilesConverter.to_34_array(self.player.tiles)
30 suits = count_tiles_by_suits(tiles_34)
32 suits = [x for x in suits if x["name"] != "honor"]
33 suits = sorted(suits, key=lambda x: x["count"], reverse=True)
34 suit = suits[0]
36 count_of_shuntsu_other_suits = 0
37 count_of_koutsu_other_suits = 0
39 count_of_shuntsu_other_suits += HonitsuStrategy._count_of_shuntsu(tiles_34, suits[1]["function"])
40 count_of_shuntsu_other_suits += HonitsuStrategy._count_of_shuntsu(tiles_34, suits[2]["function"])
42 count_of_koutsu_other_suits += HonitsuStrategy._count_of_koutsu(tiles_34, suits[1]["function"])
43 count_of_koutsu_other_suits += HonitsuStrategy._count_of_koutsu(tiles_34, suits[2]["function"])
45 # we need to have at least 9 tiles of one suit to fo for chinitsu
46 if suit["count"] < 9:
47 return False
49 # here we only check doras in different suits, we will deal
50 # with honors later
51 self._initialize_chinitsu_dora_count(tiles_136, suit)
53 # 3 non-isolated doras in other suits is too much
54 # to even try
55 if self.dora_count_not_suitable >= 3:
56 return False
58 if self.dora_count_not_suitable == 2:
59 # 2 doras in other suits, no doras in our suit
60 # let's not consider chinitsu
61 if self.dora_count_suitable == 0:
62 return False
64 # we have 2 doras in other suits and we
65 # are 1 shanten, let's not rush chinitsu
66 if self.player.ai.shanten == 1:
67 return False
69 # too late to get rid of doras in other suits
70 if self.player.round_step > 8:
71 return False
73 # we are almost tempai, chinitsu is slower
74 if suit["count"] == 9 and self.player.ai.shanten == 1:
75 return False
77 # only 10 tiles by 9th turn is too slow, considering alternative
78 if suit["count"] == 10 and self.player.ai.shanten == 1 and self.player.round_step > 8:
79 return False
81 # only 11 tiles or less by 12th turn is too slow, considering alternative
82 if suit["count"] <= 11 and self.player.round_step > 11:
83 return False
85 # if we have a pon of honors, let's not go for chinitsu
86 honor_pons = len([x for x in range(0, 34) if is_honor(x) and tiles_34[x] >= 3])
87 if honor_pons >= 1:
88 return False
90 # if we have a valued pair, let's not go for chinitsu
91 valued_pairs = len([x for x in self.player.valued_honors if tiles_34[x] == 2])
92 if valued_pairs >= 1:
93 return False
95 # if we have a pair of honor doras, let's not go for chinitsu
96 honor_doras_pairs = len(
97 [
98 x
99 for x in range(0, 34)
100 if is_honor(x) and tiles_34[x] == 2 and plus_dora(x * 4, self.player.table.dora_indicators)
101 ]
102 )
103 if honor_doras_pairs >= 1:
104 return False
106 # if we have a honor pair, we will only throw them away if it's early in the game
107 # and if we have lots of tiles in our suit
108 honor_pairs = len([x for x in range(0, 34) if is_honor(x) and tiles_34[x] == 2])
109 if honor_pairs >= 2:
110 return False
111 if honor_pairs == 1:
112 if suit["count"] < 11:
113 return False
114 if self.player.round_step > 8:
115 return False
117 # if we have a complete set in other suits, we can only throw it away if it's early in the game
118 if count_of_shuntsu_other_suits + count_of_koutsu_other_suits >= 1:
119 # too late to throw away chi after 8 step
120 if self.player.round_step > 8:
121 return False
123 # already 1 shanten, no need to throw away complete set
124 if self.player.round_step > 5 and self.player.ai.shanten == 1:
125 return False
127 # dora is not isolated and we have a complete set, let's not go for chinitsu
128 if self.dora_count_not_suitable >= 1:
129 return False
131 self.chosen_suit = suit["function"]
133 return True
135 def is_tile_suitable(self, tile):
136 """
137 We can use only tiles of chosen suit and honor tiles
138 :param tile: 136 tiles format
139 :return: True
140 """
141 tile //= 4
142 return self.chosen_suit(tile)
144 def _initialize_chinitsu_dora_count(self, tiles_136, suit):
145 tiles_34 = TilesConverter.to_34_array(tiles_136)
147 dora_count_man = 0
148 dora_count_man_not_isolated = 0
149 dora_count_pin = 0
150 dora_count_pin_not_isolated = 0
151 dora_count_sou = 0
152 dora_count_sou_not_isolated = 0
154 for tile_136 in tiles_136:
155 tile_34 = tile_136 // 4
157 dora_count = plus_dora(
158 tile_136, self.player.table.dora_indicators, add_aka_dora=self.player.table.has_aka_dora
159 )
161 if is_man(tile_34):
162 dora_count_man += dora_count
163 if not is_tile_strictly_isolated(tiles_34, tile_34):
164 dora_count_man_not_isolated += dora_count
166 if is_pin(tile_34):
167 dora_count_pin += dora_count
168 if not is_tile_strictly_isolated(tiles_34, tile_34):
169 dora_count_pin_not_isolated += dora_count
171 if is_sou(tile_34):
172 dora_count_sou += dora_count
173 if not is_tile_strictly_isolated(tiles_34, tile_34):
174 dora_count_sou_not_isolated += dora_count
176 if suit["name"] == "pin":
177 self.dora_count_suitable = dora_count_pin
178 self.dora_count_not_suitable = dora_count_man_not_isolated + dora_count_sou_not_isolated
179 elif suit["name"] == "sou":
180 self.dora_count_suitable = dora_count_sou
181 self.dora_count_not_suitable = dora_count_man_not_isolated + dora_count_pin_not_isolated
182 elif suit["name"] == "man":
183 self.dora_count_suitable = dora_count_man
184 self.dora_count_not_suitable = dora_count_sou_not_isolated + dora_count_pin_not_isolated