cosmetics, comments, some value checking

added mucked / folded / shown summary lines
This commit is contained in:
Matt Turnbull 2008-12-10 16:30:57 +00:00
parent 3ff2ec2106
commit dda88b76ba
2 changed files with 131 additions and 94 deletions

View File

@ -68,7 +68,7 @@ class Everleaf(HandHistoryConverter):
print "Initialising Everleaf converter class" print "Initialising Everleaf converter class"
HandHistoryConverter.__init__(self, config, file, sitename="Everleaf") # Call super class init. HandHistoryConverter.__init__(self, config, file, sitename="Everleaf") # Call super class init.
self.sitename = "Everleaf" self.sitename = "Everleaf"
self.setFileType("text") self.setFileType("text", "cp1252")
self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)') self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n+') self.rexx.setSplitHandRegex('\n\n+')
self.rexx.setHandInfoRegex('.*#(?P<HID>[0-9]+)\n.*\nBlinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+)\nTable (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)') self.rexx.setHandInfoRegex('.*#(?P<HID>[0-9]+)\n.*\nBlinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+)\nTable (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
@ -118,9 +118,7 @@ class Everleaf(HandHistoryConverter):
def readPlayerStacks(self, hand): def readPlayerStacks(self, hand):
m = self.rexx.player_info_re.finditer(hand.string) m = self.rexx.player_info_re.finditer(hand.string)
players = [] players = []
#print "\nReading stacks - players seen:"
for a in m: for a in m:
#print a.group('PNAME')
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand): def markStreets(self, hand):
@ -132,28 +130,11 @@ class Everleaf(HandHistoryConverter):
r"(\*\* Dealing Flop \*\* \[ \S\S, \S\S, \S\S \](?P<FLOP>.+(?=\*\* Dealing Turn \*\*)|.+))?" r"(\*\* Dealing Flop \*\* \[ \S\S, \S\S, \S\S \](?P<FLOP>.+(?=\*\* Dealing Turn \*\*)|.+))?"
r"(\*\* Dealing Turn \*\* \[ \S\S \](?P<TURN>.+(?=\*\* Dealing River \*\*)|.+))?" r"(\*\* Dealing Turn \*\* \[ \S\S \](?P<TURN>.+(?=\*\* Dealing River \*\*)|.+))?"
r"(\*\* Dealing River \*\* \[ \S\S \](?P<RIVER>.+))?", hand.string,re.DOTALL) r"(\*\* Dealing River \*\* \[ \S\S \](?P<RIVER>.+))?", hand.string,re.DOTALL)
# that wasn't easy.
#m1 = re.search(r'(\*\* Dealing down cards \*\*)?(?P<PREFLOP>.*?\n\*\*)',hand.string,re.DOTALL)
#m2 = re.search(r'(\*\* Dealing Flop \*\*)?(?P<FLOP>.*\n\*\*)',hand.string,re.DOTALL)
#print hand.string
#print "m groups:\n",m.groupdict()
#print "m1 groups:\n",m1.groupdict()
#print "m2 groups:\n",m2.groupdict()
#(\*\* Dealing Flop \*\* \[ (?P<FLOP1>\S\S), (?P<FLOP2>\S\S), (?P<FLOP3>\S\S) \](?P<FLOP>.*?)?)?
#(\*\* Dealing Turn \*\* \[ (?P<TURN1>\S\S) \](?P<TURN>.*?))?
#(\*\* Dealing River \*\* \[ (?P<RIVER1>\S\S) \](?P<RIVER>.*?))?', hand.string,re.DOTALL)
#for street in m.groupdict():
#print "DEBUG: Street: %s\tspan: %s" %(street, str(m.span(street)))
hand.streets = m hand.streets = m
#sys.exit()
def readCommunityCards(self, hand): def readCommunityCards(self, hand):
# currently regex in wrong place pls fix my brain's fried # currently regex in wrong place pls fix my brain's fried
# what a mess!
re_board = re.compile('\*\* Dealing (?P<STREET>.*) \*\* \[ (?P<CARDS>.*) \]') re_board = re.compile('\*\* Dealing (?P<STREET>.*) \*\* \[ (?P<CARDS>.*) \]')
m = re_board.finditer(hand.string) m = re_board.finditer(hand.string)
for street in m: for street in m:
@ -209,8 +190,8 @@ class Everleaf(HandHistoryConverter):
re_card = re.compile('(?P<CARD>[0-9tjqka][schd])') # copied from earlier re_card = re.compile('(?P<CARD>[0-9tjqka][schd])') # copied from earlier
cards = [card.group('CARD') for card in re_card.finditer(shows.group('CARDS'))] cards = [card.group('CARD') for card in re_card.finditer(shows.group('CARDS'))]
print cards print cards
hand.addHoleCards(cards, shows.group('PNAME')) hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand): def readCollectPot(self,hand):
m = self.rexx.collect_pot_re.search(hand.string) m = self.rexx.collect_pot_re.search(hand.string)
if m is not None: if m is not None:

