Coverage for CakeToFpdb.py: 0%

268 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 2008-2011, Carl Gherardi 

5#  

6# This program is free software; you can redistribute it and/or modify 

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

8# the Free Software Foundation; either version 2 of the License, or 

9# (at your option) any later version. 

10#  

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

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

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

14# GNU General Public License for more details. 

15#  

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

17# along with this program; if not, write to the Free Software 

18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

19######################################################################## 

20 

21from __future__ import division 

22 

23from past.utils import old_div 

24#import L10n 

25#_ = L10n.get_translation() 

26 

27from HandHistoryConverter import * 

28from decimal_wrapper import Decimal 

29 

30 

31class Cake(HandHistoryConverter): 

32 """ 

33 A class for converting Cake hand histories to FPDB format. 

34 

35 Inherits from HandHistoryConverter. 

36 

37 Class Variables: 

38 sitename (str): The name of the site. 

39 filetype (str): The file type of the hand history. 

40 codepage (tuple): The supported code pages for the hand history. 

41 siteId (int): The ID of the site. 

42 sym (dict): A dictionary mapping currencies to their symbols. 

43 substitutions (dict): A dictionary mapping shorthand codes to regular expressions for parsing hand histories. 

44 """ 

45 

46 # Class Variables 

47 

48 sitename = "Cake" 

49 filetype = "text" 

50 codepage = ("utf8", "cp1252") 

51 siteId = 17 

52 sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "€", "GBP": "\xa3", "play": ""} 

53 substitutions = { 

54 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes 

55 'LS' : r"\$|€|", # legal currency symbols - Euro(cp1252, utf-8) 

56 'PLYR': r'(?P<PNAME>.+?)', 

57 'CUR': r"(\$|€|)", 

58 'NUM' :r".(,|\s)\d\xa0", 

59 'NUM2': r'\b((?:\d{1,3}(?:\s\d{3})*)|(?:\d+))\b', # Regex pattern for matching numbers with spaces 

60 } 

61 

62 

63 # translations from captured groups to fpdb info strings 

64 Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.08': ('0.02', '0.04'), 

65 '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'), 

66 '0.40': ('0.10', '0.20'), '0.50': ('0.10', '0.25'), 

67 '1.00': ('0.25', '0.50'), '1': ('0.25', '0.50'), 

68 '2.00': ('0.50', '1.00'), '2': ('0.50', '1.00'), 

69 '4.00': ('1.00', '2.00'), '4': ('1.00', '2.00'), 

70 '6.00': ('1.50', '3.00'), '6': ('1.50', '3.00'), 

71 '8.00': ('2.00', '4.00'), '8': ('2.00', '4.00'), 

72 '10.00': ('2.50', '5.00'), '10': ('2.50', '5.00'), 

73 '12.00': ('3.00', '6.00'), '12': ('3.00', '6.00'), 

74 '20.00': ('5.00', '10.00'), '20': ('5.00', '10.00'), 

75 '30.00': ('7.50', '15.00'), '30': ('7.50', '15.00'), 

76 '40.00': ('10.00', '20.00'), '40': ('10.00', '20.00'), 

77 '60.00': ('15.00', '30.00'), '60': ('15.00', '30.00'), 

78 '80.00': ('20.00', '40.00'), '80': ('20.00', '40.00'), 

79 '100.00': ('25.00', '50.00'), '100': ('25.00', '50.00'), 

80 '200.00': ('50.00', '100.00'), '200': ('50.00', '100.00'), 

81 '400.00': ('100.00', '200.00'), '400': ('100.00', '200.00'), 

82 '800.00': ('200.00', '400.00'), '800': ('200.00', '400.00'), 

83 '1000.00': ('250.00', '500.00'),'1000': ('250.00', '500.00') 

84 } 

85 

86 limits = { 'NL':'nl', 'PL':'pl', 'FL':'fl' } 

87 games = { # base, category 

88 "Hold'em" : ('hold','holdem'), 

89 'Omaha' : ('hold','omahahi'), 

90 'Omaha Hi/Lo' : ('hold','omahahilo'), 

91 'OmahaHiLo' : ('hold','omahahilo'), 

92 } 

