Skip to content

Latest commit

 

History

History
254 lines (184 loc) · 7.21 KB

读我.md

File metadata and controls

254 lines (184 loc) · 7.21 KB

日本麻将

简介

本项目包含一个用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.9%以上的牌局中表现一致。剩下的牌局中主要是分数计算上,因为双倍役满/翻dora时机/包牌等导致不一致。

测试只能保证操作能回放,不能保证是否出现错误的操作待选项(例如无法ron的情况仍然有ron的选项)。这一点只能通过大量实战进行测试。

安装

git clone https://github.com/Agony5757/mahjong/
cd mahjong
sudo apt-get upgrade
sudo apt-get update
sudo apt-get install -y clang cmake
python setup.py install

Usage(C++)

流程

初始化
循环 
{
	获取状态(get_phase), 判断: 
	case 1 (SelfAction状态)
	    拥有3n+2张牌 允许: 出牌(立直), 自摸, 暗杠, 加杠, 九种九牌
		
	case 2 (ResponseAction状态)
	    拥有3n+1张牌 允许: 吃, 碰, 杠, 荣, 抢杠, 抢暗杠

	case 3 (GameOver状态:流局/荣/自摸……)
		退出循环

	执行操作(make_selection)
}
获取结果:役/分数/连庄/本场/供托……

Example

#include <iostream>
#include "Table.h"
int main()
{
	Table t;
	t.set_write_log(true);
	t.game_init();
	try {
		while (t.get_phase() != Table::GAME_OVER)
		{
			fmt::print("{}\n", t.to_string());
			fmt::print("{} is making selection.\n", t.who_make_selection());
			if (t.get_phase() < 4)
			{
				fmt::print("SelfAction phase.\n");

				auto& actions = t.get_self_actions();
				/* inspect all available actions here */

				int selection;
				/* replace this with your selection method */
				t.make_selection(selection);
			}
			else
			{
				fmt::print("Response Action phase.\n");

				auto& actions = t.get_response_actions();
				/* inspect all available (response) actions here */

				int selection;
				/* replace this with your selection method */
				t.make_selection(selection);
			}
		}
		fmt::print("Result: \n{}", t.get_result().to_string());
	}
	catch (std::exception& e) {
		// output replay & debug codes
		t.print_debug_replay();
	}
	return 0;
}

接口与数据

牌 Tile/BaseTile

Tile: 牌对象,保证唯一性,在初始化阶段同时初始化,理论生命周期等同于Table - BaseTile:牌枚举,详见Tile.h - id: 和天凤0~135编码一致的编码 - red_dora: 红宝牌标记

Tile*: 标记一个牌,可以被任意复制,移动。不保证唯一性。

BaseTile:见Tile

动作 Action/BaseAction/SelfAction/ResponseAction

BaseAction:动作的枚举,包括所有可以主动执行的操作(吃碰杠立出胡etc),不含“摸牌”,因为摸牌是被动执行的

SelfAction:自身动作的结构体(和ResponseAction结构相同) ResponseAction:回复动作的结构体 - BaseAction:见BaseAction - correspond_tiles:vector<Tile*> 表示这个动作关联的牌型(sorted),详见Action.h内注释

get_action_index:提供一种可以从vector<SelfAction/ResponseAction>中,根据BaseAction和Tile*/BaseTile寻找动作的方式

玩家 Player

包括一系列玩家自身持有的状态和数据

- riichi/double_riichi :立直/双立直
- 门清
- wind:自风
- 亲
- 振听(同巡/舍牌/立直)
- score:点
- hand:手牌
- river:牌河 : 见[玩家.牌河]
- 副露s:副露 : 见[玩家.副露]
- 听牌
- 一发标记
- 第一巡标记

玩家.牌河 River

RiverTile:所有牌河中的牌(弃牌,not include 暗杠/加杠)

- tile*:对应的牌
- number:是全局第几个弃牌(所有人公用一套id)
- remain:表示这张牌是否被鸣走(尽管被鸣走,牌河中也会存在一个复制)
- fromhand:手切/摸切

牌被打出去时,会立即加入牌河中,如果被鸣走,则remain=false

玩家.副露 Fulu

Type: 属于的类型(Chi, Pon,大明杠,加杠,暗杠)

tiles:vector<Tile*> 对应的牌(加杠之后会把Pon->加杠)

take:对于Chi,Pon,大明杠,加杠,第几张牌是横过来的(拿的别人的牌)

桌子 Table

Table::init_game_from_metadata

参数类型: 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"

Table::get_info 获取牌桌对象

可以进一步通过访问成员来对数据进行获取,包括:

(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立直棒)

Table::get_result 获取游戏结果

仅在从get_phase/get_phase_mt中获取到GAME_OVER/GAME_OVER_MT值之后,可以获取结果

返回类型是Result类型,参见Result部分的介绍

Table单线程版本

Table::get_phase 获取阶段

0,1,2,3分别为每个玩家的主动阶段。 4,5,6,7为回复阶段(鸣牌/pass)。 8,9,10,11为抢杠。 12,13,14,15为抢暗杠。 16为GameOver

Table::get_self_action/get_response_action 获取允许执行的动作

数据类型是SelfAction和ResponseAction的列表。参见这两个类的介绍。

Table::make_selection 进行选择

参数类型为int,表示在允许执行的动作的列表中的第几个。

Yaku枚举

(参见MahjongPy/MahjongPy.cpp和Mahjong/Yaku.h)

ResultType枚举

(pybind11封装的接口形式下)
.value("RonAgari", ResultType::荣和终局)
.value("TsumoAgari", ResultType::自摸终局)
.value("IntervalRyuuKyoku", ResultType::中途流局)
.value("NoTileRyuuKyoku", ResultType::荒牌流局)
.value("RyuuKyokuMangan", ResultType::流局满贯)

Result类型

包含一系列可以访问的属性

(pybind11封装的接口形式下)
.def_readonly("result_type", &Result::result_type)
.def_readonly("results", &Result::results)
.def_readonly("score", &Result::score)

CounterResult类型

包含一系列可以访问的属性

(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)