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/Configuration.py b/pyfpdb/Configuration.py index 174d0673..39bf5921 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -269,6 +269,10 @@ class Game: stat.hudprefix = stat_node.getAttribute("hudprefix") stat.hudsuffix = stat_node.getAttribute("hudsuffix") stat.hudcolor = stat_node.getAttribute("hudcolor") + stat.stat_loth = stat_node.getAttribute("stat_loth") + stat.stat_hith = stat_node.getAttribute("stat_hith") + stat.stat_locolor = stat_node.getAttribute("stat_locolor") + stat.stat_hicolor = stat_node.getAttribute("stat_hicolor") self.stats[stat.stat_name] = stat diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index fb70dbab..c7b3e3b8 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -621,69 +621,6 @@ class Database: 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,)) @@ -1571,7 +1508,7 @@ class Database: line[55] = gid # gametypeId line[56] = pids[p] # playerId line[57] = len(pids) # activeSeats - pos = {-2:'B', -1:'S', 0:'D', 1:'C', 2:'M', 3:'M', 4:'M', 5:'E', 6:'E', 7:'E', 8:'E', 9:'E' } + 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 @@ -1683,45 +1620,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_tourneys_players(self, tourney_id, player_ids, payin_amounts, ranks, winnings): try: result=[] diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 1e81dd8d..8b9af93b 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -48,18 +48,11 @@ class DerivedStats(): self.handsplayers[player[1]]['sawShowdown'] = False self.handsplayers[player[1]]['wonAtSD'] = 0.0 self.handsplayers[player[1]]['startCards'] = 0 - 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 - - #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]]['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 @@ -67,13 +60,22 @@ class DerivedStats(): 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]]['tourneyTypeId'] = 1 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,31 +176,44 @@ 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. + positions = [7, 6, 5, 4, 3, 2, 1, 0, 'S', 'B'] + actions = hand.actions[hand.holeStreets[0]] + players = self.pfbao(actions) + seats = len(players) + map = [] + if hand.gametype['base'] == 'stud': + # 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 + else: + # For 6 players is should look like: + # [3, 2, 1, 0, 'S', 'B'] + map = positions[-seats:] # Copy required positions from array ending in -2 + + for i, player in enumerate(players): + self.handsplayers[player]['position'] = map[i] def assembleHudCache(self, hand): # No real work to be done - HandsPlayers data already contains the correct info @@ -269,28 +284,29 @@ class DerivedStats(): Steal attemp - open raise on positions 2 1 0 S - i.e. MP3, CO, BU, SB Fold to steal - folding blind after steal attemp wo any other callers or raisers """ - if self.gametype_dict['base'] != 'hold': - # FIXME: add support for other games //grindi - return steal_attemp = False + steal_positions = ('2', '1', '0', 'S') + if hand.gametype['base'] == 'stud': + steal_positions = ('2', '1', '0') for action in hand.actions[hand.actionStreets[1]]: - hp, act = self.handplayers_by_name[action[0]], action[1] + pname, act = action[0], action[1] #print action[0], hp.position, steal_attemp, act - if hp.position == 'B': - hp.foldBbToStealChance = steal_attemp - hp.foldBbToSteal = hp.foldBbToStealChance and act == 'folds' + if self.handsplayers[pname]['position'] == 'B': + #NOTE: Stud games will never hit this section + self.handsplayers[pname]['foldBbToStealChance'] = steal_attemp + self.handsplayers[pname]['foldBbToSteal'] = self.handsplayers[pname]['foldBbToStealChance'] and act == 'folds' break - elif hp.position == 'S': - hp.foldSbToStealChance = steal_attemp - hp.foldSbToSteal = hp.foldSbToStealChance and act == 'folds' + elif self.handsplayers[pname]['position'] == 'S': + self.handsplayers[pname]['foldSbToStealChance'] = steal_attemp + self.handsplayers[pname]['foldSbToSteal'] = self.handsplayers[pname]['foldSbToStealChance'] and act == 'folds' if steal_attemp and act != 'folds': break - if hp.position in ('2', '1', '0', 'S') and not steal_attemp: - hp.stealAttemptChance = True + if self.handsplayers[pname]['position'] in steal_positions and not steal_attemp: + self.handsplayers[pname]['stealAttemptChance'] = True if act in ('bets', 'raises'): - hp.stealAttempted = True + self.handsplayers[pname]['stealAttempted'] = True steal_attemp = True def calc34BetStreet0(self, hand): @@ -298,11 +314,11 @@ class DerivedStats(): 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 - hp, aggr = self.handplayers_by_name[action[0]], action[1] in ('raises', 'bets') - hp.street0_3BChance = bet_level == 2 - hp.street0_4BChance = bet_level == 3 - hp.street0_3BDone = aggr and (hp.street0_3BChance) - hp.street0_4BDone = aggr and (hp.street0_4BChance) + 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 @@ -342,12 +358,11 @@ class DerivedStats(): pname, act = action[0], action[1] if act in ('bets', 'raises') and initial_raiser is None: initial_raiser = pname - elif act == 'check' and initial_raiser is None: + elif act == 'checks' and initial_raiser is None: checkers.add(pname) elif initial_raiser is not None and pname in checkers: - hp = self.handplayers_by_name[pname] - setattr(hp, 'street%dCheckCallRaiseChance' % i, True) - setattr(hp, 'street%dCheckCallRaiseDone' % i, act!='folds') + self.handsplayers[pname]['street%dCheckCallRaiseChance' % i] = True + self.handsplayers[pname]['street%dCheckCallRaiseDone' % i] = act!='folds' def seen(self, hand, i): pas = set() @@ -362,11 +377,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: @@ -402,6 +419,23 @@ 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. diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index b1ebd761..3acfd812 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -448,59 +448,61 @@ Left-Drag to Move" - - - - - + + + + - - + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 9d4807a2..47cfd302 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -512,7 +512,7 @@ or None if we fail to get the info """ def getTableTitleRe(type, table_name=None, tournament = None, table_number=None): "Returns string to search in windows titles" if type=="tour": - return "%s.+Table\s%s" % (tournament, table_number) + return "%s.+Table.+%s" % (tournament, table_number) else: return table_name diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index b61c17aa..1a27b682 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -606,6 +606,7 @@ class Hud: if self.update_table_position() == False: # we got killed by finding our table was gone return + self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor'])) for s in self.stat_dict: try: statd = self.stat_dict[s] @@ -629,8 +630,17 @@ class Hud: window = self.stat_windows[statd['seat']] if this_stat.hudcolor != "": - self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor'])) window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.hudcolor)) + else: + window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor'])) + + if this_stat.stat_loth != "": + if number[0] < (float(this_stat.stat_loth)/100): + window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.stat_locolor)) + + if this_stat.stat_hith != "": + if number[0] > (float(this_stat.stat_hith)/100): + window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.stat_hicolor)) window.label[r][c].set_text(statstring) if statstring != "xxx": # is there a way to tell if this particular stat window is visible already, or no? diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index 75bc574c..ce99b274 100755 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -160,7 +160,7 @@ def discover_posix_by_name(c, tablename): def discover_posix_tournament(c, t_number, s_number): """Finds the X window for a client, given tournament and table nos.""" - search_string = "%s.+Table\s%s" % (t_number, s_number) + search_string = "%s.+Table.+%s" % (t_number, s_number) for listing in os.popen('xwininfo -root -tree').readlines(): if re.search(search_string, listing): return decode_xwininfo(c, listing) diff --git a/pyfpdb/regression-test-files/cash/Stars/Flop/NLHE-6max-USD-0.05-0.10-200912.Allin-pre.txt b/pyfpdb/regression-test-files/cash/Stars/Flop/NLHE-6max-USD-0.05-0.10-200912.Allin-pre.txt new file mode 100644 index 00000000..b876c42a --- /dev/null +++ b/pyfpdb/regression-test-files/cash/Stars/Flop/NLHE-6max-USD-0.05-0.10-200912.Allin-pre.txt @@ -0,0 +1,41 @@ +PokerStars Game #37165169101: Hold'em No Limit ($0.10/$0.25 USD) - 2009/12/25 9:50:09 ET +Table 'Lucretia IV' 6-max Seat #2 is the button +Seat 1: Blåveis ($55.10 in chips) +Seat 2: Kinewma ($31.40 in chips) +Seat 3: AAALISAAAA ($20.20 in chips) +Seat 4: Arbaz ($25 in chips) +Seat 5: s0rrow ($29.85 in chips) +Seat 6: bys7 ($41.35 in chips) +AAALISAAAA: posts small blind $0.10 +Arbaz: posts big blind $0.25 +*** HOLE CARDS *** +Dealt to s0rrow [Ac As] +s0rrow: raises $0.50 to $0.75 +bys7: calls $0.75 +Blåveis: folds +Kinewma: folds +AAALISAAAA: raises $1.50 to $2.25 +Arbaz: folds +s0rrow: raises $3.50 to $5.75 +bys7: folds +AAALISAAAA: raises $14.45 to $20.20 and is all-in +s0rrow: calls $14.45 +*** FLOP *** [3d 7h Kh] +*** TURN *** [3d 7h Kh] [Ts] +*** RIVER *** [3d 7h Kh Ts] [5c] +*** SHOW DOWN *** +AAALISAAAA: shows [Kd 5d] (two pair, Kings and Fives) +s0rrow: shows [Ac As] (a pair of Aces) +AAALISAAAA collected $39.35 from pot +*** SUMMARY *** +Total pot $41.40 | Rake $2.05 +Board [3d 7h Kh Ts 5c] +Seat 1: Blåveis folded before Flop (didn't bet) +Seat 2: Kinewma (button) folded before Flop (didn't bet) +Seat 3: AAALISAAAA (small blind) showed [Kd 5d] and won ($39.35) with two pair, Kings and Fives +Seat 4: Arbaz (big blind) folded before Flop +Seat 5: s0rrow showed [Ac As] and lost with a pair of Aces +Seat 6: bys7 folded before Flop + + + diff --git a/pyfpdb/test_PokerStars.py b/pyfpdb/test_PokerStars.py index e3e45c35..72c9049c 100644 --- a/pyfpdb/test_PokerStars.py +++ b/pyfpdb/test_PokerStars.py @@ -82,18 +82,21 @@ def testFlopImport(): # River: hero (continuation bets?) all-in and is not called importer.addBulkImportImportFileOrDir( """regression-test-files/cash/Stars/Flop/NLHE-6max-USD-0.05-0.10-200912.Stats-comparision.txt""", site="PokerStars") + importer.addBulkImportImportFileOrDir( + """regression-test-files/cash/Stars/Flop/NLHE-6max-USD-0.05-0.10-200912.Allin-pre.txt""", site="PokerStars") importer.setCallHud(False) (stored, dups, partial, errs, ttime) = importer.runImport() print "DEBUG: stored: %s dups: %s partial: %s errs: %s ttime: %s" %(stored, dups, partial, errs, ttime) importer.clearFileList() - col = { 'sawShowdown': 2 + col = { 'sawShowdown': 2, 'street0Aggr':3 } q = """SELECT s.name, p.name, - hp.sawShowdown + hp.sawShowdown, + hp.street0Aggr FROM Hands as h, Sites as s, @@ -114,6 +117,33 @@ and s.id = p.siteid""" # Assert if any sawShowdown = True assert result[row][col['sawShowdown']] == 0 + q = """SELECT + s.name, + p.name, + hp.sawShowdown, + hp.street0Aggr +FROM + Hands as h, + Sites as s, + Gametypes as g, + HandsPlayers as hp, + Players as p +WHERE + h.siteHandNo = 37165169101 +and g.id = h.gametypeid +and hp.handid = h.id +and p.id = hp.playerid +and s.id = p.siteid""" + c = db.get_cursor() + c.execute(q) + result = c.fetchall() + pstats = { u'Kinewma':0, u'Arbaz':0, u's0rrow':1, u'bys7':0, u'AAALISAAAA':1, u'Bl\xe5veis':0 } + for row, data in enumerate(result): + print "DEBUG: result[%s]: %s == %s" %(row, result[row], pstats[data[1]]) + assert result[row][col['sawShowdown']] == pstats[data[1]] + + assert 0 == 1 + def testStudImport(): db.recreate_tables() importer = fpdb_import.Importer(False, settings, config)