def __repr__(self): child_actions = [child.action.to_action() for child in self.children] return "GameTreeNode(street=%r, board=%r, actor=%r, action='%s', " \ "betting_line=%r, child_actions=%r, ranges_by_userid=%r, " \ "total_contrib=%r, winners=%r, final_pot=%r)" % \ (self.street, self.board, self.actor, self.action, line_description(self.betting_line), child_actions, self.ranges_by_userid, self.total_contrib, self.winners, self.final_pot)
def display_game_tree(tree, local=False): """ Display every node of tree. """ for node in tree.children: display_game_tree(node, local) for userid in tree.ranges_by_userid: ev_map = {} for combo, equity in tree.all_combos_ev(userid, local).items(): lower_card, higher_card = sorted(combo) desc = higher_card.to_mnemonic() + lower_card.to_mnemonic() ev_map[desc] = "%0.04f" % (equity,) print "Line '%s', userid %d:" % \ (line_description(tree.betting_line), userid), ev_map
def calculate_combo_ev(self, combo, userid): """ Calculate EV for a combo at this point in the game tree. """ if self.children: # intermediate node, EV is combination of children's # oh, each child needs a weight / probability # oh, it's combo specific... "it depends" ;) # EV of combo is: # weighted sum of EV of children, but only for children where the # combo is present (where combo isn't present, probability is zero) # to assess probability: # - if this node's children are performed by this user: # - EV is the EV of the action that contains this combo # - otherwise: # - remove combo from the children's actor's current range # - consider how many combo's of children's actor's current range # proceed to each child # - voila # This conveniently works even in a multi-way pot. It's a very good # approximation of the true probabilities. And note that truly # calculating the true probabilities is not possible. actor = self.children[0].actor if actor == userid: # EV is the EV of the action that contains this combo for node in self.children: if range_contains_hand(node.ranges_by_userid[userid], combo): return node.combo_ev(combo, userid) raise InvalidComboForTree('Combo not in child ranges for userid' ' %d at betting line %s.' % (userid, line_description(self.betting_line))) else: # probabilistic weighting of child EVs # size of bucket is probability of this child valid_children = [child for child in self.children if range_contains_hand(child.ranges_by_userid[userid], combo)] buckets = {child: child.ranges_by_userid[actor] \ .generate_options(Card.many_from_text(child.board) + list(combo)) for child in valid_children} total = len(concatenate(buckets.values())) if total == 0: raise InvalidComboForTree('Combo not in child ranges for' ' userid %d at betting line %s. ' % (userid, line_description(self.betting_line))) probabilities = {child: 1.0 * len(buckets[child]) / total for child in valid_children} ev = sum(probabilities[child] * child.combo_ev(combo, userid) for child in valid_children) return ev # Invalid combos are ignored / not calculated or aggregated. elif userid not in self.winners: # they folded return 0.0 - self.total_contrib[userid] elif len(self.winners) == 1: # uncontested pot return 0.0 + self.final_pot - self.total_contrib[userid] else: # showdown ranges = {userid: range_ for userid, range_ in self.ranges_by_userid.items()} combos = set([combo]) description = unweighted_options_to_description(combos) ranges[userid] = HandRange(description) equities, _iteration = \ showdown_equity(ranges, Card.many_from_text(self.board), hard_limit=10000) # TODO: 1: this is not good enough equity = equities[userid] return equity * self.final_pot - self.total_contrib[userid]