Merge branch 'futz' from Matt

Conflicts:

	pyfpdb/EverleafToFpdb.py
	pyfpdb/FulltiltToFpdb.py
	pyfpdb/Hand.py
	pyfpdb/HandHistoryConverter.py
This commit is contained in:
Worros 2009-03-08 00:43:33 +09:00
commit b07823b372
6 changed files with 428 additions and 314 deletions

View File

@ -19,9 +19,7 @@
import sys
import logging
import Configuration
from HandHistoryConverter import *
from time import strftime
# Class for converting Everleaf HH format.
@ -35,17 +33,16 @@ class Everleaf(HandHistoryConverter):
re_PlayerInfo = re.compile(r"^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\s+(\$ (?P<CASH>[.0-9]+) USD|new player|All-in) \)", re.MULTILINE)
re_Board = re.compile(r"\[ (?P<CARDS>.+) \]")
def __init__(self, config, file):
print "Initialising Everleaf converter class"
HandHistoryConverter.__init__(self, config, file, sitename="Everleaf") # Call super class init.
self.sitename = "Everleaf"
self.setFileType("text", "cp1252")
try:
self.ofile = os.path.join(self.hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file))
except:
self.ofile = os.path.join(self.hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file))
def __init__(self, in_path = '-', out_path = '-', follow = False):
"""\
in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Everleaf", follow=follow)
logging.info("Initialising Everleaf converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.start()
def compilePlayerRegexs(self, players):
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
@ -149,13 +146,15 @@ class Everleaf(HandHistoryConverter):
hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
#print "DEBUG " + street + ":"
#print hand.streets.group(street) + "\n"
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
m = self.re_Board.search(hand.streets.group(street))
hand.setCommunityCards(street, m.group('CARDS').split(', '))
# If this has been called, street is a street which gets dealt community cards by type hand
# but it might be worth checking somehow.
# if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
logging.debug("readCommunityCards (%s)" % street)
m = self.re_Board.search(hand.streets[street])
cards = m.group('CARDS')
cards = [card.strip() for card in cards.split(',')]
hand.setCommunityCards(street=street, cards=cards)
def readBlinds(self, hand):
m = self.re_PostSB.search(hand.handText)
@ -212,6 +211,7 @@ class Everleaf(HandHistoryConverter):
logging.debug("readShowdownActions %s %s" %(cards, shows.group('PNAME')))
hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.handText):
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
@ -229,11 +229,21 @@ class Everleaf(HandHistoryConverter):
if __name__ == "__main__":
c = Configuration.Config()
if len(sys.argv) == 1:
testfile = "regression-test-files/everleaf/plo/Naos.txt"
else:
testfile = sys.argv[1]
e = Everleaf(c, testfile)
e.processFile()
print str(e)
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/everleaf/Speed_Kuala_full.txt")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet",
action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
parser.add_option("-v", "--verbose",
action="store_const", const=logging.INFO, dest="verbosity")
parser.add_option("--vv",
action="store_const", const=logging.DEBUG, dest="verbosity")
(options, args) = parser.parse_args()
LOG_FILENAME = './logging.out'
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
e = Everleaf(in_path = options.ipath, out_path = options.opath, follow = options.follow)

1
pyfpdb/Exceptions.py Normal file
View File

@ -0,0 +1 @@
class FpdbParseError(Exception): pass

View File

@ -18,7 +18,6 @@
import sys
import logging
import Configuration
from HandHistoryConverter import *
# FullTilt HH Format converter
@ -33,11 +32,16 @@ class FullTilt(HandHistoryConverter):
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[.0-9]+)\)\n')
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
def __init__(self, config, file):
print "Initialising FullTilt converter class"
HandHistoryConverter.__init__(self, config, file, sitename="FullTilt") # Call super class init.
self.sitename = "FullTilt"
self.setFileType("text", "cp1252")
def __init__(self, in_path = '-', out_path = '-', follow = False):
"""\
in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="FullTilt", follow=follow)
logging.info("Initialising FullTilt converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.start()
def compilePlayerRegexs(self, players):
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
@ -50,7 +54,7 @@ class FullTilt(HandHistoryConverter):
self.re_Antes = re.compile(r"^%s antes \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s \[(?P<CARDS>[AKQJT0-9hcsd ]+)\]( \[(?P<NEWCARD>[AKQJT0-9hcsd ]+)\])?" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s(?P<ATYPE> bets| checks| raises to| calls| folds)(\s\$(?P<BET>[.\d]+))?" % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*)" % player_re, re.MULTILINE)
@ -168,6 +172,7 @@ class FullTilt(HandHistoryConverter):
def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL)
logging.debug("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readButton(self, hand):
@ -186,19 +191,61 @@ class FullTilt(HandHistoryConverter):
cards = [c.strip() for c in cards.split(' ')]
hand.addHoleCards(cards, m.group('PNAME'))
def readPlayerCards(self, hand, street):
#Used for stud hands - borrows the HeroCards regex for now.
m = self.re_HeroCards.finditer(hand.streets.group(street))
print "DEBUG: razz/stud readPlayerCards"
print hand.streets.group(street)
def readStudPlayerCards(self, hand, street):
# This could be the most tricky one to get right.
# It looks for cards dealt in 'street',
# which may or may not be in the section of the hand designated 'street' by markStreets earlier.
# Here's an example at FTP of what 'THIRD' and 'FOURTH' look like to hero PokerAscetic
#
#"*** 3RD STREET ***
#Dealt to BFK23 [Th]
#Dealt to cutiepr1nnymaid [8c]
#Dealt to PokerAscetic [7c 8s] [3h]
#..."
#
#"*** 4TH STREET ***
#Dealt to cutiepr1nnymaid [8c] [2s]
#Dealt to PokerAscetic [7c 8s 3h] [5s]
#..."
#Note that hero's first two holecards are only reported at 3rd street as 'old' cards.
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street])
for player in m:
logging.debug(player.groupdict())
cards = player.group('CARDS')
if player.group('NEWCARD') != None:
print cards
cards = cards + " " + player.group('NEWCARD')
cards = cards.split(' ')
hand.addPlayerCards(cards, player.group('PNAME'))
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
if oldcards:
oldcards = [c.strip() for c in oldcards.split(' ')]
if newcards:
newcards = [c.strip() for c in newcards.split(' ')]
# options here:
# (1) we trust the hand will know what to do -- probably check that the old cards match what it already knows, and add the newcards to this street.
# (2) we're the experts at this particular history format and we know how we're going to be called (once for each street in Hand.streetList)
# so call addPlayerCards with the appropriate information.
# I favour (2) here but I'm afraid it is rather stud7-specific.
# in the following, the final list of cards will be in 'newcards' whilst if the first list exists (most of the time it does) it will be in 'oldcards'
if street=='ANTES':
return
elif street=='THIRD':
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# hero: [xx][o]
# others: [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# 4th:
# hero: [xxo] [o]
# others: [o] [o]
# 5th:
# hero: [xxoo] [o]
# others: [oo] [o]
# 6th:
# hero: [xxooo] [o]
# others: [ooo] [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
elif street=='SEVENTH' and newcards:
# hero: [xxoooo] [x]
# others: not reported.
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street])
@ -236,12 +283,20 @@ class FullTilt(HandHistoryConverter):
if __name__ == "__main__":
c = Configuration.Config()
if len(sys.argv) == 1:
testfile = "regression-test-files/fulltilt/razz/FT20090223 Danville - $0.50-$1 Ante $0.10 - Limit Razz.txt"
else:
testfile = sys.argv[1]
print "Converting: ", testfile
e = FullTilt(c, testfile)
e.processFile()
print str(e)
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/fulltilt/razz/FT20090223 Danville - $0.50-$1 Ante $0.10 - Limit Razz.txt")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet",
action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
parser.add_option("-v", "--verbose",
action="store_const", const=logging.INFO, dest="verbosity")
parser.add_option("--vv",
action="store_const", const=logging.DEBUG, dest="verbosity")
(options, args) = parser.parse_args()
LOG_FILENAME = './logging.out'
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
e = FullTilt(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -15,39 +15,25 @@
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import Configuration
import FpdbRegex
import Hand
import re
import sys
import traceback
import logging
import os
import os.path
import xml.dom.minidom
import codecs
from decimal import Decimal
import operator
import time
from copy import deepcopy
from Exceptions import *
class Hand:
# def __init__(self, sitename, gametype, sb, bb, string):
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'}
def __init__(self, sitename, gametype, handText):
self.sitename = sitename
self.gametype = gametype
self.handText = handText
if gametype[1] == "hold" or self.gametype[1] == "omahahi":
self.streetList = ['PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order
elif self.gametype[1] == "razz" or self.gametype[1] == "stud" or self.gametype[1] == "stud8":
self.streetList = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
self.handid = 0
self.sb = gametype[3]
self.bb = gametype[4]
self.tablename = "Slartibartfast"
self.hero = "Hiro"
self.maxseats = 10
@ -58,53 +44,37 @@ class Hand:
self.posted = []
self.involved = True
self.pot = Pot()
#
# Collections indexed by street names
#
# 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 = {}
self.bets = {}
self.lastBet = {}
self.streets = {}
self.actions = {} # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']]
self.board = {} # dict from street names to community cards
for street in self.streetList:
self.streets[street] = "" # portions of the handText, filled by markStreets()
self.bets[street] = {}
self.lastBet[street] = 0
self.actions[street] = []
self.board[street] = []
# Collections indexed by player names
self.holecards = {} # dict from player names to dicts by street ... of tuples ... of holecards
self.stacks = {}
# dict from player names to amounts collected
self.collected = []
self.collectees = {}
self.collected = [] #list of ?
self.collectees = {} # dict from player names to amounts collected (?)
# Sets of players
self.shown = set()
self.folded = set()
self.action = []
# self.action = []
# Things to do with money
self.pot = Pot()
self.totalpot = None
self.totalcollected = None
self.rake = None
self.bets = {}
self.lastBet = {}
for street in self.streetList:
self.bets[street] = {}
self.lastBet[street] = 0
def addPlayer(self, seat, name, chips):
"""\
@ -116,65 +86,23 @@ If a player has None chips he won't be added."""
if chips is not None:
self.players.append([seat, name, chips])
self.stacks[name] = Decimal(chips)
self.holecards[name] = set()
self.holecards[name] = []
self.pot.addPlayer(name)
for street in self.streetList:
self.bets[street][name] = []
self.holecards[name] = {} # dict from street names.
def addStreets(self, match):
# go through m and initialise actions to empty list for each street.
if match:
self.streets = match
for street in match.groupdict():
if match.group(street) is not None:
self.actions[street] = []
self.streets.update(match.groupdict())
logging.debug("markStreets:\n"+ str(self.streets))
else:
logging.error("markstreets didn't match")
def addHoleCards(self, cards, player):
"""\
Assigns observed holecards to a player.
cards set of card bigrams e.g. set(['2h','Jc'])
player (string) name of player
"""
#print "DEBUG: addHoleCards", cards,player
try:
self.checkPlayerExists(player)
cards = set([self.card(c) for c in cards])
self.holecards[player].update(cards)
except FpdbParseError, e:
print "[ERROR] Tried to add holecards for unknown player: %s" % (player,)
#def addHoleCards -- to Holdem subclass
def addPlayerCards(self, cards, player):
"""\
Assigns observed cards to a player.
cards set of card bigrams e.g. set(['2h','Jc'])
player (string) name of player
Should probably be merged with addHoleCards
"""
print "DEBUG: addPlayerCards", cards,player
try:
self.checkPlayerExists(player)
cards = set([self.card(c) for c in cards])
self.holecards[player].update(cards)
except FpdbParseError, e:
print "[ERROR] Tried to add holecards for unknown player: %s" % (player,)
def addShownCards(self, cards, player, holeandboard=None):
"""\
For when a player shows cards for any reason (for showdown or out of choice).
Card ranks will be uppercased
"""
#print "DEBUG: addShownCards", cards,player,holeandboard
if cards is not None:
self.shown.add(player)
self.addHoleCards(cards,player)
elif holeandboard is not None:
holeandboard = set([self.card(c) for c in holeandboard])
board = set([c for s in self.board.values() for c in s])
self.addHoleCards(holeandboard.difference(board),player)
def checkPlayerExists(self,player):
@ -212,15 +140,12 @@ Card ranks will be uppercased
def addBlind(self, player, blindtype, amount):
# if player is None, it's a missing small blind.
# TODO:
# The situation we need to cover are:
# Player in small blind posts
# - this is a bet of 1 sb, as yet uncalled.
# Player in the big blind posts
# - this is a call of 1 bb and is the new uncalled
# - this is a call of 1 sb and a raise to 1 bb
#
# If a player posts a big & small blind
# - FIXME: We dont record this for later printing yet
logging.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
if player is not None:
@ -228,7 +153,7 @@ Card ranks will be uppercased
self.stacks[player] -= Decimal(amount)
#print "DEBUG %s posts, stack %s" % (player, self.stacks[player])
act = (player, 'posts', blindtype, amount, self.stacks[player]==0)
self.actions['PREFLOP'].append(act)
self.actions['BLINDSANTES'].append(act)
self.pot.addMoney(player, Decimal(amount))
if blindtype == 'big blind':
self.lastBet['PREFLOP'] = Decimal(amount)
@ -238,13 +163,6 @@ Card ranks will be uppercased
self.posted = self.posted + [[player,blindtype]]
#print "DEBUG: self.posted: %s" %(self.posted)
def addBringIn(self, player, ante):
if player is not None:
self.bets['THIRD'][player].append(Decimal(ante))
self.stacks[player] -= Decimal(ante)
act = (player, 'bringin', "bringin", ante, self.stacks[player]==0)
self.actions['THIRD'].append(act)
self.pot.addMoney(player, Decimal(ante))
def addCall(self, street, player=None, amount=None):
@ -422,16 +340,99 @@ Map the tuple self.gametype onto the pokerstars string describing it
def writeHand(self, fh=sys.__stdout__):
if self.gametype[1] == "hold" or self.gametype[1] == "omahahi":
self.writeHoldemHand(fh)
else:
self.writeStudHand(fh)
print >>fh, "Override me"
def printHand(self):
self.writeHand(sys.stdout)
def printActionLine(self, act, fh):
if act[1] == 'folds':
print >>fh, _("%s: folds " %(act[0]))
elif act[1] == 'checks':
print >>fh, _("%s: checks " %(act[0]))
elif act[1] == 'calls':
print >>fh, _("%s: calls $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'bets':
print >>fh, _("%s: bets $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'raises':
print >>fh, _("%s: raises $%s to $%s%s" %(act[0], act[2], act[3], ' and is all-in' if act[5] else ''))
elif act[1] == 'posts':
if(act[2] == "small blind"):
print >>fh, _("%s: posts small blind $%s" %(act[0], act[3]))
elif(act[2] == "big blind"):
print >>fh, _("%s: posts big blind $%s" %(act[0], act[3]))
elif(act[2] == "both"):
print >>fh, _("%s: posts small & big blinds $%s" %(act[0], act[3]))
class HoldemOmahaHand(Hand):
def __init__(self, hhc, sitename, gametype, handText):
if gametype[1] not in ["hold","omaha"]:
pass # or indeed don't pass and complain instead
logging.debug("HoldemOmahaHand")
self.streetList = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order
self.communityStreets = ['FLOP', 'TURN', 'RIVER']
self.actionStreets = ['PREFLOP','FLOP','TURN','RIVER']
Hand.__init__(self, sitename, gametype, handText)
self.sb = gametype[3]
self.bb = gametype[4]
#Populate a HoldemOmahaHand
#Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
# which then invokes a 'addXXX' callback
hhc.readHandInfo(self)
hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(players = set([player[1] for player in self.players]))
hhc.markStreets(self)
hhc.readBlinds(self)
hhc.readButton(self)
hhc.readHeroCards(self)
hhc.readShowdownActions(self)
# Read actions in street order
for street in self.communityStreets:
if self.streets[street]:
hhc.readCommunityCards(self, street)
for street in self.actionStreets:
if self.streets[street]:
hhc.readAction(self, street)
hhc.readCollectPot(self)
hhc.readShownCards(self)
self.totalPot() # finalise it (total the pot)
hhc.getRake(self)
def addHoleCards(self, cards, player):
"""\
Assigns observed holecards to a player.
cards list of card bigrams e.g. ['2h','Jc']
player (string) name of player
"""
logging.debug("addHoleCards %s %s" % (cards, player))
try:
self.checkPlayerExists(player)
cardset = set(self.card(c) for c in cards)
if 'PREFLOP' in self.holecards[player]:
self.holecards[player]['PREFLOP'].update(cardset)
else:
self.holecards[player]['PREFLOP'] = cardset
except FpdbParseError, e:
print "[ERROR] Tried to add holecards for unknown player: %s" % (player,)
def addShownCards(self, cards, player, holeandboard=None):
"""\
For when a player shows cards for any reason (for showdown or out of choice).
Card ranks will be uppercased
"""
logging.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard))
if cards is not None:
self.shown.add(player)
self.addHoleCards(cards,player)
elif holeandboard is not None:
holeandboard = set([self.card(c) for c in holeandboard])
board = set([c for s in self.board.values() for c in s])
self.addHoleCards(holeandboard.difference(board),player)
def writeHoldemHand(self, fh=sys.__stdout__):
def writeHand(self, fh=sys.__stdout__):
# PokerStars format.
#print "\n### Pseudo stars format ###"
#print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, self.getGameTypeAsString(), self.sb, self.bb, self.starttime))
print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %("PokerStars", self.handid, self.getGameTypeAsString(), self.sb, self.bb, time.strftime('%Y/%m/%d - %H:%M:%S (ET)', self.starttime)))
print >>fh, _("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos))
@ -449,34 +450,42 @@ Map the tuple self.gametype onto the pokerstars string describing it
smallbet = self.sb
bigbet = self.bb
for a in self.posted:
if(a[1] == "small blind"):
print >>fh, _("%s: posts small blind $%s" %(a[0], smallbet))
if(a[1] == "big blind"):
print >>fh, _("%s: posts big blind $%s" %(a[0], bigbet))
if(a[1] == "both"):
print >>fh, _("%s: posts small & big blinds $%.2f" %(a[0], (Decimal(smallbet) + Decimal(bigbet))))
# for a in self.posted:
# if(a[1] == "small blind"):
# print >>fh, _("%s: posts small blind $%s" %(a[0], smallbet))
# if(a[1] == "big blind"):
# print >>fh, _("%s: posts big blind $%s" %(a[0], bigbet))
# if(a[1] == "both"):
# print >>fh, _("%s: posts small & big blinds $%.2f" %(a[0], (Decimal(smallbet) + Decimal(bigbet))))
# I think these can just be actions in 'blindsantes' round
if self.actions['BLINDSANTES']:
for act in self.actions['BLINDSANTES']:
self.printActionLine(act, fh)
print >>fh, _("*** HOLE CARDS ***")
if self.involved:
print >>fh, _("Dealt to %s [%s]" %(self.hero , " ".join(self.holecards[self.hero])))
print >>fh, _("Dealt to %s [%s]" %(self.hero , " ".join(self.holecards[self.hero]['PREFLOP'])))
if 'PREFLOP' in self.actions:
if self.actions['PREFLOP']:
for act in self.actions['PREFLOP']:
self.printActionLine(act, fh)
if 'FLOP' in self.actions:
if self.board['FLOP']:
print >>fh, _("*** FLOP *** [%s]" %( " ".join(self.board['FLOP'])))
if self.actions['FLOP']:
for act in self.actions['FLOP']:
self.printActionLine(act, fh)
if 'TURN' in self.actions:
if self.board['TURN']:
print >>fh, _("*** TURN *** [%s] [%s]" %( " ".join(self.board['FLOP']), " ".join(self.board['TURN'])))
if self.actions['TURN']:
for act in self.actions['TURN']:
self.printActionLine(act, fh)
if 'RIVER' in self.actions:
if self.board['RIVER']:
print >>fh, _("*** RIVER *** [%s] [%s]" %(" ".join(self.board['FLOP']+self.board['TURN']), " ".join(self.board['RIVER']) ))
if self.actions['RIVER']:
for act in self.actions['RIVER']:
self.printActionLine(act, fh)
@ -513,37 +522,79 @@ Map the tuple self.gametype onto the pokerstars string describing it
seatnum = player[0]
name = player[1]
if name in self.collectees and name in self.shown:
print >>fh, _("Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]), self.collectees[name]))
print >>fh, _("Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]['PREFLOP']), self.collectees[name]))
elif name in self.collectees:
print >>fh, _("Seat %d: %s collected ($%s)" % (seatnum, name, self.collectees[name]))
elif name in self.shown:
print >>fh, _("Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name])))
print >>fh, _("Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name]['PREFLOP'])))
elif name in self.folded:
print >>fh, _("Seat %d: %s folded" % (seatnum, name))
else:
print >>fh, _("Seat %d: %s mucked" % (seatnum, name))
print >>fh, "\n\n"
# TODO:
# logic for side pots
# logic for which players get to showdown
# I'm just not sure we need to do this so heavily.. and if we do, it's probably better to use pokerlib
#if self.holecards[player[1]]: # empty list default is false
#hole = self.holecards[player[1]]
##board = []
##for s in self.board.values():
##board += s
##playerhand = self.bestHand('hi', board+hole)
##print "Seat %d: %s showed %s and won/lost with %s" % (player[0], player[1], hole, playerhand)
#print "Seat %d: %s showed %s" % (player[0], player[1], hole)
#else:
#print "Seat %d: %s mucked or folded" % (player[0], player[1])
def writeStudHand(self, fh=sys.__stdout__):
class StudHand(Hand):
def __init__(self, hhc, sitename, gametype, handText):
if gametype[1] not in ["razz","stud","stud8"]:
pass # or indeed don't pass and complain instead
self.streetList = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
self.holeStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
Hand.__init__(self, sitename, gametype, handText)
self.sb = gametype[3]
self.bb = gametype[4]
#Populate the StudHand
#Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
# which then invokes a 'addXXX' callback
hhc.readHandInfo(self)
hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(players = set([player[1] for player in self.players]))
hhc.markStreets(self)
hhc.readAntes(self)
hhc.readBringIn(self)
# hhc.readShowdownActions(self) # not done yet
# Read actions in street order
for street in self.streetList:
if self.streets[street]:
logging.debug(street)
logging.debug(self.streets[street])
hhc.readStudPlayerCards(self, street)
hhc.readAction(self, street)
hhc.readCollectPot(self)
# hhc.readShownCards(self) # not done yet
self.totalPot() # finalise it (total the pot)
hhc.getRake(self)
def addPlayerCards(self, player, street, open=[], closed=[]):
"""\
Assigns observed cards to a player.
player (string) name of player
street (string) the street name (in streetList)
open list of card bigrams e.g. ['2h','Jc'], dealt face up
closed likewise, but known only to player
"""
logging.debug("addPlayerCards %s, o%s x%s" % (player, open, closed))
try:
self.checkPlayerExists(player)
self.holecards[player][street] = (open, closed)
# cards = set([self.card(c) for c in cards])
# self.holecards[player].update(cards)
except FpdbParseError, e:
print "[ERROR] Tried to add holecards for unknown player: %s" % (player,)
def addBringIn(self, player, bringin):
if player is not None:
logging.debug("Bringin: %s, %s" % (player , bringin))
self.bets['THIRD'][player].append(Decimal(bringin))
self.stacks[player] -= Decimal(bringin)
act = (player, 'bringin', "bringin", bringin, self.stacks[player]==0)
self.actions['THIRD'].append(act)
self.pot.addMoney(player, Decimal(bringin))
def writeHand(self, fh=sys.__stdout__):
# PokerStars format.
#print "\n### Pseudo stars format ###"
#print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, self.getGameTypeAsString(), self.sb, self.bb, self.starttime))
print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %("PokerStars", self.handid, self.getGameTypeAsString(), self.sb, self.bb, time.strftime('%Y/%m/%d - %H:%M:%S (ET)', self.starttime)))
print >>fh, _("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos))
@ -559,8 +610,10 @@ Map the tuple self.gametype onto the pokerstars string describing it
if 'THIRD' in self.actions:
print >>fh, _("*** 3RD STREET ***")
for player in [x for x in self.players if x[1] in players_who_post_antes]:
print >>fh, _("Dealt to ")
for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
print player, self.holecards[player]
(closed, open) = self.holecards[player]['THIRD']
print >>fh, _("Dealt to %s:%s%s") % (player, " [" + " ".join(closed) + "]" if closed else " ", " [" + " ".join(open) + "]" if open else " ")
for act in self.actions['THIRD']:
#FIXME: Need some logic here for bringin vs completes
self.printActionLine(act, fh)
@ -629,77 +682,8 @@ Map the tuple self.gametype onto the pokerstars string describing it
print >>fh, _("Seat %d: %s mucked" % (seatnum, name))
print >>fh, "\n\n"
# TODO:
# logic for side pots
# logic for which players get to showdown
# I'm just not sure we need to do this so heavily.. and if we do, it's probably better to use pokerlib
#if self.holecards[player[1]]: # empty list default is false
#hole = self.holecards[player[1]]
##board = []
##for s in self.board.values():
##board += s
##playerhand = self.bestHand('hi', board+hole)
##print "Seat %d: %s showed %s and won/lost with %s" % (player[0], player[1], hole, playerhand)
#print "Seat %d: %s showed %s" % (player[0], player[1], hole)
#else:
#print "Seat %d: %s mucked or folded" % (player[0], player[1])
def printHand(self):
self.writeHand(sys.stdout)
def printActionLine(self, act, fh):
if act[1] == 'folds':
print >>fh, _("%s: folds " %(act[0]))
elif act[1] == 'checks':
print >>fh, _("%s: checks " %(act[0]))
if act[1] == 'calls':
print >>fh, _("%s: calls $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
if act[1] == 'bets':
print >>fh, _("%s: bets $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
if act[1] == 'raises':
print >>fh, _("%s: raises $%s to $%s%s" %(act[0], act[2], act[3], ' and is all-in' if act[5] else ''))
# going to use pokereval to figure out hands at some point.
# these functions are copied from pokergame.py
def bestHand(self, side, cards):
return HandHistoryConverter.eval.best('hi', cards, [])
# from pokergame.py
# got rid of the _ for internationalisation
def readableHandValueLong(self, side, value, cards):
if value == "NoPair":
if side == "low":
if cards[0][0] == '5':
return ("The wheel")
else:
return join(map(lambda card: card[0], cards), ", ")
else:
return ("High card %(card)s") % { 'card' : (letter2name[cards[0][0]]) }
elif value == "OnePair":
return ("A pair of %(card)s") % { 'card' : (letter2names[cards[0][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[2][0]]) }
elif value == "TwoPair":
return ("Two pairs %(card1)s and %(card2)s") % { 'card1' : (letter2names[cards[0][0]]), 'card2' : _(letter2names[cards[2][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[4][0]]) }
elif value == "Trips":
return ("Three of a kind %(card)s") % { 'card' : (letter2names[cards[0][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[3][0]]) }
elif value == "Straight":
return ("Straight %(card1)s to %(card2)s") % { 'card1' : (letter2name[cards[0][0]]), 'card2' : (letter2name[cards[4][0]]) }
elif value == "Flush":
return ("Flush %(card)s high") % { 'card' : (letter2name[cards[0][0]]) }
elif value == "FlHouse":
return ("%(card1)ss full of %(card2)ss") % { 'card1' : (letter2name[cards[0][0]]), 'card2' : (letter2name[cards[3][0]]) }
elif value == "Quads":
return _("Four of a kind %(card)s") % { 'card' : (letter2names[cards[0][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[4][0]]) }
elif value == "StFlush":
if letter2name[cards[0][0]] == 'Ace':
return ("Royal flush")
else:
return ("Straight flush %(card)s high") % { 'card' : (letter2name[cards[0][0]]) }
return value
class FpdbParseError(Exception): pass
class Pot(object):

View File

@ -15,13 +15,13 @@
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import Configuration
import FpdbRegex
import Hand
import re
import sys
import threading
import traceback
import logging
from optparse import OptionParser
import os
import os.path
import xml.dom.minidom
@ -72,27 +72,26 @@ letter2names = {
import gettext
gettext.install('myapplication')
class HandHistoryConverter(threading.Thread):
class HandHistoryConverter:
def __init__(self, config, file, sitename):
def __init__(self, in_path = '-', out_path = '-', sitename = None, follow=False):
threading.Thread.__init__(self)
logging.info("HandHistory init called")
self.c = config
self.sitename = sitename
self.obs = "" # One big string
# default filetype and codepage. Subclasses should set these properly.
self.filetype = "text"
self.codepage = "utf8"
self.doc = None # For XML based HH files
self.file = file
self.hhbase = self.c.get_import_parameters().get("hhArchiveBase")
self.hhbase = os.path.expanduser(self.hhbase)
self.hhdir = os.path.join(self.hhbase,sitename)
self.gametype = []
self.ofile = os.path.join(self.hhdir, os.path.basename(file))
self.rexx = FpdbRegex.FpdbRegex()
self.players = set()
self.compiledPlayers = set()
self.in_path = in_path
self.out_path = out_path
if self.out_path == '-':
# write to stdout
self.out_fh = sys.stdout
else:
self.out_fh = open(self.out_path, 'a')
self.sitename = sitename
self.follow = follow
self.compiledPlayers = set()
self.maxseats = 10
def __str__(self):
@ -108,6 +107,51 @@ class HandHistoryConverter:
#tmp = tmp + "\tsb/bb: '%s/%s'\n" % (self.gametype[3], self.gametype[4])
return tmp
def run(self):
if self.follow:
for handtext in self.tailHands():
self.processHand(handtext)
else:
handsList = self.allHands()
logging.info("Parsing %d hands" % len(handsList))
for handtext in handsList:
self.processHand(handtext)
def tailHands(self):
"""pseudo-code"""
while True:
ifile.tell()
text = ifile.read()
if nomoretext:
wait or sleep
else:
ahand = thenexthandinthetext
yield(ahand)
def allHands(self):
"""Return a list of handtexts in the file at self.in_path"""
self.readFile()
self.obs = self.obs.strip()
self.obs = self.obs.replace('\r\n', '\n')
if self.obs == "" or self.obs == None:
logging.info("Read no hands.")
return
return re.split(self.re_SplitHands, self.obs)
def processHand(self, handtext):
gametype = self.determineGameType(handtext)
if gametype is None:
return
if gametype[1] in ("hold", "omaha"):
hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)
elif gametype[1] in ("razz","stud","stud8"):
hand = Hand.StudHand(self, self.sitename, gametype, handtext)
hand.writeHand(self.out_fh)
def processFile(self):
starttime = time.time()
if not self.sanityCheck():
@ -119,7 +163,7 @@ class HandHistoryConverter:
return
self.obs = self.obs.replace('\r\n', '\n')
self.gametype = self.determineGameType(self.obs)
self.gametype = self.determineGameType()
if self.gametype == None:
print "Unknown game type from file, aborting on this file."
return
@ -139,7 +183,7 @@ class HandHistoryConverter:
else:
# we need to recompile the player regexs.
self.players = playersThisHand
self.compilePlayerRegexs(playersThisHand)
self.compilePlayerRegexs()
self.markStreets(hand)
# Different calls if stud or holdem like
@ -183,7 +227,6 @@ class HandHistoryConverter:
endtime = time.time()
print "Processed %d hands in %.3f seconds" % (len(self.hands), endtime - starttime)
#####
# These functions are parse actions that may be overridden by the inheriting class
# This function should return a list of lists looking like:
# return [["ring", "hold", "nl"], ["tour", "hold", "nl"]]
@ -198,13 +241,13 @@ class HandHistoryConverter:
def determineGameType(self): abstract
# Read any of:
# HID HandID
# TABLE Table name
# SB small blind
# BB big blind
# GAMETYPE gametype
# YEAR MON DAY HR MIN SEC datetime
# BUTTON button seat number
# HID HandID
# TABLE Table name
# SB small blind
# BB big blind
# GAMETYPE gametype
# YEAR MON DAY HR MIN SEC datetime
# BUTTON button seat number
def readHandInfo(self, hand): abstract
# Needs to return a list of lists in the format
@ -270,7 +313,7 @@ class HandHistoryConverter:
def splitFileIntoHands(self):
hands = []
self.obs.strip()
self.obs = self.obs.strip()
list = self.re_SplitHands.split(self.obs)
list.pop() #Last entry is empty
for l in list:
@ -278,13 +321,19 @@ class HandHistoryConverter:
hands = hands + [Hand.Hand(self.sitename, self.gametype, l)]
return hands
def readFile(self, filename):
"""Read file"""
print "Reading file: '%s'" %(filename)
def readFile(self):
"""Read in path into self.obs or self.doc"""
if(self.filetype == "text"):
infile=codecs.open(filename, "r", self.codepage)
self.obs = infile.read()
infile.close()
if self.in_path == '-':
# read from stdin
logging.debug("Reading stdin with %s" % self.codepage) # is this necessary? or possible? or what?
in_fh = codecs.getreader('cp1252')(sys.stdin)
else:
logging.debug("Opening %s with %s" % (self.in_path, self.codepage))
in_fh = codecs.open(self.in_path, 'r', self.codepage)
self.obs = in_fh.read()
in_fh.close()
elif(self.filetype == "xml"):
try:
doc = xml.dom.minidom.parse(filename)

View File

@ -182,9 +182,10 @@ class Importer:
#Run import on updated files, then store latest update time.
def runUpdated(self):
#Check for new files in directory
#Check for new files in monitored directories
#todo: make efficient - always checks for new file, should be able to use mtime of directory
# ^^ May not work on windows
for site in self.dirlist:
self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
@ -197,10 +198,15 @@ class Importer:
self.updated[file] = time()
except:
self.updated[file] = time()
# This codepath only runs first time the file is found, if modified in the last
# minute run an immediate import.
if (time() - stat_info.st_mtime) < 60 or os.path.isdir(file): # TODO: figure out a way to dispatch this to the seperate thread so our main window doesn't lock up on initial import
# If modified in the last minute run an immediate import.
# This codepath only runs first time the file is found.
if (time() - stat_info.st_mtime) < 60:
# TODO attach a HHC thread to the file
# TODO import the output of the HHC thread -- this needs to wait for the HHC to block?
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
# TODO we also test if directory, why?
#if os.path.isdir(file):
#self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
for dir in self.addToDirList:
self.addImportDirectory(dir, True, self.addToDirList[dir][0], self.addToDirList[dir][1])
@ -227,10 +233,19 @@ class Importer:
# someone can just create their own python module for it
if filter == "EverleafToFpdb":
print "converting ", file
conv = EverleafToFpdb.Everleaf(self.config, file)
hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase)
hhdir = os.path.join(hhbase,site)
try:
ofile = os.path.join(hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file))
except:
ofile = os.path.join(hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file))
out_fh = open(ofile, 'w') # TODO: seek to previous place in input and append output
in_fh =
conv = EverleafToFpdb.Everleaf(in_fh = file, out_fh = out_fh)
elif filter == "FulltiltToFpdb":
print "converting ", file
conv = FulltiltToFpdb.FullTilt(self.config, file)
conv = FulltiltToFpdb.FullTilt(in_fh = file, out_fh = out_fh)
else:
print "Unknown filter ", filter
return