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

Commit e0cb25f

Browse files
committed
Merge branch 'hand-calculating-refactoring'
2 parents 45a509f + ac0d57d commit e0cb25f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+520
-4995
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ __pycache__
66
.DS_Store
77
logs
88
project/settings_local.py
9-
old_version.py
9+
project/game/ai/common
1010

1111
tests_validate_hand.py
1212
loader.py

README.md

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,7 @@ For now only **Python 3.5+** is supported.
88

99
## Mahjong hands calculation
1010

11-
We have a code which can calculate hand cost (han, fu, yaku and scores) based on the hand's tiles.
12-
13-
It supports features like:
14-
15-
- Disable\enable aka dora in the hand
16-
- Disable\enable open tanyao yaku
17-
- By now it supports double yakumans (Dai Suushii, Daburu Kokushi musou, Suu ankou tanki,
18-
Daburu Chuuren Poutou). Later I plan to have a disabling option in settings for it.
19-
20-
The code was validated on tenhou.net phoenix replays in total on **8'527'296 hands**, and
21-
results were the same in 100% cases.
22-
23-
So, we can say that our own hand calculator works the same way that tenhou.net hand calculation.
24-
25-
The example of usage you can find here: https://github.com/MahjongRepository/tenhou-python-bot/blob/master/project/validate_hand.py#L194
11+
You can find it here: https://github.com/MahjongRepository/mahjong
2612

2713
## Mahjong bot
2814

@@ -74,8 +60,8 @@ For the next version I have a plan to improve win rate, probably bot should push
7460

7561
## How to run it?
7662

77-
Run `pythone main.py` it will connect to the tenhou.net and will play a match.
78-
After the end of the match it will close connection to the server
63+
1. `pip install -r requirements.txt`
64+
2. Run `pythone main.py` it will connect to the tenhou.net and will play a game
7965

8066
## Configuration instructions
8167

@@ -84,6 +70,21 @@ They will override settings from default `settings.py` file
8470
2. Also you can override some default settings with command argument.
8571
Use `python main.py -h` to check all available commands
8672

73+
## Implement your own AI
74+
75+
We tried to isolate default AI from the project as much as we could.
76+
77+
There is `game.ai.base.InterfaceAI` with one required method `discard_tile` that had to be implemented by your AI.
78+
79+
### Start with your AI
80+
81+
This command will make a copy of the simple bot (it is discarding random tiles from the hand)
82+
1. `cd project`
83+
2. `cp -a game/ai/random game/ai/common`
84+
3. You can run new AI with command: `python main.py -a common` (or change `AI_PACKAGE` in settings)
85+
86+
After that you can change all what you want in `game.ai.common` package and test it in real games.
87+
8788
## Round reproducer
8889

8990
We built the way to reproduce already played round.
@@ -119,7 +120,7 @@ After this you can debug bot decisions.
119120

120121
### Reproduce from our log
121122

122-
Sometimes we had to debug bot <-> server communication. For this purpose we built this reproducer.
123+
Sometimes we had to debug `bot <-> server` communication. For this purpose we built this reproducer.
123124

124125
Just use it with already played game:
125126

project/game/ai/base/__init__.py

Whitespace-only changes.

project/game/ai/base/main.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
class InterfaceAI(object):
5+
"""
6+
Public interface of the bot AI
7+
"""
8+
version = 'none'
9+
10+
player = None
11+
table = None
12+
13+
def __init__(self, player):
14+
self.player = player
15+
self.table = player.table
16+
17+
def discard_tile(self, discard_tile):
18+
"""
19+
AI should decide what tile had to be discarded from the hand on bot turn
20+
:param discard_tile: 136 tile format. Sometimes we want to discard specific tile
21+
:return: 136 tile format
22+
"""
23+
raise NotImplemented()
24+
25+
def init_hand(self):
26+
"""
27+
Method will be called after bot hand initialization (when tiles will be set to the player)
28+
:return:
29+
"""
30+
31+
def erase_state(self):
32+
"""
33+
Method will be called in the start of new round.
34+
You can null here AI attributes that depends on round data
35+
:return:
36+
"""
37+
38+
def draw_tile(self, tile):
39+
"""
40+
:param tile: 136 tile format
41+
:return:
42+
"""
43+
44+
def should_call_win(self, tile, enemy_seat):
45+
"""
46+
When we can call win by other player discard this method will be called
47+
:return: boolean
48+
"""
49+
return True
50+
51+
def should_call_riichi(self):
52+
"""
53+
When bot can call riichi this method will be called.
54+
You can check additional params here to decide should be riichi called or not
55+
:return: boolean
56+
"""
57+
return False
58+
59+
def should_call_kan(self, tile, is_open_kan):
60+
"""
61+
When bot can call kan or chankan this method will be called
62+
:param tile: 136 tile format
63+
:param is_open_kan: boolean
64+
:return: kan type (Meld.KAN, Meld.CHANKAN) or None
65+
"""
66+
return False
67+
68+
def try_to_call_meld(self, tile, is_kamicha_discard):
69+
"""
70+
When bot can open hand with a set (chi or pon/kan) this method will be called
71+
:param tile: 136 format tile
72+
:param is_kamicha_discard: boolean
73+
:return: Meld and DiscardOption objects or None, None
74+
"""
75+
return None, None
76+
77+
def enemy_called_riichi(self, enemy_seat):
78+
"""
79+
Will be called after other player riichi
80+
"""
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from mahjong.constants import AKA_DORA_LIST
33
from mahjong.tile import TilesConverter
4-
from mahjong.utils import simplify, is_honor, plus_dora
5-
from utils.settings_handler import settings
4+
from mahjong.utils import is_honor, simplify, plus_dora, is_aka_dora
65

76

87
class DiscardOption(object):
@@ -48,7 +47,7 @@ def find_tile_in_hand(self, closed_hand):
4847
Find and return 136 tile in closed player hand
4948
"""
5049

51-
if settings.FIVE_REDS:
50+
if self.player.table.has_aka_dora:
5251
# special case, to keep aka dora in hand
5352
if self.tile_to_discard in [4, 13, 22]:
5453
aka_closed_hand = closed_hand[:]
@@ -78,8 +77,8 @@ def calculate_value(self, shanten=None):
7877
honored_value = 0
7978

8079
if is_honor(self.tile_to_discard):
81-
if self.tile_to_discard in self.player.ai.valued_honors:
82-
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]
8382
# for west-west, east-east we had to double tile value
8483
value += honored_value * len(count_of_winds)
8584
else:
@@ -89,6 +88,9 @@ def calculate_value(self, shanten=None):
8988
value += suit_tile_grades[simplified_tile]
9089

9190
count_of_dora = plus_dora(self.tile_to_discard * 4, self.player.table.dora_indicators)
91+
if is_aka_dora(self.tile_to_discard * 4, self.player.table.has_open_tanyao):
92+
count_of_dora += 1
93+
9294
value += 50 * count_of_dora
9395

9496
if is_honor(self.tile_to_discard):

project/game/ai/first_version/__init__.py

Whitespace-only changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)