Merge branch 'master' of git://git.assembla.com/mctfpdb

Conflicts:

	pyfpdb/EverleafToFpdb.py
	pyfpdb/FulltiltToFpdb.py
This commit is contained in:
Worros 2009-03-08 14:09:16 +09:00
commit da88e1b20c
10 changed files with 510 additions and 75 deletions

View File

@ -609,7 +609,7 @@ class Config:
def execution_path(self, filename): def execution_path(self, filename):
"""Join the fpdb path to filename.""" """Join the fpdb path to filename."""
return os.path.join(os.path.dirname(inspect.getfile(sys._getframe(1))), filename) return os.path.join(os.path.dirname(inspect.getfile(sys._getframe(0))), filename)
if __name__== "__main__": if __name__== "__main__":
c = Config() c = Config()

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: iso-8859-15 -*- # -*- coding: utf-8 -*-
# Copyright 2008, Carl Gherardi # Copyright 2008, Carl Gherardi
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -11,7 +11,7 @@
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
@ -27,13 +27,14 @@ class Everleaf(HandHistoryConverter):
# Static regexes # Static regexes
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(u"^(Blinds )?(?P<currency>\$| €|)(?P<sb>[.0-9]+)/(?:\$| €)?(?P<bb>[.0-9]+) (?P<limit>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(u".*#(?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_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(u"^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\s+((?:\$| €|) (?P<CASH>[.0-9]+) (USD|EUR|)|new player|All-in) \)", re.MULTILINE)
re_Board = re.compile(r"\[ (?P<CARDS>.+) \]") re_Board = re.compile(r"\[ (?P<CARDS>.+) \]")
def __init__(self, in_path = '-', out_path = '-', follow = False):
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
@ -42,22 +43,24 @@ follow : whether to tail -f the input"""
logging.info("Initialising Everleaf converter class") logging.info("Initialising Everleaf converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.start() if autostart:
self.start()
def compilePlayerRegexs(self, players): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# we need to recompile the player regexs. # we need to recompile the player regexs.
self.compiledPlayers = players self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: "+ player_re) logging.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(u"^%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(u"^%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(u"^%s: posts both blinds \[(?:\$| €|) (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE) self.re_HeroCards = re.compile(u"^Dealt to %s \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[\$ (?P<BET>[.\d]+) (USD|EUR)\])?" % player_re, re.MULTILINE) self.re_Action = re.compile(u"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P<BET>[.\d]+) (USD|EUR|)\])?" % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(r"^%s shows \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(u"^%s shows \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"^%s wins \$ (?P<POT>[.\d]+) (USD|EUR)(.*?\[ (?P<CARDS>.*?) \])?" % player_re, re.MULTILINE) self.re_CollectPot = re.compile(u"^%s wins (?:\$| €|) (?P<POT>[.\d]+) (USD|EUR|chips)(.*?\[ (?P<CARDS>.*?) \])?" % player_re, re.MULTILINE)
self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE) self.re_SitsOut = re.compile(u"^%s sits out" % player_re, re.MULTILINE)
def readSupportedGames(self): def readSupportedGames(self):
return [["ring", "hold", "nl"], return [["ring", "hold", "nl"],
@ -67,6 +70,33 @@ follow : whether to tail -f the input"""
] ]
def determineGameType(self, handText): def determineGameType(self, handText):
info = {}
m = self.re_GameInfo.search(handText)
if not m:
return None
info.update(m.groupdict())
limits = { 'NL':'nl', 'PL':'pl', '':'fl' }
games = { 'Hold\'em':'hold', 'Omaha':'omahahi', 'Razz':'razz','7 Card Stud':'studhi' }
currencies = { u'':'EUR', '$':'USD', '':'T$' }
for key in info:
if key == 'limit':
info[key] = limits[info[key]]
if key == 'game':
info[key] = games[info[key]]
if key == 'sb':
pass
if key == 'bb':
pass
if key == 'currency':
info[key] = currencies[info[key]]
return info
def determineGameType2(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
@ -80,6 +110,7 @@ follow : whether to tail -f the input"""
structure = "" # nl, pl, cn, cp, fl structure = "" # nl, pl, cn, cp, fl
game = "" game = ""
currency = "USD" # USD, EUR
m = self.re_GameInfo.search(handText) m = self.re_GameInfo.search(handText)
if m == None: if m == None:
@ -99,7 +130,7 @@ follow : whether to tail -f the input"""
elif m.group('GAME') == "7 Card Stud": elif m.group('GAME') == "7 Card Stud":
game = "studhi" # Everleaf currently only does Hi stud game = "studhi" # Everleaf currently only does Hi stud
gametype = ["ring", game, structure, m.group('SB'), m.group('BB')] gametype = ["ring", game, structure, m.group('SB'), m.group('BB'), currency]
return gametype return gametype
@ -107,7 +138,7 @@ follow : whether to tail -f the input"""
m = self.re_HandInfo.search(hand.handText) m = self.re_HandInfo.search(hand.handText)
if(m == None): if(m == None):
logging.info("Didn't match re_HandInfo") logging.info("Didn't match re_HandInfo")
logging.info(hand.handtext) logging.info(hand.handText)
return None return None
logging.debug("HID %s, Table %s" % (m.group('HID'), m.group('TABLE'))) logging.debug("HID %s, Table %s" % (m.group('HID'), m.group('TABLE')))
hand.handid = m.group('HID') hand.handid = m.group('HID')
@ -184,6 +215,7 @@ follow : whether to tail -f the input"""
#Not involved in hand #Not involved in hand
hand.involved = False hand.involved = False
def readAction(self, hand, street): def readAction(self, hand, street):
logging.debug("readAction (%s)" % street) logging.debug("readAction (%s)" % street)
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])

