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

Conflicts:

	pyfpdb/Hud.py
This commit is contained in:
Ray 2008-12-19 14:08:36 -05:00
commit 15e1461cca
16 changed files with 3677 additions and 2593 deletions

View File

@ -71,15 +71,17 @@ class Everleaf(HandHistoryConverter):
self.setFileType("text", "cp1252") self.setFileType("text", "cp1252")
self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)') self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n+') 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.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|new player|All-in) \)') self.rexx.setPlayerInfoRegex('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\s+(\$ (?P<CASH>[.0-9]+) USD|new player|All-in) \)')
self.rexx.setPostSbRegex('.*\n(?P<PNAME>.*): posts small blind \[\$? (?P<SB>[.0-9]+)') 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]+)') self.rexx.setPostBbRegex('.*\n(?P<PNAME>.*): posts big blind \[\$? (?P<BB>[.0-9]+)')
# mct : what about posting small & big blinds simultaneously? self.rexx.setPostBothRegex('.*\n(?P<PNAME>.*): posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)')
self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P<PNAME>.*)\s\[ (?P<HOLE1>\S\S), (?P<HOLE2>\S\S) \]') 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\])?') self.rexx.setActionStepRegex('.*\n(?P<PNAME>.*)(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[\$ (?P<BET>[.\d]+) USD\])?')
self.rexx.setShowdownActionRegex('.*\n(?P<PNAME>.*) shows \[ (?P<CARDS>.*) \]') 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(.*?\[ (?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() self.rexx.compileRegexes()
def readSupportedGames(self): def readSupportedGames(self):
@ -111,8 +113,7 @@ class Everleaf(HandHistoryConverter):
# 2008/11/10 3:58:52 ET # 2008/11/10 3:58:52 ET
#TODO: Do conversion from GMT to 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) #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')), hand.starttime = time.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S")
int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
hand.buttonpos = int(m.group('BUTTON')) hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand): 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('(\*\* 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 \*\*)|.+)" 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 Flop \*\*(?P<FLOP> \[ \S\S, \S\S, \S\S \].+(?=\*\* Dealing Turn \*\*)|.+))?"
r"(\*\* Dealing Turn \*\* \[ \S\S \](?P<TURN>.+(?=\*\* Dealing River \*\*)|.+))?" r"(\*\* Dealing Turn \*\*(?P<TURN> \[ \S\S \].+(?=\*\* Dealing River \*\*)|.+))?"
r"(\*\* Dealing River \*\* \[ \S\S \](?P<RIVER>.+))?", hand.string,re.DOTALL) 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 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
re_board = re.compile('\*\* Dealing (?P<STREET>.*) \*\* \[ (?P<CARDS>.*) \]') self.rexx.board_re = re.compile(r"\[ (?P<CARDS>.+) \]")
m = re_board.finditer(hand.string) print hand.streets.group(street)
for street in m: if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
#print street.groups() m = self.rexx.board_re.search(hand.streets.group(street))
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? hand.setCommunityCards(street, m.group('CARDS').split(', '))
cardsmatch = re_card.finditer(street.group('CARDS'))
hand.setCommunityCards(street.group('STREET'), [card.group('CARD') for card in cardsmatch])
def readBlinds(self, hand): def readBlinds(self, hand):
try: try:
m = self.rexx.small_blind_re.search(hand.string) m = self.rexx.small_blind_re.search(hand.string)
hand.addBlind(m.group('PNAME'), m.group('SB')) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
#hand.posted = [m.group('PNAME')] except: # no small blind
except: hand.addBlind(None, None, None)
hand.addBlind(None, 0) for a in self.rexx.big_blind_re.finditer(hand.string):
#hand.posted = ["FpdbNBP"] hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
m = self.rexx.big_blind_re.finditer(hand.string) for a in self.rexx.both_blinds_re.finditer(hand.string):
for a in m: hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
hand.addBlind(a.group('PNAME'), a.group('BB'))
#hand.posted = hand.posted + [a.group('PNAME')]
def readHeroCards(self, hand): def readHeroCards(self, hand):
m = self.rexx.hero_cards_re.search(hand.string) m = self.rexx.hero_cards_re.search(hand.string)
@ -163,14 +160,17 @@ class Everleaf(HandHistoryConverter):
hand.involved = False hand.involved = False
else: else:
hand.hero = m.group('PNAME') 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): def readAction(self, hand, street):
m = self.rexx.action_re.finditer(hand.streets.group(street)) m = self.rexx.action_re.finditer(hand.streets.group(street))
hand.actions[street] = []
for action in m: for action in m:
if action.group('ATYPE') == ' raises': 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': elif action.group('ATYPE') == ' calls':
hand.addCall( street, action.group('PNAME'), action.group('BET') ) hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ': bets': elif action.group('ATYPE') == ': bets':
@ -181,34 +181,33 @@ class Everleaf(HandHistoryConverter):
hand.addCheck( street, action.group('PNAME')) hand.addCheck( street, action.group('PNAME'))
else: else:
print "DEBUG: unimplemented readAction: %s %s" %(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): def readShowdownActions(self, hand):
for shows in self.rexx.showdown_action_re.finditer(hand.string): for shows in self.rexx.showdown_action_re.finditer(hand.string):
print shows.groups() cards = shows.group('CARDS')
re_card = re.compile('(?P<CARD>[0-9tjqka][schd])') # copied from earlier cards = set(cards.split(', '))
cards = [card.group('CARD') for card in re_card.finditer(shows.group('CARDS'))]
print cards
hand.addShownCards(cards, shows.group('PNAME')) hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand): def readCollectPot(self,hand):
m = self.rexx.collect_pot_re.search(hand.string) for m in self.rexx.collect_pot_re.finditer(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)
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) 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): def readShownCards(self,hand):
hand.rake = hand.totalpot * Decimal('0.05') # probably not quite right 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__": if __name__ == "__main__":
c = Configuration.Config() 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() e.processFile()
print str(e) print str(e)

