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

Commit ef487f8

Browse files
committed
Refactor AI a little bit
1 parent 4b201bd commit ef487f8

File tree

11 files changed

+143
-56
lines changed

11 files changed

+143
-56
lines changed

project/game/ai/base/main.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,79 @@
11
# -*- coding: utf-8 -*-
22

33

4-
class BaseAI(object):
4+
class InterfaceAI(object):
5+
"""
6+
Public interface of the bot AI
7+
"""
8+
version = 'none'
9+
510
player = None
611
table = None
712

813
def __init__(self, player):
914
self.player = player
15+
self.table = player.table
1016

1117
def discard_tile(self):
12-
pass
18+
"""
19+
Simple tile discard (after draw)
20+
:return: int, 136 tile format
21+
"""
22+
raise NotImplemented()
23+
24+
def init_state(self):
25+
"""
26+
Method will be called after bot hand initialization
27+
:return:
28+
"""
29+
30+
def erase_state(self):
31+
"""
32+
Method will be called in the start of new round.
33+
You can null here AI attributes that depends on round data
34+
:return:
35+
"""
36+
37+
def draw_tile(self, tile):
38+
"""
39+
:param tile: 136 tile format
40+
:return:
41+
"""
42+
43+
def process_discard_option(self, discard_option, closed_hand):
44+
"""
45+
:param discard_option: DiscardOption object
46+
:param closed_hand: list of 136 tiles format
47+
:return:
48+
"""
49+
50+
def should_call_riichi(self):
51+
"""
52+
When bot in tempai this method will be run
53+
You can check additional params here to decide should be riichi called or not
54+
:return: boolean
55+
"""
56+
return False
57+
58+
def should_call_kan(self, tile, is_open_kan):
59+
"""
60+
Method will decide should we call a kan or upgrade pon to kan (chankan)
61+
:param tile: 136 tile format
62+
:param is_open_kan: boolean
63+
:return: kan type (Meld.KAN, Meld.CHANKAN) or None
64+
"""
65+
return False
66+
67+
def try_to_call_meld(self, tile, is_kamicha_discard):
68+
"""
69+
Determine should we call a meld or not.
70+
:param tile: 136 format tile
71+
:param is_kamicha_discard: boolean
72+
:return: Meld and DiscardOption objects or None, None
73+
"""
74+
return None, None
75+
76+
def enemy_called_riichi(self, enemy_seat):
77+
"""
78+
Will be called after other player riichi
79+
"""

project/game/ai/first_version/main.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
from mahjong.tile import TilesConverter
1111
from mahjong.utils import is_pair, is_pon
1212

13-
from game.ai.base.main import BaseAI
13+
from game.ai.base.main import InterfaceAI
1414
from game.ai.discard import DiscardOption
15-
from game.ai.first_version.defence.enemy_analyzer import EnemyAnalyzer
1615
from game.ai.first_version.defence.main import DefenceHandler
1716
from game.ai.first_version.strategies.honitsu import HonitsuStrategy
1817
from game.ai.first_version.strategies.main import BaseStrategy
@@ -22,14 +21,15 @@
2221
logger = logging.getLogger('ai')
2322

2423

25-
class MainAI(BaseAI):
24+
class ImplementationAI(InterfaceAI):
2625
version = '0.3.0'
2726

2827
agari = None
2928
shanten = None
3029
defence = None
3130
hand_divider = None
3231
finished_hand = None
32+
last_discard_option = None
3333

3434
previous_shanten = 7
3535
in_defence = False
@@ -38,7 +38,7 @@ class MainAI(BaseAI):
3838
current_strategy = None
3939

4040
def __init__(self, player):
41-
super(MainAI, self).__init__(player)
41+
super(ImplementationAI, self).__init__(player)
4242

4343
self.agari = Agari()
4444
self.shanten = Shanten()
@@ -49,10 +49,25 @@ def __init__(self, player):
4949
self.current_strategy = None
5050
self.waiting = []
5151
self.in_defence = False
52+
self.last_discard_option = None
53+
54+
def init_state(self):
55+
"""
56+
Let's decide what we will do with our hand (like open for tanyao and etc.)
57+
"""
58+
self.determine_strategy()
5259

5360
def erase_state(self):
5461
self.current_strategy = None
5562
self.in_defence = False
63+
self.last_discard_option = None
64+
65+
def draw_tile(self, tile):
66+
"""
67+
:param tile: 136 tile format
68+
:return:
69+
"""
70+
self.determine_strategy()
5671

5772
def discard_tile(self):
5873
results, shanten = self.calculate_outs(self.player.tiles,
@@ -234,11 +249,11 @@ def sorting(x):
234249

235250
return selected_tile
236251

237-
def process_discard_option(self, selected_tile, closed_hand):
238-
self.waiting = selected_tile.waiting
239-
self.player.ai.previous_shanten = selected_tile.shanten
252+
def process_discard_option(self, discard_option, closed_hand):
253+
self.waiting = discard_option.waiting
254+
self.player.ai.previous_shanten = discard_option.shanten
240255
self.player.in_tempai = self.player.ai.previous_shanten == 0
241-
return selected_tile.find_tile_in_hand(closed_hand)
256+
return discard_option.find_tile_in_hand(closed_hand)
242257

243258
def estimate_hand_value(self, win_tile, tiles=None, call_riichi=False):
244259
"""
@@ -274,6 +289,9 @@ def should_call_riichi(self):
274289
if not self.waiting:
275290
return False
276291

292+
if self.in_defence:
293+
return False
294+
277295
# we have a good wait, let's riichi
278296
if len(self.waiting) > 1:
279297
return True
@@ -302,7 +320,7 @@ def should_call_riichi(self):
302320

303321
return True
304322

305-
def can_call_kan(self, tile, open_kan):
323+
def should_call_kan(self, tile, open_kan):
306324
"""
307325
Method will decide should we call a kan,
308326
or upgrade pon to kan
@@ -365,6 +383,15 @@ def can_call_kan(self, tile, open_kan):
365383

366384
return None
367385

386+
def enemy_called_riichi(self, enemy_seat):
387+
"""
388+
After enemy riichi we had to check will we fold or not
389+
it is affect open hand decisions
390+
:return:
391+
"""
392+
if self.defence.should_go_to_defence_mode():
393+
self.in_defence = True
394+
368395
@property
369396
def enemy_players(self):
370397
"""

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,13 @@ def test_upgrade_opened_pon_to_kan(self):
243243
tile = self._string_to_136_tile(man='4')
244244
player.draw_tile(tile)
245245

246-
self.assertEqual(player.can_call_kan(tile, False), None)
246+
self.assertEqual(player.should_call_kan(tile, False), None)
247247

248248
player.add_called_meld(self._make_meld(Meld.PON, man='444'))
249249

250250
self.assertEqual(len(player.melds), 1)
251251
self.assertEqual(len(player.tiles), 14)
252-
self.assertEqual(player.can_call_kan(tile, False), Meld.CHANKAN)
252+
self.assertEqual(player.should_call_kan(tile, False), Meld.CHANKAN)
253253

254254
player.discard_tile()
255255
player.draw_tile(tile)
@@ -269,15 +269,15 @@ def test_call_closed_kan(self):
269269
player.draw_tile(tile)
270270

271271
# it is pretty stupid to call closed kan with 2m
272-
self.assertEqual(player.can_call_kan(tile, False), None)
272+
self.assertEqual(player.should_call_kan(tile, False), None)
273273

274274
tiles = self._string_to_136_array(man='12223', sou='111456', pin='12')
275275
player.init_hand(tiles)
276276
tile = self._string_to_136_tile(sou='1')
277277
player.draw_tile(tile)
278278

279279
# call closed kan with 1s is fine
280-
self.assertEqual(player.can_call_kan(tile, False), Meld.KAN)
280+
self.assertEqual(player.should_call_kan(tile, False), Meld.KAN)
281281

282282
def test_opened_kan(self):
283283
table = Table()
@@ -292,14 +292,14 @@ def test_opened_kan(self):
292292

293293
# our hand is closed, we don't need to call opened kan here
294294
tile = self._string_to_136_tile(sou='1')
295-
self.assertEqual(player.can_call_kan(tile, True), None)
295+
self.assertEqual(player.should_call_kan(tile, True), None)
296296

297297
player.add_called_meld(self._make_meld(Meld.PON, honors='111'))
298298

299299
# our hand is open, but it is not tempai
300300
# we don't need to open kan here
301301
tile = self._string_to_136_tile(sou='1')
302-
self.assertEqual(player.can_call_kan(tile, True), None)
302+
self.assertEqual(player.should_call_kan(tile, True), None)
303303

304304
table = Table()
305305
player = table.player
@@ -314,7 +314,7 @@ def test_opened_kan(self):
314314

315315
# our hand is open, in tempai and with a good wait
316316
tile = self._string_to_136_tile(sou='1')
317-
self.assertEqual(player.can_call_kan(tile, True), Meld.KAN)
317+
self.assertEqual(player.should_call_kan(tile, True), Meld.KAN)
318318

319319
def test_closed_kan_and_riichi(self):
320320
table = Table()
@@ -330,7 +330,7 @@ def test_closed_kan_and_riichi(self):
330330
tile = kan_tiles[3]
331331
player.draw_tile(tile)
332332

333-
kan_type = player.can_call_kan(tile, False)
333+
kan_type = player.should_call_kan(tile, False)
334334
self.assertEqual(kan_type, Meld.KAN)
335335

336336
meld = Meld()
@@ -362,4 +362,4 @@ def test_dont_call_kan_in_defence_mode(self):
362362
table.add_called_riichi(1)
363363

364364
tile = self._string_to_136_tile(sou='1')
365-
self.assertEqual(table.player.can_call_kan(tile, False), None)
365+
self.assertEqual(table.player.should_call_kan(tile, False), None)

project/game/ai/random/main.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
# -*- coding: utf-8 -*-
22
import random
33

4-
from game.ai.base.main import BaseAI
4+
from game.ai.base.main import InterfaceAI
55

66

7-
class MainAI(BaseAI):
7+
class ImplementationAI(InterfaceAI):
88
"""
99
AI that will discard random tile from the hand
1010
"""
11-
1211
version = 'random'
1312

14-
def __init__(self, player):
15-
super(MainAI, self).__init__(player)
16-
1713
def discard_tile(self):
1814
tile_to_discard = random.randrange(len(self.player.tiles) - 1)
1915
tile_to_discard = self.player.tiles[tile_to_discard]

project/game/player.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from mahjong.meld import Meld
77
from mahjong.tile import TilesConverter, Tile
88

9-
from game.ai.first_version.main import MainAI
9+
from utils.settings_handler import settings
1010

1111
logger = logging.getLogger('tenhou')
1212

@@ -117,7 +117,7 @@ class Player(PlayerInterface):
117117
def __init__(self, table, seat, dealer_seat):
118118
super().__init__(table, seat, dealer_seat)
119119

120-
self.ai = MainAI(self)
120+
self.ai = settings.AI_CLASS(self)
121121

122122
def erase_state(self):
123123
super().erase_state()
@@ -133,7 +133,7 @@ def erase_state(self):
133133
def init_hand(self, tiles):
134134
self.tiles = tiles
135135

136-
self.ai.determine_strategy()
136+
self.ai.init_state()
137137

138138
def draw_tile(self, tile):
139139
self.last_draw = tile
@@ -142,14 +142,14 @@ def draw_tile(self, tile):
142142
# we need sort it to have a better string presentation
143143
self.tiles = sorted(self.tiles)
144144

145-
self.ai.determine_strategy()
145+
self.ai.draw_tile(tile)
146146

147147
def discard_tile(self, discard_option=None):
148148
"""
149149
:param discard_option:
150150
:return:
151151
"""
152-
# we can't use if tile, because of 0 tile
152+
153153
if discard_option:
154154
tile_to_discard = self.ai.process_discard_option(discard_option, self.closed_hand)
155155
else:
@@ -169,26 +169,28 @@ def can_call_riichi(self):
169169

170170
not self.in_riichi,
171171
not self.is_open_hand,
172-
not self.ai.in_defence,
173172

174173
self.scores >= 1000,
175174
self.table.count_of_remaining_tiles > 4
176175
])
177176

178177
return result and self.ai.should_call_riichi()
179178

180-
def can_call_kan(self, tile, open_kan):
179+
def should_call_kan(self, tile, open_kan):
181180
"""
182181
Method will decide should we call a kan,
183182
or upgrade pon to kan
184183
:param tile: 136 tile format
185184
:return:
186185
"""
187-
return self.ai.can_call_kan(tile, open_kan)
186+
return self.ai.should_call_kan(tile, open_kan)
188187

189188
def try_to_call_meld(self, tile, is_kamicha_discard):
190189
return self.ai.try_to_call_meld(tile, is_kamicha_discard)
191190

191+
def enemy_called_riichi(self, player_seat):
192+
self.ai.enemy_called_riichi(player_seat)
193+
192194
def total_tiles(self, tile, tiles_34):
193195
"""
194196
Return sum of all tiles (discarded + from melds + our hand)
@@ -198,15 +200,6 @@ def total_tiles(self, tile, tiles_34):
198200
"""
199201
return tiles_34[tile] + self.table.revealed_tiles[tile]
200202

201-
def enemy_called_riichi(self):
202-
"""
203-
After enemy riichi we had to check will we fold or not
204-
it is affect open hand decision
205-
:return:
206-
"""
207-
if self.ai.defence.should_go_to_defence_mode():
208-
self.ai.in_defence = True
209-
210203
def add_called_meld(self, meld: Meld):
211204
# we had to remove tile from the hand for closed kan set
212205
if (meld.type == Meld.KAN and not meld.opened) or meld.type == Meld.CHANKAN:

project/game/table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def add_called_riichi(self, player_seat):
9393

9494
# we had to check will we go for defence or not
9595
if player_seat != 0:
96-
self.player.enemy_called_riichi()
96+
self.player.enemy_called_riichi(player_seat)
9797

9898
def add_discarded_tile(self, player_seat, tile, is_tsumogiri):
9999
"""

0 commit comments

Comments
 (0)