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

Conflicts:

	pyfpdb/HUD_config.xml.example
This commit is contained in:
Ray 2009-01-05 10:53:10 -05:00
commit 644201e669
16 changed files with 3729 additions and 2608 deletions

View File

@ -71,15 +71,17 @@ class Everleaf(HandHistoryConverter):
self.setFileType("text", "cp1252")
self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\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>.*) \(\s+(\$ (?P<CASH>[.0-9]+) USD|new player|All-in) \)')
self.rexx.setHandInfoRegex('.*#(?P<HID>[0-9]+)\n.*\nBlinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<DATETIME>\d\d\d\d/\d\d/\d\d - \d\d:\d\d:\d\d)\nTable (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
self.rexx.setPlayerInfoRegex('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\s+(\$ (?P<CASH>[.0-9]+) USD|€ (?P<CASH>[.0-9]+) EUR|new player|All-in) \)')
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.setPostBothRegex('.*\n(?P<PNAME>.*): posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)')
self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P<PNAME>.*)\s\[ (?P<CARDS>.*) \]')
self.rexx.setActionStepRegex('.*\n(?P<PNAME>.*)(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[\$ (?P<BET>[.\d]+) (USD|EUR)\])?')
self.rexx.setShowdownActionRegex('.*\n(?P<PNAME>.*) shows \[ (?P<CARDS>.*) \]')
self.rexx.setCollectPotRegex('.*\n(?P<PNAME>.*) wins \$ (?P<POT>[.\d]+) USD(.*\[ (?P<HAND>.*) \])?')
self.rexx.setCollectPotRegex('.*\n(?P<PNAME>.*) wins \$ (?P<POT>[.\d]+) (USD|EUR)(.*?\[ (?P<CARDS>.*?) \])?')
#self.rexx.setCollectPotRegex('.*\n(?P<PNAME>.*) wins \$ (?P<POT>[.\d]+) USD(.*\[ (?P<CARDS>) \S\S, \S\S, \S\S, \S\S, \S\S \])?')
self.rexx.sits_out_re = re.compile('(?P<PNAME>.*) sits out')
self.rexx.compileRegexes()
def readSupportedGames(self):
@ -111,8 +113,7 @@ class Everleaf(HandHistoryConverter):
# 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.starttime = time.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S")
hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand):
@ -127,34 +128,30 @@ class Everleaf(HandHistoryConverter):
#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)
m = re.search(r"\*\* Dealing down cards \*\*(?P<PREFLOP>.+(?=\*\* Dealing Flop \*\*)|.+)"
r"(\*\* Dealing Flop \*\* \[ \S\S, \S\S, \S\S \](?P<FLOP>.+(?=\*\* Dealing Turn \*\*)|.+))?"
r"(\*\* Dealing Turn \*\* \[ \S\S \](?P<TURN>.+(?=\*\* Dealing River \*\*)|.+))?"
r"(\*\* Dealing River \*\* \[ \S\S \](?P<RIVER>.+))?", hand.string,re.DOTALL)
r"(\*\* Dealing Flop \*\*(?P<FLOP> \[ \S\S, \S\S, \S\S \].+(?=\*\* Dealing Turn \*\*)|.+))?"
r"(\*\* Dealing Turn \*\*(?P<TURN> \[ \S\S \].+(?=\*\* Dealing River \*\*)|.+))?"
r"(\*\* Dealing River \*\*(?P<RIVER> \[ \S\S \].+))?", hand.string,re.DOTALL)
hand.streets = m
hand.addStreets(m)
def readCommunityCards(self, hand):
# currently regex in wrong place pls fix my brain's fried
re_board = re.compile('\*\* Dealing (?P<STREET>.*) \*\* \[ (?P<CARDS>.*) \]')
m = re_board.finditer(hand.string)
for street in m:
#print street.groups()
re_card = re.compile('(?P<CARD>[0-9tjqka][schd])') # look that's weird, hole cards have a capital rank but board cards are lower case?
cardsmatch = re_card.finditer(street.group('CARDS'))
hand.setCommunityCards(street.group('STREET'), [card.group('CARD') for card in cardsmatch])
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
self.rexx.board_re = re.compile(r"\[ (?P<CARDS>.+) \]")
print hand.streets.group(street)
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
m = self.rexx.board_re.search(hand.streets.group(street))
hand.setCommunityCards(street, m.group('CARDS').split(', '))
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')]
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.rexx.big_blind_re.finditer(hand.string):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.rexx.both_blinds_re.finditer(hand.string):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
def readHeroCards(self, hand):
m = self.rexx.hero_cards_re.search(hand.string)
@ -163,14 +160,17 @@ class Everleaf(HandHistoryConverter):
hand.involved = False
else:
hand.hero = m.group('PNAME')
hand.addHoleCards([m.group('HOLE1'), m.group('HOLE2')], m.group('PNAME'))
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('CARDS')
cards = set(cards.split(', '))
hand.addHoleCards(cards, m.group('PNAME'))
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') )
hand.addCallandRaise( 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':
@ -181,34 +181,33 @@ class Everleaf(HandHistoryConverter):
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')]]
def readShowdownActions(self, hand):
for shows in self.rexx.showdown_action_re.finditer(hand.string):
print shows.groups()
re_card = re.compile('(?P<CARD>[0-9tjqka][schd])') # copied from earlier
cards = [card.group('CARD') for card in re_card.finditer(shows.group('CARDS'))]
print cards
cards = shows.group('CARDS')
cards = set(cards.split(', '))
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<CARD>[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)
for m in self.rexx.collect_pot_re.finditer(hand.string):
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
def readShownCards(self,hand):
for m in self.rexx.collect_pot_re.finditer(hand.string):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = set(cards.split(', '))
hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
if __name__ == "__main__":
c = Configuration.Config()
e = Everleaf(c, "regression-test-files/everleaf/Speed_Kuala_full.txt")
if len(sys.argv) == 1:
testfile = "regression-test-files/everleaf/Speed_Kuala_full.txt"
else:
testfile = sys.argv[1]
e = Everleaf(c, testfile)
e.processFile()
print str(e)

View File

@ -635,6 +635,11 @@ class FpdbSQLQueries:
elif(self.dbname == 'SQLite'):
self.query['getPlayerId'] = """SELECT id from Players where name = %s"""
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'):
self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
elif(self.dbname == 'SQLite'):
self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'):
self.query['getRingProfitAllHandsPlayerIdSite'] = """
SELECT hp.handId, hp.winnings, coalesce(hp.ante,0) + SUM(ha.amount)
@ -643,8 +648,10 @@ class FpdbSQLQueries:
INNER JOIN Players pl ON hp.playerId = pl.id
INNER JOIN Hands h ON h.id = hp.handId
INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id
WHERE pl.name = %s
AND pl.siteId = %s
where pl.id in <player_test>
AND pl.siteId in <site_test>
AND h.handStart > '<startdate_test>'
AND h.handStart < '<enddate_test>'
AND hp.tourneysPlayersId IS NULL
GROUP BY hp.handId, hp.winnings, h.handStart, hp.ante
ORDER BY h.handStart"""
@ -656,8 +663,10 @@ class FpdbSQLQueries:
INNER JOIN Players pl ON hp.playerId = pl.id
INNER JOIN Hands h ON h.id = hp.handId
INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id
WHERE pl.name = %s
AND pl.siteId = %s
where pl.id in <player_test>
AND pl.siteId in <site_test>
AND h.handStart > '<startdate_test>'
AND h.handStart < '<enddate_test>'
AND hp.tourneysPlayersId IS NULL
GROUP BY hp.handId, hp.winnings, h.handStart
ORDER BY h.handStart"""
@ -694,7 +703,7 @@ class FpdbSQLQueries:
AS BBlPer100
,hprof2.profitperhand AS Profitperhand
*/
,hprof2.variance as Variance
,format(hprof2.variance,2) AS Variance
FROM
(select /* stats from hudcache */
gt.base
@ -793,7 +802,7 @@ class FpdbSQLQueries:
AS BBper100
,hprof2.profitperhand AS Profitperhand
*/
,hprof2.variance as Variance
,round(hprof2.variance,2) AS Variance
FROM
(select gt.base
,gt.category
@ -825,10 +834,10 @@ class FpdbSQLQueries:
else to_char(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr))
/(sum(street1Seen)+sum(street2Seen)+sum(street3Seen)),'90D0')
end AS PoFAFq
,to_char(sum(totalProfit)/100.0,'9G999G990D00') AS Net
,round(sum(totalProfit)/100.0,2) AS Net
,to_char((sum(totalProfit)/(gt.bigBlind+0.0)) / (sum(HDs)/100.0), '990D00')
AS BBper100
,to_char(sum(totalProfit) / (sum(HDs)+0.0), '990D0000') AS Profitperhand
,to_char(sum(totalProfit/100.0) / (sum(HDs)+0.0), '990D0000') AS Profitperhand
from Gametypes gt
inner join Sites s on s.Id = gt.siteId
inner join HudCache hc on hc.gameTypeId = gt.Id
@ -867,53 +876,130 @@ class FpdbSQLQueries:
if(self.dbname == 'MySQL InnoDB'):
self.query['playerStatsByPosition'] = """
select /* stats from hudcache */
hc.position
,sum(HDs) as n
,format(round(100.0*sum(street0VPI)/sum(HDs)),1) AS vpip
,format(round(100.0*sum(street0Aggr)/sum(HDs)),1) AS pfr
,format(round(100.0*sum(street1Seen)/sum(HDs)),1) AS saw_f
,format(round(100.0*sum(sawShowdown)/sum(HDs)),1) AS sawsd
,case when sum(street1Seen) = 0 then 'oo'
else format(round(100.0*sum(sawShowdown)/sum(street1Seen)),1)
end AS wtsdwsf
,case when sum(sawShowdown) = 0 then 'oo'
else format(round(100.0*sum(wonAtSD)/sum(sawShowdown)),1)
end AS wmsd
,case when sum(street1Seen) = 0 then 'oo'
else format(round(100.0*sum(street1Aggr)/sum(street1Seen)),1)
end AS FlAFq
,case when sum(street2Seen) = 0 then 'oo'
else format(round(100.0*sum(street2Aggr)/sum(street2Seen)),1)
end AS TuAFq
,case when sum(street3Seen) = 0 then 'oo'
else format(round(100.0*sum(street3Aggr)/sum(street3Seen)),1)
end AS RvAFq
,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then 'oo'
else format(round(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr))
/(sum(street1Seen)+sum(street2Seen)+sum(street3Seen))),1)
end AS PoFAFq
,format(sum(totalProfit)/100.0,2) AS Net
,case when sum(HDs) = 0 then 'oo'
else format((sum(totalProfit)/(gt.bigBlind+0.0)) / (sum(HDs)/100.0),2)
end AS BBper100
from Gametypes gt
inner join Sites s on (s.Id = gt.siteId)
inner join HudCache hc on (hc.gameTypeId = gt.Id)
inner join Players p on (p.id = hc.playerId)
where hc.playerId in <player_test>
and gt.type = 'ring'
and gt.id = <gametype_test> /* must specify gametypeid */
/* and stats.n > 100 optional stat-based queries */
group by hc.position, gt.bigBlind
order by case when hc.position = 'B' then -2
SELECT
concat(upper(stats.limitType), ' '
,concat(upper(substring(stats.category,1,1)),substring(stats.category,2) ), ' '
,stats.name, ' $'
,cast(trim(leading ' ' from
case when stats.bigBlind < 100 then format(stats.bigBlind/100.0,2)
else format(stats.bigBlind/100.0,0)
end ) as char)
) AS Game
,case when stats.PlPosition = -2 then 'BB'
when stats.PlPosition = -1 then 'SB'
when stats.PlPosition = 0 then 'Btn'
when stats.PlPosition = 1 then 'CO'
when stats.PlPosition = 2 then 'MP'
when stats.PlPosition = 5 then 'EP'
else '??'
end AS PlPosition
,stats.n
,stats.vpip
,stats.pfr
,stats.saw_f
,stats.sawsd
,stats.wtsdwsf
,stats.wmsd
,stats.FlAFq
,stats.TuAFq
,stats.RvAFq
,stats.PoFAFq
/* if you have handsactions data the next 3 fields should give same answer as
following 3 commented out fields */
,stats.Net
,stats.BBper100
,stats.Profitperhand
/*,format(hprof2.sum_profit/100.0,2) AS Net
,format((hprof2.sum_profit/(stats.bigBlind+0.0)) / (stats.n/100.0),2)
AS BBlPer100
,hprof2.profitperhand AS Profitperhand
*/
,format(hprof2.variance,2) AS Variance
FROM
(select /* stats from hudcache */
gt.base
,gt.category
,upper(gt.limitType) as limitType
,s.name
,gt.bigBlind
,hc.gametypeId
,case when hc.position = 'B' then -2
when hc.position = 'S' then -1
when hc.position = 'D' then 0
when hc.position = 'C' then 1
when hc.position = 'M' then 2
when hc.position = 'E' then 5
else 9
end
end as PlPosition
,sum(HDs) AS n
,format(100.0*sum(street0VPI)/sum(HDs),1) AS vpip
,format(100.0*sum(street0Aggr)/sum(HDs),1) AS pfr
,format(100.0*sum(street1Seen)/sum(HDs),1) AS saw_f
,format(100.0*sum(sawShowdown)/sum(HDs),1) AS sawsd
,case when sum(street1Seen) = 0 then 'oo'
else format(100.0*sum(sawShowdown)/sum(street1Seen),1)
end AS wtsdwsf
,case when sum(sawShowdown) = 0 then 'oo'
else format(100.0*sum(wonAtSD)/sum(sawShowdown),1)
end AS wmsd
,case when sum(street1Seen) = 0 then 'oo'
else format(100.0*sum(street1Aggr)/sum(street1Seen),1)
end AS FlAFq
,case when sum(street2Seen) = 0 then 'oo'
else format(100.0*sum(street2Aggr)/sum(street2Seen),1)
end AS TuAFq
,case when sum(street3Seen) = 0 then 'oo'
else format(100.0*sum(street3Aggr)/sum(street3Seen),1)
end AS RvAFq
,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then 'oo'
else format(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr))
/(sum(street1Seen)+sum(street2Seen)+sum(street3Seen)),1)
end AS PoFAFq
,format(sum(totalProfit)/100.0,2) AS Net
,format((sum(totalProfit)/(gt.bigBlind+0.0)) / (sum(HDs)/100.0),2)
AS BBper100
,format( (sum(totalProfit)/100.0) / sum(HDs), 4) AS Profitperhand
from Gametypes gt
inner join Sites s on s.Id = gt.siteId
inner join HudCache hc on hc.gameTypeId = gt.Id
where hc.playerId in <player_test>
# use <gametype_test> here ?
group by gt.base
,gt.category
,upper(gt.limitType)
,s.name
,gt.bigBlind
,hc.gametypeId
,PlPosition
) stats
inner join
( select # profit from handsplayers/handsactions
hprof.gameTypeId,
case when hprof.position = 'B' then -2
when hprof.position = 'S' then -1
when hprof.position in ('3','4') then 2
when hprof.position in ('6','7') then 5
else hprof.position
end as PlPosition,
sum(hprof.profit) as sum_profit,
avg(hprof.profit/100.0) as profitperhand,
variance(hprof.profit/100.0) as variance
from
(select hp.handId, h.gameTypeId, hp.position, hp.winnings, SUM(ha.amount)
costs, hp.winnings - SUM(ha.amount) profit
from HandsPlayers hp
inner join Hands h ON h.id = hp.handId
left join HandsActions ha ON ha.handPlayerId = hp.id
where hp.playerId in <player_test>
# use <gametype_test> here ?
and hp.tourneysPlayersId IS NULL
group by hp.handId, h.gameTypeId, hp.position, hp.winnings
) hprof
group by hprof.gameTypeId, PlPosition
) hprof2
on ( hprof2.gameTypeId = stats.gameTypeId
and hprof2.PlPosition = stats.PlPosition)
order by stats.category, stats.limittype, stats.bigBlind, cast(stats.PlPosition as signed)
"""
elif(self.dbname == 'PostgreSQL'):
self.query['playerStatsByPosition'] = """

