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

This commit is contained in:
Mika Bostrom 2009-08-16 00:47:46 +03:00
commit 49022fcf83
22 changed files with 3640 additions and 3462 deletions

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
*.pyc
*~

View File

@ -26,6 +26,12 @@ from HandHistoryConverter import *
# Class for converting Absolute HH format. # Class for converting Absolute HH format.
class Absolute(HandHistoryConverter): class Absolute(HandHistoryConverter):
# Class Variables
sitename = "Absolute"
filetype = "text"
codepage = "cp1252"
siteid = 8
# Static regexes # Static regexes
re_SplitHands = re.compile(r"\n\n\n+") re_SplitHands = re.compile(r"\n\n\n+")
@ -48,24 +54,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 = 8 # 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

@ -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.
@ -73,11 +74,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")
@ -91,6 +98,8 @@ class Site:
self.xpad = node.getAttribute("xpad") self.xpad = node.getAttribute("xpad")
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)
@ -542,6 +551,13 @@ class Config:
if db_server != None: self.supported_databases[db_name].dp_server = db_server if db_server != None: self.supported_databases[db_name].dp_server = db_server
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 = {}
@ -573,14 +589,15 @@ 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?
paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = path
except: except:
paths['hud-defaultPath'] = "default" paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = "default"
paths['bulkImport-defaultPath'] = "default"
return paths return paths
def get_frames(self, site = "PokerStars"): def get_frames(self, site = "PokerStars"):

View File

