前言:
前面几篇炸金花的文章, 里面涉及到了一个核心问题, 就是如何实现对手的牌力提升, 以及胜率的动态调整. 这个问题是EV模型, 以及基准AI里最重要的核心概念之一.
本文将尝试实现一个版本, 望抛砖引玉, 共同提高.
相关文章:
德州扑克AI--Programming Poker AI(译).
系列文章说来惭愧, 之前一直叫嚷着写德州AI, 不过可惜懒癌晚期, 一直没去实践, T_T. 相比而言,***简单很多, 也更偏重于运气和所谓的心理对抗.
系列文章:
1. 炸金花游戏的模型设计和牌力评估
2. 炸金花游戏的胜率预估
3. 基于EV(期望收益)的简单AI模型
4. 炸金花AI基准测试评估
5. 动态收敛预期胜率的一种思路
有趣的数学:
在讲动态胜率之前, 我们先了解一下炸金花背后的一些数学概念.
炸金花背后的各类票型分布:
牌型 | 高牌 | 对子 | 顺 | 金 | 顺金 | 豹子 |
组合数 | 16440 | 3744 | 720 | 1096 | 48 | 52 |
52张牌, 总共22100种组合, 一手牌有74.3891%的概率是高牌, 因此在单挑局中, 带个A的高牌也是不小的牌, 不要轻易丢掉, ^_^.
而从出现分布上来, 顺金(48) > 豹子(52) > 顺(720) > 金(1096) > 对子(3744) > 高牌(16440), 其实牌力按这个顺序其实更合理, 不过规则就是规则, 还是尊重历史吧.
模型思路:
一副牌的炸金花, 共有22100种组合, 对这些组合我们按牌力大小进行排序(从小到大), 最后构建为一个牌力数组.
每个玩家都有一个牌力值(strength), 默认为0. 玩家的牌力随机分布在牌力数组的[strength, 22100]之间.
根据玩家的反应, 按规则提升其牌力值(strenth), 然后再利用蒙特卡洛算法重新计算其AI手牌的胜率p.
1. 构建牌型组合(初始化)
def init_cards_combination():
"""
***手牌生成器
:return:
"""
arr_ranks = []
# 生成52张牌
cards = [Card(s + r) for s in "HDSC" for r in "A23456789TJQK"]
card_len = len(cards)
# 三层循环, 枚举22110种组合
for i in range(card_len):
for j in range(i + 1, card_len):
for k in range(j + 1, card_len):
hand = [cards[i], cards[j], cards[k]]
arr_ranks.append({
# 牌力值计算
"hand_value": ThreeCardEvaluator.evaluate(hand),
# 手牌组合保存
"cards": hand
})
# 根据牌力值, 进行从小到大的排序
return sorted(arr_ranks, key=lambda item: item["hand_value"])
2. 改造胜率算法
之前的胜率算法是考虑去重的, 为了简化我们不考虑手牌重复的问题, 如果两者的胜率接近, 可以认为等价.
class ThreeCardWinRate(object):
# 初始化牌组合
_g_ranks = init_cards_combination()
@staticmethod
def win_prop_dy(hand, players=[], si