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

Commit b4d5835

Browse files
committed
Refactor AI a little more
1 parent ef487f8 commit b4d5835

File tree

16 files changed

+94
-64
lines changed

16 files changed

+94
-64
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ After this you can debug bot decisions.
105105

106106
### Reproduce from our log
107107

108-
Sometimes we had to debug bot <-> server communication. For this purpose we built this reproducer.
108+
Sometimes we had to debug `bot <-> server` communication. For this purpose we built this reproducer.
109109

110110
Just use it with already played game:
111111

project/game/ai/base/main.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ def __init__(self, player):
1414
self.player = player
1515
self.table = player.table
1616

17-
def discard_tile(self):
17+
def discard_tile(self, discard_tile):
1818
"""
1919
Simple tile discard (after draw)
20-
:return: int, 136 tile format
20+
:param discard_tile: 136 tile format
21+
:return: 136 tile format
2122
"""
2223
raise NotImplemented()
2324

@@ -40,12 +41,13 @@ def draw_tile(self, tile):
4041
:return:
4142
"""
4243

43-
def process_discard_option(self, discard_option, closed_hand):
44+
def should_call_win(self, tile, enemy_seat):
4445
"""
45-
:param discard_option: DiscardOption object
46-
:param closed_hand: list of 136 tiles format
47-
:return:
46+
When we can call win by other player discard
47+
this methid will be called
48+
:return: boolean
4849
"""
50+
return True
4951

5052
def should_call_riichi(self):
5153
"""

project/game/ai/discard.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ def calculate_value(self, shanten=None):
7777
honored_value = 0
7878

7979
if is_honor(self.tile_to_discard):
80-
if self.tile_to_discard in self.player.ai.valued_honors:
81-
count_of_winds = [x for x in self.player.ai.valued_honors if x == self.tile_to_discard]
80+
if self.tile_to_discard in self.player.valued_honors:
81+
count_of_winds = [x for x in self.player.valued_honors if x == self.tile_to_discard]
8282
# for west-west, east-east we had to double tile value
8383
value += honored_value * len(count_of_winds)
8484
else:

project/game/ai/first_version/main.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ def draw_tile(self, tile):
6969
"""
7070
self.determine_strategy()
7171

72-
def discard_tile(self):
72+
def discard_tile(self, discard_tile):
73+
# we called meld and we had discard tile that we wanted to discard
74+
if discard_tile is not None:
75+
if not self.last_discard_option:
76+
return discard_tile
77+
return self.process_discard_option(self.last_discard_option, self.player.closed_hand)
78+
7379
results, shanten = self.calculate_outs(self.player.tiles,
7480
self.player.closed_hand,
7581
self.player.open_hand_34_tiles)
@@ -168,7 +174,13 @@ def try_to_call_meld(self, tile, is_kamicha_discard):
168174
if not self.current_strategy:
169175
return None, None
170176

171-
return self.current_strategy.try_to_call_meld(tile, is_kamicha_discard)
177+
meld, discard_option = self.current_strategy.try_to_call_meld(tile, is_kamicha_discard)
178+
tile_to_discard = None
179+
if discard_option:
180+
self.last_discard_option = discard_option
181+
tile_to_discard = discard_option.tile_to_discard
182+
183+
return meld, tile_to_discard
172184

173185
def determine_strategy(self):
174186
# for already opened hand we don't need to give up on selected strategy
@@ -383,6 +395,9 @@ def should_call_kan(self, tile, open_kan):
383395

384396
return None
385397

398+
def should_call_win(self, tile, enemy_seat):
399+
return True
400+
386401
def enemy_called_riichi(self, enemy_seat):
387402
"""
388403
After enemy riichi we had to check will we fold or not
@@ -398,7 +413,3 @@ def enemy_players(self):
398413
Return list of players except our bot
399414
"""
400415
return self.player.table.players[1:]
401-
402-
@property
403-
def valued_honors(self):
404-
return [CHUN, HAKU, HATSU, self.player.table.round_wind, self.player.player_wind]

project/game/ai/first_version/strategies/tanyao.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def should_activate_strategy(self):
3535
if x in self.not_suitable_tiles and tile == 2:
3636
count_of_terminal_pairs += 1
3737

38-
if x in self.player.ai.valued_honors:
38+
if x in self.player.valued_honors:
3939
count_of_valued_pairs += 1
4040

4141
# if we already have pon of honor\terminal tiles

project/game/ai/first_version/strategies/yakuhai.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def should_activate_strategy(self):
1717
return False
1818

1919
tiles_34 = TilesConverter.to_34_array(self.player.tiles)
20-
valued_pairs = [x for x in self.player.ai.valued_honors if tiles_34[x] >= 2]
20+
valued_pairs = [x for x in self.player.valued_honors if tiles_34[x] >= 2]
2121

2222
for pair in valued_pairs:
2323
# we have valued pair in the hand and there is enough tiles
@@ -40,7 +40,7 @@ def determine_what_to_discard(self, closed_hand, outs_results, shanten, for_open
4040
tile_for_open_hand //= 4
4141

4242
tiles_34 = TilesConverter.to_34_array(self.player.tiles)
43-
valued_pairs = [x for x in self.player.ai.valued_honors if tiles_34[x] == 2]
43+
valued_pairs = [x for x in self.player.valued_honors if tiles_34[x] == 2]
4444

4545
# when we trying to open hand with tempai state, we need to chose a valued pair waiting
4646
if shanten == 0 and valued_pairs and for_open_hand and tile_for_open_hand not in valued_pairs:
@@ -74,7 +74,7 @@ def meld_had_to_be_called(self, tile):
7474

7575
tile //= 4
7676
tiles_34 = TilesConverter.to_34_array(self.player.tiles)
77-
valued_pairs = [x for x in self.player.ai.valued_honors if tiles_34[x] == 2]
77+
valued_pairs = [x for x in self.player.valued_honors if tiles_34[x] == 2]
7878

7979
for meld in self.player.melds:
8080
# for big shanten number we don't need to check already opened pon set,
@@ -95,4 +95,4 @@ def meld_had_to_be_called(self, tile):
9595
return False
9696

9797
def _is_yakuhai_pon(self, meld):
98-
return meld.type == Meld.PON and meld.tiles[0] // 4 in self.player.ai.valued_honors
98+
return meld.type == Meld.PON and meld.tiles[0] // 4 in self.player.valued_honors

project/game/ai/random/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ImplementationAI(InterfaceAI):
1010
"""
1111
version = 'random'
1212