@ -28,6 +28,7 @@ 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 logging
@ -1020,6 +1021,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):
@ -1127,15 +1144,22 @@ class Database:
sitehandno, sitehandno,
handstart, handstart,
importtime, importtime,
seats,
maxseats, maxseats,
boardcard1, boardcard1,
boardcard2, boardcard2,
boardcard3, boardcard3,
boardcard4, boardcard4,
boardcard5 boardcard5,
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)"""
#--- texture, #--- texture,
#-- playersVpi, #-- playersVpi,
#-- playersAtStreet1, #-- playersAtStreet1,
@ -1148,27 +1172,25 @@ class Database:
#-- street2Raises, #-- street2Raises,
#-- street3Raises, #-- street3Raises,
#-- street4Raises, #-- street4Raises,
#-- street1Pot,
#-- street2Pot,
#-- street3Pot,
#-- street4Pot,
#-- showdownPot
#-- seats, #-- seats,
q = q.replace('%s', self.sql.query['placeholder']) q = q.replace('%s', self.sql.query['placeholder'])
print "DEBUG: p: %s" %p
print "DEBUG: gtid: %s" % p['gameTypeId']
self.cursor.execute(q, ( self.cursor.execute(q, (
p['tableName'], p['tableName'],
p['gameTypeId'],
p['siteHandNo'], p['siteHandNo'],
p['gametypeid'],
p['handStart'], p['handStart'],
datetime.today(), #importtime datetime.today(), #importtime
# len(p['names']), #seats # 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'],
@ -1180,12 +1202,12 @@ class Database:
# 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

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,4 @@
class FpdbParseError(Exception): pass class FpdbParseError(Exception):
def __init__(self,hid=None):
self.hid = hid

View File

@ -27,8 +27,14 @@ from HandHistoryConverter import *
class Fulltilt(HandHistoryConverter): class Fulltilt(HandHistoryConverter):
sitename = "Fulltilt"
filetype = "text"
codepage = "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,7 +45,7 @@ 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\s
(?P<PLAY>Play\sChip\s|PC)? (?P<PLAY>Play\sChip\s|PC)?
@ -47,8 +53,9 @@ class Fulltilt(HandHistoryConverter):
(\((?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>.*?)\n
''', re.VERBOSE) (?:.*?\n(?P<CANCELLED>Hand\s\#(?P=HID)\shas\sbeen\scanceled))?
''', re.VERBOSE|re.DOTALL)
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_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
@ -60,20 +67,6 @@ class Fulltilt(HandHistoryConverter):
mixes = { 'HORSE': 'horse', '7-Game': '7game', 'HOSE': 'hose', 'HA': 'ha'} 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)
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):
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'
@ -118,7 +111,7 @@ follow : whether to tail -f the input"""
if not m: if not m:
return None return None
mg = m.groupdict() mg = m.groupdict()
print mg
# 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,7 +133,7 @@ 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....) #Following function is a hack, we should be dealing with this in readFile (i think correct codepage....)
@ -158,11 +151,11 @@ follow : whether to tail -f the input"""
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.") logging.info("Read no hands.")
return return []
return re.split(self.re_SplitHands, self.obs) 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 +163,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"):
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'))
@ -369,7 +366,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 = 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

@ -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">

View File

@ -32,6 +32,8 @@ import pprint
import DerivedStats import DerivedStats
import Card import Card
log = logging.getLogger("parser")
class Hand(object): class Hand(object):
###############################################################3 ###############################################################3
@ -162,7 +164,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:
@ -187,21 +189,7 @@ db: a connected fpdb_db object"""
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 #Gametypes
gtid = db.getGameTypeId(self.siteId, self.gametype)
print "DEBUG: self.gametype %s" % self.gametype
#Nice way to discover if the game is already listed in the db?
#Also this is using an old method from fpdb_simple - should probably conform to the rest of the inserts
hilo = "h"
if self.gametype['category'] in ['studhilo', 'omahahilo']:
hilo = "s"
elif self.gametype['category'] in ['razz','27_3draw','badugi']:
hilo = "l"
#FIXME - the two zeros are small_bet and big_bet for limit
gtid = db.insertGameTypes( (self.siteId, self.gametype['type'], self.gametype['base'],
self.gametype['category'], self.gametype['limitType'], hilo,
self.gametype['sb'], self.gametype['bb'], 0, 0) )
# 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
@ -210,14 +198,11 @@ db: a connected fpdb_db object"""
hh = {} hh = {}
hh['siteHandNo'] = self.handid hh['siteHandNo'] = self.handid
hh['handStart'] = self.starttime hh['handStart'] = self.starttime
hh['gameTypeId'] = gtid
# seats TINYINT NOT NULL, # seats TINYINT NOT NULL,
hh['tableName'] = self.tablename 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,
# boardcard3 smallint,
# boardcard4 smallint,
# boardcard5 smallint,
# Flop turn and river may all be empty - add (likely) too many elements and trim with range # 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'] 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]] cards = [Card.encodeCard(c) for c in boardcards[0:5]]
@ -227,7 +212,6 @@ db: a connected fpdb_db object"""
hh['boardcard4'] = cards[3] hh['boardcard4'] = cards[3]
hh['boardcard5'] = cards[4] hh['boardcard5'] = cards[4]
print hh
# 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
@ -251,17 +235,14 @@ 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
#print hh
handid = db.storeHand(hh) handid = db.storeHand(hh)
# HandsPlayers - ? ... Do we fix winnings? # HandsPlayers - ? ... Do we fix winnings?
# Tourneys ? # Tourneys ?
@ -282,7 +263,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])
@ -298,9 +279,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]:
@ -310,7 +291,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
@ -321,7 +302,7 @@ 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" % ('ANTES', 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['ANTES'][player].append(Decimal(ante))
@ -340,7 +321,7 @@ 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.bets['PREFLOP'][player].append(Decimal(amount))
@ -362,7 +343,7 @@ If a player has None chips he won't be added."""
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:
@ -432,7 +413,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)
@ -443,7 +424,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))
@ -462,7 +443,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)
@ -477,7 +458,7 @@ Add a raise on [street] by [player] to [amountTo]
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:
@ -491,7 +472,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:
@ -499,7 +480,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"""
@ -541,7 +521,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
@ -583,6 +563,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
@ -620,7 +603,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']
@ -648,6 +631,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)
@ -659,9 +643,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
@ -672,6 +656,18 @@ 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, fh=sys.__stdout__):
from nevow import tags as T from nevow import tags as T
@ -773,7 +769,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]))
@ -896,6 +892,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)
@ -916,7 +913,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)
@ -943,7 +940,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])
@ -956,6 +953,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.
@ -1061,9 +1066,9 @@ class StudHand(Hand):
for street in self.actionStreets: for street in self.actionStreets:
if street == 'ANTES': continue # OMG--sometime someone folds in the ante round if street == 'ANTES': 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)
@ -1094,7 +1099,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)
@ -1108,7 +1113,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']
@ -1126,7 +1131,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)
@ -1134,6 +1139,14 @@ 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.
@ -1292,11 +1305,12 @@ 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.total = None
self.sym = u'$' # this is the default currency symbol 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
@ -1313,6 +1327,14 @@ class Pot(object):
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())
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())

View File

@ -20,7 +20,6 @@ import Hand
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
@ -31,19 +30,37 @@ 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("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
@ -60,54 +77,72 @@ class HandHistoryConverter():
# 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) and 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 = codecs.open(self.out_path, 'w', 'cp1252') 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
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) log.info("Tailing '%s'" % self.in_path)
else: for handText in self.tailHands():
handsList = self.allHandsAsList() try:
logging.info("Parsing %d hands" % len(handsList)) self.processHand(handText)
nBadHangs = 0 numHands+=1
for handText in handsList: except FpdbParseError, e:
try: numErrors+=1
self.processedHands.append(self.processHand(handText)) log.warning("Failed to convert hand %s" % e.hid)
except Exception, e: # TODO: it's better to replace it with s-t like HhcEception log.debug(handText)
nBadHangs += 1 else:
logging.error("Caught exception while parsing hand: %s" % str(e)) handsList = self.allHandsAsList()
numHands = len(handsList) - nBadHangs log.info("Parsing %d hands" % len(handsList))
endtime = time.time() for handText in handsList:
print "read %d hands in %.3f seconds" % (numHands, endtime - starttime) try:
if self.out_fh != sys.stdout: self.processedHands.append(self.processHand(handText))
self.out_fh.close() 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))
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):
@ -134,7 +169,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:
@ -179,13 +214,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
@ -200,14 +235,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
@ -216,7 +251,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 :)
@ -342,26 +377,23 @@ or None if we fail to get the info """
return hands return hands
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))
in_fh = codecs.open(self.in_path, 'r', self.codepage) in_fh = codecs.open(self.in_path, 'r', self.codepage)
in_fh.seek(self.index) in_fh.seek(self.index)
self.obs = in_fh.read() log.debug("Opened in_path: '%s' with %s" % (self.in_path, self.codepage))
self.index = in_fh.tell() self.obs = in_fh.read()
in_fh.close() self.index = in_fh.tell()
in_fh.close()
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."""

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.
@ -95,6 +96,8 @@ class Hud:
if my_import == None: if my_import == None:
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):
@ -146,6 +149,21 @@ class Hud:
self.item4.connect("activate", self.debug_stat_windows) self.item4.connect("activate", self.debug_stat_windows)
self.item4.show() self.item4.show()
self.item5 = gtk.MenuItem('Set max seats')
self.menu.append(self.item5)
self.item5.show()
self.maxSeatsMenu = gtk.Menu()
self.item5.set_submenu(self.maxSeatsMenu)
for i in range(2, 11, 1):
item = gtk.MenuItem('%d-max' % i)
item.ms = i
self.maxSeatsMenu.append(item)
item.connect("activate", self.change_max_seats)
item.show()
setattr(self, 'maxSeatsMenuItem%d' % (i-1), item)
self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) self.ebox.connect_object("button-press-event", self.on_button_press, self.menu)
self.main_window.show_all() self.main_window.show_all()
@ -162,7 +180,19 @@ class Hud:
self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) # self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) #
self.update_table_position() self.update_table_position()
def change_max_seats(self, widget):
if self.max != widget.ms:
print 'change_max_seats', widget.ms
self.max = widget.ms
try:
self.kill()
self.create(*self.creation_attrs)
self.update(self.hand, self.config)
except Exception, e:
print "Expcetion:",str(e)
pass
def update_table_position(self): def update_table_position(self):
if os.name == 'nt': if os.name == 'nt':
if not win32gui.IsWindow(self.table.number): if not win32gui.IsWindow(self.table.number):
@ -270,6 +300,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()

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

@ -2,17 +2,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright 2009, Grigorij Indigirkin # Copyright 2009, Grigorij Indigirkin
# #
# 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
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
@ -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+
@ -75,7 +80,7 @@ class PartyPoker(HandHistoryConverter):
Seat\s(?P<SEAT>\d+):\s Seat\s(?P<SEAT>\d+):\s
(?P<PNAME>.*)\s (?P<PNAME>.*)\s
\(\s*\$?(?P<CASH>[0-9,.]+)\s*(?:USD|)\s*\) \(\s*\$?(?P<CASH>[0-9,.]+)\s*(?:USD|)\s*\)
""" , """ ,
re.VERBOSE) re.VERBOSE)
re_HandInfo = re.compile(""" re_HandInfo = re.compile("""
@ -85,55 +90,61 @@ class PartyPoker(HandHistoryConverter):
(?:[^ ]+\s+\#(?P<MTTTABLE>\d+).+)? # table number for mtt (?:[^ ]+\s+\#(?P<MTTTABLE>\d+).+)? # table number for mtt
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct \((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
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_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(
'^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'
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 ''}
for key in ('CUR_SYM', 'CUR'): for key in ('CUR_SYM', 'CUR'):
subst[key] = re.escape(subst[key]) subst[key] = re.escape(subst[key])
logging.debug("player_re: " + subst['PLYR']) log.debug("player_re: '%s'" % subst['PLYR'])
logging.debug("CUR_SYM: " + subst['CUR_SYM']) log.debug("CUR_SYM: '%s'" % subst['CUR_SYM'])
logging.debug("CUR: " + subst['CUR']) 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)
# NOTE: comma is used as a fraction part delimeter in re below
self.re_PostDead = re.compile(
r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
self.re_Antes = re.compile( self.re_Antes = re.compile(
r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.0-9]+) ?%(CUR)s\]\." % subst, r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.0-9]+) ?%(CUR)s\]" % subst,
re.MULTILINE) 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,
@ -141,28 +152,30 @@ follow : whether to tail -f the input"""
self.re_Action = re.compile(r""" self.re_Action = re.compile(r"""
^%(PLYR)s\s+(?P<ATYPE>bets|checks|raises|calls|folds|is\sall-In) ^%(PLYR)s\s+(?P<ATYPE>bets|checks|raises|calls|folds|is\sall-In)
(?:\s+\[%(CUR_SYM)s(?P<BET>[.,\d]+)\s*%(CUR)s\])? (?:\s+\[%(CUR_SYM)s(?P<BET>[.,\d]+)\s*%(CUR)s\])?
""" % subst, """ % subst,
re.MULTILINE|re.VERBOSE) re.MULTILINE|re.VERBOSE)
self.re_ShownCards = re.compile( self.re_ShownCards = re.compile(
r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re + r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re +
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)
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
@ -174,47 +187,45 @@ follow : whether to tail -f the input"""
self._gameType = m self._gameType = m
return self._gameType return self._gameType
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: gametype dict is:
{'limitType': xxx, 'base': xxx, 'category': xxx}""" {'limitType': xxx, 'base': xxx, 'category': xxx}"""
logging.debug(self.ParsingException().wrapHh( handText )) 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)
@ -223,37 +234,65 @@ follow : whether to tail -f the input"""
info['type'] = 'tour' info['type'] = 'tour'
else: else:
info['type'] = 'ring' info['type'] = 'ring'
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
# to be honest, party doesn't save play money hh
info['currency'] = currencies[mg['CURRENCY']] 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 = ??? # FIXME: it's dirty hack
hand.mixed = None # party doesnt subtract uncalled money from commited money
# so hand.totalPot calculation has to be redefined
logging.debug("readHandInfo: %s" % info) 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
@ -268,7 +307,7 @@ follow : whether to tail -f the input"""
# FIXME: some timezone correction required # 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')])
if key == 'HID': if key == 'HID':
hand.handid = info[key] hand.handid = info[key]
if key == 'TABLE': if key == 'TABLE':
@ -278,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):
@ -293,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>.+?)"
@ -317,22 +352,17 @@ follow : whether to tail -f the input"""
, hand.handText,re.DOTALL) , hand.handText,re.DOTALL)
hand.addStreets(m) hand.addStreets(m)
def readCommunityCards(self, hand, street): def readCommunityCards(self, hand, street):
if street in ('FLOP','TURN','RIVER'): if street in ('FLOP','TURN','RIVER'):
m = self.re_Board.search(hand.streets[street]) m = self.re_Board.search(hand.streets[street])
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:
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:
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':
@ -342,39 +372,44 @@ follow : whether to tail -f the input"""
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind except: # no small blind
hand.addBlind(None, None, None) hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.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'))
else:
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:
# 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
if hand.buttonpos == 0: if hand.buttonpos == 0:
self.readButton(hand) self.readButton(hand)
# NOTE: code below depends on Hand's implementation # NOTE: code below depends on Hand's implementation
# playersMap - dict {seat: (pname,stack)} # playersMap - dict {seat: (pname,stack)}
playersMap = dict([(f[0], f[1:3]) for f in hand.players]) playersMap = dict([(f[0], f[1:3]) for f in hand.players])
maxSeat = max(playersMap) maxSeat = max(playersMap)
def findFirstNonEmptySeat(startSeat): def findFirstNonEmptySeat(startSeat):
while startSeat not in playersMap: while startSeat not in playersMap:
if startSeat >= maxSeat: if startSeat >= maxSeat:
startSeat = 0 startSeat = 0
startSeat += 1 startSeat += 1
return startSeat return startSeat
smartMin = lambda A,B: A if float(A) <= float(B) else B smartMin = lambda A,B: A if float(A) <= float(B) else B
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])
hand.addBlind(playersMap[smallBlindSeat][0], 'small blind', blind) hand.addBlind(playersMap[smallBlindSeat][0], 'small blind', blind)
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):
# we need to grab hero's cards # we need to grab hero's cards
@ -391,18 +426,39 @@ 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')
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) amount = clearMoneyString(action.group('BET')) if action.group('BET') else None
elif action.group('ATYPE') == 'calls': actionType = action.group('ATYPE')
hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'bets': if actionType == 'is all-In':
hand.addBet( street, action.group('PNAME'), action.group('BET') ) # party's allin can mean either raise or bet or call
elif action.group('ATYPE') == 'folds': Bp = hand.lastBet[street]
hand.addFold( street, action.group('PNAME')) if Bp == 0:
elif action.group('ATYPE') == 'checks': actionType = 'bets'
hand.addCheck( street, action.group('PNAME')) elif Bp < Decimal(amount):
actionType = 'raises'
else:
actionType = 'calls'
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):
@ -411,7 +467,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=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):
@ -423,22 +479,22 @@ follow : whether to tail -f the input"""
else: mucked = True else: mucked = True
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()
@ -454,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,8 +27,12 @@ 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
@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -230,11 +230,20 @@ 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)
def getDefaultEncoding():
# FIXME: if somebody know better place fot this function - move it
# FIXME: it's better to use GetCPInfo for windows http://msdn.microsoft.com/en-us/library/dd318078(VS.85).aspx
# but i have no idea, how to call it
import locale
return locale.getpreferredencoding()
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.lower() in titles[hwnd].lower(): continue if not tablename.lower() in titles[hwnd].decode(getDefaultEncoding()).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

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

