Пример #1
0
 def test_player_stats(self):
     e = Engine(
         'CoinPoker',
         1,
         {
             1: {
                 'name': 'joe',
                 'balance': 1000,
                 'status': 1
             },
             2: {
                 'name': 'jane',
                 'balance': 1000,
                 'status': 1
             },
         },
         50,
         100,
         0,
     )
     e.available_actions()
     stats = ES.player_stats(e, e.s)
     assert 'actions' in stats
     assert len(stats['actions']) >= 4
     assert 0 < stats['hs'] < 100
Пример #2
0
    def __init__(self, site_name, button, players, sb, bb, ante=0, *args, **kwargs):
        logger.info(f'Engine site_name: {site_name}')
        logger.info(f'Engine button: {button}')
        logger.info(f'Engine players: {len(players)}')
        logger.info(f'Engine kwargs: {kwargs}')

        self.site_name = site_name
        self.button = button
        self.players = players
        self.sb_amt = sb
        self.bb_amt = bb
        self.ante = ante
        self.go_to_showdown = False
        self.mc = False

        if hasattr(kwargs, 'data'):
            self.data = kwargs['data']
        else:
            self.data = {s: {
                'status': 'in' if p.get('status') else 'out',
                'sitout': False,
                'hand': ['__', '__'] if p.get('status') else ['  ', '  '],
                'contrib': 0,
                'matched': 0,
                'preflop': [],
                'flop': [],
                'turn': [],
                'river': [],
                'showdown': [],  # for error at stats
                'is_SB': False,
                'is_BB': False,
            } for s, p in players.items()}
        self.vs = sum([1 if d['status'] == 'in' else 0 for d in self.data.values()])
        self.rivals = self.vs
        self.winner = None

        # leave empty: scraper compares length
        self.board = kwargs.get('board', [])
        self.pot = kwargs.get('pot', 0)
        self.phase = kwargs.get('phase', self.PHASE_PREFLOP)

        self.preflop = kwargs.get('preflop', {})
        self.flop = kwargs.get('flop', {})
        self.turn = kwargs.get('turn', {})
        self.river = kwargs.get('river', {})
        self.showdown = kwargs.get('showdown', {})

        self.q = None
        self.pe_equities = {}

        # hand_strength = PE.hand_strength(['__', '__'], self.board, self.rivals)
        for s, d in self.data.items():
            if 'in' not in d['status']:
                continue
            self.data[s]['stats'] = ES.player_stats(self, s)
            self.players[s]['hand_range'] = ES.cut_hand_range(self.data[s]['stats'])
            self.data[s]['strength'] = 0.20
Пример #3
0
 def test_player_stats(self):
     e = Engine(
         'CoinPoker', 1,
         {
             1: {'name': 'joe', 'balance': 1000, 'status': 1},
             2: {'name': 'jane', 'balance': 1000, 'status': 1},
         },
         50, 100, 0,
     )
     e.available_actions()
     stats = ES.player_stats(e, e.s)
     assert 'actions' in stats
     assert len(stats['actions']) >= 4
     assert 0 < stats['hs'] < 100
Пример #4
0
    def test_player_stats_on_hand(self):
        e = Engine(
            'CoinPoker', 1,
            {
                1: {'name': 'joe', 'balance': 1000, 'status': 1},
                2: {'name': 'jane', 'balance': 1000, 'status': 1},
                3: {'name': 'jane', 'balance': 1000, 'status': 1},
                4: {'name': 'jane', 'balance': 1000, 'status': 1},
                5: {'name': 'jane', 'balance': 1000, 'status': 1},
                6: {'name': 'jane', 'balance': 1000, 'status': 1},
            },
            50, 100, 0,
        )
        e.available_actions()

        # p4
        e.do(['r', 100])
        e.available_actions()

        # p5
        e.do(['f'])
        e.available_actions()

        # p6
        e.do(['f'])
        e.available_actions()

        # p1
        e.do(['c'])
        e.available_actions()

        # p2
        e.do(['c'])
        e.available_actions()

        # p3
        e.do(['k'])
        e.available_actions()

        # p2
        e.do(['b', 100])
        e.available_actions()

        # p3
        stats = ES.player_stats(e, e.s)
        # hs = res.aggregations['hs']['hs_agg']['values']['50.0']
        assert len(stats['actions']) >= 4
