#!/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 . #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"): 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.rebuyAmount = 0 self.addOnAmount = 0 self.totalRebuys = 0 self.totalAddOns = 0 self.koBounty = 0 self.tourneyComment = None self.players = [] # Collections indexed by player names self.finishPositions = {} self.winnings = {} self.payinAmounts = {} self.countRebuys = {} self.countAddOns = {} self.countKO = {} # 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), ("TOTAL REBUYS", self.totalRebuys), ("TOTAL ADDONS", self.totalAddOns), ("KO BOUNTY", self.koBounty), ("TOURNEY COMMENT", self.tourneyComment) ) structs = ( ("GAMETYPE", self.gametype), ("PLAYERS", self.players), ("PAYIN AMOUNTS", self.payinAmounts), ("POSITIONS", self.finishPositions), ("WINNINGS", self.winnings), ("COUNT REBUYS", self.countRebuys), ("COUNT ADDONS", self.countAddOns), ("NB OF KO", self.countKO) ) 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 ?? dbTourneyTypeId = db.tRecogniseTourneyType(self) print "Tourney Type ID = %d" % dbTourneyTypeId dbTourneyId = db.tRecognizeTourney(self, dbTourneyTypeId) print "Tourney ID = %d" % dbTourneyId dbTourneysPlayersIds = db.tStoreTourneyPlayers(self, dbTourneyId) db.tCheckTourneysHandsPlayers(self, dbTourneysPlayersIds, dbTourneyTypeId) # TO DO : Return what has been done (tourney created, updated, nothing) # ?? stored = 1 if tourney is fully created / duplicates = 1, if everything was already here and correct / partial=1 if some things were already here (between tourney, tourneyPlayers and handsplayers) # if so, prototypes may need changes to know what has been done 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, payinAmount, nbRebuys, nbAddons, nbKO): """\ 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 (decimal) 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)) self.players.append(name) self.finishPositions.update( { name : Decimal(rank) } ) self.winnings.update( { name : Decimal(winnings) } ) self.payinAmounts.update( {name : Decimal(payinAmount) } ) self.countRebuys.update( {name: Decimal(nbRebuys) } ) self.countAddOns.update( {name: Decimal(nbAddons) } ) self.countKO.update( {name : Decimal(nbKO) } ) 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 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