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

Commit 8212fa3

Browse files
committed
Improve game reproducer
1 parent 1d85b02 commit 8212fa3

File tree

11 files changed

+309
-212
lines changed

11 files changed

+309
-212
lines changed

doc/reproducer.md

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,133 @@
1-
## Round reproducer
1+
# Game reproducer
22

33
We built the way to reproduce already played round.
4-
This is really helpful when you want to reproduce table state and fix bot bad behaviour.
4+
5+
This is really helpful when you want to reproduce table state and fix bot incorrect behaviour.
56

67
There are two options to do it.
78

8-
### Reproduce from tenhou log link
9+
## Getting game meta information
910

10-
First you need to do dry run of the reproducer with command:
11+
It will be easier to find the round number to reproduce if you check the game meta-information first:
1112

13+
Command:
14+
```bash
15+
python reproducer.py --log 2020102008gm-0001-7994-9438a8f4 --meta
1216
```
13-
python reproducer.py -o "http://tenhou.net/0/?log=2017041516gm-0089-0000-23b4752d&tw=3&ts=2" -d
14-
```
15-
16-
It will print all available tags in the round. For example we want to stop before
17-
discard tile to other player ron, in given example we had to chose `<W59/>` tag as a stop tag.
1817

19-
Next command will be:
20-
21-
```
22-
python reproducer.py -o "http://tenhou.net/0/?log=2017041516gm-0089-0000-23b4752d&tw=3&ts=2" -t "<W59/>"
18+
Output:
19+
```json
20+
{
21+
"players": [
22+
{
23+
"seat": 0,
24+
"name": "Wanjirou",
25+
"rank": "新人"
26+
},
27+
{
28+
"seat": 1,
29+
"name": "Kaavi",
30+
"rank": "新人"
31+
},
32+
{
33+
"seat": 2,
34+
"name": "Xenia",
35+
"rank": "新人"
36+
},
37+
{
38+
"seat": 3,
39+
"name": "Ichihime",
40+
"rank": "新人"
41+
}
42+
],
43+
"game_rounds": [
44+
{
45+
"wind": 0,
46+
"honba": 0,
47+
"round_start_scores": [
48+
250,
49+
250,
50+
250,
51+
250
52+
]
53+
},
54+
{
55+
"wind": 1,
56+
"honba": 1,
57+
"round_start_scores": [
58+
235,
59+
235,
60+
265,
61+
265
62+
]
63+
},
64+
{
65+
"wind": 1,
66+
"honba": 2,
67+
"round_start_scores": [
68+
221,
69+
277,
70+
251,
71+
251
72+
]
73+
},
74+
{
75+
"wind": 1,
76+
"honba": 3,
77+
"round_start_scores": [
78+
221,
79+
298,
80+
230,
81+
251
82+
]
83+
},
84+
{
85+
"wind": 2,
86+
"honba": 0,
87+
"round_start_scores": [
88+
320,
89+
255,
90+
197,
91+
228
92+
]
93+
},
94+
{
95+
"wind": 3,
96+
"honba": 0,
97+
"round_start_scores": [
98+
290,
99+
215,
100+
137,
101+
358
102+
]
103+
}
104+
]
105+
}
23106
```
24107

25-
And output:
108+
From this information player seat and wind number could be useful for the next command run.
26109

27-
```
28-
Hand: 268m28p23456677s + 6p
29-
Discard: 2m
30-
```
110+
## Running the reproducing for the game
31111

32-
After this you can debug bot decisions.
112+
To reproduce game situation you need to know:
113+
- log id
114+
- player seat number or player nickname
115+
- wind number (1-4 for east, 5-8 for south, 9-12 for west)
116+
- honba number
117+
- tile where to stop the game
118+
- action
33119

34-
### Reproduce from our log
120+
There are two supported actions for the reproducer:
121+
- `draw`. Sought tile will be added to the hand, then method `discard_tile()` will be called and after that reproducer will stop.
122+
- `enemy_discard`. After enemy discard method `try_to_call_meld()` will be called (if possible) and after that reproducer will stop.
35123

36-
Sometimes we had to debug `bot <-> server` communication. For this purpose we built this reproducer.
37-
38-
Just use it with already played game:
124+
## Examples of usage
39125

126+
```bash
127+
python reproducer.py --log 2020102008gm-0001-7994-9438a8f4 --player Wanjirou --wind 3 --honba 0 --tile 7p --action enemy_discard
40128
```
41-
python reproducer.py -l d6a5e_2017-04-13\ 09_54_01.log
129+
130+
```bash
131+
python reproducer.py --log 2020102009gm-0001-7994-5e2f46c0 --player Kaavi --wind 3 --honba 1 --tile 5m --action draw
42132
```
43133

44-
It will send to the bot all commands that were send from tenhou in real game.