View File

@ -32,7 +32,7 @@ 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, in_path = '-', out_path = '-', follow = False): def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
@ -41,7 +41,8 @@ follow : whether to tail -f the input"""
logging.info("Initialising FullTilt converter class") logging.info("Initialising FullTilt converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.start() if autostart:
self.start()
def compilePlayerRegexs(self, players): def compilePlayerRegexs(self, players):
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
@ -172,7 +173,7 @@ follow : whether to tail -f the input"""
def readBringIn(self, hand): def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL) m = self.re_BringIn.search(hand.handText,re.DOTALL)
logging.debug("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))) logging.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):
@ -187,7 +188,7 @@ follow : whether to tail -f the input"""
hand.hero = m.group('PNAME') hand.hero = m.group('PNAME')
# "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('NEWCARDS')
cards = [c.strip() for c in cards.split(' ')] cards = [c.strip() for c in cards.split(' ')]
hand.addHoleCards(cards, m.group('PNAME')) hand.addHoleCards(cards, m.group('PNAME'))

55
pyfpdb/GuiAutoImport.py Normal file → Executable file
View File

@ -26,7 +26,8 @@ import os
import sys import sys
import time import time
import fpdb_import import fpdb_import
from optparse import OptionParser
import Configuration
class GuiAutoImport (threading.Thread): class GuiAutoImport (threading.Thread):
def __init__(self, settings, config): def __init__(self, settings, config):
@ -134,7 +135,8 @@ class GuiAutoImport (threading.Thread):
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
else: else:
command = self.config.execution_path('HUD_main.py') command = os.path.join(sys.path[0], 'HUD_main.py')
#command = self.config.execution_path('HUD_main.py') # Hi Ray. Sorry about this, kludging.
bs = 1 bs = 1
self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
@ -214,18 +216,39 @@ if __name__== "__main__":
def destroy(*args): # call back for terminating the main eventloop def destroy(*args): # call back for terminating the main eventloop
gtk.main_quit() gtk.main_quit()
settings = {} # settings = {}
settings['db-host'] = "192.168.1.100" # settings['db-host'] = "192.168.1.100"
settings['db-user'] = "mythtv" # settings['db-user'] = "mythtv"
settings['db-password'] = "mythtv" # settings['db-password'] = "mythtv"
settings['db-databaseName'] = "fpdb" # settings['db-databaseName'] = "fpdb"
settings['hud-defaultInterval'] = 10 # settings['hud-defaultInterval'] = 10
settings['hud-defaultPath'] = 'C:/Program Files/PokerStars/HandHistory/nutOmatic' # settings['hud-defaultPath'] = 'C:/Program Files/PokerStars/HandHistory/nutOmatic'
settings['callFpdbHud'] = True # settings['callFpdbHud'] = True
i = GuiAutoImport(settings) parser = OptionParser()
main_window = gtk.Window() parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True, help="don't start gui")
main_window.connect("destroy", destroy)
main_window.add(i.mainVBox) (options, sys.argv) = parser.parse_args()
main_window.show()
gtk.main() config = Configuration.Config()
# db = fpdb_db.fpdb_db()
settings = {}
if os.name == 'nt': settings['os'] = 'windows'
else: settings['os'] = 'linuxmac'
settings.update(config.get_db_parameters('fpdb'))
settings.update(config.get_tv_parameters())
settings.update(config.get_import_parameters())
settings.update(config.get_default_paths())
if(options.gui == True):
i = GuiAutoImport(settings, config)
main_window = gtk.Window()
main_window.connect('destroy', destroy)
main_window.add(i.mainVBox)
main_window.show()
gtk.main()
else:
pass

View File

@ -283,6 +283,8 @@ class GuiGraphViewer (threading.Thread):
win.destroy() win.destroy()
def exportGraph (self, widget, data): def exportGraph (self, widget, data):
if self.fig is None:
return # Might want to disable export button until something has been generated.
dia_chooser = gtk.FileChooserDialog(title="Please choose the directory you wish to export to:", dia_chooser = gtk.FileChooserDialog(title="Please choose the directory you wish to export to:",
action=gtk.FILE_CHOOSER_ACTION_OPEN, action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
@ -363,6 +365,7 @@ class GuiGraphViewer (threading.Thread):
graphButton.connect("clicked", self.generateGraph, "cliced data") graphButton.connect("clicked", self.generateGraph, "cliced data")
graphButton.show() graphButton.show()
self.fig = None
self.exportButton=gtk.Button("Export to File") self.exportButton=gtk.Button("Export to File")
self.exportButton.connect("clicked", self.exportGraph, "show clicked") self.exportButton.connect("clicked", self.exportGraph, "show clicked")
self.exportButton.show() self.exportButton.show()

View File

@ -83,6 +83,7 @@ seat (int) indicating the seat
name (string) player name name (string) player name
chips (string) the chips the player has at the start of the hand (can be None) chips (string) the chips the player has at the start of the hand (can be None)
If a player has None chips he won't be added.""" If a player has None chips he won't be added."""
logging.debug("addPlayer: %s %s (%s)" % (seat, name, chips))
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)
@ -110,15 +111,7 @@ If a player has None chips he won't be added."""
print "checkPlayerExists", player, "fail" print "checkPlayerExists", player, "fail"
raise FpdbParseError raise FpdbParseError
def discardHoleCards(self, cards, player):
try:
self.checkPlayerExists(player)
for card in cards:
self.holecards[player].remove(card)
except FpdbParseError, e:
pass
except ValueError:
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)) logging.debug("setCommunityCards %s %s" %(street, cards))
@ -302,7 +295,7 @@ Map the tuple self.gametype onto the pokerstars string describing it
"omahahi" : "Omaha", "omahahi" : "Omaha",
"omahahilo" : "FIXME", "omahahilo" : "FIXME",
"razz" : "Razz", "razz" : "Razz",
"studhi" : "FIXME", "studhi" : "7 Card Stud",
"studhilo" : "FIXME", "studhilo" : "FIXME",
"fivedraw" : "5 Card Draw", "fivedraw" : "5 Card Draw",
"27_1draw" : "FIXME", "27_1draw" : "FIXME",
@ -317,7 +310,7 @@ Map the tuple self.gametype onto the pokerstars string describing it
} }
logging.debug("gametype: %s" %(self.gametype)) logging.debug("gametype: %s" %(self.gametype))
retstring = "%s %s" %(gs[self.gametype[1]], ls[self.gametype[2]]) retstring = "%s %s" %(gs[self.gametype['game']], ls[self.gametype['limit']])
return retstring return retstring
@ -366,22 +359,22 @@ Map the tuple self.gametype onto the pokerstars string describing it
class HoldemOmahaHand(Hand): class HoldemOmahaHand(Hand):
def __init__(self, hhc, sitename, gametype, handText): def __init__(self, hhc, sitename, gametype, handText):
if gametype[1] not in ["hold","omaha"]: if gametype['game'] 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") logging.debug("HoldemOmahaHand")
self.streetList = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order self.streetList = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order
self.communityStreets = ['FLOP', 'TURN', 'RIVER'] self.communityStreets = ['FLOP', 'TURN', 'RIVER']
self.actionStreets = ['PREFLOP','FLOP','TURN','RIVER'] self.actionStreets = ['PREFLOP','FLOP','TURN','RIVER']
Hand.__init__(self, sitename, gametype, handText) Hand.__init__(self, sitename, gametype, handText)
self.sb = gametype[3] self.sb = gametype['sb']
self.bb = gametype[4] self.bb = gametype['bb']
#Populate a HoldemOmahaHand #Populate a HoldemOmahaHand
#Generally, we call 'read' methods here, which get the info according to the particular filter (hhc) #Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
# which then invokes a 'addXXX' callback # which then invokes a 'addXXX' callback
hhc.readHandInfo(self) hhc.readHandInfo(self)
hhc.readPlayerStacks(self) hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(players = set([player[1] for player in self.players])) hhc.compilePlayerRegexs(self)
hhc.markStreets(self) hhc.markStreets(self)
hhc.readBlinds(self) hhc.readBlinds(self)
hhc.readButton(self) hhc.readButton(self)
@ -440,11 +433,11 @@ Card ranks will be uppercased
for player in [x for x in self.players if x[1] in players_who_act_preflop]: for player in [x for x in self.players if x[1] in players_who_act_preflop]:
#Only print stacks of players who do something preflop #Only print stacks of players who do something preflop
print >>fh, _("Seat %s: %s ($%s)" %(player[0], player[1], player[2])) print >>fh, _("Seat %s: %s ($%s in chips) " %(player[0], player[1], player[2]))
#May be more than 1 bb posting #May be more than 1 bb posting
if self.gametype[2] == "fl": if self.gametype['limit'] == "fl":
(smallbet, bigbet) = self.lookupLimitBetSize() (smallbet, bigbet) = self.lookupLimitBetSize()
else: else:
smallbet = self.sb smallbet = self.sb
@ -533,24 +526,37 @@ Card ranks will be uppercased
print >>fh, _("Seat %d: %s mucked" % (seatnum, name)) print >>fh, _("Seat %d: %s mucked" % (seatnum, name))
print >>fh, "\n\n" print >>fh, "\n\n"
class DrawHand(Hand):
def __init__(self, hhc, sitename, gametype, handText):
if gametype['game'] not in ["badugi","5-card-draw"]:
pass # or indeed don't pass and complain instead
def discardHoleCards(self, cards, player):
try:
self.checkPlayerExists(player)
for card in cards:
self.holecards[player].remove(card)
except FpdbParseError, e:
pass
except ValueError:
print "[ERROR] discardHoleCard tried to discard a card %s didn't have" % (player,)
class StudHand(Hand): class StudHand(Hand):
def __init__(self, hhc, sitename, gametype, handText): def __init__(self, hhc, sitename, gametype, handText):
if gametype[1] not in ["razz","stud","stud8"]: if gametype['game'] 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'] self.holeStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
Hand.__init__(self, sitename, gametype, handText) Hand.__init__(self, sitename, gametype, handText)
self.sb = gametype[3] self.sb = gametype['sb']
self.bb = gametype[4] self.bb = gametype['bb']
#Populate the StudHand #Populate the StudHand
#Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc) #Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
# which then invokes a 'addXXX' callback # which then invokes a 'addXXX' callback
hhc.readHandInfo(self) hhc.readHandInfo(self)
hhc.readPlayerStacks(self) hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(players = set([player[1] for player in self.players])) hhc.compilePlayerRegexs(self)
hhc.markStreets(self) hhc.markStreets(self)
hhc.readAntes(self) hhc.readAntes(self)
hhc.readBringIn(self) hhc.readBringIn(self)