Пример #5
0
    def test_player_stats_on_hand(self):
        e = Engine(
            'CoinPoker',
            1,
            {
                1: {
                    'name': 'joe',
                    'balance': 1000,
                    'status': 1
                },
                2: {
                    'name': 'jane',
                    'balance': 1000,
                    'status': 1
                },
                3: {
                    'name': 'jane',
                    'balance': 1000,
                    'status': 1
                },
                4: {
                    'name': 'jane',
                    'balance': 1000,
                    'status': 1
                },
                5: {
                    'name': 'jane',
                    'balance': 1000,
                    'status': 1
                },
                6: {
                    'name': 'jane',
                    'balance': 1000,
                    'status': 1
                },
            },
            50,
            100,
            0,
        )
        e.available_actions()

        # p4
        e.do(['r', 100])
        e.available_actions()

        # p5
        e.do(['f'])
        e.available_actions()

        # p6
        e.do(['f'])
        e.available_actions()

        # p1
        e.do(['c'])
        e.available_actions()

        # p2
        e.do(['c'])
        e.available_actions()

        # p3
        e.do(['k'])
        e.available_actions()

        # p2
        e.do(['b', 100])
        e.available_actions()

        # p3
        stats = ES.player_stats(e, e.s)
        # hs = res.aggregations['hs']['hs_agg']['values']['50.0']
        assert len(stats['actions']) >= 4
Пример #6
0
    def add_actions(self, e, parent):
        """Add actions available to this node
        If in GG phase then no actions possible, ever.
        Remove 'hand'
        Bets:
            - preflop are 2-4x BB
            - postflop are 40-100% pot
        Raise:
            - always double
        Allin:
            - only on river
            - if out of money then converted to allin

        Scale non-fold probabilities even though it should not have an effect.
        """
        # logger.info('adding actions to {}'.format(parent.tag))
        actions = e.available_actions()
        s, p = e.q[0]
        d = e.data[s]
        balance_left = p['balance'] - d['contrib']

        if not actions:
            # logger.warn('no actions to add to node')
            return

        if 'gg' in actions:
            # logger.debug('no actions available, got gg')
            return

        actions.remove('hand')

        # remove fold if player can check
        if 'check' in actions:
            actions.remove('fold')
            # # logger.debug('removed fold when check available')

        # remove fold for hero
        # if s == self.hero and 'fold' in actions:
        #     actions.remove('fold')
        #     # logger.debug('removed fold from hero')

        # remove raise if player has already been aggressive
        if 'raise' in actions and any(pa['action'] in 'br' for pa in d[e.phase]):
            actions.remove('raise')
            # # logger.debug('removed raise as player has already been aggressive')

        # remove allin, but add it later with final stats (if increased from bet/raised)
        if 'allin' in actions:
            actions.remove('allin')
        # logger.debug('removed allin by default')

        # load stats (codes with counts)
        stats = ES.player_stats(e, s)
        max_contrib = max(pd['contrib'] for pd in e.data.values())
        # contrib_short = max_contrib - d['contrib']

        # allin needs to be the doc count
        # where bets and raises result in allin, add those prob dists to this
        # that will give proper probability
        go_allin = stats['actions'].get('a', 0)

        # # logger.info('filtered actions: {}'.format(actions))
        # ev 0 instead of none because of root node sum when not all traversed it gives error
        action_nodes = []
        for a in actions:
            node_data = {
                'stats': stats['actions'].get(ACTIONS_TO_ABBR[a], 0.01),
                'divider': 1,
                'action': a,
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
            }

            if a in ['bet', 'raise']:
                btps_and_amts = []
                total_pot = sum(pd['contrib'] for pd in e.data.values()) + e.pot

                # for preflop only do 2x and 3x
                if e.phase == e.PHASE_PREFLOP:
                    btps_and_amts.append(('double', e.bb_amt * 2))
                    btps_and_amts.append(('triple', e.bb_amt * 3))
                # else do half and full pots
                else:
                    btps_and_amts.append(('half_pot', total_pot * 0.50))
                    btps_and_amts.append(('full_pot', total_pot * 1.00))
                    # round bets up to a BB
                    # btps_and_amts = [(btp, -(amt // -e.bb_amt) * e.bb_amt)
                    #                  for btp, amt in btps_and_amts]

                betting_info = []
                amts_seen = []
                for btp, amt in btps_and_amts:
                    if amt in amts_seen:
                        # logger.debug('already using {}, skipping duplicate'.format(amt))
                        continue
                    if a == 'bet' and amt < e.bb_amt:
                        # logger.debug('bet cannot be less than BB {}'.format(e.bb_amt))
                        continue
                    if a == 'raise' and amt < (max_contrib * 2):
                        # logger.debug('raise cannot be less than 2x contrib  of {}'.format(max_contrib * 2))
                        continue
                    betting_info.append((btp, amt))
                    amts_seen.append(amt)

                # change raises that cause allin
                betting_info_final = []
                for btp, amt in betting_info:
                    # if amt is more than player balance, it is an allin
                    if amt >= balance_left:
                        go_allin += node_data['stats'] / len(betting_info)
                    else:
                        betting_info_final.append((btp, amt))

                # all good, can have this bet as option
                for btp, amt in betting_info_final:
                    node_data_copy = deepcopy(node_data)
                    node_data_copy['divider'] = len(betting_info_final)
                    node_data_copy['action'] = f'{a}_{btp}'
                    node_data_copy['amount'] = amt
                    action_nodes.append(node_data_copy)

            else:
                action_nodes.append(node_data)

        # allin will have doc counts (from stat, maybe from bets, maybe from raise)
        if go_allin:
            node_data = {
                'stats': go_allin,
                'divider': 1,
                'action': 'allin',
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
                'amount': balance_left,
            }
            action_nodes.append(node_data)
            # logger.debug('added allin to actions with stat {}'.format(node_data['stats']))

        # scale the stats (it is currently term counts aka histogram) and it is required to be
        # a probability distribution (p~1)
        # Also, certain actions like fold can be removed, and the total stats is not 1
        total_stats = sum(an['stats'] / an['divider'] for an in action_nodes)
        for action_node in action_nodes:
            action_node['stats'] = max(0.01, action_node['stats'] / action_node['divider'] / total_stats)
            action_node['cum_stats'] = parent.data['cum_stats'] * action_node['stats']
            node_tag = f'{action_node["action"]}_{s}_{e.phase}'
            identifier = f'{node_tag}_{str(uuid.uuid4())[:8]}'
            self.tree.create_node(identifier=identifier, tag=node_tag, parent=parent.identifier, data=action_node)
            # logger.debug('new {} for {} with data {}'.format(node_tag, s, action_node))
            item = (
                1 - action_node['cum_stats'],
                self.leaf_path + [identifier]
            )
            self.queue.put(item)
Пример #7
0
    def __init__(self,
                 site_name,
                 button,
                 players,
                 sb,
                 bb,
                 ante=0,
                 *args,
                 **kwargs):
        logger.info(f'Engine site_name: {site_name}')
        logger.info(f'Engine button: {button}')
        logger.info(f'Engine players: {len(players)}')
        logger.info(f'Engine kwargs: {kwargs}')

        self.site_name = site_name
        self.button = button
        self.players = players
        self.sb_amt = sb
        self.bb_amt = bb
        self.ante = ante
        self.go_to_showdown = False
        self.mc = False

        if hasattr(kwargs, 'data'):
            self.data = kwargs['data']
        else:
            self.data = {
                s: {
                    'status': 'in' if p.get('status') else 'out',
                    'sitout': False,
                    'hand': ['__', '__'] if p.get('status') else ['  ', '  '],
                    'contrib': 0,
                    'matched': 0,
                    'preflop': [],
                    'flop': [],
                    'turn': [],
                    'river': [],
                    'showdown': [],  # for error at stats
                    'is_SB': False,
                    'is_BB': False,
                }
                for s, p in players.items()
            }
        self.vs = sum(
            [1 if d['status'] == 'in' else 0 for d in self.data.values()])
        self.rivals = self.vs
        self.winner = None

        # leave empty: scraper compares length
        self.board = kwargs.get('board', [])
        self.pot = kwargs.get('pot', 0)
        self.phase = kwargs.get('phase', self.PHASE_PREFLOP)

        self.preflop = kwargs.get('preflop', {})
        self.flop = kwargs.get('flop', {})
        self.turn = kwargs.get('turn', {})
        self.river = kwargs.get('river', {})
        self.showdown = kwargs.get('showdown', {})

        self.q = None
        self.pe_equities = {}

        # hand_strength = PE.hand_strength(['__', '__'], self.board, self.rivals)
        for s, d in self.data.items():
            if 'in' not in d['status']:
                continue
            self.data[s]['stats'] = ES.player_stats(self, s)
            self.players[s]['hand_range'] = ES.cut_hand_range(
                self.data[s]['stats'])
            self.data[s]['strength'] = 0.20
Пример #8
0
    def add_actions(self, e, parent):
        """Add actions available to this node
        If in GG phase then no actions possible, ever.
        Remove 'hand'
        Bets:
            - preflop are 2-4x BB
            - postflop are 40-100% pot
        Raise:
            - always double
        Allin:
            - only on river
            - if out of money then converted to allin

        Scale non-fold probabilities even though it should not have an effect.
        """
        # logger.info('adding actions to {}'.format(parent.tag))
        actions = e.available_actions()
        s, p = e.q[0]
        d = e.data[s]
        balance_left = p['balance'] - d['contrib']

        if not actions:
            # logger.warn('no actions to add to node')
            return

        if 'gg' in actions:
            # logger.debug('no actions available, got gg')
            return

        actions.remove('hand')

        # remove fold if player can check
        if 'check' in actions:
            actions.remove('fold')
            # # logger.debug('removed fold when check available')

        # remove fold for hero
        # if s == self.hero and 'fold' in actions:
        #     actions.remove('fold')
        #     # logger.debug('removed fold from hero')

        # remove raise if player has already been aggressive
        if 'raise' in actions and any(pa['action'] in 'br'
                                      for pa in d[e.phase]):
            actions.remove('raise')
            # # logger.debug('removed raise as player has already been aggressive')

        # remove allin, but add it later with final stats (if increased from bet/raised)
        if 'allin' in actions:
            actions.remove('allin')
        # logger.debug('removed allin by default')

        # load stats (codes with counts)
        stats = ES.player_stats(e, s)
        max_contrib = max(pd['contrib'] for pd in e.data.values())
        # contrib_short = max_contrib - d['contrib']

        # allin needs to be the doc count
        # where bets and raises result in allin, add those prob dists to this
        # that will give proper probability
        go_allin = stats['actions'].get('a', 0)

        # # logger.info('filtered actions: {}'.format(actions))
        # ev 0 instead of none because of root node sum when not all traversed it gives error
        action_nodes = []
        for a in actions:
            node_data = {
                'stats': stats['actions'].get(ACTIONS_TO_ABBR[a], 0.01),
                'divider': 1,
                'action': a,
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
            }

            if a in ['bet', 'raise']:
                btps_and_amts = []
                total_pot = sum(pd['contrib']
                                for pd in e.data.values()) + e.pot

                # for preflop only do 2x and 3x
                if e.phase == e.PHASE_PREFLOP:
                    btps_and_amts.append(('double', e.bb_amt * 2))
                    btps_and_amts.append(('triple', e.bb_amt * 3))
                # else do half and full pots
                else:
                    btps_and_amts.append(('half_pot', total_pot * 0.50))
                    btps_and_amts.append(('full_pot', total_pot * 1.00))
                    # round bets up to a BB
                    # btps_and_amts = [(btp, -(amt // -e.bb_amt) * e.bb_amt)
                    #                  for btp, amt in btps_and_amts]

                betting_info = []
                amts_seen = []
                for btp, amt in btps_and_amts:
                    if amt in amts_seen:
                        # logger.debug('already using {}, skipping duplicate'.format(amt))
                        continue
                    if a == 'bet' and amt < e.bb_amt:
                        # logger.debug('bet cannot be less than BB {}'.format(e.bb_amt))
                        continue
                    if a == 'raise' and amt < (max_contrib * 2):
                        # logger.debug('raise cannot be less than 2x contrib  of {}'.format(max_contrib * 2))
                        continue
                    betting_info.append((btp, amt))
                    amts_seen.append(amt)

                # change raises that cause allin
                betting_info_final = []
                for btp, amt in betting_info:
                    # if amt is more than player balance, it is an allin
                    if amt >= balance_left:
                        go_allin += node_data['stats'] / len(betting_info)
                    else:
                        betting_info_final.append((btp, amt))

                # all good, can have this bet as option
                for btp, amt in betting_info_final:
                    node_data_copy = deepcopy(node_data)
                    node_data_copy['divider'] = len(betting_info_final)
                    node_data_copy['action'] = f'{a}_{btp}'
                    node_data_copy['amount'] = amt
                    action_nodes.append(node_data_copy)

            else:
                action_nodes.append(node_data)

        # allin will have doc counts (from stat, maybe from bets, maybe from raise)
        if go_allin:
            node_data = {
                'stats': go_allin,
                'divider': 1,
                'action': 'allin',
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
                'amount': balance_left,
            }
            action_nodes.append(node_data)
            # logger.debug('added allin to actions with stat {}'.format(node_data['stats']))

        # scale the stats (it is currently term counts aka histogram) and it is required to be
        # a probability distribution (p~1)
        # Also, certain actions like fold can be removed, and the total stats is not 1
        total_stats = sum(an['stats'] / an['divider'] for an in action_nodes)
        for action_node in action_nodes:
            action_node['stats'] = max(
                0.01,
                action_node['stats'] / action_node['divider'] / total_stats)
            action_node[
                'cum_stats'] = parent.data['cum_stats'] * action_node['stats']
            node_tag = f'{action_node["action"]}_{s}_{e.phase}'
            identifier = f'{node_tag}_{str(uuid.uuid4())[:8]}'
            self.tree.create_node(identifier=identifier,
                                  tag=node_tag,
                                  parent=parent.identifier,
                                  data=action_node)
            # logger.debug('new {} for {} with data {}'.format(node_tag, s, action_node))
            item = (1 - action_node['cum_stats'],
                    self.leaf_path + [identifier])
            self.queue.put(item)