@ -1,271 +1,271 @@
# Code from http://ender.snowburst.org:4747/~jjohns/interlocks.py # Code from http://ender.snowburst.org:4747/~jjohns/interlocks.py
# Thanks JJ! # Thanks JJ!
import sys import sys
import os, os.path import os, os.path
import subprocess import subprocess
import time import time
import signal import signal
import base64 import base64
InterProcessLock = None InterProcessLock = None
""" """
Just use me like a thread lock. acquire() / release() / locked() Just use me like a thread lock. acquire() / release() / locked()
Differences compared to thread locks: Differences compared to thread locks:
1. By default, acquire()'s wait parameter is false. 1. By default, acquire()'s wait parameter is false.
2. When acquire fails, SingleInstanceError is thrown instead of simply returning false. 2. When acquire fails, SingleInstanceError is thrown instead of simply returning false.
3. acquire() can take a 3rd parameter retry_time, which, if wait is True, tells the locking 3. acquire() can take a 3rd parameter retry_time, which, if wait is True, tells the locking
mechanism how long to sleep between retrying the lock. Has no effect for unix/InterProcessLockFcntl. mechanism how long to sleep between retrying the lock. Has no effect for unix/InterProcessLockFcntl.
Differences in fpdb version to JJ's original: Differences in fpdb version to JJ's original:
1. Changed acquire() to return false like other locks 1. Changed acquire() to return false like other locks
2. Made acquire fail if same process already has the lock 2. Made acquire fail if same process already has the lock
""" """
class SingleInstanceError(RuntimeError): class SingleInstanceError(RuntimeError):
"Thrown when you try to acquire an InterProcessLock and another version of the process is already running." "Thrown when you try to acquire an InterProcessLock and another version of the process is already running."
class InterProcessLockBase: class InterProcessLockBase:
def __init__(self, name=None ): def __init__(self, name=None ):
self._has_lock = False self._has_lock = False
if not name: if not name:
name = sys.argv[0] name = sys.argv[0]
self.name = name self.name = name
def getHashedName(self): def getHashedName(self):
return base64.b64encode(self.name).replace('=','') return base64.b64encode(self.name).replace('=','')
def acquire_impl(self, wait): abstract def acquire_impl(self, wait): abstract
def acquire(self, wait=False, retry_time=1): def acquire(self, wait=False, retry_time=1):
if self._has_lock: # make sure 2nd acquire in same process fails if self._has_lock: # make sure 2nd acquire in same process fails
return False return False
while not self._has_lock: while not self._has_lock:
try: try:
self.acquire_impl(wait) self.acquire_impl(wait)
self._has_lock = True self._has_lock = True
#print 'i have the lock' #print 'i have the lock'
except SingleInstanceError: except SingleInstanceError:
if not wait: if not wait:
# raise # change back to normal acquire functionality, sorry JJ! # raise # change back to normal acquire functionality, sorry JJ!
return False return False
time.sleep(retry_time) time.sleep(retry_time)
return True return True
def release(self): def release(self):
self.release_impl() self.release_impl()
self._has_lock = False self._has_lock = False
def locked(self): def locked(self):
if self._has_lock: if self._has_lock:
return True return True
try: try:
self.acquire() self.acquire()
self.release() self.release()
return False return False
except SingleInstanceError: except SingleInstanceError:
return True return True
LOCK_FILE_DIRECTORY = '/tmp' LOCK_FILE_DIRECTORY = '/tmp'
class InterProcessLockFcntl(InterProcessLockBase): class InterProcessLockFcntl(InterProcessLockBase):
def __init__(self, name=None): def __init__(self, name=None):
InterProcessLockBase.__init__(self, name) InterProcessLockBase.__init__(self, name)
self.lockfd = 0 self.lockfd = 0
self.lock_file_name = os.path.join(LOCK_FILE_DIRECTORY, self.getHashedName() + '.lck') self.lock_file_name = os.path.join(LOCK_FILE_DIRECTORY, self.getHashedName() + '.lck')
assert(os.path.isdir(LOCK_FILE_DIRECTORY)) assert(os.path.isdir(LOCK_FILE_DIRECTORY))
# This is the suggested way to get a safe file name, but I like having a descriptively named lock file. # This is the suggested way to get a safe file name, but I like having a descriptively named lock file.
def getHashedName(self): def getHashedName(self):
import re import re
bad_filename_character_re = re.compile(r'/\?<>\\\:;\*\|\'\"\^=\.\[\]') bad_filename_character_re = re.compile(r'/\?<>\\\:;\*\|\'\"\^=\.\[\]')
return bad_filename_character_re.sub('_',self.name) return bad_filename_character_re.sub('_',self.name)
def acquire_impl(self, wait): def acquire_impl(self, wait):
self.lockfd = open(self.lock_file_name, 'w') self.lockfd = open(self.lock_file_name, 'w')
fcntrl_options = fcntl.LOCK_EX fcntrl_options = fcntl.LOCK_EX
if not wait: if not wait:
fcntrl_options |= fcntl.LOCK_NB fcntrl_options |= fcntl.LOCK_NB
try: try:
fcntl.flock(self.lockfd, fcntrl_options) fcntl.flock(self.lockfd, fcntrl_options)
except IOError: except IOError:
self.lockfd.close() self.lockfd.close()
self.lockfd = 0 self.lockfd = 0
raise SingleInstanceError('Could not acquire exclusive lock on '+self.lock_file_name) raise SingleInstanceError('Could not acquire exclusive lock on '+self.lock_file_name)
def release_impl(self): def release_impl(self):
fcntl.lockf(self.lockfd, fcntl.LOCK_UN) fcntl.lockf(self.lockfd, fcntl.LOCK_UN)
self.lockfd.close() self.lockfd.close()
self.lockfd = 0 self.lockfd = 0
try: try:
os.unlink(self.lock_file_name) os.unlink(self.lock_file_name)
except IOError: except IOError:
# We don't care about the existence of the file too much here. It's the flock() we care about, # We don't care about the existence of the file too much here. It's the flock() we care about,
# And that should just go away magically. # And that should just go away magically.
pass pass
class InterProcessLockWin32(InterProcessLockBase): class InterProcessLockWin32(InterProcessLockBase):
def __init__(self, name=None): def __init__(self, name=None):
InterProcessLockBase.__init__(self, name) InterProcessLockBase.__init__(self, name)
self.mutex = None self.mutex = None
def acquire_impl(self,wait): def acquire_impl(self,wait):
self.mutex = win32event.CreateMutex(None, 0, self.getHashedName()) self.mutex = win32event.CreateMutex(None, 0, self.getHashedName())
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS: if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
self.mutex.Close() self.mutex.Close()
self.mutex = None self.mutex = None
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name) raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
def release_impl(self): def release_impl(self):
self.mutex.Close() self.mutex.Close()
class InterProcessLockSocket(InterProcessLockBase): class InterProcessLockSocket(InterProcessLockBase):
def __init__(self, name=None): def __init__(self, name=None):
InterProcessLockBase.__init__(self, name) InterProcessLockBase.__init__(self, name)
self.socket = None self.socket = None
self.portno = 65530 - abs(self.getHashedName().__hash__()) % 32749 self.portno = 65530 - abs(self.getHashedName().__hash__()) % 32749
def acquire_impl(self, wait): def acquire_impl(self, wait):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
self.socket.bind(('127.0.0.1', self.portno)) self.socket.bind(('127.0.0.1', self.portno))
except socket.error: except socket.error:
self.socket.close() self.socket.close()
self.socket = None self.socket = None
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name) raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
def release_impl(self): def release_impl(self):
self.socket.close() self.socket.close()
self.socket = None self.socket = None
# Set InterProcessLock to the correct type given the sysem parameters available # Set InterProcessLock to the correct type given the sysem parameters available
try: try:
import fcntl import fcntl
InterProcessLock = InterProcessLockFcntl InterProcessLock = InterProcessLockFcntl
except ImportError: except ImportError:
try: try:
import win32event import win32event
import win32api import win32api
import winerror import winerror
InterProcessLock = InterProcessLockWin32 InterProcessLock = InterProcessLockWin32
except ImportError: except ImportError:
import socket import socket
InterProcessLock = InterProcessLockSocket InterProcessLock = InterProcessLockSocket
def test_construct(): def test_construct():
""" """
# Making the name of the test unique so it can be executed my multiple users on the same machine. # Making the name of the test unique so it can be executed my multiple users on the same machine.
>>> test_name = 'InterProcessLockTest' +str(os.getpid()) + str(time.time()) >>> test_name = 'InterProcessLockTest' +str(os.getpid()) + str(time.time())
>>> lock1 = InterProcessLock(name=test_name) >>> lock1 = InterProcessLock(name=test_name)
>>> lock1.acquire() >>> lock1.acquire()
>>> lock2 = InterProcessLock(name=test_name) >>> lock2 = InterProcessLock(name=test_name)
>>> lock3 = InterProcessLock(name=test_name) >>> lock3 = InterProcessLock(name=test_name)
# Since lock1 is locked, other attempts to acquire it fail. # Since lock1 is locked, other attempts to acquire it fail.
>>> lock2.acquire() >>> lock2.acquire()
Traceback (most recent call last): Traceback (most recent call last):
... ...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> lock3.acquire() >>> lock3.acquire()
Traceback (most recent call last): Traceback (most recent call last):
... ...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release the lock and let lock2 have it. # Release the lock and let lock2 have it.
>>> lock1.release() >>> lock1.release()
>>> lock2.acquire() >>> lock2.acquire()
>>> lock3.acquire() >>> lock3.acquire()
Traceback (most recent call last): Traceback (most recent call last):
... ...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release it and give it back to lock1 # Release it and give it back to lock1
>>> lock2.release() >>> lock2.release()
>>> lock1.acquire() >>> lock1.acquire()
>>> lock2.acquire() >>> lock2.acquire()
Traceback (most recent call last): Traceback (most recent call last):
... ...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Test lock status # Test lock status
>>> lock2.locked() >>> lock2.locked()
True True
>>> lock3.locked() >>> lock3.locked()
True True
>>> lock1.locked() >>> lock1.locked()
True True
>>> lock1.release() >>> lock1.release()
>>> lock2.locked() >>> lock2.locked()
False False
>>> lock3.locked() >>> lock3.locked()
False False
>>> lock1.locked() >>> lock1.locked()
False False
>>> if os.name == 'posix': >>> if os.name == 'posix':
... def os_independent_kill(pid): ... def os_independent_kill(pid):
... import signal ... import signal
... os.kill(pid, signal.SIGKILL) ... os.kill(pid, signal.SIGKILL)
... else: ... else:
... assert(os.name == 'nt') ... assert(os.name == 'nt')
... def os_independent_kill(pid): ... def os_independent_kill(pid):
... ''' http://www.python.org/doc/faq/windows/#how-do-i-emulate-os-kill-in-windows ''' ... ''' http://www.python.org/doc/faq/windows/#how-do-i-emulate-os-kill-in-windows '''
... import win32api ... import win32api
... import win32con ... import win32con
... import pywintypes ... import pywintypes
... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid) ... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid)
... return (0 != win32api.TerminateProcess(handle, 0)) ... return (0 != win32api.TerminateProcess(handle, 0))
# Test to acquire the lock in another process. # Test to acquire the lock in another process.
>>> def execute(cmd): >>> def execute(cmd):
... cmd = 'import time;' + cmd + 'time.sleep(10);' ... cmd = 'import time;' + cmd + 'time.sleep(10);'
... process = subprocess.Popen([sys.executable, '-c', cmd]) ... process = subprocess.Popen([sys.executable, '-c', cmd])
... pid = process.pid ... pid = process.pid
... time.sleep(2) # quick hack, but we test synchronization in the end ... time.sleep(2) # quick hack, but we test synchronization in the end
... return pid ... return pid
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();') >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire() >>> lock1.acquire()
Traceback (most recent call last): Traceback (most recent call last):
... ...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid) >>> os_independent_kill(pid)
>>> time.sleep(1) >>> time.sleep(1)
>>> lock1.acquire() >>> lock1.acquire()
>>> lock1.release() >>> lock1.release()
# Testing wait # Testing wait
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();') >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire() >>> lock1.acquire()
Traceback (most recent call last): Traceback (most recent call last):
... ...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid) >>> os_independent_kill(pid)
>>> lock1.acquire(True) >>> lock1.acquire(True)
>>> lock1.release() >>> lock1.release()
""" """
pass pass
if __name__=='__main__': if __name__=='__main__':
import doctest import doctest
doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL) doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)