View File

@ -88,13 +88,14 @@ class HandHistoryConverter(threading.Thread):
# write to stdout # write to stdout
self.out_fh = sys.stdout self.out_fh = sys.stdout
else: else:
self.out_fh = open(self.out_path, 'a') self.out_fh = open(self.out_path, 'a') #TODO: append may be overly conservative.
self.sitename = sitename self.sitename = sitename
self.follow = follow self.follow = follow
self.compiledPlayers = set() self.compiledPlayers = set()
self.maxseats = 10 self.maxseats = 10
def __str__(self): def __str__(self):
#TODO : I got rid of most of the hhdir stuff.
tmp = "HandHistoryConverter: '%s'\n" % (self.sitename) tmp = "HandHistoryConverter: '%s'\n" % (self.sitename)
tmp = tmp + "\thhbase: '%s'\n" % (self.hhbase) tmp = tmp + "\thhbase: '%s'\n" % (self.hhbase)
tmp = tmp + "\thhdir: '%s'\n" % (self.hhdir) tmp = tmp + "\thhdir: '%s'\n" % (self.hhdir)
@ -116,6 +117,8 @@ class HandHistoryConverter(threading.Thread):
logging.info("Parsing %d hands" % len(handsList)) logging.info("Parsing %d hands" % len(handsList))
for handtext in handsList: for handtext in handsList:
self.processHand(handtext) self.processHand(handtext)
if self.out_fh != sys.stdout:
self.ouf_fh.close()
def tailHands(self): def tailHands(self):
"""pseudo-code""" """pseudo-code"""
@ -140,16 +143,22 @@ class HandHistoryConverter(threading.Thread):
def processHand(self, handtext): def processHand(self, handtext):
gametype = self.determineGameType(handtext) gametype = self.determineGameType(handtext)
logging.debug("gametype %s" % gametype)
if gametype is None: if gametype is None:
return return
if gametype[1] in ("hold", "omaha"): hand = None
if gametype['game'] in ("hold", "omaha"):
hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext) hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)
elif gametype[1] in ("razz","stud","stud8"): elif gametype['game'] in ("razz","stud","stud8"):
hand = Hand.StudHand(self, self.sitename, gametype, handtext) hand = Hand.StudHand(self, self.sitename, gametype, handtext)
hand.writeHand(self.out_fh) if hand:
hand.writeHand(self.out_fh)
else:
logging.info("Unsupported game type: %s" % gametype)
# TODO: pity we don't know the HID at this stage. Log the entire hand?
# From the log we can deduce that it is the hand after the one before :)
def processFile(self): def processFile(self):

