Merge branch 'master' of git://git.assembla.com/free_poker_tools
This commit is contained in:
commit
be116905c4
|
@ -73,7 +73,7 @@ class Absolute(HandHistoryConverter):
|
|||
#self.re_PostSB = re.compile(ur"^%s: posts small blind \[(?:\$| €|) (?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_PostBB = re.compile(ur"^%s: posts big blind \[(?:\$| €|) (?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_PostBoth = re.compile(ur"^%s: posts both blinds \[(?:\$| €|) (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_Antes = re.compile(ur"^%s: posts ante \[(?:\$| €|) (?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
self.re_Antes = re.compile(ur"^%s - Ante \[(?:\$| €|)(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
|
||||
#self.re_BringIn = re.compile(ur"^%s posts bring-in (?:\$| €|)(?P<BRINGIN>[.0-9]+)\." % player_re, re.MULTILINE)
|
||||
self.re_HeroCards = re.compile(ur"^Dealt to %s \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
|
||||
#self.re_Action = re.compile(ur"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P<BET>[.\d]+) (USD|EUR|)\])?" % player_re, re.MULTILINE)
|
||||
|
|
|
@ -47,13 +47,14 @@ class Fulltilt(HandHistoryConverter):
|
|||
re_TailSplitHands = re.compile(r"(\n\n+)")
|
||||
re_HandInfo = re.compile(r'''.*\#(?P<HID>[0-9]+):\s
|
||||
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
|
||||
Table\s
|
||||
(Table|Match)\s
|
||||
(?P<PLAY>Play\sChip\s|PC)?
|
||||
(?P<TABLE>[-\s\da-zA-Z]+)\s
|
||||
(\((?P<TABLEATTRIBUTES>.+)\)\s)?-\s
|
||||
\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\s(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
|
||||
(?P<GAMETYPE>[a-zA-Z\/\'\s]+)\s-\s
|
||||
(?P<DATETIME>.*?)\n
|
||||
(?P<DATETIME>\d+:\d+:\d+\s\w+\s-\s\d+/\d+/\d+)\s?
|
||||
(?P<PARTIAL>\(partial\))?\n
|
||||
(?:.*?\n(?P<CANCELLED>Hand\s\#(?P=HID)\shas\sbeen\scanceled))?
|
||||
''', re.VERBOSE|re.DOTALL)
|
||||
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
|
||||
|
@ -61,6 +62,46 @@ class Fulltilt(HandHistoryConverter):
|
|||
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)', re.MULTILINE)
|
||||
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
|
||||
|
||||
#static regex for tourney purpose
|
||||
re_TourneyInfo = re.compile('''Tournament\sSummary\s
|
||||
(?P<TOURNAMENT_NAME>[^$(]+)?\s*
|
||||
((?P<CURRENCY>\$|)?(?P<BUYIN>[.0-9]+)\s*\+\s*\$?(?P<FEE>[.0-9]+)\s)?
|
||||
((?P<SPECIAL>(KO|Heads\sUp|Matrix\s\dx|Rebuy))\s)?
|
||||
((?P<SHOOTOUT>Shootout)\s)?
|
||||
((?P<SNG>Sit\s&\sGo)\s)?
|
||||
(\((?P<TURBO1>Turbo)\)\s)?
|
||||
\((?P<TOURNO>\d+)\)\s
|
||||
((?P<MATCHNO>Match\s\d)\s)?
|
||||
(?P<GAME>(Hold\'em|Omaha\sHi|Omaha\sH/L|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))\s
|
||||
(\((?P<TURBO2>Turbo)\)\s)?
|
||||
(?P<LIMIT>(No\sLimit|Pot\sLimit|Limit))?
|
||||
''', re.VERBOSE)
|
||||
re_TourneyBuyInFee = re.compile("Buy-In: (?P<BUYIN_CURRENCY>\$|)?(?P<BUYIN>[.0-9]+) \+ \$?(?P<FEE>[.0-9]+)")
|
||||
re_TourneyBuyInChips = re.compile("Buy-In Chips: (?P<BUYINCHIPS>\d+)")
|
||||
re_TourneyEntries = re.compile("(?P<ENTRIES>\d+) Entries")
|
||||
re_TourneyPrizePool = re.compile("Total Prize Pool: (?P<PRIZEPOOL_CURRENCY>\$|)?(?P<PRIZEPOOL>[.,0-9]+)")
|
||||
re_TourneyRebuyAmount = re.compile("Rebuy: (?P<REBUY_CURRENCY>\$|)?(?P<REBUY_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyAddOnAmount = re.compile("Add-On: (?P<ADDON_CURRENCY>\$|)?(?P<ADDON_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyRebuyCount = re.compile("performed (?P<REBUY_COUNT>\d+) Rebuy")
|
||||
re_TourneyAddOnCount = re.compile("performed (?P<ADDON_COUNT>\d+) Add-On")
|
||||
re_TourneyRebuysTotal = re.compile("Total Rebuys: (?P<REBUY_TOTAL>\d+)")
|
||||
re_TourneyAddOnsTotal = re.compile("Total Add-Ons: (?P<ADDONS_TOTAL>\d+)")
|
||||
re_TourneyRebuyChips = re.compile("Rebuy Chips: (?P<REBUY_CHIPS>\d+)")
|
||||
re_TourneyAddOnChips = re.compile("Add-On Chips: (?P<ADDON_CHIPS>\d+)")
|
||||
re_TourneyKOBounty = re.compile("Knockout Bounty: (?P<KO_BOUNTY_CURRENCY>\$|)?(?P<KO_BOUNTY_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyCountKO = re.compile("received (?P<COUNT_KO>\d+) Knockout Bounty Award(s)?")
|
||||
re_TourneyTimeInfo = re.compile("Tournament started: (?P<STARTTIME>.*)\nTournament ((?P<IN_PROGRESS>is still in progress)?|(finished:(?P<ENDTIME>.*))?)$")
|
||||
|
||||
re_TourneyPlayersSummary = re.compile("^(?P<RANK>(Still Playing|\d+))( - |: )(?P<PNAME>[^\n,]+)(, )?(?P<WINNING_CURRENCY>\$|)?(?P<WINNING>[.\d]+)?", re.MULTILINE)
|
||||
re_TourneyHeroFinishingP = re.compile("(?P<HERO_NAME>.*) finished in (?P<HERO_FINISHING_POS>\d+)(st|nd|rd|th) place")
|
||||
|
||||
#TODO: See if we need to deal with play money tourney summaries -- Not right now (they shouldn't pass the re_TourneyInfo)
|
||||
##Full Tilt Poker Tournament Summary 250 Play Money Sit & Go (102909471) Hold'em No Limit
|
||||
##Buy-In: 250 Play Chips + 0 Play Chips
|
||||
##Buy-In Chips: 1500
|
||||
##6 Entries
|
||||
##Total Prize Pool: 1,500 Play Chips
|
||||
|
||||
# These regexes are for FTP only
|
||||
re_Mixed = re.compile(r'\s\-\s(?P<MIXED>HA|HORSE|HOSE)\s\-\s', re.VERBOSE)
|
||||
re_Max = re.compile("(?P<MAX>\d+)( max)?", re.MULTILINE)
|
||||
|
@ -116,7 +157,6 @@ class Fulltilt(HandHistoryConverter):
|
|||
if not m:
|
||||
return None
|
||||
mg = m.groupdict()
|
||||
print mg
|
||||
# translations from captured groups to our info strings
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
|
||||
games = { # base, category
|
||||
|
@ -151,7 +191,7 @@ class Fulltilt(HandHistoryConverter):
|
|||
hand.tablename = m.group('TABLE')
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
|
||||
|
||||
if m.group("CANCELLED"):
|
||||
if m.group("CANCELLED") or m.group("PARTIAL"):
|
||||
raise FpdbParseError(hid=m.group('HID'))
|
||||
|
||||
if m.group('TABLEATTRIBUTES'):
|
||||
|
@ -353,6 +393,225 @@ class Fulltilt(HandHistoryConverter):
|
|||
else:
|
||||
hand.mixed = self.mixes[m.groupdict()['MIXED']]
|
||||
|
||||
def readSummaryInfo(self, summaryInfoList):
|
||||
starttime = time.time()
|
||||
self.status = True
|
||||
|
||||
m = re.search("Tournament Summary", summaryInfoList[0])
|
||||
if m:
|
||||
# info list should be 2 lines : Tourney infos & Finsihing postions with winnings
|
||||
if (len(summaryInfoList) != 2 ):
|
||||
log.info("Too many lines (%d) in file '%s' : '%s'" % (len(summaryInfoList), self.in_path, summaryInfoList) )
|
||||
self.status = False
|
||||
else:
|
||||
self.tourney = Tourney.Tourney(sitename = self.sitename, gametype = None, summaryText = summaryInfoList, builtFrom = "HHC")
|
||||
self.status = self.determineTourneyType(self.tourney)
|
||||
if self.status == True :
|
||||
self.status = status = self.getPlayersPositionsAndWinnings(self.tourney)
|
||||
#print self.tourney
|
||||
else:
|
||||
log.info("Parsing NOK : rejected")
|
||||
else:
|
||||
log.info( "This is not a summary file : '%s'" % (self.in_path) )
|
||||
self.status = False
|
||||
|
||||
return self.status
|
||||
|
||||
def determineTourneyType(self, tourney):
|
||||
info = {'type':'tour'}
|
||||
tourneyText = tourney.summaryText[0]
|
||||
#print "Examine : '%s'" %(tourneyText)
|
||||
|
||||
m = self.re_TourneyInfo.search(tourneyText)
|
||||
if not m:
|
||||
log.info( "determineTourneyType : Parsing NOK" )
|
||||
return False
|
||||
mg = m.groupdict()
|
||||
#print mg
|
||||
|
||||
# translations from captured groups to our info strings
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
|
||||
games = { # base, category
|
||||
"Hold'em" : ('hold','holdem'),
|
||||
'Omaha Hi' : ('hold','omahahi'),
|
||||
'Omaha H/L' : ('hold','omahahilo'),
|
||||
'Razz' : ('stud','razz'),
|
||||
'Stud Hi' : ('stud','studhi'),
|
||||
'Stud H/L' : ('stud','studhilo')
|
||||
}
|
||||
currencies = { u' €':'EUR', '$':'USD', '':'T$' }
|
||||
info['limitType'] = limits[mg['LIMIT']]
|
||||
if mg['GAME'] is not None:
|
||||
(info['base'], info['category']) = games[mg['GAME']]
|
||||
if mg['CURRENCY'] is not None:
|
||||
info['currency'] = currencies[mg['CURRENCY']]
|
||||
if mg['TOURNO'] == None: info['type'] = "ring"
|
||||
else: info['type'] = "tour"
|
||||
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
|
||||
|
||||
# Info is now ready to be copied in the tourney object
|
||||
tourney.gametype = info
|
||||
|
||||
# Additional info can be stored in the tourney object
|
||||
if mg['BUYIN'] is not None:
|
||||
tourney.buyin = mg['BUYIN']
|
||||
tourney.fee = 0
|
||||
if mg['FEE'] is not None:
|
||||
tourney.fee = mg['FEE']
|
||||
if mg['TOURNAMENT_NAME'] is not None:
|
||||
# Tournament Name can have a trailing space at the end (depending on the tournament description)
|
||||
tourney.tourneyName = mg['TOURNAMENT_NAME'].rstrip()
|
||||
if mg['SPECIAL'] is not None:
|
||||
special = mg['SPECIAL']
|
||||
if special == "KO":
|
||||
tourney.isKO = True
|
||||
if special == "Heads Up":
|
||||
tourney.isHU = True
|
||||
tourney.maxseats = 2
|
||||
if re.search("Matrix", special):
|
||||
tourney.isMatrix = True
|
||||
if special == "Rebuy":
|
||||
tourney.isRebuy = True
|
||||
if mg['SHOOTOUT'] is not None:
|
||||
tourney.isShootout = True
|
||||
if mg['TURBO1'] is not None or mg['TURBO2'] is not None :
|
||||
tourney.speed = "Turbo"
|
||||
if mg['TOURNO'] is not None:
|
||||
tourney.tourNo = mg['TOURNO']
|
||||
else:
|
||||
log.info( "Unable to get a valid Tournament ID -- File rejected" )
|
||||
return False
|
||||
if tourney.isMatrix:
|
||||
if mg['MATCHNO'] is not None:
|
||||
tourney.matrixMatchId = mg['MATCHNO']
|
||||
else:
|
||||
tourney.matrixMatchId = 0
|
||||
|
||||
|
||||
# Get BuyIn/Fee
|
||||
# Try and deal with the different cases that can occur :
|
||||
# - No buy-in/fee can be on the first line (freerolls, Satellites sometimes ?, ...) but appears in the rest of the description ==> use this one
|
||||
# - Buy-In/Fee from the first line differs from the rest of the description :
|
||||
# * OK in matrix tourneys (global buy-in dispatched between the different matches)
|
||||
# * NOK otherwise ==> issue a warning and store specific data as if were a Matrix Tourney
|
||||
# - If no buy-in/fee can be found : assume it's a freeroll
|
||||
m = self.re_TourneyBuyInFee.search(tourneyText)
|
||||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if tourney.isMatrix :
|
||||
if mg['BUYIN'] is not None:
|
||||
tourney.subTourneyBuyin = mg['BUYIN']
|
||||
tourney.subTourneyFee = 0
|
||||
if mg['FEE'] is not None:
|
||||
tourney.subTourneyFee = mg['FEE']
|
||||
else :
|
||||
if mg['BUYIN'] is not None:
|
||||
if tourney.buyin is None:
|
||||
tourney.buyin = mg['BUYIN']
|
||||
else :
|
||||
if mg['BUYIN'] != tourney.buyin:
|
||||
log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (touney.buyin, mg['BUYIN']) )
|
||||
tourney.subTourneyBuyin = mg['BUYIN']
|
||||
if mg['FEE'] is not None:
|
||||
if tourney.fee is None:
|
||||
tourney.fee = mg['FEE']
|
||||
else :
|
||||
if mg['FEE'] != tourney.fee:
|
||||
log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (touney.fee, mg['FEE']) )
|
||||
tourney.subTourneyFee = mg['FEE']
|
||||
|
||||
if tourney.buyin is None:
|
||||
log.info( "Unable to affect a buyin to this tournament : assume it's a freeroll" )
|
||||
tourney.buyin = 0
|
||||
tourney.fee = 0
|
||||
else:
|
||||
if tourney.fee is None:
|
||||
#print "Couldn't initialize fee, even though buyin went OK : assume there are no fees"
|
||||
tourney.fee = 0
|
||||
|
||||
#Get single line infos
|
||||
dictRegex = { "BUYINCHIPS" : self.re_TourneyBuyInChips,
|
||||
"ENTRIES" : self.re_TourneyEntries,
|
||||
"PRIZEPOOL" : self.re_TourneyPrizePool,
|
||||
"REBUY_AMOUNT" : self.re_TourneyRebuyAmount,
|
||||
"ADDON_AMOUNT" : self.re_TourneyAddOnAmount,
|
||||
"REBUY_COUNT" : self.re_TourneyRebuyCount,
|
||||
"ADDON_COUNT" : self.re_TourneyAddOnCount,
|
||||
"REBUY_TOTAL" : self.re_TourneyRebuysTotal,
|
||||
"ADDONS_TOTAL" : self.re_TourneyAddOnsTotal,
|
||||
"REBUY_CHIPS" : self.re_TourneyRebuyChips,
|
||||
"ADDON_CHIPS" : self.re_TourneyAddOnChips,
|
||||
"STARTTIME" : self.re_TourneyTimeInfo,
|
||||
"KO_BOUNTY_AMOUNT" : self.re_TourneyKOBounty,
|
||||
"COUNT_KO" : self.re_TourneyCountKO
|
||||
}
|
||||
|
||||
|
||||
dictHolders = { "BUYINCHIPS" : "buyInChips",
|
||||
"ENTRIES" : "entries",
|
||||
"PRIZEPOOL" : "prizepool",
|
||||
"REBUY_AMOUNT" : "rebuyAmount",
|
||||
"ADDON_AMOUNT" : "addOnAmount",
|
||||
"REBUY_COUNT" : "countRebuys",
|
||||
"ADDON_COUNT" : "countAddOns",
|
||||
"REBUY_TOTAL" : "totalRebuys",
|
||||
"ADDONS_TOTAL" : "totalAddOns",
|
||||
"REBUY_CHIPS" : "rebuyChips",
|
||||
"ADDON_CHIPS" : "addOnChips",
|
||||
"STARTTIME" : "starttime",
|
||||
"KO_BOUNTY_AMOUNT" : "koBounty",
|
||||
"COUNT_KO" : "countKO"
|
||||
}
|
||||
|
||||
mg = {} # After the loop, mg will contain all the matching groups, including the ones that have not been used, like ENDTIME and IN-PROGRESS
|
||||
for data in dictRegex:
|
||||
m = dictRegex.get(data).search(tourneyText)
|
||||
if m is not None:
|
||||
mg.update(m.groupdict())
|
||||
setattr(tourney, dictHolders[data], mg[data])
|
||||
|
||||
if mg['IN_PROGRESS'] is not None or mg['ENDTIME'] is not None:
|
||||
# Assign endtime to tourney (if None, that's ok, it's because the tourney wans't over over when the summary file was produced)
|
||||
tourney.endtime = mg['ENDTIME']
|
||||
#print mg
|
||||
|
||||
return True
|
||||
|
||||
def getPlayersPositionsAndWinnings(self, tourney):
|
||||
playersText = tourney.summaryText[1]
|
||||
#print "Examine : '%s'" %(playersText)
|
||||
m = self.re_TourneyPlayersSummary.finditer(playersText)
|
||||
|
||||
for a in m:
|
||||
if a.group('PNAME') is not None and a.group('RANK') is not None:
|
||||
if a.group('RANK') == "Still Playing":
|
||||
rank = -1
|
||||
else:
|
||||
rank = Decimal(a.group('RANK'))
|
||||
|
||||
if a.group('WINNING') is not None:
|
||||
winnings = a.group('WINNING')
|
||||
else:
|
||||
winnings = "0"
|
||||
|
||||
tourney.addPlayer(rank, a.group('PNAME'), winnings)
|
||||
else:
|
||||
print "Player finishing stats unreadable : %s" % a
|
||||
|
||||
# Deal with KO tournaments for hero winnings calculation
|
||||
n = self.re_TourneyHeroFinishingP.search(playersText)
|
||||
if n is not None:
|
||||
heroName = n.group('HERO_NAME')
|
||||
tourney.hero = heroName
|
||||
# Is this really useful ?
|
||||
if (tourney.finishPositions[heroName] != Decimal(n.group('HERO_FINISHING_POS'))):
|
||||
print "Bad parsing : finish position incoherent : %s / %s" % (tourney.finishPositions[heroName], n.group('HERO_FINISHING_POS'))
|
||||
if tourney.isKO:
|
||||
#Update the winnings with the (KO amount) * (# of KO)
|
||||
tourney.incrementPlayerWinnings(n.group('HERO_NAME'), Decimal(tourney.koBounty)*Decimal(tourney.countKO))
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = OptionParser()
|
||||
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/fulltilt/razz/FT20090223 Danville - $0.50-$1 Ante $0.10 - Limit Razz.txt")
|
||||
|
@ -368,3 +627,6 @@ if __name__ == "__main__":
|
|||
(options, args) = parser.parse_args()
|
||||
|
||||
e = Fulltilt(in_path = options.ipath, out_path = options.opath, follow = options.follow)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#agpl-3.0.txt in the docs folder of the package.
|
||||
|
||||
import Hand
|
||||
import Tourney
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
@ -53,6 +54,7 @@ class HandHistoryConverter():
|
|||
# "utf_8" is more likely if there are funny characters
|
||||
codepage = "cp1252"
|
||||
|
||||
|
||||
def __init__(self, in_path = '-', out_path = '-', follow=False, index=0, autostart=True):
|
||||
"""\
|
||||
in_path (default '-' = sys.stdin)
|
||||
|
@ -67,6 +69,9 @@ follow : whether to tail -f the input"""
|
|||
self.out_path = out_path
|
||||
|
||||
self.processedHands = []
|
||||
|
||||
# Tourney object used to store TourneyInfo when called to deal with a Summary file
|
||||
self.tourney = None
|
||||
|
||||
if in_path == '-':
|
||||
self.in_fh = sys.stdin
|
||||
|
@ -88,6 +93,10 @@ follow : whether to tail -f the input"""
|
|||
self.follow = follow
|
||||
self.compiledPlayers = set()
|
||||
self.maxseats = 10
|
||||
|
||||
self.status = True
|
||||
|
||||
self.parsedObjectType = "HH" #default behaviour : parsing HH files, can be "Summary" if the parsing encounters a Summary File
|
||||
|
||||
if autostart:
|
||||
self.start()
|
||||
|
@ -116,6 +125,7 @@ Otherwise, finish at EOF.
|
|||
numHands = 0
|
||||
numErrors = 0
|
||||
if self.follow:
|
||||
#TODO: See how summary files can be handled on the fly (here they should be rejected as before)
|
||||
log.info("Tailing '%s'" % self.in_path)
|
||||
for handText in self.tailHands():
|
||||
try:
|
||||
|
@ -128,16 +138,28 @@ Otherwise, finish at EOF.
|
|||
else:
|
||||
handsList = self.allHandsAsList()
|
||||
log.info("Parsing %d hands" % len(handsList))
|
||||
for handText in handsList:
|
||||
try:
|
||||
self.processedHands.append(self.processHand(handText))
|
||||
except FpdbParseError, e:
|
||||
numErrors+=1
|
||||
log.warning("Failed to convert hand %s" % e.hid)
|
||||
log.debug(handText)
|
||||
numHands = len(handsList)
|
||||
endtime = time.time()
|
||||
log.info("Read %d hands (%d failed) in %.3f seconds" % (numHands, numErrors, endtime - starttime))
|
||||
# Determine if we're dealing with a HH file or a Summary file
|
||||
if self.isSummary(handsList[0]) == False:
|
||||
self.parsedObjectType = "HH"
|
||||
for handText in handsList:
|
||||
try:
|
||||
self.processedHands.append(self.processHand(handText))
|
||||
except FpdbParseError, e:
|
||||
numErrors+=1
|
||||
log.warning("Failed to convert hand %s" % e.hid)
|
||||
log.debug(handText)
|
||||
numHands = len(handsList)
|
||||
endtime = time.time()
|
||||
log.info("Read %d hands (%d failed) in %.3f seconds" % (numHands, numErrors, endtime - starttime))
|
||||
else:
|
||||
self.parsedObjectType = "Summary"
|
||||
summaryParsingStatus = self.readSummaryInfo(handsList)
|
||||
endtime = time.time()
|
||||
if summaryParsingStatus :
|
||||
log.info("Summary file '%s' correctly parsed (took %.3f seconds)" % (self.in_path, endtime - starttime))
|
||||
else :
|
||||
log.warning("Error converting summary file '%s' (took %.3f seconds)" % (self.in_path, endtime - starttime))
|
||||
|
||||
except IOError, ioe:
|
||||
log.exception("Error converting '%s'" % self.in_path)
|
||||
finally:
|
||||
|
@ -421,7 +443,7 @@ or None if we fail to get the info """
|
|||
|
||||
def getStatus(self):
|
||||
#TODO: Return a status of true if file processed ok
|
||||
return True
|
||||
return self.status
|
||||
|
||||
def getProcessedHands(self):
|
||||
return self.processedHands
|
||||
|
@ -431,3 +453,15 @@ or None if we fail to get the info """
|
|||
|
||||
def getLastCharacterRead(self):
|
||||
return self.index
|
||||
|
||||
def isSummary(self, topline):
|
||||
return " Tournament Summary " in topline
|
||||
|
||||
def getParsedObjectType(self):
|
||||
return self.parsedObjectType
|
||||
|
||||
#returns a status (True/False) indicating wether the parsing could be done correctly or not
|
||||
def readSummaryInfo(self, summaryInfoList): abstract
|
||||
|
||||
def getTourney(self):
|
||||
return self.tourney
|
||||
|
|
|
@ -168,15 +168,19 @@ class SummaryParser(htmllib.HTMLParser): # derive new HTML parser
|
|||
self.TempResultPos += 1
|
||||
|
||||
class EverleafSummary:
|
||||
def main(self):
|
||||
file = urllib.urlopen("http://www.poker4ever.com/en.tournaments.tournament-statistics?tid=785119")
|
||||
parser = SummaryParser(formatter.NullFormatter())
|
||||
parser.feed(file.read())
|
||||
print "site=",parser.SiteName, "tourneyname=", parser.TourneyName, "tourneyid=", parser.TourneyId
|
||||
print "start time=",parser.TourneyStartTime, "end time=",parser.TourneyEndTime
|
||||
print "structure=", parser.TourneyStructure, "game type=",parser.TourneyGameType
|
||||
print "buy-in=", parser.TourneyBuyIn, "rebuys=", parser.TourneyRebuys, "total players=", parser.TourneyPlayers, "pool=", parser.TourneyPool
|
||||
print "results=", parser.Results
|
||||
def __init__(self):
|
||||
if __name__ != "__main__":
|
||||
self.main()
|
||||
|
||||
def main(self, id="785646"):
|
||||
file = urllib.urlopen("http://www.poker4ever.com/en.tournaments.tournament-statistics?tid="+id)
|
||||
self.parser = SummaryParser(formatter.NullFormatter())
|
||||
self.parser.feed(file.read())
|
||||
print "site=",self.parser.SiteName, "tourneyname=", self.parser.TourneyName, "tourneyid=", self.parser.TourneyId
|
||||
print "start time=",self.parser.TourneyStartTime, "end time=",self.parser.TourneyEndTime
|
||||
print "structure=", self.parser.TourneyStructure, "game type=",self.parser.TourneyGameType
|
||||
print "buy-in=", self.parser.TourneyBuyIn, "rebuys=", self.parser.TourneyRebuys, "total players=", self.parser.TourneyPlayers, "pool=", self.parser.TourneyPool
|
||||
print "results=", self.parser.Results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
310
pyfpdb/TournamentTracker.py
Normal file
310
pyfpdb/TournamentTracker.py
Normal file
|
@ -0,0 +1,310 @@
|
|||
#!/usr/bin/env python
|
||||
"""TourneyTracker.py
|
||||
Based on HUD_main .. who knows if we want to actually use this or not
|
||||
"""
|
||||
# Copyright 2008, 2009, Eric Blade
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
########################################################################
|
||||
|
||||
# to do allow window resizing
|
||||
# to do hud to echo, but ignore non numbers
|
||||
# to do no stat window for hero
|
||||
# to do things to add to config.xml
|
||||
|
||||
# Standard Library modules
|
||||
import sys
|
||||
import os
|
||||
import Options
|
||||
import traceback
|
||||
|
||||
(options, sys.argv) = Options.fpdb_options()
|
||||
|
||||
if not options.errorsToConsole:
|
||||
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
|
||||
errorFile = open('tourneyerror.txt', 'w', 0)
|
||||
sys.stderr = errorFile
|
||||
|
||||
import thread
|
||||
import time
|
||||
import string
|
||||
import re
|
||||
|
||||
# pyGTK modules
|
||||
import pygtk
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
# FreePokerTools modules
|
||||
import Configuration
|
||||
import Database
|
||||
import SummaryEverleaf
|
||||
|
||||
class Tournament:
|
||||
"""Tournament will hold the information about a tournament, I guess ? Remember I'm new to this language, so I don't know the best ways to do things"""
|
||||
|
||||
def __init__(self, parent, site, tid): # site should probably be something in the config object, but i don't know how the config object works right now, so we're going to make it a str ..
|
||||
print "Tournament init"
|
||||
self.parent = parent
|
||||
self.window = None
|
||||
self.site = site
|
||||
self.id = tid
|
||||
self.starttime = time.time()
|
||||
self.endtime = None
|
||||
self.game = None
|
||||
self.structure = None
|
||||
self.buyin = 0
|
||||
self.fee = 0
|
||||
self.rebuys = False
|
||||
self.numrebuys = 0 # this should probably be attached to the players list...
|
||||
self.numplayers = 0
|
||||
self.prizepool = 0
|
||||
self.players = {} # eventually i'd guess we'd probably want to fill this with playername:playerid's
|
||||
self.results = {} # i'd guess we'd want to load this up with playerid's instead of playernames, too, as well, also
|
||||
|
||||
# if site == "Everleaf": # this should be attached to a button that says "retrieve tournament info" or something for sites that we know how to do it for
|
||||
summary = SummaryEverleaf.EverleafSummary()
|
||||
self.site = summary.parser.SiteName
|
||||
self.id = summary.parser.TourneyId
|
||||
self.starttime = summary.parser.TourneyStartTime
|
||||
self.endtime = summary.parser.TourneyEndTime
|
||||
self.game = summary.parser.TourneyGameType
|
||||
self.structure = summary.parser.TourneyStructure
|
||||
self.buyin = summary.parser.TourneyBuyIn # need to remember to parse the Fee out of this and move it to self.fee
|
||||
self.rebuys = (summary.parser.TourneyRebuys == "yes")
|
||||
self.prizepool = summary.parser.TourneyPool
|
||||
self.numplayers = summary.parser.TourneyPlayers
|
||||
|
||||
self.openwindow() # let's start by getting any info we need.. meh
|
||||
|
||||
def openwindow(self, widget=None):
|
||||
if self.window is not None:
|
||||
self.window.show() # isn't there a better way to bring something to the front? not that GTK focus works right anyway, ever
|
||||
else:
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
print "tournament edit window=", self.window
|
||||
self.window.connect("delete_event", self.delete_event)
|
||||
self.window.connect("destroy", self.destroy)
|
||||
self.window.set_title("FPDB Tournament Entry")
|
||||
self.window.set_border_width(1)
|
||||
self.window.set_default_size(480,640)
|
||||
self.window.set_resizable(True)
|
||||
|
||||
self.main_vbox = gtk.VBox(False, 1)
|
||||
self.main_vbox.set_border_width(1)
|
||||
self.window.add(self.main_vbox)
|
||||
self.window.show()
|
||||
|
||||
def addrebuy(self, widget=None):
|
||||
t = self
|
||||
t.numrebuys += 1
|
||||
t.mylabel.set_label("%s - %s - %s - %s - %s %s - %s - %s - %s - %s - %s" % (t.site, t.id, t.starttime, t.endtime, t.structure, t.game, t.buyin, t.fee, t.numrebuys, t.numplayers, t.prizepool))
|
||||
|
||||
def delete_event(self, widget, event, data=None):
|
||||
return False
|
||||
|
||||
def destroy(self, widget, data=None):
|
||||
return False
|
||||
#end def destroy
|
||||
|
||||
|
||||
class ttracker_main(object):
|
||||
"""A main() object to own both the read_stdin thread and the gui."""
|
||||
# This class mainly provides state for controlling the multiple HUDs.
|
||||
|
||||
def __init__(self, db_name = 'fpdb'):
|
||||
self.db_name = db_name
|
||||
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
|
||||
self.tourney_list = []
|
||||
|
||||
# a thread to read stdin
|
||||
gobject.threads_init() # this is required
|
||||
thread.start_new_thread(self.read_stdin, ()) # starts the thread
|
||||
|
||||
# a main window
|
||||
self.main_window = gtk.Window()
|
||||
self.main_window.connect("destroy", self.destroy)
|
||||
self.vb = gtk.VBox()
|
||||
self.label = gtk.Label('Closing this window will stop the Tournament Tracker')
|
||||
self.vb.add(self.label)
|
||||
self.addbutton = gtk.Button(label="Enter Tournament")
|
||||
self.addbutton.connect("clicked", self.addClicked, "add tournament")
|
||||
self.vb.add(self.addbutton)
|
||||
|
||||
self.main_window.add(self.vb)
|
||||
self.main_window.set_title("FPDB Tournament Tracker")
|
||||
self.main_window.show_all()
|
||||
|
||||
def addClicked(self, widget, data): # what is "data"? i'm guessing anything i pass in after the function name in connect() but unsure because the documentation sucks
|
||||
print "addClicked", widget, data
|
||||
t = Tournament(self, None, None)
|
||||
if t is not None:
|
||||
print "new tournament=", t
|
||||
self.tourney_list.append(t)
|
||||
mylabel = gtk.Label("%s - %s - %s - %s - %s %s - %s - %s - %s - %s - %s" % (t.site, t.id, t.starttime, t.endtime, t.structure, t.game, t.buyin, t.fee, t.numrebuys, t.numplayers, t.prizepool))
|
||||
print "new label=", mylabel
|
||||
editbutton = gtk.Button(label="Edit")
|
||||
print "new button=", editbutton
|
||||
editbutton.connect("clicked", t.openwindow)
|
||||
rebuybutton = gtk.Button(label="Rebuy")
|
||||
rebuybutton.connect("clicked", t.addrebuy)
|
||||
self.vb.add(rebuybutton)
|
||||
self.vb.add(editbutton) # These should probably be put in.. a.. h-box? i don't know..
|
||||
self.vb.add(mylabel)
|
||||
self.main_window.resize_children()
|
||||
self.main_window.show()
|
||||
mylabel.show()
|
||||
editbutton.show()
|
||||
rebuybutton.show()
|
||||
t.mylabel = mylabel
|
||||
t.editbutton = editbutton
|
||||
t.rebuybutton = rebuybutton
|
||||
self.vb.show()
|
||||
print self.tourney_list
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
# when we move the start command over to the main program, we can have the main program ask for the tourney id, and pipe it into the stdin here
|
||||
# at least that was my initial thought on it
|
||||
|
||||
def destroy(*args): # call back for terminating the main eventloop
|
||||
gtk.main_quit()
|
||||
|
||||
def create_HUD(self, new_hand_id, table, table_name, max, poker_game, stat_dict, cards):
|
||||
|
||||
def idle_func():
|
||||
|
||||
gtk.gdk.threads_enter()
|
||||
try:
|
||||
newlabel = gtk.Label("%s - %s" % (table.site, table_name))
|
||||
self.vb.add(newlabel)
|
||||
newlabel.show()
|
||||
self.main_window.resize_children()
|
||||
|
||||
self.hud_dict[table_name].tablehudlabel = newlabel
|
||||
self.hud_dict[table_name].create(new_hand_id, self.config, stat_dict, cards)
|
||||
for m in self.hud_dict[table_name].aux_windows:
|
||||
m.create()
|
||||
m.update_gui(new_hand_id)
|
||||
self.hud_dict[table_name].update(new_hand_id, self.config)
|
||||
self.hud_dict[table_name].reposition_windows()
|
||||
return False
|
||||
finally:
|
||||
gtk.gdk.threads_leave()
|
||||
|
||||
self.hud_dict[table_name] = Hud.Hud(self, table, max, poker_game, self.config, self.db_connection)
|
||||
self.hud_dict[table_name].table_name = table_name
|
||||
self.hud_dict[table_name].stat_dict = stat_dict
|
||||
self.hud_dict[table_name].cards = cards
|
||||
[aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[table_name].aux_windows]
|
||||
gobject.idle_add(idle_func)
|
||||
|
||||
def update_HUD(self, new_hand_id, table_name, config):
|
||||
"""Update a HUD gui from inside the non-gui read_stdin thread."""
|
||||
# This is written so that only 1 thread can touch the gui--mainly
|
||||
# for compatibility with Windows. This method dispatches the
|
||||
# function idle_func() to be run by the gui thread, at its leisure.
|
||||
def idle_func():
|
||||
gtk.gdk.threads_enter()
|
||||
try:
|
||||
self.hud_dict[table_name].update(new_hand_id, config)
|
||||
[aw.update_gui(new_hand_id) for aw in self.hud_dict[table_name].aux_windows]
|
||||
return False
|
||||
finally:
|
||||
gtk.gdk.threads_leave()
|
||||
gobject.idle_add(idle_func)
|
||||
|
||||
def read_stdin(self): # This is the thread function
|
||||
"""Do all the non-gui heavy lifting for the HUD program."""
|
||||
|
||||
# This db connection is for the read_stdin thread only. It should not
|
||||
# be passed to HUDs for use in the gui thread. HUD objects should not
|
||||
# need their own access to the database, but should open their own
|
||||
# if it is required.
|
||||
self.db_connection = Database.Database(self.config, self.db_name, 'temp')
|
||||
# self.db_connection.init_hud_stat_vars(hud_days)
|
||||
tourny_finder = re.compile('(\d+) (\d+)')
|
||||
|
||||
while 1: # wait for a new hand number on stdin
|
||||
new_hand_id = sys.stdin.readline()
|
||||
new_hand_id = string.rstrip(new_hand_id)
|
||||
if new_hand_id == "": # blank line means quit
|
||||
self.destroy()
|
||||
break # this thread is not always killed immediately with gtk.main_quit()
|
||||
# get basic info about the new hand from the db
|
||||
# if there is a db error, complain, skip hand, and proceed
|
||||
try:
|
||||
(table_name, max, poker_game, type) = self.db_connection.get_table_name(new_hand_id)
|
||||
stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, aggregate_stats[type]
|
||||
,hud_style, agg_bb_mult)
|
||||
|
||||
cards = self.db_connection.get_cards(new_hand_id)
|
||||
comm_cards = self.db_connection.get_common_cards(new_hand_id)
|
||||
if comm_cards != {}: # stud!
|
||||
cards['common'] = comm_cards['common']
|
||||
except Exception, err:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||
print "db error: skipping "+str(new_hand_id)+" "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
if new_hand_id: # new_hand_id is none if we had an error prior to the store
|
||||
sys.stderr.write("Database error %s in hand %d. Skipping.\n" % (err, int(new_hand_id)))
|
||||
continue
|
||||
|
||||
if type == "tour": # hand is from a tournament
|
||||
mat_obj = tourny_finder.search(table_name)
|
||||
if mat_obj:
|
||||
(tour_number, tab_number) = mat_obj.group(1, 2)
|
||||
temp_key = tour_number
|
||||
else: # tourney, but can't get number and table
|
||||
print "could not find tournament: skipping "
|
||||
sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id)))
|
||||
continue
|
||||
|
||||
else:
|
||||
temp_key = table_name
|
||||
|
||||
# Update an existing HUD
|
||||
if temp_key in self.hud_dict:
|
||||
self.hud_dict[temp_key].stat_dict = stat_dict
|
||||
self.hud_dict[temp_key].cards = cards
|
||||
[aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[temp_key].aux_windows]
|
||||
self.update_HUD(new_hand_id, temp_key, self.config)
|
||||
|
||||
# Or create a new HUD
|
||||
else:
|
||||
if type == "tour":
|
||||
tablewindow = Tables.discover_tournament_table(self.config, tour_number, tab_number)
|
||||
else:
|
||||
tablewindow = Tables.discover_table_by_name(self.config, table_name)
|
||||
if tablewindow == None:
|
||||
# If no client window is found on the screen, complain and continue
|
||||
if type == "tour":
|
||||
table_name = "%s %s" % (tour_number, tab_number)
|
||||
sys.stderr.write("table name "+table_name+" not found, skipping.\n")
|
||||
else:
|
||||
self.create_HUD(new_hand_id, tablewindow, temp_key, max, poker_game, stat_dict, cards)
|
||||
self.db_connection.connection.rollback()
|
||||
|
||||
if __name__== "__main__":
|
||||
|
||||
sys.stderr.write("tournament tracker starting\n")
|
||||
sys.stderr.write("Using db name = %s\n" % (options.dbname))
|
||||
|
||||
# start the HUD_main object
|
||||
hm = ttracker_main(db_name = options.dbname)
|
||||
|
||||
# start the event loop
|
||||
gtk.main()
|
462
pyfpdb/Tourney.py
Normal file
462
pyfpdb/Tourney.py
Normal file
|
@ -0,0 +1,462 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
#Copyright 2009 Stephane Alessio
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as published by
|
||||
#the Free Software Foundation, version 3 of the License.
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#In the "official" distribution you can find the license in
|
||||
#agpl-3.0.txt in the docs folder of the package.
|
||||
|
||||
# TODO: check to keep only the needed modules
|
||||
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
from decimal import Decimal
|
||||
import operator
|
||||
import time,datetime
|
||||
from copy import deepcopy
|
||||
from Exceptions import *
|
||||
import pprint
|
||||
import DerivedStats
|
||||
import Card
|
||||
|
||||
log = logging.getLogger("parser")
|
||||
|
||||
class Tourney(object):
|
||||
|
||||
################################################################
|
||||
# Class Variables
|
||||
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'} # SAL- TO KEEP ??
|
||||
LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} # SAL- TO KEEP ??
|
||||
SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
|
||||
MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'}
|
||||
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 }
|
||||
|
||||
|
||||
def __init__(self, sitename, gametype, summaryText, builtFrom = "HHC"):
|
||||
print "Tourney.__init__"
|
||||
self.sitename = sitename
|
||||
self.siteId = self.SITEIDS[sitename]
|
||||
self.gametype = gametype
|
||||
self.starttime = None
|
||||
self.endtime = None
|
||||
self.summaryText = summaryText
|
||||
self.tourneyName = None
|
||||
self.tourNo = None
|
||||
self.buyin = None
|
||||
self.fee = None # the Database code is looking for this one .. ?
|
||||
self.hero = None
|
||||
self.maxseats = None
|
||||
self.entries = 0
|
||||
self.speed = "Normal"
|
||||
self.prizepool = None # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??}
|
||||
self.buyInChips = None
|
||||
self.mixed = None
|
||||
self.isRebuy = False
|
||||
self.isKO = False
|
||||
self.isHU = False
|
||||
self.isMatrix = False
|
||||
self.isShootout = False
|
||||
self.matrixMatchId = None # For Matrix tourneys : 1-4 => match tables (traditionnal), 0 => Positional winnings info
|
||||
self.subTourneyBuyin = None
|
||||
self.subTourneyFee = None
|
||||
self.rebuyChips = 0
|
||||
self.addOnChips = 0
|
||||
self.countRebuys = 0
|
||||
self.countAddOns = 0
|
||||
self.rebuyAmount = 0
|
||||
self.addOnAmount = 0
|
||||
self.totalRebuys = 0
|
||||
self.totalAddOns = 0
|
||||
self.koBounty = 0
|
||||
self.countKO = 0 #To use for winnings calculation which is not counted in the rest of the summary file
|
||||
self.players = []
|
||||
|
||||
# Collections indexed by player names
|
||||
self.finishPositions = {}
|
||||
self.winnings = {}
|
||||
|
||||
|
||||
|
||||
# currency symbol for this summary
|
||||
self.sym = None
|
||||
#self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
|
||||
|
||||
def __str__(self):
|
||||
#TODO : Update
|
||||
vars = ( ("SITE", self.sitename),
|
||||
("START TIME", self.starttime),
|
||||
("END TIME", self.endtime),
|
||||
("TOURNEY NAME", self.tourneyName),
|
||||
("TOURNEY NO", self.tourNo),
|
||||
("BUYIN", self.buyin),
|
||||
("FEE", self.fee),
|
||||
("HERO", self.hero),
|
||||
("MAXSEATS", self.maxseats),
|
||||
("ENTRIES", self.entries),
|
||||
("SPEED", self.speed),
|
||||
("PRIZE POOL", self.prizepool),
|
||||
("STARTING CHIP COUNT", self.buyInChips),
|
||||
("MIXED", self.mixed),
|
||||
("REBUY ADDON", self.isRebuy),
|
||||
("KO", self.isKO),
|
||||
("HU", self.isHU),
|
||||
("MATRIX", self.isMatrix),
|
||||
("SHOOTOUT", self.isShootout),
|
||||
("MATRIX MATCH ID", self.matrixMatchId),
|
||||
("SUB TOURNEY BUY IN", self.subTourneyBuyin),
|
||||
("SUB TOURNEY FEE", self.subTourneyFee),
|
||||
("REBUY CHIPS", self.rebuyChips),
|
||||
("ADDON CHIPS", self.addOnChips),
|
||||
("REBUY AMOUNT", self.rebuyAmount),
|
||||
("ADDON AMOUNT", self.addOnAmount),
|
||||
("COUNT REBUYS", self.countRebuys),
|
||||
("COUNT ADDONS", self.countAddOns),
|
||||
("NB REBUYS", self.countRebuys),
|
||||
("NB ADDONS", self.countAddOns),
|
||||
("TOTAL REBUYS", self.totalRebuys),
|
||||
("TOTAL ADDONS", self.totalAddOns),
|
||||
("KO BOUNTY", self.koBounty),
|
||||
("NB OF KO", self.countKO)
|
||||
)
|
||||
|
||||
structs = ( ("GAMETYPE", self.gametype),
|
||||
("PLAYERS", self.players),
|
||||
("POSITIONS", self.finishPositions),
|
||||
("WINNINGS", self.winnings),
|
||||
)
|
||||
str = ''
|
||||
for (name, var) in vars:
|
||||
str = str + "\n%s = " % name + pprint.pformat(var)
|
||||
|
||||
for (name, struct) in structs:
|
||||
str = str + "\n%s =\n" % name + pprint.pformat(struct, 4)
|
||||
return str
|
||||
|
||||
def getSummaryText(self):
|
||||
return self.summaryText
|
||||
|
||||
def prepInsert(self, db):
|
||||
pass
|
||||
|
||||
def insert(self, db):
|
||||
print "TODO: Insert Tourney in DB"
|
||||
# First : check all needed info is filled in the object, especially for the initial select
|
||||
|
||||
# Notes on DB Insert
|
||||
# Some identified issues for tourneys already in the DB (which occurs when the HH file is parsed and inserted before the Summary)
|
||||
# Be careful on updates that could make the HH import not match the tourney inserted from a previous summary import !!
|
||||
# BuyIn/Fee can be at 0/0 => match may not be easy
|
||||
# Only one existinf Tourney entry for Matrix Tourneys, but multiple Summary files
|
||||
# Starttime may not match the one in the Summary file : HH = time of the first Hand / could be slighltly different from the one in the summary file
|
||||
# Note: If the TourneyNo could be a unique id .... this would really be a relief to deal with matrix matches ==> Ask on the IRC / Ask Fulltilt ??
|
||||
|
||||
stored = 0
|
||||
duplicates = 0
|
||||
partial = 0
|
||||
errors = 0
|
||||
ttime = 0
|
||||
return (stored, duplicates, partial, errors, ttime)
|
||||
|
||||
|
||||
def old_insert_from_Hand(self, db):
|
||||
""" Function to insert Hand into database
|
||||
Should not commit, and do minimal selects. Callers may want to cache commits
|
||||
db: a connected fpdb_db object"""
|
||||
# TODO:
|
||||
# Players - base playerid and siteid tuple
|
||||
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
|
||||
|
||||
#Gametypes
|
||||
gtid = db.getGameTypeId(self.siteId, self.gametype)
|
||||
|
||||
# HudCache data to come from DerivedStats class
|
||||
# HandsActions - all actions for all players for all streets - self.actions
|
||||
# Hands - Summary information of hand indexed by handId - gameinfo
|
||||
#This should be moved to prepInsert
|
||||
hh = {}
|
||||
hh['siteHandNo'] = self.handid
|
||||
hh['handStart'] = self.starttime
|
||||
hh['gameTypeId'] = gtid
|
||||
# seats TINYINT NOT NULL,
|
||||
hh['tableName'] = self.tablename
|
||||
hh['maxSeats'] = self.maxseats
|
||||
hh['seats'] = len(sqlids)
|
||||
# Flop turn and river may all be empty - add (likely) too many elements and trim with range
|
||||
boardcards = self.board['FLOP'] + self.board['TURN'] + self.board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
|
||||
cards = [Card.encodeCard(c) for c in boardcards[0:5]]
|
||||
hh['boardcard1'] = cards[0]
|
||||
hh['boardcard2'] = cards[1]
|
||||
hh['boardcard3'] = cards[2]
|
||||
hh['boardcard4'] = cards[3]
|
||||
hh['boardcard5'] = cards[4]
|
||||
|
||||
# texture smallint,
|
||||
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
|
||||
# Needs to be recorded
|
||||
# playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4 */
|
||||
# Needs to be recorded
|
||||
# playersAtStreet2 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtStreet3 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtStreet4 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtShowdown SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# street0Raises TINYINT NOT NULL, /* num small bets paid to see flop/street4, including blind */
|
||||
# Needs to be recorded
|
||||
# street1Raises TINYINT NOT NULL, /* num small bets paid to see turn/street5 */
|
||||
# Needs to be recorded
|
||||
# street2Raises TINYINT NOT NULL, /* num big bets paid to see river/street6 */
|
||||
# Needs to be recorded
|
||||
# street3Raises TINYINT NOT NULL, /* num big bets paid to see sd/street7 */
|
||||
# Needs to be recorded
|
||||
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
|
||||
# Needs to be recorded
|
||||
|
||||
#print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % self.getStreetTotals()
|
||||
#FIXME: Pot size still in decimal, needs to be converted to cents
|
||||
(hh['street1Pot'], hh['street2Pot'], hh['street3Pot'], hh['street4Pot'], hh['showdownPot']) = self.getStreetTotals()
|
||||
|
||||
# comment TEXT,
|
||||
# commentTs DATETIME
|
||||
#print hh
|
||||
handid = db.storeHand(hh)
|
||||
# HandsPlayers - ? ... Do we fix winnings?
|
||||
# Tourneys ?
|
||||
# TourneysPlayers
|
||||
|
||||
pass
|
||||
|
||||
def select(self, tourneyId):
|
||||
""" Function to create Tourney object from database """
|
||||
|
||||
|
||||
|
||||
def addPlayer(self, rank, name, winnings):
|
||||
"""\
|
||||
Adds a player to the tourney, and initialises data structures indexed by player.
|
||||
rank (int) indicating the finishing rank (can be -1 if unknown)
|
||||
name (string) player name
|
||||
winnings (string) the money the player ended the tourney with (can be 0, or -1 if unknown)
|
||||
"""
|
||||
log.debug("addPlayer: rank:%s - name : '%s' - Winnings (%s)" % (rank, name, winnings))
|
||||
winnings = re.sub(u',', u'', winnings) #some sites have commas
|
||||
self.players.append(name)
|
||||
self.finishPositions.update( { name : Decimal(rank) } )
|
||||
self.winnings.update( { name : Decimal(winnings) } )
|
||||
|
||||
|
||||
def incrementPlayerWinnings(self, name, additionnalWinnings):
|
||||
log.debug("incrementPlayerWinnings: name : '%s' - Add Winnings (%s)" % (name, additionnalWinnings))
|
||||
oldWins = 0
|
||||
if self.winnings.has_key(name):
|
||||
oldWins = self.winnings[name]
|
||||
else:
|
||||
self.players.append([-1, name, 0])
|
||||
|
||||
self.winnings[name] = oldWins + Decimal(additionnalWinnings)
|
||||
|
||||
|
||||
def calculatePayinAmount(self):
|
||||
return self.buyin + self.fee + (self.rebuyAmount * self.countRebuys) + (self.addOnAmount * self.countAddOns )
|
||||
|
||||
|
||||
def checkPlayerExists(self,player):
|
||||
if player not in [p[1] for p in self.players]:
|
||||
print "checkPlayerExists", player, "fail"
|
||||
raise FpdbParseError
|
||||
|
||||
|
||||
def getGameTypeAsString(self):
|
||||
"""\
|
||||
Map the tuple self.gametype onto the pokerstars string describing it
|
||||
"""
|
||||
# currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
|
||||
gs = {"holdem" : "Hold'em",
|
||||
"omahahi" : "Omaha",
|
||||
"omahahilo" : "Omaha Hi/Lo",
|
||||
"razz" : "Razz",
|
||||
"studhi" : "7 Card Stud",
|
||||
"studhilo" : "7 Card Stud Hi/Lo",
|
||||
"fivedraw" : "5 Card Draw",
|
||||
"27_1draw" : "FIXME",
|
||||
"27_3draw" : "Triple Draw 2-7 Lowball",
|
||||
"badugi" : "Badugi"
|
||||
}
|
||||
ls = {"nl" : "No Limit",
|
||||
"pl" : "Pot Limit",
|
||||
"fl" : "Limit",
|
||||
"cn" : "Cap No Limit",
|
||||
"cp" : "Cap Pot Limit"
|
||||
}
|
||||
|
||||
log.debug("gametype: %s" %(self.gametype))
|
||||
retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
|
||||
return retstring
|
||||
|
||||
|
||||
def writeSummary(self, fh=sys.__stdout__):
|
||||
print >>fh, "Override me"
|
||||
|
||||
def printSummary(self):
|
||||
self.writeSummary(sys.stdout)
|
||||
|
||||
|
||||
def assemble(cnxn, tourneyId):
|
||||
# TODO !!
|
||||
c = cnxn.cursor()
|
||||
|
||||
# We need at least sitename, gametype, handid
|
||||
# for the Hand.__init__
|
||||
c.execute("""
|
||||
select
|
||||
s.name,
|
||||
g.category,
|
||||
g.base,
|
||||
g.type,
|
||||
g.limitType,
|
||||
g.hilo,
|
||||
round(g.smallBlind / 100.0,2),
|
||||
round(g.bigBlind / 100.0,2),
|
||||
round(g.smallBet / 100.0,2),
|
||||
round(g.bigBet / 100.0,2),
|
||||
s.currency,
|
||||
h.boardcard1,
|
||||
h.boardcard2,
|
||||
h.boardcard3,
|
||||
h.boardcard4,
|
||||
h.boardcard5
|
||||
from
|
||||
hands as h,
|
||||
sites as s,
|
||||
gametypes as g,
|
||||
handsplayers as hp,
|
||||
players as p
|
||||
where
|
||||
h.id = %(handid)s
|
||||
and g.id = h.gametypeid
|
||||
and hp.handid = h.id
|
||||
and p.id = hp.playerid
|
||||
and s.id = p.siteid
|
||||
limit 1""", {'handid':handid})
|
||||
#TODO: siteid should be in hands table - we took the scenic route through players here.
|
||||
res = c.fetchone()
|
||||
gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]}
|
||||
h = HoldemOmahaHand(hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
|
||||
cards = map(Card.valueSuitFromCard, res[11:16] )
|
||||
if cards[0]:
|
||||
h.setCommunityCards('FLOP', cards[0:3])
|
||||
if cards[3]:
|
||||
h.setCommunityCards('TURN', [cards[3]])
|
||||
if cards[4]:
|
||||
h.setCommunityCards('RIVER', [cards[4]])
|
||||
#[Card.valueSuitFromCard(x) for x in cards]
|
||||
|
||||
# HandInfo : HID, TABLE
|
||||
# BUTTON - why is this treated specially in Hand?
|
||||
# answer: it is written out in hand histories
|
||||
# still, I think we should record all the active seat positions in a seat_order array
|
||||
c.execute("""
|
||||
SELECT
|
||||
h.sitehandno as hid,
|
||||
h.tablename as table,
|
||||
h.handstart as starttime
|
||||
FROM
|
||||
hands as h
|
||||
WHERE h.id = %(handid)s
|
||||
""", {'handid':handid})
|
||||
res = c.fetchone()
|
||||
h.handid = res[0]
|
||||
h.tablename = res[1]
|
||||
h.starttime = res[2] # automatically a datetime
|
||||
|
||||
# PlayerStacks
|
||||
c.execute("""
|
||||
SELECT
|
||||
hp.seatno,
|
||||
round(hp.winnings / 100.0,2) as winnings,
|
||||
p.name,
|
||||
round(hp.startcash / 100.0,2) as chips,
|
||||
hp.card1,hp.card2,
|
||||
hp.position
|
||||
FROM
|
||||
handsplayers as hp,
|
||||
players as p
|
||||
WHERE
|
||||
hp.handid = %(handid)s
|
||||
and p.id = hp.playerid
|
||||
""", {'handid':handid})
|
||||
for (seat, winnings, name, chips, card1,card2, position) in c.fetchall():
|
||||
h.addPlayer(seat,name,chips)
|
||||
if card1 and card2:
|
||||
h.addHoleCards(map(Card.valueSuitFromCard, (card1,card2)), name, dealt=True)
|
||||
if winnings > 0:
|
||||
h.addCollectPot(name, winnings)
|
||||
if position == 'B':
|
||||
h.buttonpos = seat
|
||||
|
||||
|
||||
# actions
|
||||
c.execute("""
|
||||
SELECT
|
||||
(ha.street,ha.actionno) as actnum,
|
||||
p.name,
|
||||
ha.street,
|
||||
ha.action,
|
||||
ha.allin,
|
||||
round(ha.amount / 100.0,2)
|
||||
FROM
|
||||
handsplayers as hp,
|
||||
handsactions as ha,
|
||||
players as p
|
||||
WHERE
|
||||
hp.handid = %(handid)s
|
||||
and ha.handsplayerid = hp.id
|
||||
and p.id = hp.playerid
|
||||
ORDER BY
|
||||
ha.street,ha.actionno
|
||||
""", {'handid':handid})
|
||||
res = c.fetchall()
|
||||
for (actnum,player, streetnum, act, allin, amount) in res:
|
||||
act=act.strip()
|
||||
street = h.allStreets[streetnum+1]
|
||||
if act==u'blind':
|
||||
h.addBlind(player, 'big blind', amount)
|
||||
# TODO: The type of blind is not recorded in the DB.
|
||||
# TODO: preflop street name anomalies in Hand
|
||||
elif act==u'fold':
|
||||
h.addFold(street,player)
|
||||
elif act==u'call':
|
||||
h.addCall(street,player,amount)
|
||||
elif act==u'bet':
|
||||
h.addBet(street,player,amount)
|
||||
elif act==u'check':
|
||||
h.addCheck(street,player)
|
||||
elif act==u'unbet':
|
||||
pass
|
||||
else:
|
||||
print act, player, streetnum, allin, amount
|
||||
# TODO : other actions
|
||||
|
||||
#hhc.readShowdownActions(self)
|
||||
#hc.readShownCards(self)
|
||||
h.totalPot()
|
||||
h.rake = h.totalpot - h.totalcollected
|
||||
|
||||
|
||||
return h
|
||||
|
|
@ -396,29 +396,45 @@ class Importer:
|
|||
out_path = os.path.join(hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file))
|
||||
|
||||
filter_name = filter.replace("ToFpdb", "")
|
||||
|
||||
mod = __import__(filter)
|
||||
obj = getattr(mod, filter_name, None)
|
||||
if callable(obj):
|
||||
hhc = obj(in_path = file, out_path = out_path, index = 0) # Index into file 0 until changeover
|
||||
if(hhc.getStatus() and self.NEWIMPORT == False):
|
||||
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(db, out_path, site, q)
|
||||
elif (hhc.getStatus() and self.NEWIMPORT == True):
|
||||
#This code doesn't do anything yet
|
||||
handlist = hhc.getProcessedHands()
|
||||
self.pos_in_file[file] = hhc.getLastCharacterRead()
|
||||
|
||||
for hand in handlist:
|
||||
#hand.prepInsert()
|
||||
hand.insert(self.database)
|
||||
else:
|
||||
# conversion didn't work
|
||||
# TODO: appropriate response?
|
||||
return (0, 0, 0, 1, 0, -1)
|
||||
else:
|
||||
print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter)
|
||||
return (0, 0, 0, 1, 0, -1)
|
||||
|
||||
mod = __import__(filter)
|
||||
obj = getattr(mod, filter_name, None)
|
||||
if callable(obj):
|
||||
hhc = obj(in_path = file, out_path = out_path, index = 0) # Index into file 0 until changeover
|
||||
if hhc.getParsedObjectType() == "HH":
|
||||
if(hhc.getStatus() and self.NEWIMPORT == False):
|
||||
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(db, out_path, site, q)
|
||||
elif (hhc.getStatus() and self.NEWIMPORT == True):
|
||||
#This code doesn't do anything yet
|
||||
handlist = hhc.getProcessedHands()
|
||||
self.pos_in_file[file] = hhc.getLastCharacterRead()
|
||||
|
||||
for hand in handlist:
|
||||
#hand.prepInsert()
|
||||
hand.insert(self.database)
|
||||
else:
|
||||
# conversion didn't work
|
||||
# TODO: appropriate response?
|
||||
return (0, 0, 0, 1, 0, -1)
|
||||
elif hhc.getParsedObjectType() == "Summary":
|
||||
if(hhc.getStatus()):
|
||||
tourney = hhc.getTourney()
|
||||
#print tourney
|
||||
#tourney.prepInsert()
|
||||
(stored, duplicates, partial, errors, ttime) = tourney.insert(self.database)
|
||||
return (stored, duplicates, partial, errors, ttime)
|
||||
|
||||
else:
|
||||
# conversion didn't work
|
||||
# Could just be the parsing of a non summary file (classic HH file)
|
||||
return (0, 0, 0, 0, 0)
|
||||
else:
|
||||
print "Unknown objects parsed by HHC :'%s'" %(hhc.getObjectTypeRead())
|
||||
return (0, 0, 0, 1, 0, -1)
|
||||
|
||||
else:
|
||||
print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter)
|
||||
return (0, 0, 0, 1, 0, -1)
|
||||
|
||||
#This will barf if conv.getStatus != True
|
||||
return (stored, duplicates, partial, errors, ttime)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user