93 currencies = { u'€':'EUR', '$':'USD', '':'T$' } 

94 

95 # Static regexes 

96 re_GameInfo = re.compile(r""" 

97 Hand\#(?P<HID>[A-Z0-9]+)\s+\-\s+(?P<TABLE>(?P<BUYIN1>(?P<BIAMT1>(%(LS)s)[%(NUM)s]+)\sNLH\s(?P<MAX1>\d+)\smax)?.+?)\s(\((Turbo,\s)?(?P<MAX>\d+)\-+[Mm]ax\)\s)?((?P<TOURNO>T\d+)|\d+)\s(\-\-\s(TICKET|CASH|TICKETCASH|FREEROLL)\s\-\-\s(?P<BUYIN>(?P<BIAMT>\$\d+)\s\+\s(?P<BIRAKE>\$\d+))\s\-\-\s(?P<TMAX>\d+)\sMax\s)?(\-\-\sTable\s(?P<TABLENO>\d+)\s)?\-\-\s(?P<CURRENCY>%(LS)s|)?(?P<ANTESB>(\-)?\d+)/(%(LS)s)?(?P<SBBB>\d+)(/(%(LS)s)?(?P<BB>\d+))?\s(?P<LIMIT>NL|FL||PL)\s(?P<GAME>Hold\'em|Omaha|Omaha\sHi/Lo|OmahaHiLo)\s-\-\s(?P<DATETIME>.*$) 

98  

99 """ % substitutions, re.MULTILINE|re.VERBOSE) 

100 

101 re_PlayerInfo = re.compile(r""" 

102 ^Seat\s(?P<SEAT>[0-9]+):\s 

103 (?P<PNAME>.+?)\s 

104 \((%(LS)s)?(?P<CASH>[%(NUM2)s]+)\sin\schips\) 

105 (\s\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?""" % substitutions, 

106 re.MULTILINE|re.VERBOSE) 

107 

108 re_Trim = re.compile("(Hand\#)") 

109 re_Identify = re.compile(u'Hand\#[A-Z0-9]+\s\-\s') 

110 re_SplitHands = re.compile('\n\n+') 

111 re_Button = re.compile('Dealer: Seat (?P<BUTTON>\d+)', re.MULTILINE) 

112 re_Board = re.compile(r"\[(?P<CARDS>.+)\]") 

113 

114 re_DateTime = re.compile("""(?P<Y>[0-9]{4})[\/\-\.](?P<M>[0-9]{2})[\/\-\.](?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)""", re.MULTILINE) 

115 re_PostSB = re.compile(r"^%(PLYR)s: posts small blind %(CUR)s(?P<SB>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?$" % substitutions, re.MULTILINE) 

116 re_PostBB = re.compile(r"^%(PLYR)s: posts big blind %(CUR)s(?P<BB>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?$" % substitutions, re.MULTILINE) 

117 re_Antes = re.compile(r"^%(PLYR)s: posts ante of %(CUR)s(?P<ANTE>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?" % substitutions, re.MULTILINE) 

118 re_BringIn = re.compile(r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?" % substitutions, re.MULTILINE) 

119 re_PostBoth = re.compile(r"^%(PLYR)s:posts dead blind %(CUR)s(\-)?(?P<SB>[%(NUM)s]+) and big blind %(CUR)s(?P<BB>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

120 re_HeroCards = re.compile(r"^Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % substitutions, re.MULTILINE) 

121 re_Action = re.compile(r""" 

122 ^%(PLYR)s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sis\sall\sin) 

123 (\s(to\s)?(%(CUR)s)?(?P<BET>[%(NUM)s]+))?(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?$ 

124 """ 

125 % substitutions, re.MULTILINE|re.VERBOSE) 

126 re_sitsOut = re.compile("^%s sits out" % substitutions['PLYR'], re.MULTILINE) 

127 re_ShownCards = re.compile(r"^%s: (?P<SHOWED>shows|mucks) \[(?P<CARDS>.*)\] ?(\((?P<STRING>.*)\))?" % substitutions['PLYR'], re.MULTILINE) 

128 re_CollectPot = re.compile(r"^%(PLYR)s:? wins (low pot |high pot )?%(CUR)s(?P<POT>[%(NUM)s]+)((\swith.+?)?\s+\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?" % substitutions, re.MULTILINE) 

129 re_Finished = re.compile(r"%(PLYR)s:? finished \d+ out of \d+ players" % substitutions, re.MULTILINE) 

130 re_Dealer = re.compile(r"Dealer:") #Some Cake hands just omit the game line so we can just discard them as partial 

131 re_CoinFlip = re.compile(r"Coin\sFlip\sT\d+", re.MULTILINE) 

132 re_ReturnBet = re.compile(r"returns\suncalled\sbet", re.MULTILINE) 

133 re_ShowDown = re.compile(r"\*\*\*SHOW DOWN\*\*\*") 

134 re_ShowDownLeft = re.compile(r"\*\*\*SHOW\sDOWN\*\*\*\nPlayer\sleft\sthe\stable$", re.MULTILINE) 

135 

136 def compilePlayerRegexs(self, hand): 

137 """ 

138 Compiles regular expressions representing the cards in a player's hand. 

139 

140 Args: 

141 hand (list[str]): The cards in the player's hand. 

142 

143 Returns: 

144 list[re.Pattern]: A list of compiled regular expressions, one for each card in the player's hand. 

145 """ 

146 pass # TODO: Implement this function. 

147 

148 

149 def readSupportedGames(self): 

150 """ 

151 Returns a list of supported games. 

152 

153 Each supported game is represented as a list of game modes. 

154 """ 

155 return [ 

156 ["ring", "hold", "nl"], # Ring game, hold mode, no limit 

157 ["ring", "hold", "pl"], # Ring game, hold mode, pot limit 

158 ["ring", "hold", "fl"], # Ring game, hold mode, fixed limit 

159 ["tour", "hold", "nl"], # Tournament, hold mode, no limit 

160 ["tour", "hold", "pl"], # Tournament, hold mode, pot limit 

161 ["tour", "hold", "fl"], # Tournament, hold mode, fixed limit 

162 ] 

163 

164 def determineGameType(self, handText): 

165 """ 

166 Determine the type of game from the given hand text. 

167 

168 Args: 

169 handText (str): The text of the hand. 

170 

171 Returns: 

172 dict: A dictionary containing information about the game type. 

173 """ 

174 # Initialize dictionary to store game type info 

175 info = {} 

176 

177 # Search for game info in hand text 

178 m = self.re_GameInfo.search(handText) 

179 

180 # If no game info found, raise appropriate error 

181 if not m: 

182 if self.re_Finished.search(handText): 

183 raise FpdbHandPartial 

184 if self.re_Dealer.match(handText): 

185 raise FpdbHandPartial 

186 tmp = handText[:200] 

187 log.error(f"CakeToFpdb.determineGameType: '{tmp}'") 

188 raise FpdbParseError 

189 

190 # If no ShowDown or ShowDownLeft found, raise appropriate error 

191 if not self.re_ShowDown.search(handText) or self.re_ShowDownLeft.search(handText): 

192 raise FpdbHandPartial 

193 

194 # Extract game info from match object's group dictionary 

195 mg = m.groupdict() 

196 

197 # Determine limit type and store in info dictionary 

198 if 'LIMIT' in mg: 

199 info['limitType'] = self.limits[mg['LIMIT']] 

200 

201 # Determine game category and base type and store in info dictionary 

202 if 'GAME' in mg: 

203 (info['base'], info['category']) = self.games[mg['GAME']] 

204 

205 # Determine big blind and store in info dictionary 

206 if 'BB' in mg: 

207 if not mg['BB']: 

208 info['bb'] = self.clearMoneyString(mg['SBBB']) 

209 else: 

210 info['bb'] = self.clearMoneyString(mg['BB']) 

211 

212 # Determine small blind and store in info dictionary 

213 if 'SBBB' in mg: 

214 if not mg['BB']: 

215 info['sb'] = self.clearMoneyString(mg['ANTESB']) 

216 else: 

217 info['sb'] = self.clearMoneyString(mg['SBBB']) 

218 

219 # Determine currency and store in info dictionary 

220 if 'CURRENCY' in mg: 

221 info['currency'] = self.currencies[mg['CURRENCY']] 

222 

223 # Determine mix and store in info dictionary 

224 if 'MIXED' in mg and mg['MIXED'] is not None: 

225 info['mix'] = self.mixes[mg['MIXED']] 

226 

227 # Determine game type and store in info dictionary 

228 if 'TOURNO' in mg and mg['TOURNO'] is not None: 

229 info['type'] = 'tour' 

230 else: 

231 info['type'] = 'ring' 

232 

233 # If play money game, set currency to 'play' 

234 if 'TABLE' in mg and 'Play Money' in mg['TABLE']: 

235 info['currency'] = 'play' 

236 

237 # If limit type is 'fl' and big blind is not None  

238 if info['limitType'] == 'fl' and info['bb'] is not None: 

239 # If game type is 'ring' 

240 if info['type'] == 'ring': 

241 try: 

242 # Determine small blind and big blind and store in info dictionary 

243 info['sb'] = self.Lim_Blinds[info['bb']][0] 

244 info['bb'] = self.Lim_Blinds[info['bb']][1] 

245 except KeyError as e: 

246 tmp = handText[:200] 

247 log.error( 

248 f"CakeToFpdb.determineGameType: Lim_Blinds has no lookup for '{mg['BB']}' - '{tmp}'" 

249 ) 

250 raise FpdbParseError from e 

251 # If game type is not 'ring' 

252 else: 

253 # Calculate small blind and big blind and store in info dictionary 

254 info['sb'] = str((old_div(Decimal(info['sb']),2)).quantize(Decimal("0.01"))) 

255 info['bb'] = str(Decimal(info['sb']).quantize(Decimal("0.01"))) 

256 

257 return info 

258 

259 

260 

261 def readHandInfo(self, hand): 

262 """ 

263 Reads information from a hand history string and updates the corresponding Hand object. 

264 

265 Parameters: 

266 hand (Hand): The Hand object to update. 

267 

268 Returns: 

269 None 

270 """ 

271 

272 # trim off malformatted text from partially written hands 

273 if not self.re_Trim.match(hand.handText): 

274 hand.handText = "".join(self.re_Trim.split(hand.handText)[1:]) 

275 

276 info = {} 

277 m = self.re_GameInfo.search(hand.handText) 

278 if m is None: 

279 tmp = hand.handText[:200] 

280 log.error(f"CakeToFpdb.readHandInfo: '{tmp}'") 

281 raise FpdbParseError 

282 

283 info |= m.groupdict() 

284 

285 for key in info: 

286 # extract datetime information and convert to UTC timezone 

287 if key == 'DATETIME': 

288 m1 = self.re_DateTime.finditer(info[key]) 

289 datetimestr = "2000/01/01 00:00:00" # default used if time not found 

290 for a in m1: 

291 datetimestr = f"{a.group('Y')}/{a.group('M')}/{a.group('D')} {a.group('H')}:{a.group('MIN')}:{a.group('S')}" 

292 hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") 

293 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC") 

294 

295 # extract hand ID 

296 elif key == 'HID': 

297 hand.handid = re.sub('[A-Z]+', '', info[key]) 

298 

299 # extract table name for ring games 

300 if key == 'TABLE' and hand.gametype['type'] == 'ring': 

301 hand.tablename = info[key] 

302 

303 # extract table name for tournament games 

304 if key == 'TABLENO' and hand.gametype['type'] == 'tour': 

305 hand.tablename = info[key] 

306 

307 # extract button position 

308 if key == 'BUTTON': 

309 hand.buttonpos = info[key] 

310 

311 # extract maximum number of seats 

312 if key == 'MAX' and info[key]: 

313 hand.maxseats = int(info[key]) 

314 

315 # extract tournament number 

316 if key == 'TOURNO' and info[key]: 

317 hand.tourNo = info[key].replace('T', '') 

318 

319 # extract maximum number of seats for tournament games 

320 if key == 'TMAX' and info[key]: 

321 hand.maxseats = int(info[key]) 

322 if key == 'TMAX1' and info[key]: 

323 hand.maxseats = int(info[key]) 

324 

325 # extract buy-in information 

326 if key in ['BUYIN', 'BUYIN1'] and info[key] and hand.tourNo!=None: 

327 if info[key].find("$")!=-1: 

328 hand.buyinCurrency="USD" 

329 elif info[key].find(u"£")!=-1: 

330 hand.buyinCurrency="GBP" 

331 elif info[key].find(u"€")!=-1: 

332 hand.buyinCurrency="EUR" 

333 elif re.match("^[0-9+]*$", info[key]): 

334 hand.buyinCurrency="play" 

335 else: 

336 #FIXME: handle other currencies, play money 

337 log.error( 

338 f"CakeToFpdb.readHandInfo: Failed to detect currency. Hand ID: {hand.handid}: '{info[key]}'" 

339 ) 

340 raise FpdbParseError 

341 

342 # extract buy-in amount and rake amount 

343 if key == 'BUYIN1': 

344 info['BIAMT1'] = self.clearMoneyString(info['BIAMT1'].strip(u'$€£')) 

345 hand.buyin = int(100*Decimal(info['BIAMT1'])) 

346 hand.fee = 0 

347 else: 

348 info['BIAMT'] = self.clearMoneyString(info['BIAMT'].strip(u'$€£')) 

349 info['BIRAKE'] = self.clearMoneyString(info['BIRAKE'].strip(u'$€£')) 

350 hand.buyin = int(100*Decimal(info['BIAMT'])) 

351 hand.fee = int(100*Decimal(info['BIRAKE'])) 

352 

353 if hand.gametype['type'] == 'tour' and not hand.buyin: 

354 hand.buyin = 0 

355 hand.fee = 0 

356 hand.buyinCurrency="NA" 

357 

358 

359 

360 def readButton(self, hand): 

361 """ 

362 Parses a hand for the button position and updates the hand object. 

363 

364 Args: 

365 hand (Hand): The hand object to update. 

366 

367 Returns: 

368 None 

369 """ 

370 # Search for the button position in the hand text 

371 if m := self.re_Button.search(hand.handText): 

372 # If found, update the button position in the hand object 

373 hand.buttonpos = int(m.group('BUTTON')) 

374 else: 

375 # If not found, log an info message 

376 log.info('readButton: ' + ('not found')) 

377 

378 

379 def readPlayerStacks(self, hand): 

380 """ 

381 Reads player stacks from the given `hand` object and adds them to the `hand` object. 

382 

383 Args: 

384 hand (Hand): The `Hand` object to read player stacks from. 

385 

386 Returns: 

387 None 

388 """ 

389 # Find each player's stack information in the hand text 

390 m = self.re_PlayerInfo.finditer(hand.handText) 

391 

392 # Check if there was a coinflip in the hand 

393 coinflip = bool(self.re_CoinFlip.search(hand.handText)) 

394 

395 # Iterate over each player's stack information 

396 for a in m: 

397 # Check if the stack information has a EUROVALUE and set the roundPenny flag accordingly 

398 if a.group('EUROVALUE'): 

399 hand.roundPenny = True 

400 cash_value = a.group('CASH') 

401 cash_value = cash_value.encode("utf-8") 

402 cash_value = cash_value.replace(b"\xe2\x80\xaf", b"") 

403 cash_value = cash_value.decode("utf-8") 

404 print("value:", cash_value) 

405 print("type:" , type(cash_value)) 

406 # Add the player's stack information to the `hand` object 

407 hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), cash_value) 

408 

409 # Add the player's stack information to the `hand` object 

410 #hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), self.convertMoneyString('CASH', a)) 

411 

412 # If there was a coinflip, add the ante for the player 

413 if coinflip: 

414 hand.addAnte(a.group('PNAME'), self.convertMoneyString('CASH', a)) 

415 

416 

417 def markStreets(self, hand): 

418 """ 

419 Given a Hand object, extract the street information from its handText attribute 

420 and update the Hand object with the street information. 

421 

422 Args: 

423 - hand: a Hand object to extract street information from 

424 

425 Returns: 

426 - None 

427 """ 

428 

429 # The following regex matches the different streets in a hand history and captures the information 

430 # in named groups: PREFLOP, FLOP, TURN, RIVER. 

431 # It first matches everything up to the FLOP street, then optionally matches the FLOP street, 

432 # then optionally matches the TURN street, and finally optionally matches the RIVER street. 

433 # The information captured in each street is then stored in its respective named group. 

434 regex = r"(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)" \ 

435 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S,\S\S,\S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?" \ 

436 r"(\*\*\* TURN \*\*\* (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?" \ 

437 r"(\*\*\* RIVER \*\*\* (?P<RIVER>\[\S\S\].+))?" 

438 

439 # Use the regex to search for the street information in the hand's handText attribute 

440 m = re.search(regex, hand.handText, re.DOTALL) 

441 

442 # Add the street information to the Hand object 

443 hand.addStreets(m) 

444 

445 

446 def readCommunityCards(self, hand, street): 

447 """ 

448 Reads the community cards for a given street of the current hand and sets them in the hand object. 

449 

450 Args: 

451 hand (Hand): The current hand object. 

452 street (str): The street to read the community cards for. 

453 

454 Returns: 

455 None 

456 """ 

457 if street in ('FLOP','TURN','RIVER'): 

458 # Parse the community cards from the hand object's streets dictionary 

459 m = self.re_Board.search(hand.streets[street]) 

460 # Set the community cards in the hand object 

461 hand.setCommunityCards(street, m.group('CARDS').split(',')) 

462 

463 

464 def readAntes(self, hand): 

465 """ 

466 Reads the antes from the hand and adds them to the Hand object. 

467 

468 Args: 

469 hand: The Hand object to add the antes to. 

470 

471 Returns: 

472 None 

473 """ 

474 log.debug(("reading antes")) 

475 m = self.re_Antes.finditer(hand.handText) 

476 for player in m: 

477 # Uncomment the following line to enable logging 

478 # logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE'))) 

479 hand.addAnte(player.group('PNAME'), self.convertMoneyString('ANTE', player)) 

480 

481 

482 def readBringIn(self, hand): 

483 """ 

484 Reads the BringIn information from the hand's handText and adds it to the hand object. 

485 

486 Args: 

487 hand (Hand): The Hand object to add the BringIn information to. 

488 

489 Returns: 

490 None 

491 """ 

492 if m := self.re_BringIn.search(hand.handText, re.DOTALL): 

493 # The BringIn information was found, add it to the hand object. 

494 # logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))) 

495 hand.addBringIn(m.group('PNAME'), self.convertMoneyString('BRINGIN', m)) 

496 

497 

498 def readBlinds(self, hand): 

499 """ 

500 Parses the hand text and extracts the blinds information. 

501 

502 Args: 

503 hand: An instance of the Hand class representing the hand being parsed. 

504 

505 Returns: 

506 None 

507 """ 

508 

509 # Flag to keep track of whether the small blind is still live. 

510 liveBlind = True 

511 

512 # If no bets were returned, set the uncalled bets flag to True. 

513 if not self.re_ReturnBet.search(hand.handText): 

514 hand.setUncalledBets(True) 

515 

516 # Find all instances of the small blind and add them to the Hand object. 

517 for a in self.re_PostSB.finditer(hand.handText): 

518 if liveBlind: 

519 hand.addBlind(a.group('PNAME'), 'small blind', self.convertMoneyString('SB',a)) 

520 liveBlind = False 

521 else: 

522 # Post dead blinds as ante 

523 hand.addBlind(a.group('PNAME'), 'secondsb', self.convertMoneyString('SB', a)) 

524 

525 # Find all instances of the big blind and add them to the Hand object. 

526 for a in self.re_PostBB.finditer(hand.handText): 

527 hand.addBlind(a.group('PNAME'), 'big blind', self.convertMoneyString('BB', a)) 

528 

529 # Find all instances of both blinds being posted and add them to the Hand object. 

530 for a in self.re_PostBoth.finditer(hand.handText): 

531 sb = Decimal(self.clearMoneyString(a.group('SB'))) 

532 bb = Decimal(self.clearMoneyString(a.group('BB'))) 

533 sbbb = sb + bb 

534 hand.addBlind(a.group('PNAME'), 'both', str(sbbb)) 

535 

536 

537 

538 def readHoleCards(self, hand): 

539 """ 

540 Reads the hero's hole cards from the given hand object and adds them to the corresponding streets. 

541 

542 Args: 

543 hand (Hand): The hand object containing the streets and player information. 

544 

545 Returns: 

546 None 

547 """ 

548 # Iterate through the streets where hole cards can be found 

549 for street in ('PREFLOP', 'DEAL'): 

550 # Check if the street is present in the hand object 

551 if street in list(hand.streets.keys()): 

552 # Use regex to find hero's cards in the street 

553 m = self.re_HeroCards.finditer(hand.streets[street]) 

554 # Iterate through each match found 

555 for found in m: 

556 # Save the hero's name 

557 hand.hero = found.group('PNAME') 

558 # Split the hole cards string into individual cards 

559 newcards = found.group('NEWCARDS').split(',') 

560 # Add the hole cards to the corresponding street 

561 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) 

562 

563 

564 def readAction(self, hand, street): 

565 """ 

566 Given a Hand object and a street string, reads the actions from the hand  

567 and updates the Hand object with the appropriate information. 

568 

569 Args: 

570 - hand: Hand object representing the current state of the hand 

571 - street: string representing the current betting round of the hand 

572 

573 Returns: 

574 None 

575 """ 

576 # Find all the actions in the current street of the hand 

577 m = self.re_Action.finditer(hand.streets[street]) 

578 

579 # Loop through each action and update the Hand object accordingly 

580 for action in m: 

581 acts = action.groupdict() 

582 #print "DEBUG: acts: %s" %acts 

583 bet = self.convertMoneyString('BET', action) 

584 actionType = action.group('ATYPE') 

585 

586 # If the current action is a fold and not in preflop, add a fold to the Hand object 

587 if street != 'PREFLOP' or actionType != ' folds': 

588 hand.setUncalledBets(False) 

589 if actionType == ' folds': 

590 hand.addFold(street, action.group('PNAME')) 

591 

592 # If the current action is a check, add a check to the Hand object 

593 elif actionType == ' checks': 

594 hand.addCheck(street, action.group('PNAME')) 

595 

596 # If the current action is a call, add a call to the Hand object 

597 elif actionType == ' calls': 

598 hand.addCall(street, action.group('PNAME'), bet) 

599 

600 # If the current action is a raise, add a raise to the Hand object 

601 elif actionType == ' raises': 

602 hand.setUncalledBets(None) 

603 hand.addRaiseTo(street, action.group('PNAME'), bet) 

604 

605 # If the current action is a bet, add a bet to the Hand object 

606 elif actionType == ' bets': 

607 hand.addBet(street, action.group('PNAME'), bet) 

608 

609 # If the current action is an all-in, add an all-in to the Hand object 

610 elif actionType == ' is all in': 

611 hand.addAllIn(street, action.group('PNAME'), bet) 

612 

613 # If the current action is not one of the above types, log an error 

614 else: 

615 log.error( 

616 ("DEBUG:") 

617 + " " 

618 + f"Unimplemented readAction: '{action.group('PNAME')}' '{action.group('ATYPE')}'" 

619 ) 

620 

621 

622 def readShowdownActions(self, hand): 

623 """ 

624 Parses a hand of cards and returns the best possible action to take in a game of poker. 

625 

626 Args: 

627 hand (list): A list of cards in the hand. 

628 

629 Returns: 

630 str: The best possible action to take. 

631 """ 

632 pass 

633 

634 

635 def readCollectPot(self,hand): 

636 """ 

637 Finds the collect pot for a given hand and adds it to the Hand object. 

638 

639 Args: 

640 hand (Hand): The Hand object to which the collect pot will be added. 

641 

642 Returns: 

643 None 

644 """ 

645 # Find all instances of the collect pot in the hand text. 

646 for m in self.re_CollectPot.finditer(hand.handText): 

647 # Only consider the collect pot if it is not part of a tournament hand. 

648 if not re.search('Tournament:\s', m.group('PNAME')): 

649 # Add the collect pot to the Hand object. 

650 hand.addCollectPot(player=m.group('PNAME'),pot=self.convertMoneyString('POT', m)) 

651 

652 

653 def readShownCards(self, hand): 

654 """ 

655 Finds shown cards in a hand text and adds them to the Hand object. 

656 

657 Args: 

658 hand: The Hand object to which shown cards will be added. 

659 

660 Returns: 

661 None 

662 """ 

663 

664 # Find shown cards using regular expression. 

665 for m in self.re_ShownCards.finditer(hand.handText): 

666 if m.group('CARDS') is not None: 

667 cards = m.group('CARDS') 

668 string = m.group('STRING') 

669 

670 # Check if player showed or mucked cards. 

671 (shown, mucked) = (False, False) 

672 if m.group('SHOWED') == "shows": 

