前言:
好久没写博客了, 今天来补上一篇, 是关于炸金花游戏模型的设计和牌力评估. 其核心思想和之前谈到过的德州模型很像, 本文也想为炸金花游戏这个系列开个头, 希望后面能写写AI相关的文章.
相关文章:
德州扑克AI--Programming Poker AI(译).
系列文章说来惭愧, 之前一直叫嚷着写德州AI, 不过可惜懒癌晚期, 一直没去实践, T_T. 相比而言, 炸金花简单很多, 也更偏重于运气和所谓的心理对抗.
系列文章:
1. 炸金花游戏的模型设计和牌力评估
2. 炸金花游戏的胜率预估
3. 基于EV(期望收益)的简单AI模型
4. 炸金花AI基准测试评估
5. 动态收敛预期胜率的一种思路
游戏规则简介:
炸金花是每个参与玩家, 手握三张底牌, 通过跟牌/加注/PK等操作, 最后决出最后胜利者的游戏. 它和德州不一样, 它没有公共牌这个变量, 在发牌就决定了牌力大小, 波动也小, 最后的结果取决于玩家的心理对抗和策略了.
回归主题, 炸金花的牌力大小, 按如下规则来定义:
豹子(炸弹) > 顺金(同花顺) > 金(同花) > 顺子 > 对子 > 高牌
这边要特别说明下, 如果按照出现概率来评定牌力大小, 出现豹子(炸弹)的概率>顺金的概率, 即顺金理应比豹子大, 但炸金花约定, 豹子>顺金, 这算特例(需要尊重).
模型设计:
和德州一样, 我们这边把三张手牌映射为一个可比较的整数. 其牌力大小和整数数值成正比.
先定义牌型:
# 高high HIGH_TYPE = 0 # 对子 PAIR_TYPE = 1 << 12 # 顺子 STRAIGHT_TYPE = 2 << 12 # 同花(金) FLUSH_TYPE = 3 << 12 # 同花顺 STRAIGHT_FLUSH_TYPE = 4 << 12 # 豹子 LEOPARD_TYPE = 5 << 12
牌力值其由4个半字节(4 * 4 = 16位)组成, 其最高半字节为牌型, 后续三个半字节为该牌型下, 最大的手牌值填充.
1. 牌型为豹子, 第二高半字节为豹子手牌数值, 三/四半字节缺省为0, 如(HA, DA, SA) => [5, 14, 0, 0].
2. 牌型为顺金, 第二高半字节为顺子中最大的数值, 三/四半字节缺省为0, 如(HA, HK, HQ) => [4, 14, 0, 0].
3. 牌型为金, 二到四半字节, 依次存放排序后的手牌值, 如(HA, HK, HT) => [3, 14, 13, 10].
4. 牌型为顺, 第二高半字节为顺子中最大的手牌数值, 三/四半字节缺省为0, 如(HA ,HK, SQ) => [2, 14, 0, 0].
5. 牌型为对子, 第二高半字节为对子手牌, 三半字节为剩下的单牌, 四半字节缺省为0, 如(H9, D9, ST) => [1, 9, 10, 0].
6. 牌型为高牌, 二到四半字节, 依次存放排序后的手牌值, 如(H9, DA, ST) => [0, 14, 10, 9].
核心代码:
定义常量:
# !/usr/bin/env python # -*- coding:utf-8 -*- import sys reload(sys) sys.setdefaultencoding("utf-8") CARD_CONST = { "A": 14, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "T": 10, "J": 11, "Q": 12, "K": 13 } class Card(object): """ 牌的花色+牌值 """ def __init__(self, val): self.suit = val[0] self.rank = val[1] self.value = CARD_CONST[val[1]] def __str__(self): return "%s%s" % (self.suit, self.rank)
核心评估函数:
# 核心思路和德州一致, 把牌力映射为一个整数 # 牌力组成: 4个半字节(4位), 第一个半字节为牌型, 后三个半字节为牌型下最大的牌值 # 牌型, 0: 单张, 1: 对子, 2: 顺子, 3: 金, 4: 顺金, 5: 豹子 # 高high HIGH_TYPE = 0 # 对子 PAIR_TYPE = 1 << 12 # 顺子 STRAIGHT_TYPE = 2 << 12 # 同花(金) FLUSH_TYPE = 3 << 12 # 同花顺 STRAIGHT_FLUSH_TYPE = 4 << 12 # 豹子 LEOPARD_TYPE = 5 << 12 class ThreeCardEvaluator(object): """ 工具类 """ @staticmethod def evaluate(cards): if not isinstance(cards, list): return -1 if len(cards) != 3: return -1 vals = [card.value for card in cards] # 默认是从小到大排序 vals.sort() # 豹子检测 leopard_res, leopard_val = ThreeCardEvaluator.__leopard(cards, vals) if leopard_res: return LEOPARD_TYPE + (vals[0] << 8) # 同花检测 flush_res, flush_list = ThreeCardEvaluator.__flush(cards, vals) # 顺子检测 straight_res, straight_val = ThreeCardEvaluator.__straight(cards, vals) if flush_res and straight_res: return STRAIGHT_FLUSH_TYPE + (straight_val << 8) if flush_res: return FLUSH_TYPE + (flush_list[2] << 8) + (flush_list[1] << 4) + flush_list[2] if straight_res: return STRAIGHT_TYPE + (straight_val << 8) # 对子检测 pair_res, pair_list = ThreeCardEvaluator.__pairs(cards, vals) if pair_res: return PAIR_TYPE + (pair_list[0] << 8) + (pair_list[1] << 4) # 剩下的高high return HIGH_TYPE + (vals[2] << 8) + (vals[1] << 4) + vals[2] @staticmethod def __leopard(cards, vals): if cards[0].rank == cards[1].rank and cards[1].rank == cards[2].rank: return True, cards[0].value return False, 0 @staticmethod def __flush(cards, vals): if cards[0].suit == cards[1].suit and cards[1].suit == cards[2].suit: return True, vals return False, [] @staticmethod def __straight(cards, vals): # 顺子按序递增 if vals[0] + 1 == vals[1] and vals[1] + 1 == vals[2]: return True, vals[2] # 处理特殊的牌型, A23 if vals[0] == 2 and vals[1] == 3 and vals[2] == 14: return True, 3 return False, 0 @staticmethod def __pairs(cards, vals): if vals[0] == vals[1]: return True, [vals[0], vals[2]] if vals[1] == vals[2]: return True, [vals[1], vals[0]] return False, []
测试集:
编写一些case
if __name__ == "__main__": card_cases = [ [Card('HA'), Card('SA'), Card('DA')], # 豹子 [Card('HA'), Card('HK'), Card('HQ')], # 顺金 [Card('HA'), Card('HK'), Card('HT')], # 金 [Card('HA'), Card('HK'), Card('SQ')], # 顺子 [Card('H9'), Card('D9'), Card('ST')], # 对子 [Card('H9'), Card('DA'), Card('ST')] # 高牌 ] for case in card_cases: card = ', '.join([str(_) for _ in case]) hand_value = ThreeCardEvaluator.evaluate(case) print "[{}] = {}".format(card, hand_value)
测试的输出结果:
[HA, SA, DA] = 24064 [HA, HK, HQ] = 19968 [HA, HK, HT] = 16094 [HA, HK, SQ] = 11776 [H9, D9, ST] = 6560 [H9, DA, ST] = 3758
总结:
总的来说, 炸金花的核心模型和牌力映射比德州简单多了, 因为其没有组合优化的问题, 所以比较直接暴力. 后续的文章, 希望自己写写AI方面的想法, ^_^.
对待博彩游戏, 希望大家娱乐心态行娱乐之事, 切勿赌博, ^_^.