I attempted to merge monkeyfutz into master.

Let me know how you get on.
I just remembered autoimport probably broken at this point, I have
changed the way HHC is called and only been testing standalone
filtering..
Just try it and then git checkout ^HEAD

Merge branch 'monkeyfutz'

Conflicts:

	pyfpdb/EverleafToFpdb.py
	pyfpdb/FulltiltToFpdb.py
	pyfpdb/Hand.py
This commit is contained in:
Matt Turnbull 2009-03-03 18:45:02 +00:00
commit 7049a959ec
6 changed files with 525 additions and 323 deletions

View File

@ -18,9 +18,8 @@
######################################################################## ########################################################################
import sys import sys
import Configuration
from HandHistoryConverter import * from HandHistoryConverter import *
from time import strftime
# Class for converting Everleaf HH format. # Class for converting Everleaf HH format.
@ -30,26 +29,30 @@ class Everleaf(HandHistoryConverter):
re_SplitHands = re.compile(r"\n\n+") re_SplitHands = re.compile(r"\n\n+")
re_GameInfo = re.compile(r"^(Blinds )?\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) ((?P<LTYPE>NL|PL) )?(?P<GAME>(Hold\'em|Omaha|7 Card Stud))", re.MULTILINE) re_GameInfo = re.compile(r"^(Blinds )?\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) ((?P<LTYPE>NL|PL) )?(?P<GAME>(Hold\'em|Omaha|7 Card Stud))", re.MULTILINE)
re_HandInfo = re.compile(r".*#(?P<HID>[0-9]+)\n.*\n(Blinds )?\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<DATETIME>\d\d\d\d/\d\d/\d\d - \d\d:\d\d:\d\d)\nTable (?P<TABLE>[- a-zA-Z]+)") re_HandInfo = re.compile(r".*#(?P<HID>[0-9]+)\n.*\n(Blinds )?\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<DATETIME>\d\d\d\d/\d\d/\d\d - \d\d:\d\d:\d\d)\nTable (?P<TABLE>[- a-zA-Z]+)")
# re_GameInfo = re.compile(r".*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<LTYPE>(NL|PL)) (?P<GAME>(Hold\'em|Omaha|7 Card Stud))")
#re_HandInfo = re.compile(r".*#(?P<HID>[0-9]+)\n.*\nBlinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<DATETIME>\d\d\d\d/\d\d/\d\d - \d\d:\d\d:\d\d)\nTable (?P<TABLE>[- a-zA-Z]+)", re.MULTILINE)
re_Button = re.compile(r"^Seat (?P<BUTTON>\d+) is the button", re.MULTILINE) re_Button = re.compile(r"^Seat (?P<BUTTON>\d+) is the button", re.MULTILINE)
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_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>.+) \]") re_Board = re.compile(r"\[ (?P<CARDS>.+) \]")
def __init__(self, config, file): def __init__(self, in_path = '-', out_path = '-', follow = False):
print "Initialising Everleaf converter class" """\
HandHistoryConverter.__init__(self, config, file, sitename="Everleaf") # Call super class init. in_path (default '-' = sys.stdin)
self.sitename = "Everleaf" out_path (default '-' = sys.stdout)
self.setFileType("text", "cp1252") 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):
try: if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
self.ofile = os.path.join(self.hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file)) # we need to recompile the player regexs.
except: self.compiledPlayers = players
self.ofile = os.path.join(self.hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file)) player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: "+ player_re)
def compilePlayerRegexs(self):
player_re = "(?P<PNAME>" + "|".join(map(re.escape, self.players)) + ")"
#print "DEBUG player_re: " + player_re
self.re_PostSB = re.compile(r"^%s: posts small blind \[\$? (?P<SB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostSB = re.compile(r"^%s: posts small blind \[\$? (?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r"^%s: posts big blind \[\$? (?P<BB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBB = re.compile(r"^%s: posts big blind \[\$? (?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s: posts both blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBoth = re.compile(r"^%s: posts both blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
@ -66,7 +69,7 @@ class Everleaf(HandHistoryConverter):
["ring", "omahahi", "pl"] ["ring", "omahahi", "pl"]
] ]
def determineGameType(self): def determineGameType(self, handText):
# Cheating with this regex, only support nlhe at the moment # Cheating with this regex, only support nlhe at the moment
# Blinds $0.50/$1 PL Omaha - 2008/12/07 - 21:59:48 # Blinds $0.50/$1 PL Omaha - 2008/12/07 - 21:59:48
# Blinds $0.05/$0.10 NL Hold'em - 2009/02/21 - 11:21:57 # Blinds $0.05/$0.10 NL Hold'em - 2009/02/21 - 11:21:57
@ -81,8 +84,9 @@ class Everleaf(HandHistoryConverter):
structure = "" # nl, pl, cn, cp, fl structure = "" # nl, pl, cn, cp, fl
game = "" game = ""
m = self.re_GameInfo.search(self.obs) m = self.re_GameInfo.search(handText)
if m == None: if m == None:
logging.debug("Gametype didn't match")
return None return None
if m.group('LTYPE') == "NL": if m.group('LTYPE') == "NL":
structure = "nl" structure = "nl"
@ -103,86 +107,91 @@ class Everleaf(HandHistoryConverter):
return gametype return gametype
def readHandInfo(self, hand): def readHandInfo(self, hand):
m = self.re_HandInfo.search(hand.string) m = self.re_HandInfo.search(hand.handText)
if(m == None): if(m == None):
print "DEBUG: re_HandInfo.search failed: '%s'" %(hand.string) logging.info("Didn't match re_HandInfo")
logging.info(hand.handtext)
return None
logging.debug("HID %s, Table %s" % (m.group('HID'), m.group('TABLE')))
hand.handid = m.group('HID') hand.handid = m.group('HID')
hand.tablename = m.group('TABLE') hand.tablename = m.group('TABLE')
hand.max_seats = 6 # assume 6-max unless we have proof it's a larger/smaller game, since everleaf doesn't give seat max info hand.maxseats = 6 # assume 6-max unless we have proof it's a larger/smaller game, since everleaf doesn't give seat max info
# These work, but the info is already in the Hand class - should be used for tourneys though.
# m.group('SB')
# m.group('BB')
# m.group('GAMETYPE')
# Believe Everleaf time is GMT/UTC, no transation necessary # Believe Everleaf time is GMT/UTC, no transation necessary
# Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET] # Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET]
# or : 2008/11/07 12:38:49 ET # or : 2008/11/07 12:38:49 ET
# Not getting it in my HH files yet, so using # Not getting it in my HH files yet, so using
# 2008/11/10 3:58:52 ET # 2008/11/10 3:58:52 ET
#TODO: Do conversion from GMT to ET #TODO: Do conversion from GMT to ET
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this) #TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this)
hand.starttime = time.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S") hand.starttime = time.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S")
return
def readPlayerStacks(self, hand): def readPlayerStacks(self, hand):
m = self.re_PlayerInfo.finditer(hand.string) m = self.re_PlayerInfo.finditer(hand.handText)
for a in m: for a in m:
seatnum = int(a.group('SEAT')) seatnum = int(a.group('SEAT'))
hand.addPlayer(seatnum, a.group('PNAME'), a.group('CASH')) hand.addPlayer(seatnum, a.group('PNAME'), a.group('CASH'))
if seatnum > 6: if seatnum > 6:
hand.max_seats = 10 # everleaf currently does 2/6/10 games, so if seats > 6 are in use, it must be 10-max. hand.maxseats = 10 # everleaf currently does 2/6/10 games, so if seats > 6 are in use, it must be 10-max.
# TODO: implement lookup list by table-name to determine maxes, then fall back to 6 default/10 here, if there's no entry in the list? # TODO: implement lookup list by table-name to determine maxes, then fall back to 6 default/10 here, if there's no entry in the list?
def markStreets(self, hand): def markStreets(self, hand):
# PREFLOP = ** Dealing down cards ** # PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river. # This re fails if, say, river is missing; then we don't get the ** that starts the river.
#m = re.search('(\*\* Dealing down cards \*\*\n)(?P<PREFLOP>.*?\n\*\*)?( 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) #m = re.search('(\*\* Dealing down cards \*\*\n)(?P<PREFLOP>.*?\n\*\*)?( 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.handText,re.DOTALL)
m = re.search(r"\*\* Dealing down cards \*\*(?P<PREFLOP>.+(?=\*\* Dealing Flop \*\*)|.+)" m = re.search(r"\*\* Dealing down cards \*\*(?P<PREFLOP>.+(?=\*\* Dealing Flop \*\*)|.+)"
r"(\*\* Dealing Flop \*\*(?P<FLOP> \[ \S\S, \S\S, \S\S \].+(?=\*\* Dealing Turn \*\*)|.+))?" r"(\*\* Dealing Flop \*\*(?P<FLOP> \[ \S\S, \S\S, \S\S \].+(?=\*\* Dealing Turn \*\*)|.+))?"
r"(\*\* Dealing Turn \*\*(?P<TURN> \[ \S\S \].+(?=\*\* Dealing River \*\*)|.+))?" r"(\*\* Dealing Turn \*\*(?P<TURN> \[ \S\S \].+(?=\*\* Dealing River \*\*)|.+))?"
r"(\*\* Dealing River \*\*(?P<RIVER> \[ \S\S \].+))?", hand.string,re.DOTALL) r"(\*\* Dealing River \*\*(?P<RIVER> \[ \S\S \].+))?", hand.handText,re.DOTALL)
hand.addStreets(m) hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
#print "DEBUG " + street + ":" # If this has been called, street is a street which gets dealt community cards by type hand
#print hand.streets.group(street) + "\n" # 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) # 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)) logging.debug("readCommunityCards (%s)" % street)
hand.setCommunityCards(street, m.group('CARDS').split(', ')) 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): def readBlinds(self, hand):
try: m = self.re_PostSB.search(hand.handText)
m = self.re_PostSB.search(hand.string) if m is not None:
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except Exception, e: # no small blind else:
#print e logging.debug("No small blind")
hand.addBlind(None, None, None) hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.string): for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.string): for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'both', a.group('SBBB')) hand.addBlind(a.group('PNAME'), 'both', a.group('SBBB'))
def readButton(self, hand): def readButton(self, hand):
hand.buttonpos = int(self.re_Button.search(hand.string).group('BUTTON')) hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
def readHeroCards(self, hand): def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.string) m = self.re_HeroCards.search(hand.handText)
if(m == None): if m:
#Not involved in hand
hand.involved = False
else:
hand.hero = m.group('PNAME') hand.hero = m.group('PNAME')
# "2c, qh" -> set(["2c","qc"]) # "2c, qh" -> ["2c","qc"]
# Also works with Omaha hands. # Also works with Omaha hands.
cards = m.group('CARDS') cards = m.group('CARDS')
cards = cards.split(', ') cards = [card.strip() for card in cards.split(',')]
hand.addHoleCards(cards, m.group('PNAME')) hand.addHoleCards(cards, m.group('PNAME'))
else:
#Not involved in hand
hand.involved = False
def readAction(self, hand, street): def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets.group(street)) logging.debug("readAction (%s)" % street)
m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
if action.group('ATYPE') == ' raises': if action.group('ATYPE') == ' raises':
hand.addCallandRaise( street, action.group('PNAME'), action.group('BET') ) hand.addCallandRaise( street, action.group('PNAME'), action.group('BET') )
@ -195,36 +204,52 @@ class Everleaf(HandHistoryConverter):
elif action.group('ATYPE') == ' checks': elif action.group('ATYPE') == ' checks':
hand.addCheck( street, action.group('PNAME')) hand.addCheck( street, action.group('PNAME'))
else: else:
print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),) logging.debug("Unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),))
def readShowdownActions(self, hand): def readShowdownActions(self, hand):
"""Reads lines where holecards are reported in a showdown""" """Reads lines where holecards are reported in a showdown"""
for shows in self.re_ShowdownAction.finditer(hand.string): logging.debug("readShowdownActions")
for shows in self.re_ShowdownAction.finditer(hand.handText):
cards = shows.group('CARDS') cards = shows.group('CARDS')
cards = cards.split(', ') cards = cards.split(', ')
logging.debug("readShowdownActions %s %s" %(cards, shows.group('PNAME')))
hand.addShownCards(cards, shows.group('PNAME')) hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand): def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.string): for m in self.re_CollectPot.finditer(hand.handText):
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
def readShownCards(self,hand): def readShownCards(self,hand):
"""Reads lines where hole & board cards are mixed to form a hand (summary lines)""" """Reads lines where hole & board cards are mixed to form a hand (summary lines)"""
for m in self.re_CollectPot.finditer(hand.string): for m in self.re_CollectPot.finditer(hand.handText):
if m.group('CARDS') is not None: if m.group('CARDS') is not None:
cards = m.group('CARDS') cards = m.group('CARDS')
cards = cards.split(', ') cards = cards.split(', ')
player = m.group('PNAME')
logging.debug("readShownCards %s cards=%s" % (player, cards))
hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards) hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
if __name__ == "__main__": if __name__ == "__main__":
c = Configuration.Config() parser = OptionParser()
if len(sys.argv) == 1: parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/everleaf/Speed_Kuala_full.txt")
testfile = "regression-test-files/everleaf/plo/Naos.txt" parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
else: parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
testfile = sys.argv[1] parser.add_option("-q", "--quiet",
e = Everleaf(c, testfile) action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
e.processFile() parser.add_option("-v", "--verbose",
print str(e) 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)

View File

@ -17,7 +17,6 @@
######################################################################## ########################################################################
import sys import sys
import Configuration
from HandHistoryConverter import * from HandHistoryConverter import *
# FullTilt HH Format converter # FullTilt HH Format converter
@ -32,21 +31,29 @@ class FullTilt(HandHistoryConverter):
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[.0-9]+)\)\n') re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[.0-9]+)\)\n')
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
def __init__(self, config, file): def __init__(self, in_path = '-', out_path = '-', follow = False):
print "Initialising FullTilt converter class" """\
HandHistoryConverter.__init__(self, config, file, sitename="FullTilt") # Call super class init. in_path (default '-' = sys.stdin)
self.sitename = "FullTilt" out_path (default '-' = sys.stdout)
self.setFileType("text", "cp1252") 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): def compilePlayerRegexs(self, players):
player_re = "(?P<PNAME>" + "|".join(map(re.escape, self.players)) + ")" if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
print "DEBUG player_re: " + player_re # we need to recompile the player regexs.
self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re)
self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_Antes = re.compile(r"^%s antes \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE) 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_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_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_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_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) 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)
@ -61,16 +68,17 @@ class FullTilt(HandHistoryConverter):
["ring", "omaha", "pl"] ["ring", "omaha", "pl"]
] ]
def determineGameType(self): def determineGameType(self, handText):
# Cheating with this regex, only support nlhe at the moment # Cheating with this regex, only support nlhe at the moment
# Full Tilt Poker Game #10777181585: Table Deerfly (deep 6) - $0.01/$0.02 - Pot Limit Omaha Hi - 2:24:44 ET - 2009/02/22 # Full Tilt Poker Game #10777181585: Table Deerfly (deep 6) - $0.01/$0.02 - Pot Limit Omaha Hi - 2:24:44 ET - 2009/02/22
# Full Tilt Poker Game #10773265574: Table Butte (6 max) - $0.01/$0.02 - Pot Limit Hold'em - 21:33:46 ET - 2009/02/21 # Full Tilt Poker Game #10773265574: Table Butte (6 max) - $0.01/$0.02 - Pot Limit Hold'em - 21:33:46 ET - 2009/02/21
# Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09 # Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09
# Full Tilt Poker Game #10809877615: Table Danville - $0.50/$1 Ante $0.10 - Limit Razz - 21:47:27 ET - 2009/02/23
structure = "" # nl, pl, cn, cp, fl structure = "" # nl, pl, cn, cp, fl
game = "" game = ""
m = self.re_GameInfo.search(self.obs) m = self.re_GameInfo.search(handText)
if m.group('LTYPE') == "No ": if m.group('LTYPE') == "No ":
structure = "nl" structure = "nl"
elif m.group('LTYPE') == "Pot ": elif m.group('LTYPE') == "Pot ":
@ -85,14 +93,14 @@ class FullTilt(HandHistoryConverter):
elif m.group('GAME') == "Razz": elif m.group('GAME') == "Razz":
game = "razz" game = "razz"
print m.groups() logging.debug("HandInfo: %s", m.groupdict())
gametype = ["ring", game, structure, m.group('SB'), m.group('BB')] gametype = ["ring", game, structure, m.group('SB'), m.group('BB')]
return gametype return gametype
def readHandInfo(self, hand): def readHandInfo(self, hand):
m = self.re_HandInfo.search(hand.string,re.DOTALL) m = self.re_HandInfo.search(hand.handText,re.DOTALL)
#print m.groups() #print m.groups()
hand.handid = m.group('HID') hand.handid = m.group('HID')
hand.tablename = m.group('TABLE') hand.tablename = m.group('TABLE')
@ -113,27 +121,26 @@ class FullTilt(HandHistoryConverter):
#FIXME: hand.buttonpos = int(m.group('BUTTON')) #FIXME: hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand): def readPlayerStacks(self, hand):
m = self.re_PlayerInfo.finditer(hand.string) m = self.re_PlayerInfo.finditer(hand.handText)
players = [] players = []
for a in m: for a in m:
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):
# PREFLOP = ** Dealing down cards ** # PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
if self.gametype[1] == "hold" or self.gametype[1] == "omaha": if hand.gametype[1] in ("hold", "omaha"):
m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)" m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?" r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?" r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.string,re.DOTALL) r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.handText,re.DOTALL)
elif self.gametype[1] == "razz": elif hand.gametype[1] == "razz":
m = re.search(r"(?P<ANTES>.+(?=\*\*\* 3RD STREET \*\*\*)|.+)" m = re.search(r"(?P<ANTES>.+(?=\*\*\* 3RD STREET \*\*\*)|.+)"
r"(\*\*\* 3RD STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4TH STREET \*\*\*)|.+))?" r"(\*\*\* 3RD STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4TH STREET \*\*\*)|.+))?"
r"(\*\*\* 4TH STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5TH STREET \*\*\*)|.+))?" r"(\*\*\* 4TH STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5TH STREET \*\*\*)|.+))?"
r"(\*\*\* 5TH STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6TH STREET \*\*\*)|.+))?" r"(\*\*\* 5TH STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6TH STREET \*\*\*)|.+))?"
r"(\*\*\* 6TH STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* 7TH STREET \*\*\*)|.+))?" r"(\*\*\* 6TH STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* 7TH STREET \*\*\*)|.+))?"
r"(\*\*\* 7TH STREET \*\*\*(?P<SEVENTH>.+))?", hand.string,re.DOTALL) r"(\*\*\* 7TH STREET \*\*\*(?P<SEVENTH>.+))?", hand.handText,re.DOTALL)
hand.addStreets(m) hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
@ -145,34 +152,32 @@ class FullTilt(HandHistoryConverter):
def readBlinds(self, hand): def readBlinds(self, hand):
try: try:
m = self.re_PostSB.search(hand.string) m = self.re_PostSB.search(hand.handText)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind except: # no small blind
hand.addBlind(None, None, None) hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.string): for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.string): for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB')) hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
def readAntes(self, hand): def readAntes(self, hand):
print "DEBUG: reading antes" logging.debug("reading antes")
m = self.re_Antes.finditer(hand.string) m = self.re_Antes.finditer(hand.handText)
for player in m: for player in m:
print "DEBUG: hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')) logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
hand.addAnte(player.group('PNAME'), player.group('ANTE')) hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand): def readBringIn(self, hand):
print "DEBUG: reading bring in" m = self.re_BringIn.search(hand.handText,re.DOTALL)
# print hand.string
m = self.re_BringIn.search(hand.string,re.DOTALL)
print "DEBUG: Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')) print "DEBUG: Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readButton(self, hand): def readButton(self, hand):
hand.buttonpos = int(self.re_Button.search(hand.string).group('BUTTON')) hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
def readHeroCards(self, hand): def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.string) m = self.re_HeroCards.search(hand.handText)
if(m == None): if(m == None):
#Not involved in hand #Not involved in hand
hand.involved = False hand.involved = False
@ -181,25 +186,67 @@ class FullTilt(HandHistoryConverter):
# "2c, qh" -> set(["2c","qc"]) # "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands. # Also works with Omaha hands.
cards = m.group('CARDS') cards = m.group('CARDS')
cards = cards.split(' ') cards = [c.strip() for c in cards.split(' ')]
hand.addHoleCards(cards, m.group('PNAME')) hand.addHoleCards(cards, m.group('PNAME'))
def readPlayerCards(self, hand, street): def readStudPlayerCards(self, hand, street):
#Used for stud hands - borrows the HeroCards regex for now. # This could be the most tricky one to get right.
m = self.re_HeroCards.finditer(hand.streets.group(street)) # It looks for cards dealt in 'street',
print "DEBUG: razz/stud readPlayerCards" # which may or may not be in the section of the hand designated 'street' by markStreets earlier.
print hand.streets.group(street) # 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: for player in m:
print player.groups() logging.debug(player.groupdict())
cards = player.group('CARDS') (pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
if player.group('NEWCARD') != None: if oldcards:
print cards oldcards = [c.strip() for c in oldcards.split(' ')]
cards = cards + " " + player.group('NEWCARD') if newcards:
cards = cards.split(' ') newcards = [c.strip() for c in newcards.split(' ')]
hand.addPlayerCards(cards, player.group('PNAME')) # 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): def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets.group(street)) m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
if action.group('ATYPE') == ' raises to': if action.group('ATYPE') == ' raises to':
hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') ) hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') )
@ -216,17 +263,17 @@ class FullTilt(HandHistoryConverter):
def readShowdownActions(self, hand): def readShowdownActions(self, hand):
for shows in self.re_ShowdownAction.finditer(hand.string): for shows in self.re_ShowdownAction.finditer(hand.handText):
cards = shows.group('CARDS') cards = shows.group('CARDS')
cards = cards.split(' ') cards = cards.split(' ')
hand.addShownCards(cards, shows.group('PNAME')) hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand): def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.string): for m in self.re_CollectPot.finditer(hand.handText):
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
def readShownCards(self,hand): def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.string): for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None: if m.group('CARDS') is not None:
cards = m.group('CARDS') cards = m.group('CARDS')
cards = cards.split(' ') cards = cards.split(' ')
@ -234,12 +281,20 @@ class FullTilt(HandHistoryConverter):
if __name__ == "__main__": if __name__ == "__main__":
c = Configuration.Config() parser = OptionParser()
if len(sys.argv) == 1: 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")
testfile = "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="-")
else: parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
testfile = sys.argv[1] parser.add_option("-q", "--quiet",
print "Converting: ", testfile action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
e = FullTilt(c, testfile) parser.add_option("-v", "--verbose",
e.processFile() action="store_const", const=logging.INFO, dest="verbosity")
print str(e) 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

@ -73,7 +73,7 @@ class GuiBulkImport():
self.importer.setCallHud(False) self.importer.setCallHud(False)
starttime = time() starttime = time()
(stored, dups, partial, errs, ttime) = self.importer.runImport() (stored, dups, partial, errs, ttime) = self.importer.runImport()
print 'GuiBulkImport.import_dir done: Stored: %d Duplicates: %d Partial: %d Errors: %d in %s seconds - %d/sec'\ print 'GuiBulkImport.import_dir done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %d/sec'\
% (stored, dups, partial, errs, ttime, stored / ttime) % (stored, dups, partial, errs, ttime, stored / ttime)
self.importer.clearFileList() self.importer.clearFileList()

View File

@ -18,6 +18,7 @@
import re import re
import sys import sys
import traceback import traceback
import logging
import os import os
import os.path import os.path
from decimal import Decimal from decimal import Decimal
@ -28,18 +29,11 @@ from Exceptions import *
class Hand: class Hand:
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'} UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'}
def __init__(self, sitename, gametype, string): def __init__(self, sitename, gametype, handText):
self.sitename = sitename self.sitename = sitename
self.gametype = gametype self.gametype = gametype
self.string = string 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.handid = 0
self.tablename = "Slartibartfast" self.tablename = "Slartibartfast"
self.hero = "Hiro" self.hero = "Hiro"
self.maxseats = 10 self.maxseats = 10
@ -50,53 +44,38 @@ class Hand:
self.posted = [] self.posted = []
self.involved = True self.involved = True
self.pot = Pot()
#
# Collections indexed by street names # Collections indexed by street names
# self.bets = {}
self.lastBet = {}
# A MatchObject using a groupnames to identify streets. self.streets = {}
# filled by markStreets() self.actions = {} # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']]
self.streets = None self.board = {} # dict from street names to community cards
for street in self.streetList:
# dict from street names to lists of tuples, such as self.streets[street] = "" # portions of the handText, filled by markStreets()
# [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']] self.bets[street] = {}
# actually they're clearly lists but they probably should be tuples. self.lastBet[street] = 0
self.actions = {} self.actions[street] = []
self.board[street] = []
# dict from street names to community cards
self.board = {}
#
# Collections indexed by player names # Collections indexed by player names
# self.holecards = {} # dict from player names to dicts by street ... of tuples ... of holecards
# dict from player names to lists of hole cards
self.holecards = {}
self.stacks = {} self.stacks = {}
self.collected = [] #list of ?
# dict from player names to amounts collected self.collectees = {} # dict from player names to amounts collected (?)
self.collected = []
self.collectees = {}
# Sets of players # Sets of players
self.shown = set() self.shown = set()
self.folded = set() self.folded = set()
self.action = [] # self.action = []
# Things to do with money
self.pot = Pot()
self.totalpot = None self.totalpot = None
self.totalcollected = None self.totalcollected = None
self.rake = 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): def addPlayer(self, seat, name, chips):
"""\ """\
@ -108,66 +87,24 @@ If a player has None chips he won't be added."""
if chips is not None: if chips is not None:
self.players.append([seat, name, chips]) self.players.append([seat, name, chips])
self.stacks[name] = Decimal(chips) self.stacks[name] = Decimal(chips)
self.holecards[name] = set() self.holecards[name] = []
self.pot.addPlayer(name) self.pot.addPlayer(name)
for street in self.streetList: for street in self.streetList:
self.bets[street][name] = [] self.bets[street][name] = []
self.holecards[name] = {} # dict from street names.
def addStreets(self, match): def addStreets(self, match):
# go through m and initialise actions to empty list for each street. # go through m and initialise actions to empty list for each street.
if match is not None: if match:
self.streets = match self.streets.update(match.groupdict())
for street in match.groupdict(): logging.debug("markStreets:\n"+ str(self.streets))
if match.group(street) is not None:
self.actions[street] = []
else: else:
print "empty markStreets match" # better to raise exception and put process hand in a try block logging.error("markstreets didn't match")
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
"""
#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 addPlayerCards(self, cards, player): #def addHoleCards -- to Holdem subclass
"""\
Assigns observed cards to a player.
cards list of card bigrams e.g. ['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): def checkPlayerExists(self,player):
@ -186,6 +123,7 @@ Card ranks will be uppercased
print "[ERROR] discardHoleCard tried to discard a card %s didn't have" % (player,) print "[ERROR] discardHoleCard tried to discard a card %s didn't have" % (player,)
def setCommunityCards(self, street, cards): def setCommunityCards(self, street, cards):
logging.debug("setCommunityCards %s %s" %(street, cards))
self.board[street] = [self.card(c) for c in cards] self.board[street] = [self.card(c) for c in cards]
def card(self,c): def card(self,c):
@ -204,23 +142,21 @@ Card ranks will be uppercased
def addBlind(self, player, blindtype, amount): def addBlind(self, player, blindtype, amount):
# if player is None, it's a missing small blind. # if player is None, it's a missing small blind.
# TODO:
# The situation we need to cover are: # The situation we need to cover are:
# Player in small blind posts # Player in small blind posts
# - this is a bet of 1 sb, as yet uncalled. # - this is a bet of 1 sb, as yet uncalled.
# Player in the big blind posts # Player in the big blind posts
# - this is a bet 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
#print "DEBUG addBlind: %s posts %s, %s" % (player, blindtype, amount)
logging.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
if player is not None: if player is not None:
self.bets['PREFLOP'][player].append(Decimal(amount)) self.bets['PREFLOP'][player].append(Decimal(amount))
self.stacks[player] -= Decimal(amount) self.stacks[player] -= Decimal(amount)
#print "DEBUG %s posts, stack %s" % (player, self.stacks[player]) #print "DEBUG %s posts, stack %s" % (player, self.stacks[player])
act = (player, 'posts', blindtype, amount, self.stacks[player]==0) 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)) self.pot.addMoney(player, Decimal(amount))
if blindtype == 'big blind': if blindtype == 'big blind':
self.lastBet['PREFLOP'] = Decimal(amount) self.lastBet['PREFLOP'] = Decimal(amount)
@ -230,13 +166,7 @@ Card ranks will be uppercased
self.posted = self.posted + [[player,blindtype]] self.posted = self.posted + [[player,blindtype]]
#print "DEBUG: self.posted: %s" %(self.posted) #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): def addCall(self, street, player=None, amount=None):
@ -390,10 +320,10 @@ Map the tuple self.gametype onto the pokerstars string describing it
"cp" : "Cap Pot Limit" "cp" : "Cap Pot Limit"
} }
print "DEBUG: self.gametype: %s" %(self.gametype) logging.debug("gametype: %s" %(self.gametype))
string = "%s %s" %(gs[self.gametype[1]], ls[self.gametype[2]]) retstring = "%s %s" %(gs[self.gametype[1]], ls[self.gametype[2]])
return string return retstring
def lookupLimitBetSize(self): def lookupLimitBetSize(self):
#Lookup table for limit games #Lookup table for limit games
@ -424,22 +354,87 @@ Map the tuple self.gametype onto the pokerstars string describing it
print >>fh, _("%s: folds " %(act[0])) print >>fh, _("%s: folds " %(act[0]))
elif act[1] == 'checks': elif act[1] == 'checks':
print >>fh, _("%s: checks " %(act[0])) print >>fh, _("%s: checks " %(act[0]))
if act[1] == 'calls': elif act[1] == 'calls':
print >>fh, _("%s: calls $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else '')) print >>fh, _("%s: calls $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
if act[1] == 'bets': elif act[1] == 'bets':
print >>fh, _("%s: bets $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else '')) print >>fh, _("%s: bets $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
if act[1] == 'raises': 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 '')) 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): class HoldemOmahaHand(Hand):
def __init__(self, sitename, gametype, string): def __init__(self, hhc, sitename, gametype, handText):
super(HoldemOmahaHand,self).__init__(sitename, gametype, string)
if gametype[1] not in ["hold","omaha"]: if gametype[1] not in ["hold","omaha"]:
pass # or indeed don't pass and complain instead 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.sb = gametype[3]
self.bb = gametype[4] 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 writeHand(self, fh=sys.__stdout__): def writeHand(self, fh=sys.__stdout__):
# PokerStars format. # PokerStars format.
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, _("%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)))
@ -459,34 +454,42 @@ class HoldemOmahaHand(Hand):
smallbet = self.sb smallbet = self.sb
bigbet = self.bb bigbet = self.bb
for a in self.posted: # for a in self.posted:
if(a[1] == "small blind"): # if(a[1] == "small blind"):
print >>fh, _("%s: posts small blind $%s" %(a[0], smallbet)) # print >>fh, _("%s: posts small blind $%s" %(a[0], smallbet))
if(a[1] == "big blind"): # if(a[1] == "big blind"):
print >>fh, _("%s: posts big blind $%s" %(a[0], bigbet)) # print >>fh, _("%s: posts big blind $%s" %(a[0], bigbet))
if(a[1] == "both"): # if(a[1] == "both"):
print >>fh, _("%s: posts small & big blinds $%.2f" %(a[0], (Decimal(smallbet) + Decimal(bigbet)))) # 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 ***") print >>fh, _("*** HOLE CARDS ***")
if self.involved: 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']: for act in self.actions['PREFLOP']:
self.printActionLine(act, fh) self.printActionLine(act, fh)
if 'FLOP' in self.actions: if self.board['FLOP']:
print >>fh, _("*** FLOP *** [%s]" %( " ".join(self.board['FLOP']))) print >>fh, _("*** FLOP *** [%s]" %( " ".join(self.board['FLOP'])))
if self.actions['FLOP']:
for act in self.actions['FLOP']: for act in self.actions['FLOP']:
self.printActionLine(act, fh) 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']))) print >>fh, _("*** TURN *** [%s] [%s]" %( " ".join(self.board['FLOP']), " ".join(self.board['TURN'])))
if self.actions['TURN']:
for act in self.actions['TURN']: for act in self.actions['TURN']:
self.printActionLine(act, fh) 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']) )) 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']: for act in self.actions['RIVER']:
self.printActionLine(act, fh) self.printActionLine(act, fh)
@ -523,11 +526,11 @@ class HoldemOmahaHand(Hand):
seatnum = player[0] seatnum = player[0]
name = player[1] name = player[1]
if name in self.collectees and name in self.shown: 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: elif name in self.collectees:
print >>fh, _("Seat %d: %s collected ($%s)" % (seatnum, name, self.collectees[name])) print >>fh, _("Seat %d: %s collected ($%s)" % (seatnum, name, self.collectees[name]))
elif name in self.shown: 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: elif name in self.folded:
print >>fh, _("Seat %d: %s folded" % (seatnum, name)) print >>fh, _("Seat %d: %s folded" % (seatnum, name))
else: else:
@ -538,15 +541,63 @@ class HoldemOmahaHand(Hand):
class StudHand(Hand): class StudHand(Hand):
def __init__(self, sitename, gametype, string): def __init__(self, hhc, sitename, gametype, handText):
super(StudHand,self).__init__(sitename, gametype, string)
if gametype[1] not in ["razz","stud","stud8"]: if gametype[1] not in ["razz","stud","stud8"]:
pass # or indeed don't pass and complain instead 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.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 writeStudHand(self, fh=sys.__stdout__): def writeHand(self, fh=sys.__stdout__):
# PokerStars format. # PokerStars format.
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, _("%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)) print >>fh, _("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos))
@ -563,8 +614,10 @@ class StudHand(Hand):
if 'THIRD' in self.actions: if 'THIRD' in self.actions:
print >>fh, _("*** 3RD STREET ***") print >>fh, _("*** 3RD STREET ***")
for player in [x for x in self.players if x[1] in players_who_post_antes]: for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
print >>fh, _("Dealt to ") 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']: for act in self.actions['THIRD']:
#FIXME: Need some logic here for bringin vs completes #FIXME: Need some logic here for bringin vs completes
self.printActionLine(act, fh) self.printActionLine(act, fh)

