Coverage for CakeToFpdb.py: 0%

271 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-14 11:07 +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 HandHistoryConverter, FpdbParseError, FpdbHandPartial 

28from decimal import Decimal 

29import re 

30import logging 

31import datetime 

32 

33log = logging.getLogger("parser") 

34 

35 

36class Cake(HandHistoryConverter): 

37 """ 

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

39 

40 Inherits from HandHistoryConverter. 

41 

42 Class Variables: 

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

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

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

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

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

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

49 """ 

50 

51 # Class Variables 

52 

53 sitename = "Cake" 

54 filetype = "text" 

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

56 siteId = 17 

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

58 substitutions = { 

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

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

61 "PLYR": r"(?P<PNAME>.+?)", 

62 "CUR": r"(\$|€|)", 

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

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

65 } 

66 

67 # translations from captured groups to fpdb info strings 

68 Lim_Blinds = { 

69 "0.04": ("0.01", "0.02"), 

70 "0.08": ("0.02", "0.04"), 

71 "0.10": ("0.02", "0.05"), 

72 "0.20": ("0.05", "0.10"), 

73 "0.40": ("0.10", "0.20"), 

74 "0.50": ("0.10", "0.25"), 

75 "1.00": ("0.25", "0.50"), 

76 "1": ("0.25", "0.50"), 

77 "2.00": ("0.50", "1.00"), 

78 "2": ("0.50", "1.00"), 

79 "4.00": ("1.00", "2.00"), 

80 "4": ("1.00", "2.00"), 

81 "6.00": ("1.50", "3.00"), 

82 "6": ("1.50", "3.00"), 

83 "8.00": ("2.00", "4.00"), 

84 "8": ("2.00", "4.00"), 

85 "10.00": ("2.50", "5.00"), 

86 "10": ("2.50", "5.00"), 

87 "12.00": ("3.00", "6.00"), 

88 "12": ("3.00", "6.00"), 

89 "20.00": ("5.00", "10.00"), 

90 "20": ("5.00", "10.00"), 

91 "30.00": ("7.50", "15.00"), 

92 "30": ("7.50", "15.00"), 

93 "40.00": ("10.00", "20.00"), 

94 "40": ("10.00", "20.00"), 

95 "60.00": ("15.00", "30.00"), 

96 "60": ("15.00", "30.00"), 

97 "80.00": ("20.00", "40.00"), 

98 "80": ("20.00", "40.00"), 

99 "100.00": ("25.00", "50.00"), 

100 "100": ("25.00", "50.00"), 

101 "200.00": ("50.00", "100.00"), 

102 "200": ("50.00", "100.00"), 

103 "400.00": ("100.00", "200.00"), 

104 "400": ("100.00", "200.00"), 

105 "800.00": ("200.00", "400.00"), 

106 "800": ("200.00", "400.00"), 

107 "1000.00": ("250.00", "500.00"), 

108 "1000": ("250.00", "500.00"), 

109 } 

110 

111 limits = {"NL": "nl", "PL": "pl", "FL": "fl"} 

112 games = { # base, category 

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

114 "Omaha": ("hold", "omahahi"), 

115 "Omaha Hi/Lo": ("hold", "omahahilo"), 

116 "OmahaHiLo": ("hold", "omahahilo"), 

117 } 

118 currencies = {"€": "EUR", "$": "USD", "": "T$"} 

119 

120 # Static regexes 

121 re_GameInfo = re.compile( 

122 r""" 

123 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>.*$) 

124  

125 """ 

126 % substitutions, 

127 re.MULTILINE | re.VERBOSE, 

128 ) 

129 

