Beispiel #1
0
 def consider_all(self):
     """
     Plays every action in range_action, except the zero-weighted ones.
     If the game can continue, this will return an appropriate action_result.
     If not, it will return a termination. The game will continue if any
     action results in at least two players remaining in the hand, and at
     least one player left to act. (Whether or not that includes this
     player.)
     
     Inputs: see __init__
     
     Outputs: an ActionResult: fold, passive, aggressive, or terminate
     
     Side effects:
      - reduce current factor based on non-playing ranges
      - redeal rgp's cards based on new range
      - (later) equity payments and such
     """
     # note that we only consider the possible
     # mostly copied from the old re_deal
     cards_dealt = {rgp: rgp.cards_dealt for rgp in self.game.rgps}
     dead_cards = [card for card in self.game.board if card is not None]
     dead_cards.extend(concatenate([v for k, v in cards_dealt.iteritems()
                                    if k is not self.rgp]))
     fold_options = self.range_action.fold_range  \
         .generate_options_unweighted(dead_cards)
     passive_options = self.range_action.passive_range  \
         .generate_options_unweighted(dead_cards)
     aggressive_options = self.range_action.aggressive_range  \
         .generate_options_unweighted(dead_cards)
     # Consider fold
     fold_action = ActionResult.fold()
     if len(fold_options) > 0:
         self.bough.append(Branch(self.fold_continue(),
                                  fold_options,
                                  fold_action,
                                  self.range_action.fold_range))
     # Consider call
     passive_action = ActionResult.call(self.current_options.call_cost)
     if len(passive_options) > 0:
         self.bough.append(Branch(self.passive_continue(),
                                  passive_options,
                                  passive_action,
                                  self.range_action.passive_range))
     # Consider raise
     aggressive_action = ActionResult.raise_to(
         self.range_action.raise_total, self.current_options.is_raise)
     if len(aggressive_options) > 0:
         self.bough.append(Branch(self.aggressive_continue(),
                                  aggressive_options,
                                  aggressive_action,
                                  self.range_action.aggressive_range))
Beispiel #2
0
 def consider_all(self):
     """
     Plays every action in range_action, except the zero-weighted ones.
     If the game can continue, this will return an appropriate action_result.
     If not, it will return a termination. The game will continue if any
     action results in at least two players remaining in the hand, and at
     least one player left to act. (Whether or not that includes this
     player.)
     
     Inputs: see __init__
     
     Outputs: an ActionResult: fold, passive, aggressive, or terminate
     
     Side effects:
      - reduce current factor based on non-playing ranges
      - redeal rgp's cards based on new range
      - (later) equity payments and such
     """
     # note that we only consider the possible
     # mostly copied from the old re_deal
     cards_dealt = {rgp: rgp.cards_dealt for rgp in self.game.rgps}
     dead_cards = [card for card in self.game.board if card is not None]
     dead_cards.extend(
         concatenate(
             [v for k, v in cards_dealt.iteritems() if k is not self.rgp]))
     fold_options = self.range_action.fold_range  \
         .generate_options_unweighted(dead_cards)
     passive_options = self.range_action.passive_range  \
         .generate_options_unweighted(dead_cards)
     aggressive_options = self.range_action.aggressive_range  \
         .generate_options_unweighted(dead_cards)
     # Consider fold
     fold_action = ActionResult.fold()
     if len(fold_options) > 0:
         self.bough.append(
             Branch(self.fold_continue(), fold_options, fold_action,
                    self.range_action.fold_range))
     # Consider call
     passive_action = ActionResult.call(self.current_options.call_cost)
     if len(passive_options) > 0:
         self.bough.append(
             Branch(self.passive_continue(), passive_options,
                    passive_action, self.range_action.passive_range))
     # Consider raise
     aggressive_action = ActionResult.raise_to(
         self.range_action.raise_total, self.current_options.is_raise)
     if len(aggressive_options) > 0:
         self.bough.append(
             Branch(self.aggressive_continue(), aggressive_options,
                    aggressive_action, self.range_action.aggressive_range))
