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

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

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

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

View File

@ -26,6 +26,11 @@ from HandHistoryConverter import *
class Betfair(HandHistoryConverter):
sitename = 'Betfair'
filetype = "text"
codepage = "cp1252"
siteId = 7 # Needs to match id entry in Sites database
# 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_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_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):
players = set([player[1] for player in hand.players])

View File

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

View File

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

View File

@ -26,6 +26,11 @@ from HandHistoryConverter import *
class Everleaf(HandHistoryConverter):
sitename = 'Everleaf'
filetype = "text"
codepage = "cp1252"
siteId = 3 # Needs to match id entry in Sites database
# Static regexes
re_SplitHands = 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>.+) \]")
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):
players = set([player[1] for player in hand.players])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,16 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#Copyright 2008 Carl Gherardi
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
@ -32,6 +33,8 @@ import pprint
import DerivedStats
import Card
log = logging.getLogger("parser")
class Hand(object):
###############################################################3
@ -61,6 +64,15 @@ class Hand(object):
self.fee = None # the Database code is looking for this one .. ?
self.level = None
self.mixed = None
# Some attributes for hand from a tourney
self.speed = "Normal"
self.isRebuy = False
self.isKO = False
self.isHU = False
self.isMatrix = False
self.isShootout = False
self.tourneyComment = None
self.seating = []
self.players = []
self.posted = []
@ -118,7 +130,7 @@ class Hand(object):
("MIXED", self.mixed),
("LASTBET", self.lastBet),
("ACTION STREETS", self.actionStreets),
("STREETS", self.streets),
("STREETS", self.streets),
("ALL STREETS", self.allStreets),
("COMMUNITY STREETS", self.communityStreets),
("HOLE STREETS", self.holeStreets),
@ -131,7 +143,7 @@ class Hand(object):
("RAKE", self.rake),
("START TIME", self.starttime),
)
structs = ( ("PLAYERS", self.players),
("STACKS", self.stacks),
("POSTED", self.posted),
@ -162,7 +174,7 @@ shown whether they were revealed at showdown
mucked whether they were mucked at showdown
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:
self.checkPlayerExists(player)
except FpdbParseError, e:
@ -185,31 +197,31 @@ db: a connected fpdb_db object"""
# TODO:
# Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
#Gametypes
gtid = db.getGameTypeId(self.siteId, self.gametype)
# HudCache data to come from DerivedStats class
# HandsActions - all actions for all players for all streets - self.actions
# BoardCards - Skip - no longer necessary
# Hands - Summary information of hand indexed by handId - gameinfo
#hh['siteHandNo'] = self.handid
# gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
#
#hh['handStart'] = self.starttime
# seats TINYINT NOT NULL,
#
#hh['tableName'] = self.tablenam
#hh['maxSeats'] = self.maxseats
# boardcard1 smallint, /* 0=none, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As */
# 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
# boardcards = board['FLOP'] + board['TURN'] + board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
# cards = [Card.cardFromValueSuit(v,s) for v,s in boardcards[0:4]]
# hh['boardcard1'] = cards[0]
# hh['boardcard2'] = cards[1]
# hh['boardcard3'] = cards[2]
# hh['boardcard4'] = cards[3]
# hh['boardcard5'] = cards[4]
#This should be moved to prepInsert
hh = {}
hh['siteHandNo'] = self.handid
hh['handStart'] = self.starttime
hh['gameTypeId'] = gtid
# seats TINYINT NOT NULL,
hh['tableName'] = self.tablename
hh['maxSeats'] = self.maxseats
hh['seats'] = len(sqlids)
# Flop turn and river may all be empty - add (likely) too many elements and trim with range
boardcards = self.board['FLOP'] + self.board['TURN'] + self.board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
cards = [Card.encodeCard(c) for c in boardcards[0:5]]
hh['boardcard1'] = cards[0]
hh['boardcard2'] = cards[1]
hh['boardcard3'] = cards[2]
hh['boardcard4'] = cards[3]
hh['boardcard5'] = cards[4]
# texture smallint,
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
# Needs to be recorded
@ -233,18 +245,15 @@ db: a connected fpdb_db object"""
# Needs to be recorded
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
# Needs to be recorded
# street1Pot INT, /* pot size at flop/street4 */
# Needs to be recorded
# street2Pot INT, /* pot size at turn/street5 */
# Needs to be recorded
# 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 */
#print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % self.getStreetTotals()
#FIXME: Pot size still in decimal, needs to be converted to cents
(hh['street1Pot'], hh['street2Pot'], hh['street3Pot'], hh['street4Pot'], hh['showdownPot']) = self.getStreetTotals()
# comment TEXT,
# commentTs DATETIME
# handid = db.storeHand(hh)
#print hh
handid = db.storeHand(hh)
# HandsPlayers - ? ... Do we fix winnings?
# Tourneys ?
# TourneysPlayers
@ -253,8 +262,8 @@ db: a connected fpdb_db object"""
def select(self, handId):
""" Function to create Hand object from database """
def addPlayer(self, seat, name, chips):
@ -264,7 +273,7 @@ seat (int) indicating the seat
name (string) player name
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."""
logging.debug("addPlayer: %s %s (%s)" % (seat, name, chips))
log.debug("addPlayer: %s %s (%s)" % (seat, name, chips))
if chips is not None:
chips = re.sub(u',', u'', chips) #some sites have commas
self.players.append([seat, name, chips])
@ -280,9 +289,9 @@ If a player has None chips he won't be added."""
# go through m and initialise actions to empty list for each street.
if match:
self.streets.update(match.groupdict())
logging.debug("markStreets:\n"+ str(self.streets))
log.debug("markStreets:\n"+ str(self.streets))
else:
logging.error("markstreets didn't match")
log.error("markstreets didn't match")
def checkPlayerExists(self,player):
if player not in [p[1] for p in self.players]:
@ -292,7 +301,7 @@ If a player has None chips he won't be added."""
def setCommunityCards(self, street, cards):
logging.debug("setCommunityCards %s %s" %(street, cards))
log.debug("setCommunityCards %s %s" %(street, cards))
self.board[street] = [self.card(c) for c in cards]
# print "DEBUG: self.board: %s" % self.board
@ -303,14 +312,13 @@ If a player has None chips he won't be added."""
return c
def addAnte(self, player, ante):
logging.debug("%s %s antes %s" % ('ANTES', player, ante))
log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
if player is not None:
ante = re.sub(u',', u'', ante) #some sites have commas
self.bets['ANTES'][player].append(Decimal(ante))
self.bets['BLINDSANTES'][player].append(Decimal(ante))
self.stacks[player] -= Decimal(ante)
act = (player, 'posts', "ante", ante, self.stacks[player]==0)
self.actions['ANTES'].append(act)
#~ self.lastBet['ANTES'] = Decimal(ante)
self.actions['BLINDSANTES'].append(act)
self.pot.addMoney(player, Decimal(ante))
def addBlind(self, player, blindtype, amount):
@ -322,29 +330,29 @@ If a player has None chips he won't be added."""
# - this is a call of 1 sb and a raise to 1 bb
#
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:
amount = re.sub(u',', u'', amount) #some sites have commas
self.bets['PREFLOP'][player].append(Decimal(amount))
self.stacks[player] -= Decimal(amount)
#print "DEBUG %s posts, stack %s" % (player, self.stacks[player])
act = (player, 'posts', blindtype, amount, self.stacks[player]==0)
self.actions['BLINDSANTES'].append(act)
if blindtype == 'both':
amount = self.bb
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb))
self.bets['PREFLOP'][player].append(Decimal(amount))
self.pot.addMoney(player, Decimal(amount))
if blindtype == 'big blind':
self.lastBet['PREFLOP'] = Decimal(amount)
elif blindtype == 'both':
# extra small blind is 'dead'
self.lastBet['PREFLOP'] = Decimal(self.bb)
self.lastBet['PREFLOP'] = Decimal(amount)
self.posted = self.posted + [[player,blindtype]]
#print "DEBUG: self.posted: %s" %(self.posted)
def addCall(self, street, player=None, amount=None):
if amount:
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
# corner cases include if player would be all in
if amount is not None:
@ -355,22 +363,22 @@ If a player has None chips he won't be added."""
act = (player, 'calls', amount, self.stacks[player]==0)
self.actions[street].append(act)
self.pot.addMoney(player, Decimal(amount))
def addRaiseBy(self, street, player, amountBy):
"""\
Add a raise by amountBy on [street] by [player]
Add a raise by amountBy on [street] by [player]
"""
#Given only the amount raised by, the amount of the raise can be calculated by
# working out how much this player has already in the pot
# working out how much this player has already in the pot
# (which is the sum of self.bets[street][player])
# and how much he needs to call to match the previous player
# and how much he needs to call to match the previous player
# (which is tracked by self.lastBet)
# let Bp = previous bet
# let Bp = previous bet
# Bc = amount player has committed so far
# Rb = raise by
# then: C = Bp - Bc (amount to call)
# Rt = Bp + Rb (raise to)
#
#
amountBy = re.sub(u',', u'', amountBy) #some sites have commas
self.checkPlayerExists(player)
Rb = Decimal(amountBy)
@ -378,7 +386,7 @@ Add a raise by amountBy on [street] by [player]
Bc = reduce(operator.add, self.bets[street][player], 0)
C = Bp - Bc
Rt = Bp + Rb
self._addRaise(street, player, C, Rb, Rt)
#~ self.bets[street][player].append(C + Rb)
#~ self.stacks[player] -= (C + Rb)
@ -396,7 +404,7 @@ For sites which by "raises x" mean "calls and raises putting a total of x in the
C = Bp - Bc
Rb = CRb - C
Rt = Bp + Rb
self._addRaise(street, player, C, Rb, Rt)
def addRaiseTo(self, street, player, amountTo):
@ -414,18 +422,18 @@ Add a raise on [street] by [player] to [amountTo]
self._addRaise(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.stacks[player] -= (C + Rb)
act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0)
self.actions[street].append(act)
self.lastBet[street] = Rt # TODO check this is correct
self.pot.addMoney(player, C+Rb)
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
self.checkPlayerExists(player)
self.bets[street][player].append(Decimal(amount))
@ -441,24 +449,25 @@ Add a raise on [street] by [player] to [amountTo]
self.checkPlayerExists(player)
act = (player, 'stands pat')
self.actions[street].append(act)
def addFold(self, street, player):
logging.debug("%s %s folds" % (street, player))
log.debug("%s %s folds" % (street, player))
self.checkPlayerExists(player)
self.folded.add(player)
self.pot.addFold(player)
self.actions[street].append((player, 'folds'))
def addCheck(self, street, player):
#print "DEBUG: %s %s checked" % (street, player)
logging.debug("%s %s checks" % (street, player))
self.checkPlayerExists(player)
self.actions[street].append((player, 'checks'))
def addCollectPot(self,player, pot):
logging.debug("%s collected %s" % (player, pot))
log.debug("%s collected %s" % (player, pot))
self.checkPlayerExists(player)
self.collected = self.collected + [[player, pot]]
if player not in self.collectees:
@ -472,7 +481,7 @@ Add a raise on [street] by [player] to [amountTo]
For when a player shows cards for any reason (for showdown or out of choice).
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:
self.addHoleCards(cards,player,shown, mucked)
elif holeandboard is not None:
@ -480,10 +489,9 @@ Card ranks will be uppercased
board = set([c for s in self.board.values() for c in s])
self.addHoleCards(holeandboard.difference(board),player,shown, mucked)
def totalPot(self):
"""If all bets and blinds have been added, totals up the total pot size"""
# This gives us the total amount put in the pot
if self.totalpot is None:
self.pot.end()
@ -522,7 +530,7 @@ Map the tuple self.gametype onto the pokerstars string describing it
"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']])
return retstring
@ -553,6 +561,8 @@ Map the tuple self.gametype onto the pokerstars string describing it
return ("%s: posts big blind %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
elif(act[2] == "both"):
return ("%s: posts small & big blinds %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
elif(act[2] == "ante"):
return ("%s: posts the ante %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
elif act[1] == 'bringin':
return ("%s: brings in for %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'discards':
@ -564,23 +574,33 @@ Map the tuple self.gametype onto the pokerstars string describing it
"""Return a string of the stakes of the current hand."""
return "%s%s/%s%s" % (self.sym, self.sb, self.sym, self.bb)
def getStreetTotals(self):
pass
def writeGameLine(self):
"""Return the first HH line for the current hand."""
gs = "PokerStars Game #%s: " % self.handid
if self.tourNo != None and self.mixed != None: # mixed tournament
gs = gs + "Tournament #%s, %s %s (%s) - Level %s (%s) - " % (self.tourNo, self.buyin, self.MS[self.mixed], self.getGameTypeAsString(), self.level, self.getStakesAsString())
elif self.tourNo != None: # all other tournaments
gs = gs + "Tournament #%s, %s %s - Level %s (%s) - " % (self.tourNo,
gs = gs + "Tournament #%s, %s %s - Level %s (%s) - " % (self.tourNo,
self.buyin, self.getGameTypeAsString(), self.level, self.getStakesAsString())
elif self.mixed != None: # all other mixed games
gs = gs + " %s (%s, %s) - " % (self.MS[self.mixed],
gs = gs + " %s (%s, %s) - " % (self.MS[self.mixed],
self.getGameTypeAsString(), self.getStakesAsString())
else: # non-mixed cash games
gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString())
return gs + datetime.datetime.strftime(self.starttime,'%Y/%m/%d %H:%M:%S ET')
try:
timestr = datetime.datetime.strftime(self.starttime, '%Y/%m/%d %H:%M:%S ET')
except TypeError:
print "*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:", self.starttime
print "*** Make sure your HandHistoryConverter is setting hand.starttime properly!"
print "*** Game String:", gs
return gs
else:
return gs + timestr
def writeTableLine(self):
table_string = "Table \'%s\' %s-max" % (self.tablename, self.maxseats)
@ -593,15 +613,15 @@ Map the tuple self.gametype onto the pokerstars string describing it
def writeHand(self, fh=sys.__stdout__):
# PokerStars format.
print >>fh, self.writeGameLine()
print >>fh, self.writeTableLine()
print >>fh, self.writeGameLine()
print >>fh, self.writeTableLine()
class HoldemOmahaHand(Hand):
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
if gametype['base'] != 'hold':
pass # or indeed don't pass and complain instead
logging.debug("HoldemOmahaHand")
log.debug("HoldemOmahaHand")
self.allStreets = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER']
self.holeStreets = ['PREFLOP']
self.communityStreets = ['FLOP', 'TURN', 'RIVER']
@ -609,9 +629,9 @@ class HoldemOmahaHand(Hand):
Hand.__init__(self, sitename, gametype, handText, builtFrom = "HHC")
self.sb = gametype['sb']
self.bb = gametype['bb']
#Populate a HoldemOmahaHand
#Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
#Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
# which then invokes a 'addXXX' callback
if builtFrom == "HHC":
hhc.readHandInfo(self)
@ -619,6 +639,7 @@ class HoldemOmahaHand(Hand):
hhc.compilePlayerRegexs(self)
hhc.markStreets(self)
hhc.readBlinds(self)
hhc.readAntes(self)
hhc.readButton(self)
hhc.readHeroCards(self)
hhc.readShowdownActions(self)
@ -629,6 +650,7 @@ class HoldemOmahaHand(Hand):
for street in self.actionStreets:
if self.streets[street]:
hhc.readAction(self, street)
self.pot.markTotal(street)
hhc.readCollectPot(self)
hhc.readShownCards(self)
self.totalPot() # finalise it (total the pot)
@ -640,11 +662,11 @@ class HoldemOmahaHand(Hand):
if handid is not None:
self.select(handid) # Will need a handId
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:
logging.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided")
log.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided")
pass
def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False):
if player == self.hero: # we have hero's cards just update shown/mucked
@ -653,8 +675,20 @@ class HoldemOmahaHand(Hand):
else:
self.addHoleCards('PREFLOP', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
def getStreetTotals(self):
# street1Pot INT, /* pot size at flop/street4 */
# street2Pot INT, /* pot size at turn/street5 */
# street3Pot INT, /* pot size at river/street6 */
# street4Pot INT, /* pot size at sd/street7 */
# showdownPot INT, /* pot size at sd/street7 */
tmp1 = self.pot.getTotalAtStreet('FLOP')
tmp2 = self.pot.getTotalAtStreet('TURN')
tmp3 = self.pot.getTotalAtStreet('RIVER')
tmp4 = 0
tmp5 = 0
return (tmp1,tmp2,tmp3,tmp4,tmp5)
def writeHTMLHand(self, fh=sys.__stdout__):
def writeHTMLHand(self):
from nevow import tags as T
from nevow import flat
players_who_act_preflop = (([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
@ -687,7 +721,7 @@ class HoldemOmahaHand(Hand):
]
)
if street in self.actionStreets and self.actions[street]:
lines.append(
lines.append(
T.ol(class_='actions', data=self.actions[street], render=render_action) [
T.li(pattern='list_item')[ T.slot(name='action') ]
]
@ -721,8 +755,8 @@ class HoldemOmahaHand(Hand):
context.tag[ pat().fillSlots('action', x)]
return context.tag
s = T.p[
T.h1[
s = T.p[
T.h1[
T.span(class_='site')["%s Game #%s]" % ('PokerStars', self.handid)],
T.span(class_='type_limit')[ "%s ($%s/$%s)" %(self.getGameTypeAsString(), self.sb, self.bb) ],
T.span(class_='date')[ datetime.datetime.strftime(self.starttime,'%Y/%m/%d - %H:%M:%S ET') ]
@ -748,13 +782,13 @@ class HoldemOmahaHand(Hand):
return str(tidy.parseString(flat.flatten(s), **options))
def writeHand(self, fh=sys.__stdout__):
# PokerStars format.
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']]))
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]:
#Only print stacks of players who do something preflop
print >>fh, ("Seat %s: %s ($%s in chips) " %(player[0], player[1], player[2]))
@ -762,7 +796,7 @@ class HoldemOmahaHand(Hand):
if self.actions['BLINDSANTES']:
for act in self.actions['BLINDSANTES']:
print >>fh, self.actionString(act)
print >>fh, ("*** HOLE CARDS ***")
for player in self.dealt:
print >>fh, ("Dealt to %s [%s]" %(player, " ".join(self.holecards['PREFLOP'][player][1])))
@ -808,7 +842,7 @@ class HoldemOmahaHand(Hand):
numOfHoleCardsNeeded = 2
if len(self.holecards['PREFLOP'][name]) == numOfHoleCardsNeeded:
print >>fh, ("%s shows [%s] (a hand...)" % (name, " ".join(self.holecards['PREFLOP'][name][1])))
# Current PS format has the lines:
# Uncalled bet ($111.25) returned to s0rrow
# s0rrow collected $5.15 from side pot
@ -850,7 +884,7 @@ class HoldemOmahaHand(Hand):
print >>fh, ("Seat %d: %s mucked" % (seatnum, name))
print >>fh, "\n\n"
class DrawHand(Hand):
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC"):
if gametype['base'] != 'draw':
@ -870,6 +904,7 @@ class DrawHand(Hand):
hhc.compilePlayerRegexs(self)
hhc.markStreets(self)
hhc.readBlinds(self)
hhc.readAntes(self)
hhc.readButton(self)
hhc.readHeroCards(self)
hhc.readShowdownActions(self)
@ -877,6 +912,7 @@ class DrawHand(Hand):
for street in self.streetList:
if self.streets[street]:
hhc.readAction(self, street)
self.pot.markTotal(street)
hhc.readCollectPot(self)
hhc.readShownCards(self)
self.totalPot() # finalise it (total the pot)
@ -895,9 +931,9 @@ class DrawHand(Hand):
# - this is a bet of 1 sb, as yet uncalled.
# Player in the big blind posts
# - 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:
self.bets['DEAL'][player].append(Decimal(amount))
self.stacks[player] -= Decimal(amount)
@ -906,7 +942,7 @@ class DrawHand(Hand):
self.actions['BLINDSANTES'].append(act)
self.pot.addMoney(player, Decimal(amount))
if blindtype == 'big blind':
self.lastBet['DEAL'] = Decimal(amount)
self.lastBet['DEAL'] = Decimal(amount)
elif blindtype == 'both':
# extra small blind is 'dead'
self.lastBet['DEAL'] = Decimal(self.bb)
@ -924,7 +960,7 @@ class DrawHand(Hand):
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])
@ -937,6 +973,14 @@ class DrawHand(Hand):
act = (player, 'discards', num)
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__):
# PokerStars format.
@ -1018,17 +1062,17 @@ class StudHand(Hand):
if gametype['base'] != 'stud':
pass # or indeed don't pass and complain instead
self.allStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
self.allStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
self.communityStreets = []
self.actionStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
self.actionStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
self.streetList = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
Hand.__init__(self, sitename, gametype, handText)
self.sb = gametype['sb']
self.bb = gametype['bb']
#Populate the StudHand
#Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
#Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
# which then invokes a 'addXXX' callback
if builtFrom == "HHC":
hhc.readHandInfo(self)
@ -1040,11 +1084,11 @@ class StudHand(Hand):
hhc.readHeroCards(self)
# Read actions in street order
for street in self.actionStreets:
if street == 'ANTES': continue # OMG--sometime someone folds in the ante round
if street == 'BLINDSANTES': continue # OMG--sometime someone folds in the ante round
if self.streets[street]:
logging.debug(street)
logging.debug(self.streets[street])
log.debug(street + self.streets[street])
hhc.readAction(self, street)
self.pot.markTotal(street)
hhc.readCollectPot(self)
hhc.readShownCards(self) # not done yet
self.totalPot() # finalise it (total the pot)
@ -1075,7 +1119,7 @@ street (string) the street name (in streetList)
open list of card bigrams e.g. ['2h','Jc'], dealt face up
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:
self.checkPlayerExists(player)
self.holecards[street][player] = (open, closed)
@ -1089,7 +1133,7 @@ closed likewise, but known only to player
"""\
Add a complete on [street] by [player] to [amountTo]
"""
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
self.checkPlayerExists(player)
Bp = self.lastBet['THIRD']
@ -1104,10 +1148,10 @@ Add a complete on [street] by [player] to [amountTo]
#~ self.actions[street].append(act)
#~ self.lastBet[street] = Rt # TODO check this is correct
#~ self.pot.addMoney(player, C+Rb)
def addBringIn(self, player, bringin):
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.stacks[player] -= Decimal(bringin)
act = (player, 'bringin', bringin, self.stacks[player]==0)
@ -1115,20 +1159,28 @@ Add a complete on [street] by [player] to [amountTo]
self.lastBet['THIRD'] = Decimal(bringin)
self.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__):
# PokerStars format.
super(StudHand, self).writeHand(fh)
players_who_post_antes = set([x[0] for x in self.actions['ANTES']])
players_who_post_antes = set([x[0] for x in self.actions['BLINDSANTES']])
for player in [x for x in self.players if x[1] in players_who_post_antes]:
#Only print stacks of players who do something preflop
print >>fh, _("Seat %s: %s (%s%s in chips)" %(player[0], player[1], self.sym, player[2]))
if 'ANTES' in self.actions:
for act in self.actions['ANTES']:
if 'BLINDSANTES' in self.actions:
for act in self.actions['BLINDSANTES']:
print >>fh, _("%s: posts the ante %s%s" %(act[0], self.sym, act[3]))
if 'THIRD' in self.actions:
@ -1251,7 +1303,7 @@ Add a complete on [street] by [player] to [amountTo]
else:
return hc + " ".join(self.holecards[street][player][0]) + ']'
if street == 'SEVENTH' and player != self.hero: return # only write 7th st line for hero, LDO
if street == 'SEVENTH' and player != self.hero: return # only write 7th st line for hero, LDO
return hc + " ".join(self.holecards[street][player][1]) + "] [" + " ".join(self.holecards[street][player][0]) + "]"
def join_holecards(self, player):
@ -1273,30 +1325,43 @@ class Pot(object):
def __init__(self):
self.contenders = set()
self.committed = {}
self.total = None
self.returned = {}
self.sym = u'$' # this is the default currency symbol
self.contenders = set()
self.committed = {}
self.streettotals = {}
self.common = Decimal(0)
self.total = None
self.returned = {}
self.sym = u'$' # this is the default currency symbol
def setSym(self, sym):
self.sym = sym
def addPlayer(self,player):
self.committed[player] = Decimal(0)
def addFold(self, player):
# addFold must be called when a player folds
self.contenders.discard(player)
def addCommonMoney(self, amount):
self.common += amount
def addMoney(self, player, amount):
# addMoney must be called for any actions that put money in the pot, in the order they occur
self.contenders.add(player)
self.committed[player] += amount
def markTotal(self, street):
self.streettotals[street] = sum(self.committed.values()) + self.common
def getTotalAtStreet(self, street):
if street in self.streettotals:
return self.streettotals[street]
return 0
def end(self):
self.total = sum(self.committed.values())
self.total = sum(self.committed.values()) + self.common
# Return any uncalled bet.
committed = sorted([ (v,k) for (k,v) in self.committed.items()])
lastbet = committed[-1][0] - committed[-2][0]
@ -1314,7 +1379,7 @@ class Pot(object):
self.pots = []
while len(commitsall) > 0:
commitslive = [(v,k) for (v,k) in commitsall if k in self.contenders]
v1 = commitslive[0][0]
v1 = commitslive[0][0]
self.pots += [sum([min(v,v1) for (v,k) in commitsall])]
commitsall = [((v-v1),k) for (v,k) in commitsall if v-v1 >0]
@ -1324,7 +1389,7 @@ class Pot(object):
# and y+z+r = x
# for example:
# Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2
def __str__(self):
if self.sym is None:
self.sym = "C"
@ -1332,17 +1397,17 @@ class Pot(object):
print "call Pot.end() before printing pot total"
# NB if I'm sure end() is idempotent, call it here.
raise FpdbParseError
ret = "Total pot %s%.2f" % (self.sym, self.total)
if len(self.pots) < 2:
return ret;
ret += " Main pot %s%.2f" % (self.sym, self.pots[0])
return ret + ''.join([ (" Side pot %s%.2f." % (self.sym, self.pots[x]) ) for x in xrange(1, len(self.pots)) ])
def assemble(cnxn, handid):
c = cnxn.cursor()
# We need at least sitename, gametype, handid
# for the Hand.__init__
c.execute("""
@ -1385,10 +1450,10 @@ limit 1""", {'handid':handid})
h.setCommunityCards('FLOP', cards[0:3])
if cards[3]:
h.setCommunityCards('TURN', [cards[3]])
if cards[4]:
if cards[4]:
h.setCommunityCards('RIVER', [cards[4]])
#[Card.valueSuitFromCard(x) for x in cards]
# HandInfo : HID, TABLE
# BUTTON - why is this treated specially in Hand?
# answer: it is written out in hand histories
@ -1406,7 +1471,7 @@ WHERE h.id = %(handid)s
h.handid = res[0]
h.tablename = res[1]
h.starttime = res[2] # automatically a datetime
# PlayerStacks
c.execute("""
SELECT
@ -1431,7 +1496,7 @@ and p.id = hp.playerid
h.addCollectPot(name, winnings)
if position == 'B':
h.buttonpos = seat
# actions
c.execute("""
@ -1479,7 +1544,7 @@ ORDER BY
#hc.readShownCards(self)
h.totalPot()
h.rake = h.totalpot - h.totalcollected
return h

View File

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

View File

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

View File

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

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

@ -2,17 +2,17 @@
# -*- coding: utf-8 -*-
#
# Copyright 2009, Grigorij Indigirkin
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
@ -21,43 +21,48 @@
import sys
from collections import defaultdict
from Exceptions import FpdbParseError
from HandHistoryConverter import *
# 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 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
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': "\$", }
# Static regexes
# $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
re_GameInfoRing = re.compile("""
(?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>\d+)\s*(?:USD)?\s*
(?P<LIMIT>(NL))\s+
(?P<GAME>(Texas\ Hold\'em))
(?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>[0-9,]+)\s*(?:USD)?\s*
(?P<LIMIT>(NL|PL|))\s+
(?P<GAME>(Texas\ Hold\'em|Omaha))
\s*\-\s*
(?P<DATETIME>.+)
""", re.VERBOSE)
re_GameInfoTrny = re.compile("""
(?P<LIMIT>(NL))\s+
(?P<GAME>(Texas\ Hold\'em))\s+
(?P<LIMIT>(NL|PL|))\s+
(?P<GAME>(Texas\ Hold\'em|Omaha))\s+
(?P<BUYIN>\$?[.0-9]+)\s*(?P<BUYIN_CURRENCY>USD)?\s*Buy-in\s+
Trny:\s?(?P<TOURNO>\d+)\s+
Level:\s*(?P<LEVEL>\d+)\s+
@ -70,37 +75,13 @@ class PartyPoker(HandHistoryConverter):
(?P<DATETIME>.+)
""", re.VERBOSE)
re_Hid = re.compile("^Game \#(?P<HID>\d+) starts.")
#re_GameInfo = re.compile("""
#PartyPoker\sGame\s\#(?P<HID>[0-9]+):\s+
#(Tournament\s\# # open paren of tournament info
#(?P<TOURNO>\d+),\s
#(?P<BUYIN>[%(LS)s\+\d\.]+ # here's how I plan to use LS
#\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?
#)\s)? # close paren of tournament info
#(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(?
#(?P<GAME>Hold\'em|Razz|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball)\s
#(?P<LIMIT>No\sLimit|Limit|Pot\sLimit)\)?,?\s
#(-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?
#\(? # open paren of the stakes
#(?P<CURRENCY>%(LS)s|)?
#(?P<SB>[.0-9]+)/(%(LS)s)?
#(?P<BB>[.0-9]+)
#\s?(?P<ISO>%(LEGAL_ISO)s)?
#\)\s-\s # close paren of the stakes
#(?P<DATETIME>.*$)""" % substitutions,
#re.MULTILINE|re.VERBOSE)
re_PlayerInfo = re.compile("""
Seat\s(?P<SEAT>\d+):\s
(?P<PNAME>.*)\s
\(\s*\$?(?P<CASH>[0-9,.]+)\s*(?:USD|)\s*\)
""" ,
""" ,
re.VERBOSE)
#re_PlayerInfo = re.compile("""
#^Seat\s(?P<SEAT>[0-9]+):\s
#(?P<PNAME>.*)\s
#\((%(LS)s)?(?P<CASH>[.0-9]+)\sin\schips\)""" % substitutions,
#re.MULTILINE|re.VERBOSE)
re_HandInfo = re.compile("""
^Table\s+
@ -109,110 +90,96 @@ class PartyPoker(HandHistoryConverter):
(?:[^ ]+\s+\#(?P<MTTTABLE>\d+).+)? # table number for mtt
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
""",
""",
re.MULTILINE|re.VERBOSE)
#re_HandInfo = re.compile("""
#^Table\s\'(?P<TABLE>[-\ a-zA-Z\d]+)\'\s
#((?P<MAX>\d+)-max\s)?
#(?P<PLAY>\(Play\sMoney\)\s)?
#(Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
#re.MULTILINE|re.VERBOSE)
re_TotalPlayers = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<MAXSEATS>\d+)", re.MULTILINE)
re_SplitHands = re.compile('\x00+')
re_TailSplitHands = re.compile('(\x00+)')
lineSplitter = '\n'
re_Button = re.compile('Seat (?P<BUTTON>\d+) is the button', re.MULTILINE)
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')
# 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]+)')
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):
list = HandHistoryConverter.allHandsAsList(self)
if list is None:
return []
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):
players = set([player[1] for player in hand.players])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# we need to recompile the player regexs.
# TODO: should probably rename re_HeroCards and corresponding method,
# since they are used to find all cards on lines starting with "Dealt to:"
# They still identify the hero.
self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
subst = {'PLYR': player_re, 'CUR_SYM': hand.SYMBOL[hand.gametype['currency']],
'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''}
logging.debug("player_re: " + subst['PLYR'])
logging.debug("CUR_SYM: " + subst['CUR_SYM'])
logging.debug("CUR: " + subst['CUR'])
for key in ('CUR_SYM', 'CUR'):
subst[key] = re.escape(subst[key])
log.debug("player_re: '%s'" % subst['PLYR'])
log.debug("CUR_SYM: '%s'" % subst['CUR_SYM'])
log.debug("CUR: '%s'" % subst['CUR'])
self.re_PostSB = re.compile(
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)
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)
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)
#self.re_BringIn = re.compile(
#r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[.0-9]+)" % subst,
#re.MULTILINE)
#self.re_PostBoth = re.compile(
#r"^%(PLYR)s: posts small \& big blinds \[%(CUR)s (?P<SBBB>[.0-9]+)" % subst,
#re.MULTILINE)
self.re_HeroCards = re.compile(
r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst,
re.MULTILINE)
self.re_Action = re.compile(r"""
^%(PLYR)s\s+(?P<ATYPE>bets|checks|raises|calls|folds|is\sall-In)
(?:\s+\[%(CUR_SYM)s(?P<BET>[.,\d]+)\s*%(CUR)s\])?
""" % subst,
""" % subst,
re.MULTILINE|re.VERBOSE)
self.re_ShownCards = re.compile(
r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re +
r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.",
r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re +
r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.",
re.MULTILINE)
self.re_CollectPot = re.compile(
r""""^%(PLYR)s \s+ wins \s+
%(CUR_SYM)s(?P<POT>[.\d]+)\s*%(CUR)s""" % subst,
r"""^%(PLYR)s \s+ wins \s+
%(CUR_SYM)s(?P<POT>[.,\d]+)\s*%(CUR)s""" % subst,
re.MULTILINE|re.VERBOSE)
#self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
#self.re_ShownCards = re.compile(
#"^Seat (?P<SEAT>[0-9]+): %s (\(.*\) )?
#(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re,
#re.MULTILINE)
def readSupportedGames(self):
return [["ring", "hold", "nl"],
#["ring", "hold", "pl"],
#["ring", "hold", "fl"],
["ring", "hold", "pl"],
["ring", "hold", "fl"],
["tour", "hold", "nl"],
#["tour", "hold", "pl"],
#["tour", "hold", "fl"],
["tour", "hold", "pl"],
["tour", "hold", "fl"],
]
def _getGameType(self, handText):
if not hasattr(self, '_gameType'):
self._gameType = None
if self._gameType is None:
# let's determine whether hand is trny
# and whether 5-th line contains head line
headLine = handText.split(self.lineSplitter)[4]
#print headLine
#sys.exit(1)
for headLineContainer in headLine, handText:
for regexp in self.re_GameInfoTrny, self.re_GameInfoRing:
m = regexp.search(headLineContainer)
@ -220,47 +187,45 @@ follow : whether to tail -f the input"""
self._gameType = m
return self._gameType
return self._gameType
def determineGameType(self, handText):
# inspect the handText and return the gametype dict
# gametype dict is:
# {'limitType': xxx, 'base': xxx, 'category': xxx}
print self.ParsingException().wrapHh( handText )
def determineGameType(self, handText):
"""inspect the handText and return the gametype dict
gametype dict is:
{'limitType': xxx, 'base': xxx, 'category': xxx}"""
log.debug(PartyPokerParseError().wrapHh( handText ))
info = {}
m = self._getGameType(handText)
if m is None:
return None
mg = m.groupdict()
# translations from captured groups to fpdb info strings
limits = { 'NL':'nl',
# 'Pot Limit':'pl', 'Limit':'fl'
}
limits = { 'NL':'nl', 'PL':'pl', '':'fl' }
games = { # base, category
"Texas Hold'em" : ('hold','holdem'),
#'Omaha' : ('hold','omahahi'),
"Texas Hold'em" : ('hold','holdem'),
'Omaha' : ('hold','omahahi'),
}
currencies = { '$':'USD', '':'T$' }
for expectedField in ['LIMIT', 'GAME']:
if mg[expectedField] is None:
raise self.ParsingException(
raise PartyPokerParseError(
"Cannot fetch field '%s'" % expectedField,
hh = handText)
try:
info['limitType'] = limits[mg['LIMIT']]
info['limitType'] = limits[mg['LIMIT'].strip()]
except:
raise self.ParsingException(
raise PartyPokerParseError(
"Unknown limit '%s'" % mg['LIMIT'],
hh = handText)
try:
(info['base'], info['category']) = games[mg['GAME']]
except:
raise self.ParsingException(
raise PartyPokerParseError(
"Unknown game type '%s'" % mg['GAME'],
hh = handText)
@ -269,16 +234,16 @@ follow : whether to tail -f the input"""
info['type'] = 'tour'
else:
info['type'] = 'ring'
if info['type'] == 'ring':
info['sb'], info['bb'] = ringBlinds(mg['RINGLIMIT'])
# FIXME: there are only $ and play money availible for cash
info['currency'] = currencies(mg['CURRENCY'])
# to be honest, party doesn't save play money hh
info['currency'] = currencies[mg['CURRENCY']]
else:
info['sb'] = renderTrnyMoney(mg['SB'])
info['bb'] = renderTrnyMoney(mg['BB'])
info['sb'] = clearMoneyString(mg['SB'])
info['bb'] = clearMoneyString(mg['BB'])
info['currency'] = 'T$'
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info
@ -286,22 +251,48 @@ follow : whether to tail -f the input"""
def readHandInfo(self, hand):
info = {}
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m:
info.update(m.groupdict())
else:
raise self.ParsingException("Cannot read Handinfo for current hand", hh=hand.handText)
m = self._getGameType(hand.handText)
if m: info.update(m.groupdict())
m = self.re_Hid.search(hand.handText)
try:
info.update(self.re_Hid.search(hand.handText).groupdict())
except:
raise PartyPokerParseError("Cannot read HID for current hand", hh=hand.handText)
try:
info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict())
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())
# FIXME: it's a hack cause party doesn't supply hand.maxseats info
#hand.maxseats = '9'
hand.mixed = None
# TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info)
# FIXME: it's dirty hack
# party doesnt subtract uncalled money from commited money
# so hand.totalPot calculation has to be redefined
from Hand import Pot, HoldemOmahaHand
def getNewTotalPot(origTotalPot):
def totalPot(self):
if self.totalpot is None:
self.pot.end()
self.totalpot = self.pot.total
for i,v in enumerate(self.collected):
if v[0] in self.pot.returned:
self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
return origTotalPot()
return totalPot
instancemethod = type(hand.totalPot)
hand.totalPot = instancemethod(getNewTotalPot(hand.totalPot), hand, HoldemOmahaHand)
log.debug("readHandInfo: %s" % info)
for key in info:
if key == 'DATETIME':
#Saturday, July 25, 07:53:52 EDT 2009
@ -313,9 +304,10 @@ follow : whether to tail -f the input"""
month = months.index(m2.group('M')) + 1
datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
# FIXME: some timezone correction required
#tzShift = defaultdict(lambda:0, {'EDT': -5, 'EST': -6, 'MSKS': 3})
#hand.starttime -= datetime.timedelta(hours=tzShift[m2.group('TZ')])
if key == 'HID':
hand.handid = info[key]
if key == 'TABLE':
@ -325,14 +317,14 @@ follow : whether to tail -f the input"""
if key == 'TOURNO':
hand.tourNo = info[key]
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 ''
hand.buyin = info[key] + '+%s0' % cur
if key == 'LEVEL':
hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != 'Real':
# TODO: play money wasn't tested
# hand.currency = 'play' # overrides previously set value
# if realy there's no play money hh on party
hand.gametype['currency'] = 'play'
def readButton(self, hand):
@ -340,21 +332,17 @@ follow : whether to tail -f the input"""
if m:
hand.buttonpos = int(m.group('BUTTON'))
else:
logging.info('readButton: not found')
log.info('readButton: not found')
def readPlayerStacks(self, hand):
logging.debug("readPlayerStacks")
log.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'),
renderTrnyMoney(a.group('CASH')))
clearMoneyString(a.group('CASH')))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
assert hand.gametype['base'] == "hold", \
"wtf! There're no %s games on party" % hand.gametype['base']
m = re.search(
r"\*{2} Dealing down cards \*{2}"
r"(?P<PREFLOP>.+?)"
@ -364,24 +352,17 @@ follow : whether to tail -f the input"""
, hand.handText,re.DOTALL)
hand.addStreets(m)
def readCommunityCards(self, hand, street):
if street in ('FLOP','TURN','RIVER'):
def readCommunityCards(self, hand, street):
if street in ('FLOP','TURN','RIVER'):
m = self.re_Board.search(hand.streets[street])
hand.setCommunityCards(street, renderCards(m.group('CARDS')))
def readAntes(self, hand):
logging.debug("reading antes")
log.debug("reading antes")
m = self.re_Antes.finditer(hand.handText)
for player in m:
#~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL)
if m:
#~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand):
noSmallBlind = bool(self.re_NoSmallBlind.search(hand.handText))
if hand.gametype['type'] == 'ring':
@ -391,50 +372,51 @@ follow : whether to tail -f the input"""
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
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
# so there're some cra^Wcaclulations
if hand.buttonpos == 0:
self.readButton(hand)
# NOTE: code below depends on Hand's implementation
# 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)
def findFirstNonEmptySeat(startSeat):
while startSeat not in playersMap:
if startSeat >= maxSeat:
if startSeat >= maxSeat:
startSeat = 0
startSeat += 1
return startSeat
smartMin = lambda A,B: A if float(A) <= float(B) else B
if noSmallBlind:
hand.addBlind(None, None, None)
smallBlindSeat = int(hand.buttonpos)
else:
smallBlindSeat = findFirstNonEmptySeat(int(hand.buttonpos) + 1)
blind = smartMin(hand.sb, playersMap[smallBlindSeat][1])
hand.addBlind(playersMap[smallBlindSeat][0], 'small blind', blind)
bigBlindSeat = findFirstNonEmptySeat(smallBlindSeat + 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):
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
# we need to grab hero's cards
# we need to grab hero's cards
for street in ('PREFLOP',):
if street in hand.streets.keys():
m = self.re_HeroCards.finditer(hand.streets[street])
for found in m:
# if m == None:
# hand.involved = False
# else:
hand.hero = found.group('PNAME')
newcards = renderCards(found.group('NEWCARDS'))
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
@ -444,35 +426,48 @@ follow : whether to tail -f the input"""
m = self.re_Action.finditer(hand.streets[street])
for action in m:
acts = action.groupdict()
if action.group('ATYPE') in ('raises','is all-In'):
#print action.groupdict()
#sys.exit(1)
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'calls':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
#print action.groupdict()
#sys.exit(1)
elif action.group('ATYPE') == 'bets':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'folds':
hand.addFold( street, action.group('PNAME'))
elif action.group('ATYPE') == 'checks':
hand.addCheck( street, action.group('PNAME'))
playerName = action.group('PNAME')
amount = clearMoneyString(action.group('BET')) if action.group('BET') else None
actionType = action.group('ATYPE')
if actionType == 'is all-In':
# party's allin can mean either raise or bet or call
Bp = hand.lastBet[street]
if Bp == 0:
actionType = 'bets'
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:
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):
# all action in readShownCards
pass
## TODO: pick up mucks also??
#for shows in self.re_ShowdownAction.finditer(hand.handText):
#cards = shows.group('CARDS').split(' ')
#hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand):
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):
for m in self.re_ShownCards.finditer(hand.handText):
@ -484,26 +479,26 @@ follow : whether to tail -f the input"""
else: mucked = True
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked)
def ringBlinds(ringLimit):
"Returns blinds for current limit"
ringLimit = float(ringLimit)
"Returns blinds for current limit in cash games"
ringLimit = float(clearMoneyString(ringLimit))
if ringLimit == 5.: ringLimit = 4.
return ('%.2f' % (ringLimit/200.), '%.2f' % (ringLimit/100.) )
def renderTrnyMoney(money):
"renders 'numbers' like '1 200' and '2,000'"
def clearMoneyString(money):
"Renders 'numbers' like '1 200' and '2,000'"
return money.replace(' ', '').replace(',', '')
def renderCards(string):
"splits strings like ' Js, 4d '"
"Splits strings like ' Js, 4d '"
cards = string.strip().split(' ')
return filter(len, map(lambda x: x.strip(' ,'), cards))
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/stars/horse/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt")
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet",
@ -515,7 +510,4 @@ if __name__ == "__main__":
(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)

View File

@ -19,6 +19,7 @@
########################################################################
# TODO: straighten out discards for draw games
import sys
from HandHistoryConverter import *
@ -26,13 +27,17 @@ from HandHistoryConverter import *
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
sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\x80", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE
substitutions = {
'LEGAL_ISO' : "USD|EUR|GBP|CAD", # legal ISO currency codes
'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
'LS' : "\$|\x80|\xa3" # legal currency symbols ADD Euro, Sterling, etc HERE
}
@ -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]+)')
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):
players = set([player[1] for player in hand.players])
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()
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)

File diff suppressed because it is too large Load Diff

188
pyfpdb/SummaryEverleaf.py Normal file
View File

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

View File

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

310
pyfpdb/TournamentTracker.py Normal file
View File

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

471
pyfpdb/Tourney.py Normal file
View File

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

View File

@ -26,6 +26,11 @@ from HandHistoryConverter import *
class Win2day(HandHistoryConverter):
sitename = "Win2day"
filetype = "text"
codepage = "cp1252"
siteID = 4
# 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">
@ -39,15 +44,6 @@ class Win2day(HandHistoryConverter):
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)
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):
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)
#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>'
self.re_Action = re.compile(r'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re, re.MULTILINE)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

57
pyfpdb/logging.conf Normal file
View File

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

54
pyfpdb/py2exe_setup.py Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
"""setup.py
Py2exe script for fpdb.
"""
# Copyright 2009, Ray E. Barker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
#TODO: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate
# include the lib needed to handle png files in mucked
# get rid of all the uneeded libraries (e.g., pyQT)
# think about an installer
from distutils.core import setup
import py2exe
setup(
name = 'fpdb',
description = 'Free Poker DataBase',
version = '0.12',
console = [ {'script': 'fpdb.py', },
{'script': 'HUD_main.py', }
],
options = {'py2exe': {
'packages' :'encodings',
'includes' : 'cairo, pango, pangocairo, atk, gobject, PokerStarsToFpdb',
'excludes' : '_tkagg, _agg2, cocoaagg, fltkagg',
'dll_excludes': 'libglade-2.0-0.dll',
}
},
data_files = ['HUD_config.xml',
'Cards01.png'
]
)

View File

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

View File

@ -14,7 +14,7 @@ text = ""
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")
hhc.compilePlayerRegexs(h)

View File

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