Skip to content
This repository was archived by the owner on Jul 8, 2023. It is now read-only.

Commit 68d31bb

Browse files
authored
Merge pull request #112 from MahjongRepository/chiitoitsu_shanten
Fix shanten calculation rules in regard with chiitoitsu consideration
2 parents 33b663a + be3f08a commit 68d31bb

File tree

4 files changed

+136
-37
lines changed

4 files changed

+136
-37
lines changed

project/game/ai/first_version/hand_builder.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,35 @@ def discard_tile(self, tiles, closed_hand, melds, print_log=True):
114114

115115
return self.process_discard_option(selected_tile, closed_hand, print_log=print_log)
116116

117+
def calculate_shanten(self, tiles_34, open_sets_34=None):
118+
shanten_with_chiitoitsu = self.ai.shanten_calculator.calculate_shanten(tiles_34,
119+
open_sets_34,
120+
chiitoitsu=True)
121+
shanten_without_chiitoitsu = self.ai.shanten_calculator.calculate_shanten(tiles_34,
122+
open_sets_34,
123+
chiitoitsu=False)
124+
125+
if shanten_with_chiitoitsu == 0 and shanten_without_chiitoitsu >= 1:
126+
shanten = shanten_with_chiitoitsu
127+
use_chiitoitsu = True
128+
elif shanten_with_chiitoitsu == 1 and shanten_without_chiitoitsu >= 3:
129+
shanten = shanten_with_chiitoitsu
130+
use_chiitoitsu = True
131+
else:
132+
shanten = shanten_without_chiitoitsu
133+
use_chiitoitsu = False
134+
135+
return shanten, use_chiitoitsu
136+
117137
def calculate_waits(self, tiles_34, open_sets_34=None):
118138
"""
119139
:param tiles_34: array of tiles in 34 formant, 13 of them (this is important)
120140
:param open_sets_34: array of array with tiles in 34 format
121141
:return: array of waits in 34 format and number of shanten
122142
"""
123-
shanten = self.ai.shanten_calculator.calculate_shanten(tiles_34, open_sets_34, chiitoitsu=self.ai.use_chitoitsu)
143+
144+
shanten, use_chiitoitsu = self.calculate_shanten(tiles_34, open_sets_34)
145+
124146
waiting = []
125147
for j in range(0, 34):
126148
if tiles_34[j] == 4:
@@ -131,7 +153,7 @@ def calculate_waits(self, tiles_34, open_sets_34=None):
131153
key = '{},{},{}'.format(
132154
''.join([str(x) for x in tiles_34]),
133155
';'.join([str(x) for x in open_sets_34]),
134-
self.ai.use_chitoitsu and 1 or 0
156+
use_chiitoitsu and 1 or 0
135157
)
136158

137159
if key in self.ai.hand_cache:
@@ -140,7 +162,7 @@ def calculate_waits(self, tiles_34, open_sets_34=None):
140162
new_shanten = self.ai.shanten_calculator.calculate_shanten(
141163
tiles_34,
142164
open_sets_34,
143-
chiitoitsu=self.ai.use_chitoitsu
165+
chiitoitsu=use_chiitoitsu
144166
)
145167
self.ai.hand_cache[key] = new_shanten
146168

@@ -188,10 +210,9 @@ def find_discard_options(self, tiles, closed_hand, melds=None):
188210
if is_agari:
189211
shanten = Shanten.AGARI_STATE
190212
else:
191-
shanten = self.ai.shanten_calculator.calculate_shanten(
213+
shanten, _ = self.calculate_shanten(
192214
tiles_34,
193-
open_sets_34,
194-
chiitoitsu=self.ai.use_chitoitsu
215+
open_sets_34
195216
)
196217

197218
return results, shanten
@@ -484,7 +505,7 @@ def choose_tile_to_discard(self, tiles, closed_hand, melds, print_log=True):
484505
if first_option.shanten in [1, 2, 3]:
485506
ukeire_field = 'ukeire_second'
486507
for x in possible_options:
487-
self.calculate_second_level_ukeire(x)
508+
self.calculate_second_level_ukeire(x, tiles, melds)
488509

489510
possible_options = sorted(possible_options, key=lambda x: -getattr(x, ukeire_field))
490511

@@ -600,7 +621,7 @@ def process_discard_option(self, discard_option, closed_hand, force_discard=Fals
600621
else:
601622
return discard_option.find_tile_in_hand(closed_hand)
602623

603-
def calculate_second_level_ukeire(self, discard_option):
624+
def calculate_second_level_ukeire(self, discard_option, tiles, melds):
604625
not_suitable_tiles = self.ai.current_strategy and self.ai.current_strategy.not_suitable_tiles or []
605626
call_riichi = not self.player.is_open_hand
606627

@@ -609,6 +630,8 @@ def calculate_second_level_ukeire(self, discard_option):
609630
player_tiles_original = self.player.tiles.copy()
610631

611632
tile_in_hand = discard_option.find_tile_in_hand(self.player.closed_hand)
633+
634+
self.player.tiles = tiles.copy()
612635
self.player.tiles.remove(tile_in_hand)
613636

614637
sum_tiles = 0
@@ -629,7 +652,7 @@ def calculate_second_level_ukeire(self, discard_option):
629652
results, shanten = self.find_discard_options(
630653
self.player.tiles,
631654
self.player.closed_hand,
632-
self.player.melds
655+
melds
633656
)
634657
results = [x for x in results if x.shanten == discard_option.shanten - 1]
635658

project/game/ai/first_version/main.py

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ class ImplementationAI(InterfaceAI):
4444

4545
current_strategy = None
4646
last_discard_option = None
47-
use_chitoitsu = False
4847

4948
hand_cache = {}
5049

@@ -70,7 +69,6 @@ def erase_state(self):
7069

7170
self.current_strategy = None
7271
self.last_discard_option = None
73-
self.use_chitoitsu = False
7472

7573
self.hand_cache = {}
7674

@@ -81,9 +79,7 @@ def init_hand(self):
8179
'Hand: {}'.format(self.player.format_hand_for_print()),
8280
])
8381