220
pyfpdb/FulltiltToFpdb.py Executable file
View File

@ -0,0 +1,220 @@
#!/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 *
# FullTilt HH Format
#Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09
#Seat 1: rigoise ($15.95)
#Seat 2: K2dream ($6.70)
#Seat 4: ravens2216 ($10)
#Seat 5: rizkouner ($4)
#Seat 6: Sorrowful ($8.35)
#rigoise posts the small blind of $0.05
#K2dream posts the big blind of $0.10
#5 seconds left to act
#rizkouner posts $0.10
#The button is in seat #6
#*** HOLE CARDS ***
#Dealt to Sorrowful [8h Qc]
#ravens2216 folds
#rizkouner checks
#Sorrowful has 15 seconds left to act
#Sorrowful folds
#rigoise folds
#K2dream checks
#*** FLOP *** [9d Kc 5c]
#K2dream checks
#rizkouner checks
#*** TURN *** [9d Kc 5c] [5h]
#K2dream has 15 seconds left to act
#K2dream bets $0.20
#rizkouner calls $0.20
#*** RIVER *** [9d Kc 5c 5h] [6h]
#K2dream checks
#rizkouner has 15 seconds left to act
#rizkouner bets $0.20
#K2dream folds
#Uncalled bet of $0.20 returned to rizkouner
#rizkouner mucks
#rizkouner wins the pot ($0.60)
#*** SUMMARY ***
#Total pot $0.65 | Rake $0.05
#Board: [9d Kc 5c 5h 6h]
#Seat 1: rigoise (small blind) folded before the Flop
#Seat 2: K2dream (big blind) folded on the River
#Seat 4: ravens2216 didn't bet (folded)
#Seat 5: rizkouner collected ($0.60), mucked
#Seat 6: Sorrowful (button) didn't bet (folded)
#Seat N: rizkouner (button) showed [Jh Ah] and won ($0.70) with a pair of Threes
class FullTilt(HandHistoryConverter):
def __init__(self, config, file):
print "Initialising FullTilt converter class"
HandHistoryConverter.__init__(self, config, file, sitename="FullTilt") # Call super class init.
self.sitename = "FullTilt"
self.setFileType("text", "cp1252")
self.rexx.setGameInfoRegex('- \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) -')
self.rexx.setSplitHandRegex('\n\n+')
self.rexx.setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[- a-zA-Z]+) (\((?P<TABLEATTRIBUTES>.+)\) )?- \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>[a-zA-Z\' ]+) - (?P<DATETIME>.*)')
# self.rexx.setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
self.rexx.button_re = re.compile('The button is in seat #(?P<BUTTON>\d+)')
self.rexx.setPlayerInfoRegex('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[.0-9]+)\)\n')
self.rexx.setPostSbRegex('.*\n(?P<PNAME>.*) posts the small blind of \$?(?P<SB>[.0-9]+)')
self.rexx.setPostBbRegex('.*\n(?P<PNAME>.*) posts (the big blind of )?\$?(?P<BB>[.0-9]+)')
self.rexx.setPostBothRegex('.*\n(?P<PNAME>.*) posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)')
self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P<PNAME>.*)\s\[(?P<CARDS>.*)\]')
self.rexx.setActionStepRegex('.*\n(?P<PNAME>.*)(?P<ATYPE> bets| checks| raises to| calls| folds)(\s\$(?P<BET>[.\d]+))?')
self.rexx.setShowdownActionRegex('.*\n(?P<PNAME>.*) shows \[(?P<CARDS>.*)\]')
self.rexx.setCollectPotRegex(r"Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*?) (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*)")
self.rexx.shown_cards_re = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(.*\) showed \[(?P<CARDS>.*)\].*')
self.rexx.sits_out_re = re.compile('(?P<PNAME>.*) sits out')
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,re.DOTALL)
#print m.groups()
hand.handid = m.group('HID')
hand.tablename = m.group('TABLE')
hand.buttonpos = int(self.rexx.button_re.search(hand.string).group('BUTTON'))
hand.starttime = time.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
# 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')
# 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')))
#FIXME: 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(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.string,re.DOTALL)
hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
self.rexx.board_re = re.compile(r"\[(?P<CARDS>.+)\]")
#print "DEBUG readCommunityCards:", street, hand.streets.group(street)
m = self.rexx.board_re.search(hand.streets.group(street))
hand.setCommunityCards(street, m.group('CARDS').split(' '))
def readBlinds(self, hand):
try:
m = self.rexx.small_blind_re.search(hand.string)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.rexx.big_blind_re.finditer(hand.string):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.rexx.both_blinds_re.finditer(hand.string):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
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')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('CARDS')
cards = set(cards.split(' '))
hand.addHoleCards(cards, m.group('PNAME'))
def readAction(self, hand, street):
m = self.rexx.action_re.finditer(hand.streets.group(street))
for action in m:
if action.group('ATYPE') == ' raises to':
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') )
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'),)
def readShowdownActions(self, hand):
for shows in self.rexx.showdown_action_re.finditer(hand.string):
cards = shows.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand):
for m in self.rexx.collect_pot_re.finditer(hand.string):
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
def readShownCards(self,hand):
for m in self.rexx.shown_cards_re.finditer(hand.string):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards=cards, player=m.group('PNAME'))
if __name__ == "__main__":
c = Configuration.Config()
if len(sys.argv) == 1:
testfile = "regression-test-files/FT20081209 CR - tay - $0.05-$0.10 - No Limit Hold'em.txt"
else:
testfile = sys.argv[1]
print "Converting: ", testfile
e = FullTilt(c, testfile)
e.processFile()
print str(e)

