- Author: Sanhe Hu
- Date: 2015-08-01
- Copy Right: Sanhe Hu 2015, all copyright reserved
在基本满足实时性的前提下, 也就是10秒以内, 能给出在当前局面下, 玩家输牌的概率。 具体来说, 有下面三种情况:
- 玩家有2张手牌, 桌面上有3张公牌, 在当前存活n名敌对玩家时, 玩家输牌的概率。
- 玩家有2张手牌, 桌面上有4张公牌, 在当前存活n名敌对玩家时, 玩家输牌的概率。
- 玩家有2张手牌, 桌面上有5张公牌, 在当前存活n名敌对玩家时, 玩家输牌的概率。
桌面上牌越多, 则不确定性越小。所以我们从情况3开始分析起:
当前已知的公牌和玩家的牌的情况有: C(52, 2) * C(50, 5) = 2,809,475,760种情况。 而之后对手从剩下的45张牌拿到两张手牌的情况有: C(45, 2) = 990, 那么一共就有2,809,475,760 * 990 = 2.8 * 10^12。 这么多的情况即使是用硬盘储存, 假设一对Key, value的所占的空间为: 32(key的整数t) + 32(概率的小数) = 8 bytes, 那么需要的硬盘空间是: 2.8 * 8 = 22.4 PB。 如果假设计算每一种可能性只用0.001秒, 那么计算完所有的情况需要2.8 * 10^9秒, 等于88年。 这还是最简单的情况。
在情况2, 情况1有着更高的不确定性, 所以说用传统的方法是无法将所有的可能性都计算出来的。
下面我们来介绍一些简单的算法知识, 为介绍最终的实时概率算法做铺垫。
对单张扑克牌的哈希有以下几点要求:
- 不同的牌hash值相同。
- 大一些的牌的hash值大。
我给出的算法是, 一张牌的哈希值等于: 4*rank + suit。
其中牌面2, 3, ..., Q, K, A的分值为2, 3, ..., 12, 13, 14。
而花色黑桃, 红桃, 梅花, 方片的分值分别为4, 3, 2, 1。
最大的牌是: 黑桃A = 14 * 4 + 4 = 60
多张牌的哈希用于快速的将手牌映射为一个整数, 这样可以唯一将手牌映射为整数, 也能从整数反过来解析出手牌。
在这里我使用位图(bitmap)作为多张牌的hash值。这是因为在德州扑克中手牌不可能重复, 所以是一个集合的概念。
多张牌哈希算法如下:
- 由最低位初始化一串全0的二进制数
- 对于手牌中的所有牌求hash值, 例如三张手牌: [12, 34, 51]
- 将从低到高的第x位置1, 计算完所有牌后得到的数就是多张牌的hash值。(有可能是一个大整数)
从52张牌随机选出5张牌, 有C(52, 5) = 2598960种组合。 计算出所有组合的分值(score)。计算出5张牌的hash值。那么我们可以得到一个大字典:
five_cards_score_dict = {5-bitmap: score} # dump this file to database and five_cards_score.pickle
这样我们就可以快速的获得每一种5张牌的分值了。
从52张牌随机选出7张牌, 有C(52, 7) = 种组合。 对于每种组合, 计算出所有牌组中最大的那组, 并求得其hash值, 然后利用5张牌缓存哈希表直接求得手牌大小分值。 在数据库中, 我们保存: {7-bitmap: 5-bitmap}。 而在本地文件大字典中我们保存: {7-bitmap: score}。这样我们可以直接通过7张牌的hash值, 查表获得其手牌大小。
此时玩家有2张手牌, 还有52 - 5 - 2 = 45
张牌未知。 那么对于任意一个对手, 他的手牌有C(45, 2) = 990
种可能。 我们只需要遍历所有可能的手牌, 和公牌合并, 计算出对手的分数。然后比较有多少种可能性可以击败玩家。 然后用总数除以990就是玩家输的概率。 注意, 这个概率是场面上只有一个对手时的概率。
board_5_cards # 5张公牌
rest_45_cards # 剩余的45张牌
my_score # 我当前的分值
counter = 0
for two_cards in itertools.combinations(rest_45_cards, 2):
enemy_all = board_5_cards + list(two_cards)
# 计算敌人手牌的分值
enemy_score = find_best_pocker_hand_score_using_cache(enemy_all)
if enemy_score > my_score:
counter += 1
lose_odd = counter/990
win_odd = 1 - lose_odd # 赢的概率
当此时场上还剩下N个对手时候, 此时玩家输的概率是:
lose_odd = 1 - win_odd ** N # 场上有N个玩家时输的概率
Note: 此算法在使用内存缓存的情况下, 平均耗时0.005秒
此时还有46张牌未知。 那么我们只需要遍历这46张牌, 然后与4张公牌凑成完整的5张公牌, 分别计算出每个情况下玩家赢的条件概率。求和之后除以46即是玩家的win_odd
同样使用下面公式求得在N个对手时, 玩家输的总概率:
lose_odd = 1 - win_odd ** N # 场上有N个玩家时输的概率
此时还有47张牌未知, 也就是说公牌还有C(47, 2) = 1081种可能性。 类似于Turn round, 对这1081种可能性遍历后用贝叶斯公式求得总概率, 即可得到玩家输的概率。