diff --git a/gfx/fpdb_large_icon.ico b/gfx/fpdb_large_icon.ico new file mode 100644 index 00000000..6be85f1c Binary files /dev/null and b/gfx/fpdb_large_icon.ico differ diff --git a/packaging/debian/changelog b/packaging/debian/changelog index ec66f4a7..4bacf635 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,8 +1,8 @@ -free-poker-tools (0.12-1) unstable; urgency=low +free-poker-tools (0.12~git20100122) unstable; urgency=low - * New release + * New snapshot release with reworked import code - -- Mika Bostrom Mon, 26 Oct 2009 17:49:07 +0200 + -- Mika Bostrom Fri, 22 Jan 2010 09:25:27 +0200 free-poker-tools (0.11.3+git20091023) unstable; urgency=low diff --git a/pyfpdb/AlchemyFacilities.py b/pyfpdb/AlchemyFacilities.py new file mode 100644 index 00000000..c8284efb --- /dev/null +++ b/pyfpdb/AlchemyFacilities.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +from decimal import Decimal + +from sqlalchemy import types +from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.exc import IntegrityError + +import Card + +class CardColumn(types.TypeDecorator): + """Stores cards as smallints + + Automatically converts values like '9h' to smallint + + >>> CardColumn().process_bind_param( 'Td', '' ) + 22 + >>> CardColumn().process_bind_param( u'Td', '' ) + 22 + >>> CardColumn().process_bind_param( 22, '' ) + 22 + >>> CardColumn().process_result_value( 22, '' ) + 'Td' + """ + + impl = types.SmallInteger + + def process_bind_param(self, value, dialect): + if value is None or isinstance(value, int): + return value + elif isinstance(value, basestring) and len(value) == 2: + return Card.encodeCard(str(value)) + else: + raise Exception, "Incorrect card value: " + repr(value) + + def process_result_value(self, value, dialect): + return Card.valueSuitFromCard( value ) + + +class MoneyColumn(types.TypeDecorator): + """Stores money: bets, pots, etc + + Understands: + Decimal as real amount + int as amount mupliplied by 100 + string as decimal + Returns Decimal + >>> MoneyColumn().process_bind_param( 230, '' ) + 230 + >>> MoneyColumn().process_bind_param( Decimal('2.30'), '' ) + 230 + >>> MoneyColumn().process_bind_param( '2.30', '' ) + 230 + >>> MoneyColumn().process_result_value( 230, '' ) + Decimal('2.3') + """ + + impl = types.Integer + + def process_bind_param(self, value, dialect): + if value is None or isinstance(value, int): + return value + elif isinstance(value, basestring) or isinstance(value, Decimal): + return int(Decimal(value)*100) + else: + raise Exception, "Incorrect amount:" + repr(value) + + def process_result_value(self, value, dialect): + if value is None: + return None + return Decimal(value)/100 + + +class BigIntColumn(types.TypeDecorator, types.Integer): + """Representing db-independent big integer """ + # Integer inheritance required for auto_increment flag + + impl = types.Integer + + def load_dialect_impl(self, dialect): + from sqlalchemy import databases + if dialect.name == 'mysql': + return databases.mysql.MSBigInteger() + elif dialect.name == 'postgres': + return databases.mysql.PGBigInteger() + return types.Integer() + + +class MappedBase(object): + """Provide dummy contrcutor""" + + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + def get_columns_names(self): + return [i.name for i in self._sa_class_manager.mapper.c] + +def get_or_create(klass, session, **kwargs): + """ + Looks up an object with the given kwargs, creating one if necessary. + Returns a tuple of (object, created), where created is a boolean + specifying whether an object was created. + """ + assert kwargs, \ + 'get_or_create() must be passed at least one keyword argument' + try: + return session.query(klass).filter_by(**kwargs).one(), False + except NoResultFound: + try: + obj = klass(**kwargs) + session.add(obj) + session.flush() + return obj, True + except IntegrityError: + return session.query(klass).filter_by(**kwargs).one(), False + diff --git a/pyfpdb/AlchemyMappings.py b/pyfpdb/AlchemyMappings.py new file mode 100644 index 00000000..c2e088a9 --- /dev/null +++ b/pyfpdb/AlchemyMappings.py @@ -0,0 +1,464 @@ +# -*- coding: utf-8 -*- +"""@package AlchemyMappings +This package contains all classes to be mapped and mappers themselves +""" + +import logging +import re +from decimal import Decimal +from sqlalchemy.orm import mapper, relation, reconstructor +from sqlalchemy.sql import select +from collections import defaultdict + + +from AlchemyTables import * +from AlchemyFacilities import get_or_create, MappedBase +from DerivedStats import DerivedStats +from Exceptions import IncompleteHandError, FpdbError + + +class Player(MappedBase): + """Class reflecting Players db table""" + + @staticmethod + def get_or_create(session, siteId, name): + return get_or_create(Player, session, siteId=siteId, name=name)[0] + + def __str__(self): + return '' % (self.name, self.site and self.site.name) + + +class Gametype(MappedBase): + """Class reflecting Gametypes db table""" + + @staticmethod + def get_or_create(session, siteId, gametype): + map = zip( + ['type', 'base', 'category', 'limitType', 'smallBlind', 'bigBlind', 'smallBet', 'bigBet'], + ['type', 'base', 'category', 'limitType', 'sb', 'bb', 'dummy', 'dummy', ]) + gametype = dict([(new, gametype.get(old)) for new, old in map ]) + + hilo = "h" + if gametype['category'] in ('studhilo', 'omahahilo'): + hilo = "s" + elif gametype['category'] in ('razz','27_3draw','badugi'): + hilo = "l" + gametype['hiLo'] = hilo + + for f in ['smallBlind', 'bigBlind', 'smallBet', 'bigBet']: + if gametype[f] is None: + gametype[f] = 0 + gametype[f] = int(Decimal(gametype[f])*100) + + gametype['siteId'] = siteId + return get_or_create(Gametype, session, **gametype)[0] + + +class HandActions(object): + """Class reflecting HandsActions db table""" + def initFromImportedHand(self, hand, actions): + self.hand = hand + self.actions = {} + for street, street_actions in actions.iteritems(): + self.actions[street] = [] + for v in street_actions: + hp = hand.handplayers_by_name[v[0]] + self.actions[street].append({'street': street, 'pid': hp.id, 'seat': hp.seatNo, 'action':v}) + + @property + def flat_actions(self): + actions = [] + for street in self.hand.allStreets: + actions += self.actions[street] + return actions + + + +class HandInternal(DerivedStats): + """Class reflecting Hands db table""" + + def parseImportedHandStep1(self, hand): + """Extracts values to insert into from hand returned by HHC. No db is needed he""" + hand.players = hand.getAlivePlayers() + + # also save some data for step2. Those fields aren't in Hands table + self.siteId = hand.siteId + self.gametype_dict = hand.gametype + + self.attachHandPlayers(hand) + self.attachActions(hand) + + self.assembleHands(hand) + self.assembleHandsPlayers(hand) + + def parseImportedHandStep2(self, session): + """Fetching ids for gametypes and players""" + gametype = Gametype.get_or_create(session, self.siteId, self.gametype_dict) + self.gametypeId = gametype.id + for hp in self.handPlayers: + hp.playerId = Player.get_or_create(session, self.siteId, hp.name).id + + def getPlayerByName(self, name): + if not hasattr(self, 'handplayers_by_name'): + self.handplayers_by_name = {} + for hp in self.handPlayers: + pname = getattr(hp, 'name', None) or hp.player.name + self.handplayers_by_name[pname] = hp + return self.handplayers_by_name[name] + + def attachHandPlayers(self, hand): + """Fill HandInternal.handPlayers list. Create self.handplayers_by_name""" + hand.noSb = getattr(hand, 'noSb', None) + if hand.noSb is None and self.gametype_dict['base']=='hold': + saw_sb = False + for action in hand.actions[hand.actionStreets[0]]: # blindsantes + if action[1] == 'posts' and action[2] == 'small blind' and action[0] is not None: + saw_sb = True + hand.noSb = saw_sb + + self.handplayers_by_name = {} + for seat, name, chips in hand.players: + p = HandPlayer(hand = self, imported_hand=hand, seatNo=seat, + name=name, startCash=chips) + self.handplayers_by_name[name] = p + + def attachActions(self, hand): + """Create HandActions object""" + a = HandActions() + a.initFromImportedHand(self, hand.actions) + + def parseImportedTournament(self, hand, session): + """Fetching tourney, its type and players + + Must be called after Step2 + """ + if self.gametype_dict['type'] != 'tour': return + + # check for consistense + for i in ('buyin', 'tourNo'): + if not hasattr(hand, i): + raise IncompleteHandError( + "Field '%s' required for tournaments" % i, self.id, hand ) + + # repair old-style buyin value + m = re.match('\$(\d+)\+\$(\d+)', hand.buyin) + if m is not None: + hand.buyin, self.fee = m.groups() + + # fetch tourney type + tour_type_hand2db = { + 'buyin': 'buyin', + 'fee': 'fee', + 'speed': 'speed', + 'maxSeats': 'maxseats', + 'knockout': 'isKO', + 'rebuyOrAddon': 'isRebuy', + 'headsUp': 'isHU', + 'shootout': 'isShootout', + 'matrix': 'isMatrix', + 'sng': 'isSNG', + } + tour_type_index = dict([ + ( i_db, getattr(hand, i_hand, None) ) + for i_db, i_hand in tour_type_hand2db.iteritems() + ]) + tour_type_index['siteId'] = self.siteId + tour_type = TourneyType.get_or_create(session, **tour_type_index) + + # fetch and update tourney + tour = Tourney.get_or_create(session, hand.tourNo, tour_type.id) + cols = tour.get_columns_names() + for col in cols: + hand_val = getattr(hand, col, None) + if col in ('id', 'tourneyTypeId', 'comment', 'commentTs') or hand_val is None: + continue + db_val = getattr(tour, col, None) + if db_val is None: + setattr(tour, col, hand_val) + elif col == 'koBounty': + setattr(tour, col, max(db_val, hand_val)) + elif col == 'tourStartTime' and hand.handStart: + setattr(tour, col, min(db_val, hand.handStart)) + + if tour.entries is None and tour_type.sng: + tour.entries = tour_type.maxSeats + + # fetch and update tourney players + for hp in self.handPlayers: + tp = TourneyPlayer.get_or_create(session, tour.id, hp.playerId) + # FIXME: other TourneysPlayers should be added here + + session.flush() + + def isDuplicate(self, session): + """Checks if current hand already exists in db + + siteHandNo ans gameTypeId have to be setted + """ + return session.query(HandInternal).filter_by( + siteHandNo=self.siteHandNo, gametypeId=self.gametypeId).count()!=0 + + def __str__(self): + s = list() + for i in self._sa_class_manager.mapper.c: + s.append('%25s %s' % (i, getattr(self, i.name))) + + s+=['', ''] + for i,p in enumerate(self.handPlayers): + s.append('%d. %s' % (i, p.name or '???')) + s.append(str(p)) + return '\n'.join(s) + + @property + def boardcards(self): + cards = [] + for i in range(5): + cards.append(getattr(self, 'boardcard%d' % (i+1), None)) + return filter(bool, cards) + + @property + def HandClass(self): + """Return HoldemOmahaHand or something like this""" + import Hand + if self.gametype.base == 'hold': + return Hand.HoldemOmahaHand + elif self.gametype.base == 'draw': + return Hand.DrawHand + elif self.gametype.base == 'stud': + return Hand.StudHand + raise Exception("Unknow gametype.base: '%s'" % self.gametype.base) + + @property + def allStreets(self): + return self.HandClass.allStreets + + @property + def actionStreets(self): + return self.HandClass.actionStreets + + + +class HandPlayer(MappedBase): + """Class reflecting HandsPlayers db table""" + def __init__(self, **kwargs): + if 'imported_hand' in kwargs and 'seatNo' in kwargs: + imported_hand = kwargs.pop('imported_hand') + self.position = self.getPosition(imported_hand, kwargs['seatNo']) + super(HandPlayer, self).__init__(**kwargs) + + @reconstructor + def init_on_load(self): + self.name = self.player.name + + @staticmethod + def getPosition(hand, seat): + """Returns position value like 'B', 'S', '0', '1', ... + + >>> class A(object): pass + ... + >>> A.noSb = False + >>> A.maxseats = 6 + >>> A.buttonpos = 2 + >>> A.gametype = {'base': 'hold'} + >>> A.players = [(i, None, None) for i in (2, 4, 5, 6)] + >>> HandPlayer.getPosition(A, 6) # cut off + '1' + >>> HandPlayer.getPosition(A, 2) # button + '0' + >>> HandPlayer.getPosition(A, 4) # SB + 'S' + >>> HandPlayer.getPosition(A, 5) # BB + 'B' + >>> A.noSb = True + >>> HandPlayer.getPosition(A, 5) # MP3 + '2' + >>> HandPlayer.getPosition(A, 6) # cut off + '1' + >>> HandPlayer.getPosition(A, 2) # button + '0' + >>> HandPlayer.getPosition(A, 4) # BB + 'B' + """ + from itertools import chain + if hand.gametype['base'] == 'stud': + # FIXME: i've never played stud so plz check & del comment \\grindi + bringin = None + for action in chain(*[self.actions[street] for street in hand.allStreets]): + if action[1]=='bringin': + bringin = action[0] + break + if bringin is None: + raise Exception, "Cannot find bringin" + # name -> seat + bringin = int(filter(lambda p: p[1]==bringin, bringin)[0]) + seat = (int(seat) - int(bringin))%int(hand.maxseats) + return str(seat) + else: + seats_occupied = sorted([seat_ for seat_, name, chips in hand.players], key=int) + if hand.buttonpos not in seats_occupied: + # i.e. something like + # Seat 3: PlayerX ($0), is sitting out + # The button is in seat #3 + hand.buttonpos = max(seats_occupied, + key = lambda s: int(s) + if int(s) <= int(hand.buttonpos) + else int(s) - int(hand.maxseats) + ) + seats_occupied = sorted(seats_occupied, + key = lambda seat_: ( + - seats_occupied.index(seat_) + + seats_occupied.index(hand.buttonpos) + + 2) % len(seats_occupied) + ) + # now (if SB presents) seats_occupied contains seats in order: BB, SB, BU, CO, MP3, ... + if hand.noSb: + # fix order in the case nosb + seats_occupied = seats_occupied[1:] + seats_occupied[0:1] + seats_occupied.insert(1, -1) + seat = seats_occupied.index(seat) + if seat == 0: + return 'B' + elif seat == 1: + return 'S' + else: + return str(seat-2) + + @property + def cards(self): + cards = [] + for i in range(7): + cards.append(getattr(self, 'card%d' % (i+1), None)) + return filter(bool, cards) + + def __str__(self): + s = list() + for i in self._sa_class_manager.mapper.c: + s.append('%45s %s' % (i, getattr(self, i.name))) + return '\n'.join(s) + + +class Site(object): + """Class reflecting Players db table""" + INITIAL_DATA = [ + (1 , 'Full Tilt Poker','USD'), + (2 , 'PokerStars', 'USD'), + (3 , 'Everleaf', 'USD'), + (4 , 'Win2day', 'USD'), + (5 , 'OnGame', 'USD'), + (6 , 'UltimateBet', 'USD'), + (7 , 'Betfair', 'USD'), + (8 , 'Absolute', 'USD'), + (9 , 'PartyPoker', 'USD'), + (10, 'Partouche', 'EUR'), + ] + INITIAL_DATA_KEYS = ('id', 'name', 'currency') + + INITIAL_DATA_DICTS = [ dict(zip(INITIAL_DATA_KEYS, datum)) for datum in INITIAL_DATA ] + + @classmethod + def insert_initial(cls, connection): + connection.execute(sites_table.insert(), cls.INITIAL_DATA_DICTS) + + +class Tourney(MappedBase): + """Class reflecting Tourneys db table""" + + @classmethod + def get_or_create(cls, session, siteTourneyNo, tourneyTypeId): + """Fetch tourney by index or creates one if none. """ + return get_or_create(cls, session, siteTourneyNo=siteTourneyNo, + tourneyTypeId=tourneyTypeId)[0] + + + +class TourneyType(MappedBase): + """Class reflecting TourneysType db table""" + + @classmethod + def get_or_create(cls, session, **kwargs): + """Fetch tourney type by index or creates one if none + + Required kwargs: + buyin fee speed maxSeats knockout + rebuyOrAddon headsUp shootout matrix sng + """ + return get_or_create(cls, session, **kwargs)[0] + + +class TourneyPlayer(MappedBase): + """Class reflecting TourneysPlayers db table""" + + @classmethod + def get_or_create(cls, session, tourneyId, playerId): + """Fetch tourney player by index or creates one if none """ + return get_or_create(cls, session, tourneyId=tourneyId, playerId=playerId) + + +class Version(object): + """Provides read/write access for version var""" + CURRENT_VERSION = 120 # db version for current release + # 119 - first alchemy version + # 120 - add m_factor + + conn = None + ver = None + def __init__(self, connection=None): + if self.__class__.conn is None: + self.__class__.conn = connection + + @classmethod + def is_wrong(cls): + return cls.get() != cls.CURRENT_VERSION + + @classmethod + def get(cls): + if cls.ver is None: + try: + cls.ver = cls.conn.execute(select(['version'], settings_table)).fetchone()[0] + except: + return None + return cls.ver + + @classmethod + def set(cls, value): + if cls.conn.execute(settings_table.select()).rowcount==0: + cls.conn.execute(settings_table.insert(), version=value) + else: + cls.conn.execute(settings_table.update().values(version=value)) + cls.ver = value + + @classmethod + def set_initial(cls): + cls.set(cls.CURRENT_VERSION) + + +mapper (Gametype, gametypes_table, properties={ + 'hands': relation(HandInternal, backref='gametype'), +}) +mapper (Player, players_table, properties={ + 'playerHands': relation(HandPlayer, backref='player'), + 'playerTourney': relation(TourneyPlayer, backref='player'), +}) +mapper (Site, sites_table, properties={ + 'gametypes': relation(Gametype, backref = 'site'), + 'players': relation(Player, backref = 'site'), + 'tourneyTypes': relation(TourneyType, backref = 'site'), +}) +mapper (HandActions, hands_actions_table, properties={}) +mapper (HandInternal, hands_table, properties={ + 'handPlayers': relation(HandPlayer, backref='hand'), + 'actions_all': relation(HandActions, backref='hand', uselist=False), +}) +mapper (HandPlayer, hands_players_table, properties={}) + +mapper (Tourney, tourneys_table) +mapper (TourneyType, tourney_types_table, properties={ + 'tourneys': relation(Tourney, backref='type'), +}) +mapper (TourneyPlayer, tourneys_players_table) + +class LambdaKeyDict(defaultdict): + """Operates like defaultdict but passes key argument to the factory function""" + def __missing__(key): + return self.default_factory(key) + diff --git a/pyfpdb/AlchemyTables.py b/pyfpdb/AlchemyTables.py new file mode 100644 index 00000000..3165a480 --- /dev/null +++ b/pyfpdb/AlchemyTables.py @@ -0,0 +1,438 @@ +# -*- coding: utf-8 -*- +"""@package AlchemyTables +Contains all sqlalchemy tables +""" + +from sqlalchemy import Table, Float, Column, Integer, String, MetaData, \ + ForeignKey, Boolean, SmallInteger, DateTime, Text, Index, CHAR, \ + PickleType, Unicode + +from AlchemyFacilities import CardColumn, MoneyColumn, BigIntColumn + + +metadata = MetaData() + + +autorates_table = Table('Autorates', metadata, + Column('id', Integer, primary_key=True, nullable=False), + Column('playerId', Integer, ForeignKey("Players.id"), nullable=False), + Column('gametypeId', SmallInteger, ForeignKey("Gametypes.id"), nullable=False), + Column('description', String(50), nullable=False), + Column('shortDesc', CHAR(8), nullable=False), + Column('ratingTime', DateTime, nullable=False), + Column('handCount', Integer, nullable=False), + mysql_charset='utf8', + mysql_engine='InnoDB', +) + + +gametypes_table = Table('Gametypes', metadata, + Column('id', SmallInteger, primary_key=True), + Column('siteId', SmallInteger, ForeignKey("Sites.id"), nullable=False), # SMALLINT + Column('type', String(4), nullable=False), # char(4) NOT NULL + Column('base', String(4), nullable=False), # char(4) NOT NULL + Column('category', String(9), nullable=False), # varchar(9) NOT NULL + Column('limitType', CHAR(2), nullable=False), # char(2) NOT NULL + Column('hiLo', CHAR(1), nullable=False), # char(1) NOT NULL + Column('smallBlind', Integer(3)), # int + Column('bigBlind', Integer(3)), # int + Column('smallBet', Integer(3), nullable=False), # int NOT NULL + Column('bigBet', Integer(3), nullable=False), # int NOT NULL + mysql_charset='utf8', + mysql_engine='InnoDB', +) + + +hands_table = Table('Hands', metadata, + Column('id', BigIntColumn, primary_key=True), + Column('tableName', String(30), nullable=False), + Column('siteHandNo', BigIntColumn, nullable=False), + Column('gametypeId', SmallInteger, ForeignKey('Gametypes.id'), nullable=False), + Column('handStart', DateTime, nullable=False), + Column('importTime', DateTime, nullable=False), + Column('seats', SmallInteger, nullable=False), + Column('maxSeats', SmallInteger, nullable=False), + + Column('boardcard1', CardColumn), + Column('boardcard2', CardColumn), + Column('boardcard3', CardColumn), + Column('boardcard4', CardColumn), + Column('boardcard5', CardColumn), + Column('texture', SmallInteger), + Column('playersVpi', SmallInteger, nullable=False), + Column('playersAtStreet1', SmallInteger, nullable=False, default=0), + Column('playersAtStreet2', SmallInteger, nullable=False, default=0), + Column('playersAtStreet3', SmallInteger, nullable=False, default=0), + Column('playersAtStreet4', SmallInteger, nullable=False, default=0), + Column('playersAtShowdown',SmallInteger, nullable=False), + Column('street0Raises', SmallInteger, nullable=False), + Column('street1Raises', SmallInteger, nullable=False), + Column('street2Raises', SmallInteger, nullable=False), + Column('street3Raises', SmallInteger, nullable=False), + Column('street4Raises', SmallInteger, nullable=False), + Column('street1Pot', MoneyColumn), + Column('street2Pot', MoneyColumn), + Column('street3Pot', MoneyColumn), + Column('street4Pot', MoneyColumn), + Column('showdownPot', MoneyColumn), + Column('comment', Text), + Column('commentTs', DateTime), + mysql_charset='utf8', + mysql_engine='InnoDB', +) +Index('siteHandNo', hands_table.c.siteHandNo, hands_table.c.gametypeId, unique=True) + + +hands_actions_table = Table('HandsActions', metadata, + Column('id', BigIntColumn, primary_key=True, nullable=False), + Column('handId', BigIntColumn, ForeignKey("Hands.id"), nullable=False), + Column('actions', PickleType, nullable=False), + mysql_charset='utf8', + mysql_engine='InnoDB', +) + + +hands_players_table = Table('HandsPlayers', metadata, + Column('id', BigIntColumn, primary_key=True), + Column('handId', BigIntColumn, ForeignKey("Hands.id"), nullable=False), + Column('playerId', Integer, ForeignKey("Players.id"), nullable=False), + Column('startCash', MoneyColumn), + Column('position', CHAR(1)), #CHAR(1) + Column('seatNo', SmallInteger, nullable=False), #SMALLINT NOT NULL + + Column('card1', CardColumn), #smallint NOT NULL, + Column('card2', CardColumn), #smallint NOT NULL + Column('card3', CardColumn), #smallint + Column('card4', CardColumn), #smallint + Column('card5', CardColumn), #smallint + Column('card6', CardColumn), #smallint + Column('card7', CardColumn), #smallint + Column('startCards', SmallInteger), #smallint + + Column('m_factor', Integer), # null for ring games + Column('ante', MoneyColumn), #INT + Column('winnings', MoneyColumn, nullable=False, default=0), #int NOT NULL + Column('rake', MoneyColumn, nullable=False, default=0), #int NOT NULL + Column('totalProfit', MoneyColumn), #INT + Column('comment', Text), #text + Column('commentTs', DateTime), #DATETIME + Column('tourneysPlayersId', BigIntColumn, ForeignKey("TourneysPlayers.id"),), #BIGINT UNSIGNED + Column('tourneyTypeId', Integer, ForeignKey("TourneyTypes.id"),), #SMALLINT UNSIGNED + + Column('wonWhenSeenStreet1',Float), #FLOAT + Column('wonWhenSeenStreet2',Float), #FLOAT + Column('wonWhenSeenStreet3',Float), #FLOAT + Column('wonWhenSeenStreet4',Float), #FLOAT + Column('wonAtSD', Float), #FLOAT + + Column('street0VPI', Boolean), #BOOLEAN + Column('street0Aggr', Boolean), #BOOLEAN + Column('street0_3BChance', Boolean), #BOOLEAN + Column('street0_3BDone', Boolean), #BOOLEAN + Column('street0_4BChance', Boolean), #BOOLEAN + Column('street0_4BDone', Boolean), #BOOLEAN + Column('other3BStreet0', Boolean), #BOOLEAN + Column('other4BStreet0', Boolean), #BOOLEAN + + Column('street1Seen', Boolean), #BOOLEAN + Column('street2Seen', Boolean), #BOOLEAN + Column('street3Seen', Boolean), #BOOLEAN + Column('street4Seen', Boolean), #BOOLEAN + Column('sawShowdown', Boolean), #BOOLEAN + + Column('street1Aggr', Boolean), #BOOLEAN + Column('street2Aggr', Boolean), #BOOLEAN + Column('street3Aggr', Boolean), #BOOLEAN + Column('street4Aggr', Boolean), #BOOLEAN + + Column('otherRaisedStreet0',Boolean), #BOOLEAN + Column('otherRaisedStreet1',Boolean), #BOOLEAN + Column('otherRaisedStreet2',Boolean), #BOOLEAN + Column('otherRaisedStreet3',Boolean), #BOOLEAN + Column('otherRaisedStreet4',Boolean), #BOOLEAN + Column('foldToOtherRaisedStreet0', Boolean), #BOOLEAN + Column('foldToOtherRaisedStreet1', Boolean), #BOOLEAN + Column('foldToOtherRaisedStreet2', Boolean), #BOOLEAN + Column('foldToOtherRaisedStreet3', Boolean), #BOOLEAN + Column('foldToOtherRaisedStreet4', Boolean), #BOOLEAN + + Column('stealAttemptChance', Boolean), #BOOLEAN + Column('stealAttempted', Boolean), #BOOLEAN + Column('foldBbToStealChance', Boolean), #BOOLEAN + Column('foldedBbToSteal', Boolean), #BOOLEAN + Column('foldSbToStealChance', Boolean), #BOOLEAN + Column('foldedSbToSteal', Boolean), #BOOLEAN + + Column('street1CBChance', Boolean), #BOOLEAN + Column('street1CBDone', Boolean), #BOOLEAN + Column('street2CBChance', Boolean), #BOOLEAN + Column('street2CBDone', Boolean), #BOOLEAN + Column('street3CBChance', Boolean), #BOOLEAN + Column('street3CBDone', Boolean), #BOOLEAN + Column('street4CBChance', Boolean), #BOOLEAN + Column('street4CBDone', Boolean), #BOOLEAN + + Column('foldToStreet1CBChance', Boolean), #BOOLEAN + Column('foldToStreet1CBDone', Boolean), #BOOLEAN + Column('foldToStreet2CBChance', Boolean), #BOOLEAN + Column('foldToStreet2CBDone', Boolean), #BOOLEAN + Column('foldToStreet3CBChance', Boolean), #BOOLEAN + Column('foldToStreet3CBDone', Boolean), #BOOLEAN + Column('foldToStreet4CBChance', Boolean), #BOOLEAN + Column('foldToStreet4CBDone', Boolean), #BOOLEAN + + Column('street1CheckCallRaiseChance',Boolean), #BOOLEAN + Column('street1CheckCallRaiseDone', Boolean), #BOOLEAN + Column('street2CheckCallRaiseChance',Boolean), #BOOLEAN + Column('street2CheckCallRaiseDone', Boolean), #BOOLEAN + Column('street3CheckCallRaiseChance',Boolean), #BOOLEAN + Column('street3CheckCallRaiseDone', Boolean), #BOOLEAN + Column('street4CheckCallRaiseChance',Boolean), #BOOLEAN + Column('street4CheckCallRaiseDone', Boolean), #BOOLEAN + + Column('street0Calls', SmallInteger), #TINYINT + Column('street1Calls', SmallInteger), #TINYINT + Column('street2Calls', SmallInteger), #TINYINT + Column('street3Calls', SmallInteger), #TINYINT + Column('street4Calls', SmallInteger), #TINYINT + Column('street0Bets', SmallInteger), #TINYINT + Column('street1Bets', SmallInteger), #TINYINT + Column('street2Bets', SmallInteger), #TINYINT + Column('street3Bets', SmallInteger), #TINYINT + Column('street4Bets', SmallInteger), #TINYINT + Column('street0Raises', SmallInteger), #TINYINT + Column('street1Raises', SmallInteger), #TINYINT + Column('street2Raises', SmallInteger), #TINYINT + Column('street3Raises', SmallInteger), #TINYINT + Column('street4Raises', SmallInteger), #TINYINT + + Column('actionString', String(15)), #VARCHAR(15) + mysql_charset='utf8', + mysql_engine='InnoDB', +) + + +hud_cache_table = Table('HudCache', metadata, + Column('id', BigIntColumn, primary_key=True), + Column('gametypeId', SmallInteger, ForeignKey("Gametypes.id"), nullable=False), # SMALLINT + Column('playerId', Integer, ForeignKey("Players.id"), nullable=False), # SMALLINT + Column('activeSeats', SmallInteger, nullable=False), # SMALLINT NOT NULL + Column('position', CHAR(1)), # CHAR(1) + Column('tourneyTypeId', Integer, ForeignKey("TourneyTypes.id") ), # SMALLINT + Column('styleKey', CHAR(7), nullable=False), # CHAR(7) NOT NULL + Column('m_factor', Integer), + Column('HDs', Integer, nullable=False), # INT NOT NULL + + Column('wonWhenSeenStreet1', Float), # FLOAT + Column('wonWhenSeenStreet2', Float), # FLOAT + Column('wonWhenSeenStreet3', Float), # FLOAT + Column('wonWhenSeenStreet4', Float), # FLOAT + Column('wonAtSD', Float), # FLOAT + + Column('street0VPI', Integer), # INT + Column('street0Aggr', Integer), # INT + Column('street0_3BChance', Integer), # INT + Column('street0_3BDone', Integer), # INT + Column('street0_4BChance', Integer), # INT + Column('street0_4BDone', Integer), # INT + Column('other3BStreet0', Integer), # INT + Column('other4BStreet0', Integer), # INT + + Column('street1Seen', Integer), # INT + Column('street2Seen', Integer), # INT + Column('street3Seen', Integer), # INT + Column('street4Seen', Integer), # INT + Column('sawShowdown', Integer), # INT + + Column('street1Aggr', Integer), # INT + Column('street2Aggr', Integer), # INT + Column('street3Aggr', Integer), # INT + Column('street4Aggr', Integer), # INT + + Column('otherRaisedStreet0', Integer), # INT + Column('otherRaisedStreet1', Integer), # INT + Column('otherRaisedStreet2', Integer), # INT + Column('otherRaisedStreet3', Integer), # INT + Column('otherRaisedStreet4', Integer), # INT + Column('foldToOtherRaisedStreet0', Integer), # INT + Column('foldToOtherRaisedStreet1', Integer), # INT + Column('foldToOtherRaisedStreet2', Integer), # INT + Column('foldToOtherRaisedStreet3', Integer), # INT + Column('foldToOtherRaisedStreet4', Integer), # INT + + Column('stealAttemptChance', Integer), # INT + Column('stealAttempted', Integer), # INT + Column('foldBbToStealChance', Integer), # INT + Column('foldedBbToSteal', Integer), # INT + Column('foldSbToStealChance', Integer), # INT + Column('foldedSbToSteal', Integer), # INT + + Column('street1CBChance', Integer), # INT + Column('street1CBDone', Integer), # INT + Column('street2CBChance', Integer), # INT + Column('street2CBDone', Integer), # INT + Column('street3CBChance', Integer), # INT + Column('street3CBDone', Integer), # INT + Column('street4CBChance', Integer), # INT + Column('street4CBDone', Integer), # INT + + Column('foldToStreet1CBChance', Integer), # INT + Column('foldToStreet1CBDone', Integer), # INT + Column('foldToStreet2CBChance', Integer), # INT + Column('foldToStreet2CBDone', Integer), # INT + Column('foldToStreet3CBChance', Integer), # INT + Column('foldToStreet3CBDone', Integer), # INT + Column('foldToStreet4CBChance', Integer), # INT + Column('foldToStreet4CBDone', Integer), # INT + + Column('totalProfit', Integer), # INT + + Column('street1CheckCallRaiseChance', Integer), # INT + Column('street1CheckCallRaiseDone', Integer), # INT + Column('street2CheckCallRaiseChance', Integer), # INT + Column('street2CheckCallRaiseDone', Integer), # INT + Column('street3CheckCallRaiseChance', Integer), # INT + Column('street3CheckCallRaiseDone', Integer), # INT + Column('street4CheckCallRaiseChance', Integer), # INT + Column('street4CheckCallRaiseDone', Integer), # INT + + Column('street0Calls', Integer), # INT + Column('street1Calls', Integer), # INT + Column('street2Calls', Integer), # INT + Column('street3Calls', Integer), # INT + Column('street4Calls', Integer), # INT + Column('street0Bets', Integer), # INT + Column('street1Bets', Integer), # INT + Column('street2Bets', Integer), # INT + Column('street3Bets', Integer), # INT + Column('street4Bets', Integer), # INT + Column('street0Raises', Integer), # INT + Column('street1Raises', Integer), # INT + Column('street2Raises', Integer), # INT + Column('street3Raises', Integer), # INT + Column('street4Raises', Integer), # INT + mysql_charset='utf8', + mysql_engine='InnoDB', +) + + +players_table = Table('Players', metadata, + Column('id', Integer, primary_key=True), + Column('name', Unicode(32), nullable=False), # VARCHAR(32) CHARACTER SET utf8 NOT NULL + Column('siteId', SmallInteger, ForeignKey("Sites.id"), nullable=False), # SMALLINT + Column('comment', Text), # text + Column('commentTs', DateTime), # DATETIME + mysql_charset='utf8', + mysql_engine='InnoDB', +) +Index('name', players_table.c.name, players_table.c.siteId, unique=True) + + +settings_table = Table('Settings', metadata, + Column('version', SmallInteger, nullable=False), + mysql_charset='utf8', + mysql_engine='InnoDB', +) + + +sites_table = Table('Sites', metadata, + Column('id', SmallInteger, primary_key=True), + Column('name', String(32), nullable=False), # varchar(32) NOT NULL + Column('currency', String(3), nullable=False), # char(3) NOT NULL + mysql_charset='utf8', + mysql_engine='InnoDB', +) + + +tourneys_table = Table('Tourneys', metadata, + Column('id', Integer, primary_key=True), + Column('tourneyTypeId', Integer, ForeignKey("TourneyTypes.id"), nullable=False, default=1), + Column('siteTourneyNo', BigIntColumn, nullable=False), # BIGINT NOT NULL + Column('entries', Integer), # INT NOT NULL + Column('prizepool', Integer), # INT NOT NULL + Column('tourStartTime', DateTime), # DATETIME NOT NULL + Column('tourEndTime', DateTime), # DATETIME + Column('buyinChips', Integer), # INT + Column('tourneyName', String(40)), # varchar(40) + # Mask use : 1=Positionnal Winnings|2=Match1|4=Match2|...|pow(2,n)=Matchn + Column('matrixIdProcessed',SmallInteger, default=0), # TINYINT UNSIGNED DEFAULT 0 + Column('rebuyChips', Integer, default=0), # INT DEFAULT 0 + Column('addonChips', Integer, default=0), # INT DEFAULT 0 + Column('rebuyAmount', MoneyColumn, default=0), # INT DEFAULT 0 + Column('addonAmount', MoneyColumn, default=0), # INT DEFAULT 0 + Column('totalRebuys', Integer, default=0), # INT DEFAULT 0 + Column('totalAddons', Integer, default=0), # INT DEFAULT 0 + Column('koBounty', Integer, default=0), # INT DEFAULT 0 + Column('comment', Text), # TEXT + Column('commentTs', DateTime), # DATETIME + mysql_charset='utf8', + mysql_engine='InnoDB', +) +Index('siteTourneyNo', tourneys_table.c.siteTourneyNo, tourneys_table.c.tourneyTypeId, unique=True) + + +tourney_types_table = Table('TourneyTypes', metadata, + Column('id', Integer, primary_key=True), + Column('siteId', SmallInteger, ForeignKey("Sites.id"), nullable=False), + Column('buyin', Integer, nullable=False), # INT NOT NULL + Column('fee', Integer, nullable=False, default=0), # INT NOT NULL + Column('maxSeats', Boolean, nullable=False, default=-1), # INT NOT NULL DEFAULT -1 + Column('knockout', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False + Column('rebuyOrAddon', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False + Column('speed', String(10)), # varchar(10) + Column('headsUp', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False + Column('shootout', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False + Column('matrix', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False + Column('sng', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False + mysql_charset='utf8', + mysql_engine='InnoDB', +) +Index('tourneyTypes_all', + tourney_types_table.c.siteId, tourney_types_table.c.buyin, tourney_types_table.c.fee, + tourney_types_table.c.maxSeats, tourney_types_table.c.knockout, tourney_types_table.c.rebuyOrAddon, + tourney_types_table.c.speed, tourney_types_table.c.headsUp, tourney_types_table.c.shootout, + tourney_types_table.c.matrix, tourney_types_table.c.sng) + + +tourneys_players_table = Table('TourneysPlayers', metadata, + Column('id', BigIntColumn, primary_key=True), + Column('tourneyId', Integer, ForeignKey("Tourneys.id"), nullable=False), + Column('playerId', Integer, ForeignKey("Players.id"), nullable=False), + Column('payinAmount', Integer), # INT NOT NULL + Column('rank', Integer), # INT NOT NULL + Column('winnings', Integer), # INT NOT NULL + Column('nbRebuys', Integer, default=0), # INT DEFAULT 0 + Column('nbAddons', Integer, default=0), # INT DEFAULT 0 + Column('nbKO', Integer, default=0), # INT DEFAULT 0 + Column('comment', Text), # TEXT + Column('commentTs', DateTime), # DATETIME + mysql_charset='utf8', + mysql_engine='InnoDB', +) +Index('tourneyId', tourneys_players_table.c.tourneyId, tourneys_players_table.c.playerId, unique=True) + + +def sss(): + "Debug function. Returns (config, sql, db)" + + import Configuration, SQL, Database, os + class Dummy(object): + pass + self = Dummy() + self.config = Configuration.Config() + self.settings = {} + if (os.sep=="/"): + self.settings['os']="linuxmac" + else: + self.settings['os']="windows" + + self.settings.update(self.config.get_db_parameters()) + self.settings.update(self.config.get_tv_parameters()) + self.settings.update(self.config.get_import_parameters()) + self.settings.update(self.config.get_default_paths()) + + self.sql = SQL.Sql( db_server = self.settings['db-server']) + self.db = Database.Database(self.config, sql = self.sql) + + return self.config, self.sql, self.db + diff --git a/pyfpdb/CarbonToFpdb.py b/pyfpdb/CarbonToFpdb.py index cc7afb81..0640d157 100644 --- a/pyfpdb/CarbonToFpdb.py +++ b/pyfpdb/CarbonToFpdb.py @@ -1,6 +1,7 @@ #!/usr/bin/env python -# Copyright 2008, Carl Gherardi - +# -*- coding: utf-8 -*- +# +# Copyright 2010, Matthew Boss # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,93 +19,286 @@ ######################################################################## -# Standard Library modules -import Configuration -import traceback +# This code is based heavily on EverleafToFpdb.py, by Carl Gherardi +# +# OUTSTANDING MATTERS +# +# -- No siteID assigned +# -- No support for games other than NL hold 'em cash. Hand histories for other +# games required +# -- No support for limit hold 'em yet, though this would be easy to add +# -- No support for tournaments (see also the last item below) +# -- Assumes that the currency of ring games is USD +# -- Only works for 'gametype="2"'. What is 'gametype'? +# -- Only accepts 'realmoney="true"' +# -- A hand's time-stamp does not record seconds past the minute (a +# limitation of the history format) +# -- No support for a bring-in or for antes (is the latter in fact unnecessary +# for hold 'em on Carbon?) +# -- hand.maxseats can only be guessed at +# -- The last hand in a history file will often be incomplete and is therefore +# rejected +# -- Is behaviour currently correct when someone shows an uncalled hand? +# -- Information may be lost when the hand ID is converted from the native form +# xxxxxxxx-yyy(y*) to xxxxxxxxyyy(y*) (in principle this should be stored as +# a string, but the database does not support this). Is there a possibility +# of collision between hand IDs that ought to be distinct? +# -- Cannot parse tables that run it twice (nor is this likely ever to be +# possible) +# -- Cannot parse hands in which someone is all in in one of the blinds. Until +# this is corrected tournaments will be unparseable + import sys -import re -import xml.dom.minidom -from xml.dom.minidom import Node -from HandHistoryConverter import HandHistoryConverter +import logging +from HandHistoryConverter import * +from decimal import Decimal -# Carbon format looks like: +class Carbon(HandHistoryConverter): -# 1) -# 2) -# 3) -# -# ... -# 4) -# -# -# 5) -# -# 6) -# -# .... -# + sitename = "Carbon" + filetype = "text" + codepage = "cp1252" + siteID = 11 -# The full sequence for a NHLE cash game is: -# BLINDS, PREFLOP, POSTFLOP, POSTTURN, POSTRIVER, SHOWDOWN, END_OF_GAME -# This sequence can be terminated after BLINDS at any time by END_OF_FOLDED_GAME + # Static regexes + re_SplitHands = re.compile(r'\n+(?=)') + re_GameInfo = re.compile(r'', re.MULTILINE) + re_HandInfo = re.compile(r'[0-9]+)">') + re_PlayerInfo = re.compile(r'', re.MULTILINE) + re_Board = re.compile(r'', re.MULTILINE) + re_PostBB = re.compile(r'', re.MULTILINE) + re_PostBoth = re.compile(r'', re.MULTILINE) + #re_Antes = ??? + #re_BringIn = ??? + re_HeroCards = re.compile(r'', re.MULTILINE) + re_ShowdownAction = re.compile(r'', re.MULTILINE) + re_CollectPot = re.compile(r'', re.MULTILINE) + re_ShownCards = re.compile(r'', re.MULTILINE) -class CarbonPoker(HandHistoryConverter): - def __init__(self, config, filename): - print "Initialising Carbon Poker converter class" - HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init - self.setFileType("xml") - self.siteId = 4 # Needs to match id entry in Sites database + def compilePlayerRegexs(self, hand): + pass - def readSupportedGames(self): - pass - def determineGameType(self): - gametype = [] - desc_node = self.doc.getElementsByTagName("description") - #TODO: no examples of non ring type yet - gametype = gametype + ["ring"] - type = desc_node[0].getAttribute("type") - if(type == "Holdem"): - gametype = gametype + ["hold"] - else: - print "Carbon: Unknown gametype: '%s'" % (type) + def playerNameFromSeatNo(self, seatNo, hand): + # This special function is required because Carbon Poker records + # actions by seat number, not by the player's name + for p in hand.players: + if p[0] == int(seatNo): + return p[1] - stakes = desc_node[0].getAttribute("stakes") - #TODO: no examples of anything except nlhe - m = re.match('(?PNo Limit)\s\(\$?(?P[.0-9]+)/\$?(?P[.0-9]+)\)', stakes) + def readSupportedGames(self): + return [["ring", "hold", "nl"], + ["tour", "hold", "nl"]] - if(m.group('LIMIT') == "No Limit"): - gametype = gametype + ["nl"] + def determineGameType(self, handText): + """return dict with keys/values: + 'type' in ('ring', 'tour') + 'limitType' in ('nl', 'cn', 'pl', 'cp', 'fl') + 'base' in ('hold', 'stud', 'draw') + 'category' in ('holdem', 'omahahi', omahahilo', 'razz', 'studhi', 'studhilo', 'fivedraw', '27_1draw', '27_3draw', 'badugi') + 'hilo' in ('h','l','s') + 'smallBlind' int? + 'bigBlind' int? + 'smallBet' + 'bigBet' + 'currency' in ('USD', 'EUR', 'T$', ) +or None if we fail to get the info """ - gametype = gametype + [self.float2int(m.group('SB'))] - gametype = gametype + [self.float2int(m.group('BB'))] + m = self.re_GameInfo.search(handText) + if not m: + # Information about the game type appears only at the beginning of + # a hand history file; hence it is not supplied with the second + # and subsequent hands. In these cases we use the value previously + # stored. + return self.info + self.info = {} + mg = m.groupdict() - return gametype + limits = { 'No Limit':'nl', 'Limit':'fl' } + games = { # base, category + 'Holdem' : ('hold','holdem'), + 'Holdem Tournament' : ('hold','holdem') } - def readPlayerStacks(self): - pass - def readBlinds(self): - pass - def readAction(self): - pass + if 'LIMIT' in mg: + self.info['limitType'] = limits[mg['LIMIT']] + if 'GAME' in mg: + (self.info['base'], self.info['category']) = games[mg['GAME']] + if 'SB' in mg: + self.info['sb'] = mg['SB'] + if 'BB' in mg: + self.info['bb'] = mg['BB'] + if mg['GAME'] == 'Holdem Tournament': + self.info['type'] = 'tour' + self.info['currency'] = 'T$' + else: + self.info['type'] = 'ring' + self.info['currency'] = 'USD' - # Override read function as xml.minidom barfs on the Carbon layout - # This is pretty dodgy - def readFile(self, filename): - print "Carbon: Reading file: '%s'" %(filename) - infile=open(filename, "rU") - self.obs = infile.read() - infile.close() - self.obs = "\n" + self.obs + "" - try: - doc = xml.dom.minidom.parseString(self.obs) - self.doc = doc - except: - traceback.print_exc(file=sys.stderr) + return self.info + + def readHandInfo(self, hand): + m = self.re_HandInfo.search(hand.handText) + if m is None: + logging.info("Didn't match re_HandInfo") + logging.info(hand.handText) + return None + logging.debug("HID %s-%s, Table %s" % (m.group('HID1'), + m.group('HID2'), m.group('TABLE')[:-1])) + hand.handid = m.group('HID1') + m.group('HID2') + hand.tablename = m.group('TABLE')[:-1] + hand.maxseats = 2 # This value may be increased as necessary + hand.starttime = datetime.datetime.strptime(m.group('DATETIME')[:12], + '%Y%m%d%H%M') + # Check that the hand is complete up to the awarding of the pot; if + # not, the hand is unparseable + if self.re_EndOfHand.search(hand.handText) is None: + raise FpdbParseError(hid=m.group('HID1') + "-" + m.group('HID2')) + + def readPlayerStacks(self, hand): + m = self.re_PlayerInfo.finditer(hand.handText) + for a in m: + seatno = int(a.group('SEAT')) + # It may be necessary to adjust 'hand.maxseats', which is an + # educated guess, starting with 2 (indicating a heads-up table) and + # adjusted upwards in steps to 6, then 9, then 10. An adjustment is + # made whenever a player is discovered whose seat number is + # currently above the maximum allowable for the table. + if seatno >= hand.maxseats: + if seatno > 8: + hand.maxseats = 10 + elif seatno > 5: + hand.maxseats = 9 + else: + hand.maxseats = 6 + if a.group('DEALTIN') == "true": + hand.addPlayer(seatno, a.group('PNAME'), a.group('CASH')) + + def markStreets(self, hand): + #if hand.gametype['base'] == 'hold': + m = re.search(r'(?P.+(?=(?P.+(?=(?P.+(?=(?P.+))?', hand.handText, re.DOTALL) + hand.addStreets(m) + + def readCommunityCards(self, hand, street): + m = self.re_Board.search(hand.streets[street]) + if street == 'FLOP': + hand.setCommunityCards(street, m.group('CARDS').split(',')) + elif street in ('TURN','RIVER'): + hand.setCommunityCards(street, [m.group('CARDS').split(',')[-1]]) + + def readAntes(self, hand): + pass # ??? + + def readBringIn(self, hand): + pass # ??? + + def readBlinds(self, hand): + try: + m = self.re_PostSB.search(hand.handText) + hand.addBlind(self.playerNameFromSeatNo(m.group('PSEAT'), hand), + 'small blind', m.group('SB')) + except: # no small blind + hand.addBlind(None, None, None) + for a in self.re_PostBB.finditer(hand.handText): + hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), hand), + 'big blind', a.group('BB')) + for a in self.re_PostBoth.finditer(hand.handText): + bb = Decimal(self.info['bb']) + amount = Decimal(a.group('SBBB')) + if amount < bb: + hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), + hand), 'small blind', a.group('SBBB')) + elif amount == bb: + hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), + hand), 'big blind', a.group('SBBB')) + else: + hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), + hand), 'both', a.group('SBBB')) + + def readButton(self, hand): + hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON')) + + def readHeroCards(self, hand): + m = self.re_HeroCards.search(hand.handText) + if m: + hand.hero = self.playerNameFromSeatNo(m.group('PSEAT'), hand) + cards = m.group('CARDS').split(',') + hand.addHoleCards('PREFLOP', hand.hero, closed=cards, shown=False, + mucked=False, dealt=True) + + def readAction(self, hand, street): + logging.debug("readAction (%s)" % street) + m = self.re_Action.finditer(hand.streets[street]) + for action in m: + logging.debug("%s %s" % (action.group('ATYPE'), + action.groupdict())) + player = self.playerNameFromSeatNo(action.group('PSEAT'), hand) + if action.group('ATYPE') == 'RAISE': + hand.addCallandRaise(street, player, action.group('BET')) + elif action.group('ATYPE') == 'CALL': + hand.addCall(street, player, action.group('BET')) + elif action.group('ATYPE') == 'BET': + hand.addBet(street, player, action.group('BET')) + elif action.group('ATYPE') in ('FOLD', 'SIT_OUT'): + hand.addFold(street, player) + elif action.group('ATYPE') == 'CHECK': + hand.addCheck(street, player) + elif action.group('ATYPE') == 'ALL_IN': + hand.addAllIn(street, player, action.group('BET')) + else: + logging.debug("Unimplemented readAction: %s %s" + % (action.group('PSEAT'),action.group('ATYPE'),)) + + def readShowdownActions(self, hand): + for shows in self.re_ShowdownAction.finditer(hand.handText): + cards = shows.group('CARDS').split(',') + hand.addShownCards(cards, + self.playerNameFromSeatNo(shows.group('PSEAT'), + hand)) + + def readCollectPot(self, hand): + pots = [Decimal(0) for n in range(hand.maxseats)] + for m in self.re_CollectPot.finditer(hand.handText): + pots[int(m.group('PSEAT'))] += Decimal(m.group('POT')) + # Regarding the processing logic for "committed", see Pot.end() in + # Hand.py + committed = sorted([(v,k) for (k,v) in hand.pot.committed.items()]) + for p in range(hand.maxseats): + pname = self.playerNameFromSeatNo(p, hand) + if committed[-1][1] == pname: + pots[p] -= committed[-1][0] - committed[-2][0] + if pots[p] > 0: + hand.addCollectPot(player=pname, pot=pots[p]) + + def readShownCards(self, hand): + for m in self.re_ShownCards.finditer(hand.handText): + cards = m.group('CARDS').split(',') + hand.addShownCards(cards=cards, player=self.playerNameFromSeatNo(m.group('PSEAT'), hand)) if __name__ == "__main__": - c = Configuration.Config() - e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml") - e.processFile() - print str(e) + parser = OptionParser() + parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-") + parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-") + parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False) + parser.add_option("-q", "--quiet", action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO) + parser.add_option("-v", "--verbose", action="store_const", const=logging.INFO, dest="verbosity") + parser.add_option("--vv", action="store_const", const=logging.DEBUG, dest="verbosity") + + (options, args) = parser.parse_args() + + LOG_FILENAME = './logging.out' + logging.basicConfig(filename=LOG_FILENAME, level=options.verbosity) + + e = Carbon(in_path = options.ipath, + out_path = options.opath, + follow = options.follow, + autostart = True) diff --git a/pyfpdb/Card.py b/pyfpdb/Card.py index 8639fd35..46d56262 100755 --- a/pyfpdb/Card.py +++ b/pyfpdb/Card.py @@ -4,12 +4,12 @@ #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 . #In the "official" distribution you can find the license in @@ -39,24 +39,37 @@ def calcStartCards(hand, player): def twoStartCards(value1, suit1, value2, suit2): """ Function to convert 2 value,suit pairs into a Holdem style starting hand e.g. AQo - Hand is stored as an int 13 * x + y where (x+2) represents rank of 1st card and + Incoming values should be ints 2-14 (2,3,...K,A), suits are 'd'/'h'/'c'/'s' + Hand is stored as an int 13 * x + y + 1 where (x+2) represents rank of 1st card and (y+2) represents rank of second card (2=2 .. 14=Ace) - If x > y then pair is suited, if x < y then unsuited""" - if value1 < 2 or value2 < 2: + If x > y then pair is suited, if x < y then unsuited + Examples: + 0 Unknown / Illegal cards + 1 22 + 2 32o + 3 42o + ... + 14 32s + 15 33 + 16 42o + ... + 170 AA + """ + if value1 is None or value1 < 2 or value1 > 14 or value2 is None or value2 < 2 or value2 > 14: ret = 0 - if value1 == value2: # pairs - ret = (13 * (value2-2) + (value2-2) ) + elif value1 == value2: # pairs + ret = (13 * (value2-2) + (value2-2) ) + 1 elif suit1 == suit2: if value1 > value2: - ret = 13 * (value1-2) + (value2-2) + ret = 13 * (value1-2) + (value2-2) + 1 else: - ret = 13 * (value2-2) + (value1-2) + ret = 13 * (value2-2) + (value1-2) + 1 else: if value1 > value2: - ret = 13 * (value2-2) + (value1-2) + ret = 13 * (value2-2) + (value1-2) + 1 else: - ret = 13 * (value1-2) + (value2-2) - + ret = 13 * (value1-2) + (value2-2) + 1 + # print "twoStartCards(", value1, suit1, value2, suit2, ")=", ret return ret @@ -66,8 +79,8 @@ def twoStartCardString(card): ret = 'xx' if card > 0: s = ('2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A') - x = card / 13 - y = card - 13 * x + x = (card-1) / 13 + y = (card-1) - 13 * x if x == y: ret = s[x] + s[y] elif x > y: ret = s[x] + s[y] + 's' else: ret = s[y] + s[x] + 'o' @@ -95,7 +108,7 @@ def fourStartCards(value1, suit1, value2, suit2, value3, suit3, value4, suit4): # SSSS (K, J, 6, 3) # - 13C4 = 715 possibilities # SSSx (K, J, 6),(3) - # - 13C3 * 13 = 3718 possibilities + # - 13C3 * 13 = 3718 possibilities # SSxy (K, J),(6),(3) # - 13C2 * 13*13 = 13182 possibilities # SSHH (K, J),(6, 3) @@ -118,7 +131,7 @@ suitFromCardList = ['', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'J , '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', 'Ts', 'Js', 'Qs', 'Ks', 'As' ] def valueSuitFromCard(card): - """ Function to convert a card stored in the database (int 0-52) into value + """ Function to convert a card stored in the database (int 0-52) into value and suit like 9s, 4c etc """ global suitFromCardList if card < 0 or card > 52 or not card: @@ -127,10 +140,10 @@ def valueSuitFromCard(card): return suitFromCardList[card] encodeCardList = {'2h': 1, '3h': 2, '4h': 3, '5h': 4, '6h': 5, '7h': 6, '8h': 7, '9h': 8, 'Th': 9, 'Jh': 10, 'Qh': 11, 'Kh': 12, 'Ah': 13, - '2d': 14, '3d': 15, '4d': 16, '5d': 17, '6d': 18, '7d': 19, '8d': 20, '9d': 21, 'Td': 22, 'Jd': 23, 'Qd': 24, 'Kd': 25, 'Ad': 26, - '2c': 27, '3c': 28, '4c': 29, '5c': 30, '6c': 31, '7c': 32, '8c': 33, '9c': 34, 'Tc': 35, 'Jc': 36, 'Qc': 27, 'Kc': 38, 'Ac': 39, - '2s': 40, '3s': 41, '4s': 42, '5s': 43, '6s': 44, '7s': 45, '8s': 46, '9s': 47, 'Ts': 48, 'Js': 49, 'Qs': 50, 'Ks': 51, 'As': 52, - ' ': 0 + '2d': 14, '3d': 15, '4d': 16, '5d': 17, '6d': 18, '7d': 19, '8d': 20, '9d': 21, 'Td': 22, 'Jd': 23, 'Qd': 24, 'Kd': 25, 'Ad': 26, + '2c': 27, '3c': 28, '4c': 29, '5c': 30, '6c': 31, '7c': 32, '8c': 33, '9c': 34, 'Tc': 35, 'Jc': 36, 'Qc': 37, 'Kc': 38, 'Ac': 39, + '2s': 40, '3s': 41, '4s': 42, '5s': 43, '6s': 44, '7s': 45, '8s': 46, '9s': 47, 'Ts': 48, 'Js': 49, 'Qs': 50, 'Ks': 51, 'As': 52, + ' ': 0 } def encodeCard(cardString): @@ -145,5 +158,5 @@ if __name__ == '__main__': print "card %2d = %s card %2d = %s card %2d = %s card %2d = %s" % \ (i, valueSuitFromCard(i), i+13, valueSuitFromCard(i+13), i+26, valueSuitFromCard(i+26), i+39, valueSuitFromCard(i+39)) - print + print print encodeCard('7c') diff --git a/pyfpdb/Charset.py b/pyfpdb/Charset.py new file mode 100644 index 00000000..9c49f505 --- /dev/null +++ b/pyfpdb/Charset.py @@ -0,0 +1,77 @@ +#!/usr/bin/python + +#Copyright 2010 Mika Bostrom +#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 . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +# Error logging +import sys + +# String manipulation +import codecs + +# Settings +import Configuration + +encoder_to_utf = codecs.lookup('utf-8') +encoder_to_sys = codecs.lookup(Configuration.LOCALE_ENCODING) + +# I'm saving a few cycles with this one +not_needed1, not_needed2, not_needed3 = False, False, False +if Configuration.LOCALE_ENCODING == 'UTF8': + not_needed1, not_needed2, not_needed3 = True, True, True + +def to_utf8(s): + if not_needed1: return s + + try: + #(_out, _len) = encoder_to_utf.encode(s) + _out = unicode(s, Configuration.LOCALE_ENCODING).encode('utf-8') + return _out + except UnicodeDecodeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + raise + except UnicodeEncodeError: + sys.stderr.write('Could not encode: "%s"\n' % s) + raise + except TypeError: # TypeError is raised when we give unicode() an already encoded string + return s + +def to_db_utf8(s): + if not_needed2: return s + + try: + (_out, _len) = encoder_to_utf.encode(unicode(s)) + return _out + except UnicodeDecodeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + raise + except UnicodeEncodeError: + sys.stderr.write('Could not encode: "%s"\n' % s) + raise + +def to_gui(s): + if not_needed3: return s + + try: + # we usually don't want to use 'replace' but this is only for displaying + # in the gui so it doesn't matter if names are missing an accent or two + (_out, _len) = encoder_to_sys.encode(s, 'replace') + return _out + except UnicodeDecodeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + raise + except UnicodeEncodeError: + sys.stderr.write('Could not encode: "%s"\n' % s) + raise diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 117fd0c7..f7694e90 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -31,12 +31,17 @@ import inspect import string import traceback import shutil +import locale import xml.dom.minidom from xml.dom.minidom import Node import logging, logging.config import ConfigParser +# logging has been set up in fpdb.py or HUD_main.py, use their settings: +log = logging.getLogger("config") + + ############################################################################## # Functions for finding config files and setting up logging # Also used in other modules that use logging. @@ -51,60 +56,100 @@ def get_default_config_path(): return config_path def get_exec_path(): - """Returns the path to the fpdb.(py|exe) file we are executing""" + """Returns the path to the fpdb(dir|.exe) file we are executing""" if hasattr(sys, "frozen"): # compiled by py2exe return os.path.dirname(sys.executable) else: - pathname = os.path.dirname(sys.argv[0]) - return os.path.abspath(pathname) + return os.path.dirname(sys.path[0]) # should be path to /fpdb def get_config(file_name, fallback = True): """Looks in cwd and in self.default_config_path for a config file.""" - config_path = os.path.join(get_exec_path(), file_name) + exec_dir = get_exec_path() + if file_name == 'logging.conf' and not hasattr(sys, "frozen"): + config_path = os.path.join(exec_dir, 'pyfpdb', file_name) + else: + config_path = os.path.join(exec_dir, file_name) # print "config_path=", config_path if os.path.exists(config_path): # there is a file in the cwd - return config_path # so we use it + return (config_path,False) # so we use it else: # no file in the cwd, look where it should be in the first place - config_path = os.path.join(get_default_config_path(), file_name) + default_dir = get_default_config_path() + config_path = os.path.join(default_dir, file_name) # print "config path 2=", config_path if os.path.exists(config_path): - return config_path + return (config_path,False) # No file found if not fallback: - return False + return (False,False) # OK, fall back to the .example file, should be in the start dir if os.path.exists(file_name + ".example"): try: - shutil.copyfile(file_name + ".example", file_name) - print "No %s found, using %s.example.\n" % (file_name, file_name) - print "A %s file has been created. You will probably have to edit it." % file_name - sys.stderr.write("No %s found, using %s.example.\n" % (file_name, file_name) ) + print "" + check_dir(default_dir) + shutil.copyfile(file_name + ".example", config_path) + msg = "No %s found\n in %s\n or %s\n" % (file_name, exec_dir, default_dir) \ + + "Config file has been created at %s.\n" % config_path + print msg + logging.info(msg) + file_name = config_path except: - print "No %s found, cannot fall back. Exiting.\n" % file_name - sys.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name) + print "Error copying .example file, cannot fall back. Exiting.\n" + sys.stderr.write("Error copying .example file, cannot fall back. Exiting.\n") + sys.stderr.write( str(sys.exc_info()) ) sys.exit() - return file_name + else: + print "No %s found, cannot fall back. Exiting.\n" % file_name + sys.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name) + sys.exit() + return (file_name,True) -def get_logger(file_name, config = "config", fallback = False): - conf = get_config(file_name, fallback = fallback) - if conf: +def get_logger(file_name, config = "config", fallback = False, log_dir=None, log_file=None): + (conf_file,copied) = get_config(file_name, fallback = fallback) + + if log_dir is None: + log_dir = os.path.join(get_exec_path(), 'log') + #print "\nget_logger: checking log_dir:", log_dir + check_dir(log_dir) + if log_file is None: + file = os.path.join(log_dir, 'fpdb-log.txt') + else: + file = os.path.join(log_dir, log_file) + + if conf_file: try: - logging.config.fileConfig(conf) + file = file.replace('\\', '\\\\') # replace each \ with \\ +# print " ="+file+" "+ str(type(file))+" len="+str(len(file))+"\n" + logging.config.fileConfig(conf_file, {"logFile":file}) log = logging.getLogger(config) log.debug("%s logger initialised" % config) return log except: pass - log = logging.basicConfig() + log = logging.basicConfig(filename=file, level=logging.INFO) log = logging.getLogger() - log.debug("config logger initialised") + # but it looks like default is no output :-( maybe because all the calls name a module? + log.debug("Default logger initialised for "+file) + print "Default logger intialised for "+file return log -# find a logging.conf file and set up logging -log = get_logger("logging.conf") +def check_dir(path, create = True): + """Check if a dir exists, optionally creates if not.""" + if os.path.exists(path): + if os.path.isdir(path): + return path + else: + return False + if create: + msg = "Creating directory: '%s'" % (path) + print msg + log.info(msg) + os.mkdir(path) + else: + return False + ######################################################################## # application wide consts @@ -112,10 +157,6 @@ log = get_logger("logging.conf") APPLICATION_NAME_SHORT = 'fpdb' APPLICATION_VERSION = 'xx.xx.xx' -DIR_SELF = os.path.dirname(get_exec_path()) -#TODO: imo no good idea to place 'database' in parent dir -DIR_DATABASES = os.path.join(os.path.dirname(DIR_SELF), 'database') - DATABASE_TYPE_POSTGRESQL = 'postgresql' DATABASE_TYPE_SQLITE = 'sqlite' DATABASE_TYPE_MYSQL = 'mysql' @@ -125,7 +166,20 @@ DATABASE_TYPES = ( DATABASE_TYPE_MYSQL, ) -NEWIMPORT = False +#LOCALE_ENCODING = locale.getdefaultlocale()[1] +LOCALE_ENCODING = locale.getpreferredencoding() +if LOCALE_ENCODING == "US-ASCII": + print "Default encoding set to US-ASCII, defaulting to CP1252 instead -- If you're not on a Mac, please report this problem." + LOCALE_ENCODING = "cp1252" + + +# needs LOCALE_ENCODING (above), imported for sqlite setup in Config class below + +FROZEN = hasattr(sys, "frozen") +EXEC_PATH = get_exec_path() + +import Charset + ######################################################################## def string_to_bool(string, default=True): @@ -399,11 +453,11 @@ class Tv: class Config: def __init__(self, file = None, dbname = ''): - # "file" is a path to an xml file with the fpdb/HUD configuration # we check the existence of "file" and try to recover if it doesn't exist # self.default_config_path = self.get_default_config_path() + self.example_copy = False if file is not None: # config file path passed in file = os.path.expanduser(file) if not os.path.exists(file): @@ -411,7 +465,15 @@ class Config: sys.stderr.write("Configuration file %s not found. Using defaults." % (file)) file = None - if file is None: file = get_config("HUD_config.xml") + if file is None: (file,self.example_copy) = get_config("HUD_config.xml", True) + + self.file = file + self.dir_self = get_exec_path() + self.dir_config = os.path.dirname(self.file) + self.dir_log = os.path.join(self.dir_config, 'log') + self.dir_database = os.path.join(self.dir_config, 'database') + self.log_file = os.path.join(self.dir_log, 'fpdb-log.txt') + log = get_logger("logging.conf", "config", log_dir=self.dir_log) # Parse even if there was no real config file found and we are using the example # If using the example, we'll edit it later @@ -427,7 +489,6 @@ class Config: sys.exit() self.doc = doc - self.file = file self.supported_sites = {} self.supported_games = {} self.supported_databases = {} # databaseName --> Database instance @@ -564,7 +625,11 @@ class Config: def save(self, file = None): if file is None: file = self.file - shutil.move(file, file+".backup") + try: + shutil.move(file, file+".backup") + except: + pass + with open(file, 'w') as f: self.doc.writexml(f) @@ -629,6 +694,10 @@ class Config: db['db-backend'] = 3 elif self.supported_databases[name].db_server== DATABASE_TYPE_SQLITE: db['db-backend'] = 4 + # sqlcoder: this assignment fixes unicode problems for me with sqlite (windows, cp1252) + # feel free to remove or improve this if you understand the problems + # better than me (not hard!) + Charset.not_needed1, Charset.not_needed2, Charset.not_needed3 = True, True, True else: raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server) return db @@ -977,3 +1046,9 @@ if __name__== "__main__": PrettyPrint(site_node, stream=sys.stdout, encoding="utf-8") except: print "xml.dom.ext needs PyXML to be installed!" + + print "FROZEN =", FROZEN + print "EXEC_PATH =", EXEC_PATH + + print "press enter to end" + sys.stdin.readline() diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 72b41336..0303aad2 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -38,19 +38,61 @@ from decimal import Decimal import string import re import Queue +import codecs +import math + +import logging +# logging has been set up in fpdb.py or HUD_main.py, use their settings: +log = logging.getLogger("db") + # pyGTK modules + # FreePokerTools modules -import fpdb_db -import fpdb_simple -import Configuration import SQL import Card import Tourney +import Charset from Exceptions import * +import Configuration + + +# Other library modules +try: + import sqlalchemy.pool as pool + use_pool = True +except ImportError: + log.info("Not using sqlalchemy connection pool.") + use_pool = False + +try: + from numpy import var + use_numpy = True +except ImportError: + log.info("Not using numpy to define variance in sqlite.") + use_numpy = False + + +DB_VERSION = 119 + + +# Variance created as sqlite has a bunch of undefined aggregate functions. + +class VARIANCE: + def __init__(self): + self.store = [] + + def step(self, value): + self.store.append(value) + + def finalize(self): + return float(var(self.store)) + +class sqlitemath: + def mod(self, a, b): + return a%b -log = Configuration.get_logger("logging.conf") class Database: @@ -185,10 +227,27 @@ class Database: def __init__(self, c, sql = None): - log.info("Creating Database instance, sql = %s" % sql) + #log = Configuration.get_logger("logging.conf", "db", log_dir=c.dir_log) + log.debug("Creating Database instance, sql = %s" % sql) self.config = c self.__connected = False - self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql + self.settings = {} + self.settings['os'] = "linuxmac" if os.name != "nt" else "windows" + db_params = c.get_db_parameters() + self.import_options = c.get_import_parameters() + self.backend = db_params['db-backend'] + self.db_server = db_params['db-server'] + self.database = db_params['db-databaseName'] + self.host = db_params['db-host'] + self.db_path = '' + + # where possible avoid creating new SQL instance by using the global one passed in + if sql is None: + self.sql = SQL.Sql(db_server = self.db_server) + else: + self.sql = sql + + # connect to db self.do_connect(c) if self.backend == self.PGSQL: @@ -198,12 +257,6 @@ class Database: #ISOLATION_LEVEL_SERIALIZABLE = 2 - # where possible avoid creating new SQL instance by using the global one passed in - if sql is None: - self.sql = SQL.Sql(db_server = self.db_server) - else: - self.sql = sql - if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion: log.info("sqlite/:memory: - creating") self.recreate_tables() @@ -226,8 +279,6 @@ class Database: self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero self.date_nhands_ago = {} # dates N hands ago per player - not used yet - self.cursor = self.fdb.cursor - self.saveActions = False if self.import_options['saveActions'] == False else True self.connection.rollback() # make sure any locks taken so far are released @@ -238,14 +289,20 @@ class Database: self.hud_style = style def do_connect(self, c): + if c is None: + raise FpdbError('Configuration not defined') + + db = c.get_db_parameters() try: - self.fdb.do_connect(c) + self.connect(backend=db['db-backend'], + host=db['db-host'], + database=db['db-databaseName'], + user=db['db-user'], + password=db['db-password']) except: # error during connect self.__connected = False raise - self.connection = self.fdb.db - self.wrongDbVersion = self.fdb.wrongDbVersion db_params = c.get_db_parameters() self.import_options = c.get_import_parameters() @@ -255,11 +312,155 @@ class Database: self.host = db_params['db-host'] self.__connected = True + def connect(self, backend=None, host=None, database=None, + user=None, password=None): + """Connects a database with the given parameters""" + if backend is None: + raise FpdbError('Database backend not defined') + self.backend = backend + self.host = host + self.user = user + self.password = password + self.database = database + self.connection = None + self.cursor = None + + if backend == Database.MYSQL_INNODB: + import MySQLdb + if use_pool: + MySQLdb = pool.manage(MySQLdb, pool_size=5) + try: + self.connection = MySQLdb.connect(host=host, user=user, passwd=password, db=database, use_unicode=True) + #TODO: Add port option + except MySQLdb.Error, ex: + if ex.args[0] == 1045: + raise FpdbMySQLAccessDenied(ex.args[0], ex.args[1]) + elif ex.args[0] == 2002 or ex.args[0] == 2003: # 2002 is no unix socket, 2003 is no tcp socket + raise FpdbMySQLNoDatabase(ex.args[0], ex.args[1]) + else: + print "*** WARNING UNKNOWN MYSQL ERROR", ex + elif backend == Database.PGSQL: + import psycopg2 + import psycopg2.extensions + if use_pool: + psycopg2 = pool.manage(psycopg2, pool_size=5) + psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) + # If DB connection is made over TCP, then the variables + # host, user and password are required + # For local domain-socket connections, only DB name is + # needed, and everything else is in fact undefined and/or + # flat out wrong + # sqlcoder: This database only connect failed in my windows setup?? + # Modifed it to try the 4 parameter style if the first connect fails - does this work everywhere? + connected = False + if self.host == "localhost" or self.host == "127.0.0.1": + try: + self.connection = psycopg2.connect(database = database) + connected = True + except: + # direct connection failed so try user/pass/... version + pass + if not connected: + try: + self.connection = psycopg2.connect(host = host, + user = user, + password = password, + database = database) + except Exception, ex: + if 'Connection refused' in ex.args[0]: + # meaning eg. db not running + raise FpdbPostgresqlNoDatabase(errmsg = ex.args[0]) + elif 'password authentication' in ex.args[0]: + raise FpdbPostgresqlAccessDenied(errmsg = ex.args[0]) + else: + msg = ex.args[0] + print msg + raise FpdbError(msg) + elif backend == Database.SQLITE: + import sqlite3 + if use_pool: + sqlite3 = pool.manage(sqlite3, pool_size=1) + #else: + # log.warning("SQLite won't work well without 'sqlalchemy' installed.") + + if database != ":memory:": + if not os.path.isdir(self.config.dir_database): + print "Creating directory: '%s'" % (self.config.dir_database) + log.info("Creating directory: '%s'" % (self.config.dir_database)) + os.mkdir(self.config.dir_database) + database = os.path.join(self.config.dir_database, database) + self.db_path = database + log.info("Connecting to SQLite: %(database)s" % {'database':self.db_path}) + self.connection = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES ) + sqlite3.register_converter("bool", lambda x: bool(int(x))) + sqlite3.register_adapter(bool, lambda x: "1" if x else "0") + self.connection.create_function("floor", 1, math.floor) + tmp = sqlitemath() + self.connection.create_function("mod", 2, tmp.mod) + if use_numpy: + self.connection.create_aggregate("variance", 1, VARIANCE) + else: + log.warning("Some database functions will not work without NumPy support") + self.cursor = self.connection.cursor() + self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes + self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish + else: + raise FpdbError("unrecognised database backend:"+backend) + + self.cursor = self.connection.cursor() + self.cursor.execute(self.sql.query['set tx level']) + self.check_version(database=database, create=True) + + + def check_version(self, database, create): + self.wrongDbVersion = False + try: + self.cursor.execute("SELECT * FROM Settings") + settings = self.cursor.fetchone() + if settings[0] != DB_VERSION: + log.error("outdated or too new database version (%s) - please recreate tables" + % (settings[0])) + self.wrongDbVersion = True + except:# _mysql_exceptions.ProgrammingError: + if database != ":memory:": + if create: + print "Failed to read settings table - recreating tables" + log.info("failed to read settings table - recreating tables") + self.recreate_tables() + self.check_version(database=database, create=False) + else: + print "Failed to read settings table - please recreate tables" + log.info("failed to read settings table - please recreate tables") + self.wrongDbVersion = True + else: + self.wrongDbVersion = True + #end def connect + def commit(self): - self.fdb.db.commit() + if self.backend != self.SQLITE: + self.connection.commit() + else: + # sqlite commits can fail because of shared locks on the database (SQLITE_BUSY) + # re-try commit if it fails in case this happened + maxtimes = 5 + pause = 1 + ok = False + for i in xrange(maxtimes): + try: + ret = self.connection.commit() + log.debug("commit finished ok, i = "+str(i)) + ok = True + except: + log.debug("commit "+str(i)+" failed: info=" + str(sys.exc_info()) + + " value=" + str(sys.exc_value)) + sleep(pause) + if ok: break + if not ok: + log.debug("commit failed") + raise FpdbError('sqlite commit failed') def rollback(self): - self.fdb.db.rollback() + self.connection.rollback() def connected(self): return self.__connected @@ -272,11 +473,18 @@ class Database: def disconnect(self, due_to_error=False): """Disconnects the DB (rolls back if param is true, otherwise commits""" - self.fdb.disconnect(due_to_error) + if due_to_error: + self.connection.rollback() + else: + self.connection.commit() + self.cursor.close() + self.connection.close() def reconnect(self, due_to_error=False): """Reconnects the DB""" - self.fdb.reconnect(due_to_error=False) + #print "started reconnect" + self.disconnect(due_to_error) + self.connect(self.backend, self.host, self.database, self.user, self.password) def get_backend_name(self): """Returns the name of the currently used backend""" @@ -289,6 +497,9 @@ class Database: else: raise FpdbError("invalid backend") + def get_db_info(self): + return (self.host, self.database, self.user, self.password) + def get_table_name(self, hand_id): c = self.connection.cursor() c.execute(self.sql.query['get_table_name'], (hand_id, )) @@ -358,28 +569,6 @@ class Database: cards['common'] = c.fetchone() return cards - def convert_cards(self, d): - ranks = ('', '', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A') - cards = "" - for i in xrange(1, 8): -# key = 'card' + str(i) + 'Value' -# if not d.has_key(key): continue -# if d[key] == None: -# break -# elif d[key] == 0: -# cards += "xx" -# else: -# cards += ranks[d['card' + str(i) + 'Value']] + d['card' +str(i) + 'Suit'] - cv = "card%dvalue" % i - if cv not in d or d[cv] is None: - break - elif d[cv] == 0: - cards += "xx" - else: - cs = "card%dsuit" % i - cards = "%s%s%s" % (cards, ranks[d[cv]], d[cs]) - return cards - def get_action_from_hand(self, hand_no): action = [ [], [], [], [], [] ] c = self.connection.cursor() @@ -587,6 +776,7 @@ class Database: elif not name.lower() in stat_dict[playerid]: stat_dict[playerid][name.lower()] = val elif name.lower() not in ('hand_id', 'player_id', 'seat', 'screen_name', 'seats'): + #print "DEBUG: stat_dict[%s][%s]: %s" %(playerid, name.lower(), val) stat_dict[playerid][name.lower()] += val n += 1 if n >= 10000: break # todo: don't think this is needed so set nice and high @@ -602,7 +792,9 @@ class Database: def get_player_id(self, config, site, player_name): c = self.connection.cursor() - c.execute(self.sql.query['get_player_id'], (player_name, site)) + #print "get_player_id: player_name =", player_name, type(player_name) + p_name = Charset.to_utf8(player_name) + c.execute(self.sql.query['get_player_id'], (p_name, site)) row = c.fetchone() if row: return row[0] @@ -615,73 +807,11 @@ class Database: if site_id is None: site_id = -1 c = self.get_cursor() - c.execute(self.sql.query['get_player_names'], (like_player_name, site_id, site_id)) + p_name = Charset.to_utf8(like_player_name) + c.execute(self.sql.query['get_player_names'], (p_name, site_id, site_id)) rows = c.fetchall() return rows - #returns the SQL ids of the names given in an array - # TODO: if someone gets industrious, they should make the parts that use the output of this function deal with a dict - # { playername: id } instead of depending on it's relation to the positions list - # then this can be reduced in complexity a bit - - #def recognisePlayerIDs(cursor, names, site_id): - # result = [] - # for i in xrange(len(names)): - # cursor.execute ("SELECT id FROM Players WHERE name=%s", (names[i],)) - # tmp=cursor.fetchall() - # if (len(tmp)==0): #new player - # cursor.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)", (names[i], site_id)) - # #print "Number of players rows inserted: %d" % cursor.rowcount - # cursor.execute ("SELECT id FROM Players WHERE name=%s", (names[i],)) - # tmp=cursor.fetchall() - # #print "recognisePlayerIDs, names[i]:",names[i],"tmp:",tmp - # result.append(tmp[0][0]) - # return result - - def recognisePlayerIDs(self, names, site_id): - c = self.get_cursor() - q = "SELECT name,id FROM Players WHERE siteid=%d and (name=%s)" %(site_id, " OR name=".join([self.sql.query['placeholder'] for n in names])) - c.execute(q, names) # get all playerids by the names passed in - ids = dict(c.fetchall()) # convert to dict - if len(ids) != len(names): - notfound = [n for n in names if n not in ids] # make list of names not in database - if notfound: # insert them into database - q_ins = "INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")" - q_ins = q_ins.replace('%s', self.sql.query['placeholder']) - c.executemany(q_ins, [(n,) for n in notfound]) - q2 = "SELECT name,id FROM Players WHERE siteid=%d and (name=%s)" % (site_id, " OR name=".join(["%s" for n in notfound])) - q2 = q2.replace('%s', self.sql.query['placeholder']) - c.execute(q2, notfound) # get their new ids - tmp = c.fetchall() - for n,id in tmp: # put them all into the same dict - ids[n] = id - # return them in the SAME ORDER that they came in in the names argument, rather than the order they came out of the DB - return [ids[n] for n in names] - #end def recognisePlayerIDs - - # Here's a version that would work if it wasn't for the fact that it needs to have the output in the same order as input - # this version could also be improved upon using list comprehensions, etc - - #def recognisePlayerIDs(cursor, names, site_id): - # result = [] - # notfound = [] - # cursor.execute("SELECT name,id FROM Players WHERE name='%s'" % "' OR name='".join(names)) - # tmp = dict(cursor.fetchall()) - # for n in names: - # if n not in tmp: - # notfound.append(n) - # else: - # result.append(tmp[n]) - # if notfound: - # cursor.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", (notfound)) - # cursor.execute("SELECT id FROM Players WHERE name='%s'" % "' OR name='".join(notfound)) - # tmp = cursor.fetchall() - # for n in tmp: - # result.append(n[0]) - # - # return result - - def get_site_id(self, site): c = self.get_cursor() c.execute(self.sql.query['getSiteId'], (site,)) @@ -724,120 +854,6 @@ class Database: return ret - #stores a stud/razz hand into the database - def ring_stud(self, config, settings, base, category, site_hand_no, gametype_id, hand_start_time - ,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes - ,action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName - ,seatNos): - - fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) - - hands_id = self.storeHands(self.backend, site_hand_no, gametype_id - ,hand_start_time, names, tableName, maxSeats, hudImportData - ,(None, None, None, None, None), (None, None, None, None, None)) - - #print "before calling store_hands_players_stud, antes:", antes - hands_players_ids = self.store_hands_players_stud(self.backend, hands_id, player_ids - ,start_cashes, antes, card_values - ,card_suits, winnings, rakes, seatNos) - - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) - - return hands_id - #end def ring_stud - - def ring_holdem_omaha(self, config, settings, base, category, site_hand_no, gametype_id - ,hand_start_time, names, player_ids, start_cashes, positions, card_values - ,card_suits, board_values, board_suits, winnings, rakes, action_types, allIns - ,action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): - """stores a holdem/omaha hand into the database""" - - t0 = time() - #print "in ring_holdem_omaha" - fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) - t1 = time() - fpdb_simple.fill_board_cards(board_values, board_suits) - t2 = time() - - hands_id = self.storeHands(self.backend, site_hand_no, gametype_id - ,hand_start_time, names, tableName, maxSeats - ,hudImportData, board_values, board_suits) - #TEMPORARY CALL! - Just until all functions are migrated - t3 = time() - hands_players_ids = self.store_hands_players_holdem_omaha( - self.backend, category, hands_id, player_ids, start_cashes - , positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData) - t4 = time() - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) - t5 = time() - #print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4) - return hands_id - #end def ring_holdem_omaha - - def tourney_holdem_omaha(self, config, settings, base, category, siteTourneyNo, buyin, fee, knockout - ,entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId - ,siteId #end of tourney specific params - ,site_hand_no, gametype_id, hand_start_time, names, player_ids - ,start_cashes, positions, card_values, card_suits, board_values - ,board_suits, winnings, rakes, action_types, allIns, action_amounts - ,actionNos, hudImportData, maxSeats, tableName, seatNos): - """stores a tourney holdem/omaha hand into the database""" - - fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) - fpdb_simple.fill_board_cards(board_values, board_suits) - - tourney_id = self.store_tourneys(tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start) - tourneys_players_ids = self.store_tourneys_players(tourney_id, player_ids, payin_amounts, ranks, winnings) - - hands_id = self.storeHands(self.backend, site_hand_no, gametype_id - ,hand_start_time, names, tableName, maxSeats - ,hudImportData, board_values, board_suits) - - hands_players_ids = self.store_hands_players_holdem_omaha_tourney( - self.backend, category, hands_id, player_ids, start_cashes, positions - , card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids - , hudImportData, tourneyTypeId) - - #print "tourney holdem, backend=%d" % backend - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) - - return hands_id - #end def tourney_holdem_omaha - - def tourney_stud(self, config, settings, base, category, siteTourneyNo, buyin, fee, knockout, entries - ,prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteId - ,siteHandNo, gametypeId, handStartTime, names, playerIds, startCashes, antes - ,cardValues, cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts - ,actionNos, hudImportData, maxSeats, tableName, seatNos): - #stores a tourney stud/razz hand into the database - - fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits) - - tourney_id = self.store_tourneys(tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime) - - tourneys_players_ids = self.store_tourneys_players(tourney_id, playerIds, payin_amounts, ranks, winnings) - - hands_id = self.storeHands( self.backend, siteHandNo, gametypeId - , handStartTime, names, tableName, maxSeats - , hudImportData, (None, None, None, None, None), (None, None, None, None, None) ) - # changed board_values and board_suits to arrays of None, just like the - # cash game version of this function does - i don't believe this to be - # the correct thing to do (tell me if i'm wrong) but it should keep the - # importer from crashing - - hands_players_ids = self.store_hands_players_stud_tourney(self.backend, hands_id - , playerIds, startCashes, antes, cardValues, cardSuits - , winnings, rakes, seatNos, tourneys_players_ids, tourneyTypeId) - - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametypeId, handStartTime, playerIds, hudImportData) - - return hands_id - #end def tourney_stud - def prepareBulkImport(self): """Drop some indexes/foreign keys to prepare for bulk import. Currently keeping the standalone indexes as needed to import quickly""" @@ -1041,6 +1057,7 @@ class Database: self.create_tables() self.createAllIndexes() self.commit() + print "Finished recreating tables" log.info("Finished recreating tables") #end def recreate_tables @@ -1306,7 +1323,7 @@ class Database: def fillDefaultData(self): c = self.get_cursor() - c.execute("INSERT INTO Settings (version) VALUES (118);") + c.execute("INSERT INTO Settings (version) VALUES (%s);" % (DB_VERSION)) c.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', 'USD')") @@ -1317,6 +1334,8 @@ class Database: c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('Partouche', 'EUR')") + c.execute("INSERT INTO Sites (name,currency) VALUES ('Carbon', 'USD')") + c.execute("INSERT INTO Sites (name,currency) VALUES ('PKR', 'USD')") if self.backend == self.SQLITE: c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);") elif self.backend == self.PGSQL: @@ -1462,67 +1481,9 @@ class Database: try: self.get_cursor().execute(self.sql.query['lockForInsert']) except: - print "Error during fdb.lock_for_insert:", str(sys.exc_value) + print "Error during lock_for_insert:", str(sys.exc_value) #end def lock_for_insert - def store_the_hand(self, h): - """Take a HandToWrite object and store it in the db""" - - # Following code writes hands to database and commits (or rolls back if there is an error) - try: - result = None - if h.isTourney: - ranks = map(lambda x: 0, h.names) # create an array of 0's equal to the length of names - payin_amounts = fpdb_simple.calcPayin(len(h.names), h.buyin, h.fee) - - if h.base == "hold": - result = self.tourney_holdem_omaha( - h.config, h.settings, h.base, h.category, h.siteTourneyNo, h.buyin - , h.fee, h.knockout, h.entries, h.prizepool, h.tourneyStartTime - , payin_amounts, ranks, h.tourneyTypeId, h.siteID, h.siteHandNo - , h.gametypeID, h.handStartTime, h.names, h.playerIDs, h.startCashes - , h.positions, h.cardValues, h.cardSuits, h.boardValues, h.boardSuits - , h.winnings, h.rakes, h.actionTypes, h.allIns, h.actionAmounts - , h.actionNos, h.hudImportData, h.maxSeats, h.tableName, h.seatNos) - elif h.base == "stud": - result = self.tourney_stud( - h.config, h.settings, h.base, h.category, h.siteTourneyNo - , h.buyin, h.fee, h.knockout, h.entries, h.prizepool, h.tourneyStartTime - , payin_amounts, ranks, h.tourneyTypeId, h.siteID, h.siteHandNo - , h.gametypeID, h.handStartTime, h.names, h.playerIDs, h.startCashes - , h.antes, h.cardValues, h.cardSuits, h.winnings, h.rakes, h.actionTypes - , h.allIns, h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats - , h.tableName, h.seatNos) - else: - raise FpdbError("unrecognised category") - else: - if h.base == "hold": - result = self.ring_holdem_omaha( - h.config, h.settings, h.base, h.category, h.siteHandNo - , h.gametypeID, h.handStartTime, h.names, h.playerIDs - , h.startCashes, h.positions, h.cardValues, h.cardSuits - , h.boardValues, h.boardSuits, h.winnings, h.rakes - , h.actionTypes, h.allIns, h.actionAmounts, h.actionNos - , h.hudImportData, h.maxSeats, h.tableName, h.seatNos) - elif h.base == "stud": - result = self.ring_stud( - h.config, h.settings, h.base, h.category, h.siteHandNo, h.gametypeID - , h.handStartTime, h.names, h.playerIDs, h.startCashes, h.antes - , h.cardValues, h.cardSuits, h.winnings, h.rakes, h.actionTypes, h.allIns - , h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats, h.tableName - , h.seatNos) - else: - raise FpdbError("unrecognised category") - except: - print "Error storing hand: " + str(sys.exc_value) - self.rollback() - # re-raise the exception so that the calling routine can decide what to do: - # (e.g. a write thread might try again) - raise - - return result - #end def store_the_hand - ########################### # NEWIMPORT CODE ########################### @@ -1539,6 +1500,7 @@ class Database: p['tableName'], p['gameTypeId'], p['siteHandNo'], + 0, # tourneyId: 0 means not a tourney hand p['handStart'], datetime.today(), #importtime p['seats'], @@ -1663,145 +1625,111 @@ class Database: c = self.get_cursor() c.executemany(q, inserts) - def storeHudCacheNew(self, gid, pid, hc): - q = """INSERT INTO HudCache ( - gametypeId, - playerId - ) - VALUES ( - %s, %s - )""" + def storeHudCache(self, gid, pids, starttime, pdata): + """Update cached statistics. If update fails because no record exists, do an insert.""" -# gametypeId, -# playerId, -# activeSeats, -# position, -# tourneyTypeId, -# styleKey, -# HDs, -# street0VPI, -# street0Aggr, -# street0_3BChance, -# street0_3BDone, -# street1Seen, -# street2Seen, -# street3Seen, -# street4Seen, -# sawShowdown, -# street1Aggr, -# street2Aggr, -# street3Aggr, -# street4Aggr, -# otherRaisedStreet1, -# otherRaisedStreet2, -# otherRaisedStreet3, -# otherRaisedStreet4, -# foldToOtherRaisedStreet1, -# foldToOtherRaisedStreet2, -# foldToOtherRaisedStreet3, -# foldToOtherRaisedStreet4, -# wonWhenSeenStreet1, -# wonAtSD, -# stealAttemptChance, -# stealAttempted, -# foldBbToStealChance, -# foldedBbToSteal, -# foldSbToStealChance, -# foldedSbToSteal, -# street1CBChance, -# street1CBDone, -# street2CBChance, -# street2CBDone, -# street3CBChance, -# street3CBDone, -# street4CBChance, -# street4CBDone, -# foldToStreet1CBChance, -# foldToStreet1CBDone, -# foldToStreet2CBChance, -# foldToStreet2CBDone, -# foldToStreet3CBChance, -# foldToStreet3CBDone, -# foldToStreet4CBChance, -# foldToStreet4CBDone, -# totalProfit, -# street1CheckCallRaiseChance, -# street1CheckCallRaiseDone, -# street2CheckCallRaiseChance, -# street2CheckCallRaiseDone, -# street3CheckCallRaiseChance, -# street3CheckCallRaiseDone, -# street4CheckCallRaiseChance, -# street4CheckCallRaiseDone) + if self.use_date_in_hudcache: + styleKey = datetime.strftime(starttime, 'd%y%m%d') + #styleKey = "d%02d%02d%02d" % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day) + else: + # hard-code styleKey as 'A000000' (all-time cache, no key) for now + styleKey = 'A000000' - q = q.replace('%s', self.sql.query['placeholder']) + update_hudcache = self.sql.query['update_hudcache'] + update_hudcache = update_hudcache.replace('%s', self.sql.query['placeholder']) + insert_hudcache = self.sql.query['insert_hudcache'] + insert_hudcache = insert_hudcache.replace('%s', self.sql.query['placeholder']) + + #print "DEBUG: %s %s %s" %(hid, pids, pdata) + inserts = [] + for p in pdata: + line = [0]*61 + + line[0] = 1 # HDs + if pdata[p]['street0VPI']: line[1] = 1 + if pdata[p]['street0Aggr']: line[2] = 1 + if pdata[p]['street0_3BChance']: line[3] = 1 + if pdata[p]['street0_3BDone']: line[4] = 1 + if pdata[p]['street1Seen']: line[5] = 1 + if pdata[p]['street2Seen']: line[6] = 1 + if pdata[p]['street3Seen']: line[7] = 1 + if pdata[p]['street4Seen']: line[8] = 1 + if pdata[p]['sawShowdown']: line[9] = 1 + if pdata[p]['street1Aggr']: line[10] = 1 + if pdata[p]['street2Aggr']: line[11] = 1 + if pdata[p]['street3Aggr']: line[12] = 1 + if pdata[p]['street4Aggr']: line[13] = 1 + if pdata[p]['otherRaisedStreet1']: line[14] = 1 + if pdata[p]['otherRaisedStreet2']: line[15] = 1 + if pdata[p]['otherRaisedStreet3']: line[16] = 1 + if pdata[p]['otherRaisedStreet4']: line[17] = 1 + if pdata[p]['foldToOtherRaisedStreet1']: line[18] = 1 + if pdata[p]['foldToOtherRaisedStreet2']: line[19] = 1 + if pdata[p]['foldToOtherRaisedStreet3']: line[20] = 1 + if pdata[p]['foldToOtherRaisedStreet4']: line[21] = 1 + line[22] = pdata[p]['wonWhenSeenStreet1'] + line[23] = pdata[p]['wonAtSD'] + if pdata[p]['stealAttemptChance']: line[24] = 1 + if pdata[p]['stealAttempted']: line[25] = 1 + if pdata[p]['foldBbToStealChance']: line[26] = 1 + if pdata[p]['foldedBbToSteal']: line[27] = 1 + if pdata[p]['foldSbToStealChance']: line[28] = 1 + if pdata[p]['foldedSbToSteal']: line[29] = 1 + if pdata[p]['street1CBChance']: line[30] = 1 + if pdata[p]['street1CBDone']: line[31] = 1 + if pdata[p]['street2CBChance']: line[32] = 1 + if pdata[p]['street2CBDone']: line[33] = 1 + if pdata[p]['street3CBChance']: line[34] = 1 + if pdata[p]['street3CBDone']: line[35] = 1 + if pdata[p]['street4CBChance']: line[36] = 1 + if pdata[p]['street4CBDone']: line[37] = 1 + if pdata[p]['foldToStreet1CBChance']: line[38] = 1 + if pdata[p]['foldToStreet1CBDone']: line[39] = 1 + if pdata[p]['foldToStreet2CBChance']: line[40] = 1 + if pdata[p]['foldToStreet2CBDone']: line[41] = 1 + if pdata[p]['foldToStreet3CBChance']: line[42] = 1 + if pdata[p]['foldToStreet3CBDone']: line[43] = 1 + if pdata[p]['foldToStreet4CBChance']: line[44] = 1 + if pdata[p]['foldToStreet4CBDone']: line[45] = 1 + line[46] = pdata[p]['totalProfit'] + if pdata[p]['street1CheckCallRaiseChance']: line[47] = 1 + if pdata[p]['street1CheckCallRaiseDone']: line[48] = 1 + if pdata[p]['street2CheckCallRaiseChance']: line[49] = 1 + if pdata[p]['street2CheckCallRaiseDone']: line[50] = 1 + if pdata[p]['street3CheckCallRaiseChance']: line[51] = 1 + if pdata[p]['street3CheckCallRaiseDone']: line[52] = 1 + if pdata[p]['street4CheckCallRaiseChance']: line[53] = 1 + if pdata[p]['street4CheckCallRaiseDone']: line[54] = 1 + line[55] = gid # gametypeId + line[56] = pids[p] # playerId + line[57] = len(pids) # activeSeats + pos = {'B':'B', 'S':'S', 0:'D', 1:'C', 2:'M', 3:'M', 4:'M', 5:'E', 6:'E', 7:'E', 8:'E', 9:'E' } + line[58] = pos[pdata[p]['position']] + line[59] = pdata[p]['tourneyTypeId'] + line[60] = styleKey # styleKey + inserts.append(line) - self.cursor.execute(q, ( - gid, - pid - )) -# gametypeId, -# playerId, -# activeSeats, -# position, -# tourneyTypeId, -# styleKey, -# HDs, -# street0VPI, -# street0Aggr, -# street0_3BChance, -# street0_3BDone, -# street1Seen, -# street2Seen, -# street3Seen, -# street4Seen, -# sawShowdown, -# street1Aggr, -# street2Aggr, -# street3Aggr, -# street4Aggr, -# otherRaisedStreet1, -# otherRaisedStreet2, -# otherRaisedStreet3, -# otherRaisedStreet4, -# foldToOtherRaisedStreet1, -# foldToOtherRaisedStreet2, -# foldToOtherRaisedStreet3, -# foldToOtherRaisedStreet4, -# wonWhenSeenStreet1, -# wonAtSD, -# stealAttemptChance, -# stealAttempted, -# foldBbToStealChance, -# foldedBbToSteal, -# foldSbToStealChance, -# foldedSbToSteal, -# street1CBChance, -# street1CBDone, -# street2CBChance, -# street2CBDone, -# street3CBChance, -# street3CBDone, -# street4CBChance, -# street4CBDone, -# foldToStreet1CBChance, -# foldToStreet1CBDone, -# foldToStreet2CBChance, -# foldToStreet2CBDone, -# foldToStreet3CBChance, -# foldToStreet3CBDone, -# foldToStreet4CBChance, -# foldToStreet4CBDone, -# totalProfit, -# street1CheckCallRaiseChance, -# street1CheckCallRaiseDone, -# street2CheckCallRaiseChance, -# street2CheckCallRaiseDone, -# street3CheckCallRaiseChance, -# street3CheckCallRaiseDone, -# street4CheckCallRaiseChance, -# street4CheckCallRaiseDone) + cursor = self.get_cursor() + + for row in inserts: + # Try to do the update first: + num = cursor.execute(update_hudcache, row) + #print "DEBUG: values: %s" % row[-6:] + # Test statusmessage to see if update worked, do insert if not + # num is a cursor in sqlite + if ((self.backend == self.PGSQL and cursor.statusmessage != "UPDATE 1") + or (self.backend == self.MYSQL_INNODB and num == 0) + or (self.backend == self.SQLITE and num.rowcount == 0)): + #move the last 6 items in WHERE clause of row from the end of the array + # to the beginning for the INSERT statement + #print "DEBUG: using INSERT: %s" % num + row = row[-6:] + row[:-6] + num = cursor.execute(insert_hudcache, row) + #print "DEBUG: Successfully(?: %s) updated HudCacho using INSERT" % num + else: + #print "DEBUG: Successfully updated HudCacho using UPDATE" + pass def isDuplicate(self, gametypeID, siteHandNo): dup = False @@ -1831,10 +1759,10 @@ class Database: def getSqlPlayerIDs(self, pnames, siteid): result = {} if(self.pcache == None): - self.pcache = LambdaDict(lambda key:self.insertPlayer(key, siteid)) + self.pcache = LambdaDict(lambda key:self.insertPlayer(key[0], key[1])) for player in pnames: - result[player] = self.pcache[player] + result[player] = self.pcache[(player,siteid)] # NOTE: Using the LambdaDict does the same thing as: #if player in self.pcache: # #print "DEBUG: cachehit" @@ -1847,6 +1775,7 @@ class Database: def insertPlayer(self, name, site_id): result = None + _name = Charset.to_db_utf8(name) c = self.get_cursor() q = "SELECT name, id FROM Players WHERE siteid=%s and name=%s" q = q.replace('%s', self.sql.query['placeholder']) @@ -1860,12 +1789,12 @@ class Database: #print "DEBUG: name: %s site: %s" %(name, site_id) - c.execute (q, (site_id, name)) + c.execute (q, (site_id, _name)) tmp = c.fetchone() if (tmp == None): #new player c.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)".replace('%s',self.sql.query['placeholder']) - ,(name, site_id)) + ,(_name, site_id)) #Get last id might be faster here. #c.execute ("SELECT id FROM Players WHERE name=%s", (name,)) result = self.get_last_insert_id(c) @@ -1886,535 +1815,6 @@ class Database: - def storeHands(self, backend, site_hand_no, gametype_id - ,hand_start_time, names, tableName, maxSeats, hudCache - ,board_values, board_suits): - - cards = [Card.cardFromValueSuit(v,s) for v,s in zip(board_values,board_suits)] - #stores into table hands: - try: - c = self.get_cursor() - c.execute ("""INSERT INTO Hands - (siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats - ,boardcard1,boardcard2,boardcard3,boardcard4,boardcard5 - ,playersVpi, playersAtStreet1, playersAtStreet2 - ,playersAtStreet3, playersAtStreet4, playersAtShowdown - ,street0Raises, street1Raises, street2Raises - ,street3Raises, street4Raises, street1Pot - ,street2Pot, street3Pot, street4Pot - ,showdownPot - ) - VALUES - (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """.replace('%s', self.sql.query['placeholder']) - , (site_hand_no, gametype_id, hand_start_time, len(names), tableName, datetime.today(), maxSeats - ,cards[0], cards[1], cards[2], cards[3], cards[4] - ,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2'] - ,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown'] - ,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises'] - ,hudCache['street3Raises'], hudCache['street4Raises'], hudCache['street1Pot'] - ,hudCache['street2Pot'], hudCache['street3Pot'], hudCache['street4Pot'] - ,hudCache['showdownPot'] - )) - ret = self.get_last_insert_id(c) - except: - ret = -1 - raise FpdbError( "storeHands error: " + str(sys.exc_value) ) - - return ret - #end def storeHands - - def store_hands_players_holdem_omaha(self, backend, category, hands_id, player_ids, start_cashes - ,positions, card_values, card_suits, winnings, rakes, seatNos, hudCache): - result=[] - - # postgres (and others?) needs the booleans converted to ints before saving: - # (or we could just save them as boolean ... but then we can't sum them so easily in sql ???) - # NO - storing booleans for now so don't need this - #hudCacheInt = {} - #for k,v in hudCache.iteritems(): - # if k in ('wonWhenSeenStreet1', 'wonAtSD', 'totalProfit'): - # hudCacheInt[k] = v - # else: - # hudCacheInt[k] = map(lambda x: 1 if x else 0, v) - - try: - inserts = [] - for i in xrange(len(player_ids)): - card1 = Card.cardFromValueSuit(card_values[i][0], card_suits[i][0]) - card2 = Card.cardFromValueSuit(card_values[i][1], card_suits[i][1]) - - if (category=="holdem"): - startCards = Card.twoStartCards(card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1]) - card3 = None - card4 = None - elif (category=="omahahi" or category=="omahahilo"): - startCards = Card.fourStartCards(card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1] - ,card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3]) - card3 = Card.cardFromValueSuit(card_values[i][2], card_suits[i][2]) - card4 = Card.cardFromValueSuit(card_values[i][3], card_suits[i][3]) - else: - raise FpdbError("invalid category") - - inserts.append( ( - hands_id, player_ids[i], start_cashes[i], positions[i], 1, # tourneytypeid - needed for hudcache - card1, card2, card3, card4, startCards, - winnings[i], rakes[i], seatNos[i], hudCache['totalProfit'][i], - hudCache['street0VPI'][i], hudCache['street0Aggr'][i], - hudCache['street0_3BChance'][i], hudCache['street0_3BDone'][i], - hudCache['street1Seen'][i], hudCache['street2Seen'][i], hudCache['street3Seen'][i], - hudCache['street4Seen'][i], hudCache['sawShowdown'][i], - hudCache['street1Aggr'][i], hudCache['street2Aggr'][i], hudCache['street3Aggr'][i], hudCache['street4Aggr'][i], - hudCache['otherRaisedStreet1'][i], hudCache['otherRaisedStreet2'][i], - hudCache['otherRaisedStreet3'][i], hudCache['otherRaisedStreet4'][i], - hudCache['foldToOtherRaisedStreet1'][i], hudCache['foldToOtherRaisedStreet2'][i], - hudCache['foldToOtherRaisedStreet3'][i], hudCache['foldToOtherRaisedStreet4'][i], - hudCache['wonWhenSeenStreet1'][i], hudCache['wonAtSD'][i], - hudCache['stealAttemptChance'][i], hudCache['stealAttempted'][i], hudCache['foldBbToStealChance'][i], - hudCache['foldedBbToSteal'][i], hudCache['foldSbToStealChance'][i], hudCache['foldedSbToSteal'][i], - hudCache['street1CBChance'][i], hudCache['street1CBDone'][i], hudCache['street2CBChance'][i], hudCache['street2CBDone'][i], - hudCache['street3CBChance'][i], hudCache['street3CBDone'][i], hudCache['street4CBChance'][i], hudCache['street4CBDone'][i], - hudCache['foldToStreet1CBChance'][i], hudCache['foldToStreet1CBDone'][i], - hudCache['foldToStreet2CBChance'][i], hudCache['foldToStreet2CBDone'][i], - hudCache['foldToStreet3CBChance'][i], hudCache['foldToStreet3CBDone'][i], - hudCache['foldToStreet4CBChance'][i], hudCache['foldToStreet4CBDone'][i], - hudCache['street1CheckCallRaiseChance'][i], hudCache['street1CheckCallRaiseDone'][i], - hudCache['street2CheckCallRaiseChance'][i], hudCache['street2CheckCallRaiseDone'][i], - hudCache['street3CheckCallRaiseChance'][i], hudCache['street3CheckCallRaiseDone'][i], - hudCache['street4CheckCallRaiseChance'][i], hudCache['street4CheckCallRaiseDone'][i], - hudCache['street0Calls'][i], hudCache['street1Calls'][i], hudCache['street2Calls'][i], hudCache['street3Calls'][i], hudCache['street4Calls'][i], - hudCache['street0Bets'][i], hudCache['street1Bets'][i], hudCache['street2Bets'][i], hudCache['street3Bets'][i], hudCache['street4Bets'][i] - ) ) - c = self.get_cursor() - c.executemany (""" - INSERT INTO HandsPlayers - (handId, playerId, startCash, position, tourneyTypeId, - card1, card2, card3, card4, startCards, winnings, rake, seatNo, totalProfit, - street0VPI, street0Aggr, street0_3BChance, street0_3BDone, - street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown, - street1Aggr, street2Aggr, street3Aggr, street4Aggr, - otherRaisedStreet1, otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4, - foldToOtherRaisedStreet1, foldToOtherRaisedStreet2, foldToOtherRaisedStreet3, foldToOtherRaisedStreet4, - wonWhenSeenStreet1, wonAtSD, - stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, - street1CBChance, street1CBDone, street2CBChance, street2CBDone, - street3CBChance, street3CBDone, street4CBChance, street4CBDone, - foldToStreet1CBChance, foldToStreet1CBDone, foldToStreet2CBChance, foldToStreet2CBDone, - foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance, foldToStreet4CBDone, - street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance, street2CheckCallRaiseDone, - street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone, - street0Calls, street1Calls, street2Calls, street3Calls, street4Calls, - street0Bets, street1Bets, street2Bets, street3Bets, street4Bets - ) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']) - ,inserts ) - result.append( self.get_last_insert_id(c) ) # wrong? not used currently - except: - raise FpdbError( "store_hands_players_holdem_omaha error: " + str(sys.exc_value) ) - - return result - #end def store_hands_players_holdem_omaha - - def store_hands_players_stud(self, backend, hands_id, player_ids, start_cashes, antes, - card_values, card_suits, winnings, rakes, seatNos): - #stores hands_players rows for stud/razz games. returns an array of the resulting IDs - - try: - result=[] - #print "before inserts in store_hands_players_stud, antes:", antes - for i in xrange(len(player_ids)): - card1 = Card.cardFromValueSuit(card_values[i][0], card_suits[i][0]) - card2 = Card.cardFromValueSuit(card_values[i][1], card_suits[i][1]) - card3 = Card.cardFromValueSuit(card_values[i][2], card_suits[i][2]) - card4 = Card.cardFromValueSuit(card_values[i][3], card_suits[i][3]) - card5 = Card.cardFromValueSuit(card_values[i][4], card_suits[i][4]) - card6 = Card.cardFromValueSuit(card_values[i][5], card_suits[i][5]) - card7 = Card.cardFromValueSuit(card_values[i][6], card_suits[i][6]) - - c = self.get_cursor() - c.execute ("""INSERT INTO HandsPlayers - (handId, playerId, startCash, ante, tourneyTypeId, - card1, card2, - card3, card4, - card5, card6, - card7, winnings, rake, seatNo) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']), - (hands_id, player_ids[i], start_cashes[i], antes[i], 1, - card1, card2, - card3, card4, - card5, card6, - card7, winnings[i], rakes[i], seatNos[i])) - #cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i])) - #result.append(cursor.fetchall()[0][0]) - result.append( self.get_last_insert_id(c) ) - except: - raise FpdbError( "store_hands_players_stud error: " + str(sys.exc_value) ) - - return result - #end def store_hands_players_stud - - def store_hands_players_holdem_omaha_tourney(self, backend, category, hands_id, player_ids - ,start_cashes, positions, card_values, card_suits - ,winnings, rakes, seatNos, tourneys_players_ids - ,hudCache, tourneyTypeId): - #stores hands_players for tourney holdem/omaha hands - - try: - result=[] - inserts = [] - for i in xrange(len(player_ids)): - card1 = Card.cardFromValueSuit(card_values[i][0], card_suits[i][0]) - card2 = Card.cardFromValueSuit(card_values[i][1], card_suits[i][1]) - - if len(card_values[0])==2: - startCards = Card.twoStartCards(card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1]) - card3 = None - card4 = None - elif len(card_values[0])==4: - startCards = Card.fourStartCards(card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1] - ,card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3]) - card3 = Card.cardFromValueSuit(card_values[i][2], card_suits[i][2]) - card4 = Card.cardFromValueSuit(card_values[i][3], card_suits[i][3]) - else: - raise FpdbError ("invalid card_values length:"+str(len(card_values[0]))) - - inserts.append( (hands_id, player_ids[i], start_cashes[i], positions[i], tourneyTypeId, - card1, card2, card3, card4, startCards, - winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i], hudCache['totalProfit'][i], - hudCache['street0VPI'][i], hudCache['street0Aggr'][i], - hudCache['street0_3BChance'][i], hudCache['street0_3BDone'][i], - hudCache['street1Seen'][i], hudCache['street2Seen'][i], hudCache['street3Seen'][i], - hudCache['street4Seen'][i], hudCache['sawShowdown'][i], - hudCache['street1Aggr'][i], hudCache['street2Aggr'][i], hudCache['street3Aggr'][i], hudCache['street4Aggr'][i], - hudCache['otherRaisedStreet1'][i], hudCache['otherRaisedStreet2'][i], - hudCache['otherRaisedStreet3'][i], hudCache['otherRaisedStreet4'][i], - hudCache['foldToOtherRaisedStreet1'][i], hudCache['foldToOtherRaisedStreet2'][i], - hudCache['foldToOtherRaisedStreet3'][i], hudCache['foldToOtherRaisedStreet4'][i], - hudCache['wonWhenSeenStreet1'][i], hudCache['wonAtSD'][i], - hudCache['stealAttemptChance'][i], hudCache['stealAttempted'][i], hudCache['foldBbToStealChance'][i], - hudCache['foldedBbToSteal'][i], hudCache['foldSbToStealChance'][i], hudCache['foldedSbToSteal'][i], - hudCache['street1CBChance'][i], hudCache['street1CBDone'][i], hudCache['street2CBChance'][i], hudCache['street2CBDone'][i], - hudCache['street3CBChance'][i], hudCache['street3CBDone'][i], hudCache['street4CBChance'][i], hudCache['street4CBDone'][i], - hudCache['foldToStreet1CBChance'][i], hudCache['foldToStreet1CBDone'][i], - hudCache['foldToStreet2CBChance'][i], hudCache['foldToStreet2CBDone'][i], - hudCache['foldToStreet3CBChance'][i], hudCache['foldToStreet3CBDone'][i], - hudCache['foldToStreet4CBChance'][i], hudCache['foldToStreet4CBDone'][i], - hudCache['street1CheckCallRaiseChance'][i], hudCache['street1CheckCallRaiseDone'][i], - hudCache['street2CheckCallRaiseChance'][i], hudCache['street2CheckCallRaiseDone'][i], - hudCache['street3CheckCallRaiseChance'][i], hudCache['street3CheckCallRaiseDone'][i], - hudCache['street4CheckCallRaiseChance'][i], hudCache['street4CheckCallRaiseDone'][i], - hudCache['street0Calls'][i], hudCache['street1Calls'][i], hudCache['street2Calls'][i], - hudCache['street3Calls'][i], hudCache['street4Calls'][i], - hudCache['street0Bets'][i], hudCache['street1Bets'][i], hudCache['street2Bets'][i], - hudCache['street3Bets'][i], hudCache['street4Bets'][i] - ) ) - - c = self.get_cursor() - c.executemany (""" - INSERT INTO HandsPlayers - (handId, playerId, startCash, position, tourneyTypeId, - card1, card2, card3, card4, startCards, winnings, rake, tourneysPlayersId, seatNo, totalProfit, - street0VPI, street0Aggr, street0_3BChance, street0_3BDone, - street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown, - street1Aggr, street2Aggr, street3Aggr, street4Aggr, - otherRaisedStreet1, otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4, - foldToOtherRaisedStreet1, foldToOtherRaisedStreet2, foldToOtherRaisedStreet3, foldToOtherRaisedStreet4, - wonWhenSeenStreet1, wonAtSD, - stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, - street1CBChance, street1CBDone, street2CBChance, street2CBDone, - street3CBChance, street3CBDone, street4CBChance, street4CBDone, - foldToStreet1CBChance, foldToStreet1CBDone, foldToStreet2CBChance, foldToStreet2CBDone, - foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance, foldToStreet4CBDone, - street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance, street2CheckCallRaiseDone, - street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone, - street0Calls, street1Calls, street2Calls, street3Calls, street4Calls, - street0Bets, street1Bets, street2Bets, street3Bets, street4Bets - ) - VALUES - (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']) - ,inserts ) - - result.append( self.get_last_insert_id(c) ) - #cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i])) - #result.append(cursor.fetchall()[0][0]) - except: - err = traceback.extract_tb(sys.exc_info()[2])[-1] - print "***Error storing hand: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) - raise FpdbError( "store_hands_players_holdem_omaha_tourney error: " + str(sys.exc_value) ) - - return result - #end def store_hands_players_holdem_omaha_tourney - - def store_hands_players_stud_tourney(self, backend, hands_id, player_ids, start_cashes, - antes, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids, tourneyTypeId): - #stores hands_players for tourney stud/razz hands - return # TODO: stubbed out until someone updates it for current database structuring - try: - result=[] - for i in xrange(len(player_ids)): - c = self.get_cursor() - c.execute ("""INSERT INTO HandsPlayers - (handId, playerId, startCash, ante, - card1Value, card1Suit, card2Value, card2Suit, - card3Value, card3Suit, card4Value, card4Suit, - card5Value, card5Suit, card6Value, card6Suit, - card7Value, card7Suit, winnings, rake, tourneysPlayersId, seatNo, tourneyTypeId) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']), - (hands_id, player_ids[i], start_cashes[i], antes[i], - card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], - card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], - card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5], - card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i], tourneyTypeId)) - #cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId+0=%s", (hands_id, player_ids[i])) - #result.append(cursor.fetchall()[0][0]) - result.append( self.get_last_insert_id(c) ) - except: - raise FpdbError( "store_hands_players_stud_tourney error: " + str(sys.exc_value) ) - - return result - #end def store_hands_players_stud_tourney - - def storeHudCache(self, backend, base, category, gametypeId, hand_start_time, playerIds, hudImportData): - """Update cached statistics. If update fails because no record exists, do an insert. - Can't use array updates here (not easily anyway) because we need to insert any rows - that don't get updated.""" - - # if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - try: - if self.use_date_in_hudcache: - #print "key =", "d%02d%02d%02d " % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day) - styleKey = "d%02d%02d%02d" % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day) - else: - # hard-code styleKey as 'A000000' (all-time cache, no key) for now - styleKey = 'A000000' - - #print "storeHudCache, len(playerIds)=", len(playerIds), " len(vpip)=" \ - #, len(hudImportData['street0VPI']), " len(totprof)=", len(hudImportData['totalProfit']) - for player in xrange(len(playerIds)): - - # Set up a clean row - row=[] - row.append(0)#blank for id - row.append(gametypeId) - row.append(playerIds[player]) - row.append(len(playerIds))#seats - for i in xrange(len(hudImportData)+2): - row.append(0) - - if base=="hold": - row[4]=hudImportData['position'][player] - else: - row[4]=0 - row[5]=1 #tourneysGametypeId - row[6]+=1 #HDs - if hudImportData['street0VPI'][player]: row[7]+=1 - if hudImportData['street0Aggr'][player]: row[8]+=1 - if hudImportData['street0_3BChance'][player]: row[9]+=1 - if hudImportData['street0_3BDone'][player]: row[10]+=1 - if hudImportData['street1Seen'][player]: row[11]+=1 - if hudImportData['street2Seen'][player]: row[12]+=1 - if hudImportData['street3Seen'][player]: row[13]+=1 - if hudImportData['street4Seen'][player]: row[14]+=1 - if hudImportData['sawShowdown'][player]: row[15]+=1 - if hudImportData['street1Aggr'][player]: row[16]+=1 - if hudImportData['street2Aggr'][player]: row[17]+=1 - if hudImportData['street3Aggr'][player]: row[18]+=1 - if hudImportData['street4Aggr'][player]: row[19]+=1 - if hudImportData['otherRaisedStreet1'][player]: row[20]+=1 - if hudImportData['otherRaisedStreet2'][player]: row[21]+=1 - if hudImportData['otherRaisedStreet3'][player]: row[22]+=1 - if hudImportData['otherRaisedStreet4'][player]: row[23]+=1 - if hudImportData['foldToOtherRaisedStreet1'][player]: row[24]+=1 - if hudImportData['foldToOtherRaisedStreet2'][player]: row[25]+=1 - if hudImportData['foldToOtherRaisedStreet3'][player]: row[26]+=1 - if hudImportData['foldToOtherRaisedStreet4'][player]: row[27]+=1 - if hudImportData['wonWhenSeenStreet1'][player]!=0.0: row[28]+=hudImportData['wonWhenSeenStreet1'][player] - if hudImportData['wonAtSD'][player]!=0.0: row[29]+=hudImportData['wonAtSD'][player] - if hudImportData['stealAttemptChance'][player]: row[30]+=1 - if hudImportData['stealAttempted'][player]: row[31]+=1 - if hudImportData['foldBbToStealChance'][player]: row[32]+=1 - if hudImportData['foldedBbToSteal'][player]: row[33]+=1 - if hudImportData['foldSbToStealChance'][player]: row[34]+=1 - if hudImportData['foldedSbToSteal'][player]: row[35]+=1 - - if hudImportData['street1CBChance'][player]: row[36]+=1 - if hudImportData['street1CBDone'][player]: row[37]+=1 - if hudImportData['street2CBChance'][player]: row[38]+=1 - if hudImportData['street2CBDone'][player]: row[39]+=1 - if hudImportData['street3CBChance'][player]: row[40]+=1 - if hudImportData['street3CBDone'][player]: row[41]+=1 - if hudImportData['street4CBChance'][player]: row[42]+=1 - if hudImportData['street4CBDone'][player]: row[43]+=1 - - if hudImportData['foldToStreet1CBChance'][player]: row[44]+=1 - if hudImportData['foldToStreet1CBDone'][player]: row[45]+=1 - if hudImportData['foldToStreet2CBChance'][player]: row[46]+=1 - if hudImportData['foldToStreet2CBDone'][player]: row[47]+=1 - if hudImportData['foldToStreet3CBChance'][player]: row[48]+=1 - if hudImportData['foldToStreet3CBDone'][player]: row[49]+=1 - if hudImportData['foldToStreet4CBChance'][player]: row[50]+=1 - if hudImportData['foldToStreet4CBDone'][player]: row[51]+=1 - - #print "player=", player - #print "len(totalProfit)=", len(hudImportData['totalProfit']) - if hudImportData['totalProfit'][player]: - row[52]+=hudImportData['totalProfit'][player] - - if hudImportData['street1CheckCallRaiseChance'][player]: row[53]+=1 - if hudImportData['street1CheckCallRaiseDone'][player]: row[54]+=1 - if hudImportData['street2CheckCallRaiseChance'][player]: row[55]+=1 - if hudImportData['street2CheckCallRaiseDone'][player]: row[56]+=1 - if hudImportData['street3CheckCallRaiseChance'][player]: row[57]+=1 - if hudImportData['street3CheckCallRaiseDone'][player]: row[58]+=1 - if hudImportData['street4CheckCallRaiseChance'][player]: row[59]+=1 - if hudImportData['street4CheckCallRaiseDone'][player]: row[60]+=1 - - # Try to do the update first: - cursor = self.get_cursor() - num = cursor.execute("""UPDATE HudCache - SET HDs=HDs+%s, street0VPI=street0VPI+%s, street0Aggr=street0Aggr+%s, - street0_3BChance=street0_3BChance+%s, street0_3BDone=street0_3BDone+%s, - street1Seen=street1Seen+%s, street2Seen=street2Seen+%s, street3Seen=street3Seen+%s, - street4Seen=street4Seen+%s, sawShowdown=sawShowdown+%s, - street1Aggr=street1Aggr+%s, street2Aggr=street2Aggr+%s, street3Aggr=street3Aggr+%s, - street4Aggr=street4Aggr+%s, otherRaisedStreet1=otherRaisedStreet1+%s, - otherRaisedStreet2=otherRaisedStreet2+%s, otherRaisedStreet3=otherRaisedStreet3+%s, - otherRaisedStreet4=otherRaisedStreet4+%s, - foldToOtherRaisedStreet1=foldToOtherRaisedStreet1+%s, foldToOtherRaisedStreet2=foldToOtherRaisedStreet2+%s, - foldToOtherRaisedStreet3=foldToOtherRaisedStreet3+%s, foldToOtherRaisedStreet4=foldToOtherRaisedStreet4+%s, - wonWhenSeenStreet1=wonWhenSeenStreet1+%s, wonAtSD=wonAtSD+%s, stealAttemptChance=stealAttemptChance+%s, - stealAttempted=stealAttempted+%s, foldBbToStealChance=foldBbToStealChance+%s, - foldedBbToSteal=foldedBbToSteal+%s, - foldSbToStealChance=foldSbToStealChance+%s, foldedSbToSteal=foldedSbToSteal+%s, - street1CBChance=street1CBChance+%s, street1CBDone=street1CBDone+%s, street2CBChance=street2CBChance+%s, - street2CBDone=street2CBDone+%s, street3CBChance=street3CBChance+%s, - street3CBDone=street3CBDone+%s, street4CBChance=street4CBChance+%s, street4CBDone=street4CBDone+%s, - foldToStreet1CBChance=foldToStreet1CBChance+%s, foldToStreet1CBDone=foldToStreet1CBDone+%s, - foldToStreet2CBChance=foldToStreet2CBChance+%s, foldToStreet2CBDone=foldToStreet2CBDone+%s, - foldToStreet3CBChance=foldToStreet3CBChance+%s, - foldToStreet3CBDone=foldToStreet3CBDone+%s, foldToStreet4CBChance=foldToStreet4CBChance+%s, - foldToStreet4CBDone=foldToStreet4CBDone+%s, totalProfit=totalProfit+%s, - street1CheckCallRaiseChance=street1CheckCallRaiseChance+%s, - street1CheckCallRaiseDone=street1CheckCallRaiseDone+%s, street2CheckCallRaiseChance=street2CheckCallRaiseChance+%s, - street2CheckCallRaiseDone=street2CheckCallRaiseDone+%s, street3CheckCallRaiseChance=street3CheckCallRaiseChance+%s, - street3CheckCallRaiseDone=street3CheckCallRaiseDone+%s, street4CheckCallRaiseChance=street4CheckCallRaiseChance+%s, - street4CheckCallRaiseDone=street4CheckCallRaiseDone+%s - WHERE gametypeId+0=%s - AND playerId=%s - AND activeSeats=%s - AND position=%s - AND tourneyTypeId+0=%s - AND styleKey=%s - """.replace('%s', self.sql.query['placeholder']) - ,(row[6], row[7], row[8], row[9], row[10], - row[11], row[12], row[13], row[14], row[15], - row[16], row[17], row[18], row[19], row[20], - row[21], row[22], row[23], row[24], row[25], - row[26], row[27], row[28], row[29], row[30], - row[31], row[32], row[33], row[34], row[35], - row[36], row[37], row[38], row[39], row[40], - row[41], row[42], row[43], row[44], row[45], - row[46], row[47], row[48], row[49], row[50], - row[51], row[52], row[53], row[54], row[55], - row[56], row[57], row[58], row[59], row[60], - row[1], row[2], row[3], str(row[4]), row[5], styleKey)) - # Test statusmessage to see if update worked, do insert if not - #print "storehud2, upd num =", num.rowcount - # num is a cursor in sqlite - if ( (backend == self.PGSQL and cursor.statusmessage != "UPDATE 1") - or (backend == self.MYSQL_INNODB and num == 0) - or (backend == self.SQLITE and num.rowcount == 0) - ): - #print "playerid before insert:",row[2]," num = ", num - num = cursor.execute("""INSERT INTO HudCache - (gametypeId, playerId, activeSeats, position, tourneyTypeId, styleKey, - HDs, street0VPI, street0Aggr, street0_3BChance, street0_3BDone, - street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown, - street1Aggr, street2Aggr, street3Aggr, street4Aggr, otherRaisedStreet1, - otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4, foldToOtherRaisedStreet1, foldToOtherRaisedStreet2, - foldToOtherRaisedStreet3, foldToOtherRaisedStreet4, wonWhenSeenStreet1, wonAtSD, stealAttemptChance, - stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, - street1CBChance, street1CBDone, street2CBChance, street2CBDone, street3CBChance, - street3CBDone, street4CBChance, street4CBDone, foldToStreet1CBChance, foldToStreet1CBDone, - foldToStreet2CBChance, foldToStreet2CBDone, foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance, - foldToStreet4CBDone, totalProfit, street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance, - street2CheckCallRaiseDone, street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone) - VALUES (%s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']) - , (row[1], row[2], row[3], row[4], row[5], styleKey, row[6], row[7], row[8], row[9], row[10] - ,row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20] - ,row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30] - ,row[31], row[32], row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40] - ,row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50] - ,row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60]) ) - #print "hopefully inserted hud data line: ", cursor.rowcount - # message seems to be "INSERT 0 1" - else: - #print "updated(2) hud data line" - pass - # else: - # print "todo: implement storeHudCache for stud base" - - except: - raise FpdbError( "storeHudCache error: " + str(sys.exc_value) ) - - #end def storeHudCache - - def store_tourneys(self, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime): - ret = -1 - try: - # try and create tourney record, fetch id if it already exists - # avoids race condition when doing the select first - cursor = self.get_cursor() - cursor.execute("savepoint ins_tourney") - cursor.execute("""INSERT INTO Tourneys - (tourneyTypeId, siteTourneyNo, entries, prizepool, startTime) - VALUES (%s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder']) - ,(tourneyTypeId, siteTourneyNo, entries, prizepool, startTime)) - ret = self.get_last_insert_id(cursor) - #print "created new tourneys.id:",ret - except: - #if str(sys.exc_value) contains 'sitetourneyno': - # raise FpdbError( "store_tourneys error: " + str(sys.exc_value) ) - #else: - #print "error insert tourney (%s) trying select ..." % (str(sys.exc_value),) - cursor.execute("rollback to savepoint ins_tourney") - try: - cursor.execute( "SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND tourneyTypeId+0=%s".replace('%s', self.sql.query['placeholder']) - , (siteTourneyNo, tourneyTypeId) ) - rec = cursor.fetchone() - #print "select tourney result: ", rec - try: - len(rec) - ret = rec[0] - except: - print "Tourney id not found" - except: - print "Error selecting tourney id:", str(sys.exc_info()[1]) - - cursor.execute("release savepoint ins_tourney") - #print "store_tourneys returning", ret - return ret - #end def store_tourneys - def store_tourneys_players(self, tourney_id, player_ids, payin_amounts, ranks, winnings): try: result=[] @@ -2531,7 +1931,7 @@ class Database: # end def send_finish_msg(): def tRecogniseTourneyType(self, tourney): - logging.debug("Database.tRecogniseTourneyType") + log.debug("Database.tRecogniseTourneyType") typeId = 1 # Check if Tourney exists, and if so retrieve TTypeId : in that case, check values of the ttype cursor = self.get_cursor() @@ -2547,10 +1947,10 @@ class Database: try: len(result) typeId = result[0] - logging.debug("Tourney found in db with Tourney_Type_ID = %d" % typeId) + log.debug("Tourney found in db with Tourney_Type_ID = %d" % typeId) for ev in expectedValues : if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ): - logging.debug("TypeId mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) ) + log.debug("TypeId mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) ) typeIdMatch = False #break except: @@ -2560,7 +1960,7 @@ class Database: if typeIdMatch == False : # Check for an existing TTypeId that matches tourney info (buyin/fee, knockout, rebuy, speed, matrix, shootout) # if not found create it - logging.debug("Searching for a TourneyTypeId matching TourneyType data") + log.debug("Searching for a TourneyTypeId matching TourneyType data") cursor.execute (self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']), (tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO, tourney.isRebuy, tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix) @@ -2570,9 +1970,9 @@ class Database: try: len(result) typeId = result[0] - logging.debug("Existing Tourney Type Id found : %d" % typeId) + log.debug("Existing Tourney Type Id found : %d" % typeId) except TypeError: #this means we need to create a new entry - logging.debug("Tourney Type Id not found : create one") + log.debug("Tourney Type Id not found : create one") cursor.execute (self.sql.query['insertTourneyTypes'].replace('%s', self.sql.query['placeholder']), (tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO, tourney.isRebuy, tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix) @@ -2583,183 +1983,6 @@ class Database: #end def tRecogniseTourneyType - def tRecognizeTourney(self, tourney, dbTourneyTypeId): - logging.debug("Database.tRecognizeTourney") - tourneyID = 1 - # Check if tourney exists in db (based on tourney.siteId and tourney.tourNo) - # If so retrieve all data to check for consistency - cursor = self.get_cursor() - cursor.execute (self.sql.query['getTourney'].replace('%s', self.sql.query['placeholder']), - (tourney.tourNo, tourney.siteId) - ) - result=cursor.fetchone() - - expectedValuesDecimal = { 2 : "entries", 3 : "prizepool", 6 : "buyInChips", 9 : "rebuyChips", - 10 : "addOnChips", 11 : "rebuyAmount", 12 : "addOnAmount", 13 : "totalRebuys", - 14 : "totalAddOns", 15 : "koBounty" } - expectedValues = { 7 : "tourneyName", 16 : "tourneyComment" } - - tourneyDataMatch = True - tCommentTs = None - starttime = None - endtime = None - - try: - len(result) - tourneyID = result[0] - logging.debug("Tourney found in db with TourneyID = %d" % tourneyID) - if result[1] <> dbTourneyTypeId: - tourneyDataMatch = False - logging.debug("Tourney has wrong type ID (expected : %s - found : %s)" % (dbTourneyTypeId, result[1])) - if (tourney.starttime is None and result[4] is not None) or ( tourney.starttime is not None and fpdb_simple.parseHandStartTime("- %s" % tourney.starttime) <> result[4]) : - tourneyDataMatch = False - logging.debug("Tourney data mismatch : wrong starttime : Tourney=%s / db=%s" % (tourney.starttime, result[4])) - if (tourney.endtime is None and result[5] is not None) or ( tourney.endtime is not None and fpdb_simple.parseHandStartTime("- %s" % tourney.endtime) <> result[5]) : - tourneyDataMatch = False - logging.debug("Tourney data mismatch : wrong endtime : Tourney=%s / db=%s" % (tourney.endtime, result[5])) - - for ev in expectedValues : - if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ): - logging.debug("Tourney data mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) ) - tourneyDataMatch = False - #break - for evD in expectedValuesDecimal : - if ( Decimal(getattr( tourney, expectedValuesDecimal.get(evD)) ) <> result[evD] ): - logging.debug("Tourney data mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValuesDecimal.get(evD), getattr( tourney, expectedValuesDecimal.get(evD)), result[evD]) ) - tourneyDataMatch = False - #break - - # TO DO : Deal with matrix summary mutliple parsings - - except: - # Tourney not found : create - logging.debug("Tourney is not found : create") - if tourney.tourneyComment is not None : - tCommentTs = datetime.today() - if tourney.starttime is not None : - starttime = fpdb_simple.parseHandStartTime("- %s" % tourney.starttime) - if tourney.endtime is not None : - endtime = fpdb_simple.parseHandStartTime("- %s" % tourney.endtime) - # TODO : deal with matrix Id processed - cursor.execute (self.sql.query['insertTourney'].replace('%s', self.sql.query['placeholder']), - (dbTourneyTypeId, tourney.tourNo, tourney.entries, tourney.prizepool, starttime, - endtime, tourney.buyInChips, tourney.tourneyName, 0, tourney.rebuyChips, tourney.addOnChips, - tourney.rebuyAmount, tourney.addOnAmount, tourney.totalRebuys, tourney.totalAddOns, tourney.koBounty, - tourney.tourneyComment, tCommentTs) - ) - tourneyID = self.get_last_insert_id(cursor) - - - # Deal with inconsistent tourney in db - if tourneyDataMatch == False : - # Update Tourney - if result[16] <> tourney.tourneyComment : - tCommentTs = datetime.today() - if tourney.starttime is not None : - starttime = fpdb_simple.parseHandStartTime("- %s" % tourney.starttime) - if tourney.endtime is not None : - endtime = fpdb_simple.parseHandStartTime("- %s" % tourney.endtime) - - cursor.execute (self.sql.query['updateTourney'].replace('%s', self.sql.query['placeholder']), - (dbTourneyTypeId, tourney.entries, tourney.prizepool, starttime, - endtime, tourney.buyInChips, tourney.tourneyName, 0, tourney.rebuyChips, tourney.addOnChips, - tourney.rebuyAmount, tourney.addOnAmount, tourney.totalRebuys, tourney.totalAddOns, tourney.koBounty, - tourney.tourneyComment, tCommentTs, tourneyID) - ) - - return tourneyID - #end def tRecognizeTourney - - def tStoreTourneyPlayers(self, tourney, dbTourneyId): - logging.debug("Database.tStoreTourneyPlayers") - # First, get playerids for the players and specifically the one for hero : - playersIds = self.recognisePlayerIDs(tourney.players, tourney.siteId) - # hero may be None for matrix tourneys summaries -# hero = [ tourney.hero ] -# heroId = self.recognisePlayerIDs(hero , tourney.siteId) -# logging.debug("hero Id = %s - playersId = %s" % (heroId , playersIds)) - - tourneyPlayersIds=[] - try: - cursor = self.get_cursor() - - for i in xrange(len(playersIds)): - cursor.execute(self.sql.query['getTourneysPlayers'].replace('%s', self.sql.query['placeholder']) - ,(dbTourneyId, playersIds[i])) - result=cursor.fetchone() - #print "tried SELECTing tourneys_players.id:",tmp - - try: - len(result) - # checking data - logging.debug("TourneysPlayers found : checking data") - expectedValuesDecimal = { 1 : "payinAmounts", 2 : "finishPositions", 3 : "winnings", 4 : "countRebuys", - 5 : "countAddOns", 6 : "countKO" } - - tourneyPlayersIds.append(result[0]); - - tourneysPlayersDataMatch = True - for evD in expectedValuesDecimal : - if ( Decimal(getattr( tourney, expectedValuesDecimal.get(evD))[tourney.players[i]] ) <> result[evD] ): - logging.debug("TourneysPlayers data mismatch for TourneysPlayer id=%d, name=%s : wrong %s : Tourney=%s / db=%s" % (result[0], tourney.players[i], expectedValuesDecimal.get(evD), getattr( tourney, expectedValuesDecimal.get(evD))[tourney.players[i]], result[evD]) ) - tourneysPlayersDataMatch = False - #break - - if tourneysPlayersDataMatch == False: - logging.debug("TourneysPlayers data update needed") - cursor.execute (self.sql.query['updateTourneysPlayers'].replace('%s', self.sql.query['placeholder']), - (tourney.payinAmounts[tourney.players[i]], tourney.finishPositions[tourney.players[i]], - tourney.winnings[tourney.players[i]] , tourney.countRebuys[tourney.players[i]], - tourney.countAddOns[tourney.players[i]] , tourney.countKO[tourney.players[i]], - result[7], result[8], result[0]) - ) - - except TypeError: - logging.debug("TourneysPlayers not found : need insert") - cursor.execute (self.sql.query['insertTourneysPlayers'].replace('%s', self.sql.query['placeholder']), - (dbTourneyId, playersIds[i], - tourney.payinAmounts[tourney.players[i]], tourney.finishPositions[tourney.players[i]], - tourney.winnings[tourney.players[i]] , tourney.countRebuys[tourney.players[i]], - tourney.countAddOns[tourney.players[i]] , tourney.countKO[tourney.players[i]], - None, None) - ) - tourneyPlayersIds.append(self.get_last_insert_id(cursor)) - - except: - raise fpdb_simple.FpdbError( "tStoreTourneyPlayers error: " + str(sys.exc_value) ) - - return tourneyPlayersIds - #end def tStoreTourneyPlayers - - def tUpdateTourneysHandsPlayers(self, tourney, dbTourneysPlayersIds, dbTourneyTypeId): - logging.debug("Database.tCheckTourneysHandsPlayers") - try: - # Massive update seems to take quite some time ... -# query = self.sql.query['updateHandsPlayersForTTypeId2'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner'].join([self.sql.query['placeholder'] for id in dbTourneysPlayersIds]) ) -# cursor = self.get_cursor() -# cursor.execute (query, dbTourneysPlayersIds) - - query = self.sql.query['selectHandsPlayersWithWrongTTypeId'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner'].join([self.sql.query['placeholder'] for id in dbTourneysPlayersIds]) ) - #print "query : %s" % query - cursor = self.get_cursor() - cursor.execute (query, dbTourneysPlayersIds) - result=cursor.fetchall() - - if (len(result) > 0): - logging.debug("%d lines need update : %s" % (len(result), result) ) - listIds = [] - for i in result: - listIds.append(i[0]) - - query2 = self.sql.query['updateHandsPlayersForTTypeId'] % (dbTourneyTypeId, self.sql.query['handsPlayersTTypeId_joiner_id'].join([self.sql.query['placeholder'] for id in listIds]) ) - cursor.execute (query2, listIds) - else: - logging.debug("No need to update, HandsPlayers are correct") - - except: - raise fpdb_simple.FpdbError( "tStoreTourneyPlayers error: " + str(sys.exc_value) ) - #end def tUpdateTourneysHandsPlayers - # Class used to hold all the data needed to write a hand to the db # mainParser() in fpdb_parse_logic.py creates one of these and then passes it to diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index b65b0d05..d7ded83b 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -48,32 +48,33 @@ class DerivedStats(): self.handsplayers[player[1]]['sawShowdown'] = False self.handsplayers[player[1]]['wonAtSD'] = 0.0 self.handsplayers[player[1]]['startCards'] = 0 + self.handsplayers[player[1]]['position'] = 2 + self.handsplayers[player[1]]['street0_3BChance'] = False + self.handsplayers[player[1]]['street0_3BDone'] = False + self.handsplayers[player[1]]['street0_4BChance'] = False + self.handsplayers[player[1]]['street0_4BDone'] = False + self.handsplayers[player[1]]['stealAttemptChance'] = False + self.handsplayers[player[1]]['stealAttempted'] = False + self.handsplayers[player[1]]['foldBbToStealChance'] = False + self.handsplayers[player[1]]['foldSbToStealChance'] = False + self.handsplayers[player[1]]['foldedSbToSteal'] = False + self.handsplayers[player[1]]['foldedBbToSteal'] = False for i in range(5): self.handsplayers[player[1]]['street%dCalls' % i] = 0 self.handsplayers[player[1]]['street%dBets' % i] = 0 for i in range(1,5): self.handsplayers[player[1]]['street%dCBChance' %i] = False self.handsplayers[player[1]]['street%dCBDone' %i] = False + self.handsplayers[player[1]]['street%dCheckCallRaiseChance' %i] = False + self.handsplayers[player[1]]['street%dCheckCallRaiseDone' %i] = False #FIXME - Everything below this point is incomplete. - self.handsplayers[player[1]]['position'] = 2 self.handsplayers[player[1]]['tourneyTypeId'] = 1 - self.handsplayers[player[1]]['street0_3BChance'] = False - self.handsplayers[player[1]]['street0_3BDone'] = False - self.handsplayers[player[1]]['stealAttemptChance'] = False - self.handsplayers[player[1]]['stealAttempted'] = False - self.handsplayers[player[1]]['foldBbToStealChance'] = False - self.handsplayers[player[1]]['foldBbToStealChance'] = False - self.handsplayers[player[1]]['foldSbToStealChance'] = False - self.handsplayers[player[1]]['foldedSbToSteal'] = False - self.handsplayers[player[1]]['foldedBbToSteal'] = False for i in range(1,5): self.handsplayers[player[1]]['otherRaisedStreet%d' %i] = False self.handsplayers[player[1]]['foldToOtherRaisedStreet%d' %i] = False self.handsplayers[player[1]]['foldToStreet%dCBChance' %i] = False self.handsplayers[player[1]]['foldToStreet%dCBDone' %i] = False - self.handsplayers[player[1]]['street%dCheckCallRaiseChance' %i] = False - self.handsplayers[player[1]]['street%dCheckCallRaiseDone' %i] = False self.assembleHands(self.hand) self.assembleHandsPlayers(self.hand) @@ -174,33 +175,62 @@ class DerivedStats(): self.handsplayers[player[1]]['card%s' % (i+1)] = Card.encodeCard(card) self.handsplayers[player[1]]['startCards'] = Card.calcStartCards(hand, player[1]) - # position, - #Stud 3rd street card test - # denny501: brings in for $0.02 - # s0rrow: calls $0.02 - # TomSludge: folds - # Soroka69: calls $0.02 - # rdiezchang: calls $0.02 (Seat 8) - # u.pressure: folds (Seat 1) - # 123smoothie: calls $0.02 - # gashpor: calls $0.02 - + self.setPositions(hand) + self.calcCheckCallRaise(hand) + self.calc34BetStreet0(hand) + self.calcSteals(hand) # Additional stats # 3betSB, 3betBB # Squeeze, Ratchet? - def getPosition(hand, seat): - """Returns position value like 'B', 'S', 0, 1, ...""" - # Flop/Draw games with blinds - # Need a better system??? - # -2 BB - B (all) - # -1 SB - S (all) - # 0 Button - # 1 Cutoff - # 2 Hijack + def setPositions(self, hand): + """Sets the position for each player in HandsPlayers + any blinds are negative values, and the last person to act on the + first betting round is 0 + NOTE: HU, both values are negative for non-stud games + NOTE2: I've never seen a HU stud match""" + # The position calculation must be done differently for Stud and other games as + # Stud the 'blind' acts first - in all other games they act last. + # + #This function is going to get it wrong when there in situations where there + # is no small blind. I can live with that. + actions = hand.actions[hand.holeStreets[0]] + # Note: pfbao list may not include big blind if all others folded + players = self.pfbao(actions) + + if hand.gametype['base'] == 'stud': + positions = [7, 6, 5, 4, 3, 2, 1, 0, 'S', 'B'] + seats = len(players) + map = [] + # Could posibly change this to be either -2 or -1 depending if they complete or bring-in + # First player to act is -1, last player is 0 for 6 players it should look like: + # ['S', 4, 3, 2, 1, 0] + map = positions[-seats-1:-1] # Copy required positions from postions array anding in -1 + map = map[-1:] + map[0:-1] # and move the -1 to the start of that array + + for i, player in enumerate(players): + #print "player %s in posn %s" % (player, str(map[i])) + self.handsplayers[player]['position'] = map[i] + else: + # set blinds first, then others from pfbao list, avoids problem if bb + # is missing from pfbao list or if there is no small blind + bb = [x[0] for x in hand.actions[hand.actionStreets[0]] if x[2] == 'big blind'] + sb = [x[0] for x in hand.actions[hand.actionStreets[0]] if x[2] == 'small blind'] + # if there are > 1 sb or bb only the first is used! + if bb: + self.handsplayers[bb[0]]['position'] = 'B' + if bb[0] in players: players.remove(bb[0]) + if sb: + self.handsplayers[sb[0]]['position'] = 'S' + if sb[0] in players: players.remove(sb[0]) + + #print "bb =", bb, "sb =", sb, "players =", players + for i,player in enumerate(reversed(players)): + self.handsplayers[player]['position'] = str(i) def assembleHudCache(self, hand): + # No real work to be done - HandsPlayers data already contains the correct info pass def vpip(self, hand): @@ -224,6 +254,7 @@ class DerivedStats(): # The number of unique players in the list per street gives the value for playersAtStreetXXX # FIXME?? - This isn't couting people that are all in - at least showdown needs to reflect this + # ... new code below hopefully fixes this self.hands['playersAtStreet1'] = 0 self.hands['playersAtStreet2'] = 0 @@ -231,23 +262,31 @@ class DerivedStats(): self.hands['playersAtStreet4'] = 0 self.hands['playersAtShowdown'] = 0 - alliners = set() - for (i, street) in enumerate(hand.actionStreets[2:]): - actors = set() - for action in hand.actions[street]: - if len(action) > 2 and action[-1]: # allin - alliners.add(action[0]) - actors.add(action[0]) - if len(actors)==0 and len(alliners)<2: - alliners = set() - self.hands['playersAtStreet%d' % (i+1)] = len(set.union(alliners, actors)) - - actions = hand.actions[hand.actionStreets[-1]] - pas = set.union(self.pfba(actions) - self.pfba(actions, l=('folds',)), alliners) - self.hands['playersAtShowdown'] = len(pas) +# alliners = set() +# for (i, street) in enumerate(hand.actionStreets[2:]): +# actors = set() +# for action in hand.actions[street]: +# if len(action) > 2 and action[-1]: # allin +# alliners.add(action[0]) +# actors.add(action[0]) +# if len(actors)==0 and len(alliners)<2: +# alliners = set() +# self.hands['playersAtStreet%d' % (i+1)] = len(set.union(alliners, actors)) +# +# actions = hand.actions[hand.actionStreets[-1]] +# print "p_actions:", self.pfba(actions), "p_folds:", self.pfba(actions, l=('folds',)), "alliners:", alliners +# pas = set.union(self.pfba(actions) - self.pfba(actions, l=('folds',)), alliners) + + p_in = set(x[1] for x in hand.players) + for (i, street) in enumerate(hand.actionStreets): + 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 self.hands['playersAtShowdown'] > 1: - for player in pas: + for player in p_in: self.handsplayers[player]['sawShowdown'] = True def streetXRaises(self, hand): @@ -262,13 +301,65 @@ class DerivedStats(): for (i, street) in enumerate(hand.actionStreets[1:]): self.hands['street%dRaises' % i] = len(filter( lambda action: action[1] in ('raises','bets'), hand.actions[street])) - def calcCBets(self, hand): - # Continuation Bet chance, action: - # Had the last bet (initiative) on previous street, got called, close street action - # Then no bets before the player with initiatives first action on current street - # ie. if player on street-1 had initiative - # and no donkbets occurred + def calcSteals(self, hand): + """Fills stealAttempt(Chance|ed, fold(Bb|Sb)ToSteal(Chance|) + Steal attempt - open raise on positions 1 0 S - i.e. MP3, CO, BU, SB + Fold to steal - folding blind after steal attemp wo any other callers or raisers + """ + steal_attempt = False + steal_positions = ('1', '0', 'S') + if hand.gametype['base'] == 'stud': + steal_positions = ('2', '1', '0') + for action in hand.actions[hand.actionStreets[1]]: + pname, act = action[0], action[1] + posn = self.handsplayers[pname]['position'] + #print "\naction:", action[0], posn, type(posn), steal_attempt, act + if posn == 'B': + #NOTE: Stud games will never hit this section + self.handsplayers[pname]['foldBbToStealChance'] = steal_attempt + self.handsplayers[pname]['foldedBbToSteal'] = steal_attempt and act == 'folds' + break + elif posn == 'S': + self.handsplayers[pname]['foldSbToStealChance'] = steal_attempt + self.handsplayers[pname]['foldedSbToSteal'] = steal_attempt and act == 'folds' + + if steal_attempt and act != 'folds': + break + + if posn in steal_positions and not steal_attempt: + self.handsplayers[pname]['stealAttemptChance'] = True + if act in ('bets', 'raises'): + self.handsplayers[pname]['stealAttempted'] = True + steal_attempt = True + if act == 'calls': + break + + if posn not in steal_positions and act != 'folds': + break + + def calc34BetStreet0(self, hand): + """Fills street0_(3|4)B(Chance|Done), other(3|4)BStreet0""" + bet_level = 1 # bet_level after 3-bet is equal to 3 + for action in hand.actions[hand.actionStreets[1]]: + # FIXME: fill other(3|4)BStreet0 - i have no idea what does it mean + pname, aggr = action[0], action[1] in ('raises', 'bets') + self.handsplayers[pname]['street0_3BChance'] = bet_level == 2 + self.handsplayers[pname]['street0_4BChance'] = bet_level == 3 + self.handsplayers[pname]['street0_3BDone'] = aggr and (self.handsplayers[pname]['street0_3BChance']) + self.handsplayers[pname]['street0_4BDone'] = aggr and (self.handsplayers[pname]['street0_4BChance']) + if aggr: + bet_level += 1 + + + def calcCBets(self, hand): + """Fill streetXCBChance, streetXCBDone, foldToStreetXCBDone, foldToStreetXCBChance + + Continuation Bet chance, action: + Had the last bet (initiative) on previous street, got called, close street action + Then no bets before the player with initiatives first action on current street + ie. if player on street-1 had initiative and no donkbets occurred + """ # XXX: enumerate(list, start=x) is python 2.6 syntax; 'start' # came there #for i, street in enumerate(hand.actionStreets[2:], start=1): @@ -280,6 +371,29 @@ class DerivedStats(): if chance == True: self.handsplayers[name]['street%dCBDone' % (i+1)] = self.betStreet(hand.actionStreets[i+2], name) + def calcCheckCallRaise(self, hand): + """Fill streetXCheckCallRaiseChance, streetXCheckCallRaiseDone + + streetXCheckCallRaiseChance = got raise/bet after check + streetXCheckCallRaiseDone = checked. got raise/bet. didn't fold + + CG: CheckCall would be a much better name for this. + """ + #for i, street in enumerate(hand.actionStreets[2:], start=1): + for i, street in enumerate(hand.actionStreets[2:]): + actions = hand.actions[hand.actionStreets[i+1]] + checkers = set() + initial_raiser = None + for action in actions: + pname, act = action[0], action[1] + if act in ('bets', 'raises') and initial_raiser is None: + initial_raiser = pname + elif act == 'checks' and initial_raiser is None: + checkers.add(pname) + elif initial_raiser is not None and pname in checkers: + 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]]: @@ -293,11 +407,13 @@ class DerivedStats(): def aggr(self, hand, i): aggrers = set() - for act in hand.actions[hand.actionStreets[i]]: - if act[1] in ('completes', 'raises'): + # Growl - actionStreets contains 'BLINDSANTES', which isn't actually an action street + for act in hand.actions[hand.actionStreets[i+1]]: + if act[1] in ('completes', 'bets', 'raises'): aggrers.add(act[0]) for player in hand.players: + #print "DEBUG: actionStreet[%s]: %s" %(hand.actionStreets[i+1], i) if player[1] in aggrers: self.handsplayers[player[1]]['street%sAggr' % i] = True else: @@ -333,6 +449,44 @@ class DerivedStats(): players.add(action[0]) return players + def pfbao(self, actions, f=None, l=None, unique=True): + """Helper method. Returns set of PlayersFilteredByActionsOrdered + + f - forbidden actions + l - limited to actions + """ + # Note, this is an adaptation of function 5 from: + # http://www.peterbe.com/plog/uniqifiers-benchmark + seen = {} + players = [] + for action in actions: + if l is not None and action[1] not in l: continue + if f is not None and action[1] in f: continue + if action[0] in seen and unique: continue + seen[action[0]] = 1 + players.append(action[0]) + return players + + def firstsBetOrRaiser(self, actions): + """Returns player name that placed the first bet or raise. + + None if there were no bets or raises on that street + """ + for act in actions: + if act[1] in ('bets', 'raises'): + return act[0] + return None + + def lastBetOrRaiser(self, street): + """Returns player name that placed the last bet or raise for that street. + None if there were no bets or raises on that street""" + lastbet = None + for act in self.hand.actions[street]: + if act[1] in ('bets', 'raises'): + lastbet = act[0] + return lastbet + + def noBetsBefore(self, street, player): """Returns true if there were no bets before the specified players turn, false otherwise""" betOrRaise = False @@ -345,6 +499,7 @@ class DerivedStats(): break return betOrRaise + def betStreet(self, street, player): """Returns true if player bet/raised the street as their first action""" betOrRaise = False @@ -355,12 +510,3 @@ class DerivedStats(): break return betOrRaise - - def lastBetOrRaiser(self, street): - """Returns player name that placed the last bet or raise for that street. - None if there were no bets or raises on that street""" - lastbet = None - for act in self.hand.actions[street]: - if act[1] in ('bets', 'raises'): - lastbet = act[0] - return lastbet diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index fcd76c25..5dd8d041 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -2,12 +2,12 @@ # -*- coding: utf-8 -*- # # Copyright 2008, Carl Gherardi -# +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. -# +# # 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 @@ -25,7 +25,7 @@ from HandHistoryConverter import * # Class for converting Everleaf HH format. class Everleaf(HandHistoryConverter): - + sitename = 'Everleaf' filetype = "text" codepage = "cp1252" @@ -38,10 +38,11 @@ class Everleaf(HandHistoryConverter): #re.compile(ur"^(Blinds )?(?P\$| €|)(?P[.0-9]+)/(?:\$| €)?(?P[.0-9]+) (?PNL|PL|) (?P(Hold\'em|Omaha|7 Card Stud))", re.MULTILINE) re_HandInfo = re.compile(ur".*#(?P[0-9]+)\n.*\n(Blinds )?(?:\$| €|)(?P[.0-9]+)/(?:\$| €|)(?P[.0-9]+) (?P.*) - (?P\d\d\d\d/\d\d/\d\d - \d\d:\d\d:\d\d)\nTable (?P.+$)", re.MULTILINE) re_Button = re.compile(ur"^Seat (?P
[0-9]+)\.txt") + + def compilePlayerRegexs(self, hand): players = set([player[1] for player in hand.players]) if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' @@ -55,10 +56,10 @@ class Everleaf(HandHistoryConverter): self.re_Antes = re.compile(ur"^%s: posts ante \[(?:\$| €|) (?P[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(ur"^%s posts bring-in (?:\$| €|)(?P[.0-9]+)\." % player_re, re.MULTILINE) self.re_HeroCards = re.compile(ur"^Dealt to %s \[ (?P.*) \]" % player_re, re.MULTILINE) - self.re_Action = re.compile(ur"^%s(?P: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P[.\d]+) (USD|EUR|)\])?" % player_re, re.MULTILINE) + self.re_Action = re.compile(ur"^%s(?P: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P[.,\d]+) (USD|EURO|Chips)\])?" % player_re, re.MULTILINE) #self.re_Action = re.compile(ur"^%s(?P: bets| checks| raises| calls| folds| complete to)(\s\[?(?:\$| €|) ?(?P\d+\.?\d*)\.?\s?(USD|EUR|)\]?)?" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(ur"^%s shows \[ (?P.*) \]" % player_re, re.MULTILINE) - self.re_CollectPot = re.compile(ur"^%s wins (?:\$| €|) (?P[.\d]+) (USD|EUR|chips)(.*?\[ (?P.*?) \])?" % player_re, re.MULTILINE) + self.re_CollectPot = re.compile(ur"^%s wins (?:\$| €|) (?P[.\d]+) (USD|EURO|chips)(.*?\[ (?P.*?) \])?" % player_re, re.MULTILINE) self.re_SitsOut = re.compile(ur"^%s sits out" % player_re, re.MULTILINE) def readSupportedGames(self): @@ -66,7 +67,9 @@ class Everleaf(HandHistoryConverter): ["ring", "hold", "pl"], ["ring", "hold", "fl"], ["ring", "studhi", "fl"], - ["ring", "omahahi", "pl"] + ["ring", "omahahi", "pl"], + ["ring", "omahahilo", "pl"], + ["tour", "hold", "nl"] ] def determineGameType(self, handText): @@ -83,30 +86,30 @@ class Everleaf(HandHistoryConverter): 'currency' in ('USD', 'EUR', 'T$', ) or None if we fail to get the info """ #(TODO: which parts are optional/required?) - + # Blinds $0.50/$1 PL Omaha - 2008/12/07 - 21:59:48 # Blinds $0.05/$0.10 NL Hold'em - 2009/02/21 - 11:21:57 # $0.25/$0.50 7 Card Stud - 2008/12/05 - 21:43:59 - + # Tourney: # Everleaf Gaming Game #75065769 # ***** Hand history for game #75065769 ***** # Blinds 10/20 NL Hold'em - 2009/02/25 - 17:30:32 # Table 2 info = {'type':'ring'} - + m = self.re_GameInfo.search(handText) if not m: return None - + mg = m.groupdict() - + # translations from captured groups to our info strings limits = { 'NL':'nl', 'PL':'pl', '':'fl' } games = { # base, category - "Hold'em" : ('hold','holdem'), - 'Omaha' : ('hold','omahahi'), - 'Razz' : ('stud','razz'), + "Hold'em" : ('hold','holdem'), + 'Omaha' : ('hold','omahahi'), + 'Razz' : ('stud','razz'), '7 Card Stud' : ('stud','studhi') } currencies = { u' €':'EUR', '$':'USD', '':'T$' } @@ -123,7 +126,7 @@ or None if we fail to get the info """ if info['currency'] == 'T$': info['type'] = 'tour' # NB: SB, BB must be interpreted as blinds or bets depending on limit type. - + return info @@ -138,6 +141,12 @@ or None if we fail to get the info """ hand.tablename = m.group('TABLE') hand.maxseats = 6 # assume 6-max unless we have proof it's a larger/smaller game, since everleaf doesn't give seat max info + t = self.re_TourneyInfoFromFilename.search(self.in_path) + if t: + tourno = t.group('TOURNO') + hand.tourNo = tourno + hand.tablename = t.group('TABLE') + # Believe Everleaf time is GMT/UTC, no transation necessary # Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET] # or : 2008/11/07 12:38:49 ET @@ -156,8 +165,8 @@ or None if we fail to get the info """ if seatnum > 6: hand.maxseats = 10 # everleaf currently does 2/6/10 games, so if seats > 6 are in use, it must be 10-max. # TODO: implement lookup list by table-name to determine maxes, then fall back to 6 default/10 here, if there's no entry in the list? - - + + def markStreets(self, hand): # PREFLOP = ** Dealing down cards ** # This re fails if, say, river is missing; then we don't get the ** that starts the river. @@ -196,7 +205,7 @@ or None if we fail to get the info """ def readBringIn(self, hand): m = self.re_BringIn.search(hand.handText,re.DOTALL) if m: - logging.debug("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))) + logging.debug("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))) hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) else: logging.warning("No bringin found.") @@ -285,6 +294,12 @@ or None if we fail to get the info """ # hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards) hand.addShownCards(cards=cards, player=m.group('PNAME')) + @staticmethod + def getTableTitleRe(type, table_name=None, tournament = None, table_number=None): + if tournament: + return "%s - Tournament ID: %s -" % (table_number, tournament) + return "%s -" % (table_name) + if __name__ == "__main__": @@ -305,4 +320,3 @@ if __name__ == "__main__": logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity) e = Everleaf(in_path = options.ipath, out_path = options.opath, follow = options.follow, autostart=True) - diff --git a/pyfpdb/Exceptions.py b/pyfpdb/Exceptions.py index fd7c20e3..789c7b83 100644 --- a/pyfpdb/Exceptions.py +++ b/pyfpdb/Exceptions.py @@ -48,5 +48,11 @@ class FpdbPostgresqlNoDatabase(FpdbDatabaseError): def __str__(self): return repr(self.value +" " + self.errmsg) -class DuplicateError(FpdbError): +class FpdbHandError(FpdbError): + pass + +class FpdbHandDuplicate(FpdbHandError): + pass + +class FpdbHandPartial(FpdbHandError): pass diff --git a/pyfpdb/Filters.py b/pyfpdb/Filters.py index dc2e4859..27e5a49d 100644 --- a/pyfpdb/Filters.py +++ b/pyfpdb/Filters.py @@ -26,21 +26,47 @@ from time import * import gobject #import pokereval +import logging +# logging has been set up in fpdb.py or HUD_main.py, use their settings: +log = logging.getLogger("filter") + + import Configuration -import fpdb_db -import FpdbSQLQueries +import Database +import SQL +import Charset + class Filters(threading.Thread): def __init__(self, db, config, qdict, display = {}, debug=True): # config and qdict are now redundant self.debug = debug - #print "start of GraphViewer constructor" self.db = db self.cursor = db.cursor self.sql = db.sql self.conf = db.config self.display = display + # text used on screen stored here so that it can be configured + self.filterText = {'limitsall':'All', 'limitsnone':'None', 'limitsshow':'Show _Limits' + ,'seatsbetween':'Between:', 'seatsand':'And:', 'seatsshow':'Show Number of _Players' + ,'playerstitle':'Hero:', 'sitestitle':'Sites:', 'gamestitle':'Games:' + ,'limitstitle':'Limits:', 'seatstitle':'Number of Players:' + ,'groupstitle':'Grouping:', 'posnshow':'Show Position Stats:' + ,'datestitle':'Date:' + ,'groupsall':'All Players' + ,'limitsFL':'FL', 'limitsNL':'NL', 'limitsPL':'PL', 'ring':'Ring', 'tour':'Tourney' + } + + # Outer Packing box + self.mainVBox = gtk.VBox(False, 0) + + self.label = {} + self.callback = {} + + self.make_filter() + + def make_filter(self): self.sites = {} self.games = {} self.limits = {} @@ -50,14 +76,14 @@ class Filters(threading.Thread): self.heroes = {} self.boxes = {} - # text used on screen stored here so that it can be configured - self.filterText = {'limitsall':'All', 'limitsnone':'None', 'limitsshow':'Show _Limits' - ,'seatsbetween':'Between:', 'seatsand':'And:', 'seatsshow':'Show Number of _Players' - ,'limitstitle':'Limits:', 'seatstitle':'Number of Players:' - ,'groupstitle':'Grouping:', 'posnshow':'Show Position Stats:' - ,'groupsall':'All Players' - ,'limitsFL':'FL', 'limitsNL':'NL', 'ring':'Ring', 'tour':'Tourney' - } + for site in self.conf.get_supported_sites(): + #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" # For use in date ranges. self.start_date = gtk.Entry(max=12) @@ -69,34 +95,28 @@ class Filters(threading.Thread): self.sbGroups = {} self.numHands = 0 - # Outer Packing box - self.mainVBox = gtk.VBox(False, 0) - - playerFrame = gtk.Frame("Hero:") + playerFrame = gtk.Frame() playerFrame.set_label_align(0.0, 0.0) vbox = gtk.VBox(False, 0) self.fillPlayerFrame(vbox, self.display) playerFrame.add(vbox) - self.boxes['player'] = vbox - sitesFrame = gtk.Frame("Sites:") + sitesFrame = gtk.Frame() sitesFrame.set_label_align(0.0, 0.0) vbox = gtk.VBox(False, 0) self.fillSitesFrame(vbox) sitesFrame.add(vbox) - self.boxes['sites'] = vbox # Game types - gamesFrame = gtk.Frame("Games:") + gamesFrame = gtk.Frame() gamesFrame.set_label_align(0.0, 0.0) gamesFrame.show() vbox = gtk.VBox(False, 0) self.fillGamesFrame(vbox) gamesFrame.add(vbox) - self.boxes['games'] = vbox # Limits limitsFrame = gtk.Frame() @@ -107,6 +127,7 @@ class Filters(threading.Thread): self.cbAllLimits = None self.cbFL = None self.cbNL = None + self.cbPL = None self.rb = {} # radio buttons for ring/tour self.type = None # ring/tour self.types = {} # list of all ring/tour values @@ -132,14 +153,13 @@ class Filters(threading.Thread): groupsFrame.add(vbox) # Date - dateFrame = gtk.Frame("Date:") + dateFrame = gtk.Frame() dateFrame.set_label_align(0.0, 0.0) dateFrame.show() vbox = gtk.VBox(False, 0) self.fillDateFrame(vbox) dateFrame.add(vbox) - self.boxes['date'] = vbox # Buttons self.Button1=gtk.Button("Unnamed 1") @@ -180,6 +200,17 @@ class Filters(threading.Thread): if "Button2" not in self.display or self.display["Button2"] == False: self.Button2.hide() + if 'button1' in self.label and self.label['button1']: + self.Button1.set_label( self.label['button1'] ) + if 'button2' in self.label and self.label['button2']: + self.Button2.set_label( self.label['button2'] ) + if 'button1' in self.callback and self.callback['button1']: + self.Button1.connect("clicked", self.callback['button1'], "clicked") + self.Button1.set_sensitive(True) + if 'button2' in self.callback and self.callback['button2']: + self.Button2.connect("clicked", self.callback['button2'], "clicked") + self.Button2.set_sensitive(True) + def get_vbox(self): """returns the vbox of this thread""" return self.mainVBox @@ -191,6 +222,9 @@ class Filters(threading.Thread): def getSites(self): return self.sites + def getGames(self): + return self.games + def getSiteIds(self): return self.siteid @@ -222,24 +256,29 @@ class Filters(threading.Thread): def registerButton1Name(self, title): self.Button1.set_label(title) + self.label['button1'] = title def registerButton1Callback(self, callback): self.Button1.connect("clicked", callback, "clicked") self.Button1.set_sensitive(True) + self.callback['button1'] = callback def registerButton2Name(self, title): self.Button2.set_label(title) + self.label['button2'] = title def registerButton2Callback(self, callback): self.Button2.connect("clicked", callback, "clicked") self.Button2.set_sensitive(True) + self.callback['button2'] = callback def cardCallback(self, widget, data=None): - print "DEBUG: %s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()]) + log.debug( "%s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()]) ) def createPlayerLine(self, hbox, site, player): + log.debug('add:"%s"' % player) label = gtk.Label(site +" id:") - hbox.pack_start(label, False, False, 0) + hbox.pack_start(label, False, False, 3) pname = gtk.Entry() pname.set_text(player) @@ -253,22 +292,27 @@ class Filters(threading.Thread): liststore = gtk.ListStore(gobject.TYPE_STRING) completion.set_model(liststore) completion.set_text_column(0) - names = self.db.get_player_names(self.conf) # (config=self.conf, site_id=None, like_player_name="%") - for n in names: - liststore.append(n) + names = self.db.get_player_names(self.conf, self.siteid[site]) # (config=self.conf, site_id=None, like_player_name="%") + for n in names: # list of single-element "tuples" + _n = Charset.to_gui(n[0]) + _nt = (_n, ) + liststore.append(_nt) self.__set_hero_name(pname, site) def __set_hero_name(self, w, site): - self.heroes[site] = w.get_text() -# print "DEBUG: setting heroes[%s]: %s"%(site, self.heroes[site]) + _name = w.get_text() + # get_text() returns a str but we want internal variables to be unicode: + _guiname = unicode(_name) + self.heroes[site] = _guiname +# log.debug("setting heroes[%s]: %s"%(site, self.heroes[site])) def __set_num_hands(self, w, val): try: self.numHands = int(w.get_text()) except: self.numHands = 0 -# print "DEBUG: setting numHands:", self.numHands +# log.debug("setting numHands:", self.numHands) def createSiteLine(self, hbox, site): cb = gtk.CheckButton(site) @@ -280,6 +324,7 @@ class Filters(threading.Thread): cb = gtk.CheckButton(game) cb.connect('clicked', self.__set_game_select, game) hbox.pack_start(cb, False, False, 0) + cb.set_active(True) def createLimitLine(self, hbox, limit, ltext): cb = gtk.CheckButton(str(ltext)) @@ -292,18 +337,18 @@ class Filters(threading.Thread): def __set_site_select(self, w, site): #print w.get_active() self.sites[site] = w.get_active() - print "self.sites[%s] set to %s" %(site, self.sites[site]) + log.debug("self.sites[%s] set to %s" %(site, self.sites[site])) def __set_game_select(self, w, game): #print w.get_active() self.games[game] = w.get_active() - print "self.games[%s] set to %s" %(game, self.games[game]) + log.debug("self.games[%s] set to %s" %(game, self.games[game])) def __set_limit_select(self, w, limit): #print w.get_active() self.limits[limit] = w.get_active() - print "self.limit[%s] set to %s" %(limit, self.limits[limit]) - if limit.isdigit() or (len(limit) > 2 and limit[-2:] == 'nl'): + log.debug("self.limit[%s] set to %s" %(limit, self.limits[limit])) + if limit.isdigit() or (len(limit) > 2 and (limit[-2:] == 'nl' or limit[-2:] == 'fl' or limit[-2:] == 'pl')): if self.limits[limit]: if self.cbNoLimits is not None: self.cbNoLimits.set_active(False) @@ -314,9 +359,12 @@ class Filters(threading.Thread): if limit.isdigit(): if self.cbFL is not None: self.cbFL.set_active(False) - else: + elif (len(limit) > 2 and (limit[-2:] == 'nl')): if self.cbNL is not None: self.cbNL.set_active(False) + else: + if self.cbPL is not None: + self.cbPL.set_active(False) elif limit == "all": if self.limits[limit]: #for cb in self.cbLimits.values(): @@ -325,6 +373,8 @@ class Filters(threading.Thread): self.cbFL.set_active(True) if self.cbNL is not None: self.cbNL.set_active(True) + if self.cbPL is not None: + self.cbPL.set_active(True) elif limit == "none": if self.limits[limit]: for cb in self.cbLimits.values(): @@ -333,6 +383,8 @@ class Filters(threading.Thread): self.cbNL.set_active(False) if self.cbFL is not None: self.cbFL.set_active(False) + if self.cbPL is not None: + self.cbPL.set_active(False) elif limit == "fl": if not self.limits[limit]: # only toggle all fl limits off if they are all currently on @@ -381,11 +433,39 @@ class Filters(threading.Thread): if self.limits[limit]: if not found[self.type]: if self.type == 'ring': - self.rb['tour'].set_active(True) + if 'tour' in self.rb: + self.rb['tour'].set_active(True) elif self.type == 'tour': - self.rb['ring'].set_active(True) + if 'ring' in self.rb: + self.rb['ring'].set_active(True) + elif limit == "pl": + if not self.limits[limit]: + # only toggle all nl limits off if they are all currently on + # this stops turning one off from cascading into 'nl' box off + # and then all nl limits being turned off + all_nl_on = True + for cb in self.cbLimits.values(): + t = cb.get_children()[0].get_text() + if "pl" in t and len(t) > 2: + if not cb.get_active(): + all_nl_on = False + found = {'ring':False, 'tour':False} + for cb in self.cbLimits.values(): + t = cb.get_children()[0].get_text() + if "pl" in t and len(t) > 2: + if self.limits[limit] or all_nl_on: + cb.set_active(self.limits[limit]) + found[self.types[t]] = True + if self.limits[limit]: + if not found[self.type]: + if self.type == 'ring': + if 'tour' in self.rb: + self.rb['tour'].set_active(True) + elif self.type == 'tour': + if 'ring' in self.rb: + self.rb['ring'].set_active(True) elif limit == "ring": - print "set", limit, "to", self.limits[limit] + log.debug("set", limit, "to", self.limits[limit]) if self.limits[limit]: self.type = "ring" for cb in self.cbLimits.values(): @@ -393,7 +473,7 @@ class Filters(threading.Thread): if self.types[cb.get_children()[0].get_text()] == 'tour': cb.set_active(False) elif limit == "tour": - print "set", limit, "to", self.limits[limit] + log.debug( "set", limit, "to", self.limits[limit] ) if self.limits[limit]: self.type = "tour" for cb in self.cbLimits.values(): @@ -404,24 +484,38 @@ class Filters(threading.Thread): def __set_seat_select(self, w, seat): #print "__set_seat_select: seat =", seat, "active =", w.get_active() self.seats[seat] = w.get_active() - print "self.seats[%s] set to %s" %(seat, self.seats[seat]) + log.debug( "self.seats[%s] set to %s" %(seat, self.seats[seat]) ) def __set_group_select(self, w, group): #print "__set_seat_select: seat =", seat, "active =", w.get_active() self.groups[group] = w.get_active() - print "self.groups[%s] set to %s" %(group, self.groups[group]) + log.debug( "self.groups[%s] set to %s" %(group, self.groups[group]) ) def fillPlayerFrame(self, vbox, display): + top_hbox = gtk.HBox(False, 0) + vbox.pack_start(top_hbox, False, False, 0) + lbl_title = gtk.Label(self.filterText['playerstitle']) + lbl_title.set_alignment(xalign=0.0, yalign=0.5) + top_hbox.pack_start(lbl_title, expand=True, padding=3) + showb = gtk.Button(label="refresh", stock=None, use_underline=True) + showb.set_alignment(xalign=1.0, yalign=0.5) + showb.connect('clicked', self.__refresh, 'players') + + vbox1 = gtk.VBox(False, 0) + vbox.pack_start(vbox1, False, False, 0) + self.boxes['players'] = vbox1 + for site in self.conf.get_supported_sites(): hBox = gtk.HBox(False, 0) - vbox.pack_start(hBox, False, True, 0) + vbox1.pack_start(hBox, False, True, 0) player = self.conf.supported_sites[site].screen_name - self.createPlayerLine(hBox, site, player) + _pname = Charset.to_gui(player) + self.createPlayerLine(hBox, site, _pname) if "GroupsAll" in display and display["GroupsAll"] == True: hbox = gtk.HBox(False, 0) - vbox.pack_start(hbox, False, False, 0) + vbox1.pack_start(hbox, False, False, 0) cb = gtk.CheckButton(self.filterText['groupsall']) cb.connect('clicked', self.__set_group_select, 'allplayers') hbox.pack_start(cb, False, False, 0) @@ -437,30 +531,64 @@ class Filters(threading.Thread): phands.set_width_chars(8) hbox.pack_start(phands, False, False, 0) phands.connect("changed", self.__set_num_hands, site) + top_hbox.pack_start(showb, expand=False, padding=1) def fillSitesFrame(self, vbox): + top_hbox = gtk.HBox(False, 0) + top_hbox.show() + vbox.pack_start(top_hbox, False, False, 0) + + lbl_title = gtk.Label(self.filterText['sitestitle']) + lbl_title.set_alignment(xalign=0.0, yalign=0.5) + top_hbox.pack_start(lbl_title, expand=True, padding=3) + + showb = gtk.Button(label="hide", stock=None, use_underline=True) + showb.set_alignment(xalign=1.0, yalign=0.5) + showb.connect('clicked', self.__toggle_box, 'sites') + showb.show() + top_hbox.pack_start(showb, expand=False, padding=1) + + vbox1 = gtk.VBox(False, 0) + self.boxes['sites'] = vbox1 + vbox.pack_start(vbox1, False, False, 0) + for site in self.conf.get_supported_sites(): hbox = gtk.HBox(False, 0) - vbox.pack_start(hbox, False, True, 0) + vbox1.pack_start(hbox, False, True, 0) 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" + #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 fillGamesFrame(self, vbox): + top_hbox = gtk.HBox(False, 0) + vbox.pack_start(top_hbox, False, False, 0) + lbl_title = gtk.Label(self.filterText['gamestitle']) + lbl_title.set_alignment(xalign=0.0, yalign=0.5) + top_hbox.pack_start(lbl_title, expand=True, padding=3) + showb = gtk.Button(label="hide", stock=None, use_underline=True) + showb.set_alignment(xalign=1.0, yalign=0.5) + showb.connect('clicked', self.__toggle_box, 'games') + top_hbox.pack_start(showb, expand=False, padding=1) + + vbox1 = gtk.VBox(False, 0) + vbox.pack_start(vbox1, False, False, 0) + self.boxes['games'] = vbox1 + self.cursor.execute(self.sql.query['getGames']) result = self.db.cursor.fetchall() if len(result) >= 1: for line in result: hbox = gtk.HBox(False, 0) - vbox.pack_start(hbox, False, True, 0) + vbox1.pack_start(hbox, False, True, 0) self.createGameLine(hbox, line[0]) else: print "INFO: No games returned from database" + log.info("No games returned from database") def fillLimitsFrame(self, vbox, display): top_hbox = gtk.HBox(False, 0) @@ -476,10 +604,10 @@ class Filters(threading.Thread): vbox.pack_start(vbox1, False, False, 0) self.boxes['limits'] = vbox1 - self.cursor.execute(self.sql.query['getLimits2']) + self.cursor.execute(self.sql.query['getLimits3']) # selects limitType, bigBlind result = self.db.cursor.fetchall() - found = {'nl':False, 'fl':False, 'ring':False, 'tour':False} + found = {'nl':False, 'fl':False, 'pl':False, 'ring':False, 'tour':False} if len(result) >= 1: hbox = gtk.HBox(True, 0) @@ -497,14 +625,18 @@ class Filters(threading.Thread): vbox2.pack_start(hbox, False, False, 0) else: vbox3.pack_start(hbox, False, False, 0) - if line[1] == 'fl': - name = str(line[2]) - found['fl'] = True - else: - name = str(line[2])+line[1] - found['nl'] = True - self.cbLimits[name] = self.createLimitLine(hbox, name, name) - self.types[name] = line[0] + if True: #line[0] == 'ring': + if line[1] == 'fl': + name = str(line[2]) + found['fl'] = True + elif line[1] == 'pl': + name = str(line[2])+line[1] + found['pl'] = True + else: + name = str(line[2])+line[1] + found['nl'] = True + self.cbLimits[name] = self.createLimitLine(hbox, name, name) + self.types[name] = line[0] found[line[0]] = True # type is ring/tour self.type = line[0] # if only one type, set it now if "LimitSep" in display and display["LimitSep"] == True and len(result) >= 2: @@ -532,9 +664,13 @@ class Filters(threading.Thread): hbox = gtk.HBox(False, 0) vbox3.pack_start(hbox, False, False, 0) self.cbNL = self.createLimitLine(hbox, 'nl', self.filterText['limitsNL']) + hbox = gtk.HBox(False, 0) + vbox3.pack_start(hbox, False, False, 0) + self.cbPL = self.createLimitLine(hbox, 'pl', self.filterText['limitsPL']) dest = vbox2 # for ring/tour buttons else: print "INFO: No games returned from database" + log.info("No games returned from database") if "Type" in display and display["Type"] == True and found['ring'] and found['tour']: rb1 = gtk.RadioButton(None, self.filterText['ring']) @@ -645,8 +781,22 @@ class Filters(threading.Thread): def fillDateFrame(self, vbox): # Hat tip to Mika Bostrom - calendar code comes from PokerStats + top_hbox = gtk.HBox(False, 0) + vbox.pack_start(top_hbox, False, False, 0) + lbl_title = gtk.Label(self.filterText['datestitle']) + lbl_title.set_alignment(xalign=0.0, yalign=0.5) + top_hbox.pack_start(lbl_title, expand=True, padding=3) + showb = gtk.Button(label="hide", stock=None, use_underline=True) + showb.set_alignment(xalign=1.0, yalign=0.5) + showb.connect('clicked', self.__toggle_box, 'dates') + top_hbox.pack_start(showb, expand=False, padding=1) + + vbox1 = gtk.VBox(False, 0) + vbox.pack_start(vbox1, False, False, 0) + self.boxes['dates'] = vbox1 + hbox = gtk.HBox() - vbox.pack_start(hbox, False, True, 0) + vbox1.pack_start(hbox, False, True, 0) lbl_start = gtk.Label('From:') @@ -660,7 +810,7 @@ class Filters(threading.Thread): #New row for end date hbox = gtk.HBox() - vbox.pack_start(hbox, False, True, 0) + vbox1.pack_start(hbox, False, True, 0) lbl_end = gtk.Label(' To:') btn_end = gtk.Button() @@ -676,10 +826,13 @@ class Filters(threading.Thread): hbox.pack_start(btn_clear, expand=False, padding=15) + def __refresh(self, widget, entry): + for w in self.mainVBox.get_children(): + w.destroy() + self.make_filter() + def __toggle_box(self, widget, entry): - if "Limits" not in self.display or self.display["Limits"] == False: - self.boxes[entry].hide() - elif self.boxes[entry].props.visible: + if self.boxes[entry].props.visible: self.boxes[entry].hide() widget.set_label("show") else: @@ -740,10 +893,10 @@ def main(argv=None): config = Configuration.Config() db = None - db = fpdb_db.fpdb_db() + db = Database.Database() db.do_connect(config) - qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name()) + qdict = SQL.SQL(db.get_backend_name()) i = Filters(db, config, qdict) main_window = gtk.Window() diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index 1721236a..af55fd41 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -18,12 +18,10 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## -import sys import logging from HandHistoryConverter import * # Fulltilt HH Format converter -# TODO: cat tourno and table to make table name for tournaments class Fulltilt(HandHistoryConverter): @@ -67,8 +65,8 @@ class Fulltilt(HandHistoryConverter): (\s\((?PTurbo)\))?)|(?P.+)) ''', re.VERBOSE) re_Button = re.compile('^The button is in seat #(?P
\d+)\)?\s+ + (?:[a-zA-Z0-9 ]+\s+\#(?P\d+).+)? (\(No\sDP\)\s)? - \((?PReal|Play)\s+Money\)\s* + \((?PReal|Play)\s+Money\)\s+ # FIXME: check if play money is correct + Seat\s+(?P
[-\ a-zA-Z\d]+)\'\s + ^Table\s\'(?P
[-\ \#a-zA-Z\d]+)\'\s ((?P\d+)-max\s)? (?P\(Play\sMoney\)\s)? (Seat\s\#(?P
[ a-zA-Z]+) - \$?(?P[.0-9]+)/\$?(?P[.0-9]+) - (?P.*) - (?P
[0-9]+):(?P[0-9]+) ET - (?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)Table (?P
[ a-zA-Z]+)\nSeat (?P