Coverage for TourneySummary.py: 0%

215 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-28 16:41 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3 

4#Copyright 2009-2011 Stephane Alessio 

5#This program is free software: you can redistribute it and/or modify 

6#it under the terms of the GNU Affero General Public License as published by 

7#the Free Software Foundation, version 3 of the License. 

8#  

9#This program is distributed in the hope that it will be useful, 

10#but WITHOUT ANY WARRANTY; without even the implied warranty of 

11#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12#GNU General Public License for more details. 

13#  

14#You should have received a copy of the GNU Affero General Public License 

15#along with this program. If not, see <http://www.gnu.org/licenses/>. 

16#In the "official" distribution you can find the license in agpl-3.0.txt. 

17 

18"""parses and stores summary sections from e.g. eMail or summary files""" 

19 

20#import L10n 

21#_ = L10n.get_translation() 

22 

23# TODO: check to keep only the needed modules 

24 

25import re 

26import sys 

27import logging 

28import os 

29import os.path 

30from decimal_wrapper import Decimal 

31import operator 

32import time, datetime 

33from copy import deepcopy 

34from Exceptions import * 

35import codecs 

36 

37import pprint 

38import DerivedStats 

39import Card 

40import Database 

41from HandHistoryConverter import HandHistoryConverter 

42 

43log = logging.getLogger("parser") 

44 

45try: 

46 import xlrd 

47except: 

48 xlrd = None 

49 log.info(("xlrd not found. Required for importing Excel tourney results files")) 

50 

51class TourneySummary(object): 

52 

53################################################################ 

54# Class Variables 

55 UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'} # SAL- TO KEEP ?? 

56 LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} # SAL- TO KEEP ?? 

57 SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''} 

58 MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'} 

59 SITEIDS = {'Fulltilt':1, 'Full Tilt Poker':1, 'PokerStars':2, 'Everleaf':3, 'Boss':4, 'OnGame':5, 

60 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9, 'PacificPoker':10, 

61 'Partouche':11, 'Merge':12, 'PKR':13, 'iPoker':14, 'Winamax':15, 'Everest':16, 

62 'Cake':17, 'Entraction':18, 'BetOnline':19, 'Microgaming':20, 'Bovada':21, 'Enet':22, 

63 'SealsWithClubs': 23, 'WinningPoker': 24, 'Run It Once Poker': 26} 

64 

65 

66 def __init__(self, db, config, siteName, summaryText, in_path='-', builtFrom="HHC", header=""): 

67 self.db = db 

68 self.config = config 

69 self.import_parameters = self.config.get_import_parameters() 

70 self.siteName = siteName 

71 self.siteId = None 

72 if siteName in self.SITEIDS: 

73 self.siteId = self.SITEIDS[siteName] 

74 self.in_path = in_path 

75 self.header = header 

76 

77 self.summaryText = summaryText 

78 self.tourneyName = None 

79 self.tourneyTypeId = None 

80 self.tourneyId = None 

81 self.startTime = None 

82 self.endTime = None 

83 self.tourNo = None 

84 self.currency = None 

85 self.buyinCurrency = None 

86 self.buyin = 0 

87 self.fee = 0 

88 self.hero = None 

89 self.maxseats = 0 

90 self.entries = 0 

91 self.speed = "Normal" 

92 self.prizepool = 0 # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??} 

93 self.buyInChips = 0 

94 self.mixed = None 

95 self.isRebuy = False 

96 self.isAddOn = False 

97 self.isKO = False 

98 self.isProgressive = False 

99 self.isMatrix = False 

100 self.isShootout = False 

101 self.isFast = False 

102 self.rebuyChips = None 

103 self.addOnChips = None 

104 self.rebuyCost = 0 

105 self.addOnCost = 0 

106 self.totalRebuyCount = None 

107 self.totalAddOnCount = None 

108 self.koBounty = 0 

109 self.isSng = False 

110 self.stack = "Regular" 

111 self.isStep = False 

112 self.stepNo = 0 

113 self.isChance = False 

114 self.chanceCount = 0 

115 self.isMultiEntry = False 

116 self.isReEntry = False 

117 self.isNewToGame = False 

118 self.isHomeGame = False 

119 self.isSplit = False 

120 self.isFifty50 = False 

121 self.isTime = False 

122 self.timeAmt = 0 

123 self.isSatellite = False 

124 self.isDoubleOrNothing = False 

125 self.isCashOut = False 

126 self.isOnDemand = False 

127 self.isFlighted = False 

128 self.isGuarantee = False 

129 self.guaranteeAmt = 0 

130 self.added = None 

131 self.addedCurrency = None 

132 self.gametype = {'category':None, 'limitType':None, 'mix':'none'} 

133 self.players = {} 

134 self.comment = None 

135 self.commentTs = None 

136 

137 # Collections indexed by player names 

138 self.playerIds = {} 

139 self.tourneysPlayersIds = {} 

140 self.ranks = {} 

141 self.winnings = {} 

