Allow parsing of tournaments, draw and play money.

Sorry about the massive commit. There are still numerous bugs
parsing non-holdem hands and writehand() is broken for all
but holdem cash games.
This commit is contained in:
Ray 2009-07-11 13:44:32 -04:00
parent 4e952de825
commit d8820ae1f7
2 changed files with 108 additions and 49 deletions

View File

@ -45,6 +45,9 @@ class Hand:
self.maxseats = 10 self.maxseats = 10
self.counted_seats = 0 self.counted_seats = 0
self.buttonpos = 0 self.buttonpos = 0
self.tourNo = None
self.buyin = None
self.level = None
self.seating = [] self.seating = []
self.players = [] self.players = []
self.posted = [] self.posted = []
@ -56,17 +59,19 @@ class Hand:
self.actions = {} # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']] self.actions = {} # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']]
self.board = {} # dict from street names to community cards self.board = {} # dict from street names to community cards
self.holecards = {} self.holecards = {}
self.discards = {}
for street in self.allStreets: for street in self.allStreets:
self.streets[street] = "" # portions of the handText, filled by markStreets() self.streets[street] = "" # portions of the handText, filled by markStreets()
self.actions[street] = []
for street in self.actionStreets:
self.bets[street] = {} self.bets[street] = {}
self.lastBet[street] = 0 self.lastBet[street] = 0
self.actions[street] = []
self.board[street] = [] self.board[street] = []
for street in self.holeStreets:
self.holecards[street] = {} # dict from player names to holecards self.holecards[street] = {} # dict from player names to holecards
self.discards[street] = {} # dict from player names to dicts by street ... of tuples ... of discarded holecards
# Collections indexed by player names # Collections indexed by player names
# self.holecards = {} # dict from player names to dicts by street ... of tuples ... of holecards # self.holecards = {} # dict from player names to dicts by street ... of tuples ... of holecards
self.discards = {} # dict from player names to dicts by street ... of tuples ... of discarded holecards
self.stacks = {} self.stacks = {}
self.collected = [] #list of ? self.collected = [] #list of ?
self.collectees = {} # dict from player names to amounts collected (?) self.collectees = {} # dict from player names to amounts collected (?)
@ -93,6 +98,9 @@ class Hand:
("TABLE NAME", self.tablename), ("TABLE NAME", self.tablename),
("HERO", self.hero), ("HERO", self.hero),
("MAXSEATS", self.maxseats), ("MAXSEATS", self.maxseats),
("TOURNAMENT NO", self.tourNo),
("BUYIN", self.buyin),
("LEVEL", self.level),
("LASTBET", self.lastBet), ("LASTBET", self.lastBet),
("ACTION STREETS", self.actionStreets), ("ACTION STREETS", self.actionStreets),
("STREETS", self.streets), ("STREETS", self.streets),
@ -221,10 +229,10 @@ If a player has None chips he won't be added."""
self.players.append([seat, name, chips]) self.players.append([seat, name, chips])
self.stacks[name] = Decimal(chips) self.stacks[name] = Decimal(chips)
self.pot.addPlayer(name) self.pot.addPlayer(name)
for street in self.allStreets: for street in self.actionStreets:
self.bets[street][name] = [] self.bets[street][name] = []
#self.holecards[name] = {} # dict from street names. #self.holecards[name] = {} # dict from street names.
self.discards[name] = {} # dict from street names. #self.discards[name] = {} # dict from street names.
def addStreets(self, match): def addStreets(self, match):
@ -784,8 +792,10 @@ class DrawHand(Hand):
if gametype['base'] != 'draw': if gametype['base'] != 'draw':
pass # or indeed don't pass and complain instead pass # or indeed don't pass and complain instead
self.streetList = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE'] self.streetList = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.allStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.holeStreets = ['DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE'] self.holeStreets = ['DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.actionStreets = ['PREDEAL', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE'] self.actionStreets = ['PREDEAL', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.communityStreets = []
Hand.__init__(self, sitename, gametype, handText) Hand.__init__(self, sitename, gametype, handText)
self.sb = gametype['sb'] self.sb = gametype['sb']
self.bb = gametype['bb'] self.bb = gametype['bb']
@ -849,18 +859,19 @@ player (string) name of player
self.checkPlayerExists(player) self.checkPlayerExists(player)
# if shown and len(cardset) > 0: # if shown and len(cardset) > 0:
# self.shown.add(player) # self.shown.add(player)
self.holecards[player][street] = (newcards,oldcards) self.holecards[street][player] = (newcards,oldcards)
except FpdbParseError, e: except FpdbParseError, e:
print "[ERROR] Tried to add holecards for unknown player: %s" % (player,) print "[ERROR] Tried to add holecards for unknown player: %s" % (player,)
def discardDrawHoleCards(self, cards, player, street): def discardDrawHoleCards(self, cards, player, street):
logging.debug("discardDrawHoleCards '%s' '%s' '%s'" % (cards, player, street)) logging.debug("discardDrawHoleCards '%s' '%s' '%s'" % (cards, player, street))
self.discards[player][street] = set([cards]) self.discards[street][player] = set([cards])
def addDiscard(self, street, player, num, cards): def addDiscard(self, street, player, num, cards):
self.checkPlayerExists(player) self.checkPlayerExists(player)
print "street, player, num, cards =", street, player, num, cards
if cards: if cards:
act = (player, 'discards', num, cards) act = (player, 'discards', num, cards)
self.discardDrawHoleCards(cards, player, street) self.discardDrawHoleCards(cards, player, street)
@ -869,12 +880,12 @@ player (string) name of player
self.actions[street].append(act) self.actions[street].append(act)
def addShownCards(self, cards, player, holeandboard=None): # def addShownCards(self, cards, player, holeandboard=None, shown=False, mucked=False):
"""\ # """\
For when a player shows cards for any reason (for showdown or out of choice). #For when a player shows cards for any reason (for showdown or out of choice).
Card ranks will be uppercased #Card ranks will be uppercased
""" #"""
logging.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard)) # logging.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard))
# if cards is not None: # if cards is not None:
# self.shown.add(player) # self.shown.add(player)
# self.addHoleCards(cards,player) # self.addHoleCards(cards,player)
@ -884,6 +895,33 @@ Card ranks will be uppercased
# self.addHoleCards(holeandboard.difference(board),player,shown=True) # self.addHoleCards(holeandboard.difference(board),player,shown=True)
def addHoleCards(self, cards, player, shown, mucked, dealt=False):
"""\
Assigns observed holecards to a player.
cards list of card bigrams e.g. ['2h','Jc']
player (string) name of player
shown whether they were revealed at showdown
mucked whether they were mucked at showdown
dealt whether they were seen in a 'dealt to' line
"""
# I think this only gets called for shown cards.
logging.debug("addHoleCards %s %s" % (cards, player))
try:
self.checkPlayerExists(player)
except FpdbParseError, e:
print "[ERROR] Tried to add holecards for unknown player: %s" % (player,)
return
if dealt:
self.dealt.add(player)
if shown:
self.shown.add(player)
if mucked:
self.mucked.add(player)
if player != self.hero: #skip hero, we know his cards
print "player, cards =", player, cards
self.holecards[self.holeStreets[-1]][player] = (cards, set([]))
def writeHand(self, fh=sys.__stdout__): def writeHand(self, fh=sys.__stdout__):
# PokerStars format. # PokerStars format.
print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %("PokerStars", self.handid, self.getGameTypeAsString(), self.sb, self.bb, time.strftime('%Y/%m/%d %H:%M:%S ET', self.starttime))) print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %("PokerStars", self.handid, self.getGameTypeAsString(), self.sb, self.bb, time.strftime('%Y/%m/%d %H:%M:%S ET', self.starttime)))
@ -913,8 +951,8 @@ Card ranks will be uppercased
for act in self.actions['DRAWONE']: for act in self.actions['DRAWONE']:
print >>fh, self.actionString(act) print >>fh, self.actionString(act)
if act[0] == self.hero and act[1] == 'discards': if act[0] == self.hero and act[1] == 'discards':
(nc,oc) = self.holecards[act[0]]['DRAWONE'] (nc,oc) = self.holecards['DRAWONE'][act[0]]
dc = self.discards[act[0]]['DRAWONE'] dc = self.discards['DRAWONE'][act[0]]
kc = oc - dc kc = oc - dc
print >>fh, _("Dealt to %s [%s] [%s]" % (act[0], " ".join(kc), " ".join(nc))) print >>fh, _("Dealt to %s [%s] [%s]" % (act[0], " ".join(kc), " ".join(nc)))
@ -923,8 +961,8 @@ Card ranks will be uppercased
for act in self.actions['DRAWTWO']: for act in self.actions['DRAWTWO']:
print >>fh, self.actionString(act) print >>fh, self.actionString(act)
if act[0] == self.hero and act[1] == 'discards': if act[0] == self.hero and act[1] == 'discards':
(nc,oc) = self.holecards[act[0]]['DRAWTWO'] (nc,oc) = self.holecards['DRAWTWO'][act[0]]
dc = self.discards[act[0]]['DRAWTWO'] dc = self.discards['DRAWTWO'][act[0]]
kc = oc - dc kc = oc - dc
print >>fh, _("Dealt to %s [%s] [%s]" % (act[0], " ".join(kc), " ".join(nc))) print >>fh, _("Dealt to %s [%s] [%s]" % (act[0], " ".join(kc), " ".join(nc)))
@ -933,8 +971,8 @@ Card ranks will be uppercased
for act in self.actions['DRAWTHREE']: for act in self.actions['DRAWTHREE']:
print >>fh, self.actionString(act) print >>fh, self.actionString(act)
if act[0] == self.hero and act[1] == 'discards': if act[0] == self.hero and act[1] == 'discards':
(nc,oc) = self.holecards[act[0]]['DRAWTHREE'] (nc,oc) = self.holecards['DRAWTHREE'][act[0]]
dc = self.discards[act[0]]['DRAWTHREE'] dc = self.discards['DRAWTHREE'][act[0]]
kc = oc - dc kc = oc - dc
print >>fh, _("Dealt to %s [%s] [%s]" % (act[0], " ".join(kc), " ".join(nc))) print >>fh, _("Dealt to %s [%s] [%s]" % (act[0], " ".join(kc), " ".join(nc)))

View File

@ -30,7 +30,7 @@ class PokerStars(HandHistoryConverter):
re_GameInfo = re.compile("""PokerStars\sGame\s\#(?P<HID>[0-9]+):\s+ re_GameInfo = re.compile("""PokerStars\sGame\s\#(?P<HID>[0-9]+):\s+
(Tournament\s\#(?P<TOURNO>\d+),\s(?P<BUYIN>[\$\+\d\.]+)\s)? (Tournament\s\#(?P<TOURNO>\d+),\s(?P<BUYIN>[\$\+\d\.]+)\s)?
(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(? (?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(?
(?P<GAME>Hold\'em|Razz|7\sCard Stud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi)\s (?P<GAME>Hold\'em|Razz|7\sCard Stud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball)\s
(?P<LIMIT>No\sLimit|Limit|Pot\sLimit),?\s (?P<LIMIT>No\sLimit|Limit|Pot\sLimit),?\s
(-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?\(? (-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?\(?
(?P<CURRENCY>\$|)? (?P<CURRENCY>\$|)?
@ -40,7 +40,11 @@ class PokerStars(HandHistoryConverter):
re.MULTILINE|re.VERBOSE) re.MULTILINE|re.VERBOSE)
re_SplitHands = re.compile('\n\n+') re_SplitHands = re.compile('\n\n+')
re_TailSplitHands = re.compile('(\n\n\n+)') re_TailSplitHands = re.compile('(\n\n\n+)')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE) re_HandInfo = re.compile("""^Table\s\'(?P<TABLE>[-\ a-zA-Z\d]+)\'\s
((?P<MAX>\d+)-max\s)?
(?P<PLAY>\(Play\sMoney\)\s)?
(Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
re.MULTILINE|re.VERBOSE)
re_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE) re_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE)
re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE) re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
@ -73,8 +77,11 @@ follow : whether to tail -f the input"""
self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s: posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBoth = re.compile(r"^%s: posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE) self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
# self.re_Action = re.compile(r"^%s:(?P<ATYPE> bets| checks| raises| calls| folds| discards| stands pat)( \$(?P<BET>[.\d]+))?( to \$(?P<BETTO>[.\d]+))?( (?P<NODISCARDED>\d) cards?( \[(?P<DISCARDED>.+?)\])?)?" % player_re, re.MULTILINE) # self.re_Action = re.compile(r"^%s:(?P<ATYPE> bets| checks| raises| calls| folds| discards| stands pat)( \$?(?P<BET>[.\d]+))?( to \$?(?P<BETTO>[.\d]+))?( (?P<NODISCARDED>\d) cards?( \[(?P<DISCARDED>.+?)\])?)?" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s:(?P<ATYPE> bets| checks| raises| calls| folds| discards| stands pat)( \$?(?P<BET>[.\d]+))?( to \$?(?P<BETTO>[.\d]+))?( (?P<NODISCARDED>\d) cards?( \[(?P<DISCARDED>.+?)\])?)?" % player_re, re.MULTILINE) self.re_Action = re.compile(r"""^%s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat)
(\s\$?(?P<BET>[.\d]+))?(\sto\s\$?(?P<BETTO>[.\d]+))? # the number discarded goes in <BET>
(\scards?(\s\[(?P<DISCARDED>.+?)\])?)?"""
% player_re, re.MULTILINE|re.VERBOSE)
self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*|)" % player_re, re.MULTILINE) self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*|)" % player_re, re.MULTILINE)
self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE) self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
@ -86,7 +93,8 @@ follow : whether to tail -f the input"""
["ring", "hold", "fl"], ["ring", "hold", "fl"],
["ring", "stud", "fl"], ["ring", "stud", "fl"],
#["ring", "draw", "fl"],
["ring", "draw", "fl"],
["tour", "hold", "nl"], ["tour", "hold", "nl"],
["tour", "hold", "pl"], ["tour", "hold", "pl"],
@ -96,6 +104,9 @@ follow : whether to tail -f the input"""
] ]
def determineGameType(self, handText): def determineGameType(self, handText):
# inspect the handText and return the gametype dict
# gametype dict is:
# {'limitType': xxx, 'base': xxx, 'category': xxx}
info = {} info = {}
m = self.re_GameInfo.search(handText) m = self.re_GameInfo.search(handText)
@ -103,18 +114,18 @@ follow : whether to tail -f the input"""
return None return None
mg = m.groupdict() mg = m.groupdict()
# print "mg =", mg # translations from captured groups to fpdb info strings
# translations from captured groups to our info strings
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' } limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'}
games = { # base, category games = { # base, category
"Hold'em" : ('hold','holdem'), "Hold'em" : ('hold','holdem'),
'Omaha' : ('hold','omahahi'), 'Omaha' : ('hold','omahahi'),
'Omaha Hi/Lo' : ('hold','omahahilo'), 'Omaha Hi/Lo' : ('hold','omahahilo'),
'Razz' : ('stud','razz'), 'Razz' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi'), '7 Card Stud' : ('stud','studhi'),
'7 Card Stud Hi/Lo' : ('stud','studhilo'), '7 Card Stud Hi/Lo' : ('stud','studhilo'),
'Badugi' : ('draw','badugi') 'Badugi' : ('draw','badugi'),
'Triple Draw 2-7 Lowball' : ('draw','27_3draw'),
} }
currencies = { u'':'EUR', '$':'USD', '':'T$' } currencies = { u'':'EUR', '$':'USD', '':'T$' }
# I don't think this is doing what we think. mg will always have all # I don't think this is doing what we think. mg will always have all
@ -131,17 +142,13 @@ follow : whether to tail -f the input"""
info['bb'] = mg['BB'] info['bb'] = mg['BB']
if 'CURRENCY' in mg: if 'CURRENCY' in mg:
info['currency'] = currencies[mg['CURRENCY']] info['currency'] = currencies[mg['CURRENCY']]
if 'MIXED' in mg and mg['MIXED'] != None:
info['mixedType'] = mixes[mg['MIXED']] if 'TOURNO' in mg and mg['TOURNO'] == None:
info['tourNo'] = mg['TOURNO']
if info['tourNo'] == None:
info['type'] = 'ring' info['type'] = 'ring'
else: else:
info['type'] = 'tour' info['type'] = 'tour'
info['buyin'] = mg['BUYIN']
info['level'] = mg['LEVEL']
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info return info
@ -150,14 +157,13 @@ follow : whether to tail -f the input"""
m = self.re_HandInfo.search(hand.handText,re.DOTALL) m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m: if m:
info.update(m.groupdict()) info.update(m.groupdict())
# TODO: Be less lazy and parse maxseats from the HandInfo regex # hand.maxseats = int(m2.group(1))
if m.group('TABLEATTRIBUTES'): else:
m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES')) pass # throw an exception here, eh?
hand.maxseats = int(m2.group(1))
m = self.re_GameInfo.search(hand.handText) m = self.re_GameInfo.search(hand.handText)
if m: info.update(m.groupdict()) if m: info.update(m.groupdict())
m = self.re_Button.search(hand.handText) # m = self.re_Button.search(hand.handText)
if m: info.update(m.groupdict()) # if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info # TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info) logging.debug("readHandInfo: %s" % info)
for key in info: for key in info:
@ -174,6 +180,20 @@ follow : whether to tail -f the input"""
hand.tablename = info[key] hand.tablename = info[key]
if key == 'BUTTON': if key == 'BUTTON':
hand.buttonpos = info[key] hand.buttonpos = info[key]
if key == 'MAX':
hand.maxseats = info[key]
if key == 'MIXED':
hand.mixed = info[key]
if key == 'TOURNO':
hand.tourNo = info[key]
if key == 'BUYIN':
hand.buyin = info[key]
if key == 'LEVEL':
hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != None:
hand.currency = 'play' # overrides previously set value
def readButton(self, hand): def readButton(self, hand):
m = self.re_Button.search(hand.handText) m = self.re_Button.search(hand.handText)
@ -314,6 +334,7 @@ follow : whether to tail -f the input"""
def readAction(self, hand, street): def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
acts = action.groupdict()
if action.group('ATYPE') == ' raises': if action.group('ATYPE') == ' raises':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' calls': elif action.group('ATYPE') == ' calls':
@ -325,7 +346,7 @@ follow : whether to tail -f the input"""
elif action.group('ATYPE') == ' checks': elif action.group('ATYPE') == ' checks':
hand.addCheck( street, action.group('PNAME')) hand.addCheck( street, action.group('PNAME'))
elif action.group('ATYPE') == ' discards': elif action.group('ATYPE') == ' discards':
hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED')) hand.addDiscard(street, action.group('PNAME'), action.group('BET'), action.group('DISCARDED'))
elif action.group('ATYPE') == ' stands pat': elif action.group('ATYPE') == ' stands pat':
hand.addStandsPat( street, action.group('PNAME')) hand.addStandsPat( street, action.group('PNAME'))
else: else: