Add docstrings and doctests to pbot.py + Python cleanups.
authorW. Trevor King <wking@drexel.edu>
Tue, 7 Dec 2010 16:43:02 +0000 (11:43 -0500)
committerW. Trevor King <wking@drexel.edu>
Tue, 7 Dec 2010 16:43:02 +0000 (11:43 -0500)
Also fix a number of bugs that a fixed pbot.py turned up in deck.py
and table.py.

PBOT.py [deleted file]
deck.py
pbot.py [new file with mode: 0755]
table.py

diff --git a/PBOT.py b/PBOT.py
deleted file mode 100644 (file)
index fdcb384..0000000
--- a/PBOT.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import popen2
-from DECK import *
-from GAMEPLAY import *
-global GLOBAL_TXT
-#import psyco; psyco.full()
-
-start_stack = 1000
-blinds      = [1,2,4,8,16,25,37,50]
-hand_clock  = 20
-
-# Command line arguments
-import sys
-
-try   : A = player(sys.argv[1],sys.argv[1]) 
-except: A = player('Meg')
-try   : B = player(sys.argv[2], sys.argv[2])
-except: B = player('Jack')
-try   : tournament_N = int(sys.argv[3])
-except: tournament_N = 1
-
-P = [A,B]
-for n in xrange(tournament_N):
-    A.cash = start_stack
-    B.cash = start_stack
-
-    hand_count,b = 0, 0
-
-    while A.cash > 0 and B.cash > 0:
-        hand_count += 1
-
-        if hand_count % hand_clock==0: 
-            b = b+1 if b < len(blinds)-1 else len(blinds)-1
-            
-        GLOBAL_TXT = gameplay(DECK, P[0],P[1], blinds[b])
-
-        # Allow the players to view the end result
-        board = [DECK[n] for n in xrange(4,9)]
-        flop,turn,river = board[:3], board[3:4], board[4:5]
-        if not A.FOLD and not B.FOLD:
-            GP("INFO ",A.name, pp_hand(A.hole), pp_score(A), ' ',A.cash)
-            GP("INFO ",B.name, pp_hand(B.hole), pp_score(B), ' ',B.cash)
-        GP("INFO GAMEOVER ", hand_count)
-        for p in P: 
-            if p.brain: 
-                p.GP_FLUSH(returnGP())
-                p.GP_BUFFER = []
-          
-        # Human player text
-        if not A.brain or not B.brain: # or True:
-            for g in GLOBAL_TXT: print g
-            print "INFO BOARD ", pp_hand([DECK[n] for n in xrange(4,9)])
-
-        P = [P[-1]] + P[:-1] # Cycle the player order
-
-    if A.cash: print A.name, hand_count
-    else     : print B.name, hand_count
-
-for p in P: 
-    if p.brain: p.die() # Kill the players!
-
-
-
-        
diff --git a/deck.py b/deck.py
index 4f1ca92dbdcc813479adc67d94fbb1dc2d8a3767..364f0e743d39a02dda7434a8344d3162fcdb0906 100644 (file)
--- a/deck.py
+++ b/deck.py
@@ -12,7 +12,7 @@ FACE_CARDS = ['ten', 'jack', 'queen', 'king', 'ace']
 """Ordered list of face cards.
 """
 
-DECK = dict([(c/4, c%4) for c in range(52)])
+DECK = [(c/4, c%4) for c in range(52)]
 """Cards in the deck are stored as (n,m), where `n` is the rank number
 `n=[0,12]`, and `m` is the suit `m=[0,3]`.
 
@@ -20,6 +20,8 @@ Examples:
  (0,1)  is a two of hearts, 
  (11,0) is a king of spades,
  (12,3) is an ace of clubs
+
+>>> DECK[:5]
 """
 
 
