这是我的硕士论文的一个项目。欢迎任何有兴趣制作强大的麻将人工智能的人扩展我的代理。有关所应用算法的更多详细信息以及使用此类算法的原因,请通过电子邮件与我联系。
如果您想开发自己的麻将代理,这个 Repo 也可以用作实时测试的框架(与真人玩家一起)。然后您就可以节省所有时间来为您的代理寻找最佳策略。此外,现在提供了一个开发库(游戏日志的抓取和预处理、shantin和获胜分数的计算等):https://github.com/erreurt/MahjongKit
下一次更新即将推出:
需要更好的特征工程决定是否使用随机森林(而不是条件规则)来调用融合/调用 Riichi。
正在进行中:使用 LSTM 训练等待瓷砖预测模型。
注意:如果您在同一 IP 地址下使用超过 4 个帐户并行测试您的机器人,您的 IP 地址将被 tenhou.net 禁止 24 小时。 (我不太清楚禁止玩家的具体规则,但这都是根据我的观察推断的。)
作者 | 汤建阳(托马斯) |
---|---|
电子邮件 | [email protected] |
麻将是一种信息不完全的四人策略游戏。开发智能麻将代理的主要挑战是复杂的游戏规则、巨大的搜索空间、多个对手和不完善的信息。一些现有的工作试图通过蒙特卡罗树模拟、监督学习的效用函数拟合或回归算法的对手模型来解决这些问题。尽管如此,智能麻将玩家的表现与最优秀的人类玩家仍然相差甚远。基于对麻将游戏的统计分析和人类专家知识,本工作提出了一种智能麻将代理。为了解决最先进的工作中的问题,本工作应用了启发式技术和通过采用多层感知器装袋实现的增强对手模型。实验表明,所提出的代理优于最先进的代理,并且所应用的对手模型对代理的性能具有显着的积极影响。此外,从实验中可以看出一些有趣的点,这对未来的工作非常有意义。
日本立直麻将的游戏规则请参考 https://en.wikipedia.org/wiki/ Japanese_Mahjong 。
所实现的客户端允许人们直接通过程序运行麻将代理,而不是在网络浏览器中执行此操作。在线玩日本立直麻将的网站是http://tenhou.net/
左边是日本立直麻将桌的典型场景。这张图片是为调试使用而实现的 GUI 的屏幕截图。
提议的 Mahjong 代理在 tenhou.net 上进行了测试。此次测试分为两种版本,一种带有防御模型,另一种则不带防御模型。原始游戏日志和中间游戏结果可以在我的另一个存储库中找到:https://github.com/erreurt/Experiments-result-of-mahjong-bot。实验是使用experiment_ai.py中的代理版本进行的。
有防御模型的版本进行了526场比赛,无防御模型的版本进行了532场比赛。这虽然没有两个相关作品那么多,但从智能体性能收敛行为图中可以看出,526 场游戏就足够了。
水上的扩展作品可以看作是目前英语文献中最好、最可靠的麻将代理人。下面是我的麻将代理与 Mizukami 的麻将代理的性能比较:
[1] | [2] | [3] | [4] | |
---|---|---|---|---|
玩过的游戏 | 第526章 | 第532章 | 2634 | 第1441章 |
第一名率 | 23.95% | 22.65% | 24.10% | 25.30% |
第二名率 | 26.62% | 25.92% | 28.10% | 24.80% |
第三名率 | 31.75% | 25.71% | 24.80% | 25.10% |
第四名率 | 17.68% | 25.71% | 23.00% | 24.80% |
胜率 | 24.68% | 26.50% | 24.50% | 25.60% |
损失率 | 13.92% | 20.21% | 13.10% | 14.80% |
固定水平 | 2.21 丹 | 0.77 丹 | 1.14 丹 | 1.04 丹 |
[1]我的麻将代理与防御模型
[2]我的麻将特工没有防御模型
[3] Mizukami 的扩展工作:Mizukami N.,Tsuruoka Y..构建基于蒙特卡罗模拟和对手模型的计算机麻将机。见:2015 年 IEEE 计算智能与游戏会议 (CIG),第 275-283 页。 IEEE(2015)
[4]水上等。等人。 :N. Mizukami、R. Nakahari、A. Ura、M. Miwa、Y. Tsuruoka 和 T. Chikayama。通过隔离多人方面的监督学习来实现四人计算机麻将程序。日本信息处理协会学报,卷。 55,没有。 11,第 1-11 页,2014 年,(日语)。
注意,在打麻将时,掉到第四名绝对是大忌,因为会降低等级点数。因此,一名选手跌入第四名的几率对其整体表现至关重要。由于第四名的比率较低,我的机器人具有更好的固定水平。
要运行 Mahjong 代理,必须指定一些配置。如以下 main.py 中的示例所示:
def run_example_ai ():
ai_module = importlib . import_module ( "agents.random_ai_example" )
ai_class = getattr ( ai_module , "RandomAI" )
ai_obj = ai_class () # [1]
player_module = importlib . import_module ( "client.mahjong_player" )
opponent_class = getattr ( player_module , "OpponentPlayer" ) # [2]
user = "ID696E3BCC-hLHNE8Wf" # [3]
user_name = "tst_tio" # [4]
game_type = '1' # [5]
logger_obj = Logger ( "log1" , user_name ) # [6]
connect_and_play ( ai_obj , opponent_class , user , user_name , '0' , game_type , logger_obj ) # play one game
def run_jianyang_ai ():
ai_module = importlib . import_module ( "agents.jianyang_ai" )
waiting_prediction_class = getattr ( ai_module , "EnsembleCLF" )
ensemble_clfs = waiting_prediction_class ()
ai_class = getattr ( ai_module , "MLAI" )
ai_obj = ai_class ( ensemble_clfs ) # [1]
opponent_class = getattr ( ai_module , "OppPlayer" ) # [2]
user = "ID696E3BCC-hLHNE8Wf" # [3]
user_name = "tst_tio" # [4]
game_type = '1' # [5]
logger_obj = Logger ( "log_jianyang_ai_1" , user_name ) # [6]
connect_and_play ( ai_obj , opponent_class , user , user_name , '0' , game_type , logger_obj )
AI实例:麻将代理的类实例。在这个存储库中提供了三个版本的麻将代理。第一个位于agents.random_ai_example.py中,这是一个演示类,用于向潜在开发人员展示如何实现他/她自己的代理。第二个是在agents.experiment_ai.py中,第4部分给出的实验结果是由这个AI生成的。第三个是最新的AI,位于agents.jianyang_ai.py中。
对手玩家类别:对手玩家的类别。可以使用client.mahjong_player中的默认类 OpponentPlayer 。如果由于额外的需要而扩展了 OpponentPlayer 类,则应将该变量设置为您相应的类。
用户ID :在tenhou.net注册后获得的令牌,其形式如示例所示。注意:请使用您自己的用户 ID。如果同一ID在不同的IP地址下使用次数过多,该帐户将被tenhou.net暂时封锁。
用户名:您在tenhou.net注册时创建的相应用户名。该变量仅用于识别您的测试日志。
游戏类型:游戏类型被编码为 8 位整数。以下是每个位的描述。
例如:
- Tenhou.net does not provide all possibility of the above specified combinations. Most online players play on configurations for example "1", "137", "193", "9"
记录器:初始化记录器需要两个参数。第一个是用户定义的记录器ID,以便开发人员可以自由命名他/她的测试历史记录。
指定所有这些配置后,只需将所有这些参数扔给connect_and_play()即可。那么是时候看你的麻将经纪人的表演了!!!
麻将机器人必须实现四个功能,如agents.ai_interface中的“interface”类所示。建议您的代理继承AIInterface。有关这些函数的更深入的解释和简单示例,请参阅agents.random_ai_example.py中的文档。
class AIInterface ( MainPlayer ):
def to_discard_tile ( self ):
raise NotImplementedError
def should_call_kan ( self , tile136 , from_opponent ):
raise NotImplementedError
def try_to_call_meld ( self , tile136 , might_call_chi ):
raise NotImplementedError
def can_call_reach ( self ):
raise NotImplementedError
to_discard_tile :基于有关游戏状态的所有可访问信息,此函数返回要丢弃的图块。返回值是 0-135 范围内的整数。麻将游戏共有136张牌,即34种牌,每种牌4张。在不同的场合,我们使用 34 格式(每个数字对应一种牌)或 136 格式(每个数字对应一种牌)。注意这里的返回应该是136形式。
should_call_kan :https://en.wikipedia.org/wiki/ Japanese_Mahjong#Making_melds_by_calling。此函数应决定代理是否应调用 kan(Quad) 融合。 tile136代表某个对手已经弃掉的牌,可以用来让智能体形成看牌组合。 from_opponent表示代理是否通过对手的出牌(手中的三张牌,对手出第四张)或自己的牌(手中的所有四张牌)形成kan 组合。
try_to_call_meld :https://en.wikipedia.org/wiki/ Japanese_Mahjong#Making_melds_by_calling。此函数决定代理是否应调用 Pon(Triplet)/Chi(Sequence) 融合。 tile136代表一些对手已经打掉的 136 式牌。 might_call_chi指示代理是否可以调用 Chi 融合,因为 Chi 融合只能在左侧座位上的对手弃牌的情况下调用。
can_call_reach :https://en.wikipedia.org/wiki/ Japanese_Mahjong#R%C4%ABchi。该函数决定特工是否应该认领立直。
当麻将代理类是AIInterface类的子类时,可以按照指定在代理类内部访问如下信息。
使用权 | 数据类型 | 可变的 | 描述 |
---|---|---|---|
自我瓷砖136 | 整数列表 | 是 | 136 形手牌 |
自手34 | 整数列表 | 氮 | 34 格式的手牌 (tile34 =tile136//4) |
自我丢弃136 | 整数列表 | 是 | 136-from 中代理的丢弃 |
自我丢弃34 | 整数列表 | 氮 | 34 格式的代理丢弃 |
自我融合136 | Meld 实例列表 | 是 | 代理的被调用的融合, client.mahjong_meld.py中 Meld 类的实例 |
self.total_melds34 | 整数列表的列表 | 氮 | 34 种形式的代理的调用组合 |
自我融合34 | 整数列表的列表 | 氮 | 被称为 pon/chow 的代理的 34 形式融合 |
自我pon34 | 整数列表的列表 | 氮 | 被称为 pon 的 34 形式的代理融合体 |
自我chow34 | 整数列表的列表 | 氮 | 被称为 34 形式的代理的 chow 融合 |
自我民看34 | 整数列表的列表 | 氮 | 被称为 34 种形式的代理的 minkan 融合 |
自我.ankan34 | 整数列表的列表 | 氮 | 被称为 34 种形式的代理的 ankan 融合 |
自己的名字 | 细绳 | 是 | 账户名称 |
自我水平 | 细绳 | 是 | 账户级别 |
自坐 | 整数 | 是 | 座席ID,座席始终为0 |
self.dealer_seat | 整数 | 是 | 庄家席位ID |
self.is_dealer | 布尔值 | 氮 | 代理商是否为经销商 |
self.reach_status | 布尔值 | 是 | 表示代理人是否已认领立直 |
self.just_reach() | 布尔值 | 氮 | 代理人是否刚刚声称立直 |
self.tmp_rank | 整数 | 是 | 代理在当前游戏中的排名 |
自我评分 | 整数 | 是 | 当前游戏中代理的得分 |
self.is_open_hand | 布尔值 | 氮 | 代理是否已经调用开放融合 |
self.turn_num | 整数 | 氮 | 当前匝数 |
self.player_wind | 整数 | 氮 | 玩家风是一种yaku |
self.round_wind | 整数 | 氮 | 圆风是屋久的一种 |
自我奖励荣誉 | 整数列表 | 是 | 所有有yaku的角色牌 |
通过调用self.game_table.get_player(i)可以访问对手类的实例,i 等于 1,2,3,表示对手对应的 id。
使用权 | 数据类型 | 可变的 | 描述 |
---|---|---|---|
.discard136 | 整数列表 | 是 | 136-from 中观察到的对手的弃牌 |
.discard34 | 整数列表 | 氮 | 观察对手的 34 式弃牌 |
.meld136 | Meld实例列表 | 是 | 观察到的对手的叫牌组合 |
.total_melds34 | 整数列表的列表 | 氮 | 观察到的对手的 34 式组合 |
.meld34 | 整数列表的列表 | 氮 | 观察对手的 34 式被叫 pon/chow 融合 |
.pon34 | 整数列表的列表 | 氮 | 所观察到的对手的 pon 融合以 34 式表示 |
.chow34 | 整数列表的列表 | 氮 | 观察到的对手的叫牌组合为 34 式 |
.minkan34 | 整数列表的列表 | 氮 | 观察到的对手的称为 34 式的 minkan 融合 |
.ankan34 | 整数列表的列表 | 氮 | 观察到的对手的称为 34 式的 ankan 融合 |
.safe_tiles | 整数列表 | 是 | 34 种形式的牌对代理来说绝对安全,即观察到的对手无法用这些牌获胜 |
。姓名 | 细绳 | 是 | 对手的名字 |
。等级 | 细绳 | 是 | 对手的水平 |
。座位 | 整数 | 是 | 观察对手的座位ID |
.dealer_seat | 整数 | 是 | 庄家席位ID |
.is_dealer | 布尔值 | 氮 | 观察到的对手是否是庄家 |
.reach_status | 布尔值 | 是 | 指示观察到的对手是否已认领立直 |
.just_reach() | 布尔值 | 氮 | 观察到的对手是否刚刚声称立直 |
.tmp_rank | 整数 | 是 | 当前游戏中观察到的对手的排名 |
。分数 | 整数 | 是 | 当前比赛中观察到的对手的得分 |
.is_open_hand | 布尔值 | 氮 | 观察到的对手是否已经叫了开放组合 |
.turn_num | 整数 | 氮 | 当前匝数 |
.player_wind | 整数 | 氮 | 玩家风是一种yaku |
.round_wind | 整数 | 氮 | 圆风是屋久的一种 |
.bonus_honors | 整数列表 | 是 | 所有有yaku的角色牌 |
要访问有关游戏桌的信息,可以调用self.game_table
使用权 | 数据类型 | 可变的 | 描述 |
---|---|---|---|
.bot | 代理类的实例 | 是 | 代理的类实例 |
.get_player(i) | 对手类的实例 | 是 | 对手的类实例,i=1,2,3 |
.dealer_seat | 整数 | 是 | 经销商席位ID |
.bonus_indicator | 整数列表 | 是 | 136 形式的奖金指标 |
.round_number | 整数 | 是 | 整数 |
.reach_sticks | 整数 | 是 | 桌子上有多少根立直棒。接下来获胜的玩家将获得这些立直棒的所有积分 |
.honba_sticks | 整数 | 是 | 桌子上有多少根本巴棍。如果获胜,玩家将根据本巴棒获得额外积分 |
.count_ramaining_tiles | 整数 | 是 | 剩余未揭开的瓷砖数量 |
.透露 | 整数列表 | 是 | 列表中的每个元素指示已显示该特定图块的数量(弃牌、开放组合、奖励指示器等) |
.round_win | 整数 | 氮 | 圆风是屋久的一种 |
.bonus_tiles | 整数列表 | 氮 | 奖励图块列表。手牌中每次出现奖励牌都算作一个yaku |
.last_discard | 整数 | 氮 | 对手最新出的牌,按照规则绝对安全 |
我的 Johannes Fürnkranz 教授(达姆施塔特工业大学知识工程小组)
我的导师 Tobias Joppen(达姆施塔特工业大学知识工程组)