130 re_PlayerInfo = re.compile( 

131 r""" 

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

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

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

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

136 % substitutions, 

137 re.MULTILINE | re.VERBOSE, 

138 ) 

139 

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

141 re_Identify = re.compile("Hand\#[A-Z0-9]+\s\-\s") 

142 re_SplitHands = re.compile("\n\n+") 

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

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

145 

146 re_DateTime = re.compile( 

147 """(?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]+)""", 

148 re.MULTILINE, 

149 ) 

150 re_PostSB = re.compile( 

151 r"^%(PLYR)s: posts small blind %(CUR)s(?P<SB>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?$" 

152 % substitutions, 

153 re.MULTILINE, 

154 ) 

155 re_PostBB = re.compile( 

156 r"^%(PLYR)s: posts big blind %(CUR)s(?P<BB>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?$" 

157 % substitutions, 

158 re.MULTILINE, 

159 ) 

160 re_Antes = re.compile( 

161 r"^%(PLYR)s: posts ante of %(CUR)s(?P<ANTE>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?" 

162 % substitutions, 

163 re.MULTILINE, 

164 ) 

165 re_BringIn = re.compile( 

166 r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[%(NUM)s]+)(\s\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?" 

167 % substitutions, 

168 re.MULTILINE, 

169 ) 

170 re_PostBoth = re.compile( 

171 r"^%(PLYR)s:posts dead blind %(CUR)s(\-)?(?P<SB>[%(NUM)s]+) and big blind %(CUR)s(?P<BB>[%(NUM)s]+)" 

172 % substitutions, 

173 re.MULTILINE, 

174 ) 

175 re_HeroCards = re.compile( 

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

177 ) 

178 re_Action = re.compile( 

179 r""" 

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

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

182 """ 

183 % substitutions, 

184 re.MULTILINE | re.VERBOSE, 

185 ) 

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

187 re_ShownCards = re.compile( 

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

189 ) 

190 re_CollectPot = re.compile( 

191 r"^%(PLYR)s:? wins (low pot |high pot )?%(CUR)s(?P<POT>[%(NUM)s]+)((\swith.+?)?\s+\(EUR\s(%(CUR)s)?(?P<EUROVALUE>[%(NUM)s]+)\))?" 

192 % substitutions, 

193 re.MULTILINE, 

194 ) 

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

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

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

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

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

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

201 

202 def compilePlayerRegexs(self, hand): 

203 """ 

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

205 

206 Args: 

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

208 

209 Returns: 

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

211 """ 

212 pass # TODO: Implement this function. 

213 

214 def readSupportedGames(self): 

215 """ 

216 Returns a list of supported games. 

217 

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

219 """ 

220 return [ 

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

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

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

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

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

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

227 ] 

228 

229 def determineGameType(self, handText): 

230 """ 

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

232 

233 Args: 

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

235 

236 Returns: 

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

238 """ 

239 # Initialize dictionary to store game type info 

240 info = {} 

241 

242 # Search for game info in hand text 

243 m = self.re_GameInfo.search(handText) 

244 

245 # If no game info found, raise appropriate error 

246 if not m: 

247 if self.re_Finished.search(handText): 

248 raise FpdbHandPartial 

249 if self.re_Dealer.match(handText): 

250 raise FpdbHandPartial 

251 tmp = handText[:200] 

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

253 raise FpdbParseError 

254 

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

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

257 raise FpdbHandPartial 

258 

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

260 mg = m.groupdict() 

261 

262 # Determine limit type and store in info dictionary 

263 if "LIMIT" in mg: 

264 info["limitType"] = self.limits[mg["LIMIT"]] 

265 

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

267 if "GAME" in mg: 

268 (info["base"], info["category"]) = self.games[mg["GAME"]] 

269 

270 # Determine big blind and store in info dictionary 

271 if "BB" in mg: 

272 if not mg["BB"]: 

273 info["bb"] = self.clearMoneyString(mg["SBBB"]) 

274 else: 

275 info["bb"] = self.clearMoneyString(mg["BB"]) 

276 

277 # Determine small blind and store in info dictionary 

278 if "SBBB" in mg: 

279 if not mg["BB"]: 

280 info["sb"] = self.clearMoneyString(mg["ANTESB"]) 

281 else: 

282 info["sb"] = self.clearMoneyString(mg["SBBB"]) 

283 

284 # Determine currency and store in info dictionary 

285 if "CURRENCY" in mg: 

286 info["currency"] = self.currencies[mg["CURRENCY"]] 

287 

288 # Determine mix and store in info dictionary 

289 if "MIXED" in mg and mg["MIXED"] is not None: 

290 info["mix"] = self.mixes[mg["MIXED"]] 

291 

292 # Determine game type and store in info dictionary 

293 if "TOURNO" in mg and mg["TOURNO"] is not None: 

294 info["type"] = "tour" 

295 else: 

296 info["type"] = "ring" 

297 

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

299 if "TABLE" in mg and "Play Money" in mg["TABLE"]: 

300 info["currency"] = "play" 

301 

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

303 if info["limitType"] == "fl" and info["bb"] is not None: 

304 # If game type is 'ring' 

305 if info["type"] == "ring": 

306 try: 

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

308 info["sb"] = self.Lim_Blinds[info["bb"]][0] 

309 info["bb"] = self.Lim_Blinds[info["bb"]][1] 

310 except KeyError as e: 

311 tmp = handText[:200] 

312 log.error(f"CakeToFpdb.determineGameType: Lim_Blinds has no lookup for '{mg['BB']}' - '{tmp}'") 

313 raise FpdbParseError from e 

314 # If game type is not 'ring' 

315 else: 

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

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

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

319 

320 return info 

321 

322 def readHandInfo(self, hand): 

323 """ 

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