13-
def discard_tile(self):
13+
def discard_tile(self, discard_tile):
1414
tile_to_discard = random.randrange(len(self.player.tiles) - 1)
1515
tile_to_discard = self.player.tiles[tile_to_discard]
1616
return tile_to_discard

project/game/player.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import copy
44

5-
from mahjong.constants import EAST, SOUTH, WEST, NORTH
5+
from mahjong.constants import EAST, SOUTH, WEST, NORTH, CHUN, HAKU, HATSU
66
from mahjong.meld import Meld
77
from mahjong.tile import TilesConverter, Tile
88

@@ -144,16 +144,13 @@ def draw_tile(self, tile):
144144

145145
self.ai.draw_tile(tile)
146146

147-
def discard_tile(self, discard_option=None):
147+
def discard_tile(self, discard_tile=None):
148148
"""
149-
:param discard_option:
149+
:param discard_tile: 136 tile format
150150
:return:
151151
"""
152152

153-
if discard_option:
154-
tile_to_discard = self.ai.process_discard_option(discard_option, self.closed_hand)
155-
else:
156-
tile_to_discard = self.ai.discard_tile()
153+
tile_to_discard = self.ai.discard_tile(discard_tile)
157154

158155
is_tsumogiri = tile_to_discard == self.last_draw
159156
# it is important to use table method,
@@ -164,7 +161,11 @@ def discard_tile(self, discard_option=None):
164161
return tile_to_discard
165162