View File

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

@ -29,166 +29,182 @@ import fpdb_import
class GuiAutoImport (threading.Thread): class GuiAutoImport (threading.Thread):
def __init__(self, settings, config): def __init__(self, settings, config):
"""Constructor for GuiAutoImport""" """Constructor for GuiAutoImport"""
self.settings=settings self.settings=settings
self.config=config self.config=config
imp = self.config.get_import_parameters() imp = self.config.get_import_parameters()
print "Import parameters" print "Import parameters"
print imp print imp
self.input_settings = {} self.input_settings = {}
self.pipe_to_hud = None
self.importer = fpdb_import.Importer(self,self.settings, self.config) self.importer = fpdb_import.Importer(self,self.settings, self.config)
self.importer.setCallHud(True) self.importer.setCallHud(True)
self.importer.setMinPrint(30) self.importer.setMinPrint(30)
self.importer.setQuiet(False) self.importer.setQuiet(False)
self.importer.setFailOnError(False) self.importer.setFailOnError(False)
self.importer.setHandCount(0) self.importer.setHandCount(0)
# self.importer.setWatchTime() # self.importer.setWatchTime()
self.server=settings['db-host'] self.server=settings['db-host']
self.user=settings['db-user'] self.user=settings['db-user']
self.password=settings['db-password'] self.password=settings['db-password']
self.database=settings['db-databaseName'] self.database=settings['db-databaseName']
self.mainVBox=gtk.VBox(False,1) self.mainVBox=gtk.VBox(False,1)
self.mainVBox.show() self.mainVBox.show()
self.settingsHBox = gtk.HBox(False, 0) self.settingsHBox = gtk.HBox(False, 0)
self.mainVBox.pack_start(self.settingsHBox, False, True, 0) self.mainVBox.pack_start(self.settingsHBox, False, True, 0)
self.settingsHBox.show() self.settingsHBox.show()
self.intervalLabel = gtk.Label("Interval (ie. break) between imports in seconds:") self.intervalLabel = gtk.Label("Interval (ie. break) between imports in seconds:")
self.settingsHBox.pack_start(self.intervalLabel) self.settingsHBox.pack_start(self.intervalLabel)
self.intervalLabel.show() self.intervalLabel.show()
self.intervalEntry=gtk.Entry() self.intervalEntry=gtk.Entry()
self.intervalEntry.set_text(str(self.config.get_import_parameters().get("interval"))) self.intervalEntry.set_text(str(self.config.get_import_parameters().get("interval")))
self.settingsHBox.pack_start(self.intervalEntry) self.settingsHBox.pack_start(self.intervalEntry)
self.intervalEntry.show() self.intervalEntry.show()
self.addSites(self.mainVBox) self.addSites(self.mainVBox)
self.startButton=gtk.Button("Start Autoimport") self.doAutoImportBool = False
self.startButton.connect("clicked", self.startClicked, "start clicked") self.startButton=gtk.ToggleButton("Start Autoimport")
self.mainVBox.add(self.startButton) self.startButton.connect("clicked", self.startClicked, "start clicked")
self.startButton.show() self.mainVBox.add(self.startButton)
self.startButton.show()
#end of GuiAutoImport.__init__ #end of GuiAutoImport.__init__
def browseClicked(self, widget, data): def browseClicked(self, widget, data):
"""runs when user clicks one of the browse buttons in the auto import tab""" """runs when user clicks one of the browse buttons in the auto import tab"""
current_path=data[1].get_text() current_path=data[1].get_text()
dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import",
action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
#dia_chooser.set_current_folder(pathname) #dia_chooser.set_current_folder(pathname)
dia_chooser.set_filename(current_path) dia_chooser.set_filename(current_path)
#dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import
response = dia_chooser.run() response = dia_chooser.run()
if response == gtk.RESPONSE_OK: if response == gtk.RESPONSE_OK:
#print dia_chooser.get_filename(), 'selected' #print dia_chooser.get_filename(), 'selected'
data[1].set_text(dia_chooser.get_filename()) data[1].set_text(dia_chooser.get_filename())
self.input_settings[data[0]][0] = dia_chooser.get_filename() self.input_settings[data[0]][0] = dia_chooser.get_filename()
elif response == gtk.RESPONSE_CANCEL: elif response == gtk.RESPONSE_CANCEL:
print 'Closed, no files selected' print 'Closed, no files selected'
dia_chooser.destroy() dia_chooser.destroy()
#end def GuiAutoImport.browseClicked #end def GuiAutoImport.browseClicked
def do_import(self): def do_import(self):
"""Callback for timer to do an import iteration.""" """Callback for timer to do an import iteration."""
self.importer.runUpdated() if self.doAutoImportBool:
print "GuiAutoImport.import_dir done" self.importer.runUpdated()
return True print "GuiAutoImport.import_dir done"
return True
else:
return False
def startClicked(self, widget, data): def startClicked(self, widget, data):
"""runs when user clicks start on auto import tab""" """runs when user clicks start on auto import tab"""
# Check to see if we have an open file handle to the HUD and open one if we do not. # Check to see if we have an open file handle to the HUD and open one if we do not.
# bufsize = 1 means unbuffered # bufsize = 1 means unbuffered
# We need to close this file handle sometime. # We need to close this file handle sometime.
# TODO: Allow for importing from multiple dirs - REB 29AUG2008 # TODO: Allow for importing from multiple dirs - REB 29AUG2008
# As presently written this function does nothing if there is already a pipe open. # As presently written this function does nothing if there is already a pipe open.
# That is not correct. It should open another dir for importing while piping the # That is not correct. It should open another dir for importing while piping the
# results to the same pipe. This means that self.path should be a a list of dirs # results to the same pipe. This means that self.path should be a a list of dirs
# to watch. # to watch.
try: #uhhh, I don't this this is the best way to check for the existence of an attr if widget.get_active(): # toggled on
getattr(self, "pipe_to_hud") self.doAutoImportBool = True
except AttributeError: widget.set_label(u'Stop Autoimport')
if os.name == 'nt': if self.pipe_to_hud is None:
command = "python HUD_main.py" + " %s" % (self.database) if os.name == 'nt':
bs = 0 # windows is not happy with line buffing here command = "python HUD_main.py" + " %s" % (self.database)
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, bs = 0 # windows is not happy with line buffing here
universal_newlines=True) self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
else: universal_newlines=True)
command = self.config.execution_path('HUD_main.py') else:
bs = 1 command = self.config.execution_path('HUD_main.py')
self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, bs = 1
universal_newlines=True) self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
# self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, universal_newlines=True)
# universal_newlines=True) # self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
# command = command + " %s" % (self.database) # universal_newlines=True)
# print "command = ", command # command = command + " %s" % (self.database)
# self.pipe_to_hud = os.popen(command, 'w') # print "command = ", command
# self.pipe_to_hud = os.popen(command, 'w')
# Add directories to importer object. # Add directories to importer object.
for site in self.input_settings: for site in self.input_settings:
self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1]) self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1])
print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0]) print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0])
self.do_import() self.do_import()
interval=int(self.intervalEntry.get_text()) interval=int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import) gobject.timeout_add(interval*1000, self.do_import)
#end def GuiAutoImport.startClicked 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')
def get_vbox(self):
"""returns the vbox of this thread"""
return self.mainVBox
#end def get_vbox
#Create the site line given required info and setup callbacks
#enabling and disabling sites from this interface not possible
#expects a box to layout the line horizontally
def createSiteLine(self, hbox, site, iconpath, hhpath, filter_name, active = True):
label = gtk.Label(site + " auto-import:")
hbox.pack_start(label, False, False, 0)
label.show()
dirPath=gtk.Entry() #end def GuiAutoImport.startClicked
dirPath.set_text(hhpath)
hbox.pack_start(dirPath, False, True, 0)
dirPath.show()
browseButton=gtk.Button("Browse...") def get_vbox(self):
browseButton.connect("clicked", self.browseClicked, [site] + [dirPath]) """returns the vbox of this thread"""
hbox.pack_start(browseButton, False, False, 0) return self.mainVBox
browseButton.show() #end def get_vbox
label = gtk.Label(site + " filter:") #Create the site line given required info and setup callbacks
hbox.pack_start(label, False, False, 0) #enabling and disabling sites from this interface not possible
label.show() #expects a box to layout the line horizontally
def createSiteLine(self, hbox, site, iconpath, hhpath, filter_name, active = True):
label = gtk.Label(site + " auto-import:")
hbox.pack_start(label, False, False, 0)
label.show()
filter=gtk.Entry() dirPath=gtk.Entry()
filter.set_text(filter_name) dirPath.set_text(hhpath)
hbox.pack_start(filter, False, True, 0) hbox.pack_start(dirPath, False, True, 0)
filter.show() dirPath.show()
def addSites(self, vbox): browseButton=gtk.Button("Browse...")
for site in self.config.supported_sites.keys(): browseButton.connect("clicked", self.browseClicked, [site] + [dirPath])
pathHBox = gtk.HBox(False, 0) hbox.pack_start(browseButton, False, False, 0)
vbox.pack_start(pathHBox, False, True, 0) browseButton.show()
pathHBox.show()
paths = self.config.get_default_paths(site) label = gtk.Label(site + " filter:")
params = self.config.get_site_parameters(site) hbox.pack_start(label, False, False, 0)
self.createSiteLine(pathHBox, site, False, paths['hud-defaultPath'], params['converter'], params['enabled']) label.show()
self.input_settings[site] = [paths['hud-defaultPath']] + [params['converter']]
filter=gtk.Entry()
filter.set_text(filter_name)
hbox.pack_start(filter, False, True, 0)
filter.show()
def addSites(self, vbox):
for site in self.config.supported_sites.keys():
pathHBox = gtk.HBox(False, 0)
vbox.pack_start(pathHBox, False, True, 0)
pathHBox.show()
paths = self.config.get_default_paths(site)
params = self.config.get_site_parameters(site)
self.createSiteLine(pathHBox, site, False, paths['hud-defaultPath'], params['converter'], params['enabled'])
self.input_settings[site] = [paths['hud-defaultPath']] + [params['converter']]
if __name__== "__main__": if __name__== "__main__":
def destroy(*args): # call back for terminating the main eventloop def destroy(*args): # call back for terminating the main eventloop

