Merge branch 'master' of git://git.assembla.com/fpdboz.git
This commit is contained in:
		
						commit
						793244fb6b
					
				|  | @ -33,6 +33,7 @@ psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2 | |||
| (py)pokereval v138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pypoker-eval-win32/pokereval-138.win32.exe/download | ||||
| (Note: There are no official windows builds, this installer is built from source.  A walkthrough is in the same directory as this walkthrough. | ||||
| 
 | ||||
| 
 | ||||
| 1.2/ MySQL  | ||||
| 
 | ||||
| Install the following file: | ||||
|  | @ -67,6 +68,26 @@ with this line: | |||
| 1.3.4/ Save and exit | ||||
| 
 | ||||
| 
 | ||||
| 1.4/ Patch py2exe to stop popup runtime error message | ||||
| 
 | ||||
| see http://www.py2exe.org/index.cgi/StderrLog for technical info. | ||||
| 
 | ||||
| 1.4.1/ | ||||
| 
 | ||||
| dos> write C:\Python26\Lib\site-packages\py2exe\boot_common.py | ||||
| 
 | ||||
| replace: | ||||
|                     atexit.register(alert, 0, | ||||
|                                     "See the logfile '%s' for details" % fname, | ||||
|                                     "Errors occurred") | ||||
| with: | ||||
|                     #atexit.register(alert, 0, | ||||
|                     #                "See the logfile '%s' for details" % fname, | ||||
|                     #                "Errors occurred") | ||||
| 
 | ||||
| 1.4.2/ save and exit | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| Step 2 Setup GTK | ||||
| ----------------- | ||||
|  |  | |||
|  | @ -2169,7 +2169,7 @@ class Database: | |||
|             dup = True | ||||
|         return dup | ||||
| 
 | ||||
|     def getGameTypeId(self, siteid, game): | ||||
|     def getGameTypeId(self, siteid, game, printdata = False): | ||||
|         c = self.get_cursor() | ||||
|         #FIXME: Fixed for NL at the moment | ||||
|         c.execute(self.sql.query['getGametypeNL'], (siteid, game['type'], game['category'], game['limitType'], game['currency'], | ||||
|  | @ -2181,14 +2181,22 @@ class Database: | |||
|                 hilo = "s" | ||||
|             elif game['category'] in ['razz','27_3draw','badugi', '27_1draw']: | ||||
|                 hilo = "l" | ||||
|             #FIXME: recognise currency | ||||
|             #TODO: this wont work for non-standard structures | ||||
|             tmp  = self.insertGameTypes( (siteid, game['currency'], game['type'], game['base'], game['category'], game['limitType'], hilo, | ||||
|                                     int(Decimal(game['sb'])*100), int(Decimal(game['bb'])*100), | ||||
|                                     int(Decimal(game['bb'])*100), int(Decimal(game['bb'])*200)) ) #TODO: this wont work for non-standard structures | ||||
|                                     #FIXME: recognise currency | ||||
|                                     int(Decimal(game['bb'])*100), int(Decimal(game['bb'])*200)), printdata = printdata) | ||||
|         return tmp[0] | ||||
| 
 | ||||
| 
 | ||||
|     def insertGameTypes(self, row): | ||||
|     def insertGameTypes(self, row, printdata = False): | ||||
|         if printdata: | ||||
|             print _("######## Gametype ##########") | ||||
|             import pprint | ||||
|             pp = pprint.PrettyPrinter(indent=4) | ||||
|             pp.pprint(row) | ||||
|             print _("###### End Gametype ########") | ||||
| 
 | ||||
|         c = self.get_cursor() | ||||
|         c.execute( self.sql.query['insertGameTypes'], row ) | ||||
|         return [self.get_last_insert_id(c)] | ||||
|  |  | |||
|  | @ -40,7 +40,6 @@ class DerivedStats(): | |||
|             self.handsplayers[player[1]]['winnings']    = 0 | ||||
|             self.handsplayers[player[1]]['rake']        = 0 | ||||
|             self.handsplayers[player[1]]['totalProfit'] = 0 | ||||
|             self.handsplayers[player[1]]['street4Seen'] = False | ||||
|             self.handsplayers[player[1]]['street4Aggr'] = False | ||||
|             self.handsplayers[player[1]]['wonWhenSeenStreet1'] = 0.0 | ||||
|             self.handsplayers[player[1]]['sawShowdown'] = False | ||||
|  | @ -58,6 +57,11 @@ class DerivedStats(): | |||
|             self.handsplayers[player[1]]['foldedSbToSteal']     = False | ||||
|             self.handsplayers[player[1]]['foldedBbToSteal']     = False | ||||
|             self.handsplayers[player[1]]['tourneyTypeId']       = None | ||||
|             self.handsplayers[player[1]]['street1Seen']         = False | ||||
|             self.handsplayers[player[1]]['street2Seen']         = False | ||||
|             self.handsplayers[player[1]]['street3Seen']         = False | ||||
|             self.handsplayers[player[1]]['street4Seen']         = False | ||||
| 
 | ||||
|              | ||||
|             for i in range(5):  | ||||
|                 self.handsplayers[player[1]]['street%dCalls' % i] = 0 | ||||
|  | @ -152,10 +156,11 @@ class DerivedStats(): | |||
|             else: | ||||
|                 self.handsplayers[player[1]]['tourneysPlayersIds'] = None | ||||
| 
 | ||||
|         #### seen now processed in playersAtStreetX() | ||||
|         # XXX: enumerate(list, start=x) is python 2.6 syntax; 'start' | ||||
|         #for i, street in enumerate(hand.actionStreets[2:], start=1): | ||||
|         for i, street in enumerate(hand.actionStreets[2:]): | ||||
|             self.seen(self.hand, i+1) | ||||
|         #for i, street in enumerate(hand.actionStreets[2:]): | ||||
|         #    self.seen(self.hand, i+1) | ||||
| 
 | ||||
|         for i, street in enumerate(hand.actionStreets[1:]): | ||||
|             self.aggr(self.hand, i) | ||||
|  | @ -298,6 +303,7 @@ class DerivedStats(): | |||
| 
 | ||||
|         # FIXME?? - This isn't couting people that are all in - at least showdown needs to reflect this | ||||
|         #     ... new code below hopefully fixes this | ||||
|         # partly fixed, allins are now set as seeing streets because they never do a fold action | ||||
| 
 | ||||
|         self.hands['playersAtStreet1']  = 0 | ||||
|         self.hands['playersAtStreet2']  = 0 | ||||
|  | @ -324,18 +330,58 @@ class DerivedStats(): | |||
|         # actionStreets[1] is 'DEAL', 'THIRD', 'PREFLOP', so any player dealt cards | ||||
|         # must act on this street if dealt cards. Almost certainly broken for the 'all-in blind' case | ||||
|         # and right now i don't care - CG | ||||
| 
 | ||||
|         p_in = set([x[0] for x in hand.actions[hand.actionStreets[1]]]) | ||||
| 
 | ||||
|         # | ||||
|         # discover who folded on each street and remove them from p_in | ||||
|         # | ||||
|         # i values as follows 0=BLINDSANTES 1=PREFLOP 2=FLOP 3=TURN 4=RIVER | ||||
|         #   (for flop games) | ||||
|         # | ||||
|         # At the beginning of the loop p_in contains the players with cards | ||||
|         # at the start of that street. | ||||
|         # p_in is reduced each street to become a list of players still-in | ||||
|         # e.g. when i=1 (preflop) all players who folded during preflop | ||||
|         # are found by pfba() and eliminated from p_in. | ||||
|         # Therefore at the end of the loop, p_in contains players remaining | ||||
|         # at the end of the action on that street, and can therefore be set | ||||
|         # as the value for the number of players who saw the next street | ||||
|         # | ||||
|         # note that i is 1 in advance of the actual street numbers in the db | ||||
|         # | ||||
|         # if p_in reduces to 1 player, we must bomb-out immediately | ||||
|         # because the hand is over, this will ensure playersAtStreetx | ||||
|         # is accurate. | ||||
|         # | ||||
|                      | ||||
|         for (i, street) in enumerate(hand.actionStreets): | ||||
|             if (i-1) in (1,2,3,4): | ||||
|                 # p_in stores players with cards at start of this street, | ||||
|                 # so can set streetxSeen & playersAtStreetx with this information | ||||
|                 # This hard-coded for i-1 =1,2,3,4 because those are the only columns | ||||
|                 # in the db! this code section also replaces seen() - more info log 66 | ||||
|                 # nb i=2=flop=street1Seen, hence i-1 term needed | ||||
|                 self.hands['playersAtStreet%d' % (i-1)] = len(p_in) | ||||
|                 for player_with_cards in p_in: | ||||
|                     self.handsplayers[player_with_cards]['street%sSeen' % (i-1)] = True | ||||
|             # | ||||
|             # find out who folded, and eliminate them from p_in | ||||
|             # | ||||
|             actions = hand.actions[street] | ||||
|             p_in = p_in - self.pfba(actions, l=('folds',)) | ||||
|             self.hands['playersAtStreet%d' % (i-1)] = len(p_in) | ||||
|          | ||||
|         self.hands['playersAtShowdown'] = len(p_in) | ||||
|             # | ||||
|             # if everyone folded, we are done, so exit this method immediately | ||||
|             # | ||||
|             if len(p_in) == 1: return None | ||||
| 
 | ||||
|         if self.hands['playersAtShowdown'] > 1: | ||||
|             for player in p_in: | ||||
|                 self.handsplayers[player]['sawShowdown'] = True | ||||
|         # | ||||
|         # The remaining players in p_in reached showdown (including all-ins | ||||
|         # because they never did a "fold" action in pfba() above) | ||||
|         # | ||||
|         self.hands['playersAtShowdown'] = len(p_in) | ||||
|         for showdown_player in p_in: | ||||
|             self.handsplayers[showdown_player]['sawShowdown'] = True | ||||
| 
 | ||||
|     def streetXRaises(self, hand): | ||||
|         # self.actions[street] is a list of all actions in a tuple, contining the action as the second element | ||||
|  | @ -447,17 +493,6 @@ class DerivedStats(): | |||
|                     self.handsplayers[pname]['street%dCheckCallRaiseChance' % (i+1)] = True | ||||
|                     self.handsplayers[pname]['street%dCheckCallRaiseDone' % (i+1)] = act!='folds' | ||||
| 
 | ||||
|     def seen(self, hand, i): | ||||
|         pas = set() | ||||
|         for act in hand.actions[hand.actionStreets[i+1]]: | ||||
|             pas.add(act[0]) | ||||
| 
 | ||||
|         for player in hand.players: | ||||
|             if player[1] in pas: | ||||
|                 self.handsplayers[player[1]]['street%sSeen' % i] = True | ||||
|             else: | ||||
|                 self.handsplayers[player[1]]['street%sSeen' % i] = False | ||||
| 
 | ||||
|     def aggr(self, hand, i): | ||||
|         aggrers = set() | ||||
|         others = set() | ||||
|  |  | |||
|  | @ -76,8 +76,8 @@ class Fulltilt(HandHistoryConverter): | |||
|                                          (\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+)) | ||||
|                                     ''' % substitutions, re.VERBOSE) | ||||
|     re_Button       = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) | ||||
|     re_PlayerInfo   = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \([%(LS)s](?P<CASH>[,.0-9]+)\)$' % substitutions, re.MULTILINE) | ||||
|     re_TourneysPlayerInfo   = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \([%(LS)s]?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$' % substitutions, re.MULTILINE) | ||||
|     re_PlayerInfo   = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \([%(LS)s]?(?P<CASH>[%(NUM)s]+)\)(?P<SITOUT>, is sitting out)?$' % substitutions, re.MULTILINE) | ||||
|     re_SummarySitout = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) is sitting out?$' % substitutions, re.MULTILINE) | ||||
|     re_Board        = re.compile(r"\[(?P<CARDS>.+)\]") | ||||
| 
 | ||||
|     #static regex for tourney purpose | ||||
|  | @ -150,7 +150,7 @@ class Fulltilt(HandHistoryConverter): | |||
|             self.re_HeroCards        = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE) | ||||
|             self.re_Action           = re.compile(r"^%(PLAYERS)s(?P<ATYPE> bets| checks| raises to| completes it to| calls| folds)( [%(LS)s]?(?P<BET>[%(NUM)s]+))?" % self.substitutions, 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]+): %(PLAYERS)s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \([%(LS)s]?(?P<POT>[%(NUM)s]+)\)(, mucked| with.*)" % self.substitutions, re.MULTILINE) | ||||
|             self.re_CollectPot       = re.compile(r"^Seat (?P<SEAT>[0-9]+): %(PLAYERS)s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \([%(LS)s]?(?P<POT>[%(NUM)s]+)\)(, mucked| with.*)?" % self.substitutions, re.MULTILINE) | ||||
|             self.re_SitsOut          = re.compile(r"^%s sits out" % player_re, re.MULTILINE) | ||||
|             self.re_ShownCards       = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(?P<ACT>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE) | ||||
| 
 | ||||
|  | @ -298,13 +298,24 @@ class Fulltilt(HandHistoryConverter): | |||
|         # Split hand text for FTP, as the regex matches the player names incorrectly | ||||
|         # in the summary section | ||||
|         pre, post = hand.handText.split('SUMMARY') | ||||
|         if hand.gametype['type'] == "ring" : | ||||
|             m = self.re_PlayerInfo.finditer(pre) | ||||
|         else:   #if hand.gametype['type'] == "tour" | ||||
|             m = self.re_TourneysPlayerInfo.finditer(pre) | ||||
|         m = self.re_PlayerInfo.finditer(pre) | ||||
|         plist = {} | ||||
| 
 | ||||
|         # Get list of players in header. | ||||
|         for a in m: | ||||
|             hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) | ||||
|             plist[a.group('PNAME')] = [int(a.group('SEAT')), a.group('CASH')] | ||||
| 
 | ||||
|         if hand.gametype['type'] == "ring" : | ||||
|             # Remove any listed as sitting out in the summary as start of hand info unreliable | ||||
|             n = self.re_SummarySitout.finditer(post) | ||||
|             for b in n: | ||||
|                 del plist[b.group('PNAME')] | ||||
|                 print "DEBUG: Deleting '%s' from player dict" %(b.group('PNAME')) | ||||
| 
 | ||||
|         # Add remaining players | ||||
|         for a in plist: | ||||
|             seat, stack = plist[a] | ||||
|             hand.addPlayer(seat, a, stack) | ||||
| 
 | ||||
| 
 | ||||
|     def markStreets(self, hand): | ||||
|  | @ -367,7 +378,11 @@ class Fulltilt(HandHistoryConverter): | |||
|             logging.warning(_("No bringin found, handid =%s") % hand.handid) | ||||
| 
 | ||||
|     def readButton(self, hand): | ||||
|         hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON')) | ||||
|         try: | ||||
|             hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON')) | ||||
|         except AttributeError, e: | ||||
|             # FTP has no indication that a hand is cancelled. | ||||
|             raise FpdbParseError(_("FTP: readButton: Failed to detect button (hand #%s cancelled?)") % hand.handid) | ||||
| 
 | ||||
|     def readHeroCards(self, hand): | ||||
| #    streets PREFLOP, PREDRAW, and THIRD are special cases beacause | ||||
|  |  | |||
|  | @ -59,7 +59,6 @@ class GuiAutoImport (threading.Thread): | |||
| 
 | ||||
|         self.importer = fpdb_import.Importer(self, self.settings, self.config, self.sql) | ||||
|         self.importer.setCallHud(True) | ||||
|         self.importer.setMinPrint(settings['minPrint']) | ||||
|         self.importer.setQuiet(False) | ||||
|         self.importer.setFailOnError(False) | ||||
|         self.importer.setHandCount(0) | ||||
|  | @ -338,14 +337,11 @@ if __name__== "__main__": | |||
| 
 | ||||
|     parser = OptionParser() | ||||
|     parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True, help="don't start gui") | ||||
|     parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int", | ||||
|                     help=_("How often to print a one-line status report (0 (default) means never)")) | ||||
|     (options, argv) = parser.parse_args() | ||||
| 
 | ||||
|     config = Configuration.Config() | ||||
| 
 | ||||
|     settings = {} | ||||
|     settings['minPrint'] = options.minPrint | ||||
|     if os.name == 'nt': settings['os'] = 'windows' | ||||
|     else:               settings['os'] = 'linuxmac' | ||||
| 
 | ||||
|  |  | |||
|  | @ -68,7 +68,6 @@ class GuiBulkImport(): | |||
| 
 | ||||
|                 #    get the import settings from the gui and save in the importer | ||||
|                 self.importer.setHandCount(int(self.spin_hands.get_text())) | ||||
|                 self.importer.setMinPrint(int(self.spin_hands.get_text())) | ||||
|                 self.importer.setQuiet(self.chk_st_st.get_active()) | ||||
|                 self.importer.setFailOnError(self.chk_fail.get_active()) | ||||
|                 self.importer.setThreads(int(self.spin_threads.get_text())) | ||||
|  | @ -344,8 +343,6 @@ def main(argv=None): | |||
|                     help=_("Conversion filter (*Full Tilt Poker, PokerStars, Everleaf, Absolute)")) | ||||
|     parser.add_option("-x", "--failOnError", action="store_true", default=False, | ||||
|                     help=_("If this option is passed it quits when it encounters any error")) | ||||
|     parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int", | ||||
|                     help=_("How often to print a one-line status report (0 (default) means never)")) | ||||
|     parser.add_option("-u", "--usage", action="store_true", dest="usage", default=False, | ||||
|                     help=_("Print some useful one liners")) | ||||
|     parser.add_option("-s", "--starsarchive", action="store_true", dest="starsArchive", default=False, | ||||
|  | @ -369,7 +366,6 @@ def main(argv=None): | |||
|     config = Configuration.Config() | ||||
| 
 | ||||
|     settings = {} | ||||
|     settings['minPrint'] = options.minPrint | ||||
|     if os.name == 'nt': settings['os'] = 'windows' | ||||
|     else:               settings['os'] = 'linuxmac' | ||||
| 
 | ||||
|  |  | |||
|  | @ -224,6 +224,9 @@ class GuiGraphViewer (threading.Thread): | |||
|                 self.graphBox.add(self.canvas) | ||||
|                 self.canvas.show() | ||||
|                 self.canvas.draw() | ||||
| 
 | ||||
|                 #TODO: Do something useful like alert user | ||||
|                 #print "No hands returned by graph query" | ||||
|             else: | ||||
|                 self.ax.set_title(_("Profit graph for ring games"+names),fontsize=12) | ||||
| 
 | ||||
|  | @ -337,10 +340,7 @@ class GuiGraphViewer (threading.Thread): | |||
|         if len(winnings) == 0: | ||||
|             return (None, None, None) | ||||
| 
 | ||||
|         #Insert a 0th entry into winnings so graph starts 'zerod' | ||||
|         winnings.insert(0, (0,0,0)) | ||||
| 
 | ||||
|         green = map(lambda x: float(x[1]), winnings) | ||||
|         green = map(lambda x:float(x[1]), winnings) | ||||
|         blue  = map(lambda x: float(x[1]) if x[2] == True  else 0.0, winnings) | ||||
|         red   = map(lambda x: float(x[1]) if x[2] == False else 0.0, winnings) | ||||
|         greenline = cumsum(green) | ||||
|  |  | |||
|  | @ -58,7 +58,7 @@ onlinehelp = {'Game':_('Type of Game'), | |||
|               'Saw_F':_('% Saw Flop vs hands dealt'), | ||||
|               'SawSD':_('Saw Show Down / River'), | ||||
|               'WtSDwsF':_('Went To Show Down When Saw Flop'), | ||||
|               'W$SD':_('Amount Won when Show Down seen'), | ||||
|               'W$SD':_('% Won some money at showdown'), | ||||
|               'FlAFq':_('Flop Aggression\n% Bet or Raise after seeing Flop'), | ||||
|               'TuAFq':_('Turn Aggression\n% Bet or Raise after seeing Turn'), | ||||
|               'RvAFq':_('River Aggression\n% Bet or Raise after seeing River'), | ||||
|  |  | |||
|  | @ -226,7 +226,7 @@ dealt   whether they were seen in a 'dealt to' line | |||
| 
 | ||||
|         self.holecards[street][player] = [open, closed] | ||||
| 
 | ||||
|     def prepInsert(self, db): | ||||
|     def prepInsert(self, db, printtest = False): | ||||
|         ##### | ||||
|         # Players, Gametypes, TourneyTypes are all shared functions that are needed for additional tables | ||||
|         # These functions are intended for prep insert eventually | ||||
|  | @ -235,7 +235,19 @@ dealt   whether they were seen in a 'dealt to' line | |||
|         self.dbid_pids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId) | ||||
| 
 | ||||
|         #Gametypes | ||||
|         self.dbid_gt = db.getGameTypeId(self.siteId, self.gametype) | ||||
|         hilo = "h" | ||||
|         if self.gametype['category'] in ['studhilo', 'omahahilo']: | ||||
|             hilo = "s" | ||||
|         elif self.gametype['category'] in ['razz','27_3draw','badugi', '27_1draw']: | ||||
|             hilo = "l" | ||||
| 
 | ||||
|         self.gametyperow = (self.siteId, self.gametype['currency'], self.gametype['type'], self.gametype['base'], | ||||
|                                     self.gametype['category'], self.gametype['limitType'], hilo, | ||||
|                                     int(Decimal(self.gametype['sb'])*100), int(Decimal(self.gametype['bb'])*100), | ||||
|                                     int(Decimal(self.gametype['bb'])*100), int(Decimal(self.gametype['bb'])*200)) | ||||
|         # Note: the above data is calculated in db.getGameTypeId | ||||
|         #       Only being calculated above so we can grab the testdata | ||||
|         self.dbid_gt = db.getGameTypeId(self.siteId, self.gametype, printdata = printtest) | ||||
| 
 | ||||
|         if self.tourNo!=None: | ||||
|             self.tourneyTypeId = db.createTourneyType(self) | ||||
|  | @ -500,7 +512,6 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio | |||
|         # Player in the big blind posts | ||||
|         #   - this is a call of 1 sb and a raise to 1 bb | ||||
|         # | ||||
| 
 | ||||
|         log.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount)) | ||||
|         if player is not None: | ||||
|             amount = re.sub(u',', u'', amount) #some sites have commas | ||||
|  | @ -520,9 +531,16 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio | |||
|                 self.bets['BLINDSANTES'][player].append(Decimal(self.sb)) | ||||
|                 self.pot.addCommonMoney(player, Decimal(self.sb)) | ||||
| 
 | ||||
|             self.bets['PREFLOP'][player].append(Decimal(amount)) | ||||
|             street = 'BLAH' | ||||
| 
 | ||||
|             if self.gametype['base'] == 'hold': | ||||
|                 street = 'PREFLOP' | ||||
|             elif self.gametype['base'] == 'draw': | ||||
|                 street = 'DEAL' | ||||
| 
 | ||||
|             self.bets[street][player].append(Decimal(amount)) | ||||
|             self.pot.addMoney(player, Decimal(amount)) | ||||
|             self.lastBet['PREFLOP'] = Decimal(amount) | ||||
|             self.lastBet[street] = Decimal(amount) | ||||
|             self.posted = self.posted + [[player,blindtype]] | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1142,34 +1160,6 @@ class DrawHand(Hand): | |||
|         elif builtFrom == "DB": | ||||
|             self.select("dummy") # Will need a handId | ||||
| 
 | ||||
|     # Draw games (at least Badugi has blinds - override default Holdem addBlind | ||||
|     def addBlind(self, player, blindtype, amount): | ||||
|         # if player is None, it's a missing small blind. | ||||
|         # The situation we need to cover are: | ||||
|         # Player in small blind posts | ||||
|         #   - this is a bet of 1 sb, as yet uncalled. | ||||
|         # Player in the big blind posts | ||||
|         #   - this is a call of 1 sb and a raise to 1 bb | ||||
|         # | ||||
| 
 | ||||
|         log.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount)) | ||||
|         if player is not None: | ||||
|             self.bets['DEAL'][player].append(Decimal(amount)) | ||||
|             self.stacks[player] -= Decimal(amount) | ||||
|             #print "DEBUG %s posts, stack %s" % (player, self.stacks[player]) | ||||
|             act = (player, blindtype, Decimal(amount), self.stacks[player]==0) | ||||
|             self.actions['BLINDSANTES'].append(act) | ||||
|             self.pot.addMoney(player, Decimal(amount)) | ||||
|             if blindtype == 'big blind': | ||||
|                 self.lastBet['DEAL'] = Decimal(amount) | ||||
|             elif blindtype == 'both': | ||||
|                 # extra small blind is 'dead' | ||||
|                 amount = Decimal(amount)/3 | ||||
|                 amount += amount | ||||
|                 self.lastBet['DEAL'] = Decimal(amount) | ||||
|         self.posted = self.posted + [[player,blindtype]] | ||||
|         #print "DEBUG: self.posted: %s" %(self.posted) | ||||
| 
 | ||||
|     def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False): | ||||
|         if player == self.hero: # we have hero's cards just update shown/mucked | ||||
|             if shown:  self.shown.add(player) | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ class PokerStars(HandHistoryConverter): | |||
|                        '10.00': ('2.00', '5.00'),      '10': ('2.00', '5.00'), | ||||
|                        '20.00': ('5.00', '10.00'),     '20': ('5.00', '10.00'), | ||||
|                        '30.00': ('10.00', '15.00'),    '30': ('10.00', '15.00'), | ||||
|                        '40.00': ('10.00', '20.00'),    '40': ('10.00', '20.00'), | ||||
|                        '60.00': ('15.00', '30.00'),    '60': ('15.00', '30.00'), | ||||
|                        '80.00': ('20.00', '40.00'),    '80': ('20.00', '40.00'), | ||||
|                       '100.00': ('25.00', '50.00'),   '100': ('25.00', '50.00'), | ||||
|  |  | |||
							
								
								
									
										333
									
								
								pyfpdb/RushNotesAux.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								pyfpdb/RushNotesAux.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,333 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| """RushNotesAux.py | ||||
| 
 | ||||
|             EXPERIMENTAL - USE WITH CARE | ||||
|              | ||||
| Auxilliary process to push HUD data into the FullTilt player notes XML | ||||
| This will allow a rudimentary "HUD" in rush games | ||||
| 
 | ||||
| The existing notes file will be altered by this function | ||||
| """ | ||||
| #    Copyright 2010,  "Gimick" of the FPDB project  fpdb.sourceforge.net | ||||
| # | ||||
| #This program is free software: you can redistribute it and/or modify | ||||
| #it under the terms of the GNU Affero General Public License as published by | ||||
| #the Free Software Foundation, version 3 of the License. | ||||
| # | ||||
| #This program is distributed in the hope that it will be useful, | ||||
| #but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| #GNU General Public License for more details. | ||||
| # | ||||
| #You should have received a copy of the GNU Affero General Public License | ||||
| #along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
| #In the "official" distribution you can find the license in agpl-3.0.txt. | ||||
| 
 | ||||
| ######################################################################## | ||||
| 
 | ||||
| ##########for each hand processed, attempts to create update for player notes in FullTilt | ||||
| ##########based upon the AW howto notes written by Ray E. Barker (nutomatic) at fpdb.sourceforge.net | ||||
| ##########Huge thanks to Ray for his guidance and encouragement to create this !! | ||||
| 
 | ||||
| # | ||||
| #debugmode will write logfiles for the __init__ and update_data methods | ||||
| # writes into ./pyfpdb/~Rushdebug.* | ||||
| # | ||||
| debugmode = False | ||||
| 
 | ||||
| #    Standard Library modules | ||||
| import os | ||||
| import sys | ||||
| from xml.dom import minidom  | ||||
| from datetime import datetime | ||||
| from time import * | ||||
| 
 | ||||
| #    FreePokerDatabase modules | ||||
| from Mucked import Aux_Window | ||||
| from Mucked import Seat_Window | ||||
| from Mucked import Aux_Seats | ||||
| import Stats | ||||
| import Card | ||||
| 
 | ||||
| # | ||||
| # overload minidom methods to fix bug where \n is parsed as " ". | ||||
| # described here: http://bugs.python.org/issue7139 | ||||
| # | ||||
| 
 | ||||
| def _write_data(writer, data, isAttrib=False): | ||||
|     "Writes datachars to writer." | ||||
|     if isAttrib: | ||||
|         data = data.replace("\r", "
").replace("\n", "
") | ||||
|         data = data.replace("\t", "	") | ||||
|     writer.write(data) | ||||
| minidom._write_data = _write_data | ||||
| 
 | ||||
| def writexml(self, writer, indent="", addindent="", newl=""): | ||||
|     # indent = current indentation | ||||
|     # addindent = indentation to add to higher levels | ||||
|     # newl = newline string | ||||
|     writer.write(indent+"<" + self.tagName) | ||||
| 
 | ||||
|     attrs = self._get_attributes() | ||||
|     a_names = attrs.keys() | ||||
|     a_names.sort() | ||||
| 
 | ||||
|     for a_name in a_names: | ||||
|         writer.write(" %s=\"" % a_name) | ||||
|         _write_data(writer, attrs[a_name].value, isAttrib=True) | ||||
|         writer.write("\"") | ||||
|     if self.childNodes: | ||||
|         writer.write(">%s"%(newl)) | ||||
|         for node in self.childNodes: | ||||
|             node.writexml(writer,indent+addindent,addindent,newl) | ||||
|         writer.write("%s</%s>%s" % (indent,self.tagName,newl)) | ||||
|     else: | ||||
|         writer.write("/>%s"%(newl)) | ||||
| # For an introduction to overriding instance methods, see | ||||
| #   http://irrepupavel.com/documents/python/instancemethod/ | ||||
| instancemethod = type(minidom.Element.writexml) | ||||
| minidom.Element.writexml = instancemethod( | ||||
|     writexml, None, minidom.Element) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class RushNotes(Aux_Window): | ||||
| 
 | ||||
|     def __init__(self, hud, config, params): | ||||
| 
 | ||||
|         self.hud = hud | ||||
|         self.config = config | ||||
|          | ||||
|         # | ||||
|         # following line makes all the site params magically available (thanks Ray!)         | ||||
|         # | ||||
|         site_params_dict = self.hud.config.get_site_parameters(self.hud.site) | ||||
|          | ||||
|         heroname = site_params_dict['screen_name'] | ||||
|         sitename = site_params_dict['site_name'] | ||||
|         notepath = site_params_dict['site_path']  # this is a temporary hijack of site-path | ||||
|         self.heroid = self.hud.db_connection.get_player_id(self.config, sitename, heroname) | ||||
|         self.notefile = notepath + "/" + heroname + ".xml" | ||||
|         self.rushtables = ("Mach 10", "Lightning", "Celerity", "Flash", "Zoom") | ||||
| 
 | ||||
|         if not os.path.isfile(self.notefile): | ||||
|             self.active = False | ||||
|             return | ||||
|         else: | ||||
|             self.active = True | ||||
| 
 | ||||
|         # | ||||
|         # read in existing notefile and backup with date/time in name | ||||
|         # todo change to not use dom | ||||
|         # | ||||
|         now = datetime.now() | ||||
|         notefilebackup = self.notefile + ".backup." + now.strftime("%Y%m%d%H%M%S") | ||||
|         xmlnotefile = minidom.parse(self.notefile)         | ||||
|         outputfile = open(notefilebackup, 'w') | ||||
|         xmlnotefile.writexml(outputfile) | ||||
|         outputfile.close() | ||||
|         xmlnotefile.unlink | ||||
| 
 | ||||
|         # | ||||
|         # Create a fresh queue file with skeleton XML | ||||
|         # | ||||
|         self.queuefile = self.notefile + ".queue" | ||||
|         queuedom = minidom.Document() | ||||
| 
 | ||||
|         pld=queuedom.createElement("PLAYERDATA") | ||||
|         queuedom.appendChild(pld) | ||||
| 
 | ||||
|         nts=queuedom.createElement("NOTES") | ||||
|         pld.appendChild(nts) | ||||
| 
 | ||||
|         nte = queuedom.createElement("NOTE") | ||||
|         nte = queuedom.createTextNode("\n") | ||||
|         nts.insertBefore(nte,None) | ||||
| 
 | ||||
|         outputfile = open(self.queuefile, 'w') | ||||
|         queuedom.writexml(outputfile) | ||||
|         outputfile.close() | ||||
|         queuedom.unlink | ||||
| 
 | ||||
|         if (debugmode): | ||||
|             #initialise logfiles | ||||
|             debugfile=open("~Rushdebug.init", "w") | ||||
|             debugfile.write("conf="+str(config)+"\n") | ||||
|             debugfile.write("spdi="+str(site_params_dict)+"\n") | ||||
|             debugfile.write("para="+str(params)+"\n") | ||||
|             debugfile.write("hero="+heroname+" "+str(self.heroid)+"\n") | ||||
|             debugfile.write("back="+notefilebackup+"\n") | ||||
|             debugfile.write("queu="+self.queuefile+"\n") | ||||
|             debugfile.close() | ||||
| 
 | ||||
|             open("~Rushdebug.data", "w").close() | ||||
| 
 | ||||
| 
 | ||||
|     def update_data(self, new_hand_id, db_connection): | ||||
|         #this method called once for every hand processed | ||||
|         # self.hud.stat_dict contains the stats information for this hand | ||||
| 
 | ||||
|         if not self.active: | ||||
|             return | ||||
| 
 | ||||
|         if (debugmode): | ||||
|             debugfile=open("~Rushdebug.data", "a") | ||||
|             debugfile.write(new_hand_id+"\n") | ||||
|             now = datetime.now() | ||||
|             debugfile.write(now.strftime("%Y%m%d%H%M%S")+ " update_data begins"+ "\n") | ||||
|             debugfile.write("hero="+str(self.heroid)+"\n") | ||||
|             #debugfile.write(str(self.hud.stat_dict)+"\n") | ||||
|             debugfile.write("table="+self.hud.table.name+"\n") | ||||
|             debugfile.write("players="+str(self.hud.stat_dict.keys())+"\n") | ||||
|             debugfile.write("db="+str(db_connection)+"\n") | ||||
| 
 | ||||
|         if self.hud.table.name not in self.rushtables: | ||||
|             return | ||||
|         # | ||||
|         # Grab a list of player id's | ||||
|         # | ||||
|         handplayers = self.hud.stat_dict.keys()   | ||||
| 
 | ||||
|         # | ||||
|         # build a dictionary of stats text for each player in the hand (excluding the hero) | ||||
|         # xmlqueuedict contains {playername : stats text} | ||||
|         # | ||||
|         xmlqueuedict = {} | ||||
|         for playerid in handplayers: | ||||
|             # ignore hero, no notes available for hero at Full Tilt | ||||
|             if playerid == self.heroid: continue | ||||
| 
 | ||||
|             playername=unicode(str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'playername')[1])) | ||||
|             # Use index[3] which is a short description | ||||
|             n=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'n')[3] + " ") | ||||
|             vpip=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'vpip')[3] + " ") | ||||
|             pfr=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'pfr')[3] + " ") | ||||
|             three_B=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'three_B')[3] + " ") | ||||
|             cbet=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'cbet')[3] + " ") | ||||
|             fbbsteal=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'f_BB_steal')[3] + " ") | ||||
| 
 | ||||
|             steal=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'steal')[3] + " ") | ||||
|             ffreq1=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'ffreq1')[3] + " ") | ||||
|             agg_freq=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'agg_freq')[3] + " ") | ||||
|             BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3]) | ||||
|             if BBper100[6] == "-": BBper100=BBper100[0:6] + "(" + BBper100[7:] + ")" | ||||
| 
 | ||||
| 
 | ||||
|             # | ||||
|             # grab villain known starting hands | ||||
|             # only those where they VPIP'd, so limp in the BB will not be shown | ||||
|             # sort by hand strength.  Output will show position too, | ||||
|             #  so KK.1 is KK from late posn etc. | ||||
|             # ignore non-rush hands (check against known rushtablenames) | ||||
|             #  cards decoding is hard-coded for holdem, so that's tuff atm | ||||
|             # three categories of known hands are shown: | ||||
|             #    agression preflop hands | ||||
|             #    non-aggression preflop hands | ||||
|             #    bigblind called to defend hands | ||||
|             # | ||||
|             # This isn't perfect, but it isn't too bad a starting point | ||||
|             # | ||||
| 
 | ||||
|             PFcall="PFcall" | ||||
|             PFaggr="PFaggr" | ||||
|             PFdefend="PFdefend" | ||||
| 
 | ||||
|             c = db_connection.get_cursor() | ||||
|             c.execute(("SELECT handId, position, startCards, street0Aggr, tableName " + | ||||
|                         "FROM hands, handsPlayers " + | ||||
|                         "WHERE handsplayers.handId = hands.id " + | ||||
|                         "AND street0VPI = 1 " + | ||||
|                         "AND startCards > 0 " + | ||||
|                         "AND playerId = %d " + | ||||
|                         "ORDER BY startCards DESC " + | ||||
|                         ";") | ||||
|                          % int(playerid)) | ||||
| 
 | ||||
|             for (qid, qposition, qstartcards, qstreet0Aggr, qtablename) in c.fetchall(): | ||||
|                 if (debugmode): | ||||
|                     debugfile.write("pid, hid, pos, cards, aggr, table player"+ | ||||
|                                     str(playerid)+"/"+str(qid)+"/"+str(qposition)+"/"+ | ||||
|                                     str(qstartcards)+"/"+str(qstreet0Aggr)+"/"+ | ||||
|                                     str(qtablename)+"/"+str(playername)+ | ||||
|                                     "\n") | ||||
| 
 | ||||
|                 humancards = Card.decodeStartHandValue("holdem", qstartcards) | ||||
|                  | ||||
|                 if qtablename not in self.rushtables: | ||||
|                     pass | ||||
|                 elif qposition == "B" and qstreet0Aggr == False: | ||||
|                     PFdefend=PFdefend+"/"+humancards | ||||
|                 elif qstreet0Aggr == True: | ||||
|                     PFaggr=PFaggr+"/"+humancards+"."+qposition | ||||
|                 else: | ||||
|                     PFcall=PFcall+"/"+humancards+"."+qposition | ||||
|             c.close | ||||
| 
 | ||||
|             # | ||||
|             # build up final text package (top/tail with ~fpdb~ ~ends~ | ||||
|             # for later search/replace by Merge module | ||||
|             # | ||||
|             xmlqueuedict[playername] = ("~fpdb~" + "\n" + | ||||
|                                         n + vpip + pfr + three_B + fbbsteal + "\n" + | ||||
|                                         steal + cbet + ffreq1 + "\n" + | ||||
|                                         agg_freq + BBper100 + "\n" + | ||||
|                                         PFcall+"\n"+PFaggr+"\n"+PFdefend +"\n" | ||||
|                                         "~ends~") | ||||
| 
 | ||||
|         if (debugmode): | ||||
|             now = datetime.now() | ||||
|             debugfile.write(now.strftime("%Y%m%d%H%M%S")+" villain data has been processed" + "\n") | ||||
|             debugfile.write(str(xmlqueuedict)+"\n") | ||||
| 
 | ||||
|         # | ||||
|         # delaying processing of xml until now.  Grab current queuefile contents and | ||||
|         # read each existing NOTE element in turn, if matched to a player in xmlqueuedict | ||||
|         # update their text in the xml and delete the dictionary item | ||||
|         #  | ||||
|         xmlnotefile = minidom.parse(self.queuefile) | ||||
|         notelist = xmlnotefile.getElementsByTagName('NOTE') | ||||
|          | ||||
|         for noteentry in notelist:                              #for each note in turn | ||||
|             noteplayer = noteentry.getAttribute("PlayerId")     #extract the playername from xml | ||||
|             if noteplayer in xmlqueuedict:                      # does that player exist in the queue? | ||||
|                 noteentry.setAttribute("Text",xmlqueuedict[noteplayer]) | ||||
|                 del xmlqueuedict[noteplayer]                    #remove from list, does not need to be added later on | ||||
| 
 | ||||
|         # | ||||
|         #create entries for new players (those remaining in the dictionary) | ||||
|         # | ||||
|         if len(xmlqueuedict) > 0: | ||||
|             playerdata=xmlnotefile.lastChild #move to the PLAYERDATA node (assume last one in the list) | ||||
|             notesnode=playerdata.childNodes[0] #Find NOTES node  | ||||
| 
 | ||||
|             for newplayer in xmlqueuedict: | ||||
|                 newentry = xmlnotefile.createElement("NOTE") | ||||
|                 newentry.setAttribute("PlayerId", newplayer) | ||||
|                 newentry.setAttribute("Text", xmlqueuedict[newplayer]) | ||||
|                 notesnode.insertBefore(newentry,None) | ||||
|                 newentry = xmlnotefile.createTextNode("\n") | ||||
|                 notesnode.insertBefore(newentry,None) | ||||
| 
 | ||||
|         if (debugmode): | ||||
|             now = datetime.now() | ||||
|             debugfile.write(now.strftime("%Y%m%d%H%M%S")+" xml pre-processing complete"+ "\n") | ||||
| 
 | ||||
|         # | ||||
|         # OverWrite existing xml file with updated DOM and cleanup | ||||
|         # | ||||
|         updatednotes = open(self.queuefile, 'w') | ||||
|         xmlnotefile.writexml(updatednotes) | ||||
|         updatednotes.close() | ||||
| 
 | ||||
|         xmlnotefile.unlink | ||||
| 
 | ||||
|         if (debugmode): | ||||
|             now = datetime.now() | ||||
|             debugfile.write(now.strftime("%Y%m%d%H%M%S")+" dom written, process finished"+ "\n") | ||||
|             debugfile.close() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										183
									
								
								pyfpdb/RushNotesMerge.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										183
									
								
								pyfpdb/RushNotesMerge.py
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,183 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| """RushNotesMerge.py | ||||
| 
 | ||||
|             EXPERIMENTAL - USE WITH CARE | ||||
| 
 | ||||
| Merge .queue file with hero's note to generate fresh .merge file | ||||
| 
 | ||||
| normal usage  | ||||
| $> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/heroname.xml" | ||||
| 
 | ||||
| The generated file can then replace heroname.xml (if all is well). | ||||
| 
 | ||||
| 
 | ||||
| """ | ||||
| #    Copyright 2010,  "Gimick" of the FPDB project  fpdb.sourceforge.net | ||||
| # | ||||
| #This program is free software: you can redistribute it and/or modify | ||||
| #it under the terms of the GNU Affero General Public License as published by | ||||
| #the Free Software Foundation, version 3 of the License. | ||||
| # | ||||
| #This program is distributed in the hope that it will be useful, | ||||
| #but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| #GNU General Public License for more details. | ||||
| # | ||||
| #You should have received a copy of the GNU Affero General Public License | ||||
| #along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
| #In the "official" distribution you can find the license in agpl-3.0.txt. | ||||
| 
 | ||||
| ######################################################################## | ||||
| 
 | ||||
| 
 | ||||
| #    Standard Library modules | ||||
| import os | ||||
| import sys | ||||
| from xml.dom import minidom  | ||||
| 
 | ||||
| # | ||||
| # overload minidom methods to fix bug where \n is parsed as " ". | ||||
| # described here: http://bugs.python.org/issue7139 | ||||
| # | ||||
| 
 | ||||
| def _write_data(writer, data, isAttrib=False): | ||||
|     "Writes datachars to writer." | ||||
|     if isAttrib: | ||||
|         data = data.replace("\r", "
").replace("\n", "
") | ||||
|         data = data.replace("\t", "	") | ||||
|     writer.write(data) | ||||
| minidom._write_data = _write_data | ||||
| 
 | ||||
| def writexml(self, writer, indent="", addindent="", newl=""): | ||||
|     # indent = current indentation | ||||
|     # addindent = indentation to add to higher levels | ||||
|     # newl = newline string | ||||
|     writer.write(indent+"<" + self.tagName) | ||||
| 
 | ||||
|     attrs = self._get_attributes() | ||||
|     a_names = attrs.keys() | ||||
|     a_names.sort() | ||||
| 
 | ||||
|     for a_name in a_names: | ||||
|         writer.write(" %s=\"" % a_name) | ||||
|         _write_data(writer, attrs[a_name].value, isAttrib=True) | ||||
|         writer.write("\"") | ||||
|     if self.childNodes: | ||||
|         writer.write(">%s"%(newl)) | ||||
|         for node in self.childNodes: | ||||
|             node.writexml(writer,indent+addindent,addindent,newl) | ||||
|         writer.write("%s</%s>%s" % (indent,self.tagName,newl)) | ||||
|     else: | ||||
|         writer.write("/>%s"%(newl)) | ||||
| # For an introduction to overriding instance methods, see | ||||
| #   http://irrepupavel.com/documents/python/instancemethod/ | ||||
| instancemethod = type(minidom.Element.writexml) | ||||
| minidom.Element.writexml = instancemethod( | ||||
|     writexml, None, minidom.Element) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| statqueue=0 | ||||
| statupdated=0 | ||||
| statadded=0 | ||||
| 
 | ||||
| def cleannote(textin): | ||||
|     if textin.find("~fpdb~") == -1: return textin | ||||
|     if textin.find("~ends~") == -1: return textin | ||||
|     if textin.find("~fpdb~") > textin.find("~ends~"): return textin | ||||
|     return textin[0:textin.find("~fpdb~")] + textin[textin.find("~ends~")+6:] | ||||
| # get out now if parameter not passed | ||||
| try:  | ||||
|     sys.argv[1] <> "" | ||||
| except:  | ||||
|     print "A parameter is required, quitting now" | ||||
|     print "normal usage is something like:" | ||||
|     print '$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myhero.xml"' | ||||
|     quit() | ||||
| 
 | ||||
| if not os.path.isfile(sys.argv[1]): | ||||
|     print "Hero notes file not found, quitting" | ||||
|     print "normal usage is something like:" | ||||
|     print '$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myhero.xml"' | ||||
|     quit() | ||||
| 
 | ||||
| if not os.path.isfile((sys.argv[1]+".queue")): | ||||
|     print "Nothing found to merge, quitting" | ||||
|     print "Did the HUD not get started during the last session?" | ||||
|     print "Has the HUD been stopped and started without merging?" | ||||
|     quit() | ||||
| 
 | ||||
| print "***************************************************************" | ||||
| print "IMPORTANT: *** Before running this merge: ***" | ||||
| print "Closedown the FullTiltClient and wait for it to completely stop" | ||||
| print "If FullTiltClient was running, run the merge again once it" | ||||
| print "has stopped completely" | ||||
| print "***************************************************************" | ||||
| print | ||||
| print "read from: ", sys.argv[1] | ||||
| print "merge with: ", sys.argv[1]+".queue" | ||||
| 
 | ||||
| #read queue and turn into a dict | ||||
| queuedict = {} | ||||
| xmlqueue = minidom.parse(sys.argv[1]+".queue") | ||||
| notelist = xmlqueue.getElementsByTagName('NOTE') | ||||
|          | ||||
| for noteentry in notelist:                             | ||||
|     noteplayer = noteentry.getAttribute("PlayerId")   | ||||
|     notetext = noteentry.getAttribute("Text") | ||||
|     queuedict[noteplayer] = notetext   | ||||
|     statqueue = statqueue + 1 | ||||
| 
 | ||||
| #read existing player note file | ||||
| 
 | ||||
| xmlnotefile = minidom.parse(sys.argv[1]) | ||||
| notelist = xmlnotefile.getElementsByTagName('NOTE') | ||||
| 
 | ||||
| # | ||||
| #for existing players, empty out existing fpdbtext and refill | ||||
| #         | ||||
| for noteentry in notelist:  | ||||
|     noteplayer = noteentry.getAttribute("PlayerId") | ||||
|     if noteplayer in queuedict:         | ||||
|         existingnote = noteentry.getAttribute("Text") | ||||
|         newnote=cleannote(existingnote) | ||||
|         newnote = newnote + queuedict[noteplayer] | ||||
|         noteentry.setAttribute("Text",newnote) | ||||
|         statupdated = statupdated + 1 | ||||
|         del queuedict[noteplayer]                   | ||||
| 
 | ||||
| # | ||||
| #create entries for new players (those remaining in the dictionary) | ||||
| # | ||||
| if len(queuedict) > 0: | ||||
|     playerdata=xmlnotefile.lastChild #move to the PLAYERDATA node (assume last one in the list) | ||||
|     notesnode=playerdata.childNodes[1] #Find NOTES node  | ||||
| 
 | ||||
| for newplayer in queuedict: | ||||
|     newentry = xmlnotefile.createElement("NOTE") | ||||
|     newentry.setAttribute("PlayerId", newplayer) | ||||
|     newentry.setAttribute("Text", queuedict[newplayer])               | ||||
|     notesnode.insertBefore(newentry,None) | ||||
|     newentry = xmlnotefile.createTextNode("\n") | ||||
|     notesnode.insertBefore(newentry,None) | ||||
|     statadded=statadded+1 | ||||
|                  | ||||
| #print xmlnotefile.toprettyxml() | ||||
| 
 | ||||
| mergednotes = open(sys.argv[1]+".merged", 'w') | ||||
| xmlnotefile.writexml(mergednotes) | ||||
| mergednotes.close() | ||||
| 
 | ||||
| xmlnotefile.unlink | ||||
| 
 | ||||
| print "Merged file has been written to: ", sys.argv[1]+".merged" | ||||
| print "" | ||||
| print "number in queue: ", statqueue | ||||
| print "existing players updated: ", statupdated | ||||
| print "new players added: ", statadded | ||||
| print "\n" | ||||
| print "Use a viewer to check the contents of the merge file." | ||||
| print "If you are happy, carry out the following steps:" | ||||
| print "1 Rename or delete the existing notes file (normally <heroname>.xml" | ||||
| print "2 Rename the .merged file to become the new notes file" | ||||
							
								
								
									
										194
									
								
								pyfpdb/RushNotesReadMe.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								pyfpdb/RushNotesReadMe.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,194 @@ | |||
| aux to write fpdb data to player notes on Full Tilt | ||||
| --------------------------------------------------- | ||||
| 
 | ||||
| by Gimick 30th Dec 2010 | ||||
| 
 | ||||
| RushNotesAux - auxillary processed attached to the full tilt hud  | ||||
|                 builds up fpdb notes "queue" for each villain met while the autoimport is running | ||||
|                 uses HUD aggregation stats to do this | ||||
| 
 | ||||
| RushNotesMerge - stand alone process to merge the existing ftp notes, together with queue | ||||
|                  produced by Aux. | ||||
|                 the output file can then be renamed to become the new ftp notes file | ||||
| 
 | ||||
| Important info: | ||||
| The Merge process can only be run when ftp client is shutdown | ||||
|  - otherwise ftp overwrites the xml on exit. | ||||
| 
 | ||||
| Restarting the autoimport will empty the notes"queue" so avoid restarting | ||||
|  autoimport until the previous notes "queue" has been merged.  You will | ||||
|  lose all the queued notes, but these will be regenerated the next time | ||||
|  the villian is at your table, so it isn't the end of the world. | ||||
| 
 | ||||
| Existing ftp notes _SHOULD_ be preserved, but this isn't guaranteed,  | ||||
|  you have been warned! | ||||
|   | ||||
| Existing colour codings should be preserved,  | ||||
|  this process does not change or set colourcodings. | ||||
| 
 | ||||
| Copies of the live ftp notes file should be preserved everytime | ||||
|   RushNotesAux (i.e. the HUD is started) | ||||
| 
 | ||||
| The AW is hard-coded with just the table names of Micro Rush Poker,  | ||||
|   and should ignore all other hands. | ||||
| 
 | ||||
| What might not work? | ||||
| -------------------- | ||||
| 
 | ||||
| This isn't tested with Windows, and probably won't work, feedback welcome. | ||||
| Hasn't been tested for co-existance with other sites, feedback welcome. | ||||
| Whenever FTP change their notes file format, this will all break rather spectacularly, | ||||
|     you have been warned! | ||||
|      | ||||
| Getting started: | ||||
| --------------- | ||||
| 
 | ||||
| 1. Set the Hero aggregation to alltime.  hero_stat_range="A"  | ||||
|  This overcomes a sqlite "bug" which has nothing to do with auxillary windows | ||||
|   not doing this will slow processing down to about 1 hand per minute. | ||||
| 
 | ||||
| 2. Set the site_path to be the folder containing the FTP notes xml file | ||||
| (on wine this is normally site_path="/home/blah/.wine/Program Files/Full Tilt Poker/") | ||||
| 
 | ||||
| 
 | ||||
| Wire-up the aux process: | ||||
| ----------------------- | ||||
| 
 | ||||
| <aw class="RushNotes" module="RushNotesAux" name="Rush1"> </aw> | ||||
| <game aux="Rush1" cols="3" db="fpdb" game_name="holdem" rows="3"> | ||||
| 
 | ||||
| or whatever works for you. | ||||
| 
 | ||||
| 
 | ||||
| Play some poker | ||||
| --------------- | ||||
| 
 | ||||
| Start Autoimport, and rearrange the on-screen stats out of the way | ||||
|     (the full HUD must run, killing the HUD kills the AW updates) | ||||
| 
 | ||||
| Play whatever you want | ||||
| 
 | ||||
| Stop the autoimport | ||||
| 
 | ||||
| Exit the Full tilt poker client (ensure it has fully stopped with ps -A) | ||||
| 
 | ||||
| execute the following: | ||||
| 
 | ||||
| ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myname.xml" | ||||
| 
 | ||||
| A revised notes file (blah.merge) should automagically appear in the full tilt root directory. | ||||
| If you are happy with it, replace the existing (myname.xml file) | ||||
| 
 | ||||
| 
 | ||||
| Since the updates aren't real time, it would be ok to play the rush | ||||
|     session with fpdb inactive, but before quitting any of the tables,  | ||||
|     start the HUD and wait for it to catch-up processing all the hands played. | ||||
| 
 | ||||
| 
 | ||||
| Summary | ||||
| ------- | ||||
| 
 | ||||
| This is very rough and ready, but it does what I set-out to achieve.   | ||||
| 
 | ||||
| All feedback welcome, and if this is useful as a basis for general notes | ||||
|  processing in future, then thats great. | ||||
| 
 | ||||
| As I find bugs and make improvements, I will push to git. | ||||
| 
 | ||||
| 
 | ||||
| Much more information below: | ||||
| ---------------------------- | ||||
| 
 | ||||
| Background | ||||
| ---------- | ||||
| 
 | ||||
| When playing rush poker, some sort of rudimentary HUD would answer simple questions  | ||||
| like "is this allin overbet being made by a nit, or a maniac".  Although some  | ||||
| notes may have been made previously, some statistics would help to backup the decision. | ||||
| 
 | ||||
| Currently fpdb cannot support rush because the HUD is always 1 hand or more  | ||||
| behind the current action. | ||||
| 
 | ||||
| The only way to do this at the moment is to switch to GuiPlayerStats and make a quick  | ||||
| enquiry by player name.  However, this effectively times you out of all other  | ||||
| action if multitabling. | ||||
| 
 | ||||
| Full Tilt situation | ||||
| ------------------- | ||||
| 
 | ||||
| Full Tilt notes are stored in xml format ("hero.xml").  Previously these could  | ||||
| be updated while the game was in progress, however, FullTilt now cache the | ||||
| notes and write them out when the application exits.  This makes it impossible | ||||
| to use the notes as a real-time HUD, and therefore real-time huds are now | ||||
| forced to screen-scrape or poke around in the client memory. | ||||
| 
 | ||||
| Accepting this a limitation, this implementation updates the notes only once | ||||
| the FullTilt client has been closed.  Obviously, the villain HUD stats are only | ||||
| as at the end of the last session, however, it is hoped this is significantly | ||||
| better than having nothing at all.  As the hero's hand history increases, the | ||||
| notes should progressively mature in accuracy. | ||||
| 
 | ||||
| Preamble | ||||
| -------- | ||||
| 
 | ||||
| Note that this implementation was written purely to be "good enough" to work | ||||
| for the author, and is not intended as package or production quality.  It  | ||||
| is contributed as a starting point for others, or for experimental use. | ||||
| 
 | ||||
| Thanks to Ray Barker who gave a great deal of help throughout. | ||||
| 
 | ||||
| 
 | ||||
| The implementation | ||||
| ------------------- | ||||
| 
 | ||||
| RushNotesAux is an fpdb auxilliary process, and is called for every hand | ||||
| processed by autoimport.  Each villain has a note prepared based on the current | ||||
| fpdb data, and this note (in XML format) is stored in a queue file. | ||||
| 
 | ||||
| Auxilliary windows were chosen because  | ||||
| a) the author has limited fpdb and programming skill | ||||
| b) the auxillary windows handler is well documented and supported | ||||
| c) any code created has access to the full range of stats with little or no extra work | ||||
| d) runs within the HUD, so the aggregation parameters are already available | ||||
| 
 | ||||
| 
 | ||||
| Limitations | ||||
| ----------- | ||||
| 
 | ||||
| The notes are only regenerated if a hand is played against the villain.  The  | ||||
| process does not "bulk load" notes based upon all the player stats in FPDB. | ||||
| 
 | ||||
| It is hoped that due to the relatively large hand volume and relatively small | ||||
|  player pools, this limitation will be largely overcome after a few sessions | ||||
| although there will obviously be a number of players with no fpdb note.  | ||||
| 
 | ||||
| The aggregation parameters used for the notes are based upon the HUD parameters. | ||||
|  (with the exception of the hand-ranges, which uses its' own criteria (see source) | ||||
| 
 | ||||
| Stopping and starting the HUD will erase the previously created notes queue file. | ||||
| 
 | ||||
| The HUD must run, so the individual player popups need to be manually relocated. | ||||
| 
 | ||||
| Although hard-coded for micro RUSH tablenames, the auxilliary window  could | ||||
| probably happily update notes of all cash and tournament players. | ||||
| 
 | ||||
| Process overview | ||||
| ---------------- | ||||
| 
 | ||||
| 1/ The HUD process is started.   | ||||
| 1.1/ when the first hand is received, h fresh holding file is created, and  | ||||
| a copy of the current live xml note file is created as a security backup. | ||||
| 2/ For every hand played, the auxillary window is called | ||||
| 3/ Based upon the players in the hand, fpdb will be interrogated | ||||
| and key stats are formatted in xml-style and written out to a holding file. | ||||
| 4/ At the end of the session, the HUD is stopped and the poker client closed | ||||
| 
 | ||||
| 5/ The user can then review the contents of the holding file. | ||||
| 6/ A process is begun to "merge" the holding file into the existing player notes | ||||
| 7/ A new "merged" file is created.  The process attempts to preserve any | ||||
| existing notes, but this cannot be guaranteed. | ||||
| 8/ The user can review this merged file, and if they are happy,  | ||||
| they replace the existing note file. | ||||
| 9/ Note that this process never updates the live notes file in situ, but | ||||
| there is a risk that something goes wrong, and that existing notes could be destroyed. | ||||
| 
 | ||||
|  | @ -62,6 +62,43 @@ class FpdbError: | |||
|             idx = f.find('regression') | ||||
|             print "(%3d) : %s" %(self.histogram[f], f[idx:]) | ||||
| 
 | ||||
| def compare_gametypes_file(filename, importer, errors): | ||||
|     hashfilename = filename + '.gt' | ||||
| 
 | ||||
|     in_fh = codecs.open(hashfilename, 'r', 'utf8') | ||||
|     whole_file = in_fh.read() | ||||
|     in_fh.close() | ||||
| 
 | ||||
|     testhash = eval(whole_file) | ||||
| 
 | ||||
|     hhc = importer.getCachedHHC() | ||||
|     handlist = hhc.getProcessedHands() | ||||
| 
 | ||||
|     lookup = { | ||||
|                 0:'siteId', | ||||
|                 1:'currency', | ||||
|                 2:'type', | ||||
|                 3:'base', | ||||
|                 4:'game', | ||||
|                 5:'limit', | ||||
|                 6:'hilo', | ||||
|                 7:'Small Blind', | ||||
|                 8:'Big Blind', | ||||
|                 9:'Small Bet', | ||||
|                 10:'Big Bet', | ||||
|             } | ||||
| 
 | ||||
|     for hand in handlist: | ||||
|         ghash = hand.gametyperow | ||||
|         for i in range(len(ghash)): | ||||
|             print "DEBUG: about to compare: '%s' and '%s'" %(ghash[i], testhash[i]) | ||||
|             if ghash[i] == testhash[i]: | ||||
|                 # The stats match - continue | ||||
|                 pass | ||||
|             else: | ||||
|                 errors.error_report(filename, hand, lookup[i], ghash, testhash, None) | ||||
|     pass | ||||
| 
 | ||||
| def compare_handsplayers_file(filename, importer, errors): | ||||
|     hashfilename = filename + '.hp' | ||||
| 
 | ||||
|  | @ -142,6 +179,8 @@ def compare(leaf, importer, errors, site): | |||
|                 compare_handsplayers_file(filename, importer, errors) | ||||
|             if os.path.isfile(filename + '.hands'): | ||||
|                 compare_hands_file(filename, importer, errors) | ||||
|             if os.path.isfile(filename + '.gt'): | ||||
|                 compare_gametypes_file(filename, importer, errors) | ||||
| 
 | ||||
|         importer.clearFileList() | ||||
| 
 | ||||
|  |  | |||
|  | @ -86,7 +86,6 @@ class Importer: | |||
|         self.cacheSessions = self.config.get_import_parameters().get("cacheSessions") | ||||
| 
 | ||||
|         # CONFIGURATION OPTIONS | ||||
|         self.settings.setdefault("minPrint", 30) | ||||
|         self.settings.setdefault("handCount", 0) | ||||
|         #self.settings.setdefault("allowHudcacheRebuild", True) # NOT USED NOW | ||||
|         #self.settings.setdefault("forceThreads", 2)            # NOT USED NOW | ||||
|  | @ -115,9 +114,6 @@ class Importer: | |||
|     def setCacheSessions(self, value): | ||||
|         self.cacheSessions = value | ||||
| 
 | ||||
|     def setMinPrint(self, value): | ||||
|         self.settings['minPrint'] = int(value) | ||||
| 
 | ||||
|     def setHandCount(self, value): | ||||
|         self.settings['handCount'] = int(value) | ||||
| 
 | ||||
|  | @ -477,7 +473,7 @@ class Importer: | |||
| 
 | ||||
|                 for hand in handlist: | ||||
|                     if hand is not None: | ||||
|                         hand.prepInsert(self.database) | ||||
|                         hand.prepInsert(self.database, printtest = self.settings['testData']) | ||||
|                         try: | ||||
|                             hand.insert(self.database, printtest = self.settings['testData']) | ||||
|                         except Exceptions.FpdbHandDuplicate: | ||||
|  |  | |||
|  | @ -167,7 +167,7 @@ | |||
|                     'street3CheckCallRaiseChance': False, | ||||
|                     'street3CheckCallRaiseDone': False, | ||||
|                     'street3Raises': 0, | ||||
|                     'street3Seen': False, | ||||
|                     'street3Seen': True, | ||||
|                     'street4Aggr': False, | ||||
|                     'street4Bets': 0, | ||||
|                     'street4CBChance': False, | ||||
|  | @ -184,7 +184,7 @@ | |||
|                     'wonAtSD': 1.0, | ||||
|                     'wonWhenSeenStreet1': 1.0, | ||||
|                     'wonWhenSeenStreet2': 1.0, | ||||
|                     'wonWhenSeenStreet3': 0.0, | ||||
|                     'wonWhenSeenStreet3': 1.0, | ||||
|                     'wonWhenSeenStreet4': 0.0}, | ||||
|     u'Player2': {   'card1': 0, | ||||
|                     'card2': 0, | ||||
|  | @ -355,7 +355,7 @@ | |||
|                     'street3CheckCallRaiseChance': False, | ||||
|                     'street3CheckCallRaiseDone': False, | ||||
|                     'street3Raises': 0, | ||||
|                     'street3Seen': False, | ||||
|                     'street3Seen': True, | ||||
|                     'street4Aggr': False, | ||||
|                     'street4Bets': 0, | ||||
|                     'street4CBChance': False, | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,27 @@ | |||
| FullTiltPoker Game #25314791161: Table Squaw Tea (6 max) - $150/$300 - Limit 2-7 Triple Draw - 8:19:00 ET - 2010/11/06 | ||||
| 
 | ||||
| Seat 1: Hero ($5,208), is sitting out | ||||
| Seat 2: Player1 ($6,795.50), is sitting out | ||||
| Seat 3: Player2 ($4,368) | ||||
| Seat 5: Player3 ($3,625) | ||||
| Seat 6: Player4 ($12,705) | ||||
| Player4 posts the small blind of $75 | ||||
| Player2 posts the big blind of $150 | ||||
| The button is in seat #5 | ||||
| *** HOLE CARDS *** | ||||
| Player3 folds | ||||
| Player4 has 15 seconds left to act | ||||
| Player4 folds | ||||
| Uncalled bet of $75 returned to Player2 | ||||
| Player2 mucks | ||||
| Player2 wins the pot ($150) | ||||
| *** SUMMARY *** | ||||
| Duration 24s | ||||
| Total pot $150 | Rake $0 | ||||
| Seat 1: Hero is sitting out | ||||
| Seat 2: Player1 is sitting out | ||||
| Seat 3: Player2 (big blind) collected ($150) | ||||
| Seat 5: Player3 (button) didn't bet (folded) | ||||
| Seat 6: Player4 (small blind) folded before the draw | ||||
| 
 | ||||
| 
 | ||||
|  | @ -7,8 +7,6 @@ | |||
|     'importTime': None, | ||||
|     'maxSeats': 6, | ||||
|     'playersAtShowdown': 2, | ||||
|     'playersAtStreet-1': 5, | ||||
|     'playersAtStreet0': 2, | ||||
|     'playersAtStreet1': 2, | ||||
|     'playersAtStreet2': 2, | ||||
|     'playersAtStreet3': 2, | ||||
|  |  | |||
|  | @ -0,0 +1,28 @@ | |||
| FullTiltPoker Game #25585334444: Table Rocky Cliff (6 max) - $250/$500 - Limit 2-7 Triple Draw - 18:08:00 ET - 2010/11/15 | ||||
| 
 | ||||
| Seat 2: Player5 ($1,201) | ||||
| Seat 3: Player6 ($4,108.50) | ||||
| Seat 4: Player4 ($13,124.50) | ||||
| Seat 5: Player3 ($5,245) | ||||
| Seat 6: Hero ($5,000) | ||||
| Player4 posts the small blind of $125 | ||||
| Player3 posts the big blind of $250 | ||||
| The button is in seat #3 | ||||
| *** HOLE CARDS *** | ||||
| Player5 has 15 seconds left to act | ||||
| Player5 raises to $500 | ||||
| Player6 folds | ||||
| Player4 folds | ||||
| Player3 folds | ||||
| Uncalled bet of $250 returned to Player5 | ||||
| Player5 mucks | ||||
| Player5 wins the pot ($625) | ||||
| *** SUMMARY *** | ||||
| Duration 18s | ||||
| Total pot $625 | Rake $0 | ||||
| Seat 2: Player5 collected ($625) | ||||
| Seat 3: Player6 (button) didn't bet (folded) | ||||
| Seat 4: Player4 (small blind) folded before the draw | ||||
| Seat 5: Player3 (big blind) folded before the draw | ||||
| Seat 6: Hero is sitting out | ||||
| 
 | ||||
|  | @ -0,0 +1 @@ | |||
| (1, 'USD', 'ring', 'draw', '27_3draw', 'fl', 'l', 12500, 25000, 25000, 50000) | ||||
|  | @ -0,0 +1,32 @@ | |||
| {   'boardcard1': 0, | ||||
|     'boardcard2': 0, | ||||
|     'boardcard3': 0, | ||||
|     'boardcard4': 0, | ||||
|     'boardcard5': 0, | ||||
|     'gametypeId': 1, | ||||
|     'importTime': None, | ||||
|     'maxSeats': 6, | ||||
|     'playersAtShowdown': 1, | ||||
|     'playersAtStreet-1': 4, | ||||
|     'playersAtStreet0': 1, | ||||
|     'playersAtStreet1': 1, | ||||
|     'playersAtStreet2': 1, | ||||
|     'playersAtStreet3': 1, | ||||
|     'playersAtStreet4': 0, | ||||
|     'playersVpi': 1, | ||||
|     'seats': 4, | ||||
|     'showdownPot': 0, | ||||
|     'siteHandNo': u'25585334444', | ||||
|     'startTime': datetime.datetime(2010, 11, 15, 23, 8, tzinfo=pytz.utc), | ||||
|     'street0Raises': 1, | ||||
|     'street1Pot': 0, | ||||
|     'street1Raises': 0, | ||||
|     'street2Pot': 0, | ||||
|     'street2Raises': 0, | ||||
|     'street3Pot': 0, | ||||
|     'street3Raises': 0, | ||||
|     'street4Pot': 0, | ||||
|     'street4Raises': 0, | ||||
|     'tableName': u'Rocky Cliff', | ||||
|     'texture': None, | ||||
|     'tourneyId': None} | ||||
|  | @ -0,0 +1,376 @@ | |||
| {   u'Player3': {   'card1': 0, | ||||
|                     'card2': 0, | ||||
|                     'card3': 0, | ||||
|                     'card4': 0, | ||||
|                     'card5': 0, | ||||
|                     'card6': 0, | ||||
|                     'card7': 0, | ||||
|                     'foldBbToStealChance': True, | ||||
|                     'foldSbToStealChance': False, | ||||
|                     'foldToOtherRaisedStreet0': False, | ||||
|                     'foldToOtherRaisedStreet1': False, | ||||
|                     'foldToOtherRaisedStreet2': False, | ||||
|                     'foldToOtherRaisedStreet3': False, | ||||
|                     'foldToOtherRaisedStreet4': False, | ||||
|                     'foldToStreet1CBChance': False, | ||||
|                     'foldToStreet1CBDone': False, | ||||
|                     'foldToStreet2CBChance': False, | ||||
|                     'foldToStreet2CBDone': False, | ||||
|                     'foldToStreet3CBChance': False, | ||||
|                     'foldToStreet3CBDone': False, | ||||
|                     'foldToStreet4CBChance': False, | ||||
|                     'foldToStreet4CBDone': False, | ||||
|                     'foldedBbToSteal': True, | ||||
|                     'foldedSbToSteal': False, | ||||
|                     'other3BStreet0': False, | ||||
|                     'other4BStreet0': False, | ||||
|                     'otherRaisedStreet0': False, | ||||
|                     'otherRaisedStreet1': False, | ||||
|                     'otherRaisedStreet2': False, | ||||
|                     'otherRaisedStreet3': False, | ||||
|                     'otherRaisedStreet4': False, | ||||
|                     'position': 'B', | ||||
|                     'raiseFirstInChance': False, | ||||
|                     'raisedFirstIn': False, | ||||
|                     'rake': 0, | ||||
|                     'sawShowdown': False, | ||||
|                     'seatNo': 5, | ||||
|                     'sitout': False, | ||||
|                     'startCards': 0, | ||||
|                     'startCash': 524500, | ||||
|                     'street0Aggr': False, | ||||
|                     'street0Bets': 0, | ||||
|                     'street0Calls': 0, | ||||
|                     'street0Raises': 0, | ||||
|                     'street0VPI': False, | ||||
|                     'street0_3BChance': True, | ||||
|                     'street0_3BDone': False, | ||||
|                     'street0_4BChance': False, | ||||
|                     'street0_4BDone': False, | ||||
|                     'street1Aggr': False, | ||||
|                     'street1Bets': 0, | ||||
|                     'street1CBChance': False, | ||||
|                     'street1CBDone': False, | ||||
|                     'street1Calls': 0, | ||||
|                     'street1CheckCallRaiseChance': False, | ||||
|                     'street1CheckCallRaiseDone': False, | ||||
|                     'street1Raises': 0, | ||||
|                     'street1Seen': False, | ||||
|                     'street2Aggr': False, | ||||
|                     'street2Bets': 0, | ||||
|                     'street2CBChance': False, | ||||
|                     'street2CBDone': False, | ||||
|                     'street2Calls': 0, | ||||
|                     'street2CheckCallRaiseChance': False, | ||||
|                     'street2CheckCallRaiseDone': False, | ||||
|                     'street2Raises': 0, | ||||
|                     'street2Seen': False, | ||||
|                     'street3Aggr': False, | ||||
|                     'street3Bets': 0, | ||||
|                     'street3CBChance': False, | ||||
|                     'street3CBDone': False, | ||||
|                     'street3Calls': 0, | ||||
|                     'street3CheckCallRaiseChance': False, | ||||
|                     'street3CheckCallRaiseDone': False, | ||||
|                     'street3Raises': 0, | ||||
|                     'street3Seen': False, | ||||
|                     'street4Aggr': False, | ||||
|                     'street4Bets': 0, | ||||
|                     'street4CBChance': False, | ||||
|                     'street4CBDone': False, | ||||
|                     'street4Calls': 0, | ||||
|                     'street4CheckCallRaiseChance': False, | ||||
|                     'street4CheckCallRaiseDone': False, | ||||
|                     'street4Raises': 0, | ||||
|                     'street4Seen': False, | ||||
|                     'totalProfit': -25000, | ||||
|                     'tourneyTypeId': None, | ||||
|                     'tourneysPlayersIds': None, | ||||
|                     'winnings': 0, | ||||
|                     'wonAtSD': 0.0, | ||||
|                     'wonWhenSeenStreet1': 0.0, | ||||
|                     'wonWhenSeenStreet2': 0.0, | ||||
|                     'wonWhenSeenStreet3': 0.0, | ||||
|                     'wonWhenSeenStreet4': 0.0}, | ||||
|     u'Player4': {   'card1': 0, | ||||
|                     'card2': 0, | ||||
|                     'card3': 0, | ||||
|                     'card4': 0, | ||||
|                     'card5': 0, | ||||
|                     'card6': 0, | ||||
|                     'card7': 0, | ||||
|                     'foldBbToStealChance': False, | ||||
|                     'foldSbToStealChance': True, | ||||
|                     'foldToOtherRaisedStreet0': False, | ||||
|                     'foldToOtherRaisedStreet1': False, | ||||
|                     'foldToOtherRaisedStreet2': False, | ||||
|                     'foldToOtherRaisedStreet3': False, | ||||
|                     'foldToOtherRaisedStreet4': False, | ||||
|                     'foldToStreet1CBChance': False, | ||||
|                     'foldToStreet1CBDone': False, | ||||
|                     'foldToStreet2CBChance': False, | ||||
|                     'foldToStreet2CBDone': False, | ||||
|                     'foldToStreet3CBChance': False, | ||||
|                     'foldToStreet3CBDone': False, | ||||
|                     'foldToStreet4CBChance': False, | ||||
|                     'foldToStreet4CBDone': False, | ||||
|                     'foldedBbToSteal': False, | ||||
|                     'foldedSbToSteal': True, | ||||
|                     'other3BStreet0': False, | ||||
|                     'other4BStreet0': False, | ||||
|                     'otherRaisedStreet0': False, | ||||
|                     'otherRaisedStreet1': False, | ||||
|                     'otherRaisedStreet2': False, | ||||
|                     'otherRaisedStreet3': False, | ||||
|                     'otherRaisedStreet4': False, | ||||
|                     'position': 'S', | ||||
|                     'raiseFirstInChance': False, | ||||
|                     'raisedFirstIn': False, | ||||
|                     'rake': 0, | ||||
|                     'sawShowdown': False, | ||||
|                     'seatNo': 4, | ||||
|                     'sitout': False, | ||||
|                     'startCards': 0, | ||||
|                     'startCash': 1312450, | ||||
|                     'street0Aggr': False, | ||||
|                     'street0Bets': 0, | ||||
|                     'street0Calls': 0, | ||||
|                     'street0Raises': 0, | ||||
|                     'street0VPI': False, | ||||
|                     'street0_3BChance': True, | ||||
|                     'street0_3BDone': False, | ||||
|                     'street0_4BChance': False, | ||||
|                     'street0_4BDone': False, | ||||
|                     'street1Aggr': False, | ||||
|                     'street1Bets': 0, | ||||
|                     'street1CBChance': False, | ||||
|                     'street1CBDone': False, | ||||
|                     'street1Calls': 0, | ||||
|                     'street1CheckCallRaiseChance': False, | ||||
|                     'street1CheckCallRaiseDone': False, | ||||
|                     'street1Raises': 0, | ||||
|                     'street1Seen': False, | ||||
|                     'street2Aggr': False, | ||||
|                     'street2Bets': 0, | ||||
|                     'street2CBChance': False, | ||||
|                     'street2CBDone': False, | ||||
|                     'street2Calls': 0, | ||||
|                     'street2CheckCallRaiseChance': False, | ||||
|                     'street2CheckCallRaiseDone': False, | ||||
|                     'street2Raises': 0, | ||||
|                     'street2Seen': False, | ||||
|                     'street3Aggr': False, | ||||
|                     'street3Bets': 0, | ||||
|                     'street3CBChance': False, | ||||
|                     'street3CBDone': False, | ||||
|                     'street3Calls': 0, | ||||
|                     'street3CheckCallRaiseChance': False, | ||||
|                     'street3CheckCallRaiseDone': False, | ||||
|                     'street3Raises': 0, | ||||
|                     'street3Seen': False, | ||||
|                     'street4Aggr': False, | ||||
|                     'street4Bets': 0, | ||||
|                     'street4CBChance': False, | ||||
|                     'street4CBDone': False, | ||||
|                     'street4Calls': 0, | ||||
|                     'street4CheckCallRaiseChance': False, | ||||
|                     'street4CheckCallRaiseDone': False, | ||||
|                     'street4Raises': 0, | ||||
|                     'street4Seen': False, | ||||
|                     'totalProfit': -12500, | ||||
|                     'tourneyTypeId': None, | ||||
|                     'tourneysPlayersIds': None, | ||||
|                     'winnings': 0, | ||||
|                     'wonAtSD': 0.0, | ||||
|                     'wonWhenSeenStreet1': 0.0, | ||||
|                     'wonWhenSeenStreet2': 0.0, | ||||
|                     'wonWhenSeenStreet3': 0.0, | ||||
|                     'wonWhenSeenStreet4': 0.0}, | ||||
|     u'Player5': {   'card1': 0, | ||||
|                     'card2': 0, | ||||
|                     'card3': 0, | ||||
|                     'card4': 0, | ||||
|                     'card5': 0, | ||||
|                     'card6': 0, | ||||
|                     'card7': 0, | ||||
|                     'foldBbToStealChance': False, | ||||
|                     'foldSbToStealChance': False, | ||||
|                     'foldToOtherRaisedStreet0': False, | ||||
|                     'foldToOtherRaisedStreet1': False, | ||||
|                     'foldToOtherRaisedStreet2': False, | ||||
|                     'foldToOtherRaisedStreet3': False, | ||||
|                     'foldToOtherRaisedStreet4': False, | ||||
|                     'foldToStreet1CBChance': False, | ||||
|                     'foldToStreet1CBDone': False, | ||||
|                     'foldToStreet2CBChance': False, | ||||
|                     'foldToStreet2CBDone': False, | ||||
|                     'foldToStreet3CBChance': False, | ||||
|                     'foldToStreet3CBDone': False, | ||||
|                     'foldToStreet4CBChance': False, | ||||
|                     'foldToStreet4CBDone': False, | ||||
|                     'foldedBbToSteal': False, | ||||
|                     'foldedSbToSteal': False, | ||||
|                     'other3BStreet0': False, | ||||
|                     'other4BStreet0': False, | ||||
|                     'otherRaisedStreet0': False, | ||||
|                     'otherRaisedStreet1': False, | ||||
|                     'otherRaisedStreet2': False, | ||||
|                     'otherRaisedStreet3': False, | ||||
|                     'otherRaisedStreet4': False, | ||||
|                     'position': 1, | ||||
|                     'raiseFirstInChance': True, | ||||
|                     'raisedFirstIn': True, | ||||
|                     'rake': 0, | ||||
|                     'sawShowdown': False, | ||||
|                     'seatNo': 2, | ||||
|                     'sitout': False, | ||||
|                     'startCards': 0, | ||||
|                     'startCash': 120100, | ||||
|                     'street0Aggr': True, | ||||
|                     'street0Bets': 0, | ||||
|                     'street0Calls': 0, | ||||
|                     'street0Raises': 0, | ||||
|                     'street0VPI': True, | ||||
|                     'street0_3BChance': False, | ||||
|                     'street0_3BDone': False, | ||||
|                     'street0_4BChance': False, | ||||
|                     'street0_4BDone': False, | ||||
|                     'street1Aggr': False, | ||||
|                     'street1Bets': 0, | ||||
|                     'street1CBChance': False, | ||||
|                     'street1CBDone': False, | ||||
|                     'street1Calls': 0, | ||||
|                     'street1CheckCallRaiseChance': False, | ||||
|                     'street1CheckCallRaiseDone': False, | ||||
|                     'street1Raises': 0, | ||||
|                     'street1Seen': False, | ||||
|                     'street2Aggr': False, | ||||
|                     'street2Bets': 0, | ||||
|                     'street2CBChance': False, | ||||
|                     'street2CBDone': False, | ||||
|                     'street2Calls': 0, | ||||
|                     'street2CheckCallRaiseChance': False, | ||||
|                     'street2CheckCallRaiseDone': False, | ||||
|                     'street2Raises': 0, | ||||
|                     'street2Seen': False, | ||||
|                     'street3Aggr': False, | ||||
|                     'street3Bets': 0, | ||||
|                     'street3CBChance': False, | ||||
|                     'street3CBDone': False, | ||||
|                     'street3Calls': 0, | ||||
|                     'street3CheckCallRaiseChance': False, | ||||
|                     'street3CheckCallRaiseDone': False, | ||||
|                     'street3Raises': 0, | ||||
|                     'street3Seen': False, | ||||
|                     'street4Aggr': False, | ||||
|                     'street4Bets': 0, | ||||
|                     'street4CBChance': False, | ||||
|                     'street4CBDone': False, | ||||
|                     'street4Calls': 0, | ||||
|                     'street4CheckCallRaiseChance': False, | ||||
|                     'street4CheckCallRaiseDone': False, | ||||
|                     'street4Raises': 0, | ||||
|                     'street4Seen': False, | ||||
|                     'totalProfit': -25000, | ||||
|                     'tourneyTypeId': None, | ||||
|                     'tourneysPlayersIds': None, | ||||
|                     'winnings': 0, | ||||
|                     'wonAtSD': 0.0, | ||||
|                     'wonWhenSeenStreet1': 0.0, | ||||
|                     'wonWhenSeenStreet2': 0.0, | ||||
|                     'wonWhenSeenStreet3': 0.0, | ||||
|                     'wonWhenSeenStreet4': 0.0}, | ||||
|     u'Player6': {   'card1': 0, | ||||
|                     'card2': 0, | ||||
|                     'card3': 0, | ||||
|                     'card4': 0, | ||||
|                     'card5': 0, | ||||
|                     'card6': 0, | ||||
|                     'card7': 0, | ||||
|                     'foldBbToStealChance': False, | ||||
|                     'foldSbToStealChance': False, | ||||
|                     'foldToOtherRaisedStreet0': False, | ||||
|                     'foldToOtherRaisedStreet1': False, | ||||
|                     'foldToOtherRaisedStreet2': False, | ||||
|                     'foldToOtherRaisedStreet3': False, | ||||
|                     'foldToOtherRaisedStreet4': False, | ||||
|                     'foldToStreet1CBChance': False, | ||||
|                     'foldToStreet1CBDone': False, | ||||
|                     'foldToStreet2CBChance': False, | ||||
|                     'foldToStreet2CBDone': False, | ||||
|                     'foldToStreet3CBChance': False, | ||||
|                     'foldToStreet3CBDone': False, | ||||
|                     'foldToStreet4CBChance': False, | ||||
|                     'foldToStreet4CBDone': False, | ||||
|                     'foldedBbToSteal': False, | ||||
|                     'foldedSbToSteal': False, | ||||
|                     'other3BStreet0': False, | ||||
|                     'other4BStreet0': False, | ||||
|                     'otherRaisedStreet0': False, | ||||
|                     'otherRaisedStreet1': False, | ||||
|                     'otherRaisedStreet2': False, | ||||
|                     'otherRaisedStreet3': False, | ||||
|                     'otherRaisedStreet4': False, | ||||
|                     'position': 0, | ||||
|                     'raiseFirstInChance': False, | ||||
|                     'raisedFirstIn': False, | ||||
|                     'rake': 0, | ||||
|                     'sawShowdown': False, | ||||
|                     'seatNo': 3, | ||||
|                     'sitout': False, | ||||
|                     'startCards': 0, | ||||
|                     'startCash': 410850, | ||||
|                     'street0Aggr': False, | ||||
|                     'street0Bets': 0, | ||||
|                     'street0Calls': 0, | ||||
|                     'street0Raises': 0, | ||||
|                     'street0VPI': False, | ||||
|                     'street0_3BChance': True, | ||||
|                     'street0_3BDone': False, | ||||
|                     'street0_4BChance': False, | ||||
|                     'street0_4BDone': False, | ||||
|                     'street1Aggr': False, | ||||
|                     'street1Bets': 0, | ||||
|                     'street1CBChance': False, | ||||
|                     'street1CBDone': False, | ||||
|                     'street1Calls': 0, | ||||
|                     'street1CheckCallRaiseChance': False, | ||||
|                     'street1CheckCallRaiseDone': False, | ||||
|                     'street1Raises': 0, | ||||
|                     'street1Seen': False, | ||||
|                     'street2Aggr': False, | ||||
|                     'street2Bets': 0, | ||||
|                     'street2CBChance': False, | ||||
|                     'street2CBDone': False, | ||||
|                     'street2Calls': 0, | ||||
|                     'street2CheckCallRaiseChance': False, | ||||
|                     'street2CheckCallRaiseDone': False, | ||||
|                     'street2Raises': 0, | ||||
|                     'street2Seen': False, | ||||
|                     'street3Aggr': False, | ||||
|                     'street3Bets': 0, | ||||
|                     'street3CBChance': False, | ||||
|                     'street3CBDone': False, | ||||
|                     'street3Calls': 0, | ||||
|                     'street3CheckCallRaiseChance': False, | ||||
|                     'street3CheckCallRaiseDone': False, | ||||
|                     'street3Raises': 0, | ||||
|                     'street3Seen': False, | ||||
|                     'street4Aggr': False, | ||||
|                     'street4Bets': 0, | ||||
|                     'street4CBChance': False, | ||||
|                     'street4CBDone': False, | ||||
|                     'street4Calls': 0, | ||||
|                     'street4CheckCallRaiseChance': False, | ||||
|                     'street4CheckCallRaiseDone': False, | ||||
|                     'street4Raises': 0, | ||||
|                     'street4Seen': False, | ||||
|                     'totalProfit': 0, | ||||
|                     'tourneyTypeId': None, | ||||
|                     'tourneysPlayersIds': None, | ||||
|                     'winnings': 0, | ||||
|                     'wonAtSD': 0.0, | ||||
|                     'wonWhenSeenStreet1': 0.0, | ||||
|                     'wonWhenSeenStreet2': 0.0, | ||||
|                     'wonWhenSeenStreet3': 0.0, | ||||
|                     'wonWhenSeenStreet4': 0.0}} | ||||
|  | @ -6,12 +6,10 @@ | |||
|     'gametypeId': 7, | ||||
|     'importTime': None, | ||||
|     'maxSeats': 6, | ||||
|     'playersAtShowdown': 1, | ||||
|     'playersAtStreet-1': 6, | ||||
|     'playersAtStreet0': 1, | ||||
|     'playersAtStreet1': 1, | ||||
|     'playersAtStreet2': 1, | ||||
|     'playersAtStreet3': 1, | ||||
|     'playersAtShowdown': 0, | ||||
|     'playersAtStreet1': 0, | ||||
|     'playersAtStreet2': 0, | ||||
|     'playersAtStreet3': 0, | ||||
|     'playersAtStreet4': 0, | ||||
|     'playersVpi': 1, | ||||
|     'seats': 6, | ||||
|  |  | |||
|  | @ -7,8 +7,6 @@ | |||
|     'importTime': None, | ||||
|     'maxSeats': 9, | ||||
|     'playersAtShowdown': 0, | ||||
|     'playersAtStreet-1': 8, | ||||
|     'playersAtStreet0': 0, | ||||
|     'playersAtStreet1': 0, | ||||
|     'playersAtStreet2': 0, | ||||
|     'playersAtStreet3': 0, | ||||
|  |  | |||
|  | @ -7,11 +7,9 @@ | |||
|     'importTime': None, | ||||
|     'maxSeats': 6, | ||||
|     'playersAtShowdown': 2, | ||||
|     'playersAtStreet-1': 6, | ||||
|     'playersAtStreet0': 4, | ||||
|     'playersAtStreet1': 3, | ||||
|     'playersAtStreet1': 4, | ||||
|     'playersAtStreet2': 3, | ||||
|     'playersAtStreet3': 2, | ||||
|     'playersAtStreet3': 3, | ||||
|     'playersAtStreet4': 0, | ||||
|     'playersVpi': 3, | ||||
|     'seats': 6, | ||||
|  |  | |||
|  | @ -6,13 +6,11 @@ | |||
|     'gametypeId': 5, | ||||
|     'importTime': None, | ||||
|     'maxSeats': 8, | ||||
|     'playersAtShowdown': 1, | ||||
|     'playersAtStreet-1': 7, | ||||
|     'playersAtStreet0': 3, | ||||
|     'playersAtShowdown': 0, | ||||
|     'playersAtStreet1': 3, | ||||
|     'playersAtStreet2': 2, | ||||
|     'playersAtStreet3': 1, | ||||
|     'playersAtStreet4': 1, | ||||
|     'playersAtStreet2': 3, | ||||
|     'playersAtStreet3': 2, | ||||
|     'playersAtStreet4': 0, | ||||
|     'playersVpi': 3, | ||||
|     'seats': 7, | ||||
|     'showdownPot': 0, | ||||
|  |  | |||
|  | @ -6,15 +6,13 @@ | |||
|     'gametypeId': 1, | ||||
|     'importTime': None, | ||||
|     'maxSeats': 8, | ||||
|     'playersAtShowdown': 1, | ||||
|     'playersAtStreet-1': 7, | ||||
|     'playersAtStreet0': 3, | ||||
|     'playersAtStreet1': 2, | ||||
|     'playersAtStreet2': 1, | ||||
|     'playersAtStreet3': 1, | ||||
|     'playersAtStreet4': 1, | ||||
|     'playersAtShowdown': 0, | ||||
|     'playersAtStreet1': 3, | ||||
|     'playersAtStreet2': 2, | ||||
|     'playersAtStreet3': 0, | ||||
|     'playersAtStreet4': 0, | ||||
|     'playersVpi': 2, | ||||
|     'seats': 8, | ||||
|     'seats': 7, | ||||
|     'showdownPot': 0, | ||||
|     'siteHandNo': u'26190500040', | ||||
|     'startTime': datetime.datetime(2010, 12, 7, 9, 19, tzinfo=pytz.utc), | ||||
|  |  | |||
|  | @ -1,49 +0,0 @@ | |||
| #!/usr/bin/python | ||||
| # -*- coding: utf-8 -*- | ||||
| 
 | ||||
| #Copyright 2009-2010 Carl Gherardi | ||||
| #This program is free software: you can redistribute it and/or modify | ||||
| #it under the terms of the GNU Affero General Public License as published by | ||||
| #the Free Software Foundation, version 3 of the License. | ||||
| # | ||||
| #This program is distributed in the hope that it will be useful, | ||||
| #but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| #GNU General Public License for more details. | ||||
| # | ||||
| #You should have received a copy of the GNU Affero General Public License | ||||
| #along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
| #In the "official" distribution you can find the license in agpl-3.0.txt. | ||||
| 
 | ||||
| import BetfairToFpdb | ||||
| from Hand import * | ||||
| import py | ||||
| 
 | ||||
| import Configuration | ||||
| import Database | ||||
| import SQL | ||||
| import fpdb_import | ||||
| 
 | ||||
| config = Configuration.Config(file = "HUD_config.test.xml") | ||||
| db = Database.Database(config) | ||||
| sql = SQL.Sql(db_server = 'sqlite') | ||||
| 
 | ||||
| settings = {} | ||||
| settings.update(config.get_db_parameters()) | ||||
| settings.update(config.get_import_parameters()) | ||||
| settings.update(config.get_default_paths()) | ||||
| 
 | ||||
| def testFlopImport(): | ||||
|     db.recreate_tables() | ||||
|     importer = fpdb_import.Importer(False, settings, config) | ||||
|     importer.setDropIndexes("don't drop") | ||||
|     importer.setFailOnError(True) | ||||
|     importer.setThreads(-1) | ||||
|     importer.addBulkImportImportFileOrDir( | ||||
|             """regression-test-files/cash/Betfair/Flop/PLO-6max-USD-0.05-0.10-200909.All.in.river.splitpot.txt""", site="Betfair") | ||||
|     importer.setCallHud(False) | ||||
|     (stored, dups, partial, errs, ttime) = importer.runImport() | ||||
|     importer.clearFileList() | ||||
| 
 | ||||
|     # Should actually do some testing here | ||||
|     assert 1 == 1 | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user