Beispiel #3
0
def range_action_to_action(range_action, hand, current_options):
    """
    range_action is a ActionDetails, ranges have text descriptions
    hand is a list of two Card
    returns a new range, and an action
    """
    # Note that this gives incorrect results if an option is in two ranges
    if range_contains_hand(range_action.fold_range, hand):
        return range_action.fold_range, ActionResult.fold()
    elif range_contains_hand(range_action.passive_range, hand):
        return range_action.passive_range, \
            ActionResult.call(current_options.call_cost)
    elif range_contains_hand(range_action.aggressive_range, hand):
        return range_action.aggressive_range, \
            ActionResult.raise_to(range_action.raise_total,
                                  current_options.is_raise)
    else:
        raise ValueError("hand is %s, range_action is invalid: %r" %
                         (hand, range_action))
Beispiel #4
0
 def calculate_what_will_be(self):
     """
     Choose one of the non-terminal actions, or return termination if they're
     all terminal. Also update game's current_factor. 
     """
     # reduce current factor by the ratio of non-terminal-to-terminal options
     non_terminal = []
     terminal = []
     for branch in self.bough:
         if not branch.options:
             continue
         if branch.is_continue:
             logging.debug("gameid %d, potential action %r would continue",
                           self.game.gameid, branch.action)
             non_terminal.extend(branch.options)
         else:
             logging.debug("gameid %d, potential action %r would terminate",
                           self.game.gameid, branch.action)
             terminal.extend(branch.options)
     total = len(non_terminal) + len(terminal)
     reduction = float(len(non_terminal)) / total
     logging.debug(
         "gameid %d, with %d non-terminal and %d terminal, " +
         "multiplying current factor by %0.2f from %0.2f to %0.2f",
         self.game.gameid, len(non_terminal), len(terminal), reduction,
         self.game.current_factor, self.game.current_factor * reduction)
     # the more non-terminal, the less effect on current factor
     self.game.current_factor *= reduction
     if non_terminal:
         chosen_option = random.choice(non_terminal)
         # but which action was chosen?
         for branch in self.bough:
             if chosen_option in branch.options:
                 logging.debug("gameid %d, chosen %r for action %r",
                               self.game.gameid, chosen_option,
                               branch.action)
                 self.action_result = branch.action
                 self.re_range(branch)
     else:
         logging.debug("gameid %d, what will be is to terminate",
                       self.game.gameid)
         self.action_result = ActionResult.terminate()
     return self.action_result
Beispiel #5
0
 def calculate_what_will_be(self):
     """
     Choose one of the non-terminal actions, or return termination if they're
     all terminal. Also update game's current_factor. 
     """
     # reduce current factor by the ratio of non-terminal-to-terminal options
     non_terminal = []
     terminal = []
     for branch in self.bough:
         if not branch.options:
             continue
         if branch.is_continue:
             logging.debug("gameid %d, potential action %r would continue",
                           self.game.gameid, branch.action)
             non_terminal.extend(branch.options)
         else:
             logging.debug("gameid %d, potential action %r would terminate",
                           self.game.gameid, branch.action)
             terminal.extend(branch.options)
     total = len(non_terminal) + len(terminal)
     reduction = float(len(non_terminal)) / total
     logging.debug("gameid %d, with %d non-terminal and %d terminal, " +
                   "multiplying current factor by %0.2f from %0.2f to %0.2f",
                   self.game.gameid, len(non_terminal), len(terminal),
                   reduction, self.game.current_factor,
                   self.game.current_factor * reduction)
     # the more non-terminal, the less effect on current factor
     self.game.current_factor *= reduction
     if non_terminal:
         chosen_option = random.choice(non_terminal)
         # but which action was chosen?
         for branch in self.bough:
             if chosen_option in branch.options:
                 logging.debug("gameid %d, chosen %r for action %r",
                     self.game.gameid, chosen_option, branch.action)
                 self.action_result = branch.action
                 self.re_range(branch)
     else:
         logging.debug("gameid %d, what will be is to terminate",
                       self.game.gameid)
         self.action_result = ActionResult.terminate()
     return self.action_result    
Beispiel #6
0
    def consider_all(self):
        """
        Plays every action in range_action, except the zero-weighted ones.
        If the game can continue, this will return an appropriate action_result.
        If not, it will return a termination. The game will continue if any
        action results in at least two players remaining in the hand, and at
        least one player left to act. (Whether or not that includes this
        player.)

        Inputs: see __init__

        Outputs: range ratios

        Side effects:
         - analyse effects of a fold, a passive play, or an aggressive play
        """
        # note that we only consider the possible
        # mostly copied from the old re_deal (originally!)

        # TODO: REVISIT: sometimes this considers something not to be an...
        # option when it truly is, e.g:
        # - Villain happens to draw AA for dealt/excluded cards
        # - board has two aces also
        # - Hero has only Ax in his calling range
        # - result is that villain has 0% calls for this game
        # - blech!
        dead_cards = generate_excluded_cards(self.game, hero=self.rgp)
        fold_options = self.range_action.fold_range  \
            .generate_options(dead_cards)
        passive_options = self.range_action.passive_range  \
            .generate_options(dead_cards)
        aggressive_options = self.range_action.aggressive_range  \
            .generate_options(dead_cards)
        # Consider fold
        fold_action = ActionResult.fold()
        if len(fold_options) > 0:
            self.bough.append(Branch(self.fold_continue(),
                                     fold_options,
                                     fold_action,
                                     self.range_action.fold_range))
        # Consider call
        passive_action = ActionResult.call(self.current_options.call_cost)
        if len(passive_options) > 0:
            self.bough.append(Branch(self.passive_continue(),
                                     passive_options,
                                     passive_action,
                                     self.range_action.passive_range))
        # Consider raise
        aggressive_action = ActionResult.raise_to(
            self.range_action.raise_total, self.current_options.is_raise)
        if len(aggressive_options) > 0:
            self.bough.append(Branch(self.aggressive_continue(),
                                     aggressive_options,
                                     aggressive_action,
                                     self.range_action.aggressive_range))
        len_fol = len(fold_options)
        len_pas = len(passive_options)
        len_agg = len(aggressive_options)
        total = len_fol + len_pas + len_agg
        return {
            'fold': 1.0 * len_fol / total,
            'passive': 1.0 * len_pas / total,
            'aggressive': 1.0 * len_agg / total}
 def from_game(cls, game):
     """
     Create a partial game tree from a single game. Note that this still
     creates branches (multi-child nodes) due to non-terminal folds and
     showdowns.
     """
     # TODO: REVISIT: Blackbox testing? Unit testing? Something!
     # Any error in any of this logic will create some obscure bug in some
     # game tree.
     # TODO: 4: A general purpose replayer. How many times do we have to
     # write this code before we refactor it into something reusable... and
     # demonstrably correct!
     actual_ranges = {}
     for rgp, player in zip(game.rgps, game.situation.players):
         new_range = remove_board_from_range(player.range,
             game.situation.board)
         actual_ranges[rgp.userid] = new_range.description
     board_raw = game.situation.board_raw
     session = object_session(game)
     # We need to look for:
     # - GameHistoryActionResult, to find actions that happened
     # - GameHistoryRangeAction, to find folds
     # - GameHistoryShowdown, to find showdown calls
     # - GameHistoryBoard, to know its the river, because then three-handed
     #   folds can be terminal
     history = []
     for table in [tables.GameHistoryActionResult,
                   tables.GameHistoryRangeAction,
                   tables.GameHistoryShowdown,
                   tables.GameHistoryBoard]:
         history.extend(session.query(table)  \
             .filter(table.gameid == game.gameid).all())
     history.sort(key=lambda row: row.order)
     current_round = game.situation.current_round
     stacks = {rgp.userid: player.stack
               for rgp, player in zip(game.rgps, game.situation.players)}
     contrib = {rgp.userid: player.contributed
                for rgp, player in zip(game.rgps, game.situation.players)}
     total_contrib = dict(contrib)
     to_act = {rgp.userid
               for rgp, player in zip(game.rgps, game.situation.players)
               if player.left_to_act}
     pot = game.situation.pot_pre +  \
         sum(p.contributed for p in game.situation.players)
     raise_total = max(p.contributed for p in game.situation.players)
     remain = {rgp.userid for rgp in game.rgps}
     root = cls(game.situation.current_round, board_raw, None,
                None, None, actual_ranges, total_contrib)
     node = root  # where we're adding actions
     prev_range_action = None
     for item in history:
         # reset game state for new round
         if isinstance(item, tables.GameHistoryBoard):
             current_round = item.street
             board_raw = item.cards
             to_act = set(remain)
             contrib = {rgp.userid: 0 for rgp in game.rgps}
             raise_total = 0
             for userid, old_range in actual_ranges.iteritems():
                 new_range = remove_board_from_range(HandRange(old_range),
                     Card.many_from_text(item.cards))
                 actual_ranges[userid] = new_range.description
         if isinstance(item, tables.GameHistoryShowdown):
             # add call
             call_cost = raise_total - contrib[prev_range_action.userid]
             action = ActionResult.call(call_cost)
             ranges = dict(actual_ranges)
             ranges[prev_range_action.userid] =  \
                 prev_range_action.passive_range
             showdown_contrib = dict(total_contrib)
             showdown_contrib[prev_range_action.userid] += call_cost
             showdown_pot = pot + call_cost
             child = cls(current_round, board_raw,
                         prev_range_action.userid, action,
                         node, ranges,
                         total_contrib=showdown_contrib, winners=remain,
                         final_pot=showdown_pot)
             node.children.append(child)
         # Only if the fold is terminal is it part of the tree.
         # Folds are terminal when:
         # - two-handed; or,
         # - three-handed when:
         #   - all other players have acted on the river; or,
         #   - are all in before the river; or,
         # - they fold 100%
         if isinstance(item, tables.GameHistoryRangeAction):
             prev_range_action = item
             if item.fold_ratio is None:
                 has_fold = item.fold_range != NOTHING
             else:
                 has_fold = item.fold_ratio != 0.0
             is_final_round = current_round == RIVER or  \
                 not all(stacks.values())
             heads_up = len(remain) == 2
             final_action = len(to_act) == 1 and is_final_round
             # There's no (implicit) fold when multi-way and play continues.
             if has_fold and (final_action or heads_up):
                 # Play would not continue with a fold here, so there will be
                 # no actual fold action.
                 # TODO: 3: use game_continues?
                 # Add a non-played fold. We keep the folded player's range
                 # in here, because it is relevant to consider the EV of each
                 # folded combo.
                 winners = set(remain)
                 winners.remove(item.userid)
                 # winners may be one (HU) or multiple (multiway)
                 ranges = actual_ranges
                 ranges[item.userid] = item.fold_range
                 child = cls(current_round, board_raw, item.userid,
                             ActionResult.fold(), node, ranges,
                             total_contrib=total_contrib, winners=winners,
                             final_pot=pot)
                 node.children.append(child)
         if isinstance(item, tables.GameHistoryActionResult):
             # maintain game state
             if item.is_passive:
                 actual_ranges[item.userid] = prev_range_action.passive_range
                 stacks[item.userid] -= item.call_cost
                 contrib[item.userid] += item.call_cost
                 total_contrib[item.userid] += item.call_cost
                 pot += item.call_cost
                 action = ActionResult.call(item.call_cost)
             if item.is_aggressive:
                 actual_ranges[item.userid] =  \
                     prev_range_action.aggressive_range
                 chips = item.raise_total - contrib[item.userid]
                 stacks[item.userid] -= chips
                 contrib[item.userid] += chips
                 total_contrib[item.userid] += chips
                 pot += chips
                 raise_total = item.raise_total
                 to_act = set(remain)
                 action = ActionResult.raise_to(raise_total, item.is_raise)
             if item.is_fold:
                 remain.remove(item.userid)
                 action = ActionResult.fold()
                 # and yes, we still traverse in (multi-way)
             to_act.remove(item.userid)
             # add fold, check, call, raise or bet, and traverse in
             child = cls(current_round, board_raw, item.userid, action, node,
                         actual_ranges, total_contrib)
             node.children.append(child)
             # traverse down
             node = child
     return root