merged carl's tree

This commit is contained in:
steffen123 2010-05-27 21:58:07 +02:00
commit 150901cd6e
60 changed files with 5026 additions and 5222 deletions

View File

@ -1,5 +1,5 @@
# Variable definitions # Variable definitions
VERSION = 0.12 VERSION = 0.20
DATE = $(shell date +%Y%m%d) DATE = $(shell date +%Y%m%d)
all: all:

View File

@ -1,5 +1,5 @@
README.txt README.txt
updated 26 March 2009, REB updated 22 February 2010, REB
fpdb - Free Poker Database fpdb - Free Poker Database
@ -29,7 +29,7 @@ fpdb supports:
Omaha (incl Hi/low) Omaha (incl Hi/low)
7 Card Stud (incl Hi/low) 7 Card Stud (incl Hi/low)
Razz Razz
Draw support is under development Triple Draw and Badugi
Mixed Games -- HUD under development Mixed Games -- HUD under development
Operating Systems: Operating Systems:
@ -38,23 +38,38 @@ fpdb supports:
Mac OS/X -- no support for HUD Mac OS/X -- no support for HUD
Databases: Databases:
SQLite configured by default
MySQL MySQL
PostgreSQL PostgreSQL
SQLite under development
Downloads: Downloads:
Releases: http://sourceforge.net/project/showfiles.php?group_id=226872 Releases: http://sourceforge.net/project/showfiles.php?group_id=226872
Development code via git: http://www.assembla.com/spaces/free_poker_tools/trac_git_tool Development code via git: http://www.assembla.com/spaces/free_poker_tools/trac_git_tool
Developers: Developers:
At least 7 people have contributed code or patches. Others are welcome. At least 10 people have contributed code or patches. Others are welcome.
Source Code:
If you received fpdb as the Windows compressed exe, then you did not
receive souce code for fpdb or the included libraries. If you wish, you can
obtain the source code here:
fpdb: see Downloads, above.
python: http://python.org/
gtk: http://www.gtk.org/download.html
pygtk: http://www.pygtk.org/downloads.html
psycopg2: http://initd.org/pub/software/psycopg/
mysqldb: http://sourceforge.net/projects/mysql-python/files/
sqlalchemy: http://www.sqlalchemy.org/download.html
numpy: http://www.scipy.org/Download
matplotlib: http://sourceforge.net/projects/matplotlib/files/
License License
======= =======
Trademarks of third parties have been used under Fair Use or similar laws. Trademarks of third parties have been used under Fair Use or similar laws.
Copyright 2008 Steffen Jobbagy-Felso Copyright 2008 Steffen Jobbagy-Felso
Copyright 2009 Ray E. Barker Copyright 2009,2010 Ray E. Barker
Permission is granted to copy, distribute and/or modify this Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License, document under the terms of the GNU Free Documentation License,
Version 1.2 as published by the Free Software Foundation; with Version 1.2 as published by the Free Software Foundation; with

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 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 #!/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 # 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 # it under the terms of the GNU General Public License as published by
@ -18,93 +19,286 @@
######################################################################## ########################################################################
# Standard Library modules # This code is based heavily on EverleafToFpdb.py, by Carl Gherardi
import Configuration #
import traceback # 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 sys
import re import logging
import xml.dom.minidom from HandHistoryConverter import *
from xml.dom.minidom import Node from decimal import Decimal
from HandHistoryConverter import HandHistoryConverter
# Carbon format looks like: class Carbon(HandHistoryConverter):
# 1) <description type="Holdem" stakes="No Limit ($0.25/$0.50)"/> sitename = "Carbon"
# 2) <game id="14902583-5578" starttime="20081006145401" numholecards="2" gametype="2" realmoney="true" data="20081006|Niagara Falls (14902583)|14902583|14902583-5578|false"> filetype = "text"
# 3) <players dealer="8"> codepage = "cp1252"
# <player seat="3" nickname="PlayerInSeat3" balance="$43.29" dealtin="true" /> siteID = 11
# ...
# 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"/>
# The full sequence for a NHLE cash game is: # Static regexes
# BLINDS, PREFLOP, POSTFLOP, POSTTURN, POSTRIVER, SHOWDOWN, END_OF_GAME re_SplitHands = re.compile(r'</game>\n+(?=<game)')
# This sequence can be terminated after BLINDS at any time by END_OF_FOLDED_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 compilePlayerRegexs(self, hand):
def __init__(self, config, filename): pass
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 readSupportedGames(self): def playerNameFromSeatNo(self, seatNo, hand):
pass # This special function is required because Carbon Poker records
def determineGameType(self): # actions by seat number, not by the player's name
gametype = [] for p in hand.players:
desc_node = self.doc.getElementsByTagName("description") if p[0] == int(seatNo):
#TODO: no examples of non ring type yet return p[1]
gametype = gametype + ["ring"]
type = desc_node[0].getAttribute("type")
if(type == "Holdem"):
gametype = gametype + ["hold"]
else:
print "Carbon: Unknown gametype: '%s'" % (type)
stakes = desc_node[0].getAttribute("stakes") def readSupportedGames(self):
#TODO: no examples of anything except nlhe return [["ring", "hold", "nl"],
m = re.match('(?P<LIMIT>No Limit)\s\(\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\)', stakes) ["tour", "hold", "nl"]]
if(m.group('LIMIT') == "No Limit"): def determineGameType(self, handText):
gametype = gametype + ["nl"] """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'))] m = self.re_GameInfo.search(handText)
gametype = gametype + [self.float2int(m.group('BB'))] 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): if 'LIMIT' in mg:
pass self.info['limitType'] = limits[mg['LIMIT']]
def readBlinds(self): if 'GAME' in mg:
pass (self.info['base'], self.info['category']) = games[mg['GAME']]
def readAction(self): if 'SB' in mg:
pass 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 return self.info
# This is pretty dodgy
def readFile(self, filename): def readHandInfo(self, hand):
print "Carbon: Reading file: '%s'" %(filename) m = self.re_HandInfo.search(hand.handText)
infile=open(filename, "rU") if m is None:
self.obs = infile.read() logging.info("Didn't match re_HandInfo")
infile.close() logging.info(hand.handText)
self.obs = "<CarbonHHFile>\n" + self.obs + "</CarbonHHFile>" return None
try: logging.debug("HID %s-%s, Table %s" % (m.group('HID1'),
doc = xml.dom.minidom.parseString(self.obs) m.group('HID2'), m.group('TABLE')[:-1]))
self.doc = doc hand.handid = m.group('HID1') + m.group('HID2')
except: hand.tablename = m.group('TABLE')[:-1]
traceback.print_exc(file=sys.stderr) 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__": if __name__ == "__main__":
c = Configuration.Config() parser = OptionParser()
e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml") parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-")
e.processFile() parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
print str(e) 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

@ -16,27 +16,59 @@
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
# From fpdb_simple
card_map = { "0": 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}
# FIXME: the following is a workaround until switching to newimport.
# This should be moved into DerivedStats
# I'd also like to change HandsPlayers.startCards to a different datatype
# so we can 'trivially' add different start card classifications
def calcStartCards(hand, player):
if hand.gametype['category'] == 'holdem':
hcs = hand.join_holecards(player, asList=True)
#print "DEBUG: hcs: %s" % hcs
value1 = card_map[hcs[0][0]]
value2 = card_map[hcs[1][0]]
return twoStartCards(value1, hcs[0][1], value2, hcs[1][1])
else:
# FIXME: Only do startCards value for holdem at the moment
return 0
def twoStartCards(value1, suit1, value2, suit2): def twoStartCards(value1, suit1, value2, suit2):
""" Function to convert 2 value,suit pairs into a Holdem style starting hand e.g. AQo """ 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) (y+2) represents rank of second card (2=2 .. 14=Ace)
If x > y then pair is suited, if x < y then unsuited""" If x > y then pair is suited, if x < y then unsuited
if value1 < 2 or value2 < 2: 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 ret = 0
if value1 == value2: # pairs elif value1 == value2: # pairs
ret = (13 * (value2-2) + (value2-2) ) ret = (13 * (value2-2) + (value2-2) ) + 1
elif suit1 == suit2: elif suit1 == suit2:
if value1 > value2: if value1 > value2:
ret = 13 * (value1-2) + (value2-2) ret = 13 * (value1-2) + (value2-2) + 1
else: else:
ret = 13 * (value2-2) + (value1-2) ret = 13 * (value2-2) + (value1-2) + 1
else: else:
if value1 > value2: if value1 > value2:
ret = 13 * (value2-2) + (value1-2) ret = 13 * (value2-2) + (value1-2) + 1
else: else:
ret = 13 * (value1-2) + (value2-2) ret = 13 * (value1-2) + (value2-2) + 1
# print "twoStartCards(", value1, suit1, value2, suit2, ")=", ret # print "twoStartCards(", value1, suit1, value2, suit2, ")=", ret
return ret return ret
@ -47,8 +79,8 @@ def twoStartCardString(card):
ret = 'xx' ret = 'xx'
if card > 0: if card > 0:
s = ('2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A') s = ('2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A')
x = card / 13 x = (card-1) / 13
y = card - 13 * x y = (card-1) - 13 * x
if x == y: ret = s[x] + s[y] if x == y: ret = s[x] + s[y]
elif x > y: ret = s[x] + s[y] + 's' elif x > y: ret = s[x] + s[y] + 's'
else: ret = s[y] + s[x] + 'o' else: ret = s[y] + s[x] + 'o'
@ -108,10 +140,10 @@ def valueSuitFromCard(card):
return suitFromCardList[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, 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, '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, '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, '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 ' ': 0
} }
def encodeCard(cardString): def encodeCard(cardString):

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

@ -1,66 +0,0 @@
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#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
import fpdb_simple
from optparse import OptionParser
try:
import MySQLdb
except:
diaSQLLibMissing = gtk.Dialog(title="Fatal Error - SQL interface library missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK))
print "Please note that the CLI importer only works with MySQL, if you use PostgreSQL this error is expected."
import fpdb_import
import fpdb_db
if __name__ == "__main__":
#process CLI parameters
parser = OptionParser()
parser.add_option("-c", "--handCount", default="0", type="int",
help="Number of hands to import (default 0 means unlimited)")
parser.add_option("-d", "--database", default="fpdb", help="The MySQL database to use (default fpdb)")
parser.add_option("-e", "--errorFile", default="failed.txt",
help="File to store failed hands into. (default: failed.txt) Not implemented.")
parser.add_option("-f", "--inputFile", "--file", "--inputfile", default="stdin",
help="The file you want to import (remember to use quotes if necessary)")
parser.add_option("-m", "--minPrint", "--status", default="50", type="int",
help="How often to print a one-line status report (0 means never, default is 50)")
parser.add_option("-p", "--password", help="The password for the MySQL user")
parser.add_option("-q", "--quiet", action="store_true",
help="If this is passed it doesn't print a total at the end nor the opening line. Note that this purposely does NOT change --minPrint")
parser.add_option("-s", "--server", default="localhost",
help="Hostname/IP of the MySQL server (default localhost)")
parser.add_option("-u", "--user", default="fpdb", help="The MySQL username (default fpdb)")
parser.add_option("-x", "--failOnError", action="store_true",
help="If this option is passed it quits when it encounters any error")
(options, argv) = parser.parse_args()
settings={'callFpdbHud':False, 'db-backend':2}
settings['db-host']=options.server
settings['db-user']=options.user
settings['db-password']=options.password
settings['db-databaseName']=options.database
settings['handCount']=options.handCount
settings['failOnError']=options.failOnError
importer = fpdb_import.Importer(options, settings)
importer.addImportFile(options.inputFile)
importer.runImport()

View File

@ -31,12 +31,17 @@ import inspect
import string import string
import traceback import traceback
import shutil import shutil
import locale
import xml.dom.minidom import xml.dom.minidom
from xml.dom.minidom import Node from xml.dom.minidom import Node
import logging, logging.config import logging, logging.config
import ConfigParser 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 # Functions for finding config files and setting up logging
# Also used in other modules that use logging. # Also used in other modules that use logging.
@ -51,60 +56,100 @@ def get_default_config_path():
return config_path return config_path
def get_exec_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 if hasattr(sys, "frozen"): # compiled by py2exe
return os.path.dirname(sys.executable) return os.path.dirname(sys.executable)
else: else:
pathname = os.path.dirname(sys.argv[0]) return os.path.dirname(sys.path[0]) # should be path to /fpdb
return os.path.abspath(pathname)
def get_config(file_name, fallback = True): def get_config(file_name, fallback = True):
"""Looks in cwd and in self.default_config_path for a config file.""" """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 # print "config_path=", config_path
if os.path.exists(config_path): # there is a file in the cwd 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 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 # print "config path 2=", config_path
if os.path.exists(config_path): if os.path.exists(config_path):
return config_path return (config_path,False)
# No file found # No file found
if not fallback: if not fallback:
return False return (False,False)
# OK, fall back to the .example file, should be in the start dir # OK, fall back to the .example file, should be in the start dir
if os.path.exists(file_name + ".example"): if os.path.exists(file_name + ".example"):
try: try:
shutil.copyfile(file_name + ".example", file_name) print ""
print "No %s found, using %s.example.\n" % (file_name, file_name) check_dir(default_dir)
print "A %s file has been created. You will probably have to edit it." % file_name shutil.copyfile(file_name + ".example", config_path)
sys.stderr.write("No %s found, using %s.example.\n" % (file_name, file_name) ) 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: except:
print "No %s found, cannot fall back. Exiting.\n" % file_name print "Error copying .example file, cannot fall back. Exiting.\n"
sys.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name) sys.stderr.write("Error copying .example file, cannot fall back. Exiting.\n")
sys.stderr.write( str(sys.exc_info()) )
sys.exit() 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): def get_logger(file_name, config = "config", fallback = False, log_dir=None, log_file=None):
conf = get_config(file_name, fallback = fallback) (conf_file,copied) = get_config(file_name, fallback = fallback)
if conf:
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: 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 = logging.getLogger(config)
log.debug("%s logger initialised" % config) log.debug("%s logger initialised" % config)
return log return log
except: except:
pass pass
log = logging.basicConfig() log = logging.basicConfig(filename=file, level=logging.INFO)
log = logging.getLogger() 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 return log
# find a logging.conf file and set up logging def check_dir(path, create = True):
log = get_logger("logging.conf") """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 # application wide consts
@ -112,10 +157,6 @@ log = get_logger("logging.conf")
APPLICATION_NAME_SHORT = 'fpdb' APPLICATION_NAME_SHORT = 'fpdb'
APPLICATION_VERSION = 'xx.xx.xx' 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_POSTGRESQL = 'postgresql'
DATABASE_TYPE_SQLITE = 'sqlite' DATABASE_TYPE_SQLITE = 'sqlite'
DATABASE_TYPE_MYSQL = 'mysql' DATABASE_TYPE_MYSQL = 'mysql'
@ -125,7 +166,20 @@ DATABASE_TYPES = (
DATABASE_TYPE_MYSQL, 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): def string_to_bool(string, default=True):
@ -267,6 +321,10 @@ class Game:
stat.hudprefix = stat_node.getAttribute("hudprefix") stat.hudprefix = stat_node.getAttribute("hudprefix")
stat.hudsuffix = stat_node.getAttribute("hudsuffix") stat.hudsuffix = stat_node.getAttribute("hudsuffix")
stat.hudcolor = stat_node.getAttribute("hudcolor") stat.hudcolor = stat_node.getAttribute("hudcolor")
stat.stat_loth = stat_node.getAttribute("stat_loth")
stat.stat_hith = stat_node.getAttribute("stat_hith")
stat.stat_locolor = stat_node.getAttribute("stat_locolor")
stat.stat_hicolor = stat_node.getAttribute("stat_hicolor")
self.stats[stat.stat_name] = stat self.stats[stat.stat_name] = stat
@ -356,6 +414,7 @@ class Import:
self.hhArchiveBase = node.getAttribute("hhArchiveBase") self.hhArchiveBase = node.getAttribute("hhArchiveBase")
self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True) self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True)
self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False) self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False)
self.saveStarsHH = string_to_bool(node.getAttribute("saveStarsHH"), default=False)
def __str__(self): def __str__(self):
return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \ return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \
@ -395,11 +454,11 @@ class Tv:
class Config: class Config:
def __init__(self, file = None, dbname = ''): def __init__(self, file = None, dbname = ''):
# "file" is a path to an xml file with the fpdb/HUD configuration # "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 # 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.default_config_path = self.get_default_config_path()
self.example_copy = False
if file is not None: # config file path passed in if file is not None: # config file path passed in
file = os.path.expanduser(file) file = os.path.expanduser(file)
if not os.path.exists(file): if not os.path.exists(file):
@ -407,7 +466,16 @@ class Config:
sys.stderr.write("Configuration file %s not found. Using defaults." % (file)) sys.stderr.write("Configuration file %s not found. Using defaults." % (file))
file = None 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_config = get_default_config_path()
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 # 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 # If using the example, we'll edit it later
@ -415,15 +483,21 @@ class Config:
print "\nReading configuration file %s\n" % file print "\nReading configuration file %s\n" % file
try: try:
doc = xml.dom.minidom.parse(file) doc = xml.dom.minidom.parse(file)
self.file_error = None
except: except:
log.error("Error parsing %s. See error log file." % (file)) log.error("Error parsing %s. See error log file." % (file))
traceback.print_exc(file=sys.stderr) traceback.print_exc(file=sys.stderr)
print "press enter to continue" self.file_error = sys.exc_info()[1]
sys.stdin.readline() # we could add a parameter to decide whether to return or read a line and exit?
sys.exit() return
#print "press enter to continue"
#sys.stdin.readline()
#sys.exit()
#ExpatError: not well-formed (invalid token): line 511, column 4
#sys.exc_info = (<class 'xml.parsers.expat.ExpatError'>, ExpatError('not well-formed (invalid token): line 511,
# column 4',), <traceback object at 0x024503A0>)
self.doc = doc self.doc = doc
self.file = file
self.supported_sites = {} self.supported_sites = {}
self.supported_games = {} self.supported_games = {}
self.supported_databases = {} # databaseName --> Database instance self.supported_databases = {} # databaseName --> Database instance
@ -560,7 +634,11 @@ class Config:
def save(self, file = None): def save(self, file = None):
if file is None: if file is None:
file = self.file file = self.file
shutil.move(file, file+".backup") try:
shutil.move(file, file+".backup")
except:
pass
with open(file, 'w') as f: with open(file, 'w') as f:
self.doc.writexml(f) self.doc.writexml(f)
@ -619,14 +697,8 @@ class Config:
try: db['db-server'] = self.supported_databases[name].db_server try: db['db-server'] = self.supported_databases[name].db_server
except: pass except: pass
if self.supported_databases[name].db_server== DATABASE_TYPE_MYSQL: db['db-backend'] = self.get_backend(self.supported_databases[name].db_server)
db['db-backend'] = 2
elif self.supported_databases[name].db_server== DATABASE_TYPE_POSTGRESQL:
db['db-backend'] = 3
elif self.supported_databases[name].db_server== DATABASE_TYPE_SQLITE:
db['db-backend'] = 4
else:
raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server)
return db return db
def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None, def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None,
@ -646,6 +718,23 @@ class Config:
if db_type is not None: self.supported_databases[db_name].dp_type = db_type if db_type is not None: self.supported_databases[db_name].dp_type = db_type
return return
def get_backend(self, name):
"""Returns the number of the currently used backend"""
if name == DATABASE_TYPE_MYSQL:
ret = 2
elif name == DATABASE_TYPE_POSTGRESQL:
ret = 3
elif name == DATABASE_TYPE_SQLITE:
ret = 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 ret
def getDefaultSite(self): def getDefaultSite(self):
"Returns first enabled site or None" "Returns first enabled site or None"
for site_name,site in self.supported_sites.iteritems(): for site_name,site in self.supported_sites.iteritems():
@ -735,8 +824,12 @@ class Config:
try: imp['saveActions'] = self.imp.saveActions try: imp['saveActions'] = self.imp.saveActions
except: imp['saveActions'] = True except: imp['saveActions'] = True
try: imp['saveStarsHH'] = self.imp.saveStarsHH
except: imp['saveStarsHH'] = False
try: imp['fastStoreHudCache'] = self.imp.fastStoreHudCache try: imp['fastStoreHudCache'] = self.imp.fastStoreHudCache
except: imp['fastStoreHudCache'] = True except: imp['fastStoreHudCache'] = True
return imp return imp
def get_default_paths(self, site = None): def get_default_paths(self, site = None):
@ -973,3 +1066,9 @@ if __name__== "__main__":
PrettyPrint(site_node, stream=sys.stdout, encoding="utf-8") PrettyPrint(site_node, stream=sys.stdout, encoding="utf-8")
except: except:
print "xml.dom.ext needs PyXML to be installed!" 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