View File

@ -23,11 +23,12 @@ import traceback
import os import os
import os.path import os.path
import xml.dom.minidom import xml.dom.minidom
import codecs
from decimal import Decimal from decimal import Decimal
import operator import operator
from xml.dom.minidom import Node from xml.dom.minidom import Node
from pokereval import PokerEval from pokereval import PokerEval
from time import time
#from pokerengine.pokercards import * #from pokerengine.pokercards import *
# provides letter2name{}, letter2names{}, visible_card(), not_visible_card(), is_visible(), card_value(), class PokerCards # provides letter2name{}, letter2names{}, visible_card(), not_visible_card(), is_visible(), card_value(), class PokerCards
# but it's probably not installed so here are the ones we may want: # but it's probably not installed so here are the ones we may want:
@ -67,11 +68,11 @@ class HandHistoryConverter:
eval = PokerEval() eval = PokerEval()
def __init__(self, config, file, sitename): def __init__(self, config, file, sitename):
print "HandHistory init called" print "HandHistory init called"
self.c = config self.c = config
self.sitename = sitename self.sitename = sitename
self.obs = "" # One big string self.obs = "" # One big string
self.filetype = "text" self.filetype = "text"
self.codepage = "utf8"
self.doc = None # For XML based HH files self.doc = None # For XML based HH files
self.file = file self.file = file
self.hhbase = self.c.get_import_parameters().get("hhArchiveBase") self.hhbase = self.c.get_import_parameters().get("hhArchiveBase")
@ -95,6 +96,7 @@ class HandHistoryConverter:
return tmp return tmp
def processFile(self): def processFile(self):
starttime = time()
if not self.sanityCheck(): if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check" print "Cowardly refusing to continue after failed sanity check"
return return
@ -120,7 +122,7 @@ class HandHistoryConverter:
# finalise it (total the pot) # finalise it (total the pot)
hand.totalPot() hand.totalPot()
self.getRake(hand) self.getRake(hand)
hand.printHand() hand.printHand()
#if(hand.involved == True): #if(hand.involved == True):
#self.writeHand("output file", hand) #self.writeHand("output file", hand)
@ -128,6 +130,9 @@ class HandHistoryConverter:
#else: #else:
#pass #Don't write out observed hands #pass #Don't write out observed hands
endtime = time()
print "Processed %d hands in %d seconds" % (len(self.hands), endtime-starttime)
##### #####
# These functions are parse actions that may be overridden by the inheriting class # These functions are parse actions that may be overridden by the inheriting class
# #
@ -192,8 +197,9 @@ class HandHistoryConverter:
return sane return sane
# Functions not necessary to implement in sub class # Functions not necessary to implement in sub class
def setFileType(self, filetype = "text"): def setFileType(self, filetype = "text", codepage='utf8'):
self.filetype = filetype self.filetype = filetype
self.codepage = codepage
def splitFileIntoHands(self): def splitFileIntoHands(self):
hands = [] hands = []
@ -209,7 +215,7 @@ class HandHistoryConverter:
"""Read file""" """Read file"""
print "Reading file: '%s'" %(filename) print "Reading file: '%s'" %(filename)
if(self.filetype == "text"): if(self.filetype == "text"):
infile=open(filename, "rU") infile=codecs.open(filename, "rU", self.codepage)
self.obs = infile.read() self.obs = infile.read()
infile.close() infile.close()
elif(self.filetype == "xml"): elif(self.filetype == "xml"):
@ -244,15 +250,14 @@ class Hand:
self.sitename = sitename self.sitename = sitename
self.gametype = gametype self.gametype = gametype
self.string = string self.string = string
self.streets = None # A MatchObject using a groupnames to identify streets.
self.streetList = ['BLINDS','PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order self.streetList = ['BLINDS','PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order
self.actions = {}
self.handid = 0 self.handid = 0
self.sb = gametype[3] self.sb = gametype[3]
self.bb = gametype[4] self.bb = gametype[4]
self.tablename = "Slartibartfast" self.tablename = "Slartibartfast"
self.hero = "Hiro"
self.maxseats = 10 self.maxseats = 10
self.counted_seats = 0 self.counted_seats = 0
self.buttonpos = 0 self.buttonpos = 0
@ -260,57 +265,104 @@ class Hand:
self.players = [] self.players = []
self.posted = [] self.posted = []
self.involved = True self.involved = True
self.hero = "Hiro"
self.holecards = {} # dict from player names to lists of hole cards #
self.board = {} # dict from street names to community cards # Collections indexed by street names
self.collected = {} # dict from player names to amounts collected #
# A MatchObject using a groupnames to identify streets.
# filled by markStreets()
self.streets = None
# dict from street names to lists of tuples, such as
# [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']]
# actually they're clearly lists but they probably should be tuples.
self.actions = {}
# dict from street names to community cards
self.board = {}
#
# Collections indexed by player names
#
# dict from player names to lists of hole cards
self.holecards = {}
# dict from player names to amounts collected
self.collected = {}
# Sets of players
self.shown = set()
self.folded = set()
self.action = [] self.action = []
self.totalpot = None self.totalpot = None
self.rake = None self.rake = None
self.bets = {} self.bets = {}
self.lastBet = {} self.lastBet = {}
for street in self.streetList: for street in self.streetList:
self.bets[street] = {} self.bets[street] = {}
self.lastBet[street] = 0 self.lastBet[street] = 0
def addPlayer(self, seat, name, chips): def addPlayer(self, seat, name, chips):
"""seat, an int indicating the seat """\
name, the player name Adds a player to the hand, and initialises data structures indexed by player.
chips, the chips the player has at the start of the hand (can be None)""" seat (int) indicating the seat
self.players.append([seat, name, chips]) name (string) player name
self.holecards[name] = [] chips (string) the chips the player has at the start of the hand (can be None)
#self.startChips[name] = chips If a player has None chips he won't be added."""
#self.endChips[name] = chips if chips is not None:
#self.winners[name] = 0 self.players.append([seat, name, chips])
for street in self.streetList: self.holecards[name] = []
self.bets[street][name] = [] for street in self.streetList:
self.bets[street][name] = []
def addHoleCards(self, cards, player=None): # generalise to add hole cards for a specific seat or player def addHoleCards(self, cards, player):
for c in cards: """\
self.holecards[player].append(self.card(c)) Assigns observed holecards to a player.
cards list of card bigrams e.g. ['2h','jc']
player (string) name of player
Note, will automatically uppercase the rank letter.
"""
try:
self.checkPlayerExists(player)
for c in cards:
self.holecards[player].append(self.card(c))
except FpdbParseError, e:
print "Tried to add holecards for unknown player: %s" % (player,)
def addShownCards(self, cards, player):
"""\
For when a player shows cards for any reason (for showdown or out of choice).
"""
self.shown.add(player)
self.addHoleCards(cards,player)
def discardHoleCards(self, cards, player=None): def checkPlayerExists(self,player):
if seat is None: if player not in [p[1] for p in self.players]:
#raise something raise FpdbParseError
pass
for card in cards: def discardHoleCards(self, cards, player):
try: try:
self.checkPlayerExists(player)
for card in cards:
self.holecards[player].remove(card) self.holecards[player].remove(card)
except ValueError: except FpdbParseError, e:
print "tried to discard a card player apparently didn't have" pass
except ValueError:
print "tried to discard a card %s didn't have" % (player,)
def setCommunityCards(self, street, cards): def setCommunityCards(self, street, cards):
self.board[street] = [self.card(c) for c in cards] self.board[street] = [self.card(c) for c in cards]
#print self.board[street]
def card(self,c): def card(self,c):
"""upper case the ranks but not suits, 'atjqk' => 'ATJQK'""" """upper case the ranks but not suits, 'atjqk' => 'ATJQK'"""
# don't know how to make this 'static'
for k,v in self.UPS.items(): for k,v in self.UPS.items():
c = c.replace(k,v) c = c.replace(k,v)
return c return c
@ -321,14 +373,7 @@ class Hand:
self.bets['PREFLOP'][player].append(Decimal(amount)) self.bets['PREFLOP'][player].append(Decimal(amount))
self.lastBet['PREFLOP'] = Decimal(amount) self.lastBet['PREFLOP'] = Decimal(amount)
self.posted += [player] self.posted += [player]
#def addFold(self, street, player=None):
## Called when a player folds.
#self.bets[street][player].append(None)
#def addCheck(self, street, player=None):
#self.bets[street][player].append(0)
def addCall(self, street, player=None, amount=None): def addCall(self, street, player=None, amount=None):
# Potentially calculate the amount of the call if not supplied # Potentially calculate the amount of the call if not supplied
@ -339,11 +384,15 @@ class Hand:
self.actions[street] += [[player, 'calls', amount]] self.actions[street] += [[player, 'calls', amount]]
def addRaiseTo(self, street, player, amountTo): def addRaiseTo(self, street, player, amountTo):
# Given only the amount raised to, the amount of the raise can be calculated by """\
Add a raise on [street] by [player] to [amountTo]
"""
#Given only the amount raised to, the amount of the raise can be calculated by
# working out how much this player has already in the pot # working out how much this player has already in the pot
# (which is the sum of self.bets[street][player]) # (which is the sum of self.bets[street][player])
# and how much he needs to call to match the previous player # and how much he needs to call to match the previous player
# (which is tracked by self.lastBet) # (which is tracked by self.lastBet)
self.checkPlayerExists(player)
committedThisStreet = reduce(operator.add, self.bets[street][player], 0) committedThisStreet = reduce(operator.add, self.bets[street][player], 0)
amountToCall = self.lastBet[street] - committedThisStreet amountToCall = self.lastBet[street] - committedThisStreet
self.lastBet[street] = Decimal(amountTo) self.lastBet[street] = Decimal(amountTo)
@ -351,23 +400,27 @@ class Hand:
self.bets[street][player].append(amountBy+amountToCall) self.bets[street][player].append(amountBy+amountToCall)
self.actions[street] += [[player, 'raises', amountBy, amountTo]] self.actions[street] += [[player, 'raises', amountBy, amountTo]]
def addBet(self, street, player=None, amount=0): def addBet(self, street, player, amount):
self.checkPlayerExists(player)
self.bets[street][player].append(Decimal(amount)) self.bets[street][player].append(Decimal(amount))
#self.orderedBets[street].append(Decimal(amount))
self.actions[street] += [[player, 'bets', amount]] self.actions[street] += [[player, 'bets', amount]]
def addFold(self, street, player): def addFold(self, street, player):
self.checkPlayerExists(player)
self.folded.add(player)
self.actions[street] += [[player, 'folds']] self.actions[street] += [[player, 'folds']]
def addCheck(self, street, player): def addCheck(self, street, player):
self.checkPlayerExists(player)
self.actions[street] += [[player, 'checks']] self.actions[street] += [[player, 'checks']]
def addCollectPot(self,player, pot): def addCollectPot(self,player, pot):
self.checkPlayerExists(player)
if player not in self.collected: if player not in self.collected:
self.collected[player] = pot self.collected[player] = pot
else: else:
# possibly lines like "p collected $ from pot" appear during the showdown # possibly lines like "p collected $ from pot" appear during the showdown
# but they are usually unique in the summary. # but they are usually unique in the summary, so it's best to try to get them from there.
print "%s collected pot more than once; avoidable by reading winnings only from summary lines?" print "%s collected pot more than once; avoidable by reading winnings only from summary lines?"
@ -376,20 +429,25 @@ class Hand:
Known bug: doesn't take into account side pots""" Known bug: doesn't take into account side pots"""
if self.totalpot is None: if self.totalpot is None:
self.totalpot = 0 self.totalpot = 0
# player names: # player names:
# print [x[1] for x in self.players] # print [x[1] for x in self.players]
for player in [x[1] for x in self.players]: for player in [x[1] for x in self.players]:
for street in self.streetList: for street in self.streetList:
#print street, self.bets[street][player] #print street, self.bets[street][player]
self.totalpot += reduce(operator.add, self.bets[street][player], 0) self.totalpot += reduce(operator.add, self.bets[street][player], 0)
def getGameTypeAsString(self):
"""\
Map the tuple self.gametype onto the pokerstars string describing it
"""
# currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
return "Hold'em No Limit"
def printHand(self): def printHand(self):
# PokerStars format. # PokerStars format.
print "\n### Pseudo stars format ###" print "\n### Pseudo stars format ###"
print "%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, "XXXXhand.gametype", self.sb, self.bb, self.starttime) print "%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, self.getGameTypeAsString(), self.sb, self.bb, self.starttime)
print "Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos) print "Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos)
for player in self.players: for player in self.players:
print "Seat %s: %s ($%s)" %(player[0], player[1], player[2]) print "Seat %s: %s ($%s)" %(player[0], player[1], player[2])
@ -402,7 +460,7 @@ Known bug: doesn't take into account side pots"""
#May be more than 1 bb posting #May be more than 1 bb posting
for a in self.posted[1:]: for a in self.posted[1:]:
print "%s: posts big blind $%s" %(self.posted[1], self.bb) print "%s: posts big blind $%s" %(self.posted[1], self.bb)
# What about big & small blinds? # What about big & small blinds?
print "*** HOLE CARDS ***" print "*** HOLE CARDS ***"
@ -427,14 +485,15 @@ Known bug: doesn't take into account side pots"""
print "*** RIVER *** [%s] [%s]" %(" ".join(self.board['Flop']+self.board['Turn']), " ".join(self.board['River']) ) print "*** RIVER *** [%s] [%s]" %(" ".join(self.board['Flop']+self.board['Turn']), " ".join(self.board['River']) )
for act in self.actions['RIVER']: for act in self.actions['RIVER']:
self.printActionLine(act) self.printActionLine(act)
#Some sites don't have a showdown section so we have to figure out if there should be one #Some sites don't have a showdown section so we have to figure out if there should be one
# The logic for a showdown is: at the end of river action there are at least two players in the hand # The logic for a showdown is: at the end of river action there are at least two players in the hand
# we probably don't need a showdown section in pseudo stars format for our filtering purposes
if 'SHOWDOWN' in self.actions: if 'SHOWDOWN' in self.actions:
print "*** SHOW DOWN ***" print "*** SHOW DOWN ***"
print "what do they show" print "what do they show"
print "*** SUMMARY ***" print "*** SUMMARY ***"
print "Total pot $%s | Rake $%.2f)" % (self.totalpot, self.rake) # TODO side pots print "Total pot $%s | Rake $%.2f)" % (self.totalpot, self.rake) # TODO side pots
board = [] board = []
@ -442,21 +501,22 @@ Known bug: doesn't take into account side pots"""
board += s board += s
if board: # sometimes hand ends preflop without a board if board: # sometimes hand ends preflop without a board
print "Board [%s]" % (" ".join(board)) print "Board [%s]" % (" ".join(board))
#print self.board
for player in self.players: for player in self.players:
seatnum = player[0] seatnum = player[0]
name = player[1] name = player[1]
if name in self.collected and self.holecards[name]: if name in self.collected and self.holecards[name]:
# TODO: (bug) hero cards will always be 'shown' because they are known to us. Better to explicitly flag those who 'show' their cards.
print "Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]), self.collected[name]) print "Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]), self.collected[name])
elif name in self.collected: elif name in self.collected:
print "Seat %d: %s collected ($%s)" % (seatnum, name, self.collected[name]) print "Seat %d: %s collected ($%s)" % (seatnum, name, self.collected[name])
elif self.holecards[player[1]]: elif player[1] in self.shown:
print "Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name])) print "Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name]))
elif player[1] in self.folded:
print "Seat %d: %s folded" % (seatnum, name)
else: else:
print "Seat %d: %s folded or mucked" % (seatnum, name) print "Seat %d: %s mucked" % (seatnum, name)
print print
# TODO: # TODO:
# logic for side pots # logic for side pots
@ -487,13 +547,6 @@ Known bug: doesn't take into account side pots"""
# going to use pokereval to figure out hands at some point. # going to use pokereval to figure out hands at some point.
# these functions are copied from pokergame.py # these functions are copied from pokergame.py
def bestHand(self, side, cards): def bestHand(self, side, cards):
#if self.variant == "omaha" or self.variant == "omaha8":
#hand = self.serial2player[serial].hand.tolist(True)
#board = self.board.tolist(True)
#else:
#hand = hand.tolist(True) + board.tolist(True)
#board = []
print cards
return HandHistoryConverter.eval.best('hi', cards, []) return HandHistoryConverter.eval.best('hi', cards, [])
# from pokergame.py # from pokergame.py
@ -531,4 +584,7 @@ Known bug: doesn't take into account side pots"""
return ("Royal flush") return ("Royal flush")
else: else:
return ("Straight flush %(card)s high") % { 'card' : (letter2name[cards[0][0]]) } return ("Straight flush %(card)s high") % { 'card' : (letter2name[cards[0][0]]) }
return value return value
class FpdbParseError(Exception): pass