84-
self.shanten = self.shanten_calculator.calculate_shanten(
85-
TilesConverter.to_34_array(self.player.tiles)
86-
)
82+
self.shanten, _ = self.hand_builder.calculate_shanten(TilesConverter.to_34_array(self.player.tiles))
8783

8884
def draw_tile(self, tile_136):
8985
self.determine_strategy(self.player.tiles)
@@ -104,18 +100,15 @@ def discard_tile(self, discard_tile, print_log=True):
104100
)
105101

106102
def try_to_call_meld(self, tile_136, is_kamicha_discard):
107-
tiles_136 = self.player.tiles[:] + [tile_136]
103+
tiles_136_previous = self.player.tiles[:]
104+
tiles_136 = tiles_136_previous + [tile_136]
108105
self.determine_strategy(tiles_136)
109106

110107
if not self.current_strategy:
111108
return None, None
112109

113-
tiles_34 = TilesConverter.to_34_array(tiles_136)
114-
previous_shanten = self.shanten_calculator.calculate_shanten(
115-
tiles_34,
116-
self.player.meld_34_tiles,
117-
chiitoitsu=self.use_chitoitsu
118-
)
110+
tiles_34_previous = TilesConverter.to_34_array(tiles_136_previous)
111+
previous_shanten, _ = self.hand_builder.calculate_shanten(tiles_34_previous, self.player.meld_34_tiles)
119112

120113
if previous_shanten == Shanten.AGARI_STATE and not self.current_strategy.can_meld_into_agari():
121114
return None, None
@@ -133,35 +126,32 @@ def try_to_call_meld(self, tile_136, is_kamicha_discard):
133126
return meld, discard_option
134127

135128
def determine_strategy(self, tiles_136):
136-
self.use_chitoitsu = False
137-
138129
# for already opened hand we don't need to give up on selected strategy
139130
if self.player.is_open_hand and self.current_strategy:
140131
return False
141132

142133
old_strategy = self.current_strategy
143134
self.current_strategy = None
144135

145-
# order is important
146-
strategies = [
147-
ChinitsuStrategy(BaseStrategy.CHINITSU, self.player),
148-
HonitsuStrategy(BaseStrategy.HONITSU, self.player),
149-
YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player),
150-
]
136+
# order is important, we add strategies with the highest priority first
137+
strategies = []
151138

152139
if self.player.table.has_open_tanyao:
153140
strategies.append(TanyaoStrategy(BaseStrategy.TANYAO, self.player))
154141

142+
strategies.append(YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player))
143+
strategies.append(HonitsuStrategy(BaseStrategy.HONITSU, self.player))
144+
strategies.append(ChinitsuStrategy(BaseStrategy.CHINITSU, self.player))
145+
155146
strategies.append(ChiitoitsuStrategy(BaseStrategy.CHIITOITSU, self.player))
156147
strategies.append(FormalTempaiStrategy(BaseStrategy.FORMAL_TEMPAI, self.player))
157148

158149
for strategy in strategies:
159150
if strategy.should_activate_strategy(tiles_136):
160151
self.current_strategy = strategy
152+
break
161153