325
pyfpdb/PokerStarstoFpdb.py Executable file
View File

@ -0,0 +1,325 @@
#!/usr/bin/env python
# Copyright 2008, Carl Gherardi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
import sys
from HandHistoryConverter import *
# PokerStars HH Format
#PokerStars Game #20461877044: Hold'em No Limit ($1/$2) - 2008/09/16 18:58:01 ET
#Table 'Gianfar IV' 6-max Seat #1 is the button
#Seat 1: ZeKGB ($224 in chips)
#Seat 2: quimboavida ($107.75 in chips)
#Seat 3: tropical100 ($190 in chips)
#Seat 4: jackhama33 ($54.95 in chips)
#Seat 5: Olubanu ($196 in chips)
#Seat 6: LSgambler ($205.35 in chips)
#quimboavida: posts small blind $1
#tropical100: posts big blind $2
#*** HOLE CARDS ***
#jackhama33: folds
#Olubanu: folds
#LSgambler: folds
#ZeKGB: folds
#quimboavida: calls $1
#tropical100: raises $5 to $7
#quimboavida: calls $5
#*** FLOP *** [3d Qs Kd]
#quimboavida: bets $10
#tropical100: calls $10
#*** TURN *** [3d Qs Kd] [Ah]
#quimboavida: checks
#tropical100: checks
#*** RIVER *** [3d Qs Kd Ah] [7c]
#quimboavida: bets $30
#tropical100: folds
#quimboavida collected $32.35 from pot
#*** SUMMARY ***
#Total pot $34 | Rake $1.65
#Board [3d Qs Kd Ah 7c]
#Seat 1: ZeKGB (button) folded before Flop (didn't bet)
#Seat 2: quimboavida (small blind) collected ($32.35)
#Seat 3: tropical100 (big blind) folded on the River
#Seat 4: jackhama33 folded before Flop (didn't bet)
#Seat 5: Olubanu folded before Flop (didn't bet)
#Seat 6: LSgambler folded before Flop (didn't bet)
#PokerStars Game #25381215423: HORSE (Razz Limit, $0.10/$0.20) - 2009/02/26 15:20:19 ET
#Table 'Natalie V' 8-max
class PokerStars(HandHistoryConverter):
# Static regexes
re_GameInfo = re.compile('PokerStars Game #(?P<HID>[0-9]+):\s+(HORSE)? \(?(?P<GAME>Hold\'em|Razz|7 Card Stud) (?P<LTYPE>No Limit|Limit|Pot Limit),? \(?\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\) - (?P<DATETIME>.*$)', re.MULTILINE)
re_SplitHands = re.compile('\n\n+')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE)
re_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE)
re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
"""\
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="PokerStars", follow=follow)
logging.info("Initialising PokerStars converter class")
self.filetype = "text"
self.codepage = "cp1252"
if autostart:
self.start()
def compilePlayerRegexs(self, players):
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# 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 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_Antes = re.compile(r"^%s: posts the ante \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s: brings-in low 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<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s:(?P<ATYPE> bets| checks| raises| calls| folds)( \$(?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)
self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self):
return []
def determineGameType(self, handText):
game = None
structure = None
sb = None
bb = None
info = {}
m = self.re_GameInfo.search(handText)
if m:
info.update(m.groupdict())
else:
return None
ltypes = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
gtypes = { 'Hold\'em':'hold', 'Omaha':'omahahi', 'Razz':'razz','7 Card Stud':'studhi' }
for key in info:
if key == 'LTYPE':
structure = ltypes[info[key]]
if key == 'GAME':
game = gtypes[info[key]]
if key == 'SB':
sb = info[key]
if key == 'BB':
bb = info[key]
gametype = ["ring", game, structure, sb, bb]
return gametype
def readHandInfo(self, hand):
info = {}
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m: info.update(m.groupdict())
m = self.re_GameInfo.search(hand.handText)
if m: info.update(m.groupdict())
m = self.re_Button.search(hand.handText)
if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info)
for key in info:
if key == 'DATETIME':
datetime = info[key].replace(" - "," ") # some are like "2009/02/26 - 15:22:55 ET"
datetime = datetime.replace(" (ET)","") # kludge for now.
datetime = datetime.replace(" ET","") # kludge for now.
hand.starttime = time.strptime(datetime, "%Y/%m/%d %H:%M:%S")
if key == 'HID':
hand.handid = info[key]
if key == 'TABLE':
hand.tablename = info[key]
if key == 'BUTTON':
hand.buttonpos = info[key]
def readButton(self, hand):
m = self.re_Button.search(hand.handText)
if m:
hand.buttonpos = int(m.group('BUTTON'))
else:
logging.info('readButton: not found')
def readPlayerStacks(self, hand):
logging.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
if hand.gametype[1] in ("hold", "omaha"):
m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
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"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.handText,re.DOTALL)
elif hand.gametype[1] in ("razz"):
m = re.search(r"(?P<ANTES>.+(?=\*\*\* 3rd STREET \*\*\*)|.+)"
r"(\*\*\* 3rd STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4th STREET \*\*\*)|.+))?"
r"(\*\*\* 4th STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5th STREET \*\*\*)|.+))?"
r"(\*\*\* 5th STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6th STREET \*\*\*)|.+))?"
r"(\*\*\* 6th STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* 7th STREET \*\*\*)|.+))?"
r"(\*\*\* 7th STREET \*\*\*(?P<SEVENTH>.+))?", hand.handText,re.DOTALL)
hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
#print "DEBUG readCommunityCards:", street, hand.streets.group(street)
m = self.re_Board.search(hand.streets[street])
hand.setCommunityCards(street, m.group('CARDS').split(' '))
def readAntes(self, hand):
logging.debug("reading antes")
m = self.re_Antes.finditer(hand.handText)
for player in m:
logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL)
if m:
logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand):
try:
m = self.re_PostSB.search(hand.handText)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.handText)
if(m == None):
#Not involved in hand
hand.involved = False
else:
hand.hero = m.group('PNAME')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('NEWCARDS')
cards = set(cards.split(' '))
hand.addHoleCards(cards, m.group('PNAME'))
def readStudPlayerCards(self, hand, street):
# See comments of reference implementation in FullTiltToFpdb.py
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street])
for player in m:
logging.debug(player.groupdict())
(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(' ')]
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])
for action in m:
if action.group('ATYPE') == ' raises':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' calls':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' bets':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' folds':
hand.addFold( street, action.group('PNAME'))
elif action.group('ATYPE') == ' checks':
hand.addCheck( street, action.group('PNAME'))
else:
print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),)
def readShowdownActions(self, hand):
for shows in self.re_ShowdownAction.finditer(hand.handText):
cards = shows.group('CARDS')
cards = set(cards.split(' '))
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'))
def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards=cards, player=m.group('PNAME'))
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/pokerstars/HH20090226 Natalie V - $0.10-$0.20 - HORSE.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 = PokerStars(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -118,7 +118,6 @@ class Importer:
def addBulkImportImportFileOrDir(self, inputPath,filter = "passthrough"): def addBulkImportImportFileOrDir(self, inputPath,filter = "passthrough"):
"""Add a file or directory for bulk import""" """Add a file or directory for bulk import"""
# Bulk import never monitors # Bulk import never monitors
# if directory, add all files in it. Otherwise add single file. # if directory, add all files in it. Otherwise add single file.
# TODO: only add sane files? # TODO: only add sane files?
if os.path.isdir(inputPath): if os.path.isdir(inputPath):
@ -133,6 +132,7 @@ class Importer:
#dirlist is a hash of lists: #dirlist is a hash of lists:
#dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] } #dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] }
def addImportDirectory(self,dir,monitor = False, site = "default", filter = "passthrough"): def addImportDirectory(self,dir,monitor = False, site = "default", filter = "passthrough"):
#gets called by GuiAutoImport.
#This should really be using os.walk #This should really be using os.walk
#http://docs.python.org/library/os.html #http://docs.python.org/library/os.html
if os.path.isdir(dir): if os.path.isdir(dir):
@ -231,18 +231,18 @@ class Importer:
# TODO: Shouldn't we be able to use some sort of lambda or something to just call a Python object by whatever name we specify? then we don't have to hardcode them, # TODO: Shouldn't we be able to use some sort of lambda or something to just call a Python object by whatever name we specify? then we don't have to hardcode them,
# 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 in ("EverleafToFpdb","Everleaf"):
print "converting ", file print "converting ", file
hhbase = self.config.get_import_parameters().get("hhArchiveBase") hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase) hhbase = os.path.expanduser(hhbase)
hhdir = os.path.join(hhbase,site) hhdir = os.path.join(hhbase,site)
try: try:
ofile = os.path.join(hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file)) out_path = os.path.join(hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file))
except: except:
ofile = os.path.join(hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file)) out_path = 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 #out_fh = open(ofile, 'w') # TODO: seek to previous place in input and append output
in_fh = conv = EverleafToFpdb.Everleaf(in_path = file, out_path = out_path)
conv = EverleafToFpdb.Everleaf(in_fh = file, out_fh = out_fh) conv.join()
elif filter == "FulltiltToFpdb": elif filter == "FulltiltToFpdb":
print "converting ", file print "converting ", file
conv = FulltiltToFpdb.FullTilt(in_fh = file, out_fh = out_fh) conv = FulltiltToFpdb.FullTilt(in_fh = file, out_fh = out_fh)

36
pyfpdb/test_Everleaf.py Normal file
View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import EverleafToFpdb
import py
class TestEverleaf:
def testGameInfo1(self):
e = EverleafToFpdb.Everleaf(autostart=False)
g = """Everleaf Gaming Game #3732225
***** Hand history for game #3732225 *****
Blinds 0.50/ 1 NL Hold'em - 2009/01/11 - 16:09:40
Table Casino Lyon Vert 58
Seat 3 is the button
Total number of players: 6"""
assert e.determineGameType(g) == {'sb':'0.50', 'bb':'1','game':"hold", 'currency':'EUR', 'limit':'nl'}
def testGameInfo2(self):
e = EverleafToFpdb.Everleaf(autostart=False)
g = """Everleaf Gaming Game #55198191
***** Hand history for game #55198191 *****
Blinds $0.50/$1 NL Hold'em - 2008/09/01 - 10:02:11
Table Speed Kuala
Seat 8 is the button
Total number of players: 10"""
assert e.determineGameType(g) == {'sb':'0.50', 'bb':'1','game':"hold", 'currency':'USD', 'limit':'nl'}
def testGameInfo3(self):
# Note: It looks difficult to distinguish T$ from play money.
e = EverleafToFpdb.Everleaf(autostart=False)
g = """Everleaf Gaming Game #75065769
***** Hand history for game #75065769 *****
Blinds 10/20 NL Hold'em - 2009/02/25 - 17:30:32
Table 2
Seat 1 is the button
Total number of players: 10"""
assert e.determineGameType(g) == {'sb':'10', 'bb':'20','game':"hold", 'currency':'T$', 'limit':'nl'}