142 self.winningsCurrency = {} 

143 self.rebuyCounts = {} 

144 self.addOnCounts = {} 

145 self.koCounts = {} 

146 

147 # currency symbol for this summary 

148 self.sym = None 

149 

150 if builtFrom == "IMAP": 

151 # Fix line endings? 

152 pass 

153 if self.db == None: 

154 self.db = Database.Database(config) 

155 

156 self.parseSummary() 

157 #end def __init__ 

158 

159 def __str__(self): 

160 #TODO : Update 

161 vars = ((("SITE"), self.siteName), 

162 (("START TIME"), self.startTime), 

163 (("END TIME"), self.endTime), 

164 (("TOURNEY NAME"), self.tourneyName), 

165 (("TOURNEY NO"), self.tourNo), 

166 (("TOURNEY TYPE ID"), self.tourneyTypeId), 

167 (("TOURNEY ID"), self.tourneyId), 

168 (("BUYIN"), self.buyin), 

169 (("FEE"), self.fee), 

170 (("CURRENCY"), self.currency), 

171 (("HERO"), self.hero), 

172 (("MAX SEATS"), self.maxseats), 

173 (("ENTRIES"), self.entries), 

174 (("SPEED"), self.speed), 

175 (("PRIZE POOL"), self.prizepool), 

176 (("STARTING CHIP COUNT"), self.buyInChips), 

177 (("MIXED"), self.mixed), 

178 (("REBUY"), self.isRebuy), 

179 (("ADDON"), self.isAddOn), 

180 (("KO"), self.isKO), 

181 (("MATRIX"), self.isMatrix), 

182 (("SHOOTOUT"), self.isShootout), 

183 (("REBUY CHIPS"), self.rebuyChips), 

184 (("ADDON CHIPS"), self.addOnChips), 

185 (("REBUY COST"), self.rebuyCost), 

186 (("ADDON COST"), self.addOnCost), 

187 (("TOTAL REBUYS"), self.totalRebuyCount), 

188 (("TOTAL ADDONS"), self.totalAddOnCount), 

189 (("KO BOUNTY"), self.koBounty), 

190 (("SNG"), self.isSng), 

191 (("SATELLITE"), self.isSatellite), 

192 (("DOUBLE OR NOTHING"), self.isDoubleOrNothing), 

193 (("GUARANTEEAMT"), self.guaranteeAmt), 

194 (("ADDED"), self.added), 

195 (("ADDED CURRENCY"), self.addedCurrency), 

196 (("COMMENT"), self.comment), 

197 (("COMMENT TIMESTAMP"), self.commentTs) 

198 ) 

199 

200 structs = ((("PLAYER IDS"), self.playerIds), 

201 (("PLAYERS"), self.players), 

202 (("TOURNEYS PLAYERS IDS"), self.tourneysPlayersIds), 

203 (("RANKS"), self.ranks), 

204 (("WINNINGS"), self.winnings), 

205 (("WINNINGS CURRENCY"), self.winningsCurrency), 

206 (("COUNT REBUYS"), self.rebuyCounts), 

207 (("COUNT ADDONS"), self.addOnCounts), 

208 (("COUNT KO"), self.koCounts) 

209 ) 

210 str = '' 

211 for (name, var) in vars: 

212 str = str + "\n%s = " % name + pprint.pformat(var) 

213 

214 for (name, struct) in structs: 

215 str = str + "\n%s =\n" % name + pprint.pformat(struct, 4) 

216 return str 

217 #end def __str__ 

218 

219 def getSplitRe(self, head): abstract 

220 """Function to return a re object to split the summary text into separate tourneys, based on head of file""" 

221 

222 def parseSummary(self): abstract 

223 """should fill the class variables with the parsed information""" 

224 

225 def getSummaryText(self): 

226 return self.summaryText 

227 

228 @staticmethod 

229 def clearMoneyString(money): 

230 "Renders 'numbers' like '1 200' and '2,000'" 

231 money = money.strip(u'€&euro;\u20ac$ ') 

232 return HandHistoryConverter.clearMoneyString(money) 

233 

234 def insertOrUpdate(self, printtest=False): 

235 # First : check all needed info is filled in the object, especially for the initial select 

236 

237 # Notes on DB Insert 

238 # Some identified issues for tourneys already in the DB (which occurs when the HH file is parsed and inserted before the Summary) 

239 # Be careful on updates that could make the HH import not match the tourney inserted from a previous summary import !! 

240 # BuyIn/Fee can be at 0/0 => match may not be easy 

241 # Only one existinf Tourney entry for Matrix Tourneys, but multiple Summary files 

242 # 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 

243 # 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 ?? 

244 self.db.set_printdata(printtest) 

245 

246 self.playerIds = self.db.getSqlPlayerIDs(self.players.keys(), self.siteId, self.hero) 

247 #for player in self.players: 

248 # id=self.db.get_player_id(self.config, self.siteName, player) 

249 # if not id: 

250 # id=self.db.insertPlayer(unicode(player), self.siteId) 

251 # self.playerIds.update({player:id}) 

252 

253 #print "TS.insert players",self.players,"playerIds",self.playerIds 

254 self.dbid_pids = self.playerIds #TODO:rename this field in Hand so this silly renaming can be removed 

255 

256 #print "TS.self before starting insert",self 

257 self.tourneyTypeId = self.db.createOrUpdateTourneyType(self) 

258 self.tourneyId = self.db.createOrUpdateTourney(self) 

259 self.db.createOrUpdateTourneysPlayers(self) 

260 self.db.commit() 

261 

262 logging.debug(("Tourney Insert/Update done")) 

263 

264 # TO DO : Return what has been done (tourney created, updated, nothing) 

265 # ?? 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, tourneysPlayers and handsPlayers) 

266 # if so, prototypes may need changes to know what has been done or make some kind of dict in Tourney object that could be updated during the insert process to store that information 

267 stored = 0 

268 duplicates = 0 

269 partial = 0 

270 errors = 0 

271 ttime = 0 

272 return (stored, duplicates, partial, errors, ttime) 

273 

274 

275 def addPlayer(self, rank, name, winnings, winningsCurrency, rebuyCount, addOnCount, koCount, entryId=None): 

276 """\ 

277Adds a player to the tourney, and initialises data structures indexed by player. 

278rank (int) indicating the finishing rank (can be -1 if unknown) 

279name (string) player name 

280winnings (int) the money the player ended the tourney with (can be 0, or -1 if unknown) 

281""" 

282 log.debug("addPlayer: rank:%s - name : '%s' - Winnings (%s)" % (rank, name, winnings)) 

283 if self.players.get(name) != None: 

284 if entryId is None: 

285 entries = self.players[name][-1] 

286 self.players[name].append(entries + 1) 

287 elif entryId in self.players[name]: 

288 return None 

289 else: 

290 self.players[name].append(entryId) 

291 if rank: 

292 self.ranks[name].append(rank) 

293 self.winnings[name].append(winnings) 

294 self.winningsCurrency[name].append(winningsCurrency) 

295 else: 

296 self.ranks[name].append(None) 

297 self.winnings[name].append(None) 

298 self.winningsCurrency[name].append(None) 

299 self.rebuyCounts[name].append(None) 

300 self.addOnCounts[name].append(None) 

301 self.koCounts[name].append(None) 

302 else: 

303 self.players[name] = [entryId if entryId is not None else 1] 

304 if rank: 

305 self.ranks.update({ name : [rank] }) 

306 self.winnings.update({ name : [winnings] }) 

307 self.winningsCurrency.update({ name : [winningsCurrency] }) 

308 else: 

309 self.ranks.update({ name : [None] }) 

310 self.winnings.update({ name : [None] }) 

311 self.winningsCurrency.update({ name : [None] }) 

312 self.rebuyCounts.update({name: [rebuyCount] }) 

313 self.addOnCounts.update({name: [addOnCount] }) 

314 self.koCounts.update({name : [koCount] }) 

315 #end def addPlayer 

316 

317 def writeSummary(self, fh=sys.__stdout__): 

318 print >> fh, "Override me" 

319 

320 def printSummary(self): 

321 self.writeSummary(sys.stdout) 

322 

323 @staticmethod 

324 def summaries_from_excel(filenameXLS, tourNoField): 

325 wb = xlrd.open_workbook(filenameXLS) 

326 sh = wb.sheet_by_index(0) 

327 summaryTexts, rows, header, keys, entries = [], [], None, None, {} 

328 for rownum in range(sh.nrows): 

329 if rownum==0: 

330 header = sh.row_values(rownum)[0] 

331 elif tourNoField in sh.row_values(rownum): 

332 keys = [str(c).encode('utf-8') for c in sh.row_values(rownum)] 

333 elif keys!=None: 

334 rows.append([str(c).encode('utf-8') for c in sh.row_values(rownum)]) 

335 for row in rows: 

336 data = dict(zip(keys, row)) 

337 data['header'] = header 

338 if len(data[tourNoField])>0: 

339 if entries.get(data[tourNoField])==None: 

340 entries[data[tourNoField]] = [] 

341 entries[data[tourNoField]].append(data) 

342 for k, item in entries.iteritems(): 

343 summaryTexts.append(item) 

344 return summaryTexts 

345 

346 @staticmethod 

347 def readFile(self, filename): 

348 whole_file = None 

349 for kodec in self.codepage: 

350 try: 

351 in_fh = codecs.open(filename, 'r', kodec) 

352 whole_file = in_fh.read() 

353 in_fh.close() 

354 break 

355 except UnicodeDecodeError as e: 

356 log.warning("TS.readFile: '%s' : '%s'" % (filename, e)) 

357 except UnicodeError as e: 

358 log.warning("TS.readFile: '%s' : '%s'" % (filename, e)) 

359 

360 return whole_file