本项目包含一个用C++实现的日本麻将游戏,部分基于雀魂计分规则。
提供C++与Python接口,以便: 实现不同种类的AI Agent 人类游玩(并不
C++环境下:
- 运行内存 约800KB
- 单局时间 约10ms(10.1s with 1000 plays)
测试方法:
- 基于main.cpp中test_passive_table_auto,运行1000次,取运行时间平均值
- 其中每个动作都是由std::uniform_distribution进行随机选择
关于内存:
- 本项目中主要使用stl容器,没有使用到任何new/delete运算符,因此不会发生内存泄露问题。
通过对天凤2020年30万局段位战牌谱进行重放式测试。在其中99.5%以上的牌局中表现一致。 剩下的牌局中主要是分数计算上,因为双倍役满/翻dora时机/包牌等导致不一致。
测试只能保证操作能回放,不能保证是否出现错误的操作待选项(例如无法ron的情况仍然有ron的选项)。 这一点只能通过大量实战进行测试。
git clone https://github.com/Agony5757/mahjong/
cd mahjong
sudo apt-get upgrade
sudo apt-get update
sudo apt-get install clang cmake
python setup.py install
import numpy as np
import pymahjong as mp
for n in range(1000):
t = mp.Table()
t.game_init()
for m in range(5000):
if t.get_phase() < 4:
aval_actions = t.get_self_actions()
a = np.random.randint(len(aval_actions))
if aval_actions[a].action == mp.Action.Tsumo:
print(aval_actions[a].action)
for i in range(len(aval_actions[a].correspond_tiles)):
print(aval_actions[a].correspond_tiles[i].tile)
t.make_selection(a)
else:
aval_actions = t.get_response_actions()
a = np.random.randint(len(aval_actions))
if aval_actions[a].action == mp.Action.Ron:
print(aval_actions[a].action)
for i in range(len(aval_actions[a].correspond_tiles)):
print(aval_actions[a].correspond_tiles[i].tile)
t.make_selection(a)
if t.get_phase() == 16:
print(t.get_result().result_type)
break
参数类型: unordered_map: string, string 表示元数据
metadata现在支持的Key为: "yama": "1z1z.... 牌山初始化(默认随机开始) "oya": "0"/"1"/"2"/"3" 亲家,默认为"0" "wind": 东风局/南风局/西风局分别为"east","north","south",默认为"east" "deal": "from_0"/"from_oya" 从谁开始发牌(从0号玩家/从庄家开始发牌),默认为"from_0"
可以进一步通过访问成员来对数据进行获取,包括:
(pybind11封装的接口形式下)
.def_readonly("dora_spec", &Table::dora_spec)
.def_readonly("DORA", &Table::宝牌指示牌)
.def_readonly("URA_DORA", &Table::里宝牌指示牌)
.def_readonly("YAMA", &Table::牌山)
.def_readonly("players", &Table::players)
.def_readonly("turn", &Table::turn)
.def_readonly("last_action", &Table::last_action)
.def_readonly("game_wind", &Table::场风)
.def_readonly("last_action", &Table::last_action)
.def_readonly("oya", &Table::庄家)
.def_readonly("honba", &Table::n本场)
.def_readonly("riichibo", &Table::n立直棒)
仅在从get_phase/get_phase_mt中获取到GAME_OVER/GAME_OVER_MT值之后,可以获取结果
返回类型是Result类型,参见Result部分的介绍
0,1,2,3分别为每个玩家的主动阶段。 4,5,6,7为回复阶段(鸣牌/pass)。 8,9,10,11为抢杠。 12,13,14,15为抢暗杠。 16为GameOver
数据类型是SelfAction和ResponseAction的列表。参见这两个类的介绍。
参数类型为int,表示在允许执行的动作的列表中的第几个。
0,1,2,3分别为每个玩家的主动阶段。 4为回复阶段 5为GameOver
参数为int,表示第几号玩家。
数据类型是SelfAction和ResponseAction的列表。参见这两个类的介绍。
如果返回空列表,表示还没有轮到你做出选择。这个结果和should_i_make_selection_mt是一致的。
参数为int,表示第几号玩家。
返回bool,表示你是否可以开始执行动作了。
参数1:int,表示第几号玩家 参数2:int,表示在允许执行的动作的列表中的第几个。
(参见MahjongPy/MahjongPy.cpp)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("type", &Fulu::type)
.def_readonly("tiles", &Fulu::tiles)
.def_readonly("take", &Fulu::take)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("tile", &Tile::tile)
.def_readonly("red_dora", &Tile::red_dora)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("river", &River::river)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("action", &ResponseAction::action)
.def_readonly("correspond_tiles", &ResponseAction::correspond_tiles)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("action", &ResponseAction::action)
.def_readonly("correspond_tiles", &ResponseAction::correspond_tiles)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("double_riichi", &Player::double_riichi)
.def_readonly("riichi", &Player::riichi)
.def_readonly("menchin", &Player::门清)
.def_readonly("wind", &Player::wind)
.def_readonly("oya", &Player::亲家)
.def_readonly("furiten", &Player::振听)
.def_readonly("riichi_furiten", &Player::立直振听)
.def_readonly("score", &Player::score)
.def_readonly("hand", &Player::hand)
.def_readonly("fulus", &Player::副露s)
.def_readonly("river", &Player::river)
.def_readonly("ippatsu", &Player::一发)
.def_readonly("first_round", &Player::first_round)
(参见MahjongPy/MahjongPy.cpp)
(pybind11封装的接口形式下)
.value("RonAgari", ResultType::荣和终局)
.value("TsumoAgari", ResultType::自摸终局)
.value("IntervalRyuuKyoku", ResultType::中途流局)
.value("NoTileRyuuKyoku", ResultType::荒牌流局)
.value("RyuuKyokuMangan", ResultType::流局满贯)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("result_type", &Result::result_type)
.def_readonly("results", &Result::results)
.def_readonly("score", &Result::score)
包含一系列可以访问的属性
(pybind11封装的接口形式下)
.def_readonly("yakus", &CounterResult::yakus)
.def_readonly("fan", &CounterResult::fan)
.def_readonly("fu", &CounterResult::fu)
.def_readonly("score1", &CounterResult::score1)
.def_readonly("score2", &CounterResult::score2)
参数:Yaku的list 返回:GBK编码的bytes,需要进行decode
参数1: Table 参数2:int : 表示打印选项
打印选项的二进制位来表示需要打印的值,在选项中加上这个值即可 0:牌山(1) 1:玩家信息(2) 2:DORA信息(4) 3:立直棒(8) 4:本场棒(16) 5:亲家(32) 6:剩余牌量(64)
返回的是GBK编码的bytes,需要进行decode
例: 想打印所有的信息,可以使用TableToString(table, 999) 想打印牌山和玩家信息,可以使用TableToString(table, 1+2) 想打印玩家信息,DORA信息和立直棒信息,可以使用TableToString(table, 2+4+8)
参数:River类型 返回的是GBK编码的bytes,需要进行decode
参数:Tile类型 返回的是GBK编码的bytes,需要进行decode
参数:Fulu类型 返回的是GBK编码的bytes,需要进行decode
参数:SelfAction类型 返回的是GBK编码的bytes,需要进行decode
参数:ResponseAction类型 返回的是GBK编码的bytes,需要进行decode
参数:Player类型 返回的是GBK编码的bytes,需要进行decode
参数:Result类型 返回的是GBK编码的bytes,需要进行decode
参数:CounterResult类型 返回的是GBK编码的bytes,需要进行decode