project/game/ai/discard.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def __init__(self, player, tile_to_discard, shanten, waiting, ukeire, wait_to_uk
5555
self.had_to_be_discarded = False
5656
self.wait_to_ukeire = wait_to_ukeire
5757
self.second_level_cost = 0
58+
self.average_second_level_cost = 0
5859

5960
self.calculate_value()
6061

@@ -73,6 +74,10 @@ def serialize(self):
7374
}
7475
if self.ukeire_second:
7576
data["ukeire2"] = self.ukeire_second
77+
if self.had_to_be_saved:
78+
data["had_to_be_saved"] = self.had_to_be_saved
79+
if self.had_to_be_discarded:
80+
data["had_to_be_discarded"] = self.had_to_be_discarded
7681
return data
7782

7883
def find_tile_in_hand(self, closed_hand):

project/game/ai/hand_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def discard_tile(self, tiles, closed_hand, melds):
2020
selected_tile = self.choose_tile_to_discard(tiles, closed_hand, melds)
2121
return self.process_discard_option(selected_tile, closed_hand)
2222

23-
def choose_tile_to_discard(self, tiles, closed_hand, melds):
23+
def choose_tile_to_discard(self, tiles, closed_hand, melds, for_open_hand=False):
2424
"""
2525
Try to find best tile to discard, based on different evaluations
2626
"""

project/game/ai/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def try_to_call_meld(self, tile_136, is_kamicha_discard):
117117

118118
DecisionsLogger.debug(
119119
log.MELD_CALL,
120-
"Try to call meld",
120+
"We decided to open hand",
121121
context=[
122122
f"Hand: {self.player.format_hand_for_print(tile_136)}",
123123
f"Meld: {meld.serialize()}",

project/game/ai/strategies/main.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,15 @@ def _find_best_meld_to_open(self, possible_melds, new_tiles, closed_hand, discar
254254
meld = MeldPrint()
255255
meld.type = meld_type
256256
meld.tiles = sorted(tiles)
257-
258257
melds = self.player.melds + [meld]
259258

260-
selected_tile = self.player.ai.hand_builder.choose_tile_to_discard(new_tiles, closed_hand_copy, melds)
259+
DecisionsLogger.debug(
260+
log.MELD_HAND, f"Hand: {self._format_hand_for_print(new_tiles, discarded_tile, melds)}"
261+
)
262+
263+
selected_tile = self.player.ai.hand_builder.choose_tile_to_discard(
264+
new_tiles, closed_hand_copy, melds, for_open_hand=True
265+
)
261266

262267
final_results.append(
263268
{
@@ -274,7 +279,12 @@ def _find_best_meld_to_open(self, possible_melds, new_tiles, closed_hand, discar
274279

275280
DecisionsLogger.debug(
276281
log.MELD_PREPARE,
277-
"Options with meld calling (use first one)",
282+
"Tiles could be used for open meld",
278283
context=final_results,
279284
)
280285
return final_results[0]
286+
287+
def _format_hand_for_print(self, tiles, new_tile, melds):
288+
hand_string = f"{TilesConverter.to_one_line_string(tiles)} + {TilesConverter.to_one_line_string([new_tile])}"
289+
hand_string += " [{}]".format(", ".join([TilesConverter.to_one_line_string(x.tiles) for x in melds]))
290+
return hand_string

project/main.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,20 @@ def parse_args_and_set_up_settings():
1818
default=settings.USER_ID,
1919
help="Tenhou's user id. Example: IDXXXXXXXX-XXXXXXXX. Default is {0}".format(settings.USER_ID),
2020
)
21-
2221
parser.add_option(
2322
"-g",
2423
"--game_type",
2524
type="string",
2625
default=settings.GAME_TYPE,
2726
help="The game type in Tenhou.net. Examples: 1 or 9. Default is {0}".format(settings.GAME_TYPE),
2827
)
29-
3028
parser.add_option(
3129
"-l",
3230
"--lobby",
3331
type="string",
3432
default=settings.LOBBY,
3533
help="Lobby to play. Default is {0}".format(settings.LOBBY),
3634
)
37-
3835
parser.add_option(
3936
"-t",
4037
"--timeout",
@@ -44,9 +41,12 @@ def parse_args_and_set_up_settings():
4441
"If game is not started in timeout, script will be ended. "
4542
"Default is {0}".format(settings.WAITING_GAME_TIMEOUT_MINUTES),
4643
)
47-
48-
parser.add_option("-c", "--championship", type="string", help="Tournament lobby to play.")
49-
44+
parser.add_option(
45+
"-c",
46+
"--championship",
47+
type="string",
48+
help="Tournament lobby to play.",
49+
)
5050
parser.add_option(
5151
"-s",
5252
"--settings",

0 commit comments

Comments
 (0)