@ -47,33 +47,34 @@ class DerivedStats():
self.handsplayers[player[1]]['wonWhenSeenStreet1'] = 0.0 self.handsplayers[player[1]]['wonWhenSeenStreet1'] = 0.0
self.handsplayers[player[1]]['sawShowdown'] = False self.handsplayers[player[1]]['sawShowdown'] = False
self.handsplayers[player[1]]['wonAtSD'] = 0.0 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): for i in range(5):
self.handsplayers[player[1]]['street%dCalls' % i] = 0 self.handsplayers[player[1]]['street%dCalls' % i] = 0
self.handsplayers[player[1]]['street%dBets' % i] = 0 self.handsplayers[player[1]]['street%dBets' % i] = 0
for i in range(1,5): for i in range(1,5):
self.handsplayers[player[1]]['street%dCBChance' %i] = False self.handsplayers[player[1]]['street%dCBChance' %i] = False
self.handsplayers[player[1]]['street%dCBDone' %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. #FIXME - Everything below this point is incomplete.
self.handsplayers[player[1]]['position'] = 2
self.handsplayers[player[1]]['tourneyTypeId'] = 1 self.handsplayers[player[1]]['tourneyTypeId'] = 1
self.handsplayers[player[1]]['startCards'] = 0
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): for i in range(1,5):
self.handsplayers[player[1]]['otherRaisedStreet%d' %i] = False self.handsplayers[player[1]]['otherRaisedStreet%d' %i] = False
self.handsplayers[player[1]]['foldToOtherRaisedStreet%d' %i] = False self.handsplayers[player[1]]['foldToOtherRaisedStreet%d' %i] = False
self.handsplayers[player[1]]['foldToStreet%dCBChance' %i] = False self.handsplayers[player[1]]['foldToStreet%dCBChance' %i] = False
self.handsplayers[player[1]]['foldToStreet%dCBDone' %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.assembleHands(self.hand)
self.assembleHandsPlayers(self.hand) self.assembleHandsPlayers(self.hand)
@ -161,7 +162,7 @@ class DerivedStats():
self.handsplayers[player]['wonAtSD'] = 1.0 self.handsplayers[player]['wonAtSD'] = 1.0
for player in hand.pot.committed: for player in hand.pot.committed:
self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])) self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])- (100*hand.pot.common[player]))
self.calcCBets(hand) self.calcCBets(hand)
@ -172,35 +173,64 @@ class DerivedStats():
# self.handsplayers[player[1]]['card%s' % i] = Card.encodeCard(card) # self.handsplayers[player[1]]['card%s' % i] = Card.encodeCard(card)
for i, card in enumerate(hcs[:7]): for i, card in enumerate(hcs[:7]):
self.handsplayers[player[1]]['card%s' % (i+1)] = Card.encodeCard(card) self.handsplayers[player[1]]['card%s' % (i+1)] = Card.encodeCard(card)
self.handsplayers[player[1]]['startCards'] = Card.calcStartCards(hand, player[1])
self.setPositions(hand)
# position, self.calcCheckCallRaise(hand)
#Stud 3rd street card test self.calc34BetStreet0(hand)
# denny501: brings in for $0.02 self.calcSteals(hand)
# 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
# Additional stats # Additional stats
# 3betSB, 3betBB # 3betSB, 3betBB
# Squeeze, Ratchet? # Squeeze, Ratchet?
def getPosition(hand, seat): def setPositions(self, hand):
"""Returns position value like 'B', 'S', 0, 1, ...""" """Sets the position for each player in HandsPlayers
# Flop/Draw games with blinds any blinds are negative values, and the last person to act on the
# Need a better system??? first betting round is 0
# -2 BB - B (all) NOTE: HU, both values are negative for non-stud games
# -1 SB - S (all) NOTE2: I've never seen a HU stud match"""
# 0 Button # The position calculation must be done differently for Stud and other games as
# 1 Cutoff # Stud the 'blind' acts first - in all other games they act last.
# 2 Hijack #
#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'] = i
def assembleHudCache(self, hand): def assembleHudCache(self, hand):
# No real work to be done - HandsPlayers data already contains the correct info
pass pass
def vpip(self, hand): 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 # 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 # 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['playersAtStreet1'] = 0
self.hands['playersAtStreet2'] = 0 self.hands['playersAtStreet2'] = 0
@ -231,23 +262,31 @@ class DerivedStats():
self.hands['playersAtStreet4'] = 0 self.hands['playersAtStreet4'] = 0
self.hands['playersAtShowdown'] = 0 self.hands['playersAtShowdown'] = 0
alliners = set() # alliners = set()
for (i, street) in enumerate(hand.actionStreets[2:]): # for (i, street) in enumerate(hand.actionStreets[2:]):
actors = set() # actors = set()
for action in hand.actions[street]: # for action in hand.actions[street]:
if len(action) > 2 and action[-1]: # allin # if len(action) > 2 and action[-1]: # allin
alliners.add(action[0]) # alliners.add(action[0])
actors.add(action[0]) # actors.add(action[0])
if len(actors)==0 and len(alliners)<2: # if len(actors)==0 and len(alliners)<2:
alliners = set() # alliners = set()
self.hands['playersAtStreet%d' % (i+1)] = len(set.union(alliners, actors)) # 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)
actions = hand.actions[hand.actionStreets[-1]] p_in = set(x[1] for x in hand.players)
pas = set.union(self.pfba(actions) - self.pfba(actions, l=('folds',)), alliners) for (i, street) in enumerate(hand.actionStreets):
self.hands['playersAtShowdown'] = len(pas) 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: if self.hands['playersAtShowdown'] > 1:
for player in pas: for player in p_in:
self.handsplayers[player]['sawShowdown'] = True self.handsplayers[player]['sawShowdown'] = True
def streetXRaises(self, hand): def streetXRaises(self, hand):
@ -262,13 +301,66 @@ class DerivedStats():
for (i, street) in enumerate(hand.actionStreets[1:]): 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])) self.hands['street%dRaises' % i] = len(filter( lambda action: action[1] in ('raises','bets'), hand.actions[street]))
def calcCBets(self, hand): def calcSteals(self, hand):
# Continuation Bet chance, action: """Fills stealAttempt(Chance|ed, fold(Bb|Sb)ToSteal(Chance|)
# 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
Steal attempt - open raise on positions 1 0 S - i.e. MP3, CO, BU, SB
(note: I don't think PT2 counts SB steals in HU hands, maybe we shouldn't?)
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'] = self.handsplayers[pname]['street0_3BChance'] or bet_level == 2
self.handsplayers[pname]['street0_4BChance'] = bet_level == 3
self.handsplayers[pname]['street0_3BDone'] = self.handsplayers[pname]['street0_3BDone'] or (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' # XXX: enumerate(list, start=x) is python 2.6 syntax; 'start'
# came there # came there
#for i, street in enumerate(hand.actionStreets[2:], start=1): #for i, street in enumerate(hand.actionStreets[2:], start=1):
@ -276,10 +368,33 @@ class DerivedStats():
name = self.lastBetOrRaiser(hand.actionStreets[i+1]) name = self.lastBetOrRaiser(hand.actionStreets[i+1])
if name: if name:
chance = self.noBetsBefore(hand.actionStreets[i+2], name) chance = self.noBetsBefore(hand.actionStreets[i+2], name)
self.handsplayers[name]['street%dCBChance' % (i+1)] = True
if chance == True: if chance == True:
self.handsplayers[name]['street%dCBChance' % (i+1)] = True
self.handsplayers[name]['street%dCBDone' % (i+1)] = self.betStreet(hand.actionStreets[i+2], name) 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): def seen(self, hand, i):
pas = set() pas = set()
for act in hand.actions[hand.actionStreets[i+1]]: for act in hand.actions[hand.actionStreets[i+1]]:
@ -293,11 +408,13 @@ class DerivedStats():
def aggr(self, hand, i): def aggr(self, hand, i):
aggrers = set() aggrers = set()
for act in hand.actions[hand.actionStreets[i]]: # Growl - actionStreets contains 'BLINDSANTES', which isn't actually an action street
if act[1] in ('completes', 'raises'): for act in hand.actions[hand.actionStreets[i+1]]:
if act[1] in ('completes', 'bets', 'raises'):
aggrers.add(act[0]) aggrers.add(act[0])
for player in hand.players: for player in hand.players:
#print "DEBUG: actionStreet[%s]: %s" %(hand.actionStreets[i+1], i)
if player[1] in aggrers: if player[1] in aggrers:
self.handsplayers[player[1]]['street%sAggr' % i] = True self.handsplayers[player[1]]['street%sAggr' % i] = True
else: else:
@ -333,6 +450,44 @@ class DerivedStats():
players.add(action[0]) players.add(action[0])
return players 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): def noBetsBefore(self, street, player):
"""Returns true if there were no bets before the specified players turn, false otherwise""" """Returns true if there were no bets before the specified players turn, false otherwise"""
betOrRaise = False betOrRaise = False
@ -345,22 +500,19 @@ class DerivedStats():
break break
return betOrRaise return betOrRaise
def betStreet(self, street, player): def betStreet(self, street, player):
"""Returns true if player bet/raised the street as their first action""" """Returns true if player bet/raised the street as their first action"""
betOrRaise = False betOrRaise = False
for act in self.hand.actions[street]: for act in self.hand.actions[street]:
if act[0] == player and act[1] in ('bets', 'raises'): if act[0] == player:
betOrRaise = True if act[1] in ('bets', 'raises'):
else: betOrRaise = True
else:
# player found but did not bet or raise as their first action
pass
break break
#else:
# haven't found player's first action yet
return betOrRaise 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

@ -38,8 +38,9 @@ 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.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_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_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_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): def compilePlayerRegexs(self, hand):
@ -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_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_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_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_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_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) self.re_SitsOut = re.compile(ur"^%s sits out" % player_re, re.MULTILINE)
def readSupportedGames(self): def readSupportedGames(self):
@ -66,7 +67,9 @@ class Everleaf(HandHistoryConverter):
["ring", "hold", "pl"], ["ring", "hold", "pl"],
["ring", "hold", "fl"], ["ring", "hold", "fl"],
["ring", "studhi", "fl"], ["ring", "studhi", "fl"],
["ring", "omahahi", "pl"] ["ring", "omahahi", "pl"],
["ring", "omahahilo", "pl"],
["tour", "hold", "nl"]
] ]
def determineGameType(self, handText): def determineGameType(self, handText):
@ -138,6 +141,12 @@ or None if we fail to get the info """
hand.tablename = m.group('TABLE') 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 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 # 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] # 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 # or : 2008/11/07 12:38:49 ET
@ -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=None, player=m.group('PNAME'), holeandboard=cards)
hand.addShownCards(cards=cards, player=m.group('PNAME')) 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__": if __name__ == "__main__":
@ -305,4 +320,3 @@ if __name__ == "__main__":
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity) logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
e = Everleaf(in_path = options.ipath, out_path = options.opath, follow = options.follow, autostart=True) 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): def __str__(self):
return repr(self.value +" " + self.errmsg) return repr(self.value +" " + self.errmsg)
class DuplicateError(FpdbError): class FpdbHandError(FpdbError):
pass
class FpdbHandDuplicate(FpdbHandError):
pass
class FpdbHandPartial(FpdbHandError):
pass pass

View File

@ -26,21 +26,47 @@ from time import *
import gobject import gobject
#import pokereval #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 Configuration
import fpdb_db import Database
import FpdbSQLQueries import SQL
import Charset
class Filters(threading.Thread): class Filters(threading.Thread):
def __init__(self, db, config, qdict, display = {}, debug=True): def __init__(self, db, config, qdict, display = {}, debug=True):
# config and qdict are now redundant # config and qdict are now redundant
self.debug = debug self.debug = debug
#print "start of GraphViewer constructor"
self.db = db self.db = db
self.cursor = db.cursor self.cursor = db.cursor
self.sql = db.sql self.sql = db.sql
self.conf = db.config self.conf = db.config
self.display = display 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.sites = {}
self.games = {} self.games = {}
self.limits = {} self.limits = {}
@ -50,14 +76,14 @@ class Filters(threading.Thread):
self.heroes = {} self.heroes = {}
self.boxes = {} self.boxes = {}
# text used on screen stored here so that it can be configured for site in self.conf.get_supported_sites():
self.filterText = {'limitsall':'All', 'limitsnone':'None', 'limitsshow':'Show _Limits' #Get db site id for filtering later
,'seatsbetween':'Between:', 'seatsand':'And:', 'seatsshow':'Show Number of _Players' self.cursor.execute(self.sql.query['getSiteId'], (site,))
,'limitstitle':'Limits:', 'seatstitle':'Number of Players:' result = self.db.cursor.fetchall()
,'groupstitle':'Grouping:', 'posnshow':'Show Position Stats:' if len(result) == 1:
,'groupsall':'All Players' self.siteid[site] = result[0][0]
,'limitsFL':'FL', 'limitsNL':'NL', 'ring':'Ring', 'tour':'Tourney' else:
} print "Either 0 or more than one site matched - EEK"
# For use in date ranges. # For use in date ranges.
self.start_date = gtk.Entry(max=12) self.start_date = gtk.Entry(max=12)
@ -69,34 +95,28 @@ class Filters(threading.Thread):
self.sbGroups = {} self.sbGroups = {}
self.numHands = 0 self.numHands = 0
# Outer Packing box playerFrame = gtk.Frame()
self.mainVBox = gtk.VBox(False, 0)
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0) playerFrame.set_label_align(0.0, 0.0)
vbox = gtk.VBox(False, 0) vbox = gtk.VBox(False, 0)
self.fillPlayerFrame(vbox, self.display) self.fillPlayerFrame(vbox, self.display)
playerFrame.add(vbox) playerFrame.add(vbox)
self.boxes['player'] = vbox
sitesFrame = gtk.Frame("Sites:") sitesFrame = gtk.Frame()
sitesFrame.set_label_align(0.0, 0.0) sitesFrame.set_label_align(0.0, 0.0)
vbox = gtk.VBox(False, 0) vbox = gtk.VBox(False, 0)
self.fillSitesFrame(vbox) self.fillSitesFrame(vbox)
sitesFrame.add(vbox) sitesFrame.add(vbox)
self.boxes['sites'] = vbox
# Game types # Game types
gamesFrame = gtk.Frame("Games:") gamesFrame = gtk.Frame()
gamesFrame.set_label_align(0.0, 0.0) gamesFrame.set_label_align(0.0, 0.0)
gamesFrame.show() gamesFrame.show()
vbox = gtk.VBox(False, 0) vbox = gtk.VBox(False, 0)
self.fillGamesFrame(vbox) self.fillGamesFrame(vbox)
gamesFrame.add(vbox) gamesFrame.add(vbox)
self.boxes['games'] = vbox
# Limits # Limits
limitsFrame = gtk.Frame() limitsFrame = gtk.Frame()
@ -107,6 +127,7 @@ class Filters(threading.Thread):
self.cbAllLimits = None self.cbAllLimits = None
self.cbFL = None self.cbFL = None
self.cbNL = None self.cbNL = None
self.cbPL = None
self.rb = {} # radio buttons for ring/tour self.rb = {} # radio buttons for ring/tour
self.type = None # ring/tour self.type = None # ring/tour
self.types = {} # list of all ring/tour values self.types = {} # list of all ring/tour values
@ -132,14 +153,13 @@ class Filters(threading.Thread):
groupsFrame.add(vbox) groupsFrame.add(vbox)
# Date # Date
dateFrame = gtk.Frame("Date:") dateFrame = gtk.Frame()
dateFrame.set_label_align(0.0, 0.0) dateFrame.set_label_align(0.0, 0.0)
dateFrame.show() dateFrame.show()
vbox = gtk.VBox(False, 0) vbox = gtk.VBox(False, 0)
self.fillDateFrame(vbox) self.fillDateFrame(vbox)
dateFrame.add(vbox) dateFrame.add(vbox)
self.boxes['date'] = vbox
# Buttons # Buttons
self.Button1=gtk.Button("Unnamed 1") self.Button1=gtk.Button("Unnamed 1")
@ -180,6 +200,20 @@ class Filters(threading.Thread):
if "Button2" not in self.display or self.display["Button2"] == False: if "Button2" not in self.display or self.display["Button2"] == False:
self.Button2.hide() 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)
# make sure any locks on db are released:
self.db.rollback()
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.mainVBox return self.mainVBox
@ -191,6 +225,9 @@ class Filters(threading.Thread):
def getSites(self): def getSites(self):
return self.sites return self.sites
def getGames(self):
return self.games
def getSiteIds(self): def getSiteIds(self):
return self.siteid return self.siteid
@ -222,24 +259,29 @@ class Filters(threading.Thread):
def registerButton1Name(self, title): def registerButton1Name(self, title):
self.Button1.set_label(title) self.Button1.set_label(title)
self.label['button1'] = title
def registerButton1Callback(self, callback): def registerButton1Callback(self, callback):
self.Button1.connect("clicked", callback, "clicked") self.Button1.connect("clicked", callback, "clicked")
self.Button1.set_sensitive(True) self.Button1.set_sensitive(True)
self.callback['button1'] = callback
def registerButton2Name(self, title): def registerButton2Name(self, title):
self.Button2.set_label(title) self.Button2.set_label(title)
self.label['button2'] = title
def registerButton2Callback(self, callback): def registerButton2Callback(self, callback):
self.Button2.connect("clicked", callback, "clicked") self.Button2.connect("clicked", callback, "clicked")
self.Button2.set_sensitive(True) self.Button2.set_sensitive(True)
self.callback['button2'] = callback
def cardCallback(self, widget, data=None): 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): def createPlayerLine(self, hbox, site, player):
log.debug('add:"%s"' % player)
label = gtk.Label(site +" id:") label = gtk.Label(site +" id:")
hbox.pack_start(label, False, False, 0) hbox.pack_start(label, False, False, 3)
pname = gtk.Entry() pname = gtk.Entry()
pname.set_text(player) pname.set_text(player)
@ -253,22 +295,27 @@ class Filters(threading.Thread):
liststore = gtk.ListStore(gobject.TYPE_STRING) liststore = gtk.ListStore(gobject.TYPE_STRING)
completion.set_model(liststore) completion.set_model(liststore)
completion.set_text_column(0) completion.set_text_column(0)
names = self.db.get_player_names(self.conf) # (config=self.conf, site_id=None, like_player_name="%") names = self.db.get_player_names(self.conf, self.siteid[site]) # (config=self.conf, site_id=None, like_player_name="%")
for n in names: for n in names: # list of single-element "tuples"
liststore.append(n) _n = Charset.to_gui(n[0])
_nt = (_n, )
liststore.append(_nt)
self.__set_hero_name(pname, site) self.__set_hero_name(pname, site)
def __set_hero_name(self, w, site): def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text() _name = w.get_text()
# print "DEBUG: setting heroes[%s]: %s"%(site, self.heroes[site]) # 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): def __set_num_hands(self, w, val):
try: try:
self.numHands = int(w.get_text()) self.numHands = int(w.get_text())
except: except:
self.numHands = 0 self.numHands = 0
# print "DEBUG: setting numHands:", self.numHands # log.debug("setting numHands:", self.numHands)
def createSiteLine(self, hbox, site): def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site) cb = gtk.CheckButton(site)
@ -280,6 +327,7 @@ class Filters(threading.Thread):
cb = gtk.CheckButton(game) cb = gtk.CheckButton(game)
cb.connect('clicked', self.__set_game_select, game) cb.connect('clicked', self.__set_game_select, game)
hbox.pack_start(cb, False, False, 0) hbox.pack_start(cb, False, False, 0)
cb.set_active(True)
def createLimitLine(self, hbox, limit, ltext): def createLimitLine(self, hbox, limit, ltext):
cb = gtk.CheckButton(str(ltext)) cb = gtk.CheckButton(str(ltext))
@ -292,18 +340,18 @@ class Filters(threading.Thread):
def __set_site_select(self, w, site): def __set_site_select(self, w, site):
#print w.get_active() #print w.get_active()
self.sites[site] = 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): def __set_game_select(self, w, game):
#print w.get_active() #print w.get_active()
self.games[game] = 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): def __set_limit_select(self, w, limit):
#print w.get_active() #print w.get_active()
self.limits[limit] = w.get_active() self.limits[limit] = w.get_active()
print "self.limit[%s] set to %s" %(limit, self.limits[limit]) log.debug("self.limit[%s] set to %s" %(limit, self.limits[limit]))
if limit.isdigit() or (len(limit) > 2 and limit[-2:] == 'nl'): 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.limits[limit]:
if self.cbNoLimits is not None: if self.cbNoLimits is not None:
self.cbNoLimits.set_active(False) self.cbNoLimits.set_active(False)
@ -314,9 +362,12 @@ class Filters(threading.Thread):
if limit.isdigit(): if limit.isdigit():
if self.cbFL is not None: if self.cbFL is not None:
self.cbFL.set_active(False) self.cbFL.set_active(False)
else: elif (len(limit) > 2 and (limit[-2:] == 'nl')):
if self.cbNL is not None: if self.cbNL is not None:
self.cbNL.set_active(False) self.cbNL.set_active(False)
else:
if self.cbPL is not None:
self.cbPL.set_active(False)
elif limit == "all": elif limit == "all":
if self.limits[limit]: if self.limits[limit]:
#for cb in self.cbLimits.values(): #for cb in self.cbLimits.values():
@ -325,6 +376,8 @@ class Filters(threading.Thread):
self.cbFL.set_active(True) self.cbFL.set_active(True)
if self.cbNL is not None: if self.cbNL is not None:
self.cbNL.set_active(True) self.cbNL.set_active(True)
if self.cbPL is not None:
self.cbPL.set_active(True)
elif limit == "none": elif limit == "none":
if self.limits[limit]: if self.limits[limit]:
for cb in self.cbLimits.values(): for cb in self.cbLimits.values():
@ -333,6 +386,8 @@ class Filters(threading.Thread):
self.cbNL.set_active(False) self.cbNL.set_active(False)
if self.cbFL is not None: if self.cbFL is not None:
self.cbFL.set_active(False) self.cbFL.set_active(False)
if self.cbPL is not None:
self.cbPL.set_active(False)
elif limit == "fl": elif limit == "fl":
if not self.limits[limit]: if not self.limits[limit]:
# only toggle all fl limits off if they are all currently on # only toggle all fl limits off if they are all currently on
@ -381,11 +436,39 @@ class Filters(threading.Thread):
if self.limits[limit]: if self.limits[limit]:
if not found[self.type]: if not found[self.type]:
if self.type == 'ring': 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': 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": elif limit == "ring":
print "set", limit, "to", self.limits[limit] log.debug("set", limit, "to", self.limits[limit])
if self.limits[limit]: if self.limits[limit]:
self.type = "ring" self.type = "ring"
for cb in self.cbLimits.values(): for cb in self.cbLimits.values():
@ -393,7 +476,7 @@ class Filters(threading.Thread):
if self.types[cb.get_children()[0].get_text()] == 'tour': if self.types[cb.get_children()[0].get_text()] == 'tour':
cb.set_active(False) cb.set_active(False)
elif limit == "tour": elif limit == "tour":
print "set", limit, "to", self.limits[limit] log.debug( "set", limit, "to", self.limits[limit] )
if self.limits[limit]: if self.limits[limit]:
self.type = "tour" self.type = "tour"
for cb in self.cbLimits.values(): for cb in self.cbLimits.values():
@ -404,24 +487,38 @@ class Filters(threading.Thread):
def __set_seat_select(self, w, seat): def __set_seat_select(self, w, seat):
#print "__set_seat_select: seat =", seat, "active =", w.get_active() #print "__set_seat_select: seat =", seat, "active =", w.get_active()
self.seats[seat] = 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): def __set_group_select(self, w, group):
#print "__set_seat_select: seat =", seat, "active =", w.get_active() #print "__set_seat_select: seat =", seat, "active =", w.get_active()
self.groups[group] = 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): 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(): for site in self.conf.get_supported_sites():
hBox = gtk.HBox(False, 0) 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 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: if "GroupsAll" in display and display["GroupsAll"] == True:
hbox = gtk.HBox(False, 0) 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 = gtk.CheckButton(self.filterText['groupsall'])
cb.connect('clicked', self.__set_group_select, 'allplayers') cb.connect('clicked', self.__set_group_select, 'allplayers')
hbox.pack_start(cb, False, False, 0) hbox.pack_start(cb, False, False, 0)
@ -437,30 +534,64 @@ class Filters(threading.Thread):
phands.set_width_chars(8) phands.set_width_chars(8)
hbox.pack_start(phands, False, False, 0) hbox.pack_start(phands, False, False, 0)
phands.connect("changed", self.__set_num_hands, site) phands.connect("changed", self.__set_num_hands, site)
top_hbox.pack_start(showb, expand=False, padding=1)
def fillSitesFrame(self, vbox): 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(): for site in self.conf.get_supported_sites():
hbox = gtk.HBox(False, 0) hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0) vbox1.pack_start(hbox, False, True, 0)
self.createSiteLine(hbox, site) self.createSiteLine(hbox, site)
#Get db site id for filtering later #Get db site id for filtering later
self.cursor.execute(self.sql.query['getSiteId'], (site,)) #self.cursor.execute(self.sql.query['getSiteId'], (site,))
result = self.db.cursor.fetchall() #result = self.db.cursor.fetchall()
if len(result) == 1: #if len(result) == 1:
self.siteid[site] = result[0][0] # self.siteid[site] = result[0][0]
else: #else:
print "Either 0 or more than one site matched - EEK" # print "Either 0 or more than one site matched - EEK"
def fillGamesFrame(self, vbox): 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']) self.cursor.execute(self.sql.query['getGames'])
result = self.db.cursor.fetchall() result = self.db.cursor.fetchall()
if len(result) >= 1: if len(result) >= 1:
for line in result: for line in result:
hbox = gtk.HBox(False, 0) hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0) vbox1.pack_start(hbox, False, True, 0)
self.createGameLine(hbox, line[0]) self.createGameLine(hbox, line[0])
else: else:
print "INFO: No games returned from database" print "INFO: No games returned from database"
log.info("No games returned from database")
def fillLimitsFrame(self, vbox, display): def fillLimitsFrame(self, vbox, display):
top_hbox = gtk.HBox(False, 0) top_hbox = gtk.HBox(False, 0)
@ -476,10 +607,10 @@ class Filters(threading.Thread):
vbox.pack_start(vbox1, False, False, 0) vbox.pack_start(vbox1, False, False, 0)
self.boxes['limits'] = vbox1 self.boxes['limits'] = vbox1
self.cursor.execute(self.sql.query['getLimits2']) self.cursor.execute(self.sql.query['getLimits3'])
# selects limitType, bigBlind # selects limitType, bigBlind
result = self.db.cursor.fetchall() 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: if len(result) >= 1:
hbox = gtk.HBox(True, 0) hbox = gtk.HBox(True, 0)
@ -497,14 +628,18 @@ class Filters(threading.Thread):
vbox2.pack_start(hbox, False, False, 0) vbox2.pack_start(hbox, False, False, 0)
else: else:
vbox3.pack_start(hbox, False, False, 0) vbox3.pack_start(hbox, False, False, 0)
if line[1] == 'fl': if True: #line[0] == 'ring':
name = str(line[2]) if line[1] == 'fl':
found['fl'] = True name = str(line[2])
else: found['fl'] = True
name = str(line[2])+line[1] elif line[1] == 'pl':
found['nl'] = True name = str(line[2])+line[1]
self.cbLimits[name] = self.createLimitLine(hbox, name, name) found['pl'] = True
self.types[name] = line[0] 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 found[line[0]] = True # type is ring/tour
self.type = line[0] # if only one type, set it now self.type = line[0] # if only one type, set it now
if "LimitSep" in display and display["LimitSep"] == True and len(result) >= 2: if "LimitSep" in display and display["LimitSep"] == True and len(result) >= 2:
@ -532,9 +667,13 @@ class Filters(threading.Thread):
hbox = gtk.HBox(False, 0) hbox = gtk.HBox(False, 0)
vbox3.pack_start(hbox, False, False, 0) vbox3.pack_start(hbox, False, False, 0)
self.cbNL = self.createLimitLine(hbox, 'nl', self.filterText['limitsNL']) 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 dest = vbox2 # for ring/tour buttons
else: else:
print "INFO: No games returned from database" 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']: if "Type" in display and display["Type"] == True and found['ring'] and found['tour']:
rb1 = gtk.RadioButton(None, self.filterText['ring']) rb1 = gtk.RadioButton(None, self.filterText['ring'])
@ -645,8 +784,22 @@ class Filters(threading.Thread):
def fillDateFrame(self, vbox): def fillDateFrame(self, vbox):
# Hat tip to Mika Bostrom - calendar code comes from PokerStats # 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() hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0) vbox1.pack_start(hbox, False, True, 0)
lbl_start = gtk.Label('From:') lbl_start = gtk.Label('From:')
@ -660,7 +813,7 @@ class Filters(threading.Thread):
#New row for end date #New row for end date
hbox = gtk.HBox() hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0) vbox1.pack_start(hbox, False, True, 0)
lbl_end = gtk.Label(' To:') lbl_end = gtk.Label(' To:')
btn_end = gtk.Button() btn_end = gtk.Button()
@ -676,10 +829,13 @@ class Filters(threading.Thread):
hbox.pack_start(btn_clear, expand=False, padding=15) 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): def __toggle_box(self, widget, entry):
if "Limits" not in self.display or self.display["Limits"] == False: if self.boxes[entry].props.visible:
self.boxes[entry].hide()
elif self.boxes[entry].props.visible:
self.boxes[entry].hide() self.boxes[entry].hide()
widget.set_label("show") widget.set_label("show")
else: else:
@ -740,10 +896,10 @@ def main(argv=None):
config = Configuration.Config() config = Configuration.Config()
db = None db = None
db = fpdb_db.fpdb_db() db = Database.Database()
db.do_connect(config) db.do_connect(config)
qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name()) qdict = SQL.SQL(db.get_backend_name())
i = Filters(db, config, qdict) i = Filters(db, config, qdict)
main_window = gtk.Window() main_window = gtk.Window()

View File

@ -18,12 +18,10 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
######################################################################## ########################################################################
import sys
import logging import logging
from HandHistoryConverter import * from HandHistoryConverter import *
# Fulltilt HH Format converter # Fulltilt HH Format converter
# TODO: cat tourno and table to make table name for tournaments
class Fulltilt(HandHistoryConverter): class Fulltilt(HandHistoryConverter):
@ -67,8 +65,8 @@ class Fulltilt(HandHistoryConverter):
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+)) (\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
''', re.VERBOSE) ''', re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) 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_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_TourneyPlayerInfo = 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>.{2,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
#static regex for tourney purpose #static regex for tourney purpose
@ -130,6 +128,7 @@ class Fulltilt(HandHistoryConverter):
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re) logging.debug("player_re: " + player_re)
self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostDead = re.compile(r"^%s posts a dead small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_Antes = re.compile(r"^%s antes \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE) self.re_Antes = re.compile(r"^%s antes \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
@ -139,7 +138,7 @@ class Fulltilt(HandHistoryConverter):
self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$?(?P<POT>[.,\d]+)\)(, mucked| with.*)" % 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_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): def readSupportedGames(self):
return [["ring", "hold", "nl"], return [["ring", "hold", "nl"],
@ -191,7 +190,6 @@ class Fulltilt(HandHistoryConverter):
if mg['TOURNO'] is None: info['type'] = "ring" if mg['TOURNO'] is None: info['type'] = "ring"
else: info['type'] = "tour" else: info['type'] = "tour"
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # 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 return info
def readHandInfo(self, hand): def readHandInfo(self, hand):
@ -258,15 +256,16 @@ class Fulltilt(HandHistoryConverter):
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this) #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')), #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'))) ##int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
#FIXME: hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand): 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" : if hand.gametype['type'] == "ring" :
m = self.re_PlayerInfo.finditer(hand.handText) m = self.re_PlayerInfo.finditer(pre)
else: #if hand.gametype['type'] == "tour" else: #if hand.gametype['type'] == "tour"
m = self.re_TourneyPlayerInfo.finditer(hand.handText) m = self.re_TourneyPlayerInfo.finditer(pre)
players = []
for a in m: for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
@ -300,6 +299,8 @@ class Fulltilt(HandHistoryConverter):
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind except: # no small blind
hand.addBlind(None, None, None) hand.addBlind(None, None, None)
for a in self.re_PostDead.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
for a in self.re_PostBB.finditer(hand.handText): for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText): for a in self.re_PostBoth.finditer(hand.handText):
@ -392,9 +393,10 @@ class Fulltilt(HandHistoryConverter):
def readShownCards(self,hand): def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText): for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None: if m.group('CARDS') is not None:
cards = m.group('CARDS') if m.group('ACT'):
cards = cards.split(' ') hand.addShownCards(cards=m.group('CARDS').split(' '), player=m.group('PNAME'), shown = False, mucked = True)
hand.addShownCards(cards=cards, player=m.group('PNAME')) else:
hand.addShownCards(cards=m.group('CARDS').split(' '), player=m.group('PNAME'), shown = True, mucked = False)
def guessMaxSeats(self, hand): def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH.""" """Return a guess at max_seats when not specified in HH."""
@ -422,7 +424,6 @@ class Fulltilt(HandHistoryConverter):
hand.mixed = self.mixes[m.groupdict()['MIXED']] hand.mixed = self.mixes[m.groupdict()['MIXED']]
def readSummaryInfo(self, summaryInfoList): def readSummaryInfo(self, summaryInfoList):
starttime = time.time()
self.status = True self.status = True
m = re.search("Tournament Summary", summaryInfoList[0]) m = re.search("Tournament Summary", summaryInfoList[0])
@ -542,14 +543,14 @@ class Fulltilt(HandHistoryConverter):
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
else : else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin: 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'])) tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
if mg['FEE'] is not None: if mg['FEE'] is not None:
if tourney.fee is None: if tourney.fee is None:
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
else : else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee: 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'])) tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
if tourney.buyin is None: if tourney.buyin is None:

