Merge branch 'master' of git://git.assembla.com/fpdboz
This commit is contained in:
commit
6357f22f11
78
pyfpdb/AbsoluteToFpdb.py
Normal file → Executable file
78
pyfpdb/AbsoluteToFpdb.py
Normal file → Executable file
|
@ -26,6 +26,13 @@ from HandHistoryConverter import *
|
|||
# Class for converting Absolute HH format.
|
||||
|
||||
class Absolute(HandHistoryConverter):
|
||||
|
||||
# Class Variables
|
||||
sitename = "Absolute"
|
||||
filetype = "text"
|
||||
codepage = "cp1252"
|
||||
siteid = 8
|
||||
HORSEHand = False
|
||||
|
||||
# Static regexes
|
||||
re_SplitHands = re.compile(r"\n\n\n+")
|
||||
|
@ -35,8 +42,11 @@ class Absolute(HandHistoryConverter):
|
|||
#Seat 6 - FETS63 ($0.75 in chips)
|
||||
#Board [10s 5d Kh Qh 8c]
|
||||
|
||||
re_GameInfo = re.compile(ur"^Stage #([0-9]+): (?P<GAME>Holdem|) (?P<LIMIT>No Limit|) (?P<CURRENCY>\$| €|)(?P<BB>[0-9]*[.0-9]+)", re.MULTILINE)
|
||||
re_HandInfo = re.compile(ur"^Stage #(?P<HID>[0-9]+): .*(?P<DATETIME>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d).*\nTable: (?P<TABLE>.*) \(Real Money\)", re.MULTILINE)
|
||||
re_GameInfo = re.compile(ur"^Stage #([0-9]+): (?P<GAME>Holdem|HORSE)(?: \(1 on 1\)|)? ?(?P<LIMIT>No Limit|Pot Limit|Normal|)? ?(?P<CURRENCY>\$| €|)(?P<SB>[.0-9]+)/?(?:\$| €|)(?P<BB>[.0-9]+)?", re.MULTILINE)
|
||||
re_HorseGameInfo = re.compile(ur"^Game Type: (?P<LIMIT>Limit) (?P<GAME>Holdem)", re.MULTILINE)
|
||||
# TODO: can set max seats via (1 on 1) to a known 2 ..
|
||||
re_HandInfo = re.compile(ur"^Stage #(?P<HID>[0-9]+): .*(?P<DATETIME>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d).*\n(Table: (?P<TABLE>.*) \(Real Money\))?", re.MULTILINE)
|
||||
re_TableFromFilename = re.compile(ur".*IHH([0-9]+) (?P<TABLE>.*) -") # on HORSE STUD games, the table name isn't in the hand info!
|
||||
re_Button = re.compile(ur"Seat #(?P<BUTTON>[0-9]) is the ?[dead]* dealer$", re.MULTILINE) # TODO: that's not the right way to match for "dead" dealer is it?
|
||||
re_PlayerInfo = re.compile(ur"^Seat (?P<SEAT>[0-9]) - (?P<PNAME>.*) \((?:\$| €|)(?P<CASH>[0-9]*[.0-9]+) in chips\)", re.MULTILINE)
|
||||
re_Board = re.compile(ur"\[(?P<CARDS>[^\]]*)\]? *$", re.MULTILINE)
|
||||
|
@ -48,24 +58,6 @@ class Absolute(HandHistoryConverter):
|
|||
# re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
|
||||
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, debugging=False, index=0):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
out_path (default '-' = sys.stdout)
|
||||
follow : whether to tail -f the input
|
||||
autostart: whether to run the thread (or you can call start() yourself)
|
||||
debugging: if False, pass on partially supported game types. If true, have a go and error..."""
|
||||
#print "DEBUG: XXXXXXXXXXXXXXX"
|
||||
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Absolute", follow=follow, index=index)
|
||||
logging.info("Initialising Absolute converter class")
|
||||
self.filetype = "text"
|
||||
self.codepage = "cp1252"
|
||||
self.siteId = 3 # Needs to match id entry in Sites database
|
||||
self.debugging = debugging
|
||||
if autostart:
|
||||
self.start()
|
||||
# otherwise you need to call start yourself.
|
||||
|
||||
def compilePlayerRegexs(self, hand):
|
||||
players = set([player[1] for player in hand.players])
|
||||
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
|
||||
|
@ -79,13 +71,13 @@ debugging: if False, pass on partially supported game types. If true, have a go
|
|||
# TODO: Absolute posting when coming in new: %s - Posts $0.02 .. should that be a new Post line? where do we need to add support for that? *confused*
|
||||
self.re_PostBoth = re.compile(ur"^%s - Posts dead (?:\$| €|)(?P<SBBB>[0-9]*[.0-9]+)" % player_re, re.MULTILINE)
|
||||
self.re_Action = re.compile(ur"^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[0-9]*[.0-9]+)?" % player_re, re.MULTILINE)
|
||||
print "^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[0-9]*[.0-9]+)?" % player_re
|
||||
# print "^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[0-9]*[.0-9]+)?" % player_re
|
||||
self.re_ShowdownAction = re.compile(ur"^%s - Shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
|
||||
self.re_CollectPot = re.compile(ur"^Seat [0-9]: %s(?: \(dealer\)| \(big blind\)| \(small blind\)|) (?:won|collected) Total \((?:\$| €|)(?P<POT>[0-9]*[.0-9]+)\)" % player_re, re.MULTILINE)
|
||||
self.re_CollectPot = re.compile(ur"^Seat [0-9]: %s(?: \(dealer\)|)(?: \(big blind\)| \(small blind\)|) (?:won|collected) Total \((?:\$| €|)(?P<POT>[0-9]*[.0-9]+)\)" % player_re, re.MULTILINE)
|
||||
#self.re_PostSB = re.compile(ur"^%s: posts small blind \[(?:\$| €|) (?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_PostBB = re.compile(ur"^%s: posts big blind \[(?:\$| €|) (?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_PostBoth = re.compile(ur"^%s: posts both blinds \[(?:\$| €|) (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_Antes = re.compile(ur"^%s: posts ante \[(?:\$| €|) (?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
self.re_Antes = re.compile(ur"^%s - Ante \[(?:\$| €|)(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_BringIn = re.compile(ur"^%s posts bring-in (?:\$| €|)(?P<BRINGIN>[.0-9]+)\." % player_re, re.MULTILINE)
|
||||
self.re_HeroCards = re.compile(ur"^Dealt to %s \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
|
||||
#self.re_Action = re.compile(ur"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P<BET>[.\d]+) (USD|EUR|)\])?" % player_re, re.MULTILINE)
|
||||
|
@ -124,7 +116,7 @@ or None if we fail to get the info """
|
|||
mg = m.groupdict()
|
||||
|
||||
# translations from captured groups to our info strings
|
||||
limits = { 'No Limit':'nl', 'PL':'pl', '':'fl' }
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Normal':'fl', 'Limit':'fl'}
|
||||
games = { # base, category
|
||||
"Holdem" : ('hold','holdem'),
|
||||
'Omaha' : ('hold','omahahi'),
|
||||
|
@ -132,14 +124,26 @@ or None if we fail to get the info """
|
|||
'7 Card Stud' : ('stud','studhi')
|
||||
}
|
||||
currencies = { u' €':'EUR', '$':'USD', '':'T$' }
|
||||
if 'LIMIT' in mg:
|
||||
info['limitType'] = limits[mg['LIMIT']]
|
||||
if 'GAME' in mg and mg['GAME'] == "HORSE": # if we're a HORSE game, the game type is on the next line
|
||||
self.HORSEHand = True
|
||||
m = self.re_HorseGameInfo.search(handText)
|
||||
if not m:
|
||||
return None # it's a HORSE game and we don't understand the game type
|
||||
temp = m.groupdict()
|
||||
#print "AP HORSE processing"
|
||||
if 'GAME' not in temp or 'LIMIT' not in temp:
|
||||
return None # sort of understood it but not really
|
||||
#print "temp=", temp
|
||||
mg['GAME'] = temp['GAME']
|
||||
mg['LIMIT'] = temp['LIMIT']
|
||||
if 'GAME' in mg:
|
||||
(info['base'], info['category']) = games[mg['GAME']]
|
||||
if 'LIMIT' in mg:
|
||||
info['limitType'] = limits[mg['LIMIT']]
|
||||
if 'SB' in mg:
|
||||
info['sb'] = mg['SB']
|
||||
else:
|
||||
info['sb'] = str(float(mg['BB']) * 0.5) # TODO: Apparently AP doesn't provide small blind info!? must search to see if it's posted, I guess
|
||||
info['sb'] = str(float(mg['BB']) * 0.5) # TODO: Apparently AP doesn't provide small blind info!? must search to see if it's posted, I guess
|
||||
if 'BB' in mg:
|
||||
info['bb'] = mg['BB']
|
||||
if 'CURRENCY' in mg:
|
||||
|
@ -147,6 +151,11 @@ or None if we fail to get the info """
|
|||
if info['currency'] == 'T$':
|
||||
info['type'] = 'tour'
|
||||
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
|
||||
if info['bb'] is None:
|
||||
info['bb'] = mg['SB']
|
||||
info['sb'] = str(float(mg['SB']) * 0.5) # TODO: AP does provide Small BET for Limit .. I think? at least 1-on-1 limit they do.. sigh
|
||||
|
||||
#print info;
|
||||
|
||||
return info
|
||||
|
||||
|
@ -159,9 +168,15 @@ or None if we fail to get the info """
|
|||
return None
|
||||
logging.debug("HID %s, Table %s" % (m.group('HID'), m.group('TABLE')))
|
||||
hand.handid = m.group('HID')
|
||||
hand.tablename = m.group('TABLE')
|
||||
if m.group('TABLE'):
|
||||
hand.tablename = m.group('TABLE')
|
||||
else:
|
||||
t = self.re_TableFromFilename.search(self.in_path)
|
||||
hand.tablename = t.group('TABLE')
|
||||
hand.maxseats = 6 # assume 6-max unless we have proof it's a larger/smaller game, since absolute doesn't give seat max info
|
||||
|
||||
# TODO: (1-on-1) does have that info in the game type line
|
||||
if self.HORSEHand:
|
||||
hand.maxseats = 8
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%Y-%m-%d %H:%M:%S")
|
||||
return
|
||||
|
||||
|
@ -248,8 +263,7 @@ or None if we fail to get the info """
|
|||
else:
|
||||
#Not involved in hand
|
||||
hand.involved = False
|
||||
|
||||
|
||||
|
||||
def readStudPlayerCards(self, hand, street):
|
||||
# lol. see Plymouth.txt
|
||||
logging.warning("Absolute readStudPlayerCards is only a stub.")
|
||||
|
@ -283,7 +297,7 @@ or None if we fail to get the info """
|
|||
logging.debug("readShowdownActions")
|
||||
for shows in self.re_ShowdownAction.finditer(hand.handText):
|
||||
cards = shows.group('CARDS')
|
||||
cards = [validCard(card) for card in cards.split(' ')]
|
||||
cards = [validCard(card) for card in cards.split(' ')]
|
||||
logging.debug("readShowdownActions %s %s" %(cards, shows.group('PNAME')))
|
||||
hand.addShownCards(cards, shows.group('PNAME'))
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ from HandHistoryConverter import *
|
|||
|
||||
class Betfair(HandHistoryConverter):
|
||||
|
||||
sitename = 'Betfair'
|
||||
filetype = "text"
|
||||
codepage = "cp1252"
|
||||
siteId = 7 # Needs to match id entry in Sites database
|
||||
|
||||
# Static regexes
|
||||
re_GameInfo = re.compile("^(?P<LIMIT>NL|PL|) (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAME>(Texas Hold\'em|Omaha Hi|Razz))", re.MULTILINE)
|
||||
re_SplitHands = re.compile(r'\n\n+')
|
||||
|
@ -34,19 +39,6 @@ class Betfair(HandHistoryConverter):
|
|||
re_PlayerInfo = re.compile("Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*)\s\(\s(\$(?P<CASH>[.0-9]+)) \)")
|
||||
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
out_path (default '-' = sys.stdout)
|
||||
follow : whether to tail -f the input"""
|
||||
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Betfair", follow=follow, index) # Call super class init.
|
||||
logging.info("Initialising Betfair converter class")
|
||||
self.filetype = "text"
|
||||
self.codepage = "cp1252"
|
||||
self.siteId = 7 # Needs to match id entry in Sites database
|
||||
if autostart:
|
||||
self.start()
|
||||
|
||||
|
||||
def compilePlayerRegexs(self, hand):
|
||||
players = set([player[1] for player in hand.players])
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Configuration.py
|
||||
|
||||
Handles HUD configuration files.
|
||||
|
@ -32,6 +33,11 @@ import shutil
|
|||
import xml.dom.minidom
|
||||
from xml.dom.minidom import Node
|
||||
|
||||
import logging, logging.config
|
||||
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
|
||||
log = logging.getLogger("config")
|
||||
log.debug("config logger initialised")
|
||||
|
||||
def fix_tf(x, default = True):
|
||||
# The xml parser doesn't translate "True" to True. Therefore, we never get
|
||||
# True or False from the parser only "True" or "False". So translate the
|
||||
|
@ -73,11 +79,17 @@ class Layout:
|
|||
|
||||
class Site:
|
||||
def __init__(self, node):
|
||||
def normalizePath(path):
|
||||
"Normalized existing pathes"
|
||||
if os.path.exists(path):
|
||||
return os.path.abspath(path)
|
||||
return path
|
||||
|
||||
self.site_name = node.getAttribute("site_name")
|
||||
self.table_finder = node.getAttribute("table_finder")
|
||||
self.screen_name = node.getAttribute("screen_name")
|
||||
self.site_path = node.getAttribute("site_path")
|
||||
self.HH_path = node.getAttribute("HH_path")
|
||||
self.site_path = normalizePath(node.getAttribute("site_path"))
|
||||
self.HH_path = normalizePath(node.getAttribute("HH_path"))
|
||||
self.decoder = node.getAttribute("decoder")
|
||||
self.hudopacity = node.getAttribute("hudopacity")
|
||||
self.hudbgcolor = node.getAttribute("bgcolor")
|
||||
|
@ -91,6 +103,8 @@ class Site:
|
|||
self.xpad = node.getAttribute("xpad")
|
||||
self.ypad = node.getAttribute("ypad")
|
||||
self.layout = {}
|
||||
|
||||
print self.site_name, self.HH_path
|
||||
|
||||
for layout_node in node.getElementsByTagName('layout'):
|
||||
lo = Layout(layout_node)
|
||||
|
@ -192,6 +206,9 @@ class Database:
|
|||
self.db_user = node.getAttribute("db_user")
|
||||
self.db_type = node.getAttribute("db_type")
|
||||
self.db_pass = node.getAttribute("db_pass")
|
||||
self.db_selected = fix_tf(node.getAttribute("default"),"False")
|
||||
log.debug("Database db_name:'%(name)s' db_server:'%(server)s' db_ip:'%(ip)s' db_user:'%(user)s' db_type:'%(type)s' db_pass (not logged) selected:'%(sel)s'" \
|
||||
% { 'name':self.db_name, 'server':self.db_server, 'ip':self.db_ip, 'user':self.db_user, 'type':self.db_type, 'sel':self.db_selected} )
|
||||
|
||||
def __str__(self):
|
||||
temp = 'Database = ' + self.db_name + '\n'
|
||||
|
@ -199,7 +216,7 @@ class Database:
|
|||
if key.startswith('__'): continue
|
||||
value = getattr(self, key)
|
||||
if callable(value): continue
|
||||
temp = temp + ' ' + key + " = " + value + "\n"
|
||||
temp = temp + ' ' + key + " = " + repr(value) + "\n"
|
||||
return temp
|
||||
|
||||
class Aux_window:
|
||||
|
@ -270,11 +287,10 @@ class Tv:
|
|||
(self.combinedStealFold, self.combined2B3B, self.combinedPostflop) )
|
||||
|
||||
class Config:
|
||||
def __init__(self, file = None, dbname = 'fpdb'):
|
||||
def __init__(self, file = None, dbname = ''):
|
||||
|
||||
# "file" is a path to an xml file with the fpdb/HUD configuration
|
||||
# we check the existence of "file" and try to recover if it doesn't exist
|
||||
self.dbname = dbname
|
||||
|
||||
self.default_config_path = self.get_default_config_path()
|
||||
if file != None: # configuration file path has been passed
|
||||
|
@ -300,10 +316,10 @@ class Config:
|
|||
# Parse even if there was no real config file found and we are using the example
|
||||
# If using the example, we'll edit it later
|
||||
try:
|
||||
print "Reading configuration file %s\n" % (file)
|
||||
log.info("Reading configuration file %s" % (file))
|
||||
doc = xml.dom.minidom.parse(file)
|
||||
except:
|
||||
print "Error parsing %s. See error log file." % (file)
|
||||
except:
|
||||
log.error("Error parsing %s. See error log file." % (file))
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
print "press enter to continue"
|
||||
sys.stdin.readline()
|
||||
|
@ -329,9 +345,21 @@ class Config:
|
|||
self.supported_games[game.game_name] = game
|
||||
|
||||
# s_dbs = doc.getElementsByTagName("supported_databases")
|
||||
if dbname and dbname in self.supported_databases:
|
||||
self.db_selected = dbname
|
||||
for db_node in doc.getElementsByTagName("database"):
|
||||
db = Database(node = db_node)
|
||||
self.supported_databases[db.db_name] = db
|
||||
try:
|
||||
db = Database(node = db_node)
|
||||
if db.db_name in self.supported_databases:
|
||||
raise FpdbError("Database names must be unique")
|
||||
# If there is only one Database node, or none are marked default, the first is selected
|
||||
if len(self.supported_databases) == 0:
|
||||
self.db_selected = db.db_name
|
||||
self.supported_databases[db.db_name] = db
|
||||
if db.db_selected:
|
||||
self.db_selected = db.db_name
|
||||
except:
|
||||
raise
|
||||
|
||||
# s_dbs = doc.getElementsByTagName("mucked_windows")
|
||||
for aw_node in doc.getElementsByTagName("aw"):
|
||||
|
@ -498,7 +526,7 @@ class Config:
|
|||
|
||||
def get_db_parameters(self):
|
||||
db = {}
|
||||
name = self.dbname
|
||||
name = self.db_selected
|
||||
try: db['db-databaseName'] = name
|
||||
except: pass
|
||||
|
||||
|
@ -542,6 +570,13 @@ class Config:
|
|||
if db_server != None: self.supported_databases[db_name].dp_server = db_server
|
||||
if db_type != None: self.supported_databases[db_name].dp_type = db_type
|
||||
return
|
||||
|
||||
def getDefaultSite(self):
|
||||
"Returns first enabled site or None"
|
||||
for site_name,site in self.supported_sites.iteritems():
|
||||
if site.enabled:
|
||||
return site_name
|
||||
return None
|
||||
|
||||
def get_tv_parameters(self):
|
||||
tv = {}
|
||||
|
@ -573,30 +608,32 @@ class Config:
|
|||
except: imp['fastStoreHudCache'] = True
|
||||
return imp
|
||||
|
||||
def get_default_paths(self, site = "PokerStars"):
|
||||
def get_default_paths(self, site = None):
|
||||
if site is None: site = self.getDefaultSite()
|
||||
paths = {}
|
||||
try:
|
||||
paths['hud-defaultPath'] = os.path.expanduser(self.supported_sites[site].HH_path)
|
||||
paths['bulkImport-defaultPath'] = os.path.expanduser(self.supported_sites[site].HH_path)
|
||||
except:
|
||||
paths['hud-defaultPath'] = "default"
|
||||
paths['bulkImport-defaultPath'] = "default"
|
||||
path = os.path.expanduser(self.supported_sites[site].HH_path)
|
||||
assert(os.path.isdir(path) or os.path.isfile(path)) # maybe it should try another site?
|
||||
paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = path
|
||||
except AssertionError:
|
||||
paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = "** ERROR DEFAULT PATH IN CONFIG DOES NOT EXIST **"
|
||||
return paths
|
||||
|
||||
def get_frames(self, site = "PokerStars"):
|
||||
if site not in self.supported_sites: return False
|
||||
return self.supported_sites[site].use_frames == True
|
||||
|
||||
def get_default_colors(self, site = "PokerStars"):
|
||||
colors = {}
|
||||
if self.supported_sites[site].hudopacity == "":
|
||||
if site not in self.supported_sites or self.supported_sites[site].hudopacity == "":
|
||||
colors['hudopacity'] = 0.90
|
||||
else:
|
||||
colors['hudopacity'] = float(self.supported_sites[site].hudopacity)
|
||||
if self.supported_sites[site].hudbgcolor == "":
|
||||
if site not in self.supported_sites or self.supported_sites[site].hudbgcolor == "":
|
||||
colors['hudbgcolor'] = "#FFFFFF"
|
||||
else:
|
||||
colors['hudbgcolor'] = self.supported_sites[site].hudbgcolor
|
||||
if self.supported_sites[site].hudfgcolor == "":
|
||||
if site not in self.supported_sites or self.supported_sites[site].hudfgcolor == "":
|
||||
colors['hudfgcolor'] = "#000000"
|
||||
else:
|
||||
colors['hudfgcolor'] = self.supported_sites[site].hudfgcolor
|
||||
|
@ -604,6 +641,8 @@ class Config:
|
|||
|
||||
def get_default_font(self, site = 'PokerStars'):
|
||||
(font, font_size) = ("Sans", "8")
|
||||
if site not in self.supported_sites:
|
||||
return ("Sans", "8")
|
||||
if self.supported_sites[site].font == "":
|
||||
font = "Sans"
|
||||
else:
|
||||
|
|
|
@ -24,13 +24,14 @@ Create and manage the database objects.
|
|||
# postmaster -D /var/lib/pgsql/data
|
||||
|
||||
# Standard Library modules
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime, date, time, timedelta
|
||||
from time import time, strftime, sleep
|
||||
from decimal import Decimal
|
||||
import string
|
||||
import re
|
||||
import logging
|
||||
import Queue
|
||||
|
||||
# pyGTK modules
|
||||
|
@ -41,6 +42,12 @@ import fpdb_simple
|
|||
import Configuration
|
||||
import SQL
|
||||
import Card
|
||||
import Tourney
|
||||
from Exceptions import *
|
||||
|
||||
import logging, logging.config
|
||||
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
|
||||
log = logging.getLogger('db')
|
||||
|
||||
class Database:
|
||||
|
||||
|
@ -92,6 +99,14 @@ class Database:
|
|||
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
|
||||
]
|
||||
, [ # indexes for sqlite (list index 4)
|
||||
{'tab':'Players', 'col':'name', 'drop':0}
|
||||
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
|
||||
, {'tab':'Hands', 'col':'gametypeId', 'drop':0}
|
||||
, {'tab':'HandsPlayers', 'col':'handId', 'drop':0}
|
||||
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':0}
|
||||
, {'tab':'HandsPlayers', 'col':'tourneyTypeId', 'drop':0}
|
||||
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
|
||||
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -160,8 +175,14 @@ class Database:
|
|||
# CREATE INDEX idx ON tab(col)
|
||||
# DROP INDEX idx
|
||||
|
||||
# SQLite notes:
|
||||
|
||||
# To add an index:
|
||||
# create index indexname on tablename (col);
|
||||
|
||||
|
||||
def __init__(self, c, db_name = None, game = None, sql = None): # db_name and game not used any more
|
||||
print "\ncreating Database instance, sql =", sql
|
||||
log.info("Creating Database instance, sql = %s" % sql)
|
||||
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
|
||||
self.fdb.do_connect(c)
|
||||
self.connection = self.fdb.db
|
||||
|
@ -178,12 +199,17 @@ class Database:
|
|||
#ISOLATION_LEVEL_READ_COMMITTED = 1
|
||||
#ISOLATION_LEVEL_SERIALIZABLE = 2
|
||||
|
||||
|
||||
# where possible avoid creating new SQL instance by using the global one passed in
|
||||
if sql == None:
|
||||
self.sql = SQL.Sql(type = self.type, db_server = db_params['db-server'])
|
||||
else:
|
||||
self.sql = sql
|
||||
|
||||
if self.backend == self.SQLITE and db_params['db-databaseName'] == ':memory:' and self.fdb.wrongDbVersion:
|
||||
log.info("sqlite/:memory: - creating")
|
||||
self.recreate_tables()
|
||||
|
||||
self.pcache = None # PlayerId cache
|
||||
self.cachemiss = 0 # Delete me later - using to count player cache misses
|
||||
self.cachehit = 0 # Delete me later - using to count player cache hits
|
||||
|
@ -243,7 +269,7 @@ class Database:
|
|||
elif self.backend==4:
|
||||
return "SQLite"
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("invalid backend")
|
||||
raise FpdbError("invalid backend")
|
||||
|
||||
def get_table_name(self, hand_id):
|
||||
c = self.connection.cursor()
|
||||
|
@ -438,7 +464,7 @@ class Database:
|
|||
if colnames[0].lower() == 'player_id':
|
||||
playerid = row[0]
|
||||
else:
|
||||
print "ERROR: query %s result does not have player_id as first column" % (query,)
|
||||
log.error("ERROR: query %s result does not have player_id as first column" % (query,))
|
||||
break
|
||||
|
||||
for name, val in zip(colnames, row):
|
||||
|
@ -479,7 +505,7 @@ class Database:
|
|||
if self.backend == self.MYSQL_INNODB:
|
||||
ret = self.connection.insert_id()
|
||||
if ret < 1 or ret > 999999999:
|
||||
print "getLastInsertId(): problem fetching insert_id? ret=", ret
|
||||
log.warning("getLastInsertId(): problem fetching insert_id? ret=%d" % ret)
|
||||
ret = -1
|
||||
elif self.backend == self.PGSQL:
|
||||
# some options:
|
||||
|
@ -491,14 +517,14 @@ class Database:
|
|||
ret = c.execute ("SELECT lastval()")
|
||||
row = c.fetchone()
|
||||
if not row:
|
||||
print "getLastInsertId(%s): problem fetching lastval? row=" % seq, row
|
||||
log.warning("getLastInsertId(%s): problem fetching lastval? row=%d" % (seq, row))
|
||||
ret = -1
|
||||
else:
|
||||
ret = row[0]
|
||||
elif self.backend == self.SQLITE:
|
||||
ret = cursor.lastrowid
|
||||
else:
|
||||
print "getLastInsertId(): unknown backend ", self.backend
|
||||
log.error("getLastInsertId(): unknown backend: %d" % self.backend)
|
||||
ret = -1
|
||||
except:
|
||||
ret = -1
|
||||
|
@ -583,7 +609,7 @@ class Database:
|
|||
hands_players_ids = self.store_hands_players_holdem_omaha_tourney(
|
||||
self.backend, category, hands_id, player_ids, start_cashes, positions
|
||||
, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids
|
||||
, hudImportData)
|
||||
, hudImportData, tourneyTypeId)
|
||||
|
||||
#print "tourney holdem, backend=%d" % backend
|
||||
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
|
||||
|
@ -611,7 +637,7 @@ class Database:
|
|||
|
||||
hands_players_ids = self.store_hands_players_stud_tourney(self.backend, hands_id
|
||||
, playerIds, startCashes, antes, cardValues, cardSuits
|
||||
, winnings, rakes, seatNos, tourneys_players_ids)
|
||||
, winnings, rakes, seatNos, tourneys_players_ids, tourneyTypeId)
|
||||
|
||||
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
|
||||
self.storeHudCache(self.backend, base, category, gametypeId, hand_start_time, playerIds, hudImportData)
|
||||
|
@ -822,16 +848,16 @@ class Database:
|
|||
self.create_tables()
|
||||
self.createAllIndexes()
|
||||
self.commit()
|
||||
print "Finished recreating tables"
|
||||
log.info("Finished recreating tables")
|
||||
#end def recreate_tables
|
||||
|
||||
def create_tables(self):
|
||||
#todo: should detect and fail gracefully if tables already exist.
|
||||
try:
|
||||
logging.debug(self.sql.query['createSettingsTable'])
|
||||
log.debug(self.sql.query['createSettingsTable'])
|
||||
c = self.get_cursor()
|
||||
c.execute(self.sql.query['createSettingsTable'])
|
||||
logging.debug(self.sql.query['createSitesTable'])
|
||||
log.debug(self.sql.query['createSitesTable'])
|
||||
c.execute(self.sql.query['createSitesTable'])
|
||||
c.execute(self.sql.query['createGametypesTable'])
|
||||
c.execute(self.sql.query['createPlayersTable'])
|
||||
|
@ -858,35 +884,51 @@ class Database:
|
|||
|
||||
def drop_tables(self):
|
||||
"""Drops the fpdb tables from the current db"""
|
||||
|
||||
try:
|
||||
c = self.get_cursor()
|
||||
if(self.get_backend_name() == 'MySQL InnoDB'):
|
||||
#Databases with FOREIGN KEY support need this switched of before you can drop tables
|
||||
self.drop_referential_integrity()
|
||||
|
||||
# Query the DB to see what tables exist
|
||||
c.execute(self.sql.query['list_tables'])
|
||||
for table in c:
|
||||
c.execute(self.sql.query['drop_table'] + table[0])
|
||||
elif(self.get_backend_name() == 'PostgreSQL'):
|
||||
self.commit()# I have no idea why this makes the query work--REB 07OCT2008
|
||||
c.execute(self.sql.query['list_tables'])
|
||||
tables = c.fetchall()
|
||||
for table in tables:
|
||||
c.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
|
||||
elif(self.get_backend_name() == 'SQLite'):
|
||||
c.execute(self.sql.query['list_tables'])
|
||||
for table in c.fetchall():
|
||||
logging.debug(self.sql.query['drop_table'] + table[0])
|
||||
c.execute(self.sql.query['drop_table'] + table[0])
|
||||
|
||||
self.commit()
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
self.rollback()
|
||||
raise
|
||||
print "*** Error unable to get cursor"
|
||||
else:
|
||||
backend = self.get_backend_name()
|
||||
if backend == 'MySQL InnoDB': # what happens if someone is using MyISAM?
|
||||
try:
|
||||
self.drop_referential_integrity() # needed to drop tables with foreign keys
|
||||
c.execute(self.sql.query['list_tables'])
|
||||
tables = c.fetchall()
|
||||
for table in tables:
|
||||
c.execute(self.sql.query['drop_table'] + table[0])
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
self.rollback()
|
||||
elif backend == 'PostgreSQL':
|
||||
try:
|
||||
self.commit()
|
||||
c.execute(self.sql.query['list_tables'])
|
||||
tables = c.fetchall()
|
||||
for table in tables:
|
||||
c.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
self.rollback()
|
||||
elif backend == 'SQLite':
|
||||
try:
|
||||
c.execute(self.sql.query['list_tables'])
|
||||
for table in c.fetchall():
|
||||
log.debug(self.sql.query['drop_table'] + table[0])
|
||||
c.execute(self.sql.query['drop_table'] + table[0])
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
self.rollback()
|
||||
try:
|
||||
self.commit()
|
||||
except:
|
||||
print "*** Error in committing table drop"
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
self.rollback()
|
||||
#end def drop_tables
|
||||
|
||||
def createAllIndexes(self):
|
||||
|
@ -911,14 +953,21 @@ class Database:
|
|||
self.get_cursor().execute(s)
|
||||
except:
|
||||
print " create idx failed: " + str(sys.exc_info())
|
||||
elif self.backend == self.SQLITE:
|
||||
log.debug("Creating sqlite index %s %s" % (idx['tab'], idx['col']))
|
||||
try:
|
||||
s = "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
|
||||
self.get_cursor().execute(s)
|
||||
except:
|
||||
log.debug("Create idx failed: " + str(sys.exc_info()))
|
||||
else:
|
||||
print "Only MySQL and Postgres supported so far"
|
||||
print "Only MySQL, Postgres and SQLite supported so far"
|
||||
return -1
|
||||
if self.backend == self.PGSQL:
|
||||
self.connection.set_isolation_level(1) # go back to normal isolation level
|
||||
except:
|
||||
print "Error creating indexes: " + str(sys.exc_value)
|
||||
raise fpdb_simple.FpdbError( "Error creating indexes " + str(sys.exc_value) )
|
||||
raise FpdbError( "Error creating indexes " + str(sys.exc_value) )
|
||||
#end def createAllIndexes
|
||||
|
||||
def dropAllIndexes(self):
|
||||
|
@ -951,7 +1000,7 @@ class Database:
|
|||
#end def dropAllIndexes
|
||||
|
||||
def fillDefaultData(self):
|
||||
c = self.get_cursor()
|
||||
c = self.get_cursor()
|
||||
c.execute("INSERT INTO Settings (version) VALUES (118);")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')")
|
||||
|
@ -963,12 +1012,10 @@ class Database:
|
|||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')")
|
||||
if self.backend == self.SQLITE:
|
||||
c.execute("INSERT INTO TourneyTypes VALUES (NULL, 1, 0, 0, 0, 0);")
|
||||
c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);")
|
||||
else:
|
||||
c.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
|
||||
#c.execute("""INSERT INTO TourneyTypes
|
||||
# (siteId,buyin,fee,knockout,rebuyOrAddon) VALUES
|
||||
# (1,0,0,0,?)""",(False,) )
|
||||
c.execute("insert into tourneytypes values (0,1,0,0,0,0,0,null,0,0,0);")
|
||||
|
||||
#end def fillDefaultData
|
||||
|
||||
def rebuild_hudcache(self):
|
||||
|
@ -1020,6 +1067,22 @@ class Database:
|
|||
print "Error during fdb.lock_for_insert:", str(sys.exc_value)
|
||||
#end def lock_for_insert
|
||||
|
||||
def getGameTypeId(self, siteid, game):
|
||||
c = self.get_cursor()
|
||||
#FIXME: Fixed for NL at the moment
|
||||
c.execute(self.sql.query['getGametypeNL'], (siteid, game['type'], game['category'], game['limitType'],
|
||||
int(Decimal(game['sb'])*100), int(Decimal(game['bb'])*100)))
|
||||
tmp = c.fetchone()
|
||||
if (tmp == None):
|
||||
hilo = "h"
|
||||
if game['category'] in ['studhilo', 'omahahilo']:
|
||||
hilo = "s"
|
||||
elif game['category'] in ['razz','27_3draw','badugi']:
|
||||
hilo = "l"
|
||||
tmp = self.insertGameTypes( (siteid, game['type'], game['base'], game['category'], game['limitType'], hilo,
|
||||
int(Decimal(game['sb'])*100), int(Decimal(game['bb'])*100), 0, 0) )
|
||||
return tmp[0]
|
||||
|
||||
def getSqlPlayerIDs(self, pnames, siteid):
|
||||
result = {}
|
||||
if(self.pcache == None):
|
||||
|
@ -1040,10 +1103,15 @@ class Database:
|
|||
def insertPlayer(self, name, site_id):
|
||||
result = None
|
||||
c = self.get_cursor()
|
||||
c.execute ("SELECT id FROM Players WHERE name=%s".replace('%s',self.sql.query['placeholder'])
|
||||
,(name,))
|
||||
q = "SELECT name, id FROM Players WHERE siteid=%s and name=%s"
|
||||
q = q.replace('%s', self.sql.query['placeholder'])
|
||||
|
||||
#print "DEBUG: name: %s site: %s" %(name, site_id)
|
||||
|
||||
c.execute (q, (site_id, name))
|
||||
|
||||
tmp = c.fetchone()
|
||||
if (len(tmp)==0): #new player
|
||||
if (tmp == None): #new player
|
||||
c.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)".replace('%s',self.sql.query['placeholder'])
|
||||
,(name, site_id))
|
||||
#Get last id might be faster here.
|
||||
|
@ -1085,7 +1153,7 @@ class Database:
|
|||
, h.allIns, h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats
|
||||
, h.tableName, h.seatNos)
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("unrecognised category")
|
||||
raise FpdbError("unrecognised category")
|
||||
else:
|
||||
if h.base == "hold":
|
||||
result = self.ring_holdem_omaha(
|
||||
|
@ -1103,7 +1171,7 @@ class Database:
|
|||
, h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats, h.tableName
|
||||
, h.seatNos)
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("unrecognised category")
|
||||
raise FpdbError("unrecognised category")
|
||||
except:
|
||||
print "Error storing hand: " + str(sys.exc_value)
|
||||
self.rollback()
|
||||
|
@ -1116,71 +1184,76 @@ class Database:
|
|||
|
||||
def storeHand(self, p):
|
||||
#stores into table hands:
|
||||
self.cursor.execute ("""INSERT INTO Hands (
|
||||
q = """INSERT INTO Hands (
|
||||
tablename,
|
||||
sitehandno,
|
||||
gametypeid,
|
||||
sitehandno,
|
||||
handstart,
|
||||
importtime,
|
||||
seats,
|
||||
seats,
|
||||
maxseats,
|
||||
boardcard1,
|
||||
boardcard2,
|
||||
boardcard3,
|
||||
boardcard4,
|
||||
boardcard5,
|
||||
-- texture,
|
||||
playersVpi,
|
||||
playersAtStreet1,
|
||||
playersAtStreet2,
|
||||
playersAtStreet3,
|
||||
playersAtStreet4,
|
||||
playersAtShowdown,
|
||||
street0Raises,
|
||||
street1Raises,
|
||||
street2Raises,
|
||||
street3Raises,
|
||||
street4Raises,
|
||||
-- street1Pot,
|
||||
-- street2Pot,
|
||||
-- street3Pot,
|
||||
-- street4Pot,
|
||||
-- showdownPot
|
||||
street1Pot,
|
||||
street2Pot,
|
||||
street3Pot,
|
||||
street4Pot,
|
||||
showdownPot
|
||||
)
|
||||
VALUES
|
||||
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s)""",
|
||||
(
|
||||
p['tablename'],
|
||||
p['sitehandno'],
|
||||
p['gametypeid'],
|
||||
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s)"""
|
||||
#--- texture,
|
||||
#-- playersVpi,
|
||||
#-- playersAtStreet1,
|
||||
#-- playersAtStreet2,
|
||||
#-- playersAtStreet3,
|
||||
#-- playersAtStreet4,
|
||||
#-- playersAtShowdown,
|
||||
#-- street0Raises,
|
||||
#-- street1Raises,
|
||||
#-- street2Raises,
|
||||
#-- street3Raises,
|
||||
#-- street4Raises,
|
||||
#-- seats,
|
||||
|
||||
q = q.replace('%s', self.sql.query['placeholder'])
|
||||
print "DEBUG: p: %s" %p
|
||||
print "DEBUG: gtid: %s" % p['gameTypeId']
|
||||
self.cursor.execute(q, (
|
||||
p['tableName'],
|
||||
p['gameTypeId'],
|
||||
p['siteHandNo'],
|
||||
p['handStart'],
|
||||
datetime.datetime.today(),
|
||||
len(p['names']),
|
||||
datetime.today(), #importtime
|
||||
# len(p['names']), #seats
|
||||
p['maxSeats'],
|
||||
p['seats'],
|
||||
p['boardcard1'],
|
||||
p['boardcard2'],
|
||||
p['boardcard3'],
|
||||
p['boardcard4'],
|
||||
p['boardcard5'],
|
||||
hudCache['playersVpi'],
|
||||
hudCache['playersAtStreet1'],
|
||||
hudCache['playersAtStreet2'],
|
||||
hudCache['playersAtStreet3'],
|
||||
hudCache['playersAtStreet4'],
|
||||
hudCache['playersAtShowdown'],
|
||||
hudCache['street0Raises'],
|
||||
hudCache['street1Raises'],
|
||||
hudCache['street2Raises'],
|
||||
hudCache['street3Raises'],
|
||||
hudCache['street4Raises'],
|
||||
hudCache['street1Pot'],
|
||||
hudCache['street2Pot'],
|
||||
hudCache['street3Pot'],
|
||||
hudCache['street4Pot'],
|
||||
hudCache['showdownPot']
|
||||
)
|
||||
)
|
||||
# hudCache['playersVpi'],
|
||||
# hudCache['playersAtStreet1'],
|
||||
# hudCache['playersAtStreet2'],
|
||||
# hudCache['playersAtStreet3'],
|
||||
# hudCache['playersAtStreet4'],
|
||||
# hudCache['playersAtShowdown'],
|
||||
# hudCache['street0Raises'],
|
||||
# hudCache['street1Raises'],
|
||||
# hudCache['street2Raises'],
|
||||
# hudCache['street3Raises'],
|
||||
# hudCache['street4Raises'],
|
||||
p['street1Pot'],
|
||||
p['street2Pot'],
|
||||
p['street3Pot'],
|
||||
p['street4Pot'],
|
||||
p['showdownPot']
|
||||
))
|
||||
#return getLastInsertId(backend, conn, cursor)
|
||||
# def storeHand
|
||||
|
||||
|
@ -1218,7 +1291,7 @@ class Database:
|
|||
ret = self.get_last_insert_id(c)
|
||||
except:
|
||||
ret = -1
|
||||
raise fpdb_simple.FpdbError( "storeHands error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "storeHands error: " + str(sys.exc_value) )
|
||||
|
||||
return ret
|
||||
#end def storeHands
|
||||
|
@ -1253,10 +1326,10 @@ class Database:
|
|||
card3 = Card.cardFromValueSuit(card_values[i][2], card_suits[i][2])
|
||||
card4 = Card.cardFromValueSuit(card_values[i][3], card_suits[i][3])
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("invalid category")
|
||||
raise FpdbError("invalid category")
|
||||
|
||||
inserts.append( (
|
||||
hands_id, player_ids[i], start_cashes[i], positions[i], 1, # tourneytypeid
|
||||
hands_id, player_ids[i], start_cashes[i], positions[i],
|
||||
card1, card2, card3, card4, startCards,
|
||||
winnings[i], rakes[i], seatNos[i], hudCache['totalProfit'][i],
|
||||
hudCache['street0VPI'][i], hudCache['street0Aggr'][i],
|
||||
|
@ -1287,7 +1360,7 @@ class Database:
|
|||
c = self.get_cursor()
|
||||
c.executemany ("""
|
||||
INSERT INTO HandsPlayers
|
||||
(handId, playerId, startCash, position, tourneyTypeId,
|
||||
(handId, playerId, startCash, position,
|
||||
card1, card2, card3, card4, startCards, winnings, rake, seatNo, totalProfit,
|
||||
street0VPI, street0Aggr, street0_3BChance, street0_3BDone,
|
||||
street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown,
|
||||
|
@ -1308,11 +1381,11 @@ class Database:
|
|||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder'])
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder'])
|
||||
,inserts )
|
||||
result.append( self.get_last_insert_id(c) ) # wrong? not used currently
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "store_hands_players_holdem_omaha error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "store_hands_players_holdem_omaha error: " + str(sys.exc_value) )
|
||||
|
||||
return result
|
||||
#end def store_hands_players_holdem_omaha
|
||||
|
@ -1350,7 +1423,7 @@ class Database:
|
|||
#result.append(cursor.fetchall()[0][0])
|
||||
result.append( self.get_last_insert_id(c) )
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "store_hands_players_stud error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "store_hands_players_stud error: " + str(sys.exc_value) )
|
||||
|
||||
return result
|
||||
#end def store_hands_players_stud
|
||||
|
@ -1358,7 +1431,7 @@ class Database:
|
|||
def store_hands_players_holdem_omaha_tourney(self, backend, category, hands_id, player_ids
|
||||
,start_cashes, positions, card_values, card_suits
|
||||
,winnings, rakes, seatNos, tourneys_players_ids
|
||||
,hudCache):
|
||||
,hudCache, tourneyTypeId):
|
||||
#stores hands_players for tourney holdem/omaha hands
|
||||
|
||||
try:
|
||||
|
@ -1380,7 +1453,7 @@ class Database:
|
|||
else:
|
||||
raise FpdbError ("invalid card_values length:"+str(len(card_values[0])))
|
||||
|
||||
inserts.append( (hands_id, player_ids[i], start_cashes[i], positions[i], 1, # tourneytypeid
|
||||
inserts.append( (hands_id, player_ids[i], start_cashes[i], positions[i], tourneyTypeId,
|
||||
card1, card2, card3, card4, startCards,
|
||||
winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i], hudCache['totalProfit'][i],
|
||||
hudCache['street0VPI'][i], hudCache['street0Aggr'][i],
|
||||
|
@ -1443,13 +1516,13 @@ class Database:
|
|||
#cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i]))
|
||||
#result.append(cursor.fetchall()[0][0])
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "store_hands_players_holdem_omaha_tourney error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "store_hands_players_holdem_omaha_tourney error: " + str(sys.exc_value) )
|
||||
|
||||
return result
|
||||
#end def store_hands_players_holdem_omaha_tourney
|
||||
|
||||
def store_hands_players_stud_tourney(self, backend, hands_id, player_ids, start_cashes,
|
||||
antes, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids):
|
||||
antes, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids, tourneyTypeId):
|
||||
#stores hands_players for tourney stud/razz hands
|
||||
|
||||
try:
|
||||
|
@ -1461,19 +1534,19 @@ class Database:
|
|||
card1Value, card1Suit, card2Value, card2Suit,
|
||||
card3Value, card3Suit, card4Value, card4Suit,
|
||||
card5Value, card5Suit, card6Value, card6Suit,
|
||||
card7Value, card7Suit, winnings, rake, tourneysPlayersId, seatNo)
|
||||
card7Value, card7Suit, winnings, rake, tourneysPlayersId, seatNo, tourneyTypeId)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']),
|
||||
%s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']),
|
||||
(hands_id, player_ids[i], start_cashes[i], antes[i],
|
||||
card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1],
|
||||
card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3],
|
||||
card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5],
|
||||
card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i]))
|
||||
card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i], tourneyTypeId))
|
||||
#cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i]))
|
||||
#result.append(cursor.fetchall()[0][0])
|
||||
result.append( self.get_last_insert_id(c) )
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "store_hands_players_stud_tourney error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "store_hands_players_stud_tourney error: " + str(sys.exc_value) )
|
||||
|
||||
return result
|
||||
#end def store_hands_players_stud_tourney
|
||||
|
@ -1670,7 +1743,7 @@ class Database:
|
|||
# print "todo: implement storeHudCache for stud base"
|
||||
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "storeHudCache error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "storeHudCache error: " + str(sys.exc_value) )
|
||||
|
||||
#end def storeHudCache
|
||||
|
||||
|
@ -1693,7 +1766,7 @@ class Database:
|
|||
tmp=cursor.fetchone()
|
||||
#print "created new tourneys.id:",tmp
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "store_tourneys error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "store_tourneys error: " + str(sys.exc_value) )
|
||||
|
||||
return tmp[0]
|
||||
#end def store_tourneys
|
||||
|
@ -1726,7 +1799,7 @@ class Database:
|
|||
#print "created new tourneys_players.id:",tmp
|
||||
result.append(tmp[0])
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "store_tourneys_players error: " + str(sys.exc_value) )
|
||||
raise FpdbError( "store_tourneys_players error: " + str(sys.exc_value) )
|
||||
|
||||
return result
|
||||
#end def store_tourneys_players
|
||||
|
@ -1807,6 +1880,236 @@ class Database:
|
|||
print "***Error sending finish: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
# end def send_finish_msg():
|
||||
|
||||
def tRecogniseTourneyType(self, tourney):
|
||||
logging.debug("Database.tRecogniseTourneyType")
|
||||
typeId = 1
|
||||
# Check if Tourney exists, and if so retrieve TTypeId : in that case, check values of the ttype
|
||||
cursor = self.get_cursor()
|
||||
cursor.execute (self.sql.query['getTourneyTypeIdByTourneyNo'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.tourNo, tourney.siteId)
|
||||
)
|
||||
result=cursor.fetchone()
|
||||
|
||||
expectedValues = { 1 : "buyin", 2 : "fee", 4 : "isKO", 5 : "isRebuy", 6 : "speed",
|
||||
7 : "isHU", 8 : "isShootout", 9 : "isMatrix" }
|
||||
typeIdMatch = True
|
||||
|
||||
try:
|
||||
len(result)
|
||||
typeId = result[0]
|
||||
logging.debug("Tourney found in db with Tourney_Type_ID = %d" % typeId)
|
||||
for ev in expectedValues :
|
||||
if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ):
|
||||
logging.debug("TypeId mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) )
|
||||
typeIdMatch = False
|
||||
#break
|
||||
except:
|
||||
# Tourney not found : a TourneyTypeId has to be found or created for that specific tourney
|
||||
typeIdMatch = False
|
||||
|
||||
if typeIdMatch == False :
|
||||
# Check for an existing TTypeId that matches tourney info (buyin/fee, knockout, rebuy, speed, matrix, shootout)
|
||||
# if not found create it
|
||||
logging.debug("Searching for a TourneyTypeId matching TourneyType data")
|
||||
cursor.execute (self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO,
|
||||
tourney.isRebuy, tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix)
|
||||
)
|
||||
result=cursor.fetchone()
|
||||
|
||||
try:
|
||||
len(result)
|
||||
typeId = result[0]
|
||||
logging.debug("Existing Tourney Type Id found : %d" % typeId)
|
||||
except TypeError: #this means we need to create a new entry
|
||||
logging.debug("Tourney Type Id not found : create one")
|
||||
cursor.execute (self.sql.query['insertTourneyTypes'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO, tourney.isRebuy,
|
||||
tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix)
|
||||
)
|
||||
typeId = self.get_last_insert_id(cursor)
|
||||
|
||||
return typeId
|
||||
#end def tRecogniseTourneyType
|
||||
|
||||
|
||||
def tRecognizeTourney(self, tourney, dbTourneyTypeId):
|
||||
logging.debug("Database.tRecognizeTourney")
|
||||
tourneyID = 1
|
||||
# Check if tourney exists in db (based on tourney.siteId and tourney.tourNo)
|
||||
# If so retrieve all data to check for consistency
|
||||
cursor = self.get_cursor()
|
||||
cursor.execute (self.sql.query['getTourney'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.tourNo, tourney.siteId)
|
||||
)
|
||||
result=cursor.fetchone()
|
||||
|
||||
expectedValuesDecimal = { 2 : "entries", 3 : "prizepool", 6 : "buyInChips", 9 : "rebuyChips",
|
||||
10 : "addOnChips", 11 : "rebuyAmount", 12 : "addOnAmount", 13 : "totalRebuys",
|
||||
14 : "totalAddOns", 15 : "koBounty" }
|
||||
expectedValues = { 7 : "tourneyName", 16 : "tourneyComment" }
|
||||
|
||||
tourneyDataMatch = True
|
||||
tCommentTs = None
|
||||
starttime = None
|
||||
endtime = None
|
||||
|
||||
try:
|
||||
len(result)
|
||||
tourneyID = result[0]
|
||||
logging.debug("Tourney found in db with TourneyID = %d" % tourneyID)
|
||||
if result[1] <> dbTourneyTypeId:
|
||||
tourneyDataMatch = False
|
||||
logging.debug("Tourney has wrong type ID (expected : %s - found : %s)" % (dbTourneyTypeId, result[1]))
|
||||
if (tourney.starttime is None and result[4] is not None) or ( tourney.starttime is not None and fpdb_simple.parseHandStartTime("- %s" % tourney.starttime) <> result[4]) :
|
||||
tourneyDataMatch = False
|
||||
logging.debug("Tourney data mismatch : wrong starttime : Tourney=%s / db=%s" % (tourney.starttime, result[4]))
|
||||
if (tourney.endtime is None and result[5] is not None) or ( tourney.endtime is not None and fpdb_simple.parseHandStartTime("- %s" % tourney.endtime) <> result[5]) :
|
||||
tourneyDataMatch = False
|
||||
logging.debug("Tourney data mismatch : wrong endtime : Tourney=%s / db=%s" % (tourney.endtime, result[5]))
|
||||
|
||||
for ev in expectedValues :
|
||||
if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ):
|
||||
logging.debug("Tourney data mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) )
|
||||
tourneyDataMatch = False
|
||||
#break
|
||||
for evD in expectedValuesDecimal :
|
||||
if ( Decimal(getattr( tourney, expectedValuesDecimal.get(evD)) ) <> result[evD] ):
|
||||
logging.debug("Tourney data mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValuesDecimal.get(evD), getattr( tourney, expectedValuesDecimal.get(evD)), result[evD]) )
|
||||
tourneyDataMatch = False
|
||||
#break
|
||||
|
||||
# TO DO : Deal with matrix summary mutliple parsings
|
||||
|
||||
except:
|
||||
# Tourney not found : create
|
||||
logging.debug("Tourney is not found : create")
|
||||
if tourney.tourneyComment is not None :
|
||||
tCommentTs = datetime.today()
|
||||
if tourney.starttime is not None :
|
||||
starttime = fpdb_simple.parseHandStartTime("- %s" % tourney.starttime)
|
||||
if tourney.endtime is not None :
|
||||
endtime = fpdb_simple.parseHandStartTime("- %s" % tourney.endtime)
|
||||
# TODO : deal with matrix Id processed
|
||||
cursor.execute (self.sql.query['insertTourney'].replace('%s', self.sql.query['placeholder']),
|
||||
(dbTourneyTypeId, tourney.tourNo, tourney.entries, tourney.prizepool, starttime,
|
||||
endtime, tourney.buyInChips, tourney.tourneyName, 0, tourney.rebuyChips, tourney.addOnChips,
|
||||
tourney.rebuyAmount, tourney.addOnAmount, tourney.totalRebuys, tourney.totalAddOns, tourney.koBounty,
|
||||
tourney.tourneyComment, tCommentTs)
|
||||
)
|
||||
tourneyID = self.get_last_insert_id(cursor)
|
||||
|
||||
|
||||
# Deal with inconsistent tourney in db
|
||||
if tourneyDataMatch == False :
|
||||
# Update Tourney
|
||||
if result[16] <> tourney.tourneyComment :
|
||||
tCommentTs = datetime.today()
|
||||
if tourney.starttime is not None :
|
||||
starttime = fpdb_simple.parseHandStartTime("- %s" % tourney.starttime)
|
||||
if tourney.endtime is not None :
|
||||
endtime = fpdb_simple.parseHandStartTime("- %s" % tourney.endtime)
|
||||
|
||||
cursor.execute (self.sql.query['updateTourney'].replace('%s', self.sql.query['placeholder']),
|
||||
(dbTourneyTypeId, tourney.entries, tourney.prizepool, starttime,
|
||||
endtime, tourney.buyInChips, tourney.tourneyName, 0, tourney.rebuyChips, tourney.addOnChips,
|
||||
tourney.rebuyAmount, tourney.addOnAmount, tourney.totalRebuys, tourney.totalAddOns, tourney.koBounty,
|
||||
tourney.tourneyComment, tCommentTs, tourneyID)
|
||||
)
|
||||
|
||||
return tourneyID
|
||||
#end def tRecognizeTourney
|
||||
|
||||
def tStoreTourneyPlayers(self, tourney, dbTourneyId):
|
||||
logging.debug("Database.tStoreTourneyPlayers")
|
||||
# First, get playerids for the players and specifically the one for hero :
|
||||
playersIds = fpdb_simple.recognisePlayerIDs(self, tourney.players, tourney.siteId)
|
||||
# hero may be None for matrix tourneys summaries
|
||||
# hero = [ tourney.hero ]
|
||||
# heroId = fpdb_simple.recognisePlayerIDs(self, hero , tourney.siteId)
|
||||
# logging.debug("hero Id = %s - playersId = %s" % (heroId , playersIds))
|
||||
|
||||
tourneyPlayersIds=[]
|
||||
try:
|
||||
cursor = self.get_cursor()
|
||||
|
||||
for i in xrange(len(playersIds)):
|
||||
cursor.execute(self.sql.query['getTourneysPlayers'].replace('%s', self.sql.query['placeholder'])
|
||||
,(dbTourneyId, playersIds[i]))
|
||||
result=cursor.fetchone()
|
||||
#print "tried SELECTing tourneys_players.id:",tmp
|
||||
|
||||
try:
|
||||
len(result)
|
||||
# checking data
|
||||
logging.debug("TourneysPlayers found : checking data")
|
||||
expectedValuesDecimal = { 1 : "payinAmounts", 2 : "finishPositions", 3 : "winnings", 4 : "countRebuys",
|
||||
5 : "countAddOns", 6 : "countKO" }
|
||||
|
||||
tourneyPlayersIds.append(result[0]);
|
||||
|
||||
tourneysPlayersDataMatch = True
|
||||
for evD in expectedValuesDecimal :
|
||||
if ( Decimal(getattr( tourney, expectedValuesDecimal.get(evD))[tourney.players[i]] ) <> result[evD] ):
|
||||
logging.debug("TourneysPlayers data mismatch for TourneysPlayer id=%d, name=%s : wrong %s : Tourney=%s / db=%s" % (result[0], tourney.players[i], expectedValuesDecimal.get(evD), getattr( tourney, expectedValuesDecimal.get(evD))[tourney.players[i]], result[evD]) )
|
||||
tourneysPlayersDataMatch = False
|
||||
#break
|
||||
|
||||
if tourneysPlayersDataMatch == False:
|
||||
logging.debug("TourneysPlayers data update needed")
|
||||
cursor.execute (self.sql.query['updateTourneysPlayers'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.payinAmounts[tourney.players[i]], tourney.finishPositions[tourney.players[i]],
|
||||
tourney.winnings[tourney.players[i]] , tourney.countRebuys[tourney.players[i]],
|
||||
tourney.countAddOns[tourney.players[i]] , tourney.countKO[tourney.players[i]],
|
||||
result[7], result[8], result[0])
|
||||
)
|
||||
|
||||
except TypeError:
|
||||
logging.debug("TourneysPlayers not found : need insert")
|
||||
cursor.execute (self.sql.query['insertTourneysPlayers'].replace('%s', self.sql.query['placeholder']),
|
||||
(dbTourneyId, playersIds[i],
|
||||
tourney.payinAmounts[tourney.players[i]], tourney.finishPositions[tourney.players[i]],
|
||||
tourney.winnings[tourney.players[i]] , tourney.countRebuys[tourney.players[i]],
|
||||
tourney.countAddOns[tourney.players[i]] , tourney.countKO[tourney.players[i]],
|
||||
None, None)
|
||||
)
|
||||
tourneyPlayersIds.append(self.get_last_insert_id(cursor))
|
||||
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "tStoreTourneyPlayers error: " + str(sys.exc_value) )
|
||||
|
||||
return tourneyPlayersIds
|
||||
#end def tStoreTourneyPlayers
|
||||
|
||||
def tUpdateTourneysHandsPlayers(self, tourney, dbTourneysPlayersIds, dbTourneyTypeId):
|
||||
logging.debug("Database.tCheckTourneysHandsPlayers")
|
||||
try:
|
||||
# Massive update seems to take quite some time ...
|
||||
# query = self.sql.query['updateHandsPlayersForTTypeId2'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner'].join([self.sql.query['placeholder'] for id in dbTourneysPlayersIds]) )
|
||||
# cursor = self.get_cursor()
|
||||
# cursor.execute (query, dbTourneysPlayersIds)
|
||||
|
||||
query = self.sql.query['selectHandsPlayersWithWrongTTypeId'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner'].join([self.sql.query['placeholder'] for id in dbTourneysPlayersIds]) )
|
||||
#print "query : %s" % query
|
||||
cursor = self.get_cursor()
|
||||
cursor.execute (query, dbTourneysPlayersIds)
|
||||
result=cursor.fetchall()
|
||||
|
||||
if (len(result) > 0):
|
||||
logging.debug("%d lines need update : %s" % (len(result), result) )
|
||||
listIds = []
|
||||
for i in result:
|
||||
listIds.append(i[0])
|
||||
|
||||
query2 = self.sql.query['updateHandsPlayersForTTypeId'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner_id'].join([self.sql.query['placeholder'] for id in listIds]) )
|
||||
cursor.execute (query2, listIds)
|
||||
else:
|
||||
logging.debug("No need to update, HandsPlayers are correct")
|
||||
|
||||
except:
|
||||
raise fpdb_simple.FpdbError( "tStoreTourneyPlayers error: " + str(sys.exc_value) )
|
||||
#end def tUpdateTourneysHandsPlayers
|
||||
|
||||
|
||||
# Class used to hold all the data needed to write a hand to the db
|
||||
# mainParser() in fpdb_parse_logic.py creates one of these and then passes it to
|
||||
|
|
|
@ -26,6 +26,11 @@ from HandHistoryConverter import *
|
|||
|
||||
class Everleaf(HandHistoryConverter):
|
||||
|
||||
sitename = 'Everleaf'
|
||||
filetype = "text"
|
||||
codepage = "cp1252"
|
||||
siteId = 3 # Needs to match id entry in Sites database
|
||||
|
||||
# Static regexes
|
||||
re_SplitHands = re.compile(r"\n\n\n+")
|
||||
re_TailSplitHands = re.compile(r"(\n\n\n+)")
|
||||
|
@ -37,24 +42,6 @@ class Everleaf(HandHistoryConverter):
|
|||
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
|
||||
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, debugging=False, index=0):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
out_path (default '-' = sys.stdout)
|
||||
follow : whether to tail -f the input
|
||||
autostart: whether to run the thread (or you can call start() yourself)
|
||||
debugging: if False, pass on partially supported game types. If true, have a go and error..."""
|
||||
#print "DEBUG: XXXXXXXXXXXXXXX"
|
||||
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Everleaf", follow=follow, index=index)
|
||||
logging.info("Initialising Everleaf converter class")
|
||||
self.filetype = "text"
|
||||
self.codepage = "cp1252"
|
||||
self.siteId = 3 # Needs to match id entry in Sites database
|
||||
self.debugging = debugging
|
||||
if autostart:
|
||||
self.start()
|
||||
# otherwise you need to call start yourself.
|
||||
|
||||
def compilePlayerRegexs(self, hand):
|
||||
players = set([player[1] for player in hand.players])
|
||||
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
|
||||
|
|
|
@ -1 +1,21 @@
|
|||
class FpdbParseError(Exception): pass
|
||||
class FpdbError(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
class FpdbParseError(FpdbError):
|
||||
def __init__(self,value='',hid=''):
|
||||
self.value = value
|
||||
self.hid = hid
|
||||
def __str__(self):
|
||||
if hid:
|
||||
return repr("HID:"+hid+", "+self.value)
|
||||
else:
|
||||
return repr(self.value)
|
||||
|
||||
class FpdbDatabaseError(FpdbError):
|
||||
pass
|
||||
|
||||
class DuplicateError(FpdbError):
|
||||
pass
|
||||
|
|
|
@ -27,8 +27,14 @@ from HandHistoryConverter import *
|
|||
|
||||
class Fulltilt(HandHistoryConverter):
|
||||
|
||||
sitename = "Fulltilt"
|
||||
filetype = "text"
|
||||
codepage = ["utf-16", "cp1252"]
|
||||
siteId = 1 # Needs to match id entry in Sites database
|
||||
|
||||
# Static regexes
|
||||
re_GameInfo = re.compile('''(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
|
||||
re_GameInfo = re.compile('''.*\#(?P<HID>[0-9]+):\s
|
||||
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
|
||||
.+
|
||||
-\s(?P<CURRENCY>\$|)?
|
||||
(?P<SB>[.0-9]+)/
|
||||
|
@ -39,39 +45,79 @@ class Fulltilt(HandHistoryConverter):
|
|||
''', re.VERBOSE)
|
||||
re_SplitHands = re.compile(r"\n\n+")
|
||||
re_TailSplitHands = re.compile(r"(\n\n+)")
|
||||
re_HandInfo = re.compile('''.*\#(?P<HID>[0-9]+):\s
|
||||
re_HandInfo = re.compile(r'''.*\#(?P<HID>[0-9]+):\s
|
||||
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
|
||||
Table\s
|
||||
(Table|Match)\s
|
||||
(?P<PLAY>Play\sChip\s|PC)?
|
||||
(?P<TABLE>[-\s\da-zA-Z]+)\s
|
||||
(\((?P<TABLEATTRIBUTES>.+)\)\s)?-\s
|
||||
\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\s(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
|
||||
(?P<GAMETYPE>[a-zA-Z\/\'\s]+)\s-\s
|
||||
(?P<DATETIME>.*)
|
||||
''', re.VERBOSE)
|
||||
(?P<DATETIME>\d+:\d+:\d+\s\w+\s-\s\d+/\d+/\d+)\s?
|
||||
(?P<PARTIAL>\(partial\))?\n
|
||||
(?:.*?\n(?P<CANCELLED>Hand\s\#(?P=HID)\shas\sbeen\scanceled))?
|
||||
''', re.VERBOSE|re.DOTALL)
|
||||
re_TourneyExtraInfo = re.compile('''(((?P<TOURNEY_NAME>[^$]+)?
|
||||
(?P<CURRENCY>\$)?(?P<BUYIN>[.0-9]+)?\s*\+\s*\$?(?P<FEE>[.0-9]+)?
|
||||
(\s(?P<SPECIAL>(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness)))?
|
||||
(\s(?P<SHOOTOUT>Shootout))?
|
||||
(\s(?P<SNG>Sit\s&\sGo))?
|
||||
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
|
||||
''', re.VERBOSE)
|
||||
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
|
||||
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
|
||||
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
|
||||
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)', re.MULTILINE)
|
||||
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
|
||||
|
||||
#static regex for tourney purpose
|
||||
re_TourneyInfo = re.compile('''Tournament\sSummary\s
|
||||
(?P<TOURNAMENT_NAME>[^$(]+)?\s*
|
||||
((?P<CURRENCY>\$|)?(?P<BUYIN>[.0-9]+)\s*\+\s*\$?(?P<FEE>[.0-9]+)\s)?
|
||||
((?P<SPECIAL>(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness))\s)?
|
||||
((?P<SHOOTOUT>Shootout)\s)?
|
||||
((?P<SNG>Sit\s&\sGo)\s)?
|
||||
(\((?P<TURBO1>Turbo)\)\s)?
|
||||
\((?P<TOURNO>\d+)\)\s
|
||||
((?P<MATCHNO>Match\s\d)\s)?
|
||||
(?P<GAME>(Hold\'em|Omaha\sHi|Omaha\sH/L|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))\s
|
||||
(\((?P<TURBO2>Turbo)\)\s)?
|
||||
(?P<LIMIT>(No\sLimit|Pot\sLimit|Limit))?
|
||||
''', re.VERBOSE)
|
||||
re_TourneyBuyInFee = re.compile("Buy-In: (?P<BUYIN_CURRENCY>\$|)?(?P<BUYIN>[.0-9]+) \+ \$?(?P<FEE>[.0-9]+)")
|
||||
re_TourneyBuyInChips = re.compile("Buy-In Chips: (?P<BUYINCHIPS>\d+)")
|
||||
re_TourneyEntries = re.compile("(?P<ENTRIES>\d+) Entries")
|
||||
re_TourneyPrizePool = re.compile("Total Prize Pool: (?P<PRIZEPOOL_CURRENCY>\$|)?(?P<PRIZEPOOL>[.,0-9]+)")
|
||||
re_TourneyRebuyAmount = re.compile("Rebuy: (?P<REBUY_CURRENCY>\$|)?(?P<REBUY_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyAddOnAmount = re.compile("Add-On: (?P<ADDON_CURRENCY>\$|)?(?P<ADDON_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyRebuyCount = re.compile("performed (?P<REBUY_COUNT>\d+) Rebuy")
|
||||
re_TourneyAddOnCount = re.compile("performed (?P<ADDON_COUNT>\d+) Add-On")
|
||||
re_TourneyRebuysTotal = re.compile("Total Rebuys: (?P<REBUY_TOTAL>\d+)")
|
||||
re_TourneyAddOnsTotal = re.compile("Total Add-Ons: (?P<ADDONS_TOTAL>\d+)")
|
||||
re_TourneyRebuyChips = re.compile("Rebuy Chips: (?P<REBUY_CHIPS>\d+)")
|
||||
re_TourneyAddOnChips = re.compile("Add-On Chips: (?P<ADDON_CHIPS>\d+)")
|
||||
re_TourneyKOBounty = re.compile("Knockout Bounty: (?P<KO_BOUNTY_CURRENCY>\$|)?(?P<KO_BOUNTY_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyCountKO = re.compile("received (?P<COUNT_KO>\d+) Knockout Bounty Award(s)?")
|
||||
re_TourneyTimeInfo = re.compile("Tournament started: (?P<STARTTIME>.*)\nTournament ((?P<IN_PROGRESS>is still in progress)?|(finished:(?P<ENDTIME>.*))?)$")
|
||||
|
||||
re_TourneyPlayersSummary = re.compile("^(?P<RANK>(Still Playing|\d+))( - |: )(?P<PNAME>[^\n,]+)(, )?(?P<WINNING_CURRENCY>\$|)?(?P<WINNING>[.\d]+)?", re.MULTILINE)
|
||||
re_TourneyHeroFinishingP = re.compile("(?P<HERO_NAME>.*) finished in (?P<HERO_FINISHING_POS>\d+)(st|nd|rd|th) place")
|
||||
|
||||
#TODO: See if we need to deal with play money tourney summaries -- Not right now (they shouldn't pass the re_TourneyInfo)
|
||||
##Full Tilt Poker Tournament Summary 250 Play Money Sit & Go (102909471) Hold'em No Limit
|
||||
##Buy-In: 250 Play Chips + 0 Play Chips
|
||||
##Buy-In Chips: 1500
|
||||
##6 Entries
|
||||
##Total Prize Pool: 1,500 Play Chips
|
||||
|
||||
# These regexes are for FTP only
|
||||
re_Mixed = re.compile(r'\s\-\s(?P<MIXED>HA|HORSE|HOSE)\s\-\s', re.VERBOSE)
|
||||
re_Max = re.compile("(?P<MAX>\d+)( max)?", re.MULTILINE)
|
||||
# NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports.
|
||||
|
||||
mixes = { 'HORSE': 'horse', '7-Game': '7game', 'HOSE': 'hose', 'HA': 'ha'}
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
out_path (default '-' = sys.stdout)
|
||||
follow : whether to tail -f the input"""
|
||||
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Fulltilt", follow=follow, index=index)
|
||||
logging.info("Initialising Fulltilt converter class")
|
||||
self.filetype = "text"
|
||||
self.codepage = "cp1252"
|
||||
self.siteId = 1 # Needs to match id entry in Sites database
|
||||
if autostart:
|
||||
self.start()
|
||||
|
||||
|
||||
mixes = { 'HORSE': 'horse', '7-Game': '7game', 'HOSE': 'hose', 'HA': 'ha'}
|
||||
|
||||
|
||||
def compilePlayerRegexs(self, hand):
|
||||
|
@ -89,7 +135,7 @@ follow : whether to tail -f the input"""
|
|||
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
|
||||
self.re_Action = re.compile(r"^%s(?P<ATYPE> bets| checks| raises to| completes it to| calls| folds)( \$?(?P<BET>[.,\d]+))?" % player_re, re.MULTILINE)
|
||||
self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
|
||||
self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*)" % player_re, re.MULTILINE)
|
||||
self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$?(?P<POT>[.,\d]+)\)(, mucked| with.*)" % player_re, re.MULTILINE)
|
||||
self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE)
|
||||
self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
|
||||
|
||||
|
@ -118,7 +164,6 @@ follow : whether to tail -f the input"""
|
|||
if not m:
|
||||
return None
|
||||
mg = m.groupdict()
|
||||
|
||||
# translations from captured groups to our info strings
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
|
||||
games = { # base, category
|
||||
|
@ -140,29 +185,11 @@ follow : whether to tail -f the input"""
|
|||
if mg['TOURNO'] == None: info['type'] = "ring"
|
||||
else: info['type'] = "tour"
|
||||
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
|
||||
if info['type'] == "tour": return None # importer is screwed on tournies, pass on those hands so we don't interrupt other autoimporting
|
||||
# if info['type'] == "tour": return None # importer is screwed on tournies, pass on those hands so we don't interrupt other autoimporting
|
||||
return info
|
||||
|
||||
#Following function is a hack, we should be dealing with this in readFile (i think correct codepage....)
|
||||
# Same function as parent class, removing the 2 end characters. - CG
|
||||
def allHandsAsList(self):
|
||||
"""Return a list of handtexts in the file at self.in_path"""
|
||||
#TODO : any need for this to be generator? e.g. stars support can email one huge file of all hands in a year. Better to read bit by bit than all at once.
|
||||
self.readFile()
|
||||
|
||||
# FIXME: it's a hack
|
||||
if self.obs[:2] == u'\xff\xfe':
|
||||
self.obs = self.obs[2:].replace('\x00', '')
|
||||
|
||||
self.obs = self.obs.strip()
|
||||
self.obs = self.obs.replace('\r\n', '\n')
|
||||
if self.obs == "" or self.obs == None:
|
||||
logging.info("Read no hands.")
|
||||
return
|
||||
return re.split(self.re_SplitHands, self.obs)
|
||||
|
||||
def readHandInfo(self, hand):
|
||||
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
|
||||
m = self.re_HandInfo.search(hand.handText)
|
||||
if(m == None):
|
||||
logging.info("Didn't match re_HandInfo")
|
||||
logging.info(hand.handText)
|
||||
|
@ -170,6 +197,10 @@ follow : whether to tail -f the input"""
|
|||
hand.handid = m.group('HID')
|
||||
hand.tablename = m.group('TABLE')
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
|
||||
|
||||
if m.group("CANCELLED") or m.group("PARTIAL"):
|
||||
raise FpdbParseError(hid=m.group('HID'))
|
||||
|
||||
if m.group('TABLEATTRIBUTES'):
|
||||
m2 = self.re_Max.search(m.group('TABLEATTRIBUTES'))
|
||||
if m2: hand.maxseats = int(m2.group('MAX'))
|
||||
|
@ -178,7 +209,31 @@ follow : whether to tail -f the input"""
|
|||
if m.group('PLAY') != None:
|
||||
hand.gametype['currency'] = 'play'
|
||||
|
||||
# TODO: if there's a way to figure these out, we should.. otherwise we have to stuff it with unknowns
|
||||
# Done: if there's a way to figure these out, we should.. otherwise we have to stuff it with unknowns
|
||||
if m.group('TOURNAMENT') is not None:
|
||||
n = self.re_TourneyExtraInfo.search(m.group('TOURNAMENT'))
|
||||
if n.group('UNREADABLE_INFO') is not None:
|
||||
hand.tourneyComment = n.group('UNREADABLE_INFO')
|
||||
else:
|
||||
hand.tourneyComment = n.group('TOURNEY_NAME') # can be None
|
||||
if (n.group('CURRENCY') is not None and n.group('BUYIN') is not None and n.group('FEE') is not None):
|
||||
hand.buyin = "%s%s+%s%s" %(n.group('CURRENCY'), n.group('BUYIN'), n.group('CURRENCY'), n.group('FEE'))
|
||||
if n.group('TURBO') is not None :
|
||||
hand.speed = "Turbo"
|
||||
if n.group('SPECIAL') is not None :
|
||||
special = n.group('SPECIAL')
|
||||
if special == "Rebuy":
|
||||
hand.isRebuy = True
|
||||
if special == "KO":
|
||||
hand.isKO = True
|
||||
if special == "Head's Up":
|
||||
hand.isHU = True
|
||||
if re.search("Matrix", special):
|
||||
hand.isMatrix = True
|
||||
if special == "Shootout":
|
||||
hand.isShootout = True
|
||||
|
||||
|
||||
if hand.buyin == None:
|
||||
hand.buyin = "$0.00+$0.00"
|
||||
if hand.level == None:
|
||||
|
@ -200,7 +255,11 @@ follow : whether to tail -f the input"""
|
|||
#FIXME: hand.buttonpos = int(m.group('BUTTON'))
|
||||
|
||||
def readPlayerStacks(self, hand):
|
||||
m = self.re_PlayerInfo.finditer(hand.handText)
|
||||
if hand.gametype['type'] == "ring" :
|
||||
m = self.re_PlayerInfo.finditer(hand.handText)
|
||||
else: #if hand.gametype['type'] == "tour"
|
||||
m = self.re_TourneyPlayerInfo.finditer(hand.handText)
|
||||
|
||||
players = []
|
||||
for a in m:
|
||||
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
|
||||
|
@ -322,7 +381,7 @@ follow : whether to tail -f the input"""
|
|||
|
||||
def readCollectPot(self,hand):
|
||||
for m in self.re_CollectPot.finditer(hand.handText):
|
||||
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
|
||||
hand.addCollectPot(player=m.group('PNAME'),pot=re.sub(u',',u'',m.group('POT')))
|
||||
|
||||
def readShownCards(self,hand):
|
||||
for m in self.re_ShownCards.finditer(hand.handText):
|
||||
|
@ -355,6 +414,253 @@ follow : whether to tail -f the input"""
|
|||
else:
|
||||
hand.mixed = self.mixes[m.groupdict()['MIXED']]
|
||||
|
||||
def readSummaryInfo(self, summaryInfoList):
|
||||
starttime = time.time()
|
||||
self.status = True
|
||||
|
||||
m = re.search("Tournament Summary", summaryInfoList[0])
|
||||
if m:
|
||||
# info list should be 2 lines : Tourney infos & Finsihing postions with winnings
|
||||
if (len(summaryInfoList) != 2 ):
|
||||
log.info("Too many lines (%d) in file '%s' : '%s'" % (len(summaryInfoList), self.in_path, summaryInfoList) )
|
||||
self.status = False
|
||||
else:
|
||||
self.tourney = Tourney.Tourney(sitename = self.sitename, gametype = None, summaryText = summaryInfoList, builtFrom = "HHC")
|
||||
self.status = self.getPlayersPositionsAndWinnings(self.tourney)
|
||||
if self.status == True :
|
||||
self.status = self.determineTourneyType(self.tourney)
|
||||
#print self.tourney
|
||||
else:
|
||||
log.info("Parsing NOK : rejected")
|
||||
else:
|
||||
log.info( "This is not a summary file : '%s'" % (self.in_path) )
|
||||
self.status = False
|
||||
|
||||
return self.status
|
||||
|
||||
def determineTourneyType(self, tourney):
|
||||
info = {'type':'tour'}
|
||||
tourneyText = tourney.summaryText[0]
|
||||
#print "Examine : '%s'" %(tourneyText)
|
||||
|
||||
m = self.re_TourneyInfo.search(tourneyText)
|
||||
if not m:
|
||||
log.info( "determineTourneyType : Parsing NOK" )
|
||||
return False
|
||||
mg = m.groupdict()
|
||||
#print mg
|
||||
|
||||
# translations from captured groups to our info strings
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
|
||||
games = { # base, category
|
||||
"Hold'em" : ('hold','holdem'),
|
||||
'Omaha Hi' : ('hold','omahahi'),
|
||||
'Omaha H/L' : ('hold','omahahilo'),
|
||||
'Razz' : ('stud','razz'),
|
||||
'Stud Hi' : ('stud','studhi'),
|
||||
'Stud H/L' : ('stud','studhilo')
|
||||
}
|
||||
currencies = { u' €':'EUR', '$':'USD', '':'T$' }
|
||||
info['limitType'] = limits[mg['LIMIT']]
|
||||
if mg['GAME'] is not None:
|
||||
(info['base'], info['category']) = games[mg['GAME']]
|
||||
if mg['CURRENCY'] is not None:
|
||||
info['currency'] = currencies[mg['CURRENCY']]
|
||||
if mg['TOURNO'] == None: info['type'] = "ring"
|
||||
else: info['type'] = "tour"
|
||||
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
|
||||
|
||||
# Info is now ready to be copied in the tourney object
|
||||
tourney.gametype = info
|
||||
|
||||
# Additional info can be stored in the tourney object
|
||||
if mg['BUYIN'] is not None:
|
||||
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
|
||||
tourney.fee = 0
|
||||
if mg['FEE'] is not None:
|
||||
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
|
||||
if mg['TOURNAMENT_NAME'] is not None:
|
||||
# Tournament Name can have a trailing space at the end (depending on the tournament description)
|
||||
tourney.tourneyName = mg['TOURNAMENT_NAME'].rstrip()
|
||||
if mg['SPECIAL'] is not None:
|
||||
special = mg['SPECIAL']
|
||||
if special == "KO":
|
||||
tourney.isKO = True
|
||||
if special == "Heads Up":
|
||||
tourney.isHU = True
|
||||
tourney.maxseats = 2
|
||||
if re.search("Matrix", special):
|
||||
tourney.isMatrix = True
|
||||
if special == "Rebuy":
|
||||
tourney.isRebuy = True
|
||||
if special == "Madness":
|
||||
tourney.tourneyComment = "Madness"
|
||||
if mg['SHOOTOUT'] is not None:
|
||||
tourney.isShootout = True
|
||||
if mg['TURBO1'] is not None or mg['TURBO2'] is not None :
|
||||
tourney.speed = "Turbo"
|
||||
if mg['TOURNO'] is not None:
|
||||
tourney.tourNo = mg['TOURNO']
|
||||
else:
|
||||
log.info( "Unable to get a valid Tournament ID -- File rejected" )
|
||||
return False
|
||||
if tourney.isMatrix:
|
||||
if mg['MATCHNO'] is not None:
|
||||
tourney.matrixMatchId = mg['MATCHNO']
|
||||
else:
|
||||
tourney.matrixMatchId = 0
|
||||
|
||||
|
||||
# Get BuyIn/Fee
|
||||
# Try and deal with the different cases that can occur :
|
||||
# - No buy-in/fee can be on the first line (freerolls, Satellites sometimes ?, ...) but appears in the rest of the description ==> use this one
|
||||
# - Buy-In/Fee from the first line differs from the rest of the description :
|
||||
# * OK in matrix tourneys (global buy-in dispatched between the different matches)
|
||||
# * NOK otherwise ==> issue a warning and store specific data as if were a Matrix Tourney
|
||||
# - If no buy-in/fee can be found : assume it's a freeroll
|
||||
m = self.re_TourneyBuyInFee.search(tourneyText)
|
||||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if tourney.isMatrix :
|
||||
if mg['BUYIN'] is not None:
|
||||
tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
|
||||
tourney.subTourneyFee = 0
|
||||
if mg['FEE'] is not None:
|
||||
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
|
||||
else :
|
||||
if mg['BUYIN'] is not None:
|
||||
if tourney.buyin is None:
|
||||
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
|
||||
else :
|
||||
if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin:
|
||||
log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (touney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) )
|
||||
tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
|
||||
if mg['FEE'] is not None:
|
||||
if tourney.fee is None:
|
||||
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
|
||||
else :
|
||||
if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee:
|
||||
log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (touney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) )
|
||||
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
|
||||
|
||||
if tourney.buyin is None:
|
||||
log.info( "Unable to affect a buyin to this tournament : assume it's a freeroll" )
|
||||
tourney.buyin = 0
|
||||
tourney.fee = 0
|
||||
else:
|
||||
if tourney.fee is None:
|
||||
#print "Couldn't initialize fee, even though buyin went OK : assume there are no fees"
|
||||
tourney.fee = 0
|
||||
|
||||
#Get single line infos
|
||||
dictRegex = { "BUYINCHIPS" : self.re_TourneyBuyInChips,
|
||||
"ENTRIES" : self.re_TourneyEntries,
|
||||
"PRIZEPOOL" : self.re_TourneyPrizePool,
|
||||
"REBUY_AMOUNT" : self.re_TourneyRebuyAmount,
|
||||
"ADDON_AMOUNT" : self.re_TourneyAddOnAmount,
|
||||
"REBUY_TOTAL" : self.re_TourneyRebuysTotal,
|
||||
"ADDONS_TOTAL" : self.re_TourneyAddOnsTotal,
|
||||
"REBUY_CHIPS" : self.re_TourneyRebuyChips,
|
||||
"ADDON_CHIPS" : self.re_TourneyAddOnChips,
|
||||
"STARTTIME" : self.re_TourneyTimeInfo,
|
||||
"KO_BOUNTY_AMOUNT" : self.re_TourneyKOBounty,
|
||||
}
|
||||
|
||||
|
||||
dictHolders = { "BUYINCHIPS" : "buyInChips",
|
||||
"ENTRIES" : "entries",
|
||||
"PRIZEPOOL" : "prizepool",
|
||||
"REBUY_AMOUNT" : "rebuyAmount",
|
||||
"ADDON_AMOUNT" : "addOnAmount",
|
||||
"REBUY_TOTAL" : "totalRebuys",
|
||||
"ADDONS_TOTAL" : "totalAddOns",
|
||||
"REBUY_CHIPS" : "rebuyChips",
|
||||
"ADDON_CHIPS" : "addOnChips",
|
||||
"STARTTIME" : "starttime",
|
||||
"KO_BOUNTY_AMOUNT" : "koBounty"
|
||||
}
|
||||
|
||||
mg = {} # After the loop, mg will contain all the matching groups, including the ones that have not been used, like ENDTIME and IN-PROGRESS
|
||||
for data in dictRegex:
|
||||
m = dictRegex.get(data).search(tourneyText)
|
||||
if m is not None:
|
||||
mg.update(m.groupdict())
|
||||
setattr(tourney, dictHolders[data], mg[data])
|
||||
|
||||
if mg['IN_PROGRESS'] is not None or mg['ENDTIME'] is not None:
|
||||
# Assign endtime to tourney (if None, that's ok, it's because the tourney wans't over over when the summary file was produced)
|
||||
tourney.endtime = mg['ENDTIME']
|
||||
|
||||
# Deal with hero specific information
|
||||
if tourney.hero is not None :
|
||||
m = self.re_TourneyRebuyCount.search(tourneyText)
|
||||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if mg['REBUY_COUNT'] is not None :
|
||||
tourney.countRebuys.update( { tourney.hero : Decimal(mg['REBUY_COUNT']) } )
|
||||
m = self.re_TourneyAddOnCount.search(tourneyText)
|
||||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if mg['ADDON_COUNT'] is not None :
|
||||
tourney.countAddOns.update( { tourney.hero : Decimal(mg['ADDON_COUNT']) } )
|
||||
m = self.re_TourneyCountKO.search(tourneyText)
|
||||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if mg['COUNT_KO'] is not None :
|
||||
tourney.countKO.update( { tourney.hero : Decimal(mg['COUNT_KO']) } )
|
||||
|
||||
# Deal with money amounts
|
||||
tourney.koBounty = 100*Decimal(re.sub(u',', u'', "%s" % tourney.koBounty))
|
||||
tourney.prizepool = 100*Decimal(re.sub(u',', u'', "%s" % tourney.prizepool))
|
||||
tourney.rebuyAmount = 100*Decimal(re.sub(u',', u'', "%s" % tourney.rebuyAmount))
|
||||
tourney.addOnAmount = 100*Decimal(re.sub(u',', u'', "%s" % tourney.addOnAmount))
|
||||
|
||||
# Calculate payin amounts and update winnings -- not possible to take into account nb of rebuys, addons or Knockouts for other players than hero on FTP
|
||||
for p in tourney.players :
|
||||
tourney.payinAmounts[p] = tourney.buyin + tourney.fee + (tourney.rebuyAmount * tourney.countRebuys[p]) + (tourney.addOnAmount * tourney.countAddOns[p])
|
||||
#print " player %s : payinAmount = %d" %( p, tourney.payinAmounts[p])
|
||||
if tourney.isKO :
|
||||
#tourney.incrementPlayerWinnings(tourney.players[p], Decimal(tourney.koBounty)*Decimal(tourney.countKO[p]))
|
||||
tourney.winnings[p] += Decimal(tourney.koBounty)*Decimal(tourney.countKO[p])
|
||||
#print "player %s : winnings %d" % (p, tourney.winnings[p])
|
||||
|
||||
|
||||
|
||||
#print mg
|
||||
return True
|
||||
|
||||
def getPlayersPositionsAndWinnings(self, tourney):
|
||||
playersText = tourney.summaryText[1]
|
||||
#print "Examine : '%s'" %(playersText)
|
||||
m = self.re_TourneyPlayersSummary.finditer(playersText)
|
||||
|
||||
for a in m:
|
||||
if a.group('PNAME') is not None and a.group('RANK') is not None:
|
||||
if a.group('RANK') == "Still Playing":
|
||||
rank = -1
|
||||
else:
|
||||
rank = Decimal(a.group('RANK'))
|
||||
|
||||
if a.group('WINNING') is not None:
|
||||
winnings = 100*Decimal(re.sub(u',', u'', "%s" % a.group('WINNING')))
|
||||
else:
|
||||
winnings = "0"
|
||||
|
||||
tourney.addPlayer(rank, a.group('PNAME'), winnings, 0, 0, 0, 0)
|
||||
else:
|
||||
print "Player finishing stats unreadable : %s" % a
|
||||
|
||||
# Find Hero
|
||||
n = self.re_TourneyHeroFinishingP.search(playersText)
|
||||
if n is not None:
|
||||
heroName = n.group('HERO_NAME')
|
||||
tourney.hero = heroName
|
||||
# Is this really useful ?
|
||||
if (tourney.finishPositions[heroName] != Decimal(n.group('HERO_FINISHING_POS'))):
|
||||
print "Bad parsing : finish position incoherent : %s / %s" % (tourney.finishPositions[heroName], n.group('HERO_FINISHING_POS'))
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = OptionParser()
|
||||
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/fulltilt/razz/FT20090223 Danville - $0.50-$1 Ante $0.10 - Limit Razz.txt")
|
||||
|
@ -369,7 +675,7 @@ if __name__ == "__main__":
|
|||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
LOG_FILENAME = './logging.out'
|
||||
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
|
||||
|
||||
e = Fulltilt(in_path = options.ipath, out_path = options.opath, follow = options.follow)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
import threading
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
@ -135,12 +136,23 @@ class GuiAutoImport (threading.Thread):
|
|||
def do_import(self):
|
||||
"""Callback for timer to do an import iteration."""
|
||||
if self.doAutoImportBool:
|
||||
self.startButton.set_label(u' I M P O R T I N G ')
|
||||
self.importer.runUpdated()
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
gobject.timeout_add(1000, self.reset_startbutton)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def reset_startbutton(self):
|
||||
if self.pipe_to_hud is not None:
|
||||
self.startButton.set_label(u' _Stop Autoimport ')
|
||||
else:
|
||||
self.startButton.set_label(u' _Start Autoimport ')
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def startClicked(self, widget, data):
|
||||
"""runs when user clicks start on auto import tab"""
|
||||
|
@ -160,33 +172,32 @@ class GuiAutoImport (threading.Thread):
|
|||
# - Ideally we want to release the lock if the auto-import is killed by some
|
||||
# kind of exception - is this possible?
|
||||
if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired
|
||||
try:
|
||||
print "\nGlobal lock taken ..."
|
||||
self.doAutoImportBool = True
|
||||
widget.set_label(u' _Stop Autoimport ')
|
||||
if self.pipe_to_hud is None:
|
||||
if os.name == 'nt':
|
||||
command = "python HUD_main.py" + " " + self.settings['cl_options']
|
||||
bs = 0 # windows is not happy with line buffing here
|
||||
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
else:
|
||||
command = os.path.join(sys.path[0], 'HUD_main.py')
|
||||
cl = [command, ] + string.split(self.settings['cl_options'])
|
||||
self.pipe_to_hud = subprocess.Popen(cl, bufsize = 1, stdin = subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
|
||||
# Add directories to importer object.
|
||||
print "\nGlobal lock taken ..."
|
||||
self.doAutoImportBool = True
|
||||
widget.set_label(u' _Stop Autoimport ')
|
||||
if self.pipe_to_hud is None:
|
||||
if os.name == 'nt':
|
||||
command = "python HUD_main.py " + self.settings['cl_options']
|
||||
bs = 0
|
||||
else:
|
||||
command = os.path.join(sys.path[0], 'HUD_main.py')
|
||||
command = [command, ] + string.split(self.settings['cl_options'])
|
||||
bs = 1
|
||||
try:
|
||||
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
|
||||
universal_newlines = True)
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
|
||||
else:
|
||||
for site in self.input_settings:
|
||||
self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1])
|
||||
print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0])
|
||||
print "+Import directory - Site: " + site + " dir: " + str(self.input_settings[site][0])
|
||||
self.do_import()
|
||||
|
||||
interval=int(self.intervalEntry.get_text())
|
||||
|
||||
interval = int(self.intervalEntry.get_text())
|
||||
gobject.timeout_add(interval*1000, self.do_import)
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
|
||||
else:
|
||||
print "auto-import aborted - global lock not available"
|
||||
else: # toggled off
|
||||
|
|
|
@ -284,7 +284,7 @@ def main(argv=None):
|
|||
parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True,
|
||||
help="don't start gui; deprecated (just give a filename with -f).")
|
||||
parser.add_option("-c", "--convert", dest="filtername", default="PokerStars", metavar="FILTER",
|
||||
help="Conversion filter (*Full Tilt Poker, PokerStars, Everleaf)")
|
||||
help="Conversion filter (*Full Tilt Poker, PokerStars, Everleaf, Absolute)")
|
||||
parser.add_option("-x", "--failOnError", action="store_true", default=False,
|
||||
help="If this option is passed it quits when it encounters any error")
|
||||
parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int",
|
||||
|
@ -319,6 +319,7 @@ def main(argv=None):
|
|||
# importer.setDropIndexes("auto")
|
||||
importer.setDropIndexes("don't drop")
|
||||
importer.setFailOnError(options.failOnError)
|
||||
importer.setThreads(-1)
|
||||
importer.addBulkImportImportFileOrDir(os.path.expanduser(options.filename), site=options.filtername)
|
||||
importer.setCallHud(False)
|
||||
importer.runImport()
|
||||
|
|
|
@ -20,6 +20,7 @@ import pygtk
|
|||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import os
|
||||
import traceback
|
||||
from time import *
|
||||
#import pokereval
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ class GuiPlayerStats (threading.Thread):
|
|||
self.stats_vbox = gtk.VBox(False, 0)
|
||||
self.stats_vbox.show()
|
||||
self.stats_frame.add(self.stats_vbox)
|
||||
self.fillStatsFrame(self.stats_vbox)
|
||||
# self.fillStatsFrame(self.stats_vbox)
|
||||
|
||||
self.main_hbox.pack_start(self.filters.get_vbox())
|
||||
self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
|
||||
|
@ -167,7 +167,9 @@ class GuiPlayerStats (threading.Thread):
|
|||
for site in sites:
|
||||
if sites[site] == True:
|
||||
sitenos.append(siteids[site])
|
||||
self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
|
||||
# Nasty hack to deal with multiple sites + same player name -Eric
|
||||
que = self.sql.query['getPlayerId'] + " AND siteId=%d" % siteids[site]
|
||||
self.cursor.execute(que, (heroes[site],))
|
||||
result = self.db.cursor.fetchall()
|
||||
if len(result) == 1:
|
||||
playerids.append(result[0][0])
|
||||
|
|
|
@ -21,7 +21,11 @@ pygtk.require('2.0')
|
|||
import gtk
|
||||
import os
|
||||
from time import time, strftime, localtime
|
||||
from numpy import diff, nonzero
|
||||
try:
|
||||
from numpy import diff, nonzero
|
||||
except:
|
||||
print """Failed to load numpy in Session Viewer"""
|
||||
print """This is of no consequence as the module currently doesn't do anything."""
|
||||
|
||||
import Card
|
||||
import fpdb_import
|
||||
|
|
|
@ -22,9 +22,9 @@ import gtk
|
|||
import os
|
||||
import fpdb_simple
|
||||
|
||||
|
||||
import fpdb_import
|
||||
import fpdb_db
|
||||
from Exceptions import *
|
||||
|
||||
|
||||
class GuiTableViewer (threading.Thread):
|
||||
|
@ -74,7 +74,7 @@ class GuiTableViewer (threading.Thread):
|
|||
|
||||
tmp+=("WtSD", "W$wsF", "W$SD")
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("reimplement stud")
|
||||
raise FpdbError("reimplement stud")
|
||||
arr.append(tmp)
|
||||
|
||||
#then the data rows
|
||||
|
@ -94,7 +94,7 @@ class GuiTableViewer (threading.Thread):
|
|||
elif seatCount==2 or seatCount==3:
|
||||
minSeats,maxSeats=seatCount,seatCount
|
||||
else:
|
||||
fpdb_simple.FpdbError("invalid seatCount")
|
||||
FpdbError("invalid seatCount")
|
||||
|
||||
self.cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats>=%s AND activeSeats<=%s", (self.gametype_id, self.player_ids[player][0], minSeats, maxSeats))
|
||||
rows=self.cursor.fetchall()
|
||||
|
|
|
@ -252,10 +252,10 @@
|
|||
|
||||
<site enabled="False"
|
||||
site_name="PartyPoker"
|
||||
table_finder="PartyPoker.exe"
|
||||
table_finder="PartyGaming.exe"
|
||||
screen_name="YOUR SCREEN NAME HERE"
|
||||
site_path=""
|
||||
HH_path=""
|
||||
site_path="C:/Program Files/PartyGaming/PartyPoker"
|
||||
HH_path="C:/Program Files/PartyGaming/PartyPoker/HandHistory/YOUR SCREEN NAME HERE/"
|
||||
decoder="everleaf_decode_table"
|
||||
converter="PartyPokerToFpdb"
|
||||
supported_games="holdem">
|
||||
|
@ -432,7 +432,8 @@
|
|||
</hhcs>
|
||||
|
||||
<supported_databases>
|
||||
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD" db_type="fpdb"></database>
|
||||
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD" db_type="fpdb"></database>
|
||||
<!-- <database db_ip="localhost" db_name="fpdb" db_pass="fpdb" db_server="sqlite" db_type="fpdb" db_user="fpdb"/> -->
|
||||
</supported_databases>
|
||||
|
||||
</FreePokerToolsConfig>
|
||||
|
|
|
@ -37,7 +37,7 @@ import traceback
|
|||
|
||||
if not options.errorsToConsole:
|
||||
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
|
||||
errorFile = open('fpdb-error-log.txt', 'w', 0)
|
||||
errorFile = open('HUD-error.txt', 'w', 0)
|
||||
sys.stderr = errorFile
|
||||
|
||||
import thread
|
||||
|
@ -195,7 +195,7 @@ class HUD_main(object):
|
|||
temp_key = tour_number
|
||||
else: # tourney, but can't get number and table
|
||||
print "could not find tournament: skipping "
|
||||
sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id)))
|
||||
#sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id)))
|
||||
continue
|
||||
|
||||
else:
|
||||
|
|
347
pyfpdb/Hand.py
347
pyfpdb/Hand.py
|
@ -1,15 +1,16 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008 Carl Gherardi
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as published by
|
||||
#the Free Software Foundation, version 3 of the License.
|
||||
#
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#In the "official" distribution you can find the license in
|
||||
|
@ -32,6 +33,8 @@ import pprint
|
|||
import DerivedStats
|
||||
import Card
|
||||
|
||||
log = logging.getLogger("parser")
|
||||
|
||||
class Hand(object):
|
||||
|
||||
###############################################################3
|
||||
|
@ -61,6 +64,15 @@ class Hand(object):
|
|||
self.fee = None # the Database code is looking for this one .. ?
|
||||
self.level = None
|
||||
self.mixed = None
|
||||
# Some attributes for hand from a tourney
|
||||
self.speed = "Normal"
|
||||
self.isRebuy = False
|
||||
self.isKO = False
|
||||
self.isHU = False
|
||||
self.isMatrix = False
|
||||
self.isShootout = False
|
||||
self.tourneyComment = None
|
||||
|
||||
self.seating = []
|
||||
self.players = []
|
||||
self.posted = []
|
||||
|
@ -118,7 +130,7 @@ class Hand(object):
|
|||
("MIXED", self.mixed),
|
||||
("LASTBET", self.lastBet),
|
||||
("ACTION STREETS", self.actionStreets),
|
||||
("STREETS", self.streets),
|
||||
("STREETS", self.streets),
|
||||
("ALL STREETS", self.allStreets),
|
||||
("COMMUNITY STREETS", self.communityStreets),
|
||||
("HOLE STREETS", self.holeStreets),
|
||||
|
@ -131,7 +143,7 @@ class Hand(object):
|
|||
("RAKE", self.rake),
|
||||
("START TIME", self.starttime),
|
||||
)
|
||||
|
||||
|
||||
structs = ( ("PLAYERS", self.players),
|
||||
("STACKS", self.stacks),
|
||||
("POSTED", self.posted),
|
||||
|
@ -162,7 +174,7 @@ shown whether they were revealed at showdown
|
|||
mucked whether they were mucked at showdown
|
||||
dealt whether they were seen in a 'dealt to' line
|
||||
"""
|
||||
# logging.debug("addHoleCards %s %s" % (open + closed, player))
|
||||
# log.debug("addHoleCards %s %s" % (open + closed, player))
|
||||
try:
|
||||
self.checkPlayerExists(player)
|
||||
except FpdbParseError, e:
|
||||
|
@ -185,31 +197,31 @@ db: a connected fpdb_db object"""
|
|||
# TODO:
|
||||
# Players - base playerid and siteid tuple
|
||||
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
|
||||
|
||||
#Gametypes
|
||||
gtid = db.getGameTypeId(self.siteId, self.gametype)
|
||||
|
||||
# HudCache data to come from DerivedStats class
|
||||
# HandsActions - all actions for all players for all streets - self.actions
|
||||
# BoardCards - Skip - no longer necessary
|
||||
# Hands - Summary information of hand indexed by handId - gameinfo
|
||||
#hh['siteHandNo'] = self.handid
|
||||
# gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
|
||||
#
|
||||
#hh['handStart'] = self.starttime
|
||||
# seats TINYINT NOT NULL,
|
||||
#
|
||||
#hh['tableName'] = self.tablenam
|
||||
#hh['maxSeats'] = self.maxseats
|
||||
# boardcard1 smallint, /* 0=none, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As */
|
||||
# boardcard2 smallint,
|
||||
# boardcard3 smallint,
|
||||
# boardcard4 smallint,
|
||||
# boardcard5 smallint,
|
||||
# Flop turn and river may all be empty - add (likely) too many elements and trim with range
|
||||
# boardcards = board['FLOP'] + board['TURN'] + board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
|
||||
# cards = [Card.cardFromValueSuit(v,s) for v,s in boardcards[0:4]]
|
||||
# hh['boardcard1'] = cards[0]
|
||||
# hh['boardcard2'] = cards[1]
|
||||
# hh['boardcard3'] = cards[2]
|
||||
# hh['boardcard4'] = cards[3]
|
||||
# hh['boardcard5'] = cards[4]
|
||||
#This should be moved to prepInsert
|
||||
hh = {}
|
||||
hh['siteHandNo'] = self.handid
|
||||
hh['handStart'] = self.starttime
|
||||
hh['gameTypeId'] = gtid
|
||||
# seats TINYINT NOT NULL,
|
||||
hh['tableName'] = self.tablename
|
||||
hh['maxSeats'] = self.maxseats
|
||||
hh['seats'] = len(sqlids)
|
||||
# Flop turn and river may all be empty - add (likely) too many elements and trim with range
|
||||
boardcards = self.board['FLOP'] + self.board['TURN'] + self.board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
|
||||
cards = [Card.encodeCard(c) for c in boardcards[0:5]]
|
||||
hh['boardcard1'] = cards[0]
|
||||
hh['boardcard2'] = cards[1]
|
||||
hh['boardcard3'] = cards[2]
|
||||
hh['boardcard4'] = cards[3]
|
||||
hh['boardcard5'] = cards[4]
|
||||
|
||||
# texture smallint,
|
||||
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
|
||||
# Needs to be recorded
|
||||
|
@ -233,18 +245,15 @@ db: a connected fpdb_db object"""
|
|||
# Needs to be recorded
|
||||
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
|
||||
# Needs to be recorded
|
||||
# street1Pot INT, /* pot size at flop/street4 */
|
||||
# Needs to be recorded
|
||||
# street2Pot INT, /* pot size at turn/street5 */
|
||||
# Needs to be recorded
|
||||
# street3Pot INT, /* pot size at river/street6 */
|
||||
# Needs to be recorded
|
||||
# street4Pot INT, /* pot size at sd/street7 */
|
||||
# Needs to be recorded
|
||||
# showdownPot INT, /* pot size at sd/street7 */
|
||||
|
||||
#print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % self.getStreetTotals()
|
||||
#FIXME: Pot size still in decimal, needs to be converted to cents
|
||||
(hh['street1Pot'], hh['street2Pot'], hh['street3Pot'], hh['street4Pot'], hh['showdownPot']) = self.getStreetTotals()
|
||||
|
||||
# comment TEXT,
|
||||
# commentTs DATETIME
|
||||
# handid = db.storeHand(hh)
|
||||
#print hh
|
||||
handid = db.storeHand(hh)
|
||||
# HandsPlayers - ? ... Do we fix winnings?
|
||||
# Tourneys ?
|
||||
# TourneysPlayers
|
||||
|
@ -253,8 +262,8 @@ db: a connected fpdb_db object"""
|
|||
|
||||
def select(self, handId):
|
||||
""" Function to create Hand object from database """
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def addPlayer(self, seat, name, chips):
|
||||
|
@ -264,7 +273,7 @@ seat (int) indicating the seat
|
|||
name (string) player name
|
||||
chips (string) the chips the player has at the start of the hand (can be None)
|
||||
If a player has None chips he won't be added."""
|
||||
logging.debug("addPlayer: %s %s (%s)" % (seat, name, chips))
|
||||
log.debug("addPlayer: %s %s (%s)" % (seat, name, chips))
|
||||
if chips is not None:
|
||||
chips = re.sub(u',', u'', chips) #some sites have commas
|
||||
self.players.append([seat, name, chips])
|
||||
|
@ -280,9 +289,9 @@ If a player has None chips he won't be added."""
|
|||
# go through m and initialise actions to empty list for each street.
|
||||
if match:
|
||||
self.streets.update(match.groupdict())
|
||||
logging.debug("markStreets:\n"+ str(self.streets))
|
||||
log.debug("markStreets:\n"+ str(self.streets))
|
||||
else:
|
||||
logging.error("markstreets didn't match")
|
||||
log.error("markstreets didn't match")
|
||||
|
||||
def checkPlayerExists(self,player):
|
||||
if player not in [p[1] for p in self.players]:
|
||||
|
@ -292,7 +301,7 @@ If a player has None chips he won't be added."""
|
|||
|
||||
|
||||
def setCommunityCards(self, street, cards):
|
||||
logging.debug("setCommunityCards %s %s" %(street, cards))
|
||||
log.debug("setCommunityCards %s %s" %(street, cards))
|
||||
self.board[street] = [self.card(c) for c in cards]
|
||||
# print "DEBUG: self.board: %s" % self.board
|
||||
|
||||
|
@ -303,14 +312,13 @@ If a player has None chips he won't be added."""
|
|||
return c
|
||||
|
||||
def addAnte(self, player, ante):
|
||||
logging.debug("%s %s antes %s" % ('ANTES', player, ante))
|
||||
log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
|
||||
if player is not None:
|
||||
ante = re.sub(u',', u'', ante) #some sites have commas
|
||||
self.bets['ANTES'][player].append(Decimal(ante))
|
||||
self.bets['BLINDSANTES'][player].append(Decimal(ante))
|
||||
self.stacks[player] -= Decimal(ante)
|
||||
act = (player, 'posts', "ante", ante, self.stacks[player]==0)
|
||||
self.actions['ANTES'].append(act)
|
||||
#~ self.lastBet['ANTES'] = Decimal(ante)
|
||||
self.actions['BLINDSANTES'].append(act)
|
||||
self.pot.addMoney(player, Decimal(ante))
|
||||
|
||||
def addBlind(self, player, blindtype, amount):
|
||||
|
@ -322,29 +330,29 @@ If a player has None chips he won't be added."""
|
|||
# - this is a call of 1 sb and a raise to 1 bb
|
||||
#
|
||||
|
||||
logging.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
|
||||
log.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
|
||||
if player is not None:
|
||||
amount = re.sub(u',', u'', amount) #some sites have commas
|
||||
self.bets['PREFLOP'][player].append(Decimal(amount))
|
||||
self.stacks[player] -= Decimal(amount)
|
||||
#print "DEBUG %s posts, stack %s" % (player, self.stacks[player])
|
||||
act = (player, 'posts', blindtype, amount, self.stacks[player]==0)
|
||||
self.actions['BLINDSANTES'].append(act)
|
||||
|
||||
if blindtype == 'both':
|
||||
amount = self.bb
|
||||
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
|
||||
self.pot.addCommonMoney(Decimal(self.sb))
|
||||
|
||||
self.bets['PREFLOP'][player].append(Decimal(amount))
|
||||
self.pot.addMoney(player, Decimal(amount))
|
||||
if blindtype == 'big blind':
|
||||
self.lastBet['PREFLOP'] = Decimal(amount)
|
||||
elif blindtype == 'both':
|
||||
# extra small blind is 'dead'
|
||||
self.lastBet['PREFLOP'] = Decimal(self.bb)
|
||||
self.lastBet['PREFLOP'] = Decimal(amount)
|
||||
self.posted = self.posted + [[player,blindtype]]
|
||||
#print "DEBUG: self.posted: %s" %(self.posted)
|
||||
|
||||
|
||||
|
||||
def addCall(self, street, player=None, amount=None):
|
||||
if amount:
|
||||
amount = re.sub(u',', u'', amount) #some sites have commas
|
||||
logging.debug("%s %s calls %s" %(street, player, amount))
|
||||
log.debug("%s %s calls %s" %(street, player, amount))
|
||||
# Potentially calculate the amount of the call if not supplied
|
||||
# corner cases include if player would be all in
|
||||
if amount is not None:
|
||||
|
@ -355,22 +363,22 @@ If a player has None chips he won't be added."""
|
|||
act = (player, 'calls', amount, self.stacks[player]==0)
|
||||
self.actions[street].append(act)
|
||||
self.pot.addMoney(player, Decimal(amount))
|
||||
|
||||
|
||||
def addRaiseBy(self, street, player, amountBy):
|
||||
"""\
|
||||
Add a raise by amountBy on [street] by [player]
|
||||
Add a raise by amountBy on [street] by [player]
|
||||
"""
|
||||
#Given only the amount raised by, the amount of the raise can be calculated by
|
||||
# working out how much this player has already in the pot
|
||||
# working out how much this player has already in the pot
|
||||
# (which is the sum of self.bets[street][player])
|
||||
# and how much he needs to call to match the previous player
|
||||
# and how much he needs to call to match the previous player
|
||||
# (which is tracked by self.lastBet)
|
||||
# let Bp = previous bet
|
||||
# let Bp = previous bet
|
||||
# Bc = amount player has committed so far
|
||||
# Rb = raise by
|
||||
# then: C = Bp - Bc (amount to call)
|
||||
# Rt = Bp + Rb (raise to)
|
||||
#
|
||||
#
|
||||
amountBy = re.sub(u',', u'', amountBy) #some sites have commas
|
||||
self.checkPlayerExists(player)
|
||||
Rb = Decimal(amountBy)
|
||||
|
@ -378,7 +386,7 @@ Add a raise by amountBy on [street] by [player]
|
|||
Bc = reduce(operator.add, self.bets[street][player], 0)
|
||||
C = Bp - Bc
|
||||
Rt = Bp + Rb
|
||||
|
||||
|
||||
self._addRaise(street, player, C, Rb, Rt)
|
||||
#~ self.bets[street][player].append(C + Rb)
|
||||
#~ self.stacks[player] -= (C + Rb)
|
||||
|
@ -396,7 +404,7 @@ For sites which by "raises x" mean "calls and raises putting a total of x in the
|
|||
C = Bp - Bc
|
||||
Rb = CRb - C
|
||||
Rt = Bp + Rb
|
||||
|
||||
|
||||
self._addRaise(street, player, C, Rb, Rt)
|
||||
|
||||
def addRaiseTo(self, street, player, amountTo):
|
||||
|
@ -414,18 +422,18 @@ Add a raise on [street] by [player] to [amountTo]
|
|||
self._addRaise(street, player, C, Rb, Rt)
|
||||
|
||||
def _addRaise(self, street, player, C, Rb, Rt):
|
||||
logging.debug("%s %s raise %s" %(street, player, Rt))
|
||||
log.debug("%s %s raise %s" %(street, player, Rt))
|
||||
self.bets[street][player].append(C + Rb)
|
||||
self.stacks[player] -= (C + Rb)
|
||||
act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0)
|
||||
self.actions[street].append(act)
|
||||
self.lastBet[street] = Rt # TODO check this is correct
|
||||
self.pot.addMoney(player, C+Rb)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def addBet(self, street, player, amount):
|
||||
logging.debug("%s %s bets %s" %(street, player, amount))
|
||||
log.debug("%s %s bets %s" %(street, player, amount))
|
||||
amount = re.sub(u',', u'', amount) #some sites have commas
|
||||
self.checkPlayerExists(player)
|
||||
self.bets[street][player].append(Decimal(amount))
|
||||
|
@ -441,24 +449,25 @@ Add a raise on [street] by [player] to [amountTo]
|
|||
self.checkPlayerExists(player)
|
||||
act = (player, 'stands pat')
|
||||
self.actions[street].append(act)
|
||||
|
||||
|
||||
|
||||
def addFold(self, street, player):
|
||||
logging.debug("%s %s folds" % (street, player))
|
||||
log.debug("%s %s folds" % (street, player))
|
||||
self.checkPlayerExists(player)
|
||||
self.folded.add(player)
|
||||
self.pot.addFold(player)
|
||||
self.actions[street].append((player, 'folds'))
|
||||
|
||||
|
||||
|
||||
def addCheck(self, street, player):
|
||||
#print "DEBUG: %s %s checked" % (street, player)
|
||||
logging.debug("%s %s checks" % (street, player))
|
||||
self.checkPlayerExists(player)
|
||||
self.actions[street].append((player, 'checks'))
|
||||
|
||||
|
||||
def addCollectPot(self,player, pot):
|
||||
logging.debug("%s collected %s" % (player, pot))
|
||||
log.debug("%s collected %s" % (player, pot))
|
||||
self.checkPlayerExists(player)
|
||||
self.collected = self.collected + [[player, pot]]
|
||||
if player not in self.collectees:
|
||||
|
@ -472,7 +481,7 @@ Add a raise on [street] by [player] to [amountTo]
|
|||
For when a player shows cards for any reason (for showdown or out of choice).
|
||||
Card ranks will be uppercased
|
||||
"""
|
||||
logging.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard))
|
||||
log.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard))
|
||||
if cards is not None:
|
||||
self.addHoleCards(cards,player,shown, mucked)
|
||||
elif holeandboard is not None:
|
||||
|
@ -480,10 +489,9 @@ Card ranks will be uppercased
|
|||
board = set([c for s in self.board.values() for c in s])
|
||||
self.addHoleCards(holeandboard.difference(board),player,shown, mucked)
|
||||
|
||||
|
||||
def totalPot(self):
|
||||
"""If all bets and blinds have been added, totals up the total pot size"""
|
||||
|
||||
|
||||
# This gives us the total amount put in the pot
|
||||
if self.totalpot is None:
|
||||
self.pot.end()
|
||||
|
@ -522,7 +530,7 @@ Map the tuple self.gametype onto the pokerstars string describing it
|
|||
"cp" : "Cap Pot Limit"
|
||||
}
|
||||
|
||||
logging.debug("gametype: %s" %(self.gametype))
|
||||
log.debug("gametype: %s" %(self.gametype))
|
||||
retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
|
||||
return retstring
|
||||
|
||||
|
@ -553,6 +561,8 @@ Map the tuple self.gametype onto the pokerstars string describing it
|
|||
return ("%s: posts big blind %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
|
||||
elif(act[2] == "both"):
|
||||
return ("%s: posts small & big blinds %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
|
||||
elif(act[2] == "ante"):
|
||||
return ("%s: posts the ante %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
|
||||
elif act[1] == 'bringin':
|
||||
return ("%s: brings in for %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
|
||||
elif act[1] == 'discards':
|
||||
|
@ -564,23 +574,33 @@ Map the tuple self.gametype onto the pokerstars string describing it
|
|||
"""Return a string of the stakes of the current hand."""
|
||||
return "%s%s/%s%s" % (self.sym, self.sb, self.sym, self.bb)
|
||||
|
||||
def getStreetTotals(self):
|
||||
pass
|
||||
|
||||
def writeGameLine(self):
|
||||
"""Return the first HH line for the current hand."""
|
||||
gs = "PokerStars Game #%s: " % self.handid
|
||||
|
||||
|
||||
if self.tourNo != None and self.mixed != None: # mixed tournament
|
||||
gs = gs + "Tournament #%s, %s %s (%s) - Level %s (%s) - " % (self.tourNo, self.buyin, self.MS[self.mixed], self.getGameTypeAsString(), self.level, self.getStakesAsString())
|
||||
elif self.tourNo != None: # all other tournaments
|
||||
gs = gs + "Tournament #%s, %s %s - Level %s (%s) - " % (self.tourNo,
|
||||
gs = gs + "Tournament #%s, %s %s - Level %s (%s) - " % (self.tourNo,
|
||||
self.buyin, self.getGameTypeAsString(), self.level, self.getStakesAsString())
|
||||
elif self.mixed != None: # all other mixed games
|
||||
gs = gs + " %s (%s, %s) - " % (self.MS[self.mixed],
|
||||
gs = gs + " %s (%s, %s) - " % (self.MS[self.mixed],
|
||||
self.getGameTypeAsString(), self.getStakesAsString())
|
||||
else: # non-mixed cash games
|
||||
gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString())
|
||||
|
||||
return gs + datetime.datetime.strftime(self.starttime,'%Y/%m/%d %H:%M:%S ET')
|
||||
|
||||
try:
|
||||
timestr = datetime.datetime.strftime(self.starttime, '%Y/%m/%d %H:%M:%S ET')
|
||||
except TypeError:
|
||||
print "*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:", self.starttime
|
||||
print "*** Make sure your HandHistoryConverter is setting hand.starttime properly!"
|
||||
print "*** Game String:", gs
|
||||
return gs
|
||||
else:
|
||||
return gs + timestr
|
||||
|
||||
def writeTableLine(self):
|
||||
table_string = "Table \'%s\' %s-max" % (self.tablename, self.maxseats)
|
||||
|
@ -593,15 +613,15 @@ Map the tuple self.gametype onto the pokerstars string describing it
|
|||
|
||||
def writeHand(self, fh=sys.__stdout__):
|
||||
# PokerStars format.
|
||||
print >>fh, self.writeGameLine()
|
||||
print >>fh, self.writeTableLine()
|
||||
print >>fh, self.writeGameLine()
|
||||
print >>fh, self.writeTableLine()
|
||||
|
||||
|
||||
class HoldemOmahaHand(Hand):
|
||||
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
|
||||
if gametype['base'] != 'hold':
|
||||
pass # or indeed don't pass and complain instead
|
||||
logging.debug("HoldemOmahaHand")
|
||||
log.debug("HoldemOmahaHand")
|
||||
self.allStreets = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER']
|
||||
self.holeStreets = ['PREFLOP']
|
||||
self.communityStreets = ['FLOP', 'TURN', 'RIVER']
|
||||
|
@ -609,9 +629,9 @@ class HoldemOmahaHand(Hand):
|
|||
Hand.__init__(self, sitename, gametype, handText, builtFrom = "HHC")
|
||||
self.sb = gametype['sb']
|
||||
self.bb = gametype['bb']
|
||||
|
||||
|
||||
#Populate a HoldemOmahaHand
|
||||
#Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
|
||||
#Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
|
||||
# which then invokes a 'addXXX' callback
|
||||
if builtFrom == "HHC":
|
||||
hhc.readHandInfo(self)
|
||||
|
@ -619,6 +639,7 @@ class HoldemOmahaHand(Hand):
|
|||
hhc.compilePlayerRegexs(self)
|
||||
hhc.markStreets(self)
|
||||
hhc.readBlinds(self)
|
||||
hhc.readAntes(self)
|
||||
hhc.readButton(self)
|
||||
hhc.readHeroCards(self)
|
||||
hhc.readShowdownActions(self)
|
||||
|
@ -629,6 +650,7 @@ class HoldemOmahaHand(Hand):
|
|||
for street in self.actionStreets:
|
||||
if self.streets[street]:
|
||||
hhc.readAction(self, street)
|
||||
self.pot.markTotal(street)
|
||||
hhc.readCollectPot(self)
|
||||
hhc.readShownCards(self)
|
||||
self.totalPot() # finalise it (total the pot)
|
||||
|
@ -640,11 +662,11 @@ class HoldemOmahaHand(Hand):
|
|||
if handid is not None:
|
||||
self.select(handid) # Will need a handId
|
||||
else:
|
||||
logging.warning("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid")
|
||||
log.warning("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid")
|
||||
else:
|
||||
logging.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided")
|
||||
log.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided")
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False):
|
||||
if player == self.hero: # we have hero's cards just update shown/mucked
|
||||
|
@ -653,8 +675,20 @@ class HoldemOmahaHand(Hand):
|
|||
else:
|
||||
self.addHoleCards('PREFLOP', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
|
||||
|
||||
def getStreetTotals(self):
|
||||
# street1Pot INT, /* pot size at flop/street4 */
|
||||
# street2Pot INT, /* pot size at turn/street5 */
|
||||
# street3Pot INT, /* pot size at river/street6 */
|
||||
# street4Pot INT, /* pot size at sd/street7 */
|
||||
# showdownPot INT, /* pot size at sd/street7 */
|
||||
tmp1 = self.pot.getTotalAtStreet('FLOP')
|
||||
tmp2 = self.pot.getTotalAtStreet('TURN')
|
||||
tmp3 = self.pot.getTotalAtStreet('RIVER')
|
||||
tmp4 = 0
|
||||
tmp5 = 0
|
||||
return (tmp1,tmp2,tmp3,tmp4,tmp5)
|
||||
|
||||
def writeHTMLHand(self, fh=sys.__stdout__):
|
||||
def writeHTMLHand(self):
|
||||
from nevow import tags as T
|
||||
from nevow import flat
|
||||
players_who_act_preflop = (([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
|
||||
|
@ -687,7 +721,7 @@ class HoldemOmahaHand(Hand):
|
|||
]
|
||||
)
|
||||
if street in self.actionStreets and self.actions[street]:
|
||||
lines.append(
|
||||
lines.append(
|
||||
T.ol(class_='actions', data=self.actions[street], render=render_action) [
|
||||
T.li(pattern='list_item')[ T.slot(name='action') ]
|
||||
]
|
||||
|
@ -721,8 +755,8 @@ class HoldemOmahaHand(Hand):
|
|||
context.tag[ pat().fillSlots('action', x)]
|
||||
return context.tag
|
||||
|
||||
s = T.p[
|
||||
T.h1[
|
||||
s = T.p[
|
||||
T.h1[
|
||||
T.span(class_='site')["%s Game #%s]" % ('PokerStars', self.handid)],
|
||||
T.span(class_='type_limit')[ "%s ($%s/$%s)" %(self.getGameTypeAsString(), self.sb, self.bb) ],
|
||||
T.span(class_='date')[ datetime.datetime.strftime(self.starttime,'%Y/%m/%d - %H:%M:%S ET') ]
|
||||
|
@ -748,13 +782,13 @@ class HoldemOmahaHand(Hand):
|
|||
|
||||
return str(tidy.parseString(flat.flatten(s), **options))
|
||||
|
||||
|
||||
|
||||
def writeHand(self, fh=sys.__stdout__):
|
||||
# PokerStars format.
|
||||
super(HoldemOmahaHand, self).writeHand(fh)
|
||||
|
||||
players_who_act_preflop = set(([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
|
||||
logging.debug(self.actions['PREFLOP'])
|
||||
log.debug(self.actions['PREFLOP'])
|
||||
for player in [x for x in self.players if x[1] in players_who_act_preflop]:
|
||||
#Only print stacks of players who do something preflop
|
||||
print >>fh, ("Seat %s: %s ($%s in chips) " %(player[0], player[1], player[2]))
|
||||
|
@ -762,7 +796,7 @@ class HoldemOmahaHand(Hand):
|
|||
if self.actions['BLINDSANTES']:
|
||||
for act in self.actions['BLINDSANTES']:
|
||||
print >>fh, self.actionString(act)
|
||||
|
||||
|
||||
print >>fh, ("*** HOLE CARDS ***")
|
||||
for player in self.dealt:
|
||||
print >>fh, ("Dealt to %s [%s]" %(player, " ".join(self.holecards['PREFLOP'][player][1])))
|
||||
|
@ -808,7 +842,7 @@ class HoldemOmahaHand(Hand):
|
|||
numOfHoleCardsNeeded = 2
|
||||
if len(self.holecards['PREFLOP'][name]) == numOfHoleCardsNeeded:
|
||||
print >>fh, ("%s shows [%s] (a hand...)" % (name, " ".join(self.holecards['PREFLOP'][name][1])))
|
||||
|
||||
|
||||
# Current PS format has the lines:
|
||||
# Uncalled bet ($111.25) returned to s0rrow
|
||||
# s0rrow collected $5.15 from side pot
|
||||
|
@ -850,7 +884,7 @@ class HoldemOmahaHand(Hand):
|
|||
print >>fh, ("Seat %d: %s mucked" % (seatnum, name))
|
||||
|
||||
print >>fh, "\n\n"
|
||||
|
||||
|
||||
class DrawHand(Hand):
|
||||
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC"):
|
||||
if gametype['base'] != 'draw':
|
||||
|
@ -870,6 +904,7 @@ class DrawHand(Hand):
|
|||
hhc.compilePlayerRegexs(self)
|
||||
hhc.markStreets(self)
|
||||
hhc.readBlinds(self)
|
||||
hhc.readAntes(self)
|
||||
hhc.readButton(self)
|
||||
hhc.readHeroCards(self)
|
||||
hhc.readShowdownActions(self)
|
||||
|
@ -877,6 +912,7 @@ class DrawHand(Hand):
|
|||
for street in self.streetList:
|
||||
if self.streets[street]:
|
||||
hhc.readAction(self, street)
|
||||
self.pot.markTotal(street)
|
||||
hhc.readCollectPot(self)
|
||||
hhc.readShownCards(self)
|
||||
self.totalPot() # finalise it (total the pot)
|
||||
|
@ -895,9 +931,9 @@ class DrawHand(Hand):
|
|||
# - this is a bet of 1 sb, as yet uncalled.
|
||||
# Player in the big blind posts
|
||||
# - this is a call of 1 sb and a raise to 1 bb
|
||||
#
|
||||
|
||||
logging.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
|
||||
#
|
||||
|
||||
log.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
|
||||
if player is not None:
|
||||
self.bets['DEAL'][player].append(Decimal(amount))
|
||||
self.stacks[player] -= Decimal(amount)
|
||||
|
@ -906,7 +942,7 @@ class DrawHand(Hand):
|
|||
self.actions['BLINDSANTES'].append(act)
|
||||
self.pot.addMoney(player, Decimal(amount))
|
||||
if blindtype == 'big blind':
|
||||
self.lastBet['DEAL'] = Decimal(amount)
|
||||
self.lastBet['DEAL'] = Decimal(amount)
|
||||
elif blindtype == 'both':
|
||||
# extra small blind is 'dead'
|
||||
self.lastBet['DEAL'] = Decimal(self.bb)
|
||||
|
@ -924,7 +960,7 @@ class DrawHand(Hand):
|
|||
|
||||
|
||||
def discardDrawHoleCards(self, cards, player, street):
|
||||
logging.debug("discardDrawHoleCards '%s' '%s' '%s'" % (cards, player, street))
|
||||
log.debug("discardDrawHoleCards '%s' '%s' '%s'" % (cards, player, street))
|
||||
self.discards[street][player] = set([cards])
|
||||
|
||||
|
||||
|
@ -937,6 +973,14 @@ class DrawHand(Hand):
|
|||
act = (player, 'discards', num)
|
||||
self.actions[street].append(act)
|
||||
|
||||
def getStreetTotals(self):
|
||||
# street1Pot INT, /* pot size at flop/street4 */
|
||||
# street2Pot INT, /* pot size at turn/street5 */
|
||||
# street3Pot INT, /* pot size at river/street6 */
|
||||
# street4Pot INT, /* pot size at sd/street7 */
|
||||
# showdownPot INT, /* pot size at sd/street7 */
|
||||
return (0,0,0,0,0)
|
||||
|
||||
|
||||
def writeHand(self, fh=sys.__stdout__):
|
||||
# PokerStars format.
|
||||
|
@ -1018,17 +1062,17 @@ class StudHand(Hand):
|
|||
if gametype['base'] != 'stud':
|
||||
pass # or indeed don't pass and complain instead
|
||||
|
||||
self.allStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
|
||||
self.allStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
|
||||
self.communityStreets = []
|
||||
self.actionStreets = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
|
||||
self.actionStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
|
||||
|
||||
self.streetList = ['ANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
|
||||
self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
|
||||
self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
|
||||
Hand.__init__(self, sitename, gametype, handText)
|
||||
self.sb = gametype['sb']
|
||||
self.bb = gametype['bb']
|
||||
#Populate the StudHand
|
||||
#Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
|
||||
#Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
|
||||
# which then invokes a 'addXXX' callback
|
||||
if builtFrom == "HHC":
|
||||
hhc.readHandInfo(self)
|
||||
|
@ -1040,11 +1084,11 @@ class StudHand(Hand):
|
|||
hhc.readHeroCards(self)
|
||||
# Read actions in street order
|
||||
for street in self.actionStreets:
|
||||
if street == 'ANTES': continue # OMG--sometime someone folds in the ante round
|
||||
if street == 'BLINDSANTES': continue # OMG--sometime someone folds in the ante round
|
||||
if self.streets[street]:
|
||||
logging.debug(street)
|
||||
logging.debug(self.streets[street])
|
||||
log.debug(street + self.streets[street])
|
||||
hhc.readAction(self, street)
|
||||
self.pot.markTotal(street)
|
||||
hhc.readCollectPot(self)
|
||||
hhc.readShownCards(self) # not done yet
|
||||
self.totalPot() # finalise it (total the pot)
|
||||
|
@ -1075,7 +1119,7 @@ street (string) the street name (in streetList)
|
|||
open list of card bigrams e.g. ['2h','Jc'], dealt face up
|
||||
closed likewise, but known only to player
|
||||
"""
|
||||
logging.debug("addPlayerCards %s, o%s x%s" % (player, open, closed))
|
||||
log.debug("addPlayerCards %s, o%s x%s" % (player, open, closed))
|
||||
try:
|
||||
self.checkPlayerExists(player)
|
||||
self.holecards[street][player] = (open, closed)
|
||||
|
@ -1089,7 +1133,7 @@ closed likewise, but known only to player
|
|||
"""\
|
||||
Add a complete on [street] by [player] to [amountTo]
|
||||
"""
|
||||
logging.debug("%s %s completes %s" % (street, player, amountTo))
|
||||
log.debug("%s %s completes %s" % (street, player, amountTo))
|
||||
amountTo = re.sub(u',', u'', amountTo) #some sites have commas
|
||||
self.checkPlayerExists(player)
|
||||
Bp = self.lastBet['THIRD']
|
||||
|
@ -1104,10 +1148,10 @@ Add a complete on [street] by [player] to [amountTo]
|
|||
#~ self.actions[street].append(act)
|
||||
#~ self.lastBet[street] = Rt # TODO check this is correct
|
||||
#~ self.pot.addMoney(player, C+Rb)
|
||||
|
||||
|
||||
def addBringIn(self, player, bringin):
|
||||
if player is not None:
|
||||
logging.debug("Bringin: %s, %s" % (player , bringin))
|
||||
log.debug("Bringin: %s, %s" % (player , bringin))
|
||||
self.bets['THIRD'][player].append(Decimal(bringin))
|
||||
self.stacks[player] -= Decimal(bringin)
|
||||
act = (player, 'bringin', bringin, self.stacks[player]==0)
|
||||
|
@ -1115,20 +1159,28 @@ Add a complete on [street] by [player] to [amountTo]
|
|||
self.lastBet['THIRD'] = Decimal(bringin)
|
||||
self.pot.addMoney(player, Decimal(bringin))
|
||||
|
||||
def getStreetTotals(self):
|
||||
# street1Pot INT, /* pot size at flop/street4 */
|
||||
# street2Pot INT, /* pot size at turn/street5 */
|
||||
# street3Pot INT, /* pot size at river/street6 */
|
||||
# street4Pot INT, /* pot size at sd/street7 */
|
||||
# showdownPot INT, /* pot size at sd/street7 */
|
||||
return (0,0,0,0,0)
|
||||
|
||||
|
||||
def writeHand(self, fh=sys.__stdout__):
|
||||
# PokerStars format.
|
||||
|
||||
super(StudHand, self).writeHand(fh)
|
||||
|
||||
players_who_post_antes = set([x[0] for x in self.actions['ANTES']])
|
||||
|
||||
players_who_post_antes = set([x[0] for x in self.actions['BLINDSANTES']])
|
||||
|
||||
for player in [x for x in self.players if x[1] in players_who_post_antes]:
|
||||
#Only print stacks of players who do something preflop
|
||||
print >>fh, _("Seat %s: %s (%s%s in chips)" %(player[0], player[1], self.sym, player[2]))
|
||||
|
||||
if 'ANTES' in self.actions:
|
||||
for act in self.actions['ANTES']:
|
||||
if 'BLINDSANTES' in self.actions:
|
||||
for act in self.actions['BLINDSANTES']:
|
||||
print >>fh, _("%s: posts the ante %s%s" %(act[0], self.sym, act[3]))
|
||||
|
||||
if 'THIRD' in self.actions:
|
||||
|
@ -1251,7 +1303,7 @@ Add a complete on [street] by [player] to [amountTo]
|
|||
else:
|
||||
return hc + " ".join(self.holecards[street][player][0]) + ']'
|
||||
|
||||
if street == 'SEVENTH' and player != self.hero: return # only write 7th st line for hero, LDO
|
||||
if street == 'SEVENTH' and player != self.hero: return # only write 7th st line for hero, LDO
|
||||
return hc + " ".join(self.holecards[street][player][1]) + "] [" + " ".join(self.holecards[street][player][0]) + "]"
|
||||
|
||||
def join_holecards(self, player):
|
||||
|
@ -1273,30 +1325,43 @@ class Pot(object):
|
|||
|
||||
|
||||
def __init__(self):
|
||||
self.contenders = set()
|
||||
self.committed = {}
|
||||
self.total = None
|
||||
self.returned = {}
|
||||
self.sym = u'$' # this is the default currency symbol
|
||||
self.contenders = set()
|
||||
self.committed = {}
|
||||
self.streettotals = {}
|
||||
self.common = Decimal(0)
|
||||
self.total = None
|
||||
self.returned = {}
|
||||
self.sym = u'$' # this is the default currency symbol
|
||||
|
||||
def setSym(self, sym):
|
||||
self.sym = sym
|
||||
|
||||
|
||||
def addPlayer(self,player):
|
||||
self.committed[player] = Decimal(0)
|
||||
|
||||
|
||||
def addFold(self, player):
|
||||
# addFold must be called when a player folds
|
||||
self.contenders.discard(player)
|
||||
|
||||
|
||||
def addCommonMoney(self, amount):
|
||||
self.common += amount
|
||||
|
||||
def addMoney(self, player, amount):
|
||||
# addMoney must be called for any actions that put money in the pot, in the order they occur
|
||||
self.contenders.add(player)
|
||||
self.committed[player] += amount
|
||||
|
||||
def markTotal(self, street):
|
||||
self.streettotals[street] = sum(self.committed.values()) + self.common
|
||||
|
||||
def getTotalAtStreet(self, street):
|
||||
if street in self.streettotals:
|
||||
return self.streettotals[street]
|
||||
return 0
|
||||
|
||||
def end(self):
|
||||
self.total = sum(self.committed.values())
|
||||
|
||||
self.total = sum(self.committed.values()) + self.common
|
||||
|
||||
# Return any uncalled bet.
|
||||
committed = sorted([ (v,k) for (k,v) in self.committed.items()])
|
||||
lastbet = committed[-1][0] - committed[-2][0]
|
||||
|
@ -1314,7 +1379,7 @@ class Pot(object):
|
|||
self.pots = []
|
||||
while len(commitsall) > 0:
|
||||
commitslive = [(v,k) for (v,k) in commitsall if k in self.contenders]
|
||||
v1 = commitslive[0][0]
|
||||
v1 = commitslive[0][0]
|
||||
self.pots += [sum([min(v,v1) for (v,k) in commitsall])]
|
||||
commitsall = [((v-v1),k) for (v,k) in commitsall if v-v1 >0]
|
||||
|
||||
|
@ -1324,7 +1389,7 @@ class Pot(object):
|
|||
# and y+z+r = x
|
||||
# for example:
|
||||
# Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2
|
||||
|
||||
|
||||
def __str__(self):
|
||||
if self.sym is None:
|
||||
self.sym = "C"
|
||||
|
@ -1332,17 +1397,17 @@ class Pot(object):
|
|||
print "call Pot.end() before printing pot total"
|
||||
# NB if I'm sure end() is idempotent, call it here.
|
||||
raise FpdbParseError
|
||||
|
||||
|
||||
ret = "Total pot %s%.2f" % (self.sym, self.total)
|
||||
if len(self.pots) < 2:
|
||||
return ret;
|
||||
ret += " Main pot %s%.2f" % (self.sym, self.pots[0])
|
||||
|
||||
|
||||
return ret + ''.join([ (" Side pot %s%.2f." % (self.sym, self.pots[x]) ) for x in xrange(1, len(self.pots)) ])
|
||||
|
||||
def assemble(cnxn, handid):
|
||||
c = cnxn.cursor()
|
||||
|
||||
|
||||
# We need at least sitename, gametype, handid
|
||||
# for the Hand.__init__
|
||||
c.execute("""
|
||||
|
@ -1385,10 +1450,10 @@ limit 1""", {'handid':handid})
|
|||
h.setCommunityCards('FLOP', cards[0:3])
|
||||
if cards[3]:
|
||||
h.setCommunityCards('TURN', [cards[3]])
|
||||
if cards[4]:
|
||||
if cards[4]:
|
||||
h.setCommunityCards('RIVER', [cards[4]])
|
||||
#[Card.valueSuitFromCard(x) for x in cards]
|
||||
|
||||
|
||||
# HandInfo : HID, TABLE
|
||||
# BUTTON - why is this treated specially in Hand?
|
||||
# answer: it is written out in hand histories
|
||||
|
@ -1406,7 +1471,7 @@ WHERE h.id = %(handid)s
|
|||
h.handid = res[0]
|
||||
h.tablename = res[1]
|
||||
h.starttime = res[2] # automatically a datetime
|
||||
|
||||
|
||||
# PlayerStacks
|
||||
c.execute("""
|
||||
SELECT
|
||||
|
@ -1431,7 +1496,7 @@ and p.id = hp.playerid
|
|||
h.addCollectPot(name, winnings)
|
||||
if position == 'B':
|
||||
h.buttonpos = seat
|
||||
|
||||
|
||||
|
||||
# actions
|
||||
c.execute("""
|
||||
|
@ -1479,7 +1544,7 @@ ORDER BY
|
|||
#hc.readShownCards(self)
|
||||
h.totalPot()
|
||||
h.rake = h.totalpot - h.totalcollected
|
||||
|
||||
|
||||
|
||||
return h
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008 Carl Gherardi
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
|
@ -16,10 +17,10 @@
|
|||
#agpl-3.0.txt in the docs folder of the package.
|
||||
|
||||
import Hand
|
||||
import Tourney
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import os.path
|
||||
|
@ -30,25 +31,47 @@ import operator
|
|||
from xml.dom.minidom import Node
|
||||
import time
|
||||
import datetime
|
||||
from Exceptions import FpdbParseError
|
||||
|
||||
import gettext
|
||||
gettext.install('fpdb')
|
||||
|
||||
import logging, logging.config
|
||||
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
|
||||
log = logging.getLogger("parser")
|
||||
|
||||
class HandHistoryConverter():
|
||||
|
||||
READ_CHUNK_SIZE = 10000 # bytes to read at a time from file (in tail mode)
|
||||
def __init__(self, in_path = '-', out_path = '-', sitename = None, follow=False, index=0):
|
||||
logging.info("HandHistory init")
|
||||
READ_CHUNK_SIZE = 10000 # bytes to read at a time from file in tail mode
|
||||
|
||||
# filetype can be "text" or "xml"
|
||||
# so far always "text"
|
||||
# subclass HHC_xml for xml parsing
|
||||
filetype = "text"
|
||||
|
||||
# codepage indicates the encoding of the text file.
|
||||
# cp1252 is a safe default
|
||||
# "utf_8" is more likely if there are funny characters
|
||||
codepage = "cp1252"
|
||||
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow=False, index=0, autostart=True):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
out_path (default '-' = sys.stdout)
|
||||
follow : whether to tail -f the input"""
|
||||
|
||||
log.info("HandHistory init - %s subclass, in_path '%s'; out_path '%s'" % (self.sitename, in_path, out_path) )
|
||||
|
||||
# default filetype and codepage. Subclasses should set these properly.
|
||||
self.filetype = "text"
|
||||
self.codepage = "utf8"
|
||||
self.index = 0
|
||||
|
||||
self.in_path = in_path
|
||||
self.out_path = out_path
|
||||
|
||||
self.processedHands = []
|
||||
|
||||
# Tourney object used to store TourneyInfo when called to deal with a Summary file
|
||||
self.tourney = None
|
||||
|
||||
if in_path == '-':
|
||||
self.in_fh = sys.stdin
|
||||
|
@ -58,50 +81,91 @@ class HandHistoryConverter():
|
|||
else:
|
||||
# TODO: out_path should be sanity checked.
|
||||
out_dir = os.path.dirname(self.out_path)
|
||||
if not os.path.isdir(out_dir):
|
||||
logging.info("Creatin directory '%s'" % out_dir)
|
||||
if not os.path.isdir(out_dir) and out_dir != '':
|
||||
log.info("Creating directory '%s'" % out_dir)
|
||||
os.makedirs(out_dir)
|
||||
self.out_fh = open(self.out_path, 'w')
|
||||
try:
|
||||
self.out_fh = codecs.open(self.out_path, 'w', 'cp1252')
|
||||
log.debug("out_path %s opened as %s" % (self.out_path, self.out_fh))
|
||||
except:
|
||||
log.error("out_path %s couldn't be opened" % (self.out_path))
|
||||
|
||||
self.sitename = sitename
|
||||
self.follow = follow
|
||||
self.compiledPlayers = set()
|
||||
self.maxseats = 10
|
||||
|
||||
self.status = True
|
||||
|
||||
self.parsedObjectType = "HH" #default behaviour : parsing HH files, can be "Summary" if the parsing encounters a Summary File
|
||||
|
||||
if autostart:
|
||||
self.start()
|
||||
|
||||
def __str__(self):
|
||||
return """
|
||||
HandHistoryConverter: '%(sitename)s'
|
||||
filetype: '%(filetype)s'
|
||||
in_path: '%(in_path)s'
|
||||
out_path: '%(out_path)s'
|
||||
""" % { 'sitename':self.sitename, 'filetype':self.filetype, 'in_path':self.in_path, 'out_path':self.out_path }
|
||||
filetype '%(filetype)s'
|
||||
in_path '%(in_path)s'
|
||||
out_path '%(out_path)s'
|
||||
follow '%(follow)s'
|
||||
""" % locals()
|
||||
|
||||
def start(self):
|
||||
"""process a hand at a time from the input specified by in_path.
|
||||
"""Process a hand at a time from the input specified by in_path.
|
||||
If in follow mode, wait for more data to turn up.
|
||||
Otherwise, finish at eof.
|
||||
Otherwise, finish at EOF.
|
||||
|
||||
"""
|
||||
starttime = time.time()
|
||||
if not self.sanityCheck():
|
||||
print "Cowardly refusing to continue after failed sanity check"
|
||||
log.warning("Failed sanity check")
|
||||
return
|
||||
|
||||
if self.follow:
|
||||
try:
|
||||
numHands = 0
|
||||
for handText in self.tailHands():
|
||||
numHands+=1
|
||||
self.processHand(handText)
|
||||
else:
|
||||
handsList = self.allHandsAsList()
|
||||
logging.info("Parsing %d hands" % len(handsList))
|
||||
for handText in handsList:
|
||||
self.processedHands.append(self.processHand(handText))
|
||||
numHands= len(handsList)
|
||||
endtime = time.time()
|
||||
print "read %d hands in %.3f seconds" % (numHands, endtime - starttime)
|
||||
if self.out_fh != sys.stdout:
|
||||
self.out_fh.close()
|
||||
numErrors = 0
|
||||
if self.follow:
|
||||
#TODO: See how summary files can be handled on the fly (here they should be rejected as before)
|
||||
log.info("Tailing '%s'" % self.in_path)
|
||||
for handText in self.tailHands():
|
||||
try:
|
||||
self.processHand(handText)
|
||||
numHands+=1
|
||||
except FpdbParseError, e:
|
||||
numErrors+=1
|
||||
log.warning("Failed to convert hand %s" % e.hid)
|
||||
log.debug(handText)
|
||||
else:
|
||||
handsList = self.allHandsAsList()
|
||||
log.info("Parsing %d hands" % len(handsList))
|
||||
# Determine if we're dealing with a HH file or a Summary file
|
||||
# quick fix : empty files make the handsList[0] fail ==> If empty file, go on with HH parsing
|
||||
if len(handsList) == 0 or self.isSummary(handsList[0]) == False:
|
||||
self.parsedObjectType = "HH"
|
||||
for handText in handsList:
|
||||
try:
|
||||
self.processedHands.append(self.processHand(handText))
|
||||
except FpdbParseError, e:
|
||||
numErrors+=1
|
||||
log.warning("Failed to convert hand %s" % e.hid)
|
||||
log.debug(handText)
|
||||
numHands = len(handsList)
|
||||
endtime = time.time()
|
||||
log.info("Read %d hands (%d failed) in %.3f seconds" % (numHands, numErrors, endtime - starttime))
|
||||
else:
|
||||
self.parsedObjectType = "Summary"
|
||||
summaryParsingStatus = self.readSummaryInfo(handsList)
|
||||
endtime = time.time()
|
||||
if summaryParsingStatus :
|
||||
log.info("Summary file '%s' correctly parsed (took %.3f seconds)" % (self.in_path, endtime - starttime))
|
||||
else :
|
||||
log.warning("Error converting summary file '%s' (took %.3f seconds)" % (self.in_path, endtime - starttime))
|
||||
|
||||
except IOError, ioe:
|
||||
log.exception("Error converting '%s'" % self.in_path)
|
||||
finally:
|
||||
if self.out_fh != sys.stdout:
|
||||
self.out_fh.close()
|
||||
|
||||
|
||||
def tailHands(self):
|
||||
|
@ -128,7 +192,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
|
|||
time.sleep(interval)
|
||||
fd.seek(where)
|
||||
else:
|
||||
logging.debug("%s changed inode numbers from %d to %d" % (self.in_path, fd_results[1], st_results[1]))
|
||||
log.debug("%s changed inode numbers from %d to %d" % (self.in_path, fd_results[1], st_results[1]))
|
||||
fd = codecs.open(self.in_path, 'r', self.codepage)
|
||||
fd.seek(where)
|
||||
else:
|
||||
|
@ -173,13 +237,13 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
|
|||
self.obs = self.obs.strip()
|
||||
self.obs = self.obs.replace('\r\n', '\n')
|
||||
if self.obs == "" or self.obs == None:
|
||||
logging.info("Read no hands.")
|
||||
return
|
||||
log.info("Read no hands.")
|
||||
return []
|
||||
return re.split(self.re_SplitHands, self.obs)
|
||||
|
||||
def processHand(self, handText):
|
||||
gametype = self.determineGameType(handText)
|
||||
logging.debug("gametype %s" % gametype)
|
||||
log.debug("gametype %s" % gametype)
|
||||
hand = None
|
||||
if gametype is None:
|
||||
l = None
|
||||
|
@ -194,14 +258,14 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
|
|||
l = [type] + [base] + [limit]
|
||||
if l in self.readSupportedGames():
|
||||
if gametype['base'] == 'hold':
|
||||
logging.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)")
|
||||
log.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)")
|
||||
hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handText)
|
||||
elif gametype['base'] == 'stud':
|
||||
hand = Hand.StudHand(self, self.sitename, gametype, handText)
|
||||
elif gametype['base'] == 'draw':
|
||||
hand = Hand.DrawHand(self, self.sitename, gametype, handText)
|
||||
else:
|
||||
logging.info("Unsupported game type: %s" % gametype)
|
||||
log.info("Unsupported game type: %s" % gametype)
|
||||
|
||||
if hand:
|
||||
# uncomment these to calculate some stats
|
||||
|
@ -210,7 +274,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
|
|||
hand.writeHand(self.out_fh)
|
||||
return hand
|
||||
else:
|
||||
logging.info("Unsupported game type: %s" % gametype)
|
||||
log.info("Unsupported game type: %s" % gametype)
|
||||
# TODO: pity we don't know the HID at this stage. Log the entire hand?
|
||||
# From the log we can deduce that it is the hand after the one before :)
|
||||
|
||||
|
@ -335,27 +399,36 @@ or None if we fail to get the info """
|
|||
hands = hands + [Hand.Hand(self.sitename, self.gametype, l)]
|
||||
return hands
|
||||
|
||||
def __listof(self, x):
|
||||
if isinstance(x, list) or isinstance(x, tuple): return x
|
||||
else: return [x]
|
||||
|
||||
def readFile(self):
|
||||
"""open in_path according to self.codepage"""
|
||||
"""Open in_path according to self.codepage. Exceptions caught further up"""
|
||||
|
||||
if(self.filetype == "text"):
|
||||
if self.in_path == '-':
|
||||
# read from stdin
|
||||
logging.debug("Reading stdin with %s" % self.codepage) # is this necessary? or possible? or what?
|
||||
log.debug("Reading stdin with %s" % self.codepage) # is this necessary? or possible? or what?
|
||||
in_fh = codecs.getreader('cp1252')(sys.stdin)
|
||||
else:
|
||||
logging.debug("Opening %s with %s" % (self.in_path, self.codepage))
|
||||
in_fh = codecs.open(self.in_path, 'r', self.codepage)
|
||||
in_fh.seek(self.index)
|
||||
self.obs = in_fh.read()
|
||||
self.index = in_fh.tell()
|
||||
in_fh.close()
|
||||
success = False
|
||||
for kodec in self.__listof(self.codepage):
|
||||
if success: break
|
||||
print "trying", kodec
|
||||
try:
|
||||
in_fh = codecs.open(self.in_path, 'r', kodec)
|
||||
in_fh.seek(self.index)
|
||||
log.debug("Opened in_path: '%s' with %s" % (self.in_path, kodec))
|
||||
self.obs = in_fh.read()
|
||||
self.index = in_fh.tell()
|
||||
in_fh.close()
|
||||
success = True
|
||||
except:
|
||||
pass
|
||||
elif(self.filetype == "xml"):
|
||||
try:
|
||||
doc = xml.dom.minidom.parse(filename)
|
||||
self.doc = doc
|
||||
except:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
doc = xml.dom.minidom.parse(filename)
|
||||
self.doc = doc
|
||||
|
||||
def guessMaxSeats(self, hand):
|
||||
"""Return a guess at max_seats when not specified in HH."""
|
||||
|
@ -383,7 +456,7 @@ or None if we fail to get the info """
|
|||
|
||||
def getStatus(self):
|
||||
#TODO: Return a status of true if file processed ok
|
||||
return True
|
||||
return self.status
|
||||
|
||||
def getProcessedHands(self):
|
||||
return self.processedHands
|
||||
|
@ -393,3 +466,15 @@ or None if we fail to get the info """
|
|||
|
||||
def getLastCharacterRead(self):
|
||||
return self.index
|
||||
|
||||
def isSummary(self, topline):
|
||||
return " Tournament Summary " in topline
|
||||
|
||||
def getParsedObjectType(self):
|
||||
return self.parsedObjectType
|
||||
|
||||
#returns a status (True/False) indicating wether the parsing could be done correctly or not
|
||||
def readSummaryInfo(self, summaryInfoList): abstract
|
||||
|
||||
def getTourney(self):
|
||||
return self.tourney
|
||||
|
|
282
pyfpdb/Hud.py
282
pyfpdb/Hud.py
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Hud.py
|
||||
|
||||
Create and manage the hud overlays.
|
||||
|
@ -60,6 +61,8 @@ class Hud:
|
|||
def __init__(self, parent, table, max, poker_game, config, db_connection):
|
||||
# __init__ is (now) intended to be called from the stdin thread, so it
|
||||
# cannot touch the gui
|
||||
if parent == None: # running from cli ..
|
||||
self.parent = self
|
||||
self.parent = parent
|
||||
self.table = table
|
||||
self.config = config
|
||||
|
@ -77,6 +80,10 @@ class Hud:
|
|||
|
||||
(font, font_size) = config.get_default_font(self.table.site)
|
||||
self.colors = config.get_default_colors(self.table.site)
|
||||
|
||||
self.backgroundcolor = gtk.gdk.color_parse(self.colors['hudbgcolor'])
|
||||
self.foregroundcolor = gtk.gdk.color_parse(self.colors['hudfgcolor'])
|
||||
|
||||
|
||||
if font == None:
|
||||
font = "Sans"
|
||||
|
@ -93,91 +100,107 @@ class Hud:
|
|||
if my_import == None:
|
||||
continue
|
||||
self.aux_windows.append(my_import(self, config, aux_params))
|
||||
|
||||
self.creation_attrs = None
|
||||
|
||||
def create_mw(self):
|
||||
|
||||
# Set up a main window for this this instance of the HUD
|
||||
self.main_window = gtk.Window()
|
||||
self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC)
|
||||
self.main_window.set_title("%s FPDBHUD" % (self.table.name))
|
||||
self.main_window.set_decorated(False)
|
||||
self.main_window.set_opacity(self.colors["hudopacity"])
|
||||
self.main_window.set_focus_on_map(False)
|
||||
|
||||
self.ebox = gtk.EventBox()
|
||||
self.label = gtk.Label("FPDB Menu (Right Click)\nLeft-drag to move")
|
||||
|
||||
self.backgroundcolor = gtk.gdk.color_parse(self.colors['hudbgcolor'])
|
||||
self.foregroundcolor = gtk.gdk.color_parse(self.colors['hudfgcolor'])
|
||||
|
||||
self.label.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
|
||||
self.label.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
|
||||
|
||||
self.main_window.add(self.ebox)
|
||||
self.ebox.add(self.label)
|
||||
|
||||
self.ebox.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
|
||||
self.ebox.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
|
||||
win = gtk.Window()
|
||||
win.set_gravity(gtk.gdk.GRAVITY_STATIC)
|
||||
win.set_title("%s FPDBHUD" % (self.table.name))
|
||||
win.set_decorated(False)
|
||||
win.set_opacity(self.colors["hudopacity"])
|
||||
|
||||
eventbox = gtk.EventBox()
|
||||
label = gtk.Label("FPDB Menu - Right click\nLeft-Drag to Move")
|
||||
|
||||
win.add(eventbox)
|
||||
eventbox.add(label)
|
||||
|
||||
label.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
|
||||
label.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
|
||||
|
||||
eventbox.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
|
||||
eventbox.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
|
||||
|
||||
self.main_window = win
|
||||
self.main_window.move(self.table.x, self.table.y)
|
||||
|
||||
# A popup menu for the main window
|
||||
self.menu = gtk.Menu()
|
||||
self.item1 = gtk.MenuItem('Kill this HUD')
|
||||
self.menu.append(self.item1)
|
||||
self.item1.connect("activate", self.parent.kill_hud, self.table_name)
|
||||
self.item1.show()
|
||||
menu = gtk.Menu()
|
||||
|
||||
self.item2 = gtk.MenuItem('Save Layout')
|
||||
self.menu.append(self.item2)
|
||||
self.item2.connect("activate", self.save_layout)
|
||||
self.item2.show()
|
||||
killitem = gtk.MenuItem('Kill This HUD')
|
||||
menu.append(killitem)
|
||||
if self.parent != None:
|
||||
killitem.connect("activate", self.parent.kill_hud, self.table_name)
|
||||
|
||||
self.item3 = gtk.MenuItem('Reposition Stats')
|
||||
self.menu.append(self.item3)
|
||||
self.item3.connect("activate", self.reposition_windows)
|
||||
self.item3.show()
|
||||
saveitem = gtk.MenuItem('Save HUD Layout')
|
||||
menu.append(saveitem)
|
||||
saveitem.connect("activate", self.save_layout)
|
||||
|
||||
self.item4 = gtk.MenuItem('Debug Stat Windows')
|
||||
self.menu.append(self.item4)
|
||||
self.item4.connect("activate", self.debug_stat_windows)
|
||||
self.item4.show()
|
||||
repositem = gtk.MenuItem('Reposition StatWindows')
|
||||
menu.append(repositem)
|
||||
repositem.connect("activate", self.reposition_windows)
|
||||
|
||||
self.ebox.connect_object("button-press-event", self.on_button_press, self.menu)
|
||||
debugitem = gtk.MenuItem('Debug StatWindows')
|
||||
menu.append(debugitem)
|
||||
debugitem.connect("activate", self.debug_stat_windows)
|
||||
|
||||
item5 = gtk.MenuItem('Set max seats')
|
||||
menu.append(item5)
|
||||
maxSeatsMenu = gtk.Menu()
|
||||
item5.set_submenu(maxSeatsMenu)
|
||||
for i in range(2, 11, 1):
|
||||
item = gtk.MenuItem('%d-max' % i)
|
||||
item.ms = i
|
||||
maxSeatsMenu.append(item)
|
||||
item.connect("activate", self.change_max_seats)
|
||||
setattr(self, 'maxSeatsMenuItem%d' % (i-1), item)
|
||||
|
||||
eventbox.connect_object("button-press-event", self.on_button_press, menu)
|
||||
|
||||
self.main_window.show_all()
|
||||
self.mw_created = True
|
||||
|
||||
# TODO: fold all uses of this type of 'topify' code into a single function, if the differences between the versions don't
|
||||
# create adverse effects?
|
||||
|
||||
if os.name == 'nt':
|
||||
self.topify_window(self.main_window)
|
||||
else:
|
||||
self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gets a gdk handle for poker client
|
||||
self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) # gets a gdk handle for the hud table window
|
||||
self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) #
|
||||
|
||||
self.update_table_position()
|
||||
|
||||
self.label = label
|
||||
menu.show_all()
|
||||
self.main_window.show_all()
|
||||
self.topify_window(self.main_window)
|
||||
|
||||
def change_max_seats(self, widget):
|
||||
if self.max != widget.ms:
|
||||
print 'change_max_seats', widget.ms
|
||||
self.max = widget.ms
|
||||
try:
|
||||
self.kill()
|
||||
self.create(*self.creation_attrs)
|
||||
self.update(self.hand, self.config)
|
||||
except Exception, e:
|
||||
print "Expcetion:",str(e)
|
||||
pass
|
||||
|
||||
def update_table_position(self):
|
||||
if os.name == 'nt':
|
||||
if not win32gui.IsWindow(self.table.number):
|
||||
self.parent.kill_hud(self, self.table.name)
|
||||
return False
|
||||
# anyone know how to do this in unix, or better yet, trap the X11 error that is triggered when executing the get_origin() for a closed window?
|
||||
|
||||
(x, y) = self.main_window.parentgdkhandle.get_origin()
|
||||
if self.table.x != x or self.table.y != y:
|
||||
self.table.x = x
|
||||
self.table.y = y
|
||||
self.main_window.move(x, y)
|
||||
adj = self.adj_seats(self.hand, self.config)
|
||||
loc = self.config.get_locations(self.table.site, self.max)
|
||||
# TODO: is stat_windows getting converted somewhere from a list to a dict, for no good reason?
|
||||
for i, w in enumerate(self.stat_windows.itervalues()):
|
||||
(x, y) = loc[adj[i+1]]
|
||||
w.relocate(x, y)
|
||||
if self.table.gdkhandle is not None:
|
||||
(x, y) = self.table.gdkhandle.get_origin()
|
||||
if self.table.x != x or self.table.y != y:
|
||||
self.table.x = x
|
||||
self.table.y = y
|
||||
self.main_window.move(x, y)
|
||||
adj = self.adj_seats(self.hand, self.config)
|
||||
loc = self.config.get_locations(self.table.site, self.max)
|
||||
# TODO: is stat_windows getting converted somewhere from a list to a dict, for no good reason?
|
||||
for i, w in enumerate(self.stat_windows.itervalues()):
|
||||
(x, y) = loc[adj[i+1]]
|
||||
w.relocate(x, y)
|
||||
|
||||
# While we're at it, fix the positions of mucked cards too
|
||||
for aux in self.aux_windows:
|
||||
aux.update_card_positions()
|
||||
|
||||
return True
|
||||
|
||||
def on_button_press(self, widget, event):
|
||||
|
@ -202,6 +225,7 @@ class Hud:
|
|||
self.aux_windows = []
|
||||
|
||||
def reposition_windows(self, *args):
|
||||
self.update_table_position()
|
||||
for w in self.stat_windows.itervalues():
|
||||
if type(w) == int:
|
||||
# print "in reposition, w =", w
|
||||
|
@ -233,7 +257,7 @@ class Hud:
|
|||
# Need range here, not xrange -> need the actual list
|
||||
adj = range(0, self.max + 1) # default seat adjustments = no adjustment
|
||||
# does the user have a fav_seat?
|
||||
if int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0:
|
||||
if self.table.site != None and int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0:
|
||||
try:
|
||||
fav_seat = config.supported_sites[self.table.site].layout[self.max].fav_seat
|
||||
actual_seat = self.get_actual_seat(config.supported_sites[self.table.site].screen_name)
|
||||
|
@ -261,6 +285,8 @@ class Hud:
|
|||
#
|
||||
# this method also manages the creating and destruction of stat
|
||||
# windows via calls to the Stat_Window class
|
||||
self.creation_attrs = hand, config, stat_dict, cards
|
||||
|
||||
self.hand = hand
|
||||
if not self.mw_created:
|
||||
self.create_mw()
|
||||
|
@ -333,30 +359,22 @@ class Hud:
|
|||
Stats.do_tip(window.e_box[r][c], tip)
|
||||
|
||||
def topify_window(self, window):
|
||||
# """Set the specified gtk window to stayontop in MS Windows."""
|
||||
#
|
||||
# def windowEnumerationHandler(hwnd, resultList):
|
||||
# '''Callback for win32gui.EnumWindows() to generate list of window handles.'''
|
||||
# resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
|
||||
# unique_name = 'unique name for finding this window'
|
||||
# real_name = window.get_title()
|
||||
# window.set_title(unique_name)
|
||||
# tl_windows = []
|
||||
# win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
|
||||
#
|
||||
# for w in tl_windows:
|
||||
# if w[1] == unique_name:
|
||||
self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number))
|
||||
# self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0])
|
||||
self.main_window.gdkhandle = self.main_window.window
|
||||
self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle)
|
||||
|
||||
style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE)
|
||||
style |= win32con.WS_CLIPCHILDREN
|
||||
win32gui.SetWindowLong(self.table.number, win32con.GWL_EXSTYLE, style)
|
||||
# break
|
||||
|
||||
# window.set_title(real_name)
|
||||
window.set_focus_on_map(False)
|
||||
window.set_accept_focus(False)
|
||||
|
||||
if not self.table.gdkhandle:
|
||||
self.table.gdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gtk handle to poker window
|
||||
# window.window.reparent(self.table.gdkhandle, 0, 0)
|
||||
# window.map()
|
||||
# window.window.set_transient_for(self.table.gdkhandle)
|
||||
# if os.name == "nt":
|
||||
# print "window.window.handle=",window.window.handle
|
||||
# oldparent = win32gui.SetParent(window.window.handle, self.table.number)
|
||||
# print "oldparent=",oldparent
|
||||
# win32gui.SendMessage(self.table.number, 0x0127) # WM_CHANGEUISTATE
|
||||
# win32gui.SendMessage(self.table.number, 0x0128) # WM_UPDATEUISTATE
|
||||
# window.present()
|
||||
|
||||
|
||||
class Stat_Window:
|
||||
|
||||
|
@ -366,21 +384,33 @@ class Stat_Window:
|
|||
# and double-clicks.
|
||||
|
||||
if event.button == 3: # right button event
|
||||
self.popups.append(Popup_window(widget, self))
|
||||
newpopup = Popup_window(self.window, self)
|
||||
#print "added popup", newpopup
|
||||
# TODO: how should we go about making sure it doesn't open a dozen popups if you click?
|
||||
self.popups.append(newpopup)
|
||||
return True
|
||||
|
||||
if event.button == 2: # middle button event
|
||||
self.window.hide()
|
||||
return True
|
||||
|
||||
if event.button == 1: # left button event
|
||||
# TODO: make position saving save sizes as well?
|
||||
self.window.show_all()
|
||||
if event.state & gtk.gdk.SHIFT_MASK:
|
||||
self.window.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time)
|
||||
else:
|
||||
self.window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time)
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
def noop(self, arga=None, argb=None): # i'm going to try to connect the focus-in and focus-out events here, to see if that fixes any of the focus problems.
|
||||
return True
|
||||
|
||||
def kill_popup(self, popup):
|
||||
print "remove popup", popup
|
||||
self.popups.remove(popup)
|
||||
popup.window.destroy()
|
||||
self.popups.remove(popup)
|
||||
|
||||
def kill_popups(self):
|
||||
map(lambda x: x.window.destroy(), self.popups)
|
||||
|
@ -410,7 +440,6 @@ class Stat_Window:
|
|||
|
||||
self.window.set_title("%s" % seat)
|
||||
self.window.set_property("skip-taskbar-hint", True)
|
||||
self.window.set_transient_for(parent.main_window)
|
||||
self.window.set_focus_on_map(False)
|
||||
|
||||
grid = gtk.Table(rows = game.rows, columns = game.cols, homogeneous = False)
|
||||
|
@ -452,13 +481,35 @@ class Stat_Window:
|
|||
|
||||
e_box[r][c].add(self.label[r][c])
|
||||
e_box[r][c].connect("button_press_event", self.button_press_cb)
|
||||
e_box[r][c].connect("focus-in-event", self.noop)
|
||||
e_box[r][c].connect("focus", self.noop)
|
||||
e_box[r][c].connect("focus-out-event", self.noop)
|
||||
label[r][c].modify_font(font)
|
||||
|
||||
self.window.set_opacity(parent.colors['hudopacity'])
|
||||
self.window.connect("focus", self.noop)
|
||||
self.window.connect("focus-in-event", self.noop)
|
||||
self.window.connect("focus-out-event", self.noop)
|
||||
self.window.connect("button_press_event", self.button_press_cb)
|
||||
self.window.set_focus_on_map(False)
|
||||
self.window.set_accept_focus(False)
|
||||
|
||||
|
||||
self.window.move(self.x, self.y)
|
||||
self.window.realize() # window must be realized before it has a gdkwindow so we can attach it to the table window..
|
||||
self.topify_window(self.window)
|
||||
|
||||
self.window.hide()
|
||||
|
||||
def topify_window(self, window):
|
||||
window.set_focus_on_map(False)
|
||||
window.set_accept_focus(False)
|
||||
|
||||
if not self.table.gdkhandle:
|
||||
self.table.gdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gtk handle to poker window
|
||||
# window.window.reparent(self.table.gdkhandle, 0, 0)
|
||||
window.window.set_transient_for(self.table.gdkhandle)
|
||||
# window.present()
|
||||
|
||||
def destroy(*args): # call back for terminating the main eventloop
|
||||
gtk.main_quit()
|
||||
|
@ -475,6 +526,8 @@ class Popup_window:
|
|||
self.window.set_gravity(gtk.gdk.GRAVITY_STATIC)
|
||||
self.window.set_title("popup")
|
||||
self.window.set_property("skip-taskbar-hint", True)
|
||||
self.window.set_focus_on_map(False)
|
||||
self.window.set_accept_focus(False)
|
||||
self.window.set_transient_for(parent.get_toplevel())
|
||||
|
||||
self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
|
||||
|
@ -540,9 +593,6 @@ class Popup_window:
|
|||
|
||||
self.window.set_transient_for(stat_window.window)
|
||||
|
||||
if os.name == 'nt':
|
||||
self.topify_window(self.window)
|
||||
|
||||
def button_press_cb(self, widget, event, *args):
|
||||
# This handles all callbacks from button presses on the event boxes in
|
||||
# the popup windows. There is a bit of an ugly kludge to separate single-
|
||||
|
@ -555,7 +605,9 @@ class Popup_window:
|
|||
|
||||
if event.button == 3: # right button event
|
||||
self.stat_window.kill_popup(self)
|
||||
return True
|
||||
# self.window.destroy()
|
||||
return False
|
||||
|
||||
def toggle_decorated(self, widget):
|
||||
top = widget.get_toplevel()
|
||||
|
@ -569,27 +621,15 @@ class Popup_window:
|
|||
top.move(x, y)
|
||||
|
||||
def topify_window(self, window):
|
||||
"""Set the specified gtk window to stayontop in MS Windows."""
|
||||
|
||||
# def windowEnumerationHandler(hwnd, resultList):
|
||||
# '''Callback for win32gui.EnumWindows() to generate list of window handles.'''
|
||||
# resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
|
||||
|
||||
# unique_name = 'unique name for finding this window'
|
||||
# real_name = window.get_title()
|
||||
# window.set_title(unique_name)
|
||||
# tl_windows = []
|
||||
# win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
|
||||
window.set_focus_on_map(False)
|
||||
window.set_accept_focus(False)
|
||||
|
||||
if not self.table.gdkhandle:
|
||||
self.table.gdkhandle = gtk.gdk.window_foreign_new(int(self.table.number)) # gtk handle to poker window
|
||||
# window.window.reparent(self.table.gdkhandle, 0, 0)
|
||||
window.window.set_transient_for(self.table.gdkhandle)
|
||||
# window.present()
|
||||
|
||||
# for w in tl_windows:
|
||||
# if w[1] == unique_name:
|
||||
window.set_transient_for(self.parent.main_window)
|
||||
style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE)
|
||||
style |= win32con.WS_CLIPCHILDREN
|
||||
win32gui.SetWindowLong(self.table.number, win32con.GWL_EXSTYLE, style)
|
||||
# break
|
||||
|
||||
# window.set_title(real_name)
|
||||
|
||||
if __name__== "__main__":
|
||||
main_window = gtk.Window()
|
||||
|
@ -604,11 +644,13 @@ if __name__== "__main__":
|
|||
if t is None:
|
||||
print "Table not found."
|
||||
db = Database.Database(c, 'fpdb', 'holdem')
|
||||
|
||||
stat_dict = db.get_stats_from_hand(1)
|
||||
|
||||
# for t in tables:
|
||||
win = Hud(t, 10, 'holdem', c, db)
|
||||
win.create(1, c)
|
||||
win = Hud(None, t, 10, 'holdem', c, db) # parent, table, max, poker_game, config, db_connection
|
||||
win.create(1, c, stat_dict, None) # hand, config, stat_dict, cards):
|
||||
# t.get_details()
|
||||
win.update(8300, db, c)
|
||||
win.update(8300, c) # self, hand, config):
|
||||
|
||||
gtk.main()
|
||||
|
|
|
@ -345,6 +345,22 @@ class Aux_Seats(Aux_Window):
|
|||
def create_contents(self): pass
|
||||
def update_contents(self): pass
|
||||
|
||||
def update_card_positions(self):
|
||||
# self.adj does not exist until .create() has been run
|
||||
try:
|
||||
adj = self.adj
|
||||
except AttributeError:
|
||||
return
|
||||
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
|
||||
for i in (range(1, self.hud.max + 1) + ['common']):
|
||||
if i == 'common':
|
||||
(x, y) = self.params['layout'][self.hud.max].common
|
||||
else:
|
||||
(x, y) = loc[adj[i]]
|
||||
self.positions[i] = self.card_positions(x, self.hud.table.x, y, self.hud.table.y)
|
||||
self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
|
||||
|
||||
|
||||
def create(self):
|
||||
self.adj = self.hud.adj_seats(0, self.config) # move adj_seats to aux and get rid of it in Hud.py
|
||||
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
|
||||
|
@ -362,7 +378,7 @@ class Aux_Seats(Aux_Window):
|
|||
self.m_windows[i].set_transient_for(self.hud.main_window)
|
||||
self.m_windows[i].set_focus_on_map(False)
|
||||
self.m_windows[i].connect("configure_event", self.configure_event_cb, i)
|
||||
self.positions[i] = (int(x) + self.hud.table.x, int(y) + self.hud.table.y)
|
||||
self.positions[i] = self.card_positions(x, self.hud.table.x, y, self.hud.table.y)
|
||||
self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
|
||||
if self.params.has_key('opacity'):
|
||||
self.m_windows[i].set_opacity(float(self.params['opacity']))
|
||||
|
@ -374,6 +390,13 @@ class Aux_Seats(Aux_Window):
|
|||
if self.uses_timer:
|
||||
self.m_windows[i].hide()
|
||||
|
||||
|
||||
def card_positions(self, x, table_x, y, table_y):
|
||||
_x = int(x) + int(table_x)
|
||||
_y = int(y) + int(table_y)
|
||||
return (_x, _y)
|
||||
|
||||
|
||||
def update_gui(self, new_hand_id):
|
||||
"""Update the gui, LDO."""
|
||||
for i in self.m_windows.keys():
|
||||
|
|
404
pyfpdb/PartyPokerToFpdb.py
Normal file → Executable file
404
pyfpdb/PartyPokerToFpdb.py
Normal file → Executable file
|
@ -2,17 +2,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2009, Grigorij Indigirkin
|
||||
#
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
@ -21,43 +21,48 @@
|
|||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
from Exceptions import FpdbParseError
|
||||
from HandHistoryConverter import *
|
||||
|
||||
# PartyPoker HH Format
|
||||
|
||||
class PartyPokerParseError(FpdbParseError):
|
||||
"Usage: raise PartyPokerParseError(<msg>[, hh=<hh>][, hid=<hid>])"
|
||||
def __init__(self, msg='', hh=None, hid=None):
|
||||
if hh is not None:
|
||||
msg += "\n\nHand history attached below:\n" + self.wrapHh(hh)
|
||||
return super(PartyPokerParseError, self).__init__(hid=hid)
|
||||
#return super(PartyPokerParseError, self).__init__(msg, hid=hid)
|
||||
def wrapHh(self, hh):
|
||||
return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
|
||||
{'DELIMETER': '#'*50, 'HH': hh}
|
||||
|
||||
class PartyPoker(HandHistoryConverter):
|
||||
class ParsingException(Exception):
|
||||
"Usage: raise ParsingException(<msg>[, hh=<hh>])"
|
||||
def __init__(self, *args, **kwargs):
|
||||
if len(args)==0: args=[''] + list(args)
|
||||
msg, args = args[0], args[1:]
|
||||
if 'hh' in kwargs:
|
||||
msg += self.wrapHh(kwargs['hh'])
|
||||
del kwargs['hh']
|
||||
return Exception.__init__(self, msg, *args, **kwargs)
|
||||
def wrapHh(self, hh):
|
||||
return ("\n\nHand history attached below:\n"
|
||||
"%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
|
||||
{'DELIMETER': '#'*50, 'HH': hh}
|
||||
|
||||
|
||||
############################################################
|
||||
# Class Variables
|
||||
|
||||
sitename = "PartyPoker"
|
||||
codepage = "cp1252"
|
||||
siteId = 9 # TODO: automate; it's a class variable so shouldn't hit DB too often
|
||||
filetype = "text" # "text" or "xml". I propose we subclass HHC to HHC_Text and HHC_XML.
|
||||
|
||||
|
||||
sym = {'USD': "\$", }
|
||||
|
||||
# Static regexes
|
||||
# $5 USD NL Texas Hold'em - Saturday, July 25, 07:53:52 EDT 2009
|
||||
# NL Texas Hold'em $1 USD Buy-in Trny:45685440 Level:8 Blinds-Antes(600/1 200 -50) - Sunday, May 17, 11:25:07 MSKS 2009
|
||||
re_GameInfoRing = re.compile("""
|
||||
(?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>\d+)\s*(?:USD)?\s*
|
||||
(?P<LIMIT>(NL))\s+
|
||||
(?P<GAME>(Texas\ Hold\'em))
|
||||
(?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>[0-9,]+)\s*(?:USD)?\s*
|
||||
(?P<LIMIT>(NL|PL|))\s+
|
||||
(?P<GAME>(Texas\ Hold\'em|Omaha))
|
||||
\s*\-\s*
|
||||
(?P<DATETIME>.+)
|
||||
""", re.VERBOSE)
|
||||
re_GameInfoTrny = re.compile("""
|
||||
(?P<LIMIT>(NL))\s+
|
||||
(?P<GAME>(Texas\ Hold\'em))\s+
|
||||
(?P<LIMIT>(NL|PL|))\s+
|
||||
(?P<GAME>(Texas\ Hold\'em|Omaha))\s+
|
||||
(?P<BUYIN>\$?[.0-9]+)\s*(?P<BUYIN_CURRENCY>USD)?\s*Buy-in\s+
|
||||
Trny:\s?(?P<TOURNO>\d+)\s+
|
||||
Level:\s*(?P<LEVEL>\d+)\s+
|
||||
|
@ -70,37 +75,13 @@ class PartyPoker(HandHistoryConverter):
|
|||
(?P<DATETIME>.+)
|
||||
""", re.VERBOSE)
|
||||
re_Hid = re.compile("^Game \#(?P<HID>\d+) starts.")
|
||||
#re_GameInfo = re.compile("""
|
||||
#PartyPoker\sGame\s\#(?P<HID>[0-9]+):\s+
|
||||
#(Tournament\s\# # open paren of tournament info
|
||||
#(?P<TOURNO>\d+),\s
|
||||
#(?P<BUYIN>[%(LS)s\+\d\.]+ # here's how I plan to use LS
|
||||
#\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?
|
||||
#)\s)? # close paren of tournament info
|
||||
#(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(?
|
||||
#(?P<GAME>Hold\'em|Razz|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball)\s
|
||||
#(?P<LIMIT>No\sLimit|Limit|Pot\sLimit)\)?,?\s
|
||||
#(-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?
|
||||
#\(? # open paren of the stakes
|
||||
#(?P<CURRENCY>%(LS)s|)?
|
||||
#(?P<SB>[.0-9]+)/(%(LS)s)?
|
||||
#(?P<BB>[.0-9]+)
|
||||
#\s?(?P<ISO>%(LEGAL_ISO)s)?
|
||||
#\)\s-\s # close paren of the stakes
|
||||
#(?P<DATETIME>.*$)""" % substitutions,
|
||||
#re.MULTILINE|re.VERBOSE)
|
||||
|
||||
re_PlayerInfo = re.compile("""
|
||||
Seat\s(?P<SEAT>\d+):\s
|
||||
(?P<PNAME>.*)\s
|
||||
\(\s*\$?(?P<CASH>[0-9,.]+)\s*(?:USD|)\s*\)
|
||||
""" ,
|
||||
""" ,
|
||||
re.VERBOSE)
|
||||
#re_PlayerInfo = re.compile("""
|
||||
#^Seat\s(?P<SEAT>[0-9]+):\s
|
||||
#(?P<PNAME>.*)\s
|
||||
#\((%(LS)s)?(?P<CASH>[.0-9]+)\sin\schips\)""" % substitutions,
|
||||
#re.MULTILINE|re.VERBOSE)
|
||||
|
||||
re_HandInfo = re.compile("""
|
||||
^Table\s+
|
||||
|
@ -109,110 +90,96 @@ class PartyPoker(HandHistoryConverter):
|
|||
(?:[^ ]+\s+\#(?P<MTTTABLE>\d+).+)? # table number for mtt
|
||||
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
|
||||
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
|
||||
""",
|
||||
""",
|
||||
re.MULTILINE|re.VERBOSE)
|
||||
#re_HandInfo = re.compile("""
|
||||
#^Table\s\'(?P<TABLE>[-\ a-zA-Z\d]+)\'\s
|
||||
#((?P<MAX>\d+)-max\s)?
|
||||
#(?P<PLAY>\(Play\sMoney\)\s)?
|
||||
#(Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
|
||||
#re.MULTILINE|re.VERBOSE)
|
||||
|
||||
re_TotalPlayers = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<MAXSEATS>\d+)", re.MULTILINE)
|
||||
re_SplitHands = re.compile('\x00+')
|
||||
re_TailSplitHands = re.compile('(\x00+)')
|
||||
lineSplitter = '\n'
|
||||
re_Button = re.compile('Seat (?P<BUTTON>\d+) is the button', re.MULTILINE)
|
||||
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
|
||||
re_NoSmallBlind = re.compile('^There is no Small Blind in this hand as the Big Blind of the previous hand left the table')
|
||||
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
|
||||
re_NoSmallBlind = re.compile(
|
||||
'^There is no Small Blind in this hand as the Big Blind '
|
||||
'of the previous hand left the table', re.MULTILINE)
|
||||
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
out_path (default '-' = sys.stdout)
|
||||
follow : whether to tail -f the input"""
|
||||
HandHistoryConverter.__init__(self, in_path, out_path, sitename="PartyPoker", follow=follow, index=index)
|
||||
logging.info("Initialising PartyPoker converter class")
|
||||
self.filetype = "text"
|
||||
self.codepage = "cp1252" # FIXME: wtf?
|
||||
self.siteId = 9 # Needs to match id entry in Sites database
|
||||
self._gameType = None # cached reg-parse result
|
||||
if autostart:
|
||||
self.start()
|
||||
|
||||
def allHandsAsList(self):
|
||||
list = HandHistoryConverter.allHandsAsList(self)
|
||||
if list is None:
|
||||
return []
|
||||
return filter(lambda text: len(text.strip()), list)
|
||||
|
||||
def guessMaxSeats(self, hand):
|
||||
"""Return a guess at max_seats when not specified in HH."""
|
||||
mo = self.maxOccSeat(hand)
|
||||
|
||||
if mo == 10: return mo
|
||||
if mo == 2: return 2
|
||||
if mo <= 6: return 6
|
||||
# there are 9-max tables for cash and 10-max for tournaments
|
||||
return 9 if hand.gametype['type']=='ring' else 10
|
||||
|
||||
def compilePlayerRegexs(self, hand):
|
||||
players = set([player[1] for player in hand.players])
|
||||
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
|
||||
# we need to recompile the player regexs.
|
||||
# TODO: should probably rename re_HeroCards and corresponding method,
|
||||
# since they are used to find all cards on lines starting with "Dealt to:"
|
||||
# They still identify the hero.
|
||||
|
||||
self.compiledPlayers = players
|
||||
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
|
||||
subst = {'PLYR': player_re, 'CUR_SYM': hand.SYMBOL[hand.gametype['currency']],
|
||||
'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''}
|
||||
logging.debug("player_re: " + subst['PLYR'])
|
||||
logging.debug("CUR_SYM: " + subst['CUR_SYM'])
|
||||
logging.debug("CUR: " + subst['CUR'])
|
||||
for key in ('CUR_SYM', 'CUR'):
|
||||
subst[key] = re.escape(subst[key])
|
||||
log.debug("player_re: '%s'" % subst['PLYR'])
|
||||
log.debug("CUR_SYM: '%s'" % subst['CUR_SYM'])
|
||||
log.debug("CUR: '%s'" % subst['CUR'])
|
||||
self.re_PostSB = re.compile(
|
||||
r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.0-9]+) ?%(CUR)s\]\." % subst,
|
||||
r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.0-9]+) ?%(CUR)s\]\." % subst,
|
||||
re.MULTILINE)
|
||||
self.re_PostBB = re.compile(
|
||||
r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.0-9]+) ?%(CUR)s\]\." % subst,
|
||||
r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.0-9]+) ?%(CUR)s\]\." % subst,
|
||||
re.MULTILINE)
|
||||
# NOTE: comma is used as a fraction part delimeter in re below
|
||||
self.re_PostDead = re.compile(
|
||||
r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst,
|
||||
re.MULTILINE)
|
||||
self.re_Antes = re.compile(
|
||||
r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.0-9]+) ?%(CUR)s\]\." % subst,
|
||||
r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.0-9]+) ?%(CUR)s\]" % subst,
|
||||
re.MULTILINE)
|
||||
#self.re_BringIn = re.compile(
|
||||
#r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[.0-9]+)" % subst,
|
||||
#re.MULTILINE)
|
||||
#self.re_PostBoth = re.compile(
|
||||
#r"^%(PLYR)s: posts small \& big blinds \[%(CUR)s (?P<SBBB>[.0-9]+)" % subst,
|
||||
#re.MULTILINE)
|
||||
self.re_HeroCards = re.compile(
|
||||
r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst,
|
||||
re.MULTILINE)
|
||||
self.re_Action = re.compile(r"""
|
||||
^%(PLYR)s\s+(?P<ATYPE>bets|checks|raises|calls|folds|is\sall-In)
|
||||
(?:\s+\[%(CUR_SYM)s(?P<BET>[.,\d]+)\s*%(CUR)s\])?
|
||||
""" % subst,
|
||||
""" % subst,
|
||||
re.MULTILINE|re.VERBOSE)
|
||||
self.re_ShownCards = re.compile(
|
||||
r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re +
|
||||
r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.",
|
||||
r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re +
|
||||
r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.",
|
||||
re.MULTILINE)
|
||||
self.re_CollectPot = re.compile(
|
||||
r""""^%(PLYR)s \s+ wins \s+
|
||||
%(CUR_SYM)s(?P<POT>[.\d]+)\s*%(CUR)s""" % subst,
|
||||
r"""^%(PLYR)s \s+ wins \s+
|
||||
%(CUR_SYM)s(?P<POT>[.,\d]+)\s*%(CUR)s""" % subst,
|
||||
re.MULTILINE|re.VERBOSE)
|
||||
#self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
|
||||
#self.re_ShownCards = re.compile(
|
||||
#"^Seat (?P<SEAT>[0-9]+): %s (\(.*\) )?
|
||||
#(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re,
|
||||
#re.MULTILINE)
|
||||
|
||||
def readSupportedGames(self):
|
||||
return [["ring", "hold", "nl"],
|
||||
#["ring", "hold", "pl"],
|
||||
#["ring", "hold", "fl"],
|
||||
["ring", "hold", "pl"],
|
||||
["ring", "hold", "fl"],
|
||||
|
||||
["tour", "hold", "nl"],
|
||||
#["tour", "hold", "pl"],
|
||||
#["tour", "hold", "fl"],
|
||||
["tour", "hold", "pl"],
|
||||
["tour", "hold", "fl"],
|
||||
]
|
||||
|
||||
def _getGameType(self, handText):
|
||||
if not hasattr(self, '_gameType'):
|
||||
self._gameType = None
|
||||
if self._gameType is None:
|
||||
# let's determine whether hand is trny
|
||||
# and whether 5-th line contains head line
|
||||
headLine = handText.split(self.lineSplitter)[4]
|
||||
#print headLine
|
||||
#sys.exit(1)
|
||||
for headLineContainer in headLine, handText:
|
||||
for regexp in self.re_GameInfoTrny, self.re_GameInfoRing:
|
||||
m = regexp.search(headLineContainer)
|
||||
|
@ -220,47 +187,45 @@ follow : whether to tail -f the input"""
|
|||
self._gameType = m
|
||||
return self._gameType
|
||||
return self._gameType
|
||||
|
||||
def determineGameType(self, handText):
|
||||
# inspect the handText and return the gametype dict
|
||||
# gametype dict is:
|
||||
# {'limitType': xxx, 'base': xxx, 'category': xxx}
|
||||
|
||||
print self.ParsingException().wrapHh( handText )
|
||||
|
||||
def determineGameType(self, handText):
|
||||
"""inspect the handText and return the gametype dict
|
||||
|
||||
gametype dict is:
|
||||
{'limitType': xxx, 'base': xxx, 'category': xxx}"""
|
||||
|
||||
log.debug(PartyPokerParseError().wrapHh( handText ))
|
||||
|
||||
info = {}
|
||||
m = self._getGameType(handText)
|
||||
if m is None:
|
||||
return None
|
||||
|
||||
|
||||
mg = m.groupdict()
|
||||
# translations from captured groups to fpdb info strings
|
||||
limits = { 'NL':'nl',
|
||||
# 'Pot Limit':'pl', 'Limit':'fl'
|
||||
}
|
||||
limits = { 'NL':'nl', 'PL':'pl', '':'fl' }
|
||||
games = { # base, category
|
||||
"Texas Hold'em" : ('hold','holdem'),
|
||||
#'Omaha' : ('hold','omahahi'),
|
||||
"Texas Hold'em" : ('hold','holdem'),
|
||||
'Omaha' : ('hold','omahahi'),
|
||||
}
|
||||
currencies = { '$':'USD', '':'T$' }
|
||||
|
||||
for expectedField in ['LIMIT', 'GAME']:
|
||||
if mg[expectedField] is None:
|
||||
raise self.ParsingException(
|
||||
raise PartyPokerParseError(
|
||||
"Cannot fetch field '%s'" % expectedField,
|
||||
hh = handText)
|
||||
try:
|
||||
info['limitType'] = limits[mg['LIMIT']]
|
||||
info['limitType'] = limits[mg['LIMIT'].strip()]
|
||||
except:
|
||||
raise self.ParsingException(
|
||||
raise PartyPokerParseError(
|
||||
"Unknown limit '%s'" % mg['LIMIT'],
|
||||
hh = handText)
|
||||
|
||||
try:
|
||||
(info['base'], info['category']) = games[mg['GAME']]
|
||||
except:
|
||||
raise self.ParsingException(
|
||||
raise PartyPokerParseError(
|
||||
"Unknown game type '%s'" % mg['GAME'],
|
||||
hh = handText)
|
||||
|
||||
|
@ -269,16 +234,16 @@ follow : whether to tail -f the input"""
|
|||
info['type'] = 'tour'
|
||||
else:
|
||||
info['type'] = 'ring'
|
||||
|
||||
|
||||
if info['type'] == 'ring':
|
||||
info['sb'], info['bb'] = ringBlinds(mg['RINGLIMIT'])
|
||||
# FIXME: there are only $ and play money availible for cash
|
||||
info['currency'] = currencies(mg['CURRENCY'])
|
||||
# to be honest, party doesn't save play money hh
|
||||
info['currency'] = currencies[mg['CURRENCY']]
|
||||
else:
|
||||
info['sb'] = renderTrnyMoney(mg['SB'])
|
||||
info['bb'] = renderTrnyMoney(mg['BB'])
|
||||
info['sb'] = clearMoneyString(mg['SB'])
|
||||
info['bb'] = clearMoneyString(mg['BB'])
|
||||
info['currency'] = 'T$'
|
||||
|
||||
|
||||
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
|
||||
return info
|
||||
|
@ -286,22 +251,48 @@ follow : whether to tail -f the input"""
|
|||
|
||||
def readHandInfo(self, hand):
|
||||
info = {}
|
||||
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
|
||||
if m:
|
||||
info.update(m.groupdict())
|
||||
else:
|
||||
raise self.ParsingException("Cannot read Handinfo for current hand", hh=hand.handText)
|
||||
m = self._getGameType(hand.handText)
|
||||
if m: info.update(m.groupdict())
|
||||
m = self.re_Hid.search(hand.handText)
|
||||
try:
|
||||
info.update(self.re_Hid.search(hand.handText).groupdict())
|
||||
except:
|
||||
raise PartyPokerParseError("Cannot read HID for current hand", hh=hand.handText)
|
||||
|
||||
try:
|
||||
info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict())
|
||||
except:
|
||||
raise PartyPokerParseError("Cannot read Handinfo for current hand",
|
||||
hh=hand.handText, hid = info['HID'])
|
||||
|
||||
try:
|
||||
info.update(self._getGameType(hand.handText).groupdict())
|
||||
except:
|
||||
raise PartyPokerParseError("Cannot read GameType for current hand",
|
||||
hh=hand.handText, hid = info['HID'])
|
||||
|
||||
|
||||
m = self.re_TotalPlayers.search(hand.handText)
|
||||
if m: info.update(m.groupdict())
|
||||
|
||||
# FIXME: it's a hack cause party doesn't supply hand.maxseats info
|
||||
#hand.maxseats = '9'
|
||||
hand.mixed = None
|
||||
|
||||
# TODO : I rather like the idea of just having this dict as hand.info
|
||||
logging.debug("readHandInfo: %s" % info)
|
||||
|
||||
# FIXME: it's dirty hack
|
||||
# party doesnt subtract uncalled money from commited money
|
||||
# so hand.totalPot calculation has to be redefined
|
||||
from Hand import Pot, HoldemOmahaHand
|
||||
def getNewTotalPot(origTotalPot):
|
||||
def totalPot(self):
|
||||
if self.totalpot is None:
|
||||
self.pot.end()
|
||||
self.totalpot = self.pot.total
|
||||
for i,v in enumerate(self.collected):
|
||||
if v[0] in self.pot.returned:
|
||||
self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
|
||||
return origTotalPot()
|
||||
return totalPot
|
||||
instancemethod = type(hand.totalPot)
|
||||
hand.totalPot = instancemethod(getNewTotalPot(hand.totalPot), hand, HoldemOmahaHand)
|
||||
|
||||
|
||||
|
||||
log.debug("readHandInfo: %s" % info)
|
||||
for key in info:
|
||||
if key == 'DATETIME':
|
||||
#Saturday, July 25, 07:53:52 EDT 2009
|
||||
|
@ -313,9 +304,10 @@ follow : whether to tail -f the input"""
|
|||
month = months.index(m2.group('M')) + 1
|
||||
datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
|
||||
hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
|
||||
# FIXME: some timezone correction required
|
||||
#tzShift = defaultdict(lambda:0, {'EDT': -5, 'EST': -6, 'MSKS': 3})
|
||||
#hand.starttime -= datetime.timedelta(hours=tzShift[m2.group('TZ')])
|
||||
|
||||
|
||||
if key == 'HID':
|
||||
hand.handid = info[key]
|
||||
if key == 'TABLE':
|
||||
|
@ -325,14 +317,14 @@ follow : whether to tail -f the input"""
|
|||
if key == 'TOURNO':
|
||||
hand.tourNo = info[key]
|
||||
if key == 'BUYIN':
|
||||
#FIXME: it's dirty hack T_T
|
||||
# FIXME: it's dirty hack T_T
|
||||
# code below assumes that rake is equal to zero
|
||||
cur = info[key][0] if info[key][0] not in '0123456789' else ''
|
||||
hand.buyin = info[key] + '+%s0' % cur
|
||||
if key == 'LEVEL':
|
||||
hand.level = info[key]
|
||||
if key == 'PLAY' and info['PLAY'] != 'Real':
|
||||
# TODO: play money wasn't tested
|
||||
# hand.currency = 'play' # overrides previously set value
|
||||
# if realy there's no play money hh on party
|
||||
hand.gametype['currency'] = 'play'
|
||||
|
||||
def readButton(self, hand):
|
||||
|
@ -340,21 +332,17 @@ follow : whether to tail -f the input"""
|
|||
if m:
|
||||
hand.buttonpos = int(m.group('BUTTON'))
|
||||
else:
|
||||
logging.info('readButton: not found')
|
||||
log.info('readButton: not found')
|
||||
|
||||
def readPlayerStacks(self, hand):
|
||||
logging.debug("readPlayerStacks")
|
||||
log.debug("readPlayerStacks")
|
||||
m = self.re_PlayerInfo.finditer(hand.handText)
|
||||
players = []
|
||||
for a in m:
|
||||
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'),
|
||||
renderTrnyMoney(a.group('CASH')))
|
||||
clearMoneyString(a.group('CASH')))
|
||||
|
||||
def markStreets(self, hand):
|
||||
# PREFLOP = ** Dealing down cards **
|
||||
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
|
||||
assert hand.gametype['base'] == "hold", \
|
||||
"wtf! There're no %s games on party" % hand.gametype['base']
|
||||
m = re.search(
|
||||
r"\*{2} Dealing down cards \*{2}"
|
||||
r"(?P<PREFLOP>.+?)"
|
||||
|
@ -364,24 +352,17 @@ follow : whether to tail -f the input"""
|
|||
, hand.handText,re.DOTALL)
|
||||
hand.addStreets(m)
|
||||
|
||||
def readCommunityCards(self, hand, street):
|
||||
if street in ('FLOP','TURN','RIVER'):
|
||||
def readCommunityCards(self, hand, street):
|
||||
if street in ('FLOP','TURN','RIVER'):
|
||||
m = self.re_Board.search(hand.streets[street])
|
||||
hand.setCommunityCards(street, renderCards(m.group('CARDS')))
|
||||
|
||||
def readAntes(self, hand):
|
||||
logging.debug("reading antes")
|
||||
log.debug("reading antes")
|
||||
m = self.re_Antes.finditer(hand.handText)
|
||||
for player in m:
|
||||
#~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
|
||||
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
|
||||
|
||||
def readBringIn(self, hand):
|
||||
m = self.re_BringIn.search(hand.handText,re.DOTALL)
|
||||
if m:
|
||||
#~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
|
||||
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
|
||||
|
||||
|
||||
def readBlinds(self, hand):
|
||||
noSmallBlind = bool(self.re_NoSmallBlind.search(hand.handText))
|
||||
if hand.gametype['type'] == 'ring':
|
||||
|
@ -391,50 +372,51 @@ follow : whether to tail -f the input"""
|
|||
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
|
||||
except: # no small blind
|
||||
hand.addBlind(None, None, None)
|
||||
|
||||
|
||||
for a in self.re_PostBB.finditer(hand.handText):
|
||||
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
|
||||
else:
|
||||
|
||||
deadFilter = lambda s: s.replace(',', '.')
|
||||
for a in self.re_PostDead.finditer(hand.handText):
|
||||
hand.addBlind(a.group('PNAME'), 'both', deadFilter(a.group('BBNDEAD')))
|
||||
else:
|
||||
# party doesn't track blinds for tournaments
|
||||
# so there're some cra^Wcaclulations
|
||||
if hand.buttonpos == 0:
|
||||
self.readButton(hand)
|
||||
# NOTE: code below depends on Hand's implementation
|
||||
# playersMap - dict {seat: (pname,stack)}
|
||||
playersMap = dict([(f[0], f[1:3]) for f in hand.players])
|
||||
playersMap = dict([(f[0], f[1:3]) for f in hand.players])
|
||||
maxSeat = max(playersMap)
|
||||
|
||||
|
||||
def findFirstNonEmptySeat(startSeat):
|
||||
while startSeat not in playersMap:
|
||||
if startSeat >= maxSeat:
|
||||
if startSeat >= maxSeat:
|
||||
startSeat = 0
|
||||
startSeat += 1
|
||||
return startSeat
|
||||
smartMin = lambda A,B: A if float(A) <= float(B) else B
|
||||
|
||||
|
||||
if noSmallBlind:
|
||||
hand.addBlind(None, None, None)
|
||||
smallBlindSeat = int(hand.buttonpos)
|
||||
else:
|
||||
smallBlindSeat = findFirstNonEmptySeat(int(hand.buttonpos) + 1)
|
||||
blind = smartMin(hand.sb, playersMap[smallBlindSeat][1])
|
||||
hand.addBlind(playersMap[smallBlindSeat][0], 'small blind', blind)
|
||||
|
||||
|
||||
bigBlindSeat = findFirstNonEmptySeat(smallBlindSeat + 1)
|
||||
blind = smartMin(hand.bb, playersMap[bigBlindSeat][1])
|
||||
hand.addBlind(playersMap[bigBlindSeat][0], 'small blind', blind)
|
||||
|
||||
|
||||
hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind)
|
||||
|
||||
|
||||
|
||||
def readHeroCards(self, hand):
|
||||
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
|
||||
# we need to grab hero's cards
|
||||
# we need to grab hero's cards
|
||||
for street in ('PREFLOP',):
|
||||
if street in hand.streets.keys():
|
||||
m = self.re_HeroCards.finditer(hand.streets[street])
|
||||
for found in m:
|
||||
# if m == None:
|
||||
# hand.involved = False
|
||||
# else:
|
||||
hand.hero = found.group('PNAME')
|
||||
newcards = renderCards(found.group('NEWCARDS'))
|
||||
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
|
||||
|
@ -444,35 +426,48 @@ follow : whether to tail -f the input"""
|
|||
m = self.re_Action.finditer(hand.streets[street])
|
||||
for action in m:
|
||||
acts = action.groupdict()
|
||||
if action.group('ATYPE') in ('raises','is all-In'):
|
||||
#print action.groupdict()
|
||||
#sys.exit(1)
|
||||
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
|
||||
elif action.group('ATYPE') == 'calls':
|
||||
hand.addCall( street, action.group('PNAME'), action.group('BET') )
|
||||
#print action.groupdict()
|
||||
#sys.exit(1)
|
||||
elif action.group('ATYPE') == 'bets':
|
||||
hand.addBet( street, action.group('PNAME'), action.group('BET') )
|
||||
elif action.group('ATYPE') == 'folds':
|
||||
hand.addFold( street, action.group('PNAME'))
|
||||
elif action.group('ATYPE') == 'checks':
|
||||
hand.addCheck( street, action.group('PNAME'))
|
||||
playerName = action.group('PNAME')
|
||||
amount = clearMoneyString(action.group('BET')) if action.group('BET') else None
|
||||
actionType = action.group('ATYPE')
|
||||
|
||||
if actionType == 'is all-In':
|
||||
# party's allin can mean either raise or bet or call
|
||||
Bp = hand.lastBet[street]
|
||||
if Bp == 0:
|
||||
actionType = 'bets'
|
||||
elif Bp < Decimal(amount):
|
||||
actionType = 'raises'
|
||||
else:
|
||||
actionType = 'calls'
|
||||
|
||||
if actionType == 'raises':
|
||||
if street == 'PREFLOP' and \
|
||||
playerName in [item[0] for item in hand.actions['BLINDSANTES'] if item[2]!='ante']:
|
||||
# preflop raise from blind
|
||||
hand.addRaiseBy( street, playerName, amount )
|
||||
else:
|
||||
hand.addCallandRaise( street, playerName, amount )
|
||||
elif actionType == 'calls':
|
||||
hand.addCall( street, playerName, amount )
|
||||
elif actionType == 'bets':
|
||||
hand.addBet( street, playerName, amount )
|
||||
elif actionType == 'folds':
|
||||
hand.addFold( street, playerName )
|
||||
elif actionType == 'checks':
|
||||
hand.addCheck( street, playerName )
|
||||
else:
|
||||
print "DEBUG: unimplemented readAction: '%s' '%s'" %(action.group('PNAME'),action.group('ATYPE'),)
|
||||
raise PartyPokerParseError(
|
||||
"Unimplemented readAction: '%s' '%s'" % (playerName,actionType,),
|
||||
hid = hand.hid, hh = hand.handText )
|
||||
|
||||
|
||||
def readShowdownActions(self, hand):
|
||||
# all action in readShownCards
|
||||
pass
|
||||
## TODO: pick up mucks also??
|
||||
#for shows in self.re_ShowdownAction.finditer(hand.handText):
|
||||
#cards = shows.group('CARDS').split(' ')
|
||||
#hand.addShownCards(cards, shows.group('PNAME'))
|
||||
|
||||
def readCollectPot(self,hand):
|
||||
for m in self.re_CollectPot.finditer(hand.handText):
|
||||
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
|
||||
hand.addCollectPot(player=m.group('PNAME'),pot=clearMoneyString(m.group('POT')))
|
||||
|
||||
def readShownCards(self,hand):
|
||||
for m in self.re_ShownCards.finditer(hand.handText):
|
||||
|
@ -484,26 +479,26 @@ follow : whether to tail -f the input"""
|
|||
else: mucked = True
|
||||
|
||||
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked)
|
||||
|
||||
|
||||
def ringBlinds(ringLimit):
|
||||
"Returns blinds for current limit"
|
||||
ringLimit = float(ringLimit)
|
||||
"Returns blinds for current limit in cash games"
|
||||
ringLimit = float(clearMoneyString(ringLimit))
|
||||
if ringLimit == 5.: ringLimit = 4.
|
||||
return ('%.2f' % (ringLimit/200.), '%.2f' % (ringLimit/100.) )
|
||||
|
||||
def renderTrnyMoney(money):
|
||||
"renders 'numbers' like '1 200' and '2,000'"
|
||||
def clearMoneyString(money):
|
||||
"Renders 'numbers' like '1 200' and '2,000'"
|
||||
return money.replace(' ', '').replace(',', '')
|
||||
|
||||
def renderCards(string):
|
||||
"splits strings like ' Js, 4d '"
|
||||
"Splits strings like ' Js, 4d '"
|
||||
cards = string.strip().split(' ')
|
||||
return filter(len, map(lambda x: x.strip(' ,'), cards))
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = OptionParser()
|
||||
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/stars/horse/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt")
|
||||
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history")
|
||||
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
|
||||
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
|
||||
parser.add_option("-q", "--quiet",
|
||||
|
@ -515,7 +510,4 @@ if __name__ == "__main__":
|
|||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
#LOG_FILENAME = './logging.out'
|
||||
logging.basicConfig(level=options.verbosity)
|
||||
|
||||
e = PartyPoker(in_path = options.ipath, out_path = options.opath, follow = options.follow)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
########################################################################
|
||||
|
||||
# TODO: straighten out discards for draw games
|
||||
|
||||
import sys
|
||||
from HandHistoryConverter import *
|
||||
|
||||
|
@ -26,13 +27,17 @@ from HandHistoryConverter import *
|
|||
|
||||
class PokerStars(HandHistoryConverter):
|
||||
|
||||
############################################################
|
||||
# Class Variables
|
||||
# Class Variables
|
||||
|
||||
sitename = "PokerStars"
|
||||
filetype = "text"
|
||||
codepage = "cp1252"
|
||||
siteId = 2 # Needs to match id entry in Sites database
|
||||
|
||||
mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} # Legal mixed games
|
||||
sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\x80", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE
|
||||
substitutions = {
|
||||
'LEGAL_ISO' : "USD|EUR|GBP|CAD", # legal ISO currency codes
|
||||
'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
|
||||
'LS' : "\$|\x80|\xa3" # legal currency symbols ADD Euro, Sterling, etc HERE
|
||||
}
|
||||
|
||||
|
@ -77,20 +82,6 @@ class PokerStars(HandHistoryConverter):
|
|||
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
|
||||
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
out_path (default '-' = sys.stdout)
|
||||
follow : whether to tail -f the input"""
|
||||
HandHistoryConverter.__init__(self, in_path, out_path, sitename="PokerStars", follow=follow, index=index)
|
||||
logging.info("Initialising PokerStars converter class")
|
||||
self.filetype = "text"
|
||||
self.codepage = "cp1252"
|
||||
self.siteId = 2 # Needs to match id entry in Sites database
|
||||
if autostart:
|
||||
self.start()
|
||||
|
||||
|
||||
def compilePlayerRegexs(self, hand):
|
||||
players = set([player[1] for player in hand.players])
|
||||
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
|
||||
|
@ -388,7 +379,4 @@ if __name__ == "__main__":
|
|||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
LOG_FILENAME = './logging.out'
|
||||
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
|
||||
|
||||
e = PokerStars(in_path = options.ipath, out_path = options.opath, follow = options.follow)
|
||||
|
|
5879
pyfpdb/SQL.py
5879
pyfpdb/SQL.py
File diff suppressed because it is too large
Load Diff
188
pyfpdb/SummaryEverleaf.py
Normal file
188
pyfpdb/SummaryEverleaf.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright (c) 2009 Eric Blade, and the FPDB team.
|
||||
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as published by
|
||||
#the Free Software Foundation, version 3 of the License.
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#In the "official" distribution you can find the license in
|
||||
#agpl-3.0.txt in the docs folder of the package.
|
||||
|
||||
import urllib, htmllib, formatter
|
||||
|
||||
class AppURLopener(urllib.FancyURLopener):
|
||||
version = "Free Poker Database/0.12+"
|
||||
|
||||
urllib._urlopener = AppURLopener()
|
||||
|
||||
class SummaryParser(htmllib.HTMLParser): # derive new HTML parser
|
||||
def get_attr(self, attrs, key):
|
||||
#print attrs;
|
||||
for a in attrs:
|
||||
if a[0] == key:
|
||||
# print key,"=",a[1]
|
||||
return a[1]
|
||||
return None
|
||||
|
||||
def __init__(self, formatter) : # class constructor
|
||||
htmllib.HTMLParser.__init__(self, formatter) # base class constructor
|
||||
self.nofill = True
|
||||
self.SiteName = None
|
||||
self.TourneyId = None
|
||||
self.TourneyName = None
|
||||
self.nextStartTime = False
|
||||
self.TourneyStartTime = None
|
||||
self.nextEndTime = False
|
||||
self.TourneyEndTime = None
|
||||
self.TourneyGameType = None
|
||||
self.nextGameType = False
|
||||
self.nextStructure = False
|
||||
self.TourneyStructure = None
|
||||
self.nextBuyIn = False
|
||||
self.TourneyBuyIn = None
|
||||
self.nextPool = False
|
||||
self.TourneyPool = None
|
||||
self.nextPlayers = False
|
||||
self.TourneyPlayers = None
|
||||
self.nextAllowRebuys = False
|
||||
self.TourneyRebuys = None
|
||||
self.parseResultsA = False
|
||||
self.parseResultsB = False
|
||||
self.TempResultStore = [0,0,0,0]
|
||||
self.TempResultPos = 0
|
||||
self.Results = {}
|
||||
|
||||
def start_meta(self, attrs):
|
||||
x = self.get_attr(attrs, 'name')
|
||||
if x == "author":
|
||||
self.SiteName = self.get_attr(attrs, 'content')
|
||||
|
||||
def start_input(self, attrs):
|
||||
x = self.get_attr(attrs, 'name')
|
||||
#print "input name=",x
|
||||
if x == "tid":
|
||||
self.TourneyId = self.get_attr(attrs, 'value')
|
||||
|
||||
def start_h1(self, attrs):
|
||||
if self.TourneyName is None:
|
||||
self.save_bgn()
|
||||
|
||||
def end_h1(self):
|
||||
if self.TourneyName is None:
|
||||
self.TourneyName = self.save_end()
|
||||
|
||||
def start_div(self, attrs):
|
||||
x = self.get_attr(attrs, 'id')
|
||||
if x == "result":
|
||||
self.parseResultsA = True
|
||||
|
||||
def end_div(self): # TODO: Can we get attrs in the END tag too? I don't know? Would be useful to make SURE we're closing the right div ..
|
||||
if self.parseResultsA:
|
||||
self.parseResultsA = False # TODO: Should probably just make sure everything is false at this point, since we're not going to be having anything in the middle of a DIV.. oh well
|
||||
|
||||
def start_td(self, attrs):
|
||||
self.save_bgn()
|
||||
|
||||
def end_td(self):
|
||||
x = self.save_end()
|
||||
|
||||
if not self.parseResultsA:
|
||||
if not self.nextStartTime and x == "Start:":
|
||||
self.nextStartTime = True
|
||||
elif self.nextStartTime:
|
||||
self.TourneyStartTime = x
|
||||
self.nextStartTime = False
|
||||
|
||||
if not self.nextEndTime and x == "Finished:":
|
||||
self.nextEndTime = True
|
||||
elif self.nextEndTime:
|
||||
self.TourneyEndTime = x
|
||||
self.nextEndTime = False
|
||||
|
||||
if not self.nextGameType and x == "Game Type:":
|
||||
self.nextGameType = True
|
||||
elif self.nextGameType:
|
||||
self.TourneyGameType = x
|
||||
self.nextGameType = False
|
||||
|
||||
if not self.nextStructure and x == "Limit:":
|
||||
self.nextStructure = True
|
||||
elif self.nextStructure:
|
||||
self.TourneyStructure = x
|
||||
self.nextStructure = False
|
||||
|
||||
if not self.nextBuyIn and x == "Buy In / Fee:":
|
||||
self.nextBuyIn = True
|
||||
elif self.nextBuyIn:
|
||||
self.TourneyBuyIn = x # TODO: Further parse the fee from this
|
||||
self.nextBuyIn = False
|
||||
|
||||
if not self.nextPool and x == "Prize Money:":
|
||||
self.nextPool = True
|
||||
elif self.nextPool:
|
||||
self.TourneyPool = x
|
||||
self.nextPool = False
|
||||
|
||||
if not self.nextPlayers and x == "Player Count:":
|
||||
self.nextPlayers = True
|
||||
elif self.nextPlayers:
|
||||
self.TourneyPlayers = x
|
||||
self.nextPlayers = False
|
||||
|
||||
if not self.nextAllowRebuys and x == "Rebuys possible?:":
|
||||
self.nextAllowRebuys = True
|
||||
elif self.nextAllowRebuys:
|
||||
self.TourneyRebuys = x
|
||||
self.nextAllowRebuys = False
|
||||
|
||||
else: # parse results
|
||||
if x == "Won Prize":
|
||||
self.parseResultsB = True # ok, NOW we can start parsing the results
|
||||
elif self.parseResultsB:
|
||||
if x[0] == "$": # first char of the last of each row is the dollar sign, so now we can put it into a sane order
|
||||
self.TempResultPos = 0
|
||||
name = self.TempResultStore[1]
|
||||
place = self.TempResultStore[0]
|
||||
time = self.TempResultStore[2]
|
||||
# print self.TempResultStore
|
||||
|
||||
self.Results[name] = {}
|
||||
self.Results[name]['place'] = place
|
||||
self.Results[name]['winamount'] = x
|
||||
self.Results[name]['outtime'] = time
|
||||
|
||||
# self.Results[self.TempResultStore[1]] = {}
|
||||
# self.Results[self.TempResultStore[1]]['place'] = self.TempResultStore[self.TempResultStore[0]]
|
||||
# self.Results[self.TempResultStore[1]]['winamount'] = x
|
||||
# self.Results[self.TempResultStore[1]]['outtime'] = self.TempResultStore[self.TempResultStore[2]]
|
||||
else:
|
||||
self.TempResultStore[self.TempResultPos] = x
|
||||
self.TempResultPos += 1
|
||||
|
||||
class EverleafSummary:
|
||||
def __init__(self):
|
||||
if __name__ != "__main__":
|
||||
self.main()
|
||||
|
||||
def main(self, id="785646"):
|
||||
file = urllib.urlopen("http://www.poker4ever.com/en.tournaments.tournament-statistics?tid="+id)
|
||||
self.parser = SummaryParser(formatter.NullFormatter())
|
||||
self.parser.feed(file.read())
|
||||
print "site=",self.parser.SiteName, "tourneyname=", self.parser.TourneyName, "tourneyid=", self.parser.TourneyId
|
||||
print "start time=",self.parser.TourneyStartTime, "end time=",self.parser.TourneyEndTime
|
||||
print "structure=", self.parser.TourneyStructure, "game type=",self.parser.TourneyGameType
|
||||
print "buy-in=", self.parser.TourneyBuyIn, "rebuys=", self.parser.TourneyRebuys, "total players=", self.parser.TourneyPlayers, "pool=", self.parser.TourneyPool
|
||||
print "results=", self.parser.Results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
me = EverleafSummary()
|
||||
me.main()
|
|
@ -39,6 +39,7 @@ if os.name == 'nt':
|
|||
|
||||
# FreePokerTools modules
|
||||
import Configuration
|
||||
from fpdb_simple import LOCALE_ENCODING
|
||||
|
||||
# Each TableWindow object must have the following attributes correctly populated:
|
||||
# tw.name = the table name from the title bar, which must to match the table name
|
||||
|
@ -68,6 +69,7 @@ class Table_Window:
|
|||
if 'site' in info: self.site = info['site']
|
||||
if 'title' in info: self.title = info['title']
|
||||
if 'name' in info: self.name = info['name']
|
||||
self.gdkhandle = None
|
||||
|
||||
def __str__(self):
|
||||
# __str__ method for testing
|
||||
|
@ -230,16 +232,21 @@ def discover_nt_by_name(c, tablename):
|
|||
"""Finds poker client window with the given table name."""
|
||||
titles = {}
|
||||
win32gui.EnumWindows(win_enum_handler, titles)
|
||||
|
||||
for hwnd in titles:
|
||||
#print "Tables.py: tablename =", tablename, "title =", titles[hwnd]
|
||||
try:
|
||||
# maybe it's better to make global titles[hwnd] decoding?
|
||||
# this can blow up in XP on some windows, eg firefox displaying http://docs.python.org/tutorial/classes.html
|
||||
if not tablename in titles[hwnd]: continue
|
||||
if not tablename.lower() in titles[hwnd].decode(LOCALE_ENCODING).lower(): continue
|
||||
except:
|
||||
continue
|
||||
if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window
|
||||
if 'HUD:' in titles[hwnd]: continue # FPDB HUD window
|
||||
if 'Chat:' in titles[hwnd]: continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows
|
||||
if ' - Table ' in titles[hwnd]: continue # Absolute table Chat window.. sigh. TODO: Can we tell what site we're trying to discover for somehow in here, so i can limit this check just to AP searches?
|
||||
temp = decode_windows(c, titles[hwnd], hwnd)
|
||||
#print "attach to window", temp
|
||||
return decode_windows(c, titles[hwnd], hwnd)
|
||||
return None
|
||||
|
||||
|
@ -302,7 +309,9 @@ def decode_windows(c, title, hwnd):
|
|||
return info
|
||||
|
||||
def win_enum_handler(hwnd, titles):
|
||||
titles[hwnd] = win32gui.GetWindowText(hwnd)
|
||||
str = win32gui.GetWindowText(hwnd)
|
||||
if str != "":
|
||||
titles[hwnd] = win32gui.GetWindowText(hwnd)
|
||||
|
||||
###################################################################
|
||||
# Utility routines used by all the discoverers.
|
||||
|
|
310
pyfpdb/TournamentTracker.py
Normal file
310
pyfpdb/TournamentTracker.py
Normal file
|
@ -0,0 +1,310 @@
|
|||
#!/usr/bin/env python
|
||||
"""TourneyTracker.py
|
||||
Based on HUD_main .. who knows if we want to actually use this or not
|
||||
"""
|
||||
# Copyright 2008, 2009, Eric Blade
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
########################################################################
|
||||
|
||||
# to do allow window resizing
|
||||
# to do hud to echo, but ignore non numbers
|
||||
# to do no stat window for hero
|
||||
# to do things to add to config.xml
|
||||
|
||||
# Standard Library modules
|
||||
import sys
|
||||
import os
|
||||
import Options
|
||||
import traceback
|
||||
|
||||
(options, sys.argv) = Options.fpdb_options()
|
||||
|
||||
if not options.errorsToConsole:
|
||||
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
|
||||
errorFile = open('tourneyerror.txt', 'w', 0)
|
||||
sys.stderr = errorFile
|
||||
|
||||
import thread
|
||||
import time
|
||||
import string
|
||||
import re
|
||||
|
||||
# pyGTK modules
|
||||
import pygtk
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
# FreePokerTools modules
|
||||
import Configuration
|
||||
import Database
|
||||
import SummaryEverleaf
|
||||
|
||||
class Tournament:
|
||||
"""Tournament will hold the information about a tournament, I guess ? Remember I'm new to this language, so I don't know the best ways to do things"""
|
||||
|
||||
def __init__(self, parent, site, tid): # site should probably be something in the config object, but i don't know how the config object works right now, so we're going to make it a str ..
|
||||
print "Tournament init"
|
||||
self.parent = parent
|
||||
self.window = None
|
||||
self.site = site
|
||||
self.id = tid
|
||||
self.starttime = time.time()
|
||||
self.endtime = None
|
||||
self.game = None
|
||||
self.structure = None
|
||||
self.buyin = 0
|
||||
self.fee = 0
|
||||
self.rebuys = False
|
||||
self.numrebuys = 0 # this should probably be attached to the players list...
|
||||
self.numplayers = 0
|
||||
self.prizepool = 0
|
||||
self.players = {} # eventually i'd guess we'd probably want to fill this with playername:playerid's
|
||||
self.results = {} # i'd guess we'd want to load this up with playerid's instead of playernames, too, as well, also
|
||||
|
||||
# if site == "Everleaf": # this should be attached to a button that says "retrieve tournament info" or something for sites that we know how to do it for
|
||||
summary = SummaryEverleaf.EverleafSummary()
|
||||
self.site = summary.parser.SiteName
|
||||
self.id = summary.parser.TourneyId
|
||||
self.starttime = summary.parser.TourneyStartTime
|
||||
self.endtime = summary.parser.TourneyEndTime
|
||||
self.game = summary.parser.TourneyGameType
|
||||
self.structure = summary.parser.TourneyStructure
|
||||
self.buyin = summary.parser.TourneyBuyIn # need to remember to parse the Fee out of this and move it to self.fee
|
||||
self.rebuys = (summary.parser.TourneyRebuys == "yes")
|
||||
self.prizepool = summary.parser.TourneyPool
|
||||
self.numplayers = summary.parser.TourneyPlayers
|
||||
|
||||
self.openwindow() # let's start by getting any info we need.. meh
|
||||
|
||||
def openwindow(self, widget=None):
|
||||
if self.window is not None:
|
||||
self.window.show() # isn't there a better way to bring something to the front? not that GTK focus works right anyway, ever
|
||||
else:
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
print "tournament edit window=", self.window
|
||||
self.window.connect("delete_event", self.delete_event)
|
||||
self.window.connect("destroy", self.destroy)
|
||||
self.window.set_title("FPDB Tournament Entry")
|
||||
self.window.set_border_width(1)
|
||||
self.window.set_default_size(480,640)
|
||||
self.window.set_resizable(True)
|
||||
|
||||
self.main_vbox = gtk.VBox(False, 1)
|
||||
self.main_vbox.set_border_width(1)
|
||||
self.window.add(self.main_vbox)
|
||||
self.window.show()
|
||||
|
||||
def addrebuy(self, widget=None):
|
||||
t = self
|
||||
t.numrebuys += 1
|
||||
t.mylabel.set_label("%s - %s - %s - %s - %s %s - %s - %s - %s - %s - %s" % (t.site, t.id, t.starttime, t.endtime, t.structure, t.game, t.buyin, t.fee, t.numrebuys, t.numplayers, t.prizepool))
|
||||
|
||||
def delete_event(self, widget, event, data=None):
|
||||
return False
|
||||
|
||||
def destroy(self, widget, data=None):
|
||||
return False
|
||||
#end def destroy
|
||||
|
||||
|
||||
class ttracker_main(object):
|
||||
"""A main() object to own both the read_stdin thread and the gui."""
|
||||
# This class mainly provides state for controlling the multiple HUDs.
|
||||
|
||||
def __init__(self, db_name = 'fpdb'):
|
||||
self.db_name = db_name
|
||||
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
|
||||
self.tourney_list = []
|
||||
|
||||
# a thread to read stdin
|
||||
gobject.threads_init() # this is required
|
||||
thread.start_new_thread(self.read_stdin, ()) # starts the thread
|
||||
|
||||
# a main window
|
||||
self.main_window = gtk.Window()
|
||||
self.main_window.connect("destroy", self.destroy)
|
||||
self.vb = gtk.VBox()
|
||||
self.label = gtk.Label('Closing this window will stop the Tournament Tracker')
|
||||
self.vb.add(self.label)
|
||||
self.addbutton = gtk.Button(label="Enter Tournament")
|
||||
self.addbutton.connect("clicked", self.addClicked, "add tournament")
|
||||
self.vb.add(self.addbutton)
|
||||
|
||||
self.main_window.add(self.vb)
|
||||
self.main_window.set_title("FPDB Tournament Tracker")
|
||||
self.main_window.show_all()
|
||||
|
||||
def addClicked(self, widget, data): # what is "data"? i'm guessing anything i pass in after the function name in connect() but unsure because the documentation sucks
|
||||
print "addClicked", widget, data
|
||||
t = Tournament(self, None, None)
|
||||
if t is not None:
|
||||
print "new tournament=", t
|
||||
self.tourney_list.append(t)
|
||||
mylabel = gtk.Label("%s - %s - %s - %s - %s %s - %s - %s - %s - %s - %s" % (t.site, t.id, t.starttime, t.endtime, t.structure, t.game, t.buyin, t.fee, t.numrebuys, t.numplayers, t.prizepool))
|
||||
print "new label=", mylabel
|
||||
editbutton = gtk.Button(label="Edit")
|
||||
print "new button=", editbutton
|
||||
editbutton.connect("clicked", t.openwindow)
|
||||
rebuybutton = gtk.Button(label="Rebuy")
|
||||
rebuybutton.connect("clicked", t.addrebuy)
|
||||
self.vb.add(rebuybutton)
|
||||
self.vb.add(editbutton) # These should probably be put in.. a.. h-box? i don't know..
|
||||
self.vb.add(mylabel)
|
||||
self.main_window.resize_children()
|
||||
self.main_window.show()
|
||||
mylabel.show()
|
||||
editbutton.show()
|
||||
rebuybutton.show()
|
||||
t.mylabel = mylabel
|
||||
t.editbutton = editbutton
|
||||
t.rebuybutton = rebuybutton
|
||||
self.vb.show()
|
||||
print self.tourney_list
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
# when we move the start command over to the main program, we can have the main program ask for the tourney id, and pipe it into the stdin here
|
||||
# at least that was my initial thought on it
|
||||
|
||||
def destroy(*args): # call back for terminating the main eventloop
|
||||
gtk.main_quit()
|
||||
|
||||
def create_HUD(self, new_hand_id, table, table_name, max, poker_game, stat_dict, cards):
|
||||
|
||||
def idle_func():
|
||||
|
||||
gtk.gdk.threads_enter()
|
||||
try:
|
||||
newlabel = gtk.Label("%s - %s" % (table.site, table_name))
|
||||
self.vb.add(newlabel)
|
||||
newlabel.show()
|
||||
self.main_window.resize_children()
|
||||
|
||||
self.hud_dict[table_name].tablehudlabel = newlabel
|
||||
self.hud_dict[table_name].create(new_hand_id, self.config, stat_dict, cards)
|
||||
for m in self.hud_dict[table_name].aux_windows:
|
||||
m.create()
|
||||
m.update_gui(new_hand_id)
|
||||
self.hud_dict[table_name].update(new_hand_id, self.config)
|
||||
self.hud_dict[table_name].reposition_windows()
|
||||
return False
|
||||
finally:
|
||||
gtk.gdk.threads_leave()
|
||||
|
||||
self.hud_dict[table_name] = Hud.Hud(self, table, max, poker_game, self.config, self.db_connection)
|
||||
self.hud_dict[table_name].table_name = table_name
|
||||
self.hud_dict[table_name].stat_dict = stat_dict
|
||||
self.hud_dict[table_name].cards = cards
|
||||
[aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[table_name].aux_windows]
|
||||
gobject.idle_add(idle_func)
|
||||
|
||||
def update_HUD(self, new_hand_id, table_name, config):
|
||||
"""Update a HUD gui from inside the non-gui read_stdin thread."""
|
||||
# This is written so that only 1 thread can touch the gui--mainly
|
||||
# for compatibility with Windows. This method dispatches the
|
||||
# function idle_func() to be run by the gui thread, at its leisure.
|
||||
def idle_func():
|
||||
gtk.gdk.threads_enter()
|
||||
try:
|
||||
self.hud_dict[table_name].update(new_hand_id, config)
|
||||
[aw.update_gui(new_hand_id) for aw in self.hud_dict[table_name].aux_windows]
|
||||
return False
|
||||
finally:
|
||||
gtk.gdk.threads_leave()
|
||||
gobject.idle_add(idle_func)
|
||||
|
||||
def read_stdin(self): # This is the thread function
|
||||
"""Do all the non-gui heavy lifting for the HUD program."""
|
||||
|
||||
# This db connection is for the read_stdin thread only. It should not
|
||||
# be passed to HUDs for use in the gui thread. HUD objects should not
|
||||
# need their own access to the database, but should open their own
|
||||
# if it is required.
|
||||
self.db_connection = Database.Database(self.config, self.db_name, 'temp')
|
||||
# self.db_connection.init_hud_stat_vars(hud_days)
|
||||
tourny_finder = re.compile('(\d+) (\d+)')
|
||||
|
||||
while 1: # wait for a new hand number on stdin
|
||||
new_hand_id = sys.stdin.readline()
|
||||
new_hand_id = string.rstrip(new_hand_id)
|
||||
if new_hand_id == "": # blank line means quit
|
||||
self.destroy()
|
||||
break # this thread is not always killed immediately with gtk.main_quit()
|
||||
# get basic info about the new hand from the db
|
||||
# if there is a db error, complain, skip hand, and proceed
|
||||
try:
|
||||
(table_name, max, poker_game, type) = self.db_connection.get_table_name(new_hand_id)
|
||||
stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, aggregate_stats[type]
|
||||
,hud_style, agg_bb_mult)
|
||||
|
||||
cards = self.db_connection.get_cards(new_hand_id)
|
||||
comm_cards = self.db_connection.get_common_cards(new_hand_id)
|
||||
if comm_cards != {}: # stud!
|
||||
cards['common'] = comm_cards['common']
|
||||
except Exception, err:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "db error: skipping "+str(new_hand_id)+" "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
if new_hand_id: # new_hand_id is none if we had an error prior to the store
|
||||
sys.stderr.write("Database error %s in hand %d. Skipping.\n" % (err, int(new_hand_id)))
|
||||
continue
|
||||
|
||||
if type == "tour": # hand is from a tournament
|
||||
mat_obj = tourny_finder.search(table_name)
|
||||
if mat_obj:
|
||||
(tour_number, tab_number) = mat_obj.group(1, 2)
|
||||
temp_key = tour_number
|
||||
else: # tourney, but can't get number and table
|
||||
print "could not find tournament: skipping "
|
||||
sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id)))
|
||||
continue
|
||||
|
||||
else:
|
||||
temp_key = table_name
|
||||
|
||||
# Update an existing HUD
|
||||
if temp_key in self.hud_dict:
|
||||
self.hud_dict[temp_key].stat_dict = stat_dict
|
||||
self.hud_dict[temp_key].cards = cards
|
||||
[aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[temp_key].aux_windows]
|
||||
self.update_HUD(new_hand_id, temp_key, self.config)
|
||||
|
||||
# Or create a new HUD
|
||||
else:
|
||||
if type == "tour":
|
||||
tablewindow = Tables.discover_tournament_table(self.config, tour_number, tab_number)
|
||||
else:
|
||||
tablewindow = Tables.discover_table_by_name(self.config, table_name)
|
||||
if tablewindow == None:
|
||||
# If no client window is found on the screen, complain and continue
|
||||
if type == "tour":
|
||||
table_name = "%s %s" % (tour_number, tab_number)
|
||||
sys.stderr.write("table name "+table_name+" not found, skipping.\n")
|
||||
else:
|
||||
self.create_HUD(new_hand_id, tablewindow, temp_key, max, poker_game, stat_dict, cards)
|
||||
self.db_connection.connection.rollback()
|
||||
|
||||
if __name__== "__main__":
|
||||
|
||||
sys.stderr.write("tournament tracker starting\n")
|
||||
sys.stderr.write("Using db name = %s\n" % (options.dbname))
|
||||
|
||||
# start the HUD_main object
|
||||
hm = ttracker_main(db_name = options.dbname)
|
||||
|
||||
# start the event loop
|
||||
gtk.main()
|
471
pyfpdb/Tourney.py
Normal file
471
pyfpdb/Tourney.py
Normal file
|
@ -0,0 +1,471 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
#Copyright 2009 Stephane Alessio
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as published by
|
||||
#the Free Software Foundation, version 3 of the License.
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#In the "official" distribution you can find the license in
|
||||
#agpl-3.0.txt in the docs folder of the package.
|
||||
|
||||
# TODO: check to keep only the needed modules
|
||||
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
from decimal import Decimal
|
||||
import operator
|
||||
import time,datetime
|
||||
from copy import deepcopy
|
||||
from Exceptions import *
|
||||
import pprint
|
||||
import DerivedStats
|
||||
import Card
|
||||
|
||||
log = logging.getLogger("parser")
|
||||
|
||||
class Tourney(object):
|
||||
|
||||
################################################################
|
||||
# Class Variables
|
||||
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'} # SAL- TO KEEP ??
|
||||
LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} # SAL- TO KEEP ??
|
||||
SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
|
||||
MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'}
|
||||
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 }
|
||||
|
||||
|
||||
def __init__(self, sitename, gametype, summaryText, builtFrom = "HHC"):
|
||||
self.sitename = sitename
|
||||
self.siteId = self.SITEIDS[sitename]
|
||||
self.gametype = gametype
|
||||
self.starttime = None
|
||||
self.endtime = None
|
||||
self.summaryText = summaryText
|
||||
self.tourneyName = None
|
||||
self.tourNo = None
|
||||
self.buyin = None
|
||||
self.fee = None # the Database code is looking for this one .. ?
|
||||
self.hero = None
|
||||
self.maxseats = None
|
||||
self.entries = 0
|
||||
self.speed = "Normal"
|
||||
self.prizepool = None # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??}
|
||||
self.buyInChips = None
|
||||
self.mixed = None
|
||||
self.isRebuy = False
|
||||
self.isKO = False
|
||||
self.isHU = False
|
||||
self.isMatrix = False
|
||||
self.isShootout = False
|
||||
self.matrixMatchId = None # For Matrix tourneys : 1-4 => match tables (traditionnal), 0 => Positional winnings info
|
||||
self.subTourneyBuyin = None
|
||||
self.subTourneyFee = None
|
||||
self.rebuyChips = 0
|
||||
self.addOnChips = 0
|
||||
self.rebuyAmount = 0
|
||||
self.addOnAmount = 0
|
||||
self.totalRebuys = 0
|
||||
self.totalAddOns = 0
|
||||
self.koBounty = 0
|
||||
self.tourneyComment = None
|
||||
self.players = []
|
||||
|
||||
# Collections indexed by player names
|
||||
self.finishPositions = {}
|
||||
self.winnings = {}
|
||||
self.payinAmounts = {}
|
||||
self.countRebuys = {}
|
||||
self.countAddOns = {}
|
||||
self.countKO = {}
|
||||
|
||||
# currency symbol for this summary
|
||||
self.sym = None
|
||||
#self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
|
||||
|
||||
def __str__(self):
|
||||
#TODO : Update
|
||||
vars = ( ("SITE", self.sitename),
|
||||
("START TIME", self.starttime),
|
||||
("END TIME", self.endtime),
|
||||
("TOURNEY NAME", self.tourneyName),
|
||||
("TOURNEY NO", self.tourNo),
|
||||
("BUYIN", self.buyin),
|
||||
("FEE", self.fee),
|
||||
("HERO", self.hero),
|
||||
("MAXSEATS", self.maxseats),
|
||||
("ENTRIES", self.entries),
|
||||
("SPEED", self.speed),
|
||||
("PRIZE POOL", self.prizepool),
|
||||
("STARTING CHIP COUNT", self.buyInChips),
|
||||
("MIXED", self.mixed),
|
||||
("REBUY ADDON", self.isRebuy),
|
||||
("KO", self.isKO),
|
||||
("HU", self.isHU),
|
||||
("MATRIX", self.isMatrix),
|
||||
("SHOOTOUT", self.isShootout),
|
||||
("MATRIX MATCH ID", self.matrixMatchId),
|
||||
("SUB TOURNEY BUY IN", self.subTourneyBuyin),
|
||||
("SUB TOURNEY FEE", self.subTourneyFee),
|
||||
("REBUY CHIPS", self.rebuyChips),
|
||||
("ADDON CHIPS", self.addOnChips),
|
||||
("REBUY AMOUNT", self.rebuyAmount),
|
||||
("ADDON AMOUNT", self.addOnAmount),
|
||||
("TOTAL REBUYS", self.totalRebuys),
|
||||
("TOTAL ADDONS", self.totalAddOns),
|
||||
("KO BOUNTY", self.koBounty),
|
||||
("TOURNEY COMMENT", self.tourneyComment)
|
||||
)
|
||||
|
||||
structs = ( ("GAMETYPE", self.gametype),
|
||||
("PLAYERS", self.players),
|
||||
("PAYIN AMOUNTS", self.payinAmounts),
|
||||
("POSITIONS", self.finishPositions),
|
||||
("WINNINGS", self.winnings),
|
||||
("COUNT REBUYS", self.countRebuys),
|
||||
("COUNT ADDONS", self.countAddOns),
|
||||
("NB OF KO", self.countKO)
|
||||
)
|
||||
str = ''
|
||||
for (name, var) in vars:
|
||||
str = str + "\n%s = " % name + pprint.pformat(var)
|
||||
|
||||
for (name, struct) in structs:
|
||||
str = str + "\n%s =\n" % name + pprint.pformat(struct, 4)
|
||||
return str
|
||||
|
||||
def getSummaryText(self):
|
||||
return self.summaryText
|
||||
|
||||
def prepInsert(self, db):
|
||||
pass
|
||||
|
||||
def insert(self, db):
|
||||
# First : check all needed info is filled in the object, especially for the initial select
|
||||
|
||||
# Notes on DB Insert
|
||||
# Some identified issues for tourneys already in the DB (which occurs when the HH file is parsed and inserted before the Summary)
|
||||
# Be careful on updates that could make the HH import not match the tourney inserted from a previous summary import !!
|
||||
# BuyIn/Fee can be at 0/0 => match may not be easy
|
||||
# Only one existinf Tourney entry for Matrix Tourneys, but multiple Summary files
|
||||
# Starttime may not match the one in the Summary file : HH = time of the first Hand / could be slighltly different from the one in the summary file
|
||||
# Note: If the TourneyNo could be a unique id .... this would really be a relief to deal with matrix matches ==> Ask on the IRC / Ask Fulltilt ??
|
||||
|
||||
dbTourneyTypeId = db.tRecogniseTourneyType(self)
|
||||
logging.debug("Tourney Type ID = %d" % dbTourneyTypeId)
|
||||
dbTourneyId = db.tRecognizeTourney(self, dbTourneyTypeId)
|
||||
logging.debug("Tourney ID = %d" % dbTourneyId)
|
||||
dbTourneysPlayersIds = db.tStoreTourneyPlayers(self, dbTourneyId)
|
||||
logging.debug("TourneysPlayersId = %s" % dbTourneysPlayersIds)
|
||||
db.tUpdateTourneysHandsPlayers(self, dbTourneysPlayersIds, dbTourneyTypeId)
|
||||
logging.debug("tUpdateTourneysHandsPlayers done")
|
||||
logging.debug("Tourney Insert done")
|
||||
|
||||
# TO DO : Return what has been done (tourney created, updated, nothing)
|
||||
# ?? stored = 1 if tourney is fully created / duplicates = 1, if everything was already here and correct / partial=1 if some things were already here (between tourney, tourneyPlayers and handsplayers)
|
||||
# if so, prototypes may need changes to know what has been done or make some kind of dict in Tourney object that could be updated during the insert process to store that information
|
||||
stored = 0
|
||||
duplicates = 0
|
||||
partial = 0
|
||||
errors = 0
|
||||
ttime = 0
|
||||
return (stored, duplicates, partial, errors, ttime)
|
||||
|
||||
|
||||
def old_insert_from_Hand(self, db):
|
||||
""" Function to insert Hand into database
|
||||
Should not commit, and do minimal selects. Callers may want to cache commits
|
||||
db: a connected fpdb_db object"""
|
||||
# TODO:
|
||||
# Players - base playerid and siteid tuple
|
||||
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
|
||||
|
||||
#Gametypes
|
||||
gtid = db.getGameTypeId(self.siteId, self.gametype)
|
||||
|
||||
# HudCache data to come from DerivedStats class
|
||||
# HandsActions - all actions for all players for all streets - self.actions
|
||||
# Hands - Summary information of hand indexed by handId - gameinfo
|
||||
#This should be moved to prepInsert
|
||||
hh = {}
|
||||
hh['siteHandNo'] = self.handid
|
||||
hh['handStart'] = self.starttime
|
||||
hh['gameTypeId'] = gtid
|
||||
# seats TINYINT NOT NULL,
|
||||
hh['tableName'] = self.tablename
|
||||
hh['maxSeats'] = self.maxseats
|
||||
hh['seats'] = len(sqlids)
|
||||
# Flop turn and river may all be empty - add (likely) too many elements and trim with range
|
||||
boardcards = self.board['FLOP'] + self.board['TURN'] + self.board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
|
||||
cards = [Card.encodeCard(c) for c in boardcards[0:5]]
|
||||
hh['boardcard1'] = cards[0]
|
||||
hh['boardcard2'] = cards[1]
|
||||
hh['boardcard3'] = cards[2]
|
||||
hh['boardcard4'] = cards[3]
|
||||
hh['boardcard5'] = cards[4]
|
||||
|
||||
# texture smallint,
|
||||
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
|
||||
# Needs to be recorded
|
||||
# playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4 */
|
||||
# Needs to be recorded
|
||||
# playersAtStreet2 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtStreet3 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtStreet4 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtShowdown SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# street0Raises TINYINT NOT NULL, /* num small bets paid to see flop/street4, including blind */
|
||||
# Needs to be recorded
|
||||
# street1Raises TINYINT NOT NULL, /* num small bets paid to see turn/street5 */
|
||||
# Needs to be recorded
|
||||
# street2Raises TINYINT NOT NULL, /* num big bets paid to see river/street6 */
|
||||
# Needs to be recorded
|
||||
# street3Raises TINYINT NOT NULL, /* num big bets paid to see sd/street7 */
|
||||
# Needs to be recorded
|
||||
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
|
||||
# Needs to be recorded
|
||||
|
||||
#print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % self.getStreetTotals()
|
||||
#FIXME: Pot size still in decimal, needs to be converted to cents
|
||||
(hh['street1Pot'], hh['street2Pot'], hh['street3Pot'], hh['street4Pot'], hh['showdownPot']) = self.getStreetTotals()
|
||||
|
||||
# comment TEXT,
|
||||
# commentTs DATETIME
|
||||
#print hh
|
||||
handid = db.storeHand(hh)
|
||||
# HandsPlayers - ? ... Do we fix winnings?
|
||||
# Tourneys ?
|
||||
# TourneysPlayers
|
||||
|
||||
pass
|
||||
|
||||
def select(self, tourneyId):
|
||||
""" Function to create Tourney object from database """
|
||||
|
||||
|
||||
|
||||
def addPlayer(self, rank, name, winnings, payinAmount, nbRebuys, nbAddons, nbKO):
|
||||
"""\
|
||||
Adds a player to the tourney, and initialises data structures indexed by player.
|
||||
rank (int) indicating the finishing rank (can be -1 if unknown)
|
||||
name (string) player name
|
||||
winnings (decimal) the money the player ended the tourney with (can be 0, or -1 if unknown)
|
||||
"""
|
||||
log.debug("addPlayer: rank:%s - name : '%s' - Winnings (%s)" % (rank, name, winnings))
|
||||
self.players.append(name)
|
||||
self.finishPositions.update( { name : Decimal(rank) } )
|
||||
self.winnings.update( { name : Decimal(winnings) } )
|
||||
self.payinAmounts.update( {name : Decimal(payinAmount) } )
|
||||
self.countRebuys.update( {name: Decimal(nbRebuys) } )
|
||||
self.countAddOns.update( {name: Decimal(nbAddons) } )
|
||||
self.countKO.update( {name : Decimal(nbKO) } )
|
||||
|
||||
|
||||
def incrementPlayerWinnings(self, name, additionnalWinnings):
|
||||
log.debug("incrementPlayerWinnings: name : '%s' - Add Winnings (%s)" % (name, additionnalWinnings))
|
||||
oldWins = 0
|
||||
if self.winnings.has_key(name):
|
||||
oldWins = self.winnings[name]
|
||||
else:
|
||||
self.players.append([-1, name, 0])
|
||||
|
||||
self.winnings[name] = oldWins + Decimal(additionnalWinnings)
|
||||
|
||||
def checkPlayerExists(self,player):
|
||||
if player not in [p[1] for p in self.players]:
|
||||
print "checkPlayerExists", player, "fail"
|
||||
raise FpdbParseError
|
||||
|
||||
|
||||
def getGameTypeAsString(self):
|
||||
"""\
|
||||
Map the tuple self.gametype onto the pokerstars string describing it
|
||||
"""
|
||||
# currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
|
||||
gs = {"holdem" : "Hold'em",
|
||||
"omahahi" : "Omaha",
|
||||
"omahahilo" : "Omaha Hi/Lo",
|
||||
"razz" : "Razz",
|
||||
"studhi" : "7 Card Stud",
|
||||
"studhilo" : "7 Card Stud Hi/Lo",
|
||||
"fivedraw" : "5 Card Draw",
|
||||
"27_1draw" : "FIXME",
|
||||
"27_3draw" : "Triple Draw 2-7 Lowball",
|
||||
"badugi" : "Badugi"
|
||||
}
|
||||
ls = {"nl" : "No Limit",
|
||||
"pl" : "Pot Limit",
|
||||
"fl" : "Limit",
|
||||
"cn" : "Cap No Limit",
|
||||
"cp" : "Cap Pot Limit"
|
||||
}
|
||||
|
||||
log.debug("gametype: %s" %(self.gametype))
|
||||
retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
|
||||
return retstring
|
||||
|
||||
|
||||
def writeSummary(self, fh=sys.__stdout__):
|
||||
print >>fh, "Override me"
|
||||
|
||||
def printSummary(self):
|
||||
self.writeSummary(sys.stdout)
|
||||
|
||||
|
||||
def assemble(cnxn, tourneyId):
|
||||
# TODO !!
|
||||
c = cnxn.cursor()
|
||||
|
||||
# We need at least sitename, gametype, handid
|
||||
# for the Hand.__init__
|
||||
c.execute("""
|
||||
select
|
||||
s.name,
|
||||
g.category,
|
||||
g.base,
|
||||
g.type,
|
||||
g.limitType,
|
||||
g.hilo,
|
||||
round(g.smallBlind / 100.0,2),
|
||||
round(g.bigBlind / 100.0,2),
|
||||
round(g.smallBet / 100.0,2),
|
||||
round(g.bigBet / 100.0,2),
|
||||
s.currency,
|
||||
h.boardcard1,
|
||||
h.boardcard2,
|
||||
h.boardcard3,
|
||||
h.boardcard4,
|
||||
h.boardcard5
|
||||
from
|
||||
hands as h,
|
||||
sites as s,
|
||||
gametypes as g,
|
||||
handsplayers as hp,
|
||||
players as p
|
||||
where
|
||||
h.id = %(handid)s
|
||||
and g.id = h.gametypeid
|
||||
and hp.handid = h.id
|
||||
and p.id = hp.playerid
|
||||
and s.id = p.siteid
|
||||
limit 1""", {'handid':handid})
|
||||
#TODO: siteid should be in hands table - we took the scenic route through players here.
|
||||
res = c.fetchone()
|
||||
gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]}
|
||||
h = HoldemOmahaHand(hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
|
||||
cards = map(Card.valueSuitFromCard, res[11:16] )
|
||||
if cards[0]:
|
||||
h.setCommunityCards('FLOP', cards[0:3])
|
||||
if cards[3]:
|
||||
h.setCommunityCards('TURN', [cards[3]])
|
||||
if cards[4]:
|
||||
h.setCommunityCards('RIVER', [cards[4]])
|
||||
#[Card.valueSuitFromCard(x) for x in cards]
|
||||
|
||||
# HandInfo : HID, TABLE
|
||||
# BUTTON - why is this treated specially in Hand?
|
||||
# answer: it is written out in hand histories
|
||||
# still, I think we should record all the active seat positions in a seat_order array
|
||||
c.execute("""
|
||||
SELECT
|
||||
h.sitehandno as hid,
|
||||
h.tablename as table,
|
||||
h.handstart as starttime
|
||||
FROM
|
||||
hands as h
|
||||
WHERE h.id = %(handid)s
|
||||
""", {'handid':handid})
|
||||
res = c.fetchone()
|
||||
h.handid = res[0]
|
||||
h.tablename = res[1]
|
||||
h.starttime = res[2] # automatically a datetime
|
||||
|
||||
# PlayerStacks
|
||||
c.execute("""
|
||||
SELECT
|
||||
hp.seatno,
|
||||
round(hp.winnings / 100.0,2) as winnings,
|
||||
p.name,
|
||||
round(hp.startcash / 100.0,2) as chips,
|
||||
hp.card1,hp.card2,
|
||||
hp.position
|
||||
FROM
|
||||
handsplayers as hp,
|
||||
players as p
|
||||
WHERE
|
||||
hp.handid = %(handid)s
|
||||
and p.id = hp.playerid
|
||||
""", {'handid':handid})
|
||||
for (seat, winnings, name, chips, card1,card2, position) in c.fetchall():
|
||||
h.addPlayer(seat,name,chips)
|
||||
if card1 and card2:
|
||||
h.addHoleCards(map(Card.valueSuitFromCard, (card1,card2)), name, dealt=True)
|
||||
if winnings > 0:
|
||||
h.addCollectPot(name, winnings)
|
||||
if position == 'B':
|
||||
h.buttonpos = seat
|
||||
|
||||
|
||||
# actions
|
||||
c.execute("""
|
||||
SELECT
|
||||
(ha.street,ha.actionno) as actnum,
|
||||
p.name,
|
||||
ha.street,
|
||||
ha.action,
|
||||
ha.allin,
|
||||
round(ha.amount / 100.0,2)
|
||||
FROM
|
||||
handsplayers as hp,
|
||||
handsactions as ha,
|
||||
players as p
|
||||
WHERE
|
||||
hp.handid = %(handid)s
|
||||
and ha.handsplayerid = hp.id
|
||||
and p.id = hp.playerid
|
||||
ORDER BY
|
||||
ha.street,ha.actionno
|
||||
""", {'handid':handid})
|
||||
res = c.fetchall()
|
||||
for (actnum,player, streetnum, act, allin, amount) in res:
|
||||
act=act.strip()
|
||||
street = h.allStreets[streetnum+1]
|
||||
if act==u'blind':
|
||||
h.addBlind(player, 'big blind', amount)
|
||||
# TODO: The type of blind is not recorded in the DB.
|
||||
# TODO: preflop street name anomalies in Hand
|
||||
elif act==u'fold':
|
||||
h.addFold(street,player)
|
||||
elif act==u'call':
|
||||
h.addCall(street,player,amount)
|
||||
elif act==u'bet':
|
||||
h.addBet(street,player,amount)
|
||||
elif act==u'check':
|
||||
h.addCheck(street,player)
|
||||
elif act==u'unbet':
|
||||
pass
|
||||
else:
|
||||
print act, player, streetnum, allin, amount
|
||||
# TODO : other actions
|
||||
|
||||
#hhc.readShowdownActions(self)
|
||||
#hc.readShownCards(self)
|
||||
h.totalPot()
|
||||
h.rake = h.totalpot - h.totalcollected
|
||||
|
||||
|
||||
return h
|
||||
|
|
@ -26,6 +26,11 @@ from HandHistoryConverter import *
|
|||
|
||||
class Win2day(HandHistoryConverter):
|
||||
|
||||
sitename = "Win2day"
|
||||
filetype = "text"
|
||||
codepage = "cp1252"
|
||||
siteID = 4
|
||||
|
||||
# Static regexes
|
||||
#<HISTORY ID="102271403" SESSION="session31237702.xml" TABLE="Innsbruck 3" GAME="GAME_THM" GAMETYPE="GAMETYPE_REAL" GAMEKIND="GAMEKIND_CASH" TABLECURRENCY="EUR" LIMIT="NL" STAKES="0.25/0.50" DATE="1246909773" WIN="0.00" LOSS="0.50">
|
||||
|
||||
|
@ -39,15 +44,6 @@ class Win2day(HandHistoryConverter):
|
|||
re_Card = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD>', re.MULTILINE)
|
||||
re_BoardLast = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD></ACTION>', re.MULTILINE)
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
|
||||
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Win2day", follow=follow, index=index)
|
||||
logging.info("Initialising Win2day converter class")
|
||||
self.filetype = "text"
|
||||
self.codepage = "cp1252"
|
||||
self.sideID = 4
|
||||
if autostart:
|
||||
self.start()
|
||||
|
||||
|
||||
def compilePlayerRegexs(self, hand):
|
||||
players = set([player[1] for player in hand.players])
|
||||
|
@ -65,7 +61,7 @@ class Win2day(HandHistoryConverter):
|
|||
self.re_PostBoth = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_AB" VALUE="(?P<SBBB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
|
||||
|
||||
#r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n<CARD LINK="(?P<CARD1>[0-9]+)"></CARD>\n<CARD LINK="(?P<CARD2>[0-9]+)"></CARD></ACTION>'
|
||||
self.re_HeroCards = re.compile(r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]"></CARD>)</ACTION>' % player_re, re.MULTILINE)
|
||||
self.re_HeroCards = re.compile(r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]+"></CARD>)</ACTION>' % player_re, re.MULTILINE)
|
||||
|
||||
#'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>'
|
||||
self.re_Action = re.compile(r'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re, re.MULTILINE)
|
||||
|
|
|
@ -76,6 +76,7 @@ import SQL
|
|||
import Database
|
||||
import FpdbSQLQueries
|
||||
import Configuration
|
||||
from Exceptions import *
|
||||
|
||||
VERSION = "0.11"
|
||||
|
||||
|
@ -98,7 +99,7 @@ class fpdb:
|
|||
for i in self.tab_names: #todo: check this is valid
|
||||
if i==new_tab_name:
|
||||
return # we depend on this to not create duplicate tabs, there's no reason to raise an error here?
|
||||
# raise fpdb_simple.FpdbError("duplicate tab_name not permitted")
|
||||
# raise FpdbError("duplicate tab_name not permitted")
|
||||
|
||||
self.tabs.append(new_tab)
|
||||
self.tab_names.append(new_tab_name)
|
||||
|
@ -120,7 +121,7 @@ class fpdb:
|
|||
break
|
||||
|
||||
if tab_no == -1:
|
||||
raise fpdb_simple.FpdbError("invalid tab_no")
|
||||
raise FpdbError("invalid tab_no")
|
||||
else:
|
||||
self.main_vbox.remove(self.current_tab)
|
||||
#self.current_tab.destroy()
|
||||
|
@ -243,30 +244,27 @@ class fpdb:
|
|||
if self.obtain_global_lock(): # returns true if successful
|
||||
|
||||
#lock_released = False
|
||||
try:
|
||||
dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
|
||||
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
|
||||
diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database " \
|
||||
+self.db.fdb.database+" on "+self.db.fdb.host+" they will be deleted."
|
||||
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
|
||||
dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
|
||||
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
|
||||
diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database " \
|
||||
+self.db.fdb.database+" on "+self.db.fdb.host+" they will be deleted."
|
||||
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
|
||||
|
||||
response = dia_confirm.run()
|
||||
dia_confirm.destroy()
|
||||
if response == gtk.RESPONSE_YES:
|
||||
#if self.db.fdb.backend == self.fdb_lock.fdb.MYSQL_INNODB:
|
||||
# mysql requires locks on all tables or none - easier to release this lock
|
||||
# than lock all the other tables
|
||||
# ToDo: lock all other tables so that lock doesn't have to be released
|
||||
# self.release_global_lock()
|
||||
# lock_released = True
|
||||
self.db.recreate_tables()
|
||||
#else:
|
||||
# for other dbs use same connection as holds global lock
|
||||
# self.fdb_lock.fdb.recreate_tables()
|
||||
elif response == gtk.RESPONSE_NO:
|
||||
print 'User cancelled recreating tables'
|
||||
except:
|
||||
pass
|
||||
response = dia_confirm.run()
|
||||
dia_confirm.destroy()
|
||||
if response == gtk.RESPONSE_YES:
|
||||
#if self.db.fdb.backend == self.fdb_lock.fdb.MYSQL_INNODB:
|
||||
# mysql requires locks on all tables or none - easier to release this lock
|
||||
# than lock all the other tables
|
||||
# ToDo: lock all other tables so that lock doesn't have to be released
|
||||
# self.release_global_lock()
|
||||
# lock_released = True
|
||||
self.db.recreate_tables()
|
||||
#else:
|
||||
# for other dbs use same connection as holds global lock
|
||||
# self.fdb_lock.fdb.recreate_tables()
|
||||
elif response == gtk.RESPONSE_NO:
|
||||
print 'User cancelled recreating tables'
|
||||
#if not lock_released:
|
||||
self.release_global_lock()
|
||||
#end def dia_recreate_tables
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008 Steffen Jobbagy-Felso
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
|
@ -21,8 +22,18 @@ import sys
|
|||
import logging
|
||||
from time import time, strftime
|
||||
|
||||
use_pool = False
|
||||
try:
|
||||
import sqlalchemy.pool as pool
|
||||
use_pool = True
|
||||
except:
|
||||
logging.info("Not using sqlalchemy connection pool.")
|
||||
|
||||
|
||||
import fpdb_simple
|
||||
import FpdbSQLQueries
|
||||
from Exceptions import *
|
||||
|
||||
|
||||
class fpdb_db:
|
||||
MYSQL_INNODB = 2
|
||||
|
@ -63,13 +74,17 @@ class fpdb_db:
|
|||
self.database=database
|
||||
if backend==fpdb_db.MYSQL_INNODB:
|
||||
import MySQLdb
|
||||
if use_pool:
|
||||
MySQLdb = pool.manage(MySQLdb, pool_size=5)
|
||||
try:
|
||||
self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True, charset="utf8")
|
||||
self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True)
|
||||
except:
|
||||
raise fpdb_simple.FpdbError("MySQL connection failed")
|
||||
raise FpdbError("MySQL connection failed")
|
||||
elif backend==fpdb_db.PGSQL:
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
import psycopg2.extensions
|
||||
if use_pool:
|
||||
psycopg2 = pool.manage(psycopg2, pool_size=5)
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
# If DB connection is made over TCP, then the variables
|
||||
# host, user and password are required
|
||||
|
@ -87,7 +102,7 @@ class fpdb_db:
|
|||
pass
|
||||
#msg = "PostgreSQL direct connection to database (%s) failed, trying with user ..." % (database,)
|
||||
#print msg
|
||||
#raise fpdb_simple.FpdbError(msg)
|
||||
#raise FpdbError(msg)
|
||||
if not connected:
|
||||
try:
|
||||
self.db = psycopg2.connect(host = host,
|
||||
|
@ -97,16 +112,19 @@ class fpdb_db:
|
|||
except:
|
||||
msg = "PostgreSQL connection to database (%s) user (%s) failed." % (database, user)
|
||||
print msg
|
||||
raise fpdb_simple.FpdbError(msg)
|
||||
raise FpdbError(msg)
|
||||
elif backend==fpdb_db.SQLITE:
|
||||
logging.info("Connecting to SQLite:%(database)s" % {'database':database})
|
||||
import sqlite3
|
||||
if use_pool:
|
||||
sqlite3 = pool.manage(sqlite3, pool_size=1)
|
||||
else:
|
||||
logging.warning("SQLite won't work well without 'sqlalchemy' installed.")
|
||||
self.db = sqlite3.connect(database,detect_types=sqlite3.PARSE_DECLTYPES)
|
||||
sqlite3.register_converter("bool", lambda x: bool(int(x)))
|
||||
sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
|
||||
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("unrecognised database backend:"+backend)
|
||||
raise FpdbError("unrecognised database backend:"+backend)
|
||||
self.cursor=self.db.cursor()
|
||||
# Set up query dictionary as early in the connection process as we can.
|
||||
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
|
||||
|
@ -148,7 +166,7 @@ class fpdb_db:
|
|||
elif self.backend==4:
|
||||
return "SQLite"
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("invalid backend")
|
||||
raise FpdbError("invalid backend")
|
||||
#end def get_backend_name
|
||||
|
||||
def get_db_info(self):
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
import os # todo: remove this once import_dir is in fpdb_import
|
||||
import sys
|
||||
from time import time, strftime, sleep
|
||||
import logging
|
||||
import traceback
|
||||
import math
|
||||
import datetime
|
||||
|
@ -39,21 +38,26 @@ import Database
|
|||
import fpdb_parse_logic
|
||||
import Configuration
|
||||
|
||||
import logging, logging.config
|
||||
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
|
||||
log = logging.getLogger('importer')
|
||||
|
||||
# database interface modules
|
||||
try:
|
||||
import MySQLdb
|
||||
mysqlLibFound=True
|
||||
log.debug("Import module: MySQLdb")
|
||||
except:
|
||||
pass
|
||||
log.debug("Import module: MySQLdb not found")
|
||||
|
||||
try:
|
||||
import psycopg2
|
||||
pgsqlLibFound=True
|
||||
import psycopg2.extensions
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
|
||||
log.debug("Import module: psycopg2")
|
||||
except:
|
||||
pass
|
||||
log.debug("Import module: psycopg2 not found")
|
||||
|
||||
class Importer:
|
||||
|
||||
|
@ -153,9 +157,9 @@ class Importer:
|
|||
self.siteIds[site] = result[0][0]
|
||||
else:
|
||||
if len(result) == 0:
|
||||
print "[ERROR] Database ID for %s not found" % site
|
||||
log.error("Database ID for %s not found" % site)
|
||||
else:
|
||||
print "[ERROR] More than 1 Database ID found for %s - Multiple currencies not implemented yet" % site
|
||||
log.error("[ERROR] More than 1 Database ID found for %s - Multiple currencies not implemented yet" % site)
|
||||
|
||||
|
||||
# Called from GuiBulkImport to add a file or directory.
|
||||
|
@ -168,7 +172,7 @@ class Importer:
|
|||
if os.path.isdir(inputPath):
|
||||
for subdir in os.walk(inputPath):
|
||||
for file in subdir[2]:
|
||||
self.addImportFile(os.path.join(inputPath, subdir[0], file), site=site, filter=filter)
|
||||
self.addImportFile(os.path.join(subdir[0], file), site=site, filter=filter)
|
||||
else:
|
||||
self.addImportFile(inputPath, site=site, filter=filter)
|
||||
#Add a directory of files to filelist
|
||||
|
@ -189,7 +193,7 @@ class Importer:
|
|||
#print " adding file ", file
|
||||
self.addImportFile(os.path.join(dir, file), site, filter)
|
||||
else:
|
||||
print "Warning: Attempted to add non-directory: '" + str(dir) + "' as an import directory"
|
||||
log.warning("Attempted to add non-directory: '" + str(dir) + "' as an import directory")
|
||||
|
||||
def runImport(self):
|
||||
""""Run full import on self.filelist. This is called from GuiBulkImport.py"""
|
||||
|
@ -199,7 +203,7 @@ class Importer:
|
|||
# Initial setup
|
||||
start = datetime.datetime.now()
|
||||
starttime = time()
|
||||
print "Started at", start, "--", len(self.filelist), "files to import.", self.settings['dropIndexes']
|
||||
log.info("Started at %s -- %d files to import. indexes: %s" % (start, len(self.filelist), self.settings['dropIndexes']))
|
||||
if self.settings['dropIndexes'] == 'auto':
|
||||
self.settings['dropIndexes'] = self.calculate_auto2(self.database, 12.0, 500.0)
|
||||
if 'dropHudCache' in self.settings and self.settings['dropHudCache'] == 'auto':
|
||||
|
@ -208,7 +212,7 @@ class Importer:
|
|||
if self.settings['dropIndexes'] == 'drop':
|
||||
self.database.prepareBulkImport()
|
||||
else:
|
||||
print "No need to drop indexes."
|
||||
log.debug("No need to drop indexes.")
|
||||
#print "dropInd =", self.settings['dropIndexes'], " dropHudCache =", self.settings['dropHudCache']
|
||||
|
||||
if self.settings['threads'] <= 0:
|
||||
|
@ -380,12 +384,13 @@ class Importer:
|
|||
conv = None
|
||||
(stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, 0)
|
||||
|
||||
file = file.decode(fpdb_simple.LOCALE_ENCODING)
|
||||
|
||||
# Load filter, process file, pass returned filename to import_fpdb_file
|
||||
|
||||
if self.settings['threads'] > 0 and self.writeq != None:
|
||||
print "\nConverting " + file + " (" + str(q.qsize()) + ")"
|
||||
log.info("Converting " + file + " (" + str(q.qsize()) + ")")
|
||||
else:
|
||||
print "\nConverting " + file
|
||||
log.info("Converting " + file)
|
||||
hhbase = self.config.get_import_parameters().get("hhArchiveBase")
|
||||
hhbase = os.path.expanduser(hhbase)
|
||||
hhdir = os.path.join(hhbase,site)
|
||||
|
@ -413,10 +418,10 @@ class Importer:
|
|||
else:
|
||||
# conversion didn't work
|
||||
# TODO: appropriate response?
|
||||
return (0, 0, 0, 1, 0, -1)
|
||||
return (0, 0, 0, 1, 0)
|
||||
else:
|
||||
print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter)
|
||||
return (0, 0, 0, 1, 0, -1)
|
||||
log.warning("Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter))
|
||||
return (0, 0, 0, 1, 0)
|
||||
|
||||
#This will barf if conv.getStatus != True
|
||||
return (stored, duplicates, partial, errors, ttime)
|
||||
|
@ -458,7 +463,7 @@ class Importer:
|
|||
db.commit()
|
||||
ttime = time() - starttime
|
||||
if q == None:
|
||||
print "\rTotal stored:", stored, " duplicates:", duplicates, "errors:", errors, " time:", ttime
|
||||
log.info("Total stored: %(stored)d\tduplicates:%(duplicates)d\terrors:%(errors)d\ttime:%(ttime)s" % locals())
|
||||
|
||||
if not stored:
|
||||
if duplicates:
|
||||
|
@ -533,7 +538,7 @@ class Importer:
|
|||
if self.callHud:
|
||||
#print "call to HUD here. handsId:",handsId
|
||||
#pipe the Hands.id out to the HUD
|
||||
print "sending hand to hud", handsId, "pipe =", self.caller.pipe_to_hud
|
||||
#print "sending hand to hud", handsId, "pipe =", self.caller.pipe_to_hud
|
||||
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
|
||||
except fpdb_simple.DuplicateError:
|
||||
duplicates += 1
|
||||
|
|
|
@ -22,6 +22,7 @@ import sys
|
|||
import fpdb_simple
|
||||
import Database
|
||||
from time import time, strftime
|
||||
from Exceptions import *
|
||||
|
||||
|
||||
#parses a holdem hand
|
||||
|
@ -67,7 +68,8 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
|
|||
tourneyStartTime= handStartTime #todo: read tourney start time
|
||||
rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0])
|
||||
|
||||
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(db.get_cursor(), siteID, buyin, fee, knockout, rebuyOrAddon)
|
||||
## The tourney site id has to be searched because it may already be in db with a TourneyTypeId which is different from the one automatically calculated (Summary import first)
|
||||
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(db, siteID, siteTourneyNo, buyin, fee, knockout, rebuyOrAddon)
|
||||
else:
|
||||
siteTourneyNo = -1
|
||||
buyin = -1
|
||||
|
@ -126,7 +128,7 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
|
|||
elif lineTypes[i]=="table":
|
||||
tableResult=fpdb_simple.parseTableLine(base, line)
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i])
|
||||
raise FpdbError("unrecognised lineType:"+lineTypes[i])
|
||||
|
||||
maxSeats = tableResult['maxSeats']
|
||||
tableName = tableResult['tableName']
|
||||
|
|
|
@ -25,6 +25,8 @@ import datetime
|
|||
import time
|
||||
import re
|
||||
import sys
|
||||
from Exceptions import *
|
||||
import locale
|
||||
|
||||
import Card
|
||||
|
||||
|
@ -37,18 +39,8 @@ MYSQL_INNODB = 2
|
|||
PGSQL = 3
|
||||
SQLITE = 4
|
||||
|
||||
class DuplicateError(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
class FpdbError(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
LOCALE_ENCODING = locale.getdefaultlocale()[1]
|
||||
|
||||
#returns an array of the total money paid. intending to add rebuys/addons here
|
||||
def calcPayin(count, buyin, fee):
|
||||
return [buyin + fee for i in xrange(count)]
|
||||
|
@ -224,7 +216,7 @@ def fillCardArrays(player_count, base, category, card_values, card_suits):
|
|||
elif base=="stud":
|
||||
cardCount = 7
|
||||
else:
|
||||
raise fpdb_simple.FpdbError("invalid category:", category)
|
||||
raise FpdbError("invalid category:", category)
|
||||
|
||||
for i in xrange(player_count):
|
||||
while (len(card_values[i]) < cardCount):
|
||||
|
@ -543,7 +535,7 @@ def parseActionType(line):
|
|||
#parses the ante out of the given line and checks which player paid it, updates antes accordingly.
|
||||
def parseAnteLine(line, isTourney, names, antes):
|
||||
for i, name in enumerate(names):
|
||||
if line.startswith(name.encode("latin-1")):
|
||||
if line.startswith(name.encode(LOCALE_ENCODING)):
|
||||
pos = line.rfind("$") + 1
|
||||
if not isTourney:
|
||||
antes[i] += float2int(line[pos:])
|
||||
|
@ -705,7 +697,7 @@ def parseHandStartTime(topline):
|
|||
def findName(line):
|
||||
pos1 = line.find(":") + 2
|
||||
pos2 = line.rfind("(") - 1
|
||||
return unicode(line[pos1:pos2], "latin-1")
|
||||
return unicode(line[pos1:pos2], LOCALE_ENCODING)
|
||||
|
||||
def parseNames(lines):
|
||||
return [findName(line) for line in lines]
|
||||
|
@ -822,7 +814,7 @@ def parseTourneyNo(topline):
|
|||
def parseWinLine(line, names, winnings, isTourney):
|
||||
#print "parseWinLine: line:",line
|
||||
for i,n in enumerate(names):
|
||||
n = n.encode("latin-1")
|
||||
n = n.encode(LOCALE_ENCODING)
|
||||
if line.startswith(n):
|
||||
if isTourney:
|
||||
pos1 = line.rfind("collected ") + 10
|
||||
|
@ -951,17 +943,28 @@ def recogniseGametypeID(backend, db, cursor, topline, smallBlindLine, site_id, c
|
|||
return result[0]
|
||||
#end def recogniseGametypeID
|
||||
|
||||
def recogniseTourneyTypeId(cursor, siteId, buyin, fee, knockout, rebuyOrAddon):
|
||||
cursor.execute ("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon))
|
||||
def recogniseTourneyTypeId(db, siteId, tourneySiteId, buyin, fee, knockout, rebuyOrAddon):
|
||||
cursor = db.get_cursor()
|
||||
# First we try to find the tourney itself (by its tourneySiteId) in case it has already been inserted before (by a summary file for instance)
|
||||
# The reason is that some tourneys may not be identified correctly in the HH toplines (especially Buy-In and Fee which are used to search/create the TourneyTypeId)
|
||||
#TODO: When the summary file will be dumped to BD, if the tourney is already in, Buy-In/Fee may need an update (e.g. creation of a new type and link to the Tourney)
|
||||
cursor.execute (db.sql.query['getTourneyTypeIdByTourneyNo'].replace('%s', db.sql.query['placeholder']), (tourneySiteId, siteId))
|
||||
result=cursor.fetchone()
|
||||
#print "tried SELECTing gametypes.id, result:",result
|
||||
|
||||
try:
|
||||
len(result)
|
||||
except TypeError:#this means we need to create a new entry
|
||||
cursor.execute("""INSERT INTO TourneyTypes (siteId, buyin, fee, knockout, rebuyOrAddon) VALUES (%s, %s, %s, %s, %s)""", (siteId, buyin, fee, knockout, rebuyOrAddon))
|
||||
cursor.execute("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon))
|
||||
except:
|
||||
cursor.execute ("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon))
|
||||
result=cursor.fetchone()
|
||||
#print "tried SELECTing gametypes.id, result:",result
|
||||
|
||||
try:
|
||||
len(result)
|
||||
except TypeError:#this means we need to create a new entry
|
||||
cursor.execute("""INSERT INTO TourneyTypes (siteId, buyin, fee, knockout, rebuyOrAddon) VALUES (%s, %s, %s, %s, %s)""", (siteId, buyin, fee, knockout, rebuyOrAddon))
|
||||
cursor.execute("SELECT id FROM TourneyTypes WHERE siteId=%s AND buyin=%s AND fee=%s AND knockout=%s AND rebuyOrAddon=%s", (siteId, buyin, fee, knockout, rebuyOrAddon))
|
||||
result=cursor.fetchone()
|
||||
|
||||
return result[0]
|
||||
#end def recogniseTourneyTypeId
|
||||
|
||||
|
@ -985,17 +988,17 @@ def recogniseTourneyTypeId(cursor, siteId, buyin, fee, knockout, rebuyOrAddon):
|
|||
# return result
|
||||
|
||||
def recognisePlayerIDs(db, names, site_id):
|
||||
q = "SELECT name,id FROM Players WHERE name=" + " OR name=".join([db.sql.query['placeholder'] for n in names])
|
||||
c = db.get_cursor()
|
||||
q = "SELECT name,id FROM Players WHERE siteid=%d and (name=%s)" %(site_id, " OR name=".join([db.sql.query['placeholder'] for n in names]))
|
||||
c.execute(q, names) # get all playerids by the names passed in
|
||||
ids = dict(c.fetchall()) # convert to dict
|
||||
if len(ids) != len(names):
|
||||
notfound = [n for n in names if n not in ids] # make list of names not in database
|
||||
if notfound: # insert them into database
|
||||
#q_ins = "INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")"
|
||||
#q_ins = q_ins.replace('%s', db.sql.query['placeholder'])
|
||||
c.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", [(n,) for n in notfound])
|
||||
q2 = "SELECT name,id FROM Players WHERE name=%s" % " OR name=".join(["%s" for n in notfound])
|
||||
q_ins = "INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")"
|
||||
q_ins = q_ins.replace('%s', db.sql.query['placeholder'])
|
||||
c.executemany(q_ins, [(n,) for n in notfound])
|
||||
q2 = "SELECT name,id FROM Players WHERE siteid=%d and (name=%s)" % (site_id, " OR name=".join(["%s" for n in notfound]))
|
||||
q2 = q2.replace('%s', db.sql.query['placeholder'])
|
||||
c.execute(q2, notfound) # get their new ids
|
||||
tmp = c.fetchall()
|
||||
|
@ -1032,14 +1035,15 @@ def recognisePlayerIDs(db, names, site_id):
|
|||
def recognisePlayerNo(line, names, atype):
|
||||
#print "recogniseplayerno, names:",names
|
||||
for i in xrange(len(names)):
|
||||
encodedName = names[i].encode(LOCALE_ENCODING)
|
||||
if (atype=="unbet"):
|
||||
if (line.endswith(names[i].encode("latin-1"))):
|
||||
if (line.endswith(encodedName)):
|
||||
return (i)
|
||||
elif (line.startswith("Dealt to ")):
|
||||
#print "recognisePlayerNo, card precut, line:",line
|
||||
tmp=line[9:]
|
||||
#print "recognisePlayerNo, card postcut, tmp:",tmp
|
||||
if (tmp.startswith(names[i].encode("latin-1"))):
|
||||
if (tmp.startswith(encodedName)):
|
||||
return (i)
|
||||
elif (line.startswith("Seat ")):
|
||||
if (line.startswith("Seat 10")):
|
||||
|
@ -1047,10 +1051,10 @@ def recognisePlayerNo(line, names, atype):
|
|||
else:
|
||||
tmp=line[8:]
|
||||
|
||||
if (tmp.startswith(names[i].encode("latin-1"))):
|
||||
if (tmp.startswith(encodedName)):
|
||||
return (i)
|
||||
else:
|
||||
if (line.startswith(names[i].encode("latin-1"))):
|
||||
if (line.startswith(encodedName)):
|
||||
return (i)
|
||||
#if we're here we mustve failed
|
||||
raise FpdbError ("failed to recognise player in: "+line+" atype:"+atype)
|
||||
|
|
|
@ -1,271 +1,271 @@
|
|||
|
||||
# Code from http://ender.snowburst.org:4747/~jjohns/interlocks.py
|
||||
# Thanks JJ!
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
import subprocess
|
||||
import time
|
||||
import signal
|
||||
import base64
|
||||
|
||||
InterProcessLock = None
|
||||
|
||||
"""
|
||||
Just use me like a thread lock. acquire() / release() / locked()
|
||||
|
||||
Differences compared to thread locks:
|
||||
1. By default, acquire()'s wait parameter is false.
|
||||
2. When acquire fails, SingleInstanceError is thrown instead of simply returning false.
|
||||
3. acquire() can take a 3rd parameter retry_time, which, if wait is True, tells the locking
|
||||
mechanism how long to sleep between retrying the lock. Has no effect for unix/InterProcessLockFcntl.
|
||||
|
||||
Differences in fpdb version to JJ's original:
|
||||
1. Changed acquire() to return false like other locks
|
||||
2. Made acquire fail if same process already has the lock
|
||||
"""
|
||||
|
||||
class SingleInstanceError(RuntimeError):
|
||||
"Thrown when you try to acquire an InterProcessLock and another version of the process is already running."
|
||||
|
||||
class InterProcessLockBase:
|
||||
def __init__(self, name=None ):
|
||||
self._has_lock = False
|
||||
if not name:
|
||||
name = sys.argv[0]
|
||||
self.name = name
|
||||
|
||||
def getHashedName(self):
|
||||
return base64.b64encode(self.name).replace('=','')
|
||||
|
||||
def acquire_impl(self, wait): abstract
|
||||
|
||||
def acquire(self, wait=False, retry_time=1):
|
||||
if self._has_lock: # make sure 2nd acquire in same process fails
|
||||
return False
|
||||
while not self._has_lock:
|
||||
try:
|
||||
self.acquire_impl(wait)
|
||||
self._has_lock = True
|
||||
#print 'i have the lock'
|
||||
except SingleInstanceError:
|
||||
if not wait:
|
||||
# raise # change back to normal acquire functionality, sorry JJ!
|
||||
return False
|
||||
time.sleep(retry_time)
|
||||
return True
|
||||
|
||||
def release(self):
|
||||
self.release_impl()
|
||||
self._has_lock = False
|
||||
|
||||
def locked(self):
|
||||
if self._has_lock:
|
||||
return True
|
||||
try:
|
||||
self.acquire()
|
||||
self.release()
|
||||
return False
|
||||
except SingleInstanceError:
|
||||
return True
|
||||
|
||||
LOCK_FILE_DIRECTORY = '/tmp'
|
||||
|
||||
class InterProcessLockFcntl(InterProcessLockBase):
|
||||
def __init__(self, name=None):
|
||||
InterProcessLockBase.__init__(self, name)
|
||||
self.lockfd = 0
|
||||
self.lock_file_name = os.path.join(LOCK_FILE_DIRECTORY, self.getHashedName() + '.lck')
|
||||
assert(os.path.isdir(LOCK_FILE_DIRECTORY))
|
||||
|
||||
# This is the suggested way to get a safe file name, but I like having a descriptively named lock file.
|
||||
def getHashedName(self):
|
||||
import re
|
||||
bad_filename_character_re = re.compile(r'/\?<>\\\:;\*\|\'\"\^=\.\[\]')
|
||||
return bad_filename_character_re.sub('_',self.name)
|
||||
|
||||
def acquire_impl(self, wait):
|
||||
self.lockfd = open(self.lock_file_name, 'w')
|
||||
fcntrl_options = fcntl.LOCK_EX
|
||||
if not wait:
|
||||
fcntrl_options |= fcntl.LOCK_NB
|
||||
try:
|
||||
fcntl.flock(self.lockfd, fcntrl_options)
|
||||
except IOError:
|
||||
self.lockfd.close()
|
||||
self.lockfd = 0
|
||||
raise SingleInstanceError('Could not acquire exclusive lock on '+self.lock_file_name)
|
||||
|
||||
def release_impl(self):
|
||||
fcntl.lockf(self.lockfd, fcntl.LOCK_UN)
|
||||
self.lockfd.close()
|
||||
self.lockfd = 0
|
||||
try:
|
||||
os.unlink(self.lock_file_name)
|
||||
except IOError:
|
||||
# We don't care about the existence of the file too much here. It's the flock() we care about,
|
||||
# And that should just go away magically.
|
||||
pass
|
||||
|
||||
class InterProcessLockWin32(InterProcessLockBase):
|
||||
def __init__(self, name=None):
|
||||
InterProcessLockBase.__init__(self, name)
|
||||
self.mutex = None
|
||||
|
||||
def acquire_impl(self,wait):
|
||||
self.mutex = win32event.CreateMutex(None, 0, self.getHashedName())
|
||||
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
|
||||
self.mutex.Close()
|
||||
self.mutex = None
|
||||
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
|
||||
|
||||
def release_impl(self):
|
||||
self.mutex.Close()
|
||||
|
||||
class InterProcessLockSocket(InterProcessLockBase):
|
||||
def __init__(self, name=None):
|
||||
InterProcessLockBase.__init__(self, name)
|
||||
self.socket = None
|
||||
self.portno = 65530 - abs(self.getHashedName().__hash__()) % 32749
|
||||
|
||||
def acquire_impl(self, wait):
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
self.socket.bind(('127.0.0.1', self.portno))
|
||||
except socket.error:
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
|
||||
|
||||
def release_impl(self):
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
|
||||
# Set InterProcessLock to the correct type given the sysem parameters available
|
||||
try:
|
||||
import fcntl
|
||||
InterProcessLock = InterProcessLockFcntl
|
||||
except ImportError:
|
||||
try:
|
||||
import win32event
|
||||
import win32api
|
||||
import winerror
|
||||
InterProcessLock = InterProcessLockWin32
|
||||
except ImportError:
|
||||
import socket
|
||||
InterProcessLock = InterProcessLockSocket
|
||||
|
||||
def test_construct():
|
||||
"""
|
||||
# Making the name of the test unique so it can be executed my multiple users on the same machine.
|
||||
>>> test_name = 'InterProcessLockTest' +str(os.getpid()) + str(time.time())
|
||||
|
||||
>>> lock1 = InterProcessLock(name=test_name)
|
||||
>>> lock1.acquire()
|
||||
|
||||
>>> lock2 = InterProcessLock(name=test_name)
|
||||
>>> lock3 = InterProcessLock(name=test_name)
|
||||
|
||||
# Since lock1 is locked, other attempts to acquire it fail.
|
||||
>>> lock2.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
>>> lock3.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
# Release the lock and let lock2 have it.
|
||||
>>> lock1.release()
|
||||
>>> lock2.acquire()
|
||||
|
||||
>>> lock3.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
# Release it and give it back to lock1
|
||||
>>> lock2.release()
|
||||
>>> lock1.acquire()
|
||||
|
||||
>>> lock2.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
# Test lock status
|
||||
>>> lock2.locked()
|
||||
True
|
||||
>>> lock3.locked()
|
||||
True
|
||||
>>> lock1.locked()
|
||||
True
|
||||
|
||||
>>> lock1.release()
|
||||
|
||||
>>> lock2.locked()
|
||||
False
|
||||
>>> lock3.locked()
|
||||
False
|
||||
>>> lock1.locked()
|
||||
False
|
||||
|
||||
>>> if os.name == 'posix':
|
||||
... def os_independent_kill(pid):
|
||||
... import signal
|
||||
... os.kill(pid, signal.SIGKILL)
|
||||
... else:
|
||||
... assert(os.name == 'nt')
|
||||
... def os_independent_kill(pid):
|
||||
... ''' http://www.python.org/doc/faq/windows/#how-do-i-emulate-os-kill-in-windows '''
|
||||
... import win32api
|
||||
... import win32con
|
||||
... import pywintypes
|
||||
... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid)
|
||||
... return (0 != win32api.TerminateProcess(handle, 0))
|
||||
|
||||
# Test to acquire the lock in another process.
|
||||
>>> def execute(cmd):
|
||||
... cmd = 'import time;' + cmd + 'time.sleep(10);'
|
||||
... process = subprocess.Popen([sys.executable, '-c', cmd])
|
||||
... pid = process.pid
|
||||
... time.sleep(2) # quick hack, but we test synchronization in the end
|
||||
... return pid
|
||||
|
||||
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
|
||||
|
||||
>>> lock1.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
>>> os_independent_kill(pid)
|
||||
|
||||
>>> time.sleep(1)
|
||||
|
||||
>>> lock1.acquire()
|
||||
>>> lock1.release()
|
||||
|
||||
# Testing wait
|
||||
|
||||
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
|
||||
|
||||
>>> lock1.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
>>> os_independent_kill(pid)
|
||||
|
||||
>>> lock1.acquire(True)
|
||||
>>> lock1.release()
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
if __name__=='__main__':
|
||||
import doctest
|
||||
doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)
|
||||
|
||||
# Code from http://ender.snowburst.org:4747/~jjohns/interlocks.py
|
||||
# Thanks JJ!
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
import subprocess
|
||||
import time
|
||||
import signal
|
||||
import base64
|
||||
|
||||
InterProcessLock = None
|
||||
|
||||
"""
|
||||
Just use me like a thread lock. acquire() / release() / locked()
|
||||
|
||||
Differences compared to thread locks:
|
||||
1. By default, acquire()'s wait parameter is false.
|
||||
2. When acquire fails, SingleInstanceError is thrown instead of simply returning false.
|
||||
3. acquire() can take a 3rd parameter retry_time, which, if wait is True, tells the locking
|
||||
mechanism how long to sleep between retrying the lock. Has no effect for unix/InterProcessLockFcntl.
|
||||
|
||||
Differences in fpdb version to JJ's original:
|
||||
1. Changed acquire() to return false like other locks
|
||||
2. Made acquire fail if same process already has the lock
|
||||
"""
|
||||
|
||||
class SingleInstanceError(RuntimeError):
|
||||
"Thrown when you try to acquire an InterProcessLock and another version of the process is already running."
|
||||
|
||||
class InterProcessLockBase:
|
||||
def __init__(self, name=None ):
|
||||
self._has_lock = False
|
||||
if not name:
|
||||
name = sys.argv[0]
|
||||
self.name = name
|
||||
|
||||
def getHashedName(self):
|
||||
return base64.b64encode(self.name).replace('=','')
|
||||
|
||||
def acquire_impl(self, wait): abstract
|
||||
|
||||
def acquire(self, wait=False, retry_time=1):
|
||||
if self._has_lock: # make sure 2nd acquire in same process fails
|
||||
return False
|
||||
while not self._has_lock:
|
||||
try:
|
||||
self.acquire_impl(wait)
|
||||
self._has_lock = True
|
||||
#print 'i have the lock'
|
||||
except SingleInstanceError:
|
||||
if not wait:
|
||||
# raise # change back to normal acquire functionality, sorry JJ!
|
||||
return False
|
||||
time.sleep(retry_time)
|
||||
return True
|
||||
|
||||
def release(self):
|
||||
self.release_impl()
|
||||
self._has_lock = False
|
||||
|
||||
def locked(self):
|
||||
if self._has_lock:
|
||||
return True
|
||||
try:
|
||||
self.acquire()
|
||||
self.release()
|
||||
return False
|
||||
except SingleInstanceError:
|
||||
return True
|
||||
|
||||
LOCK_FILE_DIRECTORY = '/tmp'
|
||||
|
||||
class InterProcessLockFcntl(InterProcessLockBase):
|
||||
def __init__(self, name=None):
|
||||
InterProcessLockBase.__init__(self, name)
|
||||
self.lockfd = 0
|
||||
self.lock_file_name = os.path.join(LOCK_FILE_DIRECTORY, self.getHashedName() + '.lck')
|
||||
assert(os.path.isdir(LOCK_FILE_DIRECTORY))
|
||||
|
||||
# This is the suggested way to get a safe file name, but I like having a descriptively named lock file.
|
||||
def getHashedName(self):
|
||||
import re
|
||||
bad_filename_character_re = re.compile(r'/\?<>\\\:;\*\|\'\"\^=\.\[\]')
|
||||
return bad_filename_character_re.sub('_',self.name)
|
||||
|
||||
def acquire_impl(self, wait):
|
||||
self.lockfd = open(self.lock_file_name, 'w')
|
||||
fcntrl_options = fcntl.LOCK_EX
|
||||
if not wait:
|
||||
fcntrl_options |= fcntl.LOCK_NB
|
||||
try:
|
||||
fcntl.flock(self.lockfd, fcntrl_options)
|
||||
except IOError:
|
||||
self.lockfd.close()
|
||||
self.lockfd = 0
|
||||
raise SingleInstanceError('Could not acquire exclusive lock on '+self.lock_file_name)
|
||||
|
||||
def release_impl(self):
|
||||
fcntl.lockf(self.lockfd, fcntl.LOCK_UN)
|
||||
self.lockfd.close()
|
||||
self.lockfd = 0
|
||||
try:
|
||||
os.unlink(self.lock_file_name)
|
||||
except IOError:
|
||||
# We don't care about the existence of the file too much here. It's the flock() we care about,
|
||||
# And that should just go away magically.
|
||||
pass
|
||||
|
||||
class InterProcessLockWin32(InterProcessLockBase):
|
||||
def __init__(self, name=None):
|
||||
InterProcessLockBase.__init__(self, name)
|
||||
self.mutex = None
|
||||
|
||||
def acquire_impl(self,wait):
|
||||
self.mutex = win32event.CreateMutex(None, 0, self.getHashedName())
|
||||
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
|
||||
self.mutex.Close()
|
||||
self.mutex = None
|
||||
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
|
||||
|
||||
def release_impl(self):
|
||||
self.mutex.Close()
|
||||
|
||||
class InterProcessLockSocket(InterProcessLockBase):
|
||||
def __init__(self, name=None):
|
||||
InterProcessLockBase.__init__(self, name)
|
||||
self.socket = None
|
||||
self.portno = 65530 - abs(self.getHashedName().__hash__()) % 32749
|
||||
|
||||
def acquire_impl(self, wait):
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
self.socket.bind(('127.0.0.1', self.portno))
|
||||
except socket.error:
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
|
||||
|
||||
def release_impl(self):
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
|
||||
# Set InterProcessLock to the correct type given the sysem parameters available
|
||||
try:
|
||||
import fcntl
|
||||
InterProcessLock = InterProcessLockFcntl
|
||||
except ImportError:
|
||||
try:
|
||||
import win32event
|
||||
import win32api
|
||||
import winerror
|
||||
InterProcessLock = InterProcessLockWin32
|
||||
except ImportError:
|
||||
import socket
|
||||
InterProcessLock = InterProcessLockSocket
|
||||
|
||||
def test_construct():
|
||||
"""
|
||||
# Making the name of the test unique so it can be executed my multiple users on the same machine.
|
||||
>>> test_name = 'InterProcessLockTest' +str(os.getpid()) + str(time.time())
|
||||
|
||||
>>> lock1 = InterProcessLock(name=test_name)
|
||||
>>> lock1.acquire()
|
||||
|
||||
>>> lock2 = InterProcessLock(name=test_name)
|
||||
>>> lock3 = InterProcessLock(name=test_name)
|
||||
|
||||
# Since lock1 is locked, other attempts to acquire it fail.
|
||||
>>> lock2.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
>>> lock3.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
# Release the lock and let lock2 have it.
|
||||
>>> lock1.release()
|
||||
>>> lock2.acquire()
|
||||
|
||||
>>> lock3.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
# Release it and give it back to lock1
|
||||
>>> lock2.release()
|
||||
>>> lock1.acquire()
|
||||
|
||||
>>> lock2.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
# Test lock status
|
||||
>>> lock2.locked()
|
||||
True
|
||||
>>> lock3.locked()
|
||||
True
|
||||
>>> lock1.locked()
|
||||
True
|
||||
|
||||
>>> lock1.release()
|
||||
|
||||
>>> lock2.locked()
|
||||
False
|
||||
>>> lock3.locked()
|
||||
False
|
||||
>>> lock1.locked()
|
||||
False
|
||||
|
||||
>>> if os.name == 'posix':
|
||||
... def os_independent_kill(pid):
|
||||
... import signal
|
||||
... os.kill(pid, signal.SIGKILL)
|
||||
... else:
|
||||
... assert(os.name == 'nt')
|
||||
... def os_independent_kill(pid):
|
||||
... ''' http://www.python.org/doc/faq/windows/#how-do-i-emulate-os-kill-in-windows '''
|
||||
... import win32api
|
||||
... import win32con
|
||||
... import pywintypes
|
||||
... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid)
|
||||
... return (0 != win32api.TerminateProcess(handle, 0))
|
||||
|
||||
# Test to acquire the lock in another process.
|
||||
>>> def execute(cmd):
|
||||
... cmd = 'import time;' + cmd + 'time.sleep(10);'
|
||||
... process = subprocess.Popen([sys.executable, '-c', cmd])
|
||||
... pid = process.pid
|
||||
... time.sleep(2) # quick hack, but we test synchronization in the end
|
||||
... return pid
|
||||
|
||||
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
|
||||
|
||||
>>> lock1.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
>>> os_independent_kill(pid)
|
||||
|
||||
>>> time.sleep(1)
|
||||
|
||||
>>> lock1.acquire()
|
||||
>>> lock1.release()
|
||||
|
||||
# Testing wait
|
||||
|
||||
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
|
||||
|
||||
>>> lock1.acquire()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
|
||||
|
||||
>>> os_independent_kill(pid)
|
||||
|
||||
>>> lock1.acquire(True)
|
||||
>>> lock1.release()
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
if __name__=='__main__':
|
||||
import doctest
|
||||
doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)
|
||||
|
|
57
pyfpdb/logging.conf
Normal file
57
pyfpdb/logging.conf
Normal file
|
@ -0,0 +1,57 @@
|
|||
[loggers]
|
||||
keys=root,parser,importer,config,db
|
||||
|
||||
[handlers]
|
||||
keys=consoleHandler,fileHandler
|
||||
|
||||
[formatters]
|
||||
keys=fileFormatter,stderrFormatter
|
||||
|
||||
[logger_root]
|
||||
level=INFO
|
||||
handlers=consoleHandler,fileHandler
|
||||
|
||||
[logger_parser]
|
||||
level=INFO
|
||||
handlers=consoleHandler,fileHandler
|
||||
qualname=parser
|
||||
propagate=0
|
||||
|
||||
[logger_importer]
|
||||
level=DEBUG
|
||||
handlers=consoleHandler,fileHandler
|
||||
qualname=importer
|
||||
propagate=0
|
||||
|
||||
[logger_config]
|
||||
level=DEBUG
|
||||
handlers=consoleHandler,fileHandler
|
||||
qualname=config
|
||||
propagate=0
|
||||
|
||||
[logger_db]
|
||||
level=DEBUG
|
||||
handlers=consoleHandler,fileHandler
|
||||
qualname=db
|
||||
propagate=0
|
||||
|
||||
[handler_consoleHandler]
|
||||
class=StreamHandler
|
||||
level=DEBUG
|
||||
formatter=stderrFormatter
|
||||
args=(sys.stderr,)
|
||||
|
||||
[handler_fileHandler]
|
||||
class=FileHandler
|
||||
level=DEBUG
|
||||
formatter=fileFormatter
|
||||
args=('logging.out', 'a')
|
||||
|
||||
|
||||
[formatter_fileFormatter]
|
||||
format=%(asctime)s - %(name)-12s %(levelname)-8s %(message)s
|
||||
datefmt=
|
||||
|
||||
[formatter_stderrFormatter]
|
||||
format=%(name)-12s: %(levelname)-8s %(message)s
|
||||
datefmt=
|
54
pyfpdb/py2exe_setup.py
Normal file
54
pyfpdb/py2exe_setup.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""setup.py
|
||||
|
||||
Py2exe script for fpdb.
|
||||
"""
|
||||
# Copyright 2009, Ray E. Barker
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
########################################################################
|
||||
|
||||
#TODO: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate
|
||||
# include the lib needed to handle png files in mucked
|
||||
# get rid of all the uneeded libraries (e.g., pyQT)
|
||||
# think about an installer
|
||||
|
||||
from distutils.core import setup
|
||||
import py2exe
|
||||
|
||||
setup(
|
||||
name = 'fpdb',
|
||||
description = 'Free Poker DataBase',
|
||||
version = '0.12',
|
||||
|
||||
console = [ {'script': 'fpdb.py', },
|
||||
{'script': 'HUD_main.py', }
|
||||
],
|
||||
|
||||
options = {'py2exe': {
|
||||
'packages' :'encodings',
|
||||
'includes' : 'cairo, pango, pangocairo, atk, gobject, PokerStarsToFpdb',
|
||||
'excludes' : '_tkagg, _agg2, cocoaagg, fltkagg',
|
||||
'dll_excludes': 'libglade-2.0-0.dll',
|
||||
}
|
||||
},
|
||||
|
||||
data_files = ['HUD_config.xml',
|
||||
'Cards01.png'
|
||||
]
|
||||
)
|
||||
|
|
@ -4,7 +4,12 @@ import py
|
|||
|
||||
# regression-test-files/fulltilt/nlhe/NLHE-6max-1.txt
|
||||
# Sorrowful: start: $8.85 end: $14.70 total: $5.85
|
||||
|
||||
|
||||
# 'Canceled' hand
|
||||
# regression-test-files/fulltilt/lh/Marlin.txt
|
||||
|
||||
|
||||
|
||||
def checkGameInfo(hhc, header, info):
|
||||
assert hhc.determineGameType(header) == info
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ text = ""
|
|||
|
||||
hhc = PokerStarsToFpdb.PokerStars(autostart=False)
|
||||
|
||||
h = HoldemOmahaHand(None, "ASite", gametype, text, builtFrom = "Test")
|
||||
h = HoldemOmahaHand(None, "PokerStars", gametype, text, builtFrom = "Test")
|
||||
h.addPlayer("1", "s0rrow", "100000")
|
||||
|
||||
hhc.compilePlayerRegexs(h)
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
|
||||
# script to update indexes on mysql (+other?) database
|
||||
|
||||
select '1. Dropping indexes' as ' ';
|
||||
select 'Can''t drop messages indicate index already gone' as ' ';
|
||||
|
||||
ALTER TABLE `fpdb`.`Settings` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Sites` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Gametypes` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Players` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Autorates` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Hands` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`BoardCards` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`TourneyTypes` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Tourneys` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`TourneysPlayers` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`HandsPlayers` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`HandsActions` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`HudCache` DROP INDEX `id`;
|
||||
|
||||
select '2. Adding extra indexes on useful fields' as ' ';
|
||||
select 'Duplicate key name messages indicate new indexes already there' as ' ';
|
||||
|
||||
ALTER TABLE `fpdb`.`tourneys` ADD INDEX `siteTourneyNo`(`siteTourneyNo`);
|
||||
ALTER TABLE `fpdb`.`hands` ADD INDEX `siteHandNo`(`siteHandNo`);
|
||||
ALTER TABLE `fpdb`.`players` ADD INDEX `name`(`name`);
|
||||
|
||||
|
||||
# script to update indexes on mysql (+other?) database
|
||||
|
||||
select '1. Dropping indexes' as ' ';
|
||||
select 'Can''t drop messages indicate index already gone' as ' ';
|
||||
|
||||
ALTER TABLE `fpdb`.`Settings` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Sites` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Gametypes` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Players` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Autorates` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Hands` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`BoardCards` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`TourneyTypes` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`Tourneys` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`TourneysPlayers` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`HandsPlayers` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`HandsActions` DROP INDEX `id`;
|
||||
ALTER TABLE `fpdb`.`HudCache` DROP INDEX `id`;
|
||||
|
||||
select '2. Adding extra indexes on useful fields' as ' ';
|
||||
select 'Duplicate key name messages indicate new indexes already there' as ' ';
|
||||
|
||||
ALTER TABLE `fpdb`.`tourneys` ADD INDEX `siteTourneyNo`(`siteTourneyNo`);
|
||||
ALTER TABLE `fpdb`.`hands` ADD INDEX `siteHandNo`(`siteHandNo`);
|
||||
ALTER TABLE `fpdb`.`players` ADD INDEX `name`(`name`);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user