diff --git a/create-release.sh b/create-release.sh
index bdc4333a..9f28a7ac 100755
--- a/create-release.sh
+++ b/create-release.sh
@@ -29,8 +29,8 @@ cp -R regression-test fpdb-$1/
cp -R utils fpdb-$1/
cd fpdb-$1
-zip -r ../../fpdb-1.0_$1.zip *
-tar -cf - * | bzip2 >> ../../fpdb-1.0_$1.tar.bz2
+zip -r releases/fpdb-1.0_$1.zip *
+tar -cf - * | bzip2 >> releases/fpdb-1.0_$1.tar.bz2
cd ..
rm -r fpdb-$1
diff --git a/docs/tabledesign.html b/docs/tabledesign.html
index ce39d4bc..e148b9cf 100644
--- a/docs/tabledesign.html
+++ b/docs/tabledesign.html
@@ -312,11 +312,10 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt
Table HandsPlayers
-cardX: can be 1 through 20, one for each card. In holdem only 1-2 of these are used, in omaha 1-4, in stud/razz 1-7, in single draw games 1-10 is used and in badugi 1-16 (4*4) is used.
-For the draw games: the first 5 (badugi: 4) cards are the initial cards, the next 5 (badugi: 4) are after the first draw. If a player keeps some cards then those cards' spaces are filled with "k", short for "kept".
-Example 1: If a player gets 2-6 spades for his first five cards and decides to throw away the 4 and then gets a 7 of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 4, 5, 6, k, k, 7, k, k
-Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and decides to throw away the 2 and the 3 and then gets a Q and K of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 5, 8, J, Q, K, k, k, k
-Note that it will k in the space of which card was there previously, so in example 2 where the player kept the last 3 cards, the last 3 fields of the first draw (ie. card8-10Value) are replaced with k.
+cardX: can be 1 through 20, one for each card. In holdem only 1-2 of these are used, in omaha 1-4, in stud/razz 1-7, in single draw 1-10, in tripple draw all 20 and in badugi 1-16 (4*4).
+For the draw games: the first 5 (badugi: 4) cards are the initial cards, the next 5 (badugi: 4) are after the first draw, etc.
+Example 1: If a player gets 2-6 spades for his first five cards and decides to throw away the 4 and then gets a 7 of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 4, 5, 6, 2, 3, 5, 6, 7
+Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and decides to throw away the 2 and the 3 and then gets a Q and K of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 5, 8, J, 5, 8, J, Q, K.
I did not separate this into an extra table because I felt the lost space is not sufficiently large. Also the benefit for searching is far less relevant.
@@ -370,6 +369,17 @@ Note that it will k in the space of which card was there previously, so in examp
char(1)
h=hearts, s=spades, d=diamonds, c=clubs, unknown/no card=x
+
+ cardXDiscarded
+ boolean
+ Whether the card was discarded (this only applies to draw games, X can be 1 through 15 since the final cards can obviously not be discarded).
+
+
+ DrawnX
+ smallint
+ X can be 1 through 3.
+ This field denotes how many cards the player has drawn on each draw.
+
winnings
int
diff --git a/pyfpdb/CliFpdb.py b/pyfpdb/CliFpdb.py
old mode 100644
new mode 100755
diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py
old mode 100644
new mode 100755
diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py
index 95abdd71..154edec5 100644
--- a/pyfpdb/Database.py
+++ b/pyfpdb/Database.py
@@ -151,7 +151,16 @@ class Database:
act = row[1:]
action[street].append(act)
return action
-
+
+ def get_winners_from_hand(self, hand):
+ """Returns a hash of winners:amount won, given a hand number."""
+ winners = {}
+ c = self.connection.cursor()
+ c.execute(self.sql.query['get_winners_from_hand'], (hand))
+ for row in c.fetchall():
+ winners[row[0]] = row[1]
+ return winners
+
def get_stats_from_hand(self, hand, aggregate = False):
c = self.connection.cursor()
diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py
index 1a1196c5..68e4ce5b 100755
--- a/pyfpdb/EverleafToFpdb.py
+++ b/pyfpdb/EverleafToFpdb.py
@@ -68,17 +68,18 @@ class Everleaf(HandHistoryConverter):
print "Initialising Everleaf converter class"
HandHistoryConverter.__init__(self, config, file, sitename="Everleaf") # Call super class init.
self.sitename = "Everleaf"
- self.setFileType("text")
+ self.setFileType("text", "cp1252")
self.rexx.setGameInfoRegex('.*Blinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+)')
- self.rexx.setSplitHandRegex('\n\n\n\n')
+ self.rexx.setSplitHandRegex('\n\n+')
self.rexx.setHandInfoRegex('.*#(?P[0-9]+)\n.*\nBlinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+) (?P.*) - (?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+) - (?P [0-9]+):(?P[0-9]+):(?P[0-9]+)\nTable (?P[ a-zA-Z]+)\nSeat (?P[0-9]+)')
- self.rexx.setPlayerInfoRegex('Seat (?P[0-9]+): (?P.*) \( \$ (?P[.0-9]+) USD \)')
+ self.rexx.setPlayerInfoRegex('Seat (?P[0-9]+): (?P.*) \(\s+(\$ (?P[.0-9]+) USD|new player|All-in) \)')
self.rexx.setPostSbRegex('.*\n(?P.*): posts small blind \[\$? (?P[.0-9]+)')
self.rexx.setPostBbRegex('.*\n(?P.*): posts big blind \[\$? (?P[.0-9]+)')
# mct : what about posting small & big blinds simultaneously?
self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P.*)\s\[ (?P\S\S), (?P\S\S) \]')
- self.rexx.setActionStepRegex('.*\n(?P.*) (?Pbets|checks|raises|calls|folds)(\s\[\$ (?P[.\d]+) USD\])?')
+ self.rexx.setActionStepRegex('.*\n(?P.*)(?P: bets| checks| raises| calls| folds)(\s\[\$ (?P[.\d]+) USD\])?')
self.rexx.setShowdownActionRegex('.*\n(?P.*) shows \[ (?P.*) \]')
+ self.rexx.setCollectPotRegex('.*\n(?P.*) wins \$ (?P[.\d]+) USD(.*\[ (?P.*) \])?')
self.rexx.compileRegexes()
def readSupportedGames(self):
@@ -117,20 +118,23 @@ class Everleaf(HandHistoryConverter):
def readPlayerStacks(self, hand):
m = self.rexx.player_info_re.finditer(hand.string)
players = []
-
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
- m = re.search('(\*\* Dealing down cards \*\*\n)(?P.*?\n\*\*)?( Dealing Flop \*\* \[ (?P\S\S), (?P\S\S), (?P\S\S) \])?(?P.*?\*\*)?( Dealing Turn \*\* \[ (?P\S\S) \])?(?P.*?\*\*)?( Dealing River \*\* \[ (?P\S\S) \])?(?P.*)', hand.string,re.DOTALL)
-# for street in m.groupdict():
-# print "DEBUG: Street: %s\tspan: %s" %(street, str(m.span(street)))
+ # This re fails if, say, river is missing; then we don't get the ** that starts the river.
+ #m = re.search('(\*\* Dealing down cards \*\*\n)(?P.*?\n\*\*)?( Dealing Flop \*\* \[ (?P\S\S), (?P\S\S), (?P\S\S) \])?(?P.*?\*\*)?( Dealing Turn \*\* \[ (?P\S\S) \])?(?P.*?\*\*)?( Dealing River \*\* \[ (?P\S\S) \])?(?P.*)', hand.string,re.DOTALL)
+
+ m = re.search(r"\*\* Dealing down cards \*\*(?P.+(?=\*\* Dealing Flop \*\*)|.+)"
+ r"(\*\* Dealing Flop \*\* \[ \S\S, \S\S, \S\S \](?P.+(?=\*\* Dealing Turn \*\*)|.+))?"
+ r"(\*\* Dealing Turn \*\* \[ \S\S \](?P.+(?=\*\* Dealing River \*\*)|.+))?"
+ r"(\*\* Dealing River \*\* \[ \S\S \](?P.+))?", hand.string,re.DOTALL)
+
hand.streets = m
def readCommunityCards(self, hand):
# currently regex in wrong place pls fix my brain's fried
- # what a mess!
re_board = re.compile('\*\* Dealing (?P.*) \*\* \[ (?P.*) \]')
m = re_board.finditer(hand.string)
for street in m:
@@ -165,15 +169,19 @@ class Everleaf(HandHistoryConverter):
m = self.rexx.action_re.finditer(hand.streets.group(street))
hand.actions[street] = []
for action in m:
- if action.group('ATYPE') == 'raises':
+ if action.group('ATYPE') == ' raises':
hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') )
- elif action.group('ATYPE') == 'calls':
+ elif action.group('ATYPE') == ' calls':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
- elif action.group('ATYPE') == 'bets':
+ elif action.group('ATYPE') == ': bets':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
+ elif action.group('ATYPE') == ' folds':
+ hand.addFold( street, action.group('PNAME'))
+ elif action.group('ATYPE') == ' checks':
+ hand.addCheck( street, action.group('PNAME'))
else:
- #print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),)
- hand.actions[street] += [[action.group('PNAME'), action.group('ATYPE')]]
+ print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),)
+ #hand.actions[street] += [[action.group('PNAME'), action.group('ATYPE')]]
def readShowdownActions(self, hand):
@@ -182,15 +190,25 @@ class Everleaf(HandHistoryConverter):
re_card = re.compile('(?P[0-9tjqka][schd])') # copied from earlier
cards = [card.group('CARD') for card in re_card.finditer(shows.group('CARDS'))]
print cards
- hand.addHoleCards(cards, shows.group('PNAME'))
-
+ hand.addShownCards(cards, shows.group('PNAME'))
+
+ def readCollectPot(self,hand):
+ m = self.rexx.collect_pot_re.search(hand.string)
+ if m is not None:
+ if m.group('HAND') is not None:
+ re_card = re.compile('(?P[0-9tjqka][schd])') # copied from earlier
+ cards = set([hand.card(card.group('CARD')) for card in re_card.finditer(m.group('HAND'))])
+ hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
+ hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
+ else:
+ print "WARNING: Unusual, no one collected; can happen if it's folded to big blind with a dead small blind."
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 = Everleaf(c, "regression-test-files/everleaf/Speed_Kuala_full.txt")
e.processFile()
print str(e)
diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py
index d50f6b6a..94732295 100644
--- a/pyfpdb/GuiGraphViewer.py
+++ b/pyfpdb/GuiGraphViewer.py
@@ -83,22 +83,24 @@ class GuiGraphViewer (threading.Thread):
self.ax.set_xlabel("Hands", fontsize = 12)
self.ax.set_ylabel("$", fontsize = 12)
self.ax.grid(color='g', linestyle=':', linewidth=0.2)
- #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))
+ if(line == None):
+ #TODO: Do something useful like alert user
+ print "No hands returned by graph query"
+ else:
+ text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
- self.ax.annotate(text,
- xy=(10, -10),
- xycoords='axes points',
- horizontalalignment='left', verticalalignment='top',
- fontsize=10)
+ self.ax.annotate(text,
+ xy=(10, -10),
+ xycoords='axes points',
+ horizontalalignment='left', verticalalignment='top',
+ fontsize=10)
+ #Draw plot
+ self.ax.plot(line,)
- #Draw plot
- self.ax.plot(line,)
-
- self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
- self.graphBox.add(self.canvas)
- self.canvas.show()
+ 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):
@@ -106,6 +108,9 @@ class GuiGraphViewer (threading.Thread):
#returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall()
+ if(winnings == ()):
+ return None
+
y=map(lambda x:float(x[3]), winnings)
line = cumsum(y)
return line/100
diff --git a/pyfpdb/GuiPlayerStats.py b/pyfpdb/GuiPlayerStats.py
index 7e7d52bc..a0b5a4ec 100644
--- a/pyfpdb/GuiPlayerStats.py
+++ b/pyfpdb/GuiPlayerStats.py
@@ -46,46 +46,47 @@ class GuiPlayerStats (threading.Thread):
result = self.cursor.execute(self.sql.query['getPlayerId'], (self.heroes[self.activesite],))
result = self.db.cursor.fetchall()
- pid = result[0][0]
- tmp = tmp.replace("", "(" + str(pid) + ")")
- self.cursor.execute(tmp)
- result = self.db.cursor.fetchall()
- cols = 14
- 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)
+ if not result == ():
+ pid = result[0][0]
+ pid = result[0][0]
+ tmp = tmp.replace("", "(" + str(pid) + ")")
+ self.cursor.execute(tmp)
+ result = self.db.cursor.fetchall()
+ cols = 14
+ 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 = ("Game", "Hands", "VPIP", "PFR", "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq", "PFAFq", "Net($)", "BBl/100")
+ # Create header row
+ titles = ("Game", "Hands", "VPIP", "PFR", "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq", "PFAFq", "Net($)", "BBl/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][col])
- if col == 0:
- l.set_alignment(xalign=0.0, yalign=0.5)
- else:
- l.set_alignment(xalign=1.0, yalign=0.5)
- eb.add(l)
- self.stats_table.attach(eb, col, col+1, row+1, row+2)
- l.show()
- eb.show()
+ 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][col])
+ if col == 0:
+ l.set_alignment(xalign=0.0, yalign=0.5)
+ else:
+ l.set_alignment(xalign=1.0, yalign=0.5)
+ eb.add(l)
+ self.stats_table.attach(eb, col, col+1, row+1, row+2)
+ l.show()
+ eb.show()
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py
old mode 100644
new mode 100755
diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py
new file mode 100644
index 00000000..44914048
--- /dev/null
+++ b/pyfpdb/Hand.py
@@ -0,0 +1,404 @@
+#!/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 .
+#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 Hand
+import re
+import sys
+import traceback
+import os
+import os.path
+import xml.dom.minidom
+import codecs
+from decimal import Decimal
+import operator
+from time import time
+
+class Hand:
+# def __init__(self, sitename, gametype, sb, bb, string):
+
+ UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K'}
+ def __init__(self, sitename, gametype, string):
+ self.sitename = sitename
+ self.gametype = gametype
+ self.string = string
+
+ self.streetList = ['BLINDS','PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order
+
+ self.handid = 0
+ self.sb = gametype[3]
+ self.bb = gametype[4]
+ self.tablename = "Slartibartfast"
+ self.hero = "Hiro"
+ self.maxseats = 10
+ self.counted_seats = 0
+ self.buttonpos = 0
+ self.seating = []
+ self.players = []
+ self.posted = []
+ self.involved = True
+
+
+ #
+ # Collections indexed by street names
+ #
+
+ # A MatchObject using a groupnames to identify streets.
+ # filled by markStreets()
+ self.streets = None
+
+ # dict from street names to lists of tuples, such as
+ # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']]
+ # actually they're clearly lists but they probably should be tuples.
+ self.actions = {}
+
+ # dict from street names to community cards
+ self.board = {}
+
+
+ #
+ # Collections indexed by player names
+ #
+
+ # dict from player names to lists of hole cards
+ self.holecards = {}
+
+ # dict from player names to amounts collected
+ self.collected = {}
+
+ # Sets of players
+ self.shown = set()
+ self.folded = set()
+
+ self.action = []
+ self.totalpot = None
+ self.rake = None
+
+ self.bets = {}
+ self.lastBet = {}
+ for street in self.streetList:
+ self.bets[street] = {}
+ self.lastBet[street] = 0
+
+ def addPlayer(self, seat, name, chips):
+ """\
+Adds a player to the hand, and initialises data structures indexed by player.
+seat (int) indicating the seat
+name (string) player name
+chips (string) the chips the player has at the start of the hand (can be None)
+If a player has None chips he won't be added."""
+ if chips is not None:
+ self.players.append([seat, name, chips])
+ self.holecards[name] = []
+ for street in self.streetList:
+ self.bets[street][name] = []
+
+
+ def addHoleCards(self, cards, player):
+ """\
+Assigns observed holecards to a player.
+cards list of card bigrams e.g. ['2h','jc']
+player (string) name of player
+hand
+Note, will automatically uppercase the rank letter.
+"""
+ try:
+ self.checkPlayerExists(player)
+ self.holecards[player] = set([self.card(c) for c in cards])
+ except FpdbParseError, e:
+ print "Tried to add holecards for unknown player: %s" % (player,)
+
+ def addShownCards(self, cards, player, holeandboard=None):
+ """\
+For when a player shows cards for any reason (for showdown or out of choice).
+"""
+ if cards is not None:
+ self.shown.add(player)
+ self.addHoleCards(cards,player)
+ elif holeandboard is not None:
+ board = set([c for s in self.board.values() for c in s])
+ #print board
+ #print holeandboard
+ #print holeandboard.difference(board)
+ self.addHoleCards(holeandboard.difference(board),player)
+
+
+ def checkPlayerExists(self,player):
+ if player not in [p[1] for p in self.players]:
+ raise FpdbParseError
+
+ def discardHoleCards(self, cards, player):
+ try:
+ self.checkPlayerExists(player)
+ for card in cards:
+ self.holecards[player].remove(card)
+ except FpdbParseError, e:
+ pass
+ except ValueError:
+ print "tried to discard a card %s didn't have" % (player,)
+
+ def setCommunityCards(self, street, cards):
+ self.board[street] = [self.card(c) for c in cards]
+
+ def card(self,c):
+ """upper case the ranks but not suits, 'atjqk' => 'ATJQK'"""
+ 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):
+ """\
+Add a raise on [street] by [player] to [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)
+ self.checkPlayerExists(player)
+ 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, amount):
+ self.checkPlayerExists(player)
+ self.bets[street][player].append(Decimal(amount))
+ self.actions[street] += [[player, 'bets', amount]]
+
+ def addFold(self, street, player):
+ self.checkPlayerExists(player)
+ self.folded.add(player)
+ self.actions[street] += [[player, 'folds']]
+
+ def addCheck(self, street, player):
+ self.checkPlayerExists(player)
+ self.actions[street] += [[player, 'checks']]
+
+ def addCollectPot(self,player, pot):
+ self.checkPlayerExists(player)
+ if player not in self.collected:
+ self.collected[player] = pot
+ else:
+ # possibly lines like "p collected $ from pot" appear during the showdown
+ # but they are usually unique in the summary, so it's best to try to get them from there.
+ print "%s collected pot more than once; avoidable by reading winnings only from summary lines?"
+
+
+ def totalPot(self):
+ """If all bets and blinds have been added, totals up the total pot size
+Known bug: doesn't take into account side pots"""
+ 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.streetList:
+ #print street, self.bets[street][player]
+ self.totalpot += reduce(operator.add, self.bets[street][player], 0)
+
+ def getGameTypeAsString(self):
+ """\
+Map the tuple self.gametype onto the pokerstars string describing it
+"""
+ # currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
+ gs = {"hold" : "Hold'em",
+ "omahahi" : "FIXME",
+ "omahahilo" : "FIXME",
+ "razz" : "Razz",
+ "studhi" : "FIXME",
+ "studhilo" : "FIXME",
+ "fivedraw" : "5 Card Draw",
+ "27_1draw" : "FIXME",
+ "27_3draw" : "Triple Draw 2-7 Lowball",
+ "badugi" : "FIXME"
+ }
+ ls = {"nl" : "No Limit",
+ "pl" : "Pot Limit",
+ "fl" : "Limit",
+ "cn" : "Cap No Limit",
+ "cp" : "Cap Pot Limit"
+ }
+
+ string = "%s %s" %(gs[self.gametype[1]], ls[self.gametype[2]])
+
+ return string
+
+ def printHand(self):
+ # PokerStars format.
+ print "\n### Pseudo stars format ###"
+ print "%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, self.getGameTypeAsString(), 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 ***"
+ if self.involved:
+ print "Dealt to %s [%s]" %(self.hero , " ".join(self.holecards[self.hero]))
+
+ if 'PREFLOP' in self.actions:
+ for act in self.actions['PREFLOP']:
+ self.printActionLine(act)
+
+ if 'FLOP' in self.actions:
+ print "*** FLOP *** [%s]" %( " ".join(self.board['Flop']))
+ for act in self.actions['FLOP']:
+ self.printActionLine(act)
+
+ if 'TURN' in self.actions:
+ print "*** TURN *** [%s] [%s]" %( " ".join(self.board['Flop']), " ".join(self.board['Turn']))
+ for act in self.actions['TURN']:
+ self.printActionLine(act)
+
+ if 'RIVER' in self.actions:
+ print "*** RIVER *** [%s] [%s]" %(" ".join(self.board['Flop']+self.board['Turn']), " ".join(self.board['River']) )
+ 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
+ # we probably don't need a showdown section in pseudo stars format for our filtering purposes
+ if 'SHOWDOWN' in self.actions:
+ print "*** SHOW DOWN ***"
+ print "what do they show"
+
+ print "*** SUMMARY ***"
+ print "Total pot $%s | Rake $%.2f)" % (self.totalpot, self.rake) # TODO side pots
+ board = []
+ for s in self.board.values():
+ board += s
+ if board: # sometimes hand ends preflop without a board
+ print "Board [%s]" % (" ".join(board))
+
+
+ for player in self.players:
+ seatnum = player[0]
+ name = player[1]
+ if name in self.collected and self.holecards[name]:
+ print "Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]), self.collected[name])
+ elif name in self.collected:
+ print "Seat %d: %s collected ($%s)" % (seatnum, name, self.collected[name])
+ elif player[1] in self.shown:
+ print "Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name]))
+ elif player[1] in self.folded:
+ print "Seat %d: %s folded" % (seatnum, name)
+ else:
+ print "Seat %d: %s mucked" % (seatnum, name)
+
+ print
+ # TODO:
+ # logic for side pots
+ # logic for which players get to showdown
+ # I'm just not sure we need to do this so heavily.. and if we do, it's probably better to use pokerlib
+ #if self.holecards[player[1]]: # empty list default is false
+ #hole = self.holecards[player[1]]
+ ##board = []
+ ##for s in self.board.values():
+ ##board += s
+ ##playerhand = self.bestHand('hi', board+hole)
+ ##print "Seat %d: %s showed %s and won/lost with %s" % (player[0], player[1], hole, playerhand)
+ #print "Seat %d: %s showed %s" % (player[0], player[1], hole)
+ #else:
+ #print "Seat %d: %s mucked or folded" % (player[0], player[1])
+
+
+ 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] == 'bets':
+ 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])
+
+ # going to use pokereval to figure out hands at some point.
+ # these functions are copied from pokergame.py
+ def bestHand(self, side, cards):
+ return HandHistoryConverter.eval.best('hi', cards, [])
+
+ # from pokergame.py
+ def bestHandValue(self, side, serial):
+ (value, cards) = self.bestHand(side, serial)
+ return value
+
+ # from pokergame.py
+ # got rid of the _ for internationalisation
+ def readableHandValueLong(self, side, value, cards):
+ if value == "NoPair":
+ if side == "low":
+ if cards[0][0] == '5':
+ return ("The wheel")
+ else:
+ return join(map(lambda card: card[0], cards), ", ")
+ else:
+ return ("High card %(card)s") % { 'card' : (letter2name[cards[0][0]]) }
+ elif value == "OnePair":
+ return ("A pair of %(card)s") % { 'card' : (letter2names[cards[0][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[2][0]]) }
+ elif value == "TwoPair":
+ return ("Two pairs %(card1)s and %(card2)s") % { 'card1' : (letter2names[cards[0][0]]), 'card2' : _(letter2names[cards[2][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[4][0]]) }
+ elif value == "Trips":
+ return ("Three of a kind %(card)s") % { 'card' : (letter2names[cards[0][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[3][0]]) }
+ elif value == "Straight":
+ return ("Straight %(card1)s to %(card2)s") % { 'card1' : (letter2name[cards[0][0]]), 'card2' : (letter2name[cards[4][0]]) }
+ elif value == "Flush":
+ return ("Flush %(card)s high") % { 'card' : (letter2name[cards[0][0]]) }
+ elif value == "FlHouse":
+ return ("%(card1)ss full of %(card2)ss") % { 'card1' : (letter2name[cards[0][0]]), 'card2' : (letter2name[cards[3][0]]) }
+ elif value == "Quads":
+ return _("Four of a kind %(card)s") % { 'card' : (letter2names[cards[0][0]]) } + (", %(card)s kicker") % { 'card' : (letter2name[cards[4][0]]) }
+ elif value == "StFlush":
+ if letter2name[cards[0][0]] == 'Ace':
+ return ("Royal flush")
+ else:
+ return ("Straight flush %(card)s high") % { 'card' : (letter2name[cards[0][0]]) }
+ return value
+
+
+class FpdbParseError(Exception): pass
diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py
index 06a59064..e941af33 100644
--- a/pyfpdb/HandHistoryConverter.py
+++ b/pyfpdb/HandHistoryConverter.py
@@ -17,26 +17,63 @@
import Configuration
import FpdbRegex
+import Hand
import re
import sys
import traceback
import os
import os.path
import xml.dom.minidom
+import codecs
from decimal import Decimal
import operator
from xml.dom.minidom import Node
from pokereval import PokerEval
+from time import time
+#from pokerengine.pokercards import *
+# provides letter2name{}, letter2names{}, visible_card(), not_visible_card(), is_visible(), card_value(), class PokerCards
+# but it's probably not installed so here are the ones we may want:
+letter2name = {
+ 'A': 'Ace',
+ 'K': 'King',
+ 'Q': 'Queen',
+ 'J': 'Jack',
+ 'T': 'Ten',
+ '9': 'Nine',
+ '8': 'Eight',
+ '7': 'Seven',
+ '6': 'Six',
+ '5': 'Five',
+ '4': 'Four',
+ '3': 'Trey',
+ '2': 'Deuce'
+ }
+
+letter2names = {
+ 'A': 'Aces',
+ 'K': 'Kings',
+ 'Q': 'Queens',
+ 'J': 'Jacks',
+ 'T': 'Tens',
+ '9': 'Nines',
+ '8': 'Eights',
+ '7': 'Sevens',
+ '6': 'Sixes',
+ '5': 'Fives',
+ '4': 'Fours',
+ '3': 'Treys',
+ '2': 'Deuces'
+ }
class HandHistoryConverter:
eval = PokerEval()
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.codepage = "utf8"
self.doc = None # For XML based HH files
self.file = file
self.hhbase = self.c.get_import_parameters().get("hhArchiveBase")
@@ -60,6 +97,7 @@ class HandHistoryConverter:
return tmp
def processFile(self):
+ starttime = time()
if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check"
return
@@ -67,6 +105,7 @@ class HandHistoryConverter:
self.gametype = self.determineGameType()
self.hands = self.splitFileIntoHands()
for hand in self.hands:
+ print "\nInput:\n"+hand.string
self.readHandInfo(hand)
self.readPlayerStacks(hand)
self.markStreets(hand)
@@ -76,17 +115,24 @@ class HandHistoryConverter:
self.readShowdownActions(hand)
# Read action (Note: no guarantee this is in hand order.
for street in hand.streets.groupdict():
- self.readAction(hand, street)
+ if hand.streets.group(street) is not None:
+ self.readAction(hand, street)
+
+ self.readCollectPot(hand)
# finalise it (total the pot)
hand.totalPot()
self.getRake(hand)
-
- if(hand.involved == True):
+
+ hand.printHand()
+ #if(hand.involved == True):
#self.writeHand("output file", hand)
- hand.printHand()
- else:
- pass #Don't write out observed hands
+ #hand.printHand()
+ #else:
+ #pass #Don't write out observed hands
+
+ endtime = time()
+ print "Processed %d hands in %d seconds" % (len(self.hands), endtime-starttime)
#####
# These functions are parse actions that may be overridden by the inheriting class
@@ -124,6 +170,7 @@ class HandHistoryConverter:
def readBlinds(self, hand): abstract
def readHeroCards(self, hand): abstract
def readAction(self, hand, street): abstract
+ def readCollectPot(self, hand): 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.
@@ -151,23 +198,25 @@ class HandHistoryConverter:
return sane
# Functions not necessary to implement in sub class
- def setFileType(self, filetype = "text"):
+ def setFileType(self, filetype = "text", codepage='utf8'):
self.filetype = filetype
+ self.codepage = codepage
def splitFileIntoHands(self):
hands = []
+ self.obs.strip()
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)]
+ hands = hands + [Hand.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")
+ infile=codecs.open(filename, "rU", self.codepage)
self.obs = infile.read()
infile.close()
elif(self.filetype == "xml"):
@@ -177,66 +226,6 @@ class HandHistoryConverter:
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])
-
- 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"
-
-
- 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):
@@ -253,262 +242,3 @@ class HandHistoryConverter:
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'}
- 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.streetList = ['BLINDS','PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order
- 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 = {} # dict from player names to lists of hole cards
- self.board = {} # dict from street names to community cards
-
- self.action = []
- self.totalpot = None
- self.rake = None
-
- self.bets = {}
- self.lastBet = {}
- for street in self.streetList:
- 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.holecards[name] = []
- #self.startChips[name] = chips
- #self.endChips[name] = chips
- #self.winners[name] = 0
- for street in self.streetList:
- self.bets[street][name] = []
-
-
- def addHoleCards(self, cards, player=None): # generalise to add hole cards for a specific seat or player
- for c in cards:
- self.holecards[player].append(self.card(c))
-
-
- def discardHoleCards(self, cards, player=None):
- if seat is None:
- #raise something
- pass
- for card in cards:
- try:
- self.holecards[player].remove(card)
- except ValueError:
- print "tried to discard a card player apparently didn't have"
-
- def setCommunityCards(self, street, cards):
- self.board[street] = [self.card(c) for c in cards]
- print self.board[street]
-
- 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 addFold(self, street, player=None):
- ## Called when a player folds.
- #self.bets[street][player].append(None)
-
- #def addCheck(self, street, player=None):
- #self.bets[street][player].append(0)
-
- 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 all bets and blinds have been added, totals up the total pot size
-Known bug: doesn't take into account side pots"""
- 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.streetList:
- #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[self.hero][0], self.holecards[self.hero][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"))
-
- #print self.board
- for player in self.players:
- if self.holecards[player[1]]: # empty list default is false
- hole = self.holecards[player[1]]
- #print self.board.values()
- board = []
- for s in self.board.values():
- board += s
- playerhand = self.bestHand('hi', board+hole)
- print "Seat %d: %s showed %s and won/lost with %s" % (player[0], player[1], hole, playerhand)
- else:
- print "Seat %d: %s mucked or folded" % (player[0], player[1])
-
-
- 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])
-
- # going to use pokereval to figure out hands
- # these functions are copied from pokergame.py
- # im thinking perhaps its best to use all the functionality of pokergame instead
- # of reinventing the wheel
- def bestHand(self, side, cards):
- #if self.variant == "omaha" or self.variant == "omaha8":
- #hand = self.serial2player[serial].hand.tolist(True)
- #board = self.board.tolist(True)
- #else:
- #hand = hand.tolist(True) + board.tolist(True)
- #board = []
- print cards
- return HandHistoryConverter.eval.best('hi', cards, [])
-
- def bestHandValue(self, side, serial):
- (value, cards) = self.bestHand(side, serial)
- return value
-
-
- def readableHandValueLong(self, side, value, cards):
- cards = self.eval.card2string(cards)
- if value == "NoPair":
- if side == "low":
- if cards[0][0] == '5':
- return _("The wheel")
- else:
- return join(map(lambda card: card[0], cards), ", ")
- else:
- return _("High card %(card)s") % { 'card' : _(letter2name[cards[0][0]]) }
- elif value == "OnePair":
- return _("A pair of %(card)s") % { 'card' : _(letter2names[cards[0][0]]) } + _(", %(card)s kicker") % { 'card' : _(letter2name[cards[2][0]]) }
- elif value == "TwoPair":
- return _("Two pairs %(card1)s and %(card2)s") % { 'card1' : _(letter2names[cards[0][0]]), 'card2' : _(letter2names[cards[2][0]]) } + _(", %(card)s kicker") % { 'card' : _(letter2name[cards[4][0]]) }
- elif value == "Trips":
- return _("Three of a kind %(card)s") % { 'card' : _(letter2names[cards[0][0]]) } + _(", %(card)s kicker") % { 'card' : _(letter2name[cards[3][0]]) }
- elif value == "Straight":
- return _("Straight %(card1)s to %(card2)s") % { 'card1' : _(letter2name[cards[0][0]]), 'card2' : _(letter2name[cards[4][0]]) }
- elif value == "Flush":
- return _("Flush %(card)s high") % { 'card' : _(letter2name[cards[0][0]]) }
- elif value == "FlHouse":
- return _("%(card1)ss full of %(card2)ss") % { 'card1' : _(letter2name[cards[0][0]]), 'card2' : _(letter2name[cards[3][0]]) }
- elif value == "Quads":
- return _("Four of a kind %(card)s") % { 'card' : _(letter2names[cards[0][0]]) } + _(", %(card)s kicker") % { 'card' : _(letter2name[cards[4][0]]) }
- elif value == "StFlush":
- if letter2name[cards[0][0]] == 'Ace':
- return _("Royal flush")
- else:
- return _("Straight flush %(card)s high") % { 'card' : _(letter2name[cards[0][0]]) }
- return value
\ No newline at end of file
diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py
index dcc7298c..4cfa9176 100644
--- a/pyfpdb/Hud.py
+++ b/pyfpdb/Hud.py
@@ -55,6 +55,7 @@ class Hud:
self.db_name = db_name
self.deleted = False
self.stacked = True
+ self.site = table.site
self.colors = config.get_default_colors(self.table.site)
self.stat_windows = {}
@@ -133,6 +134,8 @@ class Hud:
(x, y) = loc[adj[i]]
if self.stat_windows.has_key(i):
self.stat_windows[i].relocate(x, y)
+
+ return True
def on_button_press(self, widget, event):
if event.button == 1:
@@ -151,6 +154,7 @@ class Hud:
def reposition_windows(self, *args):
self.update_table_position()
+ return True
def debug_stat_windows(self, *args):
print self.table, "\n", self.main_window.window.get_transient_for()
@@ -225,7 +229,7 @@ class Hud:
aux_params = config.get_aux_parameters(game_params['aux'])
self.aux_windows.append(eval("%s.%s(gtk.Window(), config, 'fpdb')" % (aux_params['module'], aux_params['class'])))
- gobject.timeout_add(0.5, self.update_table_position)
+ gobject.timeout_add(500, self.update_table_position)
def update(self, hand, config, stat_dict):
self.hand = hand # this is the last hand, so it is available later
diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py
index e220bf32..aeb77103 100755
--- a/pyfpdb/Mucked.py
+++ b/pyfpdb/Mucked.py
@@ -22,14 +22,9 @@ Mucked cards display for FreePokerTools HUD.
########################################################################
# to do
-# problem with hand 30586
# Standard Library modules
import sys
-import os
-import string
-import xml.dom.minidom
-from xml.dom.minidom import Node
# pyGTK modules
import pygtk
@@ -39,28 +34,30 @@ import gobject
# FreePokerTools modules
import Configuration
import Database
-import Tables
-import Hud
-import HandHistory
class Aux_Window:
- def __init__(self, parent, config, db_name):
+ def __init__(self, parent, hud, config, db_name):
+ self.config = hud
self.config = config
- self.parent = parent #this is the parent of the mucked cards widget
+ self.parent = parent
self.db_name = db_name
self.vbox = gtk.VBox()
self.parent.add(self.vbox)
- def update(self):
+ def update_data(self):
+ pass
+
+ def update_gui(self):
pass
class Stud_mucked(Aux_Window):
- def __init__(self, parent, config, db_name):
+ def __init__(self, parent, hud, config, db_name):
- self.config = config
- self.parent = parent #this is the parent of the mucked cards widget
- self.db_name = db_name
+ self.hud = hud # hud object that this aux window supports
+ self.config = config # configuration object for this aux window to use
+ self.parent = parent # parent container for this aux window widget
+ self.db_name = db_name # database for this aux window to use
self.vbox = gtk.VBox()
self.parent.add(self.vbox)
@@ -71,9 +68,11 @@ class Stud_mucked(Aux_Window):
self.parent.show_all()
def update_data(self, new_hand_id):
+ self.mucked_cards.update_data(new_hand_id)
self.mucked_list.update_data(new_hand_id)
def update_gui(self, new_hand_id):
+ self.mucked_cards.update_gui(new_hand_id)
self.mucked_list.update_gui(new_hand_id)
class Stud_list:
@@ -89,16 +88,18 @@ class Stud_list:
self.parent.add(self.scrolled_window)
# create a ListStore to use as the model
- self.liststore = gtk.ListStore(str, str, str)
+ self.liststore = gtk.ListStore(str, str, str, str)
self.treeview = gtk.TreeView(self.liststore)
self.tvcolumn0 = gtk.TreeViewColumn('HandID')
self.tvcolumn1 = gtk.TreeViewColumn('Cards')
self.tvcolumn2 = gtk.TreeViewColumn('Net')
+ self.tvcolumn3 = gtk.TreeViewColumn('Winner')
# add tvcolumn to treeview
self.treeview.append_column(self.tvcolumn0)
self.treeview.append_column(self.tvcolumn1)
self.treeview.append_column(self.tvcolumn2)
+ self.treeview.append_column(self.tvcolumn3)
# create a CellRendererText to render the data
self.cell = gtk.CellRendererText()
@@ -107,11 +108,16 @@ class Stud_list:
self.tvcolumn0.pack_start(self.cell, True)
self.tvcolumn1.pack_start(self.cell, True)
self.tvcolumn2.pack_start(self.cell, True)
+ self.tvcolumn3.pack_start(self.cell, True)
self.tvcolumn0.add_attribute(self.cell, 'text', 0)
self.tvcolumn1.add_attribute(self.cell, 'text', 1)
self.tvcolumn2.add_attribute(self.cell, 'text', 2)
+ self.tvcolumn3.add_attribute(self.cell, 'text', 3)
# resize the cols if nec
self.tvcolumn0.set_resizable(True)
+ self.tvcolumn1.set_resizable(True)
+ self.tvcolumn2.set_resizable(True)
+ self.tvcolumn3.set_resizable(True)
self.treeview.connect("row-activated", self.activated_event)
self.scrolled_window.add_with_viewport(self.treeview)
@@ -119,22 +125,25 @@ class Stud_list:
def activated_event(self, path, column, data=None):
sel = self.treeview.get_selection()
(model, iter) = sel.get_selected()
- self.mucked_cards.update(model.get_value(iter, 0))
-
- def update(self, new_hand_id):
-# info_row = self.db_connection.get_hand_info(new_hand_id)
- info_row = ((new_hand_id, "xxxx", 0), )
- iter = self.liststore.append(info_row[0])
- sel = self.treeview.get_selection()
- sel.select_iter(iter)
-
- vadj = self.scrolled_window.get_vadjustment()
- vadj.set_value(vadj.upper)
- self.mucked_cards.update(new_hand_id)
+ self.mucked_cards.update_data(model.get_value(iter, 0))
+ self.mucked_cards.update_gui(model.get_value(iter, 0))
def update_data(self, new_hand_id):
- self.info_row = ((new_hand_id, "xxxx", 0), )
- self.mucked_cards.update_data(new_hand_id)
+ """Updates the data needed for the list box."""
+
+ db_connection = Database.Database(self.config, 'fpdb', '')
+ self.winners = db_connection.get_winners_from_hand(new_hand_id)
+ db_connection.close_connection()
+ pot = 0
+ winners = ''
+ for player in self.winners.keys():
+ pot = pot + int(self.winners[player])
+ if not winners == '':
+ winners = winners + ", "
+ winners = winners + player
+ pot_dec = "%.2f" % (float(pot)/100)
+
+ self.info_row = ((new_hand_id, "xxxx", pot_dec, winners), )
def update_gui(self, new_hand_id):
iter = self.liststore.append(self.info_row[0])
@@ -143,7 +152,6 @@ class Stud_list:
vadj = self.scrolled_window.get_vadjustment()
vadj.set_value(vadj.upper)
- self.mucked_cards.update_gui(new_hand_id)
class Stud_cards:
def __init__(self, parent, config, db_name = 'fpdb'):
@@ -204,46 +212,6 @@ class Stud_cards:
old_cards[c][key] = ranks[old_cards[c][rank]] + old_cards[c][suit]
return old_cards
- def update(self, new_hand_id):
- db_connection = Database.Database(self.config, 'fpdb', '')
- cards = db_connection.get_cards(new_hand_id)
- self.clear()
- cards = self.translate_cards(cards)
- for c in cards.keys():
- 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'),
- (4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')):
- if not cards[c][i[1]] == "xx":
- self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \
- set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])])
-
- 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"
- tips.append(temp)
-
-## 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(tips[0])
-
-# action in tools tips for later streets
- round_to_col = (0, 3, 4, 5, 6)
- for round in range(1, len(tips)):
- for r in range(0, self.rows):
- 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)
@@ -330,7 +298,7 @@ if __name__== "__main__":
main_window.connect("destroy", destroy)
aux_to_call = "Stud_mucked"
- m = eval("%s(main_window, config, 'fpdb')" % aux_to_call)
+ m = eval("%s(main_window, None, config, 'fpdb')" % aux_to_call)
main_window.show_all()
s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand)
diff --git a/pyfpdb/RegressionTest.py b/pyfpdb/RegressionTest.py
index 2b09d248..6acc8a74 100644
--- a/pyfpdb/RegressionTest.py
+++ b/pyfpdb/RegressionTest.py
@@ -30,6 +30,7 @@ import fpdb_db
import fpdb_import
import fpdb_simple
import FpdbSQLQueries
+import Tables
import unittest
@@ -89,6 +90,11 @@ class TestSequenceFunctions(unittest.TestCase):
self.failUnless(result==datetime.datetime(2008,8,17,6,14,43),
"Date incorrect, expected: 2008-08-17 01:14:43 got: " + str(result))
+ def testTableDetection(self):
+ result = Tables.clean_title("French (deep)")
+ self.failUnless(result == "French", "French (deep) parsed incorrectly. Expected 'French' got: " + str(result))
+# result = ("French (deep) - $0.25/$0.50 - No Limit Hold'em - Logged In As xxxx")
+
def testImportHandHistoryFiles(self):
"""Test import of single HH file"""
self.mysqlimporter.addImportFile("regression-test-files/hand-histories/ps-lhe-ring-3hands.txt")
diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py
index 75b6437c..8a58cc58 100644
--- a/pyfpdb/SQL.py
+++ b/pyfpdb/SQL.py
@@ -319,6 +319,14 @@ class Sql:
"""
# WHERE handId = %s AND Players.id LIKE %s
+ self.query['get_winners_from_hand'] = """
+ SELECT name, winnings
+ FROM HandsPlayers, Players
+ WHERE winnings > 0
+ AND Players.id = HandsPlayers.playerId
+ AND handId = %s;
+ """
+
self.query['get_table_name'] = """
select tableName, maxSeats, category
from Hands,Gametypes
diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py
index d2cb2a24..a626b043 100755
--- a/pyfpdb/Tables.py
+++ b/pyfpdb/Tables.py
@@ -291,17 +291,6 @@ def get_site_from_exe(c, exe):
return params['site_name']
return None
-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 = name.rstrip()
- return name
-
def pokerstars_decode_table(tw):
# Extract poker information from the window title. This is not needed for
# fpdb, since all that information is available in the db via new_hand_number.
@@ -431,8 +420,8 @@ def discover_mac_by_name(c, tablename):
if __name__=="__main__":
c = Configuration.Config()
- print discover_table_by_name(c, "Howard Lederer")
- print discover_tournament_table(c, "118942908", "3")
+ print discover_table_by_name(c, "Daddy Nasty")
+# print discover_tournament_table(c, "118942908", "3")
tables = discover(c)
for t in tables.keys():
diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py
old mode 100644
new mode 100755
diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py
index 910bad40..306c0880 100644
--- a/pyfpdb/fpdb_import.py
+++ b/pyfpdb/fpdb_import.py
@@ -60,7 +60,11 @@ class Importer:
#Set defaults
self.callHud = self.config.get_import_parameters().get("callFpdbHud")
if not self.settings.has_key('minPrint'):
+ #TODO: Is this value in the xml file?
self.settings['minPrint'] = 30
+ if not self.settings.has_key('handCount'):
+ #TODO: Is this value in the xml file?
+ self.settings['handCount'] = 0
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
self.fdb.connect(self.settings['db-backend'],
self.settings['db-host'],
diff --git a/pyfpdb/psnlheparser-mct.tgz b/pyfpdb/psnlheparser-mct.tgz
new file mode 100644
index 00000000..b04c8018
Binary files /dev/null and b/pyfpdb/psnlheparser-mct.tgz differ