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

This commit is contained in:
sqlcoder 2009-09-20 22:52:05 +01:00
commit 6357f22f11
38 changed files with 6186 additions and 4124 deletions

72
pyfpdb/AbsoluteToFpdb.py Normal file → Executable file
View File

@ -27,6 +27,13 @@ from HandHistoryConverter import *
class Absolute(HandHistoryConverter): class Absolute(HandHistoryConverter):
# Class Variables
sitename = "Absolute"
filetype = "text"
codepage = "cp1252"
siteid = 8
HORSEHand = False
# Static regexes # Static regexes
re_SplitHands = re.compile(r"\n\n\n+") re_SplitHands = re.compile(r"\n\n\n+")
re_TailSplitHands = re.compile(r"(\n\n\n+)") re_TailSplitHands = re.compile(r"(\n\n\n+)")
@ -35,8 +42,11 @@ class Absolute(HandHistoryConverter):
#Seat 6 - FETS63 ($0.75 in chips) #Seat 6 - FETS63 ($0.75 in chips)
#Board [10s 5d Kh Qh 8c] #Board [10s 5d Kh Qh 8c]
re_GameInfo = re.compile(ur"^Stage #([0-9]+): (?P<GAME>Holdem|) (?P<LIMIT>No Limit|) (?P<CURRENCY>\$| €|)(?P<BB>[0-9]*[.0-9]+)", re.MULTILINE) re_GameInfo = re.compile(ur"^Stage #([0-9]+): (?P<GAME>Holdem|HORSE)(?: \(1 on 1\)|)? ?(?P<LIMIT>No Limit|Pot Limit|Normal|)? ?(?P<CURRENCY>\$| €|)(?P<SB>[.0-9]+)/?(?:\$| €|)(?P<BB>[.0-9]+)?", re.MULTILINE)
re_HandInfo = re.compile(ur"^Stage #(?P<HID>[0-9]+): .*(?P<DATETIME>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d).*\nTable: (?P<TABLE>.*) \(Real Money\)", re.MULTILINE) re_HorseGameInfo = re.compile(ur"^Game Type: (?P<LIMIT>Limit) (?P<GAME>Holdem)", re.MULTILINE)
# TODO: can set max seats via (1 on 1) to a known 2 ..
re_HandInfo = re.compile(ur"^Stage #(?P<HID>[0-9]+): .*(?P<DATETIME>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d).*\n(Table: (?P<TABLE>.*) \(Real Money\))?", re.MULTILINE)
re_TableFromFilename = re.compile(ur".*IHH([0-9]+) (?P<TABLE>.*) -") # on HORSE STUD games, the table name isn't in the hand info!
re_Button = re.compile(ur"Seat #(?P<BUTTON>[0-9]) is the ?[dead]* dealer$", re.MULTILINE) # TODO: that's not the right way to match for "dead" dealer is it? re_Button = re.compile(ur"Seat #(?P<BUTTON>[0-9]) is the ?[dead]* dealer$", re.MULTILINE) # TODO: that's not the right way to match for "dead" dealer is it?
re_PlayerInfo = re.compile(ur"^Seat (?P<SEAT>[0-9]) - (?P<PNAME>.*) \((?:\$| €|)(?P<CASH>[0-9]*[.0-9]+) in chips\)", re.MULTILINE) re_PlayerInfo = re.compile(ur"^Seat (?P<SEAT>[0-9]) - (?P<PNAME>.*) \((?:\$| €|)(?P<CASH>[0-9]*[.0-9]+) in chips\)", re.MULTILINE)
re_Board = re.compile(ur"\[(?P<CARDS>[^\]]*)\]? *$", re.MULTILINE) re_Board = re.compile(ur"\[(?P<CARDS>[^\]]*)\]? *$", re.MULTILINE)
@ -48,24 +58,6 @@ class Absolute(HandHistoryConverter):
# re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]") # re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, debugging=False, index=0):
"""\
in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input
autostart: whether to run the thread (or you can call start() yourself)
debugging: if False, pass on partially supported game types. If true, have a go and error..."""
#print "DEBUG: XXXXXXXXXXXXXXX"
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Absolute", follow=follow, index=index)
logging.info("Initialising Absolute converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 3 # Needs to match id entry in Sites database
self.debugging = debugging
if autostart:
self.start()
# otherwise you need to call start yourself.
def compilePlayerRegexs(self, hand): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players]) 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'
@ -79,13 +71,13 @@ debugging: if False, pass on partially supported game types. If true, have a go
# TODO: Absolute posting when coming in new: %s - Posts $0.02 .. should that be a new Post line? where do we need to add support for that? *confused* # TODO: Absolute posting when coming in new: %s - Posts $0.02 .. should that be a new Post line? where do we need to add support for that? *confused*
self.re_PostBoth = re.compile(ur"^%s - Posts dead (?:\$| €|)(?P<SBBB>[0-9]*[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBoth = re.compile(ur"^%s - Posts dead (?:\$| €|)(?P<SBBB>[0-9]*[.0-9]+)" % player_re, re.MULTILINE)
self.re_Action = re.compile(ur"^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[0-9]*[.0-9]+)?" % player_re, re.MULTILINE) self.re_Action = re.compile(ur"^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[0-9]*[.0-9]+)?" % player_re, re.MULTILINE)
print "^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[0-9]*[.0-9]+)?" % player_re # print "^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[0-9]*[.0-9]+)?" % player_re
self.re_ShowdownAction = re.compile(ur"^%s - Shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(ur"^%s - Shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(ur"^Seat [0-9]: %s(?: \(dealer\)| \(big blind\)| \(small blind\)|) (?:won|collected) Total \((?:\$| €|)(?P<POT>[0-9]*[.0-9]+)\)" % player_re, re.MULTILINE) self.re_CollectPot = re.compile(ur"^Seat [0-9]: %s(?: \(dealer\)|)(?: \(big blind\)| \(small blind\)|) (?:won|collected) Total \((?:\$| €|)(?P<POT>[0-9]*[.0-9]+)\)" % player_re, re.MULTILINE)
#self.re_PostSB = re.compile(ur"^%s: posts small blind \[(?:\$| €|) (?P<SB>[.0-9]+)" % player_re, re.MULTILINE) #self.re_PostSB = re.compile(ur"^%s: posts small blind \[(?:\$| €|) (?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
#self.re_PostBB = re.compile(ur"^%s: posts big blind \[(?:\$| €|) (?P<BB>[.0-9]+)" % player_re, re.MULTILINE) #self.re_PostBB = re.compile(ur"^%s: posts big blind \[(?:\$| €|) (?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
#self.re_PostBoth = re.compile(ur"^%s: posts both blinds \[(?:\$| €|) (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE) #self.re_PostBoth = re.compile(ur"^%s: posts both blinds \[(?:\$| €|) (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
#self.re_Antes = re.compile(ur"^%s: posts ante \[(?:\$| €|) (?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE) self.re_Antes = re.compile(ur"^%s - Ante \[(?:\$| €|)(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
#self.re_BringIn = re.compile(ur"^%s posts bring-in (?:\$| €|)(?P<BRINGIN>[.0-9]+)\." % player_re, re.MULTILINE) #self.re_BringIn = re.compile(ur"^%s posts bring-in (?:\$| €|)(?P<BRINGIN>[.0-9]+)\." % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(ur"^Dealt to %s \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE) self.re_HeroCards = re.compile(ur"^Dealt to %s \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
#self.re_Action = re.compile(ur"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P<BET>[.\d]+) (USD|EUR|)\])?" % player_re, re.MULTILINE) #self.re_Action = re.compile(ur"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P<BET>[.\d]+) (USD|EUR|)\])?" % player_re, re.MULTILINE)
@ -124,7 +116,7 @@ or None if we fail to get the info """
mg = m.groupdict() mg = m.groupdict()
# translations from captured groups to our info strings # translations from captured groups to our info strings
limits = { 'No Limit':'nl', 'PL':'pl', '':'fl' } limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Normal':'fl', 'Limit':'fl'}
games = { # base, category games = { # base, category
"Holdem" : ('hold','holdem'), "Holdem" : ('hold','holdem'),
'Omaha' : ('hold','omahahi'), 'Omaha' : ('hold','omahahi'),
@ -132,10 +124,22 @@ or None if we fail to get the info """
'7 Card Stud' : ('stud','studhi') '7 Card Stud' : ('stud','studhi')
} }
currencies = { u'':'EUR', '$':'USD', '':'T$' } currencies = { u'':'EUR', '$':'USD', '':'T$' }
if 'LIMIT' in mg: if 'GAME' in mg and mg['GAME'] == "HORSE": # if we're a HORSE game, the game type is on the next line
info['limitType'] = limits[mg['LIMIT']] self.HORSEHand = True
m = self.re_HorseGameInfo.search(handText)
if not m:
return None # it's a HORSE game and we don't understand the game type
temp = m.groupdict()
#print "AP HORSE processing"
if 'GAME' not in temp or 'LIMIT' not in temp:
return None # sort of understood it but not really
#print "temp=", temp
mg['GAME'] = temp['GAME']
mg['LIMIT'] = temp['LIMIT']
if 'GAME' in mg: if 'GAME' in mg:
(info['base'], info['category']) = games[mg['GAME']] (info['base'], info['category']) = games[mg['GAME']]
if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']]
if 'SB' in mg: if 'SB' in mg:
info['sb'] = mg['SB'] info['sb'] = mg['SB']
else: else:
@ -147,6 +151,11 @@ or None if we fail to get the info """
if info['currency'] == 'T$': if info['currency'] == 'T$':
info['type'] = 'tour' info['type'] = 'tour'
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
if info['bb'] is None:
info['bb'] = mg['SB']
info['sb'] = str(float(mg['SB']) * 0.5) # TODO: AP does provide Small BET for Limit .. I think? at least 1-on-1 limit they do.. sigh
#print info;
return info return info
@ -159,9 +168,15 @@ or None if we fail to get the info """
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')
hand.tablename = m.group('TABLE') if m.group('TABLE'):
hand.tablename = m.group('TABLE')
else:
t = self.re_TableFromFilename.search(self.in_path)
hand.tablename = t.group('TABLE')
hand.maxseats = 6 # assume 6-max unless we have proof it's a larger/smaller game, since absolute doesn't give seat max info hand.maxseats = 6 # assume 6-max unless we have proof it's a larger/smaller game, since absolute doesn't give seat max info
# TODO: (1-on-1) does have that info in the game type line
if self.HORSEHand:
hand.maxseats = 8
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%Y-%m-%d %H:%M:%S") hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%Y-%m-%d %H:%M:%S")
return return
@ -249,7 +264,6 @@ or None if we fail to get the info """
#Not involved in hand #Not involved in hand
hand.involved = False hand.involved = False
def readStudPlayerCards(self, hand, street): def readStudPlayerCards(self, hand, street):
# lol. see Plymouth.txt # lol. see Plymouth.txt
logging.warning("Absolute readStudPlayerCards is only a stub.") logging.warning("Absolute readStudPlayerCards is only a stub.")

View File

@ -26,6 +26,11 @@ from HandHistoryConverter import *
class Betfair(HandHistoryConverter): class Betfair(HandHistoryConverter):
sitename = 'Betfair'
filetype = "text"
codepage = "cp1252"
siteId = 7 # Needs to match id entry in Sites database
# Static regexes # Static regexes
re_GameInfo = re.compile("^(?P<LIMIT>NL|PL|) (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAME>(Texas Hold\'em|Omaha Hi|Razz))", re.MULTILINE) re_GameInfo = re.compile("^(?P<LIMIT>NL|PL|) (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAME>(Texas Hold\'em|Omaha Hi|Razz))", re.MULTILINE)
re_SplitHands = re.compile(r'\n\n+') re_SplitHands = re.compile(r'\n\n+')
@ -34,19 +39,6 @@ class Betfair(HandHistoryConverter):
re_PlayerInfo = re.compile("Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*)\s\(\s(\$(?P<CASH>[.0-9]+)) \)") re_PlayerInfo = re.compile("Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*)\s\(\s(\$(?P<CASH>[.0-9]+)) \)")
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]") re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
"""\
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="Betfair", follow=follow, index) # Call super class init.
logging.info("Initialising Betfair converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 7 # Needs to match id entry in Sites database
if autostart:
self.start()
def compilePlayerRegexs(self, hand): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players]) players = set([player[1] for player in hand.players])

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Configuration.py """Configuration.py
Handles HUD configuration files. Handles HUD configuration files.
@ -32,6 +33,11 @@ import shutil
import xml.dom.minidom import xml.dom.minidom
from xml.dom.minidom import Node from xml.dom.minidom import Node
import logging, logging.config
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
log = logging.getLogger("config")
log.debug("config logger initialised")
def fix_tf(x, default = True): def fix_tf(x, default = True):
# The xml parser doesn't translate "True" to True. Therefore, we never get # The xml parser doesn't translate "True" to True. Therefore, we never get
# True or False from the parser only "True" or "False". So translate the # True or False from the parser only "True" or "False". So translate the
@ -73,11 +79,17 @@ class Layout:
class Site: class Site:
def __init__(self, node): def __init__(self, node):
def normalizePath(path):
"Normalized existing pathes"
if os.path.exists(path):
return os.path.abspath(path)
return path
self.site_name = node.getAttribute("site_name") self.site_name = node.getAttribute("site_name")
self.table_finder = node.getAttribute("table_finder") self.table_finder = node.getAttribute("table_finder")
self.screen_name = node.getAttribute("screen_name") self.screen_name = node.getAttribute("screen_name")
self.site_path = node.getAttribute("site_path") self.site_path = normalizePath(node.getAttribute("site_path"))
self.HH_path = node.getAttribute("HH_path") self.HH_path = normalizePath(node.getAttribute("HH_path"))
self.decoder = node.getAttribute("decoder") self.decoder = node.getAttribute("decoder")
self.hudopacity = node.getAttribute("hudopacity") self.hudopacity = node.getAttribute("hudopacity")
self.hudbgcolor = node.getAttribute("bgcolor") self.hudbgcolor = node.getAttribute("bgcolor")
@ -92,6 +104,8 @@ class Site:
self.ypad = node.getAttribute("ypad") self.ypad = node.getAttribute("ypad")
self.layout = {} self.layout = {}
print self.site_name, self.HH_path
for layout_node in node.getElementsByTagName('layout'): for layout_node in node.getElementsByTagName('layout'):
lo = Layout(layout_node) lo = Layout(layout_node)
self.layout[lo.max] = lo self.layout[lo.max] = lo
@ -192,6 +206,9 @@ class Database:
self.db_user = node.getAttribute("db_user") self.db_user = node.getAttribute("db_user")
self.db_type = node.getAttribute("db_type") self.db_type = node.getAttribute("db_type")
self.db_pass = node.getAttribute("db_pass") self.db_pass = node.getAttribute("db_pass")
self.db_selected = fix_tf(node.getAttribute("default"),"False")
log.debug("Database db_name:'%(name)s' db_server:'%(server)s' db_ip:'%(ip)s' db_user:'%(user)s' db_type:'%(type)s' db_pass (not logged) selected:'%(sel)s'" \
% { 'name':self.db_name, 'server':self.db_server, 'ip':self.db_ip, 'user':self.db_user, 'type':self.db_type, 'sel':self.db_selected} )
def __str__(self): def __str__(self):
temp = 'Database = ' + self.db_name + '\n' temp = 'Database = ' + self.db_name + '\n'
@ -199,7 +216,7 @@ class Database:
if key.startswith('__'): continue if key.startswith('__'): continue
value = getattr(self, key) value = getattr(self, key)
if callable(value): continue if callable(value): continue
temp = temp + ' ' + key + " = " + value + "\n" temp = temp + ' ' + key + " = " + repr(value) + "\n"
return temp return temp
class Aux_window: class Aux_window:
@ -270,11 +287,10 @@ class Tv:
(self.combinedStealFold, self.combined2B3B, self.combinedPostflop) ) (self.combinedStealFold, self.combined2B3B, self.combinedPostflop) )
class Config: class Config:
def __init__(self, file = None, dbname = 'fpdb'): def __init__(self, file = None, dbname = ''):
# "file" is a path to an xml file with the fpdb/HUD configuration # "file" is a path to an xml file with the fpdb/HUD configuration
# we check the existence of "file" and try to recover if it doesn't exist # we check the existence of "file" and try to recover if it doesn't exist
self.dbname = dbname
self.default_config_path = self.get_default_config_path() self.default_config_path = self.get_default_config_path()
if file != None: # configuration file path has been passed if file != None: # configuration file path has been passed
@ -300,10 +316,10 @@ class Config:
# Parse even if there was no real config file found and we are using the example # Parse even if there was no real config file found and we are using the example
# If using the example, we'll edit it later # If using the example, we'll edit it later
try: try:
print "Reading configuration file %s\n" % (file) log.info("Reading configuration file %s" % (file))
doc = xml.dom.minidom.parse(file) doc = xml.dom.minidom.parse(file)
except: except:
print "Error parsing %s. See error log file." % (file) log.error("Error parsing %s. See error log file." % (file))
traceback.print_exc(file=sys.stderr) traceback.print_exc(file=sys.stderr)
print "press enter to continue" print "press enter to continue"
sys.stdin.readline() sys.stdin.readline()
@ -329,9 +345,21 @@ class Config:
self.supported_games[game.game_name] = game self.supported_games[game.game_name] = game
# s_dbs = doc.getElementsByTagName("supported_databases") # s_dbs = doc.getElementsByTagName("supported_databases")
if dbname and dbname in self.supported_databases:
self.db_selected = dbname
for db_node in doc.getElementsByTagName("database"): for db_node in doc.getElementsByTagName("database"):
db = Database(node = db_node) try:
self.supported_databases[db.db_name] = db db = Database(node = db_node)
if db.db_name in self.supported_databases:
raise FpdbError("Database names must be unique")
# If there is only one Database node, or none are marked default, the first is selected
if len(self.supported_databases) == 0:
self.db_selected = db.db_name
self.supported_databases[db.db_name] = db
if db.db_selected:
self.db_selected = db.db_name
except:
raise
# s_dbs = doc.getElementsByTagName("mucked_windows") # s_dbs = doc.getElementsByTagName("mucked_windows")
for aw_node in doc.getElementsByTagName("aw"): for aw_node in doc.getElementsByTagName("aw"):
@ -498,7 +526,7 @@ class Config:
def get_db_parameters(self): def get_db_parameters(self):
db = {} db = {}
name = self.dbname name = self.db_selected
try: db['db-databaseName'] = name try: db['db-databaseName'] = name
except: pass except: pass
@ -543,6 +571,13 @@ class Config:
if db_type != None: self.supported_databases[db_name].dp_type = db_type if db_type != None: self.supported_databases[db_name].dp_type = db_type
return return
def getDefaultSite(self):
"Returns first enabled site or None"
for site_name,site in self.supported_sites.iteritems():
if site.enabled:
return site_name
return None
def get_tv_parameters(self): def get_tv_parameters(self):
tv = {} tv = {}
try: tv['combinedStealFold'] = self.tv.combinedStealFold try: tv['combinedStealFold'] = self.tv.combinedStealFold
@ -573,30 +608,32 @@ class Config:
except: imp['fastStoreHudCache'] = True except: imp['fastStoreHudCache'] = True
return imp return imp
def get_default_paths(self, site = "PokerStars"): def get_default_paths(self, site = None):
if site is None: site = self.getDefaultSite()
paths = {} paths = {}
try: try:
paths['hud-defaultPath'] = os.path.expanduser(self.supported_sites[site].HH_path) path = os.path.expanduser(self.supported_sites[site].HH_path)
paths['bulkImport-defaultPath'] = os.path.expanduser(self.supported_sites[site].HH_path) assert(os.path.isdir(path) or os.path.isfile(path)) # maybe it should try another site?
except: paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = path
paths['hud-defaultPath'] = "default" except AssertionError:
paths['bulkImport-defaultPath'] = "default" paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = "** ERROR DEFAULT PATH IN CONFIG DOES NOT EXIST **"
return paths return paths
def get_frames(self, site = "PokerStars"): def get_frames(self, site = "PokerStars"):
if site not in self.supported_sites: return False
return self.supported_sites[site].use_frames == True return self.supported_sites[site].use_frames == True
def get_default_colors(self, site = "PokerStars"): def get_default_colors(self, site = "PokerStars"):
colors = {} colors = {}
if self.supported_sites[site].hudopacity == "": if site not in self.supported_sites or self.supported_sites[site].hudopacity == "":
colors['hudopacity'] = 0.90 colors['hudopacity'] = 0.90
else: else:
colors['hudopacity'] = float(self.supported_sites[site].hudopacity) colors['hudopacity'] = float(self.supported_sites[site].hudopacity)
if self.supported_sites[site].hudbgcolor == "": if site not in self.supported_sites or self.supported_sites[site].hudbgcolor == "":
colors['hudbgcolor'] = "#FFFFFF" colors['hudbgcolor'] = "#FFFFFF"
else: else:
colors['hudbgcolor'] = self.supported_sites[site].hudbgcolor colors['hudbgcolor'] = self.supported_sites[site].hudbgcolor
if self.supported_sites[site].hudfgcolor == "": if site not in self.supported_sites or self.supported_sites[site].hudfgcolor == "":
colors['hudfgcolor'] = "#000000" colors['hudfgcolor'] = "#000000"
else: else:
colors['hudfgcolor'] = self.supported_sites[site].hudfgcolor colors['hudfgcolor'] = self.supported_sites[site].hudfgcolor
@ -604,6 +641,8 @@ class Config:
def get_default_font(self, site = 'PokerStars'): def get_default_font(self, site = 'PokerStars'):
(font, font_size) = ("Sans", "8") (font, font_size) = ("Sans", "8")
if site not in self.supported_sites:
return ("Sans", "8")
if self.supported_sites[site].font == "": if self.supported_sites[site].font == "":
font = "Sans" font = "Sans"
else: else:

View File

@ -24,13 +24,14 @@ Create and manage the database objects.
# postmaster -D /var/lib/pgsql/data # postmaster -D /var/lib/pgsql/data
# Standard Library modules # Standard Library modules
import os
import sys import sys
import traceback import traceback
from datetime import datetime, date, time, timedelta from datetime import datetime, date, time, timedelta
from time import time, strftime, sleep from time import time, strftime, sleep
from decimal import Decimal
import string import string
import re import re
import logging
import Queue import Queue
# pyGTK modules # pyGTK modules
@ -41,6 +42,12 @@ import fpdb_simple
import Configuration import Configuration
import SQL import SQL
import Card import Card
import Tourney
from Exceptions import *
import logging, logging.config
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
log = logging.getLogger('db')
class Database: class Database:
@ -92,6 +99,14 @@ class Database:
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0} , {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
] ]
, [ # indexes for sqlite (list index 4) , [ # indexes for sqlite (list index 4)
{'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'Hands', 'col':'gametypeId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
] ]
] ]
@ -160,8 +175,14 @@ class Database:
# CREATE INDEX idx ON tab(col) # CREATE INDEX idx ON tab(col)
# DROP INDEX idx # DROP INDEX idx
# SQLite notes:
# To add an index:
# create index indexname on tablename (col);
def __init__(self, c, db_name = None, game = None, sql = None): # db_name and game not used any more def __init__(self, c, db_name = None, game = None, sql = None): # db_name and game not used any more
print "\ncreating Database instance, sql =", sql log.info("Creating Database instance, sql = %s" % sql)
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
self.fdb.do_connect(c) self.fdb.do_connect(c)
self.connection = self.fdb.db self.connection = self.fdb.db
@ -178,12 +199,17 @@ class Database:
#ISOLATION_LEVEL_READ_COMMITTED = 1 #ISOLATION_LEVEL_READ_COMMITTED = 1
#ISOLATION_LEVEL_SERIALIZABLE = 2 #ISOLATION_LEVEL_SERIALIZABLE = 2
# where possible avoid creating new SQL instance by using the global one passed in # where possible avoid creating new SQL instance by using the global one passed in
if sql == None: if sql == None:
self.sql = SQL.Sql(type = self.type, db_server = db_params['db-server']) self.sql = SQL.Sql(type = self.type, db_server = db_params['db-server'])
else: else:
self.sql = sql self.sql = sql
if self.backend == self.SQLITE and db_params['db-databaseName'] == ':memory:' and self.fdb.wrongDbVersion:
log.info("sqlite/:memory: - creating")
self.recreate_tables()
self.pcache = None # PlayerId cache self.pcache = None # PlayerId cache
self.cachemiss = 0 # Delete me later - using to count player cache misses self.cachemiss = 0 # Delete me later - using to count player cache misses
self.cachehit = 0 # Delete me later - using to count player cache hits self.cachehit = 0 # Delete me later - using to count player cache hits
@ -243,7 +269,7 @@ class Database:
elif self.backend==4: elif self.backend==4:
return "SQLite" return "SQLite"
else: else:
raise fpdb_simple.FpdbError("invalid backend") raise FpdbError("invalid backend")
def get_table_name(self, hand_id): def get_table_name(self, hand_id):
c = self.connection.cursor() c = self.connection.cursor()
@ -438,7 +464,7 @@ class Database:
if colnames[0].lower() == 'player_id': if colnames[0].lower() == 'player_id':
playerid = row[0] playerid = row[0]
else: else:
print "ERROR: query %s result does not have player_id as first column" % (query,) log.error("ERROR: query %s result does not have player_id as first column" % (query,))
break break
for name, val in zip(colnames, row): for name, val in zip(colnames, row):
@ -479,7 +505,7 @@ class Database:
if self.backend == self.MYSQL_INNODB: if self.backend == self.MYSQL_INNODB:
ret = self.connection.insert_id() ret = self.connection.insert_id()
if ret < 1 or ret > 999999999: if ret < 1 or ret > 999999999:
print "getLastInsertId(): problem fetching insert_id? ret=", ret log.warning("getLastInsertId(): problem fetching insert_id? ret=%d" % ret)
ret = -1 ret = -1
elif self.backend == self.PGSQL: elif self.backend == self.PGSQL:
# some options: # some options:
@ -491,14 +517,14 @@ class Database:
ret = c.execute ("SELECT lastval()") ret = c.execute ("SELECT lastval()")
row = c.fetchone() row = c.fetchone()
if not row: if not row:
print "getLastInsertId(%s): problem fetching lastval? row=" % seq, row log.warning("getLastInsertId(%s): problem fetching lastval? row=%d" % (seq, row))
ret = -1 ret = -1
else: else:
ret = row[0] ret = row[0]
elif self.backend == self.SQLITE: elif self.backend == self.SQLITE:
ret = cursor.lastrowid ret = cursor.lastrowid
else: else:
print "getLastInsertId(): unknown backend ", self.backend log.error("getLastInsertId(): unknown backend: %d" % self.backend)
ret = -1 ret = -1
except: except:
ret = -1 ret = -1
@ -583,7 +609,7 @@ class Database:
hands_players_ids = self.store_hands_players_holdem_omaha_tourney( hands_players_ids = self.store_hands_players_holdem_omaha_tourney(
self.backend, category, hands_id, player_ids, start_cashes, positions self.backend, category, hands_id, player_ids, start_cashes, positions
, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids , card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids
, hudImportData) , hudImportData, tourneyTypeId)
#print "tourney holdem, backend=%d" % backend #print "tourney holdem, backend=%d" % backend
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
@ -611,7 +637,7 @@ class Database:
hands_players_ids = self.store_hands_players_stud_tourney(self.backend, hands_id hands_players_ids = self.store_hands_players_stud_tourney(self.backend, hands_id
, playerIds, startCashes, antes, cardValues, cardSuits , playerIds, startCashes, antes, cardValues, cardSuits
, winnings, rakes, seatNos, tourneys_players_ids) , winnings, rakes, seatNos, tourneys_players_ids, tourneyTypeId)
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
self.storeHudCache(self.backend, base, category, gametypeId, hand_start_time, playerIds, hudImportData) self.storeHudCache(self.backend, base, category, gametypeId, hand_start_time, playerIds, hudImportData)
@ -822,16 +848,16 @@ class Database:
self.create_tables() self.create_tables()
self.createAllIndexes() self.createAllIndexes()
self.commit() self.commit()
print "Finished recreating tables" log.info("Finished recreating tables")
#end def recreate_tables #end def recreate_tables
def create_tables(self): def create_tables(self):
#todo: should detect and fail gracefully if tables already exist. #todo: should detect and fail gracefully if tables already exist.
try: try:
logging.debug(self.sql.query['createSettingsTable']) log.debug(self.sql.query['createSettingsTable'])
c = self.get_cursor() c = self.get_cursor()
c.execute(self.sql.query['createSettingsTable']) c.execute(self.sql.query['createSettingsTable'])
logging.debug(self.sql.query['createSitesTable']) log.debug(self.sql.query['createSitesTable'])
c.execute(self.sql.query['createSitesTable']) c.execute(self.sql.query['createSitesTable'])
c.execute(self.sql.query['createGametypesTable']) c.execute(self.sql.query['createGametypesTable'])
c.execute(self.sql.query['createPlayersTable']) c.execute(self.sql.query['createPlayersTable'])
@ -858,35 +884,51 @@ class Database:
def drop_tables(self): def drop_tables(self):
"""Drops the fpdb tables from the current db""" """Drops the fpdb tables from the current db"""
try: try:
c = self.get_cursor() c = self.get_cursor()
if(self.get_backend_name() == 'MySQL InnoDB'):
#Databases with FOREIGN KEY support need this switched of before you can drop tables
self.drop_referential_integrity()
# Query the DB to see what tables exist
c.execute(self.sql.query['list_tables'])
for table in c:
c.execute(self.sql.query['drop_table'] + table[0])
elif(self.get_backend_name() == 'PostgreSQL'):
self.commit()# I have no idea why this makes the query work--REB 07OCT2008
c.execute(self.sql.query['list_tables'])
tables = c.fetchall()
for table in tables:
c.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
elif(self.get_backend_name() == 'SQLite'):
c.execute(self.sql.query['list_tables'])
for table in c.fetchall():
logging.debug(self.sql.query['drop_table'] + table[0])
c.execute(self.sql.query['drop_table'] + table[0])
self.commit()
except: except:
err = traceback.extract_tb(sys.exc_info()[2])[-1] print "*** Error unable to get cursor"
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) else:
self.rollback() backend = self.get_backend_name()
raise if backend == 'MySQL InnoDB': # what happens if someone is using MyISAM?
try:
self.drop_referential_integrity() # needed to drop tables with foreign keys
c.execute(self.sql.query['list_tables'])
tables = c.fetchall()
for table in tables:
c.execute(self.sql.query['drop_table'] + table[0])
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
self.rollback()
elif backend == 'PostgreSQL':
try:
self.commit()
c.execute(self.sql.query['list_tables'])
tables = c.fetchall()
for table in tables:
c.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
self.rollback()
elif backend == 'SQLite':
try:
c.execute(self.sql.query['list_tables'])
for table in c.fetchall():
log.debug(self.sql.query['drop_table'] + table[0])
c.execute(self.sql.query['drop_table'] + table[0])
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
self.rollback()
try:
self.commit()
except:
print "*** Error in committing table drop"
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
self.rollback()
#end def drop_tables #end def drop_tables
def createAllIndexes(self): def createAllIndexes(self):
@ -911,14 +953,21 @@ class Database:
self.get_cursor().execute(s) self.get_cursor().execute(s)
except: except:
print " create idx failed: " + str(sys.exc_info()) print " create idx failed: " + str(sys.exc_info())
elif self.backend == self.SQLITE:
log.debug("Creating sqlite index %s %s" % (idx['tab'], idx['col']))
try:
s = "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
self.get_cursor().execute(s)
except:
log.debug("Create idx failed: " + str(sys.exc_info()))
else: else:
print "Only MySQL and Postgres supported so far" print "Only MySQL, Postgres and SQLite supported so far"
return -1 return -1
if self.backend == self.PGSQL: if self.backend == self.PGSQL:
self.connection.set_isolation_level(1) # go back to normal isolation level self.connection.set_isolation_level(1) # go back to normal isolation level
except: except:
print "Error creating indexes: " + str(sys.exc_value) print "Error creating indexes: " + str(sys.exc_value)
raise fpdb_simple.FpdbError( "Error creating indexes " + str(sys.exc_value) ) raise FpdbError( "Error creating indexes " + str(sys.exc_value) )
#end def createAllIndexes #end def createAllIndexes
def dropAllIndexes(self): def dropAllIndexes(self):
@ -963,12 +1012,10 @@ class Database:
c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')")
c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')")
if self.backend == self.SQLITE: if self.backend == self.SQLITE:
c.execute("INSERT INTO TourneyTypes VALUES (NULL, 1, 0, 0, 0, 0);") c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);")
else: else:
c.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") c.execute("insert into tourneytypes values (0,1,0,0,0,0,0,null,0,0,0);")
#c.execute("""INSERT INTO TourneyTypes
# (siteId,buyin,fee,knockout,rebuyOrAddon) VALUES
# (1,0,0,0,?)""",(False,) )
#end def fillDefaultData #end def fillDefaultData
def rebuild_hudcache(self): def rebuild_hudcache(self):
@ -1020,6 +1067,22 @@ class Database:
print "Error during fdb.lock_for_insert:", str(sys.exc_value) print "Error during fdb.lock_for_insert:", str(sys.exc_value)
#end def lock_for_insert #end def lock_for_insert
def getGameTypeId(self, siteid, game):
c = self.get_cursor()
#FIXME: Fixed for NL at the moment
c.execute(self.sql.query['getGametypeNL'], (siteid, game['type'], game['category'], game['limitType'],
int(Decimal(game['sb'])*100), int(Decimal(game['bb'])*100)))
tmp = c.fetchone()
if (tmp == None):
hilo = "h"
if game['category'] in ['studhilo', 'omahahilo']:
hilo = "s"
elif game['category'] in ['razz','27_3draw','badugi']:
hilo = "l"
tmp = self.insertGameTypes( (siteid, game['type'], game['base'], game['category'], game['limitType'], hilo,
int(Decimal(game['sb'])*100), int(Decimal(game['bb'])*100), 0, 0) )
return tmp[0]
def getSqlPlayerIDs(self, pnames, siteid): def getSqlPlayerIDs(self, pnames, siteid):
result = {} result = {}
if(self.pcache == None): if(self.pcache == None):
@ -1040,10 +1103,15 @@ class Database:
def insertPlayer(self, name, site_id): def insertPlayer(self, name, site_id):
result = None result = None
c = self.get_cursor() c = self.get_cursor()
c.execute ("SELECT id FROM Players WHERE name=%s".replace('%s',self.sql.query['placeholder']) q = "SELECT name, id FROM Players WHERE siteid=%s and name=%s"
,(name,)) q = q.replace('%s', self.sql.query['placeholder'])
#print "DEBUG: name: %s site: %s" %(name, site_id)
c.execute (q, (site_id, name))
tmp = c.fetchone() tmp = c.fetchone()
if (len(tmp)==0): #new player if (tmp == None): #new player
c.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)".replace('%s',self.sql.query['placeholder']) c.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)".replace('%s',self.sql.query['placeholder'])
,(name, site_id)) ,(name, site_id))
#Get last id might be faster here. #Get last id might be faster here.
@ -1085,7 +1153,7 @@ class Database:
, h.allIns, h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats , h.allIns, h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats
, h.tableName, h.seatNos) , h.tableName, h.seatNos)
else: else:
raise fpdb_simple.FpdbError("unrecognised category") raise FpdbError("unrecognised category")
else: else:
if h.base == "hold": if h.base == "hold":
result = self.ring_holdem_omaha( result = self.ring_holdem_omaha(
@ -1103,7 +1171,7 @@ class Database:
, h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats, h.tableName , h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats, h.tableName
, h.seatNos) , h.seatNos)
else: else:
raise fpdb_simple.FpdbError("unrecognised category") raise FpdbError("unrecognised category")
except: except:
print "Error storing hand: " + str(sys.exc_value) print "Error storing hand: " + str(sys.exc_value)
self.rollback() self.rollback()
@ -1116,10 +1184,10 @@ class Database:
def storeHand(self, p): def storeHand(self, p):
#stores into table hands: #stores into table hands:
self.cursor.execute ("""INSERT INTO Hands ( q = """INSERT INTO Hands (
tablename, tablename,
sitehandno,
gametypeid, gametypeid,
sitehandno,
handstart, handstart,
importtime, importtime,
seats, seats,
@ -1129,58 +1197,63 @@ class Database:
boardcard3, boardcard3,
boardcard4, boardcard4,
boardcard5, boardcard5,
-- texture, street1Pot,
playersVpi, street2Pot,
playersAtStreet1, street3Pot,
playersAtStreet2, street4Pot,
playersAtStreet3, showdownPot
playersAtStreet4,
playersAtShowdown,
street0Raises,
street1Raises,
street2Raises,
street3Raises,
street4Raises,
-- street1Pot,
-- street2Pot,
-- street3Pot,
-- street4Pot,
-- showdownPot
) )
VALUES VALUES
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s)""", %s, %s, %s, %s, %s, %s, %s)"""
( #--- texture,
p['tablename'], #-- playersVpi,
p['sitehandno'], #-- playersAtStreet1,
p['gametypeid'], #-- playersAtStreet2,
#-- playersAtStreet3,
#-- playersAtStreet4,
#-- playersAtShowdown,
#-- street0Raises,
#-- street1Raises,
#-- street2Raises,
#-- street3Raises,
#-- street4Raises,
#-- seats,
q = q.replace('%s', self.sql.query['placeholder'])
print "DEBUG: p: %s" %p
print "DEBUG: gtid: %s" % p['gameTypeId']
self.cursor.execute(q, (
p['tableName'],
p['gameTypeId'],
p['siteHandNo'],
p['handStart'], p['handStart'],
datetime.datetime.today(), datetime.today(), #importtime
len(p['names']), # len(p['names']), #seats
p['maxSeats'], p['maxSeats'],
p['seats'],
p['boardcard1'], p['boardcard1'],
p['boardcard2'], p['boardcard2'],
p['boardcard3'], p['boardcard3'],
p['boardcard4'], p['boardcard4'],
p['boardcard5'], p['boardcard5'],
hudCache['playersVpi'], # hudCache['playersVpi'],
hudCache['playersAtStreet1'], # hudCache['playersAtStreet1'],
hudCache['playersAtStreet2'], # hudCache['playersAtStreet2'],
hudCache['playersAtStreet3'], # hudCache['playersAtStreet3'],
hudCache['playersAtStreet4'], # hudCache['playersAtStreet4'],
hudCache['playersAtShowdown'], # hudCache['playersAtShowdown'],
hudCache['street0Raises'], # hudCache['street0Raises'],
hudCache['street1Raises'], # hudCache['street1Raises'],
hudCache['street2Raises'], # hudCache['street2Raises'],
hudCache['street3Raises'], # hudCache['street3Raises'],
hudCache['street4Raises'], # hudCache['street4Raises'],
hudCache['street1Pot'], p['street1Pot'],
hudCache['street2Pot'], p['street2Pot'],
hudCache['street3Pot'], p['street3Pot'],
hudCache['street4Pot'], p['street4Pot'],
hudCache['showdownPot'] p['showdownPot']
) ))
)
#return getLastInsertId(backend, conn, cursor) #return getLastInsertId(backend, conn, cursor)
# def storeHand # def storeHand
@ -1218,7 +1291,7 @@ class Database:
ret = self.get_last_insert_id(c) ret = self.get_last_insert_id(c)
except: except:
ret = -1 ret = -1
raise fpdb_simple.FpdbError( "storeHands error: " + str(sys.exc_value) ) raise FpdbError( "storeHands error: " + str(sys.exc_value) )
return ret return ret
#end def storeHands #end def storeHands
@ -1253,10 +1326,10 @@ class Database:
card3 = Card.cardFromValueSuit(card_values[i][2], card_suits[i][2]) card3 = Card.cardFromValueSuit(card_values[i][2], card_suits[i][2])
card4 = Card.cardFromValueSuit(card_values[i][3], card_suits[i][3]) card4 = Card.cardFromValueSuit(card_values[i][3], card_suits[i][3])
else: else:
raise fpdb_simple.FpdbError("invalid category") raise FpdbError("invalid category")
inserts.append( ( inserts.append( (
hands_id, player_ids[i], start_cashes[i], positions[i], 1, # tourneytypeid hands_id, player_ids[i], start_cashes[i], positions[i],
card1, card2, card3, card4, startCards, card1, card2, card3, card4, startCards,
winnings[i], rakes[i], seatNos[i], hudCache['totalProfit'][i], winnings[i], rakes[i], seatNos[i], hudCache['totalProfit'][i],
hudCache['street0VPI'][i], hudCache['street0Aggr'][i], hudCache['street0VPI'][i], hudCache['street0Aggr'][i],
@ -1287,7 +1360,7 @@ class Database:
c = self.get_cursor() c = self.get_cursor()
c.executemany (""" c.executemany ("""
INSERT INTO HandsPlayers INSERT INTO HandsPlayers
(handId, playerId, startCash, position, tourneyTypeId, (handId, playerId, startCash, position,
card1, card2, card3, card4, startCards, winnings, rake, seatNo, totalProfit, card1, card2, card3, card4, startCards, winnings, rake, seatNo, totalProfit,
street0VPI, street0Aggr, street0_3BChance, street0_3BDone, street0VPI, street0Aggr, street0_3BChance, street0_3BDone,
street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown, street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown,
@ -1308,11 +1381,11 @@ class Database:
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']) %s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder'])
,inserts ) ,inserts )
result.append( self.get_last_insert_id(c) ) # wrong? not used currently result.append( self.get_last_insert_id(c) ) # wrong? not used currently
except: except:
raise fpdb_simple.FpdbError( "store_hands_players_holdem_omaha error: " + str(sys.exc_value) ) raise FpdbError( "store_hands_players_holdem_omaha error: " + str(sys.exc_value) )
return result return result
#end def store_hands_players_holdem_omaha #end def store_hands_players_holdem_omaha
@ -1350,7 +1423,7 @@ class Database:
#result.append(cursor.fetchall()[0][0]) #result.append(cursor.fetchall()[0][0])
result.append( self.get_last_insert_id(c) ) result.append( self.get_last_insert_id(c) )
except: except:
raise fpdb_simple.FpdbError( "store_hands_players_stud error: " + str(sys.exc_value) ) raise FpdbError( "store_hands_players_stud error: " + str(sys.exc_value) )
return result return result
#end def store_hands_players_stud #end def store_hands_players_stud
@ -1358,7 +1431,7 @@ class Database:
def store_hands_players_holdem_omaha_tourney(self, backend, category, hands_id, player_ids def store_hands_players_holdem_omaha_tourney(self, backend, category, hands_id, player_ids
,start_cashes, positions, card_values, card_suits ,start_cashes, positions, card_values, card_suits
,winnings, rakes, seatNos, tourneys_players_ids ,winnings, rakes, seatNos, tourneys_players_ids
,hudCache): ,hudCache, tourneyTypeId):
#stores hands_players for tourney holdem/omaha hands #stores hands_players for tourney holdem/omaha hands
try: try:
@ -1380,7 +1453,7 @@ class Database:
else: else:
raise FpdbError ("invalid card_values length:"+str(len(card_values[0]))) raise FpdbError ("invalid card_values length:"+str(len(card_values[0])))
inserts.append( (hands_id, player_ids[i], start_cashes[i], positions[i], 1, # tourneytypeid inserts.append( (hands_id, player_ids[i], start_cashes[i], positions[i], tourneyTypeId,
card1, card2, card3, card4, startCards, card1, card2, card3, card4, startCards,
winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i], hudCache['totalProfit'][i], winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i], hudCache['totalProfit'][i],
hudCache['street0VPI'][i], hudCache['street0Aggr'][i], hudCache['street0VPI'][i], hudCache['street0Aggr'][i],
@ -1443,13 +1516,13 @@ class Database:
#cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i])) #cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i]))
#result.append(cursor.fetchall()[0][0]) #result.append(cursor.fetchall()[0][0])
except: except:
raise fpdb_simple.FpdbError( "store_hands_players_holdem_omaha_tourney error: " + str(sys.exc_value) ) raise FpdbError( "store_hands_players_holdem_omaha_tourney error: " + str(sys.exc_value) )
return result return result
#end def store_hands_players_holdem_omaha_tourney #end def store_hands_players_holdem_omaha_tourney
def store_hands_players_stud_tourney(self, backend, hands_id, player_ids, start_cashes, def store_hands_players_stud_tourney(self, backend, hands_id, player_ids, start_cashes,
antes, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids): antes, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids, tourneyTypeId):
#stores hands_players for tourney stud/razz hands #stores hands_players for tourney stud/razz hands
try: try:
@ -1461,19 +1534,19 @@ class Database:
card1Value, card1Suit, card2Value, card2Suit, card1Value, card1Suit, card2Value, card2Suit,
card3Value, card3Suit, card4Value, card4Suit, card3Value, card3Suit, card4Value, card4Suit,
card5Value, card5Suit, card6Value, card6Suit, card5Value, card5Suit, card6Value, card6Suit,
card7Value, card7Suit, winnings, rake, tourneysPlayersId, seatNo) card7Value, card7Suit, winnings, rake, tourneysPlayersId, seatNo, tourneyTypeId)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']), %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']),
(hands_id, player_ids[i], start_cashes[i], antes[i], (hands_id, player_ids[i], start_cashes[i], antes[i],
card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1],
card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3],
card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5], card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5],
card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i])) card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i], tourneyTypeId))
#cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i])) #cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i]))
#result.append(cursor.fetchall()[0][0]) #result.append(cursor.fetchall()[0][0])
result.append( self.get_last_insert_id(c) ) result.append( self.get_last_insert_id(c) )
except: except:
raise fpdb_simple.FpdbError( "store_hands_players_stud_tourney error: " + str(sys.exc_value) ) raise FpdbError( "store_hands_players_stud_tourney error: " + str(sys.exc_value) )
return result return result
#end def store_hands_players_stud_tourney #end def store_hands_players_stud_tourney
@ -1670,7 +1743,7 @@ class Database:
# print "todo: implement storeHudCache for stud base" # print "todo: implement storeHudCache for stud base"
except: except:
raise fpdb_simple.FpdbError( "storeHudCache error: " + str(sys.exc_value) ) raise FpdbError( "storeHudCache error: " + str(sys.exc_value) )
#end def storeHudCache #end def storeHudCache
@ -1693,7 +1766,7 @@ class Database:
tmp=cursor.fetchone() tmp=cursor.fetchone()
#print "created new tourneys.id:",tmp #print "created new tourneys.id:",tmp
except: except:
raise fpdb_simple.FpdbError( "store_tourneys error: " + str(sys.exc_value) ) raise FpdbError( "store_tourneys error: " + str(sys.exc_value) )
return tmp[0] return tmp[0]
#end def store_tourneys #end def store_tourneys
@ -1726,7 +1799,7 @@ class Database:
#print "created new tourneys_players.id:",tmp #print "created new tourneys_players.id:",tmp
result.append(tmp[0]) result.append(tmp[0])
except: except:
raise fpdb_simple.FpdbError( "store_tourneys_players error: " + str(sys.exc_value) ) raise FpdbError( "store_tourneys_players error: " + str(sys.exc_value) )
return result return result
#end def store_tourneys_players #end def store_tourneys_players
@ -1807,6 +1880,236 @@ class Database:
print "***Error sending finish: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) print "***Error sending finish: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
# end def send_finish_msg(): # end def send_finish_msg():
def tRecogniseTourneyType(self, tourney):
logging.debug("Database.tRecogniseTourneyType")
typeId = 1
# Check if Tourney exists, and if so retrieve TTypeId : in that case, check values of the ttype
cursor = self.get_cursor()
cursor.execute (self.sql.query['getTourneyTypeIdByTourneyNo'].replace('%s', self.sql.query['placeholder']),
(tourney.tourNo, tourney.siteId)
)
result=cursor.fetchone()
expectedValues = { 1 : "buyin", 2 : "fee", 4 : "isKO", 5 : "isRebuy", 6 : "speed",
7 : "isHU", 8 : "isShootout", 9 : "isMatrix" }
typeIdMatch = True
try:
len(result)
typeId = result[0]
logging.debug("Tourney found in db with Tourney_Type_ID = %d" % typeId)
for ev in expectedValues :
if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ):
logging.debug("TypeId mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) )
typeIdMatch = False
#break
except:
# Tourney not found : a TourneyTypeId has to be found or created for that specific tourney
typeIdMatch = False
if typeIdMatch == False :
# Check for an existing TTypeId that matches tourney info (buyin/fee, knockout, rebuy, speed, matrix, shootout)
# if not found create it
logging.debug("Searching for a TourneyTypeId matching TourneyType data")
cursor.execute (self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']),
(tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO,
tourney.isRebuy, tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix)
)
result=cursor.fetchone()
try:
len(result)
typeId = result[0]
logging.debug("Existing Tourney Type Id found : %d" % typeId)
except TypeError: #this means we need to create a new entry
logging.debug("Tourney Type Id not found : create one")
cursor.execute (self.sql.query['insertTourneyTypes'].replace('%s', self.sql.query['placeholder']),
(tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO, tourney.isRebuy,
tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix)
)
typeId = self.get_last_insert_id(cursor)
return typeId
#end def tRecogniseTourneyType
def tRecognizeTourney(self, tourney, dbTourneyTypeId):
logging.debug("Database.tRecognizeTourney")
tourneyID = 1
# Check if tourney exists in db (based on tourney.siteId and tourney.tourNo)
# If so retrieve all data to check for consistency
cursor = self.get_cursor()
cursor.execute (self.sql.query['getTourney'].replace('%s', self.sql.query['placeholder']),
(tourney.tourNo, tourney.siteId)
)
result=cursor.fetchone()
expectedValuesDecimal = { 2 : "entries", 3 : "prizepool", 6 : "buyInChips", 9 : "rebuyChips",
10 : "addOnChips", 11 : "rebuyAmount", 12 : "addOnAmount", 13 : "totalRebuys",
14 : "totalAddOns", 15 : "koBounty" }
expectedValues = { 7 : "tourneyName", 16 : "tourneyComment" }
tourneyDataMatch = True
tCommentTs = None
starttime = None
endtime = None
try:
len(result)
tourneyID = result[0]
logging.debug("Tourney found in db with TourneyID = %d" % tourneyID)
if result[1] <> dbTourneyTypeId:
tourneyDataMatch = False
logging.debug("Tourney has wrong type ID (expected : %s - found : %s)" % (dbTourneyTypeId, result[1]))
if (tourney.starttime is None and result[4] is not None) or ( tourney.starttime is not None and fpdb_simple.parseHandStartTime("- %s" % tourney.starttime) <> result[4]) :
tourneyDataMatch = False
logging.debug("Tourney data mismatch : wrong starttime : Tourney=%s / db=%s" % (tourney.starttime, result[4]))
if (tourney.endtime is None and result[5] is not None) or ( tourney.endtime is not None and fpdb_simple.parseHandStartTime("- %s" % tourney.endtime) <> result[5]) :
tourneyDataMatch = False
logging.debug("Tourney data mismatch : wrong endtime : Tourney=%s / db=%s" % (tourney.endtime, result[5]))
for ev in expectedValues :
if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ):
logging.debug("Tourney data mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) )
tourneyDataMatch = False
#break
for evD in expectedValuesDecimal :
if ( Decimal(getattr( tourney, expectedValuesDecimal.get(evD)) ) <> result[evD] ):
logging.debug("Tourney data mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValuesDecimal.get(evD), getattr( tourney, expectedValuesDecimal.get(evD)), result[evD]) )
tourneyDataMatch = False
#break
# TO DO : Deal with matrix summary mutliple parsings
except:
# Tourney not found : create
logging.debug("Tourney is not found : create")
if tourney.tourneyComment is not None :
tCommentTs = datetime.today()
if tourney.starttime is not None :
starttime = fpdb_simple.parseHandStartTime("- %s" % tourney.starttime)
if tourney.endtime is not None :
endtime = fpdb_simple.parseHandStartTime("- %s" % tourney.endtime)
# TODO : deal with matrix Id processed
cursor.execute (self.sql.query['insertTourney'].replace('%s', self.sql.query['placeholder']),
(dbTourneyTypeId, tourney.tourNo, tourney.entries, tourney.prizepool, starttime,
endtime, tourney.buyInChips, tourney.tourneyName, 0, tourney.rebuyChips, tourney.addOnChips,
tourney.rebuyAmount, tourney.addOnAmount, tourney.totalRebuys, tourney.totalAddOns, tourney.koBounty,
tourney.tourneyComment, tCommentTs)
)
tourneyID = self.get_last_insert_id(cursor)
# Deal with inconsistent tourney in db
if tourneyDataMatch == False :
# Update Tourney
if result[16] <> tourney.tourneyComment :
tCommentTs = datetime.today()
if tourney.starttime is not None :
starttime = fpdb_simple.parseHandStartTime("- %s" % tourney.starttime)
if tourney.endtime is not None :
endtime = fpdb_simple.parseHandStartTime("- %s" % tourney.endtime)
cursor.execute (self.sql.query['updateTourney'].replace('%s', self.sql.query['placeholder']),
(dbTourneyTypeId, tourney.entries, tourney.prizepool, starttime,
endtime, tourney.buyInChips, tourney.tourneyName, 0, tourney.rebuyChips, tourney.addOnChips,
tourney.rebuyAmount, tourney.addOnAmount, tourney.totalRebuys, tourney.totalAddOns, tourney.koBounty,
tourney.tourneyComment, tCommentTs, tourneyID)
)
return tourneyID
#end def tRecognizeTourney
def tStoreTourneyPlayers(self, tourney, dbTourneyId):
logging.debug("Database.tStoreTourneyPlayers")
# First, get playerids for the players and specifically the one for hero :
playersIds = fpdb_simple.recognisePlayerIDs(self, tourney.players, tourney.siteId)
# hero may be None for matrix tourneys summaries
# hero = [ tourney.hero ]
# heroId = fpdb_simple.recognisePlayerIDs(self, hero , tourney.siteId)
# logging.debug("hero Id = %s - playersId = %s" % (heroId , playersIds))
tourneyPlayersIds=[]
try:
cursor = self.get_cursor()
for i in xrange(len(playersIds)):
cursor.execute(self.sql.query['getTourneysPlayers'].replace('%s', self.sql.query['placeholder'])
,(dbTourneyId, playersIds[i]))
result=cursor.fetchone()
#print "tried SELECTing tourneys_players.id:",tmp
try:
len(result)
# checking data
logging.debug("TourneysPlayers found : checking data")
expectedValuesDecimal = { 1 : "payinAmounts", 2 : "finishPositions", 3 : "winnings", 4 : "countRebuys",
5 : "countAddOns", 6 : "countKO" }
tourneyPlayersIds.append(result[0]);
tourneysPlayersDataMatch = True
for evD in expectedValuesDecimal :
if ( Decimal(getattr( tourney, expectedValuesDecimal.get(evD))[tourney.players[i]] ) <> result[evD] ):
logging.debug("TourneysPlayers data mismatch for TourneysPlayer id=%d, name=%s : wrong %s : Tourney=%s / db=%s" % (result[0], tourney.players[i], expectedValuesDecimal.get(evD), getattr( tourney, expectedValuesDecimal.get(evD))[tourney.players[i]], result[evD]) )
tourneysPlayersDataMatch = False
#break
if tourneysPlayersDataMatch == False:
logging.debug("TourneysPlayers data update needed")
cursor.execute (self.sql.query['updateTourneysPlayers'].replace('%s', self.sql.query['placeholder']),
(tourney.payinAmounts[tourney.players[i]], tourney.finishPositions[tourney.players[i]],
tourney.winnings[tourney.players[i]] , tourney.countRebuys[tourney.players[i]],
tourney.countAddOns[tourney.players[i]] , tourney.countKO[tourney.players[i]],
result[7], result[8], result[0])
)
except TypeError:
logging.debug("TourneysPlayers not found : need insert")
cursor.execute (self.sql.query['insertTourneysPlayers'].replace('%s', self.sql.query['placeholder']),
(dbTourneyId, playersIds[i],
tourney.payinAmounts[tourney.players[i]], tourney.finishPositions[tourney.players[i]],
tourney.winnings[tourney.players[i]] , tourney.countRebuys[tourney.players[i]],
tourney.countAddOns[tourney.players[i]] , tourney.countKO[tourney.players[i]],
None, None)
)
tourneyPlayersIds.append(self.get_last_insert_id(cursor))
except:
raise fpdb_simple.FpdbError( "tStoreTourneyPlayers error: " + str(sys.exc_value) )
return tourneyPlayersIds
#end def tStoreTourneyPlayers
def tUpdateTourneysHandsPlayers(self, tourney, dbTourneysPlayersIds, dbTourneyTypeId):
logging.debug("Database.tCheckTourneysHandsPlayers")
try:
# Massive update seems to take quite some time ...
# query = self.sql.query['updateHandsPlayersForTTypeId2'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner'].join([self.sql.query['placeholder'] for id in dbTourneysPlayersIds]) )
# cursor = self.get_cursor()
# cursor.execute (query, dbTourneysPlayersIds)
query = self.sql.query['selectHandsPlayersWithWrongTTypeId'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner'].join([self.sql.query['placeholder'] for id in dbTourneysPlayersIds]) )
#print "query : %s" % query
cursor = self.get_cursor()
cursor.execute (query, dbTourneysPlayersIds)
result=cursor.fetchall()
if (len(result) > 0):
logging.debug("%d lines need update : %s" % (len(result), result) )
listIds = []
for i in result:
listIds.append(i[0])
query2 = self.sql.query['updateHandsPlayersForTTypeId'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner_id'].join([self.sql.query['placeholder'] for id in listIds]) )
cursor.execute (query2, listIds)
else:
logging.debug("No need to update, HandsPlayers are correct")
except:
raise fpdb_simple.FpdbError( "tStoreTourneyPlayers error: " + str(sys.exc_value) )
#end def tUpdateTourneysHandsPlayers
# Class used to hold all the data needed to write a hand to the db # Class used to hold all the data needed to write a hand to the db
# mainParser() in fpdb_parse_logic.py creates one of these and then passes it to # mainParser() in fpdb_parse_logic.py creates one of these and then passes it to

View File

@ -26,6 +26,11 @@ from HandHistoryConverter import *
class Everleaf(HandHistoryConverter): class Everleaf(HandHistoryConverter):
sitename = 'Everleaf'
filetype = "text"
codepage = "cp1252"
siteId = 3 # Needs to match id entry in Sites database
# Static regexes # Static regexes
re_SplitHands = re.compile(r"\n\n\n+") re_SplitHands = re.compile(r"\n\n\n+")
re_TailSplitHands = re.compile(r"(\n\n\n+)") re_TailSplitHands = re.compile(r"(\n\n\n+)")
@ -37,24 +42,6 @@ class Everleaf(HandHistoryConverter):
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]") re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, debugging=False, index=0):
"""\
in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input
autostart: whether to run the thread (or you can call start() yourself)
debugging: if False, pass on partially supported game types. If true, have a go and error..."""
#print "DEBUG: XXXXXXXXXXXXXXX"
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Everleaf", follow=follow, index=index)
logging.info("Initialising Everleaf converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 3 # Needs to match id entry in Sites database
self.debugging = debugging
if autostart:
self.start()
# otherwise you need to call start yourself.
def compilePlayerRegexs(self, hand): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players]) 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'

View File

@ -1 +1,21 @@
class FpdbParseError(Exception): pass class FpdbError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class FpdbParseError(FpdbError):
def __init__(self,value='',hid=''):
self.value = value
self.hid = hid
def __str__(self):
if hid:
return repr("HID:"+hid+", "+self.value)
else:
return repr(self.value)
class FpdbDatabaseError(FpdbError):
pass
class DuplicateError(FpdbError):
pass

View File

@ -27,8 +27,14 @@ from HandHistoryConverter import *
class Fulltilt(HandHistoryConverter): class Fulltilt(HandHistoryConverter):
sitename = "Fulltilt"
filetype = "text"
codepage = ["utf-16", "cp1252"]
siteId = 1 # Needs to match id entry in Sites database
# Static regexes # Static regexes
re_GameInfo = re.compile('''(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)? re_GameInfo = re.compile('''.*\#(?P<HID>[0-9]+):\s
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
.+ .+
-\s(?P<CURRENCY>\$|)? -\s(?P<CURRENCY>\$|)?
(?P<SB>[.0-9]+)/ (?P<SB>[.0-9]+)/
@ -39,39 +45,79 @@ class Fulltilt(HandHistoryConverter):
''', re.VERBOSE) ''', re.VERBOSE)
re_SplitHands = re.compile(r"\n\n+") re_SplitHands = re.compile(r"\n\n+")
re_TailSplitHands = re.compile(r"(\n\n+)") re_TailSplitHands = re.compile(r"(\n\n+)")
re_HandInfo = re.compile('''.*\#(?P<HID>[0-9]+):\s re_HandInfo = re.compile(r'''.*\#(?P<HID>[0-9]+):\s
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)? (?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
Table\s (Table|Match)\s
(?P<PLAY>Play\sChip\s|PC)? (?P<PLAY>Play\sChip\s|PC)?
(?P<TABLE>[-\s\da-zA-Z]+)\s (?P<TABLE>[-\s\da-zA-Z]+)\s
(\((?P<TABLEATTRIBUTES>.+)\)\s)?-\s (\((?P<TABLEATTRIBUTES>.+)\)\s)?-\s
\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\s(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\s(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
(?P<GAMETYPE>[a-zA-Z\/\'\s]+)\s-\s (?P<GAMETYPE>[a-zA-Z\/\'\s]+)\s-\s
(?P<DATETIME>.*) (?P<DATETIME>\d+:\d+:\d+\s\w+\s-\s\d+/\d+/\d+)\s?
''', re.VERBOSE) (?P<PARTIAL>\(partial\))?\n
(?:.*?\n(?P<CANCELLED>Hand\s\#(?P=HID)\shas\sbeen\scanceled))?
''', re.VERBOSE|re.DOTALL)
re_TourneyExtraInfo = re.compile('''(((?P<TOURNEY_NAME>[^$]+)?
(?P<CURRENCY>\$)?(?P<BUYIN>[.0-9]+)?\s*\+\s*\$?(?P<FEE>[.0-9]+)?
(\s(?P<SPECIAL>(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness)))?
(\s(?P<SHOOTOUT>Shootout))?
(\s(?P<SNG>Sit\s&\sGo))?
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
''', re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)$', re.MULTILINE) re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
#static regex for tourney purpose
re_TourneyInfo = re.compile('''Tournament\sSummary\s
(?P<TOURNAMENT_NAME>[^$(]+)?\s*
((?P<CURRENCY>\$|)?(?P<BUYIN>[.0-9]+)\s*\+\s*\$?(?P<FEE>[.0-9]+)\s)?
((?P<SPECIAL>(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness))\s)?
((?P<SHOOTOUT>Shootout)\s)?
((?P<SNG>Sit\s&\sGo)\s)?
(\((?P<TURBO1>Turbo)\)\s)?
\((?P<TOURNO>\d+)\)\s
((?P<MATCHNO>Match\s\d)\s)?
(?P<GAME>(Hold\'em|Omaha\sHi|Omaha\sH/L|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))\s
(\((?P<TURBO2>Turbo)\)\s)?
(?P<LIMIT>(No\sLimit|Pot\sLimit|Limit))?
''', re.VERBOSE)
re_TourneyBuyInFee = re.compile("Buy-In: (?P<BUYIN_CURRENCY>\$|)?(?P<BUYIN>[.0-9]+) \+ \$?(?P<FEE>[.0-9]+)")
re_TourneyBuyInChips = re.compile("Buy-In Chips: (?P<BUYINCHIPS>\d+)")
re_TourneyEntries = re.compile("(?P<ENTRIES>\d+) Entries")
re_TourneyPrizePool = re.compile("Total Prize Pool: (?P<PRIZEPOOL_CURRENCY>\$|)?(?P<PRIZEPOOL>[.,0-9]+)")
re_TourneyRebuyAmount = re.compile("Rebuy: (?P<REBUY_CURRENCY>\$|)?(?P<REBUY_AMOUNT>[.,0-9]+)")
re_TourneyAddOnAmount = re.compile("Add-On: (?P<ADDON_CURRENCY>\$|)?(?P<ADDON_AMOUNT>[.,0-9]+)")
re_TourneyRebuyCount = re.compile("performed (?P<REBUY_COUNT>\d+) Rebuy")
re_TourneyAddOnCount = re.compile("performed (?P<ADDON_COUNT>\d+) Add-On")
re_TourneyRebuysTotal = re.compile("Total Rebuys: (?P<REBUY_TOTAL>\d+)")
re_TourneyAddOnsTotal = re.compile("Total Add-Ons: (?P<ADDONS_TOTAL>\d+)")
re_TourneyRebuyChips = re.compile("Rebuy Chips: (?P<REBUY_CHIPS>\d+)")
re_TourneyAddOnChips = re.compile("Add-On Chips: (?P<ADDON_CHIPS>\d+)")
re_TourneyKOBounty = re.compile("Knockout Bounty: (?P<KO_BOUNTY_CURRENCY>\$|)?(?P<KO_BOUNTY_AMOUNT>[.,0-9]+)")
re_TourneyCountKO = re.compile("received (?P<COUNT_KO>\d+) Knockout Bounty Award(s)?")
re_TourneyTimeInfo = re.compile("Tournament started: (?P<STARTTIME>.*)\nTournament ((?P<IN_PROGRESS>is still in progress)?|(finished:(?P<ENDTIME>.*))?)$")
re_TourneyPlayersSummary = re.compile("^(?P<RANK>(Still Playing|\d+))( - |: )(?P<PNAME>[^\n,]+)(, )?(?P<WINNING_CURRENCY>\$|)?(?P<WINNING>[.\d]+)?", re.MULTILINE)
re_TourneyHeroFinishingP = re.compile("(?P<HERO_NAME>.*) finished in (?P<HERO_FINISHING_POS>\d+)(st|nd|rd|th) place")
#TODO: See if we need to deal with play money tourney summaries -- Not right now (they shouldn't pass the re_TourneyInfo)
##Full Tilt Poker Tournament Summary 250 Play Money Sit & Go (102909471) Hold'em No Limit
##Buy-In: 250 Play Chips + 0 Play Chips
##Buy-In Chips: 1500
##6 Entries
##Total Prize Pool: 1,500 Play Chips
# These regexes are for FTP only # These regexes are for FTP only
re_Mixed = re.compile(r'\s\-\s(?P<MIXED>HA|HORSE|HOSE)\s\-\s', re.VERBOSE) re_Mixed = re.compile(r'\s\-\s(?P<MIXED>HA|HORSE|HOSE)\s\-\s', re.VERBOSE)
re_Max = re.compile("(?P<MAX>\d+)( max)?", re.MULTILINE) re_Max = re.compile("(?P<MAX>\d+)( max)?", re.MULTILINE)
# NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports. # NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports.
mixes = { 'HORSE': 'horse', '7-Game': '7game', 'HOSE': 'hose', 'HA': 'ha'}
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
"""\
in_path (default '-' = sys.stdin) mixes = { 'HORSE': 'horse', '7-Game': '7game', 'HOSE': 'hose', 'HA': 'ha'}
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Fulltilt", follow=follow, index=index)
logging.info("Initialising Fulltilt converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 1 # Needs to match id entry in Sites database
if autostart:
self.start()
def compilePlayerRegexs(self, hand): def compilePlayerRegexs(self, hand):
@ -89,7 +135,7 @@ follow : whether to tail -f the input"""
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % 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| completes it to| calls| folds)( \$?(?P<BET>[.,\d]+))?" % player_re, re.MULTILINE) self.re_Action = re.compile(r"^%s(?P<ATYPE> bets| checks| raises to| completes it to| 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_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)
self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE) self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE) self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
@ -118,7 +164,6 @@ follow : whether to tail -f the input"""
if not m: if not m:
return None return None
mg = m.groupdict() mg = m.groupdict()
# translations from captured groups to our info strings # translations from captured groups to our info strings
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' } limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
games = { # base, category games = { # base, category
@ -140,29 +185,11 @@ follow : whether to tail -f the input"""
if mg['TOURNO'] == None: info['type'] = "ring" if mg['TOURNO'] == None: info['type'] = "ring"
else: info['type'] = "tour" else: info['type'] = "tour"
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
if info['type'] == "tour": return None # importer is screwed on tournies, pass on those hands so we don't interrupt other autoimporting # if info['type'] == "tour": return None # importer is screwed on tournies, pass on those hands so we don't interrupt other autoimporting
return info return info
#Following function is a hack, we should be dealing with this in readFile (i think correct codepage....)
# Same function as parent class, removing the 2 end characters. - CG
def allHandsAsList(self):
"""Return a list of handtexts in the file at self.in_path"""
#TODO : any need for this to be generator? e.g. stars support can email one huge file of all hands in a year. Better to read bit by bit than all at once.
self.readFile()
# FIXME: it's a hack
if self.obs[:2] == u'\xff\xfe':
self.obs = self.obs[2:].replace('\x00', '')
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 readHandInfo(self, hand): def readHandInfo(self, hand):
m = self.re_HandInfo.search(hand.handText,re.DOTALL) 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)
@ -170,6 +197,10 @@ follow : whether to tail -f the input"""
hand.handid = m.group('HID') hand.handid = m.group('HID')
hand.tablename = m.group('TABLE') hand.tablename = m.group('TABLE')
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d") hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
if m.group("CANCELLED") or m.group("PARTIAL"):
raise FpdbParseError(hid=m.group('HID'))
if m.group('TABLEATTRIBUTES'): if m.group('TABLEATTRIBUTES'):
m2 = self.re_Max.search(m.group('TABLEATTRIBUTES')) m2 = self.re_Max.search(m.group('TABLEATTRIBUTES'))
if m2: hand.maxseats = int(m2.group('MAX')) if m2: hand.maxseats = int(m2.group('MAX'))
@ -178,7 +209,31 @@ follow : whether to tail -f the input"""
if m.group('PLAY') != None: if m.group('PLAY') != None:
hand.gametype['currency'] = 'play' hand.gametype['currency'] = 'play'
# TODO: if there's a way to figure these out, we should.. otherwise we have to stuff it with unknowns # Done: if there's a way to figure these out, we should.. otherwise we have to stuff it with unknowns
if m.group('TOURNAMENT') is not None:
n = self.re_TourneyExtraInfo.search(m.group('TOURNAMENT'))
if n.group('UNREADABLE_INFO') is not None:
hand.tourneyComment = n.group('UNREADABLE_INFO')
else:
hand.tourneyComment = n.group('TOURNEY_NAME') # can be None
if (n.group('CURRENCY') is not None and n.group('BUYIN') is not None and n.group('FEE') is not None):
hand.buyin = "%s%s+%s%s" %(n.group('CURRENCY'), n.group('BUYIN'), n.group('CURRENCY'), n.group('FEE'))
if n.group('TURBO') is not None :
hand.speed = "Turbo"
if n.group('SPECIAL') is not None :
special = n.group('SPECIAL')
if special == "Rebuy":
hand.isRebuy = True
if special == "KO":
hand.isKO = True
if special == "Head's Up":
hand.isHU = True
if re.search("Matrix", special):
hand.isMatrix = True
if special == "Shootout":
hand.isShootout = True
if hand.buyin == None: if hand.buyin == None:
hand.buyin = "$0.00+$0.00" hand.buyin = "$0.00+$0.00"
if hand.level == None: if hand.level == None:
@ -200,7 +255,11 @@ follow : whether to tail -f the input"""
#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.handText) if hand.gametype['type'] == "ring" :
m = self.re_PlayerInfo.finditer(hand.handText)
else: #if hand.gametype['type'] == "tour"
m = self.re_TourneyPlayerInfo.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'))
@ -322,7 +381,7 @@ follow : whether to tail -f the input"""
def readCollectPot(self,hand): def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.handText): 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=re.sub(u',',u'',m.group('POT')))
def readShownCards(self,hand): def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText): for m in self.re_ShownCards.finditer(hand.handText):
@ -355,6 +414,253 @@ follow : whether to tail -f the input"""
else: else:
hand.mixed = self.mixes[m.groupdict()['MIXED']] hand.mixed = self.mixes[m.groupdict()['MIXED']]
def readSummaryInfo(self, summaryInfoList):
starttime = time.time()
self.status = True
m = re.search("Tournament Summary", summaryInfoList[0])
if m:
# info list should be 2 lines : Tourney infos & Finsihing postions with winnings
if (len(summaryInfoList) != 2 ):
log.info("Too many lines (%d) in file '%s' : '%s'" % (len(summaryInfoList), self.in_path, summaryInfoList) )
self.status = False
else:
self.tourney = Tourney.Tourney(sitename = self.sitename, gametype = None, summaryText = summaryInfoList, builtFrom = "HHC")
self.status = self.getPlayersPositionsAndWinnings(self.tourney)
if self.status == True :
self.status = self.determineTourneyType(self.tourney)
#print self.tourney
else:
log.info("Parsing NOK : rejected")
else:
log.info( "This is not a summary file : '%s'" % (self.in_path) )
self.status = False
return self.status
def determineTourneyType(self, tourney):
info = {'type':'tour'}
tourneyText = tourney.summaryText[0]
#print "Examine : '%s'" %(tourneyText)
m = self.re_TourneyInfo.search(tourneyText)
if not m:
log.info( "determineTourneyType : Parsing NOK" )
return False
mg = m.groupdict()
#print mg
# translations from captured groups to our info strings
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
games = { # base, category
"Hold'em" : ('hold','holdem'),
'Omaha Hi' : ('hold','omahahi'),
'Omaha H/L' : ('hold','omahahilo'),
'Razz' : ('stud','razz'),
'Stud Hi' : ('stud','studhi'),
'Stud H/L' : ('stud','studhilo')
}
currencies = { u'':'EUR', '$':'USD', '':'T$' }
info['limitType'] = limits[mg['LIMIT']]
if mg['GAME'] is not None:
(info['base'], info['category']) = games[mg['GAME']]
if mg['CURRENCY'] is not None:
info['currency'] = currencies[mg['CURRENCY']]
if mg['TOURNO'] == None: info['type'] = "ring"
else: info['type'] = "tour"
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
# Info is now ready to be copied in the tourney object
tourney.gametype = info
# Additional info can be stored in the tourney object
if mg['BUYIN'] is not None:
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
tourney.fee = 0
if mg['FEE'] is not None:
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
if mg['TOURNAMENT_NAME'] is not None:
# Tournament Name can have a trailing space at the end (depending on the tournament description)
tourney.tourneyName = mg['TOURNAMENT_NAME'].rstrip()
if mg['SPECIAL'] is not None:
special = mg['SPECIAL']
if special == "KO":
tourney.isKO = True
if special == "Heads Up":
tourney.isHU = True
tourney.maxseats = 2
if re.search("Matrix", special):
tourney.isMatrix = True
if special == "Rebuy":
tourney.isRebuy = True
if special == "Madness":
tourney.tourneyComment = "Madness"
if mg['SHOOTOUT'] is not None:
tourney.isShootout = True
if mg['TURBO1'] is not None or mg['TURBO2'] is not None :
tourney.speed = "Turbo"
if mg['TOURNO'] is not None:
tourney.tourNo = mg['TOURNO']
else:
log.info( "Unable to get a valid Tournament ID -- File rejected" )
return False
if tourney.isMatrix:
if mg['MATCHNO'] is not None:
tourney.matrixMatchId = mg['MATCHNO']
else:
tourney.matrixMatchId = 0
# Get BuyIn/Fee
# Try and deal with the different cases that can occur :
# - No buy-in/fee can be on the first line (freerolls, Satellites sometimes ?, ...) but appears in the rest of the description ==> use this one
# - Buy-In/Fee from the first line differs from the rest of the description :
# * OK in matrix tourneys (global buy-in dispatched between the different matches)
# * NOK otherwise ==> issue a warning and store specific data as if were a Matrix Tourney
# - If no buy-in/fee can be found : assume it's a freeroll
m = self.re_TourneyBuyInFee.search(tourneyText)
if m is not None:
mg = m.groupdict()
if tourney.isMatrix :
if mg['BUYIN'] is not None:
tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
tourney.subTourneyFee = 0
if mg['FEE'] is not None:
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
else :
if mg['BUYIN'] is not None:
if tourney.buyin is None:
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin:
log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (touney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) )
tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
if mg['FEE'] is not None:
if tourney.fee is None:
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee:
log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (touney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) )
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
if tourney.buyin is None:
log.info( "Unable to affect a buyin to this tournament : assume it's a freeroll" )
tourney.buyin = 0
tourney.fee = 0
else:
if tourney.fee is None:
#print "Couldn't initialize fee, even though buyin went OK : assume there are no fees"
tourney.fee = 0
#Get single line infos
dictRegex = { "BUYINCHIPS" : self.re_TourneyBuyInChips,
"ENTRIES" : self.re_TourneyEntries,
"PRIZEPOOL" : self.re_TourneyPrizePool,
"REBUY_AMOUNT" : self.re_TourneyRebuyAmount,
"ADDON_AMOUNT" : self.re_TourneyAddOnAmount,
"REBUY_TOTAL" : self.re_TourneyRebuysTotal,
"ADDONS_TOTAL" : self.re_TourneyAddOnsTotal,
"REBUY_CHIPS" : self.re_TourneyRebuyChips,
"ADDON_CHIPS" : self.re_TourneyAddOnChips,
"STARTTIME" : self.re_TourneyTimeInfo,
"KO_BOUNTY_AMOUNT" : self.re_TourneyKOBounty,
}
dictHolders = { "BUYINCHIPS" : "buyInChips",
"ENTRIES" : "entries",
"PRIZEPOOL" : "prizepool",
"REBUY_AMOUNT" : "rebuyAmount",
"ADDON_AMOUNT" : "addOnAmount",
"REBUY_TOTAL" : "totalRebuys",
"ADDONS_TOTAL" : "totalAddOns",
"REBUY_CHIPS" : "rebuyChips",
"ADDON_CHIPS" : "addOnChips",
"STARTTIME" : "starttime",
"KO_BOUNTY_AMOUNT" : "koBounty"
}
mg = {} # After the loop, mg will contain all the matching groups, including the ones that have not been used, like ENDTIME and IN-PROGRESS
for data in dictRegex:
m = dictRegex.get(data).search(tourneyText)
if m is not None:
mg.update(m.groupdict())
setattr(tourney, dictHolders[data], mg[data])
if mg['IN_PROGRESS'] is not None or mg['ENDTIME'] is not None:
# Assign endtime to tourney (if None, that's ok, it's because the tourney wans't over over when the summary file was produced)
tourney.endtime = mg['ENDTIME']
# Deal with hero specific information
if tourney.hero is not None :
m = self.re_TourneyRebuyCount.search(tourneyText)
if m is not None:
mg = m.groupdict()
if mg['REBUY_COUNT'] is not None :
tourney.countRebuys.update( { tourney.hero : Decimal(mg['REBUY_COUNT']) } )
m = self.re_TourneyAddOnCount.search(tourneyText)
if m is not None:
mg = m.groupdict()
if mg['ADDON_COUNT'] is not None :
tourney.countAddOns.update( { tourney.hero : Decimal(mg['ADDON_COUNT']) } )
m = self.re_TourneyCountKO.search(tourneyText)
if m is not None:
mg = m.groupdict()
if mg['COUNT_KO'] is not None :
tourney.countKO.update( { tourney.hero : Decimal(mg['COUNT_KO']) } )
# Deal with money amounts
tourney.koBounty = 100*Decimal(re.sub(u',', u'', "%s" % tourney.koBounty))
tourney.prizepool = 100*Decimal(re.sub(u',', u'', "%s" % tourney.prizepool))
tourney.rebuyAmount = 100*Decimal(re.sub(u',', u'', "%s" % tourney.rebuyAmount))
tourney.addOnAmount = 100*Decimal(re.sub(u',', u'', "%s" % tourney.addOnAmount))
# Calculate payin amounts and update winnings -- not possible to take into account nb of rebuys, addons or Knockouts for other players than hero on FTP
for p in tourney.players :
tourney.payinAmounts[p] = tourney.buyin + tourney.fee + (tourney.rebuyAmount * tourney.countRebuys[p]) + (tourney.addOnAmount * tourney.countAddOns[p])
#print " player %s : payinAmount = %d" %( p, tourney.payinAmounts[p])
if tourney.isKO :
#tourney.incrementPlayerWinnings(tourney.players[p], Decimal(tourney.koBounty)*Decimal(tourney.countKO[p]))
tourney.winnings[p] += Decimal(tourney.koBounty)*Decimal(tourney.countKO[p])
#print "player %s : winnings %d" % (p, tourney.winnings[p])
#print mg
return True
def getPlayersPositionsAndWinnings(self, tourney):
playersText = tourney.summaryText[1]
#print "Examine : '%s'" %(playersText)
m = self.re_TourneyPlayersSummary.finditer(playersText)
for a in m:
if a.group('PNAME') is not None and a.group('RANK') is not None:
if a.group('RANK') == "Still Playing":
rank = -1
else:
rank = Decimal(a.group('RANK'))
if a.group('WINNING') is not None:
winnings = 100*Decimal(re.sub(u',', u'', "%s" % a.group('WINNING')))
else:
winnings = "0"
tourney.addPlayer(rank, a.group('PNAME'), winnings, 0, 0, 0, 0)
else:
print "Player finishing stats unreadable : %s" % a
# Find Hero
n = self.re_TourneyHeroFinishingP.search(playersText)
if n is not None:
heroName = n.group('HERO_NAME')
tourney.hero = heroName
# Is this really useful ?
if (tourney.finishPositions[heroName] != Decimal(n.group('HERO_FINISHING_POS'))):
print "Bad parsing : finish position incoherent : %s / %s" % (tourney.finishPositions[heroName], n.group('HERO_FINISHING_POS'))
return True
if __name__ == "__main__": if __name__ == "__main__":
parser = OptionParser() parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/fulltilt/razz/FT20090223 Danville - $0.50-$1 Ante $0.10 - Limit Razz.txt") parser.add_option("-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")
@ -369,7 +675,7 @@ if __name__ == "__main__":
(options, args) = parser.parse_args() (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) e = Fulltilt(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -17,6 +17,7 @@
import threading import threading
import subprocess import subprocess
import traceback
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
@ -135,13 +136,24 @@ class GuiAutoImport (threading.Thread):
def do_import(self): def do_import(self):
"""Callback for timer to do an import iteration.""" """Callback for timer to do an import iteration."""
if self.doAutoImportBool: if self.doAutoImportBool:
self.startButton.set_label(u' I M P O R T I N G ')
self.importer.runUpdated() self.importer.runUpdated()
sys.stdout.write(".") sys.stdout.write(".")
sys.stdout.flush() sys.stdout.flush()
gobject.timeout_add(1000, self.reset_startbutton)
return True return True
else: else:
return False return False
def reset_startbutton(self):
if self.pipe_to_hud is not None:
self.startButton.set_label(u' _Stop Autoimport ')
else:
self.startButton.set_label(u' _Start Autoimport ')
return False
def startClicked(self, widget, data): def startClicked(self, widget, data):
"""runs when user clicks start on auto import tab""" """runs when user clicks start on auto import tab"""
@ -160,33 +172,32 @@ class GuiAutoImport (threading.Thread):
# - Ideally we want to release the lock if the auto-import is killed by some # - Ideally we want to release the lock if the auto-import is killed by some
# kind of exception - is this possible? # kind of exception - is this possible?
if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired
try: print "\nGlobal lock taken ..."
print "\nGlobal lock taken ..." self.doAutoImportBool = True
self.doAutoImportBool = True widget.set_label(u' _Stop Autoimport ')
widget.set_label(u' _Stop Autoimport ') if self.pipe_to_hud is None:
if self.pipe_to_hud is None: if os.name == 'nt':
if os.name == 'nt': command = "python HUD_main.py " + self.settings['cl_options']
command = "python HUD_main.py" + " " + self.settings['cl_options'] bs = 0
bs = 0 # windows is not happy with line buffing here else:
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, command = os.path.join(sys.path[0], 'HUD_main.py')
universal_newlines=True) command = [command, ] + string.split(self.settings['cl_options'])
else: bs = 1
command = os.path.join(sys.path[0], 'HUD_main.py') try:
cl = [command, ] + string.split(self.settings['cl_options']) self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
self.pipe_to_hud = subprocess.Popen(cl, bufsize = 1, stdin = subprocess.PIPE, universal_newlines = True)
universal_newlines=True) except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
# Add directories to importer object. print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
else:
for site in self.input_settings: for site in self.input_settings:
self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1]) self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1])
print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0]) print "+Import directory - Site: " + site + " dir: " + str(self.input_settings[site][0])
self.do_import() self.do_import()
interval=int(self.intervalEntry.get_text()) interval = int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import) gobject.timeout_add(interval*1000, self.do_import)
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
else: else:
print "auto-import aborted - global lock not available" print "auto-import aborted - global lock not available"
else: # toggled off else: # toggled off

View File

@ -284,7 +284,7 @@ def main(argv=None):
parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True, parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True,
help="don't start gui; deprecated (just give a filename with -f).") help="don't start gui; deprecated (just give a filename with -f).")
parser.add_option("-c", "--convert", dest="filtername", default="PokerStars", metavar="FILTER", parser.add_option("-c", "--convert", dest="filtername", default="PokerStars", metavar="FILTER",
help="Conversion filter (*Full Tilt Poker, PokerStars, Everleaf)") help="Conversion filter (*Full Tilt Poker, PokerStars, Everleaf, Absolute)")
parser.add_option("-x", "--failOnError", action="store_true", default=False, parser.add_option("-x", "--failOnError", action="store_true", default=False,
help="If this option is passed it quits when it encounters any error") help="If this option is passed it quits when it encounters any error")
parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int", parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int",
@ -319,6 +319,7 @@ def main(argv=None):
# importer.setDropIndexes("auto") # importer.setDropIndexes("auto")
importer.setDropIndexes("don't drop") importer.setDropIndexes("don't drop")
importer.setFailOnError(options.failOnError) importer.setFailOnError(options.failOnError)
importer.setThreads(-1)
importer.addBulkImportImportFileOrDir(os.path.expanduser(options.filename), site=options.filtername) importer.addBulkImportImportFileOrDir(os.path.expanduser(options.filename), site=options.filtername)
importer.setCallHud(False) importer.setCallHud(False)
importer.runImport() importer.runImport()

View File

@ -20,6 +20,7 @@ import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import os import os
import traceback
from time import * from time import *
#import pokereval #import pokereval

View File

@ -132,7 +132,7 @@ class GuiPlayerStats (threading.Thread):
self.stats_vbox = gtk.VBox(False, 0) self.stats_vbox = gtk.VBox(False, 0)
self.stats_vbox.show() self.stats_vbox.show()
self.stats_frame.add(self.stats_vbox) self.stats_frame.add(self.stats_vbox)
self.fillStatsFrame(self.stats_vbox) # self.fillStatsFrame(self.stats_vbox)
self.main_hbox.pack_start(self.filters.get_vbox()) self.main_hbox.pack_start(self.filters.get_vbox())
self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True) self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
@ -167,7 +167,9 @@ class GuiPlayerStats (threading.Thread):
for site in sites: for site in sites:
if sites[site] == True: if sites[site] == True:
sitenos.append(siteids[site]) sitenos.append(siteids[site])
self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],)) # Nasty hack to deal with multiple sites + same player name -Eric
que = self.sql.query['getPlayerId'] + " AND siteId=%d" % siteids[site]
self.cursor.execute(que, (heroes[site],))
result = self.db.cursor.fetchall() result = self.db.cursor.fetchall()
if len(result) == 1: if len(result) == 1:
playerids.append(result[0][0]) playerids.append(result[0][0])

View File

@ -21,7 +21,11 @@ pygtk.require('2.0')
import gtk import gtk
import os import os
from time import time, strftime, localtime from time import time, strftime, localtime
from numpy import diff, nonzero try:
from numpy import diff, nonzero
except:
print """Failed to load numpy in Session Viewer"""
print """This is of no consequence as the module currently doesn't do anything."""
import Card import Card
import fpdb_import import fpdb_import