40
pyfpdb/logging.conf Normal file
View File

@ -0,0 +1,40 @@
[loggers]
keys=root,parser
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=fileFormatter,stderrFormatter
[logger_root]
level=INFO
handlers=consoleHandler,fileHandler
[logger_parser]
level=INFO
# set to NOTSET or DEBUG to see everything the parser does
handlers=consoleHandler,fileHandler
qualname=parser
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=stderrFormatter
args=(sys.stderr,)
[handler_fileHandler]
class=FileHandler
level=INFO
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=

View File

@ -4,7 +4,12 @@ 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)

View File

@ -1,27 +1,27 @@
# script to update indexes on mysql (+other?) database # script to update indexes on mysql (+other?) database
select '1. Dropping indexes' as ' '; select '1. Dropping indexes' as ' ';
select 'Can''t drop messages indicate index already gone' as ' '; select 'Can''t drop messages indicate index already gone' as ' ';
ALTER TABLE `fpdb`.`Settings` DROP INDEX `id`; ALTER TABLE `fpdb`.`Settings` DROP INDEX `id`;
ALTER TABLE `fpdb`.`Sites` DROP INDEX `id`; ALTER TABLE `fpdb`.`Sites` DROP INDEX `id`;
ALTER TABLE `fpdb`.`Gametypes` DROP INDEX `id`; ALTER TABLE `fpdb`.`Gametypes` DROP INDEX `id`;
ALTER TABLE `fpdb`.`Players` DROP INDEX `id`; ALTER TABLE `fpdb`.`Players` DROP INDEX `id`;
ALTER TABLE `fpdb`.`Autorates` DROP INDEX `id`; ALTER TABLE `fpdb`.`Autorates` DROP INDEX `id`;
ALTER TABLE `fpdb`.`Hands` DROP INDEX `id`; ALTER TABLE `fpdb`.`Hands` DROP INDEX `id`;
ALTER TABLE `fpdb`.`BoardCards` DROP INDEX `id`; ALTER TABLE `fpdb`.`BoardCards` DROP INDEX `id`;
ALTER TABLE `fpdb`.`TourneyTypes` DROP INDEX `id`; ALTER TABLE `fpdb`.`TourneyTypes` DROP INDEX `id`;
ALTER TABLE `fpdb`.`Tourneys` DROP INDEX `id`; ALTER TABLE `fpdb`.`Tourneys` DROP INDEX `id`;
ALTER TABLE `fpdb`.`TourneysPlayers` DROP INDEX `id`; ALTER TABLE `fpdb`.`TourneysPlayers` DROP INDEX `id`;
ALTER TABLE `fpdb`.`HandsPlayers` DROP INDEX `id`; ALTER TABLE `fpdb`.`HandsPlayers` DROP INDEX `id`;
ALTER TABLE `fpdb`.`HandsActions` DROP INDEX `id`; ALTER TABLE `fpdb`.`HandsActions` DROP INDEX `id`;
ALTER TABLE `fpdb`.`HudCache` DROP INDEX `id`; ALTER TABLE `fpdb`.`HudCache` DROP INDEX `id`;
select '2. Adding extra indexes on useful fields' as ' '; select '2. Adding extra indexes on useful fields' as ' ';
select 'Duplicate key name messages indicate new indexes already there' as ' '; select 'Duplicate key name messages indicate new indexes already there' as ' ';
ALTER TABLE `fpdb`.`tourneys` ADD INDEX `siteTourneyNo`(`siteTourneyNo`); ALTER TABLE `fpdb`.`tourneys` ADD INDEX `siteTourneyNo`(`siteTourneyNo`);
ALTER TABLE `fpdb`.`hands` ADD INDEX `siteHandNo`(`siteHandNo`); ALTER TABLE `fpdb`.`hands` ADD INDEX `siteHandNo`(`siteHandNo`);
ALTER TABLE `fpdb`.`players` ADD INDEX `name`(`name`); ALTER TABLE `fpdb`.`players` ADD INDEX `name`(`name`);