166163
def can_call_riichi(self):
167-
result = all([
164+
result = self.formal_riichi_conditions()
165+
return result and self.ai.should_call_riichi()
166+
167+
def formal_riichi_conditions(self):
168+
return all([
168169
self.in_tempai,
169170

170171
not self.in_riichi,
@@ -174,8 +175,6 @@ def can_call_riichi(self):
174175
self.table.count_of_remaining_tiles > 4
175176
])
176177

177-
return result and self.ai.should_call_riichi()
178-
179178
def should_call_kan(self, tile, open_kan):
180179
"""
181180
Method will decide should we call a kan,
@@ -185,6 +184,9 @@ def should_call_kan(self, tile, open_kan):
185184
"""
186185
return self.ai.should_call_kan(tile, open_kan)
187186

187+
def should_call_win(self, tile, enemy_seat):
188+
return self.ai.should_call_win(tile, enemy_seat)
189+
188190
def try_to_call_meld(self, tile, is_kamicha_discard):
189191
return self.ai.try_to_call_meld(tile, is_kamicha_discard)
190192

@@ -237,6 +239,10 @@ def open_hand_34_tiles(self):
237239
results.append([meld[0] // 4, meld[1] // 4, meld[2] // 4])
238240
return results
239241

242+
@property
243+
def valued_honors(self):
244+
return [CHUN, HAKU, HATSU, self.table.round_wind, self.player_wind]
245+
240246

241247
class EnemyPlayer(PlayerInterface):
242248
# array of tiles in 34 tile format

project/game/tests/tests_player.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from game.player import Player
99
from game.table import Table
10+
from utils.settings_handler import settings
1011

1112

1213
class PlayerTestCase(unittest.TestCase, TestMixin):
@@ -19,13 +20,12 @@ def test_can_call_riichi_and_tempai(self):
1920
player.in_riichi = False
2021
player.scores = 2000
2122
player.table.count_of_remaining_tiles = 40
22-
player.ai.waiting = [1, 2]
2323

24-
self.assertEqual(player.can_call_riichi(), False)
24+
self.assertEqual(player.formal_riichi_conditions(), False)
2525

2626
player.in_tempai = True
2727

28-
self.assertEqual(player.can_call_riichi(), True)
28+
self.assertEqual(player.formal_riichi_conditions(), True)
2929

3030
def test_can_call_riichi_and_already_in_riichi(self):
3131
table = Table()
@@ -35,13 +35,12 @@ def test_can_call_riichi_and_already_in_riichi(self):
3535
player.in_riichi = True
3636
player.scores = 2000
3737
player.table.count_of_remaining_tiles = 40
38-
player.ai.waiting = [1, 2]
3938

40-
self.assertEqual(player.can_call_riichi(), False)
39+
self.assertEqual(player.formal_riichi_conditions(), False)
4140

4241
player.in_riichi = False
4342

44-
self.assertEqual(player.can_call_riichi(), True)
43+
self.assertEqual(player.formal_riichi_conditions(), True)
4544

4645
def test_can_call_riichi_and_scores(self):
4746
table = Table()
@@ -51,13 +50,12 @@ def test_can_call_riichi_and_scores(self):
5150
player.in_riichi = False
5251
player.scores = 0
5352
player.table.count_of_remaining_tiles = 40
54-
player.ai.waiting = [1, 2]
5553

56-
self.assertEqual(player.can_call_riichi(), False)
54+
self.assertEqual(player.formal_riichi_conditions(), False)
5755

5856
player.scores = 1000
5957

60-
self.assertEqual(player.can_call_riichi(), True)
58+
self.assertEqual(player.formal_riichi_conditions(), True)
6159

6260
def test_can_call_riichi_and_remaining_tiles(self):
6361
table = Table()
@@ -67,13 +65,12 @@ def test_can_call_riichi_and_remaining_tiles(self):
6765
player.in_riichi = False
6866
player.scores = 2000
6967
player.table.count_of_remaining_tiles = 3
70-
player.ai.waiting = [1, 2]
7168

72-
self.assertEqual(player.can_call_riichi(), False)
69+
self.assertEqual(player.formal_riichi_conditions(), False)
7370

7471
player.table.count_of_remaining_tiles = 5
7572

76-
self.assertEqual(player.can_call_riichi(), True)
73+
self.assertEqual(player.formal_riichi_conditions(), True)
7774

7875
def test_can_call_riichi_and_open_hand(self):
7976
table = Table()
@@ -84,13 +81,12 @@ def test_can_call_riichi_and_open_hand(self):
8481
player.scores = 2000
8582
player.melds = [Meld()]
8683
player.table.count_of_remaining_tiles = 40
87-
player.ai.waiting = [1, 2]
8884

89-
self.assertEqual(player.can_call_riichi(), False)
85+
self.assertEqual(player.formal_riichi_conditions(), False)
9086

9187
player.melds = []
9288

93-
self.assertEqual(player.can_call_riichi(), True)
89+
self.assertEqual(player.formal_riichi_conditions(), True)
9490

9591
def test_player_wind(self):
9692
table = Table()

project/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def parse_args_and_set_up_settings():
4040

4141
parser.add_option('-a', '--ai',
4242
type='string',
43-
default=settings.AI,
43+
default=settings.AI_PACKAGE,
4444
help='AI package')
4545

4646
opts, _ = parser.parse_args()
@@ -51,6 +51,9 @@ def parse_args_and_set_up_settings():
5151
settings.WAITING_GAME_TIMEOUT_MINUTES = opts.timeout
5252
settings.AI_PACKAGE = opts.ai
5353

54+
# it is important to reload bot class
55+
settings.load_ai_class()
56+
5457
if opts.championship:
5558
settings.IS_TOURNAMENT = True
5659
settings.LOBBY = opts.championship

0 commit comments

Comments
 (0)