View File

@ -22,9 +22,9 @@ import gtk
import os import os
import fpdb_simple import fpdb_simple
import fpdb_import import fpdb_import
import fpdb_db import fpdb_db
from Exceptions import *
class GuiTableViewer (threading.Thread): class GuiTableViewer (threading.Thread):
@ -74,7 +74,7 @@ class GuiTableViewer (threading.Thread):
tmp+=("WtSD", "W$wsF", "W$SD") tmp+=("WtSD", "W$wsF", "W$SD")
else: else:
raise fpdb_simple.FpdbError("reimplement stud") raise FpdbError("reimplement stud")
arr.append(tmp) arr.append(tmp)
#then the data rows #then the data rows
@ -94,7 +94,7 @@ class GuiTableViewer (threading.Thread):
elif seatCount==2 or seatCount==3: elif seatCount==2 or seatCount==3:
minSeats,maxSeats=seatCount,seatCount minSeats,maxSeats=seatCount,seatCount
else: else:
fpdb_simple.FpdbError("invalid seatCount") FpdbError("invalid seatCount")
self.cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats>=%s AND activeSeats<=%s", (self.gametype_id, self.player_ids[player][0], minSeats, maxSeats)) self.cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats>=%s AND activeSeats<=%s", (self.gametype_id, self.player_ids[player][0], minSeats, maxSeats))
rows=self.cursor.fetchall() rows=self.cursor.fetchall()

