示例#1
0
文件: game.py 项目: dgoodwin/rounder
    def __init__(self, limit, players, dealer_index, sb_index, bb_index, callback, table=None, deck=None):
        """
        Blind indicies indicate players who have agreed to post the blinds for
        this hand, thus we can immediately retrieve them and get underway.

        Players list represents only those players who were actively at the
        table AND sitting in when the hand began. Positions in the list are
        not relative. All the game cares about is where they sit in relation
        to one another.
        """

        Game.__init__(self, limit, players, callback, dealer_index, table, deck)

        self.dealer = self.players[dealer_index]
        self.small_blind = self.players[sb_index]
        self.big_blind = self.players[bb_index]
        self.__last_raise_amount = None

        log_msg = "Starting new TexasHoldemGame: " + str(self.id)
        log_msg += "\n  Limit: " + str(limit)
        log_msg += "\n  Players:"
        self.__positions = {}  # TODO: Might need a better way to track seats
        i = 0
        for p in self.players:
            code = ""
            if p == self.dealer:
                code += "dealer "
            if p == self.small_blind:
                code += "sb "
            if p == self.big_blind:
                code += "bb "
            log_msg += "\n    %s %s" % (p, code)
            self.__positions[p] = i
            i += 1
        logger.info(log_msg)
        # Map player to their pending actions. Players are popped as they act
        # so an empty map means no pending actions and we're clear to advance
        # to the next state.
        # TODO: Looking like there's never more than one player with pending
        # actions...
        self.pending_actions = {}

        self.community_cards = []

        if self._deck == None:
            self._deck = Deck()
            self._deck.shuffle()

        self.__current_bet = Currency(0)
        self.__raise_count = 0

        self.gsm = GameStateMachine()
        self.gsm.add_state(STATE_PREFLOP, self.preflop)
        self.gsm.add_state(STATE_FLOP, self.flop)
        self.gsm.add_state(STATE_TURN, self.turn)
        self.gsm.add_state(STATE_RIVER, self.river)
        self.gsm.add_state(STATE_GAMEOVER, self.game_over)
示例#2
0
 def draw_too_many_cards(self):
     d = Deck()
     for i in range(0, 53):
         d.draw_card()
