diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..bd11db36 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +pyfpdb/HUD_config.xml.example -crlf + diff --git a/gfx/Table.png b/gfx/Table.png new file mode 100644 index 00000000..60053098 Binary files /dev/null and b/gfx/Table.png differ diff --git a/packaging/announce-0.20.905.txt b/packaging/announce-0.20.905.txt new file mode 100644 index 00000000..cf4932c5 --- /dev/null +++ b/packaging/announce-0.20.905.txt @@ -0,0 +1,32 @@ +Hello everyone, +The new snapshot 0.20.905 is now available for download as source or as packages/installers for Debian, Gentoo, Ubuntu and Windows. +This version brings many improvements and bugfixes, updating is recommended for users of previous snapshots. If you're using a stable version like 0.20 or 0.20.1 please consider trying this version and report any bugs, and in particular regressions, so we can fix them. This snapshot will hopefully be the last, next step is one or more release candidates, and then the next stable release. + +We are still looking for translators! You can find some information about what languages we are still missing here: http://sourceforge.net/apps/mediawiki/fpdb/index.php?title=Translation + +188 changesets (excl. merges) have gone in since 0.20.904. +Please note that you will have to either recreate your database or use a new one if you're updating from 0.20.904 or older. +Config files from 0.20 and later should work. Please report if you have problems with config files from that version or later. + +What's changed: +- Fpdb now supports running in languages other than English, Erki supplied a translation for Hungarian. French, Spanish and Italian are in progress by new contributors. Fpdb will use the system-configured language, a configuration option will be added before the next stable release. Note that this is about the user interface language, non-English history file parsing is a seperate topic. +- Much improved testing to improve the recording of data, especially corner cases and non-trivial stats +- OnGame network (which now includes Betfair) is now properly supported +- FTP.fr importing now works. We don't know if the HUD works yet, give it a go and let us know +- We noticed that fpdb already supports PS.fr +- PokerStars should support all limits now +- The Debian package now handles a missing config file properly +- Many minor improvements to the Gentoo ebuilds for the upcoming submission to the sunrise overlay +- We changed how email import is configured. Either delete your old config or see the HUD_config.xml.example file for how to add the section to the right place +- fpdb should now be able to run with any config file from 0.20 or later, including all 0.20.9* snapshots +- Some more fixes to window visibility and the minimise to tray icon feature +- Various other small cleanups, fixes and improvements. See the git changelog for full details + +To download: +- Debian/Ubuntu Linux: http://sourceforge.net/projects/fpdb/files/fpdb/Snapshots/python-fpdb_0.20.905-1_all.deb/download +- Gentoo Linux: http://sourceforge.net/projects/fpdb/files/fpdb/Snapshots/fpdb-0.20.905.ebuild/download +- Windows: http://sourceforge.net/projects/fpdb/files/fpdb/Snapshots/fpdb-0.20.905anyCPU.exe/download +- Source version for those who installed the dependencies manually: http://sourceforge.net/projects/fpdb/files/fpdb/Snapshots/fpdb-0.20.905.tar.bz2/download + +Thanks to everyone who contributed code, translations, testing and bug reports! +The fpdb team diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 0616ad0e..c09713a3 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,3 +1,34 @@ +free-poker-tools (0.20.906-1) unstable; urgency=low + + * New snapshot + + -- Mika Bostrom Fri, 27 Aug 2010 08:26:05 +0300 + +free-poker-tools (0.20.905-1) unstable; urgency=low + + * New snapshot + * Hungarian translation + + -- Mika Bostrom Wed, 25 Aug 2010 10:05:36 +0300 + +free-poker-tools (0.20.904-2) unstable; urgency=low + + * On fpdb start, copy example HUD_config.xml in place if none is present + + -- Mika Bostrom Tue, 17 Aug 2010 08:23:31 +0300 + +free-poker-tools (0.20.904-1) unstable; urgency=low + + * .904 snapshot release + + -- Mika Bostrom Sat, 14 Aug 2010 09:24:25 +0300 + +free-poker-tools (0.20.903-1) unstable; urgency=low + + * .903 snapshot release + + -- Mika Bostrom Tue, 03 Aug 2010 17:47:41 +0300 + free-poker-tools (0.20.902-1) unstable; urgency=low * New snapshot release; .901 was broken for FTP diff --git a/packaging/debian/rules b/packaging/debian/rules index 9106c7bc..9f8d5238 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -26,9 +26,7 @@ install: build # Copy *.pyw manually in packaging tree cp pyfpdb/*.pyw debian/$(PACKAGE)/usr/lib/python2.6/site-packages/fpdb/ # Remove scripts that are only useful in win32 - rm debian/$(PACKAGE)/usr/lib/python2.6/site-packages/fpdb//windows_make_bats.py - rm debian/$(PACKAGE)/usr/lib/python2.6/site-packages/fpdb/makeexe.py - rm debian/$(PACKAGE)/usr/lib/python2.6/site-packages/fpdb/py2exe_setup.py + rm debian/$(PACKAGE)/usr/lib/python2.6/site-packages/fpdb/windows_make_bats.py binary-indep: build install diff --git a/packaging/gentoo/ChangeLog b/packaging/gentoo/ChangeLog new file mode 100644 index 00000000..57aaabe6 --- /dev/null +++ b/packaging/gentoo/ChangeLog @@ -0,0 +1,23 @@ +# Copyright 1999-2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# created by Steffen Schaumburg, steffen@schaumburger.info + + 29 Aug 2010; Erki Ferenc + fpdb-0.20.906.ebuild, fpdb-9999.ebuild: + improve l10n handling + + 29 Aug 2010; Steffen Schaumburg + fpdb-0.20.1.ebuild, fpdb-0.20.906.ebuild, fpdb-9999.ebuild: + change required python version to 2.6 + +*fpdb-0.20.906 (29 Aug 2010) + 29 Aug 2010; Steffen Schaumburg + +fpdb-0.20.906.ebuild -fpdb-0.20.904.ebuild: + Bump version + +*fpdb-0.20.1 fpdb-0.20.904 fpdb-9999 (17 Aug 2010) + + 17 Aug 2010; Steffen Schaumburg + +fpdb-0.20.1.ebuild, +fpdb-0.20.904.ebuild, +fpdb-9999.ebuild, +metadata.xml: + Initial changelog for repoman. + diff --git a/packaging/gentoo/Manifest b/packaging/gentoo/Manifest new file mode 100644 index 00000000..1096a2e6 --- /dev/null +++ b/packaging/gentoo/Manifest @@ -0,0 +1,7 @@ +DIST fpdb-0.20.1.tar.bz2 662807 RMD160 b5f22a684c605ddbba7d2154005a822b02a19490 SHA1 e4cc40de5849d3ae33a680d917b340ab37c6448b SHA256 46eff0625f300c070ce88c519ae6019f6e1c98a7725733c5e16b50a058247fe3 +DIST fpdb-0.20.906.tar.bz2 702558 RMD160 bc5d01ef4899502aea33f286ac4274ef7ef498ef SHA1 9791680d53de1b276dc0297ac43a0e11758d3e19 SHA256 9ae706d5e9c2a2ee031d2b222ba46e088993cc892fc08b5276bbfd5e44a0436b +EBUILD fpdb-0.20.1.ebuild 1508 RMD160 7585cd1de73172649e182782d427a476afed4036 SHA1 3c92d6dbb868b8b4c26b75539771641087742761 SHA256 9a7d302016e4c4d6cc18af14514bd5112d18aeb7dc6390a3413e3e4cc71da6bd +EBUILD fpdb-0.20.906.ebuild 1643 RMD160 ed44ee49d715458b54edbbe054eb5c9775c6ac7a SHA1 ebd8ea291ace0671d4eb264754dfb0616373a51a SHA256 a9bdad768a0ab5ef065f3e6e2e1bd89dded6e0d3b64c4771944c4aae7d163efd +EBUILD fpdb-9999.ebuild 1685 RMD160 f06457ead33dca99c0acf830f26bbf2f8ca12cd1 SHA1 70444fa4a88439955472407ec0b072970993631a SHA256 2df59120b376bb4e5966f8a719bc881c756b3210b7a30d394ee1753efbfd706e +MISC ChangeLog 831 RMD160 efd32886d09b0750e680716030c0034c3a280a25 SHA1 82f3eda3cd83cbba3e45d4b75593e74f3dd4f568 SHA256 d74efef05cf51ef3840ef043218c8a30c1bcccfa9d9d2e4ca1b7198ed1c91f29 +MISC metadata.xml 550 RMD160 a6fa8799f644c0882f832a12cc9e6a0f4f09ae7f SHA1 3a40c442cadb1f532e0299040c2da79e9721dd4f SHA256 b5a1c538de3786446a87479b1023cdb4f084085feb7290959619739969ce7d3b diff --git a/packaging/gentoo/current_stable.ebuild b/packaging/gentoo/current_stable.ebuild index 7a27feb0..666c7fee 100644 --- a/packaging/gentoo/current_stable.ebuild +++ b/packaging/gentoo/current_stable.ebuild @@ -1,12 +1,10 @@ # Copyright 1999-2010 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 -# created by Steffen Schaumburg, steffen@schaumburger.info - -inherit eutils -inherit games +# $Header: $ EAPI="2" -NEED_PYTHON=2.5 + +inherit eutils games DESCRIPTION="A free/open source tracker/HUD for use with online poker" HOMEPAGE="http://fpdb.wiki.sourceforge.net/" @@ -19,18 +17,18 @@ KEYWORDS="~amd64 ~x86" IUSE="graph mysql postgres sqlite" RDEPEND=" - mysql? ( virtual/mysql - dev-python/mysql-python ) - postgres? ( dev-db/postgresql-server - dev-python/psycopg ) - sqlite? ( dev-lang/python[sqlite] - dev-python/numpy ) - >=x11-libs/gtk+-2.10 - dev-python/pygtk - graph? ( dev-python/numpy - dev-python/matplotlib[gtk] ) - dev-python/python-xlib - dev-python/pytz" + mysql? ( virtual/mysql + dev-python/mysql-python ) + postgres? ( dev-db/postgresql-server + dev-python/psycopg ) + sqlite? ( dev-lang/python[sqlite] + dev-python/numpy ) + >=x11-libs/gtk+-2.10 + dev-python/pygtk + graph? ( dev-python/numpy + dev-python/matplotlib[gtk] ) + dev-python/python-xlib + dev-python/pytz" DEPEND="${RDEPEND}" src_install() { @@ -44,17 +42,17 @@ src_install() { dodir "${GAMES_BINDIR}" dosym "${GAMES_DATADIR}"/${PN}/run_fpdb.py "${GAMES_BINDIR}"/${PN} - + newicon gfx/fpdb-icon.png ${PN}.png make_desktop_entry ${PN} - prepgamesdirs fperms +x "${GAMES_DATADIR}"/${PN}/pyfpdb/*.pyw + prepgamesdirs } pkg_postinst() { games_pkg_postinst elog "Note that if you really want to use mysql or postgresql you will have to create" - elog "the database and user yourself and enter it into the fpdb config." + elog "the database and user yourself and enter it into the fpdb config." elog "You can find the instructions on the project's website." } diff --git a/packaging/gentoo/current_testing.ebuild b/packaging/gentoo/current_testing.ebuild index 05483b62..525200fb 100644 --- a/packaging/gentoo/current_testing.ebuild +++ b/packaging/gentoo/current_testing.ebuild @@ -1,12 +1,10 @@ # Copyright 1999-2010 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 -# created by Steffen Schaumburg, steffen@schaumburger.info - -inherit eutils -inherit games +# $Header: $ EAPI="2" -NEED_PYTHON=2.5 + +inherit eutils games DESCRIPTION="A free/open source tracker/HUD for use with online poker" HOMEPAGE="http://fpdb.wiki.sourceforge.net/" @@ -17,44 +15,51 @@ SLOT="0" KEYWORDS="~amd64 ~x86" #note: this should work on other architectures too, please send me your experiences -IUSE="graph mysql postgres sqlite" +IUSE="graph mysql postgres sqlite linguas_hu" RDEPEND=" - mysql? ( virtual/mysql - dev-python/mysql-python ) - postgres? ( dev-db/postgresql-server - dev-python/psycopg ) - sqlite? ( dev-lang/python[sqlite] - dev-python/numpy ) - >=x11-libs/gtk+-2.10 - dev-python/pygtk - graph? ( dev-python/numpy - dev-python/matplotlib[gtk] ) - dev-python/python-xlib - dev-python/pytz" + mysql? ( virtual/mysql + dev-python/mysql-python ) + postgres? ( dev-db/postgresql-server + dev-python/psycopg ) + sqlite? ( dev-lang/python[sqlite] + dev-python/numpy ) + >=x11-libs/gtk+-2.10 + dev-python/pygtk + graph? ( dev-python/numpy + dev-python/matplotlib[gtk] ) + dev-python/python-xlib + dev-python/pytz" DEPEND="${RDEPEND}" src_install() { insinto "${GAMES_DATADIR}"/${PN} - doins -r gfx - doins -r pyfpdb - doins readme.txt + doins -r gfx || die "failed to install gfx directory" + doins -r pyfpdb || die "failed to install pyfpdb directory" + + if use linguas_hu; then + msgfmt pyfpdb/locale/fpdb-hu_HU.po -o pyfpdb/locale/hu.mo || die "failed to create hungarian mo file" + fi + + domo pyfpdb/locale/*.mo || die "failed to install mo files" + + doins readme.txt || die "failed to install readme.txt file" exeinto "${GAMES_DATADIR}"/${PN} - doexe run_fpdb.py + doexe run_fpdb.py || die "failed to install executable run_fpdb.py" dodir "${GAMES_BINDIR}" - dosym "${GAMES_DATADIR}"/${PN}/run_fpdb.py "${GAMES_BINDIR}"/${PN} - - newicon gfx/fpdb-icon.png ${PN}.png - make_desktop_entry ${PN} + dosym "${GAMES_DATADIR}"/${PN}/run_fpdb.py "${GAMES_BINDIR}"/${PN} || die "failed to create symlink for starting fpdb" + + newicon gfx/fpdb-icon.png ${PN}.png || die "failed to install fpdb icon" + make_desktop_entry ${PN} || die "failed to create desktop entry" - prepgamesdirs fperms +x "${GAMES_DATADIR}"/${PN}/pyfpdb/*.pyw + prepgamesdirs } pkg_postinst() { games_pkg_postinst elog "Note that if you really want to use mysql or postgresql you will have to create" - elog "the database and user yourself and enter it into the fpdb config." + elog "the database and user yourself and enter it into the fpdb config." elog "You can find the instructions on the project's website." } diff --git a/packaging/gentoo/dev-readme.txt b/packaging/gentoo/dev-readme.txt new file mode 100644 index 00000000..0effdf1b --- /dev/null +++ b/packaging/gentoo/dev-readme.txt @@ -0,0 +1,7 @@ +Repoman currently gives the following errors for our ebuilds: + ebuild.allmasked: This error can be ignored, as all our packages are supposed to be masked + +Useful Links: +http://overlays.gentoo.org/proj/sunrise/wiki/SunriseFaq +http://www.linuxhowtos.org/manpages/1/repoman.htm +http://www.gentoo.org/proj/en/devrel/handbook/handbook.xml The gentoo devrel handbook. Of particular relevance is the "Guides" section. \ No newline at end of file diff --git a/packaging/gentoo/fpdb-9999.ebuild b/packaging/gentoo/fpdb-9999.ebuild new file mode 100644 index 00000000..a2469183 --- /dev/null +++ b/packaging/gentoo/fpdb-9999.ebuild @@ -0,0 +1,72 @@ +# Copyright 1999-2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + +EAPI="2" + +inherit eutils games git + +DESCRIPTION="A free/open source tracker/HUD for use with online poker" +HOMEPAGE="http://fpdb.wiki.sourceforge.net/" +EGIT_REPO_URI="git://git.assembla.com/fpdb.git" + +LICENSE="AGPL-3" +SLOT="0" +KEYWORDS="" + +IUSE="graph mysql postgres sqlite linguas_de linguas_hu" +RDEPEND=" + mysql? ( virtual/mysql + dev-python/mysql-python ) + postgres? ( dev-db/postgresql-server + dev-python/psycopg ) + sqlite? ( dev-lang/python[sqlite] + dev-python/numpy ) + >=x11-libs/gtk+-2.10 + dev-python/pygtk + graph? ( dev-python/numpy + dev-python/matplotlib[gtk] ) + dev-python/python-xlib + dev-python/pytz" +DEPEND="${RDEPEND}" + +src_unpack() { + git_src_unpack +} + +src_install() { + insinto "${GAMES_DATADIR}"/${PN} + doins -r gfx + doins -r pyfpdb + + if use linguas_de; then + msgfmt pyfpdb/locale/fpdb-de_DE.po -o pyfpdb/locale/de.mo + fi + + if use linguas_hu; then + msgfmt pyfpdb/locale/fpdb-hu_HU.po -o pyfpdb/locale/hu.mo + fi + + domo pyfpdb/locale/*.mo + + doins readme.txt + + exeinto "${GAMES_DATADIR}"/${PN} + doexe run_fpdb.py + + dodir "${GAMES_BINDIR}" + dosym "${GAMES_DATADIR}"/${PN}/run_fpdb.py "${GAMES_BINDIR}"/${PN} + + newicon gfx/fpdb-icon.png ${PN}.png + make_desktop_entry ${PN} + + fperms +x "${GAMES_DATADIR}"/${PN}/pyfpdb/*.pyw + prepgamesdirs +} + +pkg_postinst() { + games_pkg_postinst + elog "Note that if you really want to use mysql or postgresql you will have to create" + elog "the database and user yourself and enter it into the fpdb config." + elog "You can find the instructions on the project's website." +} diff --git a/packaging/gentoo/metadata.xml b/packaging/gentoo/metadata.xml new file mode 100644 index 00000000..ebbe2f40 --- /dev/null +++ b/packaging/gentoo/metadata.xml @@ -0,0 +1,11 @@ + + + + + FPDB (Free Poker Database) is a free/open source suite of steadily growing tools to track and analyse your poker game. FPDB is able to import the hand histories that poker sites write to your computer, store additional data from each hand in a database for use in later analysis. + + + Enable dependencies for making graphs + + + diff --git a/packaging/windows/py27-links.txt b/packaging/windows/py27-links.txt new file mode 100644 index 00000000..8005ec8d --- /dev/null +++ b/packaging/windows/py27-links.txt @@ -0,0 +1,9 @@ +Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi +pywin 214 ... https://sourceforge.net/projects/pywin32/files/pywin32/Build%20214/pywin32-214.win32-py2.7.exe/download +matplotlib X ... not available as py27 as of 16aug2010: https://sourceforge.net/projects/matplotlib/files/matplotlib/ +pygtk X ... not available as py27 as of 16aug2010: http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/ +pycairo X ... not available as py27 as of 16aug2010: http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/ +pyGobject X ... not available as py27 as of 16aug2010: http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/ +py2exe 0.6.9 ... https://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download +psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.2.win32-py2.7-pg8.4.4-release.exe + diff --git a/packaging/windows/py2exeWalkthroughPython26.txt b/packaging/windows/py2exeWalkthroughPython26.txt index b023d80d..68a74c81 100644 --- a/packaging/windows/py2exeWalkthroughPython26.txt +++ b/packaging/windows/py2exeWalkthroughPython26.txt @@ -103,6 +103,7 @@ Step 4 Get the fpdb GIT tree ---------------------------- 4.1/ Best to take a copy to work with; following steps will assume that the fpdb folder is on the Desktop +4.2/ Edit the script in packaging/windows/py2exe_setup.py to set the fpdbver variable for this release 5.3/ Install correct Numpy for this build @@ -158,7 +159,7 @@ Step 6 Run py2exe to generate fpdb.exe 6.1/ Run the script to create the fpdb.exe bundle -dos> cd Desktop\fpdb\pyfpdb +dos> cd Desktop\fpdb\packaging\windows dos> c:\python26\python.exe py2exe_setup.py py2exe wait a while, watch lots of copying and whatever. @@ -183,16 +184,14 @@ Step 8 Drag out the completed bundle ------------------------------------ py2exe creates a new folder for the created software bundle, drag this out to the desktop for ease of working. -As far as I know you cannot rerun the build if the fpdb-yyyymmdd-exe exists in the tree, so dragging this out -also allows the build to re-run at step 6. -8.1/ Drag Desktop\fpdb\pyfpdb\fpdb-yyyymmdd-exe to Desktop\ +8.1/ Drag Desktop\fpdb\packaging\windows\fpdb-n.nn.nnn to Desktop\ Step 9 Initial run ------------------ -9.1/ Open the Desktop\fpdb-yyyymmdd-exe folder +9.1/ Open the Desktop\fpdb-n.nn.nnn folder 9.2/ In explorer...tools...folder options...View uncheck "Hide extensions for known file types" 9.3/ Double click run_fpdb.bat 9.4/ check the contents of pyfpdb\fpdb.exe.log, deal with any errors thrown @@ -222,7 +221,7 @@ pyfpdb/share/man Step 12 rename folder --------------------- -Rename the folder to something meaningful to the community. If you have built for NoSSE, append anyCPU to the directory name. +If needed, rename the folder to something meaningful to the community. If you have built for NoSSE, append anyCPU to the directory name. Step 13 Compress to executable archive diff --git a/pyfpdb/py2exe_setup.py b/packaging/windows/py2exe_setup.py similarity index 54% rename from pyfpdb/py2exe_setup.py rename to packaging/windows/py2exe_setup.py index a836ed76..8bf723a4 100644 --- a/pyfpdb/py2exe_setup.py +++ b/packaging/windows/py2exe_setup.py @@ -32,14 +32,10 @@ Py2exe script for fpdb. #HOW TO USE this script: # -#- cd to the folder where this script is stored, usually .../pyfpdb. -# [If there are build and dist subfolders present , delete them to get -# rid of earlier builds. Update: script now does this for you] -#- Run the script with "py2exe_setup.py py2exe" -#- You will frequently get messages about missing .dll files. E. g., -# MSVCP90.dll. These are somewhere in your windows install, so you -# can just copy them to your working folder. (or just assume other -# person will have them? any copyright issues with including them?) +#- cd to the folder where this script is stored, usually ...packaging/windows +#- Run the script with python "py2exe_setup.py py2exe" +#- You will frequently get messages about missing .dll files.just assume other +# person will have them? we have copyright issues including some dll's #- If it works, you'll have a new dir fpdb-YYYYMMDD-exe which should # contain 2 dirs; gfx and pyfpdb and run_fpdb.bat #- [ This bit is now automated: @@ -49,25 +45,10 @@ Py2exe script for fpdb. #- You can (should) then prune the etc/, lib/ and share/ folders to # remove components we don't need. (see output at end of program run) -# 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 /bin to /dist : -# -# libgobject-2.0-0.dll -# libgdk-win32-2.0-0.dll -# -# Now updated to work with python 2.6 + related dependencies # See walkthrough in packaging directory for versions used -# Updates to this script have broken python 2.5 compatibility (gio module, msvcr71 references now msvcp90) + +# steffeN: Doesnt seem necessary to gettext-ify this, but feel free to if you disagree +# Gimick: restructure to allow script to run from packaging/windows directory, and not to write to source pyfpdb import os @@ -85,33 +66,14 @@ import py2exe import glob import matplotlib import shutil -from datetime import date +#from datetime import date -origIsSystemDLL = py2exe.build_exe.isSystemDLL def isSystemDLL(pathname): #dwmapi appears to be vista-specific file, not XP if os.path.basename(pathname).lower() in ("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','pyfpdb',dist_dirname) 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): @@ -120,31 +82,64 @@ def test_and_remove(top): else: print "Unexpected file '"+top+"' found. Exiting." exit() + +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',distdir) and os.path.basename(os.getcwd()) == 'windows': + #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) -today = date.today().strftime('%Y%m%d') -print "\n" + r"Output will be created in \pyfpdb\ and \fpdb_"+today+'\\' -#print "Enter value for XXX (any length): ", # the comma means no newline -#xxx = sys.stdin.readline().rstrip() -dist_dirname = r'fpdb-' + today + '-exe' -dist_dir = r'..\fpdb-' + today + '-exe' -print +def copy_tree(source,destination): + source = source.replace('\\', '\\\\') + destination = destination.replace('\\', '\\\\') + print "*** Copying " + source + " to " + destination + " ***" + shutil.copytree( source, destination ) -# remove build and dist dirs if they exist +def copy_file(source,destination): + source = source.replace('\\', '\\\\') + destination = destination.replace('\\', '\\\\') + print "*** Copying " + source + " to " + destination + " ***" + shutil.copy( source, destination ) + + +fpdbver = '0.20.906' + +distdir = r'fpdb-' + fpdbver +rootdir = r'../../' #cwd is normally /packaging/windows +pydir = rootdir+'pyfpdb/' +gfxdir = rootdir+'gfx/' +sys.path.append( pydir ) # allows fpdb modules to be found by options/includes below + +print "\n" + r"Output will be created in "+distdir + +print "*** Cleaning working folders ***" test_and_remove('dist') test_and_remove('build') -test_and_remove('pyfpdb') +test_and_remove(distdir) -test_and_remove(dist_dirname) +print "*** Building now in dist folder ***" +origIsSystemDLL = py2exe.build_exe.isSystemDLL +py2exe.build_exe.isSystemDLL = isSystemDLL setup( name = 'fpdb', description = 'Free Poker DataBase', - version = '0.20.903', + version = fpdbver, - windows = [ {'script': 'fpdb.pyw', "icon_resources": [(1, "../gfx/fpdb_large_icon.ico")]}, - {'script': 'HUD_main.pyw', }, - {'script': 'Configuration.py', } + windows = [ {'script': pydir+'fpdb.pyw', "icon_resources": [(1, gfxdir+"fpdb_large_icon.ico")]}, + {'script': pydir+'HUD_main.pyw', }, + {'script': pydir+'Configuration.py', } ], options = {'py2exe': { @@ -157,74 +152,56 @@ setup( ,'PartyPokerToFpdb', 'PokerStarsToFpdb' ,'UltimateBetToFpdb', 'Win2dayToFpdb' ], - 'excludes' : ['_tkagg', '_agg2', 'cocoaagg', 'fltkagg'], # surely we need this? '_gtkagg' + 'excludes' : ['_tkagg', '_agg2', 'cocoaagg', 'fltkagg'], 'dll_excludes': ['libglade-2.0-0.dll', 'libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll' , 'msvcr90.dll', 'MSVCP90.dll', 'MSVCR90.dll','msvcr90.dll'], # these are vis c / c++ runtimes, and must not be redistributed } }, # files in 2nd value in tuple are moved to dir named in 1st value - #data_files updated for new locations of licences + readme nolonger exists - data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf', '../agpl-3.0.txt', '../fdl-1.2.txt', '../gpl-3.0.txt', '../gpl-2.0.txt', '../mit.txt', '../readme.txt']) - ,(dist_dir, [r'..\run_fpdb.bat']) - ,( dist_dir + r'\gfx', glob.glob(r'..\gfx\*.*') ) - # line below has problem with fonts subdir ('not a regular file') - #,(r'matplotlibdata', glob.glob(r'c:\python25\Lib\site-packages\matplotlib\mpl-data\*')) + # this code will not walk a tree + # Note: cwd for 1st value is packaging/windows/dist (this is confusing BTW) + # Note: only include files here which are to be put into the package pyfpdb folder or subfolders + + data_files = [('', glob.glob(rootdir+'*.txt')) + ,('', [pydir+'HUD_config.xml.example',pydir+'Cards01.png', pydir+'logging.conf']) ] + matplotlib.get_py2exe_datafiles() ) -# rename completed output package as pyfpdb -os.rename('dist', 'pyfpdb') +# ,(distdir, [rootdir+'run_fpdb.bat']) +# ,(distdir+r'\gfx', glob.glob(gfxdir+'*.*')) +# ] + +print "*** py2exe build phase complete ***" -# pull pytz zoneinfo into pyfpdb package folder -src_dir = r'c:\python26\Lib\site-packages\pytz\zoneinfo' -src_dir = src_dir.replace('\\', '\\\\') -dest_dir = os.path.join(r'pyfpdb', 'zoneinfo') -shutil.copytree( src_dir, dest_dir ) +# copy zone info and fpdb translation folders +copy_tree (r'c:\python26\Lib\site-packages\pytz\zoneinfo', os.path.join(r'dist', 'zoneinfo')) +copy_tree (pydir+r'locale', os.path.join(r'dist', 'locale')) -# shunt pyfpdb package over to the distribution folder -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 ) +# create distribution folder and populate with gfx + bat +copy_tree (gfxdir, os.path.join(distdir, 'gfx')) +copy_file (rootdir+'run_fpdb.bat', distdir) -# prompt for gtk location +print "*** Renaming dist folder as distribution pyfpdb folder ***" +dest = os.path.join(distdir, 'pyfpdb') +os.rename( 'dist', dest ) +print "*** copying GTK runtime ***" gtk_dir = "" while not os.path.exists(gtk_dir): - print "Enter directory name for GTK (e.g. c:\code\gtk_2.14.7-20090119)\n: ", # the comma means no newline + print "Enter directory name for GTK (e.g. c:/gtk) : ", # 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 ) +print "*** copying GTK runtime ***" +dest = os.path.join(distdir, 'pyfpdb') +copy_file(os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll'), dest ) +copy_file(os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll'), dest) +copy_tree(os.path.join(gtk_dir, 'etc'), os.path.join(dest, 'etc')) +copy_tree(os.path.join(gtk_dir, 'lib'), os.path.join(dest, 'lib')) +copy_tree(os.path.join(gtk_dir, 'share'), os.path.join(dest, 'share')) -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 ) - -print "\nIf py2exe was successful you should now have a new dir" -print dist_dirname+" in your pyfpdb dir" +print "*** All done! ***" +test_and_remove('build') +print distdir+" is in the pyfpdb dir" print """ The following dirs can probably removed to make the final package smaller: @@ -242,5 +219,3 @@ pyfpdb/share/themes/Default Use 7-zip to zip up the distribution and create a self extracting archive and that's it! """ - - diff --git a/pyfpdb/AbsoluteToFpdb.py b/pyfpdb/AbsoluteToFpdb.py index 31e21764..6b57b1e4 100755 --- a/pyfpdb/AbsoluteToFpdb.py +++ b/pyfpdb/AbsoluteToFpdb.py @@ -18,6 +18,9 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## +import L10n +_ = L10n.get_translation() + # TODO: I have no idea if AP has multi-currency options, i just copied the regex out of Everleaf converter for the currency symbols.. weeeeee - Eric import sys import logging @@ -42,7 +45,13 @@ class Absolute(HandHistoryConverter): #Seat 6 - FETS63 ($0.75 in chips) #Board [10s 5d Kh Qh 8c] - re_GameInfo = re.compile(ur"^Stage #(C?[0-9]+): (?PHoldem|HORSE)(?: \(1 on 1\)|)? ?(?PNo Limit|Pot Limit|Normal|)? ?(?P\$| €|)(?P[.0-9]+)/?(?:\$| €|)(?P[.0-9]+)?", re.MULTILINE) + re_GameInfo = re.compile(ur"""^Stage #(C?[0-9]+):\s+ + (?PHoldem|Seven\sCard\sHi\/L|HORSE) + (?:\s\(1\son\s1\)|)?\s+? + (?PNo Limit|Pot\sLimit|Normal|)?\s? + (?P\$|\s€|) + (?P[.0-9]+)/?(?:\$|\s€|)(?P[.0-9]+)? + """, re.MULTILINE|re.VERBOSE) re_HorseGameInfo = re.compile(ur"^Game Type: (?PLimit) (?PHoldem)", re.MULTILINE) # TODO: can set max seats via (1 on 1) to a known 2 .. re_HandInfo = re.compile(ur"^Stage #C?(?P[0-9]+): .*(?P\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d).*\n(Table: (?P.*) \(Real Money\))?", re.MULTILINE) @@ -50,12 +59,6 @@ class Absolute(HandHistoryConverter): re_Button = re.compile(ur"Seat #(?P
.+$)", re.MULTILINE) -# re_Button = re.compile(ur"^Seat (?P
[ a-zA-Z0-9]+) \d-max \(Real Money\)\nSeat (?P
[ a-zA-Z0-9]+) \d-max \(Real Money\)\nSeat (?P
[-\s\da-zA-Z]+)\s + (?P
[%(TAB)s]+)\s (\((?P.+)\)\s)?-\s - \$?(?P[.0-9]+)/\$?(?P[.0-9]+)\s(Ante\s\$?(?P[.0-9]+)\s)?-\s - \$?(?P[.0-9]+\sCap\s)? + [%(LS)s]?(?P[.0-9]+)/[%(LS)s]?(?P[.0-9]+)\s(Ante\s[%(LS)s]?(?P[.0-9]+)\s)?-\s + [%(LS)s]?(?P[.0-9]+\sCap\s)? (?P[a-zA-Z\/\'\s]+)\s-\s (?P\d+:\d+:\d+\s(?P\w+)\s-\s\d+/\d+/\d+|\d+:\d+\s(?P\w+)\s-\s\w+\,\s\w+\s\d+\,\s\d+) (?P\(partial\))?\n (?:.*?\n(?PHand\s\#(?P=HID)\shas\sbeen\scanceled))? - ''', re.VERBOSE|re.DOTALL) + ''' % substitutions, re.VERBOSE|re.DOTALL) re_TourneyExtraInfo = re.compile('''(((?P[^$]+)? - (?P\$)?(?P[.0-9]+)?\s*\+\s*\$?(?P[.0-9]+)? + (?P[%(LS)s])?(?P[.0-9]+)?\s*\+\s*[%(LS)s]?(?P[.0-9]+)? (\s(?P(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness)))? (\s(?PShootout))? (\s(?PSit\s&\sGo))? (\s\((?PTurbo)\))?)|(?P.+)) - ''', re.VERBOSE) + ''' % substitutions, re.VERBOSE) re_Button = re.compile('^The button is in seat #(?P+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pyfpdb/HUD_main.pyw b/pyfpdb/HUD_main.pyw old mode 100755 new mode 100644 index e42f0eb2..7d433a1c --- a/pyfpdb/HUD_main.pyw +++ b/pyfpdb/HUD_main.pyw @@ -60,6 +60,20 @@ elif os.name == 'nt': #import Tables import Hud +import locale +lang = locale.getdefaultlocale()[0][0:2] +print "lang:", lang +if lang == "en": + def _(string): + return string +else: + import gettext + try: + trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) + trans.install() + except IOError: + def _(string): + return string # get config and set up logger c = Configuration.Config(file=options.config, dbname=options.dbname) @@ -70,48 +84,80 @@ class HUD_main(object): """A main() object to own both the read_stdin thread and the gui.""" # This class mainly provides state for controlling the multiple HUDs. - def __init__(self, db_name = 'fpdb'): - print "\nHUD_main: starting ..." + def __init__(self, db_name='fpdb'): + print _("\nHUD_main: starting ...") self.db_name = db_name self.config = c - print "Logfile is " + os.path.join(self.config.dir_log, 'HUD-log.txt') - log.info("HUD_main starting: using db name = %s" % (db_name)) + print _("Logfile is ") + os.path.join(self.config.dir_log, 'HUD-log.txt') + log.info(_("HUD_main starting: using db name = %s") % (db_name)) try: if not options.errorsToConsole: fileName = os.path.join(self.config.dir_log, 'HUD-errors.txt') - print "Note: error output is being diverted to:\n"+fileName \ - + "\nAny major error will be reported there _only_.\n" - log.info("Note: error output is being diverted to:"+fileName) - log.info("Any major error will be reported there _only_.") + 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") + 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 + 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("client_moved", self.client_moved) + self.main_window.connect("client_resized", self.client_resized) + self.main_window.connect("client_destroyed", self.client_destroyed) + self.main_window.connect("game_changed", self.game_changed) + self.main_window.connect("table_changed", self.table_changed) self.main_window.connect("destroy", self.destroy) self.vb = gtk.VBox() - self.label = gtk.Label('Closing this window will exit from the HUD.') + 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.set_title(_("HUD Main Window")) + cards = os.path.join(os.getcwd(), '..','gfx','fpdb-cards.png') + if os.path.exists(cards): + self.main_window.set_icon_from_file(cards) + elif os.path.exists('/usr/share/pixmaps/fpdb-cards.png'): + self.main_window.set_icon_from_file('/usr/share/pixmaps/fpdb-cards.png') + else: + self.main_window.set_icon_stock(gtk.STOCK_HOME) self.main_window.show_all() + gobject.timeout_add(100, self.check_tables) + except: - log.error( "*** Exception in HUD_main.init() *** " ) + log.error("*** Exception in HUD_main.init() *** ") for e in traceback.format_tb(sys.exc_info()[2]): log.error(e) + def client_moved(self, widget, hud): + print "hud_main: client moved" + print hud, hud.table.name, "moved", hud.table.x, hud.table.y + + def client_resized(self, widget, hud): + print _("hud_main: Client resized") + print hud, hud.table.name, hud.table.x, hud.table.y + + def client_destroyed(self, widget, hud): # call back for terminating the main eventloop + print _("hud_main: Client destroyed") + self.kill_hud(None, hud.table.name) + + def game_changed(self, widget, hud): + print _("hud_main: Game changed.") + + def table_changed(self, widget, hud): + print _("hud_main: Table changed.") + self.kill_hud(None, hud.table.name) def destroy(self, *args): # call back for terminating the main eventloop - log.info("Terminating normally.") + log.info(_("Terminating normally.")) gtk.main_quit() def kill_hud(self, event, table): @@ -121,7 +167,12 @@ class HUD_main(object): self.hud_dict[table].main_window.destroy() self.vb.remove(self.hud_dict[table].tablehudlabel) del(self.hud_dict[table]) - self.main_window.resize(1,1) + self.main_window.resize(1, 1) + + def check_tables(self): + for hud in self.hud_dict.keys(): + self.hud_dict[hud].table.check_table(self.hud_dict[hud]) + return True def create_HUD(self, new_hand_id, table, table_name, max, poker_game, type, stat_dict, cards): """type is "ring" or "tour" used to set hud_params""" @@ -144,7 +195,7 @@ class HUD_main(object): self.hud_dict[table_name].update(new_hand_id, self.config) self.hud_dict[table_name].reposition_windows() except: - log.error( "*** Exception in HUD_main::idle_func() *** " + str(sys.exc_info()) ) + log.error("*** Exception in HUD_main::idle_func() *** " + str(sys.exc_info())) for e in traceback.format_tb(sys.exc_info()[2]): log.error(e) finally: @@ -210,12 +261,12 @@ class HUD_main(object): self.hero, self.hero_ids = {}, {} found = False - 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() t0 = time.time() t1 = t2 = t3 = t4 = t5 = t6 = t0 new_hand_id = string.rstrip(new_hand_id) - log.debug("Received hand no %s" % new_hand_id) + log.debug(_("Received hand no %s") % new_hand_id) if new_hand_id == "": # blank line means quit self.destroy() break # this thread is not always killed immediately with gtk.main_quit() @@ -234,12 +285,12 @@ class HUD_main(object): # get basic info about the new hand from the db # if there is a db error, complain, skip hand, and proceed - log.info("HUD_main.read_stdin: hand processing starting ...") + log.info(_("HUD_main.read_stdin: hand processing starting ...")) try: (table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \ self.db_connection.get_table_info(new_hand_id) except Exception: - log.error("db error: skipping %s" % new_hand_id) + log.error(_("db error: skipping %s" % new_hand_id)) continue t1 = time.time() @@ -254,18 +305,19 @@ class HUD_main(object): 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']) t2 = time.time() - 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], num_seats) + 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], num_seats) t3 = time.time() + try: self.hud_dict[temp_key].stat_dict = stat_dict except KeyError: # HUD instance has been killed off, key is stale - log.error('hud_dict[%s] was not found\n' % temp_key) - log.error('will not send hand\n') + log.error(_('hud_dict[%s] was not found\n') % temp_key) + log.error(_('will not send hand\n')) # Unlocks table, copied from end of function self.db_connection.connection.rollback() return - cards = self.db_connection.get_cards(new_hand_id) + cards = self.db_connection.get_cards(new_hand_id) t4 = time.time() comm_cards = self.db_connection.get_common_cards(new_hand_id) t5 = time.time() @@ -279,24 +331,20 @@ class HUD_main(object): else: # get stats using default params--also get cards self.db_connection.init_hud_stat_vars( self.hud_params['hud_days'], self.hud_params['h_hud_days'] ) - stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_params - ,self.hero_ids[site_id], num_seats) + stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_params, + self.hero_ids[site_id], num_seats) cards = self.db_connection.get_cards(new_hand_id) comm_cards = self.db_connection.get_common_cards(new_hand_id) if comm_cards != {}: # stud! cards['common'] = comm_cards['common'] - table_kwargs = dict(table_name = table_name, tournament = tour_number, table_number = tab_number) - search_string = getTableTitleRe(self.config, site_name, type, **table_kwargs) - # print "getTableTitleRe ", self.config, site_name, type, "=", search_string - tablewindow = Tables.Table(search_string, **table_kwargs) - + table_kwargs = dict(table_name=table_name, tournament=tour_number, table_number=tab_number) + tablewindow = Tables.Table(self.config, site_name, **table_kwargs) if tablewindow is None: # If no client window is found on the screen, complain and continue if type == "tour": table_name = "%s %s" % (tour_number, tab_number) -# log.error("HUD create: table name "+table_name+" not found, skipping.\n") - log.error("HUD create: table name %s not found, skipping." % table_name) + log.error(_("HUD create: table name %s not found, skipping.") % table_name) else: tablewindow.max = max tablewindow.site = site_name @@ -304,13 +352,15 @@ class HUD_main(object): if hasattr(tablewindow, 'number'): self.create_HUD(new_hand_id, tablewindow, temp_key, max, poker_game, type, stat_dict, cards) else: - log.error('Table "%s" no longer exists\n' % table_name) + log.error(_('Table "%s" no longer exists\n') % table_name) t6 = time.time() - log.info("HUD_main.read_stdin: hand read in %4.3f seconds (%4.3f,%4.3f,%4.3f,%4.3f,%4.3f,%4.3f)" - % (t6-t0,t1-t0,t2-t0,t3-t0,t4-t0,t5-t0,t6-t0)) + log.info(_("HUD_main.read_stdin: hand read in %4.3f seconds (%4.3f,%4.3f,%4.3f,%4.3f,%4.3f,%4.3f)") + % (t6 - t0,t1 - t0,t2 - t0,t3 - t0,t4 - t0,t5 - t0,t6 - t0)) self.db_connection.connection.rollback() - + if type == "tour": + tablewindow.check_table_no(None) + # Ray!! tablewindow::check_table_no expects a HUD as an argument! if __name__== "__main__": # start the HUD_main object diff --git a/pyfpdb/HUD_run_me.py b/pyfpdb/HUD_run_me.py index c9dfabaf..e6999e6b 100755 --- a/pyfpdb/HUD_run_me.py +++ b/pyfpdb/HUD_run_me.py @@ -42,13 +42,13 @@ def destroy(*args): # call back for terminating the main eventloop if __name__== "__main__": - sys.stderr.write("HUD_main starting\n") + sys.stderr.write(_("HUD_main starting\n")) try: HUD_main.db_name = sys.argv[1] except: HUD_main.db_name = 'fpdb' - sys.stderr.write("Using db name = %s\n" % (HUD_main.db_name)) + sys.stderr.write(_("Using db name = %s\n") % (HUD_main.db_name)) HUD_main.config = Configuration.Config() @@ -59,11 +59,11 @@ if __name__== "__main__": HUD_main.main_window = gtk.Window() HUD_main.main_window.connect("destroy", destroy) HUD_main.eb = gtk.VBox() - label = gtk.Label('Closing this window will exit from the HUD.') + label = gtk.Label(_('Closing this window will exit from the HUD.')) HUD_main.eb.add(label) HUD_main.main_window.add(HUD_main.eb) - HUD_main.main_window.set_title("HUD Main Window") + HUD_main.main_window.set_title(_("HUD Main Window")) HUD_main.main_window.show_all() gtk.main() diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index ee6d0bf2..ce0e4e7a 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -15,6 +15,9 @@ #along with this program. If not, see . #In the "official" distribution you can find the license in agpl-3.0.txt. +import L10n +_ = L10n.get_translation() + # TODO: get writehand() encoding correct import re @@ -44,16 +47,19 @@ class Hand(object): # Class Variables UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'} LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} - SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''} + SYMBOL = {'USD': '$', 'EUR': u'$', 'GBP': '$', 'T$': '', 'play': ''} MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'} - SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9, 'Partouche':10, 'Carbon':11 } + ACTION = {'ante': 1, 'small blind': 2, 'secondsb': 3, 'big blind': 4, 'both': 5, 'calls': 6, 'raises': 7, + 'bets': 8, 'stands pat': 9, 'folds': 10, 'checks': 11, 'discards': 12, 'bringin': 13, 'completes': 14} def __init__(self, config, sitename, gametype, handText, builtFrom = "HHC"): + #log.debug( _("Hand.init(): handText is ") + str(handText) ) self.config = config + self.saveActions = self.config.get_import_parameters().get('saveActions') #log = Configuration.get_logger("logging.conf", "db", log_dir=self.config.dir_log) self.sitename = sitename - self.siteId = self.SITEIDS[sitename] + self.siteId = self.config.get_site_id(sitename) self.stats = DerivedStats.DerivedStats(self) self.gametype = gametype self.startTime = 0 @@ -62,13 +68,14 @@ class Hand(object): self.cancelled = False self.dbid_hands = 0 self.dbid_pids = None + self.dbid_hpid = None self.dbid_gt = 0 self.tablename = "" self.hero = "" self.maxseats = None self.counted_seats = 0 self.buttonpos = 0 - + #tourney stuff self.tourNo = None self.tourneyId = None @@ -79,13 +86,13 @@ class Hand(object): self.fee = None # the Database code is looking for this one .. ? self.level = None self.mixed = None - self.speed = None - self.isRebuy = None - self.isAddOn = None - self.isKO = None + self.speed = "Normal" + self.isRebuy = False + self.isAddOn = False + self.isKO = False self.koBounty = None - self.isMatrix = None - self.isShootout = None + self.isMatrix = False + self.isShootout = False self.added = None self.addedCurrency = None self.tourneyComment = None @@ -94,7 +101,7 @@ class Hand(object): self.players = [] self.posted = [] self.tourneysPlayersIds = [] - + # Collections indexed by street names self.bets = {} self.lastBet = {} @@ -135,59 +142,59 @@ class Hand(object): self.is_duplicate = False # i.e. don't update hudcache if true def __str__(self): - vars = ( ("BB", self.bb), - ("SB", self.sb), - ("BUTTONPOS", self.buttonpos), - ("HAND NO.", self.handid), - ("SITE", self.sitename), - ("TABLE NAME", self.tablename), - ("HERO", self.hero), - ("MAXSEATS", self.maxseats), - ("LEVEL", self.level), - ("MIXED", self.mixed), - ("LASTBET", self.lastBet), - ("ACTION STREETS", self.actionStreets), - ("STREETS", self.streets), - ("ALL STREETS", self.allStreets), - ("COMMUNITY STREETS", self.communityStreets), - ("HOLE STREETS", self.holeStreets), - ("COUNTED SEATS", self.counted_seats), - ("DEALT", self.dealt), - ("SHOWN", self.shown), - ("MUCKED", self.mucked), - ("TOTAL POT", self.totalpot), - ("TOTAL COLLECTED", self.totalcollected), - ("RAKE", self.rake), - ("START TIME", self.startTime), - ("TOURNAMENT NO", self.tourNo), - ("TOURNEY ID", self.tourneyId), - ("TOURNEY TYPE ID", self.tourneyTypeId), - ("BUYIN", self.buyin), - ("BUYIN CURRENCY", self.buyinCurrency), - ("BUYIN CHIPS", self.buyInChips), - ("FEE", self.fee), - ("IS REBUY", self.isRebuy), - ("IS ADDON", self.isAddOn), - ("IS KO", self.isKO), - ("KO BOUNTY", self.koBounty), - ("IS MATRIX", self.isMatrix), - ("IS SHOOTOUT", self.isShootout), - ("TOURNEY COMMENT", self.tourneyComment), + vars = ( (_("BB"), self.bb), + (_("SB"), self.sb), + (_("BUTTONPOS"), self.buttonpos), + (_("HAND NO."), self.handid), + (_("SITE"), self.sitename), + (_("TABLE NAME"), self.tablename), + (_("HERO"), self.hero), + (_("MAXSEATS"), self.maxseats), + (_("LEVEL"), self.level), + (_("MIXED"), self.mixed), + (_("LASTBET"), self.lastBet), + (_("ACTION STREETS"), self.actionStreets), + (_("STREETS"), self.streets), + (_("ALL STREETS"), self.allStreets), + (_("COMMUNITY STREETS"), self.communityStreets), + (_("HOLE STREETS"), self.holeStreets), + (_("COUNTED SEATS"), self.counted_seats), + (_("DEALT"), self.dealt), + (_("SHOWN"), self.shown), + (_("MUCKED"), self.mucked), + (_("TOTAL POT"), self.totalpot), + (_("TOTAL COLLECTED"), self.totalcollected), + (_("RAKE"), self.rake), + (_("START TIME"), self.startTime), + (_("TOURNAMENT NO"), self.tourNo), + (_("TOURNEY ID"), self.tourneyId), + (_("TOURNEY TYPE ID"), self.tourneyTypeId), + (_("BUYIN"), self.buyin), + (_("BUYIN CURRENCY"), self.buyinCurrency), + (_("BUYIN CHIPS"), self.buyInChips), + (_("FEE"), self.fee), + (_("IS REBUY"), self.isRebuy), + (_("IS ADDON"), self.isAddOn), + (_("IS KO"), self.isKO), + (_("KO BOUNTY"), self.koBounty), + (_("IS MATRIX"), self.isMatrix), + (_("IS SHOOTOUT"), self.isShootout), + (_("TOURNEY COMMENT"), self.tourneyComment), ) - structs = ( ("PLAYERS", self.players), - ("STACKS", self.stacks), - ("POSTED", self.posted), - ("POT", self.pot), - ("SEATING", self.seating), - ("GAMETYPE", self.gametype), - ("ACTION", self.actions), - ("COLLECTEES", self.collectees), - ("BETS", self.bets), - ("BOARD", self.board), - ("DISCARDS", self.discards), - ("HOLECARDS", self.holecards), - ("TOURNEYS PLAYER IDS", self.tourneysPlayersIds), + structs = ( (_("PLAYERS"), self.players), + (_("STACKS"), self.stacks), + (_("POSTED"), self.posted), + (_("POT"), self.pot), + (_("SEATING"), self.seating), + (_("GAMETYPE"), self.gametype), + (_("ACTION"), self.actions), + (_("COLLECTEES"), self.collectees), + (_("BETS"), self.bets), + (_("BOARD"), self.board), + (_("DISCARDS"), self.discards), + (_("HOLECARDS"), self.holecards), + (_("TOURNEYS PLAYER IDS"), self.tourneysPlayersIds), ) str = '' for (name, var) in vars: @@ -210,7 +217,7 @@ dealt whether they were seen in a 'dealt to' line try: self.checkPlayerExists(player) except FpdbParseError, e: - print "[ERROR] Tried to add holecards for unknown player: %s" % (player,) + print _("[ERROR] Tried to add holecards for unknown player: %s") % (player,) return if dealt: self.dealt.add(player) @@ -229,7 +236,7 @@ dealt whether they were seen in a 'dealt to' line #Gametypes self.dbid_gt = db.getGameTypeId(self.siteId, self.gametype) - + if self.tourNo!=None: self.tourneyTypeId = db.createTourneyType(self) db.commit() @@ -239,7 +246,7 @@ dealt whether they were seen in a 'dealt to' line db.commit() #end def prepInsert - def insert(self, db): + def insert(self, db, printtest = False): """ Function to insert Hand into database Should not commit, and do minimal selects. Callers may want to cache commits db: a connected Database object""" @@ -259,11 +266,13 @@ db: a connected Database object""" hh['seats'] = len(self.dbid_pids) self.dbid_hands = db.storeHand(hh) - db.storeHandsPlayers(self.dbid_hands, self.dbid_pids, self.stats.getHandsPlayers()) - # TODO HandsActions - all actions for all players for all streets - self.actions - # HudCache data can be generated from HandsActions (HandsPlayers?) + self.dbid_hpid = db.storeHandsPlayers(self.dbid_hands, self.dbid_pids, + self.stats.getHandsPlayers(), printdata = printtest) + if self.saveActions: + db.storeHandsActions(self.dbid_hands, self.dbid_pids, self.dbid_hpid, + self.stats.getHandsActions(), printdata = printtest) else: - log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo']) + log.info(_("Hand.insert(): hid #: %s is a duplicate") % hh['siteHandNo']) self.is_duplicate = True # i.e. don't update hudcache raise FpdbHandDuplicate(hh['siteHandNo']) @@ -272,9 +281,143 @@ db: a connected Database object""" def select(self, handId): """ Function to create Hand object from database """ + c = cnxn.cursor() + + # We need at least sitename, gametype, handid + # for the Hand.__init__ + c.execute("""SELECT + s.name, + g.category, + g.base, + g.type, + g.limitType, + g.hilo, + round(g.smallBlind / 100.0,2), + round(g.bigBlind / 100.0,2), + round(g.smallBet / 100.0,2), + round(g.bigBet / 100.0,2), + s.currency, + h.boardcard1, + h.boardcard2, + h.boardcard3, + h.boardcard4, + h.boardcard5 + FROM + hands as h, + sites as s, + gametypes as g, + handsplayers as hp, + players as p + WHERE + h.id = %(handid)s + and g.id = h.gametypeid + and hp.handid = h.id + and p.id = hp.playerid + and s.id = p.siteid + limit 1""", {'handid':handid}) + #TODO: siteid should be in hands table - we took the scenic route through players here. + res = c.fetchone() + gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]} + c = Configuration.Config() + h = HoldemOmahaHand(config = c, hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid) + cards = map(Card.valueSuitFromCard, res[11:16] ) + if cards[0]: + h.setCommunityCards('FLOP', cards[0:3]) + if cards[3]: + h.setCommunityCards('TURN', [cards[3]]) + if cards[4]: + h.setCommunityCards('RIVER', [cards[4]]) + #[Card.valueSuitFromCard(x) for x in cards] + + # HandInfo : HID, TABLE + # BUTTON - why is this treated specially in Hand? + # answer: it is written out in hand histories + # still, I think we should record all the active seat positions in a seat_order array + c.execute("""SELECT + h.sitehandno as hid, + h.tablename as table, + h.startTime as startTime + FROM + hands as h + WHERE h.id = %(handid)s + """, {'handid':handid}) + res = c.fetchone() + h.handid = res[0] + h.tablename = res[1] + h.startTime = res[2] # automatically a datetime + + # PlayerStacks + c.execute("""SELECT + hp.seatno, + round(hp.winnings / 100.0,2) as winnings, + p.name, + round(hp.startcash / 100.0,2) as chips, + hp.card1,hp.card2, + hp.position + FROM + handsplayers as hp, + players as p + WHERE + hp.handid = %(handid)s + and p.id = hp.playerid + """, {'handid':handid}) + for (seat, winnings, name, chips, card1,card2, position) in c.fetchall(): + h.addPlayer(seat,name,chips) + if card1 and card2: + h.addHoleCards(map(Card.valueSuitFromCard, (card1,card2)), name, dealt=True) + if winnings > 0: + h.addCollectPot(name, winnings) + if position == 'B': + h.buttonpos = seat + # actions + c.execute("""SELECT + (ha.street,ha.actionno) as actnum, + p.name, + ha.street, + ha.action, + ha.allin, + round(ha.amount / 100.0,2) + FROM + handsplayers as hp, + handsactions as ha, + players as p + WHERE + hp.handid = %(handid)s + and ha.handsplayerid = hp.id + and p.id = hp.playerid + ORDER BY + ha.street,ha.actionno + """, {'handid':handid}) + res = c.fetchall() + for (actnum,player, streetnum, act, allin, amount) in res: + act=act.strip() + street = h.allStreets[streetnum+1] + if act==u'blind': + h.addBlind(player, 'big blind', amount) + # TODO: The type of blind is not recorded in the DB. + # TODO: preflop street name anomalies in Hand + elif act==u'fold': + h.addFold(street,player) + elif act==u'call': + h.addCall(street,player,amount) + elif act==u'bet': + h.addBet(street,player,amount) + elif act==u'check': + h.addCheck(street,player) + elif act==u'unbet': + pass + else: + print act, player, streetnum, allin, amount + # TODO : other actions + #hhc.readShowdownActions(self) + #hc.readShownCards(self) + h.totalPot() + h.rake = h.totalpot - h.totalcollected + + return h def addPlayer(self, seat, name, chips): """\ @@ -302,17 +445,14 @@ If a player has None chips he won't be added.""" log.debug("markStreets:\n"+ str(self.streets)) else: tmp = self.handText[0:100] - log.error("markstreets didn't match") - log.error(" - Assuming hand cancelled") + log.error(_("markstreets didn't match - Assuming hand %s was cancelled") % self.handid) self.cancelled = True - raise FpdbParseError("FpdbParseError: markStreets appeared to fail: First 100 chars: '%s'" % tmp) + raise FpdbParseError(_("FpdbParseError: markStreets appeared to fail: First 100 chars: '%s'") % tmp) def checkPlayerExists(self,player): if player not in [p[1] for p in self.players]: - print "DEBUG: checkPlayerExists %s fail" % player - raise FpdbParseError("checkPlayerExists: '%s' failed." % player) - - + print (_("DEBUG: checkPlayerExists %s fail on hand number %s") % (player, self.handid)) + raise FpdbParseError(_("checkPlayerExists: '%s fail on hand number %s") % (player, self.handid)) def setCommunityCards(self, street, cards): log.debug("setCommunityCards %s %s" %(street, cards)) @@ -349,12 +489,12 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio ante = re.sub(u',', u'', ante) #some sites have commas self.bets['BLINDSANTES'][player].append(Decimal(ante)) self.stacks[player] -= Decimal(ante) - act = (player, 'posts', "ante", ante, self.stacks[player]==0) + act = (player, 'ante', Decimal(ante), self.stacks[player]==0) self.actions['BLINDSANTES'].append(act) # 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): # if player is None, it's a missing small blind. # The situation we need to cover are: @@ -368,13 +508,13 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio if player is not None: amount = re.sub(u',', u'', amount) #some sites have commas self.stacks[player] -= Decimal(amount) - act = (player, 'posts', blindtype, amount, self.stacks[player]==0) + act = (player, blindtype, Decimal(amount), self.stacks[player]==0) self.actions['BLINDSANTES'].append(act) if blindtype == 'both': # work with the real amount. 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.pot.addCommonMoney(player, Decimal(self.sb)) @@ -393,7 +533,7 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio def addCall(self, street, player=None, amount=None): if amount: amount = re.sub(u',', u'', amount) #some sites have commas - log.debug("%s %s calls %s" %(street, player, amount)) + log.debug(_("%s %s calls %s") %(street, player, amount)) # Potentially calculate the amount of the call if not supplied # corner cases include if player would be all in if amount is not None: @@ -401,7 +541,7 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio #self.lastBet[street] = Decimal(amount) self.stacks[player] -= Decimal(amount) #print "DEBUG %s calls %s, stack %s" % (player, amount, self.stacks[player]) - act = (player, 'calls', amount, self.stacks[player]==0) + act = (player, 'calls', Decimal(amount), self.stacks[player]==0) self.actions[street].append(act) self.pot.addMoney(player, Decimal(amount)) @@ -462,11 +602,11 @@ Add a raise on [street] by [player] to [amountTo] Rb = Rt - C - Bc self._addRaise(street, player, C, Rb, Rt) - def _addRaise(self, street, player, C, Rb, Rt): - log.debug("%s %s raise %s" %(street, player, Rt)) + def _addRaise(self, street, player, C, Rb, Rt, action = 'raises'): + log.debug(_("%s %s raise %s") %(street, player, Rt)) self.bets[street][player].append(C + Rb) self.stacks[player] -= (C + Rb) - act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0) + act = (player, action, Rb, Rt, C, self.stacks[player]==0) self.actions[street].append(act) self.lastBet[street] = Rt # TODO check this is correct self.pot.addMoney(player, C+Rb) @@ -474,13 +614,13 @@ Add a raise on [street] by [player] to [amountTo] def addBet(self, street, player, amount): - log.debug("%s %s bets %s" %(street, player, amount)) + log.debug(_("%s %s bets %s") %(street, player, amount)) amount = re.sub(u',', u'', amount) #some sites have commas self.checkPlayerExists(player) self.bets[street][player].append(Decimal(amount)) self.stacks[player] -= Decimal(amount) #print "DEBUG %s bets %s, stack %s" % (player, amount, self.stacks[player]) - act = (player, 'bets', amount, self.stacks[player]==0) + act = (player, 'bets', Decimal(amount), self.stacks[player]==0) self.actions[street].append(act) self.lastBet[street] = Decimal(amount) self.pot.addMoney(player, Decimal(amount)) @@ -493,7 +633,7 @@ Add a raise on [street] by [player] to [amountTo] def addFold(self, street, player): - log.debug("%s %s folds" % (street, player)) + log.debug(_("%s %s folds") % (street, player)) self.checkPlayerExists(player) self.folded.add(player) self.pot.addFold(player) @@ -502,7 +642,7 @@ Add a raise on [street] by [player] to [amountTo] def addCheck(self, street, player): #print "DEBUG: %s %s checked" % (street, player) - logging.debug("%s %s checks" % (street, player)) + logging.debug(_("%s %s checks") % (street, player)) self.checkPlayerExists(player) self.actions[street].append((player, 'checks')) @@ -522,7 +662,7 @@ Add a raise on [street] by [player] to [amountTo] For when a player shows cards for any reason (for showdown or out of choice). Card ranks will be uppercased """ - log.debug("addShownCards %s hole=%s all=%s" % (player, cards, holeandboard)) + log.debug(_("addShownCards %s hole=%s all=%s") % (player, cards, holeandboard)) if cards is not None: self.addHoleCards(cards,player,shown, mucked) elif holeandboard is not None: @@ -543,7 +683,7 @@ Card ranks will be uppercased self.totalcollected = 0; #self.collected looks like [[p1,amount][px,amount]] for entry in self.collected: - self.totalcollected += Decimal(entry[1]) + self.totalcollected += Decimal(entry[1]) def getGameTypeAsString(self): """\ @@ -633,9 +773,9 @@ Map the tuple self.gametype onto the pokerstars string describing it try: timestr = datetime.datetime.strftime(self.startTime, '%Y/%m/%d %H:%M:%S ET') except TypeError: - print "*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:", self.startTime - print "*** Make sure your HandHistoryConverter is setting hand.startTime properly!" - print "*** Game String:", gs + print _("*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:"), self.startTime + print _("*** Make sure your HandHistoryConverter is setting hand.startTime properly!") + print _("*** Game String:"), gs return gs else: return gs + timestr @@ -683,9 +823,12 @@ class HoldemOmahaHand(Hand): hhc.readPlayerStacks(self) hhc.compilePlayerRegexs(self) hhc.markStreets(self) + if self.cancelled: return + hhc.readBlinds(self) + hhc.readAntes(self) hhc.readButton(self) hhc.readHeroCards(self) @@ -710,9 +853,9 @@ class HoldemOmahaHand(Hand): if handid is not None: self.select(handid) # Will need a handId else: - log.warning("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid") + log.warning(_("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid")) else: - log.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided") + log.warning(_("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided")) pass @@ -1016,7 +1159,7 @@ class DrawHand(Hand): self.bets['DEAL'][player].append(Decimal(amount)) self.stacks[player] -= Decimal(amount) #print "DEBUG %s posts, stack %s" % (player, self.stacks[player]) - act = (player, 'posts', blindtype, amount, self.stacks[player]==0) + act = (player, blindtype, Decimal(amount), self.stacks[player]==0) self.actions['BLINDSANTES'].append(act) self.pot.addMoney(player, Decimal(amount)) if blindtype == 'big blind': @@ -1046,10 +1189,10 @@ class DrawHand(Hand): def addDiscard(self, street, player, num, cards): self.checkPlayerExists(player) if cards: - act = (player, 'discards', num, cards) + act = (player, 'discards', Decimal(num), cards) self.discardDrawHoleCards(cards, player, street) else: - act = (player, 'discards', num) + act = (player, 'discards', Decimal(num)) self.actions[street].append(act) def holecardsAsSet(self, street, player): @@ -1208,7 +1351,8 @@ class StudHand(Hand): self.addHoleCards('FOURTH', player, open=[cards[3]], closed=[cards[2]], shown=shown, mucked=mucked) self.addHoleCards('FIFTH', player, open=[cards[4]], closed=cards[2:4], shown=shown, mucked=mucked) self.addHoleCards('SIXTH', player, open=[cards[5]], closed=cards[2:5], shown=shown, mucked=mucked) - self.addHoleCards('SEVENTH', player, open=[], closed=[cards[6]], shown=shown, mucked=mucked) + if len(cards) > 6: + self.addHoleCards('SEVENTH', player, open=[], closed=[cards[6]], shown=shown, mucked=mucked) def addPlayerCards(self, player, street, open=[], closed=[]): @@ -1224,7 +1368,7 @@ closed likewise, but known only to player self.checkPlayerExists(player) self.holecards[street][player] = (open, closed) except FpdbParseError, e: - print "[ERROR] Tried to add holecards for unknown player: %s" % (player,) + print _("[ERROR] Tried to add holecards for unknown player: %s") % (player,) # TODO: def addComplete(self, player, amount): def addComplete(self, street, player, amountTo): @@ -1233,7 +1377,7 @@ closed likewise, but known only to player """\ Add a complete on [street] by [player] to [amountTo] """ - log.debug("%s %s completes %s" % (street, player, amountTo)) + log.debug(_("%s %s completes %s") % (street, player, amountTo)) amountTo = re.sub(u',', u'', amountTo) #some sites have commas self.checkPlayerExists(player) Bp = self.lastBet['THIRD'] @@ -1241,7 +1385,7 @@ Add a complete on [street] by [player] to [amountTo] Rt = Decimal(amountTo) C = Bp - Bc Rb = Rt - C - self._addRaise(street, player, C, Rb, Rt) + self._addRaise(street, player, C, Rb, Rt, 'completes') #~ self.bets[street][player].append(C + Rb) #~ self.stacks[player] -= (C + Rb) #~ act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0) @@ -1251,10 +1395,10 @@ Add a complete on [street] by [player] to [amountTo] def addBringIn(self, player, bringin): if player is not None: - log.debug("Bringin: %s, %s" % (player , bringin)) + log.debug(_("Bringin: %s, %s") % (player , bringin)) self.bets['THIRD'][player].append(Decimal(bringin)) self.stacks[player] -= Decimal(bringin) - act = (player, 'bringin', bringin, self.stacks[player]==0) + act = (player, 'bringin', Decimal(bringin), self.stacks[player]==0) self.actions['THIRD'].append(act) self.lastBet['THIRD'] = Decimal(bringin) self.pot.addMoney(player, Decimal(bringin)) @@ -1431,8 +1575,8 @@ Add a complete on [street] by [player] to [amountTo] #Non hero folded before showdown, add first two downcards holecards = [u'0x', u'0x'] + holecards else: - log.warning("join_holecards: # of holecards should be either < 4, 4 or 7 - 5 and 6 should be impossible for anyone who is not a hero") - log.warning("join_holcards: holecards(%s): %s" %(player, holecards)) + log.warning(_("join_holecards: # of holecards should be either < 4, 4 or 7 - 5 and 6 should be impossible for anyone who is not a hero")) + log.warning(_("join_holcards: holecards(%s): %s") %(player, holecards)) return holecards @@ -1481,7 +1625,7 @@ class Pot(object): # Return any uncalled bet. committed = sorted([ (v,k) for (k,v) in self.committed.items()]) #print "DEBUG: committed: %s" % committed - #ERROR below. lastbet is correct in most cases, but wrong when + #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) @@ -1498,11 +1642,15 @@ class Pot(object): commitsall = sorted([(v,k) for (k,v) in self.committed.items() if v >0]) self.pots = [] - while len(commitsall) > 0: - commitslive = [(v,k) for (v,k) in commitsall if k in self.contenders] - v1 = commitslive[0][0] - self.pots += [sum([min(v,v1) for (v,k) in commitsall])] - commitsall = [((v-v1),k) for (v,k) in commitsall if v-v1 >0] + try: + while len(commitsall) > 0: + commitslive = [(v,k) for (v,k) in commitsall if k in self.contenders] + v1 = commitslive[0][0] + self.pots += [sum([min(v,v1) for (v,k) in commitsall])] + commitsall = [((v-v1),k) for (v,k) in commitsall if v-v1 >0] + except IndexError, e: + log.error(_("Pot.end(): Major failure while calculating pot: '%s'" % e)) + raise FpdbParseError(_("Pot.end(): Major failure while calculating pot: '%s'" % e)) # TODO: I think rake gets taken out of the pots. # so it goes: @@ -1515,9 +1663,9 @@ class Pot(object): if self.sym is None: self.sym = "C" if self.total is None: - print "DEBUG: call Pot.end() before printing pot total" + print _("DEBUG: call Pot.end() before printing pot total") # NB if I'm sure end() is idempotent, call it here. - raise FpdbParseError("FpdbError in printing Hand object") + raise FpdbParseError(_("FpdbError in printing Hand object")) ret = "Total pot %s%.2f" % (self.sym, self.total) if len(self.pots) < 2: @@ -1526,146 +1674,4 @@ class Pot(object): return ret + ''.join([ (" Side pot %s%.2f." % (self.sym, self.pots[x]) ) for x in xrange(1, len(self.pots)) ]) -def assemble(cnxn, handid): - c = cnxn.cursor() - # We need at least sitename, gametype, handid - # for the Hand.__init__ - c.execute(""" -select - s.name, - g.category, - g.base, - g.type, - g.limitType, - g.hilo, - round(g.smallBlind / 100.0,2), - round(g.bigBlind / 100.0,2), - round(g.smallBet / 100.0,2), - round(g.bigBet / 100.0,2), - s.currency, - h.boardcard1, - h.boardcard2, - h.boardcard3, - h.boardcard4, - h.boardcard5 -from - hands as h, - sites as s, - gametypes as g, - handsplayers as hp, - players as p -where - h.id = %(handid)s -and g.id = h.gametypeid -and hp.handid = h.id -and p.id = hp.playerid -and s.id = p.siteid -limit 1""", {'handid':handid}) - #TODO: siteid should be in hands table - we took the scenic route through players here. - res = c.fetchone() - gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]} - c = Configuration.Config() - h = HoldemOmahaHand(config = c, hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid) - cards = map(Card.valueSuitFromCard, res[11:16] ) - if cards[0]: - h.setCommunityCards('FLOP', cards[0:3]) - if cards[3]: - h.setCommunityCards('TURN', [cards[3]]) - if cards[4]: - h.setCommunityCards('RIVER', [cards[4]]) - #[Card.valueSuitFromCard(x) for x in cards] - - # HandInfo : HID, TABLE - # BUTTON - why is this treated specially in Hand? - # answer: it is written out in hand histories - # still, I think we should record all the active seat positions in a seat_order array - c.execute(""" -SELECT - h.sitehandno as hid, - h.tablename as table, - h.startTime as startTime -FROM - hands as h -WHERE h.id = %(handid)s -""", {'handid':handid}) - res = c.fetchone() - h.handid = res[0] - h.tablename = res[1] - h.startTime = res[2] # automatically a datetime - - # PlayerStacks - c.execute(""" -SELECT - hp.seatno, - round(hp.winnings / 100.0,2) as winnings, - p.name, - round(hp.startcash / 100.0,2) as chips, - hp.card1,hp.card2, - hp.position -FROM - handsplayers as hp, - players as p -WHERE - hp.handid = %(handid)s -and p.id = hp.playerid -""", {'handid':handid}) - for (seat, winnings, name, chips, card1,card2, position) in c.fetchall(): - h.addPlayer(seat,name,chips) - if card1 and card2: - h.addHoleCards(map(Card.valueSuitFromCard, (card1,card2)), name, dealt=True) - if winnings > 0: - h.addCollectPot(name, winnings) - if position == 'B': - h.buttonpos = seat - - - # actions - c.execute(""" -SELECT - (ha.street,ha.actionno) as actnum, - p.name, - ha.street, - ha.action, - ha.allin, - round(ha.amount / 100.0,2) -FROM - handsplayers as hp, - handsactions as ha, - players as p -WHERE - hp.handid = %(handid)s -and ha.handsplayerid = hp.id -and p.id = hp.playerid -ORDER BY - ha.street,ha.actionno -""", {'handid':handid}) - res = c.fetchall() - for (actnum,player, streetnum, act, allin, amount) in res: - act=act.strip() - street = h.allStreets[streetnum+1] - if act==u'blind': - h.addBlind(player, 'big blind', amount) - # TODO: The type of blind is not recorded in the DB. - # TODO: preflop street name anomalies in Hand - elif act==u'fold': - h.addFold(street,player) - elif act==u'call': - h.addCall(street,player,amount) - elif act==u'bet': - h.addBet(street,player,amount) - elif act==u'check': - h.addCheck(street,player) - elif act==u'unbet': - pass - else: - print act, player, streetnum, allin, amount - # TODO : other actions - - #hhc.readShowdownActions(self) - #hc.readShownCards(self) - h.totalPot() - h.rake = h.totalpot - h.totalcollected - - - return h diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 8faf5dfb..6f0d4890 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -15,6 +15,9 @@ #along with this program. If not, see . #In the "official" distribution you can find the license in agpl-3.0.txt. +import L10n +_ = L10n.get_translation() + import re import sys import traceback @@ -41,10 +44,6 @@ import Hand from Exceptions import FpdbParseError import Configuration -import gettext -gettext.install('fpdb') - - import pygtk import gtk @@ -62,8 +61,11 @@ class HandHistoryConverter(): # "utf_8" is more likely if there are funny characters codepage = "cp1252" + re_tzOffset = re.compile('^\w+[+-]\d{4}$') - def __init__(self, config, in_path = '-', out_path = '-', follow=False, index=0, autostart=True, starsArchive=False, ftpArchive=False): + # maybe archive params should be one archive param, then call method in specific converter. if archive: convert_archive() + def __init__( self, config, in_path = '-', out_path = '-', follow=False, index=0 + , autostart=True, starsArchive=False, ftpArchive=False, sitename="PokerStars" ): """\ in_path (default '-' = sys.stdin) out_path (default '-' = sys.stdout) @@ -71,8 +73,10 @@ follow : whether to tail -f the input""" self.config = config self.import_parameters = self.config.get_import_parameters() + self.sitename = sitename #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 site, %s subclass, in_path '%s'; out_path '%s'" + % (self.sitename, self.__class__, in_path, out_path) ) # should use self.filter, not self.sitename self.index = index self.starsArchive = starsArchive @@ -105,7 +109,7 @@ follow : whether to tail -f the input""" def __str__(self): return """ -HandHistoryConverter: '%(sitename)s' +HandHistoryConverter: '%(sitename)s' filetype '%(filetype)s' in_path '%(in_path)s' out_path '%(out_path)s' @@ -123,7 +127,7 @@ Otherwise, finish at EOF. starttime = time.time() if not self.sanityCheck(): - log.warning("Failed sanity check") + log.warning(_("Failed sanity check")) return try: @@ -131,17 +135,18 @@ Otherwise, finish at EOF. self.numErrors = 0 if self.follow: #TODO: See how summary files can be handled on the fly (here they should be rejected as before) - log.info("Tailing '%s'" % self.in_path) + log.info(_("Tailing '%s'") % self.in_path) for handText in self.tailHands(): try: self.processHand(handText) self.numHands += 1 except FpdbParseError, e: self.numErrors += 1 - log.warning("HHC.start(follow): processHand failed: Exception msg: '%s'" % e) + log.warning(_("HHC.start(follow): processHand failed: Exception msg: '%s'") % e) log.debug(handText) else: handsList = self.allHandsAsList() + log.debug( _("handsList is ") + str(handsList) ) log.info("Parsing %d hands" % len(handsList)) # Determine if we're dealing with a HH file or a Summary file # quick fix : empty files make the handsList[0] fail ==> If empty file, go on with HH parsing @@ -152,22 +157,22 @@ Otherwise, finish at EOF. self.processedHands.append(self.processHand(handText)) except FpdbParseError, e: self.numErrors += 1 - log.warning("HHC.start(): processHand failed: Exception msg: '%s'" % e) + log.warning(_("HHC.start(): processHand failed: Exception msg: '%s'") % e) log.debug(handText) self.numHands = len(handsList) endtime = time.time() - log.info("Read %d hands (%d failed) in %.3f seconds" % (self.numHands, self.numErrors, endtime - starttime)) + log.info(_("Read %d hands (%d failed) in %.3f seconds") % (self.numHands, self.numErrors, endtime - starttime)) else: self.parsedObjectType = "Summary" summaryParsingStatus = self.readSummaryInfo(handsList) endtime = time.time() if summaryParsingStatus : - log.info("Summary file '%s' correctly parsed (took %.3f seconds)" % (self.in_path, endtime - starttime)) + log.info(_("Summary file '%s' correctly parsed (took %.3f seconds)") % (self.in_path, endtime - starttime)) else : - log.warning("Error converting summary file '%s' (took %.3f seconds)" % (self.in_path, endtime - starttime)) + log.warning(_("Error converting summary file '%s' (took %.3f seconds)") % (self.in_path, endtime - starttime)) except IOError, ioe: - log.exception("Error converting '%s'" % self.in_path) + log.exception(_("Error converting '%s'") % self.in_path) finally: if self.out_fh != sys.stdout: self.out_fh.close() @@ -198,7 +203,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py. time.sleep(interval) fd.seek(where) else: - log.debug("%s changed inode numbers from %d to %d" % (self.in_path, fd_results[1], st_results[1])) + log.debug(_("%s changed inode numbers from %d to %d") % (self.in_path, fd_results[1], st_results[1])) fd = codecs.open(self.in_path, 'r', self.codepage) fd.seek(where) else: @@ -242,20 +247,31 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py. self.readFile() self.obs = self.obs.strip() self.obs = self.obs.replace('\r\n', '\n') + # maybe archive params should be one archive param, then call method in specific converter? + # if self.archive: + # self.obs = self.convert_archive(self.obs) if self.starsArchive == True: - log.debug("Converting starsArchive format to readable") + log.debug(_("Converting starsArchive format to readable")) m = re.compile('^Hand #\d+', re.MULTILINE) 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) + log.debug(_("Converting ftpArchive format to readable")) + # Remove ******************** # 1 ************************* + m = re.compile('\*{20}\s#\s\d+\s\*{25}\s+', re.MULTILINE) self.obs = m.sub('', self.obs) if self.obs is None or self.obs == "": - log.info("Read no hands.") + log.error(_("Read no hands.")) return [] - return re.split(self.re_SplitHands, self.obs) + handlist = re.split(self.re_SplitHands, self.obs) + # Some HH formats leave dangling text after the split + # ie. (split) EOL + # Remove this dangler if less than 50 characters and warn in the log + if len(handlist[-1]) <= 50: + handlist.pop() + log.warn(_("Removing text < 50 characters")) + return handlist def processHand(self, handText): gametype = self.determineGameType(handText) @@ -264,14 +280,15 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py. l = None if gametype is None: gametype = "unmatched" - # TODO: not ideal, just trying to not error. - # TODO: Need to count failed hands. + # TODO: not ideal, just trying to not error. Throw ParseException? + self.numErrors += 1 else: # See if gametype is supported. type = gametype['type'] base = gametype['base'] limit = gametype['limitType'] l = [type] + [base] + [limit] + if l in self.readSupportedGames(): if gametype['base'] == 'hold': log.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)") @@ -281,15 +298,15 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py. elif gametype['base'] == 'draw': hand = Hand.DrawHand(self.config, self, self.sitename, gametype, handText) else: - log.info("Unsupported game type: %s" % gametype) + log.info(_("Unsupported game type: %s" % gametype)) + raise FpdbParseError(_("Unsupported game type: %s" % gametype)) if hand: #hand.writeHand(self.out_fh) return hand else: - log.info("Unsupported game type: %s" % gametype) + log.error(_("Unsupported game type: %s" % gametype)) # TODO: pity we don't know the HID at this stage. Log the entire hand? - # From the log we can deduce that it is the hand after the one before :) # These functions are parse actions that may be overridden by the inheriting class @@ -318,33 +335,104 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py. or None if we fail to get the info """ #TODO: which parts are optional/required? - # Read any of: - # HID HandID - # TABLE Table name - # SB small blind - # BB big blind - # GAMETYPE gametype - # YEAR MON DAY HR MIN SEC datetime - # BUTTON button seat number def readHandInfo(self, hand): abstract + """Read and set information about the hand being dealt, and set the correct + variables in the Hand object 'hand + + * hand.startTime - a datetime object + * hand.handid - The site identified for the hand - a string. + * hand.tablename + * hand.buttonpos + * hand.maxseats + * hand.mixed + + Tournament fields: + + * hand.tourNo - The site identified tournament id as appropriate - a string. + * hand.buyin + * hand.fee + * hand.buyinCurrency + * hand.koBounty + * hand.isKO + * hand.level + """ + #TODO: which parts are optional/required? - # Needs to return a list of lists in the format - # [['seat#', 'player1name', 'stacksize'] ['seat#', 'player2name', 'stacksize'] [...]] def readPlayerStacks(self, hand): abstract + """This function is for identifying players at the table, and to pass the + information on to 'hand' via Hand.addPlayer(seat, name, chips) + + At the time of writing the reference function in the PS converter is: + log.debug("readPlayerStacks") + m = self.re_PlayerInfo.finditer(hand.handText) + for a in m: + hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) + + Which is pretty simple because the hand history format is consistent. Other hh formats aren't so nice. + + This is the appropriate place to identify players that are sitting out and ignore them + + *** NOTE: You may find this is a more appropriate place to set hand.maxseats *** + """ def compilePlayerRegexs(self): abstract - """Compile dynamic regexes -- these explicitly match known player names and must be updated if a new player joins""" + """Compile dynamic regexes -- compile player dependent regexes. + + Depending on the ambiguity of lines you may need to match, and the complexity of + player names - we found that we needed to recompile some regexes for player actions so that they actually contained the player names. + + eg. + We need to match the ante line: + antes $1.00 + + But is actually named + + YesI antes $4000 - A perfectly legal playername + + Giving: + + YesI antes $4000 antes $1.00 + + Which without care in your regexes most people would match 'YesI' and not 'YesI antes $4000' + """ # Needs to return a MatchObject with group names identifying the streets into the Hand object # so groups are called by street names 'PREFLOP', 'FLOP', 'STREET2' etc # blinds are done seperately def markStreets(self, hand): abstract + """For dividing the handText into sections. + + The function requires you to pass a MatchObject with groups specifically labeled with + the 'correct' street names. + + The Hand object will use the various matches for assigning actions to the correct streets. + + Flop Based Games: + PREFLOP, FLOP, TURN, RIVER + + Draw Based Games: + PREDEAL, DEAL, DRAWONE, DRAWTWO, DRAWTHREE + + Stud Based Games: + ANTES, THIRD, FOURTH, FIFTH, SIXTH, SEVENTH + + The Stars HHC has a good reference implementation + """ #Needs to return a list in the format # ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb, # addtional players are assumed to post a bb oop def readBlinds(self, hand): abstract + """Function for reading the various blinds from the hand history. + + Pass any small blind to hand.addBlind(, "small blind", ) + - unless it is a single dead small blind then use: + hand.addBlind(, 'secondsb', ) + Pass any big blind to hand.addBlind(, "big blind", ) + Pass any play posting both big and small blinds to hand.addBlind(, 'both', ) + """ def readAntes(self, hand): abstract + """Function for reading the antes from the hand history and passing the hand.addAnte""" def readBringIn(self, hand): abstract def readButton(self, hand): abstract def readHeroCards(self, hand): abstract @@ -390,7 +478,7 @@ or None if we fail to get the info """ sane = True if self.in_path != '-' and self.out_path == self.in_path: - print "HH Sanity Check: output and input files are the same, check config" + print _("HH Sanity Check: output and input files are the same, check config") sane = False @@ -401,18 +489,6 @@ or None if we fail to get the info """ self.filetype = filetype self.codepage = codepage - #This function doesn't appear to be used - def splitFileIntoHands(self): - hands = [] - self.obs = self.obs.strip() - list = self.re_SplitHands.split(self.obs) - list.pop() #Last entry is empty - for l in list: -# print "'" + l + "'" - hands = hands + [Hand.Hand(self.config, self.sitename, self.gametype, l)] - # TODO: This looks like it could be replaced with a list comp.. ? - return hands - def __listof(self, x): if isinstance(x, list) or isinstance(x, tuple): return x @@ -425,7 +501,7 @@ or None if we fail to get the info """ if self.filetype == "text": if self.in_path == '-': # read from stdin - log.debug("Reading stdin with %s" % self.codepage) # is this necessary? or possible? or what? + log.debug(_("Reading stdin with %s") % self.codepage) # is this necessary? or possible? or what? in_fh = codecs.getreader('cp1252')(sys.stdin) else: for kodec in self.__listof(self.codepage): @@ -440,7 +516,8 @@ or None if we fail to get the info """ except: pass else: - print "unable to read file with any codec in list!", self.in_path + print _("unable to read file with any codec in list!"), self.in_path + self.obs = "" elif self.filetype == "xml": doc = xml.dom.minidom.parse(filename) self.doc = doc @@ -500,17 +577,32 @@ or None if we fail to get the info """ @staticmethod def changeTimezone(time, givenTimezone, wantedTimezone): - #print "raw time:",time, "given TZ:", givenTimezone + """Takes a givenTimezone in format AAA or AAA+HHMM where AAA is a standard timezone + and +HHMM is an optional offset (+/-) in hours (HH) and minutes (MM) + (See OnGameToFpdb.py for example use of the +HHMM part) + Tries to convert the time parameter (with no timezone) from the givenTimezone to + the wantedTimeZone (currently only allows "UTC") + """ + log.debug( _("raw time:")+str(time) + _(" given TZ:")+str(givenTimezone) ) if wantedTimezone=="UTC": wantedTimezone = pytz.utc else: raise Error #TODO raise appropriate error - + + givenTZ = None + if HandHistoryConverter.re_tzOffset.match(givenTimezone): + offset = int(givenTimezone[-5:]) + givenTimezone = givenTimezone[0:-5] + log.debug( _("changeTimeZone: offset=") + str(offset) ) + else: offset=0 + if givenTimezone=="ET": - givenTimezone = timezone('US/Eastern') + givenTZ = timezone('US/Eastern') elif givenTimezone=="CET": - givenTimezone = timezone('Europe/Berlin') + givenTZ = timezone('Europe/Berlin') #Note: Daylight Saving Time is standardised across the EU so this should be fine + elif givenTimezone == 'GMT': # Greenwich Mean Time (same as UTC - no change to time) + givenTZ = timezone('GMT') elif givenTimezone == 'HST': # Hawaiian Standard Time pass elif givenTimezone == 'AKT': # Alaska Time @@ -544,23 +636,27 @@ or None if we fail to get the info """ elif givenTimezone == 'JST': # Japan Standard Time pass elif givenTimezone == 'AWST': # Australian Western Standard Time - givenTimezone = timezone('Australia/West') + givenTZ = timezone('Australia/West') elif givenTimezone == 'ACST': # Australian Central Standard Time - givenTimezone = timezone('Australia/Darwin') + givenTZ = timezone('Australia/Darwin') elif givenTimezone == 'AEST': # Australian Eastern Standard Time # Each State on the East Coast has different DSTs. # Melbournce is out because I don't like AFL, Queensland doesn't have DST # ACT is full of politicians and Tasmania will never notice. # Using Sydney. - givenTimezone = timezone('Australia/Sydney') + givenTZ = timezone('Australia/Sydney') elif givenTimezone == 'NZT': # New Zealand Time pass else: raise Error #TODO raise appropriate error - localisedTime = givenTimezone.localize(time) - utcTime = localisedTime.astimezone(wantedTimezone) - #print "utcTime:",utcTime + if givenTZ is None: + raise Error #TODO raise appropriate error + # (or just return time unchanged?) + + localisedTime = givenTZ.localize(time) + utcTime = localisedTime.astimezone(wantedTimezone) + datetime.timedelta(seconds=-3600*(offset/100)-60*(offset%100)) + log.debug( _("utcTime:")+str(utcTime) ) return utcTime #end @staticmethod def changeTimezone @@ -572,12 +668,23 @@ or None if we fail to get the info """ else: return table_name - + @staticmethod + def getTableNoRe(tournament): + "Returns string to search window title for tournament table no." +# Full Tilt: $30 + $3 Tournament (181398949), Table 1 - 600/1200 Ante 100 - Limit Razz +# PokerStars: WCOOP 2nd Chance 02: $1,050 NLHE - Tournament 307521826 Table 1 - Blinds $30/$60 + return "%s.+Table (\d+)" % (tournament, ) def getTableTitleRe(config, sitename, *args, **kwargs): "Returns string to search in windows titles for current site" return getSiteHhc(config, sitename).getTableTitleRe(*args, **kwargs) +def getTableNoRe(config, sitename, *args, **kwargs): + "Returns string to search window titles for tournament table no." + return getSiteHhc(config, sitename).getTableNoRe(*args, **kwargs) + + + def getSiteHhc(config, sitename): "Returns HHC class for current site" hhcName = config.supported_sites[sitename].converter @@ -593,13 +700,13 @@ def get_out_fh(out_path, parameters): 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 + 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) + 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)) + log.error(_("out_path %s couldn't be opened") % (out_path)) else: return(sys.stdout) diff --git a/pyfpdb/Hello.py b/pyfpdb/Hello.py index 5312bd3f..1dd48cb4 100644 --- a/pyfpdb/Hello.py +++ b/pyfpdb/Hello.py @@ -41,10 +41,10 @@ from Mucked import Aux_Seats class Hello(Aux_Window): """A 'Hello World' Aux_Window demo.""" def create(self): - print "creating Hello" + print _("creating Hello") # This demo simply creates a label in a window. self.container = gtk.Window() - self.container.add(gtk.Label("Hello World")) + self.container.add(gtk.Label(_("Hello World"))) # and shows it. There is no functionality. self.container.show_all() @@ -62,7 +62,7 @@ class Hello_plus(Aux_Window): # get the site we are playing from the HUD self.site = hud.site - print "site =", hud.site # print it to the terminal, to make sure + print _("site ="), hud.site # print it to the terminal, to make sure # now get our screen name for that site from the configuration # wrap it in a try/except in case screen name isn't set up in the config file @@ -70,7 +70,7 @@ class Hello_plus(Aux_Window): site_params = self.config.get_site_parameters(self.hud.site) self.hero = site_params['screen_name'] except: - self.hero = 'YOUR NAME HERE' + self.hero = _('YOUR NAME HERE') print "hero =", self.hero @@ -101,7 +101,7 @@ class Hello_plus(Aux_Window): # Here, we just update the label in our aux_window from the number of # hands played that was updated in the "update_data()" function. - self.label.set_text("Hello %s\nYou have played %d hands\n on %s." % (self.hero, self.hands_played, self.site)) + self.label.set_text(_("Hello %s\nYou have played %d hands\n on %s.") % (self.hero, self.hands_played, self.site)) class Hello_Seats(Aux_Seats): """A 'Hello World' Seat_Window demo.""" diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index a92682e7..56d184d5 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -22,6 +22,10 @@ Create and manage the hud overlays. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## + +import L10n +_ = L10n.get_translation() + # Standard Library modules import os import sys @@ -43,13 +47,13 @@ if os.name == 'nt': import win32api # FreePokerTools modules -import Tables # needed for testing only import Configuration import Stats import Mucked import Database #import HUD_main + def importName(module_name, name): """Import a named object 'name' from module 'module_name'.""" # Recipe 16.3 in the Python Cookbook, 2nd ed. Thanks!!!! @@ -60,14 +64,15 @@ def importName(module_name, name): return None return(getattr(module, name)) -class Hud: +class Hud: def __init__(self, parent, table, max, poker_game, config, db_connection): # __init__ is (now) intended to be called from the stdin thread, so it # cannot touch the gui - if parent is None: # running from cli .. + if parent is None: # running from cli .. self.parent = self - self.parent = parent + else: + self.parent = parent self.table = table self.config = config self.poker_game = poker_game @@ -79,11 +84,11 @@ class Hud: self.mw_created = False self.hud_params = parent.hud_params - self.stat_windows = {} self.popup_windows = {} self.aux_windows = [] + # configure default font and colors from the configuration (font, font_size) = config.get_default_font(self.table.site) self.colors = config.get_default_colors(self.table.site) self.hud_ui = config.get_hud_ui_parameters() @@ -96,6 +101,7 @@ class Hud: # do we need to add some sort of condition here for dealing with a request for a font that doesn't exist? game_params = config.get_game_parameters(self.poker_game) + # if there are AUX windows configured, set them up (Ray knows how this works, if anyone needs info) if not game_params['aux'] == [""]: for aux in game_params['aux']: aux_params = config.get_aux_parameters(aux) @@ -107,14 +113,16 @@ class Hud: self.creation_attrs = None def create_mw(self): - # Set up a main window for this this instance of the HUD win = gtk.Window() + win.set_skip_taskbar_hint(True) # invisible to taskbar win.set_gravity(gtk.gdk.GRAVITY_STATIC) - win.set_title("%s FPDBHUD" % (self.table.name)) - win.set_skip_taskbar_hint(True) - win.set_decorated(False) - win.set_opacity(self.colors["hudopacity"]) + win.set_title("%s FPDBHUD" % (self.table.name)) # give it a title that we can easily filter out in the window list when Table search code is looking + win.set_decorated(False) # kill titlebars + win.set_opacity(self.colors["hudopacity"]) # set it to configured hud opacity + win.set_focus(None) + win.set_focus_on_map(False) + win.set_accept_focus(False) eventbox = gtk.EventBox() label = gtk.Label(self.hud_ui['label']) @@ -122,6 +130,7 @@ class Hud: win.add(eventbox) eventbox.add(label) + # set it to the desired color of the HUD for this site label.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor) label.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor) @@ -129,158 +138,160 @@ class Hud: eventbox.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor) self.main_window = win + # move it to the table window's X/Y position (0,0 on the table window usually) self.main_window.move(self.table.x, self.table.y) # A popup menu for the main window +# This menu code has become extremely long - is there a better way to do this? menu = gtk.Menu() - killitem = gtk.MenuItem('Kill This HUD') + killitem = gtk.MenuItem(_('Kill This HUD')) menu.append(killitem) if self.parent is not None: killitem.connect("activate", self.parent.kill_hud, self.table_name) - saveitem = gtk.MenuItem('Save HUD Layout') + saveitem = gtk.MenuItem(_('Save HUD Layout')) menu.append(saveitem) saveitem.connect("activate", self.save_layout) - repositem = gtk.MenuItem('Reposition StatWindows') + repositem = gtk.MenuItem(_('Reposition StatWindows')) menu.append(repositem) repositem.connect("activate", self.reposition_windows) - aggitem = gtk.MenuItem('Show Player Stats') + aggitem = gtk.MenuItem(_('Show Player Stats')) menu.append(aggitem) self.aggMenu = gtk.Menu() aggitem.set_submenu(self.aggMenu) # set agg_bb_mult to 1 to stop aggregation - item = gtk.CheckMenuItem('For This Blind Level Only') + item = gtk.CheckMenuItem(_('For This Blind Level Only')) self.aggMenu.append(item) - item.connect("activate", self.set_aggregation, ('P',1)) + item.connect("activate", self.set_aggregation, ('P', 1)) setattr(self, 'h_aggBBmultItem1', item) - # - item = gtk.MenuItem('For Multiple Blind Levels:') + + item = gtk.MenuItem(_('For Multiple Blind Levels:')) self.aggMenu.append(item) - # - item = gtk.CheckMenuItem(' 0.5 to 2.0 x Current Blinds') + + item = gtk.CheckMenuItem(_(' 0.5 to 2.0 x Current Blinds')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('P',2)) setattr(self, 'h_aggBBmultItem2', item) - # - item = gtk.CheckMenuItem(' 0.33 to 3.0 x Current Blinds') + + item = gtk.CheckMenuItem(_(' 0.33 to 3.0 x Current Blinds')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('P',3)) setattr(self, 'h_aggBBmultItem3', item) - # - item = gtk.CheckMenuItem(' 0.1 to 10 x Current Blinds') + + item = gtk.CheckMenuItem(_(' 0.1 to 10 x Current Blinds')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('P',10)) setattr(self, 'h_aggBBmultItem10', item) - # - item = gtk.CheckMenuItem(' All Levels') + + item = gtk.CheckMenuItem(_(' All Levels')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('P',10000)) setattr(self, 'h_aggBBmultItem10000', item) - # - item = gtk.MenuItem('For #Seats:') + + item = gtk.MenuItem(_('For #Seats:')) self.aggMenu.append(item) - # - item = gtk.CheckMenuItem(' Any Number') + + item = gtk.CheckMenuItem(_(' Any Number')) self.aggMenu.append(item) item.connect("activate", self.set_seats_style, ('P','A')) setattr(self, 'h_seatsStyleOptionA', item) - # - item = gtk.CheckMenuItem(' Custom') + + item = gtk.CheckMenuItem(_(' Custom')) self.aggMenu.append(item) item.connect("activate", self.set_seats_style, ('P','C')) setattr(self, 'h_seatsStyleOptionC', item) - # - item = gtk.CheckMenuItem(' Exact') + + item = gtk.CheckMenuItem(_(' Exact')) self.aggMenu.append(item) item.connect("activate", self.set_seats_style, ('P','E')) setattr(self, 'h_seatsStyleOptionE', item) - # - item = gtk.MenuItem('Since:') + + item = gtk.MenuItem(_('Since:')) self.aggMenu.append(item) - # - item = gtk.CheckMenuItem(' All Time') + + item = gtk.CheckMenuItem(_(' All Time')) self.aggMenu.append(item) item.connect("activate", self.set_hud_style, ('P','A')) setattr(self, 'h_hudStyleOptionA', item) - # - item = gtk.CheckMenuItem(' Session') + + item = gtk.CheckMenuItem(_(' Session')) self.aggMenu.append(item) item.connect("activate", self.set_hud_style, ('P','S')) setattr(self, 'h_hudStyleOptionS', item) - # - item = gtk.CheckMenuItem(' %s Days' % (self.hud_params['h_hud_days'])) + + item = gtk.CheckMenuItem(_(' %s Days') % (self.hud_params['h_hud_days'])) self.aggMenu.append(item) item.connect("activate", self.set_hud_style, ('P','T')) setattr(self, 'h_hudStyleOptionT', item) - aggitem = gtk.MenuItem('Show Opponent Stats') + aggitem = gtk.MenuItem(_('Show Opponent Stats')) menu.append(aggitem) self.aggMenu = gtk.Menu() aggitem.set_submenu(self.aggMenu) # set agg_bb_mult to 1 to stop aggregation - item = gtk.CheckMenuItem('For This Blind Level Only') + item = gtk.CheckMenuItem(_('For This Blind Level Only')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('O',1)) setattr(self, 'aggBBmultItem1', item) - # - item = gtk.MenuItem('For Multiple Blind Levels:') + + item = gtk.MenuItem(_('For Multiple Blind Levels:')) self.aggMenu.append(item) - # - item = gtk.CheckMenuItem(' 0.5 to 2.0 x Current Blinds') + + item = gtk.CheckMenuItem(_(' 0.5 to 2.0 x Current Blinds')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('O',2)) setattr(self, 'aggBBmultItem2', item) - # - item = gtk.CheckMenuItem(' 0.33 to 3.0 x Current Blinds') + + item = gtk.CheckMenuItem(_(' 0.33 to 3.0 x Current Blinds')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('O',3)) setattr(self, 'aggBBmultItem3', item) - # - item = gtk.CheckMenuItem(' 0.1 to 10 x Current Blinds') + + item = gtk.CheckMenuItem(_(' 0.1 to 10 x Current Blinds')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('O',10)) setattr(self, 'aggBBmultItem10', item) - # - item = gtk.CheckMenuItem(' All Levels') + + item = gtk.CheckMenuItem(_(' All Levels')) self.aggMenu.append(item) item.connect("activate", self.set_aggregation, ('O',10000)) setattr(self, 'aggBBmultItem10000', item) - # - item = gtk.MenuItem('For #Seats:') + + item = gtk.MenuItem(_('For #Seats:')) self.aggMenu.append(item) - # - item = gtk.CheckMenuItem(' Any Number') + + item = gtk.CheckMenuItem(_(' Any Number')) self.aggMenu.append(item) item.connect("activate", self.set_seats_style, ('O','A')) setattr(self, 'seatsStyleOptionA', item) - # - item = gtk.CheckMenuItem(' Custom') + + item = gtk.CheckMenuItem(_(' Custom')) self.aggMenu.append(item) item.connect("activate", self.set_seats_style, ('O','C')) setattr(self, 'seatsStyleOptionC', item) - # - item = gtk.CheckMenuItem(' Exact') + + item = gtk.CheckMenuItem(_(' Exact')) self.aggMenu.append(item) item.connect("activate", self.set_seats_style, ('O','E')) setattr(self, 'seatsStyleOptionE', item) - # - item = gtk.MenuItem('Since:') + + item = gtk.MenuItem(_('Since:')) self.aggMenu.append(item) - # - item = gtk.CheckMenuItem(' All Time') + + item = gtk.CheckMenuItem(_(' All Time')) self.aggMenu.append(item) item.connect("activate", self.set_hud_style, ('O','A')) setattr(self, 'hudStyleOptionA', item) - # - item = gtk.CheckMenuItem(' Session') + + item = gtk.CheckMenuItem(_(' Session')) self.aggMenu.append(item) item.connect("activate", self.set_hud_style, ('O','S')) setattr(self, 'hudStyleOptionS', item) - # - item = gtk.CheckMenuItem(' %s Days' % (self.hud_params['h_hud_days'])) + + item = gtk.CheckMenuItem(_(' %s Days') % (self.hud_params['h_hud_days'])) self.aggMenu.append(item) item.connect("activate", self.set_hud_style, ('O','T')) setattr(self, 'hudStyleOptionT', item) @@ -296,7 +307,7 @@ class Hud: getattr(self, 'h_aggBBmultItem10').set_active(True) elif self.hud_params['h_agg_bb_mult'] > 9000: getattr(self, 'h_aggBBmultItem10000').set_active(True) - # + if self.hud_params['agg_bb_mult'] == 1: getattr(self, 'aggBBmultItem1').set_active(True) elif self.hud_params['agg_bb_mult'] == 2: @@ -307,28 +318,28 @@ class Hud: getattr(self, 'aggBBmultItem10').set_active(True) elif self.hud_params['agg_bb_mult'] > 9000: getattr(self, 'aggBBmultItem10000').set_active(True) - # + if self.hud_params['h_seats_style'] == 'A': getattr(self, 'h_seatsStyleOptionA').set_active(True) elif self.hud_params['h_seats_style'] == 'C': getattr(self, 'h_seatsStyleOptionC').set_active(True) elif self.hud_params['h_seats_style'] == 'E': getattr(self, 'h_seatsStyleOptionE').set_active(True) - # + if self.hud_params['seats_style'] == 'A': getattr(self, 'seatsStyleOptionA').set_active(True) elif self.hud_params['seats_style'] == 'C': getattr(self, 'seatsStyleOptionC').set_active(True) elif self.hud_params['seats_style'] == 'E': getattr(self, 'seatsStyleOptionE').set_active(True) - # + if self.hud_params['h_hud_style'] == 'A': getattr(self, 'h_hudStyleOptionA').set_active(True) elif self.hud_params['h_hud_style'] == 'S': getattr(self, 'h_hudStyleOptionS').set_active(True) elif self.hud_params['h_hud_style'] == 'T': getattr(self, 'h_hudStyleOptionT').set_active(True) - # + if self.hud_params['hud_style'] == 'A': getattr(self, 'hudStyleOptionA').set_active(True) elif self.hud_params['hud_style'] == 'S': @@ -338,11 +349,11 @@ class Hud: eventbox.connect_object("button-press-event", self.on_button_press, menu) - debugitem = gtk.MenuItem('Debug StatWindows') + debugitem = gtk.MenuItem(_('Debug StatWindows')) menu.append(debugitem) debugitem.connect("activate", self.debug_stat_windows) - item5 = gtk.MenuItem('Set max seats') + item5 = gtk.MenuItem(_('Set max seats')) menu.append(item5) maxSeatsMenu = gtk.Menu() item5.set_submenu(maxSeatsMenu) @@ -351,7 +362,7 @@ class Hud: item.ms = i maxSeatsMenu.append(item) item.connect("activate", self.change_max_seats) - setattr(self, 'maxSeatsMenuItem%d' % (i-1), item) + setattr(self, 'maxSeatsMenuItem%d' % (i - 1), item) eventbox.connect_object("button-press-event", self.on_button_press, menu) @@ -382,7 +393,7 @@ class Hud: if self.hud_params['h_agg_bb_mult'] != num \ and getattr(self, 'h_aggBBmultItem'+str(num)).get_active(): - log.debug('set_player_aggregation', num) + log.debug('set_player_aggregation %d', num) self.hud_params['h_agg_bb_mult'] = num for mult in ('1', '2', '3', '10', '10000'): if mult != str(num): @@ -393,7 +404,7 @@ class Hud: if self.hud_params['agg_bb_mult'] != num \ and getattr(self, 'aggBBmultItem'+str(num)).get_active(): - log.debug('set_opponent_aggregation', num) + log.debug('set_opponent_aggregation %d', num) self.hud_params['agg_bb_mult'] = num for mult in ('1', '2', '3', '10', '10000'): if mult != str(num): @@ -446,6 +457,13 @@ class Hud: log.debug("setting self.hud_params[%s] = %s" % (param, style)) def update_table_position(self): + # get table's X/Y position on the desktop, and relocate all of our child windows to accomodate + # In Windows, we can verify the existence of a Window, with win32gui.IsWindow(). In Linux, there doesn't seem to be a + # way to verify the existence of a Window, without trying to access it, which if it doesn't exist anymore, results in a + # big giant X trap and crash. + # People tell me this is a bad idea, because theoretically, IsWindow() could return true now, but not be true when we actually + # use it, but accessing a dead window doesn't result in a complete windowing system shutdown in Windows, whereas it does + # in X. - Eric if os.name == 'nt': if not win32gui.IsWindow(self.table.number): self.parent.kill_hud(self, self.table.name) @@ -454,17 +472,19 @@ class Hud: return False # anyone know how to do this in unix, or better yet, trap the X11 error that is triggered when executing the get_origin() for a closed window? if self.table.gdkhandle is not None: - (x, y) = self.table.gdkhandle.get_origin() - if self.table.x != x or self.table.y != y: - self.table.x = x - self.table.y = y - self.main_window.move(x + self.site_params['xshift'], y + self.site_params['yshift']) + (oldx, oldy) = self.table.gdkhandle.get_origin() # In Windows, this call returns (0,0) if it's an invalid window. In X, the X server is immediately killed. + #(x, y, width, height) = self.table.get_geometry() + #print "self.table.get_geometry=",x,y,width,height + if self.table.oldx != oldx or self.table.oldy != oldy: # If the current position does not equal the stored position, save the new position, and then move all the sub windows. + self.table.oldx = oldx + self.table.oldy = oldy + self.main_window.move(oldx + self.site_params['xshift'], oldy + self.site_params['yshift']) adj = self.adj_seats(self.hand, self.config) loc = self.config.get_locations(self.table.site, self.max) # TODO: is stat_windows getting converted somewhere from a list to a dict, for no good reason? for i, w in enumerate(self.stat_windows.itervalues()): - (x, y) = loc[adj[i+1]] - w.relocate(x, y) + (oldx, oldy) = loc[adj[i+1]] + w.relocate(oldx, oldy) # While we're at it, fix the positions of mucked cards too for aux in self.aux_windows: @@ -476,10 +496,10 @@ class Hud: return True def on_button_press(self, widget, event): - if event.button == 1: + if event.button == 1: # if primary button, start movement self.main_window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time) return True - if event.button == 3: + if event.button == 3: # if secondary button, popup our main popup window widget.popup(None, None, None, event.button, event.time) return True return False @@ -513,7 +533,10 @@ class Hud: def debug_stat_windows(self, *args): # print self.table, "\n", self.main_window.window.get_transient_for() for w in self.stat_windows: - print self.stat_windows[w].window.window.get_transient_for() + try: + print self.stat_windows[w].window.window.get_transient_for() + except AttributeError: + print "this window doesnt have get_transient_for" def save_layout(self, *args): new_layout = [(0, 0)] * self.max @@ -521,20 +544,20 @@ class Hud: loc = self.stat_windows[sw].window.get_position() new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) new_layout[self.stat_windows[sw].adj - 1] = new_loc - self.config.edit_layout(self.table.site, self.max, locations = new_layout) + self.config.edit_layout(self.table.site, self.max, locations=new_layout) # ask each aux to save its layout back to the config object [aux.save_layout() for aux in self.aux_windows] # save the config object back to the file - print "Updating config file" + print _("Updating config file") self.config.save() def adj_seats(self, hand, config): - + # determine how to adjust seating arrangements, if a "preferred seat" is set in the hud layout configuration # Need range here, not xrange -> need the actual list adj = range(0, self.max + 1) # default seat adjustments = no adjustment # does the user have a fav_seat? if self.max not in config.supported_sites[self.table.site].layout: - sys.stderr.write("No layout found for %d-max games for site %s\n" % (self.max, self.table.site) ) + sys.stderr.write(_("No layout found for %d-max games for site %s\n") % (self.max, self.table.site)) return adj if self.table.site != None and int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0: try: @@ -548,15 +571,15 @@ class Hud: if adj[j] > self.max: adj[j] = adj[j] - self.max except Exception, inst: - sys.stderr.write("exception in adj!!!\n\n") - sys.stderr.write("error is %s" % inst) # __str__ allows args to printed directly + sys.stderr.write(_("exception in Hud.adj_seats\n\n")) + sys.stderr.write(_("error is %s") % inst) # __str__ allows args to printed directly return adj def get_actual_seat(self, name): for key in self.stat_dict: if self.stat_dict[key]['screen_name'] == name: return self.stat_dict[key]['seat'] - sys.stderr.write("Error finding actual seat.\n") + sys.stderr.write(_("Error finding actual seat.\n")) def create(self, hand, config, stat_dict, cards): # update this hud, to the stats and players as of "hand" @@ -572,7 +595,7 @@ class Hud: self.stat_dict = stat_dict self.cards = cards - sys.stderr.write("------------------------------------------------------------\nCreating hud from hand %s\n" % hand) + log.info(_('Creating hud from hand ')+str(hand)) adj = self.adj_seats(hand, config) loc = self.config.get_locations(self.table.site, self.max) if loc is None and self.max != 10: @@ -607,7 +630,7 @@ class Hud: [config.supported_games[self.poker_game].stats[stat].col] = \ config.supported_games[self.poker_game].stats[stat].stat_name - if os.name == "nt": + if os.name == "nt": # we call update_table_position() regularly in Windows to see if we're moving around. See comments on that function for why this isn't done in X. gobject.timeout_add(500, self.update_table_position) def update(self, hand, config): @@ -621,8 +644,8 @@ class Hud: try: statd = self.stat_dict[s] except KeyError: - log.error("KeyError at the start of the for loop in update in hud_main. How this can possibly happen is totally beyond my comprehension. Your HUD may be about to get really weird. -Eric") - log.error("(btw, the key was ", s, " and statd is...", statd) + log.error(_("KeyError at the start of the for loop in update in hud_main. How this can possibly happen is totally beyond my comprehension. Your HUD may be about to get really weird. -Eric")) + log.error(_("(btw, the key was %s and statd is %s") % (s, statd)) continue try: self.stat_windows[statd['seat']].player_id = statd['player_id'] @@ -642,8 +665,8 @@ class Hud: if 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'])) - + 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)) @@ -654,9 +677,12 @@ class Hud: 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? - window.window.show_all() + unhidewindow = True tip = "%s\n%s\n%s, %s" % (statd['screen_name'], number[5], number[3], number[4]) Stats.do_tip(window.e_box[r][c], tip) + if unhidewindow: #and not window.window.visible: # there is no "visible" attribute in gtk.Window, although the docs seem to indicate there should be + window.window.show_all() + unhidewindow = False def topify_window(self, window): window.set_focus_on_map(False) @@ -672,7 +698,7 @@ class Stat_Window: # This handles all callbacks from button presses on the event boxes in # the stat windows. There is a bit of an ugly kludge to separate single- # and double-clicks. - self.window.show_all() + self.window.show() #_all() if event.button == 3: # right button event newpopup = Popup_window(self.window, self) @@ -731,11 +757,13 @@ class Stat_Window: self.window = gtk.Window() self.window.set_decorated(0) + self.window.set_property("skip-taskbar-hint", True) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) self.window.set_title("%s" % seat) - self.window.set_property("skip-taskbar-hint", True) + self.window.set_focus(None) # set gtk default focus widget for this window to None self.window.set_focus_on_map(False) + self.window.set_accept_focus(False) grid = gtk.Table(rows = game.rows, columns = game.cols, homogeneous = False) self.grid = grid @@ -926,26 +954,3 @@ class Popup_window: # window.present() -if __name__== "__main__": - main_window = gtk.Window() - main_window.connect("destroy", destroy) - label = gtk.Label('Fake main window, blah blah, blah\nblah, blah') - main_window.add(label) - main_window.show_all() - - c = Configuration.Config() - #tables = Tables.discover(c) - t = Tables.discover_table_by_name(c, "Corona") - if t is None: - print "Table not found." - db = Database.Database(c, 'fpdb', 'holdem') - - stat_dict = db.get_stats_from_hand(1) - -# for t in tables: - win = Hud(None, t, 10, 'holdem', c, db) # parent, table, max, poker_game, config, db_connection - win.create(1, c, stat_dict, None) # hand, config, stat_dict, cards): -# t.get_details() - win.update(8300, c) # self, hand, config): - - gtk.main() diff --git a/pyfpdb/IdentifySite.py b/pyfpdb/IdentifySite.py new file mode 100644 index 00000000..b4f1a8a7 --- /dev/null +++ b/pyfpdb/IdentifySite.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#Copyright 2010 Chaz Littlejohn +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in agpl-3.0.txt. + +import L10n +_ = L10n.get_translation() + +import re +import sys +import os +import os.path +from optparse import OptionParser +import codecs +import Configuration +import Database + +__ARCHIVE_PRE_HEADER_REGEX='^Hand #(\d+)\s*$|\*{20}\s#\s\d+\s\*+\s+' +re_SplitArchive = re.compile(__ARCHIVE_PRE_HEADER_REGEX) + + +class IdentifySite: + def __init__(self, config, in_path = '-'): + self.in_path = in_path + self.config = config + self.db = Database.Database(config) + self.sitelist = {} + self.filelist = {} + self.generateSiteList() + self.walkDirectory(self.in_path, self.sitelist) + + def generateSiteList(self): + """Generates a ordered dictionary of site, filter and filter name for each site in hhcs""" + for site, hhc in self.config.hhcs.iteritems(): + filter = hhc.converter + filter_name = filter.replace("ToFpdb", "") + result = self.db.get_site_id(site) + if len(result) == 1: + self.sitelist[result[0][0]] = (site, filter, filter_name) + else: + pass + + def walkDirectory(self, dir, sitelist): + """Walks a directory, and executes a callback on each file""" + dir = os.path.abspath(dir) + for file in [file for file in os.listdir(dir) if not file in [".",".."]]: + nfile = os.path.join(dir,file) + if os.path.isdir(nfile): + self.walkDirectory(nfile, sitelist) + else: + self.idSite(nfile, sitelist) + + def __listof(self, x): + if isinstance(x, list) or isinstance(x, tuple): + return x + else: + return [x] + + def idSite(self, file, sitelist): + """Identifies the site the hh file originated from""" + if file.endswith('.txt'): + self.filelist[file] = '' + archive = False + for site, info in sitelist.iteritems(): + mod = __import__(info[1]) + obj = getattr(mod, info[2], None) + + for kodec in self.__listof(obj.codepage): + try: + in_fh = codecs.open(file, 'r', kodec) + whole_file = in_fh.read() + in_fh.close() + + if info[2] in ('OnGame', 'Winamax'): + m = obj.re_HandInfo.search(whole_file) + elif info[2] in ('PartyPoker'): + m = obj.re_GameInfoRing.search(whole_file) + if not m: + m = obj.re_GameInfoTrny.search(whole_file) + else: + m = obj.re_GameInfo.search(whole_file) + if re_SplitArchive.search(whole_file): + archive = True + if m: + self.filelist[file] = [info[0]] + [info[1]] + [kodec] + [archive] + break + except: + pass + +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + + config = Configuration.Config(file = "HUD_config.test.xml") + in_path = 'regression-test-files/' + IdSite = IdentifySite(config, in_path) + + print "\n----------- SITE LIST -----------" + for site, info in IdSite.sitelist.iteritems(): + print site, info + print "----------- END SITE LIST -----------" + + print "\n----------- ID REGRESSION FILES -----------" + for file, site in IdSite.filelist.iteritems(): + print file, site + print "----------- END ID REGRESSION FILES -----------" + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pyfpdb/ImapFetcher.py b/pyfpdb/ImapFetcher.py index da13698c..1d2068d7 100755 --- a/pyfpdb/ImapFetcher.py +++ b/pyfpdb/ImapFetcher.py @@ -19,30 +19,55 @@ #see http://docs.python.org/library/imaplib.html for the python interface #see http://tools.ietf.org/html/rfc2060#section-6.4.4 for IMAP4 search criteria -from imaplib import IMAP4, IMAP4_SSL -import PokerStarsSummary +import L10n +_ = L10n.get_translation() + +from imaplib import IMAP4, IMAP4_SSL +import sys +import codecs +import re + +import Configuration +import Database +from Exceptions import FpdbParseError +import SQL +import Options +import PokerStarsSummary +import FullTiltPokerSummary + + +def splitPokerStarsSummaries(summaryText): #TODO: this needs to go to PSS.py + re_SplitTourneys = PokerStarsSummary.PokerStarsSummary.re_SplitTourneys + splitSummaries = re.split(re_SplitTourneys, summaryText) + + if len(splitSummaries) <= 1: + print _("DEBUG: re_SplitTourneys isn't matching") + + return splitSummaries + +def splitFullTiltSummaries(summaryText):#TODO: this needs to go to FTPS.py + re_SplitTourneys = FullTiltPokerSummary.FullTiltPokerSummary.re_SplitTourneys + splitSummaries = re.split(re_SplitTourneys, summaryText) + + if len(splitSummaries) <= 1: + print _("DEBUG: re_SplitTourneys isn't matching") -def splitPokerStarsSummaries(emailText): - splitSummaries=emailText.split("\nPokerStars Tournament #")[1:] - for i in range(len(splitSummaries)): - splitSummaries[i]="PokerStars Tournament #"+splitSummaries[i] return splitSummaries -#end def emailText def run(config, db): #print "start of IS.run" server=None #try: - #print "useSSL",config.email.useSsl,"host",config.email.host - if config.email.useSsl: - server = IMAP4_SSL(config.email.host) + #print "useSSL",config.useSsl,"host",config.host + if config.useSsl: + server = IMAP4_SSL(config.host) else: - server = IMAP4(config.email.host) - response = server.login(config.email.username, config.email.password) #TODO catch authentication error - print "response to logging in:",response + server = IMAP4(config.host) + response = server.login(config.username, config.password) #TODO catch authentication error + print _("response to logging in:"),response #print "server.list():",server.list() #prints list of folders - response = server.select(config.email.folder) + response = server.select(config.folder) #print "response to selecting INBOX:",response if response[0]!="OK": raise error #TODO: show error message @@ -51,31 +76,118 @@ def run(config, db): response, searchData = server.search(None, "SUBJECT", "PokerStars Tournament History Request") for messageNumber in searchData[0].split(" "): response, headerData = server.fetch(messageNumber, "(BODY[HEADER.FIELDS (SUBJECT)])") - #print "response to fetch subject:",response if response!="OK": raise error #TODO: show error message - if headerData[1].find("Subject: PokerStars Tournament History Request - Last x")!=1: - neededMessages.append(("PS", messageNumber)) - + neededMessages.append(("PS", messageNumber)) + + print _("ImapFetcher: Found %s messages to fetch") %(len(neededMessages)) + if (len(neededMessages)==0): raise error #TODO: show error message - for messageData in neededMessages: + + email_bodies = [] + for i, messageData in enumerate(neededMessages, start=1): + print "Retrieving message %s" % i response, bodyData = server.fetch(messageData[1], "(UID BODY[TEXT])") bodyData=bodyData[0][1] if response!="OK": raise error #TODO: show error message if messageData[0]=="PS": - summaryTexts=(splitPokerStarsSummaries(bodyData)) - for summaryText in summaryTexts: - result=PokerStarsSummary.PokerStarsSummary(db=db, config=config, siteName=u"PokerStars", summaryText=summaryText, builtFrom = "IMAP") - #print "finished importing a PS summary with result:",result - #TODO: count results and output to shell like hand importer does - - print "completed running Imap import, closing server connection" + email_bodies.append(bodyData) #finally: # try: server.close() # finally: # pass server.logout() - \ No newline at end of file + print _("Completed retrieving IMAP messages, closing server connection") + + errors = 0 + if len(email_bodies) > 0: + errors = importSummaries(db, config, email_bodies, options = None) + else: + print _("No Tournament summaries found.") + + print _("Errors: %s" % errors) + +def readFile(filename, options): + codepage = ["utf8"] + whole_file = None + if options.hhc == "PokerStars": + codepage = PokerStarsSummary.PokerStarsSummary.codepage + elif options.hhc == "Full Tilt Poker": + codepage = FullTiltPokerSummary.FullTiltPokerSummary.codepage + + for kodec in codepage: + #print "trying", kodec + try: + in_fh = codecs.open(filename, 'r', kodec) + whole_file = in_fh.read() + in_fh.close() + break + except: + pass + + return whole_file + +def runFake(db, config, options): + summaryText = readFile(options.infile, options) + importSummaries(db, config,[summaryText], options=options) + +def importSummaries(db, config, summaries, options = None): + # TODO: At this point we should have: + # - list of strings to process + # - The sitename OR specialised TourneySummary object + # Using options is pretty ugly + errors = 0 + for summaryText in summaries: + # And we should def be using a 'Split' from the site object + if options == None or options.hhc == "PokerStars": + summaryTexts=(splitPokerStarsSummaries(summaryText)) + elif options.hhc == "Full Tilt Poker": + summaryTexts=(splitFullTiltSummaries(summaryText)) + + print "Found %s summaries in email" %(len(summaryTexts)) + for j, summaryText in enumerate(summaryTexts, start=1): + try: + if options == None or options.hhc == "PokerStars": + PokerStarsSummary.PokerStarsSummary(db=db, config=config, siteName=u"PokerStars", summaryText=summaryText, builtFrom = "IMAP") + elif options.hhc == "Full Tilt Poker": + FullTiltPokerSummary.FullTiltPokerSummary(db=db, config=config, siteName=u"Fulltilt", summaryText=summaryText, builtFrom = "IMAP") + except FpdbParseError, e: + errors += 1 + print _("Finished importing %s/%s PS summaries") %(j, len(summaryTexts)) + + return errors + + +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + + (options, argv) = Options.fpdb_options() + + if options.usage == True: + #Print usage examples and exit + print _("USAGE:") + sys.exit(0) + + if options.hhc == "PokerStarsToFpdb": + print _("Need to define a converter") + exit(0) + + # These options should really come from the OptionsParser + config = Configuration.Config() + db = Database.Database(config) + sql = SQL.Sql(db_server = 'sqlite') + settings = {} + settings.update(config.get_db_parameters()) + settings.update(config.get_import_parameters()) + settings.update(config.get_default_paths()) + db.recreate_tables() + + runFake(db, config, options) + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/pyfpdb/L10n.py b/pyfpdb/L10n.py new file mode 100644 index 00000000..9510df0b --- /dev/null +++ b/pyfpdb/L10n.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#Copyright 2010 Steffen Schaumburg +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in agpl-3.0.txt. + +import locale +def pass_through(to_translate): return to_translate + +(lang, charset) = locale.getdefaultlocale() +if lang==None or lang[:2]=="en": + translation=pass_through +else: + import gettext + try: + trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) + trans.install() + translation=_ + except IOError: + translation=pass_through + +#def translate(to_translate): +# return _(to_translate) + +def get_translation(): + return translation diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py index 525ce206..cda24631 100755 --- a/pyfpdb/Mucked.py +++ b/pyfpdb/Mucked.py @@ -324,7 +324,7 @@ class Stud_cards: for k in self.parent.hud.stat_dict.keys(): if self.parent.hud.stat_dict[k]['seat'] == seat_no: return self.parent.hud.stat_dict[k]['screen_name'] - return "No Name" + return _("No Name") def clear(self): for r in range(0, self.rows): diff --git a/pyfpdb/OnGameToFpdb.py b/pyfpdb/OnGameToFpdb.py index 80d8d646..faf52522 100755 --- a/pyfpdb/OnGameToFpdb.py +++ b/pyfpdb/OnGameToFpdb.py @@ -18,185 +18,318 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## +import L10n +_ = L10n.get_translation() + import sys +import exceptions + +import logging +# logging has been set up in fpdb.py or HUD_main.py, use their settings: +log = logging.getLogger("parser") + + import Configuration from HandHistoryConverter import * +from decimal import Decimal # OnGame HH Format -#Texas Hold'em $.5-$1 NL (real money), hand #P4-76915775-797 -#Table Kuopio, 20 Sep 2008 11:59 PM - -#Seat 1: .Lucchess ($4.17 in chips) -#Seat 3: Gloff1 ($108 in chips) -#Seat 4: far a ($13.54 in chips) -#Seat 5: helander2222 ($49.77 in chips) -#Seat 6: lopllopl ($62.06 in chips) -#Seat 7: crazyhorse6 ($101.91 in chips) -#Seat 8: peeci ($25.02 in chips) -#Seat 9: Manuelhertz ($49 in chips) -#Seat 10: Eurolll ($58.25 in chips) -#ANTES/BLINDS -#helander2222 posts blind ($0.25), lopllopl posts blind ($0.50). - -#PRE-FLOP -#crazyhorse6 folds, peeci folds, Manuelhertz folds, Eurolll calls $0.50, .Lucchess calls $0.50, Gloff1 folds, far a folds, helander2222 folds, lopllopl checks. - -#FLOP [board cards AH,8H,KH ] -#lopllopl checks, Eurolll checks, .Lucchess checks. - -#TURN [board cards AH,8H,KH,6S ] -#lopllopl checks, Eurolll checks, .Lucchess checks. - -#RIVER [board cards AH,8H,KH,6S,8S ] -#lopllopl checks, Eurolll bets $1.25, .Lucchess folds, lopllopl folds. - -#SHOWDOWN -#Eurolll wins $2.92. - -#SUMMARY -#Dealer: far a -#Pot: $3, (including rake: $0.08) -#.Lucchess, loses $0.50 -#Gloff1, loses $0 -#far a, loses $0 -#helander2222, loses $0.25 -#lopllopl, loses $0.50 -#crazyhorse6, loses $0 -#peeci, loses $0 -#Manuelhertz, loses $0 -#Eurolll, bets $1.75, collects $2.92, net $1.17 - - class OnGame(HandHistoryConverter): - def __init__(self, config, file): - print "Initialising OnGame converter class" - HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init. - self.sitename = "OnGame" - self.setFileType("text", "cp1252") - self.siteId = 5 # Needs to match id entry in Sites database - #self.rexx.setGameInfoRegex('.*Blinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+)') - self.rexx.setSplitHandRegex('\n\n\n+') + filter = "OnGame" + filetype = "text" + codepage = ("utf8", "cp1252") + siteId = 5 # Needs to match id entry in Sites database + + mixes = { } # Legal mixed games + sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\xe2\x82\xac", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE + substitutions = { + 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes + 'LS' : "\$|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8) + } + + limits = { 'NO_LIMIT':'nl', 'LIMIT':'fl'} + + games = { # base, category + "TEXAS_HOLDEM" : ('hold','holdem'), + 'OMAHA_HI' : ('hold','omahahi'), + # 'Omaha Hi/Lo' : ('hold','omahahilo'), + # 'Razz' : ('stud','razz'), + # 'RAZZ' : ('stud','razz'), + 'SEVEN_CARD_STUD' : ('stud','studhi'), + 'SEVEN_CARD_STUD_HI_LO' : ('stud','studhilo'), + # 'Badugi' : ('draw','badugi'), + # 'Triple Draw 2-7 Lowball' : ('draw','27_3draw'), + 'FIVE_CARD_DRAW' : ('draw','fivedraw') + } + + # Static regexes + # ***** End of hand R5-75443872-57 ***** + re_SplitHands = re.compile(u'\*\*\*\*\*\sEnd\sof\shand\s[-A-Z\d]+.*\n(?=\*)') + + # ***** History for hand R5-75443872-57 ***** + # Start hand: Wed Aug 18 19:29:10 GMT+0100 2010 + # Table: someplace [75443872] (LIMIT TEXAS_HOLDEM 0.50/1, Real money) +#***** History for hand R5-78042004-262 ***** +#Start hand: Fri Aug 27 21:40:46 GMT+0100 2010 +#Table: Bamako [78042004] (LIMIT TEXAS_HOLDEM $0.25/$0.50, Real money) +#User: sagi34 +#{ u'BB': None +#, u'DATETIME': u'Fri Aug 27 22:38:26 GMT+0100 2010\\n' +#, u'GAME': None +#, u'HID': u'R5-78042004-346' +#, u'TABLE': u'Bamako' +#, u'LIMIT': None +#, u'SB': None +#} + re_HandInfo = re.compile(u""" + \*\*\*\*\*\sHistory\sfor\shand\s(?P[-A-Z\d]+).* + Start\shand:\s(?P.*) + Table:\s(?P
[\'\w\s]+)\s\[\d+\]\s\( + ( + (?PNO_LIMIT|Limit|LIMIT|Pot\sLimit)\s + (?PTEXAS_HOLDEM|OMAHA_HI|SEVEN_CARD_STUD|SEVEN_CARD_STUD_HI_LO|RAZZ|FIVE_CARD_DRAW)\s + (%(LS)s)?(?P[.0-9]+)/ + (%(LS)s)?(?P[.0-9]+) + )? + """ % substitutions, re.MULTILINE|re.DOTALL|re.VERBOSE) + + re_TailSplitHands = re.compile(u'(\*\*\*\*\*\sEnd\sof\shand\s[-A-Z\d]+.*\n)(?=\*)') + re_Button = re.compile('Button: seat (?P
[\' \w]+), (?P\d\d \w+ \d\d\d\d \d\d:\d\d (AM|PM))") - # SB BB HID TABLE DAY MON YEAR HR12 MIN AMPM - - self.rexx.button_re = re.compile('#SUMMARY\nDealer: (?P.*)\n') - - #Seat 1: .Lucchess ($4.17 in chips) - self.rexx.setPlayerInfoRegex('Seat (?P[0-9]+): (?P.*) \((\$(?P[.0-9]+) in chips)\)') - - #ANTES/BLINDS - #helander2222 posts blind ($0.25), lopllopl posts blind ($0.50). - self.rexx.setPostSbRegex('(?P.*) posts blind \(\$?(?P[.0-9]+)\), ') - self.rexx.setPostBbRegex('\), (?P.*) posts blind \(\$?(?P[.0-9]+)\).') - self.rexx.setPostBothRegex('.*\n(?P.*): posts small \& big blinds \[\$? (?P[.0-9]+)') - self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P.*)\s\[ (?P.*) \]') - - #lopllopl checks, Eurolll checks, .Lucchess checks. - self.rexx.setActionStepRegex('(, )?(?P.*?)(?P bets| checks| raises| calls| folds)( \$(?P\d*\.?\d*))?( and is all-in)?') - - #Uchilka shows [ KC,JD ] - self.rexx.setShowdownActionRegex('(?P.*) shows \[ (?P.+) \]') - - # TODO: read SUMMARY correctly for collected pot stuff. - #Uchilka, bets $11.75, collects $23.04, net $11.29 - self.rexx.setCollectPotRegex('(?P.*), bets.+, collects \$(?P\d*\.?\d*), net.* ') - self.rexx.sits_out_re = re.compile('(?P.*) sits out') - self.rexx.compileRegexes() + #Seat 1: .Lucchess ($4.17 in chips) + #Seat 1: phantomaas ($27.11) + #Seat 5: mleo17 ($9.37) + re_PlayerInfo = re.compile(u'Seat (?P[0-9]+):\s(?P.*)\s\((%(LS)s)?(?P[.0-9]+)\)' % substitutions) + + def compilePlayerRegexs(self, hand): + players = set([player[1] for player in hand.players]) + if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' + # we need to recompile the player regexs. +# TODO: should probably rename re_HeroCards and corresponding method, +# since they are used to find all cards on lines starting with "Dealt to:" +# They still identify the hero. + self.compiledPlayers = players + + #ANTES/BLINDS + #helander2222 posts blind ($0.25), lopllopl posts blind ($0.50). + player_re = "(?P" + "|".join(map(re.escape, players)) + ")" + subst = {'PLYR': player_re, 'CUR': self.sym[hand.gametype['currency']]} + self.re_PostSB = re.compile('(?P.*) posts small blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) + self.re_PostBB = re.compile('(?P.*) posts big blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) + self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante (%(CUR)s)?(?P[\.0-9]+)" % subst, re.MULTILINE) + self.re_BringIn = re.compile(r"^%(PLYR)s: brings[- ]in( low|) for (%(CUR)s)?(?P[\.0-9]+)" % subst, re.MULTILINE) + self.re_PostBoth = re.compile('(?P.*): posts small \& big blind \( (%(CUR)s)?(?P[\.0-9]+)\)' % subst) + self.re_PostDead = re.compile('(?P.*) posts dead blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) + self.re_HeroCards = re.compile('Dealing\sto\s%(PLYR)s:\s\[(?P.*)\]' % subst) + + #lopllopl checks, Eurolll checks, .Lucchess checks. + #chumley. calls $0.25 + self.re_Action = re.compile('(, )?(?P.*?)(?P bets| checks| raises| calls| folds)( (%(CUR)s)?(?P[\d\.]+))?( and is all-in)?' % subst) + #self.re_Board = re.compile(r"\[board cards (?P.+) \]") + + #Uchilka shows [ KC,JD ] + self.re_ShowdownAction = re.compile('(?P.*) shows \[ (?P.+) \]') + + #Main pot: $3.57 won by mleo17 ($3.40) + #Side pot 1: $3.26 won by maac_5 ($3.10) + #Main pot: $2.87 won by maac_5 ($1.37), sagi34 ($1.36) + self.re_Pot = re.compile('(Main|Side)\spot(\s\d+)?:\s.*won\sby\s(?P.*$)', re.MULTILINE) + self.re_CollectPot = re.compile('\s*(?P.*)\s\((%(CUR)s)?(?P[\.\d]+)\)' % subst) + #Seat 5: mleo17 ($3.40), net: +$2.57, [Jd, Qd] (TWO_PAIR QUEEN, JACK) + self.re_ShownCards = re.compile("^Seat (?P[0-9]+): (?P.*) \(.*\), net:.* \[(?P.*)\].*" % subst, re.MULTILINE) + self.re_sitsOut = re.compile('(?P.*) sits out') def readSupportedGames(self): - pass + return [ + ["ring", "hold", "fl"], + ["ring", "hold", "nl"], + ["ring", "stud", "fl"], + ["ring", "draw", "fl"], + ] - def determineGameType(self): - # Cheating with this regex, only support nlhe at the moment - gametype = ["ring", "hold", "nl"] + def determineGameType(self, handText): + # Inspect the handText and return the gametype dict + # gametype dict is: {'limitType': xxx, 'base': xxx, 'category': xxx} + info = {} - m = self.rexx.hand_info_re.search(self.obs) - gametype = gametype + [m.group('SB')] - gametype = gametype + [m.group('BB')] - - return gametype + m = self.re_HandInfo.search(handText) + if not m: + tmp = handText[0:100] + log.error(_("determineGameType: Unable to recognise gametype from: '%s'") % tmp) + log.error(_("determineGameType: Raising FpdbParseError")) + raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp) + + mg = m.groupdict() + + info['type'] = 'ring' + info['currency'] = 'USD' + + if 'LIMIT' in mg: + if mg['LIMIT'] in self.limits: + info['limitType'] = self.limits[mg['LIMIT']] + else: + tmp = handText[0:100] + log.error(_("determineGameType: limit not found in self.limits(%s). hand: '%s'") % (str(mg),tmp)) + log.error(_("determineGameType: Raising FpdbParseError")) + raise FpdbParseError(_("limit not found in self.limits(%s). hand: '%s'") % (str(mg),tmp)) + if 'GAME' in mg: + (info['base'], info['category']) = self.games[mg['GAME']] + if 'SB' in mg: + info['sb'] = mg['SB'] + if 'BB' in mg: + info['bb'] = mg['BB'] + + #log.debug("determinegametype: returning "+str(info)) + return info def readHandInfo(self, hand): - m = self.rexx.hand_info_re.search(hand.string) - hand.handid = m.group('HID') - hand.tablename = m.group('TABLE') - #hand.buttonpos = self.rexx.button_re.search(hand.string).group('BUTTONPNAME') -# These work, but the info is already in the Hand class - should be used for tourneys though. -# m.group('SB') -# m.group('BB') -# m.group('GAMETYPE') + info = {} + m = self.re_HandInfo.search(hand.handText) -# Believe Everleaf time is GMT/UTC, no transation necessary -# Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET] -# or : 2008/11/07 12:38:49 ET -# Not getting it in my HH files yet, so using -# 2008/11/10 3:58:52 ET -#TODO: Do conversion from GMT to ET -#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this) - - hand.startTime = time.strptime(m.group('DATETIME'), "%d %b %Y %I:%M %p") - #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'))) + if m: + info.update(m.groupdict()) + + #log.debug("readHandInfo: %s" % info) + for key in info: + if key == 'DATETIME': + #'Wed Aug 18 19:45:30 GMT+0100 2010 + # %a %b %d %H:%M:%S %z %Y + #hand.startTime = time.strptime(m.group('DATETIME'), "%a %b %d %H:%M:%S GMT%z %Y") + # Stupid library doesn't seem to support %z (http://docs.python.org/library/time.html?highlight=strptime#time.strptime) + # So we need to re-interpret te string to be useful + a = self.re_DateTime.search(info[key]) + if a: + 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')) + tzoffset = a.group('OFFSET') + else: + datetimestr = "2010/Jan/01 01:01:01" + log.error(_("readHandInfo: DATETIME not matched: '%s'" % info[key])) + print "DEBUG: readHandInfo: DATETIME not matched: '%s'" % info[key] + # TODO: Manually adjust time against OFFSET + hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%b/%d %H:%M:%S") # also timezone at end, e.g. " ET" + hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, tzoffset, "UTC") + if key == 'HID': + hand.handid = info[key] + # Need to remove non-alphanumerics for MySQL + hand.handid = hand.handid.replace('R','') + hand.handid = hand.handid.replace('-','') + if key == 'TABLE': + hand.tablename = info[key] + + # TODO: These + hand.buttonpos = 1 + hand.maxseats = None # Set to None - Hand.py will guessMaxSeats() + hand.mixed = None def readPlayerStacks(self, hand): - m = self.rexx.player_info_re.finditer(hand.string) - players = [] + #log.debug("readplayerstacks: re is '%s'" % self.re_PlayerInfo) + m = self.re_PlayerInfo.finditer(hand.handText) for a in m: hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) def markStreets(self, hand): - # PREFLOP = ** Dealing down cards ** - # This re fails if, say, river is missing; then we don't get the ** that starts the river. - #m = re.search('(\*\* Dealing down cards \*\*\n)(?P.*?\n\*\*)?( Dealing Flop \*\* \[ (?P\S\S), (?P\S\S), (?P\S\S) \])?(?P.*?\*\*)?( Dealing Turn \*\* \[ (?P\S\S) \])?(?P.*?\*\*)?( Dealing River \*\* \[ (?P\S\S) \])?(?P.*)', hand.string,re.DOTALL) - - m = re.search(r"PRE-FLOP(?P.+(?=FLOP)|.+(?=SHOWDOWN))" - r"(FLOP (?P\[board cards .+ \].+(?=TURN)|.+(?=SHOWDOWN)))?" - r"(TURN (?P\[board cards .+ \].+(?=RIVER)|.+(?=SHOWDOWN)))?" - r"(RIVER (?P\[board cards .+ \].+(?=SHOWDOWN)))?", hand.string,re.DOTALL) + if hand.gametype['base'] in ("hold"): + m = re.search(r"pocket cards(?P.+(?= Dealing flop )|.+(?=Summary))" + r"( Dealing flop (?P\[\S\S, \S\S, \S\S\].+(?= Dealing turn)|.+(?=Summary)))?" + r"( Dealing turn (?P\[\S\S\].+(?= Dealing river)|.+(?=Summary)))?" + r"( Dealing river (?P\[\S\S\].+(?=Summary)))?", hand.handText, re.DOTALL) + elif hand.gametype['base'] in ("stud"): + m = re.search(r"(?P.+(?=Dealing pocket cards)|.+)" + r"(Dealing pocket cards(?P.+(?=Dealing 4th street)|.+))?" + r"(Dealing 4th street(?P.+(?=Dealing 5th street)|.+))?" + r"(Dealing 5th street(?P.+(?=Dealing 6th street)|.+))?" + r"(Dealing 6th street(?P.+(?=Dealing river)|.+))?" + r"(Dealing river(?P.+))?", hand.handText,re.DOTALL) + elif hand.gametype['base'] in ("draw"): + m = re.search(r"(?P.+(?=Dealing pocket cards)|.+)" + r"(Dealing pocket cards(?P.+(?=\*\*\* FIRST DRAW \*\*\*)|.+))?" + r"(\*\*\* FIRST DRAW \*\*\*(?P.+(?=\*\*\* SECOND DRAW \*\*\*)|.+))?" + r"(\*\*\* SECOND DRAW \*\*\*(?P.+(?=\*\*\* THIRD DRAW \*\*\*)|.+))?" + r"(\*\*\* THIRD DRAW \*\*\*(?P.+))?", hand.handText,re.DOTALL) hand.addStreets(m) - - def readCommunityCards(self, hand, street): - self.rexx.board_re = re.compile(r"\[board cards (?P.+) \]") - print hand.streets.group(street) + #Needs to return a list in the format + # ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb, + # addtional players are assumed to post a bb oop + + def readButton(self, hand): + m = self.re_Button.search(hand.handText) + if m: + hand.buttonpos = int(m.group('BUTTON')) + else: + log.info(_('readButton: not found')) + +# def readCommunityCards(self, hand, street): +# #print hand.streets.group(street) +# if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP) +# m = self.re_Board.search(hand.streets.group(street)) +# hand.setCommunityCards(street, m.group('CARDS').split(',')) + + def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP) - m = self.rexx.board_re.search(hand.streets.group(street)) - hand.setCommunityCards(street, m.group('CARDS').split(',')) + #print "DEBUG readCommunityCards:", street, hand.streets.group(street) + m = self.re_Board.search(hand.streets[street]) + hand.setCommunityCards(street, m.group('CARDS').split(', ')) def readBlinds(self, hand): try: - m = self.rexx.small_blind_re.search(hand.string) + m = self.re_PostSB.search(hand.handText) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) - except: # no small blind - hand.addBlind(None, None, None) - for a in self.rexx.big_blind_re.finditer(hand.string): + except exceptions.AttributeError: # no small blind + log.debug( _("readBlinds in noSB exception - no SB created")+str(sys.exc_info()) ) + #hand.addBlind(None, None, None) + for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) - for a in self.rexx.both_blinds_re.finditer(hand.string): + for a in self.re_PostDead.finditer(hand.handText): + #print "DEBUG: Found dead blind: addBlind(%s, 'secondsb', %s)" %(a.group('PNAME'), a.group('DEAD')) + hand.addBlind(a.group('PNAME'), 'secondsb', a.group('DEAD')) + for a in self.re_PostBoth.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB')) + def readAntes(self, hand): + log.debug(_("reading antes")) + m = self.re_Antes.finditer(hand.handText) + for player in m: + #~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE'))) + hand.addAnte(player.group('PNAME'), player.group('ANTE')) + + def readBringIn(self, hand): + m = self.re_BringIn.search(hand.handText,re.DOTALL) + if m: + #~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))) + hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) + def readHeroCards(self, hand): - m = self.rexx.hero_cards_re.search(hand.string) - if(m == None): - #Not involved in hand - hand.involved = False - else: - hand.hero = m.group('PNAME') - # "2c, qh" -> set(["2c","qc"]) - # Also works with Omaha hands. - cards = m.group('CARDS') - cards = set(cards.split(',')) - hand.addHoleCards(cards, m.group('PNAME')) + # streets PREFLOP, PREDRAW, and THIRD are special cases beacause + # we need to grab hero's cards + for street in ('PREFLOP', 'DEAL'): + if street in hand.streets.keys(): + m = self.re_HeroCards.finditer(hand.streets[street]) + for found in m: + hand.hero = found.group('PNAME') + newcards = found.group('CARDS').split(', ') + hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) def readAction(self, hand, street): - m = self.rexx.action_re.finditer(hand.streets.group(street)) + m = self.re_Action.finditer(hand.streets[street]) for action in m: + acts = action.groupdict() + #log.debug("readaction: acts: %s" %acts) if action.group('ATYPE') == ' raises': - hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') ) + hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == ' calls': hand.addCall( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == ' bets': @@ -205,30 +338,36 @@ class OnGame(HandHistoryConverter): hand.addFold( street, action.group('PNAME')) elif action.group('ATYPE') == ' checks': hand.addCheck( street, action.group('PNAME')) + elif action.group('ATYPE') == ' discards': + hand.addDiscard(street, action.group('PNAME'), action.group('BET'), action.group('DISCARDED')) + elif action.group('ATYPE') == ' stands pat': + hand.addStandsPat( street, action.group('PNAME')) else: - print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),) - #hand.actions[street] += [[action.group('PNAME'), action.group('ATYPE')]] - # TODO: Everleaf does not record uncalled bets. + print _("DEBUG: unimplemented readAction: '%s' '%s'") %(action.group('PNAME'),action.group('ATYPE'),) def readShowdownActions(self, hand): - for shows in self.rexx.showdown_action_re.finditer(hand.string): + for shows in self.re_ShowdownAction.finditer(hand.handText): cards = shows.group('CARDS') cards = set(cards.split(',')) hand.addShownCards(cards, shows.group('PNAME')) def readCollectPot(self,hand): - for m in self.rexx.collect_pot_re.finditer(hand.string): - hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) + for m in self.re_Pot.finditer(hand.handText): + for splitpot in m.group('POT').split(','): + for m in self.re_CollectPot.finditer(splitpot): + hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) def readShownCards(self,hand): - return - #for m in self.rexx.collect_pot_re.finditer(hand.string): - #if m.group('CARDS') is not None: - #cards = m.group('CARDS') - #cards = set(cards.split(',')) - #hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards) + for m in self.re_ShownCards.finditer(hand.handText): + cards = m.group('CARDS') + cards = cards.split(', ') # needs to be a list, not a set--stud needs the order + + (shown, mucked) = (False, False) + if m.group('CARDS') is not None: + shown = True + hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked) + - if __name__ == "__main__": diff --git a/pyfpdb/Options.py b/pyfpdb/Options.py index 6b5e3285..4646cb6e 100644 --- a/pyfpdb/Options.py +++ b/pyfpdb/Options.py @@ -15,6 +15,9 @@ #along with this program. If not, see . #In the "official" distribution you can find the license in agpl-3.0.txt. +import L10n +_ = L10n.get_translation() + import sys from optparse import OptionParser # http://docs.python.org/library/optparse.html @@ -25,29 +28,41 @@ def fpdb_options(): parser = OptionParser() parser.add_option("-x", "--errorsToConsole", 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", dest="dbname", - help="Overrides the default database name") + help=_("Overrides the default database name")) parser.add_option("-c", "--configFile", dest="config", default=None, - help="Specifies a configuration file.") + help=_("Specifies a configuration file.")) parser.add_option("-r", "--rerunPython", action="store_true", - help="Indicates program was restarted with a different path (only allowed once).") + help=_("Indicates program was restarted with a different path (only allowed once).")) parser.add_option("-i", "--infile", dest="infile", default="Slartibartfast", - help="Input file") + help=_("Input file")) parser.add_option("-k", "--konverter", 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)", + 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.") + help = _("Print version information and exit.")) + parser.add_option("-u", "--usage", action="store_true", dest="usage", default=False, + help=_("Print some useful one liners")) + # The following options are used for SplitHandHistory.py + parser.add_option("-f", "--file", dest="filename", metavar="FILE", default=None, + help=_("Input file in quiet mode")) + parser.add_option("-o", "--outpath", dest="outpath", metavar="FILE", default=None, + help=_("Input out path in quiet mode")) + parser.add_option("-a", "--archive", action="store_true", dest="archive", default=False, + help=_("File to be split is a PokerStars or Full Tilt Poker archive file")) + parser.add_option("-n", "--numhands", dest="hands", default="100", type="int", + help=_("How many hands do you want saved to each file. Default is 100")) + (options, argv) = parser.parse_args() return (options, argv) @@ -58,5 +73,5 @@ if __name__== "__main__": print "database name =", options.dbname print "config file =", options.config - print "press enter to end" + print _("press enter to end") sys.stdin.readline() diff --git a/pyfpdb/P5sResultsParser.py b/pyfpdb/P5sResultsParser.py index 9d08daa2..9cbd579c 100644 --- a/pyfpdb/P5sResultsParser.py +++ b/pyfpdb/P5sResultsParser.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import urllib2, re import pprint from BeautifulSoup import BeautifulSoup @@ -6,7 +7,7 @@ from BeautifulSoup import BeautifulSoup playername = '' if playername == '': - print "You need to manually enter the playername" + print _("You need to manually enter the playername") exit(0) page = urllib2.urlopen("http://www.pocketfives.com/poker-scores/%s/" %playername) diff --git a/pyfpdb/PartyPokerToFpdb.py b/pyfpdb/PartyPokerToFpdb.py index cf29fb31..51eb3881 100755 --- a/pyfpdb/PartyPokerToFpdb.py +++ b/pyfpdb/PartyPokerToFpdb.py @@ -18,6 +18,9 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## +import L10n +_ = L10n.get_translation() + import sys from collections import defaultdict @@ -40,8 +43,8 @@ class FpdbParseError(FpdbParseError): class PartyPoker(HandHistoryConverter): sitename = "PartyPoker" codepage = "utf8" - siteId = 9 - filetype = "text" + siteId = 9 + filetype = "text" sym = {'USD': "\$", } # Static regexes @@ -50,7 +53,7 @@ class PartyPoker(HandHistoryConverter): re_GameInfoRing = re.compile(""" (?P\$|)\s*(?P[.,0-9]+)([.,0-9/$]+)?\s*(?:USD)?\s* (?P(NL|PL|))\s* - (?P(Texas\ Hold\'em|Omaha)) + (?P(Texas\ Hold\'em|Omaha|7 Card Stud Hi-Lo)) \s*\-\s* (?P.+) """, re.VERBOSE | re.UNICODE) @@ -97,8 +100,7 @@ class PartyPoker(HandHistoryConverter): re_NoSmallBlind = re.compile( '^There is no Small Blind in this hand as the Big Blind ' 'of the previous hand left the table', re.MULTILINE) - re_ringSB = re.compile(r"(?P.*) posts small blind \[\$(?P[.,0-9]*) USD\]\.") - re_ringBB = re.compile(r"(?P.*) posts big blind \[\$(?P[.,0-9]*) USD\]\.") + re_20BBmin = re.compile(r"Table 20BB Min") def allHandsAsList(self): list = HandHistoryConverter.allHandsAsList(self) @@ -192,9 +194,12 @@ class PartyPoker(HandHistoryConverter): info = {} handText = self.decode_hand_text(handText) m = self._getGameType(handText) - m_sb = self.re_ringSB.search(handText) - m_bb = self.re_ringBB.search(handText) + m_20BBmin = self.re_20BBmin.search(handText) if m is None: + tmp = handText[0:100] + log.error(_("determineGameType: Unable to recognise gametype from: '%s'") % tmp) + log.error(_("determineGameType: Raising FpdbParseError")) + raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp) return None mg = m.groupdict() @@ -203,21 +208,22 @@ class PartyPoker(HandHistoryConverter): games = { # base, category "Texas Hold'em" : ('hold','holdem'), 'Omaha' : ('hold','omahahi'), + "7 Card Stud Hi-Lo" : ('stud','studhi'), } currencies = { '$':'USD', '':'T$' } for expectedField in ['LIMIT', 'GAME']: if mg[expectedField] is None: - raise FpdbParseError( "Cannot fetch field '%s'" % expectedField) + raise FpdbParseError(_("Cannot fetch field '%s'") % expectedField) try: info['limitType'] = limits[mg['LIMIT'].strip()] except: - raise FpdbParseError("Unknown limit '%s'" % mg['LIMIT']) + raise FpdbParseError(_("Unknown limit '%s'") % mg['LIMIT']) try: (info['base'], info['category']) = games[mg['GAME']] except: - raise FpdbParseError("Unknown game type '%s'" % mg['GAME']) + raise FpdbParseError(_("Unknown game type '%s'") % mg['GAME']) if 'TOURNO' in mg: info['type'] = 'tour' @@ -225,8 +231,18 @@ class PartyPoker(HandHistoryConverter): info['type'] = 'ring' if info['type'] == 'ring': - info['sb'] = m_sb.group('RINGSB') - info['bb'] = m_bb.group('RINGBB') + if m_20BBmin is None: + bb = float(mg['RINGLIMIT'])/100.0 + else: + bb = float(mg['RINGLIMIT'])/40.0 + + if bb == 0.25: + sb = 0.10 + else: + sb = bb/2.0 + + info['bb'] = "%.2f" % (bb) + info['sb'] = "%.2f" % (sb) info['currency'] = currencies[mg['CURRENCY']] else: info['sb'] = clearMoneyString(mg['SB']) @@ -245,17 +261,17 @@ class PartyPoker(HandHistoryConverter): try: info.update(self.re_Hid.search(hand.handText).groupdict()) except: - raise FpdbParseError("Cannot read HID for current hand") + raise FpdbParseError(_("Cannot read HID for current hand")) try: info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict()) except: - raise FpdbParseError("Cannot read Handinfo for current hand", hid = info['HID']) + raise FpdbParseError(_("Cannot read Handinfo for current hand"), hid = info['HID']) try: info.update(self._getGameType(hand.handText).groupdict()) except: - raise FpdbParseError("Cannot read GameType for current hand", hid = info['HID']) + raise FpdbParseError(_("Cannot read GameType for current hand"), hid = info['HID']) m = self.re_CountedSeats.search(hand.handText) @@ -310,9 +326,9 @@ class PartyPoker(HandHistoryConverter): if key == 'TABLE': hand.tablename = info[key] if key == 'MTTTABLE': - if info[key] != None: - hand.tablename = info[key] - hand.tourNo = info['TABLE'] + if info[key] != None: + hand.tablename = info[key] + hand.tourNo = info['TABLE'] if key == 'BUTTON': hand.buttonpos = info[key] if key == 'TOURNO': @@ -343,15 +359,59 @@ class PartyPoker(HandHistoryConverter): if m: hand.buttonpos = int(m.group('BUTTON')) else: - log.info('readButton: not found') + log.info(_('readButton: not found')) def readPlayerStacks(self, hand): log.debug("readPlayerStacks") m = self.re_PlayerInfo.finditer(hand.handText) - players = [] + maxKnownStack = 0 + zeroStackPlayers = [] for a in m: - hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), - clearMoneyString(a.group('CASH'))) + if a.group('CASH') > '0': + #record max known stack for use with players with unknown stack + maxKnownStack = max(a.group('CASH'),maxKnownStack) + hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), clearMoneyString(a.group('CASH'))) + else: + #zero stacked players are added later + zeroStackPlayers.append([int(a.group('SEAT')), a.group('PNAME'), clearMoneyString(a.group('CASH'))]) + + if hand.gametype['type'] == 'ring': + #finds first vacant seat after an exact seat + def findFirstEmptySeat(startSeat): + while startSeat in occupiedSeats: + if startSeat >= hand.maxseats: + startSeat = 0 + startSeat += 1 + return startSeat + + re_JoiningPlayers = re.compile(r"(?P.*) has joined the table") + re_BBPostingPlayers = re.compile(r"(?P.*) posts big blind") + + match_JoiningPlayers = re_JoiningPlayers.findall(hand.handText) + match_BBPostingPlayers = re_BBPostingPlayers.findall(hand.handText) + + #add every player with zero stack, but: + #if a zero stacked player is just joined the table in this very hand then set his stack to maxKnownStack + for p in zeroStackPlayers: + if p[1] in match_JoiningPlayers: + p[2] = clearMoneyString(maxKnownStack) + hand.addPlayer(p[0],p[1],p[2]) + + seatedPlayers = list([(f[1]) for f in hand.players]) + + #it works for all known cases as of 2010-09-28 + #should be refined with using match_ActivePlayers instead of match_BBPostingPlayers + #as a leaving and rejoining player could be active without posting a BB (sample HH needed) + unseatedActivePlayers = list(set(match_BBPostingPlayers) - set(seatedPlayers)) + + if unseatedActivePlayers: + for player in unseatedActivePlayers: + previousBBPoster = match_BBPostingPlayers[match_BBPostingPlayers.index(player)-1] + previousBBPosterSeat = dict([(f[1], f[0]) for f in hand.players])[previousBBPoster] + occupiedSeats = list([(f[0]) for f in hand.players]) + occupiedSeats.sort() + newPlayerSeat = findFirstEmptySeat(previousBBPosterSeat) + hand.addPlayer(newPlayerSeat,player,clearMoneyString(maxKnownStack)) def markStreets(self, hand): m = re.search( @@ -471,7 +531,7 @@ class PartyPoker(HandHistoryConverter): hand.addCheck( street, playerName ) else: raise FpdbParseError( - "Unimplemented readAction: '%s' '%s'" % (playerName,actionType,), + _("Unimplemented readAction: '%s' '%s'") % (playerName,actionType,), hid = hand.hid, ) def readShowdownActions(self, hand): @@ -496,7 +556,11 @@ class PartyPoker(HandHistoryConverter): "Returns string to search in windows titles" if type=="tour": TableName = table_name.split(" ") - return "%s.+Table\s#%s" % (TableName[0], table_number) + print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (TableName[0], table_number) + if len(TableName[1]) > 6: + return "#%s" % (table_number) + else: + return "%s.+Table\s#%s" % (TableName[0], table_number) else: return table_name @@ -512,9 +576,9 @@ def renderCards(string): if __name__ == "__main__": parser = OptionParser() - parser.add_option("-i", "--input", dest="ipath", help="parse input hand history") - parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-") - parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False) + parser.add_option("-i", "--input", dest="ipath", help=_("parse input hand history")) + parser.add_option("-o", "--output", dest="opath", help=_("output translation to"), default="-") + parser.add_option("-f", "--follow", dest="follow", help=_("follow (tail -f) the input"), action="store_true", default=False) parser.add_option("-q", "--quiet", action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO) parser.add_option("-v", "--verbose", diff --git a/pyfpdb/PkrToFpdb.py b/pyfpdb/PkrToFpdb.py new file mode 100755 index 00000000..ecb43ae8 --- /dev/null +++ b/pyfpdb/PkrToFpdb.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2010, Carl Gherardi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +######################################################################## + +import L10n +_ = L10n.get_translation() + +import sys +from HandHistoryConverter import * + + +class Pkr(HandHistoryConverter): + + # Class Variables + + sitename = "PKR" + filetype = "text" + codepage = ("utf8", "cp1252") + siteId = 12 # Needs to match id entry in Sites database + + mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} # Legal mixed games + sym = {'USD': "\$"} # ADD Euro, Sterling, etc HERE + substitutions = { + 'LEGAL_ISO' : "USD", # legal ISO currency codes + 'LS' : "\$|" # legal currency symbols - Euro(cp1252, utf-8) + } + + limits = { 'NO LIMIT':'nl', 'POT LIMIT':'pl', 'LIMIT':'fl' } + games = { # base, category + "HOLD'EM" : ('hold','holdem'), + 'FIXMEOmaha' : ('hold','omahahi'), + 'FIXMEOmaha Hi/Lo' : ('hold','omahahilo'), + 'FIXME5 Card Draw' : ('draw','fivedraw') + } + currencies = { u'€':'EUR', '$':'USD', '':'T$' } + + # Static regexes + re_GameInfo = re.compile(u""" + Table\s\#\d+\s\-\s(?P
[a-zA-Z\ \d]+)\s + Starting\sHand\s\#(?P[0-9]+)\s + Start\stime\sof\shand:\s(?P.*)\s + Last\sHand\s\#[0-9]+\s + Game\sType:\s(?PHOLD'EM|5\sCard\sDraw)\s + Limit\sType:\s(?PNO\sLIMIT|LIMIT|POT\sLIMIT)\s + Table\sType\:\sRING\s + Money\sType:\sREAL\sMONEY\s + Blinds\sare\snow\s(?P%(LS)s|)? + (?P[.0-9]+)/(%(LS)s)? + (?P[.0-9]+) + """ % substitutions, re.MULTILINE|re.VERBOSE) + + re_PlayerInfo = re.compile(u""" + ^Seat\s(?P[0-9]+):\s + (?P.*)\s-\s + (%(LS)s)?(?P[.0-9]+) + """ % substitutions, re.MULTILINE|re.VERBOSE) + + re_HandInfo = re.compile(""" + ^Table\s\'(?P
[-\ a-zA-Z\d]+)\'\s + ((?P\d+)-max\s)? + (?P\(Play\sMoney\)\s)? + (Seat\s\#(?P
[ a-zA-Z]+) - \$?(?P[.0-9]+)/\$?(?P[.0-9]+) - (?P.*) - (?P
[0-9]+):(?P[0-9]+) ET - (?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)Table (?P
[ a-zA-Z]+)\nSeat (?P
[^']+)\'\s(?P\d+)\-max + """ % substitutions, re.MULTILINE|re.DOTALL|re.VERBOSE) + + re_TailSplitHands = re.compile(r'\n\s*\n') + re_Button = re.compile(r'Seat\s#(?P