673 shown = True 

674 # Split cards into a list. 

675 cards = cards.split(' ') 

676 elif m.group('SHOWED') == "mucks": 

677 mucked = True 

678 # Split cards into a list and remove any leading/trailing whitespace. 

679 cards = [c.strip() for c in cards.split(',')] 

680 

681 # Try to add shown cards to the hand. 

682 try: 

683 hand.checkPlayerExists(m.group('PNAME')) 

684 player = m.group('PNAME') 

685 except FpdbParseError: 

686 # If the player doesn't exist, replace underscores with spaces in the player name. 

687 player = m.group('PNAME').replace('_', ' ') 

688 

689 # Add shown cards to the hand. 

690 hand.addShownCards(cards=cards, player=player, shown=shown, mucked=mucked, string=string) 

691 

692 

693 def convertMoneyString(self, type, match): 

694 """ 

695 Converts a string of money to a float value. 

696 

697 Args: 

698 - type: string type of currency (e.g. "USD", "GBP", etc.) 

699 - match: string to be converted 

700 

701 Returns: 

702 - float value of the money string or None if no match found 

703 """ 

704 

705 if match.group('EUROVALUE'): 

706 # if the match is in EUROVALUE format, return the cleared money string 

707 return self.clearMoneyString(match.group('EUROVALUE')) 

708 elif match.group(type): 

709 # if the match is in the specified currency format, return the cleared money string 

710 return self.clearMoneyString(match.group(type)) 

711 else: 

712 # if no match found, return None 

713 return None 

714 

715 @staticmethod 

716 def getTableTitleRe(type, table_name=None, tournament = None, table_number=None): 

717 log.info( 

718 f"cake.getTableTitleRe: table_name='{table_name}' tournament='{tournament}' table_number='{table_number}'" 

719 ) 

720 regex = f"" 

721 print("regex get table cash title:", regex) 

722 if tournament: 

723 

724 regex = f"Tournament:\s{tournament}\sBuy\-In\s\w+\s:\sTable\s{table_number}" 

725 #Tournament: 17106061 Buy-In Freeroll : Table 10 - No Limit Holdem - 15/30 

726 print("regex get mtt sng expresso cash title:", regex) 

727 log.info(f"Seals.getTableTitleRe: returns: '{regex}'") 

728 return regex