View File

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

View File

@ -30,7 +30,6 @@ import gtk
import gobject import gobject
# fpdb/FreePokerTools modules # fpdb/FreePokerTools modules
import fpdb_simple
import fpdb_import import fpdb_import
import Configuration import Configuration
import Exceptions import Exceptions
@ -90,7 +89,8 @@ class GuiBulkImport():
for selection in selected: for selection in selected:
self.importer.addBulkImportImportFileOrDir(selection, site = sitename) 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() starttime = time()
# try: # try:
(stored, dups, partial, errs, ttime) = self.importer.runImport() (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'\ 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) % (stored, dups, partial, errs, ttime, (stored+0.0) / ttime)
self.importer.clearFileList() 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: if self.n_hands_in_db == 0 and stored > 0:
self.cb_dropindexes.set_sensitive(True) self.cb_dropindexes.set_sensitive(True)
self.cb_dropindexes.set_active(0) self.cb_dropindexes.set_active(0)
@ -229,6 +232,10 @@ class GuiBulkImport():
ypadding=0, yoptions=gtk.SHRINK) ypadding=0, yoptions=gtk.SHRINK)
self.cb_dropindexes.show() 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 # label - filter
self.lab_filter = gtk.Label("Site filter:") self.lab_filter = gtk.Label("Site filter:")
self.table.attach(self.lab_filter, 1, 2, 2, 3, xpadding=0, ypadding=0, self.table.attach(self.lab_filter, 1, 2, 2, 3, xpadding=0, ypadding=0,
@ -239,7 +246,17 @@ class GuiBulkImport():
# ComboBox - filter # ComboBox - filter
self.cbfilter = gtk.combo_box_new_text() self.cbfilter = gtk.combo_box_new_text()
disabled_sites = [] # move disabled sites to bottom of list
for w in self.config.hhcs: 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 print w
self.cbfilter.append_text(w) self.cbfilter.append_text(w)
self.cbfilter.set_active(0) self.cbfilter.set_active(0)

298
pyfpdb/GuiDatabase.py Executable file
View File

@ -0,0 +1,298 @@
#!/usr/bin/python
# -*- 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 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
import traceback
import Queue
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import pango
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("maintdbs")
import Exceptions
import Database
class GuiDatabase:
COL_DBMS = 0
COL_NAME = 1
COL_DESC = 2
COL_USER = 3
COL_PASS = 4
COL_HOST = 5
COL_ICON = 6
def __init__(self, config, mainwin, dia):
self.config = config
self.main_window = mainwin
self.dia = dia
try:
#self.dia.set_modal(True)
self.vbox = self.dia.vbox
#gtk.Widget.set_size_request(self.vbox, 700, 400);
# list of databases in self.config.supported_databases:
self.liststore = gtk.ListStore(str, str, str, str
,str, str, str, str) #object, gtk.gdk.Pixbuf)
# dbms, name, comment, user, pass, ip, status(, icon?)
# this is how to add a filter:
#
# # Creation of the filter, from the model
# filter = self.liststore.filter_new()
# filter.set_visible_column(1)
#
# # The TreeView gets the filter as model
# self.listview = gtk.TreeView(filter)
self.listview = gtk.TreeView(model=self.liststore)
self.listview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_NONE)
self.listcols = []
scrolledwindow = gtk.ScrolledWindow()
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolledwindow.add(self.listview)
self.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0)
refreshbutton = gtk.Button("Refresh")
refreshbutton.connect("clicked", self.refresh, None)
self.vbox.pack_start(refreshbutton, False, False, 3)
refreshbutton.show()
col = self.addTextColumn("Type", 0, False)
col = self.addTextColumn("Name", 1, False)
col = self.addTextColumn("Description", 2, True)
col = self.addTextColumn("Username", 3, True)
col = self.addTextColumn("Password", 4, True)
col = self.addTextColumn("Host", 5, True)
col = self.addTextObjColumn("", 6)
self.loadDbs()
self.dia.connect('response', self.dialog_response_cb)
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print 'guidbmaint: '+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
def dialog_response_cb(self, dialog, response_id):
# this is called whether close button is pressed or window is closed
dialog.destroy()
def get_dialog(self):
return self.dia
def addTextColumn(self, title, n, editable=False):
col = gtk.TreeViewColumn(title)
self.listview.append_column(col)
cRender = gtk.CellRendererText()
cRender.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
cRender.set_property('editable', editable)
cRender.connect('edited', self.edited_cb, (self.liststore,n))
col.pack_start(cRender, True)
col.add_attribute(cRender, 'text', n)
col.set_max_width(1000)
col.set_spacing(0) # no effect
self.listcols.append(col)
col.set_clickable(True)
col.connect("clicked", self.sortCols, n)
return(col)
def edited_cb(self, cell, path, new_text, user_data):
liststore, col = user_data
valid = True
name = self.liststore[path][self.COL_NAME]
# Validate new value (only for dbms so far, but dbms now not updateable so no validation at all!)
#if col == self.COL_DBMS:
# if new_text not in Configuration.DATABASE_TYPES:
# valid = False
if valid:
self.liststore[path][col] = new_text
self.config.set_db_parameters( db_server = self.liststore[path][self.COL_DBMS]
, db_name = name
, db_ip = self.liststore[path][self.COL_HOST]
, db_user = self.liststore[path][self.COL_USER]
, db_pass = self.liststore[path][self.COL_PASS] )
return
def check_new_name(self, path, new_text):
name_ok = True
for i,db in enumerate(self.liststore):
if i != path and new_text == db[self.COL_NAME]:
name_ok = False
#TODO: popup an error message telling user names must be unique
return name_ok
def addTextObjColumn(self, title, n):
col = gtk.TreeViewColumn(title)
self.listview.append_column(col)
cRenderT = gtk.CellRendererText()
cRenderT.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
col.pack_start(cRenderT, False)
col.add_attribute(cRenderT, 'text', n)
cRenderP = gtk.CellRendererPixbuf()
col.pack_start(cRenderP, False)
col.add_attribute(cRenderP, 'stock-id', n+1)
col.set_max_width(1000)
col.set_spacing(0) # no effect
self.listcols.append(col)
#col.set_clickable(True)
#col.connect("clicked", self.sortCols, p)
return(col)
def loadDbs(self):
self.liststore.clear()
self.listcols = []
self.dbs = [] # list of tuples: (dbms, name, comment, user, passwd, host, status, icon)
try:
# want to fill: dbms, name, comment, user, passwd, host, status(, icon?)
for name in self.config.supported_databases: #db_ip/db_user/db_pass/db_server
dbms = self.config.supported_databases[name].db_server # mysql/postgresql/sqlite
dbms_num = self.config.get_backend(dbms) # 2 / 3 / 4
comment = ""
if dbms == 'sqlite':
user = ""
passwd = ""
else:
user = self.config.supported_databases[name].db_user
passwd = self.config.supported_databases[name].db_pass
host = self.config.supported_databases[name].db_ip
status = ""
icon = None
err_msg = ""
db = Database.Database(self.config, sql = None, autoconnect = False)
# try to connect to db, set status and err_msg if it fails
try:
# is creating empty db for sqlite ... mod db.py further?
# add noDbTables flag to db.py?
db.connect(backend=dbms_num, host=host, database=name, user=user, password=passwd, create=False)
if db.connected:
status = 'ok'
icon = gtk.STOCK_APPLY
if db.wrongDbVersion:
status = 'old'
icon = gtk.STOCK_INFO
except Exceptions.FpdbMySQLAccessDenied:
err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?"
status = "failed"
icon = gtk.STOCK_CANCEL
except Exceptions.FpdbMySQLNoDatabase:
err_msg = "MySQL client reports: 2002 or 2003 error. Unable to connect - " \
+ "Please check that the MySQL service has been started"
status = "failed"
icon = gtk.STOCK_CANCEL
except Exceptions.FpdbPostgresqlAccessDenied:
err_msg = "Postgres Server reports: Access denied. Are your permissions set correctly?"
status = "failed"
except Exceptions.FpdbPostgresqlNoDatabase:
err_msg = "Postgres client reports: Unable to connect - " \
+ "Please check that the Postgres service has been started"
status = "failed"
icon = gtk.STOCK_CANCEL
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
log.info( 'db connection to '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: '
+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) )
status = "failed"
icon = gtk.STOCK_CANCEL
b = gtk.Button(name)
b.show()
iter = self.liststore.append( (dbms, name, comment, user, passwd, host, status, icon) )
self.listview.show()
scrolledwindow.show()
self.vbox.show()
self.dia.set_focus(self.listview)
self.vbox.show_all()
self.dia.show()
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print 'loaddbs error: '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' \
+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
def sortCols(self, col, n):
try:
if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING:
col.set_sort_order(gtk.SORT_DESCENDING)
else:
col.set_sort_order(gtk.SORT_ASCENDING)
self.liststore.set_sort_column_id(n, col.get_sort_order())
#self.liststore.set_sort_func(n, self.sortnums, (n,grid))
for i in xrange(len(self.listcols)):
self.listcols[i].set_sort_indicator(False)
self.listcols[n].set_sort_indicator(True)
# use this listcols[col].set_sort_indicator(True)
# to turn indicator off for other cols
except:
err = traceback.extract_tb(sys.exc_info()[2])
print "***sortCols error: " + str(sys.exc_info()[1])
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
def refresh(self, widget, data):
self.loadDbs()
if __name__=="__main__":
config = Configuration.Config()
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
win.set_title("Test Log Viewer")
win.set_border_width(1)
win.set_default_size(600, 500)
win.set_resizable(True)
dia = gtk.Dialog("Log Viewer",
win,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
dia.set_default_size(500, 500)
log = GuiLogView(config, win, dia.vbox)
response = dia.run()
if response == gtk.RESPONSE_ACCEPT:
pass
dia.destroy()

View File

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

View File

@ -26,13 +26,18 @@ import gtk
import gobject import gobject
import pango 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 MAX_LINES = 100000 # max lines to display in window
EST_CHARS_PER_LINE = 150 # used to guesstimate number of lines in log file EST_CHARS_PER_LINE = 150 # used to guesstimate number of lines in log file
logfile = 'logging.out' # name of logfile LOGFILES = [ [ 'Fpdb Errors', 'fpdb-errors.txt', False ] # label, filename, start value
, [ 'Fpdb Log', 'fpdb-log.txt', True ]
, [ 'HUD Errors', 'HUD-errors.txt', False ]
, [ 'HUD Log', 'HUD-log.txt', False ]
]
class GuiLogView: class GuiLogView:
@ -41,6 +46,7 @@ class GuiLogView:
self.main_window = mainwin self.main_window = mainwin
self.closeq = closeq self.closeq = closeq
self.logfile = os.path.join(self.config.dir_log, LOGFILES[1][1])
self.dia = gtk.Dialog(title="Log Messages" self.dia = gtk.Dialog(title="Log Messages"
,parent=None ,parent=None
,flags=gtk.DIALOG_DESTROY_WITH_PARENT ,flags=gtk.DIALOG_DESTROY_WITH_PARENT
@ -68,10 +74,19 @@ class GuiLogView:
scrolledwindow.add(self.listview) scrolledwindow.add(self.listview)
self.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0) self.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0)
hb = gtk.HBox(False, 0)
grp = None
for logf in LOGFILES:
rb = gtk.RadioButton(group=grp, label=logf[0], use_underline=True)
if grp is None: grp = rb
rb.set_active(logf[2])
rb.connect('clicked', self.__set_logfile, logf[0])
hb.pack_start(rb, False, False, 3)
refreshbutton = gtk.Button("Refresh") refreshbutton = gtk.Button("Refresh")
refreshbutton.connect("clicked", self.refresh, None) refreshbutton.connect("clicked", self.refresh, None)
self.vbox.pack_start(refreshbutton, False, False, 3) hb.pack_start(refreshbutton, False, False, 3)
refreshbutton.show() refreshbutton.show()
self.vbox.pack_start(hb, False, False, 0)
self.listview.show() self.listview.show()
scrolledwindow.show() scrolledwindow.show()
@ -89,6 +104,14 @@ class GuiLogView:
self.dia.connect('response', self.dialog_response_cb) self.dia.connect('response', self.dialog_response_cb)
def __set_logfile(self, w, file):
#print "w is", w, "file is", file, "active is", w.get_active()
if w.get_active():
for logf in LOGFILES:
if logf[0] == file:
self.logfile = os.path.join(self.config.dir_log, logf[1])
self.refresh(w, file) # params are not used
def dialog_response_cb(self, dialog, response_id): def dialog_response_cb(self, dialog, response_id):
# this is called whether close button is pressed or window is closed # this is called whether close button is pressed or window is closed
self.closeq.put(self.__class__) self.closeq.put(self.__class__)
@ -117,10 +140,10 @@ class GuiLogView:
self.listcols = [] self.listcols = []
# guesstimate number of lines in file # guesstimate number of lines in file
if os.path.exists(logfile): if os.path.exists(self.logfile):
stat_info = os.stat(logfile) stat_info = os.stat(self.logfile)
lines = stat_info.st_size / EST_CHARS_PER_LINE 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 # set startline to line number to start display from
startline = 0 startline = 0
@ -129,12 +152,15 @@ class GuiLogView:
startline = lines - MAX_LINES startline = lines - MAX_LINES
l = 0 l = 0
for line in open(logfile): for line in open(self.logfile):
# eg line: # example line in logfile format:
# 2009-12-02 15:23:21,716 - config DEBUG config logger initialised # 2009-12-02 15:23:21,716 - config DEBUG config logger initialised
l = l + 1 l = l + 1
if l > startline and len(line) > 49: if l > startline:
iter = self.liststore.append( (line[0:23], line[26:32], line[39:46], line[48:].strip(), True) ) if len(line) > 49 and line[23:26] == ' - ' and line[34:39] == ' ':
iter = self.liststore.append( (line[0:23], line[26:32], line[39:46], line[48:].strip(), True) )
else:
iter = self.liststore.append( ('', '', '', line.strip(), True) )
def sortCols(self, col, n): def sortCols(self, col, n):
try: try:

