def board_payment(self, board_item, board_before, board_after):
     """
     Payments in compensation of change of equity due to board cards.
     """
     range_map = {key: HandRange(txt) for key, txt in self.ranges.iteritems()
                  if key in self.remaining_userids}
     equity_map_before, _ = showdown_equity(range_map, board_before)
     equity_map_after, _ = showdown_equity(range_map, board_after)
     for userid in self.remaining_userids:
         # payment equal to what was lost
         amount = self.pot * (equity_map_before[userid] -
                              equity_map_after[userid]) * board_item.factor
         logging.debug('gameid %d, order %d, userid %d, board payment: '
                       'pot %d * (before %0.4f - after %0.4f) * factor %0.4f'
                       ' = amount %0.8f',
                       board_item.gameid, board_item.order, userid,
                       self.pot, equity_map_before[userid],
                       equity_map_after[userid], board_item.factor,
                       amount)
         payment = PaymentToPlayer()
         payment.reason = PaymentToPlayer.REASON_BOARD
         payment.gameid = board_item.gameid
         payment.order = board_item.order
         payment.userid = userid
         payment.amount = amount
         self.session.add(payment)
 def pot_payment(self, action_item, contribution):
     """
     A simple payment from a player to the pot.
     """
     if contribution == 0:
         return
     amount = -action_item.factor * contribution
     logging.debug('gameid %d, order %d, userid %d, pot payment: '
                   'factor %0.4f * contribution %d = amount %0.8f',
                   action_item.gameid, action_item.order, action_item.userid,
                   action_item.factor, contribution, amount)
     payment = PaymentToPlayer()
     payment.reason = PaymentToPlayer.REASON_POT
     payment.gameid = action_item.gameid
     payment.order = action_item.order
     payment.userid = action_item.userid
     payment.amount = amount
     self.session.add(payment)
 def showdown_call(self, gameid, order, caller, call_cost, call_ratio,
                   factor):
     """
     This is a call that doesn't really happen (it's not in the game tree
     main branch), but it's terminal (like a fold), and we pay out showdowns,
     but to do so we also need to charge for the call that doesn't happen.
     """
     payment = PaymentToPlayer()
     payment.reason = PaymentToPlayer.REASON_SHOWDOWN_CALL
     payment.gameid = gameid
     payment.order = order
     payment.userid = caller
     payment.amount = -call_cost * factor * call_ratio
     logging.debug('gameid %d, order %d, userid %d, showdown call payment: '
         'call cost %d * factor %0.4f * call ratio %0.4f = '
         'amount %0.8f',
         gameid, order, caller, call_cost, factor, call_ratio,
         payment.amount)
     self.session.add(payment)
 def showdown_payments(self, showdown, equities):
     """
     Create showdown payments for all participants of this showdown.
     """
     logging.debug('gameid %d, order %d, creating showdown payments',
                   showdown.gameid, showdown.order)
     for participant in equities:
         payment = PaymentToPlayer()
         payment.reason = PaymentToPlayer.REASON_SHOWDOWN
         payment.gameid = showdown.gameid
         payment.order = showdown.order
         payment.userid = participant.userid
         payment.amount = showdown.factor * showdown.pot * participant.equity
         logging.debug('gameid %d, order %d, userid %d, showdown payment: '
                       'factor %0.4f * pot %d * equity %0.4f = '
                       'amount %0.8f',
                       showdown.gameid, showdown.order, participant.userid,
                       showdown.factor, showdown.pot, participant.equity,
                       payment.amount)
         self.session.add(payment)
    def fold_equity_payments(self, range_action, fold_ratio):
        """
        Fold equity payment occurs for every range action with only two players
        remaining, and is a payment equal to the current pot multiplied by the
        current factor multiplied by the fold ratio (up to and including 100% of
        the pot, e.g. when in a HU situation BTN open folds 100%).

        This is not a payment from one player to the other. It is a payment
        the pot to the player who bet. The bettor gains a portion of the pot
        equal to the fold ratio, and the pot loses this by virtue of a reduction
        in the current factor.
        """
        if len(self.remaining_userids) != 2:
            return
        # Note that this includes the bet amount from the betting player, an as-
        # yet unmatched bet. This is correct, because this is (for example) the
        # amount the betting player will win if this player folds 100% - no
        # more, no less.
        if not fold_ratio:
            logging.debug('gameid %d, order %d, fold ratio 0.0, '
                          'skipping fold equity payments',
                          range_action.gameid, range_action.order)
            return
        amount = self.pot * range_action.factor * fold_ratio
        assert range_action.userid in self.remaining_userids
        if self.remaining_userids[0] == range_action.userid:
            nonfolder = self.remaining_userids[1]
        else:
            nonfolder = self.remaining_userids[0]
        logging.debug('gameid %d, order %d, userid %d, fold equity payment: '
                      'pot %d * factor %0.4f * fold ratio %0.4f = amount %0.8f',
                      range_action.gameid, range_action.order,
                      nonfolder,
                      self.pot, range_action.factor, fold_ratio, amount)
        nonfolder_payment = PaymentToPlayer()
        nonfolder_payment.reason = PaymentToPlayer.REASON_FOLD_EQUITY
        nonfolder_payment.gameid = range_action.gameid
        nonfolder_payment.order = range_action.order
        nonfolder_payment.userid = nonfolder
        nonfolder_payment.amount = amount
        self.session.add(nonfolder_payment)
    def equity_payments(self, item):
        """
        Payments in compensation due to one player's range changing. Note that
        every remaining player gets a payment.

        This happens whenever a player has multiple "play continues" ranges.
        When heads up, this means any time before the river when someone has a
        passive range and an aggressive range.

        More generally, when three-handed, it can include folds. In fact, it
        could probably be generalised to include fold equity! (But note that
        fold equity is much less contentious!) (The issue with fold equity is
        that it requires us to scale down the rest of play, because we don't
        allow the fold line to happen, ever.)

        item is a GameHistoryRangeAction
        """
        # Establish "old ranges", meaning after anything that can't happen
        # doesn't happen, but before it has been decided which of the things
        # that can happen will happen.
        userid = item.userid
        old_ranges = {k: HandRange(v) for k, v in self.ranges.iteritems()
                      if k in self.remaining_userids}
        allowed = {'f', 'p', 'a'}
        if not self.fold_continues:
            allowed.discard('f')
            old_ranges[userid] = old_ranges[userid]  \
                .subtract(HandRange(self.prev_range_action.fold_range))
        if not self.passive_continues:
            allowed.discard('p')
            old_ranges[userid] = old_ranges[userid]  \
                .subtract(HandRange(self.prev_range_action.passive_range))
        if self.prev_range_action.fold_ratio == 0.0:
            allowed.discard('f')
        if self.prev_range_action.passive_ratio == 0.0:
            allowed.discard('p')
        if self.prev_range_action.aggressive_ratio == 0.0:
            allowed.discard('a')
        if len(allowed) <= 1: # one path, or zero paths (end of the game)
            logging.debug("gameid %d, order %d, no equity payments, "
                          "allowed: %s",
                          item.gameid, item.order, ','.join(allowed))
            return
        # Establish "new ranges", what will actually be.
        new_ranges = dict(old_ranges)
        if item.is_fold:
            new_ranges[userid] = HandRange(self.prev_range_action.fold_range)
        if item.is_passive:
            new_ranges[userid] = HandRange(self.prev_range_action.passive_range)
        if item.is_aggressive:
            new_ranges[userid] =  \
                HandRange(self.prev_range_action.aggressive_range)
        # Calculate equities
        old_equities, _ = showdown_equity(old_ranges, self.board)
        new_equities, _ = showdown_equity(new_ranges, self.board)
        for userid in self.remaining_userids:
            # payment equal to what was lost
            # note the use of item.factor (new), not prev_range_action.factor
            amount = self.pot * (old_equities[userid] -
                                 new_equities[userid]) * item.factor
            logging.debug('gameid %d, order %d, userid %d, equity payment: '
                          'pot %d * (before %0.4f - after %0.4f) * factor %0.4f'
                          ' = amount %0.8f',
                          item.gameid, item.order, userid, self.pot,
                          old_equities[userid], new_equities[userid],
                          item.factor, amount)
            payment = PaymentToPlayer()
            payment.reason = PaymentToPlayer.REASON_BRANCH
            payment.gameid = item.gameid
            payment.order = item.order
            payment.userid = userid
            payment.amount = amount
            self.session.add(payment)