fpdb/pyfpdb/FulltiltToFpdb.py
Worros bc4a53d754 FTP: Fix re_PlayerInfo to find ', is sitting out' for cash games
Turns out that a player sitting out at the start of a cash game hand can (in what i hope is a small set of hands) be dealt cards and act in a hand
2010-09-09 15:14:54 +08:00

729 lines
38 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2008-2010, 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 locale
lang=locale.getdefaultlocale()[0][0:2]
if lang=="en":
def _(string): return string
else:
import gettext
try:
trans = gettext.translation("fpdb", localedir="locale", languages=[lang])
trans.install()
except IOError:
def _(string): return string
import logging
from HandHistoryConverter import *
#import TourneySummary
# Fulltilt HH Format converter
class Fulltilt(HandHistoryConverter):
sitename = "Fulltilt"
filetype = "text"
codepage = ["utf-16", "cp1252", "utf-8"]
siteId = 1 # Needs to match id entry in Sites database
substitutions = {
'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
'LS' : u"\$|\u20AC|\xe2\x82\xac|", # legal currency symbols - Euro(cp1252, utf-8)
'TAB' : u"-\u2013\s\da-zA-Z"
}
# Static regexes
re_GameInfo = re.compile(u'''.*\#(?P<HID>[0-9]+):\s
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
.+
-\s(?P<CURRENCY>[%(LS)s]|)?
(?P<SB>[.0-9]+)/
[%(LS)s]?(?P<BB>[.0-9]+)\s
(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
[%(LS)s]?(?P<CAP>[.0-9]+\sCap\s)?
(?P<LIMIT>(No\sLimit|Pot\sLimit|Limit))?\s
(?P<GAME>(Hold\'em|Omaha\sHi|Omaha\sH/L|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))
''' % substitutions, re.VERBOSE)
re_SplitHands = re.compile(r"\n\n+")
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|Match)\s
(?P<PLAY>Play\sChip\s|PC)?
(?P<TABLE>[%(TAB)s]+)\s
(\((?P<TABLEATTRIBUTES>.+)\)\s)?-\s
[%(LS)s]?(?P<SB>[.0-9]+)/[%(LS)s]?(?P<BB>[.0-9]+)\s(Ante\s[%(LS)s]?(?P<ANTE>[.0-9]+)\s)?-\s
[%(LS)s]?(?P<CAP>[.0-9]+\sCap\s)?
(?P<GAMETYPE>[a-zA-Z\/\'\s]+)\s-\s
(?P<DATETIME>\d+:\d+:\d+\s(?P<TZ1>\w+)\s-\s\d+/\d+/\d+|\d+:\d+\s(?P<TZ2>\w+)\s-\s\w+\,\s\w+\s\d+\,\s\d+)
(?P<PARTIAL>\(partial\))?\n
(?:.*?\n(?P<CANCELLED>Hand\s\#(?P=HID)\shas\sbeen\scanceled))?
''' % substitutions, re.VERBOSE|re.DOTALL)
re_TourneyExtraInfo = re.compile('''(((?P<TOURNEY_NAME>[^$]+)?
(?P<CURRENCY>[%(LS)s])?(?P<BUYIN>[.0-9]+)?\s*\+\s*[%(LS)s]?(?P<FEE>[.0-9]+)?
(\s(?P<SPECIAL>(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness)))?
(\s(?P<SHOOTOUT>Shootout))?
(\s(?P<SNG>Sit\s&\sGo))?
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
''' % substitutions, re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \([%(LS)s](?P<CASH>[,.0-9]+)\)(, is sitting out)?$' % substitutions, re.MULTILINE)
re_TourneysPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \([%(LS)s]?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$' % substitutions, 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>[%(LS)s]|)?(?P<BUYIN>[.0-9]+)\s*\+\s*[%(LS)s]?(?P<FEE>[.0-9]+)\s)?
((?P<SPECIAL>(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness))\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))?
''' % substitutions, re.VERBOSE)
re_TourneyBuyInFee = re.compile("Buy-In: (?P<BUYIN_CURRENCY>[%(LS)s]|)?(?P<BUYIN>[.0-9]+) \+ [%(LS)s]?(?P<FEE>[.0-9]+)" % substitutions)
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>[%(LS)s]|)?(?P<PRIZEPOOL>[.,0-9]+)" % substitutions)
re_TourneyRebuyCost = re.compile("Rebuy: (?P<REBUY_CURRENCY>[%(LS)s]|)?(?P<REBUY_COST>[.,0-9]+)"% substitutions)
re_TourneyAddOnCost = re.compile("Add-On: (?P<ADDON_CURRENCY>[%(LS)s]|)?(?P<ADDON_COST>[.,0-9]+)"% substitutions)
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>[%(LS)s]|)?(?P<KO_BOUNTY_AMOUNT>[.,0-9]+)" % substitutions)
re_TourneyKoCount = 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_TourneysPlayersSummary = re.compile("^(?P<RANK>(Still Playing|\d+))( - |: )(?P<PNAME>[^\n,]+)(, )?(?P<WINNING_CURRENCY>[%(LS)s]|)?(?P<WINNING>[.\d]+)?" % substitutions, 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)
# NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports.
mixes = { 'HORSE': 'horse', '7-Game': '7game', 'HOSE': 'hose', 'HA': 'ha'}
def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# we need to recompile the player regexs.
self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
self.substitutions['PLAYERS'] = player_re
logging.debug("player_re: " + player_re)
self.re_PostSB = re.compile(r"^%(PLAYERS)s posts the small blind of [%(LS)s]?(?P<SB>[.0-9]+)" % self.substitutions, re.MULTILINE)
self.re_PostDead = re.compile(r"^%(PLAYERS)s posts a dead small blind of [%(LS)s]?(?P<SB>[.0-9]+)" % self.substitutions, re.MULTILINE)
self.re_PostBB = re.compile(r"^%(PLAYERS)s posts (the big blind of )?[%(LS)s]?(?P<BB>[.0-9]+)" % self.substitutions, re.MULTILINE)
self.re_Antes = re.compile(r"^%(PLAYERS)s antes [%(LS)s]?(?P<ANTE>[.0-9]+)" % self.substitutions, re.MULTILINE)
self.re_BringIn = re.compile(r"^%(PLAYERS)s brings in for [%(LS)s]?(?P<BRINGIN>[.0-9]+)" % self.substitutions, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%(PLAYERS)s posts small \& big blinds \[[%(LS)s]? (?P<SBBB>[.0-9]+)" % self.substitutions, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%(PLAYERS)s(?P<ATYPE> bets| checks| raises to| completes it to| calls| folds)( [%(LS)s]?(?P<BET>[.,\d]+))?" % self.substitutions, re.MULTILINE)
self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %(PLAYERS)s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \([%(LS)s]?(?P<POT>[.,\d]+)\)(, mucked| with.*)" % self.substitutions, re.MULTILINE)
self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(?P<ACT>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self):
return [["ring", "hold", "nl"],
["ring", "hold", "pl"],
["ring", "hold", "fl"],
["ring", "hold", "cn"],
["ring", "stud", "fl"],
["tour", "hold", "nl"],
["tour", "hold", "pl"],
["tour", "hold", "fl"],
["tour", "stud", "fl"],
]
def determineGameType(self, handText):
# Full Tilt Poker Game #10777181585: Table Deerfly (deep 6) - $0.01/$0.02 - Pot Limit Omaha Hi - 2:24:44 ET - 2009/02/22
# Full Tilt Poker Game #10773265574: Table Butte (6 max) - $0.01/$0.02 - Pot Limit Hold'em - 21:33:46 ET - 2009/02/21
# Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09
# Full Tilt Poker Game #10809877615: Table Danville - $0.50/$1 Ante $0.10 - Limit Razz - 21:47:27 ET - 2009/02/23
# Full Tilt Poker.fr Game #23057874034: Table DouaiLens (6 max) - €0.01/€0.02 - No Limit Hold'em - 21:59:17 CET - 2010/08/13
info = {'type':'ring'}
m = self.re_GameInfo.search(handText)
if not m:
return None
mg = m.groupdict()
# 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$' }
if mg['CAP']:
info['limitType'] = 'cn'
else:
info['limitType'] = limits[mg['LIMIT']]
info['sb'] = mg['SB']
info['bb'] = mg['BB']
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'] is None: info['type'] = "ring"
else: info['type'] = "tour"
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info
def readHandInfo(self, hand):
m = self.re_HandInfo.search(hand.handText)
if m is None:
logging.info(_("Didn't match re_HandInfo"))
logging.info(hand.handText)
raise FpdbParseError("No match in readHandInfo.")
hand.handid = m.group('HID')
hand.tablename = m.group('TABLE')
timezone = "ET"
if m.group('TZ1') == "CET" or m.group('TZ2') == "CET":
timezone = "CET"
try:
stringformat = "%H:%M:%S " + m.group('TZ1') + " - %Y/%m/%d"
hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), stringformat)
except:
stringformat = "%H:%M " + m.group('TZ2') + " - %a, %B %d, %Y"
hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), stringformat)
hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, timezone, "UTC")
if m.group("CANCELLED") or m.group("PARTIAL"):
raise FpdbParseError(hid=m.group('HID'))
if m.group('TABLEATTRIBUTES'):
m2 = self.re_Max.search(m.group('TABLEATTRIBUTES'))
if m2: hand.maxseats = int(m2.group('MAX'))
hand.tourNo = m.group('TOURNO')
if m.group('PLAY') is not None:
hand.gametype['currency'] = 'play'
# Done: if there's a way to figure these out, we should.. otherwise we have to stuff it with unknowns
if m.group('TOURNAMENT') is not None:
n = self.re_TourneyExtraInfo.search(m.group('TOURNAMENT'))
if n.group('UNREADABLE_INFO') is not None:
hand.tourneyComment = n.group('UNREADABLE_INFO')
else:
hand.tourneyComment = n.group('TOURNEY_NAME') # can be None
if (n.group('CURRENCY') is not None and n.group('BUYIN') is not None and n.group('FEE') is not None):
if n.group('CURRENCY')=="$":
hand.buyinCurrency="USD"
elif n.group('CURRENCY')==u"":
hand.buyinCurrency="EUR"
else:
hand.buyinCurrency="NA"
hand.buyin = int(100*Decimal(n.group('BUYIN')))
hand.fee = int(100*Decimal(n.group('FEE')))
if n.group('TURBO') is not None :
hand.speed = "Turbo"
if n.group('SPECIAL') is not None :
special = n.group('SPECIAL')
if special == "Rebuy":
hand.isRebuy = True
if special == "KO":
hand.isKO = True
if special == "Head's Up" or special == "Heads Up":
hand.maxseats = 2
if re.search("Matrix", special):
hand.isMatrix = True
if special == "Shootout":
hand.isShootout = True
if hand.buyin is None:
hand.buyin = "$0.00+$0.00"
if hand.level is None:
hand.level = "0"
# 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')))
def readPlayerStacks(self, hand):
# Split hand text for FTP, as the regex matches the player names incorrectly
# in the summary section
pre, post = hand.handText.split('SUMMARY')
if hand.gametype['type'] == "ring" :
m = self.re_PlayerInfo.finditer(pre)
else: #if hand.gametype['type'] == "tour"
m = self.re_TourneysPlayerInfo.finditer(pre)
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
if hand.gametype['base'] == 'hold':
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.handText,re.DOTALL)
elif hand.gametype['base'] == "stud": # or should this be gametype['category'] == 'razz'
m = re.search(r"(?P<ANTES>.+(?=\*\*\* 3RD STREET \*\*\*)|.+)"
r"(\*\*\* 3RD STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4TH STREET \*\*\*)|.+))?"
r"(\*\*\* 4TH STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5TH STREET \*\*\*)|.+))?"
r"(\*\*\* 5TH STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6TH STREET \*\*\*)|.+))?"
r"(\*\*\* 6TH STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* 7TH STREET \*\*\*)|.+))?"
r"(\*\*\* 7TH STREET \*\*\*(?P<SEVENTH>.+))?", hand.handText,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)
#print "DEBUG readCommunityCards:", street, hand.streets.group(street)
m = self.re_Board.search(hand.streets[street])
hand.setCommunityCards(street, m.group('CARDS').split(' '))
def readBlinds(self, hand):
try:
m = self.re_PostSB.search(hand.handText)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostDead.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
def readAntes(self, hand):
logging.debug(_("reading antes"))
m = self.re_Antes.finditer(hand.handText)
for player in m:
logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
# if player.group() !=
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL)
if m:
logging.debug(_("Player bringing in: %s for %s") %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
else:
logging.warning(_("No bringin found, handid =%s") % hand.handid)
def readButton(self, hand):
hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
def readHeroCards(self, hand):
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
# we need to grab hero's cards
for street in ('PREFLOP', 'DEAL'):
if street in hand.streets.keys():
m = self.re_HeroCards.finditer(hand.streets[street])
for found in m:
# if m == None:
# hand.involved = False
# else:
hand.hero = found.group('PNAME')
newcards = found.group('NEWCARDS').split(' ')
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
for street, text in hand.streets.iteritems():
if not text or street in ('PREFLOP', 'DEAL'): continue # already done these
m = self.re_HeroCards.finditer(hand.streets[street])
for found in m:
player = found.group('PNAME')
if found.group('NEWCARDS') is None:
newcards = []
else:
newcards = found.group('NEWCARDS').split(' ')
if found.group('OLDCARDS') is None:
oldcards = []
else:
oldcards = found.group('OLDCARDS').split(' ')
if street == 'THIRD' and len(oldcards) == 2: # hero in stud game
hand.hero = player
hand.dealt.add(player) # need this for stud??
hand.addHoleCards(street, player, closed=oldcards, open=newcards, shown=False, mucked=False, dealt=False)
else:
hand.addHoleCards(street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False)
def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street])
for action in m:
if action.group('ATYPE') == ' raises to':
hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' completes it to':
hand.addComplete( 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 _("FullTilt: DEBUG: unimplemented readAction: '%s' '%s'") %(action.group('PNAME'),action.group('ATYPE'),)
def readShowdownActions(self, hand):
for shows in self.re_ShowdownAction.finditer(hand.handText):
cards = shows.group('CARDS')
cards = cards.split(' ')
hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.handText):
hand.addCollectPot(player=m.group('PNAME'),pot=re.sub(u',',u'',m.group('POT')))
def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None:
if m.group('ACT'):
hand.addShownCards(cards=m.group('CARDS').split(' '), player=m.group('PNAME'), shown = False, mucked = True)
else:
hand.addShownCards(cards=m.group('CARDS').split(' '), player=m.group('PNAME'), shown = True, mucked = False)
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy
if hand.gametype['base'] == 'stud':
if mo <= 8: return 8
else: return mo
if hand.gametype['base'] == 'draw':
if mo <= 6: return 6
else: return mo
if mo == 2: return 2
if mo <= 6: return 6
return 9
def readOther(self, hand):
m = self.re_Mixed.search(self.in_path)
if m is None:
hand.mixed = None
else:
hand.mixed = self.mixes[m.groupdict()['MIXED']]
def readSummaryInfo(self, summaryInfoList):
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 or too few lines (%d) in file '%s' : '%s'" % (len(summaryInfoList), self.in_path, summaryInfoList) )
# self.status = False
# else:
# self.tourney = TourneySummary.TourneySummary(sitename = self.sitename, gametype = None, summaryText = summaryInfoList, builtFrom = "HHC")
# self.status = self.getPlayersPositionsAndWinnings(self.tourney)
# if self.status == True :
# self.status = self.determineTourneyType(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'] is 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 = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
tourney.fee = 0
if mg['FEE'] is not None:
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % 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.maxseats = 2
if re.search("Matrix", special):
tourney.isMatrix = True
if special == "Rebuy":
tourney.isRebuy = True
if special == "Madness":
tourney.tourneyComment = "Madness"
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 = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
tourney.subTourneyFee = 0
if mg['FEE'] is not None:
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
else :
if mg['BUYIN'] is not None:
if tourney.buyin is None:
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin:
log.error(_("Conflict between buyins read in topline (%s) and in BuyIn field (%s)") % (tourney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) )
tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
if mg['FEE'] is not None:
if tourney.fee is None:
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee:
log.error(_("Conflict between fees read in topline (%s) and in BuyIn field (%s)") % (tourney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) )
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % 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_COST" : self.re_TourneyRebuyCost,
"ADDON_COST" : self.re_TourneyAddOnCost,
"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,
}
dictHolders = { "BUYINCHIPS" : "buyInChips",
"ENTRIES" : "entries",
"PRIZEPOOL" : "prizepool",
"REBUY_COST" : "rebuyCost",
"ADDON_COST" : "addOnCost",
"REBUY_TOTAL" : "totalRebuyCount",
"ADDONS_TOTAL" : "totalAddOnCount",
"REBUY_CHIPS" : "rebuyChips",
"ADDON_CHIPS" : "addOnChips",
"STARTTIME" : "starttime",
"KO_BOUNTY_AMOUNT" : "koBounty"
}
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']
# Deal with hero specific information
if tourney.hero is not None :
m = self.re_TourneyRebuyCount.search(tourneyText)
if m is not None:
mg = m.groupdict()
if mg['REBUY_COUNT'] is not None :
tourney.rebuyCounts.update( { tourney.hero : Decimal(mg['REBUY_COUNT']) } )
m = self.re_TourneyAddOnCount.search(tourneyText)
if m is not None:
mg = m.groupdict()
if mg['ADDON_COUNT'] is not None :
tourney.addOnCounts.update( { tourney.hero : Decimal(mg['ADDON_COUNT']) } )
m = self.re_TourneyKoCount.search(tourneyText)
if m is not None:
mg = m.groupdict()
if mg['COUNT_KO'] is not None :
tourney.koCounts.update( { tourney.hero : Decimal(mg['COUNT_KO']) } )
# Deal with money amounts
tourney.koBounty = 100*Decimal(re.sub(u',', u'', "%s" % tourney.koBounty))
tourney.prizepool = 100*Decimal(re.sub(u',', u'', "%s" % tourney.prizepool))
tourney.rebuyCost = 100*Decimal(re.sub(u',', u'', "%s" % tourney.rebuyCost))
tourney.addOnCost = 100*Decimal(re.sub(u',', u'', "%s" % tourney.addOnCost))
# Calculate payin amounts and update winnings -- not possible to take into account nb of rebuys, addons or Knockouts for other players than hero on FTP
for p in tourney.players :
if tourney.isKO :
#tourney.incrementPlayerWinnings(tourney.players[p], Decimal(tourney.koBounty)*Decimal(tourney.koCounts[p]))
tourney.winnings[p] += Decimal(tourney.koBounty)*Decimal(tourney.koCounts[p])
#print "player %s : winnings %d" % (p, tourney.winnings[p])
#print mg
return True
#end def determineTourneyType
def getPlayersPositionsAndWinnings(self, tourney):
playersText = tourney.summaryText[1]
#print "Examine : '%s'" %(playersText)
m = self.re_TourneysPlayersSummary.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 = 100*Decimal(re.sub(u',', u'', "%s" % a.group('WINNING')))
else:
winnings = "0"
tourney.addPlayer(rank, a.group('PNAME'), winnings, "USD", 0, 0, 0) #TODO: make it store actual winnings currency
else:
print (_("FullTilt: Player finishing stats unreadable : %s") % a)
# Find Hero
n = self.re_TourneyHeroFinishingP.search(playersText)
if n is not None:
heroName = n.group('HERO_NAME')
tourney.hero = heroName
# Is this really useful ?
if heroName not in tourney.ranks:
print (_("FullTilt: %s not found in tourney.ranks ...") % heroName)
elif (tourney.ranks[heroName] != Decimal(n.group('HERO_FINISHING_POS'))):
print (_("FullTilt: Bad parsing : finish position incoherent : %s / %s") % (tourney.ranks[heroName], n.group('HERO_FINISHING_POS')))
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")
parser.add_option("-o", "--output", dest="opath", help=_("output translation to"), default="-")
parser.add_option("-f", "--follow", dest="follow", help=_("follow (tail -f) the input"), action="store_true", default=False)
parser.add_option("-q", "--quiet",
action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
parser.add_option("-v", "--verbose",
action="store_const", const=logging.INFO, dest="verbosity")
parser.add_option("--vv",
action="store_const", const=logging.DEBUG, dest="verbosity")
(options, args) = parser.parse_args()
e = Fulltilt(in_path = options.ipath, out_path = options.opath, follow = options.follow)