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

This commit is contained in:
eblade 2009-07-25 13:08:21 -04:00
commit 308cfb1e56
36 changed files with 3862 additions and 3045 deletions

View File

@ -34,12 +34,12 @@ 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):
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) # Call super class init.
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"

View File

@ -57,6 +57,21 @@ def fourStartCards(value1, suit1, value2, suit2, value3, suit3, value4, suit4):
#AAKKs
#AAKKr
# Is probably what we are looking for
# mct:
# my maths says there are 4 classes of suitedness
# SSSS SSSx SSxy SSHH
# encode them as follows:
# SSSS (K, J, 6, 3)
# - 13C4 = 715 possibilities
# SSSx (K, J, 6),(3)
# - 13C3 * 13 = 3718 possibilities
# SSxy (K, J),(6),(3)
# - 13C2 * 13*13 = 13182 possibilities
# SSHH (K, J),(6, 3)
# - 13C2 * 13C2 = 6084 possibilities
# Needless to say they won't fit on a 13x13 grid.
# The actual number of hands in each class is far greater
return(0)
def cardFromValueSuit(value, suit):
@ -70,7 +85,7 @@ def cardFromValueSuit(value, suit):
def valueSuitFromCard(card):
""" Function to convert a card stored in the database (int 0-52) into value
and suit like 9s, 4c etc """
if card < 0 or card > 52:
if card < 0 or card > 52 or not card:
return('')
else:
return( ['', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh', 'Ah'
@ -80,4 +95,7 @@ def valueSuitFromCard(card):
][card] )
if __name__ == '__main__':
for i in xrange(1, 14):
print "card %2d = %s card %2d = %s card %2d = %s card %2d = %s" % \
(i, valueSuitFromCard(i), i+13, valueSuitFromCard(i+13), i+26, valueSuitFromCard(i+26), i+39, valueSuitFromCard(i+39))

View File

@ -86,14 +86,34 @@ class Site:
self.aux_window = node.getAttribute("aux_window")
self.font = node.getAttribute("font")
self.font_size = node.getAttribute("font_size")
self.use_frames = node.getAttribute("use_frames")
self.use_frames = node.getAttribute("use_frames")
self.enabled = fix_tf(node.getAttribute("enabled"), default = True)
self.xpad = node.getAttribute("xpad")
self.ypad = node.getAttribute("ypad")
self.layout = {}
for layout_node in node.getElementsByTagName('layout'):
lo = Layout(layout_node)
self.layout[lo.max] = lo
# Site defaults
if self.xpad == "": self.xpad = 1
else: self.xpad = int(self.xpad)
if self.ypad == "": self.ypad = 0
else: self.ypad = int(self.ypad)
if self.font_size == "": self.font_size = 7
else: self.font_size = int(self.font_size)
if self.hudopacity == "": self.hudopacity = 1.0
else: self.hudopacity = float(self.hudopacity)
if self.use_frames == "": self.use_frames = False
if self.font == "": self.font = "Sans"
if self.hudbgcolor == "": self.hudbgcolor = "000000"
if self.hudfgcolor == "": self.hudfgcolor = "FFFFFF"
def __str__(self):
temp = "Site = " + self.site_name + "\n"
for key in dir(self):
@ -119,9 +139,16 @@ class Stat:
class Game:
def __init__(self, node):
self.game_name = node.getAttribute("game_name")
self.db = node.getAttribute("db")
self.rows = int( node.getAttribute("rows") )
self.cols = int( node.getAttribute("cols") )
self.xpad = node.getAttribute("xpad")
self.ypad = node.getAttribute("ypad")
# Defaults
if self.xpad == "": self.xpad = 1
else: self.xpad = int(self.xpad)
if self.ypad == "": self.ypad = 0
else: self.ypad = int(self.ypad)
aux_text = node.getAttribute("aux")
aux_list = aux_text.split(',')
@ -146,9 +173,10 @@ class Game:
def __str__(self):
temp = "Game = " + self.game_name + "\n"
temp = temp + " db = %s\n" % self.db
temp = temp + " rows = %d\n" % self.rows
temp = temp + " cols = %d\n" % self.cols
temp = temp + " xpad = %d\n" % self.xpad
temp = temp + " ypad = %d\n" % self.ypad
temp = temp + " aux = %s\n" % self.aux
for stat in self.stats.keys():
@ -250,6 +278,7 @@ class Config:
self.default_config_path = self.get_default_config_path()
if file != None: # configuration file path has been passed
file = os.path.expanduser(file)
if not os.path.exists(file):
print "Configuration file %s not found. Using defaults." % (file)
sys.stderr.write("Configuration file %s not found. Using defaults." % (file))
@ -492,6 +521,8 @@ class Config:
db['db-backend'] = 2
elif string.lower(self.supported_databases[name].db_server) == 'postgresql':
db['db-backend'] = 3
elif string.lower(self.supported_databases[name].db_server) == 'sqlite':
db['db-backend'] = 4
else: db['db-backend'] = None # this is big trouble
return db
@ -630,6 +661,8 @@ class Config:
parms["font"] = self.supported_sites[site].font
parms["font_size"] = self.supported_sites[site].font_size
parms["enabled"] = self.supported_sites[site].enabled
parms["xpad"] = self.supported_sites[site].xpad
parms["ypad"] = self.supported_sites[site].ypad
return parms
def set_site_parameters(self, site_name, converter = None, decoder = None,
@ -680,9 +713,10 @@ class Config:
param = {}
if self.supported_games.has_key(name):
param['game_name'] = self.supported_games[name].game_name
param['db'] = self.supported_games[name].db
param['rows'] = self.supported_games[name].rows
param['cols'] = self.supported_games[name].cols
param['xpad'] = self.supported_games[name].xpad
param['ypad'] = self.supported_games[name].ypad
param['aux'] = self.supported_games[name].aux
return param

View File

@ -27,67 +27,48 @@ Create and manage the database objects.
import sys
import traceback
from datetime import datetime, date, time, timedelta
from time import time, strftime
import string
# pyGTK modules
# FreePokerTools modules
import fpdb_db
import fpdb_simple
import Configuration
import SQL
import Card
class Database:
def __init__(self, c, db_name, game):
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
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
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
db_params = c.get_db_parameters()
if (string.lower(db_params['db-server']) == 'postgresql' or
string.lower(db_params['db-server']) == 'postgres'):
import psycopg2 # posgres via DB-API
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
try:
if db_params['db-host'] == 'localhost' or db_params['db-host'] == '127.0.0.1':
self.connection = psycopg2.connect(database = db_params['db-databaseName'])
else:
self.connection = psycopg2.connect(host = db_params['db-host'],
user = db_params['db-user'],
password = db_params['db-password'],
database = db_params['db-databaseName'])
except:
print "Error opening database connection %s. See error log file." % (file)
traceback.print_exc(file=sys.stderr)
print "press enter to continue"
sys.stdin.readline()
sys.exit()
elif string.lower(db_params['db-server']) == 'mysql':
import MySQLdb # mysql bindings
try:
self.connection = MySQLdb.connect(host = db_params['db-host'],
user = db_params['db-user'],
passwd = db_params['db-password'],
db = db_params['db-databaseName'])
cur_iso = self.connection.cursor()
cur_iso.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED')
cur_iso.close()
except:
print "Error opening database connection %s. See error log file." % (file)
traceback.print_exc(file=sys.stderr)
print "press enter to continue"
sys.stdin.readline()
sys.exit()
else:
print "Database = %s not recognized." % (c.supported_databases[db_name].db_server)
sys.stderr.write("Database not recognized, exiting.\n")
print "press enter to continue"
sys.exit()
self.import_options = c.get_import_parameters()
self.type = db_params['db-type']
self.sql = SQL.Sql(game = game, type = self.type)
self.connection.rollback()
self.backend = db_params['db-backend']
self.db_server = db_params['db-server']
if self.backend == self.PGSQL:
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE
#ISOLATION_LEVEL_AUTOCOMMIT = 0
#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
# To add to config:
self.hud_style = 'T' # A=All-time
# S=Session
@ -95,31 +76,78 @@ class Database:
# Future values may also include:
# H=Hands (last n hands)
self.hud_hands = 1000 # Max number of hands from each player to use for hud stats
self.hud_days = 90 # Max number of days from each player to use for hud stats
self.hud_days = 30 # Max number of days from each player to use for hud stats
self.hud_session_gap = 30 # Gap (minutes) between hands that indicates a change of session
# (hands every 2 mins for 1 hour = one session, if followed
# by a 40 minute gap and then more hands on same table that is
# a new session)
cur = self.connection.cursor()
self.cursor = self.fdb.cursor
self.hand_1day_ago = 0
cur.execute(self.sql.query['get_hand_1day_ago'])
row = cur.fetchone()
if row and row[0]:
self.hand_1day_ago = row[0]
#print "hand 1day ago =", self.hand_1day_ago
if self.fdb.wrongDbVersion == False:
# self.hand_1day_ago used to fetch stats for current session (i.e. if hud_style = 'S')
self.hand_1day_ago = 0
self.cursor.execute(self.sql.query['get_hand_1day_ago'])
row = self.cursor.fetchone()
if row and row[0]:
self.hand_1day_ago = row[0]
#print "hand 1day ago =", self.hand_1day_ago
d = timedelta(days=self.hud_days)
now = datetime.utcnow() - d
self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day)
# self.date_ndays_ago used if hud_style = 'T'
d = timedelta(days=self.hud_days)
now = datetime.utcnow() - d
self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day)
self.hand_nhands_ago = 0 # todo
#cur.execute(self.sql.query['get_table_name'], (hand_id, ))
#row = cur.fetchone()
# self.hand_nhands_ago is used for fetching stats for last n hands (hud_style = 'H')
# This option not used yet
self.hand_nhands_ago = 0
# should use aggregated version of query if appropriate
self.cursor.execute(self.sql.query['get_hand_nhands_ago'], (self.hud_hands,self.hud_hands))
row = self.cursor.fetchone()
if row and row[0]:
self.hand_nhands_ago = row[0]
print "hand n hands ago =", self.hand_nhands_ago
#self.cursor.execute(self.sql.query['get_table_name'], (hand_id, ))
#row = self.cursor.fetchone()
else:
print "Bailing on DB query, not sure it exists yet"
self.saveActions = False if self.import_options['saveActions'] == False else True
self.connection.rollback() # make sure any locks taken so far are released
# could be used by hud to change hud style
def set_hud_style(self, style):
self.hud_style = style
def do_connect(self, c):
self.fdb.do_connect(c)
def commit(self):
self.fdb.db.commit()
def rollback(self):
self.fdb.db.rollback()
def get_cursor(self):
return self.connection.cursor()
def close_connection(self):
self.connection.close()
def disconnect(self, due_to_error=False):
"""Disconnects the DB (rolls back if param is true, otherwise commits"""
self.fdb.disconnect(due_to_error)
def reconnect(self, due_to_error=False):
"""Reconnects the DB"""
self.fdb.reconnect(due_to_error=False)
def get_backend_name(self):
"""Reconnects the DB"""
return self.fdb.get_backend_name()
def get_table_name(self, hand_id):
c = self.connection.cursor()
c.execute(self.sql.query['get_table_name'], (hand_id, ))
@ -156,21 +184,13 @@ class Database:
def get_cards(self, hand):
"""Get and return the cards for each player in the hand."""
cards = {} # dict of cards, the key is the seat number example: {1: 'AcQd9hTs5d'}
cards = {} # dict of cards, the key is the seat number,
# the value is a tuple of the players cards
# example: {1: (0, 0, 20, 21, 22, 0 , 0)}
c = self.connection.cursor()
c.execute(self.sql.query['get_cards'], [hand])
colnames = [desc[0] for desc in c.description]
cardnames = ['card1', 'card2', 'card3', 'card4', 'card5', 'card6', 'card7']
for row in c.fetchall():
cs = ['', '', '', '', '', '', '']
seat = -1
for col,name in enumerate(colnames):
if name in cardnames:
cs[cardnames.index(name)] = Card.valueSuitFromCard(row[col])
elif name == 'seat_number':
seat = row[col]
if seat != -1:
cards[seat] = ''.join(cs)
cards[row[0]] = row[1:]
return cards
def get_common_cards(self, hand):
@ -178,12 +198,8 @@ class Database:
cards = {}
c = self.connection.cursor()
c.execute(self.sql.query['get_common_cards'], [hand])
colnames = [desc[0] for desc in c.description]
for row in c.fetchall():
s_dict = {}
for name, val in zip(colnames, row):
s_dict[name] = val
cards['common'] = (self.convert_cards(s_dict))
# row = c.fetchone()
cards['common'] = c.fetchone()
return cards
def convert_cards(self, d):
@ -211,7 +227,7 @@ class Database:
def get_action_from_hand(self, hand_no):
action = [ [], [], [], [], [] ]
c = self.connection.cursor()
c.execute(self.sql.query['get_action_from_hand'], (hand_no, ))
c.execute(self.sql.query['get_action_from_hand'], (hand_no,))
for row in c.fetchall():
street = row[0]
act = row[1:]
@ -222,7 +238,7 @@ class Database:
"""Returns a hash of winners:amount won, given a hand number."""
winners = {}
c = self.connection.cursor()
c.execute(self.sql.query['get_winners_from_hand'], (hand, ))
c.execute(self.sql.query['get_winners_from_hand'], (hand,))
for row in c.fetchall():
winners[row[0]] = row[1]
return winners
@ -312,6 +328,159 @@ class Database:
else:
return None
def get_last_insert_id(self):
return self.fdb.getLastInsertId()
#stores a stud/razz hand into the database
def ring_stud(self, config, settings, db, cursor, base, category, site_hand_no, gametype_id, hand_start_time
,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes
,action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName
,seatNos):
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudImportData
,(None, None, None, None, None), (None, None, None, None, None))
#print "before calling store_hands_players_stud, antes:", antes
hands_players_ids = fpdb_simple.store_hands_players_stud(self.backend, db, cursor, hands_id, player_ids
,start_cashes, antes, card_values
,card_suits, winnings, rakes, seatNos)
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types
,allIns, action_amounts, actionNos)
return hands_id
#end def ring_stud
def ring_holdem_omaha(self, config, settings, db, cursor, base, category, site_hand_no, gametype_id
,hand_start_time, names, player_ids, start_cashes, positions, card_values
,card_suits, board_values, board_suits, winnings, rakes, action_types, allIns
,action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a holdem/omaha hand into the database"""
t0 = time()
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
t1 = time()
fpdb_simple.fill_board_cards(board_values, board_suits)
t2 = time()
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats,
hudImportData, board_values, board_suits)
#TEMPORARY CALL! - Just until all functions are migrated
t3 = time()
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha(
self.backend, db, cursor, category, hands_id, player_ids, start_cashes
, positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData)
t4 = time()
#print "ring holdem, backend=%d" % backend
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
t5 = time()
t6 = time()
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
t7 = time()
#print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4, t6-t5)
return hands_id
#end def ring_holdem_omaha
def tourney_holdem_omaha(self, config, settings, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout
,entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId
,siteId #end of tourney specific params
,site_hand_no, gametype_id, hand_start_time, names, player_ids
,start_cashes, positions, card_values, card_suits, board_values
,board_suits, winnings, rakes, action_types, allIns, action_amounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a tourney holdem/omaha hand into the database"""
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
fpdb_simple.fill_board_cards(board_values, board_suits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha_tourney(
self.backend, db, cursor, category, hands_id, player_ids, start_cashes, positions
, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids)
#print "tourney holdem, backend=%d" % backend
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
return hands_id
#end def tourney_holdem_omaha
def tourney_stud(self, config, settings, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries
,prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteId
,siteHandNo, gametypeId, handStartTime, names, playerIds, startCashes, antes
,cardValues, cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
#stores a tourney stud/razz hand into the database
fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, playerIds, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, siteHandNo, gametypeId, handStartTime, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_stud_tourney(self.backend, db, cursor, hands_id
, playerIds, startCashes, antes, cardValues, cardSuits
, winnings, rakes, seatNos, tourneys_players_ids)
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametypeId, hand_start_time, playerIds, hudImportData)
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos)
return hands_id
#end def tourney_stud
def rebuild_hudcache(self):
"""clears hudcache and rebuilds from the individual handsplayers records"""
stime = time()
self.connection.cursor().execute(self.sql.query['clearHudCache'])
self.connection.cursor().execute(self.sql.query['rebuildHudCache'])
self.commit()
print "Rebuild hudcache took %.1f seconds" % (time() - stime,)
#end def rebuild_hudcache
def analyzeDB(self):
"""Do whatever the DB can offer to update index/table statistics"""
stime = time()
if self.backend == self.MYSQL_INNODB:
try:
self.cursor.execute(self.sql.query['analyze'])
except:
print "Error during analyze"
elif self.backend == self.PGSQL:
self.connection.set_isolation_level(0) # allow vacuum to work
try:
self.cursor = self.get_cursor()
self.cursor.execute(self.sql.query['analyze'])
except:
print "Error during analyze:", str(sys.exc_value)
self.connection.set_isolation_level(1) # go back to normal isolation level
self.commit()
atime = time() - stime
print "Analyze took %.1f seconds" % (atime,)
#end def analyzeDB
if __name__=="__main__":
c = Configuration.Config()
@ -333,10 +502,10 @@ if __name__=="__main__":
for p in stat_dict.keys():
print p, " ", stat_dict[p]
# print "nutOmatics stats:"
# stat_dict = db_connection.get_stats_from_hand(h, hero)
# for p in stat_dict.keys():
# print p, " ", stat_dict[p]
#print "nutOmatics stats:"
#stat_dict = db_connection.get_stats_from_hand(h, hero)
#for p in stat_dict.keys():
# print p, " ", stat_dict[p]
print "cards =", db_connection.get_cards(u'1')
db_connection.close_connection

View File

@ -37,7 +37,7 @@ class Everleaf(HandHistoryConverter):
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, debugging=False):
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)
@ -45,7 +45,7 @@ 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)
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"
@ -237,11 +237,14 @@ or None if we fail to get the info """
# Also works with Omaha hands.
cards = m.group('CARDS')
cards = [card.strip() for card in cards.split(',')]
hand.addHoleCards(cards, m.group('PNAME'))
# hand.addHoleCards(cards, m.group('PNAME'))
hand.addHoleCards('PREFLOP', hand.hero, closed=cards, shown=False, mucked=False, dealt=True)
else:
#Not involved in hand
hand.involved = False
def readStudPlayerCards(self, hand, street):
# lol. see Plymouth.txt
logging.warning("Everleaf readStudPlayerCards is only a stub.")
@ -292,7 +295,8 @@ or None if we fail to get the info """
cards = cards.split(', ')
player = m.group('PNAME')
logging.debug("readShownCards %s cards=%s" % (player, cards))
hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
# hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
hand.addShownCards(cards=cards, player=m.group('PNAME'))

View File

@ -30,12 +30,11 @@ import fpdb_db
import FpdbSQLQueries
class Filters(threading.Thread):
def __init__(self, db, settings, config, qdict, display = {},debug=True):
def __init__(self, db, config, qdict, display = {}, debug=True):
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=qdict
self.conf = config
self.display = display
@ -235,7 +234,7 @@ class Filters(threading.Thread):
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
# print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
# print "DEBUG: setting heroes[%s]: %s"%(site, self.heroes[site])
def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site)
@ -556,23 +555,12 @@ def main(argv=None):
config = Configuration.Config()
db = None
settings = {}
settings.update(config.get_db_parameters())
settings.update(config.get_tv_parameters())
settings.update(config.get_import_parameters())
settings.update(config.get_default_paths())
db = fpdb_db.fpdb_db()
db.connect(settings['db-backend'],
settings['db-host'],
settings['db-databaseName'],
settings['db-user'],
settings['db-password'])
db.do_connect(config)
qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name())
i = Filters(db, settings, config, qdict)
i = Filters(db, config, qdict)
main_window = gtk.Window()
main_window.connect('destroy', destroy)
main_window.add(i.get_vbox())

View File

