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

Commit 6432fb8

Browse files
committed
Improve logic to call chankan sets
1 parent 68d31bb commit 6432fb8

File tree

2 files changed

+119
-44
lines changed

2 files changed

+119
-44
lines changed

project/game/ai/first_version/main.py

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -236,71 +236,83 @@ def should_call_kan(self, tile, open_kan, from_riichi=False):
236236
tiles_34 = TilesConverter.to_34_array(self.player.tiles)
237237

238238
closed_hand_34 = TilesConverter.to_34_array(self.player.closed_hand)
239-
pon_melds = [x for x in self.player.meld_34_tiles if is_pon(x)]
240-
241-
# let's check can we upgrade opened pon to the kan
242-
if pon_melds:
243-
for meld in pon_melds:
244-
# tile is equal to our already opened pon,
245-
# so let's call chankan!
246-
if tile_34 in meld:
247-
return Meld.CHANKAN
248239

249240
melds_34 = copy.copy(self.player.meld_34_tiles)
250241
tiles = copy.copy(self.player.tiles)
251242
closed_hand_tiles = copy.copy(self.player.closed_hand)
252243

253-
# we can try to call closed meld
254-
if closed_hand_34[tile_34] == 3:
244+
new_shanten = 0
245+
previous_shanten = 0
246+
new_waits_count = 0
247+
previous_waits_count = 0
248+
249+
# let's check can we upgrade opened pon to the kan
250+
pon_melds = [x for x in self.player.meld_34_tiles if is_pon(x)]
251+
has_chankan_candidate = False
252+
for meld in pon_melds:
253+
# tile is equal to our already opened pon
254+
if tile_34 in meld:
255+
has_chankan_candidate = True
256+
257+
tiles.append(tile)
258+
closed_hand_tiles.append(tile)
259+
260+
previous_shanten, previous_waits_count = self._calculate_shanten_for_kan(
261+
tiles,
262+
closed_hand_tiles,
263+
self.player.melds
264+
)
265+
266+
tiles_34 = TilesConverter.to_34_array(tiles)
267+
tiles_34[tile_34] -= 1
268+
269+
new_waiting, new_shanten = self.hand_builder.calculate_waits(
270+
tiles_34,
271+
self.player.meld_34_tiles
272+
)
273+
new_waits_count = self.hand_builder.count_tiles(new_waiting, tiles_34)
274+
275+
if not has_chankan_candidate:
276+
# we don't have enough tiles in the hand
277+
if closed_hand_34[tile_34] != 3:
278+
return None
279+
255280
if open_kan or from_riichi:
256281
# this 4 tiles can only be used in kan, no other options
257282
previous_waiting, previous_shanten = self.hand_builder.calculate_waits(tiles_34, melds_34)
258-
previous_waits_cnt = self.hand_builder.count_tiles(previous_waiting, closed_hand_34)
259-
260-
# shanten calculator doesn't like working with kans, so we pretend it's a pon
261-
melds_34 += [[tile_34, tile_34, tile_34]]
262-
closed_hand_34[tile_34] = 0
263-
264-
new_waiting, new_shanten = self.hand_builder.calculate_waits(tiles_34, melds_34)
265-
new_waits_cnt = self.hand_builder.count_tiles(new_waiting, closed_hand_34)
283+
previous_waits_count = self.hand_builder.count_tiles(previous_waiting, closed_hand_34)
266284
else:
267-
# if we can use or tile in the hand for the forms other than KAN
268285
tiles.append(tile)
269-
270286
closed_hand_tiles.append(tile)
271-
closed_hand_34[tile_34] += 1
272287

