DouZero分析
1.DouZero介绍
DouZero项目地址:https://github.com/kwai/DouZero
在线Demo地址:https://www.douzero.org/
原理我就不过多说明,有兴趣的可以查看论文:https://arxiv.org/abs/2106.06135
运行程序,评估胜率只需三步:进入项目目录,
1 | pip3 install -r requirements.txt # 安装依赖 |
运行结果是以胜率的形式展现。
关于如何训练以及更多参数设置,请查看DouZero项目的说明文档。
当然现在这样对实际应用是没有直接帮助的,所以要改造一下,利用训练好的AI来帮我们出牌。
2.DouZero源码分析
项目结构十分清晰,douzero
目录包含主要代码文件,baselines
目录放置预训练模型。根目录下的 evaluate.py
为入口,拿到参数后传递到 douzero/evaluation/simulation.py
中的 evaluate()
函数,再将数据分配给多个进程调用 mp_simulate()
函数。
mp_simulate()
函数中,players为三个生成的AI,代表斗地主中的三个角色。GameEnv()
类表示游戏环境,控制一局游戏流程的进行与结束。
看到这里,整个项目就清晰了,可以着手按照需求定制AI了。
DouZero定制
首先,三个AI互斗肯定是不可行的,我们只需要一个,并且代表了玩家的角色。只要给这个AI输入开局时我的手牌和三张底牌,并且告诉它谁是地主,再输入每轮中其他两人的出牌,那么AI就能够给出最优出牌决策。
对于生成AI,可以控制只生成玩家角色对应的AI。开局时获取玩家的位置(地主上家、地主、地主下家),并用
0, 1, 2
分别表示。对于获取手牌等信息,
DouZero
项目中有对扑克牌进行转换。注意到在DouZero
与rlcard-showdown
两个项目中,这种对应关系有些许差异,这是因为将10
用T
来代替就可以将扑克牌用一串字符串来表示,便于参数传递。因此本项目也采取这种做法。python
1
2
3
4
5
6
7
8
9
10
11
12
13
14# DouZero
EnvCard2RealCard = {3: '3', 4: '4', 5: '5', 6: '6', 7: '7',
8: '8', 9: '9', 10: '10', 11: 'J', 12: 'Q',
13: 'K', 14: 'A', 17: '2', 20: 'X', 30: 'D'}
RealCard2EnvCard = {'3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
'8': 8, '9': 9, '10': 10, 'J': 11, 'Q': 12,
'K': 13, 'A': 14, '2': 17, 'X': 20, 'D': 30}
# rlcard-showdown
EnvCard2RealCard = {3: '3', 4: '4', 5: '5', 6: '6', 7: '7',
8: '8', 9: '9', 10: 'T', 11: 'J', 12: 'Q',
13: 'K', 14: 'A', 17: '2', 20: 'X', 30: 'D'}
RealCard2EnvCard = {'3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
'8': 8, '9': 9, 'T': 10, 'J': 11, 'Q': 12,
'K': 13, 'A': 14, '2': 17, 'X': 20, 'D': 30}对于预测胜率,
DouZero
项目中并未给出,但是在rlcard-showdown
中能看到该参数,由deep.py
文件中DeepAgent()
类的act()
函数计算得到。python
1
best_action_confidence = y_pred[best_action_index]
感谢Couwisdet指点,变量
y_pred
是预测的所有合法动作的Q值,取其中最大的一个对应的策略即为最优策略。对于WP
模型,取值在[-1, 1]
,可以按比例换算成胜率,如下:1
2
3
4win_rates = {}
win_rate = max(best_action_confidence, -1)
win_rate = min(win_rate, 1)
win_rate = str(round(float((win_rate + 1) / 2), 4))
控制台版本
1.用法
- 这个版本仅仅通过控制台进行交互。
- 开局的时候玩家要把自己的手牌,地主的位置,三张底牌手动输入进去。手牌输入按照
333456789TJQKA2XD
的形式,然后输入玩家的角色:0-地主上家, 1-地主, 2-地主下家
,最后输入三张底牌,例如2XD
。 - 然后就开始轮流出牌,在其它两个人出牌后都需要将对应的牌输入,用于AI决策,这样在轮到玩家出牌时,AI就能告知最优策略。
- 不出的话,直接
Enter
即可,会返回空列表。同理,当AI返回空列表时表示不出。 - 这么做的优点就是,逻辑简单,并且工作量少哇。主要是想第一时间测试AI在实战斗地主中的实力。
- 缺点很明显,十分拼手速,并且没有撤销机制与输入检测,如果着急输错了,那么程序就会崩溃。
2.分析
- 去除了不必要的文件以及参数,根目录只保留
start.py
用于启动。 evaluate()
函数要求输入玩家手牌、玩家角色、三张底牌。由于原先评估代码中太多处涉及对其他角色手牌的操作如删除和检测,为避免大量改动原项目代码,需要为其他两个角色分配手牌。- 这里就将整幅牌减去玩家手牌,再按角色分配给它们。为避免其他玩家出牌时,在其手牌中找不到该牌,将在其他玩家手牌中删除刚出的牌改为在其他玩家手牌中删除与刚出的牌等量的牌。这么做的依据是:AI决策时只需要考虑其他角色手牌数量而不需要知道具体是什么牌。
- 创建AI时根据玩家角色来创建,并且在
env.step()
中,调用act()
函数获取AI决策前判断一下当前是否为玩家出牌,是则通过AI决策,否则由玩家输入。 - 当任意角色手牌数量为0时则代表游戏结束。
俗话说得好,一个成熟的AI,是应该能够自己看牌出牌的。于是我又改出了pyqt5版本,实现部分自动化操作。
pyqt5版本
1.用法
- 打开欢乐斗地主,需要窗口模式下最大化运行,并且要求屏幕分辨率
1920x1080
,程序窗口需要移至右下角,不能遮挡手牌、地主标志、底牌、历史出牌
- 在抢地主结束后,手牌出现、底牌出现、地主角色确认,点击开始,耗时几秒完成扑克牌的识别。
- 窗口内显示识别结果,地主角色使用淡红色标出。识别完成自动开始记录出牌。
- 观察AI建议的出牌,在游戏中手动选择并打出。游戏结束后会弹出对话框提示输赢。
- 识别错误或无反应导致错过出牌,可通过结束按钮停止本局。至于游戏,就自己手动打完吧。
2.分析
- 利用
pyqt5
设计一个简单的窗体用于展示出牌信息
- 使用
pyautogui
来实现自动化。借鉴了cardRecorder项目的部分代码以及模板图片,用于识别扑克牌。
- 通过
pyautogui.locateAll()
函数将所有扑克牌的模板图片与屏幕特定区域的截图进行对比,获取手牌、底牌与出牌。 - 由于出牌区域显示的牌较小,因此使用一大一小两套模板。而底牌则更小,通过
resize()
函数将截图区域放大,在进行模板比对。 pyautogui.locateOnScreen
用于白块检测与“不出”检测,“地主”检测,用来自动识别出牌流程。这里仍有一点Bug,例如王炸时出牌特效时间较长,有一定几率导致只能识别出一个王。但是缩短等待时间又会导致两人连续“不出”时无法自动切换到下一个人出牌的状态。- 另外,由于像素级操作过于局限,并且识别过程容易出错,有小几率Bug,因此放弃完全自动化的想法,即通过
pyautogui
来点击屏幕自动出牌。
总结
- 至少现在,这个AI可以辅助我们出牌了,虽然AI的思路可能跟我们完全不一样。
- 要注意到,斗地主是个运气成分很强的游戏,牌好的时候,闭着眼睛打也能赢。牌烂的时候,高手也救不了(例如被“春天”)。所以只有在自己的牌不好也不烂的时候,AI的优势才能被明显体现出来。
- 综合来看,在发完牌后,自己没有把握打赢又不至于直接认输的情况下,用AI辅助是不错的选择。毕竟,它天生自带记牌器,能够合理配合,做农民的时候是个不错的队友。
- github项目地址:DouZero_For_HappyDouDiZhu
- 演示视频链接:知乎视频
存在问题
- 有时候程序中自己的牌堆显示会与实际手牌不同
- 使用AI的胜率一般,不知道大家的体验如何