diff --git a/pbot.py b/pbot.py
new file mode 100755 (executable)
index 0000000..af7a3fb
--- /dev/null
+++ b/pbot.py
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+
+"""Run a poker championship.
+"""
+
+from table import Player, Blinds, Table
+from deck import DECK
+
+
+def run(players, blinds, start_stack, hand_limit, tournaments, verbose=False):
+    for n in xrange(tournaments):
+        for player in players:
+            player.cash = start_stack
+        table = Table(deck=DECK, players=players, blinds=blinds,
+                      verbose=verbose)
+        while len(table.players) > 1 and table.hand_count < hand_limit:
+            table.play_round()
+            # keep bots up to date, so they can think in parallel
+            for player in players:
+                if player.brain:
+                    player.log_flush(table.log)
+        if len(table.players) == 1:
+            print "INFO WINNER: Player %s" % table.players[0]
+        else:
+            assert table.hand_count >= hand_limit
+            print "INFO Time expired"
+            for player in table.players:
+                print "INFO Tie: Player %s" % player
+
+
+if __name__ == '__main__':
+    from optparse import OptionParser
+
+    usage = '%prog [options] PLAYER PLAYER ...'
+    epilog = ('`PLAYER`s should be name,brain tuples, e.g. (`a,p1.py`).  '
+              'Give just the name for a human playing from the command line.')
+    p = OptionParser(usage=usage, epilog=epilog)
+    p.add_option('-s', '--start-stack', dest='stack', default=1000,
+                 type='int', help='start stack (%default)')
+    p.add_option('-b', '--blinds', dest='blinds',
+                 default='1,2,4,8,16,25,37,50',
+                 help='blind schedule (%default)')
+    p.add_option('-c', '--hand-clock', dest='clock', default=20, type='int',
+                 help='hands between blind increments (%default)')
+    p.add_option('-l', '--hand-limit', dest='limit', default=5000, type='int',
+                 help='hand limit (-1 for unlimited play, %default')
+    p.add_option('-t', '--tournaments', dest='tournaments', default=1,
+                 type='int', help='number of tournaments (%default)')
+    p.add_option('-v', '--verbose', dest='verbose', action='store_true',
+                 help='print the log as it is written')
+
+    options,args = p.parse_args()
+
+    blinds = Blinds(blinds=[int(x) for x in options.blinds.split(',')],
+                    hand_clock=options.clock)
+
+    players = []
+    for arg in args:
+        fields = [x.strip() for x in arg.rsplit(',', 1)]
+        name = fields[0]
+        if len(fields) == 1:
+            brain = None
+        else:
+            brain = fields[1]
+        players.append(Player(name=name, brain=brain))
+
+    try:
+        run(players=players, blinds=blinds, start_stack=options.stack,
+            hand_limit=options.limit, tournaments=options.tournaments,
+            verbose=options.verbose)
+    finally:  # kill the bots
+        for p in players:
+            p.kill()
index 98f6b44f25b4bc96bf6b893687048d64cab02f39..bea4513304a54e2b5b6bb4ff4b65651c5cafc2fb 100644 (file)
--- a/table.py
+++ b/table.py
@@ -88,10 +88,14 @@ class Player (object):
         self.owe   = min(self.owe, 0)
         return wager
 
-    def _log_flush(self, stream, log):
+    def log_flush(self, log):
         """Show the brain any new log entries."""
+        if self._proc:
+            stream = self._proc.stdin
+        else:
+            stream = sys.stdout
         new_index = len(log)
-        for line in LOG[self._next_log_index:new_index]:
+        for line in log[self._next_log_index:new_index]:
             stream.write(line + '\n')
             stream.flush()
         self._next_log_index = new_index
@@ -107,17 +111,17 @@ class Player (object):
         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))
+        for phase,h in [('HOLE', self.hole), ('FLOP', flop),
+                        ('TURN', turn), ('RIVER', river)]:
+            if h:
+                self._proc.stdin.write('INFO %s %s\n' % (phase, pp_hand(h)))
         self._proc.stdin.flush()
-        self._log_flush(self._proc.stdin, log)
+        self.log_flush(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)
+        self.log_flush(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,
@@ -266,10 +270,11 @@ class Table (object):
     >>> t.players
     [<BufferedPlayer P3>]
     """
-    def __init__(self, deck=None, players=None, blinds=None):
+    def __init__(self, deck=None, players=None, blinds=None, verbose=False):
         self.deck = deck
         self.players = players # list of surviving players
         self.blinds = blinds
+        self.verbose = verbose
         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
@@ -289,6 +294,12 @@ class Table (object):
         index = index % len(self.players)
         return self.players[index:]+self.players[:index]
 
+    def _log(self, msg):
+        """Add a message to the table log."""
+        self.log.append(msg)
+        if self.verbose:
+            print msg
+
     def play_round(self, shuffle=random.shuffle):
         self.deal(shuffle=shuffle)
         self.ante_up()
@@ -297,17 +308,17 @@ class Table (object):
         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._log('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._log('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._log('ACTION RIVER %s' % pp_hand(self.river))
         self.betting_round()
         if self.hand_complete(): return self.judge()
         # SHOWDOWN!
@@ -322,7 +333,7 @@ class Table (object):
         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'
+            self._log('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)
@@ -356,7 +367,7 @@ class Table (object):
         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)
+        self._log(string)
         return contribution
 
     def betting_round(self, first_round=False):
@@ -391,9 +402,9 @@ class Table (object):
                 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)
+                    river=self.river, log=self.log)
             except IllegalBet, e:
-                self.log.append((
+                self._log((
                         'ACTION Player %s bets %s illegally and FOLDS. '
                         'Valid bets are %d or anything >= %d')
                                 % (e.player, e.bet, e.player.owe, min_raise))
@@ -446,11 +457,11 @@ class Table (object):
                 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))
+            self._log('INFO board %s' % pp_hand(self.board))
             for p in runcp:
-                self.log.append('INFO Player %s hole: %s   best: %s   cash: %d'
+                self._log('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)
+        self._log('INFO GAMEOVER %d' % self.hand_count)
 
         # prepare for the next round
         dead = [p for p in self.players if p.cash == 0]
@@ -465,10 +476,9 @@ class Table (object):
             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'
+            self._log('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):
@@ -497,4 +507,4 @@ class Table (object):
             self.per_player_pot -= pot
         for name,take in winnings.items():
             if take > 0:
-                self.log.append('ACTION Player %s wins: %s' % (name, take))
+                self._log('ACTION Player %s wins: %s' % (name, take))