273-
previous_results, previous_shanten = self.hand_builder.find_discard_options(
288+
previous_shanten, previous_waits_count = self._calculate_shanten_for_kan(
274289
tiles,
275290
closed_hand_tiles,
276291
self.player.melds
277292
)
278293

279-
previous_results = [x for x in previous_results if x.shanten == previous_shanten]
280-
281-
# it is possible that we don't have results here
282-
# when we are in agari state (but without yaku)
283-
if not previous_results:
284-
return None
294+
# shanten calculator doesn't like working with kans, so we pretend it's a pon
295+
melds_34 += [[tile_34, tile_34, tile_34]]
296+
new_waiting, new_shanten = self.hand_builder.calculate_waits(tiles_34, melds_34)
285297

286-
previous_waits_cnt = sorted(previous_results, key=lambda x: -x.ukeire)[0].ukeire
298+
closed_hand_34[tile_34] = 4
299+
new_waits_count = self.hand_builder.count_tiles(new_waiting, closed_hand_34)
287300

288-
# shanten calculator doesn't like working with kans, so we pretend it's a pon
289-
closed_hand_34[tile_34] = 0
290-
melds_34 += [[tile_34, tile_34, tile_34]]
301+
# it is possible that we don't have results here
302+
# when we are in agari state (but without yaku)
303+
if previous_shanten is None:
304+
return None
291305

292-
new_waiting, new_shanten = self.hand_builder.calculate_waits(tiles_34, melds_34)
293-
new_waits_cnt = self.hand_builder.count_tiles(new_waiting, closed_hand_34)
306+
# it is not possible to reduce number of shanten by calling a kan
307+
assert new_shanten >= previous_shanten
294308

295-
# it is not possible to reduce number of shanten by calling a kan
296-
assert new_shanten >= previous_shanten
309+
# if shanten number is the same, we should only call kan if ukeire didn't become worse
310+
if new_shanten == previous_shanten:
311+
# we cannot improve ukeire by calling kan (not considering the tile we drew from the dead wall)
312+
assert new_waits_count <= previous_waits_count
297313

298-
# if shanten number is the same, we should only call kan if ukeire didn't become worse
299-
if new_shanten == previous_shanten:
300-
# we cannot improve ukeire by calling kan (not considering the tile we drew from the dead wall)
301-
assert new_waits_cnt <= previous_waits_cnt
302-
if new_waits_cnt == previous_waits_cnt:
303-
return Meld.KAN
314+
if new_waits_count == previous_waits_count:
315+
return has_chankan_candidate and Meld.CHANKAN or Meld.KAN
304316

305317
return None
306318

@@ -324,3 +336,21 @@ def enemy_players(self):
324336
Return list of players except our bot
325337
"""
326338
return self.player.table.players[1:]
339+
340+
def _calculate_shanten_for_kan(self, tiles, closed_hand_tiles, melds):
341+
previous_results, previous_shanten = self.hand_builder.find_discard_options(
342+
tiles,
343+
closed_hand_tiles,
344+
melds
345+
)
346+
347+
previous_results = [x for x in previous_results if x.shanten == previous_shanten]
348+
349+
# it is possible that we don't have results here
350+
# when we are in agari state (but without yaku)
351+
if not previous_results:
352+
return None, None
353+
354+
previous_waits_cnt = sorted(previous_results, key=lambda x: -x.ukeire)[0].ukeire
355+
356+
return previous_shanten, previous_waits_cnt

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ def test_using_tiles_of_different_suit_for_chi(self):
395395
meld, _ = player.try_to_call_meld(tile, True)
396396
self.assertIsNotNone(meld)
397397

398-
def test_upgrade_opened_pon_to_kan(self):
398+
def test_call_chankan_and_bad_ukeire_after_call(self):
399399
table = Table()
400400
table.count_of_remaining_tiles = 10
401401

@@ -409,6 +409,31 @@ def test_upgrade_opened_pon_to_kan(self):
409409

410410
self.assertEqual(len(table.player.melds), 1)
411411
self.assertEqual(len(table.player.tiles), 13)
412+
self.assertEqual(table.player.should_call_kan(tile, False), None)
413+
414+
def test_call_chankan_and_bad_ukeire_after_call_second(self):
415+
table = Table()
416+
table.count_of_remaining_tiles = 10
417+
418+
tiles = self._string_to_136_array(man='3455567', sou='222', honors='666')
419+
table.player.init_hand(tiles)
420+
table.player.add_called_meld(self._make_meld(Meld.PON, man='555'))
421+
table.player.add_called_meld(self._make_meld(Meld.PON, honors='666'))
422+
423+
tile = self._string_to_136_tile(man='5')
424+
425+
self.assertEqual(table.player.should_call_kan(tile, False), None)
426+
427+
def test_upgrade_opened_pon_to_kan(self):
428+
table = Table()
429+
table.count_of_remaining_tiles = 10
430+
431+
tiles = self._string_to_136_array(man='3455567', sou='222', honors='666')
432+
table.player.init_hand(tiles)
433+
table.player.add_called_meld(self._make_meld(Meld.PON, honors='666'))
434+
435+
tile = self._string_to_136_tile(honors='6')
436+
412437
self.assertEqual(table.player.should_call_kan(tile, False), Meld.CHANKAN)
413438

414439
def test_call_closed_kan(self):
@@ -451,6 +476,8 @@ def test_opened_kan(self):
451476
tile = self._string_to_136_tile(sou='1')
452477
self.assertEqual(table.player.should_call_kan(tile, True), None)
453478

479+
# test case 2
480+
454481
table = Table()
455482
table.count_of_remaining_tiles = 10
456483

@@ -466,6 +493,24 @@ def test_opened_kan(self):
466493
tile = self._string_to_136_tile(sou='1')
467494
self.assertEqual(table.player.should_call_kan(tile, True), Meld.KAN)
468495

496+
# test case 3
497+
# we are in tempai already and there was a crash on 5s meld suggestion
498+
499+
table = Table()
500+
table.count_of_remaining_tiles = 10
501+
table.add_dora_indicator(self._string_to_136_tile(honors='5'))
502+
503+
tiles = self._string_to_136_array(man='456', sou='55567678', honors='66')
504+
table.player.init_hand(tiles)
505+
table.player.add_called_meld(self._make_meld(Meld.CHI, sou='678'))
506+
507+
# to rebuild all caches
508+
table.player.draw_tile(self._string_to_136_tile(pin='9'))
509+
table.player.discard_tile()
510+
511+
tile = self._string_to_136_tile(sou='5')
512+
self.assertEqual(table.player.should_call_kan(tile, True), None)
513+
469514
def test_dont_call_kan_in_defence_mode(self):
470515
table = Table()
471516

0 commit comments

Comments
 (0)