Prepull commit

This commit is contained in:
grindi 2010-02-23 20:59:27 +03:00
parent 9a145e14ad
commit 1eaa00321b
48 changed files with 3590 additions and 2224 deletions

BIN
gfx/fpdb_large_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -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 <bostik+fpdb@bostik.iki.fi> Mon, 26 Oct 2009 17:49:07 +0200
-- Mika Bostrom <bostik+fpdb@bostik.iki.fi> Fri, 22 Jan 2010 09:25:27 +0200
free-poker-tools (0.11.3+git20091023) unstable; urgency=low

116
pyfpdb/AlchemyFacilities.py Normal file
View File

@ -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

464
pyfpdb/AlchemyMappings.py Normal file
View File

@ -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 '<Player "%s" on %s>' % (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)

438
pyfpdb/AlchemyTables.py Normal file
View File

@ -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

View File

@ -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) <description type="Holdem" stakes="No Limit ($0.25/$0.50)"/>
# 2) <game id="14902583-5578" starttime="20081006145401" numholecards="2" gametype="2" realmoney="true" data="20081006|Niagara Falls (14902583)|14902583|14902583-5578|false">
# 3) <players dealer="8">
# <player seat="3" nickname="PlayerInSeat3" balance="$43.29" dealtin="true" />
# ...
# 4) <round id="BLINDS" sequence="1">
# <event sequence="1" type="SMALL_BLIND" player="0" amount="0.25"/>
# <event sequence="2" type="BIG_BLIND" player="1" amount="0.50"/>
# 5) <round id="PREFLOP" sequence="2">
# <event sequence="3" type="CALL" player="2" amount="0.50"/>
# 6) <round id="POSTFLOP" sequence="3">
# <event sequence="16" type="BET" player="3" amount="1.00"/>
# ....
# <cards type="COMMUNITY" cards="7d,Jd,Jh"/>
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'</game>\n+(?=<game)')
re_TailSplitHands = re.compile(r'(</game>)')
re_GameInfo = re.compile(r'<description type="(?P<GAME>[a-zA-Z ]+)" stakes="(?P<LIMIT>[a-zA-Z ]+) \(\$(?P<SB>[.0-9]+)/\$(?P<BB>[.0-9]+)\)"/>', re.MULTILINE)
re_HandInfo = re.compile(r'<game id="(?P<HID1>[0-9]+)-(?P<HID2>[0-9]+)" starttime="(?P<DATETIME>[0-9]+)" numholecards="2" gametype="2" realmoney="true" data="[0-9]+\|(?P<TABLE>[^\(]+)', re.MULTILINE)
re_Button = re.compile(r'<players dealer="(?P<BUTTON>[0-9]+)">')
re_PlayerInfo = re.compile(r'<player seat="(?P<SEAT>[0-9]+)" nickname="(?P<PNAME>.+)" balance="\$(?P<CASH>[.0-9]+)" dealtin="(?P<DEALTIN>(true|false))" />', re.MULTILINE)
re_Board = re.compile(r'<cards type="COMMUNITY" cards="(?P<CARDS>[^"]+)"', re.MULTILINE)
re_EndOfHand = re.compile(r'<round id="END_OF_GAME"', re.MULTILINE)
# The following are also static regexes: there is no need to call
# compilePlayerRegexes (which does nothing), since players are identified
# not by name but by seat number
re_PostSB = re.compile(r'<event sequence="[0-9]+" type="(SMALL_BLIND|RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SB>[.0-9]+)"/>', re.MULTILINE)
re_PostBB = re.compile(r'<event sequence="[0-9]+" type="(BIG_BLIND|INITIAL_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<BB>[.0-9]+)"/>', re.MULTILINE)
re_PostBoth = re.compile(r'<event sequence="[0-9]+" type="(RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SBBB>[.0-9]+)"/>', re.MULTILINE)
#re_Antes = ???
#re_BringIn = ???
re_HeroCards = re.compile(r'<cards type="HOLE" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"', re.MULTILINE)
re_Action = re.compile(r'<event sequence="[0-9]+" type="(?P<ATYPE>FOLD|CHECK|CALL|BET|RAISE|ALL_IN|SIT_OUT)" player="(?P<PSEAT>[0-9])"( amount="(?P<BET>[.0-9]+)")?/>', re.MULTILINE)
re_ShowdownAction = re.compile(r'<cards type="SHOWN" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE)
re_CollectPot = re.compile(r'<winner amount="(?P<POT>[.0-9]+)" uncalled="(true|false)" potnumber="[0-9]+" player="(?P<PSEAT>[0-9])"', re.MULTILINE)
re_SitsOut = re.compile(r'<event sequence="[0-9]+" type="SIT_OUT" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE)
re_ShownCards = re.compile(r'<cards type="(SHOWN|MUCKED)" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', 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('(?P<LIMIT>No Limit)\s\(\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.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$', <countrycode>)
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 = "<CarbonHHFile>\n" + self.obs + "</CarbonHHFile>"
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'<round id="PREFLOP" sequence="[0-9]+">(?P<PREFLOP>.+(?=<round id="POSTFLOP")|.+)(<round id="POSTFLOP" sequence="[0-9]+">(?P<FLOP>.+(?=<round id="POSTTURN")|.+))?(<round id="POSTTURN" sequence="[0-9]+">(?P<TURN>.+(?=<round id="POSTRIVER")|.+))?(<round id="POSTRIVER" sequence="[0-9]+">(?P<RIVER>.+))?', 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)

View File

@ -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 <http://www.gnu.org/licenses/>.
#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')

77
pyfpdb/Charset.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#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

View File

@ -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()

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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<CURRENCY>\$| €|)(?P<SB>[.0-9]+)/(?:\$| €)?(?P<BB>[.0-9]+) (?P<LIMIT>NL|PL|) (?P<GAME>(Hold\'em|Omaha|7 Card Stud))", re.MULTILINE)
re_HandInfo = re.compile(ur".*#(?P<HID>[0-9]+)\n.*\n(Blinds )?(?:\$| €|)(?P<SB>[.0-9]+)/(?:\$| €|)(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<DATETIME>\d\d\d\d/\d\d/\d\d - \d\d:\d\d:\d\d)\nTable (?P<TABLE>.+$)", re.MULTILINE)
re_Button = re.compile(ur"^Seat (?P<BUTTON>\d+) is the button", re.MULTILINE)
re_PlayerInfo = re.compile(ur"^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\s+((?:\$| €|) (?P<CASH>[.0-9]+) (USD|EUR|)|new player|All-in) \)", re.MULTILINE)
re_PlayerInfo = re.compile(ur"^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\s+((?:\$| €|) (?P<CASH>[.0-9]+) (USD|EURO|Chips)|new player|All-in) \)", re.MULTILINE)
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
re_TourneyInfoFromFilename = re.compile(ur".*TID_(?P<TOURNO>[0-9]+)-(?P<TABLE>[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<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(ur"^%s posts bring-in (?:\$| €|)(?P<BRINGIN>[.0-9]+)\." % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(ur"^Dealt to %s \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
self.re_Action = re.compile(ur"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P<BET>[.\d]+) (USD|EUR|)\])?" % player_re, re.MULTILINE)
self.re_Action = re.compile(ur"^%s(?P<ATYPE>: bets| checks| raises| calls| folds)(\s\[(?:\$| €|) (?P<BET>[.,\d]+) (USD|EURO|Chips)\])?" % player_re, re.MULTILINE)
#self.re_Action = re.compile(ur"^%s(?P<ATYPE>: bets| checks| raises| calls| folds| complete to)(\s\[?(?:\$| €|) ?(?P<BET>\d+\.?\d*)\.?\s?(USD|EUR|)\]?)?" % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(ur"^%s shows \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(ur"^%s wins (?:\$| €|) (?P<POT>[.\d]+) (USD|EUR|chips)(.*?\[ (?P<CARDS>.*?) \])?" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(ur"^%s wins (?:\$| €|) (?P<POT>[.\d]+) (USD|EURO|chips)(.*?\[ (?P<CARDS>.*?) \])?" % 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$', <countrycode>)
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)

View File

@ -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

View File

@ -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()

View File

@ -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\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
''', re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
#static regex for tourney purpose
@ -139,7 +137,7 @@ class Fulltilt(HandHistoryConverter):
self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$?(?P<POT>[.,\d]+)\)(, mucked| with.*)" % player_re, re.MULTILINE)
self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(?P<ACT>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self):
return [["ring", "hold", "nl"],
@ -191,7 +189,6 @@ class Fulltilt(HandHistoryConverter):
if mg['TOURNO'] is None: info['type'] = "ring"
else: info['type'] = "tour"
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
# if info['type'] == "tour": return None # importer is screwed on tournies, pass on those hands so we don't interrupt other autoimporting
return info
def readHandInfo(self, hand):
@ -258,15 +255,16 @@ class Fulltilt(HandHistoryConverter):
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this)
#hand.starttime = "%d/%02d/%02d %d:%02d:%02d ET" %(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')),
##int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
#FIXME: hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand):
# Split hand text for FTP, as the regex matches the player names incorrectly
# in the summary section
pre, post = hand.handText.split('SUMMARY')
if hand.gametype['type'] == "ring" :
m = self.re_PlayerInfo.finditer(hand.handText)
m = self.re_PlayerInfo.finditer(pre)
else: #if hand.gametype['type'] == "tour"
m = self.re_TourneyPlayerInfo.finditer(hand.handText)
m = self.re_TourneyPlayerInfo.finditer(pre)
players = []
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
@ -392,9 +390,10 @@ class Fulltilt(HandHistoryConverter):
def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = cards.split(' ')
hand.addShownCards(cards=cards, player=m.group('PNAME'))
if m.group('ACT'):
hand.addShownCards(cards=m.group('CARDS').split(' '), player=m.group('PNAME'), shown = False, mucked = True)
else:
hand.addShownCards(cards=m.group('CARDS').split(' '), player=m.group('PNAME'), shown = True, mucked = False)
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
@ -422,7 +421,6 @@ class Fulltilt(HandHistoryConverter):
hand.mixed = self.mixes[m.groupdict()['MIXED']]
def readSummaryInfo(self, summaryInfoList):
starttime = time.time()
self.status = True
m = re.search("Tournament Summary", summaryInfoList[0])
@ -542,14 +540,14 @@ class Fulltilt(HandHistoryConverter):
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin:
log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (touney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) )
log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (tourney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) )
tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
if mg['FEE'] is not None:
if tourney.fee is None:
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee:
log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (touney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) )
log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (tourney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) )
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
if tourney.buyin is None:

View File

@ -193,8 +193,13 @@ class GuiAutoImport (threading.Thread):
self.doAutoImportBool = True
widget.set_label(u' _Stop Autoimport ')
if self.pipe_to_hud is None:
if os.name == 'nt':
command = "python HUD_main.py " + self.settings['cl_options']
if Configuration.FROZEN:
path = Configuration.EXEC_PATH
command = "HUD_main.exe"
bs = 0
elif os.name == 'nt':
path = sys.path[0].replace('\\','\\\\')
command = 'python "'+path+'\\HUD_main.py" ' + self.settings['cl_options']
bs = 0
else:
command = os.path.join(sys.path[0], 'HUD_main.py')
@ -202,12 +207,14 @@ class GuiAutoImport (threading.Thread):
bs = 1
try:
print "opening pipe to HUD"
self.pipe_to_hud = subprocess.Popen(command, bufsize=bs,
stdin=subprocess.PIPE,
universal_newlines=True)
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
self.addText( "\n*** GuiAutoImport Error opening pipe: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]))
#self.addText( "\n*** GuiAutoImport Error opening pipe: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]))
self.addText( "\n*** GuiAutoImport Error opening pipe: " + traceback.format_exc() )
else:
for site in self.input_settings:
self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1])
@ -300,7 +307,6 @@ if __name__== "__main__":
(options, argv) = parser.parse_args()
config = Configuration.Config()
# db = fpdb_db.fpdb_db()
settings = {}
settings['minPrint'] = options.minPrint

View File

@ -30,7 +30,6 @@ import gtk
import gobject
# fpdb/FreePokerTools modules
import fpdb_simple
import fpdb_import
import Configuration
import Exceptions
@ -90,7 +89,8 @@ class GuiBulkImport():
for selection in selected:
self.importer.addBulkImportImportFileOrDir(selection, site = sitename)
self.importer.setCallHud(False)
self.importer.setCallHud(self.cb_testmode.get_active())
self.importer.bHudTest = self.cb_testmode.get_active()
starttime = time()
# try:
(stored, dups, partial, errs, ttime) = self.importer.runImport()
@ -106,6 +106,9 @@ class GuiBulkImport():
print 'GuiBulkImport.load done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %.0f/sec'\
% (stored, dups, partial, errs, ttime, (stored+0.0) / ttime)
self.importer.clearFileList()
# This file should really be 'logging'
#log.info('GuiBulkImport.load done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %.0f/sec'\
# % (stored, dups, partial, errs, ttime, (stored+0.0) / ttime))
if self.n_hands_in_db == 0 and stored > 0:
self.cb_dropindexes.set_sensitive(True)
self.cb_dropindexes.set_active(0)
@ -229,6 +232,10 @@ class GuiBulkImport():
ypadding=0, yoptions=gtk.SHRINK)
self.cb_dropindexes.show()
self.cb_testmode = gtk.CheckButton('HUD Test mode')
self.table.attach(self.cb_testmode, 0, 1, 2, 3, xpadding=10, ypadding=0, yoptions=gtk.SHRINK)
self.cb_testmode.show()
# label - filter
self.lab_filter = gtk.Label("Site filter:")
self.table.attach(self.lab_filter, 1, 2, 2, 3, xpadding=0, ypadding=0,
@ -239,7 +246,17 @@ class GuiBulkImport():
# ComboBox - filter
self.cbfilter = gtk.combo_box_new_text()
disabled_sites = [] # move disabled sites to bottom of list
for w in self.config.hhcs:
try:
if self.config.supported_sites[w].enabled: # include enabled ones first
print w
self.cbfilter.append_text(w)
else:
disabled_sites.append(w)
except: # self.supported_sites[w] may not exist if hud_config is bad
disabled_sites.append(w)
for w in disabled_sites: # then disabled ones
print w
self.cbfilter.append_text(w)
self.cbfilter.set_active(0)

View File

@ -27,7 +27,7 @@ from time import *
try:
import matplotlib
matplotlib.use('GTK')
matplotlib.use('GTKCairo')
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
@ -44,6 +44,7 @@ except ImportError, inst:
import fpdb_import
import Database
import Filters
import Charset
class GuiGraphViewer (threading.Thread):
@ -137,6 +138,8 @@ class GuiGraphViewer (threading.Thread):
heroes = self.filters.getHeroes()
siteids = self.filters.getSiteIds()
limits = self.filters.getLimits()
games = self.filters.getGames()
for i in ('show', 'none'):
if i in limits:
limits.remove(i)
@ -144,11 +147,10 @@ class GuiGraphViewer (threading.Thread):
for site in sites:
if sites[site] == True:
sitenos.append(siteids[site])
c = self.db.get_cursor()
c.execute(self.sql.query['getPlayerId'], (heroes[site],))
result = c.fetchall()
if len(result) == 1:
playerids.append( int(result[0][0]) )
_hname = Charset.to_utf8(heroes[site])
result = self.db.get_player_id(self.conf, site, _hname)
if result is not None:
playerids.append(int(result))
if not sitenos:
#Should probably pop up here.
@ -171,20 +173,49 @@ class GuiGraphViewer (threading.Thread):
#Get graph data from DB
starttime = time()
(green, blue, red) = self.getRingProfitGraph(playerids, sitenos, limits)
(green, blue, red) = self.getRingProfitGraph(playerids, sitenos, limits, games)
print "Graph generated in: %s" %(time() - starttime)
self.ax.set_title("Profit graph for ring games")
#Set axis labels and grid overlay properites
self.ax.set_xlabel("Hands", fontsize = 12)
self.ax.set_ylabel("$", fontsize = 12)
self.ax.grid(color='g', linestyle=':', linewidth=0.2)
if green == None or green == []:
self.ax.set_title("No Data for Player(s) Found")
green = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
700., 600., 500., 400., 300., 200., 100., 0.,
500., 1000., 1000., 1000., 1000., 1000., 1000., 1000.,
1000., 1000., 1000., 1000., 1000., 1000., 875., 750.,
625., 500., 375., 250., 125., 0., 0., 0.,
0., 500., 1000., 900., 800., 700., 600., 500.,
400., 300., 200., 100., 0., 500., 1000., 1000.])
red = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
700., 600., 500., 400., 300., 200., 100., 0.,
0., 0., 0., 0., 0., 0., 125., 250.,
375., 500., 500., 500., 500., 500., 500., 500.,
500., 500., 375., 250., 125., 0., 0., 0.,
0., 500., 1000., 900., 800., 700., 600., 500.,
400., 300., 200., 100., 0., 500., 1000., 1000.])
blue = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
700., 600., 500., 400., 300., 200., 100., 0.,
0., 0., 0., 0., 0., 0., 125., 250.,
375., 500., 625., 750., 875., 1000., 875., 750.,
625., 500., 375., 250., 125., 0., 0., 0.,
0., 500., 1000., 900., 800., 700., 600., 500.,
400., 300., 200., 100., 0., 500., 1000., 1000.])
self.ax.plot(green, color='green', label='Hands: %d\nProfit: $%.2f' %(len(green), green[-1]))
self.ax.plot(blue, color='blue', label='Showdown: $%.2f' %(blue[-1]))
self.ax.plot(red, color='red', label='Non-showdown: $%.2f' %(red[-1]))
self.graphBox.add(self.canvas)
self.canvas.show()
self.canvas.draw()
#TODO: Do something useful like alert user
print "No hands returned by graph query"
#print "No hands returned by graph query"
else:
self.ax.set_title("Profit graph for ring games")
#text = "Profit: $%.2f\nTotal Hands: %d" %(green[-1], len(green))
#self.ax.annotate(text,
# xy=(10, -10),
@ -201,7 +232,6 @@ class GuiGraphViewer (threading.Thread):
else:
self.ax.legend(loc='best', fancybox=True, shadow=True, prop=FontProperties(size='smaller'))
self.graphBox.add(self.canvas)
self.canvas.show()
self.canvas.draw()
@ -212,7 +242,7 @@ class GuiGraphViewer (threading.Thread):
#end of def showClicked
def getRingProfitGraph(self, names, sites, limits):
def getRingProfitGraph(self, names, sites, limits, games):
tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite']
# print "DEBUG: getRingProfitGraph"
start_date, end_date = self.filters.getDates()
@ -224,6 +254,22 @@ class GuiGraphViewer (threading.Thread):
sitetest = str(tuple(sites))
#nametest = nametest.replace("L", "")
q = []
for m in self.filters.display.items():
if m[0] == 'Games' and m[1]:
for n in games:
if games[n]:
q.append(n)
if len(q) > 0:
gametest = str(tuple(q))
gametest = gametest.replace("L", "")
gametest = gametest.replace(",)",")")
gametest = gametest.replace("u'","'")
gametest = "and gt.category in %s" % gametest
else:
gametest = "and gt.category IS NULL"
tmp = tmp.replace("<game_test>", gametest)
lims = [int(x) for x in limits if x.isdigit()]
potlims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'pl']
nolims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'nl']
@ -273,8 +319,8 @@ class GuiGraphViewer (threading.Thread):
winnings = self.db.cursor.fetchall()
self.db.rollback()
if winnings == ():
return None
if len(winnings) == 0:
return (None, None, None)
green = map(lambda x:float(x[1]), winnings)
blue = map(lambda x: float(x[1]) if x[2] == True else 0.0, winnings)

View File

@ -26,13 +26,13 @@ import gtk
import gobject
import pango
import Configuration
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("logview")
log = Configuration.get_logger("logging.conf", "logview")
MAX_LINES = 100000 # max lines to display in window
EST_CHARS_PER_LINE = 150 # used to guesstimate number of lines in log file
logfile = 'logging.out' # name of logfile
class GuiLogView:
@ -41,6 +41,7 @@ class GuiLogView:
self.main_window = mainwin
self.closeq = closeq
self.logfile = self.config.log_file # name of logfile
self.dia = gtk.Dialog(title="Log Messages"
,parent=None
,flags=gtk.DIALOG_DESTROY_WITH_PARENT
@ -117,10 +118,10 @@ class GuiLogView:
self.listcols = []
# guesstimate number of lines in file
if os.path.exists(logfile):
stat_info = os.stat(logfile)
if os.path.exists(self.logfile):
stat_info = os.stat(self.logfile)
lines = stat_info.st_size / EST_CHARS_PER_LINE
print "logview: size =", stat_info.st_size, "lines =", lines
#print "logview: size =", stat_info.st_size, "lines =", lines
# set startline to line number to start display from
startline = 0
@ -129,7 +130,7 @@ class GuiLogView:
startline = lines - MAX_LINES
l = 0
for line in open(logfile):
for line in open(self.logfile):
# eg line:
# 2009-12-02 15:23:21,716 - config DEBUG config logger initialised
l = l + 1

View File

@ -27,8 +27,8 @@ from time import time, strftime
import Card
import fpdb_import
import Database
import fpdb_db
import Filters
import Charset
colalias,colshow,colheading,colxalign,colformat,coltype = 0,1,2,3,4,5
ranks = {'x':0, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, 'T':10, 'J':11, 'Q':12, 'K':13, 'A':14}
@ -40,7 +40,7 @@ class GuiPlayerStats (threading.Thread):
self.conf = config
self.main_window = mainwin
self.sql = querylist
self.liststore = [] # gtk.ListStore[] stores the contents of the grids
self.listcols = [] # gtk.TreeViewColumn[][] stores the columns in the grids
@ -64,7 +64,7 @@ class GuiPlayerStats (threading.Thread):
filters_display = { "Heroes" : True,
"Sites" : True,
"Games" : False,
"Games" : True,
"Limits" : True,
"LimitSep" : True,
"LimitType" : True,
@ -180,6 +180,7 @@ class GuiPlayerStats (threading.Thread):
seats = self.filters.getSeats()
groups = self.filters.getGroups()
dates = self.filters.getDates()
games = self.filters.getGames()
sitenos = []
playerids = []
@ -187,12 +188,10 @@ class GuiPlayerStats (threading.Thread):
for site in sites:
if sites[site] == True:
sitenos.append(siteids[site])
# Nasty hack to deal with multiple sites + same player name -Eric
que = self.sql.query['getPlayerId'] + " AND siteId=%d" % siteids[site]
self.cursor.execute(que, (heroes[site],))
result = self.db.cursor.fetchall()
if len(result) == 1:
playerids.append(result[0][0])
_hname = Charset.to_utf8(heroes[site])
result = self.db.get_player_id(self.conf, site, _hname)
if result is not None:
playerids.append(int(result))
if not sitenos:
#Should probably pop up here.
@ -205,9 +204,9 @@ class GuiPlayerStats (threading.Thread):
print "No limits found"
return
self.createStatsTable(vbox, playerids, sitenos, limits, type, seats, groups, dates)
self.createStatsTable(vbox, playerids, sitenos, limits, type, seats, groups, dates, games)
def createStatsTable(self, vbox, playerids, sitenos, limits, type, seats, groups, dates):
def createStatsTable(self, vbox, playerids, sitenos, limits, type, seats, groups, dates, games):
starttime = time()
# Scrolled window for summary table
@ -223,7 +222,7 @@ class GuiPlayerStats (threading.Thread):
# gridnum - index for grid data structures
flags = [False, self.filters.getNumHands(), 0]
self.addGrid(swin, 'playerDetailedStats', flags, playerids
,sitenos, limits, type, seats, groups, dates)
,sitenos, limits, type, seats, groups, dates, games)
# Separator
vbox2 = gtk.VBox(False, 0)
@ -243,7 +242,7 @@ class GuiPlayerStats (threading.Thread):
flags[0] = True
flags[2] = 1
self.addGrid(swin, 'playerDetailedStats', flags, playerids
,sitenos, limits, type, seats, groups, dates)
,sitenos, limits, type, seats, groups, dates, games)
self.db.rollback()
print "Stats page displayed in %4.2f seconds" % (time() - starttime)
@ -275,7 +274,7 @@ class GuiPlayerStats (threading.Thread):
except: a = 0.0
try: b = float(b)
except: b = 0.0
if n == 0:
if n == 0 and grid == 1: #make sure it only works on the starting hands
a1,a2,a3 = ranks[a[0]], ranks[a[1]], (a+'o')[2]
b1,b2,b3 = ranks[b[0]], ranks[b[1]], (b+'o')[2]
if a1 > b1 or ( a1 == b1 and (a2 > b2 or (a2 == b2 and a3 > b3) ) ):
@ -317,7 +316,7 @@ class GuiPlayerStats (threading.Thread):
print "***sortcols error: " + str(sys.exc_info()[1])
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
def addGrid(self, vbox, query, flags, playerids, sitenos, limits, type, seats, groups, dates):
def addGrid(self, vbox, query, flags, playerids, sitenos, limits, type, seats, groups, dates, games):
counter = 0
row = 0
sqlrow = 0
@ -325,7 +324,7 @@ class GuiPlayerStats (threading.Thread):
else: holecards,grid = flags[0],flags[2]
tmp = self.sql.query[query]
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, type, seats, groups, dates)
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, type, seats, groups, dates, games)
self.cursor.execute(tmp)
result = self.cursor.fetchall()
colnames = [desc[0].lower() for desc in self.cursor.description]
@ -428,7 +427,7 @@ class GuiPlayerStats (threading.Thread):
#end def addGrid(self, query, vars, playerids, sitenos, limits, type, seats, groups, dates):
def refineQuery(self, query, flags, playerids, sitenos, limits, type, seats, groups, dates):
def refineQuery(self, query, flags, playerids, sitenos, limits, type, seats, groups, dates, games):
having = ''
if not flags:
holecards = False
@ -466,6 +465,23 @@ class GuiPlayerStats (threading.Thread):
query = query.replace("<playerName>", pname)
query = query.replace("<havingclause>", having)
gametest = ""
q = []
for m in self.filters.display.items():
if m[0] == 'Games' and m[1]:
for n in games:
if games[n]:
q.append(n)
if len(q) > 0:
gametest = str(tuple(q))
gametest = gametest.replace("L", "")
gametest = gametest.replace(",)",")")
gametest = gametest.replace("u'","'")
gametest = "and gt.category in %s" % gametest
else:
gametest = "and gt.category IS NULL"
query = query.replace("<game_test>", gametest)
if seats:
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
if 'show' in seats and seats['show']:
@ -504,26 +520,26 @@ class GuiPlayerStats (threading.Thread):
blindtest = str(tuple(nolims))
blindtest = blindtest.replace("L", "")
blindtest = blindtest.replace(",)",")")
bbtest = bbtest + blindtest + ' ) )'
bbtest = bbtest + blindtest + ' ) ) )'
else:
bbtest = bbtest + '(-1) ) )'
if type == 'ring':
bbtest = bbtest + " and gt.type = 'ring' "
elif type == 'tour':
bbtest = bbtest + " and gt.type = 'tour' "
bbtest = " and gt.type = 'tour' "
query = query.replace("<gtbigBlind_test>", bbtest)
if holecards: # re-use level variables for hole card query
query = query.replace("<hgameTypeId>", "hp.startcards")
query = query.replace("<orderbyhgameTypeId>"
, ",case when floor(hp.startcards/13) >= mod(hp.startcards,13) then hp.startcards + 0.1 "
+ " else 13*mod(hp.startcards,13) + floor(hp.startcards/13) "
, ",case when floor((hp.startcards-1)/13) >= mod((hp.startcards-1),13) then hp.startcards + 0.1 "
+ " else 13*mod((hp.startcards-1),13) + floor((hp.startcards-1)/13) + 1 "
+ " end desc ")
else:
query = query.replace("<orderbyhgameTypeId>", "")
groupLevels = "show" not in str(limits)
if groupLevels:
query = query.replace("<hgameTypeId>", "-1")
query = query.replace("<hgameTypeId>", "p.name")
else:
query = query.replace("<hgameTypeId>", "h.gameTypeId")

View File

@ -47,6 +47,7 @@ import fpdb_import
import Database
import Filters
import FpdbSQLQueries
import Charset
class GuiSessionViewer (threading.Thread):
def __init__(self, config, querylist, mainwin, debug=True):
@ -181,7 +182,10 @@ class GuiSessionViewer (threading.Thread):
for site in sites:
if sites[site] == True:
sitenos.append(siteids[site])
self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
_q = self.sql.query['getPlayerId']
_name = Charset.to_utf8(heroes[site])
print 'DEBUG(_name) :: %s' % _name
self.cursor.execute(_q, (_name,)) # arg = tuple
result = self.db.cursor.fetchall()
if len(result) == 1:
playerids.append(result[0][0])

View File

@ -20,7 +20,6 @@ import pygtk
pygtk.require('2.0')
import gtk
import os
import fpdb_simple
import fpdb_import
import fpdb_db
@ -80,7 +79,8 @@ class GuiTableViewer (threading.Thread):
#then the data rows
for player in range(len(self.player_names)):
tmp=[]
tmp.append(self.player_names[player][0])
p_name = Charset.to_gui(self.player_names[player][0])
tmp.append(p_name)
seatCount=len(self.player_names)
if seatCount>=8:

View File

@ -445,64 +445,102 @@ Left-Drag to Move"
<location seat="9" x="70" y="53"> </location>
</layout>
</site>
<site HH_path="C:/Program Files/Carbon Poker/HandHistory/YOUR SCREEN NAME HERE/" converter="CarbonToFpdb" decoder="everleaf_decode_table" enabled="True" screen_name="YOUR SCREEN NAME HERE" site_name="Carbon" site_path="C:/Program Files/Carbin/" supported_games="holdem" table_finder="Carbon Poker.exe">
<layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location>
<location seat="2" x="650" y="230"> </location>
<location seat="3" x="650" y="385"> </location>
<location seat="4" x="588" y="425"> </location>
<location seat="5" x="92" y="425"> </location>
<location seat="6" x="0" y="373"> </location>
<location seat="7" x="0" y="223"> </location>
<location seat="8" x="25" y="50"> </location>
</layout>
<layout fav_seat="0" height="547" max="6" width="794">
<location seat="1" x="640" y="58"> </location>
<location seat="2" x="654" y="288"> </location>
<location seat="3" x="615" y="424"> </location>
<location seat="4" x="70" y="421"> </location>
<location seat="5" x="0" y="280"> </location>
<location seat="6" x="70" y="58"> </location>
</layout>
<layout fav_seat="0" height="547" max="2" width="794">
<location seat="1" x="651" y="288"> </location>
<location seat="2" x="10" y="288"> </location>
</layout>
<layout fav_seat="0" height="547" max="9" width="794">
<location seat="1" x="634" y="38"> </location>
<location seat="2" x="667" y="184"> </location>
<location seat="3" x="667" y="321"> </location>
<location seat="4" x="667" y="445"> </location>
<location seat="5" x="337" y="459"> </location>
<location seat="6" x="0" y="400"> </location>
<location seat="7" x="0" y="322"> </location>
<location seat="8" x="0" y="181"> </location>
<location seat="9" x="70" y="53"> </location>
</layout>
</site>
</supported_sites>
<supported_games>
<game aux="mucked" cols="3" db="fpdb" game_name="holdem" rows="3">
<stat click="tog_decorate" col="0" popup="default" row="0" stat_name="vpip" stat_loth="25" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="pfr" stat_loth="20" stat_locolor ="#408000" stat_hith="35" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="0" stat_name="three_B" stat_loth="4" stat_locolor ="#408000" stat_hith="13" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" hudcolor="#98FFB0" hudprefix="" hudsuffix="" popup="default" row="1" stat_name="playername" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="1" stat_name="cb1" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="wtsd" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="steal" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="2" stat_name="totalprofit" stat_loth="0" stat_locolor ="#F05000" stat_hith="0" stat_hicolor ="#408000" tip="tip1"> </stat>
</game>
<game aux="mucked" cols="2" db="fpdb" game_name="holdem" rows="3">
<game aux="stud_mucked" cols="2" db="fpdb" game_name="razz" rows="3">
<stat click="tog_decorate" col="0" hudcolor="#98FFB0" hudprefix="" hudsuffix="" popup="default" row="0" stat_name="playername" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="wtsd" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="cb1" tip="tip1"> </stat>
</game>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<game aux="stud_mucked" cols="2" db="fpdb" game_name="razz" rows="3">
<game aux="mucked" cols="2" db="fpdb" game_name="omahahi" rows="3">
<stat click="tog_decorate" col="0" hudcolor="#98FFB0" hudprefix="" hudsuffix="" popup="default" row="0" stat_name="playername" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<game aux="mucked" cols="2" db="fpdb" game_name="omahahi" rows="3">
<game aux="mucked" cols="2" db="fpdb" game_name="omahahilo" rows="3">
<stat click="tog_decorate" col="0" hudcolor="#98FFB0" hudprefix="" hudsuffix="" popup="default" row="0" stat_name="playername" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<game aux="mucked" cols="2" db="fpdb" game_name="omahahilo" rows="3">
<game aux="stud_mucked" cols="2" db="fpdb" game_name="studhi" rows="3">
<stat click="tog_decorate" col="0" hudcolor="#98FFB0" hudprefix="" hudsuffix="" popup="default" row="0" stat_name="playername" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<game aux="stud_mucked" cols="2" db="fpdb" game_name="studhi" rows="3">
<game aux="stud_mucked" cols="2" db="fpdb" game_name="studhilo" rows="3">
<stat click="tog_decorate" col="0" hudcolor="#98FFB0" hudprefix="" hudsuffix="" popup="default" row="0" stat_name="playername" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<game aux="stud_mucked" cols="2" db="fpdb" game_name="studhilo" rows="3">
<stat click="tog_decorate" col="0" hudcolor="#98FFB0" hudprefix="" hudsuffix="" popup="default" row="0" stat_name="playername" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
<stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="vpip" stat_loth="20" stat_locolor ="#408000" stat_hith="40" stat_hicolor ="#F05000" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="2" stat_name="saw_f" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game>
</supported_games>
<popup_windows>
@ -584,11 +622,12 @@ Left-Drag to Move"
<hhc site="PartyPoker" converter="PartyPokerToFpdb"/>
<hhc site="Betfair" converter="BetfairToFpdb"/>
<hhc site="Partouche" converter="PartoucheToFpdb"/>
<hhc site="Carbon" converter="CarbonToFpdb"/>
</hhcs>
<supported_databases>
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database>
<!-- <database db_ip="localhost" db_name="fpdb" db_pass="fpdb" db_server="sqlite" db_user="fpdb"/> -->
<!-- <database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database> -->
<database db_ip="localhost" db_server="sqlite" db_name="fpdb.db3" db_user="fpdb" db_pass="fpdb"/>
</supported_databases>
</FreePokerToolsConfig>

View File

@ -35,11 +35,6 @@ import traceback
(options, argv) = Options.fpdb_options()
if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('HUD-error.txt', 'w', 0)
sys.stderr = errorFile
import thread
import time
import string
@ -52,6 +47,8 @@ import gobject
# FreePokerTools modules
import Configuration
import Database
from HandHistoryConverter import getTableTitleRe
# get the correct module for the current os
@ -63,7 +60,9 @@ elif os.name == 'nt':
import Hud
log = Configuration.get_logger("logging.conf")
# get config and set up logger
c = Configuration.Config(file=options.config)
log = Configuration.get_logger("logging.conf", "hud", log_dir=c.dir_log, log_file='HUD-log.txt')
class HUD_main(object):
@ -71,26 +70,47 @@ class HUD_main(object):
# This class mainly provides state for controlling the multiple HUDs.
def __init__(self, db_name = 'fpdb'):
print "\nHUD_main: starting ..."
self.db_name = db_name
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
self.hud_dict = {}
self.hud_params = self.config.get_hud_ui_parameters()
self.config = c
print "Logfile is " + os.path.join(self.config.dir_log, 'HUD-log.txt')
log.info("HUD_main starting: using db name = %s" % (db_name))
# a thread to read stdin
gobject.threads_init() # this is required
thread.start_new_thread(self.read_stdin, ()) # starts the thread
try:
if not options.errorsToConsole:
fileName = os.path.join(self.config.dir_log, 'HUD-errors.txt')
print "Note: error output is being diverted to:\n"+fileName \
+ "\nAny major error will be reported there _only_.\n"
log.info("Note: error output is being diverted to:"+fileName)
log.info("Any major error will be reported there _only_.")
errorFile = open(fileName, 'w', 0)
sys.stderr = errorFile
sys.stderr.write("HUD_main: starting ...\n")
self.hud_dict = {}
self.hud_params = self.config.get_hud_ui_parameters()
# a thread to read stdin
gobject.threads_init() # this is required
thread.start_new_thread(self.read_stdin, ()) # starts the thread
# a main window
self.main_window = gtk.Window()
self.main_window.connect("destroy", self.destroy)
self.vb = gtk.VBox()
self.label = gtk.Label('Closing this window will exit from the HUD.')
self.vb.add(self.label)
self.main_window.add(self.vb)
self.main_window.set_title("HUD Main Window")
self.main_window.show_all()
except:
log.error( "*** Exception in HUD_main.init() *** " )
for e in traceback.format_tb(sys.exc_info()[2]):
log.error(e)
# a main window
self.main_window = gtk.Window()
self.main_window.connect("destroy", self.destroy)
self.vb = gtk.VBox()
self.label = gtk.Label('Closing this window will exit from the HUD.')
self.vb.add(self.label)
self.main_window.add(self.vb)
self.main_window.set_title("HUD Main Window")
self.main_window.show_all()
def destroy(self, *args): # call back for terminating the main eventloop
log.info("Terminating normally.")
gtk.main_quit()
def kill_hud(self, event, table):
@ -123,8 +143,9 @@ class HUD_main(object):
self.hud_dict[table_name].update(new_hand_id, self.config)
self.hud_dict[table_name].reposition_windows()
except:
print "*** Exception in HUD_main::idle_func() *** "
traceback.print_stack()
log.error( "*** Exception in HUD_main::idle_func() *** " + str(sys.exc_info()) )
for e in traceback.format_tb(sys.exc_info()[2]):
log.error(e)
finally:
gtk.gdk.threads_leave()
return False
@ -186,30 +207,38 @@ class HUD_main(object):
# get hero's screen names and player ids
self.hero, self.hero_ids = {}, {}
for site in self.config.get_supported_sites():
result = self.db_connection.get_site_id(site)
if result:
site_id = result[0][0]
self.hero[site_id] = self.config.supported_sites[site].screen_name
self.hero_ids[site_id] = self.db_connection.get_player_id(self.config, site, self.hero[site_id])
found = False
while 1: # wait for a new hand number on stdin
new_hand_id = sys.stdin.readline()
t0 = time.time()
t1 = t2 = t3 = t4 = t5 = t6 = t0
new_hand_id = string.rstrip(new_hand_id)
log.debug("Received hand no %s" % new_hand_id)
if new_hand_id == "": # blank line means quit
self.destroy()
break # this thread is not always killed immediately with gtk.main_quit()
if not found:
for site in self.config.get_supported_sites():
result = self.db_connection.get_site_id(site)
if result:
site_id = result[0][0]
self.hero[site_id] = self.config.supported_sites[site].screen_name
self.hero_ids[site_id] = self.db_connection.get_player_id(self.config, site, self.hero[site_id])
if self.hero_ids[site_id] is not None:
found = True
else:
self.hero_ids[site_id] = -1
# get basic info about the new hand from the db
# if there is a db error, complain, skip hand, and proceed
log.info("HUD_main.read_stdin: hand processing starting ...")
try:
(table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \
self.db_connection.get_table_info(new_hand_id)
except Exception, err: # TODO: we need to make this a much less generic Exception lulz
print "db error: skipping %s" % new_hand_id
sys.stderr.write("Database error: could not find hand %s.\n" % new_hand_id)
except Exception, err:
log.error("db error: skipping %s" % new_hand_id)
continue
t1 = time.time()
@ -228,18 +257,17 @@ class HUD_main(object):
,self.hero_ids[site_id], num_seats)
t3 = time.time()
try:
self.db_connection.init_hud_stat_vars( self.hud_dict[temp_key].hud_params['hud_days']
, self.hud_dict[temp_key].hud_params['h_hud_days'])
stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_dict[temp_key].hud_params, self.hero_ids[site_id])
self.hud_dict[temp_key].stat_dict = stat_dict
except KeyError: # HUD instance has been killed off, key is stale
sys.stderr.write('hud_dict[%s] was not found\n' % temp_key)
sys.stderr.write('will not send hand\n')
log.error('hud_dict[%s] was not found\n' % temp_key)
log.error('will not send hand\n')
# Unlocks table, copied from end of function
self.db_connection.connection.rollback()
return
cards = self.db_connection.get_cards(new_hand_id)
t4 = time.time()
comm_cards = self.db_connection.get_common_cards(new_hand_id)
t5 = time.time()
if comm_cards != {}: # stud!
cards['common'] = comm_cards['common']
self.hud_dict[temp_key].cards = cards
@ -250,24 +278,24 @@ class HUD_main(object):
else:
# get stats using default params--also get cards
self.db_connection.init_hud_stat_vars( self.hud_params['hud_days'], self.hud_params['h_hud_days'] )
t4 = time.time()
stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_params
,self.hero_ids[site_id], num_seats)
t5 = time.time()
cards = self.db_connection.get_cards(new_hand_id)
comm_cards = self.db_connection.get_common_cards(new_hand_id)
if comm_cards != {}: # stud!
cards['common'] = comm_cards['common']
table_kwargs = dict(table_name = table_name, tournament = tour_number, table_number = tab_number)
search_string = getTableTitleRe(self.config, site, type, **table_kwargs)
search_string = getTableTitleRe(self.config, site_name, type, **table_kwargs)
# print "getTableTitleRe ", self.config, site_name, type, "=", search_string
tablewindow = Tables.Table(search_string, **table_kwargs)
if tablewindow is None:
# If no client window is found on the screen, complain and continue
if type == "tour":
table_name = "%s %s" % (tour_number, tab_number)
sys.stderr.write("HUD create: table name "+table_name+" not found, skipping.\n")
# log.error("HUD create: table name "+table_name+" not found, skipping.\n")
log.error("HUD create: table name %s not found, skipping." % table_name)
else:
tablewindow.max = max
tablewindow.site = site_name
@ -275,7 +303,7 @@ class HUD_main(object):
if hasattr(tablewindow, 'number'):
self.create_HUD(new_hand_id, tablewindow, temp_key, max, poker_game, type, stat_dict, cards)
else:
sys.stderr.write('Table "%s" no longer exists\n' % table_name)
log.error('Table "%s" no longer exists\n' % table_name)
t6 = time.time()
log.info("HUD_main.read_stdin: hand read in %4.3f seconds (%4.3f,%4.3f,%4.3f,%4.3f,%4.3f,%4.3f)"
@ -284,9 +312,6 @@ class HUD_main(object):
if __name__== "__main__":
sys.stderr.write("HUD_main starting\n")
sys.stderr.write("Using db name = %s\n" % (options.dbname))
# start the HUD_main object
hm = HUD_main(db_name = options.dbname)

View File

@ -21,19 +21,24 @@
import re
import sys
import traceback
import logging
import os
import os.path
from decimal import Decimal
import operator
import time,datetime
from copy import deepcopy
from Exceptions import *
import pprint
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("parser")
import Configuration
from Exceptions import *
import DerivedStats
import Card
log = logging.getLogger("parser")
class Hand(object):
@ -43,10 +48,12 @@ class Hand(object):
LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'}
SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'}
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 }
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9, 'Partouche':10, 'Carbon':11 }
def __init__(self, sitename, gametype, handText, builtFrom = "HHC"):
def __init__(self, config, sitename, gametype, handText, builtFrom = "HHC"):
self.config = config
#log = Configuration.get_logger("logging.conf", "db", log_dir=self.config.dir_log)
self.sitename = sitename
self.siteId = self.SITEIDS[sitename]
self.stats = DerivedStats.DerivedStats(self)
@ -192,7 +199,7 @@ dealt whether they were seen in a 'dealt to' line
self.holecards[street][player] = [open, closed]
def prepInsert(self, db):
#####
#####
# Players, Gametypes, TourneyTypes are all shared functions that are needed for additional tables
# These functions are intended for prep insert eventually
#####
@ -205,7 +212,7 @@ dealt whether they were seen in a 'dealt to' line
def insert(self, db):
""" Function to insert Hand into database
Should not commit, and do minimal selects. Callers may want to cache commits
db: a connected fpdb_db object"""
db: a connected Database object"""
self.stats.getStats(self)
@ -229,8 +236,10 @@ db: a connected fpdb_db object"""
# TourneysPlayers
else:
log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo'])
#Raise Duplicate exception?
pass
raise FpdbHandDuplicate(hh['siteHandNo'])
def updateHudCache(self, db):
db.storeHudCache(self.dbid_gt, self.dbid_pids, self.starttime, self.stats.getHandsPlayers())
def select(self, handId):
""" Function to create Hand object from database """
@ -286,6 +295,24 @@ If a player has None chips he won't be added."""
c = c.replace(k,v)
return c
def addAllIn(self, street, player, amount):
"""\
For sites (currently only Carbon Poker) which record "all in" as a special action, which can mean either "calls and is all in" or "raises all in".
"""
self.checkPlayerExists(player)
amount = re.sub(u',', u'', amount) #some sites have commas
Ai = Decimal(amount)
Bp = self.lastBet[street]
Bc = reduce(operator.add, self.bets[street][player], 0)
C = Bp - Bc
if Ai <= C:
self.addCall(street, player, amount)
elif Bp == 0:
self.addBet(street, player, amount)
else:
Rb = Ai - C
self._addRaise(street, player, C, Rb, Ai)
def addAnte(self, player, ante):
log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
if player is not None:
@ -317,6 +344,11 @@ If a player has None chips he won't be added."""
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb))
if blindtype == 'secondsb':
amount = Decimal(0)
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb))
self.bets['PREFLOP'][player].append(Decimal(amount))
self.pot.addMoney(player, Decimal(amount))
self.lastBet['PREFLOP'] = Decimal(amount)
@ -393,7 +425,7 @@ Add a raise on [street] by [player] to [amountTo]
Bc = reduce(operator.add, self.bets[street][player], 0)
Rt = Decimal(amountTo)
C = Bp - Bc
Rb = Rt - C
Rb = Rt - C - Bc
self._addRaise(street, player, C, Rb, Rt)
def _addRaise(self, street, player, C, Rb, Rt):
@ -597,7 +629,8 @@ Map the tuple self.gametype onto the pokerstars string describing it
class HoldemOmahaHand(Hand):
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
self.config = config
if gametype['base'] != 'hold':
pass # or indeed don't pass and complain instead
log.debug("HoldemOmahaHand")
@ -605,7 +638,7 @@ class HoldemOmahaHand(Hand):
self.holeStreets = ['PREFLOP']
self.communityStreets = ['FLOP', 'TURN', 'RIVER']
self.actionStreets = ['BLINDSANTES','PREFLOP','FLOP','TURN','RIVER']
Hand.__init__(self, sitename, gametype, handText, builtFrom = "HHC")
Hand.__init__(self, self.config, sitename, gametype, handText, builtFrom = "HHC")
self.sb = gametype['sb']
self.bb = gametype['bb']
@ -614,6 +647,8 @@ class HoldemOmahaHand(Hand):
# which then invokes a 'addXXX' callback
if builtFrom == "HHC":
hhc.readHandInfo(self)
if self.gametype['type'] == 'tour':
self.tablename = "%s %s" % (self.tourNo, self.tablename)
hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(self)
hhc.markStreets(self)
@ -678,7 +713,6 @@ class HoldemOmahaHand(Hand):
def join_holecards(self, player, asList=False):
"""With asList = True it returns the set cards for a player including down cards if they aren't know"""
# FIXME: This should actually return
hcs = [u'0x', u'0x', u'0x', u'0x']
for street in self.holeStreets:
@ -895,7 +929,8 @@ class HoldemOmahaHand(Hand):
print >>fh, "\n\n"
class DrawHand(Hand):
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC"):
def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC"):
self.config = config
if gametype['base'] != 'draw':
pass # or indeed don't pass and complain instead
self.streetList = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
@ -903,12 +938,14 @@ class DrawHand(Hand):
self.holeStreets = ['DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.actionStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.communityStreets = []
Hand.__init__(self, sitename, gametype, handText)
Hand.__init__(self, self.config, sitename, gametype, handText)
self.sb = gametype['sb']
self.bb = gametype['bb']
# Populate the draw hand.
if builtFrom == "HHC":
hhc.readHandInfo(self)
if self.gametype['type'] == 'tour':
self.tablename = "%s %s" % (self.tourNo, self.tablename)
hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(self)
hhc.markStreets(self)
@ -1085,7 +1122,8 @@ class DrawHand(Hand):
class StudHand(Hand):
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC"):
def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC"):
self.config = config
if gametype['base'] != 'stud':
pass # or indeed don't pass and complain instead
@ -1095,7 +1133,7 @@ class StudHand(Hand):
self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
Hand.__init__(self, sitename, gametype, handText)
Hand.__init__(self, self.config, sitename, gametype, handText)
self.sb = gametype['sb']
self.bb = gametype['bb']
#Populate the StudHand
@ -1103,6 +1141,8 @@ class StudHand(Hand):
# which then invokes a 'addXXX' callback
if builtFrom == "HHC":
hhc.readHandInfo(self)
if self.gametype['type'] == 'tour':
self.tablename = "%s %s" % (self.tourNo, self.tablename)
hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(self)
hhc.markStreets(self)
@ -1194,7 +1234,7 @@ Add a complete on [street] by [player] to [amountTo]
# showdownPot INT, /* pot size at sd/street7 */
return (0,0,0,0,0)
def writeHand(self, fh=sys.__stdout__):
# PokerStars format.
@ -1406,6 +1446,11 @@ class Pot(object):
# Return any uncalled bet.
committed = sorted([ (v,k) for (k,v) in self.committed.items()])
print "DEBUG: committed: %s" % committed
#ERROR below. lastbet is correct in most cases, but wrong when
# additional money is committed to the pot in cash games
# due to an additional sb being posted. (Speculate that
# posting sb+bb is also potentially wrong)
lastbet = committed[-1][0] - committed[-2][0]
if lastbet > 0: # uncalled
returnto = committed[-1][1]
@ -1486,7 +1531,8 @@ limit 1""", {'handid':handid})
#TODO: siteid should be in hands table - we took the scenic route through players here.
res = c.fetchone()
gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]}
h = HoldemOmahaHand(hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
c = Configuration.Config()
h = HoldemOmahaHand(config = c, hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
cards = map(Card.valueSuitFromCard, res[11:16] )
if cards[0]:
h.setCommunityCards('FLOP', cards[0:3])
@ -1589,4 +1635,3 @@ ORDER BY
return h

View File

@ -16,8 +16,6 @@
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import Hand
import Tourney
import re
import sys
import traceback
@ -31,13 +29,20 @@ import operator
from xml.dom.minidom import Node
import time
import datetime
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("parser")
import Hand
import Tourney
from Exceptions import FpdbParseError
import Configuration
import gettext
gettext.install('fpdb')
log = Configuration.get_logger("logging.conf")
import pygtk
import gtk
@ -57,15 +62,17 @@ class HandHistoryConverter():
codepage = "cp1252"
def __init__(self, in_path = '-', out_path = '-', follow=False, index=0, autostart=True, starsArchive=False):
def __init__(self, config, in_path = '-', out_path = '-', follow=False, index=0, autostart=True, starsArchive=False):
"""\
in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input"""
self.config = config
#log = Configuration.get_logger("logging.conf", "parser", log_dir=self.config.dir_log)
log.info("HandHistory init - %s subclass, in_path '%s'; out_path '%s'" % (self.sitename, in_path, out_path) )
self.index = 0
self.index = index
self.starsArchive = starsArchive
self.in_path = in_path
@ -77,7 +84,7 @@ follow : whether to tail -f the input"""
# Tourney object used to store TourneyInfo when called to deal with a Summary file
self.tourney = None
if in_path == '-':
self.in_fh = sys.stdin
@ -85,28 +92,11 @@ follow : whether to tail -f the input"""
self.out_fh = sys.stdout
else:
# TODO: out_path should be sanity checked.
out_dir = os.path.dirname(self.out_path)
if not os.path.isdir(out_dir) and out_dir != '':
try:
os.makedirs(out_dir)
except: # we get a WindowsError here in Windows.. pretty sure something else for Linux :D
log.error("Unable to create output directory %s for HHC!" % out_dir)
print "*** ERROR: UNABLE TO CREATE OUTPUT DIRECTORY", out_dir
# TODO: pop up a box to allow person to choose output directory?
# TODO: shouldn't that be done when we startup, actually?
else:
log.info("Created directory '%s'" % out_dir)
try:
self.out_fh = codecs.open(self.out_path, 'w', 'utf8')
except:
log.error("out_path %s couldn't be opened" % (self.out_path))
else:
log.debug("out_path %s opened as %s" % (self.out_path, self.out_fh))
self.out_fh = sys.stdout
self.follow = follow
self.compiledPlayers = set()
self.maxseats = 10
self.status = True
self.parsedObjectType = "HH" #default behaviour : parsing HH files, can be "Summary" if the parsing encounters a Summary File
@ -121,7 +111,7 @@ HandHistoryConverter: '%(sitename)s'
in_path '%(in_path)s'
out_path '%(out_path)s'
follow '%(follow)s'
""" % locals()
""" % locals()
def start(self):
"""Process a hand at a time from the input specified by in_path.
@ -141,7 +131,7 @@ Otherwise, finish at EOF.
self.numHands = 0
self.numErrors = 0
if self.follow:
#TODO: See how summary files can be handled on the fly (here they should be rejected as before)
#TODO: See how summary files can be handled on the fly (here they should be rejected as before)
log.info("Tailing '%s'" % self.in_path)
for handText in self.tailHands():
try:
@ -176,7 +166,7 @@ Otherwise, finish at EOF.
endtime = time.time()
if summaryParsingStatus :
log.info("Summary file '%s' correctly parsed (took %.3f seconds)" % (self.in_path, endtime - starttime))
else :
else :
log.warning("Error converting summary file '%s' (took %.3f seconds)" % (self.in_path, endtime - starttime))
except IOError, ioe:
@ -230,7 +220,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
# x-- [x,--,] x,--
# x--x [x,--,x] x,x
# x--x-- [x,--,x,--,] x,x,--
# The length is always odd.
# 'odd' indices are always splitters.
# 'even' indices are always paragraphs or ''
@ -264,13 +254,13 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
log.info("Read no hands.")
return []
return re.split(self.re_SplitHands, self.obs)
def processHand(self, handText):
gametype = self.determineGameType(handText)
log.debug("gametype %s" % gametype)
hand = None
l = None
if gametype is None:
if gametype is None:
gametype = "unmatched"
# TODO: not ideal, just trying to not error.
# TODO: Need to count failed hands.
@ -283,17 +273,16 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
if l in self.readSupportedGames():
if gametype['base'] == 'hold':
log.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)")
hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handText)
hand = Hand.HoldemOmahaHand(self.config, self, self.sitename, gametype, handText)
elif gametype['base'] == 'stud':
hand = Hand.StudHand(self, self.sitename, gametype, handText)
hand = Hand.StudHand(self.config, self, self.sitename, gametype, handText)
elif gametype['base'] == 'draw':
hand = Hand.DrawHand(self, self.sitename, gametype, handText)
hand = Hand.DrawHand(self.config, self, self.sitename, gametype, handText)
else:
log.info("Unsupported game type: %s" % gametype)
if hand:
if Configuration.NEWIMPORT == False:
hand.writeHand(self.out_fh)
#hand.writeHand(self.out_fh)
return hand
else:
log.info("Unsupported game type: %s" % gametype)
@ -305,7 +294,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
# This function should return a list of lists looking like:
# return [["ring", "hold", "nl"], ["tour", "hold", "nl"]]
# Showing all supported games limits and types
def readSupportedGames(self): abstract
# should return a list
@ -340,17 +329,17 @@ or None if we fail to get the info """
# Needs to return a list of lists in the format
# [['seat#', 'player1name', 'stacksize'] ['seat#', 'player2name', 'stacksize'] [...]]
def readPlayerStacks(self, hand): abstract
def compilePlayerRegexs(self): abstract
"""Compile dynamic regexes -- these explicitly match known player names and must be updated if a new player joins"""
# Needs to return a MatchObject with group names identifying the streets into the Hand object
# so groups are called by street names 'PREFLOP', 'FLOP', 'STREET2' etc
# blinds are done seperately
def markStreets(self, hand): abstract
#Needs to return a list in the format
# ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb,
# ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb,
# addtional players are assumed to post a bb oop
def readBlinds(self, hand): abstract
def readAntes(self, hand): abstract
@ -363,16 +352,16 @@ or None if we fail to get the info """
def readShownCards(self, hand): abstract
# Some sites do odd stuff that doesn't fall in to the normal HH parsing.
# e.g., FTP doesn't put mixed game info in the HH, but puts in in the
# e.g., FTP doesn't put mixed game info in the HH, but puts in in the
# file name. Use readOther() to clean up those messes.
def readOther(self, hand): pass
# Some sites don't report the rake. This will be called at the end of the hand after the pot total has been calculated
# an inheriting class can calculate it for the specific site if need be.
def getRake(self, hand):
hand.rake = hand.totalpot - hand.totalcollected # * Decimal('0.05') # probably not quite right
def sanityCheck(self):
"""Check we aren't going to do some stupid things"""
#TODO: the hhbase stuff needs to be in fpdb_import
@ -397,7 +386,7 @@ or None if we fail to get the info """
# Make sure input and output files are different or we'll overwrite the source file
if True: # basically.. I don't know
sane = True
if self.in_path != '-' and self.out_path == self.in_path:
print "HH Sanity Check: output and input files are the same, check config"
sane = False
@ -418,7 +407,7 @@ or None if we fail to get the info """
list.pop() #Last entry is empty
for l in list:
# print "'" + l + "'"
hands = hands + [Hand.Hand(self.sitename, self.gametype, l)]
hands = hands + [Hand.Hand(self.config, self.sitename, self.gametype, l)]
# TODO: This looks like it could be replaced with a list comp.. ?
return hands
@ -430,7 +419,7 @@ or None if we fail to get the info """
def readFile(self):
"""Open in_path according to self.codepage. Exceptions caught further up"""
if self.filetype == "text":
if self.in_path == '-':
# read from stdin
@ -441,10 +430,9 @@ or None if we fail to get the info """
#print "trying", kodec
try:
in_fh = codecs.open(self.in_path, 'r', kodec)
in_fh.seek(self.index)
log.debug("Opened in_path: '%s' with %s" % (self.in_path, kodec))
self.obs = in_fh.read()
self.index = in_fh.tell()
whole_file = in_fh.read()
self.obs = whole_file[self.index:]
self.index = len(whole_file)
in_fh.close()
break
except:
@ -466,7 +454,7 @@ or None if we fail to get the info """
if hand.gametype['base'] == 'stud':
if mo <= 8: return 8
else: return mo
else: return mo
if hand.gametype['base'] == 'draw':
if mo <= 6: return 6
@ -502,9 +490,9 @@ or None if we fail to get the info """
def getParsedObjectType(self):
return self.parsedObjectType
#returns a status (True/False) indicating wether the parsing could be done correctly or not
#returns a status (True/False) indicating wether the parsing could be done correctly or not
def readSummaryInfo(self, summaryInfoList): abstract
def getTourney(self):
return self.tourney
@ -527,6 +515,3 @@ def getSiteHhc(config, sitename):
hhcName = config.supported_sites[sitename].converter
hhcModule = __import__(hhcName)
return getattr(hhcModule, hhcName[:-6])

View File

@ -26,6 +26,10 @@ Create and manage the hud overlays.
import os
import sys
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("hud")
# pyGTK modules
import pygtk
import gtk
@ -365,7 +369,7 @@ class Hud:
self.create(*self.creation_attrs)
self.update(self.hand, self.config)
except Exception, e:
print "Exception:",str(e)
log.error("Exception:",str(e))
pass
def set_aggregation(self, widget, val):
@ -377,7 +381,7 @@ class Hud:
if self.hud_params['h_agg_bb_mult'] != num \
and getattr(self, 'h_aggBBmultItem'+str(num)).get_active():
print 'set_player_aggregation', num
log.debug('set_player_aggregation', num)
self.hud_params['h_agg_bb_mult'] = num
for mult in ('1', '2', '3', '10', '10000'):
if mult != str(num):
@ -388,7 +392,7 @@ class Hud:
if self.hud_params['agg_bb_mult'] != num \
and getattr(self, 'aggBBmultItem'+str(num)).get_active():
print 'set_opponent_aggregation', num
log.debug('set_opponent_aggregation', num)
self.hud_params['agg_bb_mult'] = num
for mult in ('1', '2', '3', '10', '10000'):
if mult != str(num):
@ -415,7 +419,7 @@ class Hud:
self.hud_params[param] = 'E'
getattr(self, prefix+'seatsStyleOptionA').set_active(False)
getattr(self, prefix+'seatsStyleOptionC').set_active(False)
print "setting self.hud_params[%s] = %s" % (param, style)
log.debug("setting self.hud_params[%s] = %s" % (param, style))
def set_hud_style(self, widget, val):
(player_opp, style) = val
@ -438,7 +442,7 @@ class Hud:
self.hud_params[param] = 'T'
getattr(self, prefix+'hudStyleOptionA').set_active(False)
getattr(self, prefix+'hudStyleOptionS').set_active(False)
print "setting self.hud_params[%s] = %s" % (param, style)
log.debug("setting self.hud_params[%s] = %s" % (param, style))
def update_table_position(self):
if os.name == 'nt':
@ -515,7 +519,7 @@ class Hud:
# ask each aux to save its layout back to the config object
[aux.save_layout() for aux in self.aux_windows]
# save the config object back to the file
print "saving new xml file"
print "Updating config file"
self.config.save()
def adj_seats(self, hand, config):
@ -606,12 +610,13 @@ 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]
except KeyError:
print "KeyError at the start of the for loop in update in hud_main. How this can possibly happen is totally beyond my comprehension. Your HUD may be about to get really weird. -Eric"
print "(btw, the key was ", s, " and statd is...", statd
log.error("KeyError at the start of the for loop in update in hud_main. How this can possibly happen is totally beyond my comprehension. Your HUD may be about to get really weird. -Eric")
log.error("(btw, the key was ", s, " and statd is...", statd)
continue
try:
self.stat_windows[statd['seat']].player_id = statd['player_id']
@ -629,21 +634,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:
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("#FFFFFF"))
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):
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.stat_locolor))
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):
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.stat_hicolor))
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?
@ -679,9 +680,11 @@ class Stat_Window:
return True
if event.button == 1: # left button event
# close on double click for a stat window
# for those that don't have a mouse with middle button
if event.type == gtk.gdk._2BUTTON_PRESS:
self.window.hide()
return True
self.window.hide()
return True
# TODO: make position saving save sizes as well?
if event.state & gtk.gdk.SHIFT_MASK:
self.window.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time)

View File

@ -128,7 +128,8 @@ class Stud_mucked(Aux_Window):
self.container.show_all()
def update_data(self, new_hand_id, db_connection):
self.mucked_cards.update_data(new_hand_id, db_connection)
# uncomment next line when action is available in the db
# self.mucked_cards.update_data(new_hand_id, db_connection)
self.mucked_list.update_data(new_hand_id, db_connection)
def update_gui(self, new_hand_id):
@ -357,21 +358,24 @@ class Aux_Seats(Aux_Window):
except AttributeError:
return
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
width = self.hud.table.width
height = self.hud.table.height
for i in (range(1, self.hud.max + 1) + ['common']):
if i == 'common':
(x, y) = self.params['layout'][self.hud.max].common
else:
(x, y) = loc[adj[i]]
self.positions[i] = self.card_positions(x, self.hud.table.x, y, self.hud.table.y)
self.positions[i] = self.card_positions((x * width) / 1000, self.hud.table.x, (y * height) /1000, self.hud.table.y)
self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
def create(self):
self.adj = self.hud.adj_seats(0, self.config) # move adj_seats to aux and get rid of it in Hud.py
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
self.m_windows = {} # windows to put the card images in
width = self.hud.table.width
height = self.hud.table.height
for i in (range(1, self.hud.max + 1) + ['common']):
if i == 'common':
(x, y) = self.params['layout'][self.hud.max].common
@ -383,7 +387,7 @@ class Aux_Seats(Aux_Window):
self.m_windows[i].set_transient_for(self.hud.main_window)
self.m_windows[i].set_focus_on_map(False)
self.m_windows[i].connect("configure_event", self.configure_event_cb, i)
self.positions[i] = self.card_positions(x, self.hud.table.x, y, self.hud.table.y)
self.positions[i] = self.card_positions((x * width) / 1000, self.hud.table.x, (y * height) /1000, self.hud.table.y)
self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
if self.params.has_key('opacity'):
self.m_windows[i].set_opacity(float(self.params['opacity']))
@ -425,11 +429,13 @@ class Aux_Seats(Aux_Window):
"""Save new layout back to the aux element in the config file."""
new_locs = {}
# print "adj =", self.adj
witdh = self.hud.table.width
height = self.hud.table.height
for (i, pos) in self.positions.iteritems():
if i != 'common':
new_locs[self.adj[int(i)]] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
else:
new_locs[i] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
if i != 'common':
new_locs[self.adj[int(i)]] = ((pos[0] - self.hud.table.x) * 1000 / witdh, (pos[1] - self.hud.table.y) * 1000 / height)
else:
new_locs[i] = ((pos[0] - self.hud.table.x) * 1000 / witdh, (pos[1] - self.hud.table.y) * 1000 / height)
self.config.edit_aux_layout(self.params['name'], self.hud.max, locations = new_locs)
def configure_event_cb(self, widget, event, i, *args):

View File

@ -15,9 +15,9 @@
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import os
import sys
from optparse import OptionParser
# http://docs.python.org/library/optparse.html
def fpdb_options():
@ -41,6 +41,14 @@ def fpdb_options():
parser.add_option("-k", "--konverter",
dest="hhc", default="PokerStarsToFpdb",
help="Module name for Hand History Converter")
parser.add_option("-l", "--logging",
dest = "log_level",
choices = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'EMPTY'),
help = "Error logging level. (DEBUG, INFO, WARNING, ERROR, CRITICAL, EMPTY)",
default = 'EMPTY')
parser.add_option("-v", "--version", action = "store_true",
help = "Print version information and exit.")
(options, argv) = parser.parse_args()
return (options, argv)

View File

@ -47,7 +47,7 @@ class PartyPoker(HandHistoryConverter):
# $5 USD NL Texas Hold'em - Saturday, July 25, 07:53:52 EDT 2009
# NL Texas Hold'em $1 USD Buy-in Trny:45685440 Level:8 Blinds-Antes(600/1 200 -50) - Sunday, May 17, 11:25:07 MSKS 2009
re_GameInfoRing = re.compile("""
(?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>[.,0-9]+)\s*(?:USD)?\s*
(?P<CURRENCY>\$|)\s*(?P<RINGLIMIT>[.,0-9]+)([.,0-9/$]+)?\s*(?:USD)?\s*
(?P<LIMIT>(NL|PL|))\s*
(?P<GAME>(Texas\ Hold\'em|Omaha))
\s*\-\s*
@ -60,9 +60,9 @@ class PartyPoker(HandHistoryConverter):
Trny:\s?(?P<TOURNO>\d+)\s+
Level:\s*(?P<LEVEL>\d+)\s+
((Blinds|Stakes)(?:-Antes)?)\(
(?P<SB>[,.0-9 ]+)\s*
/(?P<BB>[,.0-9 ]+)
(?:\s*-\s*(?P<ANTE>[,.0-9 ]+)\$?)?
(?P<SB>[.,0-9 ]+)\s*
/(?P<BB>[.,0-9 ]+)
(?:\s*-\s*(?P<ANTE>[.,0-9 ]+)\$?)?
\)
\s*\-\s*
(?P<DATETIME>.+)
@ -72,21 +72,17 @@ class PartyPoker(HandHistoryConverter):
re_PlayerInfo = re.compile("""
Seat\s(?P<SEAT>\d+):\s
(?P<PNAME>.*)\s
\(\s*\$?(?P<CASH>[.,0-9]+)\s*(?:USD|)\s*\)
\(\s*\$?(?P<CASH>[0-9,.]+)\s*(?:USD|)\s*\)
""" ,
re.VERBOSE)
# Table $250 Freeroll (1810210) Table #309 (Real Money)
# Table Speed #1465003 (Real Money)
re_HandInfo = re.compile("""
^Table\s+
(?P<TABLE_TYPE>[^#()]+)\s+ # Regular, Speed, etc
(?P<TABLE_ID_WRAPPER>\(|\#| ) # \# means sng, ( - mtt, nothing - cash game
(?P<TABLE_ID>\d+) \)? \s+ # it's global unique id for this table
(?:Table\s+\#(?P<TABLE_NUM>\d+).+)? # table num for mtt tournaments
^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)\s+
(?: \#|\(|)(?P<TABLE>\d+)\)?\s+
(?:[a-zA-Z0-9 ]+\s+\#(?P<MTTTABLE>\d+).+)?
(\(No\sDP\)\s)?
\((?P<PLAY>Real|Play)\s+Money\)\s*
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
""",
re.VERBOSE|re.MULTILINE)
@ -290,6 +286,14 @@ class PartyPoker(HandHistoryConverter):
if key == 'HID':
hand.handid = info[key]
if key == 'TABLE':
hand.tablename = info[key]
if key == 'MTTTABLE':
if info[key] != None:
hand.tablename = info[key]
hand.tourNo = info['TABLE']
if key == 'BUTTON':
hand.buttonpos = info[key]
if key == 'TOURNO':
hand.tourNo = info[key]
if key == 'TABLE_ID_WRAPPER':
@ -299,29 +303,16 @@ class PartyPoker(HandHistoryConverter):
if key == 'BUYIN':
# FIXME: it's dirty hack T_T
# code below assumes that tournament rake is equal to zero
# added handle for freeroll tourney's
# code added for freeroll the buyin is overwritten by $0+$0
if info[key] == None: #assume freeroll
cur = '$'
hand.buyin = "$0" + '+%s0' % cur
if info[key] == None:
hand.buyin = '$0+$0'
else:
cur = info[key][0] if info[key][0] not in '0123456789' else ''
hand.buyin = info[key] + '+%s0' % cur
if key == 'TABLE_ID':
hand.tablename = info[key]
if key == 'TABLE_NUM':
# FIXME: there is no such property in Hand class
if info[key] != None:
hand.tablename = info[key]
hand.tourNo = info['TABLE_ID']
if key == 'COUNTED_SEATS':
hand.counted_seats = info[key]
cur = info[key][0] if info[key][0] not in '0123456789' else ''
hand.buyin = info[key] + '+%s0' % cur
if key == 'LEVEL':
hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != 'Real':
# if realy party doesn's save play money hh
hand.gametype['currency'] = 'play'
hand.gametype['currency'] = 'play'
def readButton(self, hand):
m = self.re_Button.search(hand.handText)

36
pyfpdb/PokerStarsToFpdb.py Normal file → Executable file
View File

@ -40,13 +40,15 @@ class PokerStars(HandHistoryConverter):
'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
'LS' : "\$|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8)
}
# Static regexes
re_GameInfo = re.compile(u"""
PokerStars\sGame\s\#(?P<HID>[0-9]+):\s+
(Tournament\s\# # open paren of tournament info
(?P<TOURNO>\d+),\s
# here's how I plan to use LS
(?P<BUYIN>([%(LS)s\+\d\.]+\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?)|Freeroll)\s+)? # close paren of tournament info
(?P<BUYIN>([%(LS)s\+\d\.]+\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?)|Freeroll)\s+)?
# close paren of tournament info
(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(?
(?P<GAME>Hold\'em|Razz|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|5\sCard\sDraw)\s
(?P<LIMIT>No\sLimit|Limit|Pot\sLimit)\)?,?\s
@ -67,7 +69,7 @@ class PokerStars(HandHistoryConverter):
re.MULTILINE|re.VERBOSE)
re_HandInfo = re.compile("""
^Table\s\'(?P<TABLE>[-\ a-zA-Z\d]+)\'\s
^Table\s\'(?P<TABLE>[-\ \#a-zA-Z\d]+)\'\s
((?P<MAX>\d+)-max\s)?
(?P<PLAY>\(Play\sMoney\)\s)?
(Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
@ -79,6 +81,7 @@ class PokerStars(HandHistoryConverter):
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
re_DateTime = re.compile("""(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)""", re.MULTILINE)
def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players])
@ -95,15 +98,16 @@ class PokerStars(HandHistoryConverter):
self.re_PostBB = re.compile(r"^%(PLYR)s: posts big blind %(CUR)s(?P<BB>[.0-9]+)" % subst, re.MULTILINE)
self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante %(CUR)s(?P<ANTE>[.0-9]+)" % subst, re.MULTILINE)
self.re_BringIn = re.compile(r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[.0-9]+)" % subst, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%(PLYR)s: posts small \& big blinds \[%(CUR)s (?P<SBBB>[.0-9]+)" % subst, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%(PLYR)s: posts small \& big blinds %(CUR)s(?P<SBBB>[.0-9]+)" % subst, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % subst, re.MULTILINE)
self.re_Action = re.compile(r"""
^%(PLYR)s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat)
(\s(%(CUR)s)?(?P<BET>[.\d]+))?(\sto\s%(CUR)s(?P<BETTO>[.\d]+))? # the number discarded goes in <BET>
(\scards?(\s\[(?P<DISCARDED>.+?)\])?)?"""
\s*(and\sis\sall.in)?
(\scards?(\s\[(?P<DISCARDED>.+?)\])?)?\s*$"""
% subst, re.MULTILINE|re.VERBOSE)
self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %(PLYR)s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(%(CUR)s(?P<POT>[.\d]+)\)(, mucked| with.*|)" % subst, re.MULTILINE)
self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %(PLYR)s (\(button\) |\(small blind\) |\(big blind\) |\(button\) \(small blind\) )?(collected|showed \[.*\] and won) \(%(CUR)s(?P<POT>[.\d]+)\)(, mucked| with.*|)" % subst, re.MULTILINE)
self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s (\(.*\) )?(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
@ -146,7 +150,7 @@ class PokerStars(HandHistoryConverter):
'7 Card Stud Hi/Lo' : ('stud','studhilo'),
'Badugi' : ('draw','badugi'),
'Triple Draw 2-7 Lowball' : ('draw','27_3draw'),
'5 Card Draw' : ('draw','fivedraw'),
'5 Card Draw' : ('draw','fivedraw')
}
currencies = { u'':'EUR', '$':'USD', '':'T$' }
# I don't think this is doing what we think. mg will always have all
@ -192,18 +196,21 @@ class PokerStars(HandHistoryConverter):
#2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET]
#2008/08/17 - 01:14:43 (ET)
#2008/09/07 06:23:14 ET
m2 = re.search("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)", info[key])
datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), m2.group('M'),m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
m1 = self.re_DateTime.finditer(info[key])
# m2 = re.search("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)", info[key])
for a in m1:
datetimestr = "%s/%s/%s %s:%s:%s" % (a.group('Y'), a.group('M'),a.group('D'),a.group('H'),a.group('MIN'),a.group('S'))
hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
if key == 'HID':
hand.handid = info[key]
if key == 'TOURNO':
hand.tourNo = info[key]
if key == 'BUYIN':
if info[key] == 'Freeroll':
hand.buyin = '$0+$0'
else:
#FIXME: The key looks like: '€0.82+€0.18 EUR'
# This should be parsed properly and used
hand.buyin = info[key]
if key == 'LEVEL':
hand.level = info[key]
@ -234,7 +241,6 @@ class PokerStars(HandHistoryConverter):
def readPlayerStacks(self, hand):
log.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
@ -282,8 +288,13 @@ class PokerStars(HandHistoryConverter):
def readBlinds(self, hand):
try:
m = self.re_PostSB.search(hand.handText)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
count = 0
for a in self.re_PostSB.finditer(hand.handText):
if count == 0:
hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB'))
count = 1
else:
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.handText):
@ -331,6 +342,7 @@ class PokerStars(HandHistoryConverter):
m = self.re_Action.finditer(hand.streets[street])
for action in m:
acts = action.groupdict()
#print "DEBUG: acts: %s" %acts
if action.group('ATYPE') == ' raises':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' calls':

View File

@ -58,6 +58,27 @@ class Sql:
self.query['drop_table'] = """DROP TABLE IF EXISTS """
##################################################################
# Set transaction isolation level
##################################################################
if db_server == 'mysql' or db_server == 'postgresql':
self.query['set tx level'] = """SET SESSION TRANSACTION
ISOLATION LEVEL READ COMMITTED"""
elif db_server == 'sqlite':
self.query['set tx level'] = """ """
################################
# Select basic info
################################
self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
self.query['getGames'] = """SELECT DISTINCT category from Gametypes"""
self.query['getLimits'] = """SELECT DISTINCT bigBlind from Gametypes ORDER by bigBlind DESC"""
################################
# Create Settings
################################
@ -214,6 +235,7 @@ class Sql:
id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id),
tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL,
tourneyId INT UNSIGNED NOT NULL,
gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
handStart DATETIME NOT NULL,
importTime DATETIME NOT NULL,
@ -249,6 +271,7 @@ class Sql:
id BIGSERIAL, PRIMARY KEY (id),
tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL,
tourneyId INT NOT NULL,
gametypeId INT NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
handStart timestamp without time zone NOT NULL,
importTime timestamp without time zone NOT NULL,
@ -283,6 +306,7 @@ class Sql:
id INTEGER PRIMARY KEY,
tableName TEXT(22) NOT NULL,
siteHandNo INT NOT NULL,
tourneyId INT NOT NULL,
gametypeId INT NOT NULL,
handStart REAL NOT NULL,
importTime REAL NOT NULL,
@ -1824,6 +1848,15 @@ class Sql:
self.query['getLimits2'] = """SELECT DISTINCT type, limitType, bigBlind
from Gametypes
ORDER by type, limitType DESC, bigBlind DESC"""
self.query['getLimits3'] = """select DISTINCT type
, limitType
, case type
when 'ring' then bigBlind
else buyin
end as bb_or_buyin
from Gametypes gt
cross join TourneyTypes tt
order by type, limitType DESC, bb_or_buyin DESC"""
if db_server == 'mysql':
self.query['playerDetailedStats'] = """
@ -1881,6 +1914,7 @@ class Sql:
inner join Sites s on (s.Id = gt.siteId)
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test>
<game_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>
@ -1964,6 +1998,7 @@ class Sql:
inner join Sites s on (s.Id = gt.siteId)
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test>
<game_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>
@ -1995,6 +2030,7 @@ class Sql:
elif db_server == 'sqlite':
self.query['playerDetailedStats'] = """
select <hgameTypeId> AS hgametypeid
,<playerName> AS pname
,gt.base
,gt.category AS category
,upper(gt.limitType) AS limittype
@ -2046,7 +2082,9 @@ class Sql:
inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId)
inner join Sites s on (s.Id = gt.siteId)
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test>
<game_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>
@ -2573,6 +2611,7 @@ class Sql:
AND h.handStart > '<startdate_test>'
AND h.handStart < '<enddate_test>'
<limit_test>
<game_test>
AND hp.tourneysPlayersId IS NULL
GROUP BY h.handStart, hp.handId, hp.sawShowdown, hp.totalProfit
ORDER BY h.handStart"""
@ -3088,6 +3127,147 @@ class Sql:
,'d' || substr(strftime('%Y%m%d', h.handStart),3,7)
"""
self.query['insert_hudcache'] = """
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)"""
self.query['update_hudcache'] = """
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"""
self.query['get_hero_hudcache_start'] = """select min(hc.styleKey)
from HudCache hc
where hc.playerId in <playerid_list>
@ -3292,6 +3472,7 @@ class Sql:
tablename,
gametypeid,
sitehandno,
tourneyId,
handstart,
importtime,
seats,
@ -3322,7 +3503,7 @@ class Sql:
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)"""
self.query['store_hands_players'] = """INSERT INTO HandsPlayers (

View File

@ -62,9 +62,13 @@ import Database
re_Places = re.compile("_[0-9]$")
re_Percent = re.compile("%$")
# String manipulation
import codecs
encoder = codecs.lookup(Configuration.LOCALE_ENCODING)
def do_tip(widget, tip):
widget.set_tooltip_text(tip)
(_tip, _len) = encoder.encode(tip)
widget.set_tooltip_text(_tip)
def do_stat(stat_dict, player = 24, stat = 'vpip'):
match = re_Places.search(stat)
@ -241,8 +245,20 @@ def saw_f(stat_dict, player):
def n(stat_dict, player):
""" Number of hands played."""
try:
# If sample is large enough, use X.Yk notation instead
_n = stat_dict[player]['n']
fmt = '%d' % _n
if _n >= 10000:
k = _n / 1000
c = _n % 1000
_c = float(c) / 100.0
d = int(round(_c))
if d == 10:
k += 1
d = 0
fmt = '%d.%dk' % (k, d)
return (stat_dict[player]['n'],
'%d' % (stat_dict[player]['n']),
'%s' % fmt,
'n=%d' % (stat_dict[player]['n']),
'n=%d' % (stat_dict[player]['n']),
'(%d)' % (stat_dict[player]['n']),

View File

@ -11,12 +11,12 @@ of Table_Window objects representing the windows found.
# 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
# GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
@ -58,7 +58,7 @@ bad_words = ('History for table:', 'HUD:', 'Chat:')
# Here are the custom signals we define for allowing the 'client watcher'
# thread to communicate with the gui thread. Any time a poker client is
# is moved, resized, or closed on of these signals is emitted to the
# is moved, resized, or closed on of these signals is emitted to the
# HUD main window.
gobject.signal_new("client_moved", gtk.Window,
gobject.SIGNAL_RUN_LAST,
@ -80,16 +80,16 @@ gobject.signal_new("client_destroyed", gtk.Window,
# from the corresponding hand history.
# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site
# name specified in the config file.
# tw.number = This is the system id number for the client table window in the
# tw.number = This is the system id number for the client table window in the
# format that the system presents it. This is Xid in Xwindows and
# hwnd in Microsoft Windows.
# tw.title = The full title from the window title bar.
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# window borders.
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# to the top left of the display screen. This also does not include the
# title bar and window borders. To put it another way, this is the
# title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window.
class Table_Window(object):
@ -101,7 +101,7 @@ class Table_Window(object):
self.table = int(table_number)
self.name = "%s - %s" % (self.tournament, self.table)
elif table_name is not None:
search_string = table_name
# search_string = table_name
self.name = table_name
self.tournament = None
else:
@ -111,7 +111,7 @@ class Table_Window(object):
def __str__(self):
# __str__ method for testing
likely_attrs = ("site", "number", "title", "width", "height", "x", "y",
likely_attrs = ("site", "number", "title", "width", "height", "x", "y",
"tournament", "table", "gdkhandle")
temp = 'TableWindow object\n'
for a in likely_attrs:
@ -125,7 +125,7 @@ class Table_Window(object):
for game, names in game_names.iteritems():
for name in names:
if name in title:
return game
return game
return None
def check_geometry(self):

View File

@ -39,7 +39,6 @@ if os.name == 'nt':
# FreePokerTools modules
import Configuration
from fpdb_simple import LOCALE_ENCODING
# Each TableWindow object must have the following attributes correctly populated:
# tw.name = the table name from the title bar, which must to match the table name
@ -238,7 +237,7 @@ def discover_nt_by_name(c, tablename):
try:
# maybe it's better to make global titles[hwnd] decoding?
# this can blow up in XP on some windows, eg firefox displaying http://docs.python.org/tutorial/classes.html
if not tablename.lower() in titles[hwnd].decode(LOCALE_ENCODING).lower():
if not tablename.lower() in titles[hwnd].decode(Configuration.LOCALE_ENCODING).lower():
continue
except:
continue

View File

@ -185,7 +185,7 @@ class Tourney(object):
def old_insert_from_Hand(self, db):
""" Function to insert Hand into database
Should not commit, and do minimal selects. Callers may want to cache commits
db: a connected fpdb_db object"""
db: a connected Database object"""
# TODO:
# Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)

View File

@ -24,6 +24,10 @@ Routines for detecting and handling poker client windows for MS Windows.
# Standard Library modules
import re
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("hud")
# pyGTK modules
import pygtk
import gtk
@ -50,28 +54,31 @@ class Table(Table_Window):
titles = {}
win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles:
if titles[hwnd] == "": continue
# print "searching ", search_string, " in ", titles[hwnd]
if re.search(search_string, titles[hwnd]):
if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window
if 'HUD:' in titles[hwnd]: continue # FPDB HUD window
if 'Chat:' in titles[hwnd]: continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows
if 'FPDBHUD' in titles[hwnd]: continue # can't attach to ourselves!
self.window = hwnd
break
try:
if self.window == None:
print "Window %s not found. Skipping." % search_string
log.error( "Window %s not found. Skipping." % search_string )
return None
except AttributeError:
print "self.window doesn't exist? why?"
log.error( "self.window doesn't exist? why?" )
return None
(x, y, width, height) = win32gui.GetWindowRect(hwnd)
print "x = %s y = %s width = %s height = %s" % (x, y, width, height)
log.debug("x = %s y = %s width = %s height = %s" % (x, y, width, height))
self.x = int(x) + b_width
self.y = int(y) + tb_height
self.width = width - x
self.height = height - y
print "x = %s y = %s width = %s height = %s" % (self.x, self.y, self.width, self.height)
log.debug("x = %s y = %s width = %s height = %s" % (self.x, self.y, self.width, self.height))
#self.height = int(height) - b_width - tb_height
#self.width = int(width) - 2*b_width

222
pyfpdb/fpdb.py Normal file → Executable file
View File

@ -18,6 +18,7 @@
import os
import sys
import re
import Queue
# if path is set to use an old version of python look for a new one:
# (does this work in linux?)
@ -52,7 +53,7 @@ if os.name == 'nt':
raw_input("Press ENTER to continue.")
exit()
print "Python " + sys.version[0:3] + '...\n'
print "Python " + sys.version[0:3] + '...'
import traceback
import threading
@ -61,17 +62,13 @@ import string
cl_options = string.join(sys.argv[1:])
(options, argv) = Options.fpdb_options()
if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('fpdb-error-log.txt', 'w', 0)
sys.stderr = errorFile
import logging
import logging, logging.config
try:
import pygtk
pygtk.require('2.0')
import gtk
import pango
except:
print "Unable to load PYGTK modules required for GUI. Please install PyCairo, PyGObject, and PyGTK from www.pygtk.org."
raw_input("Press ENTER to continue.")
@ -79,8 +76,27 @@ except:
import interlocks
# these imports not required in this module, imported here to report version in About dialog
try:
import matplotlib
matplotlib_version = matplotlib.__version__
except:
matplotlib_version = 'not found'
try:
import numpy
numpy_version = numpy.__version__
except:
numpy_version = 'not found'
try:
import sqlite3
sqlite3_version = sqlite3.version
sqlite_version = sqlite3.sqlite_version
except:
sqlite3_version = 'not found'
sqlite_version = 'not found'
import GuiPrefs
import GuiLogView
import GuiBulkImport
import GuiPlayerStats
import GuiPositionalStats
@ -90,12 +106,12 @@ import GuiGraphViewer
import GuiSessionViewer
import SQL
import Database
import FpdbSQLQueries
import Configuration
import Exceptions
VERSION = "0.12"
class fpdb:
def tab_clicked(self, widget, tab_name):
"""called when a tab button is clicked to activate that tab"""
@ -108,12 +124,12 @@ class fpdb:
def add_tab(self, new_page, new_tab_name):
"""adds a tab, namely creates the button and displays it and appends all the relevant arrays"""
for name in self.nb_tabs: #todo: check this is valid
for name in self.nb_tab_names: #todo: check this is valid
if name == new_tab_name:
return # if tab already exists, just go to it
used_before = False
for i, name in enumerate(self.tab_names): #todo: check this is valid
for i, name in enumerate(self.tab_names):
if name == new_tab_name:
used_before = True
event_box = self.tabs[i]
@ -129,13 +145,13 @@ class fpdb:
#self.nb.append_page(new_page, gtk.Label(new_tab_name))
self.nb.append_page(page, event_box)
self.nb_tabs.append(new_tab_name)
self.nb_tab_names.append(new_tab_name)
page.show()
def display_tab(self, new_tab_name):
"""displays the indicated tab"""
tab_no = -1
for i, name in enumerate(self.nb_tabs):
for i, name in enumerate(self.nb_tab_names):
if new_tab_name == name:
tab_no = i
break
@ -186,13 +202,13 @@ class fpdb:
(nb, text) = data
page = -1
#print "\n remove_tab: start", text
for i, tab in enumerate(self.nb_tabs):
for i, tab in enumerate(self.nb_tab_names):
if text == tab:
page = i
#print " page =", page
if page >= 0 and page < self.nb.get_n_pages():
#print " removing page", page
del self.nb_tabs[page]
del self.nb_tab_names[page]
nb.remove_page(page)
# Need to refresh the widget --
# This forces the widget to redraw itself.
@ -213,10 +229,42 @@ class fpdb:
dia.set_comments("GTK AboutDialog comments here")
dia.set_license("GPL v3")
dia.set_website("http://fpdb.sourceforge.net/")
dia.set_authors("Steffen, Eratosthenes, s0rrow, EricBlade, _mt, sqlcoder, Bostik, and others")
dia.set_authors(['Steffen', 'Eratosthenes', 's0rrow',
'EricBlade', '_mt', 'sqlcoder', 'Bostik', 'and others'])
dia.set_program_name("Free Poker Database (FPDB)")
db_version = ""
#if self.db is not None:
# db_version = self.db.get_version()
nums = [ ('Operating System', os.name)
, ('Python', sys.version[0:3])
, ('GTK+', '.'.join([str(x) for x in gtk.gtk_version]))
, ('PyGTK', '.'.join([str(x) for x in gtk.pygtk_version]))
, ('matplotlib', matplotlib_version)
, ('numpy', numpy_version)
, ('sqlite3', sqlite3_version)
, ('sqlite', sqlite_version)
, ('database', self.settings['db-server'] + db_version)
]
versions = gtk.TextBuffer()
w = 20 # width used for module names and version numbers
versions.set_text( '\n'.join( [x[0].rjust(w)+' '+ x[1].ljust(w) for x in nums] ) )
view = gtk.TextView(versions)
view.set_editable(False)
view.set_justification(gtk.JUSTIFY_CENTER)
view.modify_font(pango.FontDescription('monospace 10'))
view.show()
dia.vbox.pack_end(view, True, True, 2)
l = gtk.Label('Version Information:')
l.set_alignment(0.5, 0.5)
l.show()
dia.vbox.pack_end(l, True, True, 2)
dia.run()
dia.destroy()
log.debug("Threads: ")
for t in self.threads:
log.debug("........." + str(t.__class__))
def dia_preferences(self, widget, data=None):
dia = gtk.Dialog("Preferences",
@ -432,6 +480,53 @@ class fpdb:
self.release_global_lock()
def dia_logs(self, widget, data=None):
"""opens the log viewer window"""
#lock_set = False
#if self.obtain_global_lock():
# lock_set = True
# remove members from self.threads if close messages received
self.process_close_messages()
viewer = None
for i, t in enumerate(self.threads):
if str(t.__class__) == 'GuiLogView.GuiLogView':
viewer = t
break
if viewer is None:
#print "creating new log viewer"
new_thread = GuiLogView.GuiLogView(self.config, self.window, self.closeq)
self.threads.append(new_thread)
else:
#print "showing existing log viewer"
viewer.get_dialog().present()
#if lock_set:
# self.release_global_lock()
def addLogText(self, text):
end_iter = self.logbuffer.get_end_iter()
self.logbuffer.insert(end_iter, text)
self.logview.scroll_to_mark(self.logbuffer.get_insert(), 0)
def process_close_messages(self):
# check for close messages
try:
while True:
name = self.closeq.get(False)
for i, t in enumerate(self.threads):
if str(t.__class__) == str(name):
# thread has ended so remove from list:
del self.threads[i]
break
except Queue.Empty:
# no close messages on queue, do nothing
pass
def __calendar_dialog(self, widget, entry):
self.dia_confirm.set_modal(False)
d = gtk.Window(gtk.WINDOW_TOPLEVEL)
@ -534,6 +629,7 @@ class fpdb:
</menu>
<menu action="help">
<menuitem action="Abbrev"/>
<menuitem action="Logs"/>
<separator/>
<menuitem action="About"/>
<menuitem action="License"/>
@ -575,6 +671,7 @@ class fpdb:
('stats', None, '_Statistics (todo)', None, 'View Database Statistics', self.dia_database_stats),
('help', None, '_Help'),
('Abbrev', None, '_Abbrevations (todo)', None, 'List of Abbrevations', self.tab_abbreviations),
('Logs', None, '_Log Messages', None, 'Log and Debug Messages', self.dia_logs),
('About', None, 'A_bout', None, 'About the program', self.dia_about),
('License', None, '_License and Copying (todo)', None, 'License and Copying', self.dia_licensing),
])
@ -591,6 +688,13 @@ class fpdb:
def load_profile(self):
"""Loads profile from the provided path name."""
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
log = Configuration.get_logger("logging.conf", "fpdb", log_dir=self.config.dir_log)
print "Logfile is " + os.path.join(self.config.dir_log, self.config.log_file) + "\n"
if self.config.example_copy:
self.info_box( "Config file"
, "has been created at:\n%s.\n" % self.config.file
+ "Edit your screen_name and hand history path in the supported_sites "
+ "section of the Preferences window (Main menu) before trying to import hands.")
self.settings = {}
self.settings['global_lock'] = self.lock
if (os.sep=="/"):
@ -604,19 +708,29 @@ class fpdb:
self.settings.update(self.config.get_import_parameters())
self.settings.update(self.config.get_default_paths())
if self.db is not None and self.db.fdb is not None:
if self.db is not None and self.db.connected:
self.db.disconnect()
self.sql = SQL.Sql(db_server = self.settings['db-server'])
err_msg = None
try:
self.db = Database.Database(self.config, sql = self.sql)
if self.db.get_backend_name() == 'SQLite':
# tell sqlite users where the db file is
print "Connected to SQLite: %(database)s" % {'database':self.db.db_path}
except Exceptions.FpdbMySQLAccessDenied:
self.warning_box("MySQL Server reports: Access denied. Are your permissions set correctly?")
exit()
err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?"
except Exceptions.FpdbMySQLNoDatabase:
msg = "MySQL client reports: 2002 error. Unable to connect - Please check that the MySQL service has been started"
self.warning_box(msg)
exit
err_msg = "MySQL client reports: 2002 or 2003 error. Unable to connect - " \
+ "Please check that the MySQL service has been started"
except Exceptions.FpdbPostgresqlAccessDenied:
err_msg = "Postgres Server reports: Access denied. Are your permissions set correctly?"
except Exceptions.FpdbPostgresqlNoDatabase:
err_msg = "Postgres client reports: Unable to connect - " \
+ "Please check that the Postgres service has been started"
if err_msg is not None:
self.db = None
self.warning_box(err_msg)
# except FpdbMySQLFailedError:
# self.warning_box("Unable to connect to MySQL! Is the MySQL server running?!", "FPDB ERROR")
@ -634,7 +748,7 @@ class fpdb:
# print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
# sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']))
if self.db.wrongDbVersion:
if self.db is not None and self.db.wrongDbVersion:
diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
label = gtk.Label("An invalid DB version or missing tables have been detected.")
@ -653,14 +767,15 @@ class fpdb:
diaDbVersionWarning.destroy()
if self.status_bar is None:
self.status_bar = gtk.Label("Status: Connected to %s database named %s on host %s"%(self.db.get_backend_name(),self.db.database, self.db.host))
self.status_bar = gtk.Label("")
self.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show()
else:
self.status_bar.set_text("Status: Connected to %s database named %s on host %s" % (self.db.get_backend_name(),self.db.database, self.db.host))
# Database connected to successfully, load queries to pass on to other classes
self.db.rollback()
if self.db is not None and self.db.connected:
self.status_bar.set_text("Status: Connected to %s database named %s on host %s"
% (self.db.get_backend_name(),self.db.database, self.db.host))
# rollback to make sure any locks are cleared:
self.db.rollback()
self.validate_config()
@ -681,7 +796,11 @@ class fpdb:
# TODO: can we get some / all of the stuff done in this function to execute on any kind of abort?
print "Quitting normally"
# TODO: check if current settings differ from profile, if so offer to save or abort
self.db.disconnect()
try:
if self.db is not None and self.db.connected:
self.db.disconnect()
except _mysql_exceptions.OperationalError: # oh, damn, we're already disconnected
pass
self.statusIcon.set_visible(False)
gtk.main_quit()
@ -748,7 +867,6 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.add_and_display_tab(gv_tab, "Graphs")
def __init__(self):
self.threads = []
# no more than 1 process can this lock at a time:
self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
self.db = None
@ -772,23 +890,35 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
menubar.show()
#done menubar
self.threads = [] # objects used by tabs - no need for threads, gtk handles it
self.closeq = Queue.Queue(20) # used to signal ending of a thread (only logviewer for now)
self.nb = gtk.Notebook()
self.nb.set_show_tabs(True)
self.nb.show()
self.main_vbox.pack_start(self.nb, True, True, 0)
self.pages=[]
self.tabs=[]
self.tab_names=[]
self.nb_tabs=[]
self.tabs=[] # the event_boxes forming the actual tabs
self.tab_names=[] # names of tabs used since program started, not removed if tab is closed
self.pages=[] # the contents of the page, not removed if tab is closed
self.nb_tab_names=[] # list of tab names currently displayed in notebook
self.tab_main_help(None, None)
self.window.show()
self.load_profile()
if not options.errorsToConsole:
fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt')
print "\nNote: error output is being diverted to fpdb-errors.txt and HUD-errors.txt in:\n" \
+ self.config.dir_log + "\nAny major error will be reported there _only_.\n"
errorFile = open(fileName, 'w', 0)
sys.stderr = errorFile
self.statusIcon = gtk.StatusIcon()
if os.path.exists('../gfx/fpdb-cards.png'):
self.statusIcon.set_from_file('../gfx/fpdb-cards.png')
# use getcwd() here instead of sys.path[0] so that py2exe works:
cards = os.path.join(os.getcwd(), '..','gfx','fpdb-cards.png')
if os.path.exists(cards):
self.statusIcon.set_from_file(cards)
elif os.path.exists('/usr/share/pixmaps/fpdb-cards.png'):
self.statusIcon.set_from_file('/usr/share/pixmaps/fpdb-cards.png')
else:
@ -847,16 +977,24 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.window.show()
self.window.present()
def info_box(self, str1, str2):
diapath = gtk.MessageDialog( parent=None, flags=0, type=gtk.MESSAGE_INFO
, buttons=(gtk.BUTTONS_OK), message_format=str1 )
diapath.format_secondary_text(str2)
response = diapath.run()
diapath.destroy()
return response
def warning_box(self, str, diatitle="FPDB WARNING"):
diaWarning = gtk.Dialog(title=diatitle, parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
diaWarning = gtk.Dialog(title=diatitle, parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
label = gtk.Label(str)
diaWarning.vbox.add(label)
label.show()
label = gtk.Label(str)
diaWarning.vbox.add(label)
label.show()
response = diaWarning.run()
diaWarning.destroy()
return response
response = diaWarning.run()
diaWarning.destroy()
return response
def validate_config(self):
hhbase = self.config.get_import_parameters().get("hhArchiveBase")

View File

@ -16,208 +16,6 @@
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import os
import re
import sys
import logging
import math
from time import time, strftime
from Exceptions import *
try:
import sqlalchemy.pool as pool
use_pool = True
except ImportError:
logging.info("Not using sqlalchemy connection pool.")
use_pool = False
try:
from numpy import var
use_numpy = True
except ImportError:
logging.info("Not using numpy to define variance in sqlite.")
use_numpy = False
import fpdb_simple
import FpdbSQLQueries
import Configuration
# 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
class fpdb_db:
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
def __init__(self):
"""Simple constructor, doesnt really do anything"""
self.db = None
self.cursor = None
self.sql = {}
#end def __init__
def do_connect(self, config=None):
"""Connects a database using information in config"""
if config is None:
raise FpdbError('Configuration not defined')
self.settings = {}
self.settings['os'] = "linuxmac" if os.name != "nt" else "windows"
db = config.get_db_parameters()
self.connect(backend=db['db-backend'],
host=db['db-host'],
database=db['db-databaseName'],
user=db['db-user'],
password=db['db-password'])
#end def do_connect
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
if backend == fpdb_db.MYSQL_INNODB:
import MySQLdb
if use_pool:
MySQLdb = pool.manage(MySQLdb, pool_size=5)
try:
self.db = 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 == fpdb_db.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.db = psycopg2.connect(database = database)
connected = True
except:
# direct connection failed so try user/pass/... version
pass
if not connected:
try:
self.db = 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 == fpdb_db.SQLITE:
logging.info("Connecting to SQLite:%(database)s" % {'database':database})
import sqlite3
if use_pool:
sqlite3 = pool.manage(sqlite3, pool_size=1)
else:
logging.warning("SQLite won't work well without 'sqlalchemy' installed.")
if not os.path.isdir(Configuration.DIR_DATABASES) and not database == ":memory:":
print "Creating directory: '%s'" % (Configuration.DIR_DATABASES)
os.mkdir(Configuration.DIR_DATABASES)
database = os.path.join(Configuration.DIR_DATABASES, database)
self.db = sqlite3.connect(database, 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.db.create_function("floor", 1, math.floor)
tmp = sqlitemath()
self.db.create_function("mod", 2, tmp.mod)
if use_numpy:
self.db.create_aggregate("variance", 1, VARIANCE)
else:
logging.warning("Some database functions will not work without NumPy support")
else:
raise FpdbError("unrecognised database backend:"+backend)
self.cursor = self.db.cursor()
# Set up query dictionary as early in the connection process as we can.
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
self.cursor.execute(self.sql.query['set tx level'])
self.wrongDbVersion = False
try:
self.cursor.execute("SELECT * FROM Settings")
settings = self.cursor.fetchone()
if settings[0] != 118:
print "outdated or too new database version - please recreate tables"
self.wrongDbVersion = True
except:# _mysql_exceptions.ProgrammingError:
if database != ":memory:": print "failed to read settings table - please recreate tables"
self.wrongDbVersion = True
#end def connect
def disconnect(self, due_to_error=False):
"""Disconnects the DB"""
if due_to_error:
self.db.rollback()
else:
self.db.commit()
self.cursor.close()
self.db.close()
#end def disconnect
def reconnect(self, due_to_error=False):
"""Reconnects the DB"""
#print "started fpdb_db.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"""
if self.backend==2:
return "MySQL InnoDB"
elif self.backend==3:
return "PostgreSQL"
elif self.backend==4:
return "SQLite"
else:
raise FpdbError("invalid backend")
#end def get_backend_name
def get_db_info(self):
return (self.host, self.database, self.user, self.password)
#end def get_db_info
#end class fpdb_db

212
pyfpdb/fpdb_import.py Normal file → Executable file
View File

@ -30,19 +30,19 @@ import Queue
from collections import deque # using Queue for now
import threading
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("importer")
import pygtk
import gtk
# fpdb/FreePokerTools modules
import fpdb_simple
import fpdb_db
import Database
import fpdb_parse_logic
import Configuration
import Exceptions
log = Configuration.get_logger("logging.conf", "importer")
# database interface modules
try:
@ -68,6 +68,7 @@ class Importer:
self.config = config
self.sql = sql
#log = Configuration.get_logger("logging.conf", "importer", log_dir=self.config.dir_log)
self.filelist = {}
self.dirlist = {}
self.siteIds = {}
@ -100,8 +101,6 @@ class Importer:
for i in xrange(self.settings['threads']):
self.writerdbs.append( Database.Database(self.config, sql = self.sql) )
self.NEWIMPORT = Configuration.NEWIMPORT
clock() # init clock in windows
#Set functions
@ -370,7 +369,7 @@ class Importer:
pass
(stored, duplicates, partial, errors, ttime) = self.import_file_dict(self.database, file, self.filelist[file][0], self.filelist[file][1], None)
try:
if not os.path.isdir(file):
if not os.path.isdir(file): # Note: This assumes that whatever calls us has an "addText" func
self.caller.addText(" %d stored, %d duplicates, %d partial, %d errors (time = %f)" % (stored, duplicates, partial, errors, ttime))
except KeyError: # TODO: Again, what error happens here? fix when we find out ..
pass
@ -407,9 +406,9 @@ class Importer:
return (0,0,0,0,0)
conv = None
(stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, 0)
(stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, time())
file = file.decode(fpdb_simple.LOCALE_ENCODING)
file = file.decode(Configuration.LOCALE_ENCODING)
# Load filter, process file, pass returned filename to import_fpdb_file
if self.settings['threads'] > 0 and self.writeq is not None:
@ -429,24 +428,36 @@ class Importer:
mod = __import__(filter)
obj = getattr(mod, filter_name, None)
if callable(obj):
hhc = obj(in_path = file, out_path = out_path, index = 0, starsArchive = self.settings['starsArchive']) # Index into file 0 until changeover
if hhc.getStatus() and self.NEWIMPORT == False:
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(db, out_path, site, q)
elif hhc.getStatus() and self.NEWIMPORT == True:
#This code doesn't do anything yet
idx = 0
if file in self.pos_in_file:
idx = self.pos_in_file[file]
else:
self.pos_in_file[file] = 0
hhc = obj(self.config, in_path = file, out_path = out_path, index = idx, starsArchive = self.settings['starsArchive'])
if hhc.getStatus():
handlist = hhc.getProcessedHands()
self.pos_in_file[file] = hhc.getLastCharacterRead()
to_hud = []
for hand in handlist:
if hand is not None:
#try, except duplicates here?
hand.prepInsert(self.database)
hand.insert(self.database)
if self.callHud and hand.dbid_hands != 0:
to_hud.append(hand.dbid_hands)
else:
try:
hand.insert(self.database)
except Exceptions.FpdbHandDuplicate:
duplicates += 1
else:
if self.callHud and hand.dbid_hands != 0:
to_hud.append(hand.dbid_hands)
else: # TODO: Treat empty as an error, or just ignore?
log.error("Hand processed but empty")
# Call hudcache update if not in bulk import mode
# FIXME: Need to test for bulk import that isn't rebuilding the cache
if self.callHud:
for hand in handlist:
if hand is not None:
hand.updateHudCache(self.database)
self.database.commit()
#pipe the Hands.id out to the HUD
@ -456,174 +467,21 @@ class Importer:
errors = getattr(hhc, 'numErrors')
stored = getattr(hhc, 'numHands')
stored -= duplicates
else:
# conversion didn't work
# TODO: appropriate response?
return (0, 0, 0, 1, 0)
return (0, 0, 0, 1, time() - ttime)
else:
log.warning("Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter))
return (0, 0, 0, 1, 0)
return (0, 0, 0, 1, time() - ttime)
ttime = time() - ttime
#This will barf if conv.getStatus != True
return (stored, duplicates, partial, errors, ttime)
def import_fpdb_file(self, db, file, site, q):
starttime = time()
last_read_hand = 0
loc = 0
(stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, 0)
# print "file =", file
if file == "stdin":
inputFile = sys.stdin
else:
if os.path.exists(file):
inputFile = open(file, "rU")
else:
self.removeFromFileList[file] = True
return (0, 0, 0, 1, 0)
try:
loc = self.pos_in_file[file]
#size = os.path.getsize(file)
#print "loc =", loc, 'size =', size
except KeyError:
pass
# Read input file into class and close file
inputFile.seek(loc)
#tmplines = inputFile.readlines()
#if tmplines == None or tmplines == []:
# print "tmplines = ", tmplines
#else:
# print "tmplines[0] =", tmplines[0]
self.lines = fpdb_simple.removeTrailingEOL(inputFile.readlines())
self.pos_in_file[file] = inputFile.tell()
inputFile.close()
x = clock()
(stored, duplicates, partial, errors, ttime, handsId) = self.import_fpdb_lines(db, self.lines, starttime, file, site, q)
db.commit()
y = clock()
ttime = y - x
#ttime = time() - starttime
if q is None:
log.info("Total stored: %(stored)d\tduplicates:%(duplicates)d\terrors:%(errors)d\ttime:%(ttime)s" % locals())
if not stored:
if duplicates:
for line_no in xrange(len(self.lines)):
if self.lines[line_no].find("Game #") != -1:
final_game_line = self.lines[line_no]
handsId=fpdb_simple.parseSiteHandNo(final_game_line)
else:
print "failed to read a single hand from file:", inputFile
handsId = 0
#todo: this will cause return of an unstored hand number if the last hand was error
self.handsId = handsId
return (stored, duplicates, partial, errors, ttime)
# end def import_fpdb_file
def import_fpdb_lines(self, db, lines, starttime, file, site, q = None):
"""Import an fpdb hand history held in the list lines, could be one hand or many"""
#db.lock_for_insert() # should be ok when using one thread, but doesn't help??
while gtk.events_pending():
gtk.main_iteration(False)
try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return.
firstline = lines[0]
except:
# just skip the debug message and return silently:
#print "DEBUG: import_fpdb_file: failed on lines[0]: '%s' '%s' '%s' '%s' " %( file, site, lines, loc)
return (0,0,0,1,0,0)
if "Tournament Summary" in firstline:
print "TODO: implement importing tournament summaries"
#self.faobs = readfile(inputFile)
#self.parseTourneyHistory()
return (0,0,0,1,0,0)
category = fpdb_simple.recogniseCategory(firstline)
startpos = 0
stored = 0 #counter
duplicates = 0 #counter
partial = 0 #counter
errors = 0 #counter
ttime = 0
handsId = 0
for i in xrange(len(lines)):
if len(lines[i]) < 2: #Wierd way to detect for '\r\n' or '\n'
endpos = i
hand = lines[startpos:endpos]
if len(hand[0]) < 2:
hand=hand[1:]
if len(hand) < 3:
pass
#TODO: This is ugly - we didn't actually find the start of the
# hand with the outer loop so we test again...
else:
isTourney = fpdb_simple.isTourney(hand[0])
if not isTourney:
hand = fpdb_simple.filterAnteBlindFold(hand)
self.hand = hand
try:
handsId = fpdb_parse_logic.mainParser( self.settings, self.siteIds[site]
, category, hand, self.config
, db, q )
db.commit()
stored += 1
if self.callHud:
#print "call to HUD here. handsId:",handsId
#pipe the Hands.id out to the HUD
# print "fpdb_import: sending hand to hud", handsId, "pipe =", self.caller.pipe_to_hud
try:
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
except IOError: # hud closed
self.callHud = False
pass # continue import without hud
except Exceptions.DuplicateError:
duplicates += 1
db.rollback()
except (ValueError), fe:
errors += 1
self.printEmailErrorMessage(errors, file, hand)
if (self.settings['failOnError']):
db.commit() #dont remove this, in case hand processing was cancelled.
raise
else:
db.rollback()
except (fpdb_simple.FpdbError), fe:
errors += 1
self.printEmailErrorMessage(errors, file, hand)
db.rollback()
if self.settings['failOnError']:
db.commit() #dont remove this, in case hand processing was cancelled.
raise
if self.settings['minPrint']:
if not ((stored+duplicates+errors) % self.settings['minPrint']):
print "stored:", stored, " duplicates:", duplicates, "errors:", errors
if self.settings['handCount']:
if ((stored+duplicates+errors) >= self.settings['handCount']):
if not self.settings['quiet']:
print "quitting due to reaching the amount of hands to be imported"
print "Total stored:", stored, "duplicates:", duplicates, "errors:", errors, " time:", (time() - starttime)
sys.exit(0)
startpos = endpos
return (stored, duplicates, partial, errors, ttime, handsId)
# end def import_fpdb_lines
def printEmailErrorMessage(self, errors, filename, line):
traceback.print_exc(file=sys.stderr)
print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it."

View File

@ -1,64 +1,75 @@
[loggers]
keys=root,parser,importer,config,db
keys=root,fpdb,logview,parser,importer,config,db,hud,filter
[handlers]
keys=consoleHandler,fileHandler
keys=consoleHandler,rotatingFileHandler
[formatters]
keys=fileFormatter,stderrFormatter
[logger_root]
level=INFO
handlers=consoleHandler,fileHandler
handlers=consoleHandler,rotatingFileHandler
[logger_fpdb]
level=INFO
handlers=consoleHandler,fileHandler
handlers=consoleHandler,rotatingFileHandler
qualname=fpdb
propagate=0
[logger_logview]
level=INFO
handlers=consoleHandler,fileHandler
handlers=consoleHandler,rotatingFileHandler
qualname=logview
propagate=0
[logger_parser]
level=INFO
handlers=consoleHandler,fileHandler
handlers=consoleHandler,rotatingFileHandler
qualname=parser
propagate=0
[logger_importer]
level=DEBUG
handlers=consoleHandler,fileHandler
handlers=consoleHandler,rotatingFileHandler
qualname=importer
propagate=0
[logger_config]
level=INFO
handlers=consoleHandler,fileHandler
handlers=consoleHandler,rotatingFileHandler
qualname=config
propagate=0
[logger_db]
level=DEBUG
handlers=consoleHandler,fileHandler
handlers=consoleHandler,rotatingFileHandler
qualname=db
propagate=0
[logger_hud]
level=DEBUG
handlers=consoleHandler,rotatingFileHandler
qualname=hud
propagate=0
[logger_filter]
level=INFO
handlers=consoleHandler,rotatingFileHandler
qualname=filter
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=ERROR
formatter=stderrFormatter
args=(sys.stderr,)
[handler_fileHandler]
class=FileHandler
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=fileFormatter
args=('logging.out', 'a')
args=('%(logFile)s', 'a', 100000000, 5)
[formatter_fileFormatter]
format=%(asctime)s - %(name)-12s %(levelname)-8s %(message)s

View File

@ -22,52 +22,155 @@ Py2exe script for fpdb.
########################################################################
#TODO: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate
# include the lib needed to handle png files in mucked
#TODO:
# get rid of all the uneeded libraries (e.g., pyQT)
# think about an installer
# done: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate
# include the lib needed to handle png files in mucked
#HOW TO USE this script:
#
# cd to the folder where this script is stored, usually .../pyfpdb.
# If there are build and dist subfolders present , delete them to get
# rid of earlier builds.
# Run the script with "py2exe_setup.py py2exe"
# You will frequently get messages about missing .dll files. E. g.,
#- cd to the folder where this script is stored, usually .../pyfpdb.
# [If there are build and dist subfolders present , delete them to get
# rid of earlier builds. Update: script now does this for you]
#- Run the script with "py2exe_setup.py py2exe"
#- You will frequently get messages about missing .dll files. E. g.,
# MSVCP90.dll. These are somewhere in your windows install, so you
# can just copy them to your working folder.
# If it works, you'll have 2 new folders, build and dist. Build is
# working space and should be deleted. Dist contains the files to be
# distributed. Last, you must copy the etc/, lib/ and share/ folders
# from your gtk/bin/ folder to the dist folder. (the whole folders, not
# just the contents) You can (should) then prune the etc/, lib/ and
# share/ folders to remove components we don't need.
# can just copy them to your working folder. (or just assume other
# person will have them? any copyright issues with including them?)
#- [ If it works, you'll have 3 new folders, build and dist and gfx. Build is
# working space and should be deleted. Dist and gfx contain the files to be
# distributed. ]
# If it works, you'll have a new dir fpdb-XXX-YYYYMMDD-exe which should
# contain 2 dirs; gfx and pyfpdb and run_fpdb.bat
#- Last, you must copy the etc/, lib/ and share/ folders from your
# gtk/bin/ (just /gtk/?) folder to the pyfpdb folder. (the whole folders,
# not just the contents)
#- You can (should) then prune the etc/, lib/ and share/ folders to
# remove components we don't need.
# sqlcoder notes: this worked for me with the following notes:
#- I used the following versions:
# python 2.5.4
# gtk+ 2.14.7 (gtk_2.14.7-20090119)
# pycairo 1.4.12-2
# pygobject 2.14.2-2
# pygtk 2.12.1-3
# matplotlib 0.98.3
# numpy 1.4.0
# py2exe-0.6.9 for python 2.5
#
#- I also copied these dlls manually from <gtk>/bin to /dist :
#
# libgobject-2.0-0.dll
# libgdk-win32-2.0-0.dll
import os
import sys
from distutils.core import setup
import py2exe
import glob
import matplotlib
from datetime import date
origIsSystemDLL = py2exe.build_exe.isSystemDLL
def isSystemDLL(pathname):
if os.path.basename(pathname).lower() in ("msvcp71.dll", "dwmapi.dll"):
return 0
return origIsSystemDLL(pathname)
py2exe.build_exe.isSystemDLL = isSystemDLL
def remove_tree(top):
# Delete everything reachable from the directory named in 'top',
# assuming there are no symbolic links.
# CAUTION: This is dangerous! For example, if top == '/', it
# could delete all your disk files.
# sc: Nicked this from somewhere, added the if statement to try
# make it a bit safer
if top in ('build','dist','gfx') and os.path.basename(os.getcwd()) == 'pyfpdb':
#print "removing directory '"+top+"' ..."
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
os.rmdir(top)
def test_and_remove(top):
if os.path.exists(top):
if os.path.isdir(top):
remove_tree(top)
else:
print "Unexpected file '"+top+"' found. Exiting."
exit()
# remove build and dist dirs if they exist
test_and_remove('dist')
test_and_remove('build')
test_and_remove('gfx')
today = date.today().strftime('%Y%m%d')
print "\n" + r"Output will be created in \pyfpdb\ and \fpdb_XXX_"+today+'\\'
print "Enter value for XXX (any length): ", # the comma means no newline
xxx = sys.stdin.readline().rstrip()
dist_dirname = r'fpdb-' + xxx + '-' + today + '-exe'
dist_dir = r'..\fpdb-' + xxx + '-' + today + '-exe'
print
test_and_remove(dist_dir)
setup(
name = 'fpdb',
description = 'Free Poker DataBase',
version = '0.12',
console = [ {'script': 'fpdb.py', },
console = [ {'script': 'fpdb.py', "icon_resources": [(1, "../gfx/fpdb_large_icon.ico")]},
{'script': 'HUD_main.py', },
{'script': 'Configuration.py', }
],
options = {'py2exe': {
'packages' :'encodings',
'includes' : 'cairo, pango, pangocairo, atk, gobject, PokerStarsToFpdb',
'excludes' : '_tkagg, _agg2, cocoaagg, fltkagg',
'dll_excludes': 'libglade-2.0-0.dll',
'packages' : ['encodings', 'matplotlib'],
'includes' : ['cairo', 'pango', 'pangocairo', 'atk', 'gobject'
,'matplotlib.numerix.random_array'
,'AbsoluteToFpdb', 'BetfairToFpdb'
,'CarbonToFpdb', 'EverleafToFpdb'
,'FulltiltToFpdb', 'OnGameToFpdb'
,'PartyPokerToFpdb', 'PokerStarsToFpdb'
,'UltimateBetToFpdb', 'Win2dayToFpdb'
],
'excludes' : ['_tkagg', '_agg2', 'cocoaagg', 'fltkagg'], # surely we need this? '_gtkagg'
'dll_excludes': ['libglade-2.0-0.dll', 'libgdk-win32-2.0-0.dll'
,'libgobject-2.0-0.dll'],
}
},
data_files = ['HUD_config.xml.example',
'Cards01.png',
'logging.conf',
(r'matplotlibdata', glob.glob(r'c:\python26\Lib\site-packages\matplotlib\mpl-data\*'))
]
# files in 2nd value in tuple are moved to dir named in 1st value
data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf'])
,(dist_dir, [r'..\run_fpdb.bat'])
,( dist_dir + r'\gfx', glob.glob(r'..\gfx\*.*') )
# line below has problem with fonts subdir ('not a regular file')
#,(r'matplotlibdata', glob.glob(r'c:\python25\Lib\site-packages\matplotlib\mpl-data\*'))
] + matplotlib.get_py2exe_datafiles()
)
os.rename('dist', 'pyfpdb')
print '\n' + 'If py2exe was successful add the \\etc \\lib and \\share dirs '
print 'from your gtk dir to \\%s\\pyfpdb\\\n' % dist_dirname
print 'Also copy libgobject-2.0-0.dll and libgdk-win32-2.0-0.dll from <gtk_dir>\\bin'
print 'into there'
dest = os.path.join(dist_dirname, 'pyfpdb')
#print "try renaming pyfpdb to", dest
dest = dest.replace('\\', '\\\\')
#print "dest is now", dest
os.rename( 'pyfpdb', dest )

View File

@ -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

View File

@ -22,12 +22,39 @@ Test if gtk is working.
########################################################################
import sys
import os
import Configuration
config_path = Configuration.get_default_config_path()
try:
import gobject as _gobject
print "Import of gobject:\tSuccess"
import pygtk
print "Import of pygtk:\tSuccess"
pygtk.require('2.0')
import gtk
print "Import of gtk:\t\tSuccess"
import pango
print "Import of pango:\tSuccess"
if os.name == 'nt':
import win32
import win32api
print "Import of win32:\tSuccess"
try:
import matplotlib
matplotlib.use('GTK')
print "Import of matplotlib:\tSuccess"
import numpy
print "Import of numpy:\tSuccess"
import pylab
print "Import of pylab:\tSuccess"
except:
print "\nError:", sys.exc_info()
print "\npress return to finish"
sys.stdin.readline()
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
win.set_title("Test GTK")
@ -42,7 +69,7 @@ try:
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
dia.set_default_size(500, 300)
l = gtk.Label("GTK is working!")
l = gtk.Label("GTK is working!\nConfig location: %s" %config_path)
dia.vbox.add(l)
l.show()

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import sqlite3
import fpdb_db
import Database
import math
# Should probably use our wrapper classes - creating sqlite db in memory
@ -14,11 +14,11 @@ con.isolation_level = None
con.create_function("floor", 1, math.floor)
#Mod function
tmp = fpdb_db.sqlitemath()
tmp = Database.sqlitemath()
con.create_function("mod", 2, tmp.mod)
# Aggregate function VARIANCE()
con.create_aggregate("variance", 1, fpdb_db.VARIANCE)
con.create_aggregate("variance", 1, Database.VARIANCE)
cur = con.cursor()

46
pyfpdb/test_PokerStars.py Normal file → Executable file
View File

@ -27,9 +27,9 @@ settings.update(config.get_default_paths())
gametype = {'type':'ring', 'base':'draw', 'category':'badugi', 'limitType':'fl', 'sb':'0.25', 'bb':'0.50','currency':'USD'}
text = ""
hhc = PokerStarsToFpdb.PokerStars(autostart=False)
hhc = PokerStarsToFpdb.PokerStars(config, autostart=False)
h = HoldemOmahaHand(None, "PokerStars", gametype, text, builtFrom = "Test")
h = HoldemOmahaHand(config, None, "PokerStars", gametype, text, builtFrom = "Test")
h.addPlayer("1", "s0rrow", "100000")
hhc.compilePlayerRegexs(h)
@ -39,7 +39,7 @@ def checkGameInfo(hhc, header, info):
assert hhc.determineGameType(header) == info
def testGameInfo():
hhc = PokerStarsToFpdb.PokerStars(autostart=False)
hhc = PokerStarsToFpdb.PokerStars(config, autostart=False)
pairs = (
(u"PokerStars Game #20461877044: Hold'em No Limit ($1/$2) - 2008/09/16 18:58:01 ET",
{'type':'ring', 'base':"hold", 'category':'holdem', 'limitType':'nl', 'sb':'1', 'bb':'2', 'currency':'USD'}),
@ -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)
@ -148,11 +178,7 @@ def testDrawImport():
(stored, dups, partial, errs, ttime) = importer.runImport()
importer.clearFileList()
except FpdbError:
if Configuration.NEWIMPORT == False:
#Old import code doesn't support draw
pass
else:
assert 0 == 1
assert 0 == 1
# Should actually do some testing here
assert 1 == 1

7
run_fpdb.bat Executable file
View File

@ -0,0 +1,7 @@
rem .bat script to run fpdb
cd pyfpdb
fpdb.exe

33
run_fpdb.py Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/python
#Copyright 2008 Carl Gherardi
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import os
import sys
# sys.path[0] holds the directory run_fpdb.py is in
sys.path[0] = sys.path[0]+os.sep+"pyfpdb"
os.chdir(sys.path[0])
#print "sys.path[0] =", sys.path[0], "cwd =", os.getcwd()
import fpdb
if __name__ == "__main__":
me = fpdb.fpdb()
me.main()
exit()