View File

@ -27,8 +27,8 @@ from time import time, strftime
import Card import Card
import fpdb_import import fpdb_import
import Database import Database
import fpdb_db
import Filters import Filters
import Charset
colalias,colshow,colheading,colxalign,colformat,coltype = 0,1,2,3,4,5 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} 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}
@ -64,7 +64,7 @@ class GuiPlayerStats (threading.Thread):
filters_display = { "Heroes" : True, filters_display = { "Heroes" : True,
"Sites" : True, "Sites" : True,
"Games" : False, "Games" : True,
"Limits" : True, "Limits" : True,
"LimitSep" : True, "LimitSep" : True,
"LimitType" : True, "LimitType" : True,
@ -92,7 +92,7 @@ class GuiPlayerStats (threading.Thread):
, ["hand", False, "Hand", 0.0, "%s", "str"] # true not allowed for this line , ["hand", False, "Hand", 0.0, "%s", "str"] # true not allowed for this line
, ["plposition", False, "Posn", 1.0, "%s", "str"] # true not allowed for this line (set in code) , ["plposition", False, "Posn", 1.0, "%s", "str"] # true not allowed for this line (set in code)
, ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code) , ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code)
, ["n", True, "Hds", 1.0, "%d", "str"] , ["n", True, "Hds", 1.0, "%1.0f", "str"]
, ["avgseats", False, "Seats", 1.0, "%3.1f", "str"] , ["avgseats", False, "Seats", 1.0, "%3.1f", "str"]
, ["vpip", True, "VPIP", 1.0, "%3.1f", "str"] , ["vpip", True, "VPIP", 1.0, "%3.1f", "str"]
, ["pfr", True, "PFR", 1.0, "%3.1f", "str"] , ["pfr", True, "PFR", 1.0, "%3.1f", "str"]
@ -142,7 +142,7 @@ class GuiPlayerStats (threading.Thread):
self.stats_frame = gtk.Frame() self.stats_frame = gtk.Frame()
self.stats_frame.show() self.stats_frame.show()
self.stats_vbox = gtk.VBox(False, 0) self.stats_vbox = gtk.VPaned()
self.stats_vbox.show() self.stats_vbox.show()
self.stats_frame.add(self.stats_vbox) self.stats_frame.add(self.stats_vbox)
# self.fillStatsFrame(self.stats_vbox) # self.fillStatsFrame(self.stats_vbox)
@ -155,12 +155,15 @@ class GuiPlayerStats (threading.Thread):
# make sure Hand column is not displayed # make sure Hand column is not displayed
[x for x in self.columns if x[0] == 'hand'][0][1] = False [x for x in self.columns if x[0] == 'hand'][0][1] = False
self.last_pos = -1
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.main_hbox return self.main_hbox
def refreshStats(self, widget, data): def refreshStats(self, widget, data):
self.last_pos = self.stats_vbox.get_position()
try: self.stats_vbox.destroy() try: self.stats_vbox.destroy()
except AttributeError: pass except AttributeError: pass
self.liststore = [] self.liststore = []
@ -170,6 +173,8 @@ class GuiPlayerStats (threading.Thread):
self.stats_vbox.show() self.stats_vbox.show()
self.stats_frame.add(self.stats_vbox) self.stats_frame.add(self.stats_vbox)
self.fillStatsFrame(self.stats_vbox) self.fillStatsFrame(self.stats_vbox)
if self.last_pos > 0:
self.stats_vbox.set_position(self.last_pos)
def fillStatsFrame(self, vbox): def fillStatsFrame(self, vbox):
sites = self.filters.getSites() sites = self.filters.getSites()
@ -180,6 +185,7 @@ class GuiPlayerStats (threading.Thread):
seats = self.filters.getSeats() seats = self.filters.getSeats()
groups = self.filters.getGroups() groups = self.filters.getGroups()
dates = self.filters.getDates() dates = self.filters.getDates()
games = self.filters.getGames()
sitenos = [] sitenos = []
playerids = [] playerids = []
@ -187,12 +193,10 @@ class GuiPlayerStats (threading.Thread):
for site in sites: for site in sites:
if sites[site] == True: if sites[site] == True:
sitenos.append(siteids[site]) sitenos.append(siteids[site])
# Nasty hack to deal with multiple sites + same player name -Eric _hname = Charset.to_utf8(heroes[site])
que = self.sql.query['getPlayerId'] + " AND siteId=%d" % siteids[site] result = self.db.get_player_id(self.conf, site, _hname)
self.cursor.execute(que, (heroes[site],)) if result is not None:
result = self.db.cursor.fetchall() playerids.append(int(result))
if len(result) == 1:
playerids.append(result[0][0])
if not sitenos: if not sitenos:
#Should probably pop up here. #Should probably pop up here.
@ -205,10 +209,11 @@ class GuiPlayerStats (threading.Thread):
print "No limits found" print "No limits found"
return 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() starttime = time()
show_detail = True
# Scrolled window for summary table # Scrolled window for summary table
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None) swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
@ -223,27 +228,32 @@ class GuiPlayerStats (threading.Thread):
# gridnum - index for grid data structures # gridnum - index for grid data structures
flags = [False, self.filters.getNumHands(), 0] flags = [False, self.filters.getNumHands(), 0]
self.addGrid(swin, 'playerDetailedStats', flags, playerids self.addGrid(swin, 'playerDetailedStats', flags, playerids
,sitenos, limits, type, seats, groups, dates) ,sitenos, limits, type, seats, groups, dates, games)
# Separator if 'allplayers' in groups and groups['allplayers']:
vbox2 = gtk.VBox(False, 0) # can't currently do this combination so skip detailed table
heading = gtk.Label(self.filterText['handhead']) show_detail = False
heading.show()
vbox2.pack_start(heading, expand=False, padding=3)
# Scrolled window for detailed table (display by hand) if show_detail:
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None) # Separator
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) vbox2 = gtk.VBox(False, 0)
swin.show() heading = gtk.Label(self.filterText['handhead'])
vbox2.pack_start(swin, expand=True, padding=3) heading.show()
vbox.pack2(vbox2) vbox2.pack_start(heading, expand=False, padding=3)
vbox2.show()
# Detailed table # Scrolled window for detailed table (display by hand)
flags[0] = True swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
flags[2] = 1 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.addGrid(swin, 'playerDetailedStats', flags, playerids swin.show()
,sitenos, limits, type, seats, groups, dates) vbox2.pack_start(swin, expand=True, padding=3)
vbox.pack2(vbox2)
vbox2.show()
# Detailed table
flags[0] = True
flags[2] = 1
self.addGrid(swin, 'playerDetailedStats', flags, playerids
,sitenos, limits, type, seats, groups, dates, games)
self.db.rollback() self.db.rollback()
print "Stats page displayed in %4.2f seconds" % (time() - starttime) print "Stats page displayed in %4.2f seconds" % (time() - starttime)
@ -275,7 +285,7 @@ class GuiPlayerStats (threading.Thread):
except: a = 0.0 except: a = 0.0
try: b = float(b) try: b = float(b)
except: b = 0.0 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] a1,a2,a3 = ranks[a[0]], ranks[a[1]], (a+'o')[2]
b1,b2,b3 = ranks[b[0]], ranks[b[1]], (b+'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) ) ): if a1 > b1 or ( a1 == b1 and (a2 > b2 or (a2 == b2 and a3 > b3) ) ):
@ -317,7 +327,7 @@ class GuiPlayerStats (threading.Thread):
print "***sortcols error: " + str(sys.exc_info()[1]) print "***sortcols error: " + str(sys.exc_info()[1])
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] ) 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 counter = 0
row = 0 row = 0
sqlrow = 0 sqlrow = 0
@ -325,7 +335,7 @@ class GuiPlayerStats (threading.Thread):
else: holecards,grid = flags[0],flags[2] else: holecards,grid = flags[0],flags[2]
tmp = self.sql.query[query] 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) self.cursor.execute(tmp)
result = self.cursor.fetchall() result = self.cursor.fetchall()
colnames = [desc[0].lower() for desc in self.cursor.description] colnames = [desc[0].lower() for desc in self.cursor.description]
@ -422,13 +432,14 @@ class GuiPlayerStats (threading.Thread):
else: else:
treerow.append(' ') treerow.append(' ')
iter = self.liststore[grid].append(treerow) iter = self.liststore[grid].append(treerow)
#print treerow
sqlrow += 1 sqlrow += 1
row += 1 row += 1
vbox.show_all() vbox.show_all()
#end def addGrid(self, query, vars, playerids, sitenos, limits, type, seats, groups, dates): #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 = '' having = ''
if not flags: if not flags:
holecards = False holecards = False
@ -466,6 +477,39 @@ class GuiPlayerStats (threading.Thread):
query = query.replace("<playerName>", pname) query = query.replace("<playerName>", pname)
query = query.replace("<havingclause>", having) 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)
sitetest = ""
q = []
for m in self.filters.display.items():
if m[0] == 'Sites' and m[1]:
for n in sitenos:
q.append(n)
if len(q) > 0:
sitetest = str(tuple(q))
sitetest = sitetest.replace("L", "")
sitetest = sitetest.replace(",)",")")
sitetest = sitetest.replace("u'","'")
sitetest = "and gt.siteId in %s" % sitetest
else:
sitetest = "and gt.siteId IS NULL"
query = query.replace("<site_test>", sitetest)
if seats: if seats:
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to'])) query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
if 'show' in seats and seats['show']: if 'show' in seats and seats['show']:
@ -510,14 +554,14 @@ class GuiPlayerStats (threading.Thread):
if type == 'ring': if type == 'ring':
bbtest = bbtest + " and gt.type = 'ring' " bbtest = bbtest + " and gt.type = 'ring' "
elif type == 'tour': elif type == 'tour':
bbtest = bbtest + " and gt.type = 'tour' " bbtest = " and gt.type = 'tour' "
query = query.replace("<gtbigBlind_test>", bbtest) query = query.replace("<gtbigBlind_test>", bbtest)
if holecards: # re-use level variables for hole card query if holecards: # re-use level variables for hole card query
query = query.replace("<hgameTypeId>", "hp.startcards") query = query.replace("<hgameTypeId>", "hp.startcards")
query = query.replace("<orderbyhgameTypeId>" query = query.replace("<orderbyhgameTypeId>"
, ",case when floor(hp.startcards/13) >= mod(hp.startcards,13) then hp.startcards + 0.1 " , ",case when floor((hp.startcards-1)/13) >= mod((hp.startcards-1),13) then hp.startcards + 0.1 "
+ " else 13*mod(hp.startcards,13) + floor(hp.startcards/13) " + " else 13*mod((hp.startcards-1),13) + floor((hp.startcards-1)/13) + 1 "
+ " end desc ") + " end desc ")
else: else:
query = query.replace("<orderbyhgameTypeId>", "") query = query.replace("<orderbyhgameTypeId>", "")

View File

@ -33,7 +33,7 @@ try:
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
from matplotlib.finance import candlestick2 from matplotlib.finance import candlestick2
from numpy import diff, nonzero, sum, cumsum, max, min from numpy import diff, nonzero, sum, cumsum, max, min, append
# from matplotlib.dates import DateFormatter, WeekdayLocator, HourLocator, \ # from matplotlib.dates import DateFormatter, WeekdayLocator, HourLocator, \
# DayLocator, MONDAY, timezone # DayLocator, MONDAY, timezone
@ -47,6 +47,7 @@ import fpdb_import
import Database import Database
import Filters import Filters
import FpdbSQLQueries import FpdbSQLQueries
import Charset
class GuiSessionViewer (threading.Thread): class GuiSessionViewer (threading.Thread):
def __init__(self, config, querylist, mainwin, debug=True): def __init__(self, config, querylist, mainwin, debug=True):
@ -181,7 +182,10 @@ class GuiSessionViewer (threading.Thread):
for site in sites: for site in sites:
if sites[site] == True: if sites[site] == True:
sitenos.append(siteids[site]) 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() result = self.db.cursor.fetchall()
if len(result) == 1: if len(result) == 1:
playerids.append(result[0][0]) playerids.append(result[0][0])
@ -237,6 +241,9 @@ class GuiSessionViewer (threading.Thread):
#end def fillStatsFrame(self, vbox): #end def fillStatsFrame(self, vbox):
def generateDatasets(self, playerids, sitenos, limits, seats): def generateDatasets(self, playerids, sitenos, limits, seats):
THRESHOLD = 1800 # Minimum number of seconds between consecutive hands before being considered a new session
PADDING = 5 # Additional time in minutes to add to a session, session startup, shutdown etc (FiXME: user configurable)
# Get a list of all handids and their timestampts # Get a list of all handids and their timestampts
#FIXME: Query still need to filter on blind levels #FIXME: Query still need to filter on blind levels
@ -251,23 +258,31 @@ class GuiSessionViewer (threading.Thread):
q = q.replace("<ampersand_s>", "%s") q = q.replace("<ampersand_s>", "%s")
self.db.cursor.execute(q) self.db.cursor.execute(q)
THRESHOLD = 1800
hands = self.db.cursor.fetchall() hands = self.db.cursor.fetchall()
# Take that list and create an array of the time between hands # Take that list and create an array of the time between hands
times = map(lambda x:long(x[0]), hands) times = map(lambda x:long(x[0]), hands)
handids = map(lambda x:int(x[1]), hands) handids = map(lambda x:int(x[1]), hands)
winnings = map(lambda x:float(x[4]), hands) winnings = map(lambda x:float(x[4]), hands)
print "DEBUG: len(times) %s" %(len(times)) #print "DEBUG: len(times) %s" %(len(times))
diffs = diff(times) # This array is the difference in starttime between consecutive hands diffs = diff(times) # This array is the difference in starttime between consecutive hands
index = nonzero(diff(times) > THRESHOLD) # This array represents the indexes into 'times' for start/end times of sessions diffs2 = append(diffs,THRESHOLD + 1) # Append an additional session to the end of the diffs, so the next line
# ie. times[index[0][0]] is the end of the first session # includes an index into the last 'session'
index = nonzero(diffs2 > THRESHOLD) # This array represents the indexes into 'times' for start/end times of sessions
# times[index[0][0]] is the end of the first session,
#print "DEBUG: len(index[0]) %s" %(len(index[0])) #print "DEBUG: len(index[0]) %s" %(len(index[0]))
#print "DEBUG: index %s" %(index) if len(index[0]) > 0:
#print "DEBUG: index[0][0] %s" %(index[0][0]) #print "DEBUG: index[0][0] %s" %(index[0][0])
#print "DEBUG: index %s" %(index)
pass
else:
index = [[0]]
#print "DEBUG: index %s" %(index)
#print "DEBUG: index[0][0] %s" %(index[0][0])
pass
total = 0 total = 0
last_idx = 0 first_idx = 0
lowidx = 0 lowidx = 0
uppidx = 0 uppidx = 0
opens = [] opens = []
@ -277,27 +292,36 @@ class GuiSessionViewer (threading.Thread):
results = [] results = []
cum_sum = cumsum(winnings) cum_sum = cumsum(winnings)
cum_sum = cum_sum/100 cum_sum = cum_sum/100
sid = 1
# Take all results and format them into a list for feeding into gui model. # Take all results and format them into a list for feeding into gui model.
for i in range(len(index[0])): for i in range(len(index[0])):
sid = i # Session id hds = index[0][i] - first_idx + 1 # Number of hands in session
hds = index[0][i] - last_idx # Number of hands in session
if hds > 0: if hds > 0:
stime = strftime("%d/%m/%Y %H:%M", localtime(times[last_idx])) # Formatted start time stime = strftime("%d/%m/%Y %H:%M", localtime(times[first_idx])) # Formatted start time
etime = strftime("%d/%m/%Y %H:%M", localtime(times[index[0][i]])) # Formatted end time etime = strftime("%d/%m/%Y %H:%M", localtime(times[index[0][i]])) # Formatted end time
hph = (times[index[0][i]] - times[last_idx])/60 # Hands per hour minutesplayed = (times[index[0][i]] - times[first_idx])/60
won = sum(winnings[last_idx:index[0][i]])/100.0 if minutesplayed == 0:
hwm = max(cum_sum[last_idx:index[0][i]]) minutesplayed = 1
lwm = min(cum_sum[last_idx:index[0][i]]) minutesplayed = minutesplayed + PADDING
#print "DEBUG: range: (%s, %s) - (min, max): (%s, %s)" %(last_idx, index[0][i], hwm, lwm) hph = hds*60/minutesplayed # Hands per hour
won = sum(winnings[first_idx:index[0][i]])/100.0
hwm = max(cum_sum[first_idx:index[0][i]])
lwm = min(cum_sum[first_idx:index[0][i]])
open = (sum(winnings[:first_idx]))/100
close = (sum(winnings[:index[0][i]]))/100
#print "DEBUG: range: (%s, %s) - (min, max): (%s, %s) - (open,close): (%s, %s)" %(first_idx, index[0][i], lwm, hwm, open, close)
results.append([sid, hds, stime, etime, hph, won]) results.append([sid, hds, stime, etime, hph, won])
opens.append((sum(winnings[:last_idx]))/100) opens.append(open)
closes.append((sum(winnings[:index[0][i]]))/100) closes.append(close)
highs.append(hwm) highs.append(hwm)
lows.append(lwm) lows.append(lwm)
#print "Hands in session %4s: %4s Start: %s End: %s HPH: %s Profit: %s" %(sid, hds, stime, etime, hph, won) #print "DEBUG: Hands in session %4s: %4s Start: %s End: %s HPH: %s Profit: %s" %(sid, hds, stime, etime, hph, won)
total = total + (index[0][i] - last_idx) total = total + (index[0][i] - first_idx)
last_idx = index[0][i] + 1 first_idx = index[0][i] + 1
sid = sid+1
else:
print "hds <= 0"
return (results, opens, closes, highs, lows) return (results, opens, closes, highs, lows)
@ -326,11 +350,6 @@ class GuiSessionViewer (threading.Thread):
def generateGraph(self, opens, closes, highs, lows): def generateGraph(self, opens, closes, highs, lows):
self.clearGraphData() self.clearGraphData()
#FIXME: Weird - first data entry is crashing this for me
opens = opens[1:]
closes = closes[1:]
highs = highs[1:]
lows = lows[1:]
# print "DEBUG:" # print "DEBUG:"
# print "highs = %s" % highs # print "highs = %s" % highs
# print "lows = %s" % lows # print "lows = %s" % lows

View File

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

View File

@ -445,62 +445,101 @@ Left-Drag to Move"
<location seat="9" x="70" y="53"> </location> <location seat="9" x="70" y="53"> </location>
</layout> </layout>
</site> </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_sites>
<supported_games> <supported_games>
<game aux="mucked" cols="3" db="fpdb" game_name="holdem" rows="3">
<game cols="3" db="fpdb" game_name="holdem" rows="2" aux="mucked"> <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="0" popup="default" row="0" stat_name="vpip" 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="1" popup="default" row="0" stat_name="pfr" 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="2" popup="default" row="0" stat_name="ffreq1" tip="tip1"> </stat>
<stat click="tog_decorate" col="0" popup="default" row="1" stat_name="n" 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" popup="default" row="1" stat_name="wtsd" 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="wmsd" 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>
<game cols="3" db="fpdb" game_name="razz" rows="2" aux="stud_mucked"> <game aux="stud_mucked" cols="2" db="fpdb" game_name="razz" rows="3">
<stat click="tog_decorate" col="0" popup="default" row="0" stat_name="vpip" tip="tip1"> </stat> <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="pfr" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="0" stat_name="ffreq1" 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="0" popup="default" row="1" stat_name="n" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="wtsd" 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="2" popup="default" row="1" stat_name="wmsd" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game> </game>
<game cols="3" db="fpdb" game_name="omahahi" rows="2" aux="mucked"> <game aux="mucked" cols="2" db="fpdb" game_name="omahahi" rows="3">
<stat click="tog_decorate" col="0" popup="default" row="0" stat_name="vpip" tip="tip1"> </stat> <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="pfr" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="0" stat_name="ffreq1" 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="0" popup="default" row="1" stat_name="n" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="wtsd" 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="2" popup="default" row="1" stat_name="wmsd" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game> </game>
<game cols="3" db="fpdb" game_name="omahahilo" rows="2" aux="mucked"> <game aux="mucked" cols="2" db="fpdb" game_name="omahahilo" rows="3">
<stat click="tog_decorate" col="0" popup="default" row="0" stat_name="vpip" tip="tip1"> </stat> <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="pfr" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="0" stat_name="ffreq1" 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="0" popup="default" row="1" stat_name="n" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="wtsd" 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="2" popup="default" row="1" stat_name="wmsd" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game> </game>
<game cols="3" db="fpdb" game_name="studhi" rows="2" aux="stud_mucked"> <game aux="stud_mucked" cols="2" db="fpdb" game_name="studhi" rows="3">
<stat click="tog_decorate" col="0" popup="default" row="0" stat_name="vpip" tip="tip1"> </stat> <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="pfr" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="0" stat_name="ffreq1" 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="0" popup="default" row="1" stat_name="n" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="wtsd" 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="2" popup="default" row="1" stat_name="wmsd" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game> </game>
<game cols="3" db="fpdb" game_name="studhilo" rows="2" aux="stud_mucked"> <game aux="stud_mucked" cols="2" db="fpdb" game_name="studhilo" rows="3">
<stat click="tog_decorate" col="0" popup="default" row="0" stat_name="vpip" tip="tip1"> </stat> <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="pfr" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="0" stat_name="n" tip="tip1"> </stat>
<stat click="tog_decorate" col="2" popup="default" row="0" stat_name="ffreq1" 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="0" popup="default" row="1" stat_name="n" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="1" stat_name="pfr" tip="tip1"> </stat>
<stat click="tog_decorate" col="1" popup="default" row="1" stat_name="wtsd" 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="2" popup="default" row="1" stat_name="wmsd" tip="tip1"> </stat> <stat click="tog_decorate" col="1" popup="default" row="2" stat_name="ffreq1" tip="tip1"> </stat>
</game> </game>
</supported_games> </supported_games>
@ -583,11 +622,12 @@ Left-Drag to Move"
<hhc site="PartyPoker" converter="PartyPokerToFpdb"/> <hhc site="PartyPoker" converter="PartyPokerToFpdb"/>
<hhc site="Betfair" converter="BetfairToFpdb"/> <hhc site="Betfair" converter="BetfairToFpdb"/>
<hhc site="Partouche" converter="PartoucheToFpdb"/> <hhc site="Partouche" converter="PartoucheToFpdb"/>
<hhc site="Carbon" converter="CarbonToFpdb"/>
</hhcs> </hhcs>
<supported_databases> <supported_databases>
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database> <!-- <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_ip="localhost" db_server="sqlite" db_name="fpdb.db3" db_user="fpdb" db_pass="fpdb"/>
</supported_databases> </supported_databases>
</FreePokerToolsConfig> </FreePokerToolsConfig>

