+++ /dev/null
-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)
--- /dev/null
+"""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
+ [(<BufferedPlayer P1>, <BufferedPlayer P2>)]
+ >>> t.players
+ [<BufferedPlayer P3>]
+ """
+ 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))