View File

@ -50,22 +50,27 @@ class GuiGraphViewer (threading.Thread):
try: self.canvas.destroy() try: self.canvas.destroy()
except AttributeError: pass except AttributeError: pass
# Whaich sites are selected? sitenos = []
# TODO: playerids = []
# What hero names for the selected site?
# TODO:
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": if sitenos == []:
site=2 #Should probably pop up here.
sitename="PokerStars: " print "No sites selected - defaulting to PokerStars"
elif self.sites=="Full Tilt": sitenos = [2]
site=1
sitename="Full Tilt: "
else: if playerids == []:
print "invalid text in site selection in graph, defaulting to PS" print "No player ids found"
site=2 return
self.fig = Figure(figsize=(5,4), dpi=100) self.fig = Figure(figsize=(5,4), dpi=100)
@ -74,7 +79,7 @@ class GuiGraphViewer (threading.Thread):
#Get graph data from DB #Get graph data from DB
starttime = time() starttime = time()
line = self.getRingProfitGraph(name, site) line = self.getRingProfitGraph(playerids, sitenos)
print "Graph generated in: %s" %(time() - starttime) print "Graph generated in: %s" %(time() - starttime)
self.ax.set_title("Profit graph for ring games") self.ax.set_title("Profit graph for ring games")
@ -87,7 +92,8 @@ class GuiGraphViewer (threading.Thread):
#TODO: Do something useful like alert user #TODO: Do something useful like alert user
print "No hands returned by graph query" print "No hands returned by graph query"
else: 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, self.ax.annotate(text,
xy=(10, -10), xy=(10, -10),
@ -103,8 +109,34 @@ class GuiGraphViewer (threading.Thread):
self.canvas.show() self.canvas.show()
#end of def showClicked #end of def showClicked
def getRingProfitGraph(self, name, site): def getRingProfitGraph(self, names, sites):
self.cursor.execute(self.sql.query['getRingProfitAllHandsPlayerIdSite'], (name, site)) 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) #returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall() winnings = self.db.cursor.fetchall()
@ -125,7 +157,6 @@ class GuiGraphViewer (threading.Thread):
pname.set_text(player) pname.set_text(player)
pname.set_width_chars(20) pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0) hbox.pack_start(pname, False, True, 0)
#TODO: Need to connect a callback here
pname.connect("changed", self.__set_hero_name, site) pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames #TODO: Look at GtkCompletion - to fill out usernames
pname.show() pname.show()
@ -134,7 +165,7 @@ class GuiGraphViewer (threading.Thread):
def __set_hero_name(self, w, site): def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text() 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): def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site) cb = gtk.CheckButton(site)
@ -144,8 +175,9 @@ class GuiGraphViewer (threading.Thread):
def __set_site_select(self, w, site): def __set_site_select(self, w, site):
# This doesn't behave as intended - self.site only allows 1 site for the moment. # This doesn't behave as intended - self.site only allows 1 site for the moment.
self.sites = site print w.get_active()
print "self.sites set to %s" %(self.sites) self.sites[site] = w.get_active()
print "self.sites[%s] set to %s" %(site, self.sites[site])
def fillPlayerFrame(self, vbox): def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys(): for site in self.conf.supported_sites.keys():
@ -162,6 +194,13 @@ class GuiGraphViewer (threading.Thread):
vbox.pack_start(hbox, False, True, 0) vbox.pack_start(hbox, False, True, 0)
hbox.show() hbox.show()
self.createSiteLine(hbox, site) 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): def fillDateFrame(self, vbox):
# Hat tip to Mika Bostrom - calendar code comes from PokerStats # Hat tip to Mika Bostrom - calendar code comes from PokerStats
@ -261,7 +300,8 @@ class GuiGraphViewer (threading.Thread):
self.sql=querylist self.sql=querylist
self.conf = config self.conf = config
self.sites = "PokerStars" self.sites = {}
self.siteid = {}
self.heroes = {} self.heroes = {}
# For use in date ranges. # For use in date ranges.