View File

@ -40,6 +40,7 @@ class GuiAutoImport (threading.Thread):
print imp
self.input_settings = {}
self.pipe_to_hud = None
self.importer = fpdb_import.Importer(self,self.settings, self.config)
self.importer.setCallHud(True)
@ -72,7 +73,8 @@ class GuiAutoImport (threading.Thread):
self.addSites(self.mainVBox)
self.startButton=gtk.Button("Start Autoimport")
self.doAutoImportBool = False
self.startButton=gtk.ToggleButton("Start Autoimport")
self.startButton.connect("clicked", self.startClicked, "start clicked")
self.mainVBox.add(self.startButton)
self.startButton.show()
@ -102,9 +104,12 @@ class GuiAutoImport (threading.Thread):
def do_import(self):
"""Callback for timer to do an import iteration."""
if self.doAutoImportBool:
self.importer.runUpdated()
print "GuiAutoImport.import_dir done"
return True
else:
return False
def startClicked(self, widget, data):
"""runs when user clicks start on auto import tab"""
@ -118,9 +123,10 @@ class GuiAutoImport (threading.Thread):
# 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
# to watch.
try: #uhhh, I don't this this is the best way to check for the existence of an attr
getattr(self, "pipe_to_hud")
except AttributeError:
if widget.get_active(): # toggled on
self.doAutoImportBool = True
widget.set_label(u'Stop Autoimport')
if self.pipe_to_hud is None:
if os.name == 'nt':
command = "python HUD_main.py" + " %s" % (self.database)
bs = 0 # windows is not happy with line buffing here
@ -145,6 +151,16 @@ class GuiAutoImport (threading.Thread):
interval=int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import)
else: # toggled off
self.doAutoImportBool = False # do_import will return this and stop the gobject callback timer
print "Stopping autoimport"
print >>self.pipe_to_hud.stdin, "\n"
#self.pipe_to_hud.communicate('\n') # waits for process to terminate
self.pipe_to_hud = None
self.startButton.set_label(u'Start Autoimport')
#end def GuiAutoImport.startClicked
def get_vbox(self):

View File