View File

@ -252,10 +252,10 @@
<site enabled="False" <site enabled="False"
site_name="PartyPoker" site_name="PartyPoker"
table_finder="PartyPoker.exe" table_finder="PartyGaming.exe"
screen_name="YOUR SCREEN NAME HERE" screen_name="YOUR SCREEN NAME HERE"
site_path="" site_path="C:/Program Files/PartyGaming/PartyPoker"
HH_path="" HH_path="C:/Program Files/PartyGaming/PartyPoker/HandHistory/YOUR SCREEN NAME HERE/"
decoder="everleaf_decode_table" decoder="everleaf_decode_table"
converter="PartyPokerToFpdb" converter="PartyPokerToFpdb"
supported_games="holdem"> supported_games="holdem">
@ -432,7 +432,8 @@
</hhcs> </hhcs>
<supported_databases> <supported_databases>
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD" db_type="fpdb"></database> <database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD" db_type="fpdb"></database>
<!-- <database db_ip="localhost" db_name="fpdb" db_pass="fpdb" db_server="sqlite" db_type="fpdb" db_user="fpdb"/> -->
</supported_databases> </supported_databases>
</FreePokerToolsConfig> </FreePokerToolsConfig>

View File

@ -37,7 +37,7 @@ import traceback
if not options.errorsToConsole: if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('fpdb-error-log.txt', 'w', 0) errorFile = open('HUD-error.txt', 'w', 0)
sys.stderr = errorFile sys.stderr = errorFile
import thread import thread
@ -195,7 +195,7 @@ class HUD_main(object):
temp_key = tour_number temp_key = tour_number
else: # tourney, but can't get number and table else: # tourney, but can't get number and table
print "could not find tournament: skipping " print "could not find tournament: skipping "
sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id))) #sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id)))
continue continue
else: else:

View File