@ -1,161 +0,0 @@
# pokerstars_cash.py
# -*- coding: iso-8859-15
#
# PokerStats, an online poker statistics tracking software for Linux
# Copyright (C) 2007-2008 Mika Boström <bostik@iki.fi>
#
# 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, version 3 of the License.
#
# Modified for use in fpdb by Carl Gherardi
import re
# These are PokerStars specific;
# More importantly, they are currently valid for cash game only.
#####
# XXX: There was a weird problem with saved hand histories in PokerStars
# client 2.491; if a user was present on the table (and thus anywhere in
# the hand history), with non-standard characters in their username, the
# client would prepend a literal Ctrl-P (ASCII 16, 0x10) character to
# the hand history title line. Hence, to allow these strangely saved
# hands to be parsed and imported, there is a conditional "one extra
# character" allowed at the start of the new hand regex.
class FpdbRegex:
def __init__(self):
self.__GAME_INFO_REGEX=''
self.__SPLIT_HAND_REGEX='\n\n\n'
self.__NEW_HAND_REGEX='^.?PokerStars Game #\d+:\s+Hold\'em'
self.__HAND_INFO_REGEX='^.*#(\d+):\s+(\S+)\s([\s\S]+)\s\(\$?([.0-9]+)/\$?([.0-9]+)\)\s-\s(\S+)\s-?\s?(\S+)\s\(?(\w+)\)?'
self.__TABLE_INFO_REGEX='^\S+\s+\'.*\'\s+(\d+)-max\s+Seat\s#(\d+)'
self.__PLAYER_INFO_REGEX='^Seat\s(\d+):\s(.*)\s\(\$?([.\d]+)\s'
self.__POST_SB_REGEX='^(.*):\sposts small blind'
self.__POST_BB_REGEX='^(.*):\sposts big blind'
self.__POST_BOTH_REGEX='^(.*):\sposts small & big blinds'
self.__HAND_STAGE_REGEX='^\*{3}\s(.*)\s\*{3}'
self.__HOLE_CARD_REGEX='^\*{3}\sHOLE CARDS'
self.__FLOP_CARD_REGEX='^\*{3}\sFLOP\s\*{3}\s\[(\S{2})\s(\S{2})\s(\S{2})\]'
self.__TURN_CARD_REGEX='^\*{3}\sTURN\s\*{3}\s\[\S{2}\s\S{2}\s\S{2}\]\s\[(\S{2})\]'
self.__RIVER_CARD_REGEX='^\*{3}\sRIVER\s\*{3}\s\[\S{2}\s\S{2}\s\S{2}\s\S{2}\]\s\[(\S{2})\]'
self.__SHOWDOWN_REGEX='^\*{3}\sSHOW DOWN'
self.__SUMMARY_REGEX='^\*{3}\sSUMMARY'
self.__UNCALLED_BET_REGEX='^Uncalled bet \(\$([.\d]+)\) returned to (.*)'
self.__POT_AND_RAKE_REGEX='^Total\spot\s\$([.\d]+).*\|\sRake\s\$([.\d]+)'
self.__COLLECT_POT_REGEX='^(.*)\scollected\s\$([.\d]+)\sfrom\s((main|side)\s)?pot'
self.__HERO_CARDS_REGEX='^Dealt\sto\s(.*)\s\[(\S{2})\s(\S{2})\]'
self.__SHOWN_CARDS_REGEX='^(.*):\sshows\s\[(\S{2})\s(\S{2})\]'
self.__ACTION_STEP_REGEX='^(.*):\s(bets|checks|raises|calls|folds)((\s\$([.\d]+))?(\sto\s\$([.\d]+))?)?'
self.__SHOWDOWN_ACTION_REGEX='^(.*):\s(shows|mucks)'
self.__SUMMARY_CARDS_REGEX='^Seat\s\d+:\s(.*)\s(showed|mucked)\s\[(\S{2})\s(\S{2})\]'
self.__SUMMARY_CARDS_EXTRA_REGEX='^Seat\s\d+:\s(.*)\s(\(.*\)\s)(showed|mucked)\s\[(\S{2})\s(\S{2})\]'
def compileRegexes(self):
### Compile the regexes
self.game_info_re = re.compile(self.__GAME_INFO_REGEX)
self.split_hand_re = re.compile(self.__SPLIT_HAND_REGEX)
self.hand_start_re = re.compile(self.__NEW_HAND_REGEX)
self.hand_info_re = re.compile(self.__HAND_INFO_REGEX)
self.table_info_re = re.compile(self.__TABLE_INFO_REGEX)
self.player_info_re = re.compile(self.__PLAYER_INFO_REGEX)
self.small_blind_re = re.compile(self.__POST_SB_REGEX)
self.big_blind_re = re.compile(self.__POST_BB_REGEX)
self.both_blinds_re = re.compile(self.__POST_BOTH_REGEX)
self.hand_stage_re = re.compile(self.__HAND_STAGE_REGEX)
self.hole_cards_re = re.compile(self.__HOLE_CARD_REGEX)
self.flop_cards_re = re.compile(self.__FLOP_CARD_REGEX)
self.turn_card_re = re.compile(self.__TURN_CARD_REGEX)
self.river_card_re = re.compile(self.__RIVER_CARD_REGEX)
self.showdown_re = re.compile(self.__SHOWDOWN_REGEX)
self.summary_re = re.compile(self.__SUMMARY_REGEX)
self.uncalled_bet_re = re.compile(self.__UNCALLED_BET_REGEX)
self.collect_pot_re = re.compile(self.__COLLECT_POT_REGEX)
self.hero_cards_re = re.compile(self.__HERO_CARDS_REGEX)
self.cards_shown_re = re.compile(self.__SHOWN_CARDS_REGEX)
self.summary_cards_re = re.compile(self.__SUMMARY_CARDS_REGEX)
self.summary_cards_extra_re = re.compile(self.__SUMMARY_CARDS_EXTRA_REGEX)
self.action_re = re.compile(self.__ACTION_STEP_REGEX)
self.rake_re = re.compile(self.__POT_AND_RAKE_REGEX)
self.showdown_action_re = re.compile(self.__SHOWDOWN_ACTION_REGEX)
# Set methods for plugins to override
def setGameInfoRegex(self, string):
self.__GAME_INFO_REGEX = string
def setSplitHandRegex(self, string):
self.__SPLIT_HAND_REGEX = string
def setNewHandRegex(self, string):
self.__NEW_HAND_REGEX = string
def setHandInfoRegex(self, string):
self.__HAND_INFO_REGEX = string
def setTableInfoRegex(self, string):
self.__TABLE_INFO_REGEX = string
def setPlayerInfoRegex(self, string):
self.__PLAYER_INFO_REGEX = string
def setPostSbRegex(self, string):
self.__POST_SB_REGEX = string
def setPostBbRegex(self, string):
self.__POST_BB_REGEX = string
def setPostBothRegex(self, string):
self.__POST_BOTH_REGEX = string
def setHandStageRegex(self, string):
self.__HAND_STAGE_REGEX = string
def setHoleCardRegex(self, string):
self.__HOLE_CARD_REGEX = string
def setFlopCardRegex(self, string):
self.__FLOP_CARD_REGEX = string
def setTurnCardRegex(self, string):
self.__TURN_CARD_REGEX = string
def setRiverCardRegex(self, string):
self.__RIVER_CARD_REGEX = string
def setShowdownRegex(self, string):
self.__SHOWDOWN_REGEX = string
def setSummaryRegex(self, string):
self.__SUMMARY_REGEX = string
def setUncalledBetRegex(self, string):
self.__UNCALLED_BET_REGEX = string
def setCollectPotRegex(self, string):
self.__COLLECT_POT_REGEX = string
def setHeroCardsRegex(self, string):
self.__HERO_CARDS_REGEX = string
def setShownCardsRegex(self, string):
self.__SHOWN_CARDS_REGEX = string
def setSummaryCardsRegex(self, string):
self.__SUMMARY_CARDS_REGEX = string
def setSummaryCardsExtraRegex(self, string):
self.__SUMMARY_CARDS_EXTRA_REGEX = string
def setActionStepRegex(self, string):
self.__ACTION_STEP_REGEX = string
def setPotAndRakeRegex(self, string):
self.__POT_AND_RAKE_REGEX = string
def setShowdownActionRegex(self, string):
self.__SHOWDOWN_ACTION_REGEX = string

File diff suppressed because it is too large Load Diff

View File