@ -50,22 +50,27 @@ class GuiGraphViewer (threading.Thread):
try: self.canvas.destroy()
except AttributeError: pass
# Whaich sites are selected?
# TODO:
# What hero names for the selected site?
# TODO:
sitenos = []
playerids = []
name = self.heroes[self.sites]
# Which sites are selected?
for site in self.sites:
if self.sites[site] == True:
sitenos.append(self.siteid[site])
self.cursor.execute(self.sql.query['getPlayerId'], (self.heroes[site],))
result = self.db.cursor.fetchall()
if len(result) == 1:
playerids.append(result[0][0])
if self.sites == "PokerStars":
site=2
sitename="PokerStars: "
elif self.sites=="Full Tilt":
site=1
sitename="Full Tilt: "
else:
print "invalid text in site selection in graph, defaulting to PS"
site=2
if sitenos == []:
#Should probably pop up here.
print "No sites selected - defaulting to PokerStars"
sitenos = [2]
if playerids == []:
print "No player ids found"
return
self.fig = Figure(figsize=(5,4), dpi=100)
@ -74,7 +79,7 @@ class GuiGraphViewer (threading.Thread):
#Get graph data from DB
starttime = time()
line = self.getRingProfitGraph(name, site)
line = self.getRingProfitGraph(playerids, sitenos)
print "Graph generated in: %s" %(time() - starttime)
self.ax.set_title("Profit graph for ring games")
@ -87,7 +92,8 @@ class GuiGraphViewer (threading.Thread):
#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))
# text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
text = "All Hands, " + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
self.ax.annotate(text,
xy=(10, -10),
@ -103,8 +109,34 @@ class GuiGraphViewer (threading.Thread):
self.canvas.show()
#end of def showClicked
def getRingProfitGraph(self, name, site):
self.cursor.execute(self.sql.query['getRingProfitAllHandsPlayerIdSite'], (name, site))
def getRingProfitGraph(self, names, sites):
tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite']
# print "DEBUG: getRingProfitGraph"
start_date, end_date = self.__get_dates()
if start_date == '':
start_date = '1970-01-01'
if end_date == '':
end_date = '2020-12-12'
#Buggered if I can find a way to do this 'nicely' take a list of intergers and longs
# and turn it into a tuple readale by sql.
# [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
nametest = str(tuple(names))
sitetest = str(tuple(sites))
nametest = nametest.replace("L", "")
nametest = nametest.replace(",)",")")
sitetest = sitetest.replace(",)",")")
#Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
tmp = tmp.replace("<player_test>", nametest)
tmp = tmp.replace("<site_test>", sitetest)
tmp = tmp.replace("<startdate_test>", start_date)
tmp = tmp.replace("<enddate_test>", end_date)
# print "DEBUG: sql query:"
# print tmp
self.cursor.execute(tmp)
#returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall()
@ -125,7 +157,6 @@ class GuiGraphViewer (threading.Thread):
pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
#TODO: Need to connect a callback here
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
pname.show()
@ -134,7 +165,7 @@ class GuiGraphViewer (threading.Thread):
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
# print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site)
@ -144,8 +175,9 @@ class GuiGraphViewer (threading.Thread):
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)
print w.get_active()
self.sites[site] = w.get_active()
print "self.sites[%s] set to %s" %(site, self.sites[site])
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
@ -162,6 +194,13 @@ class GuiGraphViewer (threading.Thread):
vbox.pack_start(hbox, False, True, 0)
hbox.show()
self.createSiteLine(hbox, site)
#Get db site id for filtering later
self.cursor.execute(self.sql.query['getSiteId'], (site,))
result = self.db.cursor.fetchall()
if len(result) == 1:
self.siteid[site] = result[0][0]
else:
print "Either 0 or more than one site matched - EEK"
def fillDateFrame(self, vbox):
# Hat tip to Mika Bostrom - calendar code comes from PokerStats
@ -261,7 +300,8 @@ class GuiGraphViewer (threading.Thread):
self.sql=querylist
self.conf = config
self.sites = "PokerStars"
self.sites = {}
self.siteid = {}
self.heroes = {}
# For use in date ranges.

View File

@ -60,7 +60,7 @@ class GuiPlayerStats (threading.Thread):
vbox.add(self.stats_table)
# Create header row
titles = ("Game", "Hands", "VPIP", "PFR", "saw_f", "sawsd", "wtsdwsf", "wmsd", "FlAFq", "TuAFq", "RvAFq", "PFAFq", "Net($)", "BB/100", "$/hand", "Variance")
titles = ("Game", "Hands", "VPIP", "PFR", "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq", "PoFAFq", "Net($)", "BB/100", "$/hand", "Variance")
col = 0
row = 0
@ -71,14 +71,17 @@ class GuiPlayerStats (threading.Thread):
col +=1
for row in range(rows-1):
for col in range(cols):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
for col in range(cols):
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
if result[row][col]:
l = gtk.Label(result[row][col])
else:
l = gtk.Label(' ')
if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5)
else:
@ -127,7 +130,6 @@ class GuiPlayerStats (threading.Thread):
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def __init__(self, db, config, querylist, debug=True):
self.debug=debug

View File

@ -61,7 +61,7 @@
</layout>
</site>
<site enabled="True"
site_name="Full Tilt"
site_name="Full Tilt Poker"
table_finder="FullTiltPoker.exe"
screen_name="ENTER HERO NAME"
site_path=""

View File