@ -1,4 +1,5 @@
#!/usr/bin/python #!/usr/bin/python
# -*- 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
@ -32,6 +33,8 @@ import pprint
import DerivedStats import DerivedStats
import Card import Card
log = logging.getLogger("parser")
class Hand(object): class Hand(object):
###############################################################3 ###############################################################3
@ -61,6 +64,15 @@ class Hand(object):
self.fee = None # the Database code is looking for this one .. ? self.fee = None # the Database code is looking for this one .. ?
self.level = None self.level = None
self.mixed = None self.mixed = None
# Some attributes for hand from a tourney
self.speed = "Normal"
self.isRebuy = False
self.isKO = False
self.isHU = False
self.isMatrix = False
self.isShootout = False
self.tourneyComment = None
self.seating = [] self.seating = []
self.players = [] self.players = []
self.posted = [] self.posted = []
@ -162,7 +174,7 @@ shown whether they were revealed at showdown
mucked whether they were mucked at showdown mucked whether they were mucked at showdown
dealt whether they were seen in a 'dealt to' line dealt whether they were seen in a 'dealt to' line
""" """
# logging.debug("addHoleCards %s %s" % (open + closed, player)) # log.debug("addHoleCards %s %s" % (open + closed, player))
try: try:
self.checkPlayerExists(player) self.checkPlayerExists(player)
except FpdbParseError, e: except FpdbParseError, e:
@ -185,31 +197,31 @@ db: a connected fpdb_db object"""
# TODO: # TODO:
# Players - base playerid and siteid tuple # Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId) sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
#Gametypes
gtid = db.getGameTypeId(self.siteId, self.gametype)
# HudCache data to come from DerivedStats class # HudCache data to come from DerivedStats class
# HandsActions - all actions for all players for all streets - self.actions # HandsActions - all actions for all players for all streets - self.actions
# BoardCards - Skip - no longer necessary
# Hands - Summary information of hand indexed by handId - gameinfo # Hands - Summary information of hand indexed by handId - gameinfo
#hh['siteHandNo'] = self.handid #This should be moved to prepInsert
# gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), hh = {}
# hh['siteHandNo'] = self.handid
#hh['handStart'] = self.starttime hh['handStart'] = self.starttime
# seats TINYINT NOT NULL, hh['gameTypeId'] = gtid
# # seats TINYINT NOT NULL,
#hh['tableName'] = self.tablenam hh['tableName'] = self.tablename
#hh['maxSeats'] = self.maxseats hh['maxSeats'] = self.maxseats
# boardcard1 smallint, /* 0=none, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As */ hh['seats'] = len(sqlids)
# boardcard2 smallint, # Flop turn and river may all be empty - add (likely) too many elements and trim with range
# boardcard3 smallint, boardcards = self.board['FLOP'] + self.board['TURN'] + self.board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
# boardcard4 smallint, cards = [Card.encodeCard(c) for c in boardcards[0:5]]
# boardcard5 smallint, hh['boardcard1'] = cards[0]
# Flop turn and river may all be empty - add (likely) too many elements and trim with range hh['boardcard2'] = cards[1]
# boardcards = board['FLOP'] + board['TURN'] + board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x'] hh['boardcard3'] = cards[2]
# cards = [Card.cardFromValueSuit(v,s) for v,s in boardcards[0:4]] hh['boardcard4'] = cards[3]
# hh['boardcard1'] = cards[0] hh['boardcard5'] = cards[4]
# hh['boardcard2'] = cards[1]
# hh['boardcard3'] = cards[2]
# hh['boardcard4'] = cards[3]
# hh['boardcard5'] = cards[4]
# texture smallint, # texture smallint,
# playersVpi SMALLINT NOT NULL, /* num of players vpi */ # playersVpi SMALLINT NOT NULL, /* num of players vpi */
# Needs to be recorded # Needs to be recorded
@ -233,18 +245,15 @@ db: a connected fpdb_db object"""
# Needs to be recorded # Needs to be recorded
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */ # street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
# Needs to be recorded # Needs to be recorded
# street1Pot INT, /* pot size at flop/street4 */
# Needs to be recorded #print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % self.getStreetTotals()
# street2Pot INT, /* pot size at turn/street5 */ #FIXME: Pot size still in decimal, needs to be converted to cents
# Needs to be recorded (hh['street1Pot'], hh['street2Pot'], hh['street3Pot'], hh['street4Pot'], hh['showdownPot']) = self.getStreetTotals()
# street3Pot INT, /* pot size at river/street6 */
# Needs to be recorded
# street4Pot INT, /* pot size at sd/street7 */
# Needs to be recorded
# showdownPot INT, /* pot size at sd/street7 */
# comment TEXT, # comment TEXT,
# commentTs DATETIME # commentTs DATETIME
# handid = db.storeHand(hh) #print hh
handid = db.storeHand(hh)
# HandsPlayers - ? ... Do we fix winnings? # HandsPlayers - ? ... Do we fix winnings?
# Tourneys ? # Tourneys ?
# TourneysPlayers # TourneysPlayers
@ -264,7 +273,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)) log.debug("addPlayer: %s %s (%s)" % (seat, name, chips))
if chips is not None: if chips is not None:
chips = re.sub(u',', u'', chips) #some sites have commas chips = re.sub(u',', u'', chips) #some sites have commas
self.players.append([seat, name, chips]) self.players.append([seat, name, chips])
@ -280,9 +289,9 @@ If a player has None chips he won't be added."""
# 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: if match:
self.streets.update(match.groupdict()) self.streets.update(match.groupdict())
logging.debug("markStreets:\n"+ str(self.streets)) log.debug("markStreets:\n"+ str(self.streets))
else: else:
logging.error("markstreets didn't match") log.error("markstreets didn't match")
def checkPlayerExists(self,player): def checkPlayerExists(self,player):
if player not in [p[1] for p in self.players]: if player not in [p[1] for p in self.players]:
@ -292,7 +301,7 @@ If a player has None chips he won't be added."""
def setCommunityCards(self, street, cards): def setCommunityCards(self, street, cards):
logging.debug("setCommunityCards %s %s" %(street, cards)) log.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]
# print "DEBUG: self.board: %s" % self.board # print "DEBUG: self.board: %s" % self.board
@ -303,14 +312,13 @@ If a player has None chips he won't be added."""
return c return c
def addAnte(self, player, ante): def addAnte(self, player, ante):
logging.debug("%s %s antes %s" % ('ANTES', player, ante)) log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
if player is not None: if player is not None:
ante = re.sub(u',', u'', ante) #some sites have commas ante = re.sub(u',', u'', ante) #some sites have commas
self.bets['ANTES'][player].append(Decimal(ante)) self.bets['BLINDSANTES'][player].append(Decimal(ante))
self.stacks[player] -= Decimal(ante) self.stacks[player] -= Decimal(ante)
act = (player, 'posts', "ante", ante, self.stacks[player]==0) act = (player, 'posts', "ante", ante, self.stacks[player]==0)
self.actions['ANTES'].append(act) self.actions['BLINDSANTES'].append(act)
#~ self.lastBet['ANTES'] = Decimal(ante)
self.pot.addMoney(player, Decimal(ante)) self.pot.addMoney(player, Decimal(ante))
def addBlind(self, player, blindtype, amount): def addBlind(self, player, blindtype, amount):
@ -322,29 +330,29 @@ If a player has None chips he won't be added."""
# - this is a call of 1 sb and a raise to 1 bb # - this is a call of 1 sb and a raise to 1 bb
# #
logging.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount)) log.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
if player is not None: if player is not None:
amount = re.sub(u',', u'', amount) #some sites have commas amount = re.sub(u',', u'', amount) #some sites have commas
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])
act = (player, 'posts', blindtype, amount, self.stacks[player]==0) act = (player, 'posts', blindtype, amount, self.stacks[player]==0)
self.actions['BLINDSANTES'].append(act) self.actions['BLINDSANTES'].append(act)
if blindtype == 'both':
amount = self.bb
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb))
self.bets['PREFLOP'][player].append(Decimal(amount))
self.pot.addMoney(player, Decimal(amount)) self.pot.addMoney(player, Decimal(amount))
if blindtype == 'big blind': self.lastBet['PREFLOP'] = Decimal(amount)
self.lastBet['PREFLOP'] = Decimal(amount)
elif blindtype == 'both':
# extra small blind is 'dead'
self.lastBet['PREFLOP'] = Decimal(self.bb)
self.posted = self.posted + [[player,blindtype]] self.posted = self.posted + [[player,blindtype]]
#print "DEBUG: self.posted: %s" %(self.posted)
def addCall(self, street, player=None, amount=None): def addCall(self, street, player=None, amount=None):
if amount: if amount:
amount = re.sub(u',', u'', amount) #some sites have commas amount = re.sub(u',', u'', amount) #some sites have commas
logging.debug("%s %s calls %s" %(street, player, amount)) log.debug("%s %s calls %s" %(street, player, amount))
# Potentially calculate the amount of the call if not supplied # Potentially calculate the amount of the call if not supplied
# corner cases include if player would be all in # corner cases include if player would be all in
if amount is not None: if amount is not None:
@ -414,7 +422,7 @@ Add a raise on [street] by [player] to [amountTo]
self._addRaise(street, player, C, Rb, Rt) self._addRaise(street, player, C, Rb, Rt)
def _addRaise(self, street, player, C, Rb, Rt): def _addRaise(self, street, player, C, Rb, Rt):
logging.debug("%s %s raise %s" %(street, player, Rt)) log.debug("%s %s raise %s" %(street, player, Rt))
self.bets[street][player].append(C + Rb) self.bets[street][player].append(C + Rb)
self.stacks[player] -= (C + Rb) self.stacks[player] -= (C + Rb)
act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0) act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0)
@ -425,7 +433,7 @@ Add a raise on [street] by [player] to [amountTo]
def addBet(self, street, player, amount): def addBet(self, street, player, amount):
logging.debug("%s %s bets %s" %(street, player, amount)) log.debug("%s %s bets %s" %(street, player, amount))
amount = re.sub(u',', u'', amount) #some sites have commas amount = re.sub(u',', u'', amount) #some sites have commas
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.bets[street][player].append(Decimal(amount)) self.bets[street][player].append(Decimal(amount))
@ -444,7 +452,7 @@ Add a raise on [street] by [player] to [amountTo]
def addFold(self, street, player): def addFold(self, street, player):
logging.debug("%s %s folds" % (street, player)) log.debug("%s %s folds" % (street, player))
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.folded.add(player) self.folded.add(player)
self.pot.addFold(player) self.pot.addFold(player)
@ -453,12 +461,13 @@ Add a raise on [street] by [player] to [amountTo]
def addCheck(self, street, player): def addCheck(self, street, player):
#print "DEBUG: %s %s checked" % (street, player) #print "DEBUG: %s %s checked" % (street, player)
logging.debug("%s %s checks" % (street, player))
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.actions[street].append((player, 'checks')) self.actions[street].append((player, 'checks'))
def addCollectPot(self,player, pot): def addCollectPot(self,player, pot):
logging.debug("%s collected %s" % (player, pot)) log.debug("%s collected %s" % (player, pot))
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.collected = self.collected + [[player, pot]] self.collected = self.collected + [[player, pot]]
if player not in self.collectees: if player not in self.collectees:
@ -472,7 +481,7 @@ Add a raise on [street] by [player] to [amountTo]
For when a player shows cards for any reason (for showdown or out of choice). For when a player shows cards for any reason (for showdown or out of choice).
Card ranks will be uppercased Card ranks will be uppercased
""" """
logging.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard)) log.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard))
if cards is not None: if cards is not None:
self.addHoleCards(cards,player,shown, mucked) self.addHoleCards(cards,player,shown, mucked)
elif holeandboard is not None: elif holeandboard is not None:
@ -480,7 +489,6 @@ Card ranks will be uppercased
board = set([c for s in self.board.values() for c in s]) board = set([c for s in self.board.values() for c in s])
self.addHoleCards(holeandboard.difference(board),player,shown, mucked) self.addHoleCards(holeandboard.difference(board),player,shown, mucked)
def totalPot(self): def totalPot(self):
"""If all bets and blinds have been added, totals up the total pot size""" """If all bets and blinds have been added, totals up the total pot size"""
@ -522,7 +530,7 @@ Map the tuple self.gametype onto the pokerstars string describing it
"cp" : "Cap Pot Limit" "cp" : "Cap Pot Limit"
} }
logging.debug("gametype: %s" %(self.gametype)) log.debug("gametype: %s" %(self.gametype))
retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']]) retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
return retstring return retstring
@ -553,6 +561,8 @@ Map the tuple self.gametype onto the pokerstars string describing it
return ("%s: posts big blind %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else '')) return ("%s: posts big blind %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
elif(act[2] == "both"): elif(act[2] == "both"):
return ("%s: posts small & big blinds %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else '')) return ("%s: posts small & big blinds %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
elif(act[2] == "ante"):
return ("%s: posts the ante %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
elif act[1] == 'bringin': elif act[1] == 'bringin':
return ("%s: brings in for %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else '')) return ("%s: brings in for %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'discards': elif act[1] == 'discards':
@ -564,6 +574,9 @@ Map the tuple self.gametype onto the pokerstars string describing it
"""Return a string of the stakes of the current hand.""" """Return a string of the stakes of the current hand."""
return "%s%s/%s%s" % (self.sym, self.sb, self.sym, self.bb) return "%s%s/%s%s" % (self.sym, self.sb, self.sym, self.bb)
def getStreetTotals(self):
pass
def writeGameLine(self): def writeGameLine(self):
"""Return the first HH line for the current hand.""" """Return the first HH line for the current hand."""
gs = "PokerStars Game #%s: " % self.handid gs = "PokerStars Game #%s: " % self.handid
@ -579,8 +592,15 @@ Map the tuple self.gametype onto the pokerstars string describing it
else: # non-mixed cash games else: # non-mixed cash games
gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString()) gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString())
return gs + datetime.datetime.strftime(self.starttime,'%Y/%m/%d %H:%M:%S ET') try:
timestr = datetime.datetime.strftime(self.starttime, '%Y/%m/%d %H:%M:%S ET')
except TypeError:
print "*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:", self.starttime
print "*** Make sure your HandHistoryConverter is setting hand.starttime properly!"
print "*** Game String:", gs
return gs
else:
return gs + timestr
def writeTableLine(self): def writeTableLine(self):
table_string = "Table \'%s\' %s-max" % (self.tablename, self.maxseats) table_string = "Table \'%s\' %s-max" % (self.tablename, self.maxseats)
@ -601,7 +621,7 @@ class HoldemOmahaHand(Hand):
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None): def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
if gametype['base'] != 'hold': if gametype['base'] != 'hold':
pass # or indeed don't pass and complain instead pass # or indeed don't pass and complain instead
logging.debug("HoldemOmahaHand") log.debug("HoldemOmahaHand")
self.allStreets = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER'] self.allStreets = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER']
self.holeStreets = ['PREFLOP'] self.holeStreets = ['PREFLOP']
self.communityStreets = ['FLOP', 'TURN', 'RIVER'] self.communityStreets = ['FLOP', 'TURN', 'RIVER']
@ -619,6 +639,7 @@ class HoldemOmahaHand(Hand):
hhc.compilePlayerRegexs(self) hhc.compilePlayerRegexs(self)
hhc.markStreets(self) hhc.markStreets(self)
hhc.readBlinds(self) hhc.readBlinds(self)
hhc.readAntes(self)
hhc.readButton(self) hhc.readButton(self)
hhc.readHeroCards(self) hhc.readHeroCards(self)
hhc.readShowdownActions(self) hhc.readShowdownActions(self)
@ -629,6 +650,7 @@ class HoldemOmahaHand(Hand):
for street in self.actionStreets: for street in self.actionStreets:
if self.streets[street]: if self.streets[street]:
hhc.readAction(self, street) hhc.readAction(self, street)
self.pot.markTotal(street)
hhc.readCollectPot(self) hhc.readCollectPot(self)
hhc.readShownCards(self) hhc.readShownCards(self)
self.totalPot() # finalise it (total the pot) self.totalPot() # finalise it (total the pot)
@ -640,9 +662,9 @@ class HoldemOmahaHand(Hand):
if handid is not None: if handid is not None:
self.select(handid) # Will need a handId self.select(handid) # Will need a handId
else: else:
logging.warning("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid") log.warning("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid")
else: else:
logging.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided") log.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided")
pass pass
@ -653,8 +675,20 @@ class HoldemOmahaHand(Hand):
else: else:
self.addHoleCards('PREFLOP', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt) self.addHoleCards('PREFLOP', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
def getStreetTotals(self):
# street1Pot INT, /* pot size at flop/street4 */
# street2Pot INT, /* pot size at turn/street5 */
# street3Pot INT, /* pot size at river/street6 */
# street4Pot INT, /* pot size at sd/street7 */
# showdownPot INT, /* pot size at sd/street7 */
tmp1 = self.pot.getTotalAtStreet('FLOP')
tmp2 = self.pot.getTotalAtStreet('TURN')
tmp3 = self.pot.getTotalAtStreet('RIVER')
tmp4 = 0
tmp5 = 0
return (tmp1,tmp2,tmp3,tmp4,tmp5)
def writeHTMLHand(self, fh=sys.__stdout__): def writeHTMLHand(self):
from nevow import tags as T from nevow import tags as T
from nevow import flat from nevow import flat
players_who_act_preflop = (([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']])) players_who_act_preflop = (([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
@ -754,7 +788,7 @@ class HoldemOmahaHand(Hand):
super(HoldemOmahaHand, self).writeHand(fh) super(HoldemOmahaHand, self).writeHand(fh)
players_who_act_preflop = set(([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']])) players_who_act_preflop = set(([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
logging.debug(self.actions['PREFLOP']) log.debug(self.actions['PREFLOP'])
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 in chips) " %(player[0], player[1], player[2])) print >>fh, ("Seat %s: %s ($%s in chips) " %(player[0], player[1], player[2]))
@ -870,6 +904,7 @@ class DrawHand(Hand):
hhc.compilePlayerRegexs(self) hhc.compilePlayerRegexs(self)
hhc.markStreets(self) hhc.markStreets(self)
hhc.readBlinds(self) hhc.readBlinds(self)
hhc.readAntes(self)
hhc.readButton(self) hhc.readButton(self)
hhc.readHeroCards(self) hhc.readHeroCards(self)
hhc.readShowdownActions(self) hhc.readShowdownActions(self)
@ -877,6 +912,7 @@ class DrawHand(Hand):
for street in self.streetList: for street in self.streetList:
if self.streets[street]: if self.streets[street]:
hhc.readAction(self, street) hhc.readAction(self, street)
self.pot.markTotal(street)
hhc.readCollectPot(self) hhc.readCollectPot(self)
hhc.readShownCards(self) hhc.readShownCards(self)
self.totalPot() # finalise it (total the pot) self.totalPot() # finalise it (total the pot)
@ -897,7 +933,7 @@ class DrawHand(Hand):
# - this is a call of 1 sb and a raise to 1 bb # - this is a call of 1 sb and a raise to 1 bb
# #
logging.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount)) log.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
if player is not None: if player is not None:
self.bets['DEAL'][player].append(Decimal(amount)) self.bets['DEAL'][player].append(Decimal(amount))
self.stacks[player] -= Decimal(amount) self.stacks[player] -= Decimal(amount)
@ -924,7 +960,7 @@ class DrawHand(Hand):
def discardDrawHoleCards(self, cards, player, street): def discardDrawHoleCards(self, cards, player, street):
logging.debug("discardDrawHoleCards '%s' '%s' '%s'" % (cards, player, street)) log.debug("discardDrawHoleCards '%s' '%s' '%s'" % (cards, player, street))
self.discards[street][player] = set([cards]) self.discards[street][player] = set([cards])
@ -937,6 +973,14 @@ class DrawHand(Hand):
act = (player, 'discards', num) act = (player, 'discards', num)
self.actions[street].append(act) self.actions[street].append(act)
def getStreetTotals(self):
# street1Pot INT, /* pot size at flop/street4 */
# street2Pot INT, /* pot size at turn/street5 */
# street3Pot INT, /* pot size at river/street6 */
# street4Pot INT, /* pot size at sd/street7 */
# showdownPot INT, /* pot size at sd/street7 */
return (0,0,0,0,0)
def writeHand(self, fh=sys.__stdout__): def writeHand(self, fh=sys.__stdout__):
# PokerStars format. # PokerStars format.
@ -1018,11 +1062,11 @@ class StudHand(Hand):
if gametype['base'] != 'stud': if gametype['base'] != 'stud':
pass # or indeed don't pass and complain instead pass # or indeed don't pass and complain instead
self.allStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] self.allStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
self.communityStreets = [] self.communityStreets = []
self.actionStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] self.actionStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
self.streetList = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
Hand.__init__(self, sitename, gametype, handText) Hand.__init__(self, sitename, gametype, handText)
self.sb = gametype['sb'] self.sb = gametype['sb']
@ -1040,11 +1084,11 @@ class StudHand(Hand):
hhc.readHeroCards(self) hhc.readHeroCards(self)
# Read actions in street order # Read actions in street order
for street in self.actionStreets: for street in self.actionStreets:
if street == 'ANTES': continue # OMG--sometime someone folds in the ante round if street == 'BLINDSANTES': continue # OMG--sometime someone folds in the ante round
if self.streets[street]: if self.streets[street]:
logging.debug(street) log.debug(street + self.streets[street])
logging.debug(self.streets[street])
hhc.readAction(self, street) hhc.readAction(self, street)
self.pot.markTotal(street)
hhc.readCollectPot(self) hhc.readCollectPot(self)
hhc.readShownCards(self) # not done yet hhc.readShownCards(self) # not done yet
self.totalPot() # finalise it (total the pot) self.totalPot() # finalise it (total the pot)
@ -1075,7 +1119,7 @@ street (string) the street name (in streetList)
open list of card bigrams e.g. ['2h','Jc'], dealt face up open list of card bigrams e.g. ['2h','Jc'], dealt face up
closed likewise, but known only to player closed likewise, but known only to player
""" """
logging.debug("addPlayerCards %s, o%s x%s" % (player, open, closed)) log.debug("addPlayerCards %s, o%s x%s" % (player, open, closed))
try: try:
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.holecards[street][player] = (open, closed) self.holecards[street][player] = (open, closed)
@ -1089,7 +1133,7 @@ closed likewise, but known only to player
"""\ """\
Add a complete on [street] by [player] to [amountTo] Add a complete on [street] by [player] to [amountTo]
""" """
logging.debug("%s %s completes %s" % (street, player, amountTo)) log.debug("%s %s completes %s" % (street, player, amountTo))
amountTo = re.sub(u',', u'', amountTo) #some sites have commas amountTo = re.sub(u',', u'', amountTo) #some sites have commas
self.checkPlayerExists(player) self.checkPlayerExists(player)
Bp = self.lastBet['THIRD'] Bp = self.lastBet['THIRD']
@ -1107,7 +1151,7 @@ Add a complete on [street] by [player] to [amountTo]
def addBringIn(self, player, bringin): def addBringIn(self, player, bringin):
if player is not None: if player is not None:
logging.debug("Bringin: %s, %s" % (player , bringin)) log.debug("Bringin: %s, %s" % (player , bringin))
self.bets['THIRD'][player].append(Decimal(bringin)) self.bets['THIRD'][player].append(Decimal(bringin))
self.stacks[player] -= Decimal(bringin) self.stacks[player] -= Decimal(bringin)
act = (player, 'bringin', bringin, self.stacks[player]==0) act = (player, 'bringin', bringin, self.stacks[player]==0)
@ -1115,20 +1159,28 @@ Add a complete on [street] by [player] to [amountTo]
self.lastBet['THIRD'] = Decimal(bringin) self.lastBet['THIRD'] = Decimal(bringin)
self.pot.addMoney(player, Decimal(bringin)) self.pot.addMoney(player, Decimal(bringin))
def getStreetTotals(self):
# street1Pot INT, /* pot size at flop/street4 */
# street2Pot INT, /* pot size at turn/street5 */
# street3Pot INT, /* pot size at river/street6 */
# street4Pot INT, /* pot size at sd/street7 */
# showdownPot INT, /* pot size at sd/street7 */
return (0,0,0,0,0)
def writeHand(self, fh=sys.__stdout__): def writeHand(self, fh=sys.__stdout__):
# PokerStars format. # PokerStars format.
super(StudHand, self).writeHand(fh) super(StudHand, self).writeHand(fh)
players_who_post_antes = set([x[0] for x in self.actions['ANTES']]) players_who_post_antes = set([x[0] for x in self.actions['BLINDSANTES']])
for player in [x for x in self.players if x[1] in players_who_post_antes]: for player in [x for x in self.players if x[1] in players_who_post_antes]:
#Only print stacks of players who do something preflop #Only print stacks of players who do something preflop
print >>fh, _("Seat %s: %s (%s%s in chips)" %(player[0], player[1], self.sym, player[2])) print >>fh, _("Seat %s: %s (%s%s in chips)" %(player[0], player[1], self.sym, player[2]))
if 'ANTES' in self.actions: if 'BLINDSANTES' in self.actions:
for act in self.actions['ANTES']: for act in self.actions['BLINDSANTES']:
print >>fh, _("%s: posts the ante %s%s" %(act[0], self.sym, act[3])) print >>fh, _("%s: posts the ante %s%s" %(act[0], self.sym, act[3]))
if 'THIRD' in self.actions: if 'THIRD' in self.actions:
@ -1273,11 +1325,13 @@ class Pot(object):
def __init__(self): def __init__(self):
self.contenders = set() self.contenders = set()
self.committed = {} self.committed = {}
self.total = None self.streettotals = {}
self.returned = {} self.common = Decimal(0)
self.sym = u'$' # this is the default currency symbol self.total = None
self.returned = {}
self.sym = u'$' # this is the default currency symbol
def setSym(self, sym): def setSym(self, sym):
self.sym = sym self.sym = sym
@ -1289,13 +1343,24 @@ class Pot(object):
# addFold must be called when a player folds # addFold must be called when a player folds
self.contenders.discard(player) self.contenders.discard(player)
def addCommonMoney(self, amount):
self.common += amount
def addMoney(self, player, amount): def addMoney(self, player, amount):
# addMoney must be called for any actions that put money in the pot, in the order they occur # addMoney must be called for any actions that put money in the pot, in the order they occur
self.contenders.add(player) self.contenders.add(player)
self.committed[player] += amount self.committed[player] += amount
def markTotal(self, street):
self.streettotals[street] = sum(self.committed.values()) + self.common
def getTotalAtStreet(self, street):
if street in self.streettotals:
return self.streettotals[street]
return 0
def end(self): def end(self):
self.total = sum(self.committed.values()) self.total = sum(self.committed.values()) + self.common
# Return any uncalled bet. # Return any uncalled bet.
committed = sorted([ (v,k) for (k,v) in self.committed.items()]) committed = sorted([ (v,k) for (k,v) in self.committed.items()])

View File

@ -1,4 +1,5 @@
#!/usr/bin/python #!/usr/bin/python
# -*- 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
@ -16,10 +17,10 @@
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
import Hand import Hand
import Tourney
import re import re
import sys import sys
import traceback import traceback
import logging
from optparse import OptionParser from optparse import OptionParser
import os import os
import os.path import os.path
@ -30,19 +31,38 @@ import operator
from xml.dom.minidom import Node from xml.dom.minidom import Node
import time import time
import datetime import datetime
from Exceptions import FpdbParseError
import gettext import gettext
gettext.install('fpdb') gettext.install('fpdb')
import logging, logging.config
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
log = logging.getLogger("parser")
class HandHistoryConverter(): class HandHistoryConverter():
READ_CHUNK_SIZE = 10000 # bytes to read at a time from file (in tail mode) READ_CHUNK_SIZE = 10000 # bytes to read at a time from file in tail mode
def __init__(self, in_path = '-', out_path = '-', sitename = None, follow=False, index=0):
logging.info("HandHistory init") # filetype can be "text" or "xml"
# so far always "text"
# subclass HHC_xml for xml parsing
filetype = "text"
# codepage indicates the encoding of the text file.
# cp1252 is a safe default
# "utf_8" is more likely if there are funny characters
codepage = "cp1252"
def __init__(self, in_path = '-', out_path = '-', follow=False, index=0, autostart=True):
"""\
in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input"""
log.info("HandHistory init - %s subclass, in_path '%s'; out_path '%s'" % (self.sitename, in_path, out_path) )
# default filetype and codepage. Subclasses should set these properly.
self.filetype = "text"
self.codepage = "utf8"
self.index = 0 self.index = 0
self.in_path = in_path self.in_path = in_path
@ -50,6 +70,9 @@ class HandHistoryConverter():
self.processedHands = [] self.processedHands = []
# Tourney object used to store TourneyInfo when called to deal with a Summary file
self.tourney = None
if in_path == '-': if in_path == '-':
self.in_fh = sys.stdin self.in_fh = sys.stdin
@ -58,50 +81,91 @@ class HandHistoryConverter():
else: else:
# TODO: out_path should be sanity checked. # TODO: out_path should be sanity checked.
out_dir = os.path.dirname(self.out_path) out_dir = os.path.dirname(self.out_path)
if not os.path.isdir(out_dir): if not os.path.isdir(out_dir) and out_dir != '':
logging.info("Creatin directory '%s'" % out_dir) log.info("Creating directory '%s'" % out_dir)
os.makedirs(out_dir) os.makedirs(out_dir)
self.out_fh = open(self.out_path, 'w') try:
self.out_fh = codecs.open(self.out_path, 'w', 'cp1252')
log.debug("out_path %s opened as %s" % (self.out_path, self.out_fh))
except:
log.error("out_path %s couldn't be opened" % (self.out_path))
self.sitename = sitename
self.follow = follow self.follow = follow
self.compiledPlayers = set() self.compiledPlayers = set()
self.maxseats = 10 self.maxseats = 10
self.status = True
self.parsedObjectType = "HH" #default behaviour : parsing HH files, can be "Summary" if the parsing encounters a Summary File
if autostart:
self.start()
def __str__(self): def __str__(self):
return """ return """
HandHistoryConverter: '%(sitename)s' HandHistoryConverter: '%(sitename)s'
filetype: '%(filetype)s' filetype '%(filetype)s'
in_path: '%(in_path)s' in_path '%(in_path)s'
out_path: '%(out_path)s' out_path '%(out_path)s'
""" % { 'sitename':self.sitename, 'filetype':self.filetype, 'in_path':self.in_path, 'out_path':self.out_path } follow '%(follow)s'
""" % locals()
def start(self): def start(self):
"""process a hand at a time from the input specified by in_path. """Process a hand at a time from the input specified by in_path.
If in follow mode, wait for more data to turn up. If in follow mode, wait for more data to turn up.
Otherwise, finish at eof. Otherwise, finish at EOF.
""" """
starttime = time.time() starttime = time.time()
if not self.sanityCheck(): if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check" log.warning("Failed sanity check")
return return
if self.follow: try:
numHands = 0 numHands = 0
for handText in self.tailHands(): numErrors = 0
numHands+=1 if self.follow:
self.processHand(handText) #TODO: See how summary files can be handled on the fly (here they should be rejected as before)
else: log.info("Tailing '%s'" % self.in_path)
handsList = self.allHandsAsList() for handText in self.tailHands():
logging.info("Parsing %d hands" % len(handsList)) try:
for handText in handsList: self.processHand(handText)
self.processedHands.append(self.processHand(handText)) numHands+=1
numHands= len(handsList) except FpdbParseError, e:
endtime = time.time() numErrors+=1
print "read %d hands in %.3f seconds" % (numHands, endtime - starttime) log.warning("Failed to convert hand %s" % e.hid)
if self.out_fh != sys.stdout: log.debug(handText)
self.out_fh.close() else:
handsList = self.allHandsAsList()
log.info("Parsing %d hands" % len(handsList))
# Determine if we're dealing with a HH file or a Summary file
# quick fix : empty files make the handsList[0] fail ==> If empty file, go on with HH parsing
if len(handsList) == 0 or self.isSummary(handsList[0]) == False:
self.parsedObjectType = "HH"
for handText in handsList:
try:
self.processedHands.append(self.processHand(handText))
except FpdbParseError, e:
numErrors+=1
log.warning("Failed to convert hand %s" % e.hid)
log.debug(handText)
numHands = len(handsList)
endtime = time.time()
log.info("Read %d hands (%d failed) in %.3f seconds" % (numHands, numErrors, endtime - starttime))
else:
self.parsedObjectType = "Summary"
summaryParsingStatus = self.readSummaryInfo(handsList)
endtime = time.time()
if summaryParsingStatus :
log.info("Summary file '%s' correctly parsed (took %.3f seconds)" % (self.in_path, endtime - starttime))
else :
log.warning("Error converting summary file '%s' (took %.3f seconds)" % (self.in_path, endtime - starttime))
except IOError, ioe:
log.exception("Error converting '%s'" % self.in_path)
finally:
if self.out_fh != sys.stdout:
self.out_fh.close()
def tailHands(self): def tailHands(self):
@ -128,7 +192,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
time.sleep(interval) time.sleep(interval)
fd.seek(where) fd.seek(where)
else: else:
logging.debug("%s changed inode numbers from %d to %d" % (self.in_path, fd_results[1], st_results[1])) log.debug("%s changed inode numbers from %d to %d" % (self.in_path, fd_results[1], st_results[1]))
fd = codecs.open(self.in_path, 'r', self.codepage) fd = codecs.open(self.in_path, 'r', self.codepage)
fd.seek(where) fd.seek(where)
else: else:
@ -173,13 +237,13 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
self.obs = self.obs.strip() self.obs = self.obs.strip()
self.obs = self.obs.replace('\r\n', '\n') self.obs = self.obs.replace('\r\n', '\n')
if self.obs == "" or self.obs == None: if self.obs == "" or self.obs == None:
logging.info("Read no hands.") log.info("Read no hands.")
return return []
return re.split(self.re_SplitHands, self.obs) return re.split(self.re_SplitHands, self.obs)
def processHand(self, handText): def processHand(self, handText):
gametype = self.determineGameType(handText) gametype = self.determineGameType(handText)
logging.debug("gametype %s" % gametype) log.debug("gametype %s" % gametype)
hand = None hand = None
if gametype is None: if gametype is None:
l = None l = None
@ -194,14 +258,14 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
l = [type] + [base] + [limit] l = [type] + [base] + [limit]
if l in self.readSupportedGames(): if l in self.readSupportedGames():
if gametype['base'] == 'hold': if gametype['base'] == 'hold':
logging.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)") log.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)")
hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handText) hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handText)
elif gametype['base'] == 'stud': elif gametype['base'] == 'stud':
hand = Hand.StudHand(self, self.sitename, gametype, handText) hand = Hand.StudHand(self, self.sitename, gametype, handText)
elif gametype['base'] == 'draw': elif gametype['base'] == 'draw':
hand = Hand.DrawHand(self, self.sitename, gametype, handText) hand = Hand.DrawHand(self, self.sitename, gametype, handText)
else: else:
logging.info("Unsupported game type: %s" % gametype) log.info("Unsupported game type: %s" % gametype)
if hand: if hand:
# uncomment these to calculate some stats # uncomment these to calculate some stats
@ -210,7 +274,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
hand.writeHand(self.out_fh) hand.writeHand(self.out_fh)
return hand return hand
else: else:
logging.info("Unsupported game type: %s" % gametype) log.info("Unsupported game type: %s" % gametype)
# TODO: pity we don't know the HID at this stage. Log the entire hand? # 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 :) # From the log we can deduce that it is the hand after the one before :)
@ -335,27 +399,36 @@ or None if we fail to get the info """
hands = hands + [Hand.Hand(self.sitename, self.gametype, l)] hands = hands + [Hand.Hand(self.sitename, self.gametype, l)]
return hands return hands
def __listof(self, x):
if isinstance(x, list) or isinstance(x, tuple): return x
else: return [x]
def readFile(self): def readFile(self):
"""open in_path according to self.codepage""" """Open in_path according to self.codepage. Exceptions caught further up"""
if(self.filetype == "text"): if(self.filetype == "text"):
if self.in_path == '-': if self.in_path == '-':
# read from stdin # read from stdin
logging.debug("Reading stdin with %s" % self.codepage) # is this necessary? or possible? or what? log.debug("Reading stdin with %s" % self.codepage) # is this necessary? or possible? or what?
in_fh = codecs.getreader('cp1252')(sys.stdin) in_fh = codecs.getreader('cp1252')(sys.stdin)
else: else:
logging.debug("Opening %s with %s" % (self.in_path, self.codepage)) success = False
in_fh = codecs.open(self.in_path, 'r', self.codepage) for kodec in self.__listof(self.codepage):
in_fh.seek(self.index) if success: break
self.obs = in_fh.read() print "trying", kodec
self.index = in_fh.tell() try:
in_fh.close() in_fh = codecs.open(self.in_path, 'r', kodec)
in_fh.seek(self.index)
log.debug("Opened in_path: '%s' with %s" % (self.in_path, kodec))
self.obs = in_fh.read()
self.index = in_fh.tell()
in_fh.close()
success = True
except:
pass
elif(self.filetype == "xml"): elif(self.filetype == "xml"):
try: doc = xml.dom.minidom.parse(filename)
doc = xml.dom.minidom.parse(filename) self.doc = doc
self.doc = doc
except:
traceback.print_exc(file=sys.stderr)
def guessMaxSeats(self, hand): def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH.""" """Return a guess at max_seats when not specified in HH."""
@ -383,7 +456,7 @@ or None if we fail to get the info """
def getStatus(self): def getStatus(self):
#TODO: Return a status of true if file processed ok #TODO: Return a status of true if file processed ok
return True return self.status
def getProcessedHands(self): def getProcessedHands(self):
return self.processedHands return self.processedHands
@ -393,3 +466,15 @@ or None if we fail to get the info """
def getLastCharacterRead(self): def getLastCharacterRead(self):
return self.index return self.index
def isSummary(self, topline):
return " Tournament Summary " in topline
def getParsedObjectType(self):
return self.parsedObjectType
#returns a status (True/False) indicating wether the parsing could be done correctly or not
def readSummaryInfo(self, summaryInfoList): abstract
def getTourney(self):
return self.tourney

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Hud.py """Hud.py
Create and manage the hud overlays. Create and manage the hud overlays.
@ -60,6 +61,8 @@ class Hud:
def __init__(self, parent, table, max, poker_game, config, db_connection): def __init__(self, parent, table, max, poker_game, config, db_connection):
# __init__ is (now) intended to be called from the stdin thread, so it # __init__ is (now) intended to be called from the stdin thread, so it
# cannot touch the gui # cannot touch the gui
if parent == None: # running from cli ..
self.parent = self
self.parent = parent self.parent = parent
self.table = table self.table = table
self.config = config self.config = config
@ -78,6 +81,10 @@ class Hud:
(font, font_size) = config.get_default_font(self.table.site) (font, font_size) = config.get_default_font(self.table.site)
self.colors = config.get_default_colors(self.table.site) self.colors = config.get_default_colors(self.table.site)
self.backgroundcolor = gtk.gdk.color_parse(self.colors['hudbgcolor'])
self.foregroundcolor = gtk.gdk.color_parse(self.colors['hudfgcolor'])
if font == None: if font == None:
font = "Sans" font = "Sans"
if font_size == None: if font_size == None:
@ -94,71 +101,82 @@ class Hud:
continue continue
self.aux_windows.append(my_import(self, config, aux_params)) self.aux_windows.append(my_import(self, config, aux_params))
self.creation_attrs = None
def create_mw(self): def create_mw(self):
# Set up a main window for this this instance of the HUD # Set up a main window for this this instance of the HUD
self.main_window = gtk.Window() win = gtk.Window()
self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) win.set_gravity(gtk.gdk.GRAVITY_STATIC)
self.main_window.set_title("%s FPDBHUD" % (self.table.name)) win.set_title("%s FPDBHUD" % (self.table.name))
self.main_window.set_decorated(False) win.set_decorated(False)
self.main_window.set_opacity(self.colors["hudopacity"]) win.set_opacity(self.colors["hudopacity"])
self.main_window.set_focus_on_map(False)
self.ebox = gtk.EventBox() eventbox = gtk.EventBox()
self.label = gtk.Label("FPDB Menu (Right Click)\nLeft-drag to move") label = gtk.Label("FPDB Menu - Right click\nLeft-Drag to Move")
self.backgroundcolor = gtk.gdk.color_parse(self.colors['hudbgcolor']) win.add(eventbox)
self.foregroundcolor = gtk.gdk.color_parse(self.colors['hudfgcolor']) eventbox.add(label)
self.label.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor) label.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
self.label.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor) label.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
self.main_window.add(self.ebox) eventbox.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
self.ebox.add(self.label) eventbox.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
self.ebox.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
self.ebox.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
self.main_window = win
self.main_window.move(self.table.x, self.table.y) self.main_window.move(self.table.x, self.table.y)
# A popup menu for the main window # A popup menu for the main window
self.menu = gtk.Menu() menu = gtk.Menu()
self.item1 = gtk.MenuItem('Kill this HUD')
self.menu.append(self.item1)
self.item1.connect("activate", self.parent.kill_hud, self.table_name)
self.item1.show()
self.item2 = gtk.MenuItem('Save Layout') killitem = gtk.MenuItem('Kill This HUD')
self.menu.append(self.item2) menu.append(killitem)
self.item2.connect("activate", self.save_layout) if self.parent != None:
self.item2.show() killitem.connect("activate", self.parent.kill_hud, self.table_name)
self.item3 = gtk.MenuItem('Reposition Stats') saveitem = gtk.MenuItem('Save HUD Layout')
self.menu.append(self.item3) menu.append(saveitem)
self.item3.connect("activate", self.reposition_windows) saveitem.connect("activate", self.save_layout)
self.item3.show()
self.item4 = gtk.MenuItem('Debug Stat Windows') repositem = gtk.MenuItem('Reposition StatWindows')
self.menu.append(self.item4) menu.append(repositem)
self.item4.connect("activate", self.debug_stat_windows) repositem.connect("activate", self.reposition_windows)
self.item4.show()
self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) debugitem = gtk.MenuItem('Debug StatWindows')
menu.append(debugitem)
debugitem.connect("activate", self.debug_stat_windows)
item5 = gtk.MenuItem('Set max seats')
menu.append(item5)
maxSeatsMenu = gtk.Menu()
item5.set_submenu(maxSeatsMenu)
for i in range(2, 11, 1):
item = gtk.MenuItem('%d-max' % i)
item.ms = i
maxSeatsMenu.append(item)
item.connect("activate", self.change_max_seats)
setattr(self, 'maxSeatsMenuItem%d' % (i-1), item)
eventbox.connect_object("button-press-event", self.on_button_press, menu)
self.main_window.show_all()
self.mw_created = True self.mw_created = True
self.label = label
menu.show_all()
self.main_window.show_all()
self.topify_window(self.main_window)
# TODO: fold all uses of this type of 'topify' code into a single function, if the differences between the versions don't def change_max_seats(self, widget):
# create adverse effects? if self.max != widget.ms:
print 'change_max_seats', widget.ms
if os.name == 'nt': self.max = widget.ms
self.topify_window(self.main_window) try:
else: self.kill()
self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gets a gdk handle for poker client self.create(*self.creation_attrs)
self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) # gets a gdk handle for the hud table window self.update(self.hand, self.config)
self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) # except Exception, e:
print "Expcetion:",str(e)
self.update_table_position() pass
def update_table_position(self): def update_table_position(self):
if os.name == 'nt': if os.name == 'nt':
@ -166,18 +184,23 @@ class Hud:
self.parent.kill_hud(self, self.table.name) self.parent.kill_hud(self, self.table.name)
return False return False
# anyone know how to do this in unix, or better yet, trap the X11 error that is triggered when executing the get_origin() for a closed window? # anyone know how to do this in unix, or better yet, trap the X11 error that is triggered when executing the get_origin() for a closed window?
if self.table.gdkhandle is not None:
(x, y) = self.table.gdkhandle.get_origin()
if self.table.x != x or self.table.y != y:
self.table.x = x
self.table.y = y
self.main_window.move(x, y)
adj = self.adj_seats(self.hand, self.config)
loc = self.config.get_locations(self.table.site, self.max)
# TODO: is stat_windows getting converted somewhere from a list to a dict, for no good reason?
for i, w in enumerate(self.stat_windows.itervalues()):
(x, y) = loc[adj[i+1]]
w.relocate(x, y)
# While we're at it, fix the positions of mucked cards too
for aux in self.aux_windows:
aux.update_card_positions()
(x, y) = self.main_window.parentgdkhandle.get_origin()
if self.table.x != x or self.table.y != y:
self.table.x = x
self.table.y = y
self.main_window.move(x, y)
adj = self.adj_seats(self.hand, self.config)
loc = self.config.get_locations(self.table.site, self.max)
# TODO: is stat_windows getting converted somewhere from a list to a dict, for no good reason?
for i, w in enumerate(self.stat_windows.itervalues()):
(x, y) = loc[adj[i+1]]
w.relocate(x, y)
return True return True
def on_button_press(self, widget, event): def on_button_press(self, widget, event):
@ -202,6 +225,7 @@ class Hud:
self.aux_windows = [] self.aux_windows = []
def reposition_windows(self, *args): def reposition_windows(self, *args):
self.update_table_position()
for w in self.stat_windows.itervalues(): for w in self.stat_windows.itervalues():
if type(w) == int: if type(w) == int:
# print "in reposition, w =", w # print "in reposition, w =", w
@ -233,7 +257,7 @@ class Hud:
# Need range here, not xrange -> need the actual list # Need range here, not xrange -> need the actual list
adj = range(0, self.max + 1) # default seat adjustments = no adjustment adj = range(0, self.max + 1) # default seat adjustments = no adjustment
# does the user have a fav_seat? # does the user have a fav_seat?
if int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0: if self.table.site != None and int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0:
try: try:
fav_seat = config.supported_sites[self.table.site].layout[self.max].fav_seat fav_seat = config.supported_sites[self.table.site].layout[self.max].fav_seat
actual_seat = self.get_actual_seat(config.supported_sites[self.table.site].screen_name) actual_seat = self.get_actual_seat(config.supported_sites[self.table.site].screen_name)
@ -261,6 +285,8 @@ class Hud:
# #
# this method also manages the creating and destruction of stat # this method also manages the creating and destruction of stat
# windows via calls to the Stat_Window class # windows via calls to the Stat_Window class
self.creation_attrs = hand, config, stat_dict, cards
self.hand = hand self.hand = hand
if not self.mw_created: if not self.mw_created:
self.create_mw() self.create_mw()
@ -333,30 +359,22 @@ class Hud:
Stats.do_tip(window.e_box[r][c], tip) Stats.do_tip(window.e_box[r][c], tip)
def topify_window(self, window): def topify_window(self, window):
# """Set the specified gtk window to stayontop in MS Windows.""" window.set_focus_on_map(False)
# window.set_accept_focus(False)
# def windowEnumerationHandler(hwnd, resultList):
# '''Callback for win32gui.EnumWindows() to generate list of window handles.'''
# resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
# unique_name = 'unique name for finding this window'
# real_name = window.get_title()
# window.set_title(unique_name)
# tl_windows = []
# win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
#
# for w in tl_windows:
# if w[1] == unique_name:
self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number))
# self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0])
self.main_window.gdkhandle = self.main_window.window
self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle)
style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE) if not self.table.gdkhandle:
style |= win32con.WS_CLIPCHILDREN self.table.gdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gtk handle to poker window
win32gui.SetWindowLong(self.table.number, win32con.GWL_EXSTYLE, style) # window.window.reparent(self.table.gdkhandle, 0, 0)
# break # window.map()
# window.window.set_transient_for(self.table.gdkhandle)
# if os.name == "nt":
# print "window.window.handle=",window.window.handle
# oldparent = win32gui.SetParent(window.window.handle, self.table.number)
# print "oldparent=",oldparent
# win32gui.SendMessage(self.table.number, 0x0127) # WM_CHANGEUISTATE
# win32gui.SendMessage(self.table.number, 0x0128) # WM_UPDATEUISTATE
# window.present()
# window.set_title(real_name)
class Stat_Window: class Stat_Window:
@ -366,21 +384,33 @@ class Stat_Window:
# and double-clicks. # and double-clicks.
if event.button == 3: # right button event if event.button == 3: # right button event
self.popups.append(Popup_window(widget, self)) newpopup = Popup_window(self.window, self)
#print "added popup", newpopup
# TODO: how should we go about making sure it doesn't open a dozen popups if you click?
self.popups.append(newpopup)
return True
if event.button == 2: # middle button event if event.button == 2: # middle button event
self.window.hide() self.window.hide()
return True
if event.button == 1: # left button event if event.button == 1: # left button event
# TODO: make position saving save sizes as well? # TODO: make position saving save sizes as well?
self.window.show_all()
if event.state & gtk.gdk.SHIFT_MASK: if event.state & gtk.gdk.SHIFT_MASK:
self.window.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time) self.window.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time)
else: else:
self.window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time) self.window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time)
return True
return False
def noop(self, arga=None, argb=None): # i'm going to try to connect the focus-in and focus-out events here, to see if that fixes any of the focus problems.
return True
def kill_popup(self, popup): def kill_popup(self, popup):
popup.window.destroy() print "remove popup", popup
self.popups.remove(popup) self.popups.remove(popup)
popup.window.destroy()
def kill_popups(self): def kill_popups(self):
map(lambda x: x.window.destroy(), self.popups) map(lambda x: x.window.destroy(), self.popups)
@ -410,7 +440,6 @@ class Stat_Window:
self.window.set_title("%s" % seat) self.window.set_title("%s" % seat)
self.window.set_property("skip-taskbar-hint", True) self.window.set_property("skip-taskbar-hint", True)
self.window.set_transient_for(parent.main_window)
self.window.set_focus_on_map(False) self.window.set_focus_on_map(False)
grid = gtk.Table(rows = game.rows, columns = game.cols, homogeneous = False) grid = gtk.Table(rows = game.rows, columns = game.cols, homogeneous = False)
@ -452,14 +481,36 @@ class Stat_Window:
e_box[r][c].add(self.label[r][c]) e_box[r][c].add(self.label[r][c])
e_box[r][c].connect("button_press_event", self.button_press_cb) e_box[r][c].connect("button_press_event", self.button_press_cb)
e_box[r][c].connect("focus-in-event", self.noop)
e_box[r][c].connect("focus", self.noop)
e_box[r][c].connect("focus-out-event", self.noop)
label[r][c].modify_font(font) label[r][c].modify_font(font)
self.window.set_opacity(parent.colors['hudopacity']) self.window.set_opacity(parent.colors['hudopacity'])
self.window.connect("focus", self.noop)
self.window.connect("focus-in-event", self.noop)
self.window.connect("focus-out-event", self.noop)
self.window.connect("button_press_event", self.button_press_cb)
self.window.set_focus_on_map(False)
self.window.set_accept_focus(False)
self.window.move(self.x, self.y) self.window.move(self.x, self.y)
self.window.realize() # window must be realized before it has a gdkwindow so we can attach it to the table window..
self.topify_window(self.window)
self.window.hide() self.window.hide()
def topify_window(self, window):
window.set_focus_on_map(False)
window.set_accept_focus(False)
if not self.table.gdkhandle:
self.table.gdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gtk handle to poker window
# window.window.reparent(self.table.gdkhandle, 0, 0)
window.window.set_transient_for(self.table.gdkhandle)
# window.present()
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()
@ -475,6 +526,8 @@ class Popup_window:
self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC)
self.window.set_title("popup") self.window.set_title("popup")
self.window.set_property("skip-taskbar-hint", True) self.window.set_property("skip-taskbar-hint", True)
self.window.set_focus_on_map(False)
self.window.set_accept_focus(False)
self.window.set_transient_for(parent.get_toplevel()) self.window.set_transient_for(parent.get_toplevel())
self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
@ -540,9 +593,6 @@ class Popup_window:
self.window.set_transient_for(stat_window.window) self.window.set_transient_for(stat_window.window)
if os.name == 'nt':
self.topify_window(self.window)
def button_press_cb(self, widget, event, *args): def button_press_cb(self, widget, event, *args):
# This handles all callbacks from button presses on the event boxes in # This handles all callbacks from button presses on the event boxes in
# the popup windows. There is a bit of an ugly kludge to separate single- # the popup windows. There is a bit of an ugly kludge to separate single-
@ -555,7 +605,9 @@ class Popup_window:
if event.button == 3: # right button event if event.button == 3: # right button event
self.stat_window.kill_popup(self) self.stat_window.kill_popup(self)
return True
# self.window.destroy() # self.window.destroy()
return False
def toggle_decorated(self, widget): def toggle_decorated(self, widget):
top = widget.get_toplevel() top = widget.get_toplevel()
@ -569,27 +621,15 @@ class Popup_window:
top.move(x, y) top.move(x, y)
def topify_window(self, window): def topify_window(self, window):
"""Set the specified gtk window to stayontop in MS Windows.""" window.set_focus_on_map(False)
window.set_accept_focus(False)
# def windowEnumerationHandler(hwnd, resultList): if not self.table.gdkhandle:
# '''Callback for win32gui.EnumWindows() to generate list of window handles.''' self.table.gdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gtk handle to poker window
# resultList.append((hwnd, win32gui.GetWindowText(hwnd))) # window.window.reparent(self.table.gdkhandle, 0, 0)
window.window.set_transient_for(self.table.gdkhandle)
# window.present()
# unique_name = 'unique name for finding this window'
# real_name = window.get_title()
# window.set_title(unique_name)
# tl_windows = []
# win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
# for w in tl_windows:
# if w[1] == unique_name:
window.set_transient_for(self.parent.main_window)
style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE)
style |= win32con.WS_CLIPCHILDREN
win32gui.SetWindowLong(self.table.number, win32con.GWL_EXSTYLE, style)
# break
# window.set_title(real_name)
if __name__== "__main__": if __name__== "__main__":
main_window = gtk.Window() main_window = gtk.Window()
@ -605,10 +645,12 @@ if __name__== "__main__":
print "Table not found." print "Table not found."
db = Database.Database(c, 'fpdb', 'holdem') db = Database.Database(c, 'fpdb', 'holdem')
stat_dict = db.get_stats_from_hand(1)
# for t in tables: # for t in tables:
win = Hud(t, 10, 'holdem', c, db) win = Hud(None, t, 10, 'holdem', c, db) # parent, table, max, poker_game, config, db_connection
win.create(1, c) win.create(1, c, stat_dict, None) # hand, config, stat_dict, cards):
# t.get_details() # t.get_details()
win.update(8300, db, c) win.update(8300, c) # self, hand, config):
gtk.main() gtk.main()

View File

@ -345,6 +345,22 @@ class Aux_Seats(Aux_Window):
def create_contents(self): pass def create_contents(self): pass
def update_contents(self): pass def update_contents(self): pass
def update_card_positions(self):
# self.adj does not exist until .create() has been run
try:
adj = self.adj
except AttributeError:
return
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
for i in (range(1, self.hud.max + 1) + ['common']):
if i == 'common':
(x, y) = self.params['layout'][self.hud.max].common
else:
(x, y) = loc[adj[i]]
self.positions[i] = self.card_positions(x, self.hud.table.x, y, self.hud.table.y)
self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
def create(self): def create(self):
self.adj = self.hud.adj_seats(0, self.config) # move adj_seats to aux and get rid of it in Hud.py self.adj = self.hud.adj_seats(0, self.config) # move adj_seats to aux and get rid of it in Hud.py
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max)) loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
@ -362,7 +378,7 @@ class Aux_Seats(Aux_Window):
self.m_windows[i].set_transient_for(self.hud.main_window) self.m_windows[i].set_transient_for(self.hud.main_window)
self.m_windows[i].set_focus_on_map(False) self.m_windows[i].set_focus_on_map(False)
self.m_windows[i].connect("configure_event", self.configure_event_cb, i) self.m_windows[i].connect("configure_event", self.configure_event_cb, i)
self.positions[i] = (int(x) + self.hud.table.x, int(y) + self.hud.table.y) self.positions[i] = self.card_positions(x, self.hud.table.x, y, self.hud.table.y)
self.m_windows[i].move(self.positions[i][0], self.positions[i][1]) self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
if self.params.has_key('opacity'): if self.params.has_key('opacity'):
self.m_windows[i].set_opacity(float(self.params['opacity'])) self.m_windows[i].set_opacity(float(self.params['opacity']))
@ -374,6 +390,13 @@ class Aux_Seats(Aux_Window):
if self.uses_timer: if self.uses_timer:
self.m_windows[i].hide() self.m_windows[i].hide()
def card_positions(self, x, table_x, y, table_y):
_x = int(x) + int(table_x)
_y = int(y) + int(table_y)
return (_x, _y)
def update_gui(self, new_hand_id): def update_gui(self, new_hand_id):
"""Update the gui, LDO.""" """Update the gui, LDO."""
for i in self.m_windows.keys(): for i in self.m_windows.keys():

342
pyfpdb/PartyPokerToFpdb.py Normal file → Executable file
View File

@ -21,43 +21,48 @@
import sys import sys
from collections import defaultdict from collections import defaultdict
from Exceptions import FpdbParseError
from HandHistoryConverter import * from HandHistoryConverter import *
# PartyPoker HH Format # PartyPoker HH Format
class PartyPokerParseError(FpdbParseError):
"Usage: raise PartyPokerParseError(<msg>[, hh=<hh>][, hid=<hid>])"
def __init__(self, msg='', hh=None, hid=None):
if hh is not None:
msg += "\n\nHand history attached below:\n" + self.wrapHh(hh)
return super(PartyPokerParseError, self).__init__(hid=hid)
#return super(PartyPokerParseError, self).__init__(msg, hid=hid)
def wrapHh(self, hh):
return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
{'DELIMETER': '#'*50, 'HH': hh}
class PartyPoker(HandHistoryConverter): class PartyPoker(HandHistoryConverter):
class ParsingException(Exception):
"Usage: raise ParsingException(<msg>[, hh=<hh>])"
def __init__(self, *args, **kwargs):
if len(args)==0: args=[''] + list(args)
msg, args = args[0], args[1:]
if 'hh' in kwargs:
msg += self.wrapHh(kwargs['hh'])
del kwargs['hh']
return Exception.__init__(self, msg, *args, **kwargs)
def wrapHh(self, hh):
return ("\n\nHand history attached below:\n"
"%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
{'DELIMETER': '#'*50, 'HH': hh}
############################################################ ############################################################
# Class Variables # Class Variables
sitename = "PartyPoker"
codepage = "cp1252"
siteId = 9 # TODO: automate; it's a class variable so shouldn't hit DB too often
filetype = "text" # "text" or "xml". I propose we subclass HHC to HHC_Text and HHC_XML.
sym = {'USD': "\$", } sym = {'USD': "\$", }
# Static regexes # Static regexes
# $5 USD NL Texas Hold'em - Saturday, July 25, 07:53:52 EDT 2009 # $5 USD NL Texas Hold'em - Saturday, July 25, 07:53:52 EDT 2009
# NL Texas Hold'em $1 USD Buy-in Trny:45685440 Level:8 Blinds-Antes(600/1 200 -50) - Sunday, May 17, 11:25:07 MSKS 2009 # NL Texas Hold'em $1 USD Buy-in Trny:45685440 Level:8 Blinds-Antes(600/1 200 -50) - Sunday, May 17, 11:25:07 MSKS 2009
re_GameInfoRing = re.compile(""" re_GameInfoRing = re.compile("""
(?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>\d+)\s*(?:USD)?\s* (?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>[0-9,]+)\s*(?:USD)?\s*
(?P<LIMIT>(NL))\s+ (?P<LIMIT>(NL|PL|))\s+
(?P<GAME>(Texas\ Hold\'em)) (?P<GAME>(Texas\ Hold\'em|Omaha))
\s*\-\s* \s*\-\s*
(?P<DATETIME>.+) (?P<DATETIME>.+)
""", re.VERBOSE) """, re.VERBOSE)
re_GameInfoTrny = re.compile(""" re_GameInfoTrny = re.compile("""
(?P<LIMIT>(NL))\s+ (?P<LIMIT>(NL|PL|))\s+
(?P<GAME>(Texas\ Hold\'em))\s+ (?P<GAME>(Texas\ Hold\'em|Omaha))\s+
(?P<BUYIN>\$?[.0-9]+)\s*(?P<BUYIN_CURRENCY>USD)?\s*Buy-in\s+ (?P<BUYIN>\$?[.0-9]+)\s*(?P<BUYIN_CURRENCY>USD)?\s*Buy-in\s+
Trny:\s?(?P<TOURNO>\d+)\s+ Trny:\s?(?P<TOURNO>\d+)\s+
Level:\s*(?P<LEVEL>\d+)\s+ Level:\s*(?P<LEVEL>\d+)\s+
@ -70,25 +75,6 @@ class PartyPoker(HandHistoryConverter):
(?P<DATETIME>.+) (?P<DATETIME>.+)
""", re.VERBOSE) """, re.VERBOSE)
re_Hid = re.compile("^Game \#(?P<HID>\d+) starts.") re_Hid = re.compile("^Game \#(?P<HID>\d+) starts.")
#re_GameInfo = re.compile("""
#PartyPoker\sGame\s\#(?P<HID>[0-9]+):\s+
#(Tournament\s\# # open paren of tournament info
#(?P<TOURNO>\d+),\s
#(?P<BUYIN>[%(LS)s\+\d\.]+ # here's how I plan to use LS
#\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?
#)\s)? # close paren of tournament info
#(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(?
#(?P<GAME>Hold\'em|Razz|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball)\s
#(?P<LIMIT>No\sLimit|Limit|Pot\sLimit)\)?,?\s
#(-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?
#\(? # open paren of the stakes
#(?P<CURRENCY>%(LS)s|)?
#(?P<SB>[.0-9]+)/(%(LS)s)?
#(?P<BB>[.0-9]+)
#\s?(?P<ISO>%(LEGAL_ISO)s)?
#\)\s-\s # close paren of the stakes
#(?P<DATETIME>.*$)""" % substitutions,
#re.MULTILINE|re.VERBOSE)
re_PlayerInfo = re.compile(""" re_PlayerInfo = re.compile("""
Seat\s(?P<SEAT>\d+):\s Seat\s(?P<SEAT>\d+):\s
@ -96,11 +82,6 @@ class PartyPoker(HandHistoryConverter):
\(\s*\$?(?P<CASH>[0-9,.]+)\s*(?:USD|)\s*\) \(\s*\$?(?P<CASH>[0-9,.]+)\s*(?:USD|)\s*\)
""" , """ ,
re.VERBOSE) re.VERBOSE)
#re_PlayerInfo = re.compile("""
#^Seat\s(?P<SEAT>[0-9]+):\s
#(?P<PNAME>.*)\s
#\((%(LS)s)?(?P<CASH>[.0-9]+)\sin\schips\)""" % substitutions,
#re.MULTILINE|re.VERBOSE)
re_HandInfo = re.compile(""" re_HandInfo = re.compile("""
^Table\s+ ^Table\s+
@ -111,69 +92,60 @@ class PartyPoker(HandHistoryConverter):
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
""", """,
re.MULTILINE|re.VERBOSE) re.MULTILINE|re.VERBOSE)
#re_HandInfo = re.compile("""
#^Table\s\'(?P<TABLE>[-\ a-zA-Z\d]+)\'\s
#((?P<MAX>\d+)-max\s)?
#(?P<PLAY>\(Play\sMoney\)\s)?
#(Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
#re.MULTILINE|re.VERBOSE)
re_TotalPlayers = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<MAXSEATS>\d+)", re.MULTILINE)
re_SplitHands = re.compile('\x00+') re_SplitHands = re.compile('\x00+')
re_TailSplitHands = re.compile('(\x00+)') re_TailSplitHands = re.compile('(\x00+)')
lineSplitter = '\n' lineSplitter = '\n'
re_Button = re.compile('Seat (?P<BUTTON>\d+) is the button', re.MULTILINE) re_Button = re.compile('Seat (?P<BUTTON>\d+) is the button', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
re_NoSmallBlind = re.compile('^There is no Small Blind in this hand as the Big Blind of the previous hand left the table') re_NoSmallBlind = re.compile(
# 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]+)') '^There is no Small Blind in this hand as the Big Blind '
'of the previous hand left the table', re.MULTILINE)
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
"""\
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="PartyPoker", follow=follow, index=index)
logging.info("Initialising PartyPoker converter class")
self.filetype = "text"
self.codepage = "cp1252" # FIXME: wtf?
self.siteId = 9 # Needs to match id entry in Sites database
self._gameType = None # cached reg-parse result
if autostart:
self.start()
def allHandsAsList(self): def allHandsAsList(self):
list = HandHistoryConverter.allHandsAsList(self) list = HandHistoryConverter.allHandsAsList(self)
if list is None:
return []
return filter(lambda text: len(text.strip()), list) return filter(lambda text: len(text.strip()), list)
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand)
if mo == 10: return mo
if mo == 2: return 2
if mo <= 6: return 6
# there are 9-max tables for cash and 10-max for tournaments
return 9 if hand.gametype['type']=='ring' else 10
def compilePlayerRegexs(self, hand): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players]) 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.
# TODO: should probably rename re_HeroCards and corresponding method,
# since they are used to find all cards on lines starting with "Dealt to:"
# They still identify the hero.
self.compiledPlayers = players self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
subst = {'PLYR': player_re, 'CUR_SYM': hand.SYMBOL[hand.gametype['currency']], subst = {'PLYR': player_re, 'CUR_SYM': hand.SYMBOL[hand.gametype['currency']],
'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''} 'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''}
logging.debug("player_re: " + subst['PLYR']) for key in ('CUR_SYM', 'CUR'):
logging.debug("CUR_SYM: " + subst['CUR_SYM']) subst[key] = re.escape(subst[key])
logging.debug("CUR: " + subst['CUR']) log.debug("player_re: '%s'" % subst['PLYR'])
log.debug("CUR_SYM: '%s'" % subst['CUR_SYM'])
log.debug("CUR: '%s'" % subst['CUR'])
self.re_PostSB = re.compile( self.re_PostSB = re.compile(
r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.0-9]+) ?%(CUR)s\]\." % subst, r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.0-9]+) ?%(CUR)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
self.re_PostBB = re.compile( self.re_PostBB = re.compile(
r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.0-9]+) ?%(CUR)s\]\." % subst, r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.0-9]+) ?%(CUR)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
self.re_Antes = re.compile( # NOTE: comma is used as a fraction part delimeter in re below
r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.0-9]+) ?%(CUR)s\]\." % subst, self.re_PostDead = re.compile(
r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst,
re.MULTILINE)
self.re_Antes = re.compile(
r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.0-9]+) ?%(CUR)s\]" % subst,
re.MULTILINE) re.MULTILINE)
#self.re_BringIn = re.compile(
#r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[.0-9]+)" % subst,
#re.MULTILINE)
#self.re_PostBoth = re.compile(
#r"^%(PLYR)s: posts small \& big blinds \[%(CUR)s (?P<SBBB>[.0-9]+)" % subst,
#re.MULTILINE)
self.re_HeroCards = re.compile( self.re_HeroCards = re.compile(
r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst, r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst,
re.MULTILINE) re.MULTILINE)
@ -187,32 +159,27 @@ follow : whether to tail -f the input"""
r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.", r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.",
re.MULTILINE) re.MULTILINE)
self.re_CollectPot = re.compile( self.re_CollectPot = re.compile(
r""""^%(PLYR)s \s+ wins \s+ r"""^%(PLYR)s \s+ wins \s+
%(CUR_SYM)s(?P<POT>[.\d]+)\s*%(CUR)s""" % subst, %(CUR_SYM)s(?P<POT>[.,\d]+)\s*%(CUR)s""" % subst,
re.MULTILINE|re.VERBOSE) re.MULTILINE|re.VERBOSE)
#self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
#self.re_ShownCards = re.compile(
#"^Seat (?P<SEAT>[0-9]+): %s (\(.*\) )?
#(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re,
#re.MULTILINE)
def readSupportedGames(self): def readSupportedGames(self):
return [["ring", "hold", "nl"], return [["ring", "hold", "nl"],
#["ring", "hold", "pl"], ["ring", "hold", "pl"],
#["ring", "hold", "fl"], ["ring", "hold", "fl"],
["tour", "hold", "nl"], ["tour", "hold", "nl"],
#["tour", "hold", "pl"], ["tour", "hold", "pl"],
#["tour", "hold", "fl"], ["tour", "hold", "fl"],
] ]
def _getGameType(self, handText): def _getGameType(self, handText):
if not hasattr(self, '_gameType'):
self._gameType = None
if self._gameType is None: if self._gameType is None:
# let's determine whether hand is trny # let's determine whether hand is trny
# and whether 5-th line contains head line # and whether 5-th line contains head line
headLine = handText.split(self.lineSplitter)[4] headLine = handText.split(self.lineSplitter)[4]
#print headLine
#sys.exit(1)
for headLineContainer in headLine, handText: for headLineContainer in headLine, handText:
for regexp in self.re_GameInfoTrny, self.re_GameInfoRing: for regexp in self.re_GameInfoTrny, self.re_GameInfoRing:
m = regexp.search(headLineContainer) m = regexp.search(headLineContainer)
@ -222,45 +189,43 @@ follow : whether to tail -f the input"""
return self._gameType return self._gameType
def determineGameType(self, handText): def determineGameType(self, handText):
# inspect the handText and return the gametype dict """inspect the handText and return the gametype dict
# gametype dict is:
# {'limitType': xxx, 'base': xxx, 'category': xxx}
print self.ParsingException().wrapHh( handText ) gametype dict is:
{'limitType': xxx, 'base': xxx, 'category': xxx}"""
log.debug(PartyPokerParseError().wrapHh( handText ))
info = {} info = {}
m = self._getGameType(handText) m = self._getGameType(handText)
if m is None: if m is None:
return None return None
mg = m.groupdict() mg = m.groupdict()
# translations from captured groups to fpdb info strings # translations from captured groups to fpdb info strings
limits = { 'NL':'nl', limits = { 'NL':'nl', 'PL':'pl', '':'fl' }
# 'Pot Limit':'pl', 'Limit':'fl'
}
games = { # base, category games = { # base, category
"Texas Hold'em" : ('hold','holdem'), "Texas Hold'em" : ('hold','holdem'),
#'Omaha' : ('hold','omahahi'), 'Omaha' : ('hold','omahahi'),
} }
currencies = { '$':'USD', '':'T$' } currencies = { '$':'USD', '':'T$' }
for expectedField in ['LIMIT', 'GAME']: for expectedField in ['LIMIT', 'GAME']:
if mg[expectedField] is None: if mg[expectedField] is None:
raise self.ParsingException( raise PartyPokerParseError(
"Cannot fetch field '%s'" % expectedField, "Cannot fetch field '%s'" % expectedField,
hh = handText) hh = handText)
try: try:
info['limitType'] = limits[mg['LIMIT']] info['limitType'] = limits[mg['LIMIT'].strip()]
except: except:
raise self.ParsingException( raise PartyPokerParseError(
"Unknown limit '%s'" % mg['LIMIT'], "Unknown limit '%s'" % mg['LIMIT'],
hh = handText) hh = handText)
try: try:
(info['base'], info['category']) = games[mg['GAME']] (info['base'], info['category']) = games[mg['GAME']]
except: except:
raise self.ParsingException( raise PartyPokerParseError(
"Unknown game type '%s'" % mg['GAME'], "Unknown game type '%s'" % mg['GAME'],
hh = handText) hh = handText)
@ -273,35 +238,61 @@ follow : whether to tail -f the input"""
if info['type'] == 'ring': if info['type'] == 'ring':
info['sb'], info['bb'] = ringBlinds(mg['RINGLIMIT']) info['sb'], info['bb'] = ringBlinds(mg['RINGLIMIT'])
# FIXME: there are only $ and play money availible for cash # FIXME: there are only $ and play money availible for cash
info['currency'] = currencies(mg['CURRENCY']) # to be honest, party doesn't save play money hh
info['currency'] = currencies[mg['CURRENCY']]
else: else:
info['sb'] = renderTrnyMoney(mg['SB']) info['sb'] = clearMoneyString(mg['SB'])
info['bb'] = renderTrnyMoney(mg['BB']) info['bb'] = clearMoneyString(mg['BB'])
info['currency'] = 'T$' info['currency'] = 'T$'
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info return info
def readHandInfo(self, hand): def readHandInfo(self, hand):
info = {} info = {}
m = self.re_HandInfo.search(hand.handText,re.DOTALL) try:
if m: info.update(self.re_Hid.search(hand.handText).groupdict())
info.update(m.groupdict()) except:
else: raise PartyPokerParseError("Cannot read HID for current hand", hh=hand.handText)
raise self.ParsingException("Cannot read Handinfo for current hand", hh=hand.handText)
m = self._getGameType(hand.handText) try:
if m: info.update(m.groupdict()) info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict())
m = self.re_Hid.search(hand.handText) except:
raise PartyPokerParseError("Cannot read Handinfo for current hand",
hh=hand.handText, hid = info['HID'])
try:
info.update(self._getGameType(hand.handText).groupdict())
except:
raise PartyPokerParseError("Cannot read GameType for current hand",
hh=hand.handText, hid = info['HID'])
m = self.re_TotalPlayers.search(hand.handText)
if m: info.update(m.groupdict()) if m: info.update(m.groupdict())
# FIXME: it's a hack cause party doesn't supply hand.maxseats info
#hand.maxseats = '9'
hand.mixed = None
# TODO : I rather like the idea of just having this dict as hand.info # FIXME: it's dirty hack
logging.debug("readHandInfo: %s" % info) # party doesnt subtract uncalled money from commited money
# so hand.totalPot calculation has to be redefined
from Hand import Pot, HoldemOmahaHand
def getNewTotalPot(origTotalPot):
def totalPot(self):
if self.totalpot is None:
self.pot.end()
self.totalpot = self.pot.total
for i,v in enumerate(self.collected):
if v[0] in self.pot.returned:
self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
return origTotalPot()
return totalPot
instancemethod = type(hand.totalPot)
hand.totalPot = instancemethod(getNewTotalPot(hand.totalPot), hand, HoldemOmahaHand)
log.debug("readHandInfo: %s" % info)
for key in info: for key in info:
if key == 'DATETIME': if key == 'DATETIME':
#Saturday, July 25, 07:53:52 EDT 2009 #Saturday, July 25, 07:53:52 EDT 2009
@ -313,6 +304,7 @@ follow : whether to tail -f the input"""
month = months.index(m2.group('M')) + 1 month = months.index(m2.group('M')) + 1
datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S')) datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
# FIXME: some timezone correction required
#tzShift = defaultdict(lambda:0, {'EDT': -5, 'EST': -6, 'MSKS': 3}) #tzShift = defaultdict(lambda:0, {'EDT': -5, 'EST': -6, 'MSKS': 3})
#hand.starttime -= datetime.timedelta(hours=tzShift[m2.group('TZ')]) #hand.starttime -= datetime.timedelta(hours=tzShift[m2.group('TZ')])
@ -325,14 +317,14 @@ follow : whether to tail -f the input"""
if key == 'TOURNO': if key == 'TOURNO':
hand.tourNo = info[key] hand.tourNo = info[key]
if key == 'BUYIN': if key == 'BUYIN':
#FIXME: it's dirty hack T_T # FIXME: it's dirty hack T_T
# code below assumes that rake is equal to zero
cur = info[key][0] if info[key][0] not in '0123456789' else '' cur = info[key][0] if info[key][0] not in '0123456789' else ''
hand.buyin = info[key] + '+%s0' % cur hand.buyin = info[key] + '+%s0' % cur
if key == 'LEVEL': if key == 'LEVEL':
hand.level = info[key] hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != 'Real': if key == 'PLAY' and info['PLAY'] != 'Real':
# TODO: play money wasn't tested # if realy there's no play money hh on party
# hand.currency = 'play' # overrides previously set value
hand.gametype['currency'] = 'play' hand.gametype['currency'] = 'play'
def readButton(self, hand): def readButton(self, hand):
@ -340,21 +332,17 @@ follow : whether to tail -f the input"""
if m: if m:
hand.buttonpos = int(m.group('BUTTON')) hand.buttonpos = int(m.group('BUTTON'))
else: else:
logging.info('readButton: not found') log.info('readButton: not found')
def readPlayerStacks(self, hand): def readPlayerStacks(self, hand):
logging.debug("readPlayerStacks") log.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText) 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'), hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'),
renderTrnyMoney(a.group('CASH'))) clearMoneyString(a.group('CASH')))
def markStreets(self, hand): 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.
assert hand.gametype['base'] == "hold", \
"wtf! There're no %s games on party" % hand.gametype['base']
m = re.search( m = re.search(
r"\*{2} Dealing down cards \*{2}" r"\*{2} Dealing down cards \*{2}"
r"(?P<PREFLOP>.+?)" r"(?P<PREFLOP>.+?)"
@ -370,18 +358,11 @@ follow : whether to tail -f the input"""
hand.setCommunityCards(street, renderCards(m.group('CARDS'))) hand.setCommunityCards(street, renderCards(m.group('CARDS')))
def readAntes(self, hand): def readAntes(self, hand):
logging.debug("reading antes") log.debug("reading antes")
m = self.re_Antes.finditer(hand.handText) m = self.re_Antes.finditer(hand.handText)
for player in m: for player in m:
#~ 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):
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): def readBlinds(self, hand):
noSmallBlind = bool(self.re_NoSmallBlind.search(hand.handText)) noSmallBlind = bool(self.re_NoSmallBlind.search(hand.handText))
if hand.gametype['type'] == 'ring': if hand.gametype['type'] == 'ring':
@ -394,6 +375,10 @@ follow : whether to tail -f the input"""
for a in self.re_PostBB.finditer(hand.handText): 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'))
deadFilter = lambda s: s.replace(',', '.')
for a in self.re_PostDead.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'both', deadFilter(a.group('BBNDEAD')))
else: else:
# party doesn't track blinds for tournaments # party doesn't track blinds for tournaments
# so there're some cra^Wcaclulations # so there're some cra^Wcaclulations
@ -414,6 +399,7 @@ follow : whether to tail -f the input"""
if noSmallBlind: if noSmallBlind:
hand.addBlind(None, None, None) hand.addBlind(None, None, None)
smallBlindSeat = int(hand.buttonpos)
else: else:
smallBlindSeat = findFirstNonEmptySeat(int(hand.buttonpos) + 1) smallBlindSeat = findFirstNonEmptySeat(int(hand.buttonpos) + 1)
blind = smartMin(hand.sb, playersMap[smallBlindSeat][1]) blind = smartMin(hand.sb, playersMap[smallBlindSeat][1])
@ -421,20 +407,16 @@ follow : whether to tail -f the input"""
bigBlindSeat = findFirstNonEmptySeat(smallBlindSeat + 1) bigBlindSeat = findFirstNonEmptySeat(smallBlindSeat + 1)
blind = smartMin(hand.bb, playersMap[bigBlindSeat][1]) blind = smartMin(hand.bb, playersMap[bigBlindSeat][1])
hand.addBlind(playersMap[bigBlindSeat][0], 'small blind', blind) hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind)
def readHeroCards(self, hand): def readHeroCards(self, hand):
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause # we need to grab hero's cards
# we need to grab hero's cards
for street in ('PREFLOP',): for street in ('PREFLOP',):
if street in hand.streets.keys(): if street in hand.streets.keys():
m = self.re_HeroCards.finditer(hand.streets[street]) m = self.re_HeroCards.finditer(hand.streets[street])
for found in m: for found in m:
# if m == None:
# hand.involved = False
# else:
hand.hero = found.group('PNAME') hand.hero = found.group('PNAME')
newcards = renderCards(found.group('NEWCARDS')) newcards = renderCards(found.group('NEWCARDS'))
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
@ -444,35 +426,48 @@ follow : whether to tail -f the input"""
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
acts = action.groupdict() acts = action.groupdict()
if action.group('ATYPE') in ('raises','is all-In'): playerName = action.group('PNAME')
#print action.groupdict() amount = clearMoneyString(action.group('BET')) if action.group('BET') else None
#sys.exit(1) actionType = action.group('ATYPE')
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'calls': if actionType == 'is all-In':
hand.addCall( street, action.group('PNAME'), action.group('BET') ) # party's allin can mean either raise or bet or call
#print action.groupdict() Bp = hand.lastBet[street]
#sys.exit(1) if Bp == 0:
elif action.group('ATYPE') == 'bets': actionType = 'bets'
hand.addBet( street, action.group('PNAME'), action.group('BET') ) elif Bp < Decimal(amount):
elif action.group('ATYPE') == 'folds': actionType = 'raises'
hand.addFold( street, action.group('PNAME')) else:
elif action.group('ATYPE') == 'checks': actionType = 'calls'
hand.addCheck( street, action.group('PNAME'))
if actionType == 'raises':
if street == 'PREFLOP' and \
playerName in [item[0] for item in hand.actions['BLINDSANTES'] if item[2]!='ante']:
# preflop raise from blind
hand.addRaiseBy( street, playerName, amount )
else:
hand.addCallandRaise( street, playerName, amount )
elif actionType == 'calls':
hand.addCall( street, playerName, amount )
elif actionType == 'bets':
hand.addBet( street, playerName, amount )
elif actionType == 'folds':
hand.addFold( street, playerName )
elif actionType == 'checks':
hand.addCheck( street, playerName )
else: else:
print "DEBUG: unimplemented readAction: '%s' '%s'" %(action.group('PNAME'),action.group('ATYPE'),) raise PartyPokerParseError(
"Unimplemented readAction: '%s' '%s'" % (playerName,actionType,),
hid = hand.hid, hh = hand.handText )
def readShowdownActions(self, hand): def readShowdownActions(self, hand):
# all action in readShownCards # all action in readShownCards
pass pass
## TODO: pick up mucks also??
#for shows in self.re_ShowdownAction.finditer(hand.handText):
#cards = shows.group('CARDS').split(' ')
#hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand): def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.handText): 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=clearMoneyString(m.group('POT')))
def readShownCards(self,hand): def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText): for m in self.re_ShownCards.finditer(hand.handText):
@ -486,24 +481,24 @@ follow : whether to tail -f the input"""
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked) hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked)
def ringBlinds(ringLimit): def ringBlinds(ringLimit):
"Returns blinds for current limit" "Returns blinds for current limit in cash games"
ringLimit = float(ringLimit) ringLimit = float(clearMoneyString(ringLimit))
if ringLimit == 5.: ringLimit = 4. if ringLimit == 5.: ringLimit = 4.
return ('%.2f' % (ringLimit/200.), '%.2f' % (ringLimit/100.) ) return ('%.2f' % (ringLimit/200.), '%.2f' % (ringLimit/100.) )
def renderTrnyMoney(money): def clearMoneyString(money):
"renders 'numbers' like '1 200' and '2,000'" "Renders 'numbers' like '1 200' and '2,000'"
return money.replace(' ', '').replace(',', '') return money.replace(' ', '').replace(',', '')
def renderCards(string): def renderCards(string):
"splits strings like ' Js, 4d '" "Splits strings like ' Js, 4d '"
cards = string.strip().split(' ') cards = string.strip().split(' ')
return filter(len, map(lambda x: x.strip(' ,'), cards)) return filter(len, map(lambda x: x.strip(' ,'), cards))
if __name__ == "__main__": if __name__ == "__main__":
parser = OptionParser() parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/stars/horse/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt") parser.add_option("-i", "--input", dest="ipath", help="parse input hand history")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-") 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("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet", parser.add_option("-q", "--quiet",
@ -515,7 +510,4 @@ if __name__ == "__main__":
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
#LOG_FILENAME = './logging.out'
logging.basicConfig(level=options.verbosity)
e = PartyPoker(in_path = options.ipath, out_path = options.opath, follow = options.follow) e = PartyPoker(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -19,6 +19,7 @@
######################################################################## ########################################################################
# TODO: straighten out discards for draw games # TODO: straighten out discards for draw games
import sys import sys
from HandHistoryConverter import * from HandHistoryConverter import *
@ -26,13 +27,17 @@ from HandHistoryConverter import *
class PokerStars(HandHistoryConverter): class PokerStars(HandHistoryConverter):
############################################################ # Class Variables
# Class Variables
sitename = "PokerStars"
filetype = "text"
codepage = "cp1252"
siteId = 2 # Needs to match id entry in Sites database
mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} # Legal mixed games mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} # Legal mixed games
sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\x80", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\x80", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE
substitutions = { substitutions = {
'LEGAL_ISO' : "USD|EUR|GBP|CAD", # legal ISO currency codes 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
'LS' : "\$|\x80|\xa3" # legal currency symbols ADD Euro, Sterling, etc HERE 'LS' : "\$|\x80|\xa3" # legal currency symbols ADD Euro, Sterling, etc HERE
} }
@ -77,20 +82,6 @@ class PokerStars(HandHistoryConverter):
# 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]+)') # 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, index=0):
"""\
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, index=index)
logging.info("Initialising PokerStars converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 2 # Needs to match id entry in Sites database
if autostart:
self.start()
def compilePlayerRegexs(self, hand): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players]) 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'
@ -388,7 +379,4 @@ if __name__ == "__main__":
(options, args) = parser.parse_args() (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) e = PokerStars(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -1,9 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
"""SQL.py """Returns a dict of SQL statements used in fpdb.
Set up all of the SQL statements for a given game and database type.
""" """
# Copyright 2008, Ray E. Barker # Copyright 2008-2009, Ray E. Barker
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -35,134 +33,8 @@ import re
class Sql: class Sql:
def __init__(self, game = 'holdem', type = 'PT3', db_server = 'mysql'): def __init__(self, game = 'holdem', type = 'fpdb', db_server = 'mysql'):
self.query = {} self.query = {}
############################################################################
#
# Support for the ptracks database, a cut down PT2 stud database.
# You can safely ignore this unless you are me.
#
if game == 'razz' and type == 'ptracks':
self.query['get_table_name'] = "select table_name from game where game_id = %s"
self.query['get_last_hand'] = "select max(game_id) from game"
self.query['get_recent_hands'] = "select game_id from game where game_id > %(last_hand)d"
self.query['get_xml'] = "select xml from hand_history where game_id = %s"
self.query['get_player_id'] = """
select player_id from players
where screen_name = %(player)s
"""
self.query['get_hand_info'] = """
SELECT
game_id,
CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand,
total_won-total_bet AS net
FROM game_players
WHERE game_id = %s AND player_id = 3
"""
self.query['get_cards'] = """
select
seat_number,
screen_name,
hole_card_1,
hole_card_2,
hole_card_3,
hole_card_4,
hole_card_5,
hole_card_6,
hole_card_7
from game_players, players
where game_id = %s and game_players.player_id = players.player_id
order by seat_number
"""
self.query['get_stats_from_hand'] = """
SELECT player_id,
count(*) AS n,
sum(pre_fourth_raise_n) AS pfr,
sum(fourth_raise_n) AS raise_n_2,
sum(fourth_ck_raise_n) AS cr_n_2,
sum(fifth_bet_raise_n) AS br_n_3,
sum(fifth_bet_ck_raise_n) AS cr_n_3,
sum(sixth_bet_raise_n) AS br_n_4,
sum(sixth_bet_ck_raise_n) AS cr_n_4,
sum(river_bet_raise_n) AS br_n_5,
sum(river_bet_ck_raise_n) AS cr_n_5,
sum(went_to_showdown_n) AS sd,
sum(saw_fourth_n) AS saw_f,
sum(raised_first_pf) AS first_pfr,
sum(vol_put_money_in_pot) AS vpip,
sum(limp_with_prev_callers) AS limp_w_callers,
sum(ppossible_actions) AS poss_a_pf,
sum(pfold) AS fold_pf,
sum(pcheck) AS check_pf,
sum(praise) AS raise_pf,
sum(pcall) AS raise_pf,
sum(limp_call_reraise_pf) AS limp_call_pf,
sum(pfr_check) AS check_after_raise,
sum(pfr_call) AS call_after_raise,
sum(pfr_fold) AS fold_after_raise,
sum(pfr_bet) AS bet_after_raise,
sum(pfr_raise) AS raise_after_raise,
sum(folded_to_river_bet) AS fold_to_r_bet,
sum(fpossible_actions) AS poss_a_2,
sum(ffold) AS fold_2,
sum(fcheck) AS check_2,
sum(fbet) AS bet_2,
sum(fraise) AS raise_2,
sum(fcall) AS raise_2,
sum(fifpossible_actions) AS poss_a_3,
sum(fiffold) AS fold_3,
sum(fifcheck) AS check_3,
sum(fifbet) AS bet_3,
sum(fifraise) AS raise_3,
sum(fifcall) AS call_3,
sum(spossible_actions) AS poss_a_4,
sum(sfold) AS fold_4,
sum(scheck) AS check_4,
sum(sbet) AS bet_4,
sum(sraise) AS raise_4,
sum(scall) AS call_4,
sum(rpossible_actions) AS poss_a_5,
sum(rfold) AS fold_5,
sum(rcheck) AS check_5,
sum(rbet) AS bet_5,
sum(rraise) AS raise_5,
sum(rcall) AS call_5,
sum(cold_call_pf) AS cc_pf,
sum(saw_fifth_n) AS saw_3,
sum(saw_sixth_n) AS saw_4,
sum(saw_river_n) AS saw_5
FROM game_players
WHERE player_id in
(SELECT player_id FROM game_players
WHERE game_id = %s AND NOT player_id = %s)
GROUP BY player_id
"""
# alternate form of WHERE for above
# WHERE game_id = %(hand)d AND NOT player_id = %(hero)d)
# WHERE game_id = %s AND NOT player_id = %s)
self.query['get_players_from_hand'] = """
SELECT game_players.player_id, seat_number, screen_name
FROM game_players INNER JOIN players ON (game_players.player_id = players.player_id)
WHERE game_id = %s
"""
###############################################################################3 ###############################################################################3
# Support for the Free Poker DataBase = fpdb http://fpdb.sourceforge.net/ # Support for the Free Poker DataBase = fpdb http://fpdb.sourceforge.net/
# #
@ -453,8 +325,14 @@ class Sql:
siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id),
buyin INT NOT NULL, buyin INT NOT NULL,
fee INT NOT NULL, fee INT NOT NULL,
knockout INT NOT NULL, maxSeats INT NOT NULL DEFAULT -1,
rebuyOrAddon BOOLEAN NOT NULL) knockout BOOLEAN NOT NULL DEFAULT False,
rebuyOrAddon BOOLEAN NOT NULL DEFAULT False,
speed varchar(10),
headsUp BOOLEAN NOT NULL DEFAULT False,
shootout BOOLEAN NOT NULL DEFAULT False,
matrix BOOLEAN NOT NULL DEFAULT False
)
ENGINE=INNODB""" ENGINE=INNODB"""
elif db_server == 'postgresql': elif db_server == 'postgresql':
self.query['createTourneyTypesTable'] = """CREATE TABLE TourneyTypes ( self.query['createTourneyTypesTable'] = """CREATE TABLE TourneyTypes (
@ -462,16 +340,28 @@ class Sql:
siteId INT NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), siteId INT NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id),
buyin INT NOT NULL, buyin INT NOT NULL,
fee INT NOT NULL, fee INT NOT NULL,
knockout INT NOT NULL, maxSeats INT NOT NULL DEFAULT -1,
rebuyOrAddon BOOLEAN NOT NULL)""" knockout BOOLEAN NOT NULL DEFAULT False,
rebuyOrAddon BOOLEAN NOT NULL DEFAULT False,
speed varchar(10),
headsUp BOOLEAN NOT NULL DEFAULT False,
shootout BOOLEAN NOT NULL DEFAULT False,
matrix BOOLEAN NOT NULL DEFAULT False
)"""
elif db_server == 'sqlite': elif db_server == 'sqlite':
self.query['createTourneyTypesTable'] = """CREATE TABLE TourneyTypes ( self.query['createTourneyTypesTable'] = """CREATE TABLE TourneyTypes (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
siteId INT NOT NULL, siteId INT NOT NULL,
buyin INT NOT NULL, buyin INT NOT NULL,
fee INT NOT NULL, fee INT NOT NULL,
knockout INT NOT NULL, maxSeats INT NOT NULL DEFAULT -1,
rebuyOrAddon BOOLEAN NOT NULL)""" knockout BOOLEAN NOT NULL DEFAULT 0,
rebuyOrAddon BOOLEAN NOT NULL DEFAULT 0,
speed TEXT,
headsUp BOOLEAN NOT NULL DEFAULT 0,
shootout BOOLEAN NOT NULL DEFAULT 0,
matrix BOOLEAN NOT NULL DEFAULT 0
)"""
################################ ################################
# Create Tourneys # Create Tourneys
@ -480,32 +370,65 @@ class Sql:
if db_server == 'mysql': if db_server == 'mysql':
self.query['createTourneysTable'] = """CREATE TABLE Tourneys ( self.query['createTourneysTable'] = """CREATE TABLE Tourneys (
id INT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), id INT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id),
tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), tourneyTypeId SMALLINT UNSIGNED NOT NULL DEFAULT 1, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
siteTourneyNo BIGINT NOT NULL, siteTourneyNo BIGINT NOT NULL,
entries INT NOT NULL, entries INT NOT NULL,
prizepool INT NOT NULL, prizepool INT NOT NULL,
startTime DATETIME NOT NULL, startTime DATETIME NOT NULL,
endTime DATETIME,
buyinChips INT,
tourneyName varchar(20),
matrixIdProcessed TINYINT UNSIGNED DEFAULT 0, /* Mask use : 1=Positionnal Winnings|2=Match1|4=Match2|...|pow(2,n)=Matchn */
rebuyChips INT DEFAULT 0,
addonChips INT DEFAULT 0,
rebuyAmount INT DEFAULT 0,
addonAmount INT DEFAULT 0,
totalRebuys INT DEFAULT 0,
totalAddons INT DEFAULT 0,
koBounty INT DEFAULT 0,
comment TEXT, comment TEXT,
commentTs DATETIME) commentTs DATETIME)
ENGINE=INNODB""" ENGINE=INNODB"""
elif db_server == 'postgresql': elif db_server == 'postgresql':
self.query['createTourneysTable'] = """CREATE TABLE Tourneys ( self.query['createTourneysTable'] = """CREATE TABLE Tourneys (
id SERIAL, PRIMARY KEY (id), id SERIAL, PRIMARY KEY (id),
tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), tourneyTypeId INT DEFAULT 1, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
siteTourneyNo BIGINT, siteTourneyNo BIGINT,
entries INT, entries INT,
prizepool INT, prizepool INT,
startTime timestamp without time zone, startTime timestamp without time zone,
endTime timestamp without time zone,
buyinChips INT,
tourneyName varchar(20),
matrixIdProcessed SMALLINT UNSIGNED DEFAULT 0, /* Mask use : 1=Positionnal Winnings|2=Match1|4=Match2|...|pow(2,n)=Matchn */
rebuyChips INT DEFAULT 0,
addonChips INT DEFAULT 0,
rebuyAmount INT DEFAULT 0,
addonAmount INT DEFAULT 0,
totalRebuys INT DEFAULT 0,
totalAddons INT DEFAULT 0,
koBounty INT DEFAULT 0,
comment TEXT, comment TEXT,
commentTs timestamp without time zone)""" commentTs timestamp without time zone)"""
elif db_server == 'sqlite': elif db_server == 'sqlite':
self.query['createTourneysTable'] = """CREATE TABLE Tourneys ( self.query['createTourneysTable'] = """CREATE TABLE Tourneys (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
tourneyTypeId INT, tourneyTypeId INT DEFAULT 1,
siteTourneyNo INT, siteTourneyNo INT,
entries INT, entries INT,
prizepool INT, prizepool INT,
startTime REAL, startTime REAL,
endTime REAL,
buyinChips INT,
tourneyName TEXT,
matrixIdProcessed INT UNSIGNED DEFAULT 0, /* Mask use : 1=Positionnal Winnings|2=Match1|4=Match2|...|pow(2,n)=Matchn */
rebuyChips INT DEFAULT 0,
addonChips INT DEFAULT 0,
rebuyAmount INT DEFAULT 0,
addonAmount INT DEFAULT 0,
totalRebuys INT DEFAULT 0,
totalAddons INT DEFAULT 0,
koBounty INT DEFAULT 0,
comment TEXT, comment TEXT,
commentTs REAL)""" commentTs REAL)"""
################################ ################################
@ -537,7 +460,7 @@ class Sql:
comment text, comment text,
commentTs DATETIME, commentTs DATETIME,
tourneysPlayersId BIGINT UNSIGNED, tourneysPlayersId BIGINT UNSIGNED,
tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), tourneyTypeId SMALLINT UNSIGNED NOT NULL DEFAULT 1, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
wonWhenSeenStreet1 FLOAT, wonWhenSeenStreet1 FLOAT,
wonWhenSeenStreet2 FLOAT, wonWhenSeenStreet2 FLOAT,
@ -655,7 +578,7 @@ class Sql:
comment text, comment text,
commentTs timestamp without time zone, commentTs timestamp without time zone,
tourneysPlayersId BIGINT, tourneysPlayersId BIGINT,
tourneyTypeId INT NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), tourneyTypeId INT NOT NULL DEFAULT 1, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
wonWhenSeenStreet1 FLOAT, wonWhenSeenStreet1 FLOAT,
wonWhenSeenStreet2 FLOAT, wonWhenSeenStreet2 FLOAT,
@ -772,7 +695,7 @@ class Sql:
comment TEXT, comment TEXT,
commentTs REAL, commentTs REAL,
tourneysPlayersId INT, tourneysPlayersId INT,
tourneyTypeId INT NOT NULL, tourneyTypeId INT NOT NULL DEFAULT 1,
wonWhenSeenStreet1 REAL, wonWhenSeenStreet1 REAL,
wonWhenSeenStreet2 REAL, wonWhenSeenStreet2 REAL,
@ -877,6 +800,9 @@ class Sql:
payinAmount INT NOT NULL, payinAmount INT NOT NULL,
rank INT NOT NULL, rank INT NOT NULL,
winnings INT NOT NULL, winnings INT NOT NULL,
nbRebuys INT DEFAULT 0,
nbAddons INT DEFAULT 0,
nbKO INT DEFAULT 0,
comment TEXT, comment TEXT,
commentTs DATETIME) commentTs DATETIME)
ENGINE=INNODB""" ENGINE=INNODB"""
@ -888,6 +814,9 @@ class Sql:
payinAmount INT, payinAmount INT,
rank INT, rank INT,
winnings INT, winnings INT,
nbRebuys INT DEFAULT 0,
nbAddons INT DEFAULT 0,
nbKO INT DEFAULT 0,
comment TEXT, comment TEXT,
commentTs timestamp without time zone)""" commentTs timestamp without time zone)"""
elif db_server == 'sqlite': elif db_server == 'sqlite':
@ -936,7 +865,7 @@ class Sql:
playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id),
activeSeats SMALLINT NOT NULL, activeSeats SMALLINT NOT NULL,
position CHAR(1), position CHAR(1),
tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), tourneyTypeId SMALLINT UNSIGNED NOT NULL DEFAULT 1, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
styleKey CHAR(7) NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */ styleKey CHAR(7) NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */
HDs INT NOT NULL, HDs INT NOT NULL,
@ -1037,7 +966,7 @@ class Sql:
playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id),
activeSeats SMALLINT, activeSeats SMALLINT,
position CHAR(1), position CHAR(1),
tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), tourneyTypeId INT DEFAULT 1, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
styleKey CHAR(7) NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */ styleKey CHAR(7) NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */
HDs INT, HDs INT,
@ -1136,7 +1065,7 @@ class Sql:
playerId INT, playerId INT,
activeSeats INT, activeSeats INT,
position TEXT, position TEXT,
tourneyTypeId INT, tourneyTypeId INT DEFAULT 1,
styleKey TEXT NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */ styleKey TEXT NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */
HDs INT, HDs INT,
@ -1715,6 +1644,9 @@ class Sql:
# used in GuiPlayerStats: # used in GuiPlayerStats:
self.query['getPlayerId'] = """SELECT id from Players where name = %s""" self.query['getPlayerId'] = """SELECT id from Players where name = %s"""
self.query['getPlayerIdBySite'] = """SELECT id from Players where name = %s AND siteId = %s"""
# used in Filters: # used in Filters:
self.query['getSiteId'] = """SELECT id from Sites where name = %s""" self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
self.query['getGames'] = """SELECT DISTINCT category from Gametypes""" self.query['getGames'] = """SELECT DISTINCT category from Gametypes"""
@ -2884,6 +2816,143 @@ class Sql:
WHERE gametypeId=%s AND siteHandNo=%s WHERE gametypeId=%s AND siteHandNo=%s
""" """
self.query['getTourneyTypeIdByTourneyNo'] = """SELECT tt.id,
tt.buyin,
tt.fee,
tt.maxSeats,
tt.knockout,
tt.rebuyOrAddon,
tt.speed,
tt.headsUp,
tt.shootout,
tt.matrix
FROM TourneyTypes tt
INNER JOIN Tourneys t ON (t.tourneyTypeId = tt.id)
WHERE t.siteTourneyNo=%s AND tt.siteId=%s
"""
self.query['getTourneyTypeId'] = """SELECT id
FROM TourneyTypes
WHERE siteId=%s
AND buyin=%s
AND fee=%s
AND knockout=%s
AND rebuyOrAddon=%s
AND speed=%s
AND headsUp=%s
AND shootout=%s
AND matrix=%s
"""
self.query['insertTourneyTypes'] = """INSERT INTO TourneyTypes
(siteId, buyin, fee, knockout, rebuyOrAddon
,speed, headsUp, shootout, matrix)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
self.query['getTourney'] = """SELECT t.id,
t.tourneyTypeId,
t.entries,
t.prizepool,
t.startTime,
t.endTime,
t.buyinChips,
t.tourneyName,
t.matrixIdProcessed,
t.rebuyChips,
t.addonChips,
t.rebuyAmount,
t.addonAmount,
t.totalRebuys,
t.totalAddons,
t.koBounty,
t.comment
FROM Tourneys t
INNER JOIN TourneyTypes tt ON (t.tourneyTypeId = tt.id)
WHERE t.siteTourneyNo=%s AND tt.siteId=%s
"""
self.query['insertTourney'] = """INSERT INTO Tourneys
(tourneyTypeId, siteTourneyNo, entries, prizepool,
startTime, endTime, buyinChips, tourneyName, matrixIdProcessed,
rebuyChips, addonChips, rebuyAmount, addonAmount, totalRebuys,
totalAddons, koBounty, comment, commentTs)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s)
"""
self.query['updateTourney'] = """UPDATE Tourneys
SET tourneyTypeId = %s,
entries = %s,
prizepool = %s,
startTime = %s,
endTime = %s,
buyinChips = %s,
tourneyName = %s,
matrixIdProcessed = %s,
rebuyChips = %s,
addonChips = %s,
rebuyAmount = %s,
addonAmount = %s,
totalRebuys = %s,
totalAddons = %s,
koBounty = %s,
comment = %s,
commentTs = %s
WHERE id=%s
"""
self.query['getTourneysPlayers'] = """SELECT id,
payinAmount,
rank,
winnings,
nbRebuys,
nbAddons,
nbKO,
comment,
commentTs
FROM TourneysPlayers
WHERE tourneyId=%s AND playerId+0=%s
"""
self.query['updateTourneysPlayers'] = """UPDATE TourneysPlayers
SET payinAmount = %s,
rank = %s,
winnings = %s,
nbRebuys = %s,
nbAddons = %s,
nbKO = %s,
comment = %s,
commentTs = %s
WHERE id=%s
"""
self.query['insertTourneysPlayers'] = """INSERT INTO TourneysPlayers
(tourneyId, playerId, payinAmount, rank, winnings, nbRebuys, nbAddons, nbKO, comment, commentTs)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
self.query['selectHandsPlayersWithWrongTTypeId'] = """SELECT id
FROM HandsPlayers
WHERE tourneyTypeId <> %s AND (TourneysPlayersId+0=%s)
"""
# self.query['updateHandsPlayersForTTypeId2'] = """UPDATE HandsPlayers
# SET tourneyTypeId= %s
# WHERE (TourneysPlayersId+0=%s)
# """
self.query['updateHandsPlayersForTTypeId'] = """UPDATE HandsPlayers
SET tourneyTypeId= %s
WHERE (id=%s)
"""
self.query['handsPlayersTTypeId_joiner'] = " OR TourneysPlayersId+0="
self.query['handsPlayersTTypeId_joiner_id'] = " OR id="
if db_server == 'mysql': if db_server == 'mysql':
self.query['placeholder'] = u'%s' self.query['placeholder'] = u'%s'
elif db_server == 'postgresql': elif db_server == 'postgresql':
@ -2899,7 +2968,7 @@ class Sql:
if __name__== "__main__": if __name__== "__main__":
# just print the default queries and exit # just print the default queries and exit
s = Sql(game = 'razz', type = 'ptracks') s = Sql()
for key in s.query: for key in s.query:
print "For query " + key + ", sql =" print "For query " + key + ", sql ="
print s.query[key] print s.query[key]