View File

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

View File

@ -21,19 +21,24 @@
import re import re
import sys import sys
import traceback import traceback
import logging
import os import os
import os.path import os.path
from decimal import Decimal from decimal import Decimal
import operator import operator
import time,datetime import time,datetime
from copy import deepcopy from copy import deepcopy
from Exceptions import *
import pprint 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 DerivedStats
import Card import Card
log = logging.getLogger("parser")
class Hand(object): class Hand(object):
@ -43,10 +48,12 @@ class Hand(object):
LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'}
SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''} SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'} 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.sitename = sitename
self.siteId = self.SITEIDS[sitename] self.siteId = self.SITEIDS[sitename]
self.stats = DerivedStats.DerivedStats(self) self.stats = DerivedStats.DerivedStats(self)
@ -118,6 +125,7 @@ class Hand(object):
# currency symbol for this hand # currency symbol for this hand
self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
self.pot.setSym(self.sym) self.pot.setSym(self.sym)
self.is_duplicate = False # i.e. don't update hudcache if true
def __str__(self): def __str__(self):
vars = ( ("BB", self.bb), vars = ( ("BB", self.bb),
@ -192,7 +200,7 @@ dealt whether they were seen in a 'dealt to' line
self.holecards[street][player] = [open, closed] self.holecards[street][player] = [open, closed]
def prepInsert(self, db): def prepInsert(self, db):
##### #####
# Players, Gametypes, TourneyTypes are all shared functions that are needed for additional tables # Players, Gametypes, TourneyTypes are all shared functions that are needed for additional tables
# These functions are intended for prep insert eventually # These functions are intended for prep insert eventually
##### #####
@ -205,7 +213,7 @@ dealt whether they were seen in a 'dealt to' line
def insert(self, db): def insert(self, db):
""" Function to insert Hand into database """ Function to insert Hand into database
Should not commit, and do minimal selects. Callers may want to cache commits 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) self.stats.getStats(self)
@ -229,8 +237,11 @@ db: a connected fpdb_db object"""
# TourneysPlayers # TourneysPlayers
else: else:
log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo']) log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo'])
#Raise Duplicate exception? self.is_duplicate = True # i.e. don't update hudcache
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): def select(self, handId):
""" Function to create Hand object from database """ """ Function to create Hand object from database """
@ -286,6 +297,24 @@ If a player has None chips he won't be added."""
c = c.replace(k,v) c = c.replace(k,v)
return c 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): def addAnte(self, player, ante):
log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante)) log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
if player is not None: if player is not None:
@ -294,7 +323,9 @@ If a player has None chips he won't be added."""
self.stacks[player] -= Decimal(ante) self.stacks[player] -= Decimal(ante)
act = (player, 'posts', "ante", ante, self.stacks[player]==0) act = (player, 'posts', "ante", ante, self.stacks[player]==0)
self.actions['BLINDSANTES'].append(act) self.actions['BLINDSANTES'].append(act)
self.pot.addMoney(player, Decimal(ante)) # self.pot.addMoney(player, Decimal(ante))
self.pot.addCommonMoney(player, Decimal(ante))
#I think the antes should be common money, don't have enough hand history to check
def addBlind(self, player, blindtype, amount): def addBlind(self, player, blindtype, amount):
# if player is None, it's a missing small blind. # if player is None, it's a missing small blind.
@ -313,9 +344,16 @@ If a player has None chips he won't be added."""
self.actions['BLINDSANTES'].append(act) self.actions['BLINDSANTES'].append(act)
if blindtype == 'both': if blindtype == 'both':
# work with the real ammount. limit games are listed as $1, $2, where
# the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2....
amount = self.bb amount = self.bb
self.bets['BLINDSANTES'][player].append(Decimal(self.sb)) self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb)) self.pot.addCommonMoney(player, Decimal(self.sb))
if blindtype == 'secondsb':
amount = Decimal(0)
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(player, Decimal(self.sb))
self.bets['PREFLOP'][player].append(Decimal(amount)) self.bets['PREFLOP'][player].append(Decimal(amount))
self.pot.addMoney(player, Decimal(amount)) self.pot.addMoney(player, Decimal(amount))
@ -393,7 +431,7 @@ Add a raise on [street] by [player] to [amountTo]
Bc = reduce(operator.add, self.bets[street][player], 0) Bc = reduce(operator.add, self.bets[street][player], 0)
Rt = Decimal(amountTo) Rt = Decimal(amountTo)
C = Bp - Bc C = Bp - Bc
Rb = Rt - C Rb = Rt - C - Bc
self._addRaise(street, player, C, Rb, Rt) self._addRaise(street, player, C, Rb, Rt)
def _addRaise(self, street, player, C, Rb, Rt): def _addRaise(self, street, player, C, Rb, Rt):
@ -479,7 +517,6 @@ Card ranks will be uppercased
for entry in self.collected: for entry in self.collected:
self.totalcollected += Decimal(entry[1]) self.totalcollected += Decimal(entry[1])
def getGameTypeAsString(self): def getGameTypeAsString(self):
"""\ """\
Map the tuple self.gametype onto the pokerstars string describing it Map the tuple self.gametype onto the pokerstars string describing it
@ -595,7 +632,8 @@ Map the tuple self.gametype onto the pokerstars string describing it
class HoldemOmahaHand(Hand): 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': if gametype['base'] != 'hold':
pass # or indeed don't pass and complain instead pass # or indeed don't pass and complain instead
log.debug("HoldemOmahaHand") log.debug("HoldemOmahaHand")
@ -603,7 +641,7 @@ class HoldemOmahaHand(Hand):
self.holeStreets = ['PREFLOP'] self.holeStreets = ['PREFLOP']
self.communityStreets = ['FLOP', 'TURN', 'RIVER'] self.communityStreets = ['FLOP', 'TURN', 'RIVER']
self.actionStreets = ['BLINDSANTES','PREFLOP','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.sb = gametype['sb']
self.bb = gametype['bb'] self.bb = gametype['bb']
@ -612,6 +650,8 @@ class HoldemOmahaHand(Hand):
# which then invokes a 'addXXX' callback # which then invokes a 'addXXX' callback
if builtFrom == "HHC": if builtFrom == "HHC":
hhc.readHandInfo(self) hhc.readHandInfo(self)
if self.gametype['type'] == 'tour':
self.tablename = "%s %s" % (self.tourNo, self.tablename)
hhc.readPlayerStacks(self) hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(self) hhc.compilePlayerRegexs(self)
hhc.markStreets(self) hhc.markStreets(self)
@ -637,6 +677,7 @@ class HoldemOmahaHand(Hand):
if self.maxseats is None: if self.maxseats is None:
self.maxseats = hhc.guessMaxSeats(self) self.maxseats = hhc.guessMaxSeats(self)
hhc.readOther(self) hhc.readOther(self)
#print "\nHand:\n"+str(self)
elif builtFrom == "DB": elif builtFrom == "DB":
if handid is not None: if handid is not None:
self.select(handid) # Will need a handId self.select(handid) # Will need a handId
@ -676,7 +717,6 @@ class HoldemOmahaHand(Hand):
def join_holecards(self, player, asList=False): 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""" """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'] hcs = [u'0x', u'0x', u'0x', u'0x']
for street in self.holeStreets: for street in self.holeStreets:
@ -893,7 +933,8 @@ class HoldemOmahaHand(Hand):
print >>fh, "\n\n" print >>fh, "\n\n"
class DrawHand(Hand): 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': if gametype['base'] != 'draw':
pass # or indeed don't pass and complain instead pass # or indeed don't pass and complain instead
self.streetList = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE'] self.streetList = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
@ -901,12 +942,14 @@ class DrawHand(Hand):
self.holeStreets = ['DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE'] self.holeStreets = ['DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.actionStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE'] self.actionStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
self.communityStreets = [] self.communityStreets = []
Hand.__init__(self, sitename, gametype, handText) Hand.__init__(self, self.config, sitename, gametype, handText)
self.sb = gametype['sb'] self.sb = gametype['sb']
self.bb = gametype['bb'] self.bb = gametype['bb']
# Populate the draw hand. # Populate the draw hand.
if builtFrom == "HHC": if builtFrom == "HHC":
hhc.readHandInfo(self) hhc.readHandInfo(self)
if self.gametype['type'] == 'tour':
self.tablename = "%s %s" % (self.tourNo, self.tablename)
hhc.readPlayerStacks(self) hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(self) hhc.compilePlayerRegexs(self)
hhc.markStreets(self) hhc.markStreets(self)
@ -952,11 +995,12 @@ class DrawHand(Hand):
self.lastBet['DEAL'] = Decimal(amount) self.lastBet['DEAL'] = Decimal(amount)
elif blindtype == 'both': elif blindtype == 'both':
# extra small blind is 'dead' # extra small blind is 'dead'
self.lastBet['DEAL'] = Decimal(self.bb) amount = Decimal(amount)/3
amount += amount
self.lastBet['DEAL'] = Decimal(amount)
self.posted = self.posted + [[player,blindtype]] self.posted = self.posted + [[player,blindtype]]
#print "DEBUG: self.posted: %s" %(self.posted) #print "DEBUG: self.posted: %s" %(self.posted)
def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False): def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False):
if player == self.hero: # we have hero's cards just update shown/mucked if player == self.hero: # we have hero's cards just update shown/mucked
if shown: self.shown.add(player) if shown: self.shown.add(player)
@ -1083,7 +1127,8 @@ class DrawHand(Hand):
class StudHand(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': if gametype['base'] != 'stud':
pass # or indeed don't pass and complain instead pass # or indeed don't pass and complain instead
@ -1093,7 +1138,7 @@ class StudHand(Hand):
self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] 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.sb = gametype['sb']
self.bb = gametype['bb'] self.bb = gametype['bb']
#Populate the StudHand #Populate the StudHand
@ -1101,6 +1146,8 @@ class StudHand(Hand):
# which then invokes a 'addXXX' callback # which then invokes a 'addXXX' callback
if builtFrom == "HHC": if builtFrom == "HHC":
hhc.readHandInfo(self) hhc.readHandInfo(self)
if self.gametype['type'] == 'tour':
self.tablename = "%s %s" % (self.tourNo, self.tablename)
hhc.readPlayerStacks(self) hhc.readPlayerStacks(self)
hhc.compilePlayerRegexs(self) hhc.compilePlayerRegexs(self)
hhc.markStreets(self) hhc.markStreets(self)
@ -1368,7 +1415,7 @@ class Pot(object):
self.contenders = set() self.contenders = set()
self.committed = {} self.committed = {}
self.streettotals = {} self.streettotals = {}
self.common = Decimal(0) self.common = {}
self.total = None self.total = None
self.returned = {} self.returned = {}
self.sym = u'$' # this is the default currency symbol self.sym = u'$' # this is the default currency symbol
@ -1378,13 +1425,14 @@ class Pot(object):
def addPlayer(self,player): def addPlayer(self,player):
self.committed[player] = Decimal(0) self.committed[player] = Decimal(0)
self.common[player] = Decimal(0)
def addFold(self, player): def addFold(self, player):
# addFold must be called when a player folds # addFold must be called when a player folds
self.contenders.discard(player) self.contenders.discard(player)
def addCommonMoney(self, amount): def addCommonMoney(self, player, amount):
self.common += amount self.common[player] += amount
def addMoney(self, player, amount): def addMoney(self, player, amount):
# addMoney must be called for any actions that put money in the pot, in the order they occur # addMoney must be called for any actions that put money in the pot, in the order they occur
@ -1392,7 +1440,7 @@ class Pot(object):
self.committed[player] += amount self.committed[player] += amount
def markTotal(self, street): def markTotal(self, street):
self.streettotals[street] = sum(self.committed.values()) + self.common self.streettotals[street] = sum(self.committed.values()) + sum(self.common.values())
def getTotalAtStreet(self, street): def getTotalAtStreet(self, street):
if street in self.streettotals: if street in self.streettotals:
@ -1400,12 +1448,15 @@ class Pot(object):
return 0 return 0
def end(self): def end(self):
self.total = sum(self.committed.values()) + self.common self.total = sum(self.committed.values()) + sum(self.common.values())
# Return any uncalled bet. # Return any uncalled bet.
committed = sorted([ (v,k) for (k,v) in self.committed.items()]) committed = sorted([ (v,k) for (k,v) in self.committed.items()])
if len(committed)<2: #print "DEBUG: committed: %s" % committed
raise FpdbParseError("length of committed array is too small") #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] lastbet = committed[-1][0] - committed[-2][0]
if lastbet > 0: # uncalled if lastbet > 0: # uncalled
returnto = committed[-1][1] returnto = committed[-1][1]
@ -1486,7 +1537,8 @@ limit 1""", {'handid':handid})
#TODO: siteid should be in hands table - we took the scenic route through players here. #TODO: siteid should be in hands table - we took the scenic route through players here.
res = c.fetchone() 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]} 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] ) cards = map(Card.valueSuitFromCard, res[11:16] )
if cards[0]: if cards[0]:
h.setCommunityCards('FLOP', cards[0:3]) h.setCommunityCards('FLOP', cards[0:3])
@ -1589,4 +1641,3 @@ ORDER BY
return h return h

View File

@ -16,8 +16,6 @@
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
import Hand
import Tourney
import re import re
import sys import sys
import traceback import traceback
@ -31,13 +29,20 @@ import operator
from xml.dom.minidom import Node from xml.dom.minidom import Node
import time import time
import datetime 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 from Exceptions import FpdbParseError
import Configuration import Configuration
import gettext import gettext
gettext.install('fpdb') gettext.install('fpdb')
log = Configuration.get_logger("logging.conf")
import pygtk import pygtk
import gtk import gtk
@ -57,16 +62,20 @@ class HandHistoryConverter():
codepage = "cp1252" 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, ftpArchive=False):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
follow : whether to tail -f the input""" follow : whether to tail -f the input"""
self.config = config
self.import_parameters = self.config.get_import_parameters()
#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) ) 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.starsArchive = starsArchive
self.ftpArchive = ftpArchive
self.in_path = in_path self.in_path = in_path
self.out_path = out_path self.out_path = out_path
@ -80,28 +89,7 @@ follow : whether to tail -f the input"""
if in_path == '-': if in_path == '-':
self.in_fh = sys.stdin self.in_fh = sys.stdin
self.out_fh = get_out_fh(out_path, self.import_parameters)
if out_path == '-':
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.follow = follow self.follow = follow
self.compiledPlayers = set() self.compiledPlayers = set()
@ -260,6 +248,11 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
m = re.compile('^Hand #\d+', re.MULTILINE) m = re.compile('^Hand #\d+', re.MULTILINE)
self.obs = m.sub('', self.obs) self.obs = m.sub('', self.obs)
if self.ftpArchive == True:
log.debug("Converting ftpArchive format to readable")
m = re.compile('^\*\*\*\*\*\*+\s#\s\d+\s\*\*\*\*\*+$', re.MULTILINE)
self.obs = m.sub('', self.obs)
if self.obs is None or self.obs == "": if self.obs is None or self.obs == "":
log.info("Read no hands.") log.info("Read no hands.")
return [] return []
@ -283,17 +276,16 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
if l in self.readSupportedGames(): if l in self.readSupportedGames():
if gametype['base'] == 'hold': if gametype['base'] == 'hold':
log.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)") 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': 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': elif gametype['base'] == 'draw':
hand = Hand.DrawHand(self, self.sitename, gametype, handText) hand = Hand.DrawHand(self.config, self, self.sitename, gametype, handText)
else: else:
log.info("Unsupported game type: %s" % gametype) log.info("Unsupported game type: %s" % gametype)
if hand: if hand:
if Configuration.NEWIMPORT == False: #hand.writeHand(self.out_fh)
hand.writeHand(self.out_fh)
return hand return hand
else: else:
log.info("Unsupported game type: %s" % gametype) log.info("Unsupported game type: %s" % gametype)
@ -418,7 +410,7 @@ or None if we fail to get the info """
list.pop() #Last entry is empty list.pop() #Last entry is empty
for l in list: for l in list:
# print "'" + l + "'" # 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.. ? # TODO: This looks like it could be replaced with a list comp.. ?
return hands return hands
@ -441,10 +433,9 @@ or None if we fail to get the info """
#print "trying", kodec #print "trying", kodec
try: try:
in_fh = codecs.open(self.in_path, 'r', kodec) in_fh = codecs.open(self.in_path, 'r', kodec)
in_fh.seek(self.index) whole_file = in_fh.read()
log.debug("Opened in_path: '%s' with %s" % (self.in_path, kodec)) self.obs = whole_file[self.index:]
self.obs = in_fh.read() self.index = len(whole_file)
self.index = in_fh.tell()
in_fh.close() in_fh.close()
break break
except: except:
@ -458,8 +449,8 @@ or None if we fail to get the info """
def guessMaxSeats(self, hand): def guessMaxSeats(self, hand):
"""Return a guess at maxseats when not specified in HH.""" """Return a guess at maxseats when not specified in HH."""
# if some other code prior to this has already set it, return it # if some other code prior to this has already set it, return it
if maxseats > 1 and maxseats < 11: if self.maxseats > 1 and self.maxseats < 11:
return maxseats return self.maxseats
mo = self.maxOccSeat(hand) mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy if mo == 10: return 10 #that was easy
@ -512,7 +503,7 @@ or None if we fail to get the info """
def getTableTitleRe(type, table_name=None, tournament = None, table_number=None): def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
"Returns string to search in windows titles" "Returns string to search in windows titles"
if type=="tour": if type=="tour":
return "%s.+Table\s%s" % (tournament, table_number) return "%s.+Table.+%s" % (tournament, table_number)
else: else:
return table_name return table_name
@ -528,5 +519,22 @@ def getSiteHhc(config, sitename):
hhcModule = __import__(hhcName) hhcModule = __import__(hhcName)
return getattr(hhcModule, hhcName[:-6]) return getattr(hhcModule, hhcName[:-6])
def get_out_fh(out_path, parameters):
if out_path == '-':
return(sys.stdout)
elif parameters['saveStarsHH']:
out_dir = os.path.dirname(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
else:
log.info("Created directory '%s'" % out_dir)
try:
return(codecs.open(out_path, 'w', 'utf8'))
except:
log.error("out_path %s couldn't be opened" % (out_path))
else:
return(sys.stdout)

View File

@ -26,6 +26,10 @@ Create and manage the hud overlays.
import os import os
import sys 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 # pyGTK modules
import pygtk import pygtk
import gtk import gtk
@ -365,7 +369,7 @@ class Hud:
self.create(*self.creation_attrs) self.create(*self.creation_attrs)
self.update(self.hand, self.config) self.update(self.hand, self.config)
except Exception, e: except Exception, e:
print "Exception:",str(e) log.error("Exception:",str(e))
pass pass
def set_aggregation(self, widget, val): def set_aggregation(self, widget, val):
@ -377,7 +381,7 @@ class Hud:
if self.hud_params['h_agg_bb_mult'] != num \ if self.hud_params['h_agg_bb_mult'] != num \
and getattr(self, 'h_aggBBmultItem'+str(num)).get_active(): 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 self.hud_params['h_agg_bb_mult'] = num
for mult in ('1', '2', '3', '10', '10000'): for mult in ('1', '2', '3', '10', '10000'):
if mult != str(num): if mult != str(num):
@ -388,7 +392,7 @@ class Hud:
if self.hud_params['agg_bb_mult'] != num \ if self.hud_params['agg_bb_mult'] != num \
and getattr(self, 'aggBBmultItem'+str(num)).get_active(): 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 self.hud_params['agg_bb_mult'] = num
for mult in ('1', '2', '3', '10', '10000'): for mult in ('1', '2', '3', '10', '10000'):
if mult != str(num): if mult != str(num):
@ -415,7 +419,7 @@ class Hud:
self.hud_params[param] = 'E' self.hud_params[param] = 'E'
getattr(self, prefix+'seatsStyleOptionA').set_active(False) getattr(self, prefix+'seatsStyleOptionA').set_active(False)
getattr(self, prefix+'seatsStyleOptionC').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): def set_hud_style(self, widget, val):
(player_opp, style) = val (player_opp, style) = val
@ -438,7 +442,7 @@ class Hud:
self.hud_params[param] = 'T' self.hud_params[param] = 'T'
getattr(self, prefix+'hudStyleOptionA').set_active(False) getattr(self, prefix+'hudStyleOptionA').set_active(False)
getattr(self, prefix+'hudStyleOptionS').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): def update_table_position(self):
if os.name == 'nt': if os.name == 'nt':
@ -515,7 +519,7 @@ class Hud:
# ask each aux to save its layout back to the config object # ask each aux to save its layout back to the config object
[aux.save_layout() for aux in self.aux_windows] [aux.save_layout() for aux in self.aux_windows]
# save the config object back to the file # save the config object back to the file
print "saving new xml file" print "Updating config file"
self.config.save() self.config.save()
def adj_seats(self, hand, config): 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 if self.update_table_position() == False: # we got killed by finding our table was gone
return return
self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor']))
for s in self.stat_dict: for s in self.stat_dict:
try: try:
statd = self.stat_dict[s] statd = self.stat_dict[s]
except KeyError: 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" 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")
print "(btw, the key was ", s, " and statd is...", statd log.error("(btw, the key was ", s, " and statd is...", statd)
continue continue
try: try:
self.stat_windows[statd['seat']].player_id = statd['player_id'] self.stat_windows[statd['seat']].player_id = statd['player_id']
@ -629,8 +634,17 @@ class Hud:
window = self.stat_windows[statd['seat']] window = self.stat_windows[statd['seat']]
if this_stat.hudcolor != "": 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)) window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.hudcolor))
else:
window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor']))
if this_stat.stat_loth != "":
if number[0] < (float(this_stat.stat_loth)/100):
window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.stat_locolor))
if this_stat.stat_hith != "":
if number[0] > (float(this_stat.stat_hith)/100):
window.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.stat_hicolor))
window.label[r][c].set_text(statstring) 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? if statstring != "xxx": # is there a way to tell if this particular stat window is visible already, or no?
@ -666,6 +680,11 @@ class Stat_Window:
return True return True
if event.button == 1: # left button event 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
# TODO: make position saving save sizes as well? # TODO: make position saving save sizes as well?
if event.state & gtk.gdk.SHIFT_MASK: 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) 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() self.container.show_all()
def update_data(self, new_hand_id, db_connection): 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) self.mucked_list.update_data(new_hand_id, db_connection)
def update_gui(self, new_hand_id): def update_gui(self, new_hand_id):
@ -357,21 +358,24 @@ class Aux_Seats(Aux_Window):
except AttributeError: except AttributeError:
return return
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max)) 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']): for i in (range(1, self.hud.max + 1) + ['common']):
if i == 'common': if i == 'common':
(x, y) = self.params['layout'][self.hud.max].common (x, y) = self.params['layout'][self.hud.max].common
else: else:
(x, y) = loc[adj[i]] (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]) self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
def create(self): 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 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)) loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
self.m_windows = {} # windows to put the card images in 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']): for i in (range(1, self.hud.max + 1) + ['common']):
if i == 'common': if i == 'common':
(x, y) = self.params['layout'][self.hud.max].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_transient_for(self.hud.main_window)
self.m_windows[i].set_focus_on_map(False) self.m_windows[i].set_focus_on_map(False)
self.m_windows[i].connect("configure_event", self.configure_event_cb, i) 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]) self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
if self.params.has_key('opacity'): if self.params.has_key('opacity'):
self.m_windows[i].set_opacity(float(self.params['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.""" """Save new layout back to the aux element in the config file."""
new_locs = {} new_locs = {}
# print "adj =", self.adj # print "adj =", self.adj
witdh = self.hud.table.width
height = self.hud.table.height
for (i, pos) in self.positions.iteritems(): for (i, pos) in self.positions.iteritems():
if i != 'common': if i != 'common':
new_locs[self.adj[int(i)]] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y) new_locs[self.adj[int(i)]] = ((pos[0] - self.hud.table.x) * 1000 / witdh, (pos[1] - self.hud.table.y) * 1000 / height)
else: else:
new_locs[i] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y) 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) self.config.edit_aux_layout(self.params['name'], self.hud.max, locations = new_locs)
def configure_event_cb(self, widget, event, i, *args): 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 #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
import os
import sys import sys
from optparse import OptionParser from optparse import OptionParser
# http://docs.python.org/library/optparse.html
def fpdb_options(): def fpdb_options():
@ -27,7 +27,7 @@ def fpdb_options():
action="store_true", action="store_true",
help="If passed error output will go to the console rather than .") help="If passed error output will go to the console rather than .")
parser.add_option("-d", "--databaseName", parser.add_option("-d", "--databaseName",
dest="dbname", default="fpdb", dest="dbname",
help="Overrides the default database name") help="Overrides the default database name")
parser.add_option("-c", "--configFile", parser.add_option("-c", "--configFile",
dest="config", default=None, dest="config", default=None,
@ -41,6 +41,14 @@ def fpdb_options():
parser.add_option("-k", "--konverter", parser.add_option("-k", "--konverter",
dest="hhc", default="PokerStarsToFpdb", dest="hhc", default="PokerStarsToFpdb",
help="Module name for Hand History Converter") 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() (options, argv) = parser.parse_args()
return (options, argv) return (options, argv)

View File

@ -26,51 +26,43 @@ from HandHistoryConverter import *
# PartyPoker HH Format # PartyPoker HH Format
class PartyPokerParseError(FpdbParseError): class FpdbParseError(FpdbParseError):
"Usage: raise PartyPokerParseError(<msg>[, hh=<hh>][, hid=<hid>])" "Usage: raise FpdbParseError(<msg>[, hh=<hh>][, hid=<hid>])"
def __init__(self, msg='', hh=None, hid=None): def __init__(self, msg='', hh=None, hid=None):
if hh is not None: return super(FpdbParseError, self).__init__(msg, hid=hid)
msg += "\n\nHand history attached below:\n" + self.wrapHh(hh)
return super(PartyPokerParseError, self).__init__(msg, hid=hid)
def wrapHh(self, hh): def wrapHh(self, hh):
return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \ return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
{'DELIMETER': '#'*50, 'HH': hh} {'DELIMETER': '#'*50, 'HH': hh}
class PartyPoker(HandHistoryConverter): class PartyPoker(HandHistoryConverter):
############################################################
# Class Variables
sitename = "PartyPoker" sitename = "PartyPoker"
codepage = "cp1252" codepage = "cp1252"
siteId = 9 # TODO: automate; it's a class variable so shouldn't hit DB too often siteId = 9
filetype = "text" # "text" or "xml". I propose we subclass HHC to HHC_Text and HHC_XML. filetype = "text"
sym = {'USD': "\$", } sym = {'USD': "\$", }
# Static regexes # Static regexes
# $5 USD NL Texas Hold'em - Saturday, July 25, 07:53:52 EDT 2009 # $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 # 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(""" 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<LIMIT>(NL|PL|))\s*
(?P<GAME>(Texas\ Hold\'em|Omaha)) (?P<GAME>(Texas\ Hold\'em|Omaha))
\s*\-\s* \s*\-\s*
(?P<DATETIME>.+) (?P<DATETIME>.+)
""", re.VERBOSE) """, re.VERBOSE)
re_GameInfoTrny = re.compile(""" re_GameInfoTrny = re.compile("""
(?P<LIMIT>(NL|PL|))\s+ (?P<LIMIT>(NL|PL|))\s*
(?P<GAME>(Texas\ Hold\'em|Omaha))\s+ (?P<GAME>(Texas\ Hold\'em|Omaha))\s+
(?P<BUYIN>\$?[.0-9]+)\s*(?P<BUYIN_CURRENCY>USD)?\s*Buy-in\s+ (?:(?P<BUYIN>\$?[.,0-9]+)\s*(?P<BUYIN_CURRENCY>USD)?\s*Buy-in\s+)?
Trny:\s?(?P<TOURNO>\d+)\s+ Trny:\s?(?P<TOURNO>\d+)\s+
Level:\s*(?P<LEVEL>\d+)\s+ Level:\s*(?P<LEVEL>\d+)\s+
Blinds(?:-Antes)?\( ((Blinds|Stakes)(?:-Antes)?)\(
(?P<SB>[.0-9 ]+)\s* (?P<SB>[.,0-9 ]+)\s*
/(?P<BB>[.0-9 ]+) /(?P<BB>[.,0-9 ]+)
(?:\s*-\s*(?P<ANTE>[.0-9 ]+)\$?)? (?:\s*-\s*(?P<ANTE>[.,0-9 ]+)\$?)?
\) \)
\s*\-\s* \s*\-\s*
(?P<DATETIME>.+) (?P<DATETIME>.+)
@ -85,16 +77,17 @@ class PartyPoker(HandHistoryConverter):
re.VERBOSE) re.VERBOSE)
re_HandInfo = re.compile(""" re_HandInfo = re.compile("""
^Table\s+ ^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)?\s+
(?P<TTYPE>[a-zA-Z0-9 ]+)\s+
(?: \#|\(|)(?P<TABLE>\d+)\)?\s+ (?: \#|\(|)(?P<TABLE>\d+)\)?\s+
(?:[^ ]+\s+\#(?P<MTTTABLE>\d+).+)? # table number for mtt (?:[a-zA-Z0-9 ]+\s+\#(?P<MTTTABLE>\d+).+)?
(\(No\sDP\)\s)?
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct \((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
\s+Total\s+number\s+of\s+players\s+\:\s+(?P<PLYRS>\d+)/?(?P<MAX>\d+)?
""", """,
re.MULTILINE|re.VERBOSE) re.VERBOSE|re.MULTILINE|re.DOTALL)
# re_TotalPlayers = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<MAXSEATS>\d+)", re.MULTILINE) re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE)
re_SplitHands = re.compile('\x00+') re_SplitHands = re.compile('\x00+')
re_TailSplitHands = re.compile('(\x00+)') re_TailSplitHands = re.compile('(\x00+)')
lineSplitter = '\n' lineSplitter = '\n'
@ -114,7 +107,6 @@ class PartyPoker(HandHistoryConverter):
def guessMaxSeats(self, hand): def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH.""" """Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand) mo = self.maxOccSeat(hand)
if mo == 10: return mo if mo == 10: return mo
if mo == 2: return 2 if mo == 2: return 2
if mo <= 6: return 6 if mo <= 6: return 6
@ -131,21 +123,17 @@ class PartyPoker(HandHistoryConverter):
'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''} 'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''}
for key in ('CUR_SYM', 'CUR'): for key in ('CUR_SYM', 'CUR'):
subst[key] = re.escape(subst[key]) subst[key] = re.escape(subst[key])
log.debug("player_re: '%s'" % subst['PLYR'])
log.debug("CUR_SYM: '%s'" % subst['CUR_SYM'])
log.debug("CUR: '%s'" % subst['CUR'])
self.re_PostSB = re.compile( self.re_PostSB = re.compile(
r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.0-9]+) ?%(CUR)s\]\." % subst, r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.,0-9]+) ?%(CUR)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
self.re_PostBB = re.compile( self.re_PostBB = re.compile(
r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.0-9]+) ?%(CUR)s\]\." % subst, r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.,0-9]+) ?%(CUR)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
# NOTE: comma is used as a fraction part delimeter in re below
self.re_PostDead = re.compile( self.re_PostDead = re.compile(
r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst, r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
self.re_Antes = re.compile( self.re_Antes = re.compile(
r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.0-9]+) ?%(CUR)s\]" % subst, r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.,0-9]+) ?%(CUR)s\]" % subst,
re.MULTILINE) re.MULTILINE)
self.re_HeroCards = re.compile( self.re_HeroCards = re.compile(
r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst, r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst,
@ -195,8 +183,6 @@ class PartyPoker(HandHistoryConverter):
gametype dict is: gametype dict is:
{'limitType': xxx, 'base': xxx, 'category': xxx}""" {'limitType': xxx, 'base': xxx, 'category': xxx}"""
log.debug(PartyPokerParseError().wrapHh( handText ))
info = {} info = {}
m = self._getGameType(handText) m = self._getGameType(handText)
if m is None: if m is None:
@ -213,22 +199,16 @@ class PartyPoker(HandHistoryConverter):
for expectedField in ['LIMIT', 'GAME']: for expectedField in ['LIMIT', 'GAME']:
if mg[expectedField] is None: if mg[expectedField] is None:
raise PartyPokerParseError( raise FpdbParseError( "Cannot fetch field '%s'" % expectedField)
"Cannot fetch field '%s'" % expectedField,
hh = handText)
try: try:
info['limitType'] = limits[mg['LIMIT'].strip()] info['limitType'] = limits[mg['LIMIT'].strip()]
except: except:
raise PartyPokerParseError( raise FpdbParseError("Unknown limit '%s'" % mg['LIMIT'])
"Unknown limit '%s'" % mg['LIMIT'],
hh = handText)
try: try:
(info['base'], info['category']) = games[mg['GAME']] (info['base'], info['category']) = games[mg['GAME']]
except: except:
raise PartyPokerParseError( raise FpdbParseError("Unknown game type '%s'" % mg['GAME'])
"Unknown game type '%s'" % mg['GAME'],
hh = handText)
if 'TOURNO' in mg: if 'TOURNO' in mg:
info['type'] = 'tour' info['type'] = 'tour'
@ -251,23 +231,21 @@ class PartyPoker(HandHistoryConverter):
try: try:
info.update(self.re_Hid.search(hand.handText).groupdict()) info.update(self.re_Hid.search(hand.handText).groupdict())
except: except:
raise PartyPokerParseError("Cannot read HID for current hand", hh=hand.handText) raise FpdbParseError("Cannot read HID for current hand")
try: try:
info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict()) info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict())
except: except:
raise PartyPokerParseError("Cannot read Handinfo for current hand", raise FpdbParseError("Cannot read Handinfo for current hand", hid = info['HID'])
hh=hand.handText, hid = info['HID'])
try: try:
info.update(self._getGameType(hand.handText).groupdict()) info.update(self._getGameType(hand.handText).groupdict())
except: except:
raise PartyPokerParseError("Cannot read GameType for current hand", raise FpdbParseError("Cannot read GameType for current hand", hid = info['HID'])
hh=hand.handText, hid = info['HID'])
# m = self.re_TotalPlayers.search(hand.handText) m = self.re_CountedSeats.search(hand.handText)
# if m: info.update(m.groupdict()) if m: info.update(m.groupdict())
# FIXME: it's dirty hack # FIXME: it's dirty hack
@ -282,6 +260,7 @@ class PartyPoker(HandHistoryConverter):
for i,v in enumerate(self.collected): for i,v in enumerate(self.collected):
if v[0] in self.pot.returned: if v[0] in self.pot.returned:
self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]] self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
self.collectees[v[0]] -= self.pot.returned[v[0]]
return origTotalPot() return origTotalPot()
return totalPot return totalPot
instancemethod = type(hand.totalPot) instancemethod = type(hand.totalPot)
@ -294,6 +273,7 @@ class PartyPoker(HandHistoryConverter):
if key == 'DATETIME': if key == 'DATETIME':
#Saturday, July 25, 07:53:52 EDT 2009 #Saturday, July 25, 07:53:52 EDT 2009
#Thursday, July 30, 21:40:41 MSKS 2009 #Thursday, July 30, 21:40:41 MSKS 2009
#Sunday, October 25, 13:39:07 MSK 2009
m2 = re.search("\w+, (?P<M>\w+) (?P<D>\d+), (?P<H>\d+):(?P<MIN>\d+):(?P<S>\d+) (?P<TZ>[A-Z]+) (?P<Y>\d+)", info[key]) m2 = re.search("\w+, (?P<M>\w+) (?P<D>\d+), (?P<H>\d+):(?P<MIN>\d+):(?P<S>\d+) (?P<TZ>[A-Z]+) (?P<Y>\d+)", info[key])
# we cant use '%B' due to locale problems # we cant use '%B' due to locale problems
months = ['January', 'February', 'March', 'April','May', 'June', months = ['January', 'February', 'March', 'April','May', 'June',
@ -309,20 +289,34 @@ class PartyPoker(HandHistoryConverter):
hand.handid = info[key] hand.handid = info[key]
if key == 'TABLE': if key == 'TABLE':
hand.tablename = info[key] hand.tablename = info[key]
if key == 'MTTTABLE':
if info[key] != None:
hand.tablename = info[key]
hand.tourNo = info['TABLE']
if key == 'BUTTON': if key == 'BUTTON':
hand.buttonpos = info[key] hand.buttonpos = info[key]
if key == 'TOURNO': if key == 'TOURNO':
hand.tourNo = info[key] hand.tourNo = info[key]
if key == 'TABLE_ID_WRAPPER':
if info[key] == '#':
# FIXME: there is no such property in Hand class
self.isSNG = True
if key == 'BUYIN': if key == 'BUYIN':
# FIXME: it's dirty hack T_T # FIXME: it's dirty hack T_T
# code below assumes that tournament rake is equal to zero # code below assumes that tournament rake is equal to zero
cur = info[key][0] if info[key][0] not in '0123456789' else '' if info[key] == None:
hand.buyin = info[key] + '+%s0' % cur 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 == 'LEVEL': if key == 'LEVEL':
hand.level = info[key] hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != 'Real': if key == 'PLAY' and info['PLAY'] != 'Real':
# if realy there's no play money hh on party # if realy party doesn's save play money hh
hand.gametype['currency'] = 'play' hand.gametype['currency'] = 'play'
if key == 'MAX' and info[key] is not None:
hand.maxseats = int(info[key])
def readButton(self, hand): def readButton(self, hand):
m = self.re_Button.search(hand.handText) m = self.re_Button.search(hand.handText)
@ -406,8 +400,6 @@ class PartyPoker(HandHistoryConverter):
blind = smartMin(hand.bb, playersMap[bigBlindSeat][1]) blind = smartMin(hand.bb, playersMap[bigBlindSeat][1])
hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind) hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind)
def readHeroCards(self, hand): def readHeroCards(self, hand):
# we need to grab hero's cards # we need to grab hero's cards
for street in ('PREFLOP',): for street in ('PREFLOP',):
@ -418,7 +410,6 @@ class PartyPoker(HandHistoryConverter):
newcards = renderCards(found.group('NEWCARDS')) newcards = renderCards(found.group('NEWCARDS'))
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
def readAction(self, hand, street): def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
@ -453,10 +444,9 @@ class PartyPoker(HandHistoryConverter):
elif actionType == 'checks': elif actionType == 'checks':
hand.addCheck( street, playerName ) hand.addCheck( street, playerName )
else: else:
raise PartyPokerParseError( raise FpdbParseError(
"Unimplemented readAction: '%s' '%s'" % (playerName,actionType,), "Unimplemented readAction: '%s' '%s'" % (playerName,actionType,),
hid = hand.hid, hh = hand.handText ) hid = hand.hid, )
def readShowdownActions(self, hand): def readShowdownActions(self, hand):
# all action in readShownCards # all action in readShownCards
@ -475,6 +465,18 @@ class PartyPoker(HandHistoryConverter):
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=True, mucked=mucked) hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=True, mucked=mucked)
@staticmethod
def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
"Returns string to search in windows titles"
if type=="tour":
TableName = table_name.split(" ")
print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (TableName[0], table_number)
return "%s.+Table\s#%s" % (TableName[0], table_number)
else:
print 'party', 'getTableTitleRe', table_number
return table_name
def ringBlinds(ringLimit): def ringBlinds(ringLimit):
"Returns blinds for current limit in cash games" "Returns blinds for current limit in cash games"
ringLimit = float(clearMoneyString(ringLimit)) ringLimit = float(clearMoneyString(ringLimit))

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