View File

@ -60,7 +60,7 @@ class GuiPlayerStats (threading.Thread):
vbox.add(self.stats_table) vbox.add(self.stats_table)
# Create header row # 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 col = 0
row = 0 row = 0
@ -71,14 +71,17 @@ class GuiPlayerStats (threading.Thread):
col +=1 col +=1
for row in range(rows-1): for row in range(rows-1):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
for col in range(cols): for col in range(cols):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
eb = gtk.EventBox() eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor)) eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
l = gtk.Label(result[row][col]) if result[row][col]:
l = gtk.Label(result[row][col])
else:
l = gtk.Label(' ')
if col == 0: if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5) l.set_alignment(xalign=0.0, yalign=0.5)
else: else:
@ -127,7 +130,6 @@ class GuiPlayerStats (threading.Thread):
def __set_hero_name(self, w, site): def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text() self.heroes[site] = w.get_text()
print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def __init__(self, db, config, querylist, debug=True): def __init__(self, db, config, querylist, debug=True):
self.debug=debug self.debug=debug

View File

@ -49,7 +49,7 @@
<location seat="2" x="10" y="288"> </location> <location seat="2" x="10" y="288"> </location>
</layout> </layout>
</site> </site>
<site enabled="True" site_name="Full Tilt" table_finder="FullTiltPoker.exe" screen_name="ENTER HERO NAME" site_path="~/.wine/drive_c/Program Files/Full Tilt Poker/" HH_path="~/.wine/drive_c/Program Files/Full Tilt Poker/HandHistory/abc/" decoder="fulltilt_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo"> <site enabled="True" site_name="Full Tilt Poker" table_finder="FullTiltPoker.exe" screen_name="ENTER HERO NAME" site_path="~/.wine/drive_c/Program Files/Full Tilt Poker/" HH_path="~/.wine/drive_c/Program Files/Full Tilt Poker/HandHistory/abc/" decoder="fulltilt_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo">
<layout fav_seat="0" height="547" max="8" width="794"> <layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location> <location seat="1" x="640" y="64"> </location>
<location seat="2" x="650" y="230"> </location> <location seat="2" x="650" y="230"> </location>

