前言:
好久沒寫博客了, 今天來補上一篇, 是關於炸金花游戲模型的設計和牌力評估. 其核心思想和之前談到過的德州模型很像, 本文也想為炸金花游戲這個系列開個頭, 希望后面能寫寫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方面的想法, ^_^.
對待博彩游戲, 希望大家娛樂心態行娛樂之事, 切勿賭博, ^_^.