@ -46,12 +46,12 @@ class PokerStars(HandHistoryConverter):
PokerStars\sGame\s\#(?P<HID>[0-9]+):\s+ PokerStars\sGame\s\#(?P<HID>[0-9]+):\s+
(Tournament\s\# # open paren of tournament info (Tournament\s\# # open paren of tournament info
(?P<TOURNO>\d+),\s (?P<TOURNO>\d+),\s
(?P<BUYIN>[%(LS)s\+\d\.]+ # here's how I plan to use LS # here's how I plan to use LS
\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)? (?P<BUYIN>([%(LS)s\+\d\.]+\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?)|Freeroll)\s+)?
)\s)? # close paren of tournament info # close paren of tournament info
(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(? (?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<GAME>Hold\'em|Razz|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 (?P<LIMIT>No\sLimit|Limit|LIMIT|Pot\sLimit)\)?,?\s
(-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)? (-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?
\(? # open paren of the stakes \(? # open paren of the stakes
(?P<CURRENCY>%(LS)s|)? (?P<CURRENCY>%(LS)s|)?
@ -69,7 +69,7 @@ class PokerStars(HandHistoryConverter):
re.MULTILINE|re.VERBOSE) re.MULTILINE|re.VERBOSE)
re_HandInfo = re.compile(""" 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<MAX>\d+)-max\s)?
(?P<PLAY>\(Play\sMoney\)\s)? (?P<PLAY>\(Play\sMoney\)\s)?
(Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""", (Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
@ -81,6 +81,7 @@ class PokerStars(HandHistoryConverter):
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") 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]+)') # 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): def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players]) players = set([player[1] for player in hand.players])
@ -97,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_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_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_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_HeroCards = re.compile(r"^Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % subst, re.MULTILINE)
self.re_Action = re.compile(r""" self.re_Action = re.compile(r"""
^%(PLYR)s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat) ^%(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> (\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) % subst, re.MULTILINE|re.VERBOSE)
self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %(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_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) self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s (\(.*\) )?(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
@ -133,17 +135,29 @@ class PokerStars(HandHistoryConverter):
info = {} info = {}
m = self.re_GameInfo.search(handText) m = self.re_GameInfo.search(handText)
if not m: if not m:
print "DEBUG: determineGameType(): did not match" tmp = handText[0:100]
return None log.error("determineGameType: Unable to recognise gametype from: '%s'" % tmp)
log.error("determineGameType: Raising FpdbParseError")
raise FpdbParseError
mg = m.groupdict() mg = m.groupdict()
# translations from captured groups to fpdb info strings # translations from captured groups to fpdb info strings
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' } Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'),
'0.50': ('0.10', '0.25'), '1.00': ('0.25', '0.50'), '2.00': ('0.50', '1.00'),
'2': ('0.50', '1.00'), '4': ('1.00', '2.00'), '6': ('1.00', '3.00'),
'4.00': ('1.00', '2.00'), '6.00': ('1.00', '3.00'), '10.00': ('2.00', '5.00'),
'20.00': ('5.00', '10.00'), '30.00': ('10.00', '15.00'), '60.00': ('15.00', '30.00'),
'100.00': ('25.00', '50.00'),'200.00': ('50.00', '100.00'),'400.00': ('100.00', '200.00'),
'1000.00': ('250.00', '500.00')}
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl', 'LIMIT':'fl' }
games = { # base, category games = { # base, category
"Hold'em" : ('hold','holdem'), "Hold'em" : ('hold','holdem'),
'Omaha' : ('hold','omahahi'), 'Omaha' : ('hold','omahahi'),
'Omaha Hi/Lo' : ('hold','omahahilo'), 'Omaha Hi/Lo' : ('hold','omahahilo'),
'Razz' : ('stud','razz'), 'Razz' : ('stud','razz'),
'RAZZ' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi'), '7 Card Stud' : ('stud','studhi'),
'7 Card Stud Hi/Lo' : ('stud','studhilo'), '7 Card Stud Hi/Lo' : ('stud','studhilo'),
'Badugi' : ('draw','badugi'), 'Badugi' : ('draw','badugi'),
@ -171,6 +185,15 @@ class PokerStars(HandHistoryConverter):
else: else:
info['type'] = 'tour' info['type'] = 'tour'
if info['limitType'] == 'fl' and info['bb'] is not None and info['type'] == 'ring' and info['base'] != 'stud':
try:
info['sb'] = Lim_Blinds[mg['BB']][0]
info['bb'] = Lim_Blinds[mg['BB']][1]
except KeyError:
log.error("determineGameType: Lim_Blinds has no lookup for '%s'" % mg['BB'])
log.error("determineGameType: Raising FpdbParseError")
raise FpdbParseError
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info return info
@ -194,16 +217,22 @@ class PokerStars(HandHistoryConverter):
#2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] #2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET]
#2008/08/17 - 01:14:43 (ET) #2008/08/17 - 01:14:43 (ET)
#2008/09/07 06:23:14 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]) m1 = self.re_DateTime.finditer(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')) # 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") hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
if key == 'HID': if key == 'HID':
hand.handid = info[key] hand.handid = info[key]
if key == 'TOURNO': if key == 'TOURNO':
hand.tourNo = info[key] hand.tourNo = info[key]
if key == 'BUYIN': if key == 'BUYIN':
hand.buyin = info[key] 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': if key == 'LEVEL':
hand.level = info[key] hand.level = info[key]
@ -233,7 +262,6 @@ class PokerStars(HandHistoryConverter):
def readPlayerStacks(self, hand): def readPlayerStacks(self, hand):
log.debug("readPlayerStacks") log.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText) m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m: for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
@ -280,11 +308,14 @@ class PokerStars(HandHistoryConverter):
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand): def readBlinds(self, hand):
try: liveBlind = True
m = self.re_PostSB.search(hand.handText) for a in self.re_PostSB.finditer(hand.handText):
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) if liveBlind:
except: # no small blind hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB'))
hand.addBlind(None, None, None) liveBlind = False
else:
# Post dead blinds as ante
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
for a in self.re_PostBB.finditer(hand.handText): for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText): for a in self.re_PostBoth.finditer(hand.handText):
@ -330,6 +361,7 @@ class PokerStars(HandHistoryConverter):
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
acts = action.groupdict() acts = action.groupdict()
#print "DEBUG: acts: %s" %acts
if action.group('ATYPE') == ' raises': if action.group('ATYPE') == ' raises':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' calls': elif action.group('ATYPE') == ' calls':

View File

@ -58,6 +58,27 @@ class Sql:
self.query['drop_table'] = """DROP TABLE IF EXISTS """ 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 # Create Settings
################################ ################################
@ -214,6 +235,7 @@ class Sql:
id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id),
tableName VARCHAR(22) NOT NULL, tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL, siteHandNo BIGINT NOT NULL,
tourneyId INT UNSIGNED NOT NULL,
gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
handStart DATETIME NOT NULL, handStart DATETIME NOT NULL,
importTime DATETIME NOT NULL, importTime DATETIME NOT NULL,
@ -249,6 +271,7 @@ class Sql:
id BIGSERIAL, PRIMARY KEY (id), id BIGSERIAL, PRIMARY KEY (id),
tableName VARCHAR(22) NOT NULL, tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL, siteHandNo BIGINT NOT NULL,
tourneyId INT NOT NULL,
gametypeId INT NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), gametypeId INT NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
handStart timestamp without time zone NOT NULL, handStart timestamp without time zone NOT NULL,
importTime timestamp without time zone NOT NULL, importTime timestamp without time zone NOT NULL,
@ -283,6 +306,7 @@ class Sql:
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
tableName TEXT(22) NOT NULL, tableName TEXT(22) NOT NULL,
siteHandNo INT NOT NULL, siteHandNo INT NOT NULL,
tourneyId INT NOT NULL,
gametypeId INT NOT NULL, gametypeId INT NOT NULL,
handStart REAL NOT NULL, handStart REAL NOT NULL,
importTime REAL NOT NULL, importTime REAL NOT NULL,
@ -1322,6 +1346,7 @@ class Sql:
# same as above except stats are aggregated for all blind/limit levels # same as above except stats are aggregated for all blind/limit levels
self.query['get_stats_from_hand_aggregated'] = """ self.query['get_stats_from_hand_aggregated'] = """
/* explain query plan */
SELECT hc.playerId AS player_id, SELECT hc.playerId AS player_id,
max(case when hc.gametypeId = h.gametypeId max(case when hc.gametypeId = h.gametypeId
then hp.seatNo then hp.seatNo
@ -1824,6 +1849,15 @@ class Sql:
self.query['getLimits2'] = """SELECT DISTINCT type, limitType, bigBlind self.query['getLimits2'] = """SELECT DISTINCT type, limitType, bigBlind
from Gametypes from Gametypes
ORDER by type, limitType DESC, bigBlind DESC""" 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': if db_server == 'mysql':
self.query['playerDetailedStats'] = """ self.query['playerDetailedStats'] = """
@ -1881,6 +1915,8 @@ class Sql:
inner join Sites s on (s.Id = gt.siteId) inner join Sites s on (s.Id = gt.siteId)
inner join Players p on (p.Id = hp.playerId) inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test> where hp.playerId in <player_test>
<game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/ /*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test> and h.seats <seats_test>
<flagtest> <flagtest>
@ -1964,6 +2000,8 @@ class Sql:
inner join Sites s on (s.Id = gt.siteId) inner join Sites s on (s.Id = gt.siteId)
inner join Players p on (p.Id = hp.playerId) inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test> where hp.playerId in <player_test>
<game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/ /*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test> and h.seats <seats_test>
<flagtest> <flagtest>
@ -1995,6 +2033,7 @@ class Sql:
elif db_server == 'sqlite': elif db_server == 'sqlite':
self.query['playerDetailedStats'] = """ self.query['playerDetailedStats'] = """
select <hgameTypeId> AS hgametypeid select <hgameTypeId> AS hgametypeid
,<playerName> AS pname
,gt.base ,gt.base
,gt.category AS category ,gt.category AS category
,upper(gt.limitType) AS limittype ,upper(gt.limitType) AS limittype
@ -2040,13 +2079,15 @@ class Sql:
,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr ,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr
,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr ,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr
,avg(h.seats+0.0) AS avgseats ,avg(h.seats+0.0) AS avgseats
/*,variance(hp.totalProfit/100.0) AS variance*/ ,variance(hp.totalProfit/100.0) AS variance
,0.0 AS variance
from HandsPlayers hp from HandsPlayers hp
inner join Hands h on (h.id = hp.handId) inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId) inner join Gametypes gt on (gt.Id = h.gameTypeId)
inner join Sites s on (s.Id = gt.siteId) inner join Sites s on (s.Id = gt.siteId)
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test> where hp.playerId in <player_test>
<game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/ /*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test> and h.seats <seats_test>
<flagtest> <flagtest>
@ -2060,6 +2101,7 @@ class Sql:
,plposition ,plposition
,upper(gt.limitType) ,upper(gt.limitType)
,s.name ,s.name
having 1 = 1 <havingclause>
order by hp.playerId order by hp.playerId
,gt.base ,gt.base
,gt.category ,gt.category
@ -2573,6 +2615,7 @@ class Sql:
AND h.handStart > '<startdate_test>' AND h.handStart > '<startdate_test>'
AND h.handStart < '<enddate_test>' AND h.handStart < '<enddate_test>'
<limit_test> <limit_test>
<game_test>
AND hp.tourneysPlayersId IS NULL AND hp.tourneysPlayersId IS NULL
GROUP BY h.handStart, hp.handId, hp.sawShowdown, hp.totalProfit GROUP BY h.handStart, hp.handId, hp.sawShowdown, hp.totalProfit
ORDER BY h.handStart""" ORDER BY h.handStart"""
@ -3088,6 +3131,147 @@ class Sql:
,'d' || substr(strftime('%Y%m%d', h.handStart),3,7) ,'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) self.query['get_hero_hudcache_start'] = """select min(hc.styleKey)
from HudCache hc from HudCache hc
where hc.playerId in <playerid_list> where hc.playerId in <playerid_list>
@ -3292,6 +3476,7 @@ class Sql:
tablename, tablename,
gametypeid, gametypeid,
sitehandno, sitehandno,
tourneyId,
handstart, handstart,
importtime, importtime,
seats, seats,
@ -3322,7 +3507,7 @@ class Sql:
VALUES 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)"""
self.query['store_hands_players'] = """INSERT INTO HandsPlayers ( self.query['store_hands_players'] = """INSERT INTO HandsPlayers (

View File

@ -62,9 +62,13 @@ import Database
re_Places = re.compile("_[0-9]$") re_Places = re.compile("_[0-9]$")
re_Percent = re.compile("%$") re_Percent = re.compile("%$")
# String manipulation
import codecs
encoder = codecs.lookup(Configuration.LOCALE_ENCODING)
def do_tip(widget, tip): 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'): def do_stat(stat_dict, player = 24, stat = 'vpip'):
match = re_Places.search(stat) match = re_Places.search(stat)
@ -241,8 +245,20 @@ def saw_f(stat_dict, player):
def n(stat_dict, player): def n(stat_dict, player):
""" Number of hands played.""" """ Number of hands played."""
try: 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'], return (stat_dict[player]['n'],
'%d' % (stat_dict[player]['n']), '%s' % fmt,
'n=%d' % (stat_dict[player]['n']), 'n=%d' % (stat_dict[player]['n']),
'n=%d' % (stat_dict[player]['n']), 'n=%d' % (stat_dict[player]['n']),
'(%d)' % (stat_dict[player]['n']), '(%d)' % (stat_dict[player]['n']),

View File

@ -101,7 +101,7 @@ class Table_Window(object):
self.table = int(table_number) self.table = int(table_number)
self.name = "%s - %s" % (self.tournament, self.table) self.name = "%s - %s" % (self.tournament, self.table)
elif table_name is not None: elif table_name is not None:
search_string = table_name # search_string = table_name
self.name = table_name self.name = table_name
self.tournament = None self.tournament = None
else: else:

View File

@ -39,7 +39,6 @@ if os.name == 'nt':
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
from fpdb_simple import LOCALE_ENCODING
# Each TableWindow object must have the following attributes correctly populated: # 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 # tw.name = the table name from the title bar, which must to match the table name
@ -161,7 +160,7 @@ def discover_posix_by_name(c, tablename):
def discover_posix_tournament(c, t_number, s_number): def discover_posix_tournament(c, t_number, s_number):
"""Finds the X window for a client, given tournament and table nos.""" """Finds the X window for a client, given tournament and table nos."""
search_string = "%s.+Table\s%s" % (t_number, s_number) search_string = "%s.+Table.+%s" % (t_number, s_number)
for listing in os.popen('xwininfo -root -tree').readlines(): for listing in os.popen('xwininfo -root -tree').readlines():
if re.search(search_string, listing): if re.search(search_string, listing):
return decode_xwininfo(c, listing) return decode_xwininfo(c, listing)
@ -238,7 +237,7 @@ def discover_nt_by_name(c, tablename):
try: try:
# maybe it's better to make global titles[hwnd] decoding? # 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 # 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 continue
except: except:
continue continue

View File

@ -32,12 +32,16 @@ import gtk
import gobject import gobject
# fpdb/free poker tools modules # fpdb/free poker tools modules
import Configuration
from HandHistoryConverter import getTableTitleRe
# get the correct module for the current os # get the correct module for the current os
if os.name == 'posix': if os.name == 'posix':
import XTables as Tables import XTables as Tables
elif os.name == 'nt': elif os.name == 'nt':
import WinTables as Tables import WinTables as Tables
config = Configuration.Config()
# Main function used for testing # Main function used for testing
if __name__=="__main__": if __name__=="__main__":
# c = Configuration.Config() # c = Configuration.Config()
@ -82,11 +86,16 @@ if __name__=="__main__":
(tour_no, tab_no) = table_name.split(",", 1) (tour_no, tab_no) = table_name.split(",", 1)
tour_no = tour_no.rstrip() tour_no = tour_no.rstrip()
tab_no = tab_no.rstrip() tab_no = tab_no.rstrip()
table = Tables.Table(None, tournament = tour_no, table_number = tab_no) type = "tour"
table_kwargs = dict(tournament = tour_no, table_number = tab_no)
else: # not a tournament else: # not a tournament
print "cash game" print "cash game"
table_name = table_name.rstrip() table_name = table_name.rstrip()
table = Tables.Table(None, table_name = table_name) type = "cash"
table_kwargs = dict(table_name = table_name)
search_string = getTableTitleRe(config, "Full Tilt Poker", type, **table_kwargs)
table = Tables.Table(search_string, **table_kwargs)
table.gdk_handle = gtk.gdk.window_foreign_new(table.number) table.gdk_handle = gtk.gdk.window_foreign_new(table.number)
print "table =", table print "table =", table

View File

@ -185,7 +185,7 @@ class Tourney(object):
def old_insert_from_Hand(self, db): def old_insert_from_Hand(self, db):
""" Function to insert Hand into database """ Function to insert Hand into database
Should not commit, and do minimal selects. Callers may want to cache commits 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: # TODO:
# Players - base playerid and siteid tuple # Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId) 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 # Standard Library modules
import re 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 # pyGTK modules
import pygtk import pygtk
import gtk import gtk
@ -50,28 +54,31 @@ class Table(Table_Window):
titles = {} titles = {}
win32gui.EnumWindows(win_enum_handler, titles) win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles: for hwnd in titles:
if titles[hwnd] == "": continue
# print "searching ", search_string, " in ", titles[hwnd]
if re.search(search_string, titles[hwnd]): if re.search(search_string, titles[hwnd]):
if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window
if 'HUD:' in titles[hwnd]: continue # FPDB HUD 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 '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 self.window = hwnd
break break
try: try:
if self.window == None: if self.window == None:
print "Window %s not found. Skipping." % search_string log.error( "Window %s not found. Skipping." % search_string )
return None return None
except AttributeError: except AttributeError:
print "self.window doesn't exist? why?" log.error( "self.window doesn't exist? why?" )
return None return None
(x, y, width, height) = win32gui.GetWindowRect(hwnd) (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.x = int(x) + b_width
self.y = int(y) + tb_height self.y = int(y) + tb_height
self.width = width - x self.width = width - x
self.height = height - y 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.height = int(height) - b_width - tb_height
#self.width = int(width) - 2*b_width #self.width = int(width) - 2*b_width

View File

@ -68,7 +68,7 @@ class Table(Table_Window):
window_number = None window_number = None
for listing in os.popen('xwininfo -root -tree').readlines(): for listing in os.popen('xwininfo -root -tree').readlines():
if re.search(search_string, listing): if re.search(search_string, listing):
print listing # print listing
mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing)
self.number = int( mo.group(1), 0) self.number = int( mo.group(1), 0)
self.width = int( mo.group(4) ) self.width = int( mo.group(4) )
@ -89,7 +89,6 @@ class Table(Table_Window):
# break # break
if window_number is None: if window_number is None:
print "Window %s not found. Skipping." % search_string
return None return None
# my_geo = self.window.get_geometry() # my_geo = self.window.get_geometry()

View File

@ -53,7 +53,7 @@ if os.name == 'nt':
raw_input("Press ENTER to continue.") raw_input("Press ENTER to continue.")
exit() exit()
print "Python " + sys.version[0:3] + '...\n' print "Python " + sys.version[0:3] + '...'
import traceback import traceback
import threading import threading
@ -62,12 +62,6 @@ import string
cl_options = string.join(sys.argv[1:]) cl_options = string.join(sys.argv[1:])
(options, argv) = Options.fpdb_options() (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 import logging, logging.config
try: try:
@ -103,6 +97,7 @@ except:
import GuiPrefs import GuiPrefs
import GuiLogView import GuiLogView
import GuiDatabase
import GuiBulkImport import GuiBulkImport
import GuiPlayerStats import GuiPlayerStats
import GuiPositionalStats import GuiPositionalStats
@ -112,13 +107,11 @@ import GuiGraphViewer
import GuiSessionViewer import GuiSessionViewer
import SQL import SQL
import Database import Database
import FpdbSQLQueries
import Configuration import Configuration
import Exceptions import Exceptions
VERSION = "0.12" VERSION = "0.12"
log = Configuration.get_logger("logging.conf", "fpdb")
class fpdb: class fpdb:
def tab_clicked(self, widget, tab_name): def tab_clicked(self, widget, tab_name):
@ -296,10 +289,31 @@ class fpdb:
dia.destroy() dia.destroy()
def dia_create_del_database(self, widget, data=None): def dia_maintain_dbs(self, widget, data=None):
self.warning_box("Unimplemented: Create/Delete Database") self.warning_box("Unimplemented: Maintain Databases")
self.obtain_global_lock() return
self.release_global_lock() if len(self.tab_names) == 1:
if self.obtain_global_lock(): # returns true if successful
# only main tab has been opened, open dialog
dia = gtk.Dialog("Maintain Databases",
self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
dia.set_default_size(700, 320)
prefs = GuiDatabase.GuiDatabase(self.config, self.window, dia)
response = dia.run()
if response == gtk.RESPONSE_ACCEPT:
# save updated config
self.config.save()
self.release_global_lock()
dia.destroy()
else:
self.warning_box("Cannot open Database Maintenance window because "
+ "other windows have been opened. Re-start fpdb to use this option.")
def dia_create_del_user(self, widget, data=None): def dia_create_del_user(self, widget, data=None):
self.warning_box("Unimplemented: Create/Delete user") self.warning_box("Unimplemented: Create/Delete user")
@ -628,7 +642,7 @@ class fpdb:
<menuitem action="tableviewer"/> <menuitem action="tableviewer"/>
</menu> </menu>
<menu action="database"> <menu action="database">
<menuitem action="createdb"/> <menuitem action="maintaindbs"/>
<menuitem action="createuser"/> <menuitem action="createuser"/>
<menuitem action="createtabs"/> <menuitem action="createtabs"/>
<menuitem action="rebuildhudcache"/> <menuitem action="rebuildhudcache"/>
@ -671,7 +685,7 @@ class fpdb:
('sessionreplay', None, '_Session Replayer (todo)', None, 'Session Replayer (todo)', self.not_implemented), ('sessionreplay', None, '_Session Replayer (todo)', None, 'Session Replayer (todo)', self.not_implemented),
('tableviewer', None, 'Poker_table Viewer (mostly obselete)', None, 'Poker_table Viewer (mostly obselete)', self.tab_table_viewer), ('tableviewer', None, 'Poker_table Viewer (mostly obselete)', None, 'Poker_table Viewer (mostly obselete)', self.tab_table_viewer),
('database', None, '_Database'), ('database', None, '_Database'),
('createdb', None, 'Create or Delete _Database (todo)', None, 'Create or Delete Database', self.dia_create_del_database), ('maintaindbs', None, '_Maintain Databases (todo)', None, 'Maintain Databases', self.dia_maintain_dbs),
('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user), ('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user),
('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables), ('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables),
('rebuildhudcache', None, 'Rebuild HUD Cache', None, 'Rebuild HUD Cache', self.dia_recreate_hudcache), ('rebuildhudcache', None, 'Rebuild HUD Cache', None, 'Rebuild HUD Cache', self.dia_recreate_hudcache),
@ -693,9 +707,22 @@ class fpdb:
window.add_accel_group(accel_group) window.add_accel_group(accel_group)
return menubar return menubar
def load_profile(self): def load_profile(self, create_db = False):
"""Loads profile from the provided path name.""" """Loads profile from the provided path name."""
self.config = Configuration.Config(file=options.config, dbname=options.dbname) self.config = Configuration.Config(file=options.config, dbname=options.dbname)
if self.config.file_error:
self.warning_box( "There is an error in your config file\n" + self.config.file
+ "\n\nError is: " + str(self.config.file_error)
, diatitle="CONFIG FILE ERROR" )
exit()
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 = {}
self.settings['global_lock'] = self.lock self.settings['global_lock'] = self.lock
if (os.sep=="/"): if (os.sep=="/"):
@ -716,6 +743,9 @@ class fpdb:
err_msg = None err_msg = None
try: try:
self.db = Database.Database(self.config, sql = self.sql) 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: except Exceptions.FpdbMySQLAccessDenied:
err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?" err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?"
except Exceptions.FpdbMySQLNoDatabase: except Exceptions.FpdbMySQLNoDatabase:
@ -794,8 +824,11 @@ class fpdb:
# TODO: can we get some / all of the stuff done in this function to execute on any kind of abort? # TODO: can we get some / all of the stuff done in this function to execute on any kind of abort?
print "Quitting normally" print "Quitting normally"
# TODO: check if current settings differ from profile, if so offer to save or abort # TODO: check if current settings differ from profile, if so offer to save or abort
if self.db is not None and self.db.connected: try:
self.db.disconnect() 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) self.statusIcon.set_visible(False)
gtk.main_quit() gtk.main_quit()
@ -900,11 +933,20 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tab_main_help(None, None) self.tab_main_help(None, None)
self.window.show() self.window.show()
self.load_profile() self.load_profile(create_db = True)
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() self.statusIcon = gtk.StatusIcon()
if os.path.exists('../gfx/fpdb-cards.png'): # use getcwd() here instead of sys.path[0] so that py2exe works:
self.statusIcon.set_from_file('../gfx/fpdb-cards.png') 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'): elif os.path.exists('/usr/share/pixmaps/fpdb-cards.png'):
self.statusIcon.set_from_file('/usr/share/pixmaps/fpdb-cards.png') self.statusIcon.set_from_file('/usr/share/pixmaps/fpdb-cards.png')
else: else:
@ -963,35 +1005,44 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.window.show() self.window.show()
self.window.present() 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"): 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) label = gtk.Label(str)
diaWarning.vbox.add(label) diaWarning.vbox.add(label)
label.show() label.show()
response = diaWarning.run() response = diaWarning.run()
diaWarning.destroy() diaWarning.destroy()
return response return response
def validate_config(self): def validate_config(self):
hhbase = self.config.get_import_parameters().get("hhArchiveBase") if self.config.get_import_parameters().get('saveStarsHH'):
hhbase = os.path.expanduser(hhbase) hhbase = self.config.get_import_parameters().get("hhArchiveBase")
#hhdir = os.path.join(hhbase,site) hhbase = os.path.expanduser(hhbase)
hhdir = hhbase #hhdir = os.path.join(hhbase,site)
if not os.path.isdir(hhdir): hhdir = hhbase
diapath = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Setup hh dir") if not os.path.isdir(hhdir):
diastring = "WARNING: Unable to find output hh directory %s\n\n Press YES to create this directory, or NO to select a new one." % hhdir diapath = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Setup hh dir")
diapath.format_secondary_text(diastring) diastring = "WARNING: Unable to find output hh directory %s\n\n Press YES to create this directory, or NO to select a new one." % hhdir
response = diapath.run() diapath.format_secondary_text(diastring)
diapath.destroy() response = diapath.run()
if response == gtk.RESPONSE_YES: diapath.destroy()
try: if response == gtk.RESPONSE_YES:
os.makedirs(hhdir) try:
except: os.makedirs(hhdir)
self.warning_box("WARNING: Unable to create hand output directory. Importing is not likely to work until this is fixed.") except:
elif response == gtk.RESPONSE_NO: self.warning_box("WARNING: Unable to create hand output directory. Importing is not likely to work until this is fixed.")
self.select_hhArchiveBase() elif response == gtk.RESPONSE_NO:
self.select_hhArchiveBase()
def select_hhArchiveBase(self, widget=None): def select_hhArchiveBase(self, widget=None):
fc = gtk.FileChooserDialog(title="Select HH Output Directory", parent=None, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_OPEN,gtk.RESPONSE_OK), backend=None) fc = gtk.FileChooserDialog(title="Select HH Output Directory", parent=None, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_OPEN,gtk.RESPONSE_OK), backend=None)

View File

@ -16,208 +16,6 @@
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #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 #end class fpdb_db

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

@ -30,19 +30,19 @@ import Queue
from collections import deque # using Queue for now from collections import deque # using Queue for now
import threading 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 pygtk
import gtk import gtk
# fpdb/FreePokerTools modules # fpdb/FreePokerTools modules
import fpdb_simple
import fpdb_db
import Database import Database
import fpdb_parse_logic
import Configuration import Configuration
import Exceptions import Exceptions
log = Configuration.get_logger("logging.conf", "importer")
# database interface modules # database interface modules
try: try:
@ -68,6 +68,7 @@ class Importer:
self.config = config self.config = config
self.sql = sql self.sql = sql
#log = Configuration.get_logger("logging.conf", "importer", log_dir=self.config.dir_log)
self.filelist = {} self.filelist = {}
self.dirlist = {} self.dirlist = {}
self.siteIds = {} self.siteIds = {}
@ -100,8 +101,6 @@ class Importer:
for i in xrange(self.settings['threads']): for i in xrange(self.settings['threads']):
self.writerdbs.append( Database.Database(self.config, sql = self.sql) ) self.writerdbs.append( Database.Database(self.config, sql = self.sql) )
self.NEWIMPORT = Configuration.NEWIMPORT
clock() # init clock in windows clock() # init clock in windows
#Set functions #Set functions
@ -370,7 +369,7 @@ class Importer:
pass pass
(stored, duplicates, partial, errors, ttime) = self.import_file_dict(self.database, file, self.filelist[file][0], self.filelist[file][1], None) (stored, duplicates, partial, errors, ttime) = self.import_file_dict(self.database, file, self.filelist[file][0], self.filelist[file][1], None)
try: 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)) 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 .. except KeyError: # TODO: Again, what error happens here? fix when we find out ..
pass pass
@ -407,9 +406,9 @@ class Importer:
return (0,0,0,0,0) return (0,0,0,0,0)
conv = None 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 # Load filter, process file, pass returned filename to import_fpdb_file
if self.settings['threads'] > 0 and self.writeq is not None: if self.settings['threads'] > 0 and self.writeq is not None:
@ -429,24 +428,36 @@ class Importer:
mod = __import__(filter) mod = __import__(filter)
obj = getattr(mod, filter_name, None) obj = getattr(mod, filter_name, None)
if callable(obj): if callable(obj):
hhc = obj(in_path = file, out_path = out_path, index = 0, starsArchive = self.settings['starsArchive']) # Index into file 0 until changeover idx = 0
if hhc.getStatus() and self.NEWIMPORT == False: if file in self.pos_in_file:
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(db, out_path, site, q) idx = self.pos_in_file[file]
elif hhc.getStatus() and self.NEWIMPORT == True: else:
#This code doesn't do anything yet 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() handlist = hhc.getProcessedHands()
self.pos_in_file[file] = hhc.getLastCharacterRead() self.pos_in_file[file] = hhc.getLastCharacterRead()
to_hud = [] to_hud = []
for hand in handlist: for hand in handlist:
if hand is not None: if hand is not None:
#try, except duplicates here?
hand.prepInsert(self.database) hand.prepInsert(self.database)
hand.insert(self.database) try:
if self.callHud and hand.dbid_hands != 0: hand.insert(self.database)
to_hud.append(hand.dbid_hands) except Exceptions.FpdbHandDuplicate:
else: 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") 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 and not hand.is_duplicate:
hand.updateHudCache(self.database)
self.database.commit() self.database.commit()
#pipe the Hands.id out to the HUD #pipe the Hands.id out to the HUD
@ -456,174 +467,22 @@ class Importer:
errors = getattr(hhc, 'numErrors') errors = getattr(hhc, 'numErrors')
stored = getattr(hhc, 'numHands') stored = getattr(hhc, 'numHands')
stored -= duplicates
stored -= errors
else: else:
# conversion didn't work # conversion didn't work
# TODO: appropriate response? # TODO: appropriate response?
return (0, 0, 0, 1, 0) return (0, 0, 0, 1, time() - ttime)
else: else:
log.warning("Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter)) 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 #This will barf if conv.getStatus != True
return (stored, duplicates, partial, errors, ttime) 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): def printEmailErrorMessage(self, errors, filename, line):
traceback.print_exc(file=sys.stderr) 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." print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it."

View File

@ -1,235 +0,0 @@
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#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.
#parses an in-memory fpdb hand history and calls db routine to store it
import sys
from time import time, strftime
from Exceptions import *
import fpdb_simple
import Database
def mainParser(settings, siteID, category, hand, config, db = None, writeq = None):
""" mainParser for Holdem Hands """
t0 = time()
backend = settings['db-backend']
# Ideally db connection is passed in, if not use sql list if passed in,
# otherwise start from scratch
if db is None:
db = Database.Database(c = config, sql = None)
category = fpdb_simple.recogniseCategory(hand[0])
base = "hold" if (category == "holdem" or category == "omahahi" or
category == "omahahilo") else "stud"
#part 0: create the empty arrays
# lineTypes valid values: header, name, cards, action, win, rake, ignore
# lineStreets valid values: predeal, preflop, flop, turn, river
lineTypes = []
lineStreets = []
cardValues = []
cardSuits = []
boardValues = []
boardSuits = []
antes = []
allIns = []
actionAmounts = []
actionNos = []
actionTypes = []
actionTypeByNo = []
seatLines = []
winnings = []
rakes = []
#part 1: read hand no and check for duplicate
siteHandNo = fpdb_simple.parseSiteHandNo(hand[0])
handStartTime = fpdb_simple.parseHandStartTime(hand[0])
isTourney = fpdb_simple.isTourney(hand[0])
smallBlindLine = None
for i, line in enumerate(hand):
if 'posts small blind' in line or 'posts the small blind' in line:
if line[-2:] == "$0": continue
smallBlindLine = i
break
else:
smallBlindLine = 0
# If we did not find a small blind line, what happens?
# if we leave it at None, it errors two lines down.
gametypeID = fpdb_simple.recogniseGametypeID(backend, db, db.get_cursor(),
hand[0], hand[smallBlindLine],
siteID, category, isTourney)
if isTourney:
siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0])
buyin = fpdb_simple.parseBuyin(hand[0])
fee = fpdb_simple.parseFee(hand[0])
entries = -1 #todo: parse this
prizepool = -1 #todo: parse this
knockout = False
tourneyStartTime= handStartTime #todo: read tourney start time
rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0])
# The tourney site id has to be searched because it may already be in
# db with a TourneyTypeId which is different from the one automatically
# calculated (Summary import first)
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(db, siteID,
siteTourneyNo,
buyin, fee,
knockout,
rebuyOrAddon)
else:
siteTourneyNo = -1
buyin = -1
fee = -1
entries = -1
prizepool = -1
knockout = 0
tourneyStartTime= None
rebuyOrAddon = -1
tourneyTypeId = 1
fpdb_simple.isAlreadyInDB(db, gametypeID, siteHandNo)
hand = fpdb_simple.filterCrap(hand, isTourney)
#part 2: classify lines by type (e.g. cards, action, win, sectionchange) and street
fpdb_simple.classifyLines(hand, category, lineTypes, lineStreets)
#part 3: read basic player info
#3a read player names, startcashes
for i, line in enumerate(hand):
if lineTypes[i] == "name":
seatLines.append(line)
names = fpdb_simple.parseNames(seatLines)
playerIDs = db.recognisePlayerIDs(names, siteID) # inserts players as needed
tmp = fpdb_simple.parseCashesAndSeatNos(seatLines)
startCashes = tmp['startCashes']
seatNos = tmp['seatNos']
fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes,
winnings, rakes, actionTypes, allIns,
actionAmounts, actionNos, actionTypeByNo)
#3b read positions
if base == "hold":
positions = fpdb_simple.parsePositions(hand, names)
#part 4: take appropriate action for each line based on linetype
for i, line in enumerate(hand):
if lineTypes[i] == "cards":
fpdb_simple.parseCardLine(category, lineStreets[i], line, names,
cardValues, cardSuits, boardValues,
boardSuits)
#if category=="studhilo":
# print "hand[i]:", hand[i]
# print "cardValues:", cardValues
# print "cardSuits:", cardSuits
elif lineTypes[i] == "action":
fpdb_simple.parseActionLine(base, isTourney, line, lineStreets[i],
playerIDs, names, actionTypes, allIns,
actionAmounts, actionNos, actionTypeByNo)
elif lineTypes[i] == "win":
fpdb_simple.parseWinLine(line, names, winnings, isTourney)
elif lineTypes[i] == "rake":
totalRake = 0 if isTourney else fpdb_simple.parseRake(line)
fpdb_simple.splitRake(winnings, rakes, totalRake)
elif (lineTypes[i] == "header" or lineTypes[i] == "rake" or
lineTypes[i] == "name" or lineTypes[i] == "ignore"):
pass
elif lineTypes[i] == "ante":
fpdb_simple.parseAnteLine(line, isTourney, names, antes)
elif lineTypes[i] == "table":
tableResult=fpdb_simple.parseTableLine(base, line)
else:
raise FpdbError("unrecognised lineType:" + lineTypes[i])
maxSeats = tableResult['maxSeats']
tableName = tableResult['tableName']
#print "before part5, antes:", antes
#part 5: final preparations, then call Database.* with
# the arrays as they are - that file will fill them.
fpdb_simple.convertCardValues(cardValues)
if base == "hold":
fpdb_simple.convertCardValuesBoard(boardValues)
fpdb_simple.convertBlindBet(actionTypes, actionAmounts)
fpdb_simple.checkPositions(positions)
c = db.get_cursor()
c.execute("SELECT limitType FROM Gametypes WHERE id=%s" % (db.sql.query['placeholder'],), (gametypeID, ))
limit_type = c.fetchone()[0]
fpdb_simple.convert3B4B(category, limit_type, actionTypes, actionAmounts)
totalWinnings = sum(winnings)
# if hold'em, use positions and not antes, if stud do not use positions, use antes
# this is used for handsplayers inserts, so still needed even if hudcache update is being skipped
if base == "hold":
hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base,
category, actionTypes,
allIns, actionTypeByNo,
winnings,
totalWinnings,
positions, actionTypes,
actionAmounts, None)
else:
hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base,
category, actionTypes,
allIns, actionTypeByNo,
winnings,
totalWinnings, None,
actionTypes,
actionAmounts, antes)
try:
db.commit() # need to commit new players as different db connection used
# for other writes. maybe this will change maybe not ...
except: # TODO: this really needs to be narrowed down
print "parse: error during commit: " + str(sys.exc_value)
# HERE's an ugly kludge to keep from failing when positions is undef
# We'll fix this by getting rid of the legacy importer. REB
try:
if positions:
pass
except NameError:
positions = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# save data structures in a HandToWrite instance and then insert into database:
htw = Database.HandToWrite()
htw.set_all( config, settings, base, category, siteTourneyNo, buyin
, fee, knockout, entries, prizepool, tourneyStartTime
, isTourney, tourneyTypeId, siteID, siteHandNo
, gametypeID, handStartTime, names, playerIDs, startCashes
, positions, antes, cardValues, cardSuits, boardValues, boardSuits
, winnings, rakes, actionTypes, allIns, actionAmounts
, actionNos, hudImportData, maxSeats, tableName, seatNos)
# save hand in db via direct call or via q if in a thread
if writeq is None:
result = db.store_the_hand(htw)
else:
writeq.put(htw)
result = -999 # meaning unknown
t9 = time()
#print "parse and save=(%4.3f)" % (t9-t0)
return result
#end def mainParser

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -22,52 +22,189 @@ Py2exe script for fpdb.
######################################################################## ########################################################################
#TODO: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate #TODO:
# include the lib needed to handle png files in mucked
# get rid of all the uneeded libraries (e.g., pyQT) # get rid of all the uneeded libraries (e.g., pyQT)
# think about an installer # 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: #HOW TO USE this script:
# #
# cd to the folder where this script is stored, usually .../pyfpdb. #- cd to the folder where this script is stored, usually .../pyfpdb.
# If there are build and dist subfolders present , delete them to get # [If there are build and dist subfolders present , delete them to get
# rid of earlier builds. # rid of earlier builds. Update: script now does this for you]
# Run the script with "py2exe_setup.py py2exe" #- Run the script with "py2exe_setup.py py2exe"
# You will frequently get messages about missing .dll files. E. g., #- You will frequently get messages about missing .dll files. E. g.,
# MSVCP90.dll. These are somewhere in your windows install, so you # MSVCP90.dll. These are somewhere in your windows install, so you
# can just copy them to your working folder. # can just copy them to your working folder. (or just assume other
# If it works, you'll have 2 new folders, build and dist. Build is # person will have them? any copyright issues with including them?)
# working space and should be deleted. Dist contains the files to be #- [ If it works, you'll have 3 new folders, build and dist and gfx. Build is
# distributed. Last, you must copy the etc/, lib/ and share/ folders # working space and should be deleted. Dist and gfx contain the files to be
# from your gtk/bin/ folder to the dist folder. (the whole folders, not # distributed. ]
# just the contents) You can (should) then prune the etc/, lib/ and # If it works, you'll have a new dir fpdb-XXX-YYYYMMDD-exe which should
# share/ folders to remove components we don't need. # 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 from distutils.core import setup
import py2exe import py2exe
import glob
import matplotlib
import shutil
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( setup(
name = 'fpdb', name = 'fpdb',
description = 'Free Poker DataBase', description = 'Free Poker DataBase',
version = '0.12', version = '0.12',
console = [ {'script': 'fpdb.py', }, console = [ {'script': 'fpdb.py', "icon_resources": [(1, "../gfx/fpdb_large_icon.ico")]},
{'script': 'HUD_main.py', }, {'script': 'HUD_main.py', },
{'script': 'Configuration.py', } {'script': 'Configuration.py', }
], ],
options = {'py2exe': { options = {'py2exe': {
'packages' :'encodings', 'packages' : ['encodings', 'matplotlib'],
'includes' : 'cairo, pango, pangocairo, atk, gobject, PokerStarsToFpdb', 'includes' : ['cairo', 'pango', 'pangocairo', 'atk', 'gobject'
'excludes' : '_tkagg, _agg2, cocoaagg, fltkagg', ,'matplotlib.numerix.random_array'
'dll_excludes': 'libglade-2.0-0.dll', ,'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', # files in 2nd value in tuple are moved to dir named in 1st value
'Cards01.png', data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf', '../docs/readme.txt'])
'logging.conf', ,(dist_dir, [r'..\run_fpdb.bat'])
(r'matplotlibdata', glob.glob(r'c:\python26\Lib\site-packages\matplotlib\mpl-data\*')) ,( 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 )
print "Enter directory name for GTK 2.14 (e.g. c:\code\gtk_2.14.7-20090119)\n: ", # the comma means no newline
gtk_dir = sys.stdin.readline().rstrip()
print "\ncopying files and dirs from ", gtk_dir, "to", dest.replace('\\\\', '\\'), "..."
src = os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll')
src = src.replace('\\', '\\\\')
shutil.copy( src, dest )
src = os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll')
src = src.replace('\\', '\\\\')
shutil.copy( src, dest )
src_dir = os.path.join(gtk_dir, 'etc')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'etc')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )
src_dir = os.path.join(gtk_dir, 'lib')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'lib')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )
src_dir = os.path.join(gtk_dir, 'share')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'share')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )

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 sys
import os
import Configuration
config_path = Configuration.get_default_config_path()
try: try:
import gobject as _gobject
print "Import of gobject:\tSuccess"
import pygtk import pygtk
print "Import of pygtk:\tSuccess"
pygtk.require('2.0') pygtk.require('2.0')
import gtk 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 = gtk.Window(gtk.WINDOW_TOPLEVEL)
win.set_title("Test GTK") win.set_title("Test GTK")
@ -42,7 +69,7 @@ try:
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)) (gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
dia.set_default_size(500, 300) 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) dia.vbox.add(l)
l.show() l.show()

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sqlite3 import sqlite3
import fpdb_db import Database
import math import math
# Should probably use our wrapper classes - creating sqlite db in memory # 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) con.create_function("floor", 1, math.floor)
#Mod function #Mod function
tmp = fpdb_db.sqlitemath() tmp = Database.sqlitemath()
con.create_function("mod", 2, tmp.mod) con.create_function("mod", 2, tmp.mod)
# Aggregate function VARIANCE() # Aggregate function VARIANCE()
con.create_aggregate("variance", 1, fpdb_db.VARIANCE) con.create_aggregate("variance", 1, Database.VARIANCE)
cur = con.cursor() 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'} gametype = {'type':'ring', 'base':'draw', 'category':'badugi', 'limitType':'fl', 'sb':'0.25', 'bb':'0.50','currency':'USD'}
text = "" 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") h.addPlayer("1", "s0rrow", "100000")
hhc.compilePlayerRegexs(h) hhc.compilePlayerRegexs(h)
@ -39,7 +39,7 @@ def checkGameInfo(hhc, header, info):
assert hhc.determineGameType(header) == info assert hhc.determineGameType(header) == info
def testGameInfo(): def testGameInfo():
hhc = PokerStarsToFpdb.PokerStars(autostart=False) hhc = PokerStarsToFpdb.PokerStars(config, autostart=False)
pairs = ( pairs = (
(u"PokerStars Game #20461877044: Hold'em No Limit ($1/$2) - 2008/09/16 18:58:01 ET", (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'}), {'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 # River: hero (continuation bets?) all-in and is not called
importer.addBulkImportImportFileOrDir( importer.addBulkImportImportFileOrDir(
"""regression-test-files/cash/Stars/Flop/NLHE-6max-USD-0.05-0.10-200912.Stats-comparision.txt""", site="PokerStars") """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) importer.setCallHud(False)
(stored, dups, partial, errs, ttime) = importer.runImport() (stored, dups, partial, errs, ttime) = importer.runImport()
print "DEBUG: stored: %s dups: %s partial: %s errs: %s ttime: %s" %(stored, dups, partial, errs, ttime) print "DEBUG: stored: %s dups: %s partial: %s errs: %s ttime: %s" %(stored, dups, partial, errs, ttime)
importer.clearFileList() importer.clearFileList()
col = { 'sawShowdown': 2 col = { 'sawShowdown': 2, 'street0Aggr':3
} }
q = """SELECT q = """SELECT
s.name, s.name,
p.name, p.name,
hp.sawShowdown hp.sawShowdown,
hp.street0Aggr
FROM FROM
Hands as h, Hands as h,
Sites as s, Sites as s,
@ -114,6 +117,33 @@ and s.id = p.siteid"""
# Assert if any sawShowdown = True # Assert if any sawShowdown = True
assert result[row][col['sawShowdown']] == 0 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(): def testStudImport():
db.recreate_tables() db.recreate_tables()
importer = fpdb_import.Importer(False, settings, config) importer = fpdb_import.Importer(False, settings, config)
@ -148,11 +178,7 @@ def testDrawImport():
(stored, dups, partial, errs, ttime) = importer.runImport() (stored, dups, partial, errs, ttime) = importer.runImport()
importer.clearFileList() importer.clearFileList()
except FpdbError: except FpdbError:
if Configuration.NEWIMPORT == False: assert 0 == 1
#Old import code doesn't support draw
pass
else:
assert 0 == 1
# Should actually do some testing here # Should actually do some testing here
assert 1 == 1 assert 1 == 1

View File

@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
import fpdb_simple
import datetime
import py
def checkDateParse(header, site, result):
assert fpdb_simple.parseHandStartTime(header, site) == result
def testPokerStarsHHDate():
tuples = (
("PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET]", "ps",
datetime.datetime(2008,11,12,15,00,48)),
("PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/08/17 - 01:14:43 (ET)", "ps",
datetime.datetime(2008,8,17,6,14,43)),
("PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/09/07 06:23:14 ET", "ps",
datetime.datetime(2008,9,7,11,23,14))
)
#def testTableDetection():
# result = Tables.clean_title("French (deep)")
# assert result == "French"
# result = Tables.clean_title("French (deep) - $0.25/$0.50 - No Limit Hold'em - Logged In As xxxx")
# assert result == "French"
#
# for (header, site, result) in tuples:
# yield checkDateParse, header, site, result

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