View File

@ -15,12 +15,13 @@
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
import Configuration
import FpdbRegex
import Hand import Hand
import re import re
import sys import sys
import threading
import traceback import traceback
import logging
from optparse import OptionParser
import os import os
import os.path import os.path
import xml.dom.minidom import xml.dom.minidom
@ -71,24 +72,26 @@ letter2names = {
import gettext import gettext
gettext.install('myapplication') gettext.install('myapplication')
class HandHistoryConverter: class HandHistoryConverter(threading.Thread):
# eval = PokerEval()
def __init__(self, config, file, sitename): def __init__(self, in_path = '-', out_path = '-', sitename = None, follow=False):
print "HandHistory init called" threading.Thread.__init__(self)
self.c = config logging.info("HandHistory init called")
self.sitename = sitename
self.obs = "" # One big string # default filetype and codepage. Subclasses should set these properly.
self.filetype = "text" self.filetype = "text"
self.codepage = "utf8" self.codepage = "utf8"
self.doc = None # For XML based HH files
self.file = file self.in_path = in_path
self.hhbase = self.c.get_import_parameters().get("hhArchiveBase") self.out_path = out_path
self.hhbase = os.path.expanduser(self.hhbase) if self.out_path == '-':
self.hhdir = os.path.join(self.hhbase,sitename) # write to stdout
self.gametype = [] self.out_fh = sys.stdout
self.ofile = os.path.join(self.hhdir, os.path.basename(file)) else:
self.rexx = FpdbRegex.FpdbRegex() self.out_fh = open(self.out_path, 'a')
self.players = set() self.sitename = sitename
self.follow = follow
self.compiledPlayers = set()
self.maxseats = 10 self.maxseats = 10
def __str__(self): def __str__(self):
@ -104,6 +107,51 @@ class HandHistoryConverter:
#tmp = tmp + "\tsb/bb: '%s/%s'\n" % (self.gametype[3], self.gametype[4]) #tmp = tmp + "\tsb/bb: '%s/%s'\n" % (self.gametype[3], self.gametype[4])
return tmp 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): def processFile(self):
starttime = time.time() starttime = time.time()
if not self.sanityCheck(): if not self.sanityCheck():
@ -122,7 +170,7 @@ class HandHistoryConverter:
self.hands = self.splitFileIntoHands() self.hands = self.splitFileIntoHands()
outfile = open(self.ofile, 'w') outfile = open(self.ofile, 'w')
for hand in self.hands: for hand in self.hands:
#print "\nDEBUG: Input:\n"+hand.string #print "\nDEBUG: Input:\n"+hand.handText
self.readHandInfo(hand) self.readHandInfo(hand)
self.readPlayerStacks(hand) self.readPlayerStacks(hand)
@ -265,7 +313,7 @@ class HandHistoryConverter:
def splitFileIntoHands(self): def splitFileIntoHands(self):
hands = [] hands = []
self.obs.strip() self.obs = self.obs.strip()
list = self.re_SplitHands.split(self.obs) list = self.re_SplitHands.split(self.obs)
list.pop() #Last entry is empty list.pop() #Last entry is empty
for l in list: for l in list:
@ -273,13 +321,19 @@ class HandHistoryConverter:
hands = hands + [Hand.Hand(self.sitename, self.gametype, l)] hands = hands + [Hand.Hand(self.sitename, self.gametype, l)]
return hands return hands
def readFile(self, filename): def readFile(self):
"""Read file""" """Read in_path into self.obs or self.doc"""
print "Reading file: '%s'" %(filename)
if(self.filetype == "text"): if(self.filetype == "text"):
infile=codecs.open(filename, "r", self.codepage) if self.in_path == '-':
self.obs = infile.read() # read from stdin
infile.close() 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"): elif(self.filetype == "xml"):
try: try:
doc = xml.dom.minidom.parse(filename) doc = xml.dom.minidom.parse(filename)

