炸金花遊戲(2)--炸金花遊戲的勝率預估

 

前言:
  我也是忽然心血來潮, 想寫寫炸金花這類遊戲的AI實現. 本文算是這一系列的第二篇, 主要寫炸金花的勝率預估, 主要基於蒙特卡羅的思想, 勝率是炸金花AI的核心決策數據, ^_^. html

 

相關文章:
  德州撲克AI--Programming Poker AI(譯)
  系列文章說來慚愧, 以前一直叫嚷着寫德州AI, 不過惋惜懶癌晚期, 一直沒去實踐, T_T. 相比而言, 炸金花簡單不少, 也更偏重於運氣和所謂的心理對抗.
  系列文章:
  1. 炸金花遊戲的模型設計和牌力評估 
  2. 炸金花遊戲的勝率預估 
  3. 基於EV(指望收益)的簡單AI模型
  4. 炸金花AI基準測試評估
  5. 動態收斂預期勝率的一種思路python

 

蒙特卡羅(Monte Carlo):
  該算法屬於模擬統計, 經過大量的隨機模擬, 來達到/接近精確解的方法, 簡單有效.
  它的一個最有名的例子, 就是模擬求解PI(圓周率), 在2*2的正方形中區域中, 隨機生成大量的點, 最後PI知足以下公式:算法

圓面積/正方形面=圓內覆蓋的點數/所有點=PI/4

  
  這邊再也不具體闡述了, 具體能夠參考博文: 蒙特卡羅(Monte Carlo)方法計算圓周率π app

 

勝率預估:
  手牌勝率預估, 咱們假定一副牌(52張), 玩家數N(2~6)之間變化, 在經歷足夠多的模擬隨機發牌後, 手牌的勝率趨於真實值.
  僞代碼以下(炸金花沒有平局, 這裏把牌力相等, 認爲輸):dom

	# 假定隨即模擬10000局, 其餘玩家n個
	sim_n = 10000
	player_n = 其餘玩家數
	hand_cards = 玩家本身的手牌	

	# 玩家勝利的次數
	win_n = 0	
	for i in range(sim_n):
		players <- 隨機給n個玩家發牌
		if 玩家的手牌 > 全部其餘玩家的手牌:
			win_n += 1

	# 此次機率值, 就接近真實的勝率
	return win_n / sim_n 

  是否是以爲很是的簡單, ^_^.工具

 

各種牌型的勝率統計:
  這邊選擇了一些典型的牌型, 看看它在不一樣的對局用戶數下, 勝率的變化:測試

牌型/幾人桌 兩人桌 三人桌 四人桌 五人桌 六人桌
[HK, SK, DK] 豹子  0.9997  0.9997  0.9995  0.9989 0.9988
[HA, HK, HQ] 同花順  0.997 0.9949   0.9926  0.9894 0.989
[HA, HK, HT] 金  0.9951  0.9869  0.9805  0.976 0.9668
[HA, HK, SQ] 順  0.9425  0.8928  0.8427  0.8006 0.7506
[H9, D9, ST] 對子  0.847  0.7113  0.605  0.5197 0.4335
[H9, DA, ST] 高牌  0.6644  0.4423  0.292  0.1901 0.1245

   因而可知, 拿到順以上的牌, 勝率至關的高, 並且隨人數變化小. 拿到對子也是不錯的牌, 須要根據對子自己的大小和參與人數來作一個合理的評估.ui

 

真實代碼:
  貼一下代碼:lua

import random
import time

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)


class Shoe(object):

    def __init__(self, deck_num=1):
        """
        :param deck_num: 幾副牌, 默認爲1副牌
        """
        self.deck_num = deck_num
        self.cards = [Card(s+c) for s in "HDSC" for c in "A23456789TJQK"] * self.deck_num
        self.idx = 0

    def reshuffle(self):
        # 打散牌
        self.idx = 0
        random.shuffle(self.cards)

    def deal(self, exc_arr=[]):
        """
        :param exc_arr: 發牌須要過濾掉的牌, 避免重複
        :return:
        """
        while self.idx < len(self.cards):
            card = self.cards[self.idx]
            self.idx = self.idx + 1
            if str(card) in exc_arr:
                continue
            return card
        return None


# 核心思路和德州一致, 把牌力映射爲一個整數
# 牌力組成: 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 win_prop(cards, n=2, sim_n=10000):
        """
        勝率計算
        :param cards:
        :param n: 玩家數(包含玩家本身)
        :param sim_n: 模擬的輪數, 輪數越多越接近真實值
        :return:
        """
        random.seed(time.time())
        shoe = Shoe(deck_num=1)
        exc_arr = [str(_) for _ in cards]
        owner_hand_value = ThreeCardEvaluator.evaluate(cards)

        # 勝利次數
        win_n = 0

        for _ in xrange(sim_n):
            # 打散牌譜
            shoe.reshuffle()

            player_cards = []
            for j in xrange(n - 1):
                player_cards.append([shoe.deal(exc_arr=exc_arr) for _ in range(3)])

            # 統計其餘玩家中最大的手牌值
            max_hand_value = max([ThreeCardEvaluator.evaluate(_) for _ in player_cards])
            if owner_hand_value > max_hand_value:
                win_n += 1

        # 大量模擬後的勝率
        return win_n * 1.0 / sim_n

    @staticmethod
    def evaluate(cards):
        """
        牌力值計算
        :param cards: 三張牌構成的手牌
        :return:
        """
        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, []

  測試代碼:.net

# !/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

if __name__ == "__main__":

    card_cases = [
        [Card('HK'), Card('SK'), Card('DK')],      # 豹子
        [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:
        p = ThreeCardEvaluator.win_prop(case, n=6, sim_n=10000)
        card = ', '.join([str(_) for _ in case])
        print "[{}] = {}".format(card, p)

  測試結果:

[HK, SK, DK] = 0.9988
[HA, HK, HQ] = 0.989
[HA, HK, HT] = 0.9668
[HA, HK, SQ] = 0.7506
[H9, D9, ST] = 0.4335
[H9, DA, ST] = 0.1245

 

總結:   本文是炸金花系列的第二篇, 後續要講講炸金花AI的編寫, ^_^, 但願本身能堅持.

相關文章
相關標籤/搜索