From: W. Trevor King Date: Tue, 7 Dec 2010 15:57:52 +0000 (-0500) Subject: Add docstrings and doctests to table.py + Python cleanups. X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=ed7812e180e6b477c412f364fac493335c4fcd09;p=poker.git Add docstrings and doctests to table.py + Python cleanups. --- diff --git a/GAMEPLAY.py b/GAMEPLAY.py deleted file mode 100644 index 101f249..0000000 --- a/GAMEPLAY.py +++ /dev/null @@ -1,235 +0,0 @@ -from DECK import * -GLOBAL_TXT = [] - -# *************************************** -from os import system, kill -from popen2 import popen2,popen3 -class player: - def __init__(self, name='', brain=''): - self.cash = 0 - self.owe = 0 - self.option= True - self.hole = [] - self.IN, self.OUT = 0,0 - self.brain = brain.strip() - self.name = name+' ' - self.FOLD = False - self.score = 0 - self.GP_BUFFER = [] - - if self.brain: - self.OUT, self.IN = popen2("./"+self.brain) - - def die(self): - self.IN.write("END\n"); self.IN.flush() - system("pkill -9 "+self.name) - - def bet(self, wager, other_player): - wager = int(wager) - if wager > self.cash: wager = self.cash - if wager < 0: wager = 0 - if wager-self.owe > other_player.cash: - wager = other_player.cash - self.cash -= wager - self.owe -= wager - self.owe = self.owe if self.owe < 0 else 0 - GP("ACTION Player ", self.name, " bets: ", wager) - return wager - - def record_info(self, pot, min_raise, flop, turn, river, other_player): - global GLOBAL_TXT - self.IN.write("INFO NAME " + str(self.name) + '\n'); self.IN.flush() - self.IN.write("INFO ONAME " + str(other_player.name) + '\n'); self.IN.flush() - self.IN.write("INFO STACK " + str(self.cash) + '\n'); self.IN.flush() - self.IN.write("INFO OSTACK " + str(other_player.cash) + '\n'); self.IN.flush() - self.IN.write("INFO POT " + str(pot) + '\n'); self.IN.flush() - self.IN.write("INFO MINRAISE " + str(min_raise)+'\n'); self.IN.flush() - self.IN.write("INFO OWE " + str(self.owe) + '\n'); self.IN.flush() - self.IN.write("INFO HOLE " + pp_hand(self.hole) + '\n'); self.IN.flush() - self.IN.write("INFO FLOP " + pp_hand(flop) + '\n'); self.IN.flush() - self.IN.write("INFO TURN " + pp_hand(turn) + '\n'); self.IN.flush() - self.IN.write("INFO RIVER " + pp_hand(river) + '\n'); self.IN.flush() - self.GP_FLUSH(GLOBAL_TXT) - - def GP_FLUSH(self, TXT): - for g in TXT: - if g not in self.GP_BUFFER: - self.GP_BUFFER.append(g) - self.IN.write(g + "\n") - self.IN.flush() - - def human_play(self, pot, min_raise, flop, turn, river, other_player): - if GLOBAL_TXT: print GLOBAL_TXT[-1] - print "[", pp_hand(self.hole), "] Board: ", - print "[", pp_hand(flop), pp_hand(turn), pp_hand(river), "]" - print "POT: ", pot, " OWE: ", self.owe, " MIN_RAISE: ", min_raise, - print " | ", "STACK: ", self.cash, "Opp. STACK: ", other_player.cash - return raw_input("What do you want to bet, " + self.name + ": ") - - def decide(self, pot, min_raise, flop, turn, river, other_player,endgame=False): - - in_bet = '' - - if not self.brain: - in_bet = self.human_play(pot,min_raise, flop, turn, river, other_player) - - else: - self.record_info(pot,min_raise,flop,turn,river,other_player) - self.IN.write("MOVE \n"); self.IN.flush() - in_bet = self.OUT.readline().strip() - - if endgame: return False - - if in_bet.isalpha(): - in_bet = in_bet.upper() - if in_bet == 'A': in_bet = self.cash - elif in_bet == 'C': in_bet = self.owe - else : self.FOLD = True - else: - try : in_bet = int(in_bet) - except: self.FOLD = True - - if self.FOLD: return False - - if in_bet >= self.cash or in_bet == self.owe or in_bet >= min_raise: - out_bet = self.bet(in_bet, other_player) - other_player.owe += out_bet - return out_bet - - # Bets between whats owed and min_raise are considered a call - if in_bet >= self.owe and in_bet < min_raise: - out_bet = self.bet(self.owe, other_player) - other_player.owe += out_bet - return out_bet - - # Illegal bets will be counted as folds! - GP("ACTION Player ", self.name, "bets ", str(in_bet), " illegally and FOLDS. Valid bets are ", str(self.owe), " or anything >= ", min_raise) - self.FOLD = True - return False - - -def GP(*S): GLOBAL_TXT.append(''.join(map(str,S))) -def returnGP(): return GLOBAL_TXT - -def hand_judgement(A,B, board, pot): - if A.FOLD: - GP("ACTION Player ", A.name, " folds" ) - GP("ACTION Player ", B.name, " wins: ", pot) - B.cash += pot - return GLOBAL_TXT - - elif B.FOLD: - GP("ACTION Player ", B.name, "folds") - GP("ACTION Player ", A.name, "wins: ", pot) - A.cash += pot - return GLOBAL_TXT - - S = HOLDEM_score(A, B, board) - - if S == 1: - GP("ACTION Player ", A.name, "wins: ", pot) - A.cash += pot - return GLOBAL_TXT - - elif S ==-1: - GP( "ACTION Player ", B.name, "wins: ", pot) - B.cash += pot - return GLOBAL_TXT - - elif S == 0: - GP("ACTION Split pot ") - split_pot, carry = pot/2, pot%2 - A.cash += split_pot + carry - B.cash += split_pot - return GLOBAL_TXT - -def checkFOLD(A,B): - if A.FOLD or B.FOLD: return True - return False - -def betting_round(P,pot,min_raise,flop,turn,river): - for player in P: - player.option = True - - while((P[0].owe>0 or P[1].owe>0) or (P[0].option or P[1].option) and (P[0].cash)): - action = P[0].decide(pot, min_raise, flop,turn,river, P[-1]) - P[0].option = False - - if action >= min_raise: min_raise = 2*action - if checkFOLD(P[0],P[1]): return pot - - pot += action - P = [P[-1]] + P[:-1] # Cycle the player order - return pot - - -def gameplay(DECK, A, B, smallB, ): - global GLOBAL_TXT - - # Shuffle the deck and deal two cards to each player - shuffle(DECK) - - A.hole = [DECK[n] for n in xrange(2) ] - B.hole = [DECK[n] for n in xrange(2,4)] - board = [DECK[n] for n in xrange(4,9)] - - flop, turn, river = '', '', '' - GLOBAL_TXT = [] - - pot = 0 - A.owe = 2*smallB - B.owe = smallB - A.FOLD, B.FOLD = False, False - - # Handle the case if a player can't post the blinds completely, ALL_IN - if B.owe > B.cash: - pot += B.bet(B.owe, A) - pot += A.bet(pot, B) - return hand_judgement(A,B,board,pot) - elif A.owe > A.cash: - pot += A.bet(A.owe, B) - pot += B.bet(pot, A) - return hand_judgement(A,B,board,pot) - - # Both players can post the blinds - pot += A.bet(A.owe, B) - pot += B.bet(B.owe, A) - - # PRE-FLOP ACTION - Player B is small blind, first to act - B.owe += smallB - A.owe -= smallB - min_raise = smallB*4 - play_order = [B,A] - pot = betting_round(play_order,pot,min_raise,flop,turn,river) - if checkFOLD(A,B): return hand_judgement(A,B,board,pot) - if not A.cash or not B.cash: return hand_judgement(A,B,board,pot) - - # FLOP ACTION - Player A is now first to act - flop = board[:3] - GP( "ACTION FLOP ", pp_hand(flop) ) - A.owe, B.owe = 0, 0 - min_raise = smallB*2 - play_order = [A,B] - pot = betting_round(play_order,pot,min_raise,flop,turn,river) - if checkFOLD(A,B): return hand_judgement(A,B,board,pot) - if not A.cash or not B.cash: return hand_judgement(A,B,board,pot) - - # TURN ACTION - turn = board[3:4] - GP( "ACTION TURN ", pp_hand(turn) ) - A.owe, B.owe = 0, 0 - play_order = [A,B] - pot = betting_round(play_order,pot,min_raise,flop,turn,river) - if checkFOLD(A,B): return hand_judgement(A,B,board,pot) - if not A.cash or not B.cash: return hand_judgement(A,B,board,pot) - - # RIVER ACTION - river = board[4:5] - GP( "ACTION RIVER ", pp_hand(river)) - A.owe, B.owe = 0, 0 - play_order = [A,B] - pot = betting_round(play_order,pot,min_raise,flop,turn,river) - if checkFOLD(A,B): return hand_judgement(A,B,board,pot) - - # SHOWDOWN! - return hand_judgement(A,B,board,pot) diff --git a/table.py b/table.py new file mode 100644 index 0000000..98f6b44 --- /dev/null +++ b/table.py @@ -0,0 +1,500 @@ +"""Define a poker table with players. +""" + +from math import floor +import random +import sys +import subprocess + +from deck import pp_hand, SevenChooseFiveHand + + +class IllegalBet (ValueError): + """Raised on invalid bet attempts. + """ + def __init__(self, player, bet): + super(IllegalBet, self).__init__(bet) + self.player = player + self.bet = bet + + def __str__(self): + return 'Illegal bet %s by %s' % (self.player, self.name) + + +class Player (object): + """Poker player. + """ + def __init__(self, cash=0, name='', brain=None): + self.cash = cash # uncommitted chips + self.name = name + self.brain = brain + self._proc = None + self.clear_log() + + if self.brain: + self._proc = subprocess.Popen( + './'+self.brain, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + + def __str__(self): + return self.name + + def kill(self): + if self._proc != None: + self._proc.stdin.write('END\n') + self._proc.stdin.flush() + exit_code = self._proc.wait() + assert exit_code == 0, 'Error exiting from %s: %d' % ( + self, exit_code) + self._proc = None + + def __str__(self): + return self.name + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, self.name) + + def __cmp__(self, other): + if other == None: + return 1 + return cmp(self.name, other.name) + + def cmp_hands(self, other): + return cmp(self.hand, other.hand) + + def new_hand(self): + self.clear_log() + self.owe = 0 # amount required to call + self.pot = 0 # amount committed to the pot this round + self.status = 'in' # one of ['in', 'all in', 'fold'] + self.hole = [] # pair of down cards + self.hand = None # holds the player's hand in the event of a showdown + + def active(self): + """Does this player still need to think?""" + return self.status not in ['all in', 'fold'] + + def pay(self, wager): + """Settle what you owe the table. + + Called during play as well as at the end of a hand. + """ + if wager >= self.cash: + self.status = 'all in' # may be forced all-in (e.g. by a blind) + wager = self.cash + wager = max(wager, 0) + self.cash -= wager + self.owe -= wager + self.owe = min(self.owe, 0) + return wager + + def _log_flush(self, stream, log): + """Show the brain any new log entries.""" + new_index = len(log) + for line in LOG[self._next_log_index:new_index]: + stream.write(line + '\n') + stream.flush() + self._next_log_index = new_index + + def clear_log(self): + self._next_log_index = 0 # unsent log index + + def _record_info(self, active_players, pot, min_raise, flop, turn, river, + log): + """Show the brain the current state of events.""" + self._proc.stdin.write('INFO NAME %s\n' % str(self.name)) + self._proc.stdin.write('INFO STACK %s\n' % str(self.cash)) + self._proc.stdin.write('INFO POT %s\n' % str(pot)) + self._proc.stdin.write('INFO MINRAISE %s\n' % str(min_raise)) + self._proc.stdin.write('INFO OWE %s\n' % str(self.owe)) + self._proc.stdin.write('INFO HOLE %s\n' % pp_hand(self.hole)) + self._proc.stdin.write('INFO FLOP %s\n' % pp_hand(flop)) + self._proc.stdin.write('INFO TURN %s\n' % pp_hand(turn)) + self._proc.stdin.write('INFO RIVER %s\n' % pp_hand(river)) + self._proc.stdin.flush() + self._log_flush(self._proc.stdin, log) + + def _human_play(self, active_players, pot, min_raise, flop, turn, river, + log): + """Ask the human at `sys.stdin`/`sys.stdout` for guidance.""" + self._log_flush(sys.stdout, log) + print '[', pp_hand(self.hole), '] Board: ', + print '[', pp_hand(flop), pp_hand(turn), pp_hand(river), ']' + print 'POT: ', pot, ' OWE: ', self.owe, ' MIN_RAISE: ', min_raise, + print ' | ', 'STACK: ', self.cash + for player in active_players: + if player != self: + print 'Opp. %s STACK: %d' % (player, player.cash) + return raw_input('What do you want to bet, %s:' % self) + + def _bet(self, **kwargs): + if not self.brain: + return self._human_play(**kwargs) + else: + self._record_info(**kwargs) + self._proc.stdin.write('MOVE\n') + self._proc.stdin.flush() + return self._proc.stdout.readline().strip() + + def decide(self, **kwargs): + """Decide on a course of action (raise, fold, ...). + + The program accepts an (A) as an all-in bet, (C) as a call or + check and any valid bet. Valid bets are either the amount + owed to the pot or at least the min raise. All bets made + greater then the players stack will be considered an all-in. + All bets that are less than the amount owed will be considered + a fold. A bet in the range of the amount owed to the min + raise will be considered a CALL and the player will put in the + amount owed to the table. Any other input (negative, decimal, + or alpha) will be considered to be a FOLD. To keep a + presistant state (and for speed reasons) your program will + have the information fed to it. This is done by piping + `stdin` and `stdout` to your program. The dealer will wait + once the command `MOVE` is sent. Along with the `MOVE` tag, + your code will be given information on the current game state + and history. See `Player.record_info()` for details. + """ + bet = self._bet(**kwargs) + if bet.isalpha(): + bet = bet.upper() + if bet == 'A': bet = self.cash + elif bet == 'C': bet = self.owe + else : self.status = 'fold' + else: + try : bet = int(bet) + except: self.status = 'fold' + + if bet >= self.cash: + self.status = 'all in' + return self.cash + elif bet < self.owe: + self.status = 'fold' + return 0 + elif self.owe <= bet and bet < min_raise: # round down to a check + return self.owe + elif self.owe >= min_raise: # let's see some action! + return bet + raise IllegalBet(self, str(bet)) + + +class Blinds (object): + """Blind schedule and per-hand blind calculation. + + >>> b = Blinds(blinds=[1, 2, 4, 8], hand_clock=10) + >>> b(0) + (1, 2, 2) + >>> b(9) + (1, 2, 2) + >>> b(10) + (2, 4, 4) + >>> b(100) + (8, 16, 16) + """ + def __init__(self, blinds, hand_clock): + self._blinds = blinds + self._hand_clock = hand_clock + + def __call__(self, hand): + """Small blind, big blind, and min raise for the `hand`th hand. + """ + i = min(hand / self._hand_clock, len(self._blinds)-1) + small_blind = self._blinds[i] + return (small_blind, small_blind*2, small_blind*2) + + +class Table (object): + """A poker table with a deck, players, and blind schedule. + + Setup buffered players so we can control `Table` testing. + + >>> class BufferedPlayer(Player): + ... def __init__(self, **kwargs): + ... super(BufferedPlayer, self).__init__(**kwargs) + ... self.buffer = [] + ... def _bet(self, **kwargs): + ... return self.buffer.pop(0) + >>> players = [BufferedPlayer(cash=10, name='P%d' % (i+1)) + ... for i in range(3)] + >>> t = Table(players=players, blinds=Blinds((1, 2, 4), 3)) + + Stack the deck so we know what's coming. + >>> from deck import unpp_card + >>> t.deck = [unpp_card(c) + ... for c in 'As Ks Qs Js Ts 9s 8s 7s 6s 5s 4s'.split()] + + Everyone folds the first round (P3, going last, will win). + >>> for p in t.players: + ... p.buffer = ['-1'] + >>> t.play_round(shuffle=False) + >>> print '\\n'.join(t.log) # doctest: +REPORT_UDIFF + INFO CHIPCOUNT Player P1 10 + INFO CHIPCOUNT Player P2 10 + INFO CHIPCOUNT Player P3 10 + ACTION Player P2 (pot 1) blinds 1 + ACTION Player P3 (pot 2) blinds 2 + ACTION Player P1 (pot 0) folds + ACTION Player P2 (pot 1) folds + ACTION Player P3 wins: 3 + INFO GAMEOVER 0 + + Play another round on the same deck, everyone goes all in. P3 + will win with the higher straight flush. + >>> for p in t.players: + ... p.buffer = ['a'] + >>> t.play_round(shuffle=False) + >>> print '\\n'.join(t.log) # doctest: +REPORT_UDIFF + INFO CHIPCOUNT Player P1 10 + INFO CHIPCOUNT Player P2 9 + INFO CHIPCOUNT Player P3 11 + ACTION Player P3 (pot 1) blinds 1 + ACTION Player P1 (pot 2) blinds 2 + ACTION Player P2 (pot 9) meets and raises for a total of 9 + ACTION Player P3 (pot 11) goes all in with 10 + ACTION Player P1 (pot 10) goes all in with 8 + ACTION Player P3 wins: 30 + INFO board 8s 7s 6s 5s 4s + INFO Player P3 hole: Ts 9s best: straight flush - Ts 9s 8s 7s 6s 5s 4s cash: 30 + INFO Player P1 hole: As Ks best: straight flush - 8s 7s 6s 5s 4s As Ks cash: 0 + INFO Player P2 hole: Qs Js best: straight flush - 8s 7s 6s 5s 4s Qs Js cash: 0 + INFO GAMEOVER 1 + INFO Player P1 died after hand 2 + INFO Player P2 died after hand 2 + + >>> t.dead_players + [(, )] + >>> t.players + [] + """ + def __init__(self, deck=None, players=None, blinds=None): + self.deck = deck + self.players = players # list of surviving players + self.blinds = blinds + self.dealer = 0 # index of player acting as dealer (dealer button) + self.hand_count = 0 + self.dead_players = [] # list of players in the order they died + # e.g. [(lastplayer,), (AtiedForThird, BtiedForThird), (secondplayer,)] + + def players_from(self, index): + """Rotate the list of players to start from `self.players[index]`. + + >>> t = Table(players=['a', 'b', 'c', 'd']) + >>> t.players_from(0) + ['a', 'b', 'c', 'd'] + >>> t.players_from(1) + ['b', 'c', 'd', 'a'] + >>> t.players_from(2) + ['c', 'd', 'a', 'b'] + """ + index = index % len(self.players) + return self.players[index:]+self.players[:index] + + def play_round(self, shuffle=random.shuffle): + self.deal(shuffle=shuffle) + self.ante_up() + # PRE-FLOP ACTION + self.betting_round(first_round=True) + if self.hand_complete(): return self.judge() + # FLOP ACTION + self.flop = self.board[:3] + self.log.append('ACTION FLOP %s' % pp_hand(self.flop)) + self.betting_round() + if self.hand_complete(): return self.judge() + # TURN ACTION + self.turn = self.board[3:4] + self.log.append('ACTION TURN %s' % pp_hand(self.turn)) + self.betting_round() + if self.hand_complete(): return self.judge() + # RIVER ACTION + self.river = self.board[4:5] + self.log.append('ACTION RIVER %s' % pp_hand(self.river)) + self.betting_round() + if self.hand_complete(): return self.judge() + # SHOWDOWN! + self.judge() + + def deal(self, shuffle=random.shuffle): + """Shuffle the deck and deal two cards to each player.""" + self.log = [] + if shuffle: + shuffle(self.deck) + N = len(self.players) + self.board = [self.deck[n] for n in xrange(2*N,2*N+5)] # common cards + for i,player in enumerate(self.players): + player.new_hand() + self.log.append('INFO CHIPCOUNT Player %s %s' + % (player, player.cash)) + player.hole = [self.deck[n] for n in xrange(2*i, 2*(i+1))] + player.hand = SevenChooseFiveHand(player.hole+self.board) + self.flop = self.turn = self.river = None + + def ante_up(self): + self.small_blind,self.big_blind,self.min_raise = self.blinds( + self.hand_count) + self.pot = 0 # total chips up for grabs + self.per_player_pot = 0 # needed to stay active + if len(self.players) > 2: + sb,bb = self.players_from(self.dealer+1)[0:2] + elif len(self.players) == 2: + # When only two players remain, special 'head-to-head' or + # 'heads up' rules are enforced and the blinds are posted + # differently. In this case, the person with the dealer + # button posts the small blind, while his/her opponent + # places the big blind. + sb,bb = self.players_from(self.dealer)[0:2] + else: + raise Exception, "can't play with %d players" % len(self.players) + sb.owe = self.small_blind + self.square_player(sb, 'blinds') + bb.owe = self.big_blind + self.square_player(bb, 'blinds') + + def square_player(self, player, verb, append_cost=True): + contribution = player.pay(player.owe) + self.pot += contribution + player.pot += contribution + self.per_player_pot = max(self.per_player_pot, player.pot) + string = 'ACTION Player %s (pot %s) %s' % (player, player.pot, verb) + if append_cost: string += ' %d' % contribution + self.log.append(string) + return contribution + + def betting_round(self, first_round=False): + all_in = [p for p in self.players if p.status == 'all in'] + folded = [p for p in self.players if p.status == 'fold'] + + if len(self.players) > 2: + if first_round == True: # start after the big blind + order = self.players_from(self.dealer+3) + else: # start with the small blind + order = self.players_from(self.dealer+1) + elif len(self.players) == 2: # 'head-to-head' rules + # The dealer acts first before the flop. After the flop, + # the dealer acts last and continues to do so for the + # remainder of the hand. + if first_round == True: + order = self.players_from(self.dealer) + else: + order = self.players_from(self.dealer+1) + + active = [p for p in order if p.active()] + tail = active[-1] + min_raise = self.min_raise + raiser = None # no-one has raised yet this round + while (len(active) > 0): # i.e. continue until everyone folds/calls + player = active.pop(0) + player.owe = self.per_player_pot - player.pot + if player == raiser or (raiser == None + and player == tail and len(active) == 0): + break + try: + action = player.decide( + active_players=[player]+active+all_in, pot=self.pot, + min_raise=min_raise, flop=self.flop, turn=self.turn, + river=self.river) + except IllegalBet, e: + self.log.append(( + 'ACTION Player %s bets %s illegally and FOLDS. ' + 'Valid bets are %d or anything >= %d') + % (e.player, e.bet, e.player.owe, min_raise)) + player.status = 'fold' + if player.status == 'fold': + player.owe = 0 # don't charge the folding player + folded.append(player) + self.square_player(player, 'folds', append_cost=False) + else: # settle up + if action >= min_raise: + min_raise = 2*action + raiser = player + active.append(player) + verb = 'meets and raises for a total of' + elif player.status == 'all in': + all_in.append(player) + verb = 'goes all in with' + else: + assert action == 0, action + verb = 'checks with' + if player == tail and action == 0: + active = [] + active.append(player) + player.owe += action + self.square_player(player, verb) + + def hand_complete(self): + """Return `True` if there aren't enough active players for betting. + """ + return len([p for p in self.players if p.active()]) <= 1 + + def judge(self): + """Judge the winner and allocate the winnings.""" + runners = [p for p in self.players if p.status != 'fold'] + + if len(runners) == 1: # everyone else folded, no need to show cards + folders = [p for p in self.players if p.status == 'fold'] + self.pay_players(runners, folders) + else: # we need to look show hands + runners.sort(Player.cmp_hands, reverse=True) # sort high -> low + runcp = [p for p in runners] + losers = [p for p in self.players] # copy into a new list + # start from the top, breaking ties... + while len(runners) > 0: + winners = [runners.pop(0)] + losers.remove(winners[-1]) # remove from losers pool + while len(runners) > 0 and runners[0] == winners[0]: + winners.append(runners.pop(0)) # pop any winning tie + losers.remove(winners[-1]) # remove from losers pool + self.pay_players(winners, losers) + # allow the players to view the end result + flop,turn,river = self.board[:3], self.board[3:4], self.board[4:5] + self.log.append('INFO board %s' % pp_hand(self.board)) + for p in runcp: + self.log.append('INFO Player %s hole: %s best: %s cash: %d' + % (p, pp_hand(p.hole), p.hand.pp_score(), p.cash)) + self.log.append('INFO GAMEOVER %d' % self.hand_count) + + # prepare for the next round + dead = [p for p in self.players if p.cash == 0] + if dead: + self.dead_players.append(tuple(dead)) + self.hand_count += 1 + + self.dealer = (self.dealer + 1) % len(self.players) + next_dealing_player = self.players[self.dealer] + while next_dealing_player.cash == 0: # next dealer is dead + # pass the dealer card to successor + self.dealer = (self.dealer + 1) % len(self.players) + next_dealing_player = self.players[self.dealer] + for p in dead: + self.log.append('INFO Player %s died after hand %d' + % (p, self.hand_count)) + self.players.remove(p) + p.kill() + self.dealer = self.players.index(next_dealing_player) + + def pay_players(self, winners, losers): + winning_pots = sorted(set([p.pot for p in winners])) + winnings = dict().fromkeys([p.name for p in winners], 0) + for pot in winning_pots: # smallest to largest winning pots + sum = 0 + for player in winners+losers: + contribution = min(player.pot, pot) + player.pot -= contribution + sum += contribution + per_winner = int(floor(sum / len(winners))) + residual = sum % len(winners) + for player in winners: + player.cash += per_winner + winnings[player.name] += per_winner + # any extra chips go to the first players after the dealer + for player in self.players_from(self.dealer+1): + if residual == 0: break + if player in winners: + player.cash += 1 + winnings[player.name] += 1 + residual -= 1 + # drop the winners who are no longer invested in the pot + winners = [p for p in winners if p.pot > 0] + self.per_player_pot -= pot + for name,take in winnings.items(): + if take > 0: + self.log.append('ACTION Player %s wins: %s' % (name, take))