325 

326 Parameters: 

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

328 

329 Returns: 

330 None 

331 """ 

332 

333 # trim off malformatted text from partially written hands 

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

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

336 

337 info = {} 

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

339 if m is None: 

340 tmp = hand.handText[:200] 

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

342 raise FpdbParseError 

343 

344 info |= m.groupdict() 

345 

346 for key in info: 

347 # extract datetime information and convert to UTC timezone 

348 if key == "DATETIME": 

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

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

351 for a in m1: 

352 datetimestr = ( 

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

354 ) 

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

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

357 

358 # extract hand ID 

359 elif key == "HID": 

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

361 

362 # extract table name for ring games 

363 if key == "TABLE" and hand.gametype["type"] == "ring": 

364 hand.tablename = info[key] 

365 

366 # extract table name for tournament games 

367 if key == "TABLENO" and hand.gametype["type"] == "tour": 

368 hand.tablename = info[key] 

369 

370 # extract button position 

371 if key == "BUTTON": 

372 hand.buttonpos = info[key] 

373 

374 # extract maximum number of seats 

375 if key == "MAX" and info[key]: 

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

377 

378 # extract tournament number 

379 if key == "TOURNO" and info[key]: 

380 hand.tourNo = info[key].replace("T", "") 

381 

382 # extract maximum number of seats for tournament games 

383 if key == "TMAX" and info[key]: 

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

385 if key == "TMAX1" and info[key]: 

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

387 

388 # extract buy-in information 

389 if key in ["BUYIN", "BUYIN1"] and info[key] and hand.tourNo is not None: 

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

391 hand.buyinCurrency = "USD" 

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

393 hand.buyinCurrency = "GBP" 

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

395 hand.buyinCurrency = "EUR" 

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

397 hand.buyinCurrency = "play" 

398 else: 

399 # FIXME: handle other currencies, play money 

400 log.error( 

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

402 ) 

403 raise FpdbParseError 

404 

405 # extract buy-in amount and rake amount 

406 if key == "BUYIN1": 

407 info["BIAMT1"] = self.clearMoneyString(info["BIAMT1"].strip("$€£")) 

408 hand.buyin = int(100 * Decimal(info["BIAMT1"])) 

409 hand.fee = 0 

410 else: 

411 info["BIAMT"] = self.clearMoneyString(info["BIAMT"].strip("$€£")) 

412 info["BIRAKE"] = self.clearMoneyString(info["BIRAKE"].strip("$€£")) 

413 hand.buyin = int(100 * Decimal(info["BIAMT"])) 

414 hand.fee = int(100 * Decimal(info["BIRAKE"])) 

415 

416 if hand.gametype["type"] == "tour" and not hand.buyin: 

417 hand.buyin = 0 

418 hand.fee = 0 

419 hand.buyinCurrency = "NA" 

420 

421 def readButton(self, hand): 

422 """ 

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

