Paipu Replay¶
Paipu (牌譜) is the Japanese term for game records. pymahjong supports replaying games from Tenhou.net paipu files for validation and analysis.
Overview¶
The paipu replay system allows you to:
Validate the game engine against real game records
Analyze professional player decisions
Generate training data from human games
Tenhou XML Paipu Format¶
Tenhou.net publishes game records as XML files (.txt extension). The complete format is defined in pymahjong/tenhou_paipu_check.py.
XML Tag Reference¶
The following table documents all supported XML tags in Tenhou paipu files:
Tag |
说明 |
属性/内容 |
|---|---|---|
|
牌山生成 |
|
|
游戏规则 |
|
|
玩家信息 |
名字/等级等(解析但不使用) |
|
游戏初始化 |
见下方详解 |
|
翻开宝牌 |
|
|
立直宣言 |
step1=宣言,step2=成功确认 |
|
摸牌 |
Tag索引=玩家ID(0-3),内容为tile ID |
|
舍牌 |
Tag索引=玩家ID(0-3),内容为tile ID |
|
鸣牌 |
|
|
和牌 |
含详细得分/役种信息 |
|
流局 |
含tenpai信息 |
Game Type (<GO type="N">)¶
type 是位掩码:
位 |
值 |
含义 |
|---|---|---|
0 |
1 |
PVP(人对人) |
1 |
2 |
无赤宝牌 |
2 |
4 |
无食断 |
3 |
8 |
半庄(hanchan) |
4 |
16 |
三人麻将 |
5 |
32 |
Pro(专业) |
6 |
64 |
快速 |
7 |
128 |
上级 |
四人东风为例:type=0(东风/新人)
<INIT> 详解¶
<INIT ten="25000,25000,25000,25000" oya="0"
seed="123456789,0,0,3,5,..."
hai0="0,8,18,26,27,30,32,..."
hai1="1,9,19,27,28,31,33,..."
hai2="2,10,20,27,29,30,32,..."
hai3="3,11,21,27,28,31,33,...">
ten: 初始分数(除以100后传入PaipuReplayer.init())oya: 庄家玩家 ID (0-3)seed: 逗号分隔:[0]: game_order(决定场风:game_order // 4 = 0=东, 1=南, 2=西, 3=北)[1]: honba 本场数[2]: kyoutaku 立直棒[3]: dice1 骰子1[4]: dice2 骰子2[5+]: dora0, dora1, … 初始宝牌指示牌
hai0..hai3: 各玩家初始手牌(13张,逗号分隔 tile ID)
Tile ID 映射规则¶
Tenhou tile ID (0-135) 编码:
tile_id = basetile × 4 + copy_index
basetile = tile_id // 4 (0-33)
copy_index = tile_id % 4 (0-3)
花色/数字映射:
basetile |
牌 |
|---|---|
0-8 |
1m-9m |
9-17 |
1p-9p |
18-26 |
1s-9s |
27-33 |
1z-7z(东/南/西/北/白/发/中) |
赤宝牌:5m红=id4, 5p红=id13, 5s红=id22
MT19937 Seed → Yama 生成¶
<SHOWER seed="mt19937ar-sha512-n288-base64,..."> 中的 seed 用于初始化 MT19937 伪随机数生成器,产生确定性牌山。
在代码中调用:
from MahjongPy import TenhouShuffle
TenhouShuffle.instance().init(seed) # base64 seed 字符串
yama = TenhouShuffle.instance().generate_yama() # 136 tile IDs
鸣牌编码 (<N who="N" m="NNNN">)¶
m 是一个16位整数,二进制编码鸣牌信息:
bit[0-1]: 鸣牌类型 (0=Chi, 1=Pon, 2=Kan, 3=AnKan)
bit[2-7]: 被鸣牌 basetile
bit[8-15]: 手牌中用于鸣牌的牌(3个basetile)
decodem() 函数(pymahjong/tenhou_paipu_check.py)负责解码。
<REACH> 立直流程¶
step=1: 立直宣言,
who玩家宣布立直(score -= 1000, kyoutaku++)step=2: 立直确认,1巡后若未被发现荣和则立直成功
前端实现时,step=1 宣言后须进入特殊状态,等待 step=2 确认。
Using Paipu Replay¶
Python: paipu_replay()¶
import pymahjong as pm
pm.paipu_replay(mode='debug') # 自动重放 paipuxmls/ 目录下的所有 XML
C++ PaipuReplayer¶
import MahjongPyWrapper as pm
# Create replayer
replayer = pm.PaipuReplayer()
# Initialize with game state
replayer.init(
yama=[...], # 136 tile IDs
init_scores=[25000]*4,
kyoutaku=0,
honba=0,
game_wind=0, # 0=East, 1=South, etc.
oya=0 # Parent player
)
# Get available actions
phase = replayer.get_phase()
if phase < 4:
actions = replayer.get_self_actions()
else:
actions = replayer.get_response_actions()
# Make selection
replayer.make_selection(selection_index)
# Get result (when game_over)
result = replayer.get_result()
Complete Flow: Tenhou XML → Replayer¶
import MahjongPyWrapper as pm
from pymahjong.tenhou_paipu_check import PaipuReplay
# Parse XML
p = PaipuReplay(xml_path)
p._paipu_replay() # populates replayer
# Use replayer
rp = p.replayer
for _ in range(200): # max steps
if rp.get_phase() == 16: # GAME_OVER
break
phase = rp.get_phase()
if phase < 4:
actions = rp.get_self_actions()
else:
actions = rp.get_response_actions()
# replay with same actions
rp.make_selection(0) # pass
result = rp.get_result()
Replay Validation¶
CI validates the engine against real Tenhou games:
Download paipu files from
paipuxmls/or releasesParse XML →
PaipuReplayerReplay every action
Compare final scores with XML
<AGARI>/<RYUUKYOKU>result
This ensures the engine faithfully reproduces real game outcomes.
Paipu Data Locations¶
来源 |
路径 |
说明 |
|---|---|---|
CI 数据集 |
|
预置的 Tenhou XML 文件 |
Tenhou 下载 |
|
需登录获取 |
Releases |
|
预处理数据集 |
Debug Mode¶
When debug mode is enabled, the game generates replayable code:
import pymahjong as pm
env = pm.MahjongEnv()
env.reset(debug_mode=1) # Enable debug mode
# ... play the game ...
# Get replay code
replay_code = env.t.get_debug_replay()
print(replay_code)
This outputs code that can be used to reproduce the exact game:
Table table;
table.game_init_for_replay([...], [25000, 25000, 25000, 25000], 0, 0, 0, 0);
table.make_selection(0);
table.make_selection(1);
# ... etc
Offline Dataset¶
Pre-processed datasets are available for machine learning:
import scipy.io
data = scipy.io.loadmat("mahjong_data.mat")
# data["X"] - Executor observation (93×34)
# data["O"] - Oracle observation (18×34)
# data["A"] - Actions taken
# data["M"] - Valid action masks
# data["R"] - Rewards
# data["D"] - Done signals