pulled and merged from fpdboz

This commit is contained in:
sqlcoder 2008-12-07 23:38:33 +00:00
commit 4dc15bfd94
30 changed files with 7474 additions and 5957 deletions

View File

@ -1,67 +1,67 @@
Hi, Hi,
This document is to serve as a little overview (later: full technical doc) for current and prospective developers with: This document is to serve as a little overview (later: full technical doc) for current and prospective developers with:
a) introduction into the code structure a) introduction into the code structure
b) organisational/legal things b) organisational/legal things
What to do? What to do?
=========== ===========
- Anything you want. - Anything you want.
- The most useful (because it's the most boring) would be to update print_hand.py, update the expected files (testdata/*.found.txt) and create more .found.txt to ensure import processing is running correctly. - The most useful (because it's the most boring) would be to update print_hand.py, update the expected files (testdata/*.found.txt) and create more .found.txt to ensure import processing is running correctly.
- There's a list of various bugs, deficiencies and important missing features in known_bugs_and_planned_features.txt. - There's a list of various bugs, deficiencies and important missing features in known_bugs_and_planned_features.txt.
- In the main GUI there's various menu points marked with todo - all of these will have to be done eventually. - In the main GUI there's various menu points marked with todo - all of these will have to be done eventually.
If you want to take a look at coding-style.txt. If you want to take a look at coding-style.txt.
Ideally use git (see git-instructions.txt for some commands) and let me know where to pull from, alternatively feel free to send patches or even just changed file in whatever code layout or naming convention you like best. I will, of course, still give you full credit. Ideally use git (see git-instructions.txt for some commands) and let me know where to pull from, alternatively feel free to send patches or even just changed file in whatever code layout or naming convention you like best. I will, of course, still give you full credit.
Contact/Communication Contact/Communication
===================== =====================
If you start working on something please open a bug or feature request at sf to avoid someone else from doing the same If you start working on something please open a bug or feature request at sf to avoid someone else from doing the same
Please see readme-overview Please see readme-overview
Dependencies Dependencies
============ ============
Please let me know before you add any new dependencies and ensure that they are source-compatible between *nix and Windows Please let me know before you add any new dependencies and ensure that they are source-compatible between *nix and Windows
Code/File/Class Structure Code/File/Class Structure
========================= =========================
Basically the code runs like this Basically the code runs like this
fpdb.py -> bulk importer tab (import_threaded.py) -> fpdb_import.py -> fpdb_parse_logic.py -> fpdb_save_to_db.py fpdb.py -> bulk importer tab (import_threaded.py) -> fpdb_import.py -> fpdb_parse_logic.py -> fpdb_save_to_db.py
or or
fpdb.py -> table viewer tab (table_viewer.py) (todo: -> libTableViewer) fpdb.py -> table viewer tab (table_viewer.py) (todo: -> libTableViewer)
All files call the simple methods that I just collected in fpdb_simple.py, to abstract the other files off the nitty gritty details as I was learning python. All files call the simple methods that I just collected in fpdb_simple.py, to abstract the other files off the nitty gritty details as I was learning python.
I'm currently working on (amongst other things) integrating everything into the fpdb.py GUI with a view to allow easy creation of a CLI client, too. I'm currently working on (amongst other things) integrating everything into the fpdb.py GUI with a view to allow easy creation of a CLI client, too.
Also see filelist.txt. Also see filelist.txt.
How to Commit How to Commit
============= =============
Please make sure you read and accept the copyright policy as stated in this file. Then see git-instructions.txt. Don't get me wrong, I hate all this legalese, but unfortunately it's kinda necessary. Please make sure you read and accept the copyright policy as stated in this file. Then see git-instructions.txt. Don't get me wrong, I hate all this legalese, but unfortunately it's kinda necessary.
Copyright/Licensing Copyright/Licensing
=================== ===================
Copyright by default is handled on a per-file basis. If you send in a patch or make a commit to an existing file it is done on the understanding that you transfer all rights (as far as legally possible in your jurisdiction) to the current copyright holder of that file, unless otherwise stated. If you create a new file please ensure to include a copyright and license statement. Copyright by default is handled on a per-file basis. If you send in a patch or make a commit to an existing file it is done on the understanding that you transfer all rights (as far as legally possible in your jurisdiction) to the current copyright holder of that file, unless otherwise stated. If you create a new file please ensure to include a copyright and license statement.
The licenses used by this project are the AGPL3 for code and FDL1.2 for documentation. See readme-overview.txt for reasons and if you wish to use fpdb with different licensing. The licenses used by this project are the AGPL3 for code and FDL1.2 for documentation. See readme-overview.txt for reasons and if you wish to use fpdb with different licensing.
Preferred File Formats Preferred File Formats
====================== ======================
Preferred: Where possible simple text-based formats, e.g. plain text (with Unix end of line char) or (X)HTML. Preferred picture format is PNG. IE-compability for HTML files is optional as IE was never meant to be a real web browser, if it were they would've implemented web standards. Preferred: Where possible simple text-based formats, e.g. plain text (with Unix end of line char) or (X)HTML. Preferred picture format is PNG. IE-compability for HTML files is optional as IE was never meant to be a real web browser, if it were they would've implemented web standards.
Also good: Other free and open formats, e.g. ODF. Also good: Other free and open formats, e.g. ODF.
Not good: Any format that doesn't have full documentation freely and publicly available with a proper license for anyone to implement it. Sadly, Microsoft has chosen not fulfil these requirements for ISO MS OOXML to become a truly open standard. Not good: Any format that doesn't have full documentation freely and publicly available with a proper license for anyone to implement it. Sadly, Microsoft has chosen not fulfil these requirements for ISO MS OOXML to become a truly open standard.
License (of this file) License (of this file)
======= =======
Trademarks of third parties have been used under Fair Use or similar laws. Trademarks of third parties have been used under Fair Use or similar laws.
Copyright 2008 Steffen Jobbagy-Felso Copyright 2008 Steffen Jobbagy-Felso
Permission is granted to copy, distribute and/or modify this Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License, document under the terms of the GNU Free Documentation License,
Version 1.2 as published by the Free Software Foundation; with Version 1.2 as published by the Free Software Foundation; with
no Invariant Sections, no Front-Cover Texts, and with no Back-Cover no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
Texts. A copy of the license can be found in fdl-1.2.txt Texts. A copy of the license can be found in fdl-1.2.txt
The program itself is licensed under AGPLv3, see agpl-3.0.txt The program itself is licensed under AGPLv3, see agpl-3.0.txt

View File

@ -1,29 +1,29 @@
#!/usr/bin/pugs #!/usr/bin/pugs
#Copyright 2008 Steffen Jobbagy-Felso #Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify #This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by #it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License. #the Free Software Foundation, version 3 of the License.
# #
#This program is distributed in the hope that it will be useful, #This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of #but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details. #GNU General Public License for more details.
# #
#You should have received a copy of the GNU Affero General Public License #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/>. #along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
use v6; use v6;
#use strict; #use strict;
use LibFpdbImport; use LibFpdbImport;
use LibFpdbShared; use LibFpdbShared;
my Database $db .= new(:backend<MySQL InnoDB>, :host<localhost>, :database<fpdb>, :user<fpdb>, :password<myPW>); my Database $db .= new(:backend<MySQL InnoDB>, :host<localhost>, :database<fpdb>, :user<fpdb>, :password<myPW>);
#todo: below doesnt work #todo: below doesnt work
my Importer $imp .= new(:db($db), :filename<HH-LHE1.txt>); my Importer $imp .= new(:db($db), :filename<HH-LHE1.txt>);
#perlbug?: adding another named argument that isnt listed in the constructor gave very weird error. #perlbug?: adding another named argument that isnt listed in the constructor gave very weird error.
say $imp; say $imp;

109
pyfpdb/CarbonToFpdb.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python
# Copyright 2008, Carl Gherardi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# Standard Library modules
import Configuration
import traceback
import sys
import re
import xml.dom.minidom
from xml.dom.minidom import Node
from HandHistoryConverter import HandHistoryConverter
# Carbon format looks like:
# 1) <description type="Holdem" stakes="No Limit ($0.25/$0.50)"/>
# 2) <game id="14902583-5578" starttime="20081006145401" numholecards="2" gametype="2" realmoney="true" data="20081006|Niagara Falls (14902583)|14902583|14902583-5578|false">
# 3) <players dealer="8">
# <player seat="3" nickname="PlayerInSeat3" balance="$43.29" dealtin="true" />
# ...
# 4) <round id="BLINDS" sequence="1">
# <event sequence="1" type="SMALL_BLIND" player="0" amount="0.25"/>
# <event sequence="2" type="BIG_BLIND" player="1" amount="0.50"/>
# 5) <round id="PREFLOP" sequence="2">
# <event sequence="3" type="CALL" player="2" amount="0.50"/>
# 6) <round id="POSTFLOP" sequence="3">
# <event sequence="16" type="BET" player="3" amount="1.00"/>
# ....
# <cards type="COMMUNITY" cards="7d,Jd,Jh"/>
# The full sequence for a NHLE cash game is:
# BLINDS, PREFLOP, POSTFLOP, POSTTURN, POSTRIVER, SHOWDOWN, END_OF_GAME
# This sequence can be terminated after BLINDS at any time by END_OF_FOLDED_GAME
class CarbonPoker(HandHistoryConverter):
def __init__(self, config, filename):
print "Initialising Carbon Poker converter class"
HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init
self.setFileType("xml")
def readSupportedGames(self):
pass
def determineGameType(self):
gametype = []
desc_node = self.doc.getElementsByTagName("description")
#TODO: no examples of non ring type yet
gametype = gametype + ["ring"]
type = desc_node[0].getAttribute("type")
if(type == "Holdem"):
gametype = gametype + ["hold"]
else:
print "Unknown gametype: '%s'" % (type)
stakes = desc_node[0].getAttribute("stakes")
#TODO: no examples of anything except nlhe
m = re.match('(?P<LIMIT>No Limit)\s\(\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\)', stakes)
if(m.group('LIMIT') == "No Limit"):
gametype = gametype + ["nl"]
gametype = gametype + [self.float2int(m.group('SB'))]
gametype = gametype + [self.float2int(m.group('BB'))]
return gametype
def readPlayerStacks(self):
pass
def readBlinds(self):
pass
def readAction(self):
pass
# Override read function as xml.minidom barfs on the Carbon layout
# This is pretty dodgy
def readFile(self, filename):
print "Carbon: Reading file: '%s'" %(filename)
infile=open(filename, "rU")
self.obs = infile.read()
infile.close()
self.obs = "<CarbonHHFile>\n" + self.obs + "</CarbonHHFile>"
try:
doc = xml.dom.minidom.parseString(self.obs)
self.doc = doc
except:
traceback.print_exc(file=sys.stderr)
if __name__ == "__main__":
c = Configuration.Config()
e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml")
e.processFile()
print str(e)

View File

@ -53,7 +53,7 @@ if __name__ == "__main__":
(options, sys.argv) = parser.parse_args() (options, sys.argv) = parser.parse_args()
settings={'imp-callFpdbHud':False, 'db-backend':2} settings={'callFpdbHud':False, 'db-backend':2}
settings['db-host']=options.server settings['db-host']=options.server
settings['db-user']=options.user settings['db-user']=options.user
settings['db-password']=options.password settings['db-password']=options.password

File diff suppressed because it is too large Load Diff

View File

@ -142,12 +142,27 @@ class Database:
cards[s_dict['seat_number']] = s_dict cards[s_dict['seat_number']] = s_dict
return (cards) return (cards)
def get_stats_from_hand(self, hand, player_id = False): def get_action_from_hand(self, hand_no):
action = [ [], [], [], [], [] ]
c = self.connection.cursor()
c.execute(self.sql.query['get_action_from_hand'], (hand_no))
for row in c.fetchall():
street = row[0]
act = row[1:]
action[street].append(act)
return action
def get_stats_from_hand(self, hand, aggregate = False):
c = self.connection.cursor() c = self.connection.cursor()
if not player_id: player_id = "%" if aggregate:
query = 'get_stats_from_hand'
subs = (hand, hand)
else:
query = 'get_stats_from_hand_aggregated'
subs = (hand, hand, hand)
# get the players in the hand and their seats # get the players in the hand and their seats
# c.execute(self.sql.query['get_players_from_hand'], (hand, player_id))
c.execute(self.sql.query['get_players_from_hand'], (hand, )) c.execute(self.sql.query['get_players_from_hand'], (hand, ))
names = {} names = {}
seats = {} seats = {}
@ -156,8 +171,7 @@ class Database:
seats[row[0]] = row[1] seats[row[0]] = row[1]
# now get the stats # now get the stats
# c.execute(self.sql.query['get_stats_from_hand'], (hand, hand, player_id)) c.execute(self.sql.query[query], subs)
c.execute(self.sql.query['get_stats_from_hand'], (hand, hand))
colnames = [desc[0] for desc in c.description] colnames = [desc[0] for desc in c.description]
stat_dict = {} stat_dict = {}
for row in c.fetchall(): for row in c.fetchall():

176
pyfpdb/EverleafToFpdb.py Executable file
View File

@ -0,0 +1,176 @@
#!/usr/bin/env python
# Copyright 2008, Carl Gherardi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
import sys
import Configuration
from HandHistoryConverter import *
# Everleaf HH format
# Everleaf Gaming Game #55208539
# ***** Hand history for game #55208539 *****
# Blinds $0.50/$1 NL Hold'em - 2008/09/01 - 13:35:01
# Table Speed Kuala
# Seat 1 is the button
# Total number of players: 9
# Seat 1: BadBeatBox ( $ 98.97 USD )
# Seat 3: EricBlade ( $ 73.96 USD )
# Seat 4: randy888 ( $ 196.50 USD )
# Seat 5: BaronSengir ( $ 182.80 USD )
# Seat 6: dogge ( $ 186.06 USD )
# Seat 7: wings ( $ 50 USD )
# Seat 8: schoffeltje ( $ 282.05 USD )
# Seat 9: harrydebeng ( $ 109.45 USD )
# Seat 10: smaragdar ( $ 96.50 USD )
# EricBlade: posts small blind [$ 0.50 USD]
# randy888: posts big blind [$ 1 USD]
# wings: posts big blind [$ 1 USD]
# ** Dealing down cards **
# Dealt to EricBlade [ qc, 3c ]
# BaronSengir folds
# dogge folds
# wings raises [$ 2.50 USD]
# schoffeltje folds
# harrydebeng calls [$ 3.50 USD]
# smaragdar raises [$ 15.50 USD]
# BadBeatBox folds
# EricBlade folds
# randy888 folds
# wings calls [$ 12 USD]
# harrydebeng folds
# ** Dealing Flop ** [ qs, 3d, 8h ]
# wings: bets [$ 34.50 USD]
# smaragdar calls [$ 34.50 USD]
# ** Dealing Turn ** [ 2d ]
# ** Dealing River ** [ 6c ]
# dogge shows [ 9h, 9c ]a pair of nines
# spicybum shows [ 5d, 6d ]a straight, eight high
# harrydebeng does not show cards
# smaragdar wins $ 102 USD from main pot with a pair of aces [ ad, ah, qs, 8h, 6c ]
class Everleaf(HandHistoryConverter):
def __init__(self, config, file):
print "Initialising Everleaf converter class"
HandHistoryConverter.__init__(self, config, file, "Everleaf") # Call super class init.
self.sitename = "Everleaf"
self.setFileType("text")
self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n\n\n')
self.rexx.setHandInfoRegex('.*#(?P<HID>[0-9]+)\n.*\nBlinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+)\nTable (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
self.rexx.setPlayerInfoRegex('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \( \$ (?P<CASH>[.0-9]+) USD \)')
self.rexx.setPostSbRegex('.*\n(?P<PNAME>.*): posts small blind \[\$? (?P<SB>[.0-9]+)')
self.rexx.setPostBbRegex('.*\n(?P<PNAME>.*): posts big blind \[\$? (?P<BB>[.0-9]+)')
# mct : what about posting small & big blinds simultaneously?
self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P<PNAME>.*)\s\[ (?P<HOLE1>\S\S), (?P<HOLE2>\S\S) \]')
self.rexx.setActionStepRegex('.*\n(?P<PNAME>.*) (?P<ATYPE>bets|checks|raises|calls|folds)(\s\[\$ (?P<BET>[.\d]+) USD\])?')
self.rexx.compileRegexes()
def readSupportedGames(self):
pass
def determineGameType(self):
# Cheating with this regex, only support nlhe at the moment
gametype = ["ring", "hold", "nl"]
m = self.rexx.game_info_re.search(self.obs)
gametype = gametype + [m.group('SB')]
gametype = gametype + [m.group('BB')]
return gametype
def readHandInfo(self, hand):
m = self.rexx.hand_info_re.search(hand.string)
hand.handid = m.group('HID')
hand.tablename = m.group('TABLE')
# These work, but the info is already in the Hand class - should be used for tourneys though.
# m.group('SB')
# m.group('BB')
# m.group('GAMETYPE')
# Believe Everleaf time is GMT/UTC, no transation necessary
# Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET]
# or : 2008/11/07 12:38:49 ET
# Not getting it in my HH files yet, so using
# 2008/11/10 3:58:52 ET
#TODO: Do conversion from GMT to ET
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this)
hand.starttime = "%d/%02d/%02d %d:%02d:%02d ET" %(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')),
int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand):
m = self.rexx.player_info_re.finditer(hand.string)
players = []
for a in m:
hand.addPlayer(a.group('SEAT'), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
m = re.search('(\*\* Dealing down cards \*\*\n)(?P<PREFLOP>.*?\n\*\*)?( Dealing Flop \*\* \[ (?P<FLOP1>\S\S), (?P<FLOP2>\S\S), (?P<FLOP3>\S\S) \])?(?P<FLOP>.*?\*\*)?( Dealing Turn \*\* \[ (?P<TURN1>\S\S) \])?(?P<TURN>.*?\*\*)?( Dealing River \*\* \[ (?P<RIVER1>\S\S) \])?(?P<RIVER>.*)', hand.string,re.DOTALL)
# for street in m.groupdict():
# print "DEBUG: Street: %s\tspan: %s" %(street, str(m.span(street)))
hand.streets = m
def readBlinds(self, hand):
try:
m = self.rexx.small_blind_re.search(hand.string)
hand.addBlind(m.group('PNAME'), m.group('SB'))
#hand.posted = [m.group('PNAME')]
except:
hand.addBlind(None, 0)
#hand.posted = ["FpdbNBP"]
m = self.rexx.big_blind_re.finditer(hand.string)
for a in m:
hand.addBlind(a.group('PNAME'), a.group('BB'))
#hand.posted = hand.posted + [a.group('PNAME')]
def readHeroCards(self, hand):
m = self.rexx.hero_cards_re.search(hand.string)
if(m == None):
#Not involved in hand
hand.involved = False
else:
hand.hero = m.group('PNAME')
hand.addHoleCards(m.group('HOLE1'), m.group('HOLE2'))
def readAction(self, hand, street):
m = self.rexx.action_re.finditer(hand.streets.group(street))
hand.actions[street] = []
for action in m:
if action.group('ATYPE') == 'raises':
hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'calls':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'bets':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
else:
print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),)
hand.actions[street] += [[action.group('PNAME'), action.group('ATYPE')]]
def getRake(self, hand):
hand.rake = hand.totalpot * Decimal('0.05') # probably not quite right
if __name__ == "__main__":
c = Configuration.Config()
e = Everleaf(c, "Speed_Kuala.txt")
e.processFile()
print str(e)

161
pyfpdb/FpdbRegex.py Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,211 +1,211 @@
#!/usr/bin/python #!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso #Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify #This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by #it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License. #the Free Software Foundation, version 3 of the License.
# #
#This program is distributed in the hope that it will be useful, #This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of #but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details. #GNU General Public License for more details.
# #
#You should have received a copy of the GNU Affero General Public License #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/>. #along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
import threading import threading
import subprocess import subprocess
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import gobject import gobject
import os import os
import time import time
import fpdb_import import fpdb_import
class GuiAutoImport (threading.Thread): class GuiAutoImport (threading.Thread):
def __init__(self, settings, config): def __init__(self, settings, config):
"""Constructor for GuiAutoImport""" """Constructor for GuiAutoImport"""
self.settings=settings self.settings=settings
self.config=config self.config=config
imp = self.config.get_import_parameters() imp = self.config.get_import_parameters()
print "Import parameters" print "Import parameters"
print imp print imp
self.input_settings = {} self.input_settings = {}
self.importer = fpdb_import.Importer(self, self.settings, self.config) self.importer = fpdb_import.Importer(self,self.settings, self.config)
self.importer.setCallHud(True) self.importer.setCallHud(True)
self.importer.setMinPrint(30) self.importer.setMinPrint(30)
self.importer.setQuiet(False) self.importer.setQuiet(False)
self.importer.setFailOnError(False) self.importer.setFailOnError(False)
self.importer.setHandCount(0) self.importer.setHandCount(0)
# self.importer.setWatchTime() # self.importer.setWatchTime()
self.server=settings['db-host'] self.server=settings['db-host']
self.user=settings['db-user'] self.user=settings['db-user']
self.password=settings['db-password'] self.password=settings['db-password']
self.database=settings['db-databaseName'] self.database=settings['db-databaseName']
self.mainVBox=gtk.VBox(False,1) self.mainVBox=gtk.VBox(False,1)
self.mainVBox.show() self.mainVBox.show()
self.settingsHBox = gtk.HBox(False, 0) self.settingsHBox = gtk.HBox(False, 0)
self.mainVBox.pack_start(self.settingsHBox, False, True, 0) self.mainVBox.pack_start(self.settingsHBox, False, True, 0)
self.settingsHBox.show() self.settingsHBox.show()
self.intervalLabel = gtk.Label("Time between imports in seconds:") self.intervalLabel = gtk.Label("Interval (ie. break) between imports in seconds:")
self.settingsHBox.pack_start(self.intervalLabel) self.settingsHBox.pack_start(self.intervalLabel)
self.intervalLabel.show() self.intervalLabel.show()
self.intervalEntry=gtk.Entry() self.intervalEntry=gtk.Entry()
self.intervalEntry.set_text(str(self.config.get_import_parameters().get("interval"))) self.intervalEntry.set_text(str(self.config.get_import_parameters().get("interval")))
self.settingsHBox.pack_start(self.intervalEntry) self.settingsHBox.pack_start(self.intervalEntry)
self.intervalEntry.show() self.intervalEntry.show()
self.addSites(self.mainVBox) self.addSites(self.mainVBox)
self.startButton=gtk.Button("Start Autoimport") self.startButton=gtk.Button("Start Autoimport")
self.startButton.connect("clicked", self.startClicked, "start clicked") self.startButton.connect("clicked", self.startClicked, "start clicked")
self.mainVBox.add(self.startButton) self.mainVBox.add(self.startButton)
self.startButton.show() self.startButton.show()
#end of GuiAutoImport.__init__ #end of GuiAutoImport.__init__
def browseClicked(self, widget, data): def browseClicked(self, widget, data):
"""runs when user clicks one of the browse buttons in the auto import tab""" """runs when user clicks one of the browse buttons in the auto import tab"""
current_path=data[1].get_text() current_path=data[1].get_text()
dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import",
action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
#dia_chooser.set_current_folder(pathname) #dia_chooser.set_current_folder(pathname)
dia_chooser.set_filename(current_path) dia_chooser.set_filename(current_path)
#dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import
response = dia_chooser.run() response = dia_chooser.run()
if response == gtk.RESPONSE_OK: if response == gtk.RESPONSE_OK:
#print dia_chooser.get_filename(), 'selected' #print dia_chooser.get_filename(), 'selected'
data[1].set_text(dia_chooser.get_filename()) data[1].set_text(dia_chooser.get_filename())
self.input_settings[data[0]][0] = dia_chooser.get_filename() self.input_settings[data[0]][0] = dia_chooser.get_filename()
elif response == gtk.RESPONSE_CANCEL: elif response == gtk.RESPONSE_CANCEL:
print 'Closed, no files selected' print 'Closed, no files selected'
dia_chooser.destroy() dia_chooser.destroy()
#end def GuiAutoImport.browseClicked #end def GuiAutoImport.browseClicked
def do_import(self): def do_import(self):
"""Callback for timer to do an import iteration.""" """Callback for timer to do an import iteration."""
self.importer.runUpdated() self.importer.runUpdated()
print "GuiAutoImport.import_dir done" print "GuiAutoImport.import_dir done"
return True return True
def startClicked(self, widget, data): def startClicked(self, widget, data):
"""runs when user clicks start on auto import tab""" """runs when user clicks start on auto import tab"""
# Check to see if we have an open file handle to the HUD and open one if we do not. # Check to see if we have an open file handle to the HUD and open one if we do not.
# bufsize = 1 means unbuffered # bufsize = 1 means unbuffered
# We need to close this file handle sometime. # We need to close this file handle sometime.
# TODO: Allow for importing from multiple dirs - REB 29AUG2008 # TODO: Allow for importing from multiple dirs - REB 29AUG2008
# As presently written this function does nothing if there is already a pipe open. # As presently written this function does nothing if there is already a pipe open.
# That is not correct. It should open another dir for importing while piping the # That is not correct. It should open another dir for importing while piping the
# results to the same pipe. This means that self.path should be a a list of dirs # results to the same pipe. This means that self.path should be a a list of dirs
# to watch. # to watch.
try: #uhhh, I don't this this is the best way to check for the existence of an attr try: #uhhh, I don't this this is the best way to check for the existence of an attr
getattr(self, "pipe_to_hud") getattr(self, "pipe_to_hud")
except AttributeError: except AttributeError:
if os.name == 'nt': if os.name == 'nt':
command = "python HUD_main.py" + " %s" % (self.database) command = "python HUD_main.py" + " %s" % (self.database)
bs = 0 # windows is not happy with line buffing here bs = 0 # windows is not happy with line buffing here
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
else: else:
cwd = os.getcwd() cwd = os.getcwd()
command = os.path.join(cwd, 'HUD_main.py') command = os.path.join(cwd, 'HUD_main.py')
bs = 1 bs = 1
self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
# self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, # self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
# universal_newlines=True) # universal_newlines=True)
# command = command + " %s" % (self.database) # command = command + " %s" % (self.database)
# print "command = ", command # print "command = ", command
# self.pipe_to_hud = os.popen(command, 'w') # self.pipe_to_hud = os.popen(command, 'w')
# Add directories to importer object. # Add directories to importer object.
for site in self.input_settings: for site in self.input_settings:
self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1]) self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1])
print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0]) print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0])
self.do_import() self.do_import()
interval=int(self.intervalEntry.get_text()) interval=int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import) gobject.timeout_add(interval*1000, self.do_import)
#end def GuiAutoImport.startClicked #end def GuiAutoImport.startClicked
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.mainVBox return self.mainVBox
#end def get_vbox #end def get_vbox
#Create the site line given required info and setup callbacks #Create the site line given required info and setup callbacks
#enabling and disabling sites from this interface not possible #enabling and disabling sites from this interface not possible
#expects a box to layout the line horizontally #expects a box to layout the line horizontally
def createSiteLine(self, hbox, site, iconpath, hhpath, filter_name, active = True): def createSiteLine(self, hbox, site, iconpath, hhpath, filter_name, active = True):
label = gtk.Label(site + " auto-import:") label = gtk.Label(site + " auto-import:")
hbox.pack_start(label, False, False, 0) hbox.pack_start(label, False, False, 0)
label.show() label.show()
dirPath=gtk.Entry() dirPath=gtk.Entry()
dirPath.set_text(hhpath) dirPath.set_text(hhpath)
hbox.pack_start(dirPath, False, True, 0) hbox.pack_start(dirPath, False, True, 0)
dirPath.show() dirPath.show()
browseButton=gtk.Button("Browse...") browseButton=gtk.Button("Browse...")
browseButton.connect("clicked", self.browseClicked, [site] + [dirPath]) browseButton.connect("clicked", self.browseClicked, [site] + [dirPath])
hbox.pack_start(browseButton, False, False, 0) hbox.pack_start(browseButton, False, False, 0)
browseButton.show() browseButton.show()
label = gtk.Label(site + " filter:") label = gtk.Label(site + " filter:")
hbox.pack_start(label, False, False, 0) hbox.pack_start(label, False, False, 0)
label.show() label.show()
filter=gtk.Entry() filter=gtk.Entry()
filter.set_text(filter_name) filter.set_text(filter_name)
hbox.pack_start(filter, False, True, 0) hbox.pack_start(filter, False, True, 0)
filter.show() filter.show()
def addSites(self, vbox): def addSites(self, vbox):
for site in self.config.supported_sites.keys(): for site in self.config.supported_sites.keys():
pathHBox = gtk.HBox(False, 0) pathHBox = gtk.HBox(False, 0)
vbox.pack_start(pathHBox, False, True, 0) vbox.pack_start(pathHBox, False, True, 0)
pathHBox.show() pathHBox.show()
paths = self.config.get_default_paths(site) paths = self.config.get_default_paths(site)
params = self.config.get_site_parameters(site) params = self.config.get_site_parameters(site)
self.createSiteLine(pathHBox, site, False, paths['hud-defaultPath'], params['converter'], params['enabled']) self.createSiteLine(pathHBox, site, False, paths['hud-defaultPath'], params['converter'], params['enabled'])
self.input_settings[site] = [paths['hud-defaultPath']] + [params['converter']] self.input_settings[site] = [paths['hud-defaultPath']] + [params['converter']]
if __name__== "__main__": if __name__== "__main__":
def destroy(*args): # call back for terminating the main eventloop def destroy(*args): # call back for terminating the main eventloop
gtk.main_quit() gtk.main_quit()
settings = {} settings = {}
settings['db-host'] = "192.168.1.100" settings['db-host'] = "192.168.1.100"
settings['db-user'] = "mythtv" settings['db-user'] = "mythtv"
settings['db-password'] = "mythtv" settings['db-password'] = "mythtv"
settings['db-databaseName'] = "fpdb" settings['db-databaseName'] = "fpdb"
settings['hud-defaultInterval'] = 10 settings['hud-defaultInterval'] = 10
settings['hud-defaultPath'] = 'C:/Program Files/PokerStars/HandHistory/nutOmatic' settings['hud-defaultPath'] = 'C:/Program Files/PokerStars/HandHistory/nutOmatic'
settings['callFpdbHud'] = True settings['callFpdbHud'] = True
i = GuiAutoImport(settings) i = GuiAutoImport(settings)
main_window = gtk.Window() main_window = gtk.Window()
main_window.connect("destroy", destroy) main_window.connect("destroy", destroy)
main_window.add(i.mainVBox) main_window.add(i.mainVBox)
main_window.show() main_window.show()
gtk.main() gtk.main()

View File

@ -22,14 +22,17 @@ import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import os #todo: remove this once import_dir is in fpdb_import import os #todo: remove this once import_dir is in fpdb_import
from time import time
class GuiBulkImport (threading.Thread): class GuiBulkImport (threading.Thread):
def import_dir(self): def import_dir(self):
"""imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it""" """imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it"""
self.path=self.inputFile self.path=self.inputFile
self.importer.addImportDirectory(self.path) self.importer.addImportDirectory(self.path)
self.importer.setCallHud(False)
starttime = time()
self.importer.runImport() self.importer.runImport()
print "GuiBulkImport.import_dir done" print "GuiBulkImport.import_dir done in %s" %(time() - starttime)
def load_clicked(self, widget, data=None): def load_clicked(self, widget, data=None):
self.inputFile=self.chooser.get_filename() self.inputFile=self.chooser.get_filename()
@ -64,6 +67,7 @@ class GuiBulkImport (threading.Thread):
self.import_dir() self.import_dir()
else: else:
self.importer.addImportFile(self.inputFile) self.importer.addImportFile(self.inputFile)
self.importer.setCallHud(False)
self.importer.runImport() self.importer.runImport()
self.importer.clearFileList() self.importer.clearFileList()
@ -80,7 +84,7 @@ class GuiBulkImport (threading.Thread):
self.db=db self.db=db
self.settings=settings self.settings=settings
self.config=config self.config=config
self.importer = fpdb_import.Importer(self,self.settings) self.importer = fpdb_import.Importer(self,self.settings, config)
self.vbox=gtk.VBox(False,1) self.vbox=gtk.VBox(False,1)
self.vbox.show() self.vbox.show()

View File

@ -20,130 +20,304 @@ import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import os import os
from time import time
#import pokereval #import pokereval
try: try:
from matplotlib.figure import Figure import matplotlib
from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas matplotlib.use('GTK')
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar from matplotlib.figure import Figure
from numpy import arange, cumsum from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
from pylab import * from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
from numpy import arange, cumsum
from pylab import *
except: except:
print "Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib if you want to use graphs." print """Failed to load libs for graphing, graphing will not function. Please in
print "This is of no consequence for other parts of the program, e.g. import and HUD are NOT affected by this problem." stall numpy and matplotlib if you want to use graphs."""
print """This is of no consequence for other parts of the program, e.g. import
and HUD are NOT affected by this problem."""
import fpdb_import import fpdb_import
import fpdb_db import fpdb_db
class GuiGraphViewer (threading.Thread): class GuiGraphViewer (threading.Thread):
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.mainVBox return self.mainHBox
#end def get_vbox #end def get_vbox
def showClicked(self, widget, data):
try: self.canvas.destroy()
except AttributeError: pass
name=self.nameEntry.get_text() def generateGraph(self, widget, data):
try: self.canvas.destroy()
site=self.siteEntry.get_text() except AttributeError: pass
if site=="PS":
site=2
sitename="PokerStars: "
elif site=="FTP":
site=1
sitename="Full Tilt: "
else:
print "invalid text in site selection in graph, defaulting to PS"
site=2
self.fig = Figure(figsize=(5,4), dpi=100)
#Set graph properties # Whaich sites are selected?
self.ax = self.fig.add_subplot(111) # TODO:
# What hero names for the selected site?
# TODO:
# name = self.heroes[self.sites]
self.ax.set_title("Profit graph for ring games")
#Set axis labels and grid overlay properites if self.sites == "PokerStars":
self.ax.set_xlabel("Hands", fontsize = 12) site=2
self.ax.set_ylabel("$", fontsize = 12) sitename="PokerStars: "
self.ax.grid(color='g', linestyle=':', linewidth=0.2) elif self.sites=="Full Tilt":
text = "All Hands, " + sitename + str(name) site=1
sitename="Full Tilt: "
else:
print "invalid text in site selection in graph, defaulting to PS"
site=2
self.ax.annotate (text, (61,25), xytext =(0.1, 0.9) , textcoords ="axes fraction" ,) self.fig = Figure(figsize=(5,4), dpi=100)
#Get graph data from DB #Set graph properties
line = self.getRingProfitGraph(name, site) self.ax = self.fig.add_subplot(111)
#Draw plot #Get graph data from DB
self.ax.plot(line,) starttime = time()
line = self.getRingProfitGraph(name, site)
print "Graph generated in: %s" %(time() - starttime)
self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea self.ax.set_title("Profit graph for ring games")
self.mainVBox.pack_start(self.canvas)
self.canvas.show()
#end of def showClicked
def getRingProfitGraph(self, name, site): #Set axis labels and grid overlay properites
#self.cursor.execute(self.sql.query['getRingWinningsAllGamesPlayerIdSite'], (name, site)) self.ax.set_xlabel("Hands", fontsize = 12)
self.cursor.execute(self.sql.query['getRingProfitAllHandsPlayerIdSite'], (name, site)) self.ax.set_ylabel("$", fontsize = 12)
# returns (HandId,Winnings,Costs,Profit) self.ax.grid(color='g', linestyle=':', linewidth=0.2)
winnings = self.db.cursor.fetchall() #This line will crash if no hands exist in the query.
text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
#profit=range(len(winnings)) self.ax.annotate(text,
#for i in profit: xy=(10, -10),
# self.cursor.execute(self.sql.query['getRingProfitFromHandId'], (name, winnings[i][0], site)) xycoords='axes points',
# spent = self.db.cursor.fetchone() horizontalalignment='left', verticalalignment='top',
# profit[i]=(i, winnings[i][1]-spent[0]) fontsize=10)
#y=map(lambda x:float(x[1]), profit)
y=map(lambda x:float(x[3]), winnings)
line = cumsum(y)
return line/100 #Draw plot
self.ax.plot(line,)
self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
self.graphBox.add(self.canvas)
self.canvas.show()
#end of def showClicked
def getRingProfitGraph(self, name, site):
self.cursor.execute(self.sql.query['getRingProfitAllHandsPlayerIdSite'], (name, site))
#returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall()
y=map(lambda x:float(x[3]), winnings)
line = cumsum(y)
return line/100
#end of def getRingProfitGraph #end of def getRingProfitGraph
def __init__(self, db, settings, querylist, config, debug=True): def createPlayerLine(self, hbox, site, player):
"""Constructor for GraphViewer""" label = gtk.Label(site +" id:")
self.debug=debug hbox.pack_start(label, False, False, 0)
#print "start of GraphViewer constructor" label.show()
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=querylist
self.mainVBox = gtk.VBox(False, 0)
self.mainVBox.show()
self.settingsHBox = gtk.HBox(False, 0)
self.mainVBox.pack_start(self.settingsHBox, False, True, 0)
self.settingsHBox.show()
self.nameLabel = gtk.Label("Name of the player to be graphed:")
self.settingsHBox.pack_start(self.nameLabel)
self.nameLabel.show()
self.nameEntry=gtk.Entry()
self.nameEntry.set_text("name")
self.settingsHBox.pack_start(self.nameEntry)
self.nameEntry.show()
self.siteLabel = gtk.Label("Site (PS or FTP):")
self.settingsHBox.pack_start(self.siteLabel)
self.siteLabel.show()
self.siteEntry=gtk.Entry()
self.siteEntry.set_text("PS")
self.settingsHBox.pack_start(self.siteEntry)
self.siteEntry.show()
#Note: Assumes PokerStars is in the config pname = gtk.Entry()
self.nameEntry.set_text(config.supported_sites["PokerStars"].screen_name) pname.set_text(player)
pname.set_width_chars(20)
self.showButton=gtk.Button("Show/Refresh") hbox.pack_start(pname, False, True, 0)
self.showButton.connect("clicked", self.showClicked, "show clicked") #TODO: Need to connect a callback here
self.settingsHBox.pack_start(self.showButton) pname.connect("changed", self.__set_hero_name, site)
self.showButton.show() #TODO: Look at GtkCompletion - to fill out usernames
#end of GuiGraphViewer.__init__ pname.show()
self.__set_hero_name(pname, site)
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site)
cb.connect('clicked', self.__set_site_select, site)
hbox.pack_start(cb, False, False, 0)
cb.show()
def __set_site_select(self, w, site):
# This doesn't behave as intended - self.site only allows 1 site for the moment.
self.sites = site
print "self.sites set to %s" %(self.sites)
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
pathHBox = gtk.HBox(False, 0)
vbox.pack_start(pathHBox, False, True, 0)
pathHBox.show()
player = self.conf.supported_sites[site].screen_name
self.createPlayerLine(pathHBox, site, player)
def fillSitesFrame(self, vbox):
for site in self.conf.supported_sites.keys():
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
self.createSiteLine(hbox, site)
def fillDateFrame(self, vbox):
# Hat tip to Mika Bostrom - calendar code comes from PokerStats
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
hbox.show()
lbl_start = gtk.Label('From:')
lbl_start.show()
btn_start = gtk.Button()
btn_start.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_start.connect('clicked', self.__calendar_dialog, self.start_date)
btn_start.show()
hbox.pack_start(lbl_start, expand=False, padding=3)
hbox.pack_start(btn_start, expand=False, padding=3)
hbox.pack_start(self.start_date, expand=False, padding=2)
self.start_date.show()
#New row for end date
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
hbox.show()
lbl_end = gtk.Label(' To:')
lbl_end.show()
btn_end = gtk.Button()
btn_end.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_end.connect('clicked', self.__calendar_dialog, self.end_date)
btn_end.show()
btn_clear = gtk.Button(label=' Clear Dates ')
btn_clear.connect('clicked', self.__clear_dates)
btn_clear.show()
hbox.pack_start(lbl_end, expand=False, padding=3)
hbox.pack_start(btn_end, expand=False, padding=3)
hbox.pack_start(self.end_date, expand=False, padding=2)
self.end_date.show()
hbox.pack_start(btn_clear, expand=False, padding=15)
def __calendar_dialog(self, widget, entry):
d = gtk.Window(gtk.WINDOW_TOPLEVEL)
d.set_title('Pick a date')
vb = gtk.VBox()
cal = gtk.Calendar()
vb.pack_start(cal, expand=False, padding=0)
btn = gtk.Button('Done')
btn.connect('clicked', self.__get_date, cal, entry, d)
vb.pack_start(btn, expand=False, padding=4)
d.add(vb)
d.set_position(gtk.WIN_POS_MOUSE)
d.show_all()
def __clear_dates(self, w):
self.start_date.set_text('')
self.end_date.set_text('')
def __get_dates(self):
t1 = self.start_date.get_text()
t2 = self.end_date.get_text()
return (t1, t2)
def __get_date(self, widget, calendar, entry, win):
# year and day are correct, month is 0..11
(year, month, day) = calendar.get_date()
month += 1
ds = '%04d-%02d-%02d' % (year, month, day)
entry.set_text(ds)
win.destroy()
def exportGraph (self, widget, data):
dia_chooser = gtk.FileChooserDialog(title="Please choose the directory you wish to export to:",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
response = dia_chooser.run()
if response == gtk.RESPONSE_OK:
self.exportDir = dia_chooser.get_filename()
elif response == gtk.RESPONSE_CANCEL:
print 'Closed, no graph exported'
dia_chooser.destroy()
def __init__(self, db, settings, querylist, config, debug=True):
"""Constructor for GraphViewer"""
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=querylist
self.conf = config
self.sites = "PokerStars"
self.heroes = {}
# For use in date ranges.
self.start_date = gtk.Entry(max=12)
self.end_date = gtk.Entry(max=12)
self.start_date.set_property('editable', False)
self.end_date.set_property('editable', False)
self.mainHBox = gtk.HBox(False, 0)
self.mainHBox.show()
self.leftPanelBox = gtk.VBox(False, 0)
self.graphBox = gtk.VBox(False, 0)
self.hpane = gtk.HPaned()
self.hpane.pack1(self.leftPanelBox)
self.hpane.pack2(self.graphBox)
self.hpane.show()
self.mainHBox.add(self.hpane)
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
playerFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
sitesFrame = gtk.Frame("Sites:")
sitesFrame.set_label_align(0.0, 0.0)
sitesFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillSitesFrame(vbox)
sitesFrame.add(vbox)
dateFrame = gtk.Frame("Date:")
dateFrame.set_label_align(0.0, 0.0)
dateFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillDateFrame(vbox)
dateFrame.add(vbox)
graphButton=gtk.Button("Generate Graph")
graphButton.connect("clicked", self.generateGraph, "cliced data")
graphButton.show()
self.exportButton=gtk.Button("Export to File")
self.exportButton.connect("clicked", self.exportGraph, "show clicked")
self.exportButton.show()
self.leftPanelBox.add(playerFrame)
self.leftPanelBox.add(sitesFrame)
self.leftPanelBox.add(dateFrame)
self.leftPanelBox.add(graphButton)
self.leftPanelBox.add(self.exportButton)
self.leftPanelBox.show()
self.graphBox.show()

165
pyfpdb/GuiPlayerStats.py Normal file
View File

@ -0,0 +1,165 @@
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import threading
import pygtk
pygtk.require('2.0')
import gtk
import os
import fpdb_import
import fpdb_db
import FpdbSQLQueries
class GuiPlayerStats (threading.Thread):
def get_vbox(self):
"""returns the vbox of this thread"""
return self.main_hbox
def toggleCallback(self, widget, data=None):
# print "%s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()])
self.activesite = data
print "DEBUG: activesite set to %s" %(self.activesite)
def refreshStats(self, widget, data):
try: self.stats_table.destroy()
except AttributeError: pass
self.fillStatsFrame(self.stats_frame)
def fillStatsFrame(self, vbox):
# Get currently active site and grab playerid
tmp = self.sql.query['playerStats']
result = self.cursor.execute(self.sql.query['getPlayerId'], self.heroes[self.activesite])
result = self.db.cursor.fetchall()
pid = result[0][0]
tmp = tmp.replace("<player_test>", "(" + str(pid) + ")")
self.cursor.execute(tmp)
result = self.db.cursor.fetchall()
cols = 18
rows = len(result)+1 # +1 for title row
self.stats_table = gtk.Table(rows, cols, False)
self.stats_table.set_col_spacings(4)
self.stats_table.show()
vbox.add(self.stats_table)
# Create header row
titles = ("GID", "base", "Style", "Site", "$BB", "Hands", "VPIP", "PFR", "saw_f", "sawsd", "wtsdwsf", "wmsd", "FlAFq", "TuAFq", "RvAFq", "PFAFq", "Net($)", "BB/100")
col = 0
row = 0
for t in titles:
l = gtk.Label(titles[col])
l.show()
self.stats_table.attach(l, col, col+1, row, row+1)
col +=1
for row in range(rows-1):
for col in range(cols):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
l = gtk.Label(result[row-1][col])
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2)
l.show()
eb.show()
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
player = self.conf.supported_sites[site].screen_name
self.createPlayerLine(hbox, site, player)
hbox = gtk.HBox(False, 0)
button = gtk.Button("Refresh")
button.connect("clicked", self.refreshStats, False)
button.show()
hbox.add(button)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
def createPlayerLine(self, hbox, site, player):
if(self.buttongroup == None):
button = gtk.RadioButton(None, site + " id:")
button.set_active(True)
self.buttongroup = button
self.activesite = site
else:
button = gtk.RadioButton(self.buttongroup, site + " id:")
hbox.pack_start(button, True, True, 0)
button.connect("toggled", self.toggleCallback, site)
button.show()
pname = gtk.Entry()
pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
pname.show()
self.__set_hero_name(pname, site)
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def __init__(self, db, config, querylist, debug=True):
self.debug=debug
self.db=db
self.cursor=db.cursor
self.conf=config
self.sql = querylist
self.activesite = None
self.buttongroup = None
self.heroes = {}
self.stat_table = None
self.stats_frame = None
self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show()
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
playerFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
statsFrame = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0)
statsFrame.show()
self.stats_frame = gtk.VBox(False, 0)
self.stats_frame.show()
self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame)
self.main_hbox.pack_start(playerFrame)
self.main_hbox.pack_start(statsFrame)

View File

@ -2,7 +2,7 @@
<FreePokerToolsConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FreePokerToolsConfig.xsd"> <FreePokerToolsConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FreePokerToolsConfig.xsd">
<supported_sites> <supported_sites>
<site enabled="True" site_name="PokerStars" table_finder="PokerStars.exe" screen_name="DO NOT NEED THIS YET" site_path="~/.wine/drive_c/Program Files/PokerStars/" HH_path="~/.wine/drive_c/Program Files/PokerStars/HandHistory/abc/" decoder="pokerstars_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo"> <site enabled="True" site_name="PokerStars" table_finder="PokerStars.exe" screen_name="ENTER HERO NAME" site_path="~/.wine/drive_c/Program Files/PokerStars/" HH_path="~/.wine/drive_c/Program Files/PokerStars/HandHistory/abc/" decoder="pokerstars_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo">
<layout max="8" width="792" height="546" fav_seat="0"> <layout max="8" width="792" height="546" fav_seat="0">
<location seat="1" x="684" y="61"> </location> <location seat="1" x="684" y="61"> </location>
<location seat="2" x="689" y="239"> </location> <location seat="2" x="689" y="239"> </location>
@ -49,7 +49,7 @@
<location seat="2" x="10" y="288"> </location> <location seat="2" x="10" y="288"> </location>
</layout> </layout>
</site> </site>
<site enabled="True" site_name="Full Tilt" table_finder="FullTiltPoker.exe" screen_name="DO NOT NEED THIS YET" site_path="~/.wine/drive_c/Program Files/Full Tilt Poker/" HH_path="~/.wine/drive_c/Program Files/Full Tilt Poker/HandHistory/abc/" decoder="fulltilt_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo"> <site enabled="True" site_name="Full Tilt" table_finder="FullTiltPoker.exe" screen_name="ENTER HERO NAME" site_path="~/.wine/drive_c/Program Files/Full Tilt Poker/" HH_path="~/.wine/drive_c/Program Files/Full Tilt Poker/HandHistory/abc/" decoder="fulltilt_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo">
<layout fav_seat="0" height="547" max="8" width="794"> <layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location> <location seat="1" x="640" y="64"> </location>
<location seat="2" x="650" y="230"> </location> <location seat="2" x="650" y="230"> </location>
@ -84,7 +84,7 @@
<location seat="9" x="70" y="53"> </location> <location seat="9" x="70" y="53"> </location>
</layout> </layout>
</site> </site>
<site enabled="False" site_name="Everleaf" table_finder="Poker.exe" screen_name="DO NOT NEED THIS YET" site_path="" HH_path="" decoder="Unknown" converter="EverleafToFpdb" supported_games="holdem,razz,omahahi,omahahilo,studhi" fgcolor="#48D1CC" bgcolor="#000000"> <site enabled="False" site_name="Everleaf" table_finder="Poker.exe" screen_name="ENTER HERO NAME" site_path="" HH_path="" decoder="Unknown" converter="EverleafToFpdb" supported_games="holdem,razz,omahahi,omahahilo,studhi" fgcolor="#48D1CC" bgcolor="#000000" opacity="0.75">
<layout fav_seat="0" height="546" max="6" width="792"> <layout fav_seat="0" height="546" max="6" width="792">
<location seat="1" x="581" y="109"> </location> <location seat="1" x="581" y="109"> </location>
<location seat="2" x="605" y="287"> </location> <location seat="2" x="605" y="287"> </location>
@ -187,7 +187,7 @@
<pu_stat pu_stat_name="ffreq_4"> </pu_stat> <pu_stat pu_stat_name="ffreq_4"> </pu_stat>
</pu> </pu>
</popup_windows> </popup_windows>
<import callFpdbHud = "True" interval = "10" ></import> <import callFpdbHud = "True" interval = "10" hhArchiveBase="~/.fpdb/HandHistories/"></import>
<tv combinedStealFold = "True" combined2B3B = "True" combinedPostflop = "True"></tv> <tv combinedStealFold = "True" combined2B3B = "True" combinedPostflop = "True"></tv>
<supported_databases> <supported_databases>

View File

@ -1,171 +1,171 @@
#!/usr/bin/env python #!/usr/bin/env python
"""Hud_main.py """Hud_main.py
Main for FreePokerTools HUD. Main for FreePokerTools HUD.
""" """
# Copyright 2008, Ray E. Barker # Copyright 2008, Ray E. Barker
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
######################################################################## ########################################################################
# to do kill window on my seat # to do kill window on my seat
# to do adjust for preferred seat # to do adjust for preferred seat
# to do allow window resizing # to do allow window resizing
# to do hud to echo, but ignore non numbers # to do hud to echo, but ignore non numbers
# to do no hud window for hero # to do no hud window for hero
# to do things to add to config.xml # to do things to add to config.xml
# to do font and size # to do font and size
# to do opacity # to do opacity
# Standard Library modules # Standard Library modules
import sys import sys
import os import os
import thread import thread
import time import time
import string import string
import re import re
errorfile = open('HUD-error.txt', 'w', 0) errorfile = open('HUD-error.txt', 'w', 0)
sys.stderr = errorfile sys.stderr = errorfile
# pyGTK modules # pyGTK modules
import pygtk import pygtk
import gtk import gtk
import gobject import gobject
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
import Database import Database
import Tables import Tables
import Hud import Hud
# global dict for keeping the huds # global dict for keeping the huds
hud_dict = {} hud_dict = {}
db_connection = 0; db_connection = 0;
config = 0; config = 0;
def destroy(*args): # call back for terminating the main eventloop def destroy(*args): # call back for terminating the main eventloop
gtk.main_quit() gtk.main_quit()
def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_connection, config, stat_dict): def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_connection, config, stat_dict):
global hud_dict global hud_dict
def idle_func(): def idle_func():
global hud_dict global hud_dict
gtk.gdk.threads_enter() gtk.gdk.threads_enter()
try: try:
hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_name) hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_name)
hud_dict[table_name].create(new_hand_id, config) hud_dict[table_name].create(new_hand_id, config)
hud_dict[table_name].update(new_hand_id, config, stat_dict) hud_dict[table_name].update(new_hand_id, config, stat_dict)
hud_dict[table_name].reposition_windows() hud_dict[table_name].reposition_windows()
return False return False
finally: finally:
gtk.gdk.threads_leave() gtk.gdk.threads_leave()
gobject.idle_add(idle_func) gobject.idle_add(idle_func)
def update_HUD(new_hand_id, table_name, config, stat_dict): def update_HUD(new_hand_id, table_name, config, stat_dict):
global hud_dict global hud_dict
def idle_func(): def idle_func():
gtk.gdk.threads_enter() gtk.gdk.threads_enter()
try: try:
hud_dict[table_name].update(new_hand_id, config, stat_dict) hud_dict[table_name].update(new_hand_id, config, stat_dict)
for m in hud_dict[table_name].aux_windows: for m in hud_dict[table_name].aux_windows:
m.update_gui(new_hand_id) m.update_gui(new_hand_id)
return False return False
finally: finally:
gtk.gdk.threads_leave() gtk.gdk.threads_leave()
gobject.idle_add(idle_func) gobject.idle_add(idle_func)
def read_stdin(): # This is the thread function def read_stdin(): # This is the thread function
global hud_dict global hud_dict
db_connection = Database.Database(config, db_name, 'temp') db_connection = Database.Database(config, db_name, 'temp')
tourny_finder = re.compile('(\d+) (\d+)') tourny_finder = re.compile('(\d+) (\d+)')
while True: # wait for a new hand number on stdin while True: # wait for a new hand number on stdin
new_hand_id = sys.stdin.readline() new_hand_id = sys.stdin.readline()
new_hand_id = string.rstrip(new_hand_id) new_hand_id = string.rstrip(new_hand_id)
if new_hand_id == "": # blank line means quit if new_hand_id == "": # blank line means quit
destroy() destroy()
# delete hud_dict entries for any HUD destroyed since last iteration # delete hud_dict entries for any HUD destroyed since last iteration
for h in hud_dict.keys(): for h in hud_dict.keys():
if hud_dict[h].deleted: if hud_dict[h].deleted:
del(hud_dict[h]) del(hud_dict[h])
# get basic info about the new hand from the db # get basic info about the new hand from the db
(table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id)
# find out if this hand is from a tournament # find out if this hand is from a tournament
is_tournament = False is_tournament = False
(tour_number, tab_number) = (0, 0) (tour_number, tab_number) = (0, 0)
mat_obj = tourny_finder.search(table_name) mat_obj = tourny_finder.search(table_name)
# if len(mat_obj.groups) == 2: # if len(mat_obj.groups) == 2:
if mat_obj: if mat_obj:
is_tournament = True is_tournament = True
(tour_number, tab_number) = mat_obj.group(1, 2) (tour_number, tab_number) = mat_obj.group(1, 2)
stat_dict = db_connection.get_stats_from_hand(new_hand_id) stat_dict = db_connection.get_stats_from_hand(new_hand_id)
# if a hud for this CASH table exists, just update it # if a hud for this CASH table exists, just update it
if hud_dict.has_key(table_name): if hud_dict.has_key(table_name):
# update the data for the aux_windows # update the data for the aux_windows
for aw in hud_dict[table_name].aux_windows: for aw in hud_dict[table_name].aux_windows:
aw.update_data(new_hand_id) aw.update_data(new_hand_id)
update_HUD(new_hand_id, table_name, config, stat_dict) update_HUD(new_hand_id, table_name, config, stat_dict)
# if a hud for this TOURNAMENT table exists, just update it # if a hud for this TOURNAMENT table exists, just update it
elif hud_dict.has_key(tour_number): elif hud_dict.has_key(tour_number):
update_HUD(new_hand_id, tour_number, config, stat_dict) update_HUD(new_hand_id, tour_number, config, stat_dict)
# otherwise create a new hud # otherwise create a new hud
else: else:
if is_tournament: if is_tournament:
tablewindow = Tables.discover_tournament_table(config, tour_number, tab_number) tablewindow = Tables.discover_tournament_table(config, tour_number, tab_number)
if tablewindow == None: if tablewindow == None:
sys.stderr.write("tournament %s, table %s not found\n" % (tour_number, tab_number)) sys.stderr.write("tournament %s, table %s not found\n" % (tour_number, tab_number))
else: else:
create_HUD(new_hand_id, tablewindow, db_name, tour_number, max, poker_game, db_connection, config, stat_dict) create_HUD(new_hand_id, tablewindow, db_name, tour_number, max, poker_game, db_connection, config, stat_dict)
else: else:
tablewindow = Tables.discover_table_by_name(config, table_name) tablewindow = Tables.discover_table_by_name(config, table_name)
if tablewindow == None: if tablewindow == None:
sys.stderr.write("table name "+table_name+" not found\n") sys.stderr.write("table name "+table_name+" not found\n")
else: else:
create_HUD(new_hand_id, tablewindow, db_name, table_name, max, poker_game, db_connection, config, stat_dict) create_HUD(new_hand_id, tablewindow, db_name, table_name, max, poker_game, db_connection, config, stat_dict)
if __name__== "__main__": if __name__== "__main__":
sys.stderr.write("HUD_main starting\n") sys.stderr.write("HUD_main starting\n")
try: try:
db_name = sys.argv[1] db_name = sys.argv[1]
except: except:
db_name = 'fpdb' db_name = 'fpdb'
sys.stderr.write("Using db name = %s\n" % (db_name)) sys.stderr.write("Using db name = %s\n" % (db_name))
config = Configuration.Config() config = Configuration.Config()
gobject.threads_init() # this is required gobject.threads_init() # this is required
thread.start_new_thread(read_stdin, ()) # starts the thread thread.start_new_thread(read_stdin, ()) # starts the thread
main_window = gtk.Window() main_window = gtk.Window()
main_window.connect("destroy", destroy) main_window.connect("destroy", destroy)
eb = gtk.EventBox() eb = gtk.EventBox()
label = gtk.Label('Closing this window will exit from the HUD.') label = gtk.Label('Closing this window will exit from the HUD.')
eb.add(label) eb.add(label)
main_window.add(eb) main_window.add(eb)
main_window.set_title("HUD Main Window") main_window.set_title("HUD Main Window")
main_window.show_all() main_window.show_all()
gtk.main() gtk.main()

View File

@ -0,0 +1,427 @@
#!/usr/bin/python
#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
#agpl-3.0.txt in the docs folder of the package.
import Configuration
import FpdbRegex
import re
import sys
import traceback
import os
import os.path
import xml.dom.minidom
from decimal import Decimal
import operator
from xml.dom.minidom import Node
class HandHistoryConverter:
def __init__(self, config, file, sitename):
print "HandHistory init called"
self.c = config
self.sitename = sitename
self.obs = "" # One big string
self.filetype = "text"
self.doc = None # For XML based HH files
self.file = file
self.hhbase = self.c.get_import_parameters().get("hhArchiveBase")
self.hhbase = os.path.expanduser(self.hhbase)
self.hhdir = os.path.join(self.hhbase,sitename)
self.gametype = []
# self.ofile = os.path.join(self.hhdir,file)
self.rexx = FpdbRegex.FpdbRegex()
def __str__(self):
tmp = "HandHistoryConverter: '%s'\n" % (self.sitename)
tmp = tmp + "\thhbase: '%s'\n" % (self.hhbase)
tmp = tmp + "\thhdir: '%s'\n" % (self.hhdir)
tmp = tmp + "\tfiletype: '%s'\n" % (self.filetype)
tmp = tmp + "\tinfile: '%s'\n" % (self.file)
# tmp = tmp + "\toutfile: '%s'\n" % (self.ofile)
# tmp = tmp + "\tgametype: '%s'\n" % (self.gametype[0])
# tmp = tmp + "\tgamebase: '%s'\n" % (self.gametype[1])
# tmp = tmp + "\tlimit: '%s'\n" % (self.gametype[2])
# tmp = tmp + "\tsb/bb: '%s/%s'\n" % (self.gametype[3], self.gametype[4])
return tmp
def processFile(self):
if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check"
return
self.readFile(self.file)
self.gametype = self.determineGameType()
self.hands = self.splitFileIntoHands()
for hand in self.hands:
self.readHandInfo(hand)
self.readPlayerStacks(hand)
self.markStreets(hand)
self.readBlinds(hand)
self.readHeroCards(hand)
# Read action (Note: no guarantee this is in hand order.
for street in hand.streets.groupdict():
self.readAction(hand, street)
# finalise it (total the pot)
hand.totalPot()
self.getRake(hand)
if(hand.involved == True):
#self.writeHand("output file", hand)
hand.printHand()
else:
pass #Don't write out observed hands
#####
# These functions are parse actions that may be overridden by the inheriting class
#
def readSupportedGames(self): abstract
# should return a list
# type base limit
# [ ring, hold, nl , sb, bb ]
# Valid types specified in docs/tabledesign.html in Gametypes
def determineGameType(self): abstract
# Read any of:
# HID HandID
# TABLE Table name
# SB small blind
# BB big blind
# GAMETYPE gametype
# YEAR MON DAY HR MIN SEC datetime
# BUTTON button seat number
def readHandInfo(self, hand): abstract
# Needs to return a list of lists in the format
# [['seat#', 'player1name', 'stacksize'] ['seat#', 'player2name', 'stacksize'] [...]]
def readPlayerStacks(self, hand): abstract
# Needs to return a MatchObject with group names identifying the streets into the Hand object
# that is, pulls the chunks of preflop, flop, turn and river text into hand.streets MatchObject.
def markStreets(self, hand): abstract
#Needs to return a list in the format
# ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb,
# addtional players are assumed to post a bb oop
def readBlinds(self, hand): abstract
def readHeroCards(self, hand): abstract
def readAction(self, hand, street): abstract
# Some sites don't report the rake. This will be called at the end of the hand after the pot total has been calculated
# so that an inheriting class can calculate it for the specific site if need be.
def getRake(self, hand): abstract
def sanityCheck(self):
sane = True
base_w = False
#Check if hhbase exists and is writable
#Note: Will not try to create the base HH directory
if not (os.access(self.hhbase, os.W_OK) and os.path.isdir(self.hhbase)):
print "HH Sanity Check: Directory hhbase '" + self.hhbase + "' doesn't exist or is not writable"
else:
#Check if hhdir exists and is writable
if not os.path.isdir(self.hhdir):
# In first pass, dir may not exist. Attempt to create dir
print "Creating directory: '%s'" % (self.hhdir)
os.mkdir(self.hhdir)
sane = True
elif os.access(self.hhdir, os.W_OK):
sane = True
else:
print "HH Sanity Check: Directory hhdir '" + self.hhdir + "' or its parent directory are not writable"
return sane
# Functions not necessary to implement in sub class
def setFileType(self, filetype = "text"):
self.filetype = filetype
def splitFileIntoHands(self):
hands = []
list = self.rexx.split_hand_re.split(self.obs)
list.pop() #Last entry is empty
for l in list:
# print "'" + l + "'"
hands = hands + [Hand(self.sitename, self.gametype, l)]
return hands
def readFile(self, filename):
"""Read file"""
print "Reading file: '%s'" %(filename)
if(self.filetype == "text"):
infile=open(filename, "rU")
self.obs = infile.read()
infile.close()
elif(self.filetype == "xml"):
try:
doc = xml.dom.minidom.parse(filename)
self.doc = doc
except:
traceback.print_exc(file=sys.stderr)
def writeHand(self, file, hand):
"""Write out parsed data"""
print "DEBUG: *************************"
print "DEBUG: Start of print hand"
print "DEBUG: *************************"
print "%s Game #%s: %s ($%s/$%s) - %s" %(hand.sitename, hand.handid, "XXXXhand.gametype", hand.sb, hand.bb, hand.starttime)
print "Table '%s' %d-max Seat #%s is the button" %(hand.tablename, hand.maxseats, hand.buttonpos)
for player in hand.players:
print "Seat %s: %s ($%s)" %(player[0], player[1], player[2])
if(hand.posted[0] == "FpdbNBP"):
print "No small blind posted"
else:
print "%s: posts small blind $%s" %(hand.posted[0], hand.sb)
#May be more than 1 bb posting
print "%s: posts big blind $%s" %(hand.posted[1], hand.bb)
if(len(hand.posted) > 2):
# Need to loop on all remaining big blinds - lazy
print "XXXXXXXXX FIXME XXXXXXXX"
print "*** HOLE CARDS ***"
print "Dealt to %s [%s %s]" %(hand.hero , hand.holecards[0], hand.holecards[1])
#
## ACTION STUFF
# This is no limit only at the moment
for act in hand.actions['PREFLOP']:
self.printActionLine(act, 0)
if 'PREFLOP' in hand.actions:
for act in hand.actions['PREFLOP']:
print "PF action"
if 'FLOP' in hand.actions:
print "*** FLOP *** [%s %s %s]" %(hand.streets.group("FLOP1"), hand.streets.group("FLOP2"), hand.streets.group("FLOP3"))
for act in hand.actions['FLOP']:
self.printActionLine(act, 0)
if 'TURN' in hand.actions:
print "*** TURN *** [%s %s %s] [%s]" %(hand.streets.group("FLOP1"), hand.streets.group("FLOP2"), hand.streets.group("FLOP3"), hand.streets.group("TURN1"))
for act in hand.actions['TURN']:
self.printActionLine(act, 0)
if 'RIVER' in hand.actions:
print "*** RIVER *** [%s %s %s %s] [%s]" %(hand.streets.group("FLOP1"), hand.streets.group("FLOP2"), hand.streets.group("FLOP3"), hand.streets.group("TURN1"), hand.streets.group("RIVER1"))
for act in hand.actions['RIVER']:
self.printActionLine(act, 0)
print "*** SUMMARY ***"
print "XXXXXXXXXXXX Need sumary info XXXXXXXXXXX"
# print "Total pot $%s | Rake $%s)" %(hand.totalpot $" + hand.rake)
# print "Board [" + boardcards + "]"
#
# SUMMARY STUFF
def printActionLine(self, act, pot):
if act[1] == 'folds' or act[1] == 'checks':
print "%s: %s " %(act[0], act[1])
if act[1] == 'calls':
print "%s: %s $%s" %(act[0], act[1], act[2])
if act[1] == 'raises':
print "%s: %s $%s to XXXpottotalXXX" %(act[0], act[1], act[2])
#takes a poker float (including , for thousand seperator and converts it to an int
def float2int (self, string):
pos=string.find(",")
if (pos!=-1): #remove , the thousand seperator
string=string[0:pos]+string[pos+1:]
pos=string.find(".")
if (pos!=-1): #remove decimal point
string=string[0:pos]+string[pos+1:]
result = int(string)
if pos==-1: #no decimal point - was in full dollars - need to multiply with 100
result*=100
return result
#end def float2int
class Hand:
# def __init__(self, sitename, gametype, sb, bb, string):
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K'}
STREETS = ['BLINDS','PREFLOP','FLOP','TURN','RIVER']
def __init__(self, sitename, gametype, string):
self.sitename = sitename
self.gametype = gametype
self.string = string
self.streets = None # A MatchObject using a groupnames to identify streets.
self.actions = {}
self.handid = 0
self.sb = gametype[3]
self.bb = gametype[4]
self.tablename = "Slartibartfast"
self.maxseats = 10
self.counted_seats = 0
self.buttonpos = 0
self.seating = []
self.players = []
self.posted = []
self.involved = True
self.hero = "Hiro"
self.holecards = "Xx Xx"
self.action = []
self.totalpot = None
self.rake = None
self.bets = {}
self.lastBet = {}
for street in self.STREETS:
self.bets[street] = {}
self.lastBet[street] = 0
def addPlayer(self, seat, name, chips):
"""seat, an int indicating the seat
name, the player name
chips, the chips the player has at the start of the hand"""
#self.players.append(name)
self.players.append([seat, name, chips])
#self.startChips[name] = chips
#self.endChips[name] = chips
#self.winners[name] = 0
for street in self.STREETS:
self.bets[street][name] = []
def addHoleCards(self,h1,h2,seat=None): # generalise to add hole cards for a specific seat or player
self.holecards = [self.card(h1), self.card(h2)]
def card(self,c):
"""upper case the ranks but not suits, 'atjqk' => 'ATJQK'"""
# don't know how to make this 'static'
for k,v in self.UPS.items():
c = c.replace(k,v)
return c
def addBlind(self, player, amount):
# if player is None, it's a missing small blind.
if player is not None:
self.bets['PREFLOP'][player].append(Decimal(amount))
self.lastBet['PREFLOP'] = Decimal(amount)
self.posted += [player]
def addCall(self, street, player=None, amount=None):
# Potentially calculate the amount of the call if not supplied
# corner cases include if player would be all in
if amount is not None:
self.bets[street][player].append(Decimal(amount))
#self.lastBet[street] = Decimal(amount)
self.actions[street] += [[player, 'calls', amount]]
def addRaiseTo(self, street, player, amountTo):
# Given only the amount raised to, the amount of the raise can be calculated by
# 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
# (which is tracked by self.lastBet)
committedThisStreet = reduce(operator.add, self.bets[street][player], 0)
amountToCall = self.lastBet[street] - committedThisStreet
self.lastBet[street] = Decimal(amountTo)
amountBy = Decimal(amountTo) - amountToCall
self.bets[street][player].append(amountBy+amountToCall)
self.actions[street] += [[player, 'raises', amountBy, amountTo]]
def addBet(self, street, player=None, amount=0):
self.bets[street][name].append(Decimal(amount))
self.orderedBets[street].append(Decimal(amount))
self.actions[street] += [[player, 'bets', amount]]
def totalPot(self):
if self.totalpot is None:
self.totalpot = 0
# player names:
# print [x[1] for x in self.players]
for player in [x[1] for x in self.players]:
for street in self.STREETS:
print street, self.bets[street][player]
self.totalpot += reduce(operator.add, self.bets[street][player], 0)
def printHand(self):
# PokerStars format.
print "### DEBUG ###"
print "%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, "XXXXhand.gametype", self.sb, self.bb, self.starttime)
print "Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos)
for player in self.players:
print "Seat %s: %s ($%s)" %(player[0], player[1], player[2])
if(self.posted[0] is None):
print "No small blind posted"
else:
print "%s: posts small blind $%s" %(self.posted[0], self.sb)
#May be more than 1 bb posting
for a in self.posted[1:]:
print "%s: posts big blind $%s" %(self.posted[1], self.bb)
# What about big & small blinds?
print "*** HOLE CARDS ***"
print "Dealt to %s [%s %s]" %(self.hero , self.holecards[0], self.holecards[1])
if 'PREFLOP' in self.actions:
for act in self.actions['PREFLOP']:
self.printActionLine(act)
if 'FLOP' in self.actions:
print "*** FLOP *** [%s %s %s]" %(self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"))
for act in self.actions['FLOP']:
self.printActionLine(act)
if 'TURN' in self.actions:
print "*** TURN *** [%s %s %s] [%s]" %(self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"), self.streets.group("TURN1"))
for act in self.actions['TURN']:
self.printActionLine(act)
if 'RIVER' in self.actions:
print "*** RIVER *** [%s %s %s %s] [%s]" %(self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"), self.streets.group("TURN1"), self.streets.group("RIVER1"))
for act in self.actions['RIVER']:
self.printActionLine(act)
#Some sites don't have a showdown section so we have to figure out if there should be one
# The logic for a showdown is: at the end of river action there are at least two players in the hand
if 'SHOWDOWN' in self.actions:
print "*** SHOW DOWN ***"
print "what do they show"
print "*** SUMMARY ***"
print "Total pot $%s | Rake $%s)" % (self.totalpot, self.rake)
print "Board [%s %s %s %s %s]" % (self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"), self.streets.group("TURN1"), self.streets.group("RIVER1"))
def printActionLine(self, act):
if act[1] == 'folds' or act[1] == 'checks':
print "%s: %s " %(act[0], act[1])
if act[1] == 'calls':
print "%s: %s $%s" %(act[0], act[1], act[2])
if act[1] == 'raises':
print "%s: %s $%s to $%s" %(act[0], act[1], act[2], act[3])

File diff suppressed because it is too large Load Diff

View File

@ -41,30 +41,47 @@ import Configuration
import Database import Database
import Tables import Tables
import Hud import Hud
import Mucked
import HandHistory import HandHistory
class Mucked: class Aux_Window:
def __init__(self, parent, db_connection): def __init__(self, parent, config, db_name):
self.config = config
self.parent = parent #this is the parent of the mucked cards widget self.parent = parent #this is the parent of the mucked cards widget
self.db_connection = db_connection self.db_name = db_name
self.vbox = gtk.VBox() self.vbox = gtk.VBox()
self.parent.add(self.vbox) self.parent.add(self.vbox)
self.mucked_list = MuckedList (self.vbox, db_connection) def update(self):
self.mucked_cards = MuckedCards(self.vbox, db_connection) pass
class Stud_mucked(Aux_Window):
def __init__(self, parent, config, db_name):
self.config = config
self.parent = parent #this is the parent of the mucked cards widget
self.db_name = db_name
self.vbox = gtk.VBox()
self.parent.add(self.vbox)
self.mucked_list = Stud_list(self.vbox, config, db_name)
self.mucked_cards = Stud_cards(self.vbox, config, db_name)
self.mucked_list.mucked_cards = self.mucked_cards self.mucked_list.mucked_cards = self.mucked_cards
self.parent.show_all()
def update(self, new_hand_id): def update_data(self, new_hand_id):
self.mucked_list.update(new_hand_id) self.mucked_list.update_data(new_hand_id)
class MuckedList: def update_gui(self, new_hand_id):
def __init__(self, parent, db_connection): self.mucked_list.update_gui(new_hand_id)
class Stud_list:
def __init__(self, parent, config, db_name):
self.parent = parent self.parent = parent
self.db_connection = db_connection self.config = config
self.db_name = db_name
# set up a scrolled window to hold the listbox # set up a scrolled window to hold the listbox
self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window = gtk.ScrolledWindow()
@ -114,12 +131,26 @@ class MuckedList:
vadj = self.scrolled_window.get_vadjustment() vadj = self.scrolled_window.get_vadjustment()
vadj.set_value(vadj.upper) vadj.set_value(vadj.upper)
self.mucked_cards.update(new_hand_id) self.mucked_cards.update(new_hand_id)
def update_data(self, new_hand_id):
self.info_row = ((new_hand_id, "xxxx", 0), )
self.mucked_cards.update_data(new_hand_id)
class MuckedCards: def update_gui(self, new_hand_id):
def __init__(self, parent, db_connection): iter = self.liststore.append(self.info_row[0])
sel = self.treeview.get_selection()
sel.select_iter(iter)
self.parent = parent #this is the parent of the mucked cards widget vadj = self.scrolled_window.get_vadjustment()
self.db_connection = db_connection vadj.set_value(vadj.upper)
self.mucked_cards.update_gui(new_hand_id)
class Stud_cards:
def __init__(self, parent, config, db_name = 'fpdb'):
self.parent = parent #this is the parent of the mucked cards widget
self.config = config
self.db_name = db_name
self.card_images = self.get_card_images() self.card_images = self.get_card_images()
self.seen_cards = {} self.seen_cards = {}
@ -159,44 +190,106 @@ class MuckedCards:
self.parent.add(self.grid) self.parent.add(self.grid)
def translate_cards(self, old_cards): def translate_cards(self, old_cards):
pass ranks = ('', '', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A')
for c in old_cards.keys():
for i in range(1, 8):
rank = 'card' + str(i) + 'Value'
suit = 'card' + str(i) + 'Suit'
key = 'hole_card_' + str(i)
if old_cards[c][rank] == 0:
old_cards[c][key] = 'xx'
else:
old_cards[c][key] = ranks[old_cards[c][rank]] + old_cards[c][suit]
return old_cards
def update(self, new_hand_id): def update(self, new_hand_id):
cards = self.db_connection.get_cards(new_hand_id) db_connection = Database.Database(self.config, 'fpdb', '')
cards = db_connection.get_cards(new_hand_id)
self.clear() self.clear()
cards = self.translate_cards(cards) cards = self.translate_cards(cards)
for c in cards.keys(): for c in cards.keys():
self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name']) self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name'])
for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'), for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'),
(4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')): (4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')):
if not cards[c][i[1]] == "": if not cards[c][i[1]] == "xx":
self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \ self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \
set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])]) set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])])
xml_text = self.db_connection.get_xml(new_hand_id)
hh = HandHistory.HandHistory(xml_text, ('BETTING'))
# action in tool tips for 3rd street cards tips = []
tip = "%s" % hh.BETTING.rounds[0] action = db_connection.get_action_from_hand(new_hand_id)
for street in action:
temp = ''
for act in street:
temp = temp + act[0] + " " + act[1] + "s "
if act[2] > 0:
if act[2]%100 > 0:
temp = temp + "%4.2f\n" % (float(act[2])/100)
else:
temp = temp + "%d\n" % (act[2]/100)
else:
temp = temp + "\n"
tips.append(temp)
## action in tool tips for 3rd street cards
for c in (0, 1, 2): for c in (0, 1, 2):
for r in range(0, self.rows): for r in range(0, self.rows):
self.eb[(c, r)].set_tooltip_text(tip) self.eb[(c, r)].set_tooltip_text(tips[0])
# action in tools tips for later streets # action in tools tips for later streets
round_to_col = (0, 3, 4, 5, 6) round_to_col = (0, 3, 4, 5, 6)
for round in range(1, len(hh.BETTING.rounds)): for round in range(1, len(tips)):
tip = "%s" % hh.BETTING.rounds[round]
for r in range(0, self.rows): for r in range(0, self.rows):
self.eb[(round_to_col[round], r)].set_tooltip_text(tip) self.eb[(round_to_col[round], r)].set_tooltip_text(tips[round])
db_connection.close_connection()
def update_data(self, new_hand_id):
db_connection = Database.Database(self.config, 'fpdb', '')
cards = db_connection.get_cards(new_hand_id)
self.clear()
self.cards = self.translate_cards(cards)
self.tips = []
action = db_connection.get_action_from_hand(new_hand_id)
for street in action:
temp = ''
for act in street:
temp = temp + act[0] + " " + act[1] + "s "
if act[2] > 0:
if act[2]%100 > 0:
temp = temp + "%4.2f\n" % (float(act[2])/100)
else:
temp = temp + "%d\n" % (act[2]/100)
else:
temp = temp + "\n"
self.tips.append(temp)
db_connection.close_connection()
def update_gui(self, new_hand_id):
for c in self.cards.keys():
self.grid_contents[(1, self.cards[c]['seat_number'] - 1)].set_text(self.cards[c]['screen_name'])
for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'),
(4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')):
if not self.cards[c][i[1]] == "xx":
self.seen_cards[(i[0], self.cards[c]['seat_number'] - 1)]. \
set_from_pixbuf(self.card_images[self.split_cards(self.cards[c][i[1]])])
## action in tool tips for 3rd street cards
for c in (0, 1, 2):
for r in range(0, self.rows):
self.eb[(c, r)].set_tooltip_text(self.tips[0])
# action in tools tips for later streets
round_to_col = (0, 3, 4, 5, 6)
for round in range(1, len(self.tips)):
for r in range(0, self.rows):
self.eb[(round_to_col[round], r)].set_tooltip_text(self.tips[round])
def split_cards(self, card): def split_cards(self, card):
return (card[0], card[1].upper()) return (card[0], card[1].upper())
def clear(self): def clear(self):
for r in range(0, self.rows): for r in range(0, self.rows):
self.grid_contents[(1, r)].set_text(" ") self.grid_contents[(1, r)].set_text(" ")
for c in range(0, 7): for c in range(0, 7):
self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')]) self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')])
self.eb[(c, r)].set_tooltip_text('') self.eb[(c, r)].set_tooltip_text('')
@ -225,19 +318,19 @@ if __name__== "__main__":
# just read it and pass it to update # just read it and pass it to update
new_hand_id = sys.stdin.readline() new_hand_id = sys.stdin.readline()
new_hand_id = new_hand_id.rstrip() # remove trailing whitespace new_hand_id = new_hand_id.rstrip() # remove trailing whitespace
m.update(new_hand_id) m.update_data(new_hand_id)
m.update_gui(new_hand_id)
return(True) return(True)
config = Configuration.Config() config = Configuration.Config()
db_connection = Database.Database(config, 'fpdb', '') # db_connection = Database.Database(config, 'fpdb', '')
main_window = gtk.Window() main_window = gtk.Window()
main_window.set_keep_above(True) main_window.set_keep_above(True)
main_window.connect("destroy", destroy) main_window.connect("destroy", destroy)
m = Mucked(main_window, db_connection) aux_to_call = "Stud_mucked"
m = eval("%s(main_window, config, 'fpdb')" % aux_to_call)
main_window.show_all() main_window.show_all()
s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand)
gtk.main() gtk.main()

View File

@ -24,63 +24,83 @@
import os import os
import sys import sys
import datetime
import Configuration
import fpdb_db import fpdb_db
import fpdb_import import fpdb_import
import fpdb_simple
import FpdbSQLQueries import FpdbSQLQueries
import unittest import unittest
class TestSequenceFunctions(unittest.TestCase): class TestSequenceFunctions(unittest.TestCase):
def setUp(self): def setUp(self):
"""Configure MySQL settings/database and establish connection""" """Configure MySQL settings/database and establish connection"""
self.mysql_settings={ 'db-host':"localhost", self.c = Configuration.Config()
'db-backend':2, self.mysql_settings={ 'db-host':"localhost",
'db-databaseName':"fpdbtest", 'db-backend':2,
'db-user':"fpdb", 'db-databaseName':"fpdbtest",
'db-password':"fpdb"} 'db-user':"fpdb",
self.mysql_db = fpdb_db.fpdb_db() 'db-password':"fpdb"}
self.mysql_db.connect(self.mysql_settings['db-backend'], self.mysql_settings['db-host'], self.mysql_db = fpdb_db.fpdb_db()
self.mysql_settings['db-databaseName'], self.mysql_settings['db-user'], self.mysql_db.connect(self.mysql_settings['db-backend'], self.mysql_settings['db-host'],
self.mysql_settings['db-password']) self.mysql_settings['db-databaseName'], self.mysql_settings['db-user'],
self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB') self.mysql_settings['db-password'])
self.mysqlimporter = fpdb_import.Importer(self, self.mysql_settings) self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB')
self.mysqlimporter = fpdb_import.Importer(self, self.mysql_settings, self.c)
self.mysqlimporter.setCallHud(False)
# """Configure Postgres settings/database and establish connection""" # """Configure Postgres settings/database and establish connection"""
# self.pg_settings={ 'db-host':"localhost", 'db-backend':3, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"} # self.pg_settings={ 'db-host':"localhost", 'db-backend':3, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"}
# self.pg_db = fpdb_db.fpdb_db() # self.pg_db = fpdb_db.fpdb_db()
# self.pg_db.connect(self.pg_settings['db-backend'], self.pg_settings['db-host'], # self.pg_db.connect(self.pg_settings['db-backend'], self.pg_settings['db-host'],
# self.pg_settings['db-databaseName'], self.pg_settings['db-user'], # self.pg_settings['db-databaseName'], self.pg_settings['db-user'],
# self.pg_settings['db-password']) # self.pg_settings['db-password'])
# self.pgdict = FpdbSQLQueries.FpdbSQLQueries('PostgreSQL') # self.pgdict = FpdbSQLQueries.FpdbSQLQueries('PostgreSQL')
def testDatabaseConnection(self): def testDatabaseConnection(self):
"""Test all supported DBs""" """Test all supported DBs"""
self.result = self.mysql_db.cursor.execute(self.mysqldict.query['list_tables']) self.result = self.mysql_db.cursor.execute(self.mysqldict.query['list_tables'])
self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) # self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables'])
# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) # self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
def testMySQLRecreateTables(self): def testMySQLRecreateTables(self):
"""Test droping then recreating fpdb table schema""" """Test droping then recreating fpdb table schema"""
self.mysql_db.recreate_tables() self.mysql_db.recreate_tables()
self.result = self.mysql_db.cursor.execute("SHOW TABLES") self.result = self.mysql_db.cursor.execute("SHOW TABLES")
self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
def testImportHandHistoryFiles(self): def testPokerStarsHHDate(self):
"""Test import of single HH file""" latest = "PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET]"
self.mysqlimporter.addImportFile("regression-test-files/hand-histories/ps-lhe-ring-3hands.txt") previous = "PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/08/17 - 01:14:43 (ET)"
self.mysqlimporter.runImport() older1 = "PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/09/07 06:23:14 ET"
self.mysqlimporter.addImportDirectory("regression-test-files/hand-histories")
self.mysqlimporter.runImport()
# def testPostgresSQLRecreateTables(self): result = fpdb_simple.parseHandStartTime(older1, "ps")
# """Test droping then recreating fpdb table schema""" self.failUnless(result==datetime.datetime(2008,9,7,11,23,14),
# self.pg_db.recreate_tables() "Date incorrect, expected: 2008-09-07 11:23:14 got: " + str(result))
# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) result = fpdb_simple.parseHandStartTime(latest, "ps")
# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) self.failUnless(result==datetime.datetime(2008,11,12,15,00,48),
"Date incorrect, expected: 2008-11-12 15:00:48 got: " + str(result))
result = fpdb_simple.parseHandStartTime(previous, "ps")
self.failUnless(result==datetime.datetime(2008,8,17,6,14,43),
"Date incorrect, expected: 2008-08-17 01:14:43 got: " + str(result))
def testImportHandHistoryFiles(self):
"""Test import of single HH file"""
self.mysqlimporter.addImportFile("regression-test-files/hand-histories/ps-lhe-ring-3hands.txt")
self.mysqlimporter.runImport()
self.mysqlimporter.addImportDirectory("regression-test-files/hand-histories")
self.mysqlimporter.runImport()
# def testPostgresSQLRecreateTables(self):
# """Test droping then recreating fpdb table schema"""
# self.pg_db.recreate_tables()
# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables'])
# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -237,18 +237,80 @@ class Sql:
GROUP BY HudCache.PlayerId GROUP BY HudCache.PlayerId
""" """
# FROM HudCache, Hands # same as above except stats are aggregated for all blind/limit levels
# WHERE HudCache.PlayerId in self.query['get_stats_from_hand_aggregated'] = """
# (SELECT PlayerId FROM HandsPlayers SELECT HudCache.playerId AS player_id,
# WHERE handId = %s) sum(HDs) AS n,
# AND Hands.id = %s sum(street0VPI) AS vpip,
# AND Hands.gametypeId = HudCache.gametypeId sum(street0Aggr) AS pfr,
sum(street0_3B4BChance) AS TB_opp_0,
# AND PlayerId LIKE %s sum(street0_3B4BDone) AS TB_0,
# HudCache.gametypeId AS gametypeId, sum(street1Seen) AS saw_f,
# activeSeats AS n_active, sum(street1Seen) AS saw_1,
# position AS position, sum(street2Seen) AS saw_2,
# HudCache.tourneyTypeId AS tourneyTypeId, sum(street3Seen) AS saw_3,
sum(street4Seen) AS saw_4,
sum(sawShowdown) AS sd,
sum(street1Aggr) AS aggr_1,
sum(street2Aggr) AS aggr_2,
sum(street3Aggr) AS aggr_3,
sum(street4Aggr) AS aggr_4,
sum(otherRaisedStreet1) AS was_raised_1,
sum(otherRaisedStreet2) AS was_raised_2,
sum(otherRaisedStreet3) AS was_raised_3,
sum(otherRaisedStreet4) AS was_raised_4,
sum(foldToOtherRaisedStreet1) AS f_freq_1,
sum(foldToOtherRaisedStreet2) AS f_freq_2,
sum(foldToOtherRaisedStreet3) AS f_freq_3,
sum(foldToOtherRaisedStreet4) AS f_freq_4,
sum(wonWhenSeenStreet1) AS w_w_s_1,
sum(wonAtSD) AS wmsd,
sum(stealAttemptChance) AS steal_opp,
sum(stealAttempted) AS steal,
sum(foldSbToStealChance) AS SBstolen,
sum(foldedSbToSteal) AS SBnotDef,
sum(foldBbToStealChance) AS BBstolen,
sum(foldedBbToSteal) AS BBnotDef,
sum(street1CBChance) AS CB_opp_1,
sum(street1CBDone) AS CB_1,
sum(street2CBChance) AS CB_opp_2,
sum(street2CBDone) AS CB_2,
sum(street3CBChance) AS CB_opp_3,
sum(street3CBDone) AS CB_3,
sum(street4CBChance) AS CB_opp_4,
sum(street4CBDone) AS CB_4,
sum(foldToStreet1CBChance) AS f_cb_opp_1,
sum(foldToStreet1CBDone) AS f_cb_1,
sum(foldToStreet2CBChance) AS f_cb_opp_2,
sum(foldToStreet2CBDone) AS f_cb_2,
sum(foldToStreet3CBChance) AS f_cb_opp_3,
sum(foldToStreet3CBDone) AS f_cb_3,
sum(foldToStreet4CBChance) AS f_cb_opp_4,
sum(foldToStreet4CBDone) AS f_cb_4,
sum(totalProfit) AS net,
sum(street1CheckCallRaiseChance) AS ccr_opp_1,
sum(street1CheckCallRaiseDone) AS ccr_1,
sum(street2CheckCallRaiseChance) AS ccr_opp_2,
sum(street2CheckCallRaiseDone) AS ccr_2,
sum(street3CheckCallRaiseChance) AS ccr_opp_3,
sum(street3CheckCallRaiseDone) AS ccr_3,
sum(street4CheckCallRaiseChance) AS ccr_opp_4,
sum(street4CheckCallRaiseDone) AS ccr_4
FROM HudCache, Hands
WHERE HudCache.PlayerId in
(SELECT PlayerId FROM HandsPlayers
WHERE handId = %s)
AND Hands.id = %s
AND HudCache.gametypeId in
(SELECT gt1.id from Gametypes gt1, Gametypes gt2, Hands
WHERE gt1.siteid = gt2.siteid
AND gt1.type = gt2.type
AND gt1.category = gt2.category
AND gt1.limittype = gt2.limittype
AND gt2.id = Hands.gametypeId
AND Hands.id = %s)
GROUP BY HudCache.PlayerId
"""
self.query['get_players_from_hand'] = """ self.query['get_players_from_hand'] = """
SELECT HandsPlayers.playerId, seatNo, name SELECT HandsPlayers.playerId, seatNo, name
@ -288,15 +350,15 @@ class Sql:
order by seatNo order by seatNo
""" """
# self.query['get_hand_info'] = """ self.query['get_action_from_hand'] = """
# SELECT SELECT street, Players.name, HandsActions.action, HandsActions.amount, actionno
# game_id, FROM Players, HandsActions, HandsPlayers
# CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, WHERE HandsActions.action != 'blind'
# total_won-total_bet AS net AND HandsPlayers.handid = %s
# FROM game_players AND HandsPlayers.playerid = Players.id
# WHERE game_id = %s AND player_id = 3 AND HandsActions.handPlayerId = HandsPlayers.id
# """ ORDER BY street, actionno
"""
if __name__== "__main__": if __name__== "__main__":
# just print the default queries and exit # just print the default queries and exit
s = Sql(game = 'razz', type = 'ptracks') s = Sql(game = 'razz', type = 'ptracks')

View File

@ -89,14 +89,14 @@ def vpip(stat_dict, player):
'v=%3.1f' % (100*stat) + '%', 'v=%3.1f' % (100*stat) + '%',
'vpip=%3.1f' % (100*stat) + '%', 'vpip=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']), '(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']),
'vpip' 'Voluntarily Put In Pot %'
) )
except: return (stat, except: return (stat,
'%3.1f' % (0) + '%', '%3.1f' % (0) + '%',
'w=%3.1f' % (0) + '%', 'v=%3.1f' % (0) + '%',
'wtsd=%3.1f' % (0) + '%', 'vpip=%3.1f' % (0) + '%',
'(%d/%d)' % (0, 0), '(%d/%d)' % (0, 0),
'wtsd' 'Voluntarily Put In Pot %'
) )
def vpip_0(stat_dict, player): def vpip_0(stat_dict, player):
@ -129,7 +129,7 @@ def pfr(stat_dict, player):
'p=%3.1f' % (100*stat) + '%', 'p=%3.1f' % (100*stat) + '%',
'pfr=%3.1f' % (100*stat) + '%', 'pfr=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']), '(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']),
'pfr' 'Pre-Flop Raise %'
) )
except: except:
return (stat, return (stat,
@ -137,7 +137,7 @@ def pfr(stat_dict, player):
'p=%3.1f' % (0) + '%', 'p=%3.1f' % (0) + '%',
'pfr=%3.1f' % (0) + '%', 'pfr=%3.1f' % (0) + '%',
'(%d/%d)' % (0, 0), '(%d/%d)' % (0, 0),
'pfr' 'Pre-Flop Raise %'
) )
def pfr_0(stat_dict, player): def pfr_0(stat_dict, player):
@ -214,7 +214,7 @@ def saw_f(stat_dict, player):
'sf=%3.1f' % (100*stat) + '%', 'sf=%3.1f' % (100*stat) + '%',
'saw_f=%3.1f' % (100*stat) + '%', 'saw_f=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['saw_f'], stat_dict[player]['n']), '(%d/%d)' % (stat_dict[player]['saw_f'], stat_dict[player]['n']),
'saw_f' 'Flop Seen %'
) )
except: except:
stat = 0.0 stat = 0.0
@ -225,7 +225,7 @@ def saw_f(stat_dict, player):
'sf=%3.1f' % (stat) + '%', 'sf=%3.1f' % (stat) + '%',
'saw_f=%3.1f' % (stat) + '%', 'saw_f=%3.1f' % (stat) + '%',
'(%d/%d)' % (num, den), '(%d/%d)' % (num, den),
'saw_f' 'Flop Seen %'
) )
def n(stat_dict, player): def n(stat_dict, player):
@ -303,12 +303,11 @@ def f_SB_steal(stat_dict, player):
) )
except: except:
return (stat, return (stat,
'%3.1f' % (0) + '%', 'NA',
'fSB=%3.1f' % (0) + '%', 'fSB=NA',
'fSB_s=%3.1f' % (0) + '%', 'fSB_s=NA',
'(%d/%d)' % (0, 0), '0/0',
'% folded SB to steal' '% folded SB to steal')
)
def f_BB_steal(stat_dict, player): def f_BB_steal(stat_dict, player):
""" Folded BB to steal.""" """ Folded BB to steal."""
@ -324,12 +323,11 @@ def f_BB_steal(stat_dict, player):
) )
except: except:
return (stat, return (stat,
'%3.1f' % (0) + '%', 'NA',
'fBB=%3.1f' % (0) + '%', 'fBB=NA',
'fBB_s=%3.1f' % (0) + '%', 'fBB_s=NA',
'(%d/%d)' % (0, 0), '0/0',
'% folded BB to steal' '% folded BB to steal')
)
def three_B_0(stat_dict, player): def three_B_0(stat_dict, player):
""" Three bet preflop/3rd.""" """ Three bet preflop/3rd."""
@ -454,7 +452,7 @@ def a_freq_4(stat_dict, player):
'a4=%3.1f' % (0) + '%', 'a4=%3.1f' % (0) + '%',
'a_fq_4=%3.1f' % (0) + '%', 'a_fq_4=%3.1f' % (0) + '%',
'(%d/%d)' % (0, 0), '(%d/%d)' % (0, 0),
'Aggression Freq flop/4th' 'Aggression Freq 7th'
) )
def a_freq_123(stat_dict, player): def a_freq_123(stat_dict, player):

417
pyfpdb/Tables.py Normal file → Executable file
View File

@ -7,7 +7,6 @@ of Table_Window objects representing the windows found.
""" """
# Copyright 2008, Ray E. Barker # Copyright 2008, Ray E. Barker
#
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
@ -40,7 +39,35 @@ if os.name == 'nt':
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
# 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
# from the corresponding hand history.
# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site
# name specified in the config file.
# tw.number = This is the system id number for the client table window in the
# format that the system presents it. This is Xid in Xwindows and
# hwnd in Microsoft Windows.
# tw.title = The full title from the window title bar.
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# window borders.
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# to the top left of the display screen. This also does not include the
# title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window.
class Table_Window: class Table_Window:
def __init__(self, info = {}):
if info.has_key('number'): self.number = info['number']
if info.has_key('exe'): self.exe = info['exe']
if info.has_key('width'): self.width = info['width']
if info.has_key('height'): self.height = info['height']
if info.has_key('x'): self.x = info['x']
if info.has_key('y'): self.y = info['y']
if info.has_key('site'): self.site = info['site']
if info.has_key('title'): self.title = info['title']
if info.has_key('name'): self.name = info['name']
def __str__(self): def __str__(self):
# __str__ method for testing # __str__ method for testing
temp = 'TableWindow object\n' temp = 'TableWindow object\n'
@ -51,13 +78,10 @@ class Table_Window:
temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table) temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table)
return temp return temp
def get_details(table): ############################################################################
table.game = 'razz' # Top-level discovery routines--these are the modules interface
table.max = 8
table.struture = 'limit'
table.tournament = 0
def discover(c): def discover(c):
"""Dispatch routine for finding all potential poker client windows."""
if os.name == 'posix': if os.name == 'posix':
tables = discover_posix(c) tables = discover_posix(c)
elif os.name == 'nt': elif os.name == 'nt':
@ -66,86 +90,99 @@ def discover(c):
tables = discover_mac(c) tables = discover_mac(c)
else: else:
tables = {} tables = {}
return tables
return(tables)
def discover_table_by_name(c, tablename): def discover_table_by_name(c, tablename):
"""Dispatch routine for finding poker client windows with the given name."""
if os.name == 'posix': if os.name == 'posix':
table = discover_posix_by_name(c, tablename) info = discover_posix_by_name(c, tablename)
elif os.name == 'nt': elif os.name == 'nt':
table = discover_nt_by_name(c, tablename) info = discover_nt_by_name(c, tablename)
elif os.name == 'mac': elif os.name == 'mac':
table = discover_mac_by_name(c, tablename) info = discover_mac_by_name(c, tablename)
else: else:
table = None return None
return(table) if info == None:
return None
return Table_Window(info)
def discover_tournament_table(c, tour_number, tab_number):
"""Dispatch routine for finding poker clients with tour and table number."""
if os.name == 'posix':
info = discover_posix_tournament(c, tour_number, tab_number)
elif os.name == 'nt':
info = discover_nt_tournament(c, tour_number, tab_number)
elif os.name == 'mac':
info = discover_mac_tournament(c, tour_number, tab_number)
else:
return None
if info:
return Table_Window(info)
return None
#############################################################################
# Posix (= XWindows) specific routines
def discover_posix(c): def discover_posix(c):
""" Poker client table window finder for posix/Linux = XWindows.""" """Poker client table window finder for posix/Linux = XWindows."""
tables = {} tables = {}
for listing in os.popen('xwininfo -root -tree').readlines(): for listing in os.popen('xwininfo -root -tree').readlines():
# xwininfo -root -tree -id 0xnnnnn gets the info on a single window # xwininfo -root -tree -id 0xnnnnn gets the info on a single window
if re.search('Lobby', listing): continue for s in c.get_supported_sites():
if re.search('Instant Hand History', listing): continue params = c.get_site_parameters(s)
if not re.search('Logged In as ', listing, re.IGNORECASE): continue if re.search(params['table_finder'], listing):
if re.search('\"Full Tilt Poker\"', listing): continue # FTP Lobby if re.search('Lobby', listing): continue
for s in c.supported_sites.keys(): if re.search('Instant Hand History', listing): continue
if re.search(c.supported_sites[s].table_finder, listing): if re.search('\"Full Tilt Poker\"', listing): continue # FTP Lobby
mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) if re.search('History for table:', listing): continue
if mo.group(2) == '(has no name)': continue if re.search('has no name', listing): continue
if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup info = decode_xwininfo(c, listing)
tw = Table_Window() if info['site'] == None: continue
tw.site = c.supported_sites[s].site_name if info['title'] == info['exe']: continue
tw.number = int(mo.group(1), 0) # this appears to be a poker client, so make a table object for it
tw.title = mo.group(2) tw = Table_Window(info)
tw.width = int( mo.group(3) ) eval("%s(tw)" % params['decoder'])
tw.height = int( mo.group(4) )
tw.x = int (mo.group(5) )
tw.y = int (mo.group(6) )
tw.title = re.sub('\"', '', tw.title)
# use this eval thingie to call the title bar decoder specified in the config file
eval("%s(tw)" % c.supported_sites[s].decoder)
tables[tw.name] = tw tables[tw.name] = tw
return tables return tables
def discover_posix_by_name(c, tablename): def discover_posix_by_name(c, tablename):
tables = discover_posix(c) """Find an XWindows poker client of the given name."""
for t in tables: for listing in os.popen('xwininfo -root -tree').readlines():
if tables[t].name.find(tablename) > -1: if re.search(tablename, listing):
return tables[t] if re.search('History for table:', listing): continue
return None info = decode_xwininfo(c, listing)
if not info['name'] == tablename: continue
return info
return False
# def discover_posix_tournament(c, t_number, s_number):
# The discover_xx functions query the system and report on the poker clients """Finds the X window for a client, given tournament and table nos."""
# currently displayed on the screen. The discover_posix should give you search_string = "%s.+Table\s%s" % (t_number, s_number)
# some idea how to support other systems. for listing in os.popen('xwininfo -root -tree').readlines():
# if re.search(search_string, listing):
# discover_xx() returns a dict of TableWindow objects--one TableWindow return decode_xwininfo(c, listing)
# object for each poker client table on the screen. return False
#
# Each TableWindow object must have the following attributes correctly populated:
# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site
# name specified in the config file.
# tw.number = This is the system id number for the client table window in the
# format that the system presents it.
# tw.title = The full title from the window title bar.
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# window borders.
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# to the top left of the display screen. This also does not include the
# title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window.
def win_enum_handler(hwnd, titles): def decode_xwininfo(c, info_string):
titles[hwnd] = win32gui.GetWindowText(hwnd) """Gets window parameters from xwinifo string--XWindows."""
info = {}
mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', info_string)
def child_enum_handler(hwnd, children): if not mo:
print hwnd, win32.GetWindowRect(hwnd) return None
else:
info['number'] = int( mo.group(1), 0)
info['exe'] = mo.group(3)
info['width'] = int( mo.group(4) )
info['height'] = int( mo.group(5) )
info['x'] = int( mo.group(6) )
info['y'] = int( mo.group(7) )
info['site'] = get_site_from_exe(c, info['exe'])
info['title'] = re.sub('\"', '', mo.group(2))
title_bits = re.split(' - ', info['title'])
info['name'] = clean_title(title_bits[0])
return info
##############################################################################
# NT (= Windows) specific routines
def discover_nt(c): def discover_nt(c):
""" Poker client table window finder for Windows.""" """ Poker client table window finder for Windows."""
# #
@ -185,72 +222,88 @@ def discover_nt(c):
return tables return tables
def discover_nt_by_name(c, tablename): def discover_nt_by_name(c, tablename):
# this is pretty much identical to the 'search all windows for all poker sites' code, but made to dig just for a specific table name """Finds poker client window with the given table name."""
# it could be implemented a bunch better - and we need to not assume the width/height thing that (steffen?) assumed above, we should titles = {}
# be able to dig up the window's titlebar handle and get it's information, and such .. but.. for now, i guess this will work. win32gui.EnumWindows(win_enum_handler, titles)
# - eric for hwnd in titles.keys():
if titles[hwnd].find(tablename) == -1: continue
if titles[hwnd].find("History for table:") > -1: continue
if titles[hwnd].find("HUD:") > -1: continue
if titles[hwnd].find("Chat:") > -1: continue
return decode_windows(c, titles[hwnd], hwnd)
return False
def discover_nt_tournament(c, tour_number, tab_number):
"""Finds the Windows window handle for the given tournament/table."""
search_string = "%s.+%s" % (tour_number, tab_number)
titles ={}
win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles.keys():
if re.search(search_string, titles[hwnd]):
return decode_windows(c, titles[hwnd], hwnd)
return False
def get_nt_exe(hwnd):
"""Finds the name of the executable that the given window handle belongs to."""
processid = win32process.GetWindowThreadProcessId(hwnd)
pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1])
return win32process.GetModuleFileNameEx(pshandle, 0)
def decode_windows(c, title, hwnd):
"""Gets window parameters from the window title and handle--Windows."""
# I cannot figure out how to get the inside dimensions of the poker table
# windows. So I just assume all borders are 3 thick and all title bars
# are 29 high. No doubt this will be off when used with certain themes.
b_width = 3 b_width = 3
tb_height = 29 tb_height = 29
titles = {}
# tables = discover_nt(c) info = {}
win32gui.EnumWindows(win_enum_handler, titles) info['number'] = hwnd
for s in c.supported_sites.keys(): info['title'] = re.sub('\"', '', title)
for hwnd in titles.keys(): (x, y, width, height) = win32gui.GetWindowRect(hwnd)
processid = win32process.GetWindowThreadProcessId(hwnd)
pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1]) info['x'] = int(x) + b_width
exe = win32process.GetModuleFileNameEx(pshandle, 0) info['y'] = int( y ) + tb_height
if exe.find(c.supported_sites[s].table_finder) == -1: info['width'] = int( width ) - 2*b_width
continue info['height'] = int( height ) - b_width - tb_height
if titles[hwnd].find(tablename) > -1: info['exe'] = get_nt_exe(hwnd)
if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1:
continue title_bits = re.split(' - ', info['title'])
tw = Table_Window() info['name'] = title_bits[0]
tw.number = hwnd info['site'] = get_site_from_exe(c, info['exe'])
(x, y, width, height) = win32gui.GetWindowRect(hwnd)
tw.title = titles[hwnd] return info
tw.width = int(width) - 2 * b_width
tw.height = int(height) - b_width - tb_height def win_enum_handler(hwnd, titles):
tw.x = int(x) + b_width titles[hwnd] = win32gui.GetWindowText(hwnd)
tw.y = int(y) + tb_height
tw.site = c.supported_sites[s].site_name ###################################################################
if not tw.site == "Unknown" and not c.supported_sites[tw.site].decoder == "Unknown": # Utility routines used by all the discoverers.
eval("%s(tw)" % c.supported_sites[tw.site].decoder) def get_site_from_exe(c, exe):
else: """Look up the site from config, given the exe."""
tw.name = tablename for s in c.get_supported_sites():
return tw params = c.get_site_parameters(s)
if re.search(params['table_finder'], exe):
# if we don't find anything by process name, let's search one more time, and call it Unknown ? return params['site_name']
for hwnd in titles.keys():
if titles[hwnd].find(tablename) > -1:
if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1:
continue
tw = Table_Window()
tw.number = hwnd
(x, y, width, height) = win32gui.GetWindowRect(hwnd)
tw.title = titles[hwnd]
tw.width = int(width) - 2 * b_width
tw.height = int(height) - b_width - tb_height
tw.x = int(x) + b_width
tw.y = int(y) + tb_height
tw.site = "Unknown"
tw.name = tablename
return tw
return None return None
def discover_mac(c): def clean_title(name):
""" Poker client table window finder for Macintosh.""" """Clean the little info strings from the table name."""
tables = {} # these strings could go in a config file
return tables for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)',
' \(deep hu\)', ' \(deep 6\)', ' \(2\)',
def discover_mac_by_name(c, tablename): ' \(edu\)', ' \(edu, 6 max\)', ' \(6\)',
# again, i have no mac to test this on, sorry -eric ' no all-in', ' fast', ',', ' 50BB min', '\s+$']:
return discover_mac(c) name = re.sub(pattern, '', name)
name = name.rstrip()
return name
def pokerstars_decode_table(tw): def pokerstars_decode_table(tw):
# extract the table name OR the tournament number and table name from the title # Extract poker information from the window title. This is not needed for
# other info in title is redundant with data in the database # fpdb, since all that information is available in the db via new_hand_number.
# This is needed only when using the HUD with a backend less integrated.
title_bits = re.split(' - ', tw.title) title_bits = re.split(' - ', tw.title)
name = title_bits[0] name = title_bits[0]
mo = re.search('Tournament (\d+) Table (\d+)', name) mo = re.search('Tournament (\d+) Table (\d+)', name)
@ -260,12 +313,8 @@ def pokerstars_decode_table(tw):
tw.name = name tw.name = name
else: else:
tw.tournament = None tw.tournament = None
for pattern in [' no all-in', ' fast', ',', ' 50BB min']: tw.name = clean_title(name)
name = re.sub(pattern, '', name) mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball|Badugi)', tw.title)
name = re.sub('\s+$', '', name)
tw.name = name
mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball)', tw.title)
tw.game = mo.group(1).lower() tw.game = mo.group(1).lower()
tw.game = re.sub('\'', '', tw.game) tw.game = re.sub('\'', '', tw.game)
@ -288,25 +337,103 @@ def pokerstars_decode_table(tw):
pass pass
def fulltilt_decode_table(tw): def fulltilt_decode_table(tw):
# extract the table name OR the tournament number and table name from the title # Extract poker information from the window title. This is not needed for
# other info in title is redundant with data in the database # fpdb, since all that information is available in the db via new_hand_number.
# This is needed only when using the HUD with a backend less integrated.
title_bits = re.split(' - ', tw.title) title_bits = re.split(' - ', tw.title)
name = title_bits[0] name = title_bits[0]
tw.tournament = None tw.tournament = None
for pattern in [' (6 max)', ' (heads up)', ' (deep)', tw.name = clean_title(name)
' (deep hu)', ' (deep 6)', ' (2)',
' (edu)', ' (edu, 6 max)', ' (6)' ]: def clean_title(name):
"""Clean the little info strings from the table name."""
# these strings could go in a config file
for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)',
' \(deep hu\)', ' \(deep 6\)', ' \(2\)',
' \(edu\)', ' \(edu, 6 max\)', ' \(6\)',
' no all-in', ' fast', ',', ' 50BB min', '\s+$']:
name = re.sub(pattern, '', name) name = re.sub(pattern, '', name)
# (tw.name, trash) = name.split(r' (', 1) name = name.rstrip()
tw.name = name.rstrip() return name
###########################################################################
# Mac specific routines....all stubs for now
def discover_mac_tournament(c, tour_number, tab_number):
"""Mac users need help."""
return None
def discover_mac(c):
"""Poker client table window finder for Macintosh."""
tables = {}
return tables
def discover_mac_by_name(c, tablename):
"""Oh, the humanity."""
# again, i have no mac to test this on, sorry -eric
return None
#def discover_nt_by_name(c, tablename):
# # this is pretty much identical to the 'search all windows for all poker sites' code, but made to dig just for a specific table name
# # it could be implemented a bunch better - and we need to not assume the width/height thing that (steffen?) assumed above, we should
# # be able to dig up the window's titlebar handle and get it's information, and such .. but.. for now, i guess this will work.
# # - eric
# b_width = 3
# tb_height = 29
# titles = {}
## tables = discover_nt(c)
# win32gui.EnumWindows(win_enum_handler, titles)
# for s in c.supported_sites.keys():
# for hwnd in titles.keys():
# processid = win32process.GetWindowThreadProcessId(hwnd)
# pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1])
# exe = win32process.GetModuleFileNameEx(pshandle, 0)
# if exe.find(c.supported_sites[s].table_finder) == -1:
# continue
# if titles[hwnd].find(tablename) > -1:
# if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1:
# continue
# tw = Table_Window()
# tw.number = hwnd
# (x, y, width, height) = win32gui.GetWindowRect(hwnd)
# tw.title = titles[hwnd]
# tw.width = int(width) - 2 * b_width
# tw.height = int(height) - b_width - tb_height
# tw.x = int(x) + b_width
# tw.y = int(y) + tb_height
# tw.site = c.supported_sites[s].site_name
# if not tw.site == "Unknown" and not c.supported_sites[tw.site].decoder == "Unknown":
# eval("%s(tw)" % c.supported_sites[tw.site].decoder)
# else:
# tw.name = tablename
# return tw
#
# # if we don't find anything by process name, let's search one more time, and call it Unknown ?
# for hwnd in titles.keys():
# if titles[hwnd].find(tablename) > -1:
# if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1:
# continue
# tw = Table_Window()
# tw.number = hwnd
# (x, y, width, height) = win32gui.GetWindowRect(hwnd)
# tw.title = titles[hwnd]
# tw.width = int(width) - 2 * b_width
# tw.height = int(height) - b_width - tb_height
# tw.x = int(x) + b_width
# tw.y = int(y) + tb_height
# tw.site = "Unknown"
# tw.name = tablename
# return tw
#
# return None
if __name__=="__main__": if __name__=="__main__":
c = Configuration.Config() c = Configuration.Config()
print discover_table_by_name(c, "Catacaos")
print discover_table_by_name(c, "Howard Lederer")
print discover_tournament_table(c, "118942908", "3")
tables = discover(c) tables = discover(c)
for t in tables.keys(): for t in tables.keys():
print "t = ", t
print tables[t] print tables[t]
print "press enter to continue" print "press enter to continue"

View File

@ -1,449 +1,461 @@
#!/usr/bin/python #!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso #Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify #This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by #it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License. #the Free Software Foundation, version 3 of the License.
# #
#This program is distributed in the hope that it will be useful, #This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of #but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details. #GNU General Public License for more details.
# #
#You should have received a copy of the GNU Affero General Public License #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/>. #along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
import os import os
import sys import sys
from optparse import OptionParser from optparse import OptionParser
parser = OptionParser() parser = OptionParser()
parser.add_option("-x", "--errorsToConsole", action="store_true", parser.add_option("-x", "--errorsToConsole", action="store_true",
help="If passed error output will go to the console rather than .") help="If passed error output will go to the console rather than .")
(options, sys.argv) = parser.parse_args() (options, sys.argv) = parser.parse_args()
if not options.errorsToConsole: if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('fpdb-error-log.txt', 'w', 0) errorFile = open('fpdb-error-log.txt', 'w', 0)
sys.stderr = errorFile sys.stderr = errorFile
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import fpdb_db import fpdb_db
import fpdb_simple import fpdb_simple
import GuiBulkImport import GuiBulkImport
import GuiTableViewer import GuiPlayerStats
import GuiAutoImport import GuiTableViewer
import GuiGraphViewer import GuiAutoImport
import FpdbSQLQueries import GuiGraphViewer
import Configuration import FpdbSQLQueries
import Configuration
class fpdb:
def tab_clicked(self, widget, tab_name): class fpdb:
"""called when a tab button is clicked to activate that tab""" def tab_clicked(self, widget, tab_name):
#print "start of tab_clicked" """called when a tab button is clicked to activate that tab"""
self.display_tab(tab_name) #print "start of tab_clicked"
#end def tab_clicked self.display_tab(tab_name)
#end def tab_clicked
def add_and_display_tab(self, new_tab, new_tab_name):
"""just calls the component methods""" def add_and_display_tab(self, new_tab, new_tab_name):
self.add_tab(new_tab, new_tab_name) """just calls the component methods"""
self.display_tab(new_tab_name) self.add_tab(new_tab, new_tab_name)
#end def add_and_display_tab self.display_tab(new_tab_name)
#end def add_and_display_tab
def add_tab(self, new_tab, new_tab_name):
"""adds a tab, namely creates the button and displays it and appends all the relevant arrays""" def add_tab(self, new_tab, new_tab_name):
#print "start of add_tab" """adds a tab, namely creates the button and displays it and appends all the relevant arrays"""
for i in self.tab_names: #todo: check this is valid #print "start of add_tab"
if i==new_tab_name: for i in self.tab_names: #todo: check this is valid
raise fpdb_simple.FpdbError("duplicate tab_name not permitted") if i==new_tab_name:
raise fpdb_simple.FpdbError("duplicate tab_name not permitted")
self.tabs.append(new_tab)
self.tab_names.append(new_tab_name) self.tabs.append(new_tab)
self.tab_names.append(new_tab_name)
new_tab_sel_button=gtk.ToggleButton(new_tab_name)
new_tab_sel_button.connect("clicked", self.tab_clicked, new_tab_name) new_tab_sel_button=gtk.ToggleButton(new_tab_name)
self.tab_box.add(new_tab_sel_button) new_tab_sel_button.connect("clicked", self.tab_clicked, new_tab_name)
new_tab_sel_button.show() self.tab_box.add(new_tab_sel_button)
self.tab_buttons.append(new_tab_sel_button) new_tab_sel_button.show()
#end def add_tab self.tab_buttons.append(new_tab_sel_button)
#end def add_tab
def display_tab(self, new_tab_name):
"""displays the indicated tab""" def display_tab(self, new_tab_name):
#print "start of display_tab, len(self.tab_names):",len(self.tab_names) """displays the indicated tab"""
tab_no=-1 #print "start of display_tab, len(self.tab_names):",len(self.tab_names)
#if len(self.tab_names)>1: tab_no=-1
for i in range(len(self.tab_names)): #if len(self.tab_names)>1:
#print "display_tab, new_tab_name:",new_tab_name," self.tab_names[i]:", self.tab_names[i] for i in range(len(self.tab_names)):
if (new_tab_name==self.tab_names[i]): #print "display_tab, new_tab_name:",new_tab_name," self.tab_names[i]:", self.tab_names[i]
tab_no=i if (new_tab_name==self.tab_names[i]):
#self.tab_buttons[i].set_active(False) tab_no=i
#else: #self.tab_buttons[i].set_active(False)
# tab_no=0 #else:
# tab_no=0
#current_tab_no=-1
for i in range(len(self.tab_names)): #current_tab_no=-1
if self.current_tab==self.tabs[i]: for i in range(len(self.tab_names)):
#self.tab_buttons[i].set_active(False) if self.current_tab==self.tabs[i]:
pass #self.tab_buttons[i].set_active(False)
pass
if tab_no==-1:
raise fpdb_simple.FpdbError("invalid tab_no") if tab_no==-1:
else: raise fpdb_simple.FpdbError("invalid tab_no")
self.main_vbox.remove(self.current_tab) else:
#self.current_tab.destroy() self.main_vbox.remove(self.current_tab)
self.current_tab=self.tabs[tab_no] #self.current_tab.destroy()
self.main_vbox.add(self.current_tab) self.current_tab=self.tabs[tab_no]
self.tab_buttons[tab_no].set_active(True) self.main_vbox.add(self.current_tab)
self.current_tab.show() self.tab_buttons[tab_no].set_active(True)
#end def display_tab self.current_tab.show()
#end def display_tab
def delete_event(self, widget, event, data=None):
return False def delete_event(self, widget, event, data=None):
#end def delete_event return False
#end def delete_event
def destroy(self, widget, data=None):
self.quit(widget, data) def destroy(self, widget, data=None):
#end def destroy self.quit(widget, data)
#end def destroy
def dia_about(self, widget, data):
print "todo: implement dia_about" def dia_about(self, widget, data):
#end def dia_about print "todo: implement dia_about"
#end def dia_about
def dia_create_del_database(self, widget, data):
print "todo: implement dia_create_del_database" def dia_create_del_database(self, widget, data):
obtain_global_lock() print "todo: implement dia_create_del_database"
#end def dia_create_del_database self.obtain_global_lock()
#end def dia_create_del_database
def dia_create_del_user(self, widget, data):
print "todo: implement dia_create_del_user" def dia_create_del_user(self, widget, data):
obtain_global_lock() print "todo: implement dia_create_del_user"
#end def dia_create_del_user self.obtain_global_lock()
#end def dia_create_del_user
def dia_database_stats(self, widget, data):
print "todo: implement dia_database_stats" def dia_database_stats(self, widget, data):
#string=fpdb_db.getDbStats(db, cursor) print "todo: implement dia_database_stats"
#end def dia_database_stats #string=fpdb_db.getDbStats(db, cursor)
#end def dia_database_stats
def dia_delete_db_parts(self, widget, data):
print "todo: implement dia_delete_db_parts" def dia_delete_db_parts(self, widget, data):
obtain_global_lock() print "todo: implement dia_delete_db_parts"
#end def dia_delete_db_parts self.obtain_global_lock()
#end def dia_delete_db_parts
def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None):
print "todo: implement dia_edit_profile" def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None):
obtain_global_lock() print "todo: implement dia_edit_profile"
#end def dia_edit_profile self.obtain_global_lock()
#end def dia_edit_profile
def dia_export_db(self, widget, data):
print "todo: implement dia_export_db" def dia_export_db(self, widget, data):
obtain_global_lock() print "todo: implement dia_export_db"
#end def dia_export_db self.obtain_global_lock()
#end def dia_export_db
def dia_get_db_root_credentials(self):
"""obtains db root credentials from user""" def dia_get_db_root_credentials(self):
print "todo: implement dia_get_db_root_credentials" """obtains db root credentials from user"""
# user, pw=None, None print "todo: implement dia_get_db_root_credentials"
# # user, pw=None, None
# dialog=gtk.Dialog(title="DB Credentials needed", parent=None, flags=0, #
# buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,"Connect and recreate",gtk.RESPONSE_OK)) # dialog=gtk.Dialog(title="DB Credentials needed", parent=None, flags=0,
# # buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,"Connect and recreate",gtk.RESPONSE_OK))
# label_warning1=gtk.Label("Please enter credentials for a database user for "+self.host+" that has permissions to create a database.") #
# # label_warning1=gtk.Label("Please enter credentials for a database user for "+self.host+" that has permissions to create a database.")
# #
# label_user=gtk.Label("Username") #
# dialog.vbox.add(label_user) # label_user=gtk.Label("Username")
# label_user.show() # dialog.vbox.add(label_user)
# # label_user.show()
# response=dialog.run() #
# dialog.destroy() # response=dialog.run()
# return (user, pw, response) # dialog.destroy()
#end def dia_get_db_root_credentials # return (user, pw, response)
#end def dia_get_db_root_credentials
def dia_import_db(self, widget, data):
print "todo: implement dia_import_db" def dia_import_db(self, widget, data):
obtain_global_lock() print "todo: implement dia_import_db"
#end def dia_import_db self.obtain_global_lock()
#end def dia_import_db
def dia_licensing(self, widget, data):
print "todo: implement dia_licensing" def dia_licensing(self, widget, data):
#end def dia_licensing print "todo: implement dia_licensing"
#end def dia_licensing
def dia_load_profile(self, widget, data):
"""Dialogue to select a file to load a profile from""" def dia_load_profile(self, widget, data):
self.obtain_global_lock() """Dialogue to select a file to load a profile from"""
chooser = gtk.FileChooserDialog(title="Please select a profile file to load", self.obtain_global_lock()
action=gtk.FILE_CHOOSER_ACTION_OPEN, chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) action=gtk.FILE_CHOOSER_ACTION_OPEN,
chooser.set_filename(self.profile) buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_filename(self.profile)
response = chooser.run()
chooser.destroy() response = chooser.run()
if response == gtk.RESPONSE_OK: chooser.destroy()
self.load_profile(chooser.get_filename()) if response == gtk.RESPONSE_OK:
elif response == gtk.RESPONSE_CANCEL: self.load_profile(chooser.get_filename())
print 'User cancelled loading profile' elif response == gtk.RESPONSE_CANCEL:
#end def dia_load_profile print 'User cancelled loading profile'
#end def dia_load_profile
def dia_recreate_tables(self, widget, data):
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables""" def dia_recreate_tables(self, widget, data):
self.obtain_global_lock() """Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
dia_confirm=gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, self.obtain_global_lock()
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables") dia_confirm=gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
diastring=("Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted.") buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted diastring=("Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted.")
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
response=dia_confirm.run()
dia_confirm.destroy() response=dia_confirm.run()
if response == gtk.RESPONSE_YES: dia_confirm.destroy()
self.db.recreate_tables() if response == gtk.RESPONSE_YES:
elif response == gtk.RESPONSE_NO: self.db.recreate_tables()
print 'User cancelled recreating tables' elif response == gtk.RESPONSE_NO:
#end def dia_recreate_tables print 'User cancelled recreating tables'
#end def dia_recreate_tables
def dia_regression_test(self, widget, data):
print "todo: implement dia_regression_test" def dia_regression_test(self, widget, data):
self.obtain_global_lock() print "todo: implement dia_regression_test"
#end def dia_regression_test self.obtain_global_lock()
#end def dia_regression_test
def dia_save_profile(self, widget, data):
print "todo: implement dia_save_profile" def dia_save_profile(self, widget, data):
#end def dia_save_profile print "todo: implement dia_save_profile"
#end def dia_save_profile
def diaSetupWizard(self, path):
print "todo: implement setup wizard" def diaSetupWizard(self, path):
print "setup wizard not implemented - please create the default configuration file:", path print "todo: implement setup wizard"
diaSetupWizard = gtk.Dialog(title="Fatal Error - Config File Missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) print "setup wizard not implemented - please create the default configuration file:", path
diaSetupWizard = gtk.Dialog(title="Fatal Error - Config File Missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK))
label = gtk.Label("Please copy the config file from the docs folder to:")
diaSetupWizard.vbox.add(label) label = gtk.Label("Please copy the config file from the docs folder to:")
label.show() diaSetupWizard.vbox.add(label)
label.show()
label = gtk.Label(path)
diaSetupWizard.vbox.add(label) label = gtk.Label(path)
label.show() diaSetupWizard.vbox.add(label)
label.show()
label = gtk.Label("and edit it according to the install documentation at http://fpdb.sourceforge.net")
diaSetupWizard.vbox.add(label) label = gtk.Label("and edit it according to the install documentation at http://fpdb.sourceforge.net")
label.show() diaSetupWizard.vbox.add(label)
label.show()
response = diaSetupWizard.run()
sys.exit(1) response = diaSetupWizard.run()
#end def diaSetupWizard sys.exit(1)
#end def diaSetupWizard
def get_menu(self, window):
"""returns the menu for this program""" def get_menu(self, window):
accel_group = gtk.AccelGroup() """returns the menu for this program"""
self.item_factory = gtk.ItemFactory(gtk.MenuBar, "<main>", accel_group) accel_group = gtk.AccelGroup()
self.item_factory.create_items(self.menu_items) self.item_factory = gtk.ItemFactory(gtk.MenuBar, "<main>", accel_group)
window.add_accel_group(accel_group) self.item_factory.create_items(self.menu_items)
return self.item_factory.get_widget("<main>") window.add_accel_group(accel_group)
#end def get_menu return self.item_factory.get_widget("<main>")
#end def get_menu
def load_profile(self):
"""Loads profile from the provided path name.""" def load_profile(self):
self.settings = {} """Loads profile from the provided path name."""
if (os.sep=="/"): self.settings = {}
self.settings['os']="linuxmac" if (os.sep=="/"):
else: self.settings['os']="linuxmac"
self.settings['os']="windows" else:
self.settings['os']="windows"
self.settings.update(self.config.get_db_parameters())
self.settings.update(self.config.get_tv_parameters()) self.settings.update(self.config.get_db_parameters())
self.settings.update(self.config.get_import_parameters()) self.settings.update(self.config.get_tv_parameters())
self.settings.update(self.config.get_default_paths()) self.settings.update(self.config.get_import_parameters())
self.settings.update(self.config.get_default_paths())
if self.db!=None:
self.db.disconnect() if self.db!=None:
self.db.disconnect()
self.db = fpdb_db.fpdb_db()
#print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName'] self.db = fpdb_db.fpdb_db()
self.db.connect(self.settings['db-backend'], self.settings['db-host'], self.settings['db-databaseName'], self.settings['db-user'], self.settings['db-password']) #print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName']
if self.db.wrongDbVersion: self.db.connect(self.settings['db-backend'],
diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) self.settings['db-host'],
self.settings['db-databaseName'],
label = gtk.Label("An invalid DB version or missing tables have been detected.") self.settings['db-user'],
diaDbVersionWarning.vbox.add(label) self.settings['db-password'])
label.show() if self.db.wrongDbVersion:
diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
label = gtk.Label("This error is not necessarily fatal but it is strongly recommended that you recreate the tables by using the Database menu.")
diaDbVersionWarning.vbox.add(label) label = gtk.Label("An invalid DB version or missing tables have been detected.")
label.show() diaDbVersionWarning.vbox.add(label)
label.show()
label = gtk.Label("Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc.")
diaDbVersionWarning.vbox.add(label) label = gtk.Label("This error is not necessarily fatal but it is strongly recommended that you recreate the tables by using the Database menu.")
label.show() diaDbVersionWarning.vbox.add(label)
label.show()
response = diaDbVersionWarning.run()
diaDbVersionWarning.destroy() label = gtk.Label("Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc.")
diaDbVersionWarning.vbox.add(label)
# Database connected to successfully, load queries to pass on to other classes label.show()
self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name())
#end def load_profile response = diaDbVersionWarning.run()
diaDbVersionWarning.destroy()
def not_implemented(self):
print "todo: called unimplemented menu entry (users: pls ignore this)"#remove this once more entries are implemented # Database connected to successfully, load queries to pass on to other classes
#end def not_implemented self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name())
#end def load_profile
def obtain_global_lock(self):
print "todo: implement obtain_global_lock (users: pls ignore this)" def not_implemented(self):
#end def obtain_global_lock print "todo: called unimplemented menu entry (users: pls ignore this)"#remove this once more entries are implemented
#end def not_implemented
def quit(self, widget, data):
print "Quitting normally" def obtain_global_lock(self):
#check if current settings differ from profile, if so offer to save or abort print "todo: implement obtain_global_lock (users: pls ignore this)"
self.db.disconnect() #end def obtain_global_lock
gtk.main_quit()
#end def quit_cliecked def quit(self, widget, data):
print "Quitting normally"
def release_global_lock(self): #check if current settings differ from profile, if so offer to save or abort
print "todo: implement release_global_lock" self.db.disconnect()
#end def release_global_lock gtk.main_quit()
#end def quit_cliecked
def tab_abbreviations(self, widget, data):
print "todo: implement tab_abbreviations" def release_global_lock(self):
#end def tab_abbreviations print "todo: implement release_global_lock"
#end def release_global_lock
def tab_auto_import(self, widget, data):
"""opens the auto import tab""" def tab_abbreviations(self, widget, data):
new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings, self.config) print "todo: implement tab_abbreviations"
self.threads.append(new_aimp_thread) #end def tab_abbreviations
aimp_tab=new_aimp_thread.get_vbox()
self.add_and_display_tab(aimp_tab, "Auto Import") def tab_auto_import(self, widget, data):
#end def tab_auto_import """opens the auto import tab"""
new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings, self.config)
def tab_bulk_import(self, widget, data): self.threads.append(new_aimp_thread)
"""opens a tab for bulk importing""" aimp_tab=new_aimp_thread.get_vbox()
#print "start of tab_bulk_import" self.add_and_display_tab(aimp_tab, "Auto Import")
new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings, self.config) #end def tab_auto_import
self.threads.append(new_import_thread)
bulk_tab=new_import_thread.get_vbox() def tab_bulk_import(self, widget, data):
self.add_and_display_tab(bulk_tab, "Bulk Import") """opens a tab for bulk importing"""
#end def tab_bulk_import #print "start of tab_bulk_import"
new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings, self.config)
def tab_main_help(self, widget, data): self.threads.append(new_import_thread)
"""Displays a tab with the main fpdb help screen""" bulk_tab=new_import_thread.get_vbox()
#print "start of tab_main_help" self.add_and_display_tab(bulk_tab, "Bulk Import")
mh_tab=gtk.Label("""Welcome to Fpdb! #end def tab_bulk_import
For documentation please visit our website at http://fpdb.sourceforge.net/ or check the docs directory in the fpdb folder.
Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml def tab_player_stats(self, widget, data):
This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.db, self.config, self.querydict)
self.add_and_display_tab(mh_tab, "Help") self.threads.append(new_ps_thread)
#end def tab_main_help ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Player Stats")
def tab_table_viewer(self, widget, data):
"""opens a table viewer tab"""
#print "start of tab_table_viewer" def tab_main_help(self, widget, data):
new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings) """Displays a tab with the main fpdb help screen"""
self.threads.append(new_tv_thread) #print "start of tab_main_help"
tv_tab=new_tv_thread.get_vbox() mh_tab=gtk.Label("""Welcome to Fpdb!
self.add_and_display_tab(tv_tab, "Table Viewer") For documentation please visit our website at http://fpdb.sourceforge.net/ or check the docs directory in the fpdb folder.
#end def tab_table_viewer Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml
This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
def tabGraphViewer(self, widget, data): self.add_and_display_tab(mh_tab, "Help")
"""opens a graph viewer tab""" #end def tab_main_help
#print "start of tabGraphViewer"
new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config) def tab_table_viewer(self, widget, data):
self.threads.append(new_gv_thread) """opens a table viewer tab"""
gv_tab=new_gv_thread.get_vbox() #print "start of tab_table_viewer"
self.add_and_display_tab(gv_tab, "Graphs") new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings)
#end def tabGraphViewer self.threads.append(new_tv_thread)
tv_tab=new_tv_thread.get_vbox()
def __init__(self): self.add_and_display_tab(tv_tab, "Table Viewer")
self.threads=[] #end def tab_table_viewer
self.db=None
self.config = Configuration.Config() def tabGraphViewer(self, widget, data):
self.load_profile() """opens a graph viewer tab"""
#print "start of tabGraphViewer"
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config)
self.window.connect("delete_event", self.delete_event) self.threads.append(new_gv_thread)
self.window.connect("destroy", self.destroy) gv_tab=new_gv_thread.get_vbox()
self.window.set_title("Free Poker DB - version: alpha9+, p143 or higher") self.add_and_display_tab(gv_tab, "Graphs")
self.window.set_border_width(1) #end def tabGraphViewer
self.window.set_size_request(1020,400)
self.window.set_resizable(True) def __init__(self):
self.threads=[]
self.menu_items = ( self.db=None
( "/_Main", None, None, 0, "<Branch>" ), self.config = Configuration.Config()
( "/Main/_Load Profile (broken)", "<control>L", self.dia_load_profile, 0, None ), self.load_profile()
( "/Main/_Edit Profile (todo)", "<control>E", self.dia_edit_profile, 0, None ),
( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ), self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
("/Main/sep1", None, None, 0, "<Separator>" ), self.window.connect("delete_event", self.delete_event)
("/Main/_Quit", "<control>Q", self.quit, 0, None ), self.window.connect("destroy", self.destroy)
("/_Import", None, None, 0, "<Branch>" ), self.window.set_title("Free Poker DB - version: alpha9+, p143 or higher")
("/Import/_Bulk Import", "<control>B", self.tab_bulk_import, 0, None ), self.window.set_border_width(1)
("/Import/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ), self.window.set_size_request(1020,400)
("/Import/Auto _Rating (todo)", "<control>R", self.not_implemented, 0, None ), self.window.set_resizable(True)
("/_Viewers", None, None, 0, "<Branch>" ),
("/_Viewers/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ), self.menu_items = (
("/Viewers/_Graphs", "<control>G", self.tabGraphViewer, 0, None ), ( "/_Main", None, None, 0, "<Branch>" ),
("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ), ( "/Main/_Load Profile (broken)", "<control>L", self.dia_load_profile, 0, None ),
("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ), ( "/Main/_Edit Profile (todo)", "<control>E", self.dia_edit_profile, 0, None ),
("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), ( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ),
("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), ("/Main/sep1", None, None, 0, "<Separator>" ),
("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), ("/Main/_Quit", "<control>Q", self.quit, 0, None ),
("/Viewers/Poker_table Viewer (mostly obselete)", "<control>T", self.tab_table_viewer, 0, None ), ("/_Import", None, None, 0, "<Branch>" ),
#( "/Viewers/Tourney Replayer ("/Import/_Bulk Import", "<control>B", self.tab_bulk_import, 0, None ),
( "/_Database", None, None, 0, "<Branch>" ), ("/Import/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ),
( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ), ("/Import/Auto _Rating (todo)", "<control>R", self.not_implemented, 0, None ),
( "/Database/Create or Delete _User (todo)", None, self.dia_create_del_user, 0, None ), ("/_Viewers", None, None, 0, "<Branch>" ),
( "/Database/Create or Recreate _Tables", None, self.dia_recreate_tables, 0, None ), ("/_Viewers/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ),
( "/Database/_Statistics (todo)", None, self.dia_database_stats, 0, None ), ("/Viewers/_Graphs", "<control>G", self.tabGraphViewer, 0, None ),
( "/D_ebugging", None, None, 0, "<Branch>" ), ("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ),
( "/Debugging/_Delete Parts of Database (todo)", None, self.dia_delete_db_parts, 0, None ), ("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ),
( "/Debugging/_Export DB (todo)", None, self.dia_export_db, 0, None ), ("/Viewers/_Player Stats (tabulated view)", None, self.tab_player_stats, 0, None ),
( "/Debugging/_Import DB (todo)", None, self.dia_import_db, 0, None ), ("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ),
( "/Debugging/_Regression test (todo)", None, self.dia_regression_test, 0, None ), ("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ),
( "/_Help", None, None, 0, "<LastBranch>" ), ("/Viewers/Poker_table Viewer (mostly obselete)", "<control>T", self.tab_table_viewer, 0, None ),
( "/Help/_Main Help", "<control>H", self.tab_main_help, 0, None ), #( "/Viewers/Tourney Replayer
( "/Help/_Abbrevations (todo)", None, self.tab_abbreviations, 0, None ), ( "/_Database", None, None, 0, "<Branch>" ),
( "/Help/sep1", None, None, 0, "<Separator>" ), ( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ),
( "/Help/A_bout (todo)", None, self.dia_about, 0, None ), ( "/Database/Create or Delete _User (todo)", None, self.dia_create_del_user, 0, None ),
( "/Help/_License and Copying (todo)", None, self.dia_licensing, 0, None ) ( "/Database/Create or Recreate _Tables", None, self.dia_recreate_tables, 0, None ),
) ( "/Database/_Statistics (todo)", None, self.dia_database_stats, 0, None ),
( "/D_ebugging", None, None, 0, "<Branch>" ),
self.main_vbox = gtk.VBox(False, 1) ( "/Debugging/_Delete Parts of Database (todo)", None, self.dia_delete_db_parts, 0, None ),
self.main_vbox.set_border_width(1) ( "/Debugging/_Export DB (todo)", None, self.dia_export_db, 0, None ),
self.window.add(self.main_vbox) ( "/Debugging/_Import DB (todo)", None, self.dia_import_db, 0, None ),
self.main_vbox.show() ( "/Debugging/_Regression test (todo)", None, self.dia_regression_test, 0, None ),
( "/_Help", None, None, 0, "<LastBranch>" ),
menubar = self.get_menu(self.window) ( "/Help/_Main Help", "<control>H", self.tab_main_help, 0, None ),
self.main_vbox.pack_start(menubar, False, True, 0) ( "/Help/_Abbrevations (todo)", None, self.tab_abbreviations, 0, None ),
menubar.show() ( "/Help/sep1", None, None, 0, "<Separator>" ),
#done menubar ( "/Help/A_bout (todo)", None, self.dia_about, 0, None ),
( "/Help/_License and Copying (todo)", None, self.dia_licensing, 0, None )
self.tabs=[] )
self.tab_names=[]
self.tab_buttons=[] self.main_vbox = gtk.VBox(False, 1)
self.tab_box = gtk.HBox(False,1) self.main_vbox.set_border_width(1)
self.main_vbox.pack_start(self.tab_box, False, True, 0) self.window.add(self.main_vbox)
self.tab_box.show() self.main_vbox.show()
#done tab bar
menubar = self.get_menu(self.window)
self.current_tab = gtk.VBox(False,1) self.main_vbox.pack_start(menubar, False, True, 0)
self.current_tab.set_border_width(1) menubar.show()
self.main_vbox.add(self.current_tab) #done menubar
self.current_tab.show()
self.tabs=[]
self.tab_main_help(None, None) self.tab_names=[]
self.tab_buttons=[]
self.status_bar = gtk.Label("Status: Connected to "+self.db.get_backend_name()+" database named "+self.db.database+" on host "+self.db.host) self.tab_box = gtk.HBox(False,1)
self.main_vbox.pack_end(self.status_bar, False, True, 0) self.main_vbox.pack_start(self.tab_box, False, True, 0)
self.status_bar.show() self.tab_box.show()
#done tab bar
self.window.show()
#end def __init__ self.current_tab = gtk.VBox(False,1)
self.current_tab.set_border_width(1)
def main(self): self.main_vbox.add(self.current_tab)
gtk.main() self.current_tab.show()
return 0
#end def main self.tab_main_help(None, None)
if __name__ == "__main__": self.status_bar = gtk.Label("Status: Connected to "+self.db.get_backend_name()+" database named "+self.db.database+" on host "+self.db.host)
me = fpdb() self.main_vbox.pack_end(self.status_bar, False, True, 0)
me.main() self.status_bar.show()
self.window.show()
#end def __init__
def main(self):
gtk.main()
return 0
#end def main
if __name__ == "__main__":
me = fpdb()
me.main()

View File

@ -31,26 +31,39 @@ class fpdb_db:
self.SQLITE=4 self.SQLITE=4
#end def __init__ #end def __init__
def connect(self, backend, host, database, user, password): def connect(self, backend=None, host=None, database=None,
user=None, password=None):
"""Connects a database with the given parameters""" """Connects a database with the given parameters"""
if backend is None:
raise FpdbError('Database backend not defined')
self.backend=backend self.backend=backend
self.host=host self.host=host
self.database=database
self.user=user self.user=user
self.password=password self.password=password
self.database=database
if backend==self.MYSQL_INNODB: if backend==self.MYSQL_INNODB:
import MySQLdb import MySQLdb
self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database)
elif backend==self.PGSQL: elif backend==self.PGSQL:
import psycopg2 import psycopg2
self.db = psycopg2.connect(host = host, user = user, password = password, database = database) # If DB connection is made over TCP, then the variables
# host, user and password are required
if self.host or self.user:
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
# For local domain-socket connections, only DB name is
# needed, and everything else is in fact undefined and/or
# flat out wrong
else:
self.db = psycopg2.connect(database = database)
else: else:
raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) raise fpdb_simple.FpdbError("unrecognised database backend:"+backend)
self.cursor=self.db.cursor() self.cursor=self.db.cursor()
self.cursor.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED') self.cursor.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED')
# Set up query dictionary as early in the connection process as we can. # Set up query dictionary as early in the connection process as we can.
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
self.wrongDbVersion=False self.wrongDbVersion=False
try: try:
self.cursor.execute("SELECT * FROM Settings") self.cursor.execute("SELECT * FROM Settings")
@ -82,23 +95,23 @@ class fpdb_db:
def create_tables(self): def create_tables(self):
#todo: should detect and fail gracefully if tables already exist. #todo: should detect and fail gracefully if tables already exist.
self.cursor.execute(self.sql.query['createSettingsTable']) self.cursor.execute(self.sql.query['createSettingsTable'])
self.cursor.execute(self.sql.query['createSitesTable']) self.cursor.execute(self.sql.query['createSitesTable'])
self.cursor.execute(self.sql.query['createGametypesTable']) self.cursor.execute(self.sql.query['createGametypesTable'])
self.cursor.execute(self.sql.query['createPlayersTable']) self.cursor.execute(self.sql.query['createPlayersTable'])
self.cursor.execute(self.sql.query['createAutoratesTable']) self.cursor.execute(self.sql.query['createAutoratesTable'])
self.cursor.execute(self.sql.query['createHandsTable']) self.cursor.execute(self.sql.query['createHandsTable'])
self.cursor.execute(self.sql.query['createBoardCardsTable']) self.cursor.execute(self.sql.query['createBoardCardsTable'])
self.cursor.execute(self.sql.query['createTourneyTypesTable']) self.cursor.execute(self.sql.query['createTourneyTypesTable'])
self.cursor.execute(self.sql.query['createTourneysTable']) self.cursor.execute(self.sql.query['createTourneysTable'])
self.cursor.execute(self.sql.query['createTourneysPlayersTable']) self.cursor.execute(self.sql.query['createTourneysPlayersTable'])
self.cursor.execute(self.sql.query['createHandsPlayersTable']) self.cursor.execute(self.sql.query['createHandsPlayersTable'])
self.cursor.execute(self.sql.query['createHandsActionsTable']) self.cursor.execute(self.sql.query['createHandsActionsTable'])
self.cursor.execute(self.sql.query['createHudCacheTable']) self.cursor.execute(self.sql.query['createHudCacheTable'])
self.cursor.execute(self.sql.query['addTourneyIndex']) self.cursor.execute(self.sql.query['addTourneyIndex'])
self.cursor.execute(self.sql.query['addHandsIndex']) self.cursor.execute(self.sql.query['addHandsIndex'])
self.cursor.execute(self.sql.query['addPlayersIndex']) self.cursor.execute(self.sql.query['addPlayersIndex'])
self.fillDefaultData() self.fillDefaultData()
self.db.commit() self.db.commit()
#end def disconnect #end def disconnect
def drop_tables(self): def drop_tables(self):
@ -106,23 +119,23 @@ class fpdb_db:
if(self.get_backend_name() == 'MySQL InnoDB'): if(self.get_backend_name() == 'MySQL InnoDB'):
#Databases with FOREIGN KEY support need this switched of before you can drop tables #Databases with FOREIGN KEY support need this switched of before you can drop tables
self.drop_referencial_integrity() self.drop_referencial_integrity()
# Query the DB to see what tables exist # Query the DB to see what tables exist
self.cursor.execute(self.sql.query['list_tables']) self.cursor.execute(self.sql.query['list_tables'])
for table in self.cursor: for table in self.cursor:
self.cursor.execute(self.sql.query['drop_table'] + table[0]) self.cursor.execute(self.sql.query['drop_table'] + table[0])
elif(self.get_backend_name() == 'PostgreSQL'): elif(self.get_backend_name() == 'PostgreSQL'):
self.db.commit()# I have no idea why this makes the query work--REB 07OCT2008 self.db.commit()# I have no idea why this makes the query work--REB 07OCT2008
self.cursor.execute(self.sql.query['list_tables']) self.cursor.execute(self.sql.query['list_tables'])
tables = self.cursor.fetchall() tables = self.cursor.fetchall()
for table in tables: for table in tables:
self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade') self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
elif(self.get_backend_name() == 'SQLite'): elif(self.get_backend_name() == 'SQLite'):
#todo: sqlite version here #todo: sqlite version here
print "Empty function here" print "Empty function here"
self.db.commit() self.db.commit()
#end def drop_tables #end def drop_tables
def drop_referencial_integrity(self): def drop_referencial_integrity(self):

View File

@ -1,310 +1,322 @@
#!/usr/bin/python #!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso #Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify #This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by #it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License. #the Free Software Foundation, version 3 of the License.
# #
#This program is distributed in the hope that it will be useful, #This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of #but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details. #GNU General Public License for more details.
# #
#You should have received a copy of the GNU Affero General Public License #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/>. #along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
#see status.txt for site/games support info #see status.txt for site/games support info
import sys import sys
try: try:
import MySQLdb import MySQLdb
mysqlLibFound=True mysqlLibFound=True
except: except:
pass pass
try: try:
import psycopg2 import psycopg2
pgsqlLibFound=True pgsqlLibFound=True
except: except:
pass pass
import math import traceback
import os import math
import datetime import os
import re import datetime
import fpdb_simple import re
import fpdb_parse_logic import fpdb_simple
from time import time import fpdb_parse_logic
from time import time
class Importer:
class Importer:
def __init__(self, caller, settings, config):
"""Constructor""" def __init__(self, caller, settings, config):
self.settings=settings """Constructor"""
self.caller=caller self.settings=settings
self.config = config self.caller=caller
self.db = None self.config = config
self.cursor = None self.db = None
self.filelist = {} self.cursor = None
self.dirlist = {} self.filelist = {}
self.monitor = False self.dirlist = {}
self.updated = {} #Time last import was run {file:mtime} self.monitor = False
self.lines = None self.updated = {} #Time last import was run {file:mtime}
self.faobs = None #File as one big string self.lines = None
self.pos_in_file = {} # dict to remember how far we have read in the file self.faobs = None #File as one big string
#Set defaults self.pos_in_file = {} # dict to remember how far we have read in the file
self.callHud = self.config.get_import_parameters().get("callFpdbHud") #Set defaults
if not self.settings.has_key('minPrint'): self.callHud = self.config.get_import_parameters().get("callFpdbHud")
self.settings['minPrint'] = 30 if not self.settings.has_key('minPrint'):
self.dbConnect() self.settings['minPrint'] = 30
self.dbConnect()
def dbConnect(self):
#connect to DB # XXX: Why is this here, when fpdb_db.connect() already does the
if self.settings['db-backend'] == 2: # same?
if not mysqlLibFound: def dbConnect(self):
raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") #connect to DB
self.db = MySQLdb.connect(self.settings['db-host'], self.settings['db-user'], if self.settings['db-backend'] == 2:
self.settings['db-password'], self.settings['db-databaseName']) if not mysqlLibFound:
elif self.settings['db-backend'] == 3: raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file")
if not pgsqlLibFound: self.db = MySQLdb.connect(self.settings['db-host'], self.settings['db-user'],
raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") self.settings['db-password'], self.settings['db-databaseName'])
print self.settings elif self.settings['db-backend'] == 3:
self.db = psycopg2.connect(host = self.settings['db-host'], if not pgsqlLibFound:
user = self.settings['db-user'], raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file")
password = self.settings['db-password'], print self.settings
database = self.settings['db-databaseName']) if not self.settings.has_key('db-host') or \
elif self.settings['db-backend'] == 4: not self.settings.has_key('db-user'):
pass self.db = psycopg2.connect(host = self.settings['db-host'],
else: user = self.settings['db-user'],
pass password = self.settings['db-password'],
self.cursor = self.db.cursor() database = self.settings['db-databaseName'])
else:
#Set functions dbname = self.settings['db-databaseName']
def setCallHud(self, value): self.db = psycopg2.connect(database = dbname)
self.callHud = value elif self.settings['db-backend'] == 4:
pass
def setMinPrint(self, value): else:
self.settings['minPrint'] = int(value) pass
self.cursor = self.db.cursor()
def setHandCount(self, value):
self.settings['handCount'] = int(value) #Set functions
def setCallHud(self, value):
def setQuiet(self, value): self.callHud = value
self.settings['quiet'] = value
def setMinPrint(self, value):
def setFailOnError(self, value): self.settings['minPrint'] = int(value)
self.settings['failOnError'] = value
def setHandCount(self, value):
# def setWatchTime(self): self.settings['handCount'] = int(value)
# self.updated = time()
def setQuiet(self, value):
def clearFileList(self): self.settings['quiet'] = value
self.filelist = {}
def setFailOnError(self, value):
#Add an individual file to filelist self.settings['failOnError'] = value
def addImportFile(self, filename, site = "default", filter = "passthrough"):
#TODO: test it is a valid file # def setWatchTime(self):
self.filelist[filename] = [site] + [filter] # self.updated = time()
#Add a directory of files to filelist def clearFileList(self):
#Only one import directory per site supported. self.filelist = {}
#dirlist is a hash of lists:
#dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] } #Add an individual file to filelist
def addImportDirectory(self, dir, monitor = False, site = "default", filter = "passthrough"): def addImportFile(self, filename, site = "default", filter = "passthrough"):
if dir != "/dev/null" and dir.lower() != "none": #TODO: test it is a valid file
if os.path.isdir(dir): self.filelist[filename] = [site] + [filter]
if monitor == True:
self.monitor = True #Add a directory of files to filelist
self.dirlist[site] = [dir] + [filter] #Only one import directory per site supported.
#dirlist is a hash of lists:
for file in os.listdir(dir): #dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] }
self.addImportFile(os.path.join(dir, file), site, filter) def addImportDirectory(self,dir,monitor = False, site = "default", filter = "passthrough"):
else: if os.path.isdir(dir):
print "Warning: Attempted to add: '" + str(dir) + "' as an import directory\n" if monitor == True:
self.monitor = True
#Run full import on filelist self.dirlist[site] = [dir] + [filter]
def runImport(self):
for file in self.filelist: for file in os.listdir(dir):
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) self.addImportFile(os.path.join(dir, file), site, filter)
else:
#Run import on updated files, then store latest update time. print "Warning: Attempted to add: '" + str(dir) + "' as an import directory"
def runUpdated(self):
#Check for new files in directory #Run full import on filelist
#todo: make efficient - always checks for new file, should be able to use mtime of directory def runImport(self):
# ^^ May not work on windows for file in self.filelist:
for site in self.dirlist: self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
#Run import on updated files, then store latest update time.
for file in self.filelist: def runUpdated(self):
stat_info = os.stat(file) #Check for new files in directory
try: #todo: make efficient - always checks for new file, should be able to use mtime of directory
lastupdate = self.updated[file] # ^^ May not work on windows
if stat_info.st_mtime > lastupdate: for site in self.dirlist:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
self.updated[file] = time()
except: for file in self.filelist:
self.updated[file] = time() stat_info = os.stat(file)
# This codepath only runs first time the file is found, if modified in the last try:
# minute run an immediate import. lastupdate = self.updated[file]
if (time() - stat_info.st_mtime) < 60: if stat_info.st_mtime > lastupdate:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
self.updated[file] = time()
# This is now an internal function that should not be called directly. except:
def import_file_dict(self, file, site, filter): self.updated[file] = time()
if(filter == "passthrough"): # This codepath only runs first time the file is found, if modified in the last
self.import_fpdb_file(file, site) # minute run an immediate import.
else: if (time() - stat_info.st_mtime) < 60:
#Load filter, and run filtered file though main importer self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
self.import_fpdb_file(file, site)
# This is now an internal function that should not be called directly.
def import_file_dict(self, file, site, filter):
def import_fpdb_file(self, file, site): if(filter == "passthrough"):
starttime = time() self.import_fpdb_file(file, site)
last_read_hand=0 else:
loc = 0 #Load filter, and run filtered file though main importer
if (file=="stdin"): self.import_fpdb_file(file, site)
inputFile=sys.stdin
else:
inputFile=open(file, "rU") def import_fpdb_file(self, file, site):
try: loc = self.pos_in_file[file] starttime = time()
except: pass last_read_hand=0
loc = 0
# Read input file into class and close file if (file=="stdin"):
inputFile.seek(loc) inputFile=sys.stdin
self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) else:
self.pos_in_file[file] = inputFile.tell() inputFile=open(file, "rU")
inputFile.close() try: loc = self.pos_in_file[file]
except: pass
try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return.
firstline = self.lines[0] # Read input file into class and close file
except: inputFile.seek(loc)
# print "import_fpdb_file", file, site, self.lines, "\n" self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines())
return self.pos_in_file[file] = inputFile.tell()
inputFile.close()
if firstline.find("Tournament Summary")!=-1:
print "TODO: implement importing tournament summaries" try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return.
#self.faobs = readfile(inputFile) firstline = self.lines[0]
#self.parseTourneyHistory() except:
return 0 # print "import_fpdb_file", file, site, self.lines, "\n"
return
site=fpdb_simple.recogniseSite(firstline)
category=fpdb_simple.recogniseCategory(firstline) if firstline.find("Tournament Summary")!=-1:
print "TODO: implement importing tournament summaries"
startpos=0 #self.faobs = readfile(inputFile)
stored=0 #counter #self.parseTourneyHistory()
duplicates=0 #counter return 0
partial=0 #counter
errors=0 #counter site=fpdb_simple.recogniseSite(firstline)
category=fpdb_simple.recogniseCategory(firstline)
for i in range (len(self.lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method
if (len(self.lines[i])<2): startpos=0
endpos=i stored=0 #counter
hand=self.lines[startpos:endpos] duplicates=0 #counter
partial=0 #counter
if (len(hand[0])<2): errors=0 #counter
hand=hand[1:]
for i in range (len(self.lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method
cancelled=False if (len(self.lines[i])<2):
damaged=False endpos=i
if (site=="ftp"): hand=self.lines[startpos:endpos]
for i in range (len(hand)):
if (hand[i].endswith(" has been canceled")): #this is their typo. this is a typo, right? if (len(hand[0])<2):
cancelled=True hand=hand[1:]
seat1=hand[i].find("Seat ") #todo: make this recover by skipping this line cancelled=False
if (seat1!=-1): damaged=False
if (hand[i].find("Seat ", seat1+3)!=-1): if (site=="ftp"):
damaged=True for i in range (len(hand)):
if (hand[i].endswith(" has been canceled")): #this is their typo. this is a typo, right?
if (len(hand)<3): cancelled=True
pass
#todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work. seat1=hand[i].find("Seat ") #todo: make this recover by skipping this line
elif (hand[0].endswith(" (partial)")): #partial hand - do nothing if (seat1!=-1):
partial+=1 if (hand[i].find("Seat ", seat1+3)!=-1):
elif (hand[1].find("Seat")==-1 and hand[2].find("Seat")==-1 and hand[3].find("Seat")==-1):#todo: should this be or instead of and? damaged=True
partial+=1
elif (cancelled or damaged): if (len(hand)<3):
partial+=1 pass
else: #normal processing #todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work.
isTourney=fpdb_simple.isTourney(hand[0]) elif (hand[0].endswith(" (partial)")): #partial hand - do nothing
if not isTourney: partial+=1
fpdb_simple.filterAnteBlindFold(site,hand) elif (hand[1].find("Seat")==-1 and hand[2].find("Seat")==-1 and hand[3].find("Seat")==-1):#todo: should this be or instead of and?
hand=fpdb_simple.filterCrap(site, hand, isTourney) partial+=1
self.hand=hand elif (cancelled or damaged):
partial+=1
try: else: #normal processing
handsId=fpdb_parse_logic.mainParser(self.db, self.cursor, site, category, hand) isTourney=fpdb_simple.isTourney(hand[0])
self.db.commit() if not isTourney:
fpdb_simple.filterAnteBlindFold(site,hand)
stored+=1 hand=fpdb_simple.filterCrap(site, hand, isTourney)
self.db.commit() self.hand=hand
if self.callHud:
#print "call to HUD here. handsId:",handsId try:
#pipe the Hands.id out to the HUD handsId=fpdb_parse_logic.mainParser(self.db, self.cursor, site, category, hand)
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) self.db.commit()
except fpdb_simple.DuplicateError:
duplicates+=1 stored+=1
except (ValueError), fe: self.db.commit()
errors+=1 if self.callHud:
self.printEmailErrorMessage(errors, file, hand[0]) #print "call to HUD here. handsId:",handsId
#pipe the Hands.id out to the HUD
if (self.settings['failOnError']): self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
self.db.commit() #dont remove this, in case hand processing was cancelled. except fpdb_simple.DuplicateError:
raise duplicates+=1
except (fpdb_simple.FpdbError), fe: except (ValueError), fe:
errors+=1 errors+=1
self.printEmailErrorMessage(errors, file, hand[0]) self.printEmailErrorMessage(errors, file, hand)
#fe.printStackTrace() #todo: get stacktrace if (self.settings['failOnError']):
self.db.rollback() self.db.commit() #dont remove this, in case hand processing was cancelled.
raise
if (self.settings['failOnError']): except (fpdb_simple.FpdbError), fe:
self.db.commit() #dont remove this, in case hand processing was cancelled. errors+=1
raise self.printEmailErrorMessage(errors, file, hand)
if (self.settings['minPrint']!=0):
if ((stored+duplicates+partial+errors)%self.settings['minPrint']==0): #fe.printStackTrace() #todo: get stacktrace
print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors self.db.rollback()
if (self.settings['handCount']!=0): if (self.settings['failOnError']):
if ((stored+duplicates+partial+errors)>=self.settings['handCount']): self.db.commit() #dont remove this, in case hand processing was cancelled.
if (not self.settings['quiet']): raise
print "quitting due to reaching the amount of hands to be imported" if (self.settings['minPrint']!=0):
print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", if ((stored+duplicates+partial+errors)%self.settings['minPrint']==0):
partial, "errors:", errors, " time: %5.3f" % (time() - starttime) print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors
sys.exit(0)
startpos=endpos if (self.settings['handCount']!=0):
print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors, " time: %5.3f" % (time() - starttime) if ((stored+duplicates+partial+errors)>=self.settings['handCount']):
if (not self.settings['quiet']):
if stored==0: print "quitting due to reaching the amount of hands to be imported"
if duplicates>0: print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime)
for line_no in range(len(self.lines)): sys.exit(0)
if self.lines[line_no].find("Game #")!=-1: startpos=endpos
final_game_line=self.lines[line_no] print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors, " time:", (time() - starttime)
handsId=fpdb_simple.parseSiteHandNo(final_game_line)
else: if stored==0:
print "failed to read a single hand from file:", inputFile if duplicates>0:
handsId=0 for line_no in range(len(self.lines)):
#todo: this will cause return of an unstored hand number if the last hand was error or partial if self.lines[line_no].find("Game #")!=-1:
self.db.commit() final_game_line=self.lines[line_no]
self.handsId=handsId handsId=fpdb_simple.parseSiteHandNo(final_game_line)
return handsId else:
#end def import_file_dict print "failed to read a single hand from file:", inputFile
handsId=0
def parseTourneyHistory(self): #todo: this will cause return of an unstored hand number if the last hand was error or partial
print "Tourney history parser stub" self.db.commit()
#Find tournament boundaries. self.handsId=handsId
#print self.foabs return handsId
#end def import_file_dict
def printEmailErrorMessage(self, errors, filename, line): def parseTourneyHistory(self):
print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." print "Tourney history parser stub"
print "Filename:", filename #Find tournament boundaries.
print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" #print self.foabs
print self.hand[0]
def printEmailErrorMessage(self, errors, filename, line):
if __name__ == "__main__": traceback.print_exc(file=sys.stderr)
print "CLI for fpdb_import is now available as CliFpdb.py" print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it."
print "Filename:", filename
print "Here is the first line so you can identify it. Please mention that the error was a ValueError:"
print self.hand[0]
print "Hand logged to hand-errors.txt"
logfile = open('hand-errors.txt', 'a')
for s in self.hand:
logfile.write(str(s) + "\n")
logfile.write("\n")
logfile.close()
if __name__ == "__main__":
print "CLI for fpdb_import is now available as CliFpdb.py"

View File

@ -42,6 +42,8 @@ def mainParser(db, cursor, site, category, hand):
smallBlindLine=0 smallBlindLine=0
for i in range(len(hand)): for i in range(len(hand)):
if hand[i].find("posts small blind")!=-1 or hand[i].find("posts the small blind")!=-1: if hand[i].find("posts small blind")!=-1 or hand[i].find("posts the small blind")!=-1:
if hand[i][-2:] == "$0":
continue
smallBlindLine=i smallBlindLine=i
#print "found small blind line:",smallBlindLine #print "found small blind line:",smallBlindLine
break break

4332
pyfpdb/fpdb_simple.py Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,271 +1,271 @@
Full Tilt Poker Game #6929537410: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:15:44 ET - 2008/06/22 Full Tilt Poker Game #6929537410: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:15:44 ET - 2008/06/22
Seat 1: player16 ($94.90) Seat 1: player16 ($94.90)
Seat 2: player25 ($147) Seat 2: player25 ($147)
Seat 3: player18 ($62.80) Seat 3: player18 ($62.80)
Seat 4: player19 ($136.55) Seat 4: player19 ($136.55)
Seat 5: play-er26 ($56.05) Seat 5: play-er26 ($56.05)
Seat 6: player21 ($252.95) Seat 6: player21 ($252.95)
Seat 7: player22 ($200) Seat 7: player22 ($200)
Seat 8: player23 ($162.50) Seat 8: player23 ($162.50)
Seat 9: player24 ($270.70) Seat 9: player24 ($270.70)
player24 posts the small blind of $0.50 player24 posts the small blind of $0.50
player16 posts the big blind of $1 player16 posts the big blind of $1
player22 posts $1 player22 posts $1
The button is in seat #8 The button is in seat #8
*** HOLE CARDS *** *** HOLE CARDS ***
player25 folds player25 folds
player25 stands up player25 stands up
player18 folds player18 folds
player19 folds player19 folds
play-er26 folds play-er26 folds
player21 folds player21 folds
player22 checks player22 checks
player23 calls $1 player23 calls $1
player17 adds $100 player17 adds $100
player24 calls $0.50 player24 calls $0.50
player16 checks player16 checks
*** FLOP *** [4s Kc 8s] *** FLOP *** [4s Kc 8s]
player24 has 15 seconds left to act player24 has 15 seconds left to act
player24 checks player24 checks
player16 checks player16 checks
player22 checks player22 checks
player23 checks player23 checks
*** TURN *** [4s Kc 8s] [6s] *** TURN *** [4s Kc 8s] [6s]
player24 checks player24 checks
player16 checks player16 checks
player22 checks player22 checks
player23 bets $4 player23 bets $4
player24 calls $4 player24 calls $4
player16 folds player16 folds
player22 folds player22 folds
*** RIVER *** [4s Kc 8s 6s] [Qc] *** RIVER *** [4s Kc 8s 6s] [Qc]
player24 checks player24 checks
player23 checks player23 checks
*** SHOW DOWN *** *** SHOW DOWN ***
player23 shows [Td 5s 3d Js] a flush, Jack high player23 shows [Td 5s 3d Js] a flush, Jack high
player24 mucks player24 mucks
player23 wins the pot ($11.40) with a flush, Jack high player23 wins the pot ($11.40) with a flush, Jack high
*** SUMMARY *** *** SUMMARY ***
Total pot $12 | Rake $0.60 Total pot $12 | Rake $0.60
Board: [4s Kc 8s 6s Qc] Board: [4s Kc 8s 6s Qc]
Seat 1: player16 (big blind) folded on the Turn Seat 1: player16 (big blind) folded on the Turn
Seat 2: player25 didn't bet (folded) Seat 2: player25 didn't bet (folded)
Seat 3: player18 didn't bet (folded) Seat 3: player18 didn't bet (folded)
Seat 4: player19 didn't bet (folded) Seat 4: player19 didn't bet (folded)
Seat 5: play-er26 didn't bet (folded) Seat 5: play-er26 didn't bet (folded)
Seat 6: player21 didn't bet (folded) Seat 6: player21 didn't bet (folded)
Seat 7: player22 folded on the Turn Seat 7: player22 folded on the Turn
Seat 8: player23 (button) collected ($11.40) Seat 8: player23 (button) collected ($11.40)
Seat 9: player24 (small blind) mucked Seat 9: player24 (small blind) mucked
Full Tilt Poker Game #6929553738: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:17:06 ET - 2008/06/22 Full Tilt Poker Game #6929553738: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:17:06 ET - 2008/06/22
Seat 1: player16 ($93.90) Seat 1: player16 ($93.90)
Seat 2: player17 ($100) Seat 2: player17 ($100)
Seat 3: player18 ($62.80) Seat 3: player18 ($62.80)
Seat 4: player19 ($136.55) Seat 4: player19 ($136.55)
Seat 5: play-er26 ($56.05) Seat 5: play-er26 ($56.05)
Seat 6: player21 ($252.95) Seat 6: player21 ($252.95)
Seat 7: player22 ($199) Seat 7: player22 ($199)
Seat 8: player23 ($168.90) Seat 8: player23 ($168.90)
Seat 9: player24 ($265.70) Seat 9: player24 ($265.70)
player16 posts the small blind of $0.50 player16 posts the small blind of $0.50
player17 posts the big blind of $1 player17 posts the big blind of $1
The button is in seat #9 The button is in seat #9
*** HOLE CARDS *** *** HOLE CARDS ***
player18 folds player18 folds
play-er26 stands up play-er26 stands up
player19 raises to $2 player19 raises to $2
play-er26 folds play-er26 folds
player21 calls $2 player21 calls $2
player22 has 15 seconds left to act player22 has 15 seconds left to act
player22 folds player22 folds
player23 folds player23 folds
player24 folds player24 folds
player16 calls $1.50 player16 calls $1.50
player17 calls $1 player17 calls $1
*** FLOP *** [Jc 4c Kc] *** FLOP *** [Jc 4c Kc]
player16 checks player16 checks
player17 checks player17 checks
player19 checks player19 checks
player21 checks player21 checks
*** TURN *** [Jc 4c Kc] [7h] *** TURN *** [Jc 4c Kc] [7h]
player16 checks player16 checks
player17 checks player17 checks
player19 bets $3.50 player19 bets $3.50
player21 folds player21 folds
player16 folds player16 folds
player17 calls $3.50 player17 calls $3.50
*** RIVER *** [Jc 4c Kc 7h] [8s] *** RIVER *** [Jc 4c Kc 7h] [8s]
player17 checks player17 checks
player19 has 15 seconds left to act player19 has 15 seconds left to act
player19 bets $10 player19 bets $10
player17 calls $10 player17 calls $10
*** SHOW DOWN *** *** SHOW DOWN ***
player19 shows [4s Tc As Ac] a flush, Ace high player19 shows [4s Tc As Ac] a flush, Ace high
player17 mucks player17 mucks
player19 wins the pot ($33.25) with a flush, Ace high player19 wins the pot ($33.25) with a flush, Ace high
*** SUMMARY *** *** SUMMARY ***
Total pot $35 | Rake $1.75 Total pot $35 | Rake $1.75
Board: [Jc 4c Kc 7h 8s] Board: [Jc 4c Kc 7h 8s]
Seat 1: player16 (small blind) folded on the Turn Seat 1: player16 (small blind) folded on the Turn
Seat 2: player17 (big blind) mucked Seat 2: player17 (big blind) mucked
Seat 3: player18 didn't bet (folded) Seat 3: player18 didn't bet (folded)
Seat 4: player19 collected ($33.25) Seat 4: player19 collected ($33.25)
Seat 5: play-er26 didn't bet (folded) Seat 5: play-er26 didn't bet (folded)
Seat 6: player21 folded on the Turn Seat 6: player21 folded on the Turn
Seat 7: player22 didn't bet (folded) Seat 7: player22 didn't bet (folded)
Seat 8: player23 didn't bet (folded) Seat 8: player23 didn't bet (folded)
Seat 9: player24 (button) didn't bet (folded) Seat 9: player24 (button) didn't bet (folded)
Full Tilt Poker Game #6929572212: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:18:40 ET - 2008/06/22 Full Tilt Poker Game #6929572212: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:18:40 ET - 2008/06/22
Seat 1: player16 ($91.90) Seat 1: player16 ($91.90)
Seat 2: player17 ($84.50) Seat 2: player17 ($84.50)
Seat 3: player18 ($62.80) Seat 3: player18 ($62.80)
Seat 4: player19 ($154.30) Seat 4: player19 ($154.30)
Seat 6: player21 ($250.95) Seat 6: player21 ($250.95)
Seat 7: player22 ($199) Seat 7: player22 ($199)
Seat 8: player23 ($168.90) Seat 8: player23 ($168.90)
Seat 9: player24 ($265.70) Seat 9: player24 ($265.70)
player17 posts the small blind of $0.50 player17 posts the small blind of $0.50
player18 posts the big blind of $1 player18 posts the big blind of $1
The button is in seat #1 The button is in seat #1
*** HOLE CARDS *** *** HOLE CARDS ***
player19 folds player19 folds
player21 folds player21 folds
player20 adds $50 player20 adds $50
player22 folds player22 folds
player23 folds player23 folds
player24 folds player24 folds
player20 is sitting out player20 is sitting out
player16 raises to $2 player16 raises to $2
player17 folds player17 folds
player18 folds player18 folds
Uncalled bet of $1 returned to player16 Uncalled bet of $1 returned to player16
player16 mucks player16 mucks
player16 wins the pot ($2.50) player16 wins the pot ($2.50)
*** SUMMARY *** *** SUMMARY ***
Total pot $2.50 | Rake $0 Total pot $2.50 | Rake $0
Seat 1: player16 (button) collected ($2.50), mucked Seat 1: player16 (button) collected ($2.50), mucked
Seat 2: player17 (small blind) folded before the Flop Seat 2: player17 (small blind) folded before the Flop
Seat 3: player18 (big blind) folded before the Flop Seat 3: player18 (big blind) folded before the Flop
Seat 4: player19 didn't bet (folded) Seat 4: player19 didn't bet (folded)
Seat 6: player21 didn't bet (folded) Seat 6: player21 didn't bet (folded)
Seat 7: player22 didn't bet (folded) Seat 7: player22 didn't bet (folded)
Seat 8: player23 didn't bet (folded) Seat 8: player23 didn't bet (folded)
Seat 9: player24 didn't bet (folded) Seat 9: player24 didn't bet (folded)
Full Tilt Poker Game #6929576743: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:19:03 ET - 2008/06/22 Full Tilt Poker Game #6929576743: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:19:03 ET - 2008/06/22
Seat 1: player16 ($93.40) Seat 1: player16 ($93.40)
Seat 2: player17 ($84) Seat 2: player17 ($84)
Seat 3: player18 ($61.80) Seat 3: player18 ($61.80)
Seat 4: player19 ($154.30) Seat 4: player19 ($154.30)
Seat 5: player20 ($50), is sitting out Seat 5: player20 ($50), is sitting out
Seat 6: player21 ($250.95) Seat 6: player21 ($250.95)
Seat 7: player22 ($199) Seat 7: player22 ($199)
Seat 8: player23 ($168.90) Seat 8: player23 ($168.90)
Seat 9: player24 ($265.70) Seat 9: player24 ($265.70)
player18 posts the small blind of $0.50 player18 posts the small blind of $0.50
player19 posts the big blind of $1 player19 posts the big blind of $1
The button is in seat #2 The button is in seat #2
*** HOLE CARDS *** *** HOLE CARDS ***
player20 has returned player20 has returned
player21 calls $1 player21 calls $1
player22 folds player22 folds
player23 calls $1 player23 calls $1
player24 calls $1 player24 calls $1
player16 raises to $4 player16 raises to $4
player17 folds player17 folds
player18 folds player18 folds
player19 folds player19 folds
player21 folds player21 folds
player23 folds player23 folds
player17 is sitting out player17 is sitting out
player24 has 15 seconds left to act player24 has 15 seconds left to act
player24 calls $3 player24 calls $3
*** FLOP *** [Tc 9s 7h] *** FLOP *** [Tc 9s 7h]
player24 checks player24 checks
player16 has 15 seconds left to act player16 has 15 seconds left to act
player16 bets $8 player16 bets $8
player24 folds player24 folds
Uncalled bet of $8 returned to player16 Uncalled bet of $8 returned to player16
player16 mucks player16 mucks
player16 wins the pot ($10.95) player16 wins the pot ($10.95)
*** SUMMARY *** *** SUMMARY ***
Total pot $11.50 | Rake $0.55 Total pot $11.50 | Rake $0.55
Board: [Tc 9s 7h] Board: [Tc 9s 7h]
Seat 1: player16 collected ($10.95), mucked Seat 1: player16 collected ($10.95), mucked
Seat 2: player17 (button) didn't bet (folded) Seat 2: player17 (button) didn't bet (folded)
Seat 3: player18 (small blind) folded before the Flop Seat 3: player18 (small blind) folded before the Flop
Seat 4: player19 (big blind) folded before the Flop Seat 4: player19 (big blind) folded before the Flop
Seat 5: player20 is sitting out Seat 5: player20 is sitting out
Seat 6: player21 folded before the Flop Seat 6: player21 folded before the Flop
Seat 7: player22 didn't bet (folded) Seat 7: player22 didn't bet (folded)
Seat 8: player23 folded before the Flop Seat 8: player23 folded before the Flop
Seat 9: player24 folded on the Flop Seat 9: player24 folded on the Flop
Full Tilt Poker Game #6929587483: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:19:57 ET - 2008/06/22 Full Tilt Poker Game #6929587483: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:19:57 ET - 2008/06/22
Seat 1: player16 ($100.35) Seat 1: player16 ($100.35)
Seat 2: player17 ($84), is sitting out Seat 2: player17 ($84), is sitting out
Seat 3: player18 ($61.30) Seat 3: player18 ($61.30)
Seat 4: player19 ($153.30) Seat 4: player19 ($153.30)
Seat 5: player20 ($50) Seat 5: player20 ($50)
Seat 6: player21 ($249.95) Seat 6: player21 ($249.95)
Seat 7: player22 ($199) Seat 7: player22 ($199)
Seat 8: player23 ($167.90) Seat 8: player23 ($167.90)
Seat 9: player24 ($261.70) Seat 9: player24 ($261.70)
player19 posts the small blind of $0.50 player19 posts the small blind of $0.50
player20 posts the big blind of $1 player20 posts the big blind of $1
The button is in seat #3 The button is in seat #3
*** HOLE CARDS *** *** HOLE CARDS ***
player21 folds player21 folds
player22 folds player22 folds
player21 stands up player21 stands up
player23 calls $1 player23 calls $1
player24 calls $1 player24 calls $1
player16 folds player16 folds
player18 folds player18 folds
player19 calls $0.50 player19 calls $0.50
player20 checks player20 checks
*** FLOP *** [Jd Td 2c] *** FLOP *** [Jd Td 2c]
roguern adds $100 roguern adds $100
player19 bets $3 player19 bets $3
player20 folds player20 folds
player23 folds player23 folds
player24 has 15 seconds left to act player24 has 15 seconds left to act
player24 raises to $11 player24 raises to $11
player19 raises to $37 player19 raises to $37
player24 raises to $115 player24 raises to $115
player19 raises to $152.30, and is all in player19 raises to $152.30, and is all in
player24 calls $37.30 player24 calls $37.30
player19 shows [Jc Jh 7s 5h] player19 shows [Jc Jh 7s 5h]
player24 shows [Kh Ad 6h Qd] player24 shows [Kh Ad 6h Qd]
*** TURN *** [Jd Td 2c] [As] *** TURN *** [Jd Td 2c] [As]
*** RIVER *** [Jd Td 2c As] [8s] *** RIVER *** [Jd Td 2c As] [8s]
player19 shows three of a kind, Jacks player19 shows three of a kind, Jacks
player24 shows a straight, Ace high player24 shows a straight, Ace high
player24 wins the pot ($305.60) with a straight, Ace high player24 wins the pot ($305.60) with a straight, Ace high
player19 is sitting out player19 is sitting out
*** SUMMARY *** *** SUMMARY ***
Total pot $308.60 | Rake $3 Total pot $308.60 | Rake $3
Board: [Jd Td 2c As 8s] Board: [Jd Td 2c As 8s]
Seat 1: player16 didn't bet (folded) Seat 1: player16 didn't bet (folded)
Seat 2: player17 is sitting out Seat 2: player17 is sitting out
Seat 3: player18 (button) didn't bet (folded) Seat 3: player18 (button) didn't bet (folded)
Seat 4: player19 (small blind) showed [Jc Jh 7s 5h] and lost with three of a kind, Jacks Seat 4: player19 (small blind) showed [Jc Jh 7s 5h] and lost with three of a kind, Jacks
Seat 5: player20 (big blind) folded on the Flop Seat 5: player20 (big blind) folded on the Flop
Seat 6: player21 didn't bet (folded) Seat 6: player21 didn't bet (folded)
Seat 7: player22 didn't bet (folded) Seat 7: player22 didn't bet (folded)
Seat 8: player23 folded on the Flop Seat 8: player23 folded on the Flop
Seat 9: player24 showed [Kh Ad 6h Qd] and won ($305.60) with a straight, Ace high Seat 9: player24 showed [Kh Ad 6h Qd] and won ($305.60) with a straight, Ace high

View File

@ -1,61 +1,61 @@
Full Tilt Poker Game #6367428246: Table Mountain Mesa - $15/$30 Ante $3 - Limit Stud H/L - 23:47:38 ET - 2008/05/10 Full Tilt Poker Game #6367428246: Table Mountain Mesa - $15/$30 Ante $3 - Limit Stud H/L - 23:47:38 ET - 2008/05/10
Seat 1: Player_8 ($446), is sitting out Seat 1: Player_8 ($446), is sitting out
Seat 2: Play er9 ($303.50) Seat 2: Play er9 ($303.50)
Seat 3: P layer10 ($613), is sitting out Seat 3: P layer10 ($613), is sitting out
Seat 4: Player_11 ($164) Seat 4: Player_11 ($164)
Seat 5: Player1 2 ($543.50), is sitting out Seat 5: Player1 2 ($543.50), is sitting out
Seat 6: Player13 ($912.50) Seat 6: Player13 ($912.50)
Seat 7: Player14 ($430), is sitting out Seat 7: Player14 ($430), is sitting out
Seat 8: Player15 ($531.50) Seat 8: Player15 ($531.50)
Player15 antes $3 Player15 antes $3
Player_11 antes $3 Player_11 antes $3
Player13 antes $3 Player13 antes $3
Play er9 antes $3 Play er9 antes $3
*** 3RD STREET *** *** 3RD STREET ***
Dealt to Play er9 [2s] Dealt to Play er9 [2s]
Dealt to Player_11 [3c] Dealt to Player_11 [3c]
Dealt to Player13 [8c] Dealt to Player13 [8c]
Dealt to Player15 [Jc] Dealt to Player15 [Jc]
Play er9 is low with [2s] Play er9 is low with [2s]
Play er9 brings in for $5 Play er9 brings in for $5
Player_11 folds Player_11 folds
Player13 completes it to $15 Player13 completes it to $15
Player15 folds Player15 folds
Play er9 calls $10 Play er9 calls $10
*** 4TH STREET *** *** 4TH STREET ***
Dealt to Play er9 [2s] [6c] Dealt to Play er9 [2s] [6c]
Dealt to Player13 [8c] [5h] Dealt to Player13 [8c] [5h]
Player13 bets $15 Player13 bets $15
Play er9 calls $15 Play er9 calls $15
*** 5TH STREET *** *** 5TH STREET ***
Dealt to Play er9 [2s 6c] [Ac] Dealt to Play er9 [2s 6c] [Ac]
Dealt to Player13 [8c 5h] [Ah] Dealt to Player13 [8c 5h] [Ah]
Player13 bets $30 Player13 bets $30
Play er9 calls $30 Play er9 calls $30
*** 6TH STREET *** *** 6TH STREET ***
Dealt to Play er9 [2s 6c Ac] [2c] Dealt to Play er9 [2s 6c Ac] [2c]
Dealt to Player13 [8c 5h Ah] [Jd] Dealt to Player13 [8c 5h Ah] [Jd]
Play er9 bets $30 Play er9 bets $30
Player13 calls $30 Player13 calls $30
*** 7TH STREET *** *** 7TH STREET ***
Play er9 bets $30 Play er9 bets $30
Player13 calls $30 Player13 calls $30
*** SHOW DOWN *** *** SHOW DOWN ***
Play er9 shows [5c 4h 2s 6c Ac 2c 2h] three of a kind, Twos, for high and 6,5,4,2,A, for low Play er9 shows [5c 4h 2s 6c Ac 2c 2h] three of a kind, Twos, for high and 6,5,4,2,A, for low
Player13 mucks Player13 mucks
Play er9 wins the high pot ($125) with three of a kind, Twos Play er9 wins the high pot ($125) with three of a kind, Twos
Play er9 wins the low pot ($125) with 6,5,4,2,A Play er9 wins the low pot ($125) with 6,5,4,2,A
*** SUMMARY *** *** SUMMARY ***
Total pot $252 | Rake $2 Total pot $252 | Rake $2
Seat 1: Player_8 is sitting out Seat 1: Player_8 is sitting out
Seat 2: Play er9 collected ($250) Seat 2: Play er9 collected ($250)
Seat 3: P layer10 is sitting out Seat 3: P layer10 is sitting out
Seat 4: Player_11 folded on 3rd St. Seat 4: Player_11 folded on 3rd St.
Seat 5: Player1 2 is sitting out Seat 5: Player1 2 is sitting out
Seat 6: Player13 mucked Seat 6: Player13 mucked
Seat 7: Player14 is sitting out Seat 7: Player14 is sitting out
Seat 8: Player15 folded on 3rd St. Seat 8: Player15 folded on 3rd St.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 48 KiB