View File

@ -27,18 +27,19 @@ import xml.dom.minidom
import codecs import codecs
from decimal import Decimal from decimal import Decimal
import operator import operator
from time import time import time
from copy import deepcopy
class Hand: class Hand:
# def __init__(self, sitename, gametype, sb, bb, string): # 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): def __init__(self, sitename, gametype, string):
self.sitename = sitename self.sitename = sitename
self.gametype = gametype self.gametype = gametype
self.string = string 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.handid = 0
self.sb = gametype[3] self.sb = gametype[3]
@ -78,6 +79,8 @@ class Hand:
# dict from player names to lists of hole cards # dict from player names to lists of hole cards
self.holecards = {} self.holecards = {}
self.stacks = {}
# dict from player names to amounts collected # dict from player names to amounts collected
self.collected = {} self.collected = {}
@ -87,6 +90,8 @@ class Hand:
self.action = [] self.action = []
self.totalpot = None self.totalpot = None
self.totalcollected = None
self.rake = None self.rake = None
self.bets = {} self.bets = {}
@ -104,37 +109,49 @@ 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 a player has None chips he won't be added."""
if chips is not None: if chips is not None:
self.players.append([seat, name, chips]) self.players.append([seat, name, chips])
self.holecards[name] = [] self.stacks[name] = Decimal(chips)
self.holecards[name] = set()
for street in self.streetList: for street in self.streetList:
self.bets[street][name] = [] 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): def addHoleCards(self, cards, player):
"""\ """\
Assigns observed holecards to a 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 player (string) name of player
hand
Note, will automatically uppercase the rank letter.
""" """
print "DEBUG: addHoleCards", cards,player
try: try:
self.checkPlayerExists(player) 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: 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): def addShownCards(self, cards, player, holeandboard=None):
"""\ """\
For when a player shows cards for any reason (for showdown or out of choice). 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: if cards is not None:
self.shown.add(player) self.shown.add(player)
self.addHoleCards(cards,player) self.addHoleCards(cards,player)
elif holeandboard is not None: 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]) 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) self.addHoleCards(holeandboard.difference(board),player)
@ -150,7 +167,7 @@ For when a player shows cards for any reason (for showdown or out of choice).
except FpdbParseError, e: except FpdbParseError, e:
pass pass
except ValueError: 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): def setCommunityCards(self, street, cards):
self.board[street] = [self.card(c) for c in cards] self.board[street] = [self.card(c) for c in cards]
@ -161,11 +178,19 @@ For when a player shows cards for any reason (for showdown or out of choice).
c = c.replace(k,v) c = c.replace(k,v)
return c return c
def addBlind(self, player, amount): def addBlind(self, player, blindtype, amount):
# if player is None, it's a missing small blind. # if player is None, it's a missing small blind.
print "DEBUG addBlind: %s posts %s, %s" % (player, blindtype, amount)
if player is not None: if player is not None:
self.bets['PREFLOP'][player].append(Decimal(amount)) self.bets['PREFLOP'][player].append(Decimal(amount))
self.lastBet['PREFLOP'] = Decimal(amount) self.stacks[player] -= Decimal(amount)
#print "DEBUG %s posts, stack %s" % (player, self.stacks[player])
self.actions['PREFLOP'] += [(player, 'posts', blindtype, amount, self.stacks[player]==0)]
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] self.posted += [player]
@ -175,52 +200,99 @@ For when a player shows cards for any reason (for showdown or out of choice).
if amount is not None: if amount is not None:
self.bets[street][player].append(Decimal(amount)) self.bets[street][player].append(Decimal(amount))
#self.lastBet[street] = 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])
self.actions[street] += [(player, 'calls', amount, self.stacks[player]==0)]
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 _addRaise(self, street, player, C, Rb, Rt):
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 addRaiseTo(self, street, player, amountTo): def addRaiseTo(self, street, player, amountTo):
"""\ """\
Add a raise on [street] by [player] to [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) self.checkPlayerExists(player)
committedThisStreet = reduce(operator.add, self.bets[street][player], 0) Bc = reduce(operator.add, self.bets[street][player], 0)
amountToCall = self.lastBet[street] - committedThisStreet Rt = Decimal(amountTo)
self.lastBet[street] = Decimal(amountTo) C = Bp - Bc
amountBy = Decimal(amountTo) - amountToCall Rb = Rt - C
self.bets[street][player].append(amountBy+amountToCall) self._addRaise(street, player, C, Rb, Rt)
self.actions[street] += [[player, 'raises', amountBy, amountTo]]
def addBet(self, street, player, amount): def addBet(self, street, player, amount):
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.bets[street][player].append(Decimal(amount)) 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])
self.actions[street] += [(player, 'bets', amount, self.stacks[player]==0)]
self.lastBet[street] = Decimal(amount)
def addFold(self, street, player): def addFold(self, street, player):
print "DEBUG: %s %s folded" % (street, player)
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.folded.add(player) self.folded.add(player)
self.actions[street] += [[player, 'folds']] self.actions[street] += [(player, 'folds')]
def addCheck(self, street, player): def addCheck(self, street, player):
print "DEBUG: %s %s checked" % (street, player)
self.checkPlayerExists(player) self.checkPlayerExists(player)
self.actions[street] += [[player, 'checks']] self.actions[street] += [(player, 'checks')]
def addCollectPot(self,player, pot): def addCollectPot(self,player, pot):
print "DEBUG: %s collected %s" % (player, pot)
self.checkPlayerExists(player) self.checkPlayerExists(player)
if player not in self.collected: if player not in self.collected:
self.collected[player] = pot self.collected[player] = pot
else: else:
# possibly lines like "p collected $ from pot" appear during the showdown print "[WARNING] %s collected pot more than once; avoidable by reading winnings only from summary lines?"
# 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): def totalPot(self):
"""If all bets and blinds have been added, totals up the total pot size """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: if self.totalpot is None:
self.totalpot = 0 self.totalpot = 0
@ -231,6 +303,63 @@ Known bug: doesn't take into account side pots"""
#print street, self.bets[street][player] #print street, self.bets[street][player]
self.totalpot += reduce(operator.add, self.bets[street][player], 0) self.totalpot += reduce(operator.add, self.bets[street][player], 0)
print "DEBUG conventional totalpot:", self.totalpot
self.totalpot = 0
for street in self.actions:
uncalled = 0
calls = [0]
for act in self.actions[street]:
if act[1] == 'bets': # [name, 'bets', amount]
self.totalpot += Decimal(act[2])
uncalled = Decimal(act[2]) # only the last bet or raise can be uncalled
calls = [0]
print "uncalled: ", uncalled
elif act[1] == 'raises': # [name, 'raises', amountby, amountto, amountcalled]
print "calls %s and raises %s to %s" % (act[4],act[2],act[3])
self.totalpot += Decimal(act[2]) + Decimal(act[4])
calls = [0]
uncalled = Decimal(act[2])
print "uncalled: ", uncalled
elif act[1] == 'calls': # [name, 'calls', amount]
self.totalpot += Decimal(act[2])
calls = calls + [Decimal(act[2])]
print "calls:", calls
elif act[1] == 'posts':
self.totalpot += Decimal(act[3])
if act[2] == 'big blind':
# the bb gets called by out-of-blinds posts; but sb+bb only calls bb
if uncalled == Decimal(act[3]): # a bb is already posted
calls = calls + [Decimal(act[3])]
elif 0 < uncalled < Decimal(act[3]): # a sb is already posted, btw wow python can do a<b<c.
# treat this as tho called & raised
calls = [0]
uncalled = Decimal(act[3]) - uncalled
else: # no blind yet posted.
uncalled = Decimal(act[3])
elif act[2] == 'small blind':
uncalled = Decimal(act[3])
calls = [0]
pass
if uncalled > 0 and max(calls+[0]) < uncalled:
print "DEBUG returning some bet, calls:", calls
print "DEBUG returned: %.2f from %.2f" % ((uncalled - max(calls)), self.totalpot,)
self.totalpot -= (uncalled - max(calls))
print "DEBUG new totalpot:", self.totalpot
if self.totalcollected is None:
self.totalcollected = 0;
for amount in self.collected.values():
self.totalcollected += Decimal(amount)
def getGameTypeAsString(self): def getGameTypeAsString(self):
"""\ """\
Map the tuple self.gametype onto the pokerstars string describing it Map the tuple self.gametype onto the pokerstars string describing it
@ -258,80 +387,88 @@ Map the tuple self.gametype onto the pokerstars string describing it
return string return string
def printHand(self): def writeHand(self, fh=sys.__stdout__):
# PokerStars format. # PokerStars format.
print "\n### Pseudo stars 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 >>fh, _("%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) 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)))
for player in self.players: print >>fh, _("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos))
print "Seat %s: %s ($%s)" %(player[0], player[1], player[2])
players_who_act_preflop = set([x[0] for x in self.actions['PREFLOP']])
#print players_who_act_preflop
#print [x[1] for x in self.players]
#print [x for x in self.players if x[1] in players_who_act_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): if(self.posted[0] is None):
print "No small blind posted" #print >>fh, _("No small blind posted") # PS doesn't say this
pass
else: 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 #May be more than 1 bb posting
for a in self.posted[1:]: 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: 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: if 'PREFLOP' in self.actions:
for act in self.actions['PREFLOP']: for act in self.actions['PREFLOP']:
self.printActionLine(act) self.printActionLine(act, fh)
if 'FLOP' in self.actions: 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']: for act in self.actions['FLOP']:
self.printActionLine(act) self.printActionLine(act, fh)
if 'TURN' in self.actions: 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']: for act in self.actions['TURN']:
self.printActionLine(act) self.printActionLine(act, fh)
if 'RIVER' in self.actions: 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']: 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 #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 # 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 # we probably don't need a showdown section in pseudo stars format for our filtering purposes
if 'SHOWDOWN' in self.actions: if 'SHOWDOWN' in self.actions:
print "*** SHOW DOWN ***" print >>fh, _("*** SHOW DOWN ***")
print "what do they show" print >>fh, "DEBUG: what do they show"
print >>fh, _("*** SUMMARY ***")
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 = [] board = []
for s in self.board.values(): for s in self.board.values():
board += s board += s
if board: # sometimes hand ends preflop without a board if board: # sometimes hand ends preflop without a board
print "Board [%s]" % (" ".join(board)) print >>fh, _("Board [%s]" % (" ".join(board)))
for player in [x for x in self.players if x[1] in players_who_act_preflop]:
for player in self.players:
seatnum = player[0] seatnum = player[0]
name = player[1] name = player[1]
if name in self.collected and self.holecards[name]: if name in self.collected and name in self.shown:
print "Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]), self.collected[name]) print >>fh, _("Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]), self.collected[name]))
elif name in self.collected: elif name in self.collected:
print "Seat %d: %s collected ($%s)" % (seatnum, name, self.collected[name]) print >>fh, _("Seat %d: %s collected ($%s)" % (seatnum, name, self.collected[name]))
elif player[1] in self.shown: elif name in self.shown:
print "Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name])) print >>fh, _("Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name])))
elif player[1] in self.folded: elif name in self.folded:
print "Seat %d: %s folded" % (seatnum, name) print >>fh, _("Seat %d: %s folded" % (seatnum, name))
else: else:
print "Seat %d: %s mucked" % (seatnum, name) print >>fh, _("Seat %d: %s mucked" % (seatnum, name))
print print >>fh, "\n\n"
# TODO: # TODO:
# logic for side pots # logic for side pots
# logic for which players get to showdown # logic for which players get to showdown
@ -348,15 +485,20 @@ Map the tuple self.gametype onto the pokerstars string describing it
#print "Seat %d: %s mucked or folded" % (player[0], player[1]) #print "Seat %d: %s mucked or folded" % (player[0], player[1])
def printActionLine(self, act): def printHand(self):
if act[1] == 'folds' or act[1] == 'checks': self.writeHand(sys.stdout)
print "%s: %s " %(act[0], act[1])
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': 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': 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': 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. # going to use pokereval to figure out hands at some point.
# these functions are copied from pokergame.py # these functions are copied from pokergame.py