188
pyfpdb/SummaryEverleaf.py Normal file
View File

@ -0,0 +1,188 @@
#!/usr/bin/python
# Copyright (c) 2009 Eric Blade, and the FPDB team.
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#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 Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import urllib, htmllib, formatter
class AppURLopener(urllib.FancyURLopener):
version = "Free Poker Database/0.12+"
urllib._urlopener = AppURLopener()
class SummaryParser(htmllib.HTMLParser): # derive new HTML parser
def get_attr(self, attrs, key):
#print attrs;
for a in attrs:
if a[0] == key:
# print key,"=",a[1]
return a[1]
return None
def __init__(self, formatter) : # class constructor
htmllib.HTMLParser.__init__(self, formatter) # base class constructor
self.nofill = True
self.SiteName = None
self.TourneyId = None
self.TourneyName = None
self.nextStartTime = False
self.TourneyStartTime = None
self.nextEndTime = False
self.TourneyEndTime = None
self.TourneyGameType = None
self.nextGameType = False
self.nextStructure = False
self.TourneyStructure = None
self.nextBuyIn = False
self.TourneyBuyIn = None
self.nextPool = False
self.TourneyPool = None
self.nextPlayers = False
self.TourneyPlayers = None
self.nextAllowRebuys = False
self.TourneyRebuys = None
self.parseResultsA = False
self.parseResultsB = False
self.TempResultStore = [0,0,0,0]
self.TempResultPos = 0
self.Results = {}
def start_meta(self, attrs):
x = self.get_attr(attrs, 'name')
if x == "author":
self.SiteName = self.get_attr(attrs, 'content')
def start_input(self, attrs):
x = self.get_attr(attrs, 'name')
#print "input name=",x
if x == "tid":
self.TourneyId = self.get_attr(attrs, 'value')
def start_h1(self, attrs):
if self.TourneyName is None:
self.save_bgn()
def end_h1(self):
if self.TourneyName is None:
self.TourneyName = self.save_end()
def start_div(self, attrs):
x = self.get_attr(attrs, 'id')
if x == "result":
self.parseResultsA = True
def end_div(self): # TODO: Can we get attrs in the END tag too? I don't know? Would be useful to make SURE we're closing the right div ..
if self.parseResultsA:
self.parseResultsA = False # TODO: Should probably just make sure everything is false at this point, since we're not going to be having anything in the middle of a DIV.. oh well
def start_td(self, attrs):
self.save_bgn()
def end_td(self):
x = self.save_end()
if not self.parseResultsA:
if not self.nextStartTime and x == "Start:":
self.nextStartTime = True
elif self.nextStartTime:
self.TourneyStartTime = x
self.nextStartTime = False
if not self.nextEndTime and x == "Finished:":
self.nextEndTime = True
elif self.nextEndTime:
self.TourneyEndTime = x
self.nextEndTime = False
if not self.nextGameType and x == "Game Type:":
self.nextGameType = True
elif self.nextGameType:
self.TourneyGameType = x
self.nextGameType = False
if not self.nextStructure and x == "Limit:":
self.nextStructure = True
elif self.nextStructure:
self.TourneyStructure = x
self.nextStructure = False
if not self.nextBuyIn and x == "Buy In / Fee:":
self.nextBuyIn = True
elif self.nextBuyIn:
self.TourneyBuyIn = x # TODO: Further parse the fee from this
self.nextBuyIn = False
if not self.nextPool and x == "Prize Money:":
self.nextPool = True
elif self.nextPool:
self.TourneyPool = x
self.nextPool = False
if not self.nextPlayers and x == "Player Count:":
self.nextPlayers = True
elif self.nextPlayers:
self.TourneyPlayers = x
self.nextPlayers = False
if not self.nextAllowRebuys and x == "Rebuys possible?:":
self.nextAllowRebuys = True
elif self.nextAllowRebuys:
self.TourneyRebuys = x
self.nextAllowRebuys = False
else: # parse results
if x == "Won Prize":
self.parseResultsB = True # ok, NOW we can start parsing the results
elif self.parseResultsB:
if x[0] == "$": # first char of the last of each row is the dollar sign, so now we can put it into a sane order
self.TempResultPos = 0
name = self.TempResultStore[1]
place = self.TempResultStore[0]
time = self.TempResultStore[2]
# print self.TempResultStore
self.Results[name] = {}
self.Results[name]['place'] = place
self.Results[name]['winamount'] = x
self.Results[name]['outtime'] = time
# self.Results[self.TempResultStore[1]] = {}
# self.Results[self.TempResultStore[1]]['place'] = self.TempResultStore[self.TempResultStore[0]]
# self.Results[self.TempResultStore[1]]['winamount'] = x
# self.Results[self.TempResultStore[1]]['outtime'] = self.TempResultStore[self.TempResultStore[2]]
else:
self.TempResultStore[self.TempResultPos] = x
self.TempResultPos += 1
class EverleafSummary:
def __init__(self):
if __name__ != "__main__":
self.main()
def main(self, id="785646"):
file = urllib.urlopen("http://www.poker4ever.com/en.tournaments.tournament-statistics?tid="+id)
self.parser = SummaryParser(formatter.NullFormatter())
self.parser.feed(file.read())
print "site=",self.parser.SiteName, "tourneyname=", self.parser.TourneyName, "tourneyid=", self.parser.TourneyId
print "start time=",self.parser.TourneyStartTime, "end time=",self.parser.TourneyEndTime
print "structure=", self.parser.TourneyStructure, "game type=",self.parser.TourneyGameType
print "buy-in=", self.parser.TourneyBuyIn, "rebuys=", self.parser.TourneyRebuys, "total players=", self.parser.TourneyPlayers, "pool=", self.parser.TourneyPool
print "results=", self.parser.Results
if __name__ == "__main__":
me = EverleafSummary()
me.main()