示例#3
0
文件: game.py 项目: dgoodwin/rounder
class TexasHoldemGame(Game):

    """ Texas Hold'em, the Cadillac of poker. """

    def __init__(self, limit, players, dealer_index, sb_index, bb_index, callback, table=None, deck=None):
        """
        Blind indicies indicate players who have agreed to post the blinds for
        this hand, thus we can immediately retrieve them and get underway.

        Players list represents only those players who were actively at the
        table AND sitting in when the hand began. Positions in the list are
        not relative. All the game cares about is where they sit in relation
        to one another.
        """

        Game.__init__(self, limit, players, callback, dealer_index, table, deck)

        self.dealer = self.players[dealer_index]
        self.small_blind = self.players[sb_index]
        self.big_blind = self.players[bb_index]
        self.__last_raise_amount = None

        log_msg = "Starting new TexasHoldemGame: " + str(self.id)
        log_msg += "\n  Limit: " + str(limit)
        log_msg += "\n  Players:"
        self.__positions = {}  # TODO: Might need a better way to track seats
        i = 0
        for p in self.players:
            code = ""
            if p == self.dealer:
                code += "dealer "
            if p == self.small_blind:
                code += "sb "
            if p == self.big_blind:
                code += "bb "
            log_msg += "\n    %s %s" % (p, code)
            self.__positions[p] = i
            i += 1
        logger.info(log_msg)
        # Map player to their pending actions. Players are popped as they act
        # so an empty map means no pending actions and we're clear to advance
        # to the next state.
        # TODO: Looking like there's never more than one player with pending
        # actions...
        self.pending_actions = {}

        self.community_cards = []

        if self._deck == None:
            self._deck = Deck()
            self._deck.shuffle()

        self.__current_bet = Currency(0)
        self.__raise_count = 0

        self.gsm = GameStateMachine()
        self.gsm.add_state(STATE_PREFLOP, self.preflop)
        self.gsm.add_state(STATE_FLOP, self.flop)
        self.gsm.add_state(STATE_TURN, self.turn)
        self.gsm.add_state(STATE_RIVER, self.river)
        self.gsm.add_state(STATE_GAMEOVER, self.game_over)

    def __reset_betting_round_state(self):
        """
        Resets any members tracking data for the current round of betting. Can
        be used both when starting a new game or moving on to the next round
        of betting within an existing game.
        """
        logger.debug("Resetting betting round state.")
        self.__last_actor = self.dealer  # overridden in __collect_blinds
        self.__last_raise_amount = None
        self.__current_bet = Currency(0)
        self.__raise_count = 0

    def __finish_betting_round(self):
        """ Finish this round """

        # get bets from all players, pass then to the potmanager
        amounts = defaultdict(list)
        for player in self.players:
            amount = player.new_round()
            if amount > 0:
                amounts[amount].append(player)

        self.pot_mgr.add(amounts)

    def preflop(self):
        """ Initiate preflop game state. """
        logger.info("Preflop")
        self._check_if_finished()
        self.__collect_blinds()
        self.__deal_hole_cards()
        self.__continue_betting_round()

    def __collect_blinds(self):
        """
        Collect blinds from the players who agreed to post them.
        (normally handled by the table)
        """
        self._check_if_finished()

        # -1 is used as the pre-betting round
        self.small_blind.bet(self.limit.small_blind, -1)
        logger.info(
            "Table %s: %s posts the small blind: %s",
            self.__get_table_id(),
            self.small_blind.username,
            self.limit.small_blind,
        )

        # -1 is used as the pre-betting round
        self.big_blind.bet(self.limit.big_blind, -1)
        logger.info(
            "Table %s: %s posts the big blind: %s", self.__get_table_id(), self.big_blind.username, self.limit.big_blind
        )

        self.__last_actor = self.big_blind
        self.__current_bet = self.limit.big_blind
        # TODO!!!!!!!!!!!!
        # odd todo - what's this about? -kmd
        # self.pot_mgr.pots[0].bet_to_match = self.limit.big_blind

    def __deal_hole_cards(self):
        """ Deal 2 cards face down to each player. """
        self._check_if_finished()
        for i in range(2):  # execute loop twice
            for p in self.players:
                card = self._deck.draw_card()
                p.cards.append(card)

        # Send out notifications, done separately so we only have to
        # send one event containing both cards:
        for p in self.players:
            hole_card_event = HoleCardsDealt(self.table, p.cards)
            self.table.notify(p.username, hole_card_event)

    def __continue_betting_round(self):
        """
        Check if all players have either folded or contributed their share to
        the pot. If not, find the next to act and prompt them. If so, advance
        the game state.
        """
        # TODO: handle all-ins
        self._check_if_finished()

        logger.debug("active players: %s", len(self.active_players))
        # Check if everyone has folded:
        if len(self.active_players) == 1:
            self.__finish_betting_round()
            self.game_over()
            return

        last_actor_position = self.__positions[self.__last_actor]
        next_to_act = find_next_to_act(self.players, last_actor_position, self.__raise_count)

        if next_to_act is not None:
            in_pot = next_to_act.current_bet

            options = self.limit.create_actions(
                next_to_act, in_pot, self.__current_bet, self.__get_bet_level(), self.__last_raise_amount
            )
            self.prompt_player(next_to_act, options)
            return

        logger.debug("Betting round complete.")
        self.advance()

    def __get_bet_level(self):
        """
        Bet Level is somewhat of a hack to indicate to the code when
        we're using the small bet or the big bet of the limit.
        """
        # Set the bet level to 1 on preflop and on the flop, 2 otherwise:
        bet_level = 2
        if self.gsm.get_current_state() == STATE_PREFLOP or self.gsm.get_current_state() == STATE_FLOP:
            bet_level = 1
        return bet_level

    def __get_table_id(self):
        """ Handy for logger statements."""
        if self.table != None:
            return self.table.id
        return "None"

    def flop(self):
        """
        Deal the flop and initiate the betting.
        """
        logger.info("Table %s: Dealing the flop." % self.__get_table_id())
        self._check_if_finished()

        for i in range(3):
            self.community_cards.append(self._deck.draw_card())

        event = CommunityCardsDealt(self.table, self.community_cards)
        self.table.notify_all(event)
        self.__continue_betting_round()

    def turn(self):
        """ Deal the turn and initiate the betting. """
        self._check_if_finished()
        turn_card = self._deck.draw_card()
        self.community_cards.append(turn_card)

        event = CommunityCardsDealt(self.table, [turn_card])
        self.table.notify_all(event)
        self.__continue_betting_round()

    def river(self):
        """ Deal the river and initiate the betting. """
        self._check_if_finished()
        river_card = self._deck.draw_card()
        self.community_cards.append(river_card)

        event = CommunityCardsDealt(self.table, [river_card])
        self.table.notify_all(event)
        self.__continue_betting_round()

    def prompt_player(self, player, actions_list):
        """ Prompt the player with a list of actions. """
        self._check_if_finished()
        if player in self.pending_actions.keys():
            # Shouldn't happen, but just in case:
            logger.error("Error adding pending actions for player: " + str(player))
            logger.error("   Pre-existing pending actions: " + str(self.pending_actions[player]))
            raise RounderException("Pending actions already exist")

        self.pending_actions[player] = actions_list

        # If player has sat out, simulate a fold here:
        if player.sitting_out:
            logger.debug("   player is sitting out, simulating fold.")
            fold = find_action_in_list(Fold, actions_list)
            self.process_action(player, fold)
        else:
            logger.debug("Prompting %s with actions: %s" % (player.username, actions_list))
            # TODO: Two prompt calls here, should probably be one:
            player.prompt(actions_list)
            if self.table != None:
                self.table.prompt_player(player, actions_list)

    def sit_out(self, player):
        """
        Handle a player sitting out.

        Note this method is called by the table object which has
        already actually marked the player object as sitting out. Here we
        just deal with any mess related to ongoing action that may have
        involved the player.
        """
        # If the player sitting out is the one we were currently awaiting a
        # response from, simulate a fold:
        logger.debug("Player sitting out: %s" % player.username)
        if player in self.pending_actions.keys():
            logger.debug("   player had pending actions, simulating fold.")
            fold = find_action_in_list(Fold, self.pending_actions[player])
            self.process_action(player, fold)

    def process_action(self, player, action):
        logger.info("Incoming action: " + str(action))
        self._check_if_finished()

        if isinstance(action, Call):
            req_amount = self.__current_bet - player.current_bet
            if req_amount > player.chips:
                req_amount = player.chips

            player.bet(req_amount, self.__raise_count)

            if action.amount == 0:
                logger.debug("Table %s: %s checks" % (self.table.id, player.username))
            event = PlayerCalled(self.table, player.username, action.amount)
            self.table.notify_all(event)

        if isinstance(action, Raise):
            req_amount = self.__current_bet + action.amount - player.current_bet
            self.__raise_count += 1
            self.__last_raise_amount = req_amount

            # This is also checked in limit.py, but I think here makes more
            # sense:
            if action.amount > player.chips:
                raise InvalidPlay("Player tried to raise more then their chips")

            self.__current_bet += action.amount
            player.bet(req_amount, self.__raise_count)

            event = PlayerRaised(self.table, player.username, action.amount)
            self.table.notify_all(event)

        if isinstance(action, Fold):
            player.folded = True
            self.pot_mgr.fold(player)
            event = PlayerFolded(self.table, player.username)
            self.table.notify_all(event)

        # Remove this player from the pending actions map:
        self.__last_actor = player
        self.pending_actions.pop(player)
        player.clear_pending_actions()
        self.__continue_betting_round()

    def advance(self):
        """
        Check if we no longer have any actions pending and advance the
        game state if possible.
        """
        self._check_if_finished()

        if len(self.pending_actions.keys()) == 0:
            logger.debug("No actions pending, advancing game.")
            self.__reset_betting_round_state()
            self.__finish_betting_round()
            self.gsm.advance()

        else:
            logger.debug("Actions still pending:")
            for p in self.pending_actions.keys():
                logger.debug("   " + p.username + " " + str(self.pending_actions[p]))

    def game_over(self):
        """
        Finalize this game and return control to our parent object.
        (usually a table)
        """
        logger.info("Game over.")
        self._check_if_finished()
        self.finished = True

        # Before calculating winners, notify clients that the game is ending:
        event = GameEnding(self.table)
        self.table.notify_all(event)

        players = filter(lambda x: x.folded == False, self.players)
        if len(players) > 1:
            # Show hole cards for anyone who hasn't folded:
            # TODO: Implement optional showing of cards before we process hand
            # winners here?
            # TODO: Start with last player to push the action, or small
            # blind, and
            # loop through from there:
            for p in self.players:
                if p.in_hand:
                    event = PlayerShowedCards(self.table, p.username, p.cards)
                    self.table.notify_all(event)

        board = []
        for c in self.community_cards:
            board.append(str(c).lower())

        # List of tuples, (PotState, [PotWinner, ...]):
        results = []

        for pot in self.pot_mgr.pots:
            winners = []
            players = filter(lambda x: x.folded == False, pot.players)

            cards = self.__cards_for_players(players)
            result = get_winners(cards, board)

            for index, hand in result:
                logger.debug("%s wins with %s" % (players[index].username, hand))
                winners.append((players[index], str(hand)))

            pot_winners = self.__payout_pot(pot, winners)
            results.append((PotState(pot), pot_winners))

        event = GameOver(self.table, results)
        self.table.notify_all(event)

        for p in self.players:
            p.reset()

        # TODO: safe way to return without building a neverending callstack?
        self.callback()

    def __payout_pot(self, pot, players):
        """
        Split the pot as evenly as possible amongst the list of winners.

        Potential remaining cent is given to first player in the list.

        Returns PotWinner dto's for this pot.
        """
        player_count = len(players)

        assert (player_count > 0, "No Winner for this pot?")

        pot_winners = []
        if player_count == 1:
            player, hand = players[0]
            logger.info("Single winner of pot amount %d" % pot.amount)
            player.add_chips(pot.amount)
            pot_winners.append(PotWinner(player.username, pot.amount, hand))
        else:
            logger.info("Splitting pot of %d between %d winners" % (pot.amount, player_count))
            per_player = pot.amount / player_count
            remainder = pot.amount - (per_player * player_count)

            for player, hand in players:
                winnings = per_player
                if remainder:
                    winnings += 0.01
                    remainder -= 0.01

                player.add_chips(winnings)
                pot_winners.append(PotWinner(player.username, winnings, hand))
                logger.info("%s won %s" % (player.username, winnings))

        return pot_winners

    def __cards_for_players(self, players):
        pockets = []
        for player in players:
            cards = []
            cards.append(str(player.cards[0]).lower())
            cards.append(str(player.cards[1]).lower())
            pockets.append(cards)
        return pockets
示例#4
0
 def test_has_more_cards(self):
     d = Deck()
     c = []
     while d.has_more_cards():
         c.append(d.draw_card())
     self.assertEquals(52, len(c))