424 

425 Args: 

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

427 

428 Returns: 

429 None 

430 """ 

431 # Search for the button position in the hand text 

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

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

434 hand.buttonpos = int(m.group("BUTTON")) 

435 else: 

436 # If not found, log an info message 

437 log.info("readButton: " + ("not found")) 

438 

439 def readPlayerStacks(self, hand): 

440 """ 

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

442 

443 Args: 

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

445 

446 Returns: 

447 None 

448 """ 

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

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

451 

452 # Check if there was a coinflip in the hand 

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

454 

455 # Iterate over each player's stack information 

456 for a in m: 

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

458 if a.group("EUROVALUE"): 

459 hand.roundPenny = True 

460 cash_value = a.group("CASH") 

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

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

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

464 print("value:", cash_value) 

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

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

467 hand.addPlayer(int(a.group("SEAT")), a.group("PNAME"), cash_value) 

468 

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

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

471 

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

473 if coinflip: 

474 hand.addAnte(a.group("PNAME"), self.convertMoneyString("CASH", a)) 

475 

476 def markStreets(self, hand): 

477 """ 

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

479 and update the Hand object with the street information. 

480 

481 Args: 

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

483 

484 Returns: 

485 - None 

486 """ 

487 

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

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

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

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

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

493 regex = ( 

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

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

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

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

498 ) 

499 

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

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

502 

503 # Add the street information to the Hand object 

504 hand.addStreets(m) 

505 

506 def readCommunityCards(self, hand, street): 

507 """ 

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

509 

510 Args: 

511 hand (Hand): The current hand object. 

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

513 

514 Returns: 

515 None 

516 """ 

517 if street in ("FLOP", "TURN", "RIVER"): 

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

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

520 # Set the community cards in the hand object 

521 hand.setCommunityCards(street, m.group("CARDS").split(",")) 

522 

523 def readAntes(self, hand): 

524 """ 

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

526 

527 Args: 

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

529 

530 Returns: 

531 None 

532 """ 

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

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

535 for player in m: 

536 # Uncomment the following line to enable logging 

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

538 hand.addAnte(player.group("PNAME"), self.convertMoneyString("ANTE", player)) 

539 

540 def readBringIn(self, hand): 

541 """ 

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

543 

544 Args: 

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

546 

547 Returns: 

548 None 

549 """ 

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

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

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

553 hand.addBringIn(m.group("PNAME"), self.convertMoneyString("BRINGIN", m)) 

554 

555 def readBlinds(self, hand): 

556 """ 

557 Parses the hand text and extracts the blinds information. 

558 

559 Args: 

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

561 

562 Returns: 

563 None 

564 """ 

565 

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

567 liveBlind = True 

568 

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

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

571 hand.setUncalledBets(True) 

572 

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

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

575 if liveBlind: 

576 hand.addBlind(a.group("PNAME"), "small blind", self.convertMoneyString("SB", a)) 

577 liveBlind = False 

578 else: 

579 # Post dead blinds as ante 

580 hand.addBlind(a.group("PNAME"), "secondsb", self.convertMoneyString("SB", a)) 

581 

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

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

584 hand.addBlind(a.group("PNAME"), "big blind", self.convertMoneyString("BB", a)) 

585 

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

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

588 sb = Decimal(self.clearMoneyString(a.group("SB"))) 

589 bb = Decimal(self.clearMoneyString(a.group("BB"))) 

590 sbbb = sb + bb 

591 hand.addBlind(a.group("PNAME"), "both", str(sbbb)) 

592 

593 def readHoleCards(self, hand): 

594 """ 

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

596 

597 Args: 

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

599 

600 Returns: 

601 None 

602 """ 

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

604 for street in ("PREFLOP", "DEAL"): 

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

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

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

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

609 # Iterate through each match found 

610 for found in m: 

611 # Save the hero's name 

612 hand.hero = found.group("PNAME") 

613 # Split the hole cards string into individual cards 

614 newcards = found.group("NEWCARDS").split(",") 

615 # Add the hole cards to the corresponding street 

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

617 

618 def readAction(self, hand, street): 

619 """ 

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

621 and updates the Hand object with the appropriate information. 

622 

623 Args: 

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

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

626 

627 Returns: 

628 None 

629 """ 

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

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

632 

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

634 for action in m: 

635 # acts = action.groupdict() 

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

637 bet = self.convertMoneyString("BET", action) 

638 actionType = action.group("ATYPE") 

639 

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

641 if street != "PREFLOP" or actionType != " folds": 

642 hand.setUncalledBets(False) 

643 if actionType == " folds": 

644 hand.addFold(street, action.group("PNAME")) 

645 

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

647 elif actionType == " checks": 

648 hand.addCheck(street, action.group("PNAME")) 

649 

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

651 elif actionType == " calls": 

652 hand.addCall(street, action.group("PNAME"), bet) 

653 

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

655 elif actionType == " raises": 

656 hand.setUncalledBets(None) 

657 hand.addRaiseTo(street, action.group("PNAME"), bet) 

658 

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

660 elif actionType == " bets": 

661 hand.addBet(street, action.group("PNAME"), bet) 

662 

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

664 elif actionType == " is all in": 

665 hand.addAllIn(street, action.group("PNAME"), bet) 

666 

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

668 else: 

669 log.error( 

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

671 ) 

672 

673 def readShowdownActions(self, hand): 

674 """ 

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

676 

677 Args: 

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

679 

680 Returns: 

681 str: The best possible action to take. 

682 """ 

683 pass 

684 

685 def readCollectPot(self, hand): 

686 """ 

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

688 

689 Args: 

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

691 

692 Returns: 

693 None 

694 """ 

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

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

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

698 if not re.search("Tournament:\s", m.group("PNAME")): 

699 # Add the collect pot to the Hand object. 

700 hand.addCollectPot(player=m.group("PNAME"), pot=self.convertMoneyString("POT", m)) 

701 

702 def readShownCards(self, hand): 

703 """ 

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

705 

706 Args: 

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

708 

709 Returns: 

710 None 

711 """ 

712 

713 # Find shown cards using regular expression. 

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

715 if m.group("CARDS") is not None: 

716 cards = m.group("CARDS") 

717 string = m.group("STRING") 

718 

719 # Check if player showed or mucked cards. 

720 (shown, mucked) = (False, False) 

721 if m.group("SHOWED") == "shows": 

722 shown = True 

723 # Split cards into a list. 

724 cards = cards.split(" ") 

725 elif m.group("SHOWED") == "mucks": 

726 mucked = True 

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

728 cards = [c.strip() for c in cards.split(",")] 

729 

730 # Try to add shown cards to the hand. 

731 try: 

732 hand.checkPlayerExists(m.group("PNAME")) 

733 player = m.group("PNAME") 

734 except FpdbParseError: 

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

736 player = m.group("PNAME").replace("_", " ") 

737 

738 # Add shown cards to the hand. 

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

740 

741 def convertMoneyString(self, type, match): 

742 """ 

743 Converts a string of money to a float value. 

744 

745 Args: 

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

747 - match: string to be converted 

748 

749 Returns: 

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

751 """ 

752 

753 if match.group("EUROVALUE"): 

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

755 return self.clearMoneyString(match.group("EUROVALUE")) 

756 elif match.group(type): 

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

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

759 else: 

760 # if no match found, return None 

761 return None 

762 

763 @staticmethod 

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

765 log.info( 

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

767 ) 

768 regex = "" 

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

770 if tournament: 

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

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

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

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

775 return regex