View File

@ -39,6 +39,7 @@ if os.name == 'nt':
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
from fpdb_simple import LOCALE_ENCODING
# Each TableWindow object must have the following attributes correctly populated: # Each TableWindow object must have the following attributes correctly populated:
# tw.name = the table name from the title bar, which must to match the table name # tw.name = the table name from the title bar, which must to match the table name
@ -68,6 +69,7 @@ class Table_Window:
if 'site' in info: self.site = info['site'] if 'site' in info: self.site = info['site']
if 'title' in info: self.title = info['title'] if 'title' in info: self.title = info['title']
if 'name' in info: self.name = info['name'] if 'name' in info: self.name = info['name']
self.gdkhandle = None
def __str__(self): def __str__(self):
# __str__ method for testing # __str__ method for testing
@ -230,16 +232,21 @@ def discover_nt_by_name(c, tablename):
"""Finds poker client window with the given table name.""" """Finds poker client window with the given table name."""
titles = {} titles = {}
win32gui.EnumWindows(win_enum_handler, titles) win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles: for hwnd in titles:
#print "Tables.py: tablename =", tablename, "title =", titles[hwnd] #print "Tables.py: tablename =", tablename, "title =", titles[hwnd]
try: try:
# maybe it's better to make global titles[hwnd] decoding?
# this can blow up in XP on some windows, eg firefox displaying http://docs.python.org/tutorial/classes.html # this can blow up in XP on some windows, eg firefox displaying http://docs.python.org/tutorial/classes.html
if not tablename in titles[hwnd]: continue if not tablename.lower() in titles[hwnd].decode(LOCALE_ENCODING).lower(): continue
except: except:
continue continue
if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window
if 'HUD:' in titles[hwnd]: continue # FPDB HUD window if 'HUD:' in titles[hwnd]: continue # FPDB HUD window
if 'Chat:' in titles[hwnd]: continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows if 'Chat:' in titles[hwnd]: continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows
if ' - Table ' in titles[hwnd]: continue # Absolute table Chat window.. sigh. TODO: Can we tell what site we're trying to discover for somehow in here, so i can limit this check just to AP searches?
temp = decode_windows(c, titles[hwnd], hwnd)
#print "attach to window", temp
return decode_windows(c, titles[hwnd], hwnd) return decode_windows(c, titles[hwnd], hwnd)
return None return None
@ -302,7 +309,9 @@ def decode_windows(c, title, hwnd):
return info return info
def win_enum_handler(hwnd, titles): def win_enum_handler(hwnd, titles):
titles[hwnd] = win32gui.GetWindowText(hwnd) str = win32gui.GetWindowText(hwnd)
if str != "":
titles[hwnd] = win32gui.GetWindowText(hwnd)
################################################################### ###################################################################
# Utility routines used by all the discoverers. # Utility routines used by all the discoverers.