@ -27,18 +27,19 @@ import xml.dom.minidom
import codecs
from decimal import Decimal
import operator
from time import time
import time
from copy import deepcopy
class Hand:
# def __init__(self, sitename, gametype, sb, bb, string):
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K'}
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'}
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.streetList = ['PREFLOP','FLOP','TURN','RIVER'] # a list of the observed street names in order
self.handid = 0
self.sb = gametype[3]
@ -53,6 +54,7 @@ class Hand:
self.posted = []
self.involved = True
self.pot = Pot()
#
# Collections indexed by street names
@ -78,6 +80,8 @@ class Hand:
# dict from player names to lists of hole cards
self.holecards = {}
self.stacks = {}
# dict from player names to amounts collected
self.collected = {}
@ -87,6 +91,8 @@ class Hand:
self.action = []
self.totalpot = None
self.totalcollected = None
self.rake = None
self.bets = {}
@ -104,37 +110,50 @@ 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] = []
self.stacks[name] = Decimal(chips)
self.holecards[name] = set()
self.pot.addPlayer(name)
for street in self.streetList:
self.bets[street][name] = []
def addStreets(self, match):
# go through m and initialise actions to empty list for each street.
if match is not None:
self.streets = match
for street in match.groupdict():
if match.group(street) is not None:
self.actions[street] = []
else:
print "empty markStreets match" # better to raise exception and put process hand in a try block
def addHoleCards(self, cards, player):
"""\
Assigns observed holecards to a player.
cards list of card bigrams e.g. ['2h','jc']
cards set of card bigrams e.g. set(['2h','Jc'])
player (string) name of player
hand
Note, will automatically uppercase the rank letter.
"""
print "DEBUG: addHoleCards", cards,player
try:
self.checkPlayerExists(player)
self.holecards[player] = set([self.card(c) for c in cards])
cards = set([self.card(c) for c in cards])
self.holecards[player].update(cards)
except FpdbParseError, e:
print "Tried to add holecards for unknown player: %s" % (player,)
print "[ERROR] 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).
Card ranks will be uppercased
"""
print "DEBUG: addShownCards", cards,player,holeandboard
if cards is not None:
self.shown.add(player)
self.addHoleCards(cards,player)
elif holeandboard is not None:
holeandboard = set([self.card(c) for c in holeandboard])
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)
@ -150,7 +169,7 @@ For when a player shows cards for any reason (for showdown or out of choice).
except FpdbParseError, e:
pass
except ValueError:
print "tried to discard a card %s didn't have" % (player,)
print "[ERROR] discardHoleCard 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]
@ -161,11 +180,31 @@ For when a player shows cards for any reason (for showdown or out of choice).
c = c.replace(k,v)
return c
def addBlind(self, player, amount):
def addBlind(self, player, blindtype, amount):
# if player is None, it's a missing small blind.
# TODO:
# The situation we need to cover are:
# Player in small blind posts
# - this is a bet of 1 sb, as yet uncalled.
# Player in the big blind posts
# - this is a bet of 1 bb and is the new uncalled
#
# If a player posts a big & small blind
#
print "DEBUG addBlind: %s posts %s, %s" % (player, blindtype, amount)
if player is not None:
self.bets['PREFLOP'][player].append(Decimal(amount))
self.stacks[player] -= Decimal(amount)
#print "DEBUG %s posts, stack %s" % (player, self.stacks[player])
act = (player, 'posts', blindtype, amount, self.stacks[player]==0)
self.actions['PREFLOP'].append(act)
self.pot.addMoney(player, Decimal(amount))
if blindtype == 'big blind':
self.lastBet['PREFLOP'] = Decimal(amount)
elif blindtype == 'small & big blinds':
# extra small blind is 'dead'
self.lastBet['PREFLOP'] = Decimal(self.bb)
self.posted += [player]
@ -175,61 +214,122 @@ For when a player shows cards for any reason (for showdown or out of choice).
if amount is not None:
self.bets[street][player].append(Decimal(amount))
#self.lastBet[street] = Decimal(amount)
self.actions[street] += [[player, 'calls', amount]]
self.stacks[player] -= Decimal(amount)
print "DEBUG %s calls %s, stack %s" % (player, amount, self.stacks[player])
act = (player, 'calls', amount, self.stacks[player]==0)
self.actions[street].append(act)
self.pot.addMoney(player, Decimal(amount))
def addRaiseBy(self, street, player, amountBy):
"""\
Add a raise by amountBy on [street] by [player]
"""
#Given only the amount raised by, the amount of the raise can be calculated by
# working out how much this player has already in the pot
# (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)
# let Bp = previous bet
# Bc = amount player has committed so far
# Rb = raise by
# then: C = Bp - Bc (amount to call)
# Rt = Bp + Rb (raise to)
#
self.checkPlayerExists(player)
Rb = Decimal(amountBy)
Bp = self.lastBet[street]
Bc = reduce(operator.add, self.bets[street][player], 0)
C = Bp - Bc
Rt = Bp + Rb
self.bets[street][player].append(C + Rb)
self.stacks[player] -= (C + Rb)
self.actions[street] += [(player, 'raises', Rb, Rt, C, self.stacks[player]==0)]
self.lastBet[street] = Rt
def addCallandRaise(self, street, player, amount):
"""\
For sites which by "raises x" mean "calls and raises putting a total of x in the por". """
self.checkPlayerExists(player)
CRb = Decimal(amount)
Bp = self.lastBet[street]
Bc = reduce(operator.add, self.bets[street][player], 0)
C = Bp - Bc
Rb = CRb - C
Rt = Bp + Rb
self._addRaise(street, player, C, Rb, Rt)
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]]
Bc = reduce(operator.add, self.bets[street][player], 0)
Rt = Decimal(amountTo)
C = Bp - Bc
Rb = Rt - C
self._addRaise(street, player, C, Rb, Rt)
def _addRaise(self, street, player, C, Rb, Rt):
self.bets[street][player].append(C + Rb)
self.stacks[player] -= (C + Rb)
act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0)
self.actions[street].append(act)
self.lastBet[street] = Rt # TODO check this is correct
self.pot.addMoney(player, C+Rb)
def addBet(self, street, player, amount):
self.checkPlayerExists(player)
self.bets[street][player].append(Decimal(amount))
self.actions[street] += [[player, 'bets', amount]]
self.stacks[player] -= Decimal(amount)
print "DEBUG %s bets %s, stack %s" % (player, amount, self.stacks[player])
act = (player, 'bets', amount, self.stacks[player]==0)
self.actions[street].append(act)
self.lastBet[street] = Decimal(amount)
self.pot.addMoney(player, Decimal(amount))
def addFold(self, street, player):
print "DEBUG: %s %s folded" % (street, player)
self.checkPlayerExists(player)
self.folded.add(player)
self.actions[street] += [[player, 'folds']]
self.pot.addFold(player)
self.actions[street].append((player, 'folds'))
def addCheck(self, street, player):
print "DEBUG: %s %s checked" % (street, player)
self.checkPlayerExists(player)
self.actions[street] += [[player, 'checks']]
self.actions[street].append((player, 'checks'))
def addCollectPot(self,player, pot):
print "DEBUG: %s collected %s" % (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?"
print "[WARNING] %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
"""If all bets and blinds have been added, totals up the total pot size"""
# This gives us the total amount put in the pot
if self.totalpot is None:
self.pot.end()
self.totalpot = self.pot.total
# This gives us the amount collected, i.e. after rake
if self.totalcollected is None:
self.totalcollected = 0;
for amount in self.collected.values():
self.totalcollected += Decimal(amount)
# 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):
"""\
@ -258,80 +358,87 @@ Map the tuple self.gametype onto the pokerstars string describing it
return string
def printHand(self):
def writeHand(self, fh=sys.__stdout__):
# 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])
#print "\n### Pseudo stars format ###"
#print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, self.getGameTypeAsString(), self.sb, self.bb, self.starttime))
print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %("PokerStars", self.handid, self.getGameTypeAsString(), self.sb, self.bb, time.strftime('%Y/%m/%d - %H:%M:%S (ET)', self.starttime)))
print >>fh, _("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos))
players_who_act_preflop = set([x[0] for x in self.actions['PREFLOP']])
for player in [x for x in self.players if x[1] in players_who_act_preflop]:
#Only print stacks of players who do something preflop
print >>fh, _("Seat %s: %s ($%s)" %(player[0], player[1], player[2]))
if(self.posted[0] is None):
print "No small blind posted"
#print >>fh, _("No small blind posted") # PS doesn't say this
pass
else:
print "%s: posts small blind $%s" %(self.posted[0], self.sb)
print >>fh, _("%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)
print >>fh, _("%s: posts big blind $%s" %(self.posted[1], self.bb))
# What about big & small blinds?
# TODO: What about big & small blinds?
print "*** HOLE CARDS ***"
print >>fh, _("*** HOLE CARDS ***")
if self.involved:
print "Dealt to %s [%s]" %(self.hero , " ".join(self.holecards[self.hero]))
print >>fh, _("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)
self.printActionLine(act, fh)
if 'FLOP' in self.actions:
print "*** FLOP *** [%s]" %( " ".join(self.board['Flop']))
print >>fh, _("*** FLOP *** [%s]" %( " ".join(self.board['FLOP'])))
for act in self.actions['FLOP']:
self.printActionLine(act)
self.printActionLine(act, fh)
if 'TURN' in self.actions:
print "*** TURN *** [%s] [%s]" %( " ".join(self.board['Flop']), " ".join(self.board['Turn']))
print >>fh, _("*** TURN *** [%s] [%s]" %( " ".join(self.board['FLOP']), " ".join(self.board['TURN'])))
for act in self.actions['TURN']:
self.printActionLine(act)
self.printActionLine(act, fh)
if 'RIVER' in self.actions:
print "*** RIVER *** [%s] [%s]" %(" ".join(self.board['Flop']+self.board['Turn']), " ".join(self.board['River']) )
print >>fh, _("*** RIVER *** [%s] [%s]" %(" ".join(self.board['FLOP']+self.board['TURN']), " ".join(self.board['RIVER']) ))
for act in self.actions['RIVER']:
self.printActionLine(act)
self.printActionLine(act, fh)
#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 >>fh, _("*** SHOW DOWN ***")
print >>fh, "DEBUG: what do they show"
print >>fh, _("*** SUMMARY ***")
print >>fh, "%s | Rake $%.2f" % (self.pot, self.rake)
#print >>fh, _("Total pot $%s | Rake $%.2f" % (self.totalpot, self.rake)) # TODO: side pots
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))
print >>fh, _("Board [%s]" % (" ".join(board)))
for player in self.players:
for player in [x for x in self.players if x[1] in players_who_act_preflop]:
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])
if name in self.collected and name in self.shown:
print >>fh, _("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)
print >>fh, _("Seat %d: %s collected ($%s)" % (seatnum, name, self.collected[name]))
elif name in self.shown:
print >>fh, _("Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name])))
elif name in self.folded:
print >>fh, _("Seat %d: %s folded" % (seatnum, name))
else:
print "Seat %d: %s mucked" % (seatnum, name)
print >>fh, _("Seat %d: %s mucked" % (seatnum, name))
print
print >>fh, "\n\n"
# TODO:
# logic for side pots
# logic for which players get to showdown
@ -348,25 +455,26 @@ Map the tuple self.gametype onto the pokerstars string describing it
#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])
def printHand(self):
self.writeHand(sys.stdout)
def printActionLine(self, act, fh):
if act[1] == 'folds':
print >>fh, _("%s: folds " %(act[0]))
elif act[1] == 'checks':
print >>fh, _("%s: checks " %(act[0]))
if act[1] == 'calls':
print "%s: %s $%s" %(act[0], act[1], act[2])
print >>fh, _("%s: calls $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
if act[1] == 'bets':
print "%s: %s $%s" %(act[0], act[1], act[2])
print >>fh, _("%s: bets $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
if act[1] == 'raises':
print "%s: %s $%s to $%s" %(act[0], act[1], act[2], act[3])
print >>fh, _("%s: raises $%s to $%s%s" %(act[0], act[2], act[3], ' and is all-in' if act[5] else ''))
# 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
@ -402,3 +510,71 @@ Map the tuple self.gametype onto the pokerstars string describing it
class FpdbParseError(Exception): pass
class Pot(object):
def __init__(self):
self.contenders = set()
self.committed = {}
self.total = None
def addPlayer(self,player):
self.committed[player] = Decimal(0)
def addFold(self, player):
# addFold must be called when a player folds
self.contenders.discard(player)
def addMoney(self, player, amount):
# addMoney must be called for any actions that put money in the pot, in the order they occur
self.contenders.add(player)
self.committed[player] += amount
def end(self):
self.total = sum(self.committed.values())
# Return any uncalled bet.
committed = sorted([ (v,k) for (k,v) in self.committed.items()])
lastbet = committed[-1][0] - committed[-2][0]
if lastbet > 0: # uncalled
returnto = committed[-1][1]
#print "returning %f to %s" % (lastbet, returnto)
self.total -= lastbet
self.committed[returnto] -= lastbet
# Work out side pots
commitsall = sorted([(v,k) for (k,v) in self.committed.items() if v >0])
self.pots = []
while len(commitsall) > 0:
commitslive = [(v,k) for (v,k) in commitsall if k in self.contenders]
v1 = commitslive[0][0]
self.pots += [sum([min(v,v1) for (v,k) in commitsall])]
commitsall = [((v-v1),k) for (v,k) in commitsall if v-v1 >0]
# TODO: I think rake gets taken out of the pots.
# so it goes:
# total pot x. main pot y, side pot z. | rake r
# and y+z+r = x
# for example:
# Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2
def __str__(self):
if self.total is None:
print "call Pot.end() before printing pot total"
# NB if I'm sure end() is idempotent, call it here.
raise FpdbParseError
if len(self.pots) == 1: # (only use Total pot)
return "Total pot $%.2f" % (self.total,)
elif len(self.pots) == 2:
return "Total pot $%.2f Main pot $%.2f. Side pot $%2.f." % (self.total, self.pots[0], self.pots[1])
elif len(self.pots) == 3:
return "Total pot $%.2f Main pot $%.2f. Side pot-1 $%2.2f. Side pot-2 $%.2f." % (self.total, self.pots[0], self.pots[1], self.pots[2])
else:
return "too many pots.. fix me.", self.pots

View File

@ -29,7 +29,10 @@ from decimal import Decimal
import operator
from xml.dom.minidom import Node
from pokereval import PokerEval
from time import time
import time
import datetime
import gettext
#from pokerengine.pokercards import *
# provides letter2name{}, letter2names{}, visible_card(), not_visible_card(), is_visible(), card_value(), class PokerCards
# but it's probably not installed so here are the ones we may want:
@ -65,6 +68,11 @@ letter2names = {
'2': 'Deuces'
}
import gettext
gettext.install('myapplication')
class HandHistoryConverter:
eval = PokerEval()
def __init__(self, config, file, sitename):
@ -97,7 +105,7 @@ class HandHistoryConverter:
return tmp
def processFile(self):
starttime = time()
starttime = time.time()
if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check"
return
@ -108,30 +116,35 @@ class HandHistoryConverter:
print "\nInput:\n"+hand.string
self.readHandInfo(hand)
self.readPlayerStacks(hand)
print "DEBUG stacks:", hand.stacks
self.markStreets(hand)
self.readBlinds(hand)
self.readHeroCards(hand) # want to generalise to draw games
self.readCommunityCards(hand) # read community cards
self.readShowdownActions(hand)
# Read action (Note: no guarantee this is in hand order.
for street in hand.streets.groupdict():
# Read actions in street order
for street in hand.streetList: # go through them in order
if hand.streets.group(street) is not None:
self.readCommunityCards(hand, street) # read community cards
self.readAction(hand, street)
self.readCollectPot(hand)
self.readShownCards(hand)
# finalise it (total the pot)
hand.totalPot()
self.getRake(hand)
hand.printHand()
hand.writeHand(sys.stderr)
#if(hand.involved == True):
#self.writeHand("output file", hand)
#hand.printHand()
#else:
#pass #Don't write out observed hands
endtime = time()
endtime = time.time()
print "Processed %d hands in %d seconds" % (len(self.hands), endtime-starttime)
#####
@ -161,7 +174,8 @@ class HandHistoryConverter:
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.
# so groups are called by street names 'PREFLOP', 'FLOP', 'STREET2' etc
# blinds are done seperately
def markStreets(self, hand): abstract
#Needs to return a list in the format
@ -171,13 +185,16 @@ class HandHistoryConverter:
def readHeroCards(self, hand): abstract
def readAction(self, hand, street): abstract
def readCollectPot(self, hand): abstract
def readShownCards(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.
def getRake(self, hand): abstract
# an inheriting class can calculate it for the specific site if need be.
def getRake(self, hand):
hand.rake = hand.totalpot - hand.totalcollected # * Decimal('0.05') # probably not quite right
def sanityCheck(self):
sane = True
sane = False
base_w = False
#Check if hhbase exists and is writable
#Note: Will not try to create the base HH directory

View File

@ -129,6 +129,13 @@ class Hud:
self.main_window.set_destroy_with_parent(True)
def update_table_position(self):
# self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number)
# if self.main_window.parentgdkhandle == None:
if os.name == 'nt':
if not win32gui.IsWindow(self.table.number):
self.kill_hud()
return False
(x, y) = self.main_window.parentgdkhandle.get_origin()
if self.table.x != x or self.table.y != y:
self.table.x = x
@ -140,6 +147,7 @@ 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):
@ -234,7 +242,8 @@ class Hud:
aux_params = config.get_aux_parameters(game_params['aux'])
self.aux_windows.append(eval("%s.%s(gtk.Window(), self, config, aux_params)" % (aux_params['module'], aux_params['class'])))
# gobject.timeout_add(500, self.update_table_position)
if os.name == "nt":
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

239
pyfpdb/OnGameToFpdb.py Executable file
View File

@ -0,0 +1,239 @@
#!/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 *
# OnGame HH Format
#Texas Hold'em $.5-$1 NL (real money), hand #P4-76915775-797
#Table Kuopio, 20 Sep 2008 11:59 PM
#Seat 1: .Lucchess ($4.17 in chips)
#Seat 3: Gloff1 ($108 in chips)
#Seat 4: far a ($13.54 in chips)
#Seat 5: helander2222 ($49.77 in chips)
#Seat 6: lopllopl ($62.06 in chips)
#Seat 7: crazyhorse6 ($101.91 in chips)
#Seat 8: peeci ($25.02 in chips)
#Seat 9: Manuelhertz ($49 in chips)
#Seat 10: Eurolll ($58.25 in chips)
#ANTES/BLINDS
#helander2222 posts blind ($0.25), lopllopl posts blind ($0.50).
#PRE-FLOP
#crazyhorse6 folds, peeci folds, Manuelhertz folds, Eurolll calls $0.50, .Lucchess calls $0.50, Gloff1 folds, far a folds, helander2222 folds, lopllopl checks.
#FLOP [board cards AH,8H,KH ]
#lopllopl checks, Eurolll checks, .Lucchess checks.
#TURN [board cards AH,8H,KH,6S ]
#lopllopl checks, Eurolll checks, .Lucchess checks.
#RIVER [board cards AH,8H,KH,6S,8S ]
#lopllopl checks, Eurolll bets $1.25, .Lucchess folds, lopllopl folds.
#SHOWDOWN
#Eurolll wins $2.92.
#SUMMARY
#Dealer: far a
#Pot: $3, (including rake: $0.08)
#.Lucchess, loses $0.50
#Gloff1, loses $0
#far a, loses $0
#helander2222, loses $0.25
#lopllopl, loses $0.50
#crazyhorse6, loses $0
#peeci, loses $0
#Manuelhertz, loses $0
#Eurolll, bets $1.75, collects $2.92, net $1.17
class OnGame(HandHistoryConverter):
def __init__(self, config, file):
print "Initialising OnGame converter class"
HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init.
self.sitename = "OnGame"
self.setFileType("text", "cp1252")
#self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n\n+')
#Texas Hold'em $.5-$1 NL (real money), hand #P4-76915775-797
#Table Kuopio, 20 Sep 2008 11:59 PM
self.rexx.setHandInfoRegex(r"Texas Hold'em \$?(?P<SB>[.0-9]+)-\$?(?P<BB>[.0-9]+) NL \(real money\), hand #(?P<HID>[-A-Z\d]+)\nTable\ (?P<TABLE>[\' \w]+), (?P<DATETIME>\d\d \w+ \d\d\d\d \d\d:\d\d (AM|PM))")
# SB BB HID TABLE DAY MON YEAR HR12 MIN AMPM
self.rexx.button_re = re.compile('#SUMMARY\nDealer: (?P<BUTTONPNAME>.*)\n')
#Seat 1: .Lucchess ($4.17 in chips)
self.rexx.setPlayerInfoRegex('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \((\$(?P<CASH>[.0-9]+) in chips)\)')
#ANTES/BLINDS
#helander2222 posts blind ($0.25), lopllopl posts blind ($0.50).
self.rexx.setPostSbRegex('(?P<PNAME>.*) posts blind \(\$?(?P<SB>[.0-9]+)\), ')
self.rexx.setPostBbRegex('\), (?P<PNAME>.*) posts blind \(\$?(?P<BB>[.0-9]+)\).')
self.rexx.setPostBothRegex('.*\n(?P<PNAME>.*): posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)')
self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P<PNAME>.*)\s\[ (?P<CARDS>.*) \]')
#lopllopl checks, Eurolll checks, .Lucchess checks.
self.rexx.setActionStepRegex('(, )?(?P<PNAME>.*?)(?P<ATYPE> bets| checks| raises| calls| folds)( \$(?P<BET>\d*\.?\d*))?( and is all-in)?')
#Uchilka shows [ KC,JD ]
self.rexx.setShowdownActionRegex('(?P<PNAME>.*) shows \[ (?P<CARDS>.+) \]')
# TODO: read SUMMARY correctly for collected pot stuff.
#Uchilka, bets $11.75, collects $23.04, net $11.29
self.rexx.setCollectPotRegex('(?P<PNAME>.*), bets.+, collects \$(?P<POT>\d*\.?\d*), net.* ')
self.rexx.sits_out_re = re.compile('(?P<PNAME>.*) sits out')
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.hand_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')
#hand.buttonpos = self.rexx.button_re.search(hand.string).group('BUTTONPNAME')
# 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 = time.strptime(m.group('DATETIME'), "%d %b %Y %I:%M %p")
#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')))
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 **
# 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<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)
m = re.search(r"PRE-FLOP(?P<PREFLOP>.+(?=FLOP)|.+(?=SHOWDOWN))"
r"(FLOP (?P<FLOP>\[board cards .+ \].+(?=TURN)|.+(?=SHOWDOWN)))?"
r"(TURN (?P<TURN>\[board cards .+ \].+(?=RIVER)|.+(?=SHOWDOWN)))?"
r"(RIVER (?P<RIVER>\[board cards .+ \].+(?=SHOWDOWN)))?", hand.string,re.DOTALL)
hand.addStreets(m)
def readCommunityCards(self, hand, street):
self.rexx.board_re = re.compile(r"\[board cards (?P<CARDS>.+) \]")
print hand.streets.group(street)
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
m = self.rexx.board_re.search(hand.streets.group(street))
hand.setCommunityCards(street, m.group('CARDS').split(','))
def readBlinds(self, hand):
try:
m = self.rexx.small_blind_re.search(hand.string)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.rexx.big_blind_re.finditer(hand.string):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.rexx.both_blinds_re.finditer(hand.string):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
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')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('CARDS')
cards = set(cards.split(','))
hand.addHoleCards(cards, m.group('PNAME'))
def readAction(self, hand, street):
m = self.rexx.action_re.finditer(hand.streets.group(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') )
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')]]
# TODO: Everleaf does not record uncalled bets.
def readShowdownActions(self, hand):
for shows in self.rexx.showdown_action_re.finditer(hand.string):
cards = shows.group('CARDS')
cards = set(cards.split(','))
hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand):
for m in self.rexx.collect_pot_re.finditer(hand.string):
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
def readShownCards(self,hand):
return
#for m in self.rexx.collect_pot_re.finditer(hand.string):
#if m.group('CARDS') is not None:
#cards = m.group('CARDS')
#cards = set(cards.split(','))
#hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
if __name__ == "__main__":
c = Configuration.Config()
if len(sys.argv) == 1:
testfile = "regression-test-files/ongame/nlhe/ong NLH handhq_0.txt"
else:
testfile = sys.argv[1]
e = OnGame(c, testfile)
e.processFile()
print str(e)

View File

@ -108,9 +108,9 @@ class fpdb_db:
self.cursor.execute(self.sql.query['createHandsPlayersTable'])
self.cursor.execute(self.sql.query['createHandsActionsTable'])
self.cursor.execute(self.sql.query['createHudCacheTable'])
self.cursor.execute(self.sql.query['addTourneyIndex'])
self.cursor.execute(self.sql.query['addHandsIndex'])
self.cursor.execute(self.sql.query['addPlayersIndex'])
#self.cursor.execute(self.sql.query['addTourneyIndex'])
#self.cursor.execute(self.sql.query['addHandsIndex'])
#self.cursor.execute(self.sql.query['addPlayersIndex'])
self.fillDefaultData()
self.db.commit()
#end def disconnect
@ -177,6 +177,7 @@ class fpdb_db:
self.cursor.execute("INSERT INTO Settings VALUES (118);")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');")
self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
#end def fillDefaultData
@ -185,6 +186,7 @@ class fpdb_db:
self.drop_tables()
self.create_tables()
fpdb_simple.createAllIndexes(self)
self.db.commit()
print "Finished recreating tables"
#end def recreate_tables

View File

@ -116,8 +116,11 @@ class Importer:
#Run full import on filelist
def runImport(self):
fpdb_simple.prepareBulkImport(self.fdb)
for file in self.filelist:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
fpdb_simple.afterBulkImport(self.fdb)
fpdb_simple.analyzeDB(self.fdb)
#Run import on updated files, then store latest update time.
def runUpdated(self):

View File

@ -22,6 +22,11 @@ from time import time
import fpdb_simple
saveActions=True # set this to False to avoid storing action data
# Pros: speeds up imports
# Cons: no action data is saved, so you need to keep the hand histories
# variance not available on stats page
#stores a stud/razz hand into the database
def ring_stud(backend, db, cursor, base, category, site_hand_no, gametype_id, hand_start_time
,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes
@ -39,6 +44,7 @@ def ring_stud(backend, db, cursor, base, category, site_hand_no, gametype_id, ha
fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types
,allIns, action_amounts, actionNos)
return hands_id
@ -66,10 +72,10 @@ def ring_holdem_omaha(backend, db, cursor, base, category, site_hand_no, gametyp
t5 = time()
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
t6 = time()
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
t7 = time()
print "cards=%4.3f board=%4.3f hands=%4.3f plyrs=%4.3f hudcache=%4.3f board=%4.3f actions=%4.3f" \
% (t1-t0, t2-t1, t3-t2, t4-t3, t5-t4, t6-t5, t7-t6)
#print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4, t6-t5)
return hands_id
#end def ring_holdem_omaha
@ -98,6 +104,7 @@ def tourney_holdem_omaha(backend, db, cursor, base, category, siteTourneyNo, buy
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
return hands_id
#end def tourney_holdem_omaha
@ -122,6 +129,7 @@ def tourney_stud(backend, db, cursor, base, category, siteTourneyNo, buyin, fee,
fpdb_simple.storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos)
return hands_id
#end def tourney_stud

312
pyfpdb/fpdb_simple.py Executable file → Normal file
View File

@ -26,6 +26,308 @@ FTP=2
MYSQL_INNODB=2
PGSQL=3
SQLITE=4
# Data Structures for index and foreign key creation
# drop_code is an int with possible values: 0 - don't drop for bulk import
# 1 - drop during bulk import
# db differences:
# - note that mysql automatically creates indexes on constrained columns when
# foreign keys are created, while postgres does not. Hence the much longer list
# of indexes is required for postgres.
# all primary keys are left on all the time
#
# table column drop_code
indexes = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # indexes for mysql (list index 2)
{'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
]
, [ # indexes for postgres (list index 3)
{'tab':'Boardcards', 'col':'handId', 'drop':0}
, {'tab':'Gametypes', 'col':'siteId', 'drop':0}
, {'tab':'Hands', 'col':'gametypeId', 'drop':1}
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'HandsActions', 'col':'handplayerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
, {'tab':'HudCache', 'col':'gametypeId', 'drop':1}
, {'tab':'HudCache', 'col':'playerId', 'drop':0}
, {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'Players', 'col':'siteId', 'drop':1}
, {'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
]
foreignKeys = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # foreign keys for mysql
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
, [ # foreign keys for postgres
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
]
# MySQL Notes:
# "FOREIGN KEY (handId) REFERENCES Hands(id)" - requires index on Hands.id
# - creates index handId on <thistable>.handId
# alter table t drop foreign key fk
# alter table t add foreign key (fkcol) references tab(rcol)
# alter table t add constraint c foreign key (fkcol) references tab(rcol)
# (fkcol is used for foreigh key name)
# mysql to list indexes:
# SELECT table_name, index_name, non_unique, column_name
# FROM INFORMATION_SCHEMA.STATISTICS
# WHERE table_name = 'tbl_name'
# AND table_schema = 'db_name'
# ORDER BY table_name, index_name, seq_in_index
#
# ALTER TABLE Tourneys ADD INDEX siteTourneyNo(siteTourneyNo)
# ALTER TABLE tab DROP INDEX idx
# mysql to list fks:
# SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
# FROM information_schema.KEY_COLUMN_USAGE
# WHERE REFERENCED_TABLE_SCHEMA = (your schema name here)
# AND REFERENCED_TABLE_NAME is not null
# ORDER BY TABLE_NAME, COLUMN_NAME;
# this may indicate missing object
# _mysql_exceptions.OperationalError: (1025, "Error on rename of '.\\fpdb\\hands' to '.\\fpdb\\#sql2-7f0-1b' (errno: 152)")
# PG notes:
# To add a foreign key constraint to a table:
# ALTER TABLE tab ADD CONSTRAINT c FOREIGN KEY (col) REFERENCES t2(col2) MATCH FULL;
# ALTER TABLE tab DROP CONSTRAINT zipchk
#
# Note: index names must be unique across a schema
# CREATE INDEX idx ON tab(col)
# DROP INDEX idx
def prepareBulkImport(fdb):
"""Drop some indexes/foreign keys to prepare for bulk import.
Currently keeping the standalone indexes as needed to import quickly"""
# fdb is a fpdb_db object including backend, db, cursor, sql variables
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for fk in foreignKeys[fdb.backend]:
if fk['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
fdb.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = fdb.cursor.fetchone()
print "preparebulk: cons=", cons
if cons:
print "dropping mysql fk", cons[0], fk['fktab'], fk['fkcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " drop foreign key " + cons[0])
except:
pass
elif fdb.backend == PGSQL:
print "dropping pg fk", fk['fktab'], fk['fkcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " drop constraint "
+ fk['fktab'] + '_' + fk['fkcol'] + '_fkey')
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in indexes[fdb.backend]:
if idx['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s drop index %s", (idx['tab'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
print "dropping pg index ", idx['tab'], idx['col']
# mod to use tab_col for index name?
try:
fdb.cursor.execute( "drop index %s_%s_idx" % (idx['tab'],idx['col']) )
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit() # seems to clear up errors if there were any in postgres
#end def prepareBulkImport
def afterBulkImport(fdb):
"""Re-create any dropped indexes/foreign keys after bulk import"""
# fdb is a fpdb_db object including backend, db, cursor, sql variables
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for fk in foreignKeys[fdb.backend]:
if fk['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
fdb.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = fdb.cursor.fetchone()
print "afterbulk: cons=", cons
if cons:
pass
else:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " add foreign key ("
+ fk['fkcol'] + ") references " + fk['rtab'] + "("
+ fk['rcol'] + ")")
except:
pass
elif fdb.backend == PGSQL:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " add constraint "
+ fk['fktab'] + '_' + fk['fkcol'] + '_fkey'
+ " foreign key (" + fk['fkcol']
+ ") references " + fk['rtab'] + "(" + fk['rcol'] + ")")
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in indexes[fdb.backend]:
if idx['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
fdb.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit() # seems to clear up errors if there were any in postgres
#end def afterBulkImport
def createAllIndexes(fdb):
"""Create new indexes"""
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for idx in indexes[fdb.backend]:
if fdb.backend == MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
fdb.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
#end def createAllIndexes
def dropAllIndexes(fdb):
"""Drop all standalone indexes (i.e. not including primary keys or foreign keys)
using list of indexes in indexes data structure"""
# maybe upgrade to use data dictionary?? (but take care to exclude PK and FK)
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for idx in indexes[fdb.backend]:
if fdb.backend == MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s drop index %s"
, (idx['tab'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
print "dropping pg index ", idx['tab'], idx['col']
# mod to use tab_col for index name?
try:
fdb.cursor.execute( "drop index %s_%s_idx"
% (idx['tab'],idx['col']) )
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
#end def dropAllIndexes
def analyzeDB(fdb):
"""Do whatever the DB can offer to update index/table statistics"""
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow vacuum to work
try:
fdb.cursor.execute("vacuum analyze")
except:
print "Error during vacuum"
fdb.db.set_isolation_level(1) # go back to normal isolation level
#end def analyzeDB
class DuplicateError(Exception):
@ -90,7 +392,7 @@ def checkPositions(positions):
pass
### RHH modified to allow for "position 9" here (pos==9 is when you're a dead hand before the BB
if (pos!="B" and pos!="S" and pos!=0 and pos!=1 and pos!=2 and pos!=3 and pos!=4 and pos!=5 and pos!=6 and pos!=7 and pos!=9):
if (pos!="B" and pos!="S" and pos!=0 and pos!=1 and pos!=2 and pos!=3 and pos!=4 and pos!=5 and pos!=6 and pos!=7 and pos != 8 and pos!=9):
raise FpdbError("invalid position found in checkPositions. i: "+str(i)+" position: "+str(pos))
#end def fpdb_simple.checkPositions
@ -868,14 +1170,16 @@ def parseHandStartTime(topline, site):
tmp=topline[pos1:pos2]
isUTC=True
else:
tmp=topline[-30:]
tmp=topline
#print "parsehandStartTime, tmp:", tmp
pos = tmp.find("-")+2
tmp = tmp[pos:]
#Need to match either
# 2008/09/07 06:23:14 ET or
# 2008/08/17 - 01:14:43 (ET)
m = re.match('(?P<YEAR>[0-9]{4})\/(?P<MON>[0-9]{2})\/(?P<DAY>[0-9]{2})[\- ]+(?P<HR>[0-9]{2}):(?P<MIN>[0-9]{2}):(?P<SEC>[0-9]{2})',tmp)
# 2008/08/17 - 01:14:43 (ET) or
# 2008/11/12 9:33:31 CET [2008/11/12 3:33:31 ET]
rexx = '(?P<YEAR>[0-9]{4})\/(?P<MON>[0-9]{2})\/(?P<DAY>[0-9]{2})[\- ]+(?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+)'
m = re.search(rexx,tmp)
#print "year:", int(m.group('YEAR')), "month", int(m.group('MON')), "day", int(m.group('DAY')), "hour", int(m.group('HR')), "minute", int(m.group('MIN')), "second", int(m.group('SEC'))
result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
else:

Binary file not shown.