View File

@ -182,9 +182,10 @@ class Importer:
#Run import on updated files, then store latest update time. #Run import on updated files, then store latest update time.
def runUpdated(self): 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 #todo: make efficient - always checks for new file, should be able to use mtime of directory
# ^^ May not work on windows # ^^ May not work on windows
for site in self.dirlist: for site in self.dirlist:
self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1]) self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
@ -197,10 +198,15 @@ class Importer:
self.updated[file] = time() self.updated[file] = time()
except: except:
self.updated[file] = time() self.updated[file] = time()
# This codepath only runs first time the file is found, if modified in the last # If modified in the last minute run an immediate import.
# minute run an immediate import. # This codepath only runs first time the file is found.
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 (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]) 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: for dir in self.addToDirList:
self.addImportDirectory(dir, True, self.addToDirList[dir][0], self.addToDirList[dir][1]) 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 # someone can just create their own python module for it
if filter == "EverleafToFpdb": if filter == "EverleafToFpdb":
print "converting ", file 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": elif filter == "FulltiltToFpdb":
print "converting ", file print "converting ", file
conv = FulltiltToFpdb.FullTilt(self.config, file) conv = FulltiltToFpdb.FullTilt(in_fh = file, out_fh = out_fh)
else: else:
print "Unknown filter ", filter print "Unknown filter ", filter
return return