310
pyfpdb/TournamentTracker.py Normal file
View File

@ -0,0 +1,310 @@
#!/usr/bin/env python
"""TourneyTracker.py
Based on HUD_main .. who knows if we want to actually use this or not
"""
# Copyright 2008, 2009, Eric Blade
#
# 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
########################################################################
# to do allow window resizing
# to do hud to echo, but ignore non numbers
# to do no stat window for hero
# to do things to add to config.xml
# Standard Library modules
import sys
import os
import Options
import traceback
(options, sys.argv) = Options.fpdb_options()
if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('tourneyerror.txt', 'w', 0)
sys.stderr = errorFile
import thread
import time
import string
import re
# pyGTK modules
import pygtk
import gtk
import gobject
# FreePokerTools modules
import Configuration
import Database
import SummaryEverleaf
class Tournament:
"""Tournament will hold the information about a tournament, I guess ? Remember I'm new to this language, so I don't know the best ways to do things"""
def __init__(self, parent, site, tid): # site should probably be something in the config object, but i don't know how the config object works right now, so we're going to make it a str ..
print "Tournament init"
self.parent = parent
self.window = None
self.site = site
self.id = tid
self.starttime = time.time()
self.endtime = None
self.game = None
self.structure = None
self.buyin = 0
self.fee = 0
self.rebuys = False
self.numrebuys = 0 # this should probably be attached to the players list...
self.numplayers = 0
self.prizepool = 0
self.players = {} # eventually i'd guess we'd probably want to fill this with playername:playerid's
self.results = {} # i'd guess we'd want to load this up with playerid's instead of playernames, too, as well, also
# if site == "Everleaf": # this should be attached to a button that says "retrieve tournament info" or something for sites that we know how to do it for
summary = SummaryEverleaf.EverleafSummary()
self.site = summary.parser.SiteName
self.id = summary.parser.TourneyId
self.starttime = summary.parser.TourneyStartTime
self.endtime = summary.parser.TourneyEndTime
self.game = summary.parser.TourneyGameType
self.structure = summary.parser.TourneyStructure
self.buyin = summary.parser.TourneyBuyIn # need to remember to parse the Fee out of this and move it to self.fee
self.rebuys = (summary.parser.TourneyRebuys == "yes")
self.prizepool = summary.parser.TourneyPool
self.numplayers = summary.parser.TourneyPlayers
self.openwindow() # let's start by getting any info we need.. meh
def openwindow(self, widget=None):
if self.window is not None:
self.window.show() # isn't there a better way to bring something to the front? not that GTK focus works right anyway, ever
else:
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
print "tournament edit window=", self.window
self.window.connect("delete_event", self.delete_event)
self.window.connect("destroy", self.destroy)
self.window.set_title("FPDB Tournament Entry")
self.window.set_border_width(1)
self.window.set_default_size(480,640)
self.window.set_resizable(True)
self.main_vbox = gtk.VBox(False, 1)
self.main_vbox.set_border_width(1)
self.window.add(self.main_vbox)
self.window.show()
def addrebuy(self, widget=None):
t = self
t.numrebuys += 1
t.mylabel.set_label("%s - %s - %s - %s - %s %s - %s - %s - %s - %s - %s" % (t.site, t.id, t.starttime, t.endtime, t.structure, t.game, t.buyin, t.fee, t.numrebuys, t.numplayers, t.prizepool))
def delete_event(self, widget, event, data=None):
return False
def destroy(self, widget, data=None):
return False
#end def destroy
class ttracker_main(object):
"""A main() object to own both the read_stdin thread and the gui."""
# This class mainly provides state for controlling the multiple HUDs.
def __init__(self, db_name = 'fpdb'):
self.db_name = db_name
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
self.tourney_list = []
# a thread to read stdin
gobject.threads_init() # this is required
thread.start_new_thread(self.read_stdin, ()) # starts the thread
# a main window
self.main_window = gtk.Window()
self.main_window.connect("destroy", self.destroy)
self.vb = gtk.VBox()
self.label = gtk.Label('Closing this window will stop the Tournament Tracker')
self.vb.add(self.label)
self.addbutton = gtk.Button(label="Enter Tournament")
self.addbutton.connect("clicked", self.addClicked, "add tournament")
self.vb.add(self.addbutton)
self.main_window.add(self.vb)
self.main_window.set_title("FPDB Tournament Tracker")
self.main_window.show_all()
def addClicked(self, widget, data): # what is "data"? i'm guessing anything i pass in after the function name in connect() but unsure because the documentation sucks
print "addClicked", widget, data
t = Tournament(self, None, None)
if t is not None:
print "new tournament=", t
self.tourney_list.append(t)
mylabel = gtk.Label("%s - %s - %s - %s - %s %s - %s - %s - %s - %s - %s" % (t.site, t.id, t.starttime, t.endtime, t.structure, t.game, t.buyin, t.fee, t.numrebuys, t.numplayers, t.prizepool))
print "new label=", mylabel
editbutton = gtk.Button(label="Edit")
print "new button=", editbutton
editbutton.connect("clicked", t.openwindow)
rebuybutton = gtk.Button(label="Rebuy")
rebuybutton.connect("clicked", t.addrebuy)
self.vb.add(rebuybutton)
self.vb.add(editbutton) # These should probably be put in.. a.. h-box? i don't know..
self.vb.add(mylabel)
self.main_window.resize_children()
self.main_window.show()
mylabel.show()
editbutton.show()
rebuybutton.show()
t.mylabel = mylabel
t.editbutton = editbutton
t.rebuybutton = rebuybutton
self.vb.show()
print self.tourney_list
return True
else:
return False
# when we move the start command over to the main program, we can have the main program ask for the tourney id, and pipe it into the stdin here
# at least that was my initial thought on it
def destroy(*args): # call back for terminating the main eventloop
gtk.main_quit()
def create_HUD(self, new_hand_id, table, table_name, max, poker_game, stat_dict, cards):
def idle_func():
gtk.gdk.threads_enter()
try:
newlabel = gtk.Label("%s - %s" % (table.site, table_name))
self.vb.add(newlabel)
newlabel.show()
self.main_window.resize_children()
self.hud_dict[table_name].tablehudlabel = newlabel
self.hud_dict[table_name].create(new_hand_id, self.config, stat_dict, cards)
for m in self.hud_dict[table_name].aux_windows:
m.create()
m.update_gui(new_hand_id)
self.hud_dict[table_name].update(new_hand_id, self.config)
self.hud_dict[table_name].reposition_windows()
return False
finally:
gtk.gdk.threads_leave()
self.hud_dict[table_name] = Hud.Hud(self, table, max, poker_game, self.config, self.db_connection)
self.hud_dict[table_name].table_name = table_name
self.hud_dict[table_name].stat_dict = stat_dict
self.hud_dict[table_name].cards = cards
[aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[table_name].aux_windows]
gobject.idle_add(idle_func)
def update_HUD(self, new_hand_id, table_name, config):
"""Update a HUD gui from inside the non-gui read_stdin thread."""
# This is written so that only 1 thread can touch the gui--mainly
# for compatibility with Windows. This method dispatches the
# function idle_func() to be run by the gui thread, at its leisure.
def idle_func():
gtk.gdk.threads_enter()
try:
self.hud_dict[table_name].update(new_hand_id, config)
[aw.update_gui(new_hand_id) for aw in self.hud_dict[table_name].aux_windows]
return False
finally:
gtk.gdk.threads_leave()
gobject.idle_add(idle_func)
def read_stdin(self): # This is the thread function
"""Do all the non-gui heavy lifting for the HUD program."""
# This db connection is for the read_stdin thread only. It should not
# be passed to HUDs for use in the gui thread. HUD objects should not
# need their own access to the database, but should open their own
# if it is required.
self.db_connection = Database.Database(self.config, self.db_name, 'temp')
# self.db_connection.init_hud_stat_vars(hud_days)
tourny_finder = re.compile('(\d+) (\d+)')
while 1: # wait for a new hand number on stdin
new_hand_id = sys.stdin.readline()
new_hand_id = string.rstrip(new_hand_id)
if new_hand_id == "": # blank line means quit
self.destroy()
break # this thread is not always killed immediately with gtk.main_quit()
# get basic info about the new hand from the db
# if there is a db error, complain, skip hand, and proceed
try:
(table_name, max, poker_game, type) = self.db_connection.get_table_name(new_hand_id)
stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, aggregate_stats[type]
,hud_style, agg_bb_mult)
cards = self.db_connection.get_cards(new_hand_id)
comm_cards = self.db_connection.get_common_cards(new_hand_id)
if comm_cards != {}: # stud!
cards['common'] = comm_cards['common']
except Exception, err:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "db error: skipping "+str(new_hand_id)+" "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
if new_hand_id: # new_hand_id is none if we had an error prior to the store
sys.stderr.write("Database error %s in hand %d. Skipping.\n" % (err, int(new_hand_id)))
continue
if type == "tour": # hand is from a tournament
mat_obj = tourny_finder.search(table_name)
if mat_obj:
(tour_number, tab_number) = mat_obj.group(1, 2)
temp_key = tour_number
else: # tourney, but can't get number and table
print "could not find tournament: skipping "
sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id)))
continue
else:
temp_key = table_name
# Update an existing HUD
if temp_key in self.hud_dict:
self.hud_dict[temp_key].stat_dict = stat_dict
self.hud_dict[temp_key].cards = cards
[aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[temp_key].aux_windows]
self.update_HUD(new_hand_id, temp_key, self.config)
# Or create a new HUD
else:
if type == "tour":
tablewindow = Tables.discover_tournament_table(self.config, tour_number, tab_number)
else:
tablewindow = Tables.discover_table_by_name(self.config, table_name)
if tablewindow == None:
# If no client window is found on the screen, complain and continue
if type == "tour":
table_name = "%s %s" % (tour_number, tab_number)
sys.stderr.write("table name "+table_name+" not found, skipping.\n")
else:
self.create_HUD(new_hand_id, tablewindow, temp_key, max, poker_game, stat_dict, cards)
self.db_connection.connection.rollback()
if __name__== "__main__":
sys.stderr.write("tournament tracker starting\n")
sys.stderr.write("Using db name = %s\n" % (options.dbname))
# start the HUD_main object
hm = ttracker_main(db_name = options.dbname)
# start the event loop
gtk.main()

471
pyfpdb/Tourney.py Normal file
View File

@ -0,0 +1,471 @@
#!/usr/bin/python
#Copyright 2009 Stephane Alessio
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#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 Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
# TODO: check to keep only the needed modules
import re
import sys
import traceback
import logging
import os
import os.path
from decimal import Decimal
import operator
import time,datetime
from copy import deepcopy
from Exceptions import *
import pprint
import DerivedStats
import Card
log = logging.getLogger("parser")
class Tourney(object):
################################################################
# Class Variables
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'} # SAL- TO KEEP ??
LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} # SAL- TO KEEP ??
SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'}
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 }
def __init__(self, sitename, gametype, summaryText, builtFrom = "HHC"):
self.sitename = sitename
self.siteId = self.SITEIDS[sitename]
self.gametype = gametype
self.starttime = None
self.endtime = None
self.summaryText = summaryText
self.tourneyName = None
self.tourNo = None
self.buyin = None
self.fee = None # the Database code is looking for this one .. ?
self.hero = None
self.maxseats = None
self.entries = 0
self.speed = "Normal"
self.prizepool = None # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??}
self.buyInChips = None
self.mixed = None
self.isRebuy = False
self.isKO = False
self.isHU = False
self.isMatrix = False
self.isShootout = False
self.matrixMatchId = None # For Matrix tourneys : 1-4 => match tables (traditionnal), 0 => Positional winnings info
self.subTourneyBuyin = None
self.subTourneyFee = None
self.rebuyChips = 0
self.addOnChips = 0
self.rebuyAmount = 0
self.addOnAmount = 0
self.totalRebuys = 0
self.totalAddOns = 0
self.koBounty = 0
self.tourneyComment = None
self.players = []
# Collections indexed by player names
self.finishPositions = {}
self.winnings = {}
self.payinAmounts = {}
self.countRebuys = {}
self.countAddOns = {}
self.countKO = {}
# currency symbol for this summary
self.sym = None
#self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
def __str__(self):
#TODO : Update
vars = ( ("SITE", self.sitename),
("START TIME", self.starttime),
("END TIME", self.endtime),
("TOURNEY NAME", self.tourneyName),
("TOURNEY NO", self.tourNo),
("BUYIN", self.buyin),
("FEE", self.fee),
("HERO", self.hero),
("MAXSEATS", self.maxseats),
("ENTRIES", self.entries),
("SPEED", self.speed),
("PRIZE POOL", self.prizepool),
("STARTING CHIP COUNT", self.buyInChips),
("MIXED", self.mixed),
("REBUY ADDON", self.isRebuy),
("KO", self.isKO),
("HU", self.isHU),
("MATRIX", self.isMatrix),
("SHOOTOUT", self.isShootout),
("MATRIX MATCH ID", self.matrixMatchId),
("SUB TOURNEY BUY IN", self.subTourneyBuyin),
("SUB TOURNEY FEE", self.subTourneyFee),
("REBUY CHIPS", self.rebuyChips),
("ADDON CHIPS", self.addOnChips),
("REBUY AMOUNT", self.rebuyAmount),
("ADDON AMOUNT", self.addOnAmount),
("TOTAL REBUYS", self.totalRebuys),
("TOTAL ADDONS", self.totalAddOns),
("KO BOUNTY", self.koBounty),
("TOURNEY COMMENT", self.tourneyComment)
)
structs = ( ("GAMETYPE", self.gametype),
("PLAYERS", self.players),
("PAYIN AMOUNTS", self.payinAmounts),
("POSITIONS", self.finishPositions),
("WINNINGS", self.winnings),
("COUNT REBUYS", self.countRebuys),
("COUNT ADDONS", self.countAddOns),
("NB OF KO", self.countKO)
)
str = ''
for (name, var) in vars:
str = str + "\n%s = " % name + pprint.pformat(var)
for (name, struct) in structs:
str = str + "\n%s =\n" % name + pprint.pformat(struct, 4)
return str
def getSummaryText(self):
return self.summaryText
def prepInsert(self, db):
pass
def insert(self, db):
# First : check all needed info is filled in the object, especially for the initial select
# Notes on DB Insert
# Some identified issues for tourneys already in the DB (which occurs when the HH file is parsed and inserted before the Summary)
# Be careful on updates that could make the HH import not match the tourney inserted from a previous summary import !!
# BuyIn/Fee can be at 0/0 => match may not be easy
# Only one existinf Tourney entry for Matrix Tourneys, but multiple Summary files
# Starttime may not match the one in the Summary file : HH = time of the first Hand / could be slighltly different from the one in the summary file
# Note: If the TourneyNo could be a unique id .... this would really be a relief to deal with matrix matches ==> Ask on the IRC / Ask Fulltilt ??
dbTourneyTypeId = db.tRecogniseTourneyType(self)
logging.debug("Tourney Type ID = %d" % dbTourneyTypeId)
dbTourneyId = db.tRecognizeTourney(self, dbTourneyTypeId)
logging.debug("Tourney ID = %d" % dbTourneyId)
dbTourneysPlayersIds = db.tStoreTourneyPlayers(self, dbTourneyId)
logging.debug("TourneysPlayersId = %s" % dbTourneysPlayersIds)
db.tUpdateTourneysHandsPlayers(self, dbTourneysPlayersIds, dbTourneyTypeId)
logging.debug("tUpdateTourneysHandsPlayers done")
logging.debug("Tourney Insert done")
# TO DO : Return what has been done (tourney created, updated, nothing)
# ?? stored = 1 if tourney is fully created / duplicates = 1, if everything was already here and correct / partial=1 if some things were already here (between tourney, tourneyPlayers and handsplayers)
# if so, prototypes may need changes to know what has been done or make some kind of dict in Tourney object that could be updated during the insert process to store that information
stored = 0
duplicates = 0
partial = 0
errors = 0
ttime = 0
return (stored, duplicates, partial, errors, ttime)
def old_insert_from_Hand(self, db):
""" Function to insert Hand into database
Should not commit, and do minimal selects. Callers may want to cache commits
db: a connected fpdb_db object"""
# TODO:
# Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
#Gametypes
gtid = db.getGameTypeId(self.siteId, self.gametype)
# HudCache data to come from DerivedStats class
# HandsActions - all actions for all players for all streets - self.actions
# Hands - Summary information of hand indexed by handId - gameinfo
#This should be moved to prepInsert
hh = {}
hh['siteHandNo'] = self.handid
hh['handStart'] = self.starttime
hh['gameTypeId'] = gtid
# seats TINYINT NOT NULL,
hh['tableName'] = self.tablename
hh['maxSeats'] = self.maxseats
hh['seats'] = len(sqlids)
# Flop turn and river may all be empty - add (likely) too many elements and trim with range
boardcards = self.board['FLOP'] + self.board['TURN'] + self.board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
cards = [Card.encodeCard(c) for c in boardcards[0:5]]
hh['boardcard1'] = cards[0]
hh['boardcard2'] = cards[1]
hh['boardcard3'] = cards[2]
hh['boardcard4'] = cards[3]
hh['boardcard5'] = cards[4]
# texture smallint,
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
# Needs to be recorded
# playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4 */
# Needs to be recorded
# playersAtStreet2 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtStreet3 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtStreet4 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtShowdown SMALLINT NOT NULL,
# Needs to be recorded
# street0Raises TINYINT NOT NULL, /* num small bets paid to see flop/street4, including blind */
# Needs to be recorded
# street1Raises TINYINT NOT NULL, /* num small bets paid to see turn/street5 */
# Needs to be recorded
# street2Raises TINYINT NOT NULL, /* num big bets paid to see river/street6 */
# Needs to be recorded
# street3Raises TINYINT NOT NULL, /* num big bets paid to see sd/street7 */
# Needs to be recorded
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
# Needs to be recorded
#print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % self.getStreetTotals()
#FIXME: Pot size still in decimal, needs to be converted to cents
(hh['street1Pot'], hh['street2Pot'], hh['street3Pot'], hh['street4Pot'], hh['showdownPot']) = self.getStreetTotals()
# comment TEXT,
# commentTs DATETIME
#print hh
handid = db.storeHand(hh)
# HandsPlayers - ? ... Do we fix winnings?
# Tourneys ?
# TourneysPlayers
pass
def select(self, tourneyId):
""" Function to create Tourney object from database """
def addPlayer(self, rank, name, winnings, payinAmount, nbRebuys, nbAddons, nbKO):
"""\
Adds a player to the tourney, and initialises data structures indexed by player.
rank (int) indicating the finishing rank (can be -1 if unknown)
name (string) player name
winnings (decimal) the money the player ended the tourney with (can be 0, or -1 if unknown)
"""
log.debug("addPlayer: rank:%s - name : '%s' - Winnings (%s)" % (rank, name, winnings))
self.players.append(name)
self.finishPositions.update( { name : Decimal(rank) } )
self.winnings.update( { name : Decimal(winnings) } )
self.payinAmounts.update( {name : Decimal(payinAmount) } )
self.countRebuys.update( {name: Decimal(nbRebuys) } )
self.countAddOns.update( {name: Decimal(nbAddons) } )
self.countKO.update( {name : Decimal(nbKO) } )
def incrementPlayerWinnings(self, name, additionnalWinnings):
log.debug("incrementPlayerWinnings: name : '%s' - Add Winnings (%s)" % (name, additionnalWinnings))
oldWins = 0
if self.winnings.has_key(name):
oldWins = self.winnings[name]
else:
self.players.append([-1, name, 0])
self.winnings[name] = oldWins + Decimal(additionnalWinnings)
def checkPlayerExists(self,player):
if player not in [p[1] for p in self.players]:
print "checkPlayerExists", player, "fail"
raise FpdbParseError
def getGameTypeAsString(self):
"""\
Map the tuple self.gametype onto the pokerstars string describing it
"""
# currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
gs = {"holdem" : "Hold'em",
"omahahi" : "Omaha",
"omahahilo" : "Omaha Hi/Lo",
"razz" : "Razz",
"studhi" : "7 Card Stud",
"studhilo" : "7 Card Stud Hi/Lo",
"fivedraw" : "5 Card Draw",
"27_1draw" : "FIXME",
"27_3draw" : "Triple Draw 2-7 Lowball",
"badugi" : "Badugi"
}
ls = {"nl" : "No Limit",
"pl" : "Pot Limit",
"fl" : "Limit",
"cn" : "Cap No Limit",
"cp" : "Cap Pot Limit"
}
log.debug("gametype: %s" %(self.gametype))
retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
return retstring
def writeSummary(self, fh=sys.__stdout__):
print >>fh, "Override me"
def printSummary(self):
self.writeSummary(sys.stdout)
def assemble(cnxn, tourneyId):
# TODO !!
c = cnxn.cursor()
# We need at least sitename, gametype, handid
# for the Hand.__init__
c.execute("""
select
s.name,
g.category,
g.base,
g.type,
g.limitType,
g.hilo,
round(g.smallBlind / 100.0,2),
round(g.bigBlind / 100.0,2),
round(g.smallBet / 100.0,2),
round(g.bigBet / 100.0,2),
s.currency,
h.boardcard1,
h.boardcard2,
h.boardcard3,
h.boardcard4,
h.boardcard5
from
hands as h,
sites as s,
gametypes as g,
handsplayers as hp,
players as p
where
h.id = %(handid)s
and g.id = h.gametypeid
and hp.handid = h.id
and p.id = hp.playerid
and s.id = p.siteid
limit 1""", {'handid':handid})
#TODO: siteid should be in hands table - we took the scenic route through players here.
res = c.fetchone()
gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]}
h = HoldemOmahaHand(hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
cards = map(Card.valueSuitFromCard, res[11:16] )
if cards[0]:
h.setCommunityCards('FLOP', cards[0:3])
if cards[3]:
h.setCommunityCards('TURN', [cards[3]])
if cards[4]:
h.setCommunityCards('RIVER', [cards[4]])
#[Card.valueSuitFromCard(x) for x in cards]
# HandInfo : HID, TABLE
# BUTTON - why is this treated specially in Hand?
# answer: it is written out in hand histories
# still, I think we should record all the active seat positions in a seat_order array
c.execute("""
SELECT
h.sitehandno as hid,
h.tablename as table,
h.handstart as starttime
FROM
hands as h
WHERE h.id = %(handid)s
""", {'handid':handid})
res = c.fetchone()
h.handid = res[0]
h.tablename = res[1]
h.starttime = res[2] # automatically a datetime
# PlayerStacks
c.execute("""
SELECT
hp.seatno,
round(hp.winnings / 100.0,2) as winnings,
p.name,
round(hp.startcash / 100.0,2) as chips,
hp.card1,hp.card2,
hp.position
FROM
handsplayers as hp,
players as p
WHERE
hp.handid = %(handid)s
and p.id = hp.playerid
""", {'handid':handid})
for (seat, winnings, name, chips, card1,card2, position) in c.fetchall():
h.addPlayer(seat,name,chips)
if card1 and card2:
h.addHoleCards(map(Card.valueSuitFromCard, (card1,card2)), name, dealt=True)
if winnings > 0:
h.addCollectPot(name, winnings)
if position == 'B':
h.buttonpos = seat
# actions
c.execute("""
SELECT
(ha.street,ha.actionno) as actnum,
p.name,
ha.street,
ha.action,
ha.allin,
round(ha.amount / 100.0,2)
FROM
handsplayers as hp,
handsactions as ha,
players as p
WHERE
hp.handid = %(handid)s
and ha.handsplayerid = hp.id
and p.id = hp.playerid
ORDER BY
ha.street,ha.actionno
""", {'handid':handid})
res = c.fetchall()
for (actnum,player, streetnum, act, allin, amount) in res:
act=act.strip()
street = h.allStreets[streetnum+1]
if act==u'blind':
h.addBlind(player, 'big blind', amount)
# TODO: The type of blind is not recorded in the DB.
# TODO: preflop street name anomalies in Hand
elif act==u'fold':
h.addFold(street,player)
elif act==u'call':
h.addCall(street,player,amount)
elif act==u'bet':
h.addBet(street,player,amount)
elif act==u'check':
h.addCheck(street,player)
elif act==u'unbet':
pass
else:
print act, player, streetnum, allin, amount
# TODO : other actions
#hhc.readShowdownActions(self)
#hc.readShownCards(self)
h.totalPot()
h.rake = h.totalpot - h.totalcollected
return h

View File

@ -26,6 +26,11 @@ from HandHistoryConverter import *
class Win2day(HandHistoryConverter): class Win2day(HandHistoryConverter):
sitename = "Win2day"
filetype = "text"
codepage = "cp1252"
siteID = 4
# Static regexes # Static regexes
#<HISTORY ID="102271403" SESSION="session31237702.xml" TABLE="Innsbruck 3" GAME="GAME_THM" GAMETYPE="GAMETYPE_REAL" GAMEKIND="GAMEKIND_CASH" TABLECURRENCY="EUR" LIMIT="NL" STAKES="0.25/0.50" DATE="1246909773" WIN="0.00" LOSS="0.50"> #<HISTORY ID="102271403" SESSION="session31237702.xml" TABLE="Innsbruck 3" GAME="GAME_THM" GAMETYPE="GAMETYPE_REAL" GAMEKIND="GAMEKIND_CASH" TABLECURRENCY="EUR" LIMIT="NL" STAKES="0.25/0.50" DATE="1246909773" WIN="0.00" LOSS="0.50">
@ -39,15 +44,6 @@ class Win2day(HandHistoryConverter):
re_Card = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD>', re.MULTILINE) re_Card = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD>', re.MULTILINE)
re_BoardLast = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD></ACTION>', re.MULTILINE) re_BoardLast = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD></ACTION>', re.MULTILINE)
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Win2day", follow=follow, index=index)
logging.info("Initialising Win2day converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.sideID = 4
if autostart:
self.start()
def compilePlayerRegexs(self, hand): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players]) players = set([player[1] for player in hand.players])
@ -65,7 +61,7 @@ class Win2day(HandHistoryConverter):
self.re_PostBoth = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_AB" VALUE="(?P<SBBB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE) self.re_PostBoth = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_AB" VALUE="(?P<SBBB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
#r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n<CARD LINK="(?P<CARD1>[0-9]+)"></CARD>\n<CARD LINK="(?P<CARD2>[0-9]+)"></CARD></ACTION>' #r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n<CARD LINK="(?P<CARD1>[0-9]+)"></CARD>\n<CARD LINK="(?P<CARD2>[0-9]+)"></CARD></ACTION>'
self.re_HeroCards = re.compile(r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]"></CARD>)</ACTION>' % player_re, re.MULTILINE) self.re_HeroCards = re.compile(r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]+"></CARD>)</ACTION>' % player_re, re.MULTILINE)
#'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' #'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>'
self.re_Action = re.compile(r'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re, re.MULTILINE) self.re_Action = re.compile(r'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re, re.MULTILINE)

