Coverage for CakeToFpdb.py: 0%
268 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 18:50 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 18:50 +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########################################################################
21from __future__ import division
23from past.utils import old_div
24#import L10n
25#_ = L10n.get_translation()
27from HandHistoryConverter import *
28from decimal_wrapper import Decimal
31class Cake(HandHistoryConverter):
32 """
33 A class for converting Cake hand histories to FPDB format.
35 Inherits from HandHistoryConverter.
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 """
46 # Class Variables
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 }
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 }
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$' }
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>.*$)
99 """ % substitutions, re.MULTILINE|re.VERBOSE)
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)
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>.+)\]")
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)
136 def compilePlayerRegexs(self, hand):
137 """
138 Compiles regular expressions representing the cards in a player's hand.
140 Args:
141 hand (list[str]): The cards in the player's hand.
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.
149 def readSupportedGames(self):
150 """
151 Returns a list of supported games.
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 ]
164 def determineGameType(self, handText):
165 """
166 Determine the type of game from the given hand text.
168 Args:
169 handText (str): The text of the hand.
171 Returns:
172 dict: A dictionary containing information about the game type.
173 """
174 # Initialize dictionary to store game type info
175 info = {}
177 # Search for game info in hand text
178 m = self.re_GameInfo.search(handText)
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
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
194 # Extract game info from match object's group dictionary
195 mg = m.groupdict()
197 # Determine limit type and store in info dictionary
198 if 'LIMIT' in mg:
199 info['limitType'] = self.limits[mg['LIMIT']]
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']]
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'])
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'])
219 # Determine currency and store in info dictionary
220 if 'CURRENCY' in mg:
221 info['currency'] = self.currencies[mg['CURRENCY']]
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']]
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'
233 # If play money game, set currency to 'play'
234 if 'TABLE' in mg and 'Play Money' in mg['TABLE']:
235 info['currency'] = 'play'
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")))
257 return info
261 def readHandInfo(self, hand):
262 """
263 Reads information from a hand history string and updates the corresponding Hand object.
265 Parameters:
266 hand (Hand): The Hand object to update.
268 Returns:
269 None
270 """
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:])
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
283 info |= m.groupdict()
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")
295 # extract hand ID
296 elif key == 'HID':
297 hand.handid = re.sub('[A-Z]+', '', info[key])
299 # extract table name for ring games
300 if key == 'TABLE' and hand.gametype['type'] == 'ring':
301 hand.tablename = info[key]
303 # extract table name for tournament games
304 if key == 'TABLENO' and hand.gametype['type'] == 'tour':
305 hand.tablename = info[key]
307 # extract button position
308 if key == 'BUTTON':
309 hand.buttonpos = info[key]
311 # extract maximum number of seats
312 if key == 'MAX' and info[key]:
313 hand.maxseats = int(info[key])
315 # extract tournament number
316 if key == 'TOURNO' and info[key]:
317 hand.tourNo = info[key].replace('T', '')
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])
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
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']))
353 if hand.gametype['type'] == 'tour' and not hand.buyin:
354 hand.buyin = 0
355 hand.fee = 0
356 hand.buyinCurrency="NA"
360 def readButton(self, hand):
361 """
362 Parses a hand for the button position and updates the hand object.
364 Args:
365 hand (Hand): The hand object to update.
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'))
379 def readPlayerStacks(self, hand):
380 """
381 Reads player stacks from the given `hand` object and adds them to the `hand` object.
383 Args:
384 hand (Hand): The `Hand` object to read player stacks from.
386 Returns:
387 None
388 """
389 # Find each player's stack information in the hand text
390 m = self.re_PlayerInfo.finditer(hand.handText)
392 # Check if there was a coinflip in the hand
393 coinflip = bool(self.re_CoinFlip.search(hand.handText))
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)
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))
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))
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.
422 Args:
423 - hand: a Hand object to extract street information from
425 Returns:
426 - None
427 """
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\].+))?"
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)
442 # Add the street information to the Hand object
443 hand.addStreets(m)
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.
450 Args:
451 hand (Hand): The current hand object.
452 street (str): The street to read the community cards for.
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(','))
464 def readAntes(self, hand):
465 """
466 Reads the antes from the hand and adds them to the Hand object.
468 Args:
469 hand: The Hand object to add the antes to.
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))
482 def readBringIn(self, hand):
483 """
484 Reads the BringIn information from the hand's handText and adds it to the hand object.
486 Args:
487 hand (Hand): The Hand object to add the BringIn information to.
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))
498 def readBlinds(self, hand):
499 """
500 Parses the hand text and extracts the blinds information.
502 Args:
503 hand: An instance of the Hand class representing the hand being parsed.
505 Returns:
506 None
507 """
509 # Flag to keep track of whether the small blind is still live.
510 liveBlind = True
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)
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))
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))
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))
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.
542 Args:
543 hand (Hand): The hand object containing the streets and player information.
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)
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.
569 Args:
570 - hand: Hand object representing the current state of the hand
571 - street: string representing the current betting round of the hand
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])
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')
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'))
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'))
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)
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)
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)
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)
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 )
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.
626 Args:
627 hand (list): A list of cards in the hand.
629 Returns:
630 str: The best possible action to take.
631 """
632 pass
635 def readCollectPot(self,hand):
636 """
637 Finds the collect pot for a given hand and adds it to the Hand object.
639 Args:
640 hand (Hand): The Hand object to which the collect pot will be added.
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))
653 def readShownCards(self, hand):
654 """
655 Finds shown cards in a hand text and adds them to the Hand object.
657 Args:
658 hand: The Hand object to which shown cards will be added.
660 Returns:
661 None
662 """
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')
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(',')]
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('_', ' ')
689 # Add shown cards to the hand.
690 hand.addShownCards(cards=cards, player=player, shown=shown, mucked=mucked, string=string)
693 def convertMoneyString(self, type, match):
694 """
695 Converts a string of money to a float value.
697 Args:
698 - type: string type of currency (e.g. "USD", "GBP", etc.)
699 - match: string to be converted
701 Returns:
702 - float value of the money string or None if no match found
703 """
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
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:
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