162154
if self.current_strategy:
163-
self.use_chitoitsu = self.current_strategy.type == BaseStrategy.CHIITOITSU
164-
165155
if not old_strategy or self.current_strategy.type != old_strategy.type:
166156
DecisionsLogger.debug(
167157
log.STRATEGY_ACTIVATE,

project/game/ai/first_version/tests/strategies/tests_chiitoitsu.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,47 @@ def test_dont_call_meld(self):
5050
tile = self._string_to_136_tile(man='9')
5151
meld, _ = player.try_to_call_meld(tile, True)
5252
self.assertEqual(meld, None)
53+
54+
def test_keep_chiitoitsu_tempai(self):
55+
table = Table()
56+
player = table.player
57+
58+
tiles = self._string_to_136_array(sou='113355', man='22669', pin='99')
59+
player.init_hand(tiles)
60+
61+
player.draw_tile(self._string_to_136_tile(man='6'))
62+
63+
discard = player.discard_tile()
64+
self.assertEqual(self._to_string([discard]), '6m')
65+
66+
def test_5_pairs_yakuhai_not_chiitoitsu(self):
67+
table = Table()
68+
player = table.player
69+
70+
table.add_dora_indicator(self._string_to_136_tile(sou='9'))
71+
table.add_dora_indicator(self._string_to_136_tile(sou='1'))
72+
73+
tiles = self._string_to_136_array(sou='112233', pin='16678', honors='66')
74+
player.init_hand(tiles)
75+
76+
tile = self._string_to_136_tile(honors='6')
77+
meld, _ = player.try_to_call_meld(tile, True)
78+
79+
self.assertNotEqual(player.ai.current_strategy.type, BaseStrategy.CHIITOITSU)
80+
81+
self.assertEqual(player.ai.current_strategy.type, BaseStrategy.YAKUHAI)
82+
83+
self.assertNotEqual(meld, None)
84+
85+
def chiitoitsu_tanyao_tempai(self):
86+
table = Table()
87+
player = table.player
88+
89+
tiles = self._string_to_136_array(sou='223344', pin='788', man='4577')
90+
player.init_hand(tiles)
91+
92+
player.draw_tile(self._string_to_136_tile(man='4'))
93+
94+
discard = player.discard_tile()
95+
discard_correct = self._to_string([discard]) == '7p' or self._to_string([discard]) == '5m'
96+
self.assertEqual(discard_correct, True)

project/game/ai/first_version/tests/tests_discards.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -762,30 +762,72 @@ def test_calculate_second_level_ukeire(self):
762762

763763
tile = self._string_to_136_tile(man='4')
764764
discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0]
765-
player.ai.hand_builder.calculate_second_level_ukeire(discard_option)
765+
player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds)
766766
self.assertEqual(discard_option.ukeire_second, 108)
767767

768768
tile = self._string_to_136_tile(man='3')
769769
discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0]
770-
player.ai.hand_builder.calculate_second_level_ukeire(discard_option)
770+
player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds)
771771
self.assertEqual(discard_option.ukeire_second, 108)
772772

773773
tile = self._string_to_136_tile(pin='2')
774774
discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0]
775-
player.ai.hand_builder.calculate_second_level_ukeire(discard_option)
775+
player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds)
776776
self.assertEqual(discard_option.ukeire_second, 96)
777777

778778
tile = self._string_to_136_tile(pin='3')
779779
discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0]
780-
player.ai.hand_builder.calculate_second_level_ukeire(discard_option)
780+
player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds)
781781
self.assertEqual(discard_option.ukeire_second, 96)
782782

783783
tile = self._string_to_136_tile(pin='5')
784784
discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0]
785-
player.ai.hand_builder.calculate_second_level_ukeire(discard_option)
785+
player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds)
786786
self.assertEqual(discard_option.ukeire_second, 96)
787787

788788
tile = self._string_to_136_tile(pin='6')
789789
discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0]
790-
player.ai.hand_builder.calculate_second_level_ukeire(discard_option)
790+
player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds)
791791
self.assertEqual(discard_option.ukeire_second, 96)
792+
793+
def test_choose_1_shanten_with_cost_possibility_draw(self):
794+
table = Table()
795+
player = table.player
796+
table.add_dora_indicator(self._string_to_136_tile(sou='4'))
797+
798+
tiles = self._string_to_136_array(man='557', pin='468', sou='55577', honors='66')
799+
player.init_hand(tiles)
800+
801+
meld = self._make_meld(Meld.PON, sou='555')
802+
player.add_called_meld(meld)
803+
804+
tile = self._string_to_136_tile(sou='7')
805+
player.draw_tile(tile)
806+
discarded_tile = player.discard_tile()
807+
self.assertNotEqual(player.ai.current_strategy, None)
808+
self.assertEqual(player.ai.current_strategy.type, BaseStrategy.YAKUHAI)
809+
self.assertEqual(self._to_string([discarded_tile]), '7m')
810+
811+
def test_choose_1_shanten_with_cost_possibility_meld(self):
812+
table = Table()
813+
player = table.player
814+
table.add_dora_indicator(self._string_to_136_tile(sou='4'))
815+
816+
tiles = self._string_to_136_array(man='557', pin='468', sou='55577', honors='66')
817+
player.init_hand(tiles)
818+
819+
meld = self._make_meld(Meld.PON, sou='555')
820+
player.add_called_meld(meld)
821+
822+
tile = self._string_to_136_tile(sou='7')
823+
meld, discard_option = player.try_to_call_meld(tile, False)
824+
self.assertNotEqual(meld, None)
825+
self.assertEqual(meld.type, Meld.PON)
826+
self.assertEqual(self._to_string(meld.tiles), '777s')
827+
828+
self.assertNotEqual(player.ai.current_strategy, None)
829+
self.assertEqual(player.ai.current_strategy.type, BaseStrategy.YAKUHAI)
830+
831+
discarded_tile = discard_option.find_tile_in_hand(player.closed_hand)
832+
833+
self.assertEqual(self._to_string([discarded_tile]), '7m')

0 commit comments

Comments
 (0)