@ -23,25 +23,49 @@ import logging
from HandHistoryConverter import *
# Fulltilt HH Format converter
# TODO: cat tourno and table to make table name for tournaments
class Fulltilt(HandHistoryConverter):
# Static regexes
re_GameInfo = re.compile('- (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (Ante \$(?P<ANTE>[.0-9]+) )?- (?P<LIMIT>(No Limit|Pot Limit|Limit))? (?P<GAME>(Hold\'em|Omaha Hi|Razz))')
re_GameInfo = re.compile('''(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
.+
-\s(?P<CURRENCY>\$|)?
(?P<SB>[.0-9]+)/
\$?(?P<BB>[.0-9]+)\s
(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
(?P<LIMIT>(No\sLimit|Pot\sLimit|Limit))?\s
(?P<GAME>(Hold\'em|Omaha\sHi|Omaha\sH/L|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))
''', re.VERBOSE)
re_SplitHands = re.compile(r"\n\n+")
re_TailSplitHands = re.compile(r"(\n\n+)")
re_HandInfo = re.compile('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[- a-zA-Z]+) (\((?P<TABLEATTRIBUTES>.+)\) )?- \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (Ante \$(?P<ANTE>[.0-9]+) )?- (?P<GAMETYPE>[a-zA-Z\' ]+) - (?P<DATETIME>.*)')
re_HandInfo = re.compile('''.*\#(?P<HID>[0-9]+):\s
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
Table\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)
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]+)\)\n')
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# 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.
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
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)
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"
@ -63,19 +87,24 @@ follow : whether to tail -f the input"""
self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_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)(\s\$(?P<BET>[.\d]+))?" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s(?P<ATYPE> bets| checks| raises to| completes it to| calls| folds)( \$?(?P<BET>[.,\d]+))?" % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_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)
def readSupportedGames(self):
return [["ring", "hold", "nl"],
["ring", "hold", "pl"],
["ring", "hold", "fl"],
["ring", "stud", "fl"],
["ring", "omaha", "pl"]
["tour", "hold", "nl"],
["tour", "hold", "pl"],
["tour", "hold", "fl"],
["tour", "stud", "fl"],
]
def determineGameType(self, handText):
@ -88,7 +117,6 @@ follow : whether to tail -f the input"""
m = self.re_GameInfo.search(handText)
if not m:
return None
mg = m.groupdict()
# translations from captured groups to our info strings
@ -96,39 +124,41 @@ follow : whether to tail -f the input"""
games = { # base, category
"Hold'em" : ('hold','holdem'),
'Omaha Hi' : ('hold','omahahi'),
'Omaha H/L' : ('hold','omahahilo'),
'Razz' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi')
'Stud Hi' : ('stud','studhi'),
'Stud H/L' : ('stud','studhilo')
}
currencies = { u'':'EUR', '$':'USD', '':'T$' }
if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']]
if 'GAME' in mg:
info['limitType'] = limits[mg['LIMIT']]
info['sb'] = mg['SB']
info['bb'] = mg['BB']
if mg['GAME'] != None:
(info['base'], info['category']) = games[mg['GAME']]
if 'SB' in mg:
info['sb'] = mg['SB']
if 'BB' in mg:
info['bb'] = mg['BB']
if 'CURRENCY' in mg:
if mg['CURRENCY'] != 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.
return info
def readHandInfo(self, hand):
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if(m == None):
logging.info("Didn't match re_HandInfo")
logging.info(hand.handText)
return None
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")
hand.maxseats = 8 # assume 8-max until we see otherwise
if m.group('TABLEATTRIBUTES'):
m2 = re.search("(deep )?(\d+)( max)?", m.group('TABLEATTRIBUTES'))
hand.maxseats = int(m2.group(2))
m2 = self.re_Max.search(m.group('TABLEATTRIBUTES'))
if m2: hand.maxseats = int(m2.group('MAX'))
hand.tourNo = m.group('TOURNO')
if m.group('PLAY') != None:
hand.gametype['currency'] = 'play'
# These work, but the info is already in the Hand class - should be used for tourneys though.
# m.group('SB')
# m.group('BB')
@ -190,6 +220,7 @@ follow : whether to tail -f the input"""
m = self.re_Antes.finditer(hand.handText)
for player in m:
logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
# if player.group() !=
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand):
@ -198,79 +229,46 @@ follow : whether to tail -f the input"""
logging.debug("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
else:
logging.warning("No bringin found")
logging.warning("No bringin found, handid =%s" % hand.handid)
def readButton(self, hand):
hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.handText)
if(m == None):
#Not involved in hand
hand.involved = False
else:
hand.hero = m.group('PNAME')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('NEWCARDS')
cards = [c.strip() for c in cards.split(' ')]
hand.addHoleCards(cards, m.group('PNAME'))
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
# we need to grab hero's cards
for street in ('PREFLOP', 'DEAL'):
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 = found.group('NEWCARDS').split(' ')
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
for street, text in hand.streets.iteritems():
if not text or street in ('PREFLOP', 'DEAL'): continue # already done these
m = self.re_HeroCards.finditer(hand.streets[street])
for found in m:
player = found.group('PNAME')
if found.group('NEWCARDS') == None:
newcards = []
else:
newcards = found.group('NEWCARDS').split(' ')
if found.group('OLDCARDS') == None:
oldcards = []
else:
oldcards = found.group('OLDCARDS').split(' ')
if street == 'THIRD' and len(oldcards) == 2: # hero in stud game
hand.hero = player
hand.dealt.add(player) # need this for stud??
hand.addHoleCards(street, player, closed=oldcards, open=newcards, shown=False, mucked=False, dealt=False)
else:
hand.addHoleCards(street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False)
def readStudPlayerCards(self, hand, street):
# This could be the most tricky one to get right.
# It looks for cards dealt in 'street',
# which may or may not be in the section of the hand designated 'street' by markStreets earlier.
# Here's an example at FTP of what 'THIRD' and 'FOURTH' look like to hero PokerAscetic
#
#"*** 3RD STREET ***
#Dealt to BFK23 [Th]
#Dealt to cutiepr1nnymaid [8c]
#Dealt to PokerAscetic [7c 8s] [3h]
#..."
#
#"*** 4TH STREET ***
#Dealt to cutiepr1nnymaid [8c] [2s]
#Dealt to PokerAscetic [7c 8s 3h] [5s]
#..."
#Note that hero's first two holecards are only reported at 3rd street as 'old' cards.
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street])
for player in m:
logging.debug(player.groupdict())
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
if oldcards:
oldcards = [c.strip() for c in oldcards.split(' ')]
if newcards:
newcards = [c.strip() for c in newcards.split(' ')]
# options here:
# (1) we trust the hand will know what to do -- probably check that the old cards match what it already knows, and add the newcards to this street.
# (2) we're the experts at this particular history format and we know how we're going to be called (once for each street in Hand.streetList)
# so call addPlayerCards with the appropriate information.
# I favour (2) here but I'm afraid it is rather stud7-specific.
# in the following, the final list of cards will be in 'newcards' whilst if the first list exists (most of the time it does) it will be in 'oldcards'
if street=='ANTES':
return
elif street=='THIRD':
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# hero: [xx][o]
# others: [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# 4th:
# hero: [xxo] [o]
# others: [o] [o]
# 5th:
# hero: [xxoo] [o]
# others: [oo] [o]
# 6th:
# hero: [xxooo] [o]
# others: [ooo] [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
elif street=='SEVENTH' and newcards:
# hero: [xxoooo] [x]
# others: not reported.
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street])
@ -308,6 +306,29 @@ follow : whether to tail -f the input"""
cards = cards.split(' ')
hand.addShownCards(cards=cards, player=m.group('PNAME'))
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy
if hand.gametype['base'] == 'stud':
if mo <= 8: return 8
else: return mo
if hand.gametype['base'] == 'draw':
if mo <= 6: return 6
else: return mo
if mo == 2: return 2
if mo <= 6: return 6
return 9
def readOther(self, hand):
m = self.re_Mixed.search(self.in_path)
if m == None: hand.mixed = None
else:
hand.mixed = self.mixes[m.groupdict()['MIXED']]
if __name__ == "__main__":
parser = OptionParser()

View File

@ -28,6 +28,7 @@ import time
import fpdb_import
from optparse import OptionParser
import Configuration
import string
class GuiAutoImport (threading.Thread):
def __init__(self, settings, config):
@ -153,37 +154,40 @@ class GuiAutoImport (threading.Thread):
# results to the same pipe. This means that self.path should be a a list of dirs
# to watch.
if widget.get_active(): # toggled on
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" + " %s" % (self.database)
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')
#command = self.config.execution_path('HUD_main.py') # Hi Ray. Sorry about this, kludging.
bs = 1
self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True)
# self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
# universal_newlines=True)
# command = command + " %s" % (self.database)
# print "command = ", command
# self.pipe_to_hud = os.popen(command, 'w')
# - Does the lock acquisition need to be more sophisticated for multiple dirs?
# (see comment above about what to do if pipe already open)
# - 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
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.
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])
self.do_import()
# Add directories to importer object.
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])
self.do_import()
interval=int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import)
interval=int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import)
else:
print "auto-import aborted - global lock not available"
else: # toggled off
self.settings['global_lock'].release()
self.doAutoImportBool = False # do_import will return this and stop the gobject callback timer
print "Stopping autoimport"
print "Stopping autoimport - global lock released."
if self.pipe_to_hud.poll() is not None:
print "HUD already terminated"
else:
@ -191,8 +195,6 @@ class GuiAutoImport (threading.Thread):
self.pipe_to_hud.communicate('\n') # waits for process to terminate
self.pipe_to_hud = None
self.startButton.set_label(u' _Start Autoimport ')
#end def GuiAutoImport.startClicked

View File

@ -49,37 +49,47 @@ class GuiBulkImport():
self.importer.RunImportThreaded()
def load_clicked(self, widget, data=None):
# get the dir to import from the chooser
self.inputFile = self.chooser.get_filename()
# Does the lock acquisition need to be more sophisticated for multiple dirs?
# (see comment above about what to do if pipe already open)
if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired
try:
print "\nGlobal lock taken ..."
# get the dir to import from the chooser
self.inputFile = self.chooser.get_filename()
# get the import settings from the gui and save in the importer
self.importer.setHandCount(int(self.spin_hands.get_text()))
self.importer.setMinPrint(int(self.spin_hands.get_text()))
self.importer.setQuiet(self.chk_st_st.get_active())
self.importer.setFailOnError(self.chk_fail.get_active())
self.importer.setThreads(int(self.spin_threads.get_text()))
self.importer.setHandsInDB(self.n_hands_in_db)
cb_model = self.cb_dropindexes.get_model()
cb_index = self.cb_dropindexes.get_active()
if cb_index:
self.importer.setDropIndexes(cb_model[cb_index][0])
# get the import settings from the gui and save in the importer
self.importer.setHandCount(int(self.spin_hands.get_text()))
self.importer.setMinPrint(int(self.spin_hands.get_text()))
self.importer.setQuiet(self.chk_st_st.get_active())
self.importer.setFailOnError(self.chk_fail.get_active())
self.importer.setThreads(int(self.spin_threads.get_text()))
self.importer.setHandsInDB(self.n_hands_in_db)
cb_model = self.cb_dropindexes.get_model()
cb_index = self.cb_dropindexes.get_active()
if cb_index:
self.importer.setDropIndexes(cb_model[cb_index][0])
else:
self.importer.setDropIndexes("auto")
sitename = self.cbfilter.get_model()[self.cbfilter.get_active()][0]
self.lab_info.set_text("Importing")
self.importer.addBulkImportImportFileOrDir(self.inputFile, site = sitename)
self.importer.setCallHud(False)
starttime = time()
(stored, dups, partial, errs, ttime) = self.importer.runImport()
ttime = time() - starttime
if ttime == 0:
ttime = 1
print 'GuiBulkImport.import_dir done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %d/sec'\
% (stored, dups, partial, errs, ttime, stored / ttime)
self.importer.clearFileList()
self.lab_info.set_text("Import finished")
except:
pass
self.settings['global_lock'].release()
else:
self.importer.setDropIndexes("auto")
sitename = self.cbfilter.get_model()[self.cbfilter.get_active()][0]
self.lab_info.set_text("Importing")
self.importer.addBulkImportImportFileOrDir(self.inputFile, site = sitename)
self.importer.setCallHud(False)
starttime = time()
(stored, dups, partial, errs, ttime) = self.importer.runImport()
ttime = time() - starttime
if ttime == 0:
ttime = 1
print 'GuiBulkImport.import_dir done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %d/sec'\
% (stored, dups, partial, errs, ttime, stored / ttime)
self.importer.clearFileList()
self.lab_info.set_text("Import finished")
print "bulk-import aborted - global lock not available"
def get_vbox(self):
"""returns the vbox of this thread"""
@ -88,8 +98,7 @@ class GuiBulkImport():
def __init__(self, settings, config):
self.settings = settings
self.config = config
self.importer = fpdb_import.Importer(self, self.settings,
config)
self.importer = fpdb_import.Importer(self, self.settings, config)
self.vbox = gtk.VBox(False, 0)
self.vbox.show()
@ -196,10 +205,11 @@ class GuiBulkImport():
self.load_button.show()
# see how many hands are in the db and adjust accordingly
tcursor = self.importer.fdb.db.cursor()
tcursor = self.importer.database.cursor
tcursor.execute("Select count(1) from Hands")
row = tcursor.fetchone()
tcursor.close()
self.importer.database.rollback()
self.n_hands_in_db = row[0]
if self.n_hands_in_db == 0:
self.cb_dropindexes.set_active(2)

View File

@ -38,20 +38,19 @@ except:
and HUD are NOT affected by this problem."""
import fpdb_import
import fpdb_db
import Database
import Filters
class GuiGraphViewer (threading.Thread):
def __init__(self, db, settings, querylist, config, debug=True):
def __init__(self, querylist, config, debug=True):
"""Constructor for GraphViewer"""
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=querylist
self.sql = querylist
self.conf = config
self.debug = debug
#print "start of GraphViewer constructor"
self.db = Database.Database(self.conf, sql=self.sql)
filters_display = { "Heroes" : True,
"Sites" : True,
@ -63,7 +62,7 @@ class GuiGraphViewer (threading.Thread):
"Button2" : True
}
self.filters = Filters.Filters(db, settings, config, querylist, display = filters_display)
self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton1Name("Refresh Graph")
self.filters.registerButton1Callback(self.generateGraph)
self.filters.registerButton2Name("Export to File")
@ -90,7 +89,7 @@ class GuiGraphViewer (threading.Thread):
self.canvas = None
self.db.db.rollback()
self.db.rollback()
#################################
#
@ -126,7 +125,7 @@ class GuiGraphViewer (threading.Thread):
#end def get_vbox
def clearGraphData(self):
self.fig.clf()
self.fig.clear()
if self.canvas is not None:
self.canvas.destroy()
@ -146,7 +145,7 @@ class GuiGraphViewer (threading.Thread):
for site in sites:
if sites[site] == True:
sitenos.append(siteids[site])
self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
self.db.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
result = self.db.cursor.fetchall()
if len(result) == 1:
playerids.append(result[0][0])
@ -154,7 +153,7 @@ class GuiGraphViewer (threading.Thread):
if not sitenos:
#Should probably pop up here.
print "No sites selected - defaulting to PokerStars"
sitenos = [2]
return
if not playerids:
print "No player ids found"
@ -197,6 +196,7 @@ class GuiGraphViewer (threading.Thread):
self.graphBox.add(self.canvas)
self.canvas.show()
self.canvas.draw()
#self.exportButton.set_sensitive(True)
#end of def showClicked
@ -205,7 +205,7 @@ class GuiGraphViewer (threading.Thread):
# print "DEBUG: getRingProfitGraph"
start_date, end_date = self.filters.getDates()
#Buggered if I can find a way to do this 'nicely' take a list of intergers and longs
#Buggered if I can find a way to do this 'nicely' take a list of integers and longs
# and turn it into a tuple readale by sql.
# [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
nametest = str(tuple(names))
@ -226,10 +226,10 @@ class GuiGraphViewer (threading.Thread):
#print "DEBUG: sql query:"
#print tmp
self.cursor.execute(tmp)
self.db.cursor.execute(tmp)
#returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall()
self.db.db.rollback()
self.db.rollback()
if(winnings == ()):
return None

View File

@ -20,34 +20,35 @@ import pygtk
pygtk.require('2.0')
import gtk
import os
import sys
from time import time, strftime
import Card
import fpdb_import
import Database
import fpdb_db
import Filters
import FpdbSQLQueries
class GuiPlayerStats (threading.Thread):
def __init__(self, config, querylist, mainwin, debug=True):
self.debug=debug
self.conf=config
self.main_window=mainwin
self.debug = debug
self.conf = config
self.main_window = mainwin
self.sql = querylist
self.MYSQL_INNODB = 2
self.PGSQL = 3
self.SQLITE = 4
# create new db connection to avoid conflicts with other threads
self.db = fpdb_db.fpdb_db()
self.db.do_connect(self.conf)
self.cursor=self.db.cursor
self.sql = querylist
self.db = Database.Database(self.conf, sql=self.sql)
self.cursor = self.db.cursor
settings = {}
settings.update(config.get_db_parameters())
settings.update(config.get_tv_parameters())
settings.update(config.get_import_parameters())
settings.update(config.get_default_paths())
settings.update(self.conf.get_db_parameters())
settings.update(self.conf.get_tv_parameters())
settings.update(self.conf.get_import_parameters())
settings.update(self.conf.get_default_paths())
# text used on screen stored here so that it can be configured
self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
@ -66,7 +67,7 @@ class GuiPlayerStats (threading.Thread):
"Button2" : True
}
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display)
self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton1Name("_Filters")
self.filters.registerButton1Callback(self.showDetailFilter)
self.filters.registerButton2Name("_Refresh")
@ -216,7 +217,7 @@ class GuiPlayerStats (threading.Thread):
flags = [True]
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates)
self.db.db.commit()
self.db.rollback()
print "Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox):
@ -227,11 +228,6 @@ class GuiPlayerStats (threading.Thread):
if not flags: holecards = False
else: holecards = flags[0]
self.stats_table = gtk.Table(1, 1, False)
self.stats_table.set_col_spacings(4)
self.stats_table.show()
tmp = self.sql.query[query]
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats, groups, dates)
self.cursor.execute(tmp)
@ -279,18 +275,16 @@ class GuiPlayerStats (threading.Thread):
while sqlrow < rows:
treerow = []
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
for col,column in enumerate(cols_to_show):
if column[colalias] in colnames:
value = result[sqlrow][colnames.index(column[colalias])]
if column[colalias] == 'plposition':
if value == 'B':
value = 'BB'
if value == 'S':
elif value == 'S':
value = 'SB'
elif value == '0':
value = 'Btn'
else:
if column[colalias] == 'game':
if holecards:
@ -388,7 +382,8 @@ class GuiPlayerStats (threading.Thread):
# Group by position?
if groups['posn']:
query = query.replace("<position>", 'hp.position')
#query = query.replace("<position>", "case hp.position when '0' then 'Btn' else hp.position end")
query = query.replace("<position>", "hp.position")
# set flag in self.columns to show posn column
[x for x in self.columns if x[0] == 'plposition'][0][1] = True
else:

View File

@ -20,31 +20,31 @@ import pygtk
pygtk.require('2.0')
import gtk
import os
from time import time, strftime
import fpdb_import
import fpdb_db
import Database
import Filters
import FpdbSQLQueries
class GuiPositionalStats (threading.Thread):
def __init__(self, config, querylist, debug=True):
self.debug=debug
self.conf=config
self.debug = debug
self.conf = config
self.sql = querylist
self.MYSQL_INNODB = 2
self.PGSQL = 3
self.SQLITE = 4
# create new db connection to avoid conflicts with other threads
self.db = fpdb_db.fpdb_db()
self.db.do_connect(self.conf)
self.cursor=self.db.cursor
self.sql = querylist
self.db = Database.Database(self.conf, sql=self.sql)
self.cursor = self.db.cursor
settings = {}
settings.update(config.get_db_parameters())
settings.update(config.get_tv_parameters())
settings.update(config.get_import_parameters())
settings.update(config.get_default_paths())
settings.update(self.conf.get_db_parameters())
settings.update(self.conf.get_tv_parameters())
settings.update(self.conf.get_import_parameters())
settings.update(self.conf.get_default_paths())
filters_display = { "Heroes" : True,
"Sites" : True,
@ -58,21 +58,50 @@ class GuiPositionalStats (threading.Thread):
"Button2" : False
}
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display)
self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton1Name("Refresh")
self.filters.registerButton1Callback(self.refreshStats)
# ToDo: store in config
# ToDo: create popup to adjust column config
# columns to display, keys match column name returned by sql, values in tuple are:
# is column displayed, column heading, xalignment, formatting
self.columns = [ ["game", True, "Game", 0.0, "%s"]
, ["hand", False, "Hand", 0.0, "%s"] # true not allowed for this line
, ["plposition", False, "Posn", 1.0, "%s"] # true not allowed for this line (set in code)
, ["n", True, "Hds", 1.0, "%d"]
, ["avgseats", True, "Seats", 1.0, "%3.1f"]
, ["vpip", True, "VPIP", 1.0, "%3.1f"]
, ["pfr", True, "PFR", 1.0, "%3.1f"]
, ["pf3", True, "PF3", 1.0, "%3.1f"]
, ["steals", True, "Steals", 1.0, "%3.1f"]
, ["saw_f", True, "Saw_F", 1.0, "%3.1f"]
, ["sawsd", True, "SawSD", 1.0, "%3.1f"]
, ["wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f"]
, ["wmsd", True, "W$SD", 1.0, "%3.1f"]
, ["flafq", True, "FlAFq", 1.0, "%3.1f"]
, ["tuafq", True, "TuAFq", 1.0, "%3.1f"]
, ["rvafq", True, "RvAFq", 1.0, "%3.1f"]
, ["pofafq", False, "PoFAFq", 1.0, "%3.1f"]
, ["net", True, "Net($)", 1.0, "%6.2f"]
, ["bbper100", True, "bb/100", 1.0, "%4.2f"]
, ["rake", True, "Rake($)", 1.0, "%6.2f"]
, ["bb100xr", True, "bbxr/100", 1.0, "%4.2f"]
, ["variance", True, "Variance", 1.0, "%5.2f"]
]
self.stat_table = None
self.stats_frame = None
self.stats_vbox = None
self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show()
statsFrame = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0)
statsFrame.show()
self.stats_frame = gtk.VBox(False, 0)
self.stats_frame = gtk.Frame()
self.stats_frame.set_label_align(0.0, 0.0)
self.stats_frame.show()
self.stats_vbox = gtk.VBox(False, 0)
self.stats_vbox.show()
# This could be stored in config eventually, or maybe configured in this window somehow.
# Each posncols element is the name of a column returned by the sql
@ -90,11 +119,11 @@ class GuiPositionalStats (threading.Thread):
, "PoFAFq", "Net($)", "bb/100", "$/hand", "Variance", "Hds"
)
self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame)
self.fillStatsFrame(self.stats_vbox)
self.stats_frame.add(self.stats_vbox)
self.main_hbox.pack_start(self.filters.get_vbox())
self.main_hbox.pack_start(statsFrame)
self.main_hbox.pack_start(self.stats_frame)
def get_vbox(self):
@ -107,9 +136,12 @@ class GuiPositionalStats (threading.Thread):
print "DEBUG: activesite set to %s" %(self.activesite)
def refreshStats(self, widget, data):
try: self.stats_table.destroy()
try: self.stats_vbox.destroy()
except AttributeError: pass
self.fillStatsFrame(self.stats_frame)
self.stats_vbox = gtk.VBox(False, 0)
self.stats_vbox.show()
self.stats_frame.add(self.stats_vbox)
self.fillStatsFrame(self.stats_vbox)
def fillStatsFrame(self, vbox):
sites = self.filters.getSites()
@ -144,66 +176,104 @@ class GuiPositionalStats (threading.Thread):
self.createStatsTable(vbox, playerids, sitenos, limits, seats, dates)
def createStatsTable(self, vbox, playerids, sitenos, limits, seats, dates):
self.stats_table = gtk.Table(1, 1, False) # gtk table expands as required
self.stats_table.set_col_spacings(4)
self.stats_table.show()
vbox.add(self.stats_table)
starttime = time()
colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4
row = 0
col = 0
for t in self.posnheads:
l = gtk.Label(self.posnheads[col])
l.show()
self.stats_table.attach(l, col, col+1, row, row+1, yoptions=gtk.SHRINK)
col +=1
tmp = self.sql.query['playerStatsByPosition']
tmp = self.refineQuery(tmp, playerids, sitenos, limits, seats, dates)
self.cursor.execute(tmp)
result = self.cursor.fetchall()
colnames = [desc[0].lower() for desc in self.cursor.description]
liststore = gtk.ListStore(*([str] * len(colnames)))
view = gtk.TreeView(model=liststore)
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
vbox.pack_start(view, expand=False, padding=3)
# left-aligned cells:
textcell = gtk.CellRendererText()
# centred cells:
textcell50 = gtk.CellRendererText()
textcell50.set_property('xalign', 0.5)
# right-aligned cells:
numcell = gtk.CellRendererText()
numcell.set_property('xalign', 1.0)
listcols = []
for t in self.posnheads:
listcols.append(gtk.TreeViewColumn(self.posnheads[col]))
view.append_column(listcols[col])
if col == 0:
listcols[col].pack_start(textcell, expand=True)
listcols[col].add_attribute(textcell, 'text', col)
listcols[col].set_expand(True)
elif col in (1, 2):
listcols[col].pack_start(textcell50, expand=True)
listcols[col].add_attribute(textcell50, 'text', col)
listcols[col].set_expand(True)
else:
listcols[col].pack_start(numcell, expand=True)
listcols[col].add_attribute(numcell, 'text', col)
listcols[col].set_expand(True)
col +=1
# Code below to be used when full column data structures implemented like in player stats:
# Create header row eg column: ("game", True, "Game", 0.0, "%s")
#for col, column in enumerate(cols_to_show):
# if column[colalias] == 'game' and holecards:
# s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading]
# else:
# s = column[colheading]
# listcols.append(gtk.TreeViewColumn(s))
# view.append_column(listcols[col])
# if column[colformat] == '%s':
# if column[colxalign] == 0.0:
# listcols[col].pack_start(textcell, expand=True)
# listcols[col].add_attribute(textcell, 'text', col)
# else:
# listcols[col].pack_start(textcell50, expand=True)
# listcols[col].add_attribute(textcell50, 'text', col)
# listcols[col].set_expand(True)
# else:
# listcols[col].pack_start(numcell, expand=True)
# listcols[col].add_attribute(numcell, 'text', col)
# listcols[col].set_expand(True)
# #listcols[col].set_alignment(column[colxalign]) # no effect?
rows = len(result)
colnames = [desc[0].lower() for desc in self.cursor.description]
last_game,last_seats,sqlrow = "","",0
while sqlrow < rows:
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
rowprinted=0
treerow = []
avgcol = colnames.index('avgseats')
for col,colname in enumerate(self.posncols):
if colname in colnames:
sqlcol = colnames.index(colname)
else:
continue
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
# print blank row between levels:
if result[sqlrow][sqlcol]:
if sqlrow == 0:
l = gtk.Label(result[sqlrow][sqlcol])
value = result[sqlrow][sqlcol]
rowprinted=1
elif result[sqlrow][0] != last_game:
l = gtk.Label(' ')
value = ' '
elif 'show' in seats and seats['show'] and result[sqlrow][avgcol] != last_seats:
l = gtk.Label(' ')
value = ' '
else:
l = gtk.Label(result[sqlrow][sqlcol])
value = result[sqlrow][sqlcol]
rowprinted=1
else:
l = gtk.Label(' ')
if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5)
elif col == 1:
l.set_alignment(xalign=0.5, yalign=0.5)
value = ' '
if value and value != -999:
treerow.append(value)
else:
l.set_alignment(xalign=1.0, yalign=0.5)
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
treerow.append(' ')
iter = liststore.append(treerow)
last_game = result[sqlrow][0]
last_seats = result[sqlrow][avgcol]
if rowprinted:
@ -220,50 +290,36 @@ class GuiPositionalStats (threading.Thread):
# blank row between main stats and totals:
col = 0
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
l = gtk.Label(' ')
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
treerow = [' ' for x in self.posncols]
iter = liststore.append(treerow)
row = row + 1
for sqlrow in range(rows):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
treerow = []
for col,colname in enumerate(self.posncols):
if colname in colnames:
sqlcol = colnames.index(colname)
elif colname != "plposition":
continue
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
if colname == 'plposition':
l = gtk.Label('Totals')
value = 'Totals'
elif result[sqlrow][sqlcol]:
l = gtk.Label(result[sqlrow][sqlcol])
value = result[sqlrow][sqlcol]
else:
l = gtk.Label(' ')
if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5)
elif col == 1:
l.set_alignment(xalign=0.5, yalign=0.5)
value = ' '
if value and value != -999:
treerow.append(value)
else:
l.set_alignment(xalign=1.0, yalign=0.5)
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
treerow.append(' ')
iter = liststore.append(treerow)
row = row + 1
vbox.show_all()
self.db.db.rollback()
self.db.rollback()
print "Positional Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox):
def refineQuery(self, query, playerids, sitenos, limits, seats, dates):

View File

@ -25,29 +25,28 @@ from numpy import diff, nonzero
import Card
import fpdb_import
import fpdb_db
import Database
import Filters
import FpdbSQLQueries
class GuiSessionViewer (threading.Thread):
def __init__(self, config, querylist, debug=True):
self.debug=debug
self.conf=config
self.debug = debug
self.conf = config
self.sql = querylist
self.MYSQL_INNODB = 2
self.PGSQL = 3
self.SQLITE = 4
# create new db connection to avoid conflicts with other threads
self.db = fpdb_db.fpdb_db()
self.db.do_connect(self.conf)
self.cursor=self.db.cursor
self.sql = querylist
self.db = Database.Database(self.conf, sql=self.sql)
self.cursor = self.db.cursor
settings = {}
settings.update(config.get_db_parameters())
settings.update(config.get_tv_parameters())
settings.update(config.get_import_parameters())
settings.update(config.get_default_paths())
settings.update(self.conf.get_db_parameters())
settings.update(self.conf.get_tv_parameters())
settings.update(self.conf.get_import_parameters())
settings.update(self.conf.get_default_paths())
# text used on screen stored here so that it can be configured
self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
@ -66,7 +65,7 @@ class GuiSessionViewer (threading.Thread):
"Button2" : True
}
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display)
self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton2Name("_Refresh")
self.filters.registerButton2Callback(self.refreshStats)
@ -195,7 +194,7 @@ class GuiSessionViewer (threading.Thread):
flags = [True]
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats)
self.db.db.commit()
self.db.rollback()
print "Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox):

View File

@ -159,6 +159,50 @@
<location seat="9" x="70" y="53"> </location>
</layout>
</site>
<site enabled="False"
site_name="Win2day"
table_finder="Win2day.exe"
screen_name="YOUR SCREEN NAME HERE"
site_path=""
HH_path=""
decoder="everleaf_decode_table"
converter="Win2dayToFpdb"
supported_games="holdem">
<layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location>
<location seat="2" x="650" y="230"> </location>
<location seat="3" x="650" y="385"> </location>
<location seat="4" x="588" y="425"> </location>
<location seat="5" x="92" y="425"> </location>
<location seat="6" x="0" y="373"> </location>
<location seat="7" x="0" y="223"> </location>
<location seat="8" x="25" y="50"> </location>
</layout>
<layout fav_seat="0" height="547" max="6" width="794">
<location seat="1" x="640" y="58"> </location>
<location seat="2" x="654" y="288"> </location>
<location seat="3" x="615" y="424"> </location>
<location seat="4" x="70" y="421"> </location>
<location seat="5" x="0" y="280"> </location>
<location seat="6" x="70" y="58"> </location>
</layout>
<layout fav_seat="0" height="547" max="2" width="794">
<location seat="1" x="651" y="288"> </location>
<location seat="2" x="10" y="288"> </location>
</layout>
<layout fav_seat="0" height="547" max="9" width="794">
<location seat="1" x="634" y="38"> </location>
<location seat="2" x="667" y="184"> </location>
<location seat="3" x="667" y="321"> </location>
<location seat="4" x="667" y="445"> </location>
<location seat="5" x="337" y="459"> </location>
<location seat="6" x="0" y="400"> </location>
<location seat="7" x="0" y="322"> </location>
<location seat="8" x="0" y="181"> </location>
<location seat="9" x="70" y="53"> </location>
</layout>
</site>
</supported_sites>
<supported_games>
@ -292,6 +336,7 @@
<hhc site="PokerStars" converter="PokerStarsToFpdb"/>
<hhc site="Full Tilt Poker" converter="FulltiltToFpdb"/>
<hhc site="Everleaf" converter="EverleafToFpdb"/>
<hhc site="Win2day" converter="Win2dayToFpdb"/>
</hhcs>
<supported_databases>
@ -300,4 +345,3 @@
</FreePokerToolsConfig>

View File

@ -29,12 +29,16 @@ Main for FreePokerTools HUD.
# Standard Library modules
import sys
# redirect the stderr
errorfile = open('HUD-error.txt', 'w', 0)
sys.stderr = errorfile
import os
import Options
(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('fpdb-error-log.txt', 'w', 0)
sys.stderr = errorFile
import thread
import time
import string
@ -59,7 +63,7 @@ class HUD_main(object):
def __init__(self, db_name = 'fpdb'):
self.db_name = db_name
self.config = Configuration.Config()
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
self.hud_dict = {}
# a thread to read stdin
@ -198,17 +202,12 @@ class HUD_main(object):
self.db_connection.connection.rollback()
if __name__== "__main__":
sys.stderr.write("HUD_main starting\n")
# database name can be passed on command line
try:
db_name = sys.argv[1]
except:
db_name = 'fpdb'
sys.stderr.write("Using db name = %s\n" % (db_name))
sys.stderr.write("HUD_main starting\n")
sys.stderr.write("Using db name = %s\n" % (options.dbname))
# start the HUD_main object
hm = HUD_main(db_name = db_name)
hm = HUD_main(db_name = options.dbname)
# start the event loop
gtk.main()

File diff suppressed because it is too large Load Diff

View File

@ -28,93 +28,56 @@ import codecs
from decimal import Decimal
import operator
from xml.dom.minidom import Node
# from pokereval import PokerEval
import time
import datetime
import gettext
#from pokerengine.pokercards import *
# provides letter2name{}, letter2names{}, visible_card(), not_visible_card(), is_visible(), card_value(), class PokerCards
# but it's probably not installed so here are the ones we may want:
letter2name = {
'A': 'Ace',
'K': 'King',
'Q': 'Queen',
'J': 'Jack',
'T': 'Ten',
'9': 'Nine',
'8': 'Eight',
'7': 'Seven',
'6': 'Six',
'5': 'Five',
'4': 'Four',
'3': 'Trey',
'2': 'Deuce'
}
letter2names = {
'A': 'Aces',
'K': 'Kings',
'Q': 'Queens',
'J': 'Jacks',
'T': 'Tens',
'9': 'Nines',
'8': 'Eights',
'7': 'Sevens',
'6': 'Sixes',
'5': 'Fives',
'4': 'Fours',
'3': 'Treys',
'2': 'Deuces'
}
import gettext
gettext.install('myapplication')
gettext.install('fpdb')
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):
logging.info("HandHistory init called")
def __init__(self, in_path = '-', out_path = '-', sitename = None, follow=False, index=0):
logging.info("HandHistory init")
# 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
if self.out_path == '-':
# write to stdout
self.processedHands = []
if in_path == '-':
self.in_fh = sys.stdin
if out_path == '-':
self.out_fh = sys.stdout
else:
# TODO: out_path should be sanity checked before opening. Perhaps in fpdb_import?
# I'm not sure what we're looking for, although we don't want out_path==in_path!='-'
self.out_fh = open(self.out_path, 'w') # doomswitch is now on :|
# TODO: out_path should be sanity checked.
self.out_fh = open(self.out_path, 'w')
self.sitename = sitename
self.follow = follow
self.compiledPlayers = set()
self.maxseats = 10
def __str__(self):
#TODO : I got rid of most of the hhdir stuff.
tmp = "HandHistoryConverter: '%s'\n" % (self.sitename)
#tmp = tmp + "\thhbase: '%s'\n" % (self.hhbase)
#tmp = tmp + "\thhdir: '%s'\n" % (self.hhdir)
tmp = tmp + "\tfiletype: '%s'\n" % (self.filetype)
tmp = tmp + "\tinfile: '%s'\n" % (self.in_path)
tmp = tmp + "\toutfile: '%s'\n" % (self.out_path)
#tmp = tmp + "\tgametype: '%s'\n" % (self.gametype[0])
#tmp = tmp + "\tgamebase: '%s'\n" % (self.gametype[1])
#tmp = tmp + "\tlimit: '%s'\n" % (self.gametype[2])
#tmp = tmp + "\tsb/bb: '%s/%s'\n" % (self.gametype[3], self.gametype[4])
return tmp
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 }
def start(self):
"""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"
@ -129,7 +92,7 @@ Otherwise, finish at eof...
handsList = self.allHandsAsList()
logging.info("Parsing %d hands" % len(handsList))
for handText in handsList:
self.processHand(handText)
self.processedHands.append(self.processHand(handText))
numHands= len(handsList)
endtime = time.time()
print "read %d hands in %.3f seconds" % (numHands, endtime - starttime)
@ -213,6 +176,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
def processHand(self, handText):
gametype = self.determineGameType(handText)
logging.debug("gametype %s" % gametype)
hand = None
if gametype is None:
l = None
gametype = "unmatched"
@ -224,9 +188,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
base = gametype['base']
limit = gametype['limitType']
l = [type] + [base] + [limit]
hand = None
if l in self.readSupportedGames():
hand = None
if gametype['base'] == 'hold':
logging.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)")
hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handText)
@ -238,7 +200,9 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
logging.info("Unsupported game type: %s" % gametype)
if hand:
# print hand
hand.writeHand(self.out_fh)
return hand
else:
logging.info("Unsupported game type: %s" % gametype)
# TODO: pity we don't know the HID at this stage. Log the entire hand?
@ -305,6 +269,11 @@ or None if we fail to get the info """
def readAction(self, hand, street): abstract
def readCollectPot(self, hand): abstract
def readShownCards(self, hand): abstract
# Some sites do odd stuff that doesn't fall in to the normal HH parsing.
# e.g., FTP doesn't put mixed game info in the HH, but puts in in the
# file name. Use readOther() to clean up those messes.
def readOther(self, hand): pass
# Some sites don't report the rake. This will be called at the end of the hand after the pot total has been calculated
# an inheriting class can calculate it for the specific site if need be.
@ -349,6 +318,7 @@ or None if we fail to get the info """
self.filetype = filetype
self.codepage = codepage
#This function doesn't appear to be used
def splitFileIntoHands(self):
hands = []
self.obs = self.obs.strip()
@ -370,7 +340,9 @@ or None if we fail to get the info """
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()
elif(self.filetype == "xml"):
try:
@ -379,10 +351,39 @@ or None if we fail to get the info """
except:
traceback.print_exc(file=sys.stderr)
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy
if hand.gametype['base'] == 'stud':
if mo <= 8: return 8
else: return mo
if hand.gametype['base'] == 'draw':
if mo <= 6: return 6
else: return mo
if mo == 2: return 2
if mo <= 6: return 6
return 10
def maxOccSeat(self, hand):
max = 0
for player in hand.players:
if player[0] > max: max = player[0]
return max
def getStatus(self):
#TODO: Return a status of true if file processed ok
return True
def getProcessedHands(self):
return self.processedHands
def getProcessedFile(self):
return self.out_path
def getLastCharacterRead(self):
return self.index

View File

@ -34,10 +34,13 @@ import gobject
# FreePokerTools modules
from Mucked import Aux_Window
from Mucked import Seat_Window
from Mucked import Aux_Seats
class Hello(Aux_Window):
"""A 'Hello World' Aux_Window demo."""
def create(self):
print "creating Hello"
# This demo simply creates a label in a window.
self.container = gtk.Window()
self.container.add(gtk.Label("Hello World"))
@ -99,15 +102,18 @@ class Hello_plus(Aux_Window):
# hands played that was updated in the "update_data()" function.
self.label.set_text("Hello %s\nYou have played %d hands\n on %s." % (self.hero, self.hands_played, self.site))
class Hello_Menu(Aux_Window):
"""A 'Hello World' Aux_Window demo."""
def create(self):
# This demo puts a menu item on the HUD mainwindow.
self.item = gtk.MenuItem('Print cards')
self.hud.menu.append(self.item)
self.item.connect("activate", self.print_cards)
self.item.show()
class Hello_Seats(Aux_Seats):
"""A 'Hello World' Seat_Window demo."""
def print_cards(self, *args):
# callback for the menu item
print "cards =", self.hud.cards
def create_contents(self, container, i):
container.label = gtk.Label("empty")
container.add(container.label)
container.show_all()
def update_contents(self, container, i):
if i == "common": return
id = self.get_id_from_seat(i)
if id == None:
container.label.set_text("empty")
else:
container.label.set_text("player = %s" % self.hud.stat_dict[id]['screen_name'])

View File

@ -43,7 +43,7 @@ import Configuration
import Stats
import Mucked
import Database
import HUD_main
#import HUD_main
def importName(module_name, name):
"""Import a named object 'name' from module 'module_name'."""
@ -445,10 +445,10 @@ class Stat_Window:
Stats.do_tip(e_box[r][c], 'stuff')
if usegtkframes:
grid.attach(self.frame[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0)
grid.attach(self.frame[r][c], c, c+1, r, r+1, xpadding = game.xpad, ypadding = game.ypad)
self.frame[r][c].add(e_box[r][c])
else:
grid.attach(e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0)
grid.attach(e_box[r][c], c, c+1, r, r+1, xpadding = game.xpad, ypadding = game.ypad)
label[r].append( gtk.Label('xxx') )
if usegtkframes:

View File

@ -35,28 +35,20 @@ import gobject
# FreePokerTools modules
import Configuration
import Database
import Card
class Aux_Window:
class Aux_Window(object):
def __init__(self, hud, params, config):
self.hud = hud
self.params = params
self.config = config
def update_data(self, *args):
pass
def update_gui(self, *args):
pass
def create(self, *args):
pass
def relocate(self, *args):
pass
def save_layout(self, *args):
pass
# Override these methods as needed
def update_data(self, *args): pass
def update_gui(self, *args): pass
def create(self, *args): pass
def relocate(self, *args): pass
def save_layout(self, *args): pass
def destroy(self):
try:
self.container.destroy()
@ -67,28 +59,41 @@ class Aux_Window:
# Some utility routines useful for Aux_Windows
#
def get_card_images(self):
card_images = {}
suits = ('S', 'H', 'D', 'C')
ranks = ('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'B')
card_images = 53 * [0]
suits = ('s', 'h', 'd', 'c')
ranks = (14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2)
pb = gtk.gdk.pixbuf_new_from_file(self.config.execution_path(self.params['deck']))
for j in range(0, 14):
for j in range(0, 13):
for i in range(0, 4):
temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42)
pb.copy_area(30*j, 42*i, 30, 42, temp_pb, 0, 0)
card_images[(ranks[j], suits[i])] = temp_pb
card_images[Card.cardFromValueSuit(ranks[j], suits[i])] = self.cropper(pb, i, j)
temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42)
# also pick out a card back and store in [0]
card_images[0] = self.cropper(pb, 2, 13)
return(card_images)
# cards are 30 wide x 42 high
def split_cards(self, card):
if card == 'xx': return ('B', 'S')
return (card[0], card[1].upper())
def cropper(self, pb, i, j):
"""Crop out a card image given an FTP deck and the i, j position."""
temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42)
pb.copy_area(30*j, 42*i, 30, 42, temp_pb, 0, 0)
return temp_pb
def has_cards(self, cards):
"""Returns the number of cards in the list."""
n = 0
for c in cards:
if c in set('shdc'): return True
return False
if c != None and c > 0: n = n + 1
return n
def get_id_from_seat(self, seat):
"""Determine player id from seat number, given stat_dict."""
for id, dict in self.hud.stat_dict.iteritems():
if seat == dict['seat']:
return id
return None
class Stud_mucked(Aux_Window):
def __init__(self, hud, config, params):
@ -108,9 +113,10 @@ class Stud_mucked(Aux_Window):
def create(self):
self.container =gtk.Window()
self.container = gtk.Window()
self.vbox = gtk.VBox()
self.container.add(self.vbox)
self.container.set_title(self.hud.table.name)
self.mucked_list.create(self.vbox)
self.mucked_cards.create(self.vbox)
@ -207,7 +213,9 @@ class Stud_list:
# find the hero's seat from the stat_dict
for stat in self.parent.hud.stat_dict.itervalues():
if stat['screen_name'] == hero:
return self.parent.hud.cards[stat['seat']][0:6]
return Card.valueSuitFromCard(self.parent.hud.cards[stat['seat']][0]) +\
Card.valueSuitFromCard(self.parent.hud.cards[stat['seat']][1]) +\
Card.valueSuitFromCard(self.parent.hud.cards[stat['seat']][2])
return "xxxxxx"
def update_gui(self, new_hand_id):
@ -240,7 +248,7 @@ class Stud_cards:
for r in range(0, self.rows):
for c in range(0, self.cols):
self.seen_cards[(c, r)] = gtk.image_new_from_pixbuf(self.card_images[('B', 'S')])
self.seen_cards[(c, r)] = gtk.image_new_from_pixbuf(self.card_images[(0)])
self.eb[(c, r)]= gtk.EventBox()
# set up the contents for the cells
@ -287,11 +295,11 @@ class Stud_cards:
self.clear()
for c, cards in self.parent.hud.cards.iteritems():
self.grid_contents[(1, c - 1)].set_text(self.get_screen_name(c))
for i in ((0, cards[0:2]), (1, cards[2:4]), (2, cards[4:6]), (3, cards[6:8]),
(4, cards[8:10]), (5, cards[10:12]), (6, cards[12:14])):
if not i[1] == "xx":
for i in ((0, cards[0]), (1, cards[1]), (2, cards[2]), (3, cards[3]),
(4, cards[4]), (5, cards[5]), (6, cards[6])):
if not i[1] == 0:
self.seen_cards[(i[0], c - 1)]. \
set_from_pixbuf(self.card_images[self.parent.split_cards(i[1])])
set_from_pixbuf(self.card_images[i[1]])
## action in tool tips for 3rd street cards
for c in (0, 1, 2):
for r in range(0, self.rows):
@ -314,90 +322,155 @@ class Stud_cards:
for r in range(0, self.rows):
self.grid_contents[(1, r)].set_text(" ")
for c in range(0, 7):
self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')])
self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[0])
self.eb[(c, r)].set_tooltip_text('')
class Flop_Mucked(Aux_Window):
"""Aux_Window class for displaying mucked cards for flop games."""
class Seat_Window(gtk.Window):
"""Subclass gtk.Window for the seat windows."""
class Aux_Seats(Aux_Window):
"""A super class to display an aux_window at each seat."""
def __init__(self, hud, config, params):
self.hud = hud # hud object that this aux window supports
self.config = config # configuration object for this aux window to use
self.params = params # dict aux params from config
self.positions = {} # dict of window positions
# self.rel_positions = {} # dict of window positions, relative to the table origin
self.displayed_cards = False
self.displayed = False # the seat windows are displayed
self.uses_timer = False # the Aux_seats object uses a timer to control hiding
self.timer_on = False # bool = Ture if the timeout for removing the cards is on
self.card_images = self.get_card_images()
# placeholders that should be overridden--so we don't throw errors
def create_contents(self): pass
def update_contents(self): pass
def create(self):
self.adj = self.hud.adj_seats(0, self.config)
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))
self.m_windows = {} # windows to put the card images in
self.eb = {} # event boxes so we can interact with the mucked cards
self.seen_cards = {} # image objects to stash the cards in
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[self.adj[i]]
self.m_windows[i] = gtk.Window()
self.m_windows[i] = Seat_Window()
self.m_windows[i].set_decorated(False)
self.m_windows[i].set_property("skip-taskbar-hint", True)
self.m_windows[i].set_transient_for(self.hud.main_window)
self.m_windows[i].set_focus_on_map(False)
self.eb[i] = gtk.EventBox()
self.eb[i].connect("button_press_event", self.button_press_cb)
self.m_windows[i].connect("configure_event", self.configure_event_cb, i)
self.m_windows[i].add(self.eb[i])
self.seen_cards[i] = gtk.image_new_from_pixbuf(self.card_images[('B', 'H')])
self.eb[i].add(self.seen_cards[i])
self.positions[i] = (int(x) + self.hud.table.x, int(y) + self.hud.table.y)
# self.rel_positions[i] = (int(x), int(y))
self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
self.m_windows[i].set_opacity(float(self.params['opacity']))
if self.params.has_key('opacity'):
self.m_windows[i].set_opacity(float(self.params['opacity']))
# the create_contents method is supplied by the subclass
self.create_contents(self.m_windows[i], i)
self.m_windows[i].show_all()
self.m_windows[i].hide()
if self.uses_timer:
self.m_windows[i].hide()
def update_gui(self, new_hand_id):
"""Update the gui, LDO."""
for i in self.m_windows.keys():
self.update_contents(self.m_windows[i], i)
# Methods likely to be of use for any Seat_Window implementation
def destroy(self):
"""Destroy all of the seat windows."""
for i in self.m_windows.keys():
self.m_windows[i].destroy()
del(self.m_windows[i])
# Methods likely to be useful for mucked card windows (or similar) only
def hide(self):
"""Hide the seat windows."""
for (i, w) in self.m_windows.iteritems():
w.hide()
self.displayed = False
def save_layout(self, *args):
"""Save new layout back to the aux element in the config file."""
new_locs = {}
# print "adj =", self.adj
for (i, pos) in self.positions.iteritems():
if i != 'common':
new_locs[self.adj[int(i)]] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
else:
new_locs[i] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
self.config.edit_aux_layout(self.params['name'], self.hud.max, locations = new_locs)
def configure_event_cb(self, widget, event, i, *args):
self.positions[i] = widget.get_position()
# self.rel_positions[i] = (self.positions[i][0] - self.hud.table.x, self.positions[i][1] - self.hud.table.y)
class Flop_Mucked(Aux_Seats):
"""Aux_Window class for displaying mucked cards for flop games."""
def __init__(self, hud, config, params):
super(Flop_Mucked, self).__init__(hud, config, params)
self.card_images = self.get_card_images()
self.uses_timer = True # this Aux_seats object uses a timer to control hiding
def create_contents(self, container, i):
"""Create the widgets for showing the contents of the Aux_seats window."""
container.eb = gtk.EventBox()
container.eb.connect("button_press_event", self.button_press_cb)
container.add(container.eb)
container.seen_cards = gtk.image_new_from_pixbuf(self.card_images[0])
container.eb.add(container.seen_cards)
def update_contents(self, container, i):
if not self.hud.cards.has_key(i): return
cards = self.hud.cards[i]
n_cards = self.has_cards(cards)
if n_cards > 1:
# scratch is a working pixbuf, used to assemble the image
scratch = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
int(self.params['card_wd'])*n_cards,
int(self.params['card_ht']))
x = 0 # x coord where the next card starts in scratch
for card in cards:
# concatenate each card image to scratch
if card == None or card ==0:
break
self.card_images[card].copy_area(0, 0,
int(self.params['card_wd']), int(self.params['card_ht']),
scratch, x, 0)
x = x + int(self.params['card_wd'])
container.seen_cards.set_from_pixbuf(scratch)
container.resize(1,1)
container.show()
container.move(self.positions[i][0], self.positions[i][1]) # here is where I move back
self.displayed = True
if i != "common":
id = self.get_id_from_seat(i)
self.m_windows[i].eb.set_tooltip_text(self.hud.stat_dict[id]['screen_name'])
def update_gui(self, new_hand_id):
"""Prepare and show the mucked cards."""
if self.displayed_cards:
self.hide_mucked_cards()
self.displayed_cards = False
if self.displayed: self.hide()
# See how many players showed a hand. Skip if only 1 shows (= hero)
n_sd = 0
for (i, cards) in self.hud.cards.iteritems():
if self.has_cards(cards):
# scratch is a working pixbuf, used to assemble the image
scratch = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
int(self.params['card_wd'])*len(cards)/2,
int(self.params['card_ht']))
x = 0 # x coord where the next card starts in scratch
for card in [cards[k:k+2] for k in xrange(0, len(cards), 2)]:
# concatenate each card image to scratch
self.card_images[self.split_cards(card)].copy_area(0, 0,
int(self.params['card_wd']), int(self.params['card_ht']),
scratch, x, 0)
x = x + int(self.params['card_wd'])
self.seen_cards[i].set_from_pixbuf(scratch)
# self.m_windows[i].show_all()
self.m_windows[i].resize(1,1)
self.m_windows[i].show()
self.m_windows[i].move(self.positions[i][0], self.positions[i][1]) # here is where I move back
self.displayed_cards = True
n_cards = self.has_cards(cards)
if n_cards > 0 and i != 'common':
n_sd = n_sd + 1
if n_sd < 2:
print "skipping, n_sd =", n_sd
return
for stats in self.hud.stat_dict.itervalues():
self.eb[stats['seat']].set_tooltip_text(stats['screen_name'])
super(Flop_Mucked, self).update_gui(new_hand_id)
if self.displayed_cards and float(self.params['timeout']) > 0:
if self.displayed and float(self.params['timeout']) > 0:
self.timer_on = True
gobject.timeout_add(int(1000*float(self.params['timeout'])), self.timed_out)
def destroy(self):
"""Destroy all of the mucked windows."""
for w in self.m_windows.values():
w.destroy()
def timed_out(self):
# this is the callback from the timeout
@ -406,15 +479,9 @@ class Flop_Mucked(Aux_Window):
if not self.timer_on:
return False
else:
self.hide_mucked_cards()
self.hide()
return False
def hide_mucked_cards(self):
"""Hide the mucked card windows."""
for (i, w) in self.m_windows.iteritems():
w.hide()
self.displayed_cards = False
def button_press_cb(self, widget, event, *args):
"""Handle button clicks in the event boxes."""
@ -432,56 +499,14 @@ class Flop_Mucked(Aux_Window):
self.timer_on = False
else:
self.timer_on = False
self.hide_mucked_cards()
self.hide()
elif event.button == 1: # left button event
window = widget.get_parent()
window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time)
def configure_event_cb(self, widget, event, i, *args):
self.positions[i] = widget.get_position()
# self.rel_positions[i] = (self.positions[i][0] - self.hud.table.x, self.positions[i][1] - self.hud.table.y)
def expose_all(self):
for (i, cards) in self.hud.cards.iteritems():
self.m_windows[i].show()
self.m_windows[i].move(self.positions[i][0], self.positions[i][1]) # here is where I move back
self.displayed_cards = True
def save_layout(self, *args):
"""Save new layout back to the aux element in the config file."""
new_locs = {}
# print "adj =", self.adj
for (i, pos) in self.positions.iteritems():
if i != 'common':
new_locs[self.adj[int(i)]] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
else:
new_locs[i] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
self.config.edit_aux_layout(self.params['name'], self.hud.max, locations = new_locs)
if __name__== "__main__":
def destroy(*args): # call back for terminating the main eventloop
gtk.main_quit() # used only for testing
def process_new_hand(source, condition, db_connection): #callback from stdin watch -- testing only
# there is a new hand_id to be processed
# just read it and pass it to update
new_hand_id = sys.stdin.readline()
new_hand_id = new_hand_id.rstrip() # remove trailing whitespace
m.update_data(new_hand_id, db_connection)
m.update_gui(new_hand_id)
return(True)
config = Configuration.Config()
db_connection = Database.Database(config, 'fpdb', '')
main_window = gtk.Window()
main_window.set_keep_above(True)
main_window.connect("destroy", destroy)
aux_to_call = "stud_mucked"
aux_params = config.get_aux_parameters(aux_to_call)
m = eval("%s(main_window, None, config, aux_params)" % aux_params['class'])
s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand, db_connection)
gtk.main()
self.displayed = True

45
pyfpdb/Options.py Normal file
View File

@ -0,0 +1,45 @@
#!/usr/bin/python
#Copyright 2008 Ray E. Barker
#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 os
import sys
from optparse import OptionParser
def fpdb_options():
"""Process command line options for fpdb and HUD_main."""
parser = OptionParser()
parser.add_option("-x", "--errorsToConsole",
action="store_true",
help="If passed error output will go to the console rather than .")
parser.add_option("-d", "--databaseName",
dest="dbname", default="fpdb",
help="Overrides the default database name")
parser.add_option("-c", "--configFile",
dest="config", default=None,
help="Specifies a configuration file.")
(options, sys.argv) = parser.parse_args()
return (options, sys.argv)
if __name__== "__main__":
(options, sys.argv) = fpdb_options()
print "errorsToConsole =", options.errorsToConsole
print "database name =", options.dbname
print "config file =", options.config
print "press enter to end"
sys.stdin.readline()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2008, Carl Gherardi
@ -18,6 +18,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# TODO: straighten out discards for draw games
import sys
from HandHistoryConverter import *
@ -25,22 +26,41 @@ from HandHistoryConverter import *
class PokerStars(HandHistoryConverter):
############################################################
# Class Variables
# Static regexes
re_GameInfo = re.compile("PokerStars Game #(?P<HID>[0-9]+):\s+(HORSE)? \(?(?P<GAME>Hold\'em|Razz|7 Card Stud|7 Card Stud Hi/Lo|Omaha|Omaha Hi/Lo|Badugi) (?P<LIMIT>No Limit|Limit|Pot Limit),? \(?(?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\) - (?P<DATETIME>.*$)", re.MULTILINE)
re_GameInfo = re.compile("""PokerStars\sGame\s\#(?P<HID>[0-9]+):\s+
(Tournament\s\#(?P<TOURNO>\d+),\s(?P<BUYIN>[\$\+\d\.]+)\s)?
(?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)?\(?
(?P<CURRENCY>\$|)?
(?P<SB>[.0-9]+)/\$?
(?P<BB>[.0-9]+)\)\s-\s
(?P<DATETIME>.*$)""",
re.MULTILINE|re.VERBOSE)
re_SplitHands = re.compile('\n\n+')
re_TailSplitHands = re.compile('(\n\n\n+)')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE)
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_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE)
re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'}
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)
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"
@ -53,6 +73,9 @@ follow : whether to tail -f the input"""
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)) + ")"
logging.debug("player_re: " + player_re)
@ -62,43 +85,59 @@ follow : whether to tail -f the input"""
self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s: posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s:(?P<ATYPE> bets| checks| raises| calls| folds| discards| stands pat)( \$(?P<BET>[.\d]+))?( to \$(?P<BETTO>[.\d]+))?( (?P<NODISCARDED>\d) cards?( \[(?P<DISCARDED>.+?)\])?)?" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"""^%s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat)
(\s\$?(?P<BET>[.\d]+))?(\sto\s\$?(?P<BETTO>[.\d]+))? # the number discarded goes in <BET>
(\scards?(\s\[(?P<DISCARDED>.+?)\])?)?"""
% player_re, re.MULTILINE|re.VERBOSE)
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("^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
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", "stud", "fl"],
#["ring", "draw", "fl"],
["ring", "omaha", "pl"]
["ring", "draw", "fl"],
["tour", "hold", "nl"],
["tour", "hold", "pl"],
["tour", "hold", "fl"],
["tour", "stud", "fl"],
]
def determineGameType(self, handText):
info = {'type':'ring'}
# inspect the handText and return the gametype dict
# gametype dict is:
# {'limitType': xxx, 'base': xxx, 'category': xxx}
info = {}
m = self.re_GameInfo.search(handText)
if not m:
return None
mg = m.groupdict()
# translations from captured groups to our info strings
# translations from captured groups to fpdb info strings
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
games = { # base, category
"Hold'em" : ('hold','holdem'),
'Omaha' : ('hold','omahahi'),
'Omaha Hi/Lo' : ('hold','omahahilo'),
'Razz' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi'),
'7 Card Stud Hi/Lo' : ('stud','studhilo'),
'Badugi' : ('draw','badugi')
games = { # base, category
"Hold'em" : ('hold','holdem'),
'Omaha' : ('hold','omahahi'),
'Omaha Hi/Lo' : ('hold','omahahilo'),
'Razz' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi'),
'7 Card Stud Hi/Lo' : ('stud','studhilo'),
'Badugi' : ('draw','badugi'),
'Triple Draw 2-7 Lowball' : ('draw','27_3draw'),
}
currencies = { u'':'EUR', '$':'USD', '':'T$' }
# I don't think this is doing what we think. mg will always have all
# the expected keys, but the ones that didn't match in the regex will
# have a value of None. It is OK if it throws an exception when it
# runs across an unknown game or limit or whatever.
if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']]
if 'GAME' in mg:
@ -109,8 +148,13 @@ follow : whether to tail -f the input"""
info['bb'] = mg['BB']
if 'CURRENCY' in mg:
info['currency'] = currencies[mg['CURRENCY']]
if 'TOURNO' in mg and mg['TOURNO'] == None:
info['type'] = 'ring'
else:
info['type'] = 'tour'
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info
@ -119,14 +163,13 @@ follow : whether to tail -f the input"""
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m:
info.update(m.groupdict())
# TODO: Be less lazy and parse maxseats from the HandInfo regex
if m.group('TABLEATTRIBUTES'):
m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES'))
hand.maxseats = int(m2.group(1))
# hand.maxseats = int(m2.group(1))
else:
pass # throw an exception here, eh?
m = self.re_GameInfo.search(hand.handText)
if m: info.update(m.groupdict())
m = self.re_Button.search(hand.handText)
if m: info.update(m.groupdict())
# m = self.re_Button.search(hand.handText)
# if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info)
for key in info:
@ -143,7 +186,23 @@ follow : whether to tail -f the input"""
hand.tablename = info[key]
if key == 'BUTTON':
hand.buttonpos = info[key]
if key == 'MAX':
hand.maxseats = int(info[key])
if key == 'MIXED':
if info[key] == None: hand.mixed = None
else: hand.mixed = self.mixes[info[key]]
if key == 'TOURNO':
hand.tourNo = info[key]
if key == 'BUYIN':
hand.buyin = info[key]
if key == 'LEVEL':
hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != None:
# hand.currency = 'play' # overrides previously set value
hand.gametype['currency'] = 'play'
def readButton(self, hand):
m = self.re_Button.search(hand.handText)
if m:
@ -209,80 +268,117 @@ follow : whether to tail -f the input"""
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
hand.addBlind(a.group('PNAME'), 'both', a.group('SBBB'))
# def readHeroCards(self, hand):
# m = self.re_HeroCards.search(hand.handText)
# if(m == None):
# #Not involved in hand
# hand.involved = False
# else:
# hand.hero = m.group('PNAME')
# # "2c, qh" -> set(["2c","qc"])
# # Also works with Omaha hands.
# cards = m.group('NEWCARDS')
# cards = set(cards.split(' '))
# hand.addHoleCards(cards, m.group('PNAME'), shown=False, mucked=False, dealt=True)
def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.handText)
if(m == None):
#Not involved in hand
hand.involved = False
else:
hand.hero = m.group('PNAME')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('NEWCARDS')
cards = set(cards.split(' '))
hand.addHoleCards(cards, m.group('PNAME'))
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
# we need to grab hero's cards
for street in ('PREFLOP', 'DEAL'):
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 = found.group('NEWCARDS').split(' ')
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
def readDrawCards(self, hand, street):
logging.debug("readDrawCards")
m = self.re_HeroCards.finditer(hand.streets[street])
if m == None:
hand.involved = False
else:
for player in m:
hand.hero = player.group('PNAME') # Only really need to do this once
newcards = player.group('NEWCARDS')
oldcards = player.group('OLDCARDS')
if newcards == None:
newcards = set()
for street, text in hand.streets.iteritems():
if not text or street in ('PREFLOP', 'DEAL'): continue # already done these
m = self.re_HeroCards.finditer(hand.streets[street])
for found in m:
player = found.group('PNAME')
if found.group('NEWCARDS') == None:
newcards = []
else:
newcards = set(newcards.split(' '))
if oldcards == None:
oldcards = set()
newcards = found.group('NEWCARDS').split(' ')
if found.group('OLDCARDS') == None:
oldcards = []
else:
oldcards = set(oldcards.split(' '))
hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
oldcards = found.group('OLDCARDS').split(' ')
if street == 'THIRD' and len(newcards) == 3: # hero in stud game
hand.hero = player
hand.dealt.add(player) # need this for stud??
hand.addHoleCards(street, player, closed=newcards[0:2], open=[newcards[2]], shown=False, mucked=False, dealt=False)
else:
hand.addHoleCards(street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False)
# def readDrawCards(self, hand, street):
# logging.debug("readDrawCards")
# m = self.re_HeroCards.finditer(hand.streets[street])
# if m == None:
# hand.involved = False
# else:
# for player in m:
# hand.hero = player.group('PNAME') # Only really need to do this once
# newcards = player.group('NEWCARDS')
# oldcards = player.group('OLDCARDS')
# if newcards == None:
# newcards = set()
# else:
# newcards = set(newcards.split(' '))
# if oldcards == None:
# oldcards = set()
# else:
# oldcards = set(oldcards.split(' '))
# hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
def readStudPlayerCards(self, hand, street):
# See comments of reference implementation in FullTiltToFpdb.py
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street])
for player in m:
#~ logging.debug(player.groupdict())
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
if oldcards:
oldcards = [c.strip() for c in oldcards.split(' ')]
if newcards:
newcards = [c.strip() for c in newcards.split(' ')]
if street=='ANTES':
return
elif street=='THIRD':
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# hero: [xx][o]
# others: [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# 4th:
# hero: [xxo] [o]
# others: [o] [o]
# 5th:
# hero: [xxoo] [o]
# others: [oo] [o]
# 6th:
# hero: [xxooo] [o]
# others: [ooo] [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
elif street=='SEVENTH' and newcards:
# hero: [xxoooo] [x]
# others: not reported.
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
# def readStudPlayerCards(self, hand, street):
# # See comments of reference implementation in FullTiltToFpdb.py
# logging.debug("readStudPlayerCards")
# m = self.re_HeroCards.finditer(hand.streets[street])
# for player in m:
# #~ logging.debug(player.groupdict())
# (pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
# if oldcards:
# oldcards = [c.strip() for c in oldcards.split(' ')]
# if newcards:
# newcards = [c.strip() for c in newcards.split(' ')]
# if street=='ANTES':
# return
# elif street=='THIRD':
# # we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# # hero: [xx][o]
# # others: [o]
# hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
# elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# # 4th:
# # hero: [xxo] [o]
# # others: [o] [o]
# # 5th:
# # hero: [xxoo] [o]
# # others: [oo] [o]
# # 6th:
# # hero: [xxooo] [o]
# # others: [ooo] [o]
# hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# # we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
# elif street=='SEVENTH' and newcards:
# # hero: [xxoooo] [x]
# # others: not reported.
# hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street])
for action in m:
acts = action.groupdict()
if action.group('ATYPE') == ' raises':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' calls':
@ -294,7 +390,7 @@ follow : whether to tail -f the input"""
elif action.group('ATYPE') == ' checks':
hand.addCheck( street, action.group('PNAME'))
elif action.group('ATYPE') == ' discards':
hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED'))
hand.addDiscard(street, action.group('PNAME'), action.group('BET'), action.group('DISCARDED'))
elif action.group('ATYPE') == ' stands pat':
hand.addStandsPat( street, action.group('PNAME'))
else:
@ -302,9 +398,9 @@ follow : whether to tail -f the input"""
def readShowdownActions(self, hand):
# TODO: pick up mucks also??
for shows in self.re_ShowdownAction.finditer(hand.handText):
cards = shows.group('CARDS')
cards = set(cards.split(' '))
cards = shows.group('CARDS').split(' ')
hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand):
@ -315,12 +411,17 @@ follow : whether to tail -f the input"""
for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards=cards, player=m.group('PNAME'))
cards = cards.split(' ') # needs to be a list, not a set--stud needs the order
(shown, mucked) = (False, False)
if m.group('SHOWED') == "showed": shown = True
elif m.group('SHOWED') == "mucked": mucked = True
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked)
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/pokerstars/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt")
parser.add_option("-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("-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",

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,8 @@
########################################################################
# How to write a new stat:
# 0 Do not use a name like "xyz_2". Names ending in _ and a single digit are
# used to indicate the number of decimal places the user wants to see in the Hud.
# 1 You can see a listing of all the raw stats (e.g., from the HudCache table)
# by running Database.py as a stand along program. You need to combine
# those raw stats to get stats to present to the HUD. If you need more
@ -50,16 +52,34 @@
# pyGTK modules
import pygtk
import gtk
import re
# FreePokerTools modules
import Configuration
import Database
re_Places = re.compile("_[0-9]$")
re_Percent = re.compile("%$")
def do_tip(widget, tip):
widget.set_tooltip_text(tip)
def do_stat(stat_dict, player = 24, stat = 'vpip'):
return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player})
match = re_Places.search(stat)
if match == None:
result = eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player})
else:
base = stat[0:-2]
places = int(stat[-1:])
result = eval("%(stat)s(stat_dict, %(player)d)" % {'stat': base, 'player': player})
match = re_Percent.search(result[1])
if match == None:
result = (result[0], "%.*f" % (places, result[0]), result[2], result[3], result[4], result[5])
else:
result = (result[0], "%.*f%%" % (places, 100*result[0]), result[2], result[3], result[4], result[5])
return result
# OK, for reference the tuple returned by the stat is:
# 0 - The stat, raw, no formating, eg 0.33333333
@ -108,26 +128,6 @@ def vpip(stat_dict, player):
'Voluntarily Put In Pot %'
)
def vpip_0(stat_dict, player):
""" Voluntarily put $ in the pot (no decimals)."""
stat = 0.0
try:
stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n'])
return (stat,
'%2.0f' % (100*stat) + '%',
'v=%2.0f' % (100*stat) + '%',
'vpip=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']),
'vpip'
)
except: return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wtsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'wtsd'
)
def pfr(stat_dict, player):
""" Preflop (3rd street) raise."""
stat = 0.0
@ -149,27 +149,6 @@ def pfr(stat_dict, player):
'Pre-Flop Raise %'
)
def pfr_0(stat_dict, player):
""" Preflop (3rd street) raise (no decimals)."""
stat = 0.0
try:
stat = float(stat_dict[player]['pfr'])/float(stat_dict[player]['n'])
return (stat,
'%2.0f' % (100*stat) + '%',
'p=%2.0f' % (100*stat) + '%',
'pfr=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']),
'pfr'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'p=%2.0f' % (0) + '%',
'pfr=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'pfr'
)
def wtsd(stat_dict, player):
""" Went to SD when saw flop/4th."""
stat = 0.0
@ -191,27 +170,6 @@ def wtsd(stat_dict, player):
'% went to showdown'
)
def wtsd_0(stat_dict, player):
""" Went to SD when saw flop/4th."""
stat = 0.0
try:
stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f'])
return (stat,
'%2.0f' % (100*stat) + '%',
'w=%2.0f' % (100*stat) + '%',
'wtsd=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['sd'], stat_dict[player]['saw_f']),
'% went to showdown'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wtsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% went to showdown'
)
def wmsd(stat_dict, player):
""" Won $ at showdown."""
stat = 0.0
@ -233,28 +191,7 @@ def wmsd(stat_dict, player):
'% won money at showdown'
)
def wmsd_0(stat_dict, player):
""" Won $ at showdown."""
stat = 0.0
try:
stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd'])
return (stat,
'%2.0f' % (100*stat) + '%',
'w=%2.0f' % (100*stat) + '%',
'wmsd=%2.0f' % (100*stat) + '%',
'(%5.1f/%d)' % (float(stat_dict[player]['wmsd']), stat_dict[player]['sd']),
'% won money at showdown'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wmsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% won money at showdown'
)
def profit100_0(stat_dict, player):
def profit100(stat_dict, player):
""" Profit won per 100 hands (no decimal places)."""
stat = 0.0
try:
@ -355,27 +292,6 @@ def steal(stat_dict, player):
)
except:
return (stat, 'NA', 'st=NA', 'steal=NA', '(0/0)', '% steal attempted')
def steal_0(stat_dict, player):
""" Steal %."""
stat = 0.0
try:
stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp'])
return (stat,
'%2.0f' % (100*stat) + '%',
'st=%2.0f' % (100*stat) + '%',
'steal=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['steal'], stat_dict[player]['steal_opp']),
'% steal attempted'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'st=%2.0f' % (0) + '%',
'steal=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% steal attempted'
)
def f_SB_steal(stat_dict, player):
""" Folded SB to steal."""
@ -417,27 +333,7 @@ def f_BB_steal(stat_dict, player):
'(0/0)',
'% folded BB to steal')
def f_BB_steal_0(stat_dict, player):
""" Folded BB to steal."""
stat = 0.0
try:
stat = float(stat_dict[player]['bbnotdef'])/float(stat_dict[player]['bbstolen'])
return (stat,
'%2.0f' % (100*stat) + '%',
'fBB=%2.0f' % (100*stat) + '%',
'fBB_s=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['bbnotdef'], stat_dict[player]['bbstolen']),
'% folded BB to steal'
)
except:
return (stat,
'NA',
'fBB=NA',
'fBB_s=NA',
'(0/0)',
'% folded BB to steal')
def three_B_0(stat_dict, player):
def three_B(stat_dict, player):
""" Three bet preflop/3rd."""
stat = 0.0
try:
@ -479,7 +375,7 @@ def WMsF(stat_dict, player):
'% won$/saw flop/4th'
)
def a_freq_1(stat_dict, player):
def a_freq1(stat_dict, player):
""" Flop/4th aggression frequency."""
stat = 0.0
try:
@ -500,7 +396,7 @@ def a_freq_1(stat_dict, player):
'Aggression Freq flop/4th'
)
def a_freq_2(stat_dict, player):
def a_freq2(stat_dict, player):
""" Turn/5th aggression frequency."""
stat = 0.0
try:
@ -521,7 +417,7 @@ def a_freq_2(stat_dict, player):
'Aggression Freq turn/5th'
)
def a_freq_3(stat_dict, player):
def a_freq3(stat_dict, player):
""" River/6th aggression frequency."""
stat = 0.0
try:
@ -542,7 +438,7 @@ def a_freq_3(stat_dict, player):
'Aggression Freq river/6th'
)
def a_freq_4(stat_dict, player):
def a_freq4(stat_dict, player):
""" 7th street aggression frequency."""
stat = 0.0
try:
@ -590,35 +486,8 @@ def a_freq_123(stat_dict, player):
'(%d/%d)' % (0, 0),
'Post-Flop Aggression Freq'
)
def a_freq_123_0(stat_dict, player):
""" Post-Flop aggression frequency (no decimals)."""
stat = 0.0
try:
stat = float( stat_dict[player]['aggr_1'] + stat_dict[player]['aggr_2'] + stat_dict[player]['aggr_3']) / float( stat_dict[player]['saw_1'] + stat_dict[player]['saw_2'] + stat_dict[player]['saw_3']);
return (stat,
'%2.0f' % (100*stat) + '%',
'afq=%2.0f' % (100*stat) + '%',
'postf_aggfq=%2.0f' % (100*stat) + '%',
'(%d/%d)' % ( stat_dict[player]['aggr_1']
+ stat_dict[player]['aggr_2']
+ stat_dict[player]['aggr_3']
, stat_dict[player]['saw_1']
+ stat_dict[player]['saw_2']
+ stat_dict[player]['saw_3']
),
'Post-Flop Aggression Freq'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'a3=%2.0f' % (0) + '%',
'a_fq_3=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'Post-Flop Aggression Freq'
)
def cb_1(stat_dict, player):
def cb1(stat_dict, player):
""" Flop continuation bet."""
stat = 0.0
try:
@ -639,7 +508,7 @@ def cb_1(stat_dict, player):
'% continuation bet flop/4th'
)
def cb_2(stat_dict, player):
def cb2(stat_dict, player):
""" Turn continuation bet."""
stat = 0.0
try:
@ -660,7 +529,7 @@ def cb_2(stat_dict, player):
'% continuation bet turn/5th'
)
def cb_3(stat_dict, player):
def cb3(stat_dict, player):
""" River continuation bet."""
stat = 0.0
try:
@ -681,7 +550,7 @@ def cb_3(stat_dict, player):
'% continuation bet river/6th'
)
def cb_4(stat_dict, player):
def cb4(stat_dict, player):
""" 7th street continuation bet."""
stat = 0.0
try:
@ -702,7 +571,7 @@ def cb_4(stat_dict, player):
'% continuation bet 7th'
)
def ffreq_1(stat_dict, player):
def ffreq1(stat_dict, player):
""" Flop/4th fold frequency."""
stat = 0.0
try:
@ -723,7 +592,7 @@ def ffreq_1(stat_dict, player):
'% fold frequency flop/4th'
)
def ffreq_2(stat_dict, player):
def ffreq2(stat_dict, player):
""" Turn/5th fold frequency."""
stat = 0.0
try:
@ -744,7 +613,7 @@ def ffreq_2(stat_dict, player):
'% fold frequency turn/5th'
)
def ffreq_3(stat_dict, player):
def ffreq3(stat_dict, player):
""" River/6th fold frequency."""
stat = 0.0
try:
@ -765,7 +634,7 @@ def ffreq_3(stat_dict, player):
'% fold frequency river/6th'
)
def ffreq_4(stat_dict, player):
def ffreq4(stat_dict, player):
""" 7th fold frequency."""
stat = 0.0
try:
@ -804,24 +673,24 @@ if __name__== "__main__":
print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_sb_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_bb_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_b_0')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsf')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_4')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B_0')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq4')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123_0')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_4')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb4')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq4')
print "\n"
print "\n\nLegal stats:"

View File

@ -231,7 +231,12 @@ def discover_nt_by_name(c, tablename):
titles = {}
win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles:
if not tablename in titles[hwnd]: continue
#print "Tables.py: tablename =", tablename, "title =", titles[hwnd]
try:
# 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
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
@ -364,7 +369,7 @@ def clean_title(name):
for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)',
' \(deep hu\)', ' \(deep 6\)', ' \(2\)',
' \(edu\)', ' \(edu, 6 max\)', ' \(6\)',
' \(speed\)',
' \(speed\)', 'special', 'newVPP',
' no all-in', ' fast', ',', ' 50BB min', '50bb min', '\s+$']:
name = re.sub(pattern, '', name)
name = name.rstrip()

View File

@ -33,12 +33,12 @@ class UltimateBet(HandHistoryConverter):
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
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="UltimateBet", follow=follow)
HandHistoryConverter.__init__(self, in_path, out_path, sitename="UltimateBet", follow=follow, index=index)
logging.info("Initialising UltimateBetconverter class")
self.filetype = "text"
self.codepage = "cp1252"

381
pyfpdb/Win2dayToFpdb.py Executable file
View File

@ -0,0 +1,381 @@
#!/usr/bin/env 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
import sys
import datetime
from HandHistoryConverter import *
# Win2day HH Format
class Win2day(HandHistoryConverter):
# Static regexes
#<HISTORY ID="102271403" SESSION="session31237702.xml" TABLE="Innsbruck 3" GAME="GAME_THM" GAMETYPE="GAMETYPE_REAL" GAMEKIND="GAMEKIND_CASH" TABLECURRENCY="EUR" LIMIT="NL" STAKES="0.25/0.50" DATE="1246909773" WIN="0.00" LOSS="0.50">
#'^<HISTORY ID="(?P<HID>[0-9]+)" SESSION="session[0-9]+\.xml" TABLE="(?P<TABLE>[- a-zA-Z0-9]+)" GAME="(?P<GAME>[_A-Z]+)" GAMETYPE="[_a-zA-Z]+" GAMEKIND="[_a-zA-Z]+" TABLECURRENCY="(?P<CURRENCY>[A-Z]+)" LIMIT="(?P<LIMIT>NL|PL)" STAKES="(?P<SB>[.0-9]+)/(?P<BB>[.0-9]+)" DATE="(?P<DATETIME>[0-9]+)" WIN="[.0-9]+" LOSS="[.0-9]+">$'
re_GameInfo = re.compile('^<HISTORY ID="(?P<HID>[0-9]+)" SESSION="session[0-9]+\.xml" TABLE="(?P<TABLE>[- a-zA-Z0-9]+)" GAME="(?P<GAME>[_A-Z]+)" GAMETYPE="[_a-zA-Z]+" GAMEKIND="[_a-zA-Z]+" TABLECURRENCY="(?P<CURRENCY>[A-Z]+)" LIMIT="(?P<LIMIT>NL|PL)" STAKES="(?P<SB>[.0-9]+)/(?P<BB>[.0-9]+)" DATE="(?P<DATETIME>[0-9]+)" WIN="[.0-9]+" LOSS="[.0-9]+">', re.MULTILINE)
re_SplitHands = re.compile('</HISTORY>')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE)
re_Button = re.compile('<ACTION TYPE="HAND_DEAL" PLAYER="(?P<BUTTON>[^"]+)">\n<CARD LINK="[0-9b]+"></CARD>\n<CARD LINK="[0-9b]+"></CARD></ACTION>\n<ACTION TYPE="ACTION_', re.MULTILINE)
#<PLAYER NAME="prato" SEAT="1" AMOUNT="61.29"></PLAYER>
re_PlayerInfo = re.compile('^<PLAYER NAME="(?P<PNAME>.*)" SEAT="(?P<SEAT>[0-9]+)" AMOUNT="(?P<CASH>[.0-9]+)"></PLAYER>', re.MULTILINE)
re_Card = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD>', re.MULTILINE)
re_BoardLast = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD></ACTION>', re.MULTILINE)
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])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# we need to recompile the player regexs.
self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re)
#<ACTION TYPE="HAND_BLINDS" PLAYER="prato" KIND="HAND_SB" VALUE="0.25"></ACTION>
self.re_PostSB = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_SB" VALUE="(?P<SB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_BB" VALUE="(?P<BB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
self.re_Antes = re.compile(r"^%s: posts the ante \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r'^<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)
#'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>'
self.re_Action = re.compile(r'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(r'<RESULT PLAYER="%s" WIN="[.0-9]+" HAND="(?P<HAND>\(\$STR_G_FOLD\)|[\$\(\)_ A-Z]+)">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]+"></CARD>)</RESULT>' % player_re, re.MULTILINE)
#<RESULT PLAYER="wig0r" WIN="4.10" HAND="$(STR_G_WIN_TWOPAIR) $(STR_G_CARDS_TENS) $(STR_G_ANDTEXT) $(STR_G_CARDS_EIGHTS)">
#
self.re_CollectPot = re.compile(r'<RESULT PLAYER="%s" WIN="(?P<POT>[.\d]+)" HAND=".+">' % player_re, re.MULTILINE)
self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self):
return [["ring", "hold", "nl"],
["ring", "hold", "pl"],
["ring", "hold", "fl"],
["ring", "stud", "fl"],
["ring", "draw", "fl"],
["ring", "omaha", "pl"]
]
def determineGameType(self, handText):
info = {'type':'ring'}
m = self.re_GameInfo.search(handText)
if not m:
print "determineGameType:", handText
return None
mg = m.groupdict()
# translations from captured groups to our info strings
#limits = { 'NL':'nl', 'PL':'pl', 'Limit':'fl' }
limits = { 'NL':'nl', 'PL':'pl'}
games = { # base, category
"GAME_THM" : ('hold','holdem'),
# 'Omaha' : ('hold','omahahi'),
#'Omaha Hi/Lo' : ('hold','omahahilo'),
# 'Razz' : ('stud','razz'),
#'7 Card Stud' : ('stud','studhi'),
# 'Badugi' : ('draw','badugi')
}
if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']]
if 'GAME' in mg:
(info['base'], info['category']) = games[mg['GAME']]
if 'SB' in mg:
info['sb'] = mg['SB']
if 'BB' in mg:
info['bb'] = mg['BB']
if 'CURRENCY' in mg:
info['currency'] = mg['CURRENCY']
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info
def readHandInfo(self, hand):
info = {}
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m:
info.update(m.groupdict())
# TODO: Be less lazy and parse maxseats from the HandInfo regex
if m.group('TABLEATTRIBUTES'):
m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES'))
hand.maxseats = int(m2.group(1))
m = self.re_GameInfo.search(hand.handText)
if m: info.update(m.groupdict())
m = self.re_Button.search(hand.handText)
if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info)
for key in info:
if key == 'DATETIME':
# Win2day uses UTC timestamp
hand.starttime = datetime.datetime.fromtimestamp(int(info[key]))
if key == 'HID':
hand.handid = info[key]
if key == 'TABLE':
hand.tablename = info[key]
if key == 'BUTTON':
hand.buttonpos = info[key]
def readButton(self, hand):
m = self.re_Button.search(hand.handText)
if m:
for player in hand.players:
if player[1] == m.group('BUTTON'):
hand.buttonpos = player[0]
break
else:
logging.info('readButton: not found')
def readPlayerStacks(self, hand):
logging.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
if hand.gametype['base'] in ("hold"):
#m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
# r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
# r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
# r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.handText,re.DOTALL)
m = re.search('<ACTION TYPE="HAND_BLINDS" PLAYER=".+" KIND="HAND_BB" VALUE="[.0-9]+"></ACTION>(?P<PREFLOP>.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP")|.+)'
'((?P<FLOP><ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN")|.+))?'
'((?P<TURN><ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER")|.+))?'
'((?P<RIVER><ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER" POT="[.0-9]+">.+(?=<SHOWDOWN NAME="HAND_SHOWDOWN")|.+))?', hand.handText,re.DOTALL)
hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
#print "DEBUG readCommunityCards:", street, hand.streets.group(street)
boardCards = set([])
if street == 'FLOP':
m = self.re_Card.findall(hand.streets[street])
for card in m:
boardCards.add(self.convertWin2dayCards(card))
else:
m = self.re_BoardLast.search(hand.streets[street])
boardCards.add(self.convertWin2dayCards(m.group('CARD')))
hand.setCommunityCards(street, boardCards)
def readAntes(self, hand):
logging.debug("reading antes")
m = self.re_Antes.finditer(hand.handText)
for player in m:
#~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL)
if m:
#~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand):
try:
m = self.re_PostSB.search(hand.handText)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
def readHeroCards(self, hand):
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
# we need to grab hero's cards
m = self.re_HeroCards.finditer(hand.streets['PREFLOP'])
newcards = []
for found in m:
hand.hero = found.group('PNAME')
for card in self.re_Card.finditer(found.group('CARDS')):
print self.convertWin2dayCards(card.group('CARD'))
newcards.append(self.convertWin2dayCards(card.group('CARD')))
#hand.addHoleCards(holeCards, m.group('PNAME'))
hand.addHoleCards('PREFLOP', hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
def convertWin2dayCards(self, card):
card = int(card)
retCard = ''
cardconvert = { 1:'A',
10:'T',
11:'J',
12:'Q',
13:'K'}
realNumber = card % 13 + 1
if(realNumber in cardconvert):
retCard += cardconvert[realNumber]
else:
retCard += str(realNumber)
if(card > 38):
retCard += 's'
elif(card > 25):
retCard += 'h'
elif(card > 12):
retCard += 'c'
else:
retCard += 'd'
return(retCard)
def readDrawCards(self, hand, street):
logging.debug("readDrawCards")
m = self.re_HeroCards.finditer(hand.streets[street])
if m == None:
hand.involved = False
else:
for player in m:
hand.hero = player.group('PNAME') # Only really need to do this once
newcards = player.group('NEWCARDS')
oldcards = player.group('OLDCARDS')
if newcards == None:
newcards = set()
else:
newcards = set(newcards.split(' '))
if oldcards == None:
oldcards = set()
else:
oldcards = set(oldcards.split(' '))
hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
def readStudPlayerCards(self, hand, street):
# See comments of reference implementation in FullTiltToFpdb.py
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street])
for player in m:
#~ logging.debug(player.groupdict())
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
if oldcards:
oldcards = [c.strip() for c in oldcards.split(' ')]
if newcards:
newcards = [c.strip() for c in newcards.split(' ')]
if street=='ANTES':
return
elif street=='THIRD':
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# hero: [xx][o]
# others: [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# 4th:
# hero: [xxo] [o]
# others: [o] [o]
# 5th:
# hero: [xxoo] [o]
# others: [oo] [o]
# 6th:
# hero: [xxooo] [o]
# others: [ooo] [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
elif street=='SEVENTH' and newcards:
# hero: [xxoooo] [x]
# others: not reported.
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street])
for action in m:
if action.group('ATYPE') == 'ACTION_RAISE':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_CALL':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_ALLIN':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_BET':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_FOLD':
hand.addFold( street, action.group('PNAME'))
elif action.group('ATYPE') == 'ACTION_CHECK':
hand.addCheck( street, action.group('PNAME'))
elif action.group('ATYPE') == 'ACTION_DISCARD':
hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED'))
elif action.group('ATYPE') == 'ACTION_STAND':
hand.addStandsPat( street, action.group('PNAME'))
else:
print "DEBUG: unimplemented readAction: '%s' '%s'" %(action.group('PNAME'),action.group('ATYPE'),)
def readShowdownActions(self, hand):
for shows in self.re_ShowdownAction.finditer(hand.handText):
showdownCards = set([])
for card in self.re_Card.finditer(shows.group('CARDS')):
#print "DEBUG:", card, card.group('CARD'), self.convertWin2dayCards(card.group('CARD'))
showdownCards.add(self.convertWin2dayCards(card.group('CARD')))
hand.addShownCards(showdownCards, shows.group('PNAME'))
def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.handText):
potcoll = Decimal(m.group('POT'))
if potcoll > 0:
hand.addCollectPot(player=m.group('PNAME'),pot=potcoll)
def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards=cards, player=m.group('PNAME'))
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet",
action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
parser.add_option("-v", "--verbose",
action="store_const", const=logging.INFO, dest="verbosity")
parser.add_option("--vv",
action="store_const", const=logging.DEBUG, dest="verbosity")
(options, args) = parser.parse_args()
LOG_FILENAME = './logging.out'
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
e = Win2day(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -17,26 +17,26 @@
import os
import sys
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-x", "--errorsToConsole", action="store_true",
help="If passed error output will go to the console rather than .")
parser.add_option("-d", "--databaseName", dest="dbname", default="fpdb",
help="Overrides the default database name")
(options, sys.argv) = parser.parse_args()
import threading
import Options
import string
cl_options = string.join(sys.argv[1:])
(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('fpdb-error-log.txt', 'w', 0)
sys.stderr = errorFile
import logging
import pygtk
pygtk.require('2.0')
import gtk
import fpdb_db
import interlocks
import fpdb_simple
import GuiBulkImport
import GuiPlayerStats
@ -45,6 +45,8 @@ import GuiTableViewer
import GuiAutoImport
import GuiGraphViewer
import GuiSessionViewer
import SQL
import Database
import FpdbSQLQueries
import Configuration
@ -117,11 +119,13 @@ class fpdb:
def dia_create_del_database(self, widget, data=None):
print "todo: implement dia_create_del_database"
self.obtain_global_lock()
self.release_global_lock()
#end def dia_create_del_database
def dia_create_del_user(self, widget, data=None):
print "todo: implement dia_create_del_user"
self.obtain_global_lock()
self.release_global_lock()
#end def dia_create_del_user
def dia_database_stats(self, widget, data=None):
@ -130,7 +134,7 @@ class fpdb:
#end def dia_database_stats
def dia_database_sessions(self, widget, data=None):
new_sessions_thread=GuiSessionViewer.GuiSessionViewer(self.config, self.querydict)
new_sessions_thread=GuiSessionViewer.GuiSessionViewer(self.config, self.sql)
self.threads.append(new_sessions_thread)
sessions_tab=new_sessions_thread.get_vbox()
self.add_and_display_tab(sessions_tab, "Sessions")
@ -138,16 +142,19 @@ class fpdb:
def dia_delete_db_parts(self, widget, data=None):
print "todo: implement dia_delete_db_parts"
self.obtain_global_lock()
self.release_global_lock()
#end def dia_delete_db_parts
def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None):
print "todo: implement dia_edit_profile"
self.obtain_global_lock()
self.release_global_lock()
#end def dia_edit_profile
def dia_export_db(self, widget, data=None):
print "todo: implement dia_export_db"
self.obtain_global_lock()
self.release_global_lock()
#end def dia_export_db
def dia_get_db_root_credentials(self):
@ -173,6 +180,7 @@ class fpdb:
def dia_import_db(self, widget, data=None):
print "todo: implement dia_import_db"
self.obtain_global_lock()
self.release_global_lock()
#end def dia_import_db
def dia_licensing(self, widget, data=None):
@ -181,19 +189,23 @@ class fpdb:
def dia_load_profile(self, widget, data=None):
"""Dialogue to select a file to load a profile from"""
if self.obtain_global_lock() == 0: # returns 0 if successful
try:
chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_filename(self.profile)
if self.obtain_global_lock(): # returns true if successful
#try:
# chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
# action=gtk.FILE_CHOOSER_ACTION_OPEN,
# buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
# chooser.set_filename(self.profile)
response = chooser.run()
chooser.destroy()
if response == gtk.RESPONSE_OK:
self.load_profile(chooser.get_filename())
elif response == gtk.RESPONSE_CANCEL:
print 'User cancelled loading profile'
# response = chooser.run()
# chooser.destroy()
# if response == gtk.RESPONSE_OK:
# self.load_profile(chooser.get_filename())
# elif response == gtk.RESPONSE_CANCEL:
# print 'User cancelled loading profile'
#except:
# pass
try:
self.load_profile()
except:
pass
self.release_global_lock()
@ -201,39 +213,41 @@ class fpdb:
def dia_recreate_tables(self, widget, data=None):
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
if self.obtain_global_lock() in (0,2): # returns 0 if successful, 2 if Hands table does not exist
if self.obtain_global_lock(): # returns true if successful
lock_released = False
#lock_released = False
try:
dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
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.database+" on "+self.db.host+" they will be deleted."
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.backend == self.fdb_lock.MYSQL_INNODB:
#if self.db.fdb.backend == self.fdb_lock.fdb.MYSQL_INNODB:
# mysql requires locks on all tables or none - easier to release this lock
# 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:
# self.release_global_lock()
# lock_released = True
self.db.fdb.recreate_tables()
#else:
# for other dbs use same connection as holds global lock
self.fdb_lock.recreate_tables()
# self.fdb_lock.fdb.recreate_tables()
elif response == gtk.RESPONSE_NO:
print 'User cancelled recreating tables'
except:
pass
if not lock_released:
self.release_global_lock()
#if not lock_released:
self.release_global_lock()
#end def dia_recreate_tables
def dia_regression_test(self, widget, data=None):
print "todo: implement dia_regression_test"
self.obtain_global_lock()
self.release_global_lock()
#end def dia_regression_test
def dia_save_profile(self, widget, data=None):
@ -350,28 +364,27 @@ class fpdb:
def load_profile(self):
"""Loads profile from the provided path name."""
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
self.settings = {}
self.settings['global_lock'] = self.lock
if (os.sep=="/"):
self.settings['os']="linuxmac"
else:
self.settings['os']="windows"
self.settings.update({'cl_options': cl_options})
self.settings.update(self.config.get_db_parameters())
self.settings.update(self.config.get_tv_parameters())
self.settings.update(self.config.get_import_parameters())
self.settings.update(self.config.get_default_paths())
if self.db!=None:
if self.db != None and self.db.fdb != None:
self.db.disconnect()
self.db = fpdb_db.fpdb_db()
#print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName']
self.db.connect(self.settings['db-backend'],
self.settings['db-host'],
self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
if self.db.wrongDbVersion:
self.sql = SQL.Sql(type = self.settings['db-type'], db_server = self.settings['db-server'])
self.db = Database.Database(self.config, sql = self.sql)
if self.db.fdb.wrongDbVersion:
diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
label = gtk.Label("An invalid DB version or missing tables have been detected.")
@ -389,9 +402,16 @@ class fpdb:
response = diaDbVersionWarning.run()
diaDbVersionWarning.destroy()
if self.status_bar == None:
self.status_bar = gtk.Label("Status: Connected to %s database named %s on host %s"%(self.db.get_backend_name(),self.db.fdb.database, self.db.fdb.host))
self.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show()
else:
self.status_bar.set_text("Status: Connected to %s database named %s on host %s" % (self.db.get_backend_name(),self.db.fdb.database, self.db.fdb.host))
# Database connected to successfully, load queries to pass on to other classes
self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name())
self.db.db.rollback()
self.db.connection.rollback()
#end def load_profile
def not_implemented(self, widget, data=None):
@ -399,17 +419,18 @@ class fpdb:
#end def not_implemented
def obtain_global_lock(self):
print "\nTaking global lock ..."
self.fdb_lock = fpdb_db.fpdb_db()
self.fdb_lock.connect(self.settings['db-backend'],
self.settings['db-host'],
self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
return self.fdb_lock.get_global_lock()
ret = self.lock.acquire(False) # will return false if lock is already held
if ret:
print "\nGlobal lock taken ..."
else:
print "\nFailed to get global lock."
return ret
# need to release it later:
# self.lock.release()
#end def obtain_global_lock
def quit(self, widget, data):
def quit(self, widget, data=None):
print "Quitting normally"
#check if current settings differ from profile, if so offer to save or abort
self.db.disconnect()
@ -417,9 +438,8 @@ class fpdb:
#end def quit_cliecked
def release_global_lock(self):
self.fdb_lock.db.rollback()
self.fdb_lock.disconnect()
print "Global lock released."
self.lock.release()
print "Global lock released.\n"
#end def release_global_lock
def tab_abbreviations(self, widget, data=None):
@ -444,13 +464,13 @@ class fpdb:
#end def tab_bulk_import
def tab_player_stats(self, widget, data=None):
new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.config, self.querydict, self.window)
new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.config, self.sql, self.window)
self.threads.append(new_ps_thread)
ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Player Stats")
def tab_positional_stats(self, widget, data=None):
new_ps_thread=GuiPositionalStats.GuiPositionalStats(self.config, self.querydict)
new_ps_thread = GuiPositionalStats.GuiPositionalStats(self.config, self.sql)
self.threads.append(new_ps_thread)
ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Positional Stats")
@ -468,7 +488,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
def tab_table_viewer(self, widget, data=None):
"""opens a table viewer tab"""
#print "start of tab_table_viewer"
new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings)
new_tv_thread=GuiTableViewer.GuiTableViewer(self.db.fdb, self.settings)
self.threads.append(new_tv_thread)
tv_tab=new_tv_thread.get_vbox()
self.add_and_display_tab(tv_tab, "Table Viewer")
@ -477,17 +497,18 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
def tabGraphViewer(self, widget, data=None):
"""opens a graph viewer tab"""
#print "start of tabGraphViewer"
new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config)
new_gv_thread = GuiGraphViewer.GuiGraphViewer(self.sql, self.config)
self.threads.append(new_gv_thread)
gv_tab=new_gv_thread.get_vbox()
self.add_and_display_tab(gv_tab, "Graphs")
#end def tabGraphViewer
def __init__(self):
self.threads=[]
self.db=None
self.config = Configuration.Config(dbname=options.dbname)
self.load_profile()
self.threads = []
# no more than 1 process can this lock at a time:
self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
self.db = None
self.status_bar = None
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("delete_event", self.delete_event)
@ -522,11 +543,8 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tab_main_help(None, None)
self.status_bar = gtk.Label("Status: Connected to %s database named %s on host %s"%(self.db.get_backend_name(),self.db.database, self.db.host))
self.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show()
self.window.show()
self.load_profile()
sys.stderr.write("fpdb starting ...")
#end def __init__

View File

@ -18,20 +18,21 @@
import os
import re
import sys
import logging
from time import time, strftime
import fpdb_simple
import FpdbSQLQueries
class fpdb_db:
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
def __init__(self):
"""Simple constructor, doesnt really do anything"""
self.db = None
self.cursor = None
self.sql = {}
self.MYSQL_INNODB = 2
self.PGSQL = 3
self.SQLITE = 4
# Data Structures for index and foreign key creation
# drop_code is an int with possible values: 0 - don't drop for bulk import
@ -72,6 +73,8 @@ class fpdb_db:
, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
, [ # indexes for sqlite (list index 4)
]
]
self.foreignKeys = [
@ -146,12 +149,12 @@ class fpdb_db:
self.settings = {}
self.settings['os'] = "linuxmac" if os.name != "nt" else "windows"
self.settings.update(config.get_db_parameters())
self.connect(self.settings['db-backend'],
self.settings['db-host'],
self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
db = config.get_db_parameters()
self.connect(backend=db['db-backend'],
host=db['db-host'],
database=db['db-databaseName'],
user=db['db-user'],
password=db['db-password'])
#end def do_connect
def connect(self, backend=None, host=None, database=None,
@ -164,13 +167,13 @@ class fpdb_db:
self.user=user
self.password=password
self.database=database
if backend==self.MYSQL_INNODB:
if backend==fpdb_db.MYSQL_INNODB:
import MySQLdb
try:
self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True)
except:
raise fpdb_simple.FpdbError("MySQL connection failed")
elif backend==self.PGSQL:
elif backend==fpdb_db.PGSQL:
import psycopg2
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
@ -179,19 +182,41 @@ class fpdb_db:
# For local domain-socket connections, only DB name is
# needed, and everything else is in fact undefined and/or
# flat out wrong
# sqlcoder: This database only connect failed in my windows setup??
# Modifed it to try the 4 parameter style if the first connect fails - does this work everywhere?
connected = False
if self.host == "localhost" or self.host == "127.0.0.1":
self.db = psycopg2.connect(database = database)
else:
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
try:
self.db = psycopg2.connect(database = database)
connected = True
except:
pass
#msg = "PostgreSQL direct connection to database (%s) failed, trying with user ..." % (database,)
#print msg
#raise fpdb_simple.FpdbError(msg)
if not connected:
try:
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
except:
msg = "PostgreSQL connection to database (%s) user (%s) failed." % (database, user)
print msg
raise fpdb_simple.FpdbError(msg)
elif backend==fpdb_db.SQLITE:
logging.info("Connecting to SQLite:%(database)s" % {'database':database})
import sqlite3
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)
self.cursor=self.db.cursor()
self.cursor.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED')
# Set up query dictionary as early in the connection process as we can.
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
self.cursor.execute(self.sql.query['set tx level'])
self.wrongDbVersion=False
try:
self.cursor.execute("SELECT * FROM Settings")
@ -222,13 +247,14 @@ class fpdb_db:
def create_tables(self):
#todo: should detect and fail gracefully if tables already exist.
logging.debug(self.sql.query['createSettingsTable'])
self.cursor.execute(self.sql.query['createSettingsTable'])
logging.debug(self.sql.query['createSitesTable'])
self.cursor.execute(self.sql.query['createSitesTable'])
self.cursor.execute(self.sql.query['createGametypesTable'])
self.cursor.execute(self.sql.query['createPlayersTable'])
self.cursor.execute(self.sql.query['createAutoratesTable'])
self.cursor.execute(self.sql.query['createHandsTable'])
self.cursor.execute(self.sql.query['createBoardCardsTable'])
self.cursor.execute(self.sql.query['createTourneyTypesTable'])
self.cursor.execute(self.sql.query['createTourneysTable'])
self.cursor.execute(self.sql.query['createTourneysPlayersTable'])
@ -260,10 +286,12 @@ class fpdb_db:
for table in tables:
self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
elif(self.get_backend_name() == 'SQLite'):
#todo: sqlite version here
print "Empty function here"
self.cursor.execute(self.sql.query['list_tables'])
for table in self.cursor.fetchall():
logging.debug(self.sql.query['drop_table'] + table[0])
self.cursor.execute(self.sql.query['drop_table'] + table[0])
self.db.commit()
self.db.commit()
#end def drop_tables
def drop_referential_integrity(self):
@ -292,6 +320,8 @@ class fpdb_db:
return "MySQL InnoDB"
elif self.backend==3:
return "PostgreSQL"
elif self.backend==4:
return "SQLite"
else:
raise fpdb_simple.FpdbError("invalid backend")
#end def get_backend_name
@ -301,11 +331,15 @@ class fpdb_db:
#end def get_db_info
def fillDefaultData(self):
self.cursor.execute("INSERT INTO Settings VALUES (118);")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');")
self.cursor.execute("INSERT INTO Settings (version) VALUES (118);")
self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')")
self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')")
self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', 'USD')")
self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('Win2day', 'USD')")
self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
#self.cursor.execute("""INSERT INTO TourneyTypes
# (siteId,buyin,fee,knockout,rebuyOrAddon) VALUES
# (1,0,0,0,?)""",(False,) )
#end def fillDefaultData
def recreate_tables(self):
@ -538,72 +572,100 @@ class fpdb_db:
self.db.set_isolation_level(1) # go back to normal isolation level
#end def dropAllIndexes
def analyzeDB(self):
"""Do whatever the DB can offer to update index/table statistics"""
stime = time()
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow vacuum to work
try:
self.cursor.execute("vacuum analyze")
except:
print "Error during vacuum"
self.db.set_isolation_level(1) # go back to normal isolation level
self.db.commit()
atime = time() - stime
print "analyze took", atime, "seconds"
#end def analyzeDB
# Currently uses an exclusive lock on the Hands table as a global lock
# Return values are Unix style, 0 for success, positive integers for errors
# 1 = generic error
# 2 = hands table does not exist (error message is suppressed)
def get_global_lock(self):
def getLastInsertId(self):
if self.backend == self.MYSQL_INNODB:
try:
self.cursor.execute( "lock tables Hands write" )
except:
# Table 'fpdb.hands' doesn't exist
if str(sys.exc_value).find(".hands' doesn't exist") >= 0:
return(2)
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(1)
ret = self.db.insert_id()
if ret < 1 or ret > 999999999:
print "getLastInsertId(): problem fetching insert_id? ret=", ret
ret = -1
elif self.backend == self.PGSQL:
try:
self.cursor.execute( "lock table Hands in exclusive mode nowait" )
#print "... after lock table, status =", self.cursor.statusmessage
except:
# relation "hands" does not exist
if str(sys.exc_value).find('relation "hands" does not exist') >= 0:
return(2)
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(1)
return(0)
# some options:
# currval(hands_id_seq) - use name of implicit seq here
# lastval() - still needs sequences set up?
# insert ... returning is useful syntax (but postgres specific?)
# see rules (fancy trigger type things)
self.cursor.execute ("SELECT lastval()")
row = self.cursor.fetchone()
if not row:
print "getLastInsertId(%s): problem fetching lastval? row=" % seq, row
ret = -1
else:
ret = row[0]
elif self.backend == fpdb_db.SQLITE:
# don't know how to do this in sqlite
print "getLastInsertId(): not coded for sqlite yet"
ret = -1
else:
print "getLastInsertId(): unknown backend ", self.backend
ret = -1
return ret
def storeHand(self, p):
#stores into table hands:
self.cursor.execute ("""INSERT INTO Hands
(siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats
,playersVpi, playersAtStreet1, playersAtStreet2
,playersAtStreet3, playersAtStreet4, playersAtShowdown
,street0Raises, street1Raises, street2Raises
,street3Raises, street4Raises, street1Pot
,street2Pot, street3Pot, street4Pot
,showdownPot
self.cursor.execute ("""INSERT INTO Hands (
tablename,
sitehandno,
gametypeid,
handstart,
importtime,
seats,
maxseats,
boardcard1,
boardcard2,
boardcard3,
boardcard4,
boardcard5,
-- texture,
playersVpi,
playersAtStreet1,
playersAtStreet2,
playersAtStreet3,
playersAtStreet4,
playersAtShowdown,
street0Raises,
street1Raises,
street2Raises,
street3Raises,
street4Raises,
-- 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['siteHandNo'], gametype_id, p['handStart'], len(names), p['tableName'], datetime.datetime.today(), p['maxSeats']
,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']
)
)
(%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'],
p['handStart'],
datetime.datetime.today(),
len(p['names']),
p['maxSeats'],
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']
)
)
#return getLastInsertId(backend, conn, cursor)
#end class fpdb_db

View File

@ -22,6 +22,7 @@
import os # todo: remove this once import_dir is in fpdb_import
import sys
from time import time, strftime
import logging
import traceback
import math
import datetime
@ -31,6 +32,7 @@ import re
import fpdb_simple
import fpdb_db
import Database
import fpdb_parse_logic
import Configuration
@ -57,7 +59,8 @@ class Importer:
self.settings = settings
self.caller = caller
self.config = config
self.fdb = None
self.database = None # database will be the main db interface eventually
self.fdb = None # fdb may disappear or just hold the simple db connection
self.cursor = None
self.filelist = {}
self.dirlist = {}
@ -75,9 +78,13 @@ class Importer:
self.settings.setdefault("minPrint", 30)
self.settings.setdefault("handCount", 0)
self.database = Database.Database(self.config) # includes .connection and .sql variables
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
self.fdb.do_connect(self.config)
self.fdb.db.rollback()
self.fdb.db.rollback() # make sure all locks are released
self.NEWIMPORT = False
self.allow_hudcache_rebuild = True;
#Set functions
def setCallHud(self, value):
@ -162,12 +169,19 @@ class Importer:
def runImport(self):
""""Run full import on self.filelist."""
start = datetime.datetime.now()
print "started at", start, "--", len(self.filelist), "files to import.", self.settings['dropIndexes']
print "Started at", start, "--", len(self.filelist), "files to import.", self.settings['dropIndexes']
if self.settings['dropIndexes'] == 'auto':
self.settings['dropIndexes'] = self.calculate_auto()
self.settings['dropIndexes'] = self.calculate_auto2(12.0, 500.0)
if self.allow_hudcache_rebuild:
self.settings['dropHudCache'] = self.calculate_auto2(25.0, 500.0) # returns "drop"/"don't drop"
if self.settings['dropIndexes'] == 'drop':
self.fdb.prepareBulkImport()
else:
print "No need drop indexes."
#print "dropInd =", self.settings['dropIndexes'], " dropHudCache =", self.settings['dropHudCache']
totstored = 0
totdups = 0
totpartial = 0
@ -183,7 +197,13 @@ class Importer:
tottime += ttime
if self.settings['dropIndexes'] == 'drop':
self.fdb.afterBulkImport()
self.fdb.analyzeDB()
else:
print "No need rebuild indexes."
if self.settings['dropHudCache'] == 'drop':
self.database.rebuild_hudcache()
else:
print "No need to rebuild hudcache."
self.database.analyzeDB()
return (totstored, totdups, totpartial, toterrors, tottime)
# else: import threaded
@ -202,6 +222,41 @@ class Importer:
if self.settings['handsInDB'] > 50000: return "don't drop"
return "drop"
def calculate_auto2(self, scale, increment):
"""A second heuristic to determine a reasonable value of drop/don't drop
This one adds up size of files to import to guess number of hands in them
Example values of scale and increment params might be 10 and 500 meaning
roughly: drop if importing more than 10% (100/scale) of hands in db or if
less than 500 hands in db"""
size_per_hand = 1300.0 # wag based on a PS 6-up FLHE file. Actual value not hugely important
# as values of scale and increment compensate for it anyway.
# decimal used to force float arithmetic
# get number of hands in db
if 'handsInDB' not in self.settings:
try:
tmpcursor = self.fdb.db.cursor()
tmpcursor.execute("Select count(1) from Hands;")
self.settings['handsInDB'] = tmpcursor.fetchone()[0]
except:
pass # if this fails we're probably doomed anyway
# add up size of import files
total_size = 0.0
for file in self.filelist:
if os.path.exists(file):
stat_info = os.stat(file)
total_size += stat_info.st_size
# if hands_in_db is zero or very low, we want to drop indexes, otherwise compare
# import size with db size somehow:
ret = "don't drop"
if self.settings['handsInDB'] < scale * (total_size/size_per_hand) + increment:
ret = "drop"
#print "auto2: handsindb =", self.settings['handsInDB'], "total_size =", total_size, "size_per_hand =", \
# size_per_hand, "inc =", increment, "return:", ret
return ret
#Run import on updated files, then store latest update time.
def runUpdated(self):
#Check for new files in monitored directories
@ -253,36 +308,60 @@ class Importer:
if os.path.isdir(file):
self.addToDirList[file] = [site] + [filter]
return
if filter == "passthrough" or filter == "":
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(file, site)
else:
conv = None
# Load filter, process file, pass returned filename to import_fpdb_file
conv = None
# Load filter, process file, pass returned filename to import_fpdb_file
print "\nConverting %s" % file
hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase)
hhdir = os.path.join(hhbase,site)
try:
out_path = os.path.join(hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file))
except:
out_path = os.path.join(hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file))
print "\nConverting %s" % file
hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase)
hhdir = os.path.join(hhbase,site)
try:
out_path = os.path.join(hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file))
except:
out_path = os.path.join(hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file))
filter_name = filter.replace("ToFpdb", "")
filter_name = filter.replace("ToFpdb", "")
mod = __import__(filter)
obj = getattr(mod, filter_name, None)
if callable(obj):
conv = obj(in_path = file, out_path = out_path)
if(conv.getStatus()):
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(out_path, site)
else:
# conversion didn't work
# TODO: appropriate response?
return (0, 0, 0, 1, 0)
# Example code for using threads & queues: (maybe for obj and import_fpdb_file??)
#def worker():
# while True:
# item = q.get()
# do_work(item)
# q.task_done()
#
#q = Queue()
#for i in range(num_worker_threads):
# t = Thread(target=worker)
# t.setDaemon(True)
# t.start()
#
#for item in source():
# q.put(item)
#
#q.join() # block until all tasks are done
mod = __import__(filter)
obj = getattr(mod, filter_name, None)
if callable(obj):
conv = obj(in_path = file, out_path = out_path, index = 0) # Index into file 0 until changeover
if(conv.getStatus() and self.NEWIMPORT == False):
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(out_path, site)
elif (conv.getStatus() and self.NEWIMPORT == True):
#This code doesn't do anything yet
handlist = hhc.getProcessedHands()
self.pos_in_file[file] = hhc.getLastCharacterRead()
for hand in handlist:
hand.prepInsert()
hand.insert()
else:
print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter)
return
# conversion didn't work
# TODO: appropriate response?
return (0, 0, 0, 1, 0)
else:
print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter)
return
#This will barf if conv.getStatus != True
return (stored, duplicates, partial, errors, ttime)
@ -359,8 +438,9 @@ class Importer:
self.hand=hand
try:
handsId = fpdb_parse_logic.mainParser(self.settings['db-backend'], self.fdb.db
,self.fdb.cursor, self.siteIds[site], category, hand, self.config)
handsId = fpdb_parse_logic.mainParser( self.settings, self.fdb
, self.siteIds[site], category, hand
, self.config, self.database )
self.fdb.db.commit()
stored += 1

View File

@ -18,10 +18,16 @@
#methods that are specific to holdem but not trivial
import fpdb_simple
import fpdb_save_to_db
import Database
#parses a holdem hand
def mainParser(backend, db, cursor, siteID, category, hand, config):
def mainParser(settings, fdb, siteID, category, hand, config, db = None):
backend = settings['db-backend']
if db == None:
#This is redundant - hopefully fdb will be a Database object in an iteration soon
db = Database.Database(c = config, sql = None)
else:
db = db
category = fpdb_simple.recogniseCategory(hand[0])
base = "hold" if category == "holdem" or category == "omahahi" or category == "omahahilo" else "stud"
@ -34,6 +40,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
#part 1: read hand no and check for duplicate
siteHandNo = fpdb_simple.parseSiteHandNo(hand[0])
#print "siteHandNo =", siteHandNo
handStartTime = fpdb_simple.parseHandStartTime(hand[0])
isTourney = fpdb_simple.isTourney(hand[0])
@ -45,7 +52,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
break
#print "small blind line:",smallBlindLine
gametypeID = fpdb_simple.recogniseGametypeID(backend, db, cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney)
gametypeID = fpdb_simple.recogniseGametypeID(backend, fdb.db, fdb.cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney)
if isTourney:
siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0])
buyin = fpdb_simple.parseBuyin(hand[0])
@ -56,9 +63,9 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
tourneyStartTime= handStartTime #todo: read tourney start time
rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0])
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(cursor, siteID, buyin, fee, knockout, rebuyOrAddon)
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(fdb.cursor, siteID, buyin, fee, knockout, rebuyOrAddon)
fpdb_simple.isAlreadyInDB(cursor, gametypeID, siteHandNo)
fpdb_simple.isAlreadyInDB(fdb.cursor, gametypeID, siteHandNo)
hand = fpdb_simple.filterCrap(hand, isTourney)
@ -72,7 +79,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
seatLines.append(line)
names = fpdb_simple.parseNames(seatLines)
playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID)
playerIDs = fpdb_simple.recognisePlayerIDs(fdb.cursor, names, siteID)
tmp = fpdb_simple.parseCashesAndSeatNos(seatLines)
startCashes = tmp['startCashes']
seatNos = tmp['seatNos']
@ -111,7 +118,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
tableName = tableResult['tableName']
#print "before part5, antes:", antes
#part 5: final preparations, then call fpdb_save_to_db.* with
#part 5: final preparations, then call Database.* with
# the arrays as they are - that file will fill them.
fpdb_simple.convertCardValues(cardValues)
if base == "hold":
@ -119,13 +126,14 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
fpdb_simple.convertBlindBet(actionTypes, actionAmounts)
fpdb_simple.checkPositions(positions)
cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, ))
limit_type = cursor.fetchone()[0]
fdb.cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, ))
limit_type = fdb.cursor.fetchone()[0]
fpdb_simple.convert3B4B(category, limit_type, actionTypes, actionAmounts)
totalWinnings = sum(winnings)
# if hold'em, use positions and not antes, if stud do not use positions, use antes
# this is used for handsplayers inserts, so still needed even if hudcache update is being skipped
if base == "hold":
hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes
, allIns, actionTypeByNo, winnings, totalWinnings, positions
@ -140,8 +148,8 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
payin_amounts = fpdb_simple.calcPayin(len(names), buyin, fee)
if base == "hold":
result = fpdb_save_to_db.tourney_holdem_omaha(
config, backend, db, cursor, base, category, siteTourneyNo, buyin
result = db.tourney_holdem_omaha(
config, settings, fdb.db, fdb.cursor, base, category, siteTourneyNo, buyin
, fee, knockout, entries, prizepool, tourneyStartTime
, payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo
, gametypeID, handStartTime, names, playerIDs, startCashes
@ -149,8 +157,8 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
, winnings, rakes, actionTypes, allIns, actionAmounts
, actionNos, hudImportData, maxSeats, tableName, seatNos)
elif base == "stud":
result = fpdb_save_to_db.tourney_stud(
config, backend, db, cursor, base, category, siteTourneyNo
result = db.tourney_stud(
config, settings, fdb.db, fdb.cursor, base, category, siteTourneyNo
, buyin, fee, knockout, entries, prizepool, tourneyStartTime
, payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo
, gametypeID, handStartTime, names, playerIDs, startCashes
@ -161,23 +169,23 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
raise fpdb_simple.FpdbError("unrecognised category")
else:
if base == "hold":
result = fpdb_save_to_db.ring_holdem_omaha(
config, backend, db, cursor, base, category, siteHandNo
result = db.ring_holdem_omaha(
config, settings, fdb.db, fdb.cursor, base, category, siteHandNo
, gametypeID, handStartTime, names, playerIDs
, startCashes, positions, cardValues, cardSuits
, boardValues, boardSuits, winnings, rakes
, actionTypes, allIns, actionAmounts, actionNos
, hudImportData, maxSeats, tableName, seatNos)
elif base == "stud":
result = fpdb_save_to_db.ring_stud(
config, backend, db, cursor, base, category, siteHandNo, gametypeID
result = db.ring_stud(
config, settings, fdb.db, fdb.cursor, base, category, siteHandNo, gametypeID
, handStartTime, names, playerIDs, startCashes, antes
, cardValues, cardSuits, winnings, rakes, actionTypes, allIns
, actionAmounts, actionNos, hudImportData, maxSeats, tableName
, seatNos)
else:
raise fpdb_simple.FpdbError ("unrecognised category")
db.commit()
fdb.db.commit()
return result
#end def mainParser

View File

@ -1,173 +0,0 @@
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#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.
#This file contains methods to store hands into the db. decides to move this
#into a seperate file since its ugly, fairly long and just generally in the way.
from time import time
import fpdb_simple
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
fastStoreHudCache = False # set this to True to test the new storeHudCache routine
saveActions = True # set this to False to avoid storing action data
# Pros: speeds up imports
# Cons: no action data is saved, so you need to keep the hand histories
# variance not available on stats page
# : No graphs
#stores a stud/razz hand into the database
def ring_stud(config, backend, db, cursor, base, category, site_hand_no, gametype_id, hand_start_time
,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes
,action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName
,seatNos):
import_options = config.get_import_parameters()
saveActions = False if import_options['saveActions'] == False else True
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
hands_id = fpdb_simple.storeHands(backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudImportData)
#print "before calling store_hands_players_stud, antes:", antes
hands_players_ids = fpdb_simple.store_hands_players_stud(backend, db, cursor, hands_id, player_ids
,start_cashes, antes, card_values
,card_suits, winnings, rakes, seatNos)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types
,allIns, action_amounts, actionNos)
return hands_id
#end def ring_stud
def ring_holdem_omaha(config, backend, db, cursor, base, category, site_hand_no, gametype_id
,hand_start_time, names, player_ids, start_cashes, positions, card_values
,card_suits, board_values, board_suits, winnings, rakes, action_types, allIns
,action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a holdem/omaha hand into the database"""
import_options = config.get_import_parameters()
saveActions = False if import_options['saveActions'] == False else True
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
# print "DEBUG: saveActions = '%s' fastStoreHudCache = '%s'"%(saveActions, fastStoreHudCache)
# print "DEBUG: import_options = ", import_options
t0 = time()
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
t1 = time()
fpdb_simple.fill_board_cards(board_values, board_suits)
t2 = time()
hands_id = fpdb_simple.storeHands(backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudImportData)
t3 = time()
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha(
backend, db, cursor, category, hands_id, player_ids, start_cashes
, positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData)
t4 = time()
#print "ring holdem, backend=%d" % backend
if fastStoreHudCache:
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
else:
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
t5 = time()
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
t6 = time()
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
t7 = time()
#print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4, t6-t5)
return hands_id
#end def ring_holdem_omaha
def tourney_holdem_omaha(config, backend, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout
,entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId
,siteId #end of tourney specific params
,site_hand_no, gametype_id, hand_start_time, names, player_ids
,start_cashes, positions, card_values, card_suits, board_values
,board_suits, winnings, rakes, action_types, allIns, action_amounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a tourney holdem/omaha hand into the database"""
import_options = config.get_import_parameters()
saveActions = True if import_options['saveActions'] == True else False
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
fpdb_simple.fill_board_cards(board_values, board_suits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha_tourney(
backend, db, cursor, category, hands_id, player_ids, start_cashes, positions
, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids)
#print "tourney holdem, backend=%d" % backend
if fastStoreHudCache:
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
else:
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
return hands_id
#end def tourney_holdem_omaha
def tourney_stud(config, backend, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries
,prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteId
,siteHandNo, gametypeId, handStartTime, names, playerIds, startCashes, antes
,cardValues, cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
#stores a tourney stud/razz hand into the database
import_options = config.get_import_parameters()
saveActions = True if import_options['saveActions'] == True else False
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, playerIds, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(backend, db, cursor, siteHandNo, gametypeId, handStartTime, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_stud_tourney(backend, db, cursor, hands_id
, playerIds, startCashes, antes, cardValues, cardSuits
, winnings, rakes, seatNos, tourneys_players_ids)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametypeId, hand_start_time, playerIds, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos)
return hands_id
#end def tourney_stud

View File

@ -1136,21 +1136,14 @@ def storeActions(cursor, handsPlayersIds, actionTypes, allIns, actionAmounts, ac
cursor.executemany("INSERT INTO HandsActions (handsPlayerId, street, actionNo, action, allIn, amount) VALUES (%s, %s, %s, %s, %s, %s)", inserts)
#end def storeActions
def store_board_cards(cursor, hands_id, board_values, board_suits):
#stores into table board_cards
cursor.execute ("""INSERT INTO BoardCards (handId, card1Value, card1Suit,
card2Value, card2Suit, card3Value, card3Suit, card4Value, card4Suit,
card5Value, card5Suit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
(hands_id, board_values[0], board_suits[0], board_values[1], board_suits[1],
board_values[2], board_suits[2], board_values[3], board_suits[3],
board_values[4], board_suits[4]))
#end def store_board_cards
def storeHands(backend, conn, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudCache):
,hand_start_time, names, tableName, maxSeats, hudCache,
board_values, board_suits):
cards = [Card.cardFromValueSuit(v,s) for v,s in zip(board_values,board_suits)]
#stores into table hands:
cursor.execute ("""INSERT INTO Hands
(siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats
,boardcard1,boardcard2,boardcard3,boardcard4,boardcard5
,playersVpi, playersAtStreet1, playersAtStreet2
,playersAtStreet3, playersAtStreet4, playersAtShowdown
,street0Raises, street1Raises, street2Raises
@ -1159,9 +1152,11 @@ def storeHands(backend, conn, cursor, site_hand_no, gametype_id
,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)
(%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)
"""
, (site_hand_no, gametype_id, hand_start_time, len(names), tableName, datetime.datetime.today(), maxSeats
,cards[0], cards[1], cards[2], cards[3], cards[4]
,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2']
,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown']
,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises']
@ -2121,7 +2116,7 @@ def storeHudCache(backend, cursor, base, category, gametypeId, hand_start_time,
# hard-code styleKey as 'A000000' (all-time cache, no key) for now
styleKey = 'A000000'
#print "storeHudCache, len(playerIds)=", len(playerIds), " len(vpip)=" \
#print "storeHudCache2, len(playerIds)=", len(playerIds), " len(vpip)=" \
#, len(hudImportData['street0VPI']), " len(totprof)=", len(hudImportData['totalProfit'])
for player in xrange(len(playerIds)):
@ -2292,189 +2287,6 @@ VALUES (%s, %s, %s, %s, %s, %s,
pass
# else:
# print "todo: implement storeHudCache for stud base"
#end def storeHudCache
def storeHudCache2(backend, cursor, base, category, gametypeId, hand_start_time, playerIds, hudImportData):
"""Modified version aiming for more speed ..."""
# if (category=="holdem" or category=="omahahi" or category=="omahahilo"):
if use_date_in_hudcache:
#print "key =", "d%02d%02d%02d " % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day)
styleKey = "d%02d%02d%02d" % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day)
else:
# hard-code styleKey as 'A000000' (all-time cache, no key) for now
styleKey = 'A000000'
#print "storeHudCache2, len(playerIds)=", len(playerIds), " len(vpip)=" \
#, len(hudImportData['street0VPI']), " len(totprof)=", len(hudImportData['totalProfit'])
for player in xrange(len(playerIds)):
# Set up a clean row
row=[]
row.append(0)#blank for id
row.append(gametypeId)
row.append(playerIds[player])
row.append(len(playerIds))#seats
for i in xrange(len(hudImportData)+2):
row.append(0)
if base=="hold":
row[4]=hudImportData['position'][player]
else:
row[4]=0
row[5]=1 #tourneysGametypeId
row[6]+=1 #HDs
if hudImportData['street0VPI'][player]: row[7]+=1
if hudImportData['street0Aggr'][player]: row[8]+=1
if hudImportData['street0_3BChance'][player]: row[9]+=1
if hudImportData['street0_3BDone'][player]: row[10]+=1
if hudImportData['street1Seen'][player]: row[11]+=1
if hudImportData['street2Seen'][player]: row[12]+=1
if hudImportData['street3Seen'][player]: row[13]+=1
if hudImportData['street4Seen'][player]: row[14]+=1
if hudImportData['sawShowdown'][player]: row[15]+=1
if hudImportData['street1Aggr'][player]: row[16]+=1
if hudImportData['street2Aggr'][player]: row[17]+=1
if hudImportData['street3Aggr'][player]: row[18]+=1
if hudImportData['street4Aggr'][player]: row[19]+=1
if hudImportData['otherRaisedStreet1'][player]: row[20]+=1
if hudImportData['otherRaisedStreet2'][player]: row[21]+=1
if hudImportData['otherRaisedStreet3'][player]: row[22]+=1
if hudImportData['otherRaisedStreet4'][player]: row[23]+=1
if hudImportData['foldToOtherRaisedStreet1'][player]: row[24]+=1
if hudImportData['foldToOtherRaisedStreet2'][player]: row[25]+=1
if hudImportData['foldToOtherRaisedStreet3'][player]: row[26]+=1
if hudImportData['foldToOtherRaisedStreet4'][player]: row[27]+=1
if hudImportData['wonWhenSeenStreet1'][player]!=0.0: row[28]+=hudImportData['wonWhenSeenStreet1'][player]
if hudImportData['wonAtSD'][player]!=0.0: row[29]+=hudImportData['wonAtSD'][player]
if hudImportData['stealAttemptChance'][player]: row[30]+=1
if hudImportData['stealAttempted'][player]: row[31]+=1
if hudImportData['foldBbToStealChance'][player]: row[32]+=1
if hudImportData['foldedBbToSteal'][player]: row[33]+=1
if hudImportData['foldSbToStealChance'][player]: row[34]+=1
if hudImportData['foldedSbToSteal'][player]: row[35]+=1
if hudImportData['street1CBChance'][player]: row[36]+=1
if hudImportData['street1CBDone'][player]: row[37]+=1
if hudImportData['street2CBChance'][player]: row[38]+=1
if hudImportData['street2CBDone'][player]: row[39]+=1
if hudImportData['street3CBChance'][player]: row[40]+=1
if hudImportData['street3CBDone'][player]: row[41]+=1
if hudImportData['street4CBChance'][player]: row[42]+=1
if hudImportData['street4CBDone'][player]: row[43]+=1
if hudImportData['foldToStreet1CBChance'][player]: row[44]+=1
if hudImportData['foldToStreet1CBDone'][player]: row[45]+=1
if hudImportData['foldToStreet2CBChance'][player]: row[46]+=1
if hudImportData['foldToStreet2CBDone'][player]: row[47]+=1
if hudImportData['foldToStreet3CBChance'][player]: row[48]+=1
if hudImportData['foldToStreet3CBDone'][player]: row[49]+=1
if hudImportData['foldToStreet4CBChance'][player]: row[50]+=1
if hudImportData['foldToStreet4CBDone'][player]: row[51]+=1
#print "player=", player
#print "len(totalProfit)=", len(hudImportData['totalProfit'])
if hudImportData['totalProfit'][player]:
row[52]+=hudImportData['totalProfit'][player]
if hudImportData['street1CheckCallRaiseChance'][player]: row[53]+=1
if hudImportData['street1CheckCallRaiseDone'][player]: row[54]+=1
if hudImportData['street2CheckCallRaiseChance'][player]: row[55]+=1
if hudImportData['street2CheckCallRaiseDone'][player]: row[56]+=1
if hudImportData['street3CheckCallRaiseChance'][player]: row[57]+=1
if hudImportData['street3CheckCallRaiseDone'][player]: row[58]+=1
if hudImportData['street4CheckCallRaiseChance'][player]: row[59]+=1
if hudImportData['street4CheckCallRaiseDone'][player]: row[60]+=1
# Try to do the update first:
num = cursor.execute("""UPDATE HudCache
SET HDs=HDs+%s, street0VPI=street0VPI+%s, street0Aggr=street0Aggr+%s,
street0_3B4BChance=street0_3B4BChance+%s, street0_3B4BDone=street0_3B4BDone+%s,
street1Seen=street1Seen+%s, street2Seen=street2Seen+%s, street3Seen=street3Seen+%s,
street4Seen=street4Seen+%s, sawShowdown=sawShowdown+%s,
street1Aggr=street1Aggr+%s, street2Aggr=street2Aggr+%s, street3Aggr=street3Aggr+%s,
street4Aggr=street4Aggr+%s, otherRaisedStreet1=otherRaisedStreet1+%s,
otherRaisedStreet2=otherRaisedStreet2+%s, otherRaisedStreet3=otherRaisedStreet3+%s,
otherRaisedStreet4=otherRaisedStreet4+%s,
foldToOtherRaisedStreet1=foldToOtherRaisedStreet1+%s, foldToOtherRaisedStreet2=foldToOtherRaisedStreet2+%s,
foldToOtherRaisedStreet3=foldToOtherRaisedStreet3+%s, foldToOtherRaisedStreet4=foldToOtherRaisedStreet4+%s,
wonWhenSeenStreet1=wonWhenSeenStreet1+%s, wonAtSD=wonAtSD+%s, stealAttemptChance=stealAttemptChance+%s,
stealAttempted=stealAttempted+%s, foldBbToStealChance=foldBbToStealChance+%s,
foldedBbToSteal=foldedBbToSteal+%s,
foldSbToStealChance=foldSbToStealChance+%s, foldedSbToSteal=foldedSbToSteal+%s,
street1CBChance=street1CBChance+%s, street1CBDone=street1CBDone+%s, street2CBChance=street2CBChance+%s,
street2CBDone=street2CBDone+%s, street3CBChance=street3CBChance+%s,
street3CBDone=street3CBDone+%s, street4CBChance=street4CBChance+%s, street4CBDone=street4CBDone+%s,
foldToStreet1CBChance=foldToStreet1CBChance+%s, foldToStreet1CBDone=foldToStreet1CBDone+%s,
foldToStreet2CBChance=foldToStreet2CBChance+%s, foldToStreet2CBDone=foldToStreet2CBDone+%s,
foldToStreet3CBChance=foldToStreet3CBChance+%s,
foldToStreet3CBDone=foldToStreet3CBDone+%s, foldToStreet4CBChance=foldToStreet4CBChance+%s,
foldToStreet4CBDone=foldToStreet4CBDone+%s, totalProfit=totalProfit+%s,
street1CheckCallRaiseChance=street1CheckCallRaiseChance+%s,
street1CheckCallRaiseDone=street1CheckCallRaiseDone+%s, street2CheckCallRaiseChance=street2CheckCallRaiseChance+%s,
street2CheckCallRaiseDone=street2CheckCallRaiseDone+%s, street3CheckCallRaiseChance=street3CheckCallRaiseChance+%s,
street3CheckCallRaiseDone=street3CheckCallRaiseDone+%s, street4CheckCallRaiseChance=street4CheckCallRaiseChance+%s,
street4CheckCallRaiseDone=street4CheckCallRaiseDone+%s
WHERE gametypeId+0=%s
AND playerId=%s
AND activeSeats=%s
AND position=%s
AND tourneyTypeId+0=%s
AND styleKey=%s
""", (row[6], row[7], row[8], row[9], row[10],
row[11], row[12], row[13], row[14], row[15],
row[16], row[17], row[18], row[19], row[20],
row[21], row[22], row[23], row[24], row[25],
row[26], row[27], row[28], row[29], row[30],
row[31], row[32], row[33], row[34], row[35],
row[36], row[37], row[38], row[39], row[40],
row[41], row[42], row[43], row[44], row[45],
row[46], row[47], row[48], row[49], row[50],
row[51], row[52], row[53], row[54], row[55],
row[56], row[57], row[58], row[59], row[60],
row[1], row[2], row[3], str(row[4]), row[5], styleKey))
# Test statusmessage to see if update worked, do insert if not
#print "storehud2, upd num =", num
if ( (backend == PGSQL and cursor.statusmessage != "UPDATE 1")
or (backend == MYSQL_INNODB and num == 0) ):
#print "playerid before insert:",row[2]," num = ", num
cursor.execute("""INSERT INTO HudCache
(gametypeId, playerId, activeSeats, position, tourneyTypeId, styleKey,
HDs, street0VPI, street0Aggr, street0_3BChance, street0_3BDone,
street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown,
street1Aggr, street2Aggr, street3Aggr, street4Aggr, otherRaisedStreet1,
otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4, foldToOtherRaisedStreet1, foldToOtherRaisedStreet2,
foldToOtherRaisedStreet3, foldToOtherRaisedStreet4, wonWhenSeenStreet1, wonAtSD, stealAttemptChance,
stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal,
street1CBChance, street1CBDone, street2CBChance, street2CBDone, street3CBChance,
street3CBDone, street4CBChance, street4CBDone, foldToStreet1CBChance, foldToStreet1CBDone,
foldToStreet2CBChance, foldToStreet2CBDone, foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance,
foldToStreet4CBDone, totalProfit, street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance,
street2CheckCallRaiseDone, street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone)
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)"""
, (row[1], row[2], row[3], row[4], row[5], styleKey, row[6], row[7], row[8], row[9], row[10]
,row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20]
,row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30]
,row[31], row[32], row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40]
,row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50]
,row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60]) )
#print "hopefully inserted hud data line: ", cursor.statusmessage
# message seems to be "INSERT 0 1"
else:
#print "updated(2) hud data line"
pass
# else:
# print "todo: implement storeHudCache for stud base"
#end def storeHudCache2
def store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime):

271
pyfpdb/interlocks.py Executable file
View File

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