• 炸金花游戏(2)--炸金花游戏的胜率预估


    前言:
      我也是突然心血来潮, 想写写炸金花这类游戏的AI实现. 本文算是这一系列的第二篇, 主要写炸金花的胜率预估, 主要基于蒙特卡罗的思想, 胜率是炸金花AI的核心决策数据, ^_^. 

    相关文章:
      德州扑克AI--Programming Poker AI(译)
      系列文章说来惭愧, 之前一直叫嚷着写德州AI, 不过可惜懒癌晚期, 一直没去实践, T_T. 相比而言, 炸金花简单很多, 也更偏重于运气和所谓的心理对抗.
      系列文章:
      1. 炸金花游戏的模型设计和牌力评估 
      2. 炸金花游戏的胜率预估 
      3. 基于EV(期望收益)的简单AI模型
      4. 炸金花AI基准测试评估
      5. 动态收敛预期胜率的一种思路

    蒙特卡罗(Monte Carlo):
      该算法属于模拟统计, 通过大量的随机模拟, 来达到/接近精确解的方法, 简单有效.
      它的一个最有名的例子, 就是模拟求解PI(圆周率), 在2*2的正方形中区域中, 随机生成大量的点, 最后PI满足如下公式:

    圆面积/正方形面=圆内覆盖的点数/全部点=PI/4

      
      这边不再具体阐述了, 具体可以参考博文: 蒙特卡罗(Monte Carlo)方法计算圆周率π 

    胜率预估:
      手牌胜率预估, 我们假定一副牌(52张), 玩家数N(2~6)之间变化, 在经历足够多的模拟随机发牌后, 手牌的胜率趋于真实值.
      伪代码如下(炸金花没有平局, 这里把牌力相等, 认为输):

    	# 假定随即模拟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

       由此可见, 拿到顺以上的牌, 胜率相当的高, 而且随人数变化小. 拿到对子也是不错的牌, 需要根据对子本身的大小和参与人数来做一个合理的评估.

    真实代码:
      贴一下代码:

    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, []

      测试代码:

    # !/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的编写, ^_^, 希望自己能坚持.

      

  • 相关阅读:
    python接口之request测试:以json格式发送post请求,.json方法,查看响应结果的情况
    python字典转化成json格式。JSONEncoder和JSONDecoder两个类来实现Json字符串和dict类型数据的互相转换
    webservice接口测试wsdl,参数是xml格式。python,入参转化成str,返回值转化成dict调用
    jenkins配置自动发送邮件,抄送
    让 Python 的1、数据库查询返回字典记录--- 2、利用zip函数将两个列表(list)组成字典(dict)
    如何从日期对象python获取以毫秒(秒后3位小数)为单位的时间值?
    Python之测试webservice接口
    eclipese pyDEV安装----可以直接运行python文件
    jenkins安装与配置---windows系统,war直接运行
    python pip 安装第三方库 mysql模块步骤--pip install mysql-connector-python
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/10300235.html
Copyright © 2020-2023  润新知