View File

@ -29,7 +29,10 @@ from decimal import Decimal
import operator import operator
from xml.dom.minidom import Node from xml.dom.minidom import Node
from pokereval import PokerEval from pokereval import PokerEval
from time import time import time
import datetime
import gettext
#from pokerengine.pokercards import * #from pokerengine.pokercards import *
# provides letter2name{}, letter2names{}, visible_card(), not_visible_card(), is_visible(), card_value(), class PokerCards # 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: # but it's probably not installed so here are the ones we may want:
@ -65,6 +68,11 @@ letter2names = {
'2': 'Deuces' '2': 'Deuces'
} }
import gettext
gettext.install('myapplication')
class HandHistoryConverter: class HandHistoryConverter:
eval = PokerEval() eval = PokerEval()
def __init__(self, config, file, sitename): def __init__(self, config, file, sitename):
@ -97,7 +105,7 @@ class HandHistoryConverter:
return tmp return tmp
def processFile(self): def processFile(self):
starttime = time() starttime = time.time()
if not self.sanityCheck(): if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check" print "Cowardly refusing to continue after failed sanity check"
return return
@ -108,30 +116,35 @@ class HandHistoryConverter:
print "\nInput:\n"+hand.string print "\nInput:\n"+hand.string
self.readHandInfo(hand) self.readHandInfo(hand)
self.readPlayerStacks(hand) self.readPlayerStacks(hand)
print "DEBUG stacks:", hand.stacks
self.markStreets(hand) self.markStreets(hand)
self.readBlinds(hand) self.readBlinds(hand)
self.readHeroCards(hand) # want to generalise to draw games self.readHeroCards(hand) # want to generalise to draw games
self.readCommunityCards(hand) # read community cards
self.readShowdownActions(hand) 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: if hand.streets.group(street) is not None:
self.readCommunityCards(hand, street) # read community cards
self.readAction(hand, street) self.readAction(hand, street)
self.readCollectPot(hand) self.readCollectPot(hand)
self.readShownCards(hand)
# finalise it (total the pot) # finalise it (total the pot)
hand.totalPot() hand.totalPot()
self.getRake(hand) self.getRake(hand)
hand.printHand() hand.writeHand(sys.stderr)
#if(hand.involved == True): #if(hand.involved == True):
#self.writeHand("output file", hand) #self.writeHand("output file", hand)
#hand.printHand() #hand.printHand()
#else: #else:
#pass #Don't write out observed hands #pass #Don't write out observed hands
endtime = time() endtime = time.time()
print "Processed %d hands in %d seconds" % (len(self.hands), endtime-starttime) print "Processed %d hands in %d seconds" % (len(self.hands), endtime-starttime)
##### #####
@ -161,7 +174,8 @@ class HandHistoryConverter:
def readPlayerStacks(self, hand): abstract def readPlayerStacks(self, hand): abstract
# Needs to return a MatchObject with group names identifying the streets into the Hand object # 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 def markStreets(self, hand): abstract
#Needs to return a list in the format #Needs to return a list in the format
@ -171,13 +185,16 @@ class HandHistoryConverter:
def readHeroCards(self, hand): abstract def readHeroCards(self, hand): abstract
def readAction(self, hand, street): abstract def readAction(self, hand, street): abstract
def readCollectPot(self, hand): 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 # 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. # an inheriting class can calculate it for the specific site if need be.
def getRake(self, hand): abstract def getRake(self, hand):
hand.rake = hand.totalpot - hand.totalcollected # * Decimal('0.05') # probably not quite right
def sanityCheck(self): def sanityCheck(self):
sane = True sane = False
base_w = False base_w = False
#Check if hhbase exists and is writable #Check if hhbase exists and is writable
#Note: Will not try to create the base HH directory #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) self.main_window.set_destroy_with_parent(True)
def update_table_position(self): 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() (x, y) = self.main_window.parentgdkhandle.get_origin()
if self.table.x != x or self.table.y != y: if self.table.x != x or self.table.y != y:
self.table.x = x self.table.x = x
@ -234,7 +241,8 @@ class Hud:
aux_params = config.get_aux_parameters(game_params['aux']) 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']))) 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): def update(self, hand, config, stat_dict):
self.hand = hand # this is the last hand, so it is available later 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['createHandsPlayersTable'])
self.cursor.execute(self.sql.query['createHandsActionsTable']) self.cursor.execute(self.sql.query['createHandsActionsTable'])
self.cursor.execute(self.sql.query['createHudCacheTable']) self.cursor.execute(self.sql.query['createHudCacheTable'])
self.cursor.execute(self.sql.query['addTourneyIndex']) #self.cursor.execute(self.sql.query['addTourneyIndex'])
self.cursor.execute(self.sql.query['addHandsIndex']) #self.cursor.execute(self.sql.query['addHandsIndex'])
self.cursor.execute(self.sql.query['addPlayersIndex']) #self.cursor.execute(self.sql.query['addPlayersIndex'])
self.fillDefaultData() self.fillDefaultData()
self.db.commit() self.db.commit()
#end def disconnect #end def disconnect
@ -177,6 +177,7 @@ class fpdb_db:
self.cursor.execute("INSERT INTO Settings VALUES (118);") 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, 'Full Tilt Poker', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', '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);") self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
#end def fillDefaultData #end def fillDefaultData
@ -185,6 +186,7 @@ class fpdb_db:
self.drop_tables() self.drop_tables()
self.create_tables() self.create_tables()
fpdb_simple.createAllIndexes(self)
self.db.commit() self.db.commit()
print "Finished recreating tables" print "Finished recreating tables"
#end def recreate_tables #end def recreate_tables

View File

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

View File

@ -22,6 +22,11 @@ from time import time
import fpdb_simple 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 #stores a stud/razz hand into the database
def ring_stud(backend, db, cursor, base, category, site_hand_no, gametype_id, hand_start_time 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 ,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes
@ -39,8 +44,9 @@ 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) fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData)
fpdb_simple.storeActions(cursor, hands_players_ids, action_types if saveActions:
,allIns, action_amounts, actionNos) fpdb_simple.storeActions(cursor, hands_players_ids, action_types
,allIns, action_amounts, actionNos)
return hands_id return hands_id
#end def ring_stud #end def ring_stud
@ -66,10 +72,10 @@ def ring_holdem_omaha(backend, db, cursor, base, category, site_hand_no, gametyp
t5 = time() t5 = time()
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
t6 = time() t6 = time()
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos) if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
t7 = time() t7 = time()
print "cards=%4.3f board=%4.3f hands=%4.3f plyrs=%4.3f hudcache=%4.3f board=%4.3f actions=%4.3f" \ #print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4, t6-t5)
% (t1-t0, t2-t1, t3-t2, t4-t3, t5-t4, t6-t5, t7-t6)
return hands_id return hands_id
#end def ring_holdem_omaha #end def ring_holdem_omaha
@ -98,7 +104,8 @@ def tourney_holdem_omaha(backend, db, cursor, base, category, siteTourneyNo, buy
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos) if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
return hands_id return hands_id
#end def tourney_holdem_omaha #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) fpdb_simple.storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData)
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos) if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos)
return hands_id return hands_id
#end def tourney_stud #end def tourney_stud

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

@ -26,6 +26,308 @@ FTP=2
MYSQL_INNODB=2 MYSQL_INNODB=2
PGSQL=3 PGSQL=3
SQLITE=4 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): class DuplicateError(Exception):
@ -90,7 +392,7 @@ def checkPositions(positions):
pass pass
### RHH modified to allow for "position 9" here (pos==9 is when you're a dead hand before the BB ### 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)) raise FpdbError("invalid position found in checkPositions. i: "+str(i)+" position: "+str(pos))
#end def fpdb_simple.checkPositions #end def fpdb_simple.checkPositions

Binary file not shown.