View File

@ -76,6 +76,7 @@ import SQL
import Database import Database
import FpdbSQLQueries import FpdbSQLQueries
import Configuration import Configuration
from Exceptions import *
VERSION = "0.11" VERSION = "0.11"
@ -98,7 +99,7 @@ class fpdb:
for i in self.tab_names: #todo: check this is valid for i in self.tab_names: #todo: check this is valid
if i==new_tab_name: if i==new_tab_name:
return # we depend on this to not create duplicate tabs, there's no reason to raise an error here? return # we depend on this to not create duplicate tabs, there's no reason to raise an error here?
# raise fpdb_simple.FpdbError("duplicate tab_name not permitted") # raise FpdbError("duplicate tab_name not permitted")
self.tabs.append(new_tab) self.tabs.append(new_tab)
self.tab_names.append(new_tab_name) self.tab_names.append(new_tab_name)
@ -120,7 +121,7 @@ class fpdb:
break break
if tab_no == -1: if tab_no == -1:
raise fpdb_simple.FpdbError("invalid tab_no") raise FpdbError("invalid tab_no")
else: else:
self.main_vbox.remove(self.current_tab) self.main_vbox.remove(self.current_tab)
#self.current_tab.destroy() #self.current_tab.destroy()
@ -243,30 +244,27 @@ class fpdb:
if self.obtain_global_lock(): # returns true if successful if self.obtain_global_lock(): # returns true if successful
#lock_released = False #lock_released = False
try: dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables") diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database " \
diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database " \ +self.db.fdb.database+" on "+self.db.fdb.host+" they will be deleted."
+self.db.fdb.database+" on "+self.db.fdb.host+" they will be deleted." dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
response = dia_confirm.run() response = dia_confirm.run()
dia_confirm.destroy() dia_confirm.destroy()
if response == gtk.RESPONSE_YES: if response == gtk.RESPONSE_YES:
#if self.db.fdb.backend == self.fdb_lock.fdb.MYSQL_INNODB: #if self.db.fdb.backend == self.fdb_lock.fdb.MYSQL_INNODB:
# mysql requires locks on all tables or none - easier to release this lock # mysql requires locks on all tables or none - easier to release this lock
# than lock all the other tables # than lock all the other tables
# ToDo: lock all other tables so that lock doesn't have to be released # ToDo: lock all other tables so that lock doesn't have to be released
# self.release_global_lock() # self.release_global_lock()
# lock_released = True # lock_released = True
self.db.recreate_tables() self.db.recreate_tables()
#else: #else:
# for other dbs use same connection as holds global lock # for other dbs use same connection as holds global lock
# self.fdb_lock.fdb.recreate_tables() # self.fdb_lock.fdb.recreate_tables()
elif response == gtk.RESPONSE_NO: elif response == gtk.RESPONSE_NO:
print 'User cancelled recreating tables' print 'User cancelled recreating tables'
except:
pass
#if not lock_released: #if not lock_released:
self.release_global_lock() self.release_global_lock()
#end def dia_recreate_tables #end def dia_recreate_tables

View File

@ -1,4 +1,5 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*-
#Copyright 2008 Steffen Jobbagy-Felso #Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify #This program is free software: you can redistribute it and/or modify
@ -21,8 +22,18 @@ import sys
import logging import logging
from time import time, strftime from time import time, strftime
use_pool = False
try:
import sqlalchemy.pool as pool
use_pool = True
except:
logging.info("Not using sqlalchemy connection pool.")
import fpdb_simple import fpdb_simple
import FpdbSQLQueries import FpdbSQLQueries
from Exceptions import *
class fpdb_db: class fpdb_db:
MYSQL_INNODB = 2 MYSQL_INNODB = 2
@ -63,13 +74,17 @@ class fpdb_db:
self.database=database self.database=database
if backend==fpdb_db.MYSQL_INNODB: if backend==fpdb_db.MYSQL_INNODB:
import MySQLdb import MySQLdb
if use_pool:
MySQLdb = pool.manage(MySQLdb, pool_size=5)
try: try:
self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True, charset="utf8") self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True)
except: except:
raise fpdb_simple.FpdbError("MySQL connection failed") raise FpdbError("MySQL connection failed")
elif backend==fpdb_db.PGSQL: elif backend==fpdb_db.PGSQL:
import psycopg2 import psycopg2
import psycopg2.extensions import psycopg2.extensions
if use_pool:
psycopg2 = pool.manage(psycopg2, pool_size=5)
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
# If DB connection is made over TCP, then the variables # If DB connection is made over TCP, then the variables
# host, user and password are required # host, user and password are required
@ -87,7 +102,7 @@ class fpdb_db:
pass pass
#msg = "PostgreSQL direct connection to database (%s) failed, trying with user ..." % (database,) #msg = "PostgreSQL direct connection to database (%s) failed, trying with user ..." % (database,)
#print msg #print msg
#raise fpdb_simple.FpdbError(msg) #raise FpdbError(msg)
if not connected: if not connected:
try: try:
self.db = psycopg2.connect(host = host, self.db = psycopg2.connect(host = host,
@ -97,16 +112,19 @@ class fpdb_db:
except: except:
msg = "PostgreSQL connection to database (%s) user (%s) failed." % (database, user) msg = "PostgreSQL connection to database (%s) user (%s) failed." % (database, user)
print msg print msg
raise fpdb_simple.FpdbError(msg) raise FpdbError(msg)
elif backend==fpdb_db.SQLITE: elif backend==fpdb_db.SQLITE:
logging.info("Connecting to SQLite:%(database)s" % {'database':database}) logging.info("Connecting to SQLite:%(database)s" % {'database':database})
import sqlite3 import sqlite3
if use_pool:
sqlite3 = pool.manage(sqlite3, pool_size=1)
else:
logging.warning("SQLite won't work well without 'sqlalchemy' installed.")
self.db = sqlite3.connect(database,detect_types=sqlite3.PARSE_DECLTYPES) self.db = sqlite3.connect(database,detect_types=sqlite3.PARSE_DECLTYPES)
sqlite3.register_converter("bool", lambda x: bool(int(x))) sqlite3.register_converter("bool", lambda x: bool(int(x)))
sqlite3.register_adapter(bool, lambda x: "1" if x else "0") sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
else: else:
raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) raise FpdbError("unrecognised database backend:"+backend)
self.cursor=self.db.cursor() self.cursor=self.db.cursor()
# Set up query dictionary as early in the connection process as we can. # Set up query dictionary as early in the connection process as we can.
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
@ -148,7 +166,7 @@ class fpdb_db:
elif self.backend==4: elif self.backend==4:
return "SQLite" return "SQLite"
else: else:
raise fpdb_simple.FpdbError("invalid backend") raise FpdbError("invalid backend")
#end def get_backend_name #end def get_backend_name
def get_db_info(self): def get_db_info(self):

View File

@ -22,7 +22,6 @@
import os # todo: remove this once import_dir is in fpdb_import import os # todo: remove this once import_dir is in fpdb_import
import sys import sys
from time import time, strftime, sleep from time import time, strftime, sleep
import logging
import traceback import traceback
import math import math
import datetime import datetime
@ -39,21 +38,26 @@ import Database
import fpdb_parse_logic import fpdb_parse_logic
import Configuration import Configuration
import logging, logging.config
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
log = logging.getLogger('importer')
# database interface modules # database interface modules
try: try:
import MySQLdb import MySQLdb
mysqlLibFound=True mysqlLibFound=True
log.debug("Import module: MySQLdb")
except: except:
pass log.debug("Import module: MySQLdb not found")
try: try:
import psycopg2 import psycopg2
pgsqlLibFound=True pgsqlLibFound=True
import psycopg2.extensions import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
log.debug("Import module: psycopg2")
except: except:
pass log.debug("Import module: psycopg2 not found")
class Importer: class Importer:
@ -153,9 +157,9 @@ class Importer:
self.siteIds[site] = result[0][0] self.siteIds[site] = result[0][0]
else: else:
if len(result) == 0: if len(result) == 0:
print "[ERROR] Database ID for %s not found" % site log.error("Database ID for %s not found" % site)
else: else:
print "[ERROR] More than 1 Database ID found for %s - Multiple currencies not implemented yet" % site log.error("[ERROR] More than 1 Database ID found for %s - Multiple currencies not implemented yet" % site)
# Called from GuiBulkImport to add a file or directory. # Called from GuiBulkImport to add a file or directory.
@ -168,7 +172,7 @@ class Importer:
if os.path.isdir(inputPath): if os.path.isdir(inputPath):
for subdir in os.walk(inputPath): for subdir in os.walk(inputPath):
for file in subdir[2]: for file in subdir[2]:
self.addImportFile(os.path.join(inputPath, subdir[0], file), site=site, filter=filter) self.addImportFile(os.path.join(subdir[0], file), site=site, filter=filter)
else: else:
self.addImportFile(inputPath, site=site, filter=filter) self.addImportFile(inputPath, site=site, filter=filter)
#Add a directory of files to filelist #Add a directory of files to filelist
@ -189,7 +193,7 @@ class Importer:
#print " adding file ", file #print " adding file ", file
self.addImportFile(os.path.join(dir, file), site, filter) self.addImportFile(os.path.join(dir, file), site, filter)
else: else:
print "Warning: Attempted to add non-directory: '" + str(dir) + "' as an import directory" log.warning("Attempted to add non-directory: '" + str(dir) + "' as an import directory")
def runImport(self): def runImport(self):
""""Run full import on self.filelist. This is called from GuiBulkImport.py""" """"Run full import on self.filelist. This is called from GuiBulkImport.py"""
@ -199,7 +203,7 @@ class Importer:
# Initial setup # Initial setup
start = datetime.datetime.now() start = datetime.datetime.now()
starttime = time() starttime = time()
print "Started at", start, "--", len(self.filelist), "files to import.", self.settings['dropIndexes'] log.info("Started at %s -- %d files to import. indexes: %s" % (start, len(self.filelist), self.settings['dropIndexes']))
if self.settings['dropIndexes'] == 'auto': if self.settings['dropIndexes'] == 'auto':
self.settings['dropIndexes'] = self.calculate_auto2(self.database, 12.0, 500.0) self.settings['dropIndexes'] = self.calculate_auto2(self.database, 12.0, 500.0)
if 'dropHudCache' in self.settings and self.settings['dropHudCache'] == 'auto': if 'dropHudCache' in self.settings and self.settings['dropHudCache'] == 'auto':
@ -208,7 +212,7 @@ class Importer:
if self.settings['dropIndexes'] == 'drop': if self.settings['dropIndexes'] == 'drop':
self.database.prepareBulkImport() self.database.prepareBulkImport()
else: else:
print "No need to drop indexes." log.debug("No need to drop indexes.")
#print "dropInd =", self.settings['dropIndexes'], " dropHudCache =", self.settings['dropHudCache'] #print "dropInd =", self.settings['dropIndexes'], " dropHudCache =", self.settings['dropHudCache']
if self.settings['threads'] <= 0: if self.settings['threads'] <= 0:
@ -380,12 +384,13 @@ class Importer:
conv = None conv = None
(stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, 0) (stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, 0)
# Load filter, process file, pass returned filename to import_fpdb_file file = file.decode(fpdb_simple.LOCALE_ENCODING)
# Load filter, process file, pass returned filename to import_fpdb_file
if self.settings['threads'] > 0 and self.writeq != None: if self.settings['threads'] > 0 and self.writeq != None:
print "\nConverting " + file + " (" + str(q.qsize()) + ")" log.info("Converting " + file + " (" + str(q.qsize()) + ")")
else: else:
print "\nConverting " + file log.info("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)
@ -413,10 +418,10 @@ class Importer:
else: else:
# conversion didn't work # conversion didn't work
# TODO: appropriate response? # TODO: appropriate response?
return (0, 0, 0, 1, 0, -1) return (0, 0, 0, 1, 0)
else: else:
print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter) log.warning("Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter))
return (0, 0, 0, 1, 0, -1) return (0, 0, 0, 1, 0)
#This will barf if conv.getStatus != True #This will barf if conv.getStatus != True
return (stored, duplicates, partial, errors, ttime) return (stored, duplicates, partial, errors, ttime)
@ -458,7 +463,7 @@ class Importer:
db.commit() db.commit()
ttime = time() - starttime ttime = time() - starttime
if q == None: if q == None:
print "\rTotal stored:", stored, " duplicates:", duplicates, "errors:", errors, " time:", ttime log.info("Total stored: %(stored)d\tduplicates:%(duplicates)d\terrors:%(errors)d\ttime:%(ttime)s" % locals())
if not stored: if not stored:
if duplicates: if duplicates:
@ -533,7 +538,7 @@ class Importer:
if self.callHud: if self.callHud:
#print "call to HUD here. handsId:",handsId #print "call to HUD here. handsId:",handsId
#pipe the Hands.id out to the HUD #pipe the Hands.id out to the HUD
print "sending hand to hud", handsId, "pipe =", self.caller.pipe_to_hud #print "sending hand to hud", handsId, "pipe =", self.caller.pipe_to_hud
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
except fpdb_simple.DuplicateError: except fpdb_simple.DuplicateError:
duplicates += 1 duplicates += 1

View File

@ -22,6 +22,7 @@ import sys
import fpdb_simple import fpdb_simple
import Database import Database
from time import time, strftime from time import time, strftime
from Exceptions import *
#parses a holdem hand #parses a holdem hand
@ -67,7 +68,8 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
tourneyStartTime= handStartTime #todo: read tourney start time tourneyStartTime= handStartTime #todo: read tourney start time
rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0]) rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0])
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(db.get_cursor(), siteID, buyin, fee, knockout, rebuyOrAddon) ## The tourney site id has to be searched because it may already be in db with a TourneyTypeId which is different from the one automatically calculated (Summary import first)
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(db, siteID, siteTourneyNo, buyin, fee, knockout, rebuyOrAddon)
else: else:
siteTourneyNo = -1 siteTourneyNo = -1
buyin = -1 buyin = -1
@ -126,7 +128,7 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
elif lineTypes[i]=="table": elif lineTypes[i]=="table":
tableResult=fpdb_simple.parseTableLine(base, line) tableResult=fpdb_simple.parseTableLine(base, line)
else: else:
raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i]) raise FpdbError("unrecognised lineType:"+lineTypes[i])
maxSeats = tableResult['maxSeats'] maxSeats = tableResult['maxSeats']
tableName = tableResult['tableName'] tableName = tableResult['tableName']

View File

@ -25,6 +25,8 @@ import datetime
import time import time
import re import re
import sys import sys
from Exceptions import *
import locale
import Card import Card
@ -37,17 +39,7 @@ MYSQL_INNODB = 2
PGSQL = 3 PGSQL = 3
SQLITE = 4 SQLITE = 4
class DuplicateError(Exception): LOCALE_ENCODING = locale.getdefaultlocale()[1]
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class FpdbError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
#returns an array of the total money paid. intending to add rebuys/addons here #returns an array of the total money paid. intending to add rebuys/addons here
def calcPayin(count, buyin, fee): def calcPayin(count, buyin, fee):
@ -224,7 +216,7 @@ def fillCardArrays(player_count, base, category, card_values, card_suits):
elif base=="stud": elif base=="stud":
cardCount = 7 cardCount = 7
else: else:
raise fpdb_simple.FpdbError("invalid category:", category) raise FpdbError("invalid category:", category)
for i in xrange(player_count): for i in xrange(player_count):
while (len(card_values[i]) < cardCount): while (len(card_values[i]) < cardCount):
@ -543,7 +535,7 @@ def parseActionType(line):
#parses the ante out of the given line and checks which player paid it, updates antes accordingly. #parses the ante out of the given line and checks which player paid it, updates antes accordingly.
def parseAnteLine(line, isTourney, names, antes): def parseAnteLine(line, isTourney, names, antes):
for i, name in enumerate(names): for i, name in enumerate(names):
if line.startswith(name.encode("latin-1")): if line.startswith(name.encode(LOCALE_ENCODING)):
pos = line.rfind("$") + 1 pos = line.rfind("$") + 1
if not isTourney: if not isTourney:
antes[i] += float2int(line[pos:]) antes[i] += float2int(line[pos:])
@ -705,7 +697,7 @@ def parseHandStartTime(topline):
def findName(line): def findName(line):
pos1 = line.find(":") + 2 pos1 = line.find(":") + 2
pos2 = line.rfind("(") - 1 pos2 = line.rfind("(") - 1
return unicode(line[pos1:pos2], "latin-1") return unicode(line[pos1:pos2], LOCALE_ENCODING)
def parseNames(lines): def parseNames(lines):
return [findName(line) for line in lines] return [findName(line) for line in lines]
@ -822,7 +814,7 @@ def parseTourneyNo(topline):
def parseWinLine(line, names, winnings, isTourney): def parseWinLine(line, names, winnings, isTourney):
#print "parseWinLine: line:",line #print "parseWinLine: line:",line
for i,n in enumerate(names): for i,n in enumerate(names):
n = n.encode("latin-1") n = n.encode(LOCALE_ENCODING)
if line.startswith(n): if line.startswith(n):
if isTourney: if isTourney:
pos1 = line.rfind("collected ") + 10 pos1 = line.rfind("collected ") + 10
@ -951,17 +943,28 @@ def recogniseGametypeID(backend, db, cursor, topline, smallBlindLine, site_id, c
return result[0] return result[0]
#end def recogniseGametypeID #end def recogniseGametypeID
def recogniseTourneyTypeId(cursor, siteId, buyin, fee, knockout, rebuyOrAddon): def recogniseTourneyTypeId(db, siteId, tourneySiteId, buyin, fee, knockout, rebuyOrAddon):
cursor.execute ("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon)) cursor = db.get_cursor()
# First we try to find the tourney itself (by its tourneySiteId) in case it has already been inserted before (by a summary file for instance)
# The reason is that some tourneys may not be identified correctly in the HH toplines (especially Buy-In and Fee which are used to search/create the TourneyTypeId)
#TODO: When the summary file will be dumped to BD, if the tourney is already in, Buy-In/Fee may need an update (e.g. creation of a new type and link to the Tourney)
cursor.execute (db.sql.query['getTourneyTypeIdByTourneyNo'].replace('%s', db.sql.query['placeholder']), (tourneySiteId, siteId))
result=cursor.fetchone() result=cursor.fetchone()
#print "tried SELECTing gametypes.id, result:",result
try: try:
len(result) len(result)
except TypeError:#this means we need to create a new entry except:
cursor.execute("""INSERT INTO TourneyTypes (siteId, buyin, fee, knockout, rebuyOrAddon) VALUES (%s, %s, %s, %s, %s)""", (siteId, buyin, fee, knockout, rebuyOrAddon)) cursor.execute ("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon))
cursor.execute("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon))
result=cursor.fetchone() result=cursor.fetchone()
#print "tried SELECTing gametypes.id, result:",result
try:
len(result)
except TypeError:#this means we need to create a new entry
cursor.execute("""INSERT INTO TourneyTypes (siteId, buyin, fee, knockout, rebuyOrAddon) VALUES (%s, %s, %s, %s, %s)""", (siteId, buyin, fee, knockout, rebuyOrAddon))
cursor.execute("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon))
result=cursor.fetchone()
return result[0] return result[0]
#end def recogniseTourneyTypeId #end def recogniseTourneyTypeId
@ -985,17 +988,17 @@ def recogniseTourneyTypeId(cursor, siteId, buyin, fee, knockout, rebuyOrAddon):
# return result # return result
def recognisePlayerIDs(db, names, site_id): def recognisePlayerIDs(db, names, site_id):
q = "SELECT name,id FROM Players WHERE name=" + " OR name=".join([db.sql.query['placeholder'] for n in names])
c = db.get_cursor() c = db.get_cursor()
q = "SELECT name,id FROM Players WHERE siteid=%d and (name=%s)" %(site_id, " OR name=".join([db.sql.query['placeholder'] for n in names]))
c.execute(q, names) # get all playerids by the names passed in c.execute(q, names) # get all playerids by the names passed in
ids = dict(c.fetchall()) # convert to dict ids = dict(c.fetchall()) # convert to dict
if len(ids) != len(names): if len(ids) != len(names):
notfound = [n for n in names if n not in ids] # make list of names not in database notfound = [n for n in names if n not in ids] # make list of names not in database
if notfound: # insert them into database if notfound: # insert them into database
#q_ins = "INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")" q_ins = "INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")"
#q_ins = q_ins.replace('%s', db.sql.query['placeholder']) q_ins = q_ins.replace('%s', db.sql.query['placeholder'])
c.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", [(n,) for n in notfound]) c.executemany(q_ins, [(n,) for n in notfound])
q2 = "SELECT name,id FROM Players WHERE name=%s" % " OR name=".join(["%s" for n in notfound]) q2 = "SELECT name,id FROM Players WHERE siteid=%d and (name=%s)" % (site_id, " OR name=".join(["%s" for n in notfound]))
q2 = q2.replace('%s', db.sql.query['placeholder']) q2 = q2.replace('%s', db.sql.query['placeholder'])
c.execute(q2, notfound) # get their new ids c.execute(q2, notfound) # get their new ids
tmp = c.fetchall() tmp = c.fetchall()
@ -1032,14 +1035,15 @@ def recognisePlayerIDs(db, names, site_id):
def recognisePlayerNo(line, names, atype): def recognisePlayerNo(line, names, atype):
#print "recogniseplayerno, names:",names #print "recogniseplayerno, names:",names
for i in xrange(len(names)): for i in xrange(len(names)):
encodedName = names[i].encode(LOCALE_ENCODING)
if (atype=="unbet"): if (atype=="unbet"):
if (line.endswith(names[i].encode("latin-1"))): if (line.endswith(encodedName)):
return (i) return (i)
elif (line.startswith("Dealt to ")): elif (line.startswith("Dealt to ")):
#print "recognisePlayerNo, card precut, line:",line #print "recognisePlayerNo, card precut, line:",line
tmp=line[9:] tmp=line[9:]
#print "recognisePlayerNo, card postcut, tmp:",tmp #print "recognisePlayerNo, card postcut, tmp:",tmp
if (tmp.startswith(names[i].encode("latin-1"))): if (tmp.startswith(encodedName)):
return (i) return (i)
elif (line.startswith("Seat ")): elif (line.startswith("Seat ")):
if (line.startswith("Seat 10")): if (line.startswith("Seat 10")):
@ -1047,10 +1051,10 @@ def recognisePlayerNo(line, names, atype):
else: else:
tmp=line[8:] tmp=line[8:]
if (tmp.startswith(names[i].encode("latin-1"))): if (tmp.startswith(encodedName)):
return (i) return (i)
else: else:
if (line.startswith(names[i].encode("latin-1"))): if (line.startswith(encodedName)):
return (i) return (i)
#if we're here we mustve failed #if we're here we mustve failed
raise FpdbError ("failed to recognise player in: "+line+" atype:"+atype) raise FpdbError ("failed to recognise player in: "+line+" atype:"+atype)

57
pyfpdb/logging.conf Normal file
View File

@ -0,0 +1,57 @@
[loggers]
keys=root,parser,importer,config,db
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=fileFormatter,stderrFormatter
[logger_root]
level=INFO
handlers=consoleHandler,fileHandler
[logger_parser]
level=INFO
handlers=consoleHandler,fileHandler
qualname=parser
propagate=0
[logger_importer]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=importer
propagate=0
[logger_config]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=config
propagate=0
[logger_db]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=db
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=stderrFormatter
args=(sys.stderr,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=fileFormatter
args=('logging.out', 'a')
[formatter_fileFormatter]
format=%(asctime)s - %(name)-12s %(levelname)-8s %(message)s
datefmt=
[formatter_stderrFormatter]
format=%(name)-12s: %(levelname)-8s %(message)s
datefmt=

54
pyfpdb/py2exe_setup.py Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
"""setup.py
Py2exe script for fpdb.
"""
# Copyright 2009, Ray E. Barker
#
# 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
########################################################################
#TODO: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate
# include the lib needed to handle png files in mucked
# get rid of all the uneeded libraries (e.g., pyQT)
# think about an installer
from distutils.core import setup
import py2exe
setup(
name = 'fpdb',
description = 'Free Poker DataBase',
version = '0.12',
console = [ {'script': 'fpdb.py', },
{'script': 'HUD_main.py', }
],
options = {'py2exe': {
'packages' :'encodings',
'includes' : 'cairo, pango, pangocairo, atk, gobject, PokerStarsToFpdb',
'excludes' : '_tkagg, _agg2, cocoaagg, fltkagg',
'dll_excludes': 'libglade-2.0-0.dll',
}
},
data_files = ['HUD_config.xml',
'Cards01.png'
]
)

View File

@ -5,6 +5,11 @@ import py
# regression-test-files/fulltilt/nlhe/NLHE-6max-1.txt # regression-test-files/fulltilt/nlhe/NLHE-6max-1.txt
# Sorrowful: start: $8.85 end: $14.70 total: $5.85 # Sorrowful: start: $8.85 end: $14.70 total: $5.85
# 'Canceled' hand
# regression-test-files/fulltilt/lh/Marlin.txt
def checkGameInfo(hhc, header, info): def checkGameInfo(hhc, header, info):
assert hhc.determineGameType(header) == info assert hhc.determineGameType(header) == info

View File

@ -14,7 +14,7 @@ text = ""
hhc = PokerStarsToFpdb.PokerStars(autostart=False) hhc = PokerStarsToFpdb.PokerStars(autostart=False)
h = HoldemOmahaHand(None, "ASite", gametype, text, builtFrom = "Test") h = HoldemOmahaHand(None, "PokerStars", gametype, text, builtFrom = "Test")
h.addPlayer("1", "s0rrow", "100000") h.addPlayer("1", "s0rrow", "100000")
hhc.compilePlayerRegexs(h) hhc.compilePlayerRegexs(h)