From 877f0771ab1695d0e763d5c636b6e93c473a373b Mon Sep 17 00:00:00 2001 From: Matt Turnbull Date: Wed, 17 Dec 2008 11:22:02 +0000 Subject: [PATCH 01/13] nongreedy matches in collect_pot_re to fix kicker being picked up instead of hand bug --- pyfpdb/EverleafToFpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index 03b22995..bce92e79 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -79,7 +79,7 @@ class Everleaf(HandHistoryConverter): self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P.*)\s\[ (?P.*) \]') self.rexx.setActionStepRegex('.*\n(?P.*)(?P: bets| checks| raises| calls| folds)(\s\[\$ (?P[.\d]+) USD\])?') self.rexx.setShowdownActionRegex('.*\n(?P.*) shows \[ (?P.*) \]') - self.rexx.setCollectPotRegex('.*\n(?P.*) wins \$ (?P[.\d]+) USD(.*\[ (?P.*) \])?') + self.rexx.setCollectPotRegex('.*\n(?P.*) wins \$ (?P[.\d]+) USD(.*?\[ (?P.*?) \])?') self.rexx.sits_out_re = re.compile('(?P.*) sits out') self.rexx.compileRegexes() From d16816649558f4a4cd08f3e5b513ae65619b278e Mon Sep 17 00:00:00 2001 From: Matt Turnbull Date: Wed, 17 Dec 2008 11:54:26 +0000 Subject: [PATCH 02/13] Added: addCallandRaise - when reported amount is the actual amount transfered addRaiseBy - when reported is the amount additional to the previous bet _addRaise - common helper --- pyfpdb/Hand.py | 61 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index e215e99d..f4a7bec7 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -204,25 +204,62 @@ Card ranks will be uppercased print "DEBUG %s calls %s, stack %s" % (player, amount, self.stacks[player]) self.actions[street] += [(player, 'calls', amount, self.stacks[player]==0)] - - def addRaiseTo(self, street, player, amountTo): + def addRaiseBy(self, street, player, amountBy): """\ -Add a raise on [street] by [player] to [amountTo] +Add a raise by amountBy on [street] by [player] """ - #Given only the amount raised to, the amount of the raise can be calculated by + #Given only the amount raised by, the amount of the raise can be calculated by # working out how much this player has already in the pot # (which is the sum of self.bets[street][player]) # and how much he needs to call to match the previous player # (which is tracked by self.lastBet) + # let Bp = previous bet + # Bc = amount player has committed so far + # Rb = raise by + # then: C = Bp - Bc (amount to call) + # Rt = Bp + Rb (raise to) + # self.checkPlayerExists(player) - committedThisStreet = reduce(operator.add, self.bets[street][player], 0) - amountToCall = self.lastBet[street] - committedThisStreet - self.lastBet[street] = Decimal(amountTo) - amountBy = Decimal(amountTo) - amountToCall - self.bets[street][player].append(amountBy+amountToCall) - self.stacks[player] -= (Decimal(amountBy)+Decimal(amountToCall)) - print "DEBUG %s stack %s" % (player, self.stacks[player]) - self.actions[street] += [(player, 'raises', amountBy, amountTo, amountToCall, self.stacks[player]==0)] + Rb = Decimal(amountBy) + Bp = self.lastBet[street] + Bc = reduce(operator.add, self.bets[street][player], 0) + C = Bp - Bc + Rt = Bp + Rb + + self.bets[street][player].append(C + Rb) + self.stacks[player] -= (C + Rb) + self.actions[street] += [(player, 'raises', Rb, Rt, C, self.stacks[player]==0)] + self.lastBet[street] = Rt + + def addCallandRaise(self, street, player, amount): + """\ +For sites which by "raises x" mean "calls and raises putting a total of x in the por". """ + self.checkPlayerExists(player) + CRb = Decimal(amount) + Bp = self.lastBet[street] + Bc = reduce(operator.add, self.bets[street][player], 0) + C = Bp - Bc + Rb = CRb - C + Rt = Bp + Rb + + self._addRaise(street, player, C, Rb, Rt) + + def _addRaise(self, street, player, C, Rb, Rt): + self.bets[street][player].append(C + Rb) + self.stacks[player] -= (C + Rb) + self.actions[street] += [(player, 'raises', Rb, Rt, C, self.stacks[player]==0)] + self.lastBet[street] = Rt + + def addRaiseTo(self, street, player, amountTo): + """\ +Add a raise on [street] by [player] to [amountTo] +""" + self.checkPlayerExists(player) + Bc = reduce(operator.add, self.bets[street][player], 0) + Rt = Decimal(amountTo) + C = Bp - Bc + Rb = Rt - C + self._addRaise(street, player, C, Rb, Rt) def addBet(self, street, player, amount): From fe2c8068226ce5108b8c379041c27fd960826b2d Mon Sep 17 00:00:00 2001 From: Matt Turnbull Date: Wed, 17 Dec 2008 11:57:06 +0000 Subject: [PATCH 03/13] Everleaf appears to need addCallandRaise --- pyfpdb/EverleafToFpdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index bce92e79..d0885d92 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -80,6 +80,7 @@ class Everleaf(HandHistoryConverter): self.rexx.setActionStepRegex('.*\n(?P.*)(?P: bets| checks| raises| calls| folds)(\s\[\$ (?P[.\d]+) USD\])?') self.rexx.setShowdownActionRegex('.*\n(?P.*) shows \[ (?P.*) \]') self.rexx.setCollectPotRegex('.*\n(?P.*) wins \$ (?P[.\d]+) USD(.*?\[ (?P.*?) \])?') + #self.rexx.setCollectPotRegex('.*\n(?P.*) wins \$ (?P[.\d]+) USD(.*\[ (?P) \S\S, \S\S, \S\S, \S\S, \S\S \])?') self.rexx.sits_out_re = re.compile('(?P.*) sits out') self.rexx.compileRegexes() @@ -169,7 +170,7 @@ class Everleaf(HandHistoryConverter): m = self.rexx.action_re.finditer(hand.streets.group(street)) for action in m: if action.group('ATYPE') == ' raises': - hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') ) + hand.addCallandRaise( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == ' calls': hand.addCall( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == ': bets': From e0105c5ed1a2004c49d14a4d82d9a331318db2a4 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 17 Dec 2008 13:24:37 -0500 Subject: [PATCH 04/13] Clean up db connection in aux window. Hole cards to stud mucked list. --- pyfpdb/HUD_main.py | 15 ++++--- pyfpdb/Hud.py | 21 +++++---- pyfpdb/Mucked.py | 108 +++++++++++++++++++++++++++------------------ 3 files changed, 84 insertions(+), 60 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 0251c7cf..12417a23 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -22,14 +22,11 @@ Main for FreePokerTools HUD. ######################################################################## -# to do kill window on my seat -# to do adjust for preferred seat # to do allow window resizing # to do hud to echo, but ignore non numbers -# to do no hud window for hero +# to do no stat window for hero # to do things to add to config.xml # to do font and size -# to do opacity # Standard Library modules import sys @@ -68,8 +65,11 @@ def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_conn global hud_dict gtk.gdk.threads_enter() try: - hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_name) + hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_connection) hud_dict[table_name].create(new_hand_id, config) + for m in hud_dict[table_name].aux_windows: + m.update_data(new_hand_id, db_connection) + m.update_gui(new_hand_id) hud_dict[table_name].update(new_hand_id, config, stat_dict) hud_dict[table_name].reposition_windows() return False @@ -114,7 +114,6 @@ def read_stdin(): # This is the thread function is_tournament = False (tour_number, tab_number) = (0, 0) mat_obj = tourny_finder.search(table_name) -# if len(mat_obj.groups) == 2: if mat_obj: is_tournament = True (tour_number, tab_number) = mat_obj.group(1, 2) @@ -125,11 +124,13 @@ def read_stdin(): # This is the thread function if hud_dict.has_key(table_name): # update the data for the aux_windows for aw in hud_dict[table_name].aux_windows: - aw.update_data(new_hand_id) + aw.update_data(new_hand_id, db_connection) update_HUD(new_hand_id, table_name, config, stat_dict) + # if a hud for this TOURNAMENT table exists, just update it elif hud_dict.has_key(tour_number): update_HUD(new_hand_id, tour_number, config, stat_dict) + # otherwise create a new hud else: if is_tournament: diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 92f38bb7..93bbc2f5 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -47,12 +47,12 @@ import HUD_main class Hud: - def __init__(self, table, max, poker_game, config, db_name): + def __init__(self, table, max, poker_game, config, db_connection): self.table = table self.config = config self.poker_game = poker_game self.max = max - self.db_name = db_name + self.db_connection = db_connection self.deleted = False self.stacked = True self.site = table.site @@ -133,8 +133,7 @@ class Hud: for i in range(1, self.max + 1): (x, y) = loc[adj[i]] if self.stat_windows.has_key(i): - self.stat_windows[i].relocate(x, y) - + self.stat_windows[i].relocate(x, y) return True def on_button_press(self, widget, event): @@ -177,9 +176,9 @@ class Hud: try: if int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0: fav_seat = config.supported_sites[self.table.site].layout[self.max].fav_seat - db_connection = Database.Database(config, self.db_name, 'temp') - actual_seat = db_connection.get_actual_seat(hand, config.supported_sites[self.table.site].screen_name) - db_connection.close_connection() +# db_connection = Database.Database(config, self.db_name, 'temp') + actual_seat = self.db_connection.get_actual_seat(hand, config.supported_sites[self.table.site].screen_name) +# db_connection.close_connection() for i in range(0, self.max + 1): j = actual_seat + i if j > self.max: j = j - self.max @@ -227,7 +226,7 @@ class Hud: game_params = config.get_game_parameters(self.poker_game) if not game_params['aux'] == "": aux_params = config.get_aux_parameters(game_params['aux']) - self.aux_windows.append(eval("%s.%s(gtk.Window(), self, config, 'fpdb')" % (aux_params['module'], aux_params['class']))) + self.aux_windows.append(eval("%s.%s(gtk.Window(), self, config, aux_params)" % (aux_params['module'], aux_params['class']))) # gobject.timeout_add(500, self.update_table_position) @@ -471,12 +470,12 @@ class Popup_window: break # get a database connection - db_connection = Database.Database(stat_window.parent.config, stat_window.parent.db_name, 'temp') +# db_connection = Database.Database(stat_window.parent.config, stat_window.parent.db_name, 'temp') # calculate the stat_dict and then create the text for the pu # stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand, stat_window.player_id) - stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand) - db_connection.close_connection() + stat_dict = self.db_connection.get_stats_from_hand(stat_window.parent.hand) +# db_connection.close_connection() pu_text = "" for s in stat_list: diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py index 7cb62918..5d4d8047 100755 --- a/pyfpdb/Mucked.py +++ b/pyfpdb/Mucked.py @@ -36,14 +36,13 @@ import Configuration import Database class Aux_Window: - def __init__(self, parent, hud, config, db_name): + def __init__(self, container, hud, params, config): self.config = hud self.config = config - self.parent = parent - self.db_name = db_name + self.container = container self.vbox = gtk.VBox() - self.parent.add(self.vbox) + self.container.add(self.vbox) def update_data(self): pass @@ -52,40 +51,49 @@ class Aux_Window: pass class Stud_mucked(Aux_Window): - def __init__(self, parent, hud, config, db_name): + def __init__(self, container, hud, config, params): self.hud = hud # hud object that this aux window supports self.config = config # configuration object for this aux window to use - self.parent = parent # parent container for this aux window widget - self.db_name = db_name # database for this aux window to use + self.container = container # parent container for this aux window widget + self.params = params # hash aux params from config + + try: + site_params = self.config.get_site_parameters(self.hud.site) + self.hero = site_params['screen_name'] + except: + self.hero = '' self.vbox = gtk.VBox() - self.parent.add(self.vbox) + self.container.add(self.vbox) - self.mucked_list = Stud_list(self.vbox, config, db_name) - self.mucked_cards = Stud_cards(self.vbox, config, db_name) + self.mucked_list = Stud_list(self.vbox, self, params, config, self.hero) + self.mucked_cards = Stud_cards(self.vbox, self, params, config) self.mucked_list.mucked_cards = self.mucked_cards - self.parent.show_all() + self.container.show_all() - def update_data(self, new_hand_id): - self.mucked_cards.update_data(new_hand_id) - self.mucked_list.update_data(new_hand_id) + def update_data(self, new_hand_id, db_connection): + self.mucked_cards.update_data(new_hand_id, db_connection) + self.mucked_list.update_data(new_hand_id, db_connection) def update_gui(self, new_hand_id): self.mucked_cards.update_gui(new_hand_id) self.mucked_list.update_gui(new_hand_id) class Stud_list: - def __init__(self, parent, config, db_name): + def __init__(self, container, parent, params, config, hero): - self.parent = parent + self.container = container + self.parent = parent + self.params = params self.config = config - self.db_name = db_name + self.hero = hero +# self.db_name = db_name # set up a scrolled window to hold the listbox self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) - self.parent.add(self.scrolled_window) + self.container.add(self.scrolled_window) # create a ListStore to use as the model self.liststore = gtk.ListStore(str, str, str, str) @@ -123,17 +131,17 @@ class Stud_list: self.scrolled_window.add_with_viewport(self.treeview) def activated_event(self, path, column, data=None): - sel = self.treeview.get_selection() - (model, iter) = sel.get_selected() - self.mucked_cards.update_data(model.get_value(iter, 0)) - self.mucked_cards.update_gui(model.get_value(iter, 0)) + pass +# sel = self.treeview.get_selection() +# (model, iter) = sel.get_selected() +# self.mucked_cards.update_data(model.get_value(iter, 0)) +# self.mucked_cards.update_gui(model.get_value(iter, 0)) - def update_data(self, new_hand_id): + def update_data(self, new_hand_id, db_connection): """Updates the data needed for the list box.""" - db_connection = Database.Database(self.config, 'fpdb', '') +# db_connection = Database.Database(self.config, 'fpdb', '') self.winners = db_connection.get_winners_from_hand(new_hand_id) - db_connection.close_connection() pot = 0 winners = '' for player in self.winners.keys(): @@ -143,8 +151,22 @@ class Stud_list: winners = winners + player pot_dec = "%.2f" % (float(pot)/100) - self.info_row = ((new_hand_id, "xxxx", pot_dec, winners), ) + hero_cards = self.get_hero_cards(self.parent.hero, self.parent.mucked_cards.cards) + self.info_row = ((new_hand_id, hero_cards, pot_dec, winners), ) + def get_hero_cards(self, hero, cards): + """Formats the hero cards for inclusion in the tree.""" + trans = ('0', 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A') + if hero == '': + return "xxxxxx" + else: + for k in cards.keys(): + if cards[k]['screen_name'] == hero: + return trans[cards[k]['card1Value']] + cards[k]['card1Suit'] \ + + trans[cards[k]['card2Value']] + cards[k]['card2Suit'] \ + + trans[cards[k]['card3Value']] + cards[k]['card3Suit'] + return "xxxxxx" + def update_gui(self, new_hand_id): iter = self.liststore.append(self.info_row[0]) sel = self.treeview.get_selection() @@ -154,11 +176,13 @@ class Stud_list: vadj.set_value(vadj.upper) class Stud_cards: - def __init__(self, parent, config, db_name = 'fpdb'): + def __init__(self, container, parent, params, config): - self.parent = parent #this is the parent of the mucked cards widget + self.container = container #this is the parent container of the mucked cards widget + self.parent = parent + self.params = params self.config = config - self.db_name = db_name +# self.db_name = db_name self.card_images = self.get_card_images() self.seen_cards = {} @@ -196,7 +220,7 @@ class Stud_cards: for r in range(0, self.rows): self.grid.attach(self.grid_contents[(c, r)], c, c+1, r, r+1, xpadding = 1, ypadding = 1) - self.parent.add(self.grid) + self.container.add(self.grid) def translate_cards(self, old_cards): ranks = ('', '', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A') @@ -212,10 +236,9 @@ class Stud_cards: old_cards[c][key] = ranks[old_cards[c][rank]] + old_cards[c][suit] return old_cards - def update_data(self, new_hand_id): - db_connection = Database.Database(self.config, 'fpdb', '') + def update_data(self, new_hand_id, db_connection): +# db_connection = Database.Database(self.config, 'fpdb', '') cards = db_connection.get_cards(new_hand_id) - self.clear() self.cards = self.translate_cards(cards) self.tips = [] @@ -232,9 +255,9 @@ class Stud_cards: else: temp = temp + "\n" self.tips.append(temp) - db_connection.close_connection() def update_gui(self, new_hand_id): + self.clear() for c in self.cards.keys(): self.grid_contents[(1, self.cards[c]['seat_number'] - 1)].set_text(self.cards[c]['screen_name']) for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'), @@ -262,11 +285,12 @@ class Stud_cards: for c in range(0, 7): self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')]) self.eb[(c, r)].set_tooltip_text('') + def get_card_images(self): card_images = {} suits = ('S', 'H', 'D', 'C') ranks = ('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'B') - pb = gtk.gdk.pixbuf_new_from_file(self.config.execution_path("54PFozzycards0.png")) + pb = gtk.gdk.pixbuf_new_from_file(self.config.execution_path(self.params['deck'])) for j in range(0, 14): for i in range(0, 4): @@ -282,24 +306,24 @@ if __name__== "__main__": def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() # used only for testing - def process_new_hand(source, condition): #callback from stdin watch -- testing only + def process_new_hand(source, condition, db_connection): #callback from stdin watch -- testing only # there is a new hand_id to be processed # just read it and pass it to update new_hand_id = sys.stdin.readline() new_hand_id = new_hand_id.rstrip() # remove trailing whitespace - m.update_data(new_hand_id) + m.update_data(new_hand_id, db_connection) m.update_gui(new_hand_id) return(True) config = Configuration.Config() -# db_connection = Database.Database(config, 'fpdb', '') + db_connection = Database.Database(config, 'fpdb', '') main_window = gtk.Window() main_window.set_keep_above(True) main_window.connect("destroy", destroy) - aux_to_call = "Stud_mucked" - m = eval("%s(main_window, None, config, 'fpdb')" % aux_to_call) - main_window.show_all() + aux_to_call = "stud_mucked" + aux_params = config.get_aux_parameters(aux_to_call) + m = eval("%s(main_window, None, config, aux_params)" % aux_params['class']) - s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) + s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand, db_connection) gtk.main() From bb9f85233eb53f149d0e7f1510b4e3ec069cd648 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 17 Dec 2008 21:57:05 -0500 Subject: [PATCH 05/13] font and font_size added to config and used in HUD. --- pyfpdb/Configuration.py | 45 ++++++++++++++++++++++++++++++----------- pyfpdb/Hud.py | 10 +++++++-- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 091b2d47..9b8f4f08 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -60,6 +60,8 @@ class Site: self.converter = node.getAttribute("converter") self.enabled = node.getAttribute("enabled") self.aux_window = node.getAttribute("aux_window") + self.font = node.getAttribute("font") + self.font_size = node.getAttribute("font_size") self.layout = {} for layout_node in node.getElementsByTagName('layout'): @@ -479,6 +481,19 @@ class Config: else: colors['hudfgcolor'] = self.supported_sites[site].hudfgcolor return colors + + def get_default_font(self, site = 'PokerStars'): + (font, font_size) = ("Sans", "8") + if self.supported_sites[site].font == "": + font = "Sans" + else: + font = self.supported_sites[site].font + + if self.supported_sites[site].font_size == "": + font_size = "8" + else: + font_size = self.supported_sites[site].font_size + return (font, font_size) def get_locations(self, site = "PokerStars", max = "8"): @@ -511,13 +526,16 @@ class Config: parms["site_name"] = self.supported_sites[site].site_name parms["enabled"] = self.supported_sites[site].enabled parms["aux_window"] = self.supported_sites[site].aux_window + parms["font"] = self.supported_sites[site].font + parms["font_size"] = self.supported_sites[site].font_size return parms def set_site_parameters(self, site_name, converter = None, decoder = None, hudbgcolor = None, hudfgcolor = None, hudopacity = None, screen_name = None, site_path = None, table_finder = None, - HH_path = None, enabled = None): + HH_path = None, enabled = None, + font = None, font_size = None): """Sets the specified site parameters for the specified site.""" site_node = self.get_site_node(site_name) if not db_node == None: @@ -531,18 +549,20 @@ class Config: if not table_finder == None: site_node.setAttribute("table_finder", table_finder) if not HH_path == None: site_node.setAttribute("HH_path", HH_path) if not enabled == None: site_node.setAttribute("enabled", enabled) + if not font == None: site_node.setAttribute("font", font) + if not font_size == None: site_node.setAttribute("font_size", font_size) - if self.supported_databases.has_key(db_name): - if not converter == None: self.supported_sites[site].converter = converter - if not decoder == None: self.supported_sites[site].decoder = decoder - if not hudbgcolor == None: self.supported_sites[site].hudbgcolor = hudbgcolor - if not hudfgcolor == None: self.supported_sites[site].hudfgcolor = hudfgcolor - if not hudopacity == None: self.supported_sites[site].hudopacity = hudopacity - if not screen_name == None: self.supported_sites[site].screen_name = screen_name - if not site_path == None: self.supported_sites[site].site_path = site_path - if not table_finder == None: self.supported_sites[site].table_finder = table_finder - if not HH_path == None: self.supported_sites[site].HH_path = HH_path - if not enabled == None: self.supported_sites[site].enabled = enabled +# if self.supported_databases.has_key(db_name): +# if not converter == None: self.supported_sites[site].converter = converter +# if not decoder == None: self.supported_sites[site].decoder = decoder +# if not hudbgcolor == None: self.supported_sites[site].hudbgcolor = hudbgcolor +# if not hudfgcolor == None: self.supported_sites[site].hudfgcolor = hudfgcolor +# if not hudopacity == None: self.supported_sites[site].hudopacity = hudopacity +# if not screen_name == None: self.supported_sites[site].screen_name = screen_name +# if not site_path == None: self.supported_sites[site].site_path = site_path +# if not table_finder == None: self.supported_sites[site].table_finder = table_finder +# if not HH_path == None: self.supported_sites[site].HH_path = HH_path +# if not enabled == None: self.supported_sites[site].enabled = enabled return def get_aux_windows(self): @@ -640,6 +660,7 @@ if __name__== "__main__": for site in c.supported_sites.keys(): print "site = ", site, print c.get_site_parameters(site) + print c.get_default_font(site) for game in c.get_supported_games(): print c.get_game_parameters(game) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 93bbc2f5..5ff8fc7c 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -61,7 +61,13 @@ class Hud: self.stat_windows = {} self.popup_windows = {} self.aux_windows = [] - self.font = pango.FontDescription("Sans 7") + (font, font_size) = config.get_default_font(self.table.site) + print "font = ", font, "size = ", font_size + if font == None or font_size == None: + self.font = pango.FontDescription("Sans 7") + else: + print "Setting font to ", font + " " + font_size + self.font = pango.FontDescription(font + " " + font_size) # Set up a main window for this this instance of the HUD self.main_window = gtk.Window() @@ -401,7 +407,7 @@ class Stat_Window: self.e_box[r][c].add(self.label[r][c]) self.e_box[r][c].connect("button_press_event", self.button_press_cb) - font = pango.FontDescription("Sans 7") +# font = pango.FontDescription(self.font) self.label[r][c].modify_font(font) self.window.set_opacity(parent.colors['hudopacity']) From 7803f523071d9b81d5e81480745cda1dc089c705 Mon Sep 17 00:00:00 2001 From: Matt Turnbull Date: Thu, 18 Dec 2008 17:49:17 +0000 Subject: [PATCH 06/13] autoimport a bit better, no? --- pyfpdb/GuiAutoImport.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 30fe7e67..820642e4 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -104,9 +104,12 @@ class GuiAutoImport (threading.Thread): def do_import(self): """Callback for timer to do an import iteration.""" - self.importer.runUpdated() - print "GuiAutoImport.import_dir done" - return self.doAutoImportBool + if self.doAutoImportBool: + self.importer.runUpdated() + print "GuiAutoImport.import_dir done" + return True + else: + return False def startClicked(self, widget, data): """runs when user clicks start on auto import tab""" @@ -149,12 +152,15 @@ class GuiAutoImport (threading.Thread): interval=int(self.intervalEntry.get_text()) gobject.timeout_add(interval*1000, self.do_import) else: # toggled off - self.doAutoImportBool = False # do_import will return this and stop the gobject callback timer - #TODO: other clean up, such as killing HUD - print "Stopping autoimport" - self.pipe_to_hud.communicate('\n') # waits for process to terminate - self.pipe_to_hud = None - widget.set_label(u'Start Autoimport') + self.doAutoImportBool = False # do_import will return this and stop the gobject callback timer + print "Stopping autoimport" + print >>self.pipe_to_hud.stdin, "\n" + #self.pipe_to_hud.communicate('\n') # waits for process to terminate + self.pipe_to_hud = None + self.startButton.set_label(u'Start Autoimport') + + + #end def GuiAutoImport.startClicked def get_vbox(self): From b37ddc5ace00dd1e49c0bd8f7b7f586d636edf33 Mon Sep 17 00:00:00 2001 From: Matt Turnbull Date: Fri, 19 Dec 2008 03:01:45 +0000 Subject: [PATCH 07/13] So close, yet so far. Need to calculate rake to output the side pots line correctly. --- pyfpdb/Hand.py | 152 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 4 deletions(-) diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index f4a7bec7..f06a7926 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -307,8 +307,13 @@ Add a raise on [street] by [player] to [amountTo] self.totalpot = 0 + #print "[POT] stack list" + #print dict([(player[1], Decimal(player[2])) for player in self.players]) + players_who_act_preflop = set([x[0] for x in self.actions['PREFLOP']]) + self.pot = Pot(players_who_act_preflop) - for street in self.actions: + #for street in self.actions: + for street in [x for x in self.streetList if x in self.actions]: uncalled = 0 calls = [0] for act in self.actions[street]: @@ -317,18 +322,30 @@ Add a raise on [street] by [player] to [amountTo] uncalled = Decimal(act[2]) # only the last bet or raise can be uncalled calls = [0] print "uncalled: ", uncalled + + self.pot.addMoney(act[0], Decimal(act[2])) + elif act[1] == 'raises': # [name, 'raises', amountby, amountto, amountcalled] print "calls %s and raises %s to %s" % (act[4],act[2],act[3]) self.totalpot += Decimal(act[2]) + Decimal(act[4]) calls = [0] uncalled = Decimal(act[2]) print "uncalled: ", uncalled + + self.pot.addMoney(act[0], Decimal(act[2])+Decimal(act[4])) + elif act[1] == 'calls': # [name, 'calls', amount] self.totalpot += Decimal(act[2]) calls = calls + [Decimal(act[2])] print "calls:", calls + + self.pot.addMoney(act[0], Decimal(act[2])) + elif act[1] == 'posts': self.totalpot += Decimal(act[3]) + + self.pot.addMoney(act[0], Decimal(act[3])) + if act[2] == 'big blind': # the bb gets called by out-of-blinds posts; but sb+bb only calls bb if uncalled == Decimal(act[3]): # a bb is already posted @@ -343,14 +360,15 @@ Add a raise on [street] by [player] to [amountTo] uncalled = Decimal(act[3]) calls = [0] pass - + elif act[1] == 'folds': + self.pot.addFold(act[0]) if uncalled > 0 and max(calls+[0]) < uncalled: print "DEBUG returning some bet, calls:", calls print "DEBUG returned: %.2f from %.2f" % ((uncalled - max(calls)), self.totalpot,) self.totalpot -= (uncalled - max(calls)) print "DEBUG new totalpot:", self.totalpot - + print "DEBUG new Pot.total:", self.pot if self.totalcollected is None: self.totalcollected = 0; @@ -446,7 +464,8 @@ Map the tuple self.gametype onto the pokerstars string describing it print >>fh, "DEBUG: what do they show" print >>fh, _("*** SUMMARY ***") - print >>fh, _("Total pot $%s | Rake $%.2f" % (self.totalpot, self.rake)) # TODO: side pots + print >>fh, "%s | Rake $%.2f" % (self.pot, self.rake) + #print >>fh, _("Total pot $%s | Rake $%.2f" % (self.totalpot, self.rake)) # TODO: side pots board = [] for s in self.board.values(): @@ -544,3 +563,128 @@ Map the tuple self.gametype onto the pokerstars string describing it class FpdbParseError(Exception): pass + +class Pot(object): + + def __init__(self, contenders): + self.contenders = contenders + self.committed = dict([(player,Decimal(0)) for player in contenders]) + self.total = Decimal(0) + + def addFold(self, player): + self.contenders.remove(player) + + def addMoney(self, player, amount): + self.committed[player] += amount + + def __str__(self): + self.total = sum(self.committed.values()) + committed = sorted([ (v,k) for (k,v) in self.committed.items()]) + lastbet = committed[-1][0] - committed[-2][0] + if lastbet > 0: # uncalled + returnto = committed[-1][1] + #print "returning %f to %s" % (lastbet, returnto) + self.total -= lastbet + self.committed[returnto] -= lastbet + + + + # now: for those contenders still contending.. + commitsall = sorted([(v,k) for (k,v) in self.committed.items() if v >0]) + + pots = [] + while len(commitsall) > 0: + commitslive = [(v,k) for (v,k) in commitsall if k in self.contenders] + v1 = commitslive[0][0] + pots += [sum([min(v,v1) for (v,k) in commitsall])] + #print "all: ", commitsall + #print "live:", commitslive + commitsall = [((v-v1),k) for (v,k) in commitsall if v-v1 >0] + + + #print "[**]", pots + + # TODO: I think rake gets taken out of the pots. + # so it goes: + # total pot x. main pot y, side pot z. | rake r + # and y+z+r = x + # for example: + # Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2 + # so....... that's tricky. + if len(pots) == 1: + return "Main pot $%.2f" % pots[0] + elif len(pots) == 2: + return "Main pot $%.2f, side pot $%2.f" % (pots[0],pots[1]) + elif len(pots) == 3: + return "Main pot $%.2f, side pot-1 $%2.f, side pot-2 $.2f" % (pots[0],pots[1],pots[2]) + else: + return "too many pots.. fix me" + + + + + #def addMoney(self, player, amount): + #uncalled = max(self.committed.values()) - self.committed[player] + + #if self.cap: + #overflow = self.committed[player] + amount - self.cap + #if overflow > 0: + #self.total += amount - overflow + #self.committed[player] = self.cap + #self.sidepot.addMoney(player, overflow) + #else: + ## because it was capped, we can only be calling here. + #self.calls.append(min(uncalled,amount)) + #self.committed[player] += amount + #self.total += amount + #else: + ## no player is currently all-in. + + #self.committed[player] += amount + #self.total += amount + + ## is this a new uncalled bet? + #r = amount - uncalled + #if r > 0: + #self.uncalled = (player, r) + #self.calls = [0] + #else: + #self.calls.append(amount + r) + + ## is this player all-in? + #if self.committed[player] == self.stacks[player]: + #self.cap = self.stacks[player] + #contenders = self.contenders[:] + #contenders.remove(player) + #sidepotstacks = dict([(player, self.stacks[player]-self.committed[player]) for player in contenders]) + #self.sidepot = Pot(contenders, sidepotstacks, self.sidepotnum+1) + #elif self.committed[player] > self.stacks[player]: + #print "ERROR %s problem" % (player,) + #print self.committed[player], self.stacks[player] + #raise FpdbParseError + + #def returnUncalled(self): + #print "[POT]" + #print "last bet", self.uncalled + #print "calls:", self.calls + #print + #if self.uncalled: + #if max(self.calls) < self.uncalled[1]: + #self.total -= self.uncalled[1] + #print "returned", self.uncalled[0],self.uncalled[1]-max(self.calls), "from", self.uncalled[1] + + + #def __str__(self): + #total = self.total + #if self.sidepotnum == 0: + #return "Main pot $%.2f%s" %(total, self.sidepot or '' ) + #elif self.sidepotnum > 0: + #if self.sidepot: + #return ", side pot-%d $%.2f%s" % (self.sidepotnum, total, self.sidepot) + #else: + #return ", side pot $%.2f." % (total,) + + + + + From 49aa8921e39427aa0a697b281170fabe414a446a Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 19 Dec 2008 16:52:32 +0900 Subject: [PATCH 08/13] Grapher: Update to support mutiple sites and players Makes sites actually selectable via checkboxes. Removed the sitename from the graph string for the moment - How that string is generated needs a major overhaul --- pyfpdb/FpdbSQLQueries.py | 9 +++- pyfpdb/GuiGraphViewer.py | 78 ++++++++++++++++++++++++---------- pyfpdb/HUD_config.xml.example | 2 +- pyfpdb/psnlheparser-mct.tgz | Bin 26184 -> 0 bytes 4 files changed, 63 insertions(+), 26 deletions(-) delete mode 100644 pyfpdb/psnlheparser-mct.tgz diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 07fa15bb..9afdc28f 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -635,6 +635,11 @@ class FpdbSQLQueries: elif(self.dbname == 'SQLite'): self.query['getPlayerId'] = """SELECT id from Players where name = %s""" + if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'): + self.query['getSiteId'] = """SELECT id from Sites where name = %s""" + elif(self.dbname == 'SQLite'): + self.query['getSiteId'] = """SELECT id from Sites where name = %s""" + if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'): self.query['getRingProfitAllHandsPlayerIdSite'] = """ SELECT hp.handId, hp.winnings, coalesce(hp.ante,0) + SUM(ha.amount) @@ -643,8 +648,8 @@ class FpdbSQLQueries: INNER JOIN Players pl ON hp.playerId = pl.id INNER JOIN Hands h ON h.id = hp.handId INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id - WHERE pl.name = %s - AND pl.siteId = %s + where pl.id in + AND pl.siteId in AND hp.tourneysPlayersId IS NULL GROUP BY hp.handId, hp.winnings, h.handStart, hp.ante ORDER BY h.handStart""" diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 94732295..a0a2e326 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -50,22 +50,27 @@ class GuiGraphViewer (threading.Thread): try: self.canvas.destroy() except AttributeError: pass - # Whaich sites are selected? - # TODO: - # What hero names for the selected site? - # TODO: + sitenos = [] + playerids = [] - name = self.heroes[self.sites] + # Which sites are selected? + for site in self.sites: + if self.sites[site] == True: + sitenos.append(self.siteid[site]) + self.cursor.execute(self.sql.query['getPlayerId'], (self.heroes[site],)) + result = self.db.cursor.fetchall() + if len(result) == 1: + playerids.append(result[0][0]) - if self.sites == "PokerStars": - site=2 - sitename="PokerStars: " - elif self.sites=="Full Tilt": - site=1 - sitename="Full Tilt: " - else: - print "invalid text in site selection in graph, defaulting to PS" - site=2 + if sitenos == []: + #Should probably pop up here. + print "No sites selected - defaulting to PokerStars" + sitenos = [2] + + + if playerids == []: + print "No player ids found" + return self.fig = Figure(figsize=(5,4), dpi=100) @@ -74,7 +79,7 @@ class GuiGraphViewer (threading.Thread): #Get graph data from DB starttime = time() - line = self.getRingProfitGraph(name, site) + line = self.getRingProfitGraph(playerids, sitenos) print "Graph generated in: %s" %(time() - starttime) self.ax.set_title("Profit graph for ring games") @@ -87,7 +92,8 @@ class GuiGraphViewer (threading.Thread): #TODO: Do something useful like alert user print "No hands returned by graph query" else: - text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line)) +# text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line)) + text = "All Hands, " + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line)) self.ax.annotate(text, xy=(10, -10), @@ -103,8 +109,26 @@ class GuiGraphViewer (threading.Thread): self.canvas.show() #end of def showClicked - def getRingProfitGraph(self, name, site): - self.cursor.execute(self.sql.query['getRingProfitAllHandsPlayerIdSite'], (name, site)) + def getRingProfitGraph(self, names, sites): + tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite'] +# print "DEBUG: getRingProfitGraph" + + #Buggered if I can find a way to do this 'nicely' take a list of intergers and longs + # and turn it into a tuple readale by sql. + # [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829) + nametest = str(tuple(names)) + sitetest = str(tuple(sites)) + nametest = nametest.replace("L", "") + nametest = nametest.replace(",)",")") + sitetest = sitetest.replace(",)",")") + + #Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf + tmp = tmp.replace("", nametest) + tmp = tmp.replace("", sitetest) + +# print "DEBUG: sql query:" +# print tmp + self.cursor.execute(tmp) #returns (HandId,Winnings,Costs,Profit) winnings = self.db.cursor.fetchall() @@ -125,7 +149,6 @@ class GuiGraphViewer (threading.Thread): pname.set_text(player) pname.set_width_chars(20) hbox.pack_start(pname, False, True, 0) - #TODO: Need to connect a callback here pname.connect("changed", self.__set_hero_name, site) #TODO: Look at GtkCompletion - to fill out usernames pname.show() @@ -134,7 +157,7 @@ class GuiGraphViewer (threading.Thread): def __set_hero_name(self, w, site): self.heroes[site] = w.get_text() - print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site]) +# print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site]) def createSiteLine(self, hbox, site): cb = gtk.CheckButton(site) @@ -144,8 +167,9 @@ class GuiGraphViewer (threading.Thread): def __set_site_select(self, w, site): # This doesn't behave as intended - self.site only allows 1 site for the moment. - self.sites = site - print "self.sites set to %s" %(self.sites) + print w.get_active() + self.sites[site] = w.get_active() + print "self.sites[%s] set to %s" %(site, self.sites[site]) def fillPlayerFrame(self, vbox): for site in self.conf.supported_sites.keys(): @@ -162,6 +186,13 @@ class GuiGraphViewer (threading.Thread): vbox.pack_start(hbox, False, True, 0) hbox.show() self.createSiteLine(hbox, site) + #Get db site id for filtering later + self.cursor.execute(self.sql.query['getSiteId'], (site)) + result = self.db.cursor.fetchall() + if len(result) == 1: + self.siteid[site] = result[0][0] + else: + print "Either 0 or more than one site matched - EEK" def fillDateFrame(self, vbox): # Hat tip to Mika Bostrom - calendar code comes from PokerStats @@ -261,7 +292,8 @@ class GuiGraphViewer (threading.Thread): self.sql=querylist self.conf = config - self.sites = "PokerStars" + self.sites = {} + self.siteid = {} self.heroes = {} # For use in date ranges. diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index 355c9d2f..bb48c482 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -49,7 +49,7 @@ - + diff --git a/pyfpdb/psnlheparser-mct.tgz b/pyfpdb/psnlheparser-mct.tgz deleted file mode 100644 index b04c80183ec34f4a740acfe9b0f9bebfbcf8e36d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26184 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 z 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 Y^1)&MzteJi9f=iZ()H#E3(k}O1NKQU4FCWD From 2d700820f42ec95edb21213d1e2bbeb65fb46ea7 Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 19 Dec 2008 16:58:24 +0900 Subject: [PATCH 09/13] Add Everleaf to DB init process Should these come from config on startup...? --- pyfpdb/fpdb_db.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index e51538ee..8c0ab66e 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -177,6 +177,7 @@ class fpdb_db: self.cursor.execute("INSERT INTO Settings VALUES (118);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');") self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") #end def fillDefaultData From db6a8c5b31adbf665e52e95bbd71fb5fa856686e Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 19 Dec 2008 17:21:58 +0900 Subject: [PATCH 10/13] Grapher: Make date ranges work - MySQL --- pyfpdb/FpdbSQLQueries.py | 2 ++ pyfpdb/GuiGraphViewer.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 9afdc28f..4df2fa75 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -650,6 +650,8 @@ class FpdbSQLQueries: INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id where pl.id in AND pl.siteId in + AND h.handStart > '' + AND h.handStart < '' AND hp.tourneysPlayersId IS NULL GROUP BY hp.handId, hp.winnings, h.handStart, hp.ante ORDER BY h.handStart""" diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index a0a2e326..ae8895ff 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -112,6 +112,12 @@ class GuiGraphViewer (threading.Thread): def getRingProfitGraph(self, names, sites): tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite'] # print "DEBUG: getRingProfitGraph" + start_date, end_date = self.__get_dates() + + if start_date == '': + start_date = '1970-01-01' + if end_date == '': + end_date = '2020-12-12' #Buggered if I can find a way to do this 'nicely' take a list of intergers and longs # and turn it into a tuple readale by sql. @@ -125,6 +131,8 @@ class GuiGraphViewer (threading.Thread): #Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf tmp = tmp.replace("", nametest) tmp = tmp.replace("", sitetest) + tmp = tmp.replace("", start_date) + tmp = tmp.replace("", end_date) # print "DEBUG: sql query:" # print tmp From 659f0bb508aefd11f29a90a75b49101b6defb4fc Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 19 Dec 2008 17:27:18 +0900 Subject: [PATCH 11/13] Grapher: Fix Postgres to work again --- pyfpdb/FpdbSQLQueries.py | 6 ++++-- pyfpdb/GuiGraphViewer.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 4df2fa75..fe170c8c 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -663,8 +663,10 @@ class FpdbSQLQueries: INNER JOIN Players pl ON hp.playerId = pl.id INNER JOIN Hands h ON h.id = hp.handId INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id - WHERE pl.name = %s - AND pl.siteId = %s + where pl.id in + AND pl.siteId in + AND h.handStart > '' + AND h.handStart < '' AND hp.tourneysPlayersId IS NULL GROUP BY hp.handId, hp.winnings, h.handStart ORDER BY h.handStart""" diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index ae8895ff..8026a2c7 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -195,7 +195,7 @@ class GuiGraphViewer (threading.Thread): hbox.show() self.createSiteLine(hbox, site) #Get db site id for filtering later - self.cursor.execute(self.sql.query['getSiteId'], (site)) + self.cursor.execute(self.sql.query['getSiteId'], (site,)) result = self.db.cursor.fetchall() if len(result) == 1: self.siteid[site] = result[0][0] From 16f9906d84f9c407c69ddc4339a7e714f091988e Mon Sep 17 00:00:00 2001 From: Matt Turnbull Date: Sat, 20 Dec 2008 02:22:21 +0000 Subject: [PATCH 12/13] pot total line output matches pokerstars better --- pyfpdb/Hand.py | 95 ++++++-------------------------------------------- 1 file changed, 10 insertions(+), 85 deletions(-) diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index f06a7926..e33d2d0c 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -296,22 +296,20 @@ Add a raise on [street] by [player] to [amountTo] if self.totalpot is None: self.totalpot = 0 - # player names: - # print [x[1] for x in self.players] for player in [x[1] for x in self.players]: for street in self.streetList: - #print street, self.bets[street][player] self.totalpot += reduce(operator.add, self.bets[street][player], 0) print "DEBUG conventional totalpot:", self.totalpot self.totalpot = 0 - #print "[POT] stack list" - #print dict([(player[1], Decimal(player[2])) for player in self.players]) + players_who_act_preflop = set([x[0] for x in self.actions['PREFLOP']]) self.pot = Pot(players_who_act_preflop) + + # this can now be pruned substantially if Pot is working. #for street in self.actions: for street in [x for x in self.streetList if x in self.actions]: uncalled = 0 @@ -413,9 +411,7 @@ Map the tuple self.gametype onto the pokerstars string describing it print >>fh, _("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos)) players_who_act_preflop = set([x[0] for x in self.actions['PREFLOP']]) - #print players_who_act_preflop - #print [x[1] for x in self.players] - #print [x for x in self.players if x[1] in players_who_act_preflop] + for player in [x for x in self.players if x[1] in players_who_act_preflop]: #Only print stacks of players who do something preflop print >>fh, _("Seat %s: %s ($%s)" %(player[0], player[1], player[2])) @@ -524,10 +520,6 @@ Map the tuple self.gametype onto the pokerstars string describing it def bestHand(self, side, cards): return HandHistoryConverter.eval.best('hi', cards, []) - # from pokergame.py - def bestHandValue(self, side, serial): - (value, cards) = self.bestHand(side, serial) - return value # from pokergame.py # got rid of the _ for internationalisation @@ -611,80 +603,13 @@ class Pot(object): # for example: # Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2 # so....... that's tricky. - if len(pots) == 1: - return "Main pot $%.2f" % pots[0] + if len(pots) == 1: # (only use Total pot) + #return "Main pot $%.2f." % pots[0] + return "Total pot $%.2f" % (self.total,) elif len(pots) == 2: - return "Main pot $%.2f, side pot $%2.f" % (pots[0],pots[1]) + return "Total pot $%.2f Main pot $%.2f. Side pot $%2.f." % (self.total, pots[0],pots[1]) elif len(pots) == 3: - return "Main pot $%.2f, side pot-1 $%2.f, side pot-2 $.2f" % (pots[0],pots[1],pots[2]) + return "Total pot $%.2f Main pot $%.2f. Side pot-1 $%2.f. Side pot-2 $.2f." % (self.total, pots[0],pots[1],pots[2]) else: - return "too many pots.. fix me" - - - - - #def addMoney(self, player, amount): - #uncalled = max(self.committed.values()) - self.committed[player] - - #if self.cap: - #overflow = self.committed[player] + amount - self.cap - #if overflow > 0: - #self.total += amount - overflow - #self.committed[player] = self.cap - #self.sidepot.addMoney(player, overflow) - #else: - ## because it was capped, we can only be calling here. - #self.calls.append(min(uncalled,amount)) - #self.committed[player] += amount - #self.total += amount - #else: - ## no player is currently all-in. + return "too many pots.. fix me.", pots - #self.committed[player] += amount - #self.total += amount - - ## is this a new uncalled bet? - #r = amount - uncalled - #if r > 0: - #self.uncalled = (player, r) - #self.calls = [0] - #else: - #self.calls.append(amount + r) - - ## is this player all-in? - #if self.committed[player] == self.stacks[player]: - #self.cap = self.stacks[player] - #contenders = self.contenders[:] - #contenders.remove(player) - #sidepotstacks = dict([(player, self.stacks[player]-self.committed[player]) for player in contenders]) - #self.sidepot = Pot(contenders, sidepotstacks, self.sidepotnum+1) - #elif self.committed[player] > self.stacks[player]: - #print "ERROR %s problem" % (player,) - #print self.committed[player], self.stacks[player] - #raise FpdbParseError - - #def returnUncalled(self): - #print "[POT]" - #print "last bet", self.uncalled - #print "calls:", self.calls - #print - #if self.uncalled: - #if max(self.calls) < self.uncalled[1]: - #self.total -= self.uncalled[1] - #print "returned", self.uncalled[0],self.uncalled[1]-max(self.calls), "from", self.uncalled[1] - - - #def __str__(self): - #total = self.total - #if self.sidepotnum == 0: - #return "Main pot $%.2f%s" %(total, self.sidepot or '' ) - #elif self.sidepotnum > 0: - #if self.sidepot: - #return ", side pot-%d $%.2f%s" % (self.sidepotnum, total, self.sidepot) - #else: - #return ", side pot $%.2f." % (total,) - - - - - From 5d909fb64898330f4f16ea623b114480f0a4fa28 Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 20 Dec 2008 12:20:18 +0900 Subject: [PATCH 13/13] Reapply stars regex changed reverted during a merge --- pyfpdb/fpdb_simple.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 6636c656..e1e0f737 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1170,14 +1170,16 @@ def parseHandStartTime(topline, site): tmp=topline[pos1:pos2] isUTC=True else: - tmp=topline[-30:] + tmp=topline #print "parsehandStartTime, tmp:", tmp pos = tmp.find("-")+2 tmp = tmp[pos:] #Need to match either # 2008/09/07 06:23:14 ET or - # 2008/08/17 - 01:14:43 (ET) - m = re.match('(?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\- ]+(?P
[0-9]{2}):(?P[0-9]{2}):(?P[0-9]{2})',tmp) + # 2008/08/17 - 01:14:43 (ET) or + # 2008/11/12 9:33:31 CET [2008/11/12 3:33:31 ET] + rexx = '(?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\- ]+(?P
[0-9]+):(?P[0-9]+):(?P[0-9]+)' + m = re.search(rexx,tmp) #print "year:", int(m.group('YEAR')), "month", int(m.group('MON')), "day", int(m.group('DAY')), "hour", int(m.group('HR')), "minute", int(m.group('MIN')), "second", int(m.group('SEC')) result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC'))) else: