From f17e8b54b4a3c44924aa74e74f7dfa12f511c41f Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 04:44:28 +0100 Subject: [PATCH 001/262] git1 - initial git commit. note that right now nothing works due to reworking things --- docs/abbreviations.txt | 34 + docs/agpl-3.0.txt | 661 ++++++++++ docs/benchmarks.txt | 75 ++ docs/codingstyle.txt | 25 + docs/default.conf | 6 + docs/fdl-1.2.txt | 397 ++++++ docs/filelist.txt | 74 ++ docs/git-instructions.txt | 27 + docs/install-in-gentoo.txt | 45 + docs/install-in-windows.txt | 74 ++ docs/known-bugs-and-planned-features.txt | 92 ++ docs/readme-dev.txt | 64 + docs/readme-overview.txt | 84 ++ docs/readme-user.txt | 42 + docs/release-notes.txt | 69 ++ docs/requirements.txt | 117 ++ docs/status.txt | 72 ++ docs/tabledesign.html | 1289 ++++++++++++++++++++ perlfpdb/LibFpdbImport.pm | 178 +++ perlfpdb/LibFpdbShared.pm | 84 ++ perlfpdb/LibFpdbView.pm | 17 + perlfpdb/RunFpdbCLI.perl6 | 29 + prepare-git.sh | 21 + pyfpdb/fpdb.py | 428 +++++++ pyfpdb/fpdb_db.py | 270 +++++ pyfpdb/fpdb_import.py | 192 +++ pyfpdb/fpdb_parse_logic.py | 152 +++ pyfpdb/fpdb_save_to_db.py | 123 ++ pyfpdb/fpdb_simple.py | 1345 +++++++++++++++++++++ pyfpdb/import_threaded.py | 153 +++ pyfpdb/table_viewer.py | 271 +++++ testdata/ftp-omaha-hi-pl-ring-001-005.txt | 271 +++++ testdata/ftp-stud-hilo-ring-001.txt | 61 + testdata/ftp.6367428246.expected.txt | 38 + testdata/ftp.6929537410.expected.txt | 46 + testdata/ftp.6929553738.expected.txt | 45 + testdata/mysql-dump.sql | 376 ++++++ testdata/ps-holdem-ring-001to003.txt | 169 +++ testdata/ps.14519394979.expected.txt | 40 + testdata/ps.14519420999.expected.txt | 40 + testdata/ps.14519433154.expected.txt | 49 + utils/dump_db_basedata.py | 38 + utils/fpdb_util_lib.py | 79 ++ utils/get_db_stats.py | 83 ++ utils/print_hand.py | 169 +++ utils/regression-test.sh | 40 + 46 files changed, 8054 insertions(+) create mode 100644 docs/abbreviations.txt create mode 100644 docs/agpl-3.0.txt create mode 100644 docs/benchmarks.txt create mode 100644 docs/codingstyle.txt create mode 100644 docs/default.conf create mode 100644 docs/fdl-1.2.txt create mode 100755 docs/filelist.txt create mode 100644 docs/git-instructions.txt create mode 100644 docs/install-in-gentoo.txt create mode 100644 docs/install-in-windows.txt create mode 100644 docs/known-bugs-and-planned-features.txt create mode 100755 docs/readme-dev.txt create mode 100755 docs/readme-overview.txt create mode 100644 docs/readme-user.txt create mode 100755 docs/release-notes.txt create mode 100755 docs/requirements.txt create mode 100755 docs/status.txt create mode 100644 docs/tabledesign.html create mode 100644 perlfpdb/LibFpdbImport.pm create mode 100644 perlfpdb/LibFpdbShared.pm create mode 100644 perlfpdb/LibFpdbView.pm create mode 100644 perlfpdb/RunFpdbCLI.perl6 create mode 100755 prepare-git.sh create mode 100755 pyfpdb/fpdb.py create mode 100755 pyfpdb/fpdb_db.py create mode 100755 pyfpdb/fpdb_import.py create mode 100644 pyfpdb/fpdb_parse_logic.py create mode 100644 pyfpdb/fpdb_save_to_db.py create mode 100644 pyfpdb/fpdb_simple.py create mode 100755 pyfpdb/import_threaded.py create mode 100755 pyfpdb/table_viewer.py create mode 100644 testdata/ftp-omaha-hi-pl-ring-001-005.txt create mode 100644 testdata/ftp-stud-hilo-ring-001.txt create mode 100644 testdata/ftp.6367428246.expected.txt create mode 100644 testdata/ftp.6929537410.expected.txt create mode 100644 testdata/ftp.6929553738.expected.txt create mode 100644 testdata/mysql-dump.sql create mode 100644 testdata/ps-holdem-ring-001to003.txt create mode 100644 testdata/ps.14519394979.expected.txt create mode 100644 testdata/ps.14519420999.expected.txt create mode 100644 testdata/ps.14519433154.expected.txt create mode 100755 utils/dump_db_basedata.py create mode 100644 utils/fpdb_util_lib.py create mode 100755 utils/get_db_stats.py create mode 100755 utils/print_hand.py create mode 100755 utils/regression-test.sh diff --git a/docs/abbreviations.txt b/docs/abbreviations.txt new file mode 100644 index 00000000..9e7edd16 --- /dev/null +++ b/docs/abbreviations.txt @@ -0,0 +1,34 @@ +In utility output and HUD +========================= +CLI=Command Line Interface (Shell, Terminal, "DOS-window") +A3-7=3rd-7th street Complete/Raise percentage +AF=Flop Bet/Raise percentage +AT=River Bet/Raise percentage +AR=Turn Bet/Raise percentage +F3-7=3rd-7th street Fold percentage +FF=Flop Fold percentage +FR=River Fold percentage +FT=Turn Fold percentage +FTP=Full Tilt Poker +GUI=Graphical User Interface (normal interface with buttons and menus) +HD=Hands +HUD=Heads-Up Display (shows stats directly in the poker software) +MTT=Multi Table Tournament +PFR=Pre Flop Raise +PS=PokerStars +SnG=Sit and Go +VPI3=Voluntary Put In on 3rd Street (ie. call+complete+raise) +VPIP=Voluntary Put In Preflop (ie. call+raise) + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/agpl-3.0.txt b/docs/agpl-3.0.txt new file mode 100644 index 00000000..dba13ed2 --- /dev/null +++ b/docs/agpl-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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, either version 3 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 Affero 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 . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/docs/benchmarks.txt b/docs/benchmarks.txt new file mode 100644 index 00000000..9f3bc680 --- /dev/null +++ b/docs/benchmarks.txt @@ -0,0 +1,75 @@ +benchmarks +========== +All measurements on my Athlon64@1.1GHz, 1 GB RAM, RAID5, Hardened Gentoo Linux 2.6.23 +From svn47 all measurements on my Athlon64 X2@1GHz, 2GB RAM +All measurements were ran several times in a row, unless otherwise noted the computer was mostly idle +svn12/15 were with PostgreSQL, svn23 onwards with MySQL +All import measurements were taken immediately after reseting the tables + +svn12, whilst compiling open office in the background +===== +reseting tables (incl filling default data): 0.5-0.6s +importing one hand from a file: 0.5-0.7s + +svn15 +===== +reseting tables (incl filling default data): 0.35-0.4s +importing one FTP hand from a file: 0.2-0.3s or 12-18k/hr +importing 51 FTP hands: 2.8-2.9s or 63-66k/hr +The large speedup will be partially due to not compiling in the background, and +partially due to less connecting/disconnecting the DB I'd imagine. +I'm VERY happy with this performance, but large gains could probably be made by +making the importer just a bit more sensible. + +svn23 +===== +reseting tables (incl filling default data): 0.3-0.35s +importing 75 PS hands: 3.2s or 84k/hr +importing 180 FTP hands: 7.7-7.9s 82k/hr +This is the rewrite in import_FTP_and_PS.py, svn12/15 are with import_file.py +Nice, it's substantially faster inspite of supporting PS and FTP (ish) rather +than just FTP and handling more special cases. + +svn24 +===== +importing 75 PS hands: 3.2-3.3s or 82k/hr +importing 507 FTP hands: 23.5-23.8s or 77k/hr +importing 947 FTP hands: 76.2-72.7s or 44k/hr +As you can see FTP has gotten about 6% slower on 500 hands - but the much expanded handling +of new cases (particularly the ante folding will be ALOT of extra string +comparisons) easily explains that. In fact I expected a much bigger impact. + (I suspect this is due to the parsing pausing whilst my shell catches up) + +svn47 +===== +It significantly slows down as the database gets larger, after a few thousand +hands it got to just one hand per second and slower. However, around 90% of the +CPU load was MySQL. Any tips are most welcome.. + +svn52 (whilst watching movie in a small window) +===== +Importing 2500 hands (mixed sites+types): 6m15s or 31k/hr (ran twice) +Of these 230 were skipped as duplicate or partial so counting 2270 hands + +svn53 (whilst watching movie in a small window) +===== +Importing the 2270 hands from svn52: 1m50 or 74k/hr (82 if counting dupes/partial) +Importing my 35661 (plus 427 dupe/part) hands: 51m51s or 41k/hr (ran once) + +The dbsize still has a significant impact, but with these sizes certainly it's +just not a problem. If anyone runs a larger database let me know how long it takes. +Thanks bwarycha for the tip with the foreign keys! + + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/codingstyle.txt b/docs/codingstyle.txt new file mode 100644 index 00000000..2fc578e9 --- /dev/null +++ b/docs/codingstyle.txt @@ -0,0 +1,25 @@ +This is just a loose collection of things so far, but might as well make a start :) + +A word on wrapping: Please avoid making manual line breaks, the computer can and therefore should do it. Whether people use a phone or a 40" super-uber-HD screen, they should be allowed to use as much of it as they wish to. + +Comments (or prints) with todo are things that are missing, bugs, or just messy code. + +After every def (ie. at the end of the method) there should be a comment including the name, e.g. +#def end of parseActionLine + +If you don't mind make names in java style, ie.: +Classes, files or tables like this: MyClassName +Methods and variables like this: myMethodName + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/default.conf b/docs/default.conf new file mode 100644 index 00000000..05bdb743 --- /dev/null +++ b/docs/default.conf @@ -0,0 +1,6 @@ +backend=2 +host=localhost +database=fpdb +user=fpdb +password=enterYourPwHere + diff --git a/docs/fdl-1.2.txt b/docs/fdl-1.2.txt new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/docs/fdl-1.2.txt @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/docs/filelist.txt b/docs/filelist.txt new file mode 100755 index 00000000..bf65ca39 --- /dev/null +++ b/docs/filelist.txt @@ -0,0 +1,74 @@ +todo: update filelist.txt + +File list +========= +.: +docs/ Documentation files +importer/ Directory with the importer (in python) +setup/ Directory with files for setting up this program +testdata/ Directory with test data +utils/ A good abbreviation for "all the crap that doesn't fit in anywhere else" +viewer/ Directory with the GUI (in Java) + +./docs: +abbreviations.txt A list of abbreviations used and their meaning +agpl-3.0.txt License of the program (everything under /code) +benchmarks.txt Some benchmark results +codingstyle.txt Some notes on formatting. Feel free to ignore. +fdl-1.2.txt License of the documentation (the files in /) +filelist.txt This file +howto-import.txt Instructions on how to run the importer +install-in-gentoo.txt Installation instructions for Gentoo GNU/Linux +install-in-windows.txt Installation instructions for Windows +readme.txt This file +status.txt Details of support for poker sites +tabledesign.odt The table design master (OpenDocument file, MS Office needs plugin) +tabledesign.html Table design copy (HTML file) + +./importer: +fpdb.py The main GUI. This is what the user will start and use to access the other things. +fpdb_import.py Main import program. Calls methods in the other files. + Takes one hand history file as input. This is the file + you execute, do not run the other ones individually. + Except import_gui.py of course. +fpdb_parse_logic.py Parses a holdem/omaha/razz/stud hand. +fpdb_save_to_db.py Just methods to store the parsed data into SQL. + Seperate file because these calls are very unwieldy. +fpdb_simple.py Simple methods called by the other files. Most work is + actually done in this file to make the other ones look + much easier than they are. +import_gui.py GUI interface to the importer (obselete) + +./setup: +insert-basedata.sql Fills sites and gametypes tables. Run this once after running the above. +recreate-tables.sql File for mysql to recreate the tables. THIS WILL DELETE EXISTING TABLES!!! + +./testdata: +should be self explanatory + +./utils: +dump_db_basedata.py Prints the contents of the tables sites and gametypes +fpdb_util_lib.py Helper methods for the utilities. +get_DB_stats.py Prints some counts (like no. of players in the DB) +get_player_stats.py Prints certain stats about players with CLI-passed constraints +mysql-reset-tables.sh Reset tables for MySQL. THIS WILL DELETE EXISTING TABLES!!! +print_hand.py Prints a hand in legible format. +psql-interactive.sh *nix script to connect to a PostgreSQL database in + interactive mode (note that we're currently only supporting MySQL) +regression-test.sh Resets tables and checks manually verified hands for errors + +./viewer: +todo + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/git-instructions.txt b/docs/git-instructions.txt new file mode 100644 index 00000000..1d455453 --- /dev/null +++ b/docs/git-instructions.txt @@ -0,0 +1,27 @@ +Hi, welcome to my minimal git guide for fpdb devs! +I'll expand this on request, if you have any questions just send me a mail at steffen@sycamoretest.info. + +How to make a local git commit +============================== +go to the root of your fpdb directory and type: +git-add--interactive +If you added any new files press a and Enter, then type the number of your new file and press Enter twice. If you made any changes to existing files press u and enter. If you want to commit all changes press * and Enter twice. Press q to leave git-add--interactive. +Then create a file for your commit message (I call it since_last_commit.txt) but don't add this to the repository. In the first line of this file put a summary of your changes. If you wish to you can also add in a revision number. My tree (the "central" or "official" repository) uses the format gitX where X is a running number, e.g. git91 is followed by git92. Then give some details of your changes, try to mention anything non-trivial and definitely any user-visible bug fixes. If the table design has been changed that has to be mentioned in the first line. +Then run this: +git-commit -F since_last_commit.txt + +todo: how to pull/push changes to/from me +todo: git-diff, git-rm, git-mv + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/install-in-gentoo.txt b/docs/install-in-gentoo.txt new file mode 100644 index 00000000..b9f646e2 --- /dev/null +++ b/docs/install-in-gentoo.txt @@ -0,0 +1,45 @@ +Last checked: 3 Aug 2008, git99 + +These instructions are for Gentoo GNU/Linux, but if you adapt the steps +installing and starting stuff it should work on any other OS as well. + +1. Install everything. Check if anything is already installed and if it is remove it from the command. + +For mysql: +emerge mysql mysql-python pygtk -av +/etc/init.d/mysql start +rc-update add mysql default + +For postgresql: +emerge postgresql pygresql pygtk +/etc/init.d/postgresql start +rc-update add postgresql default + + +2. Manual configuration steps + +emerge --config mysql +The --config step will ask you for the mysql root user - set this securely, we will create a seperate account for fpdb + +Create a user and a database, the default names are fpdb but you can choose whatever you want to. I did this in webmin, but it will be added to the GUI as well. Then set permissions for that user to: Select | Insert | Update | Delete | Create | Drop + +Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edit it according to what you configured just now, in particular you will definitely have to put in the password you configured. YES, THIS IS INSECURE. + +3. Guided installation steps +Run the GUI as described in readme-user and click the menu database -> recreate tables + +That's it! + + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/install-in-windows.txt b/docs/install-in-windows.txt new file mode 100644 index 00000000..b7939f94 --- /dev/null +++ b/docs/install-in-windows.txt @@ -0,0 +1,74 @@ +These instructions are for 32/64bit Windows NT/2k/XP/2k3/Vista/2k8. Well, in principle. I +made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP. +Also see the other install-in-*.txt files. +The length of these instructions is due to MS refusal to provide any kind of package management. + +For some packages I've given direct(ish) download links here, for the remainder check requirements.txt. + +1a. Install MySQL and do its basic setup +Download Windows ZIP/Setup.exe from http://dev.mysql.com/downloads/mysql/5.0.html#win32 +Note that there is a link to skip registration/login. +As of this writing the latest binary version is 5.0.51b whilst the latest version is 5.0.56... Windows. +- Unzip the archive, execute the setup file + At the end make sure you activate that you want to configure it now. + Use the advanced/detailed config. Leave everything as default unless stated below, or unless you have reason not to. + Make sure to DEACTIVATE TCP/IP networking, unless you want that and know how to secure it + Activate "include bin directory in windows PATH" + Set a root password. Note that this is not the account/pw that fpdb will use. + +Once finished it shold confirm "service started successfully" + +2. Install python +Go to http://www.python.org/download/ and get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts. + + +3. Install the Python-DBAPI package for MySQL: +Go to http://sourceforge.net/project/showfiles.php?group_id=22307 and get the latest version of MySQL-python-1.2.2.win32-py2.5.exe +Double click to install. + + +4. In MySQL create a new database fpdb and a user by the same name. Set a password. I did this in webmin. Then set permissions for that user to: Select | Insert | Update | Delete | Create | Drop + +5. Time for GTK+ - here's the instructions from their bundle + +To use it, create some empty folder like c:\gtk . Using either +Windows Explorer's built-in zip file management, or the command-line +unzip.exe from +ftp://tug.ctan.org/tex-archive/tools/zip/info-zip/WIN32/unz552xN.exe +unzip this bundle. (But you presumably already did that, as you are +reading this file.) + +Then add the bin folder to your PATH. Make sure you have no other +versions of GTK+ in PATH. +To do that: +Right click on "My Computer" ("Arbeitsplatz" in German Windows) on the Desktop or in (Windows) Explorer. Select Properties. Then click on the tab Advanced and then you should see Environment Variables. Simply append GTK's bin folder to the existing PATH (make sure to put a ; between the old PATH and GTK's folder to seperate the entries in this list). + +6. Install pycairo, pygobject and pygtk with double click. + +7. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf + +Now edit the file, in particular you will always have to type in the correct password (insecure, I know) and if you differ from the default setup you may need to change host, database or user. + +8. Open a shell (aka command prompt, aka DOS window) and go to the fpdb-python folder and run fpdb.py, e.g.: +c: +cd \fpdb\fpdb-python +python fpdb.py + +You can run this by double click, but then any error message would be lost. + +When the program started open the menu Database and click "Create or Recreate Tables". + +That's it! Now you can use the bulk importer and the table viewer, more's coming. See readme-user.txt + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt new file mode 100644 index 00000000..51d6f7a2 --- /dev/null +++ b/docs/known-bugs-and-planned-features.txt @@ -0,0 +1,92 @@ +todolist (db=database, imp=importer, tv=tableviewer) + +before beta +=========== +current speedup attempt todo: +holdem in fpdb_simple done - now update remaining files, test, then add back postflop and stud functionality + +import fails on stud/razz +tv doesnt display street 3-5 in stud/razz i think +Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu +find out if i can SQL for the rowcount, rather than select a field and then just take the rowcount. this might bring a significant performance improvement +make a quick benchmark of mysql and postgresql: import of my whole db, some tableviewer refreshes with and without updated file +fix tv browse button size +tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem +tourney bug: fails recognisePlayer +tourney bug: fails with tuple error in recogniseplayerid +verify at least 2 or 3 sng hands +db+imp+tv WtSD (went to showdown) +db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) +db+imp+tv WwSF (Won when seen flop - partial taken into account) +db+imp+tv steal blind from btn, co, lmp. fold SB/BB/BI to steal +remove unused flags fields +update install instructions +setup wizard +change action_no to be total for this street rather than just for one player +catch index error, type error, file not found error +add field for if a game was mixed +implement error file in importer +Make tab and enter work as sensible in GUIs and implement Ctrl+Q, Ctrl+X and Alt+F4 for close. +use profile file for bulk import and table viewer settings and pathes +fold% also counts rounds where nobody raised +handle errors properly, in particular wrt to SQL rollback. +remove mysql/myisam support. +check that we read sitout correctly in: Full Tilt Poker Game #6150325318: Table Bogside +setup database, database-user and permission from GUI. +update prepare-git to check for license header and copyright. + +change/expand print_hand to cover everything new and update verified hands' .found file + +no rush but before 1.0RC +======================== +make tv work with ftp e.g. by making importer return site as well (easy) +make the gui display errors +log file +move directory import code from gui to backend +convert fpdb_import to not require passing "self" +(tedious general stability improvement for unusual playernames): change all the str.find so they dont accidentially count player names containing the searched phrase. e.g. with rfind. +Doesn't handle Daylight Saving Time (I don't think at least) +Need to store if someone goes all-in, particularly for better NL/PL support. +verify at least 3 hands per category per site per limit_type (when cap then do 2 normal and one 1 capped) incl tv display +put lines in tv to make it easier to read +speed up so that refresh takes no more than 10 seks on my P3M-800 +select range of stakes and sng/mtt values and types for tv +change "for i" to more sensible var name instead of i +change stud street storage from 3-7 to 0-4 throughout +in all importer: stop doing if site=="ftp", make class constants for site_id instead +recognise somewhere if a file is still active and if so keep it open and only read new hands rather than detecting dupes +gentoo ebuild +separate all gui and all processing into files that are named accordingly +ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. +why do we have to reconnect in tv.read_names_clicked? +HUD for PokerStars +HTMLify docs + +can wait till 1.x +================= +finish cleaning tabledesign html code +It treats fold due to disconnect as voluntary fold which is not ideal +auto-import +check for unnecessary db.commit() +aliases +Probably PartyPoker for all or most supported games +repair hands where the seat lines are missing, happens when observing at FTP +flags for storing the reason for winning (best hi, tie for best low, etc.) to DB. not sure actually if this is such a good idea remember that there can be multiple reasons for the same player in the same hand +windows integrated installer +benchmark properly on mysql innodb, mysql myisam, postgresql, sqlite, more? +rename things like this: ClassName.methodName and variableName. do this on tables too. update codingstyle +CLI (not ncurses, proper CLI) equivalent for fpdb.py +optimise/simplify storing by creating the SQL statements depending on hand rather than calling different methods + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/readme-dev.txt b/docs/readme-dev.txt new file mode 100755 index 00000000..ee578f46 --- /dev/null +++ b/docs/readme-dev.txt @@ -0,0 +1,64 @@ +Hi, +This document is to serve as a first point of contact for current and prospective developers with: +a) organisational/legal things +b) introduction into the code structure + +What to do? +=========== +- Anything you want. +- The most useful (because it's the most boring) would be to update print_hand.py, update the expected files (testdata/*.found.txt) and create more .found.txt to ensure import processing is running correctly. +- There's a list of various bugs, deficiencies and important missing features in known_bugs_and_planned_features.txt. +- In the main GUI there's various menu points marked with todo - all of these will have to be done eventually. + +If you want to take a look at coding-style.txt and git-instructions.txt or feel free to send patches or even just changed file in whatever code layout or naming convention you like best. I will, of course, still give you full credit. + +Contact/Communication +===================== +Please see readme-overview + +Dependencies +============ +Since all real OS' have easy built in handling for dependencies feel free to add requirements on new libraries etc. Unfortunately due to the reality of the online poker market (namely the complete absence of clients for free/libre systems) it doesn't make sense to write this without supporting Windows so all dependencies must have a source-compatible Windows version. Please ensure to list any new deps in requirements.txt or let me know. + +Code/File/Class Structure +========================= +Basically the code runs like this + +fpdb.py -> bulk importer tab (import_threaded.py) -> fpdb_import.py -> fpdb_parse_logic.py -> fpdb_save_to_db.py + -> table viewer tab (table_viewer.py) + +All files can call the simple methods that I just collected in fpdb_simple.py, in essence to reduce the other files mostly to high-level calls that direct the execution flow. +I'm currently working on (amongst other things) integrating everything into the fpdb.py GUI with a view to allow easy creation of a CLI client, too. + +Also see filelist.txt. + +How to Commit +============= +Please make sure you read and accept the copyright policy as stated in this file. Then see git-instructions.txt. Don't get me wrong, I hate all this legalese, but unfortunately it's kinda necessary. + +Copyright/Licensing +=================== +Copyright by default is handled on a per-file basis. If you send in a patch or make a commit to an existing file it is done on the understanding that you transfer all rights (as far as legally possible in your jurisdiction) to the current copyright holder of that file, unless otherwise stated. If you create a new file please ensure to include a copyright and license statement. + +The licenses used by this project are the AGPL3 for code and FDL1.2 for documentation. Why AGPL3? As far as I know it is currently the strongest copyleft license. + +Preferred File Formats +====================== +Preferred: Where possible simple text-based formats, e.g. plain text (with Unix end of line char) or (X)HTML. Preferred picture format is PNG. IE-compability for HTML files is optional as IE was never meant to be a real web browser, if it were they would've implemented web standards. + +Also good: Other free and open formats, e.g. ODF. + +Not good: Any format that doesn't have full documentation freely and publicly available with a full license for anyone to implement it. Sadly, Microsoft has chosen not fulfil these requirements for ISO MS OOXML to become a truly open standard. + +License (of this file) +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/readme-overview.txt b/docs/readme-overview.txt new file mode 100755 index 00000000..b0711dc5 --- /dev/null +++ b/docs/readme-overview.txt @@ -0,0 +1,84 @@ +Summary +======= +A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Support Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future + +Longer summary +============== +This program is poker tracking software, a class of utilities that will record and track every little detail of your poker sessions. Want to know how many times player Y has raised before the flop at your current stakes? This program will tell you - allowing you to make much more accurate reads (poker slang for "[educated] guessing your opponents hand") and as a result better decisions. Of course in poker the end result in $ is never guaranteed, but this software will make it much easier for you. +The software currently supports importing and processing hand history files for: +- Holdem, Omaha (Hi and Hi/Lo), Stud (Hi and Hi/Lo) and Razz (I am not aware of any modern software that supports more than Holdem) +- No Limit, Pot Limit, Fixed Limit and Full Tilt's CapNL and CapPL +- Any stakes +- Cash games, Sit and Gos and Multi Table Tourneys +- All the above from PokerStars, all the above except SnG/Tourney for Full Tilt + +Installing +========== +See the install-*.txt for your operating system. If your OS is missing or if you have problems let me know (contacts are further below). In particular I'd be happy to provide packages for GNU/Linux and *BSD distributions. + +Using it +======== +See readme-user.txt +If you have a problem, request or question see the contacts section below + +Changing it +=========== +See readme-dev.txt + +Requirements +============ +Software requirements are listed in requirements.txt +As for hardware, my main test machine is a Pentium 3-M 800 with 256 RAM and Gentoo GNU/Linux +(running the poker client through what most people will call emulation). So this +program will have to work on that. If you run an even more ancient machine and +its too slow let me know and I'll see what I can do :) + +Contact +======= +Please contact me directly using one of the below (quickest response will be by jabber/xmpp/google talk) until I have setup Savannah. +mail: steffen(at)sycamoretest.info +jabber/xmpp/Google Talk: as above +ICQ: 7806355 +MSN: steffenjf@gmx.de (don't email that) + +Why Free Software? +================== +This program is released under the terms of the free/libre software license AGPL3 as released by the FSF. The AGPL3 protects your rights and those of the wider community. As Richard Stallman, one of the founders of the free software movement, put it: "Free software is a matter of liberty, not price. To understand the concept, you should think of free as in free speech, not as in free beer." (well, it is both really, like the right to vote used to be free) + +For example, a "pirated" copy of proprietary software X is free of charge, but you don't actually have a legal right to use it, you don't have any possibility to fix its bugs and you certainly don't have any legal right to share it with your friends. You also won't be able to get support, often not even security fixes. Actually, even if you pay hundreds of pounds for your program they deny your right to fix their errors for them. Imagine buying a car where you're not permitted (under threat of jail) to replace parts.. + +With free/libre software (also known as open source software, or short FOSS or FLOSS) on the other hand you get all these freedoms: +(note: the legally binding terms are in the license text, this is merely an amateur summary so normal people don't have to read pages of silly legalese) + +Freedom 0: The freedom to use: To run the program, for any purpose. +Freedom 1: The freedom to study and help yourself. This freedom guarantees your right to study and learn from the source code of the program, and to fix it if it is broken. If you're not a programmer yourself the developers will generally be happy to fix it for you. Failing that you can always pay someone from the money you saved on not having to pay for it. +Freedom 2: The freedom to be a decent human being and help your neighbour: I don't threaten you with criminal charges or jail time if you share with your friends and neighbours, subject to the very modest restrictions of the AGPL3. +Freedom 3: The freedom to improve the program and release your improvements to the public (or parts thereof) so that the whole community benefits. Note that you are PERMITTED, but not REQUIRED to distribute your changes. If you do distribute your changes you must do so under the terms of the AGPL3 however. + +Note that this is the license - I retain full copyright over my code, including the right to change the license for future versions. I do not intend to do this however. In any case, any version I released under AGPL3 remains available under that license forever, or more accurately until my copyright expires at which point it goes into the public domain. + +I reject the concept of software patents as a crime and under the European Patent Agreement software patents - even if you mislabel them as "computer-implemented inventions" or whatever - are explicitly prohibited. This view has been uphelp by numerous courts including the highest German appeals courts for patent matters (BGH-Zivilsenat?). +I therefore do not recognise the validity or applicability of any software patent. I realise that the European Patent Office completely ignores the laws and treaties that govern its operation but that means that EPO management are criminals, not that software patents are valid. + +Can I get/use this under a different license? +============================================= +The short answer: Maybe. +The long one: As detailed, I fully support what the FSF does and aims to achieve with the GPL. However, I realise that many free software developers don't object to closed source, some don't even object to closed source profiteering of their charity, and I don't think I have any right to go and tell them they're wrong. +So if anyone wishes to use all or part of my code in another open source project with an AGPL3-incompatible license then let me know and we should be able to come to an agreement. +If you wish to use all or part of this in closed source let me know how much that is worth to you :) + + +Disclaimer, License of this Document +==================================== +The views expressed in this document are those of Steffen Jobbagy-Felso, other members of the fpdb team and external contributors may or may not agree. + +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/readme-user.txt b/docs/readme-user.txt new file mode 100644 index 00000000..7ae81cb6 --- /dev/null +++ b/docs/readme-user.txt @@ -0,0 +1,42 @@ +Before you do this make sure you setup the dependencies, the database, user, tables and config file. + +Running it +========== +If you have python setup properly you can execute it by double clicking fpdb-python/fpdb.py. + +Note however that all error messages are currently only printed if you call it from a shell. It'll be much easier to diagnose possible problems (which are likely in alpha stage) if you run it from a shell. To do that open a shell (aka command prompt aka DOS window aka terminal) and run the following commands. +For *nix, e.g. if its in /home/sycamore/fpdb/: +cd /home/sycamore/fpdb/fpdb-python +python fpdb.py + +In Windows, e.g. if its in C:\Program Files\fpdb +C: +cd "\Program Files\fpdb\fpdb-python\" +python fpdb.py + +That will start the main GUI. + +Have a look at the menues, the stuff that is marked todo is not yet implemented. + +The main things are the bulk importer and the table viewer. To use the importer open it from the menu (import files and directories). You can set a few options at the bottom, then select a folder or single file in the main are and click Import. Please report any errors by one of the contacts listed in readme-overview.txt. +Currently this will block the interface, but you can open another instance of this program e.g. if you wanna play whilst a big import is running. + +To use the table viewer open it from the menu, select the hand history file of the table you're at, and click the Import&Read&Refresh button. The abbreviations there are explained in abbreviations.txt, but feel free to ask. + +Please check the output at the shell for errors, if there are any please get in touch by one of the methods listed in readme-overview.txt + +Please let us know whether you like this software :) +Contacts are listed in readme-overview or email steffen@sycamoretest.info + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/release-notes.txt b/docs/release-notes.txt new file mode 100755 index 00000000..58d800b0 --- /dev/null +++ b/docs/release-notes.txt @@ -0,0 +1,69 @@ +v0.01(alpha) draft (ie. minimum requirements for alpha1) +Hi everyone, +we are proud to announce the first release of our new poker tracking software fpdb (freepokerdb, very imaginative I know ;) ). You may wonder why we bothered when now with HM and PT3 there are at least two excellent packages to choose from. + +Three main reasons: +1. Fpdb is free/libre open source software. In short, this means you don't depend on us if sth. is wrong or you want something more in this program as you can freely change it yourself. You also don't have to pay anything for it. If you like it and think we deserve to be paid contact us :) +2. HM and PT3 only support holdem. Fpdb (initially) supports Holdem, Omaha, Razz and Stud including Hi/Lo versions. +3. HM and PT3 run on Windows only, and for me at least did not work in wine even after installing Mono. Fpdb runs natively on any plattform that has the required software, which will cover roundabout 99.9% of PCs that are in use ;) + You still need to run Windows or wine to run the actual poker client though. +4. Fpdb won't irritate you with copy prevention measures, e.g. HM will require re-activation after some types of partition change. To be fair I should add that the support is fast, friendly and helpful. Nevertheless I just don't appreciate being hassled AFTER I pay. + +This is alpha1, as the name indicates it is still at a very early stage. The importer and database are nearing completion but the GUI in particular is not very functional yet and the HUD is missing altogether. However the difficult bit (getting it all to work nicely together) for v1 is done, now we "just" need to add lots and lots of options, testing and output. + +Current feature list: + +Interface +========= +- Central interface programs with tabs (similar to Azureus classic) +- Follows convention on how things are arranged and what they look like. +- Works equally in *nix and Windows (tested on Gentoo GNU/Linux, MacOSX and WinXP) +- Command line interface planned +- DB setup from inside the GUI +- Bulk importer for single files, multiple files, or directories (incl recursion) +- Auto-importer +- Multi-threaded - running the importer doesn't freeze the view tabs and they don't freeze each other either (blocker for beta) +- Profiles (to store different settings) +- HUD (would be nice for alpha but not a blocker for release) + +Backend, Distribution +===================== +- Choice of MySQL/InnoDB or PostgreSQL. (not tested on PostgreSQL) +- It is possible to run the database on one PC, the importer on another, and then access the database with the table viewer or HUD from a third PC. (note: do NOT do this unencrypted over an untrusted network like your employer's LAN or the Internet!) + +Site/Game Support +================= +- Initially only full support for PS, ring games also work in FTP. +- Supports Holdem, Omaha, Razz and Stud including Hi/Lo split where applicable +- Supports No Limit, Pot Limit, Fixed Limit NL, Cap NL and Cap PL + Note that currently it does not display extra stats for NL/PL so usefulness is limited for these limit types. Suggestions welcome, I don't play these. +- Supports ring/cash games as well as SnG and MTT tourneys + + +- Tableviewer (tv) interface to the database. The application is currently single-threaded (though the backend DB doesn't have to be) but I will fix that. Until then you can just open the interface twice, once for import and once for tv. Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are: + - VPIP, PFR, Steal from SB, Steal from BTN, Steal from CO, Avg Steal + - average bet size for NL/PL (blocker for beta) + - 3/4B, 2B, Raise and Fold % on flop, turn and river. + - Aggr % if everyone folded to player in final position of round + - Number of hands this is based on. + - Equivalent functions for Razz/Stud + +- You can edit/add whatever you like, it's all python and SQL. The code should be fairly straightforward I think and I put some notes into readme-dev.txt but feel free to ask. + +If you can live with alpha/beta software please give this a go and send any feedback, feature requests, bug reports and animal names to steffen@sycamoretest.info. Of course support and feature requests with patches or payment offers will be prioritised. + +IMPORTANT: The database format will undergo more changes and at this point I am not planning to write a converter so please keep your history files so you can re-import when necessary. Independent of this you should always keep the original raw files in a save place with any tracking software. + + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100755 index 00000000..a28b3ff7 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,117 @@ +I recommend using a free/libre operating system, meaning a GNU/Linux distribution or a BSD variant (e.g. Gentoo GNU/Linux or OpenBSD) for ethical and practical reasons. Would you buy a car where you're prohibited from opening the bonnet under threat of jail? If the answer is no you should by the same logic not use closed source software for real money Poker :) + +Unfortunately you will always need one piece of unfree software: The poker client itself. Although not a direct dependency of fpdb you obviously will have a hard time putting this to productive use without running some poker client. As far as I know, only unfree clients are available. If you know better please let me know ASAP! + +If you can be bothered please do contact your poker site(s) and ask them to release free/libre clients, even if it is only for Windows. But lets be realistic, the chance of a positive answer is very low. + +Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least). + + +Make new entries in this format: +X. Program Name +=============== +a. Optional? +b. Required Version and Why +c. Project Webpage +d. License + +1. MySQL +======== +a. Optional? + Choose MySQL or PostgreSQL +b. Required Version and Why + At least 3.23 required due to mysql-python. + I use 5.0.54 and 5.0.60-r1 (GNU/Linux) and 5.0.51b (Windows). +c. Project Webpage + http://www.mysql.com +d. License + GPL2 + +2. PostgreSQL +============= +a. Optional? + Choose MySQL or PostgreSQL +b. Required Version and Why + I use 8.0.15 (GNU/Linux) and 8.3.3 (Windows) but I am not aware of any incompatibilities + with older or newer versions, pls report success/failure. +c. Project Webpage + http://www.postgresql.org +d. License + BSD License + +3. mysql-python +=============== +a. Optional? + Required if you want to use MySQL backend +b. Required Version and Why + I use 1.2.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure. +c. Project Webpage + http://sourceforge.net/projects/mysql-python/ +d. License + SF lists GNU General Public License (GPL), Python License (CNRI Python License), Zope Public License. + Project states GPL without version in Pkg-info. + +4. pygresql +=========== +a. Optional? + Required if you want to use PostgreSQL backend +b. Required Version and Why + I use 3.6.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure. +c. Project Webpage + http://www.pygresql.org/ +d. License + http://www.pygresql.org/readme.html#copyright-notice (BSD License?) + Summary: "Permission to use, copy, modify, and distribute this software and its + documentation for any purpose, without fee, and without a written agreement + is hereby granted[...]" plus Disclaimer. + +5. Python +========= +a. Optional? + Required. +b. Required Version and Why + I use 2.4.4 and 2.5.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure. +c. Project Webpage + http://www.python.org +d. License + Python License + +6. GTK+ and dependencies +======= +a. Optional? + Required. +b. Required Version and Why + I use 2.12.9 but it should run with 2.10 or higher. That is needed as I used MessageDialog updates +c. Project Webpage + Main: http://www.gtk.org/ + API spec: http://library.gnome.org/devel/gtk/2.12/ + Windows DLs (get the bundle unless you know what you're doing): http://www.gtk.org/download-windows.html +d. License + LGPL2 + +7. PyGTK +======== +a. Optional? + Required. +b. Required Version and Why + I use 2.12.0 but it should run with 2.10. That is needed as I used AccelMap. +c. Project Webpage + main: http://www.pygtk.org + Note for Windows: Due to the lack of package management you have to manually get PyGTK's dependencies (PyCairo and PyGobject). +d. License + LGPL2.1 + + + +License (of this file) +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/status.txt b/docs/status.txt new file mode 100755 index 00000000..fb7c0f3c --- /dev/null +++ b/docs/status.txt @@ -0,0 +1,72 @@ +For all support please note that the tables will almost certainly be changed without keeping backwards compatibility. Therefore you should keep your history files after import so you can re-import if necessary. + +If support for another site/game would encourage you to help with this software please let me know at steffen@sycamoretest.info. + +Note for git100 until further notice (not long though): deactivated all import of stud/razz pending update of that code for the new caching tables. + +Database +======== +Holdem, Omaha Hi/HiLo, Razz and Stud Hi/HiLo in NL/PL/FL are supported for ring games and tourneys. Actual storage tables are nearly final. Support/cache tables for improved viewer speed are in heavy flux. + +Importer (imp) +============== +Ringgames on FTP and PS: +Holdem, Omaha Hi/HiLo, Razz and Stud Hi/HiLo in NL/PL/FL/CapNL/CapPL: beta + This should be pretty much done, I imported 30k hands. + Bug reports welcome, please send the hand that caused the error. + +Tourney support: on PS alpha, on FTP incomplete, see svn comments. + +Mixed Games (e.g. HORSE): alpha + +GUI for importer +================ +alpha +Most functionality still missing, but thats a backend problem not really a GUI thing. + +CLI/automated testing interface +=============================== +Simple DB Statistics Script -> migrate to GUI +Simple player stats report script -> migrate to GUI +Hand printout script -> badly needs updating +Simple regression testing script done. Need to updating existing data and create (much) more. + +Table Viewer (tv) +================= +pre-alpha +Currently the importer fills a flags table, and tv selects an id field from that and does some things to create the data. Thinking of instead or additionally creating a count table that imp would also directly access. Imp would increment the values for each player that tv currently has to select for - implementing this now, as of git100. +I know tv is rather low on functionality so far, but as soon as I've finished the above update adding more will be easy and quick. + +HUD +=== +? + +Packaging +========= +Gentoo GNU/Linux: I'll make ebuilds, if you want one now let me know. +Other free/libre systems (e.g. other GNU/Linux-Distros, *BSD): Let me know what you use (OS/Distribution+version, arch) and I'll make a package. +Windows/MacOSX: Manual installation of dependencies for now, integrated installer would be nice - volunteers welcome, but I'll do it eventually in any case. + +Legal +===== +General: This will offer more or less the same kind of functionality as HM and PT so there shouldn't be any problem on any site that allows tracking. +PS: I have asked them and they said that due to the fast changing nature + of pre-release software they don't review that. However, looking at their + posted guidelines we're fine +FTP: I sent them an email and what I described there was ok for them. + +A note on viewing/tracking mucked hands. Many people will say this is cheating. I agree. However, I don't make the rules on these sites. The sites that store mucked hands into the history file clearly think that being able to see other people's mucked hands is ok. This software - like HM and PT - merely takes info provided by the poker site and makes it more easily accessible. +Apparently PS has now turned this off. + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/tabledesign.html b/docs/tabledesign.html new file mode 100644 index 00000000..3cfa5c48 --- /dev/null +++ b/docs/tabledesign.html @@ -0,0 +1,1289 @@ + + + + + Free Poker DB Tabledesign + + +

see commit comments for version info

+

Direct suggestions, praise and animal names to steffen@sycamoretest.info

+

TODO clean all the crap out of this like i did in HudData

+

I decided to be generous on the sizes of the types - if computing +experience shows one thing then its that it will come back to bite +you in the ass if you save 2 bits in the wrong place. If performance +and/or db size are too bad we can still shrink some fields.

+

Relationships +are noted in the comment (need to double check that all are listed)

+

If +you want more comments or if anything is confusing or bad let me +know.

+


+

+

All +money/cash amounts are stored in cents/pennies/whatever (e.g. $4.27 +would be stored a 427). Chips are stored as-is (e.g. 3675 chips would +be stored as 3675).

+


+

+

Support +for ringgames in Holdem, Omaha, Razz and Stud complete. Support for +SnG/MTT is alpha

+


+

+

Notes +on use/editing:

+

Any +change to this must be carried to to the table creation code unless it +is clearly noted in appropriate places.

+

If the code (in particular the importer) and this document disagree then this document is to be considered authorative. Please report such mismatches to steffen@sycamoretest.org or through sourceforge.

+

License
+Trademarks of third parties have been used under Fair Use or similar laws.
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 as published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license can be found in fdl-1.2.txt
+The program itself is licensed under AGPLv3, see agpl-3.0.txt

+

See +readme.txt for copying

+


+

+

Table +players

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Field name

Type

Comment

+

id

+
+

int

+
+


+

+
+

name

+
+

varchar(32)

+
+


+

+
+

site_id

+
+

smallint

+
+

references sites.id

+
+

comment

+
+

text

+
+


+

+
+

comment_ts

+
+

datetime (in UTC)

+
+


+

+
+


+

+

Table +autorates

+

An +autorating is a computer-"recognised" label/category for a +player. Examples could include "Calling Station" if a +player has <20% each for aggression and folding postflop. Or +"Tight-Aggressive/Aggressive" for players with <20% +VPIP, >10% PFR and >40% postflop aggression.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Field name

+
+

Type

+
+

Comment

+
+

id

+
+

bigint

+
+


+

+
+

player_id

+
+

int

+
+

references players.id

+
+

gametype_id

+
+

smallint

+
+

references gametypes.id

+
+

description

+
+

varchar(50)

+
+

autorating description

+
+

short_desc

+
+

char(8)

+
+

short description e.g. for + display in HUD

+
+

rating_time

+
+

datetime (in UTC)

+
+

timestamp of rating

+
+

hand_count

+
+

int

+
+

number of hands rating is + based on

+
+


+

+


+

+


+

+

Table +gametypes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Field name

+
+

Type

+
+

Comment

+
+

id

+
+

smallint

+
+


+

+
+

site_id

+
+

smallint

+
+

references sites.id

+
+

type

+
+

char(4)

+
+

valid + entries:

+

cash + - aka ringgames

+

tour - tournament

+
+

category

+
+

varchar(9)

+
+

valid + entries:

+

holdem=Texas + Hold'em

+

omahahi=Omaha + High only

+

omahahilo=Omaha + 8 or better

+

razz=Razz

+

studhi=7 + Card Stud High only

+

studhl=7 Card Stud 8 or + better

+
+

limit_type

+
+

char(2)

+
+

nl=No + Limit

+

cn=Cap + No Limit

+

pl=Pot + Limit

+

cp=Cap + Pot Limit

+

fl=Fixed Limit

+
+

small_blind

+
+

int

+
+


+

+
+

big_blind

+
+

int

+
+


+

+
+

small_bet

+
+

int

+
+


+

+
+

big_bet

+
+

int

+
+


+

+
+


+

+

Table +sites

+ + + + + + + + + + + + + + + + + + + + + + + + +
+

Field name

+
+

Type

+
+

Comment

+
+

id

+
+

smallint

+
+


+

+
+

name

+
+

varchar(32)

+
+


+

+
+

currency

+
+

char(3)

+
+

currency code, e.g. USD, + GBP, EUR

+
+


+

+

Table +hands

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Field Name

+
+

Type

+
+

Comment

+
+

id

+
+

bigint

+
+


+

+
+

site_hand_no

+
+

bigint

+
+

the site's hand number

+
+

gametype_id

+
+

smallint

+
+

references gametypes.id

+
+

hand_start

+
+

datetime (in UTC)

+
+

start date&time of the + hand

+
+

seats

+
+

smallint

+
+

number of used seats (ie. + that got dealt cards)

+
+

comment

+
+

text

+
+


+

+
+

comment_ts

+
+

datetime (in UTC)

+
+


+

+
+


+

+

Table +board_cards

+

cardX +-> can be 1 through 5

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Field Name

+
+

Type

+
+

Comment

+
+

id

+
+

bigint

+
+


+

+
+

hand_id

+
+

bigint

+
+

the site's hand number

+
+

cardX_value

+
+

smallint

+
+

2-10=2-10, J=11, Q=12, + K=13, A=14 (even in razz), unknown/no card=x

+
+

cardX_suit

+
+

char(1)

+
+

h=hearts, s=spades, + d=diamonds, c=clubs, unknown/no card=x

+
+


+

+


+

+

+
+

+

Table +hands_players

+

cardX: +can be 1 through 7, one for each card. In holdem/omaha this stores +the hole cards so 3-7 or 5-7 are empty

+

I +did not separate this into an extra table because I felt the lost +space is not sufficiently large. Also the benefit for searching is +far less relevant.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Field Name

+
+

Type

+
+

Comment

+
+

id

+
+

bigint

+
+


+

+
+

hand_id

+
+

bigint

+
+

references hands_stud.id

+
+

player_id

+
+

int

+
+

references players.id

+
+

player_startcash

+
+

int

+
+


+

+
+

position

+
+

char(1)

+
+

BB=B, + SB=S, Button=0, Cutoff=1, etc.

+

This is used in + holdem/omaha only.

+
+

ante

+
+

int

+
+

note: for cash this could + be boolean, but in tourneys you may enter a hand with less than + the full ante

+
+

cardX_value

+
+

smallint

+
+

2-10=2-10, + J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x

+

see note above table

+
+

cardX_suit

+
+

char(1)

+
+

h=hearts, s=spades, + d=diamonds, c=clubs, unknown/no card=x

+
+

winnings

+
+

int

+
+

winnings in this hand + (bets, antes, etc. are NOT deducted, but rake already is)

+
+

rake

+
+

int

+
+

rake for this player for + this hand

+
+

comment

+
+

text

+
+


+

+
+

comment_ts

+
+

datetime (in UTC)

+
+


+

+
+

tourneys_players_id

+
+

bigint

+
+

references + tourneys_players.id

+
+


+

+

Table HudDataHoldemOmaha

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Field Name

Type

Comment

id

bigint

gametypeId

smallint

references gametypes.id

playerId

int

references players.id

activeSeats

smallint

range 2-10

HDs

int

number of hands this player played in this gametype with this number of seats

VPIP

int

number of hands where player paid to see flop

PFR

int

number of hands where player raised before flop

PF3B4B

int

number of hands where player 3bet/4bet before flop

sawFlop

int

number of hands where player saw flop

sawTurn

int

number of hands where player saw turn

sawRiver

int

number of hands where player saw river

sawShowdown

int

number of hands where player saw showdown

raisedFlop

int

number of hands where player raised flop

raisedTurn

int

number of hands where player raised turn

raisedRiver

int

number of hands where player raised river

otherRaisedFlop

int

number of hands where someone else raised flop

otherRaisedFlopFold

int

number of hands where someone else raised flop and the player folded

otherRaisedTurn

int

number of hands where someone else raised Turn

otherRaisedTurnFold

int

number of hands where someone else raised Turn and the player folded

otherRaisedRiver

int

number of hands where someone else raised River

otherRaisedRiverFold

int

number of hands where someone else raised River and the player folded

+

+

Table hands_actions

+

Did +separate this into an extra table because it makes SELECTing across +different streets so much easier. Also the space saving will be very +large.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Field Name

+

Type

+
+

Comment

+
+

id

+
+

bigint

+
+


+

+
+

hand_player_id

+
+

bigint

+
+

references + hands_players.id

+
+

street

+
+

smallint

+
+

street + number, 0-3 (preflop, flop, turn, river) for holdem/omaha or 0-4 + for razz/stud

+

-1 for seen showdown

+
+

action_no

+
+

smallint

+
+

action number, 1-4

+
+

action

+
+

char(5)

+
+

bet + stands for bring in, complete, bet, double bet, raise and double + raise, since they all - technically - do the same thing. unbet is + used for when an uncalled bet is returned.

+

Other valid values: blind + call check fold

+
+

amount

+
+

int

+
+

amount put into the middle + for this action

+
+

comment

+
+

text

+
+


+

+
+

comment_ts

+
+

datetime (in UTC)

+
+


+

+
+


+

+

Tournament Tables

+


+

Table tourneys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Field name

+
+

Type

+
+

Comment

+
+

id

+
+

int

+
+


+

+
+

site_id

+
+

smallint

+
+

References sites.id

+
+

site_tourney_no

+
+

bigint

+
+


+

+
+

buyin

+
+

int

+
+

Buy-in in cents. Without + rebuy/add-on

+
+

fee

+
+

int

+
+


+

+
+

knockout

+
+

int

+
+


+

+
+

entries

+
+

int

+
+

-1 if unknown

+
+

prizepool

+
+

int

+
+

Need + this as separate field to support rebuy/addon

+

-1 if unknown

+
+

start_time

+
+

datetime (in UTC)

+
+

Empty if unknown

+
+

comment

+
+

text

+
+


+

+
+

comment_ts

+
+

datetime (in UTC)

+
+


+

+
+


+

+

Table +tourneys_players

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Field Name

+
+

Type

+
+

Comment

+
+

id

+
+

bigint

+
+


+

+
+

tourney_id

+
+

int

+
+

References tourneys.id

+
+

player_id

+
+

int

+
+

References players.id

+
+

payin_amount

+
+

int

+
+

Buyin, fee, rebuys and + add-ons

+
+

rank

+
+

int

+
+

Finishing rank

+
+

winnings

+
+

signed int

+
+

Winnings (not profit) by + this player, -1 if unknown.

+
+

comment

+
+

text

+
+


+

+
+

comment_ts

+
+

datetime (in UTC)

+
+


+

+
+ + diff --git a/perlfpdb/LibFpdbImport.pm b/perlfpdb/LibFpdbImport.pm new file mode 100644 index 00000000..ac7db336 --- /dev/null +++ b/perlfpdb/LibFpdbImport.pm @@ -0,0 +1,178 @@ +#!/usr/bin/pugs + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +module LibFpdbImport; +use v6; +#use strict; +use LibFpdbShared; +#use LibFpdbImport2; + +class Player { + has Str $name; + has Int $start_cash; + has Card @.cards; + has Char $position; + + submethod BUILD (Str @strings) { + say "todo: implement Player.BUILD"; + }#end Player.BUILD + + our Player method find_players(@strings) { + #todo: i think this should be sub since its a class method not an instance method + say "todo: implement Player.find_players"; + } +}#end class Player + +class Line { + has Str $line; + has Bool $processed; + + our protected submethod BUILD() { + say "todo: implement Line.BUILD?" + }#end Line.BUILD + + our Line method recognise_and_parse(@strings) { + #todo: i think this should be sub since its a class method not an instance method + say "todo: implement Line.recognise_and_parse"; + }#end Line.recognise_and_parse +}#end class Line + +class ActionLine is Line { + has Player $player; + has Str $type; + has Int $amount; + has Bool $all_in; + has Int $action_no; +}#end class ActionLine + +class WinLine is Line { + has Player $player; + has Int $amount; +}#end class WinLine + +class RakeLine is Line { + has Int $amount; +}#end class RakeLine + +class CardLine is Line { + has Bool $board_line; + has Player $player; + has Card @cards; +}#end class CardLine + +#for useless lines +class CrapLine is Line { + has Str $type; +}#end class CrapLine + +class Hand { + has Line @.lines; + #has Str @strings; + has Site $site; + has Str $currency; + + has Str $type; + has Str $category; + has Str $limit_type;#todo: above ; missing causes error, but that doesnt list ; as a possibility + has Player @.players; + has Card @.board; + has Int $db_id; + + submethod BUILD(Str @strings) { + Util.debug("running Hand.BUILD"); + say "strings:",@strings; + #this contructor automatically parses the hand. call .store for storing + + @.players=Player.find_players(@strings); + @.lines=Line.recognise_and_parse(@strings); + + for @strings -> $line { + if class_of(line)==CardLine { + if line.board { + board=line.cards; + } else { + for player in players { + if line.player==player { + player.cards=line.cards; + } + } + } + } + } + }#end Hand.BUILD + + our Bool method is_holdem(){ + if category==("holdem"|"omahahi"|"omahahilo") { + return True; + } else { + return False; + } + }#end Hand.is_holdem + + our Bool method is_stud(){ + return not is_holdem(); + }#end Hand.is_stud + + our Bool method store($db) { + say "todo: Hand.store"; + }#end Hand.store +}#end class Hand + +class Importer { +#todo: be Thread? + submethod BUILD (Database $db, Str $filename) { + Util.debug("running Importer.BUILD"); + if (not ($db.is_connected())) { + Util.fatal("not connected to DB"); + } + + my IO $?filehandle=$filename; + #for =$filehandle -> $line {say $line} + my Str @lines =$filehandle; + + my Int $hand_start=0; + my Int $hand_end=0; + my Int $loopcount=0; + loop {#one loop of this per hand + $loopcount++; + say "loopcount", $loopcount; + my Int $current_line_index=$hand_end+1; #previous hand end is new hand start + for (my Int $i, $i<5, $i++) {#remove blank hands + if (@lines[$current_line_index].bytes) < 6 { + $current_line_index++; + } else { + $hand_start=$current_line_index; + break; + } + } + my Bool $continue=True; #todo: this is dumb, find out correct loop + while $continue {#loop through the lines to find end of hand + $current_line_index++; + if (@lines[$current_line_index].bytes) < 6 { + $hand_end=$current_line_index; + $continue=False; + } + }#end of find end of hand loop + my Str @handlines=@lines[$hand_start..$hand_end]; + my Hand $hand .= new(:lines(@handlines)); + $hand.store($db); + say "todo: record \$db_id"; + say "todo: terminate on EOF"; + } + }#end new Importer +}#end class Importer + diff --git a/perlfpdb/LibFpdbShared.pm b/perlfpdb/LibFpdbShared.pm new file mode 100644 index 00000000..abf2eb69 --- /dev/null +++ b/perlfpdb/LibFpdbShared.pm @@ -0,0 +1,84 @@ +#!/usr/bin/pugs + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +module LibFpdbShared; +use v6; +#use strict; + +class Util { + method debug(Str $string) { + #todo: i think this should be sub since its a class method not an instance method + say "debug notice: ", $string; + }#end debug_msg + + sub warn(Str $string) { + say "todo: Util.warning"; + }#end warning + + sub fatal(Str $string, Database $db) { + say "todo: Util.fatal_error"; + }#end fatal_error +}#end class Util + +class Database { + has Str $backend; + has Str $host; + has Str $name; + has Str $user; + my Str $password; + submethod BUILD (Str $!backend, Str $!host, Str $!name, Str $!user, Str $!password) { + Util.debug("running Database.BUILD"); + self.connect(); + }#end new Database + + our method connect() { + say "todo: db.connect"; + }#end connect + + method disconnect() { + say "todo: db.disconnect"; + }#end disconnect + + method cancel_import() { + say "todo: db.cancel_import"; + }#end cancel_import + + my method drop_tables() { + #todo: make this one private + say "todo: db.drop_tables"; + }#end drop_tables + + method recreate_tables() { + say "todo: db.recreate_tables"; + }#end recreate_tables + + #returns the id of the insert + our Int method insert(Str $sql_command) { + #todo: is it a bug that i need the "our" above? + say "todo: db.insert"; + return 0; + }#end insert + + our Str method fetch(Str $sql_command) { + say "todo: db.fetch"; + }#end fetch + + our Bool method is_connected() { + say "todo: db.is_connected"; + }#end +}#end class Database + diff --git a/perlfpdb/LibFpdbView.pm b/perlfpdb/LibFpdbView.pm new file mode 100644 index 00000000..e4f994d1 --- /dev/null +++ b/perlfpdb/LibFpdbView.pm @@ -0,0 +1,17 @@ +#!/usr/bin/pugs + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + diff --git a/perlfpdb/RunFpdbCLI.perl6 b/perlfpdb/RunFpdbCLI.perl6 new file mode 100644 index 00000000..90401cdf --- /dev/null +++ b/perlfpdb/RunFpdbCLI.perl6 @@ -0,0 +1,29 @@ +#!/usr/bin/pugs + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +use v6; +#use strict; +use LibFpdbImport; +use LibFpdbShared; + + +my Database $db .= new(:backend, :host, :database, :user, :password); +#todo: below doesnt work +my Importer $imp .= new(:db($db), :filename); +#perlbug?: adding another named argument that isnt listed in the constructor gave very weird error. +say $imp; + diff --git a/prepare-git.sh b/prepare-git.sh new file mode 100755 index 00000000..44007d5f --- /dev/null +++ b/prepare-git.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +rm testdata/*.found.txt +rm utils/*.pyc +rm fpdb-python/*.pyc +git-add--interactive diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py new file mode 100755 index 00000000..2645c7b1 --- /dev/null +++ b/pyfpdb/fpdb.py @@ -0,0 +1,428 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import os +import sys + +import pygtk +pygtk.require('2.0') +import gtk + +import fpdb_db +import fpdb_simple +import import_threaded +import table_viewer + +class fpdb: + def tab_clicked(self, widget, tab_name): + """called when a tab button is clicked to activate that tab""" + print "start of tab_clicked" + self.display_tab(tab_name) + #end def tab_clicked + + def add_and_display_tab(self, new_tab, new_tab_name): + """just calls the component methods""" + self.add_tab(new_tab, new_tab_name) + self.display_tab(new_tab_name) + #end def add_and_display_tab + + def add_tab(self, new_tab, new_tab_name): + """adds a tab, namely creates the button and displays it and appends all the relevant arrays""" + print "start of add_tab" + for i in self.tab_names: #todo: check this is valid + if i==new_tab_name: + raise fpdb_simple.FpdbError("duplicate tab_name not permitted") + + self.tabs.append(new_tab) + self.tab_names.append(new_tab_name) + + new_tab_sel_button=gtk.ToggleButton(new_tab_name) + new_tab_sel_button.connect("clicked", self.tab_clicked, new_tab_name) + self.tab_box.add(new_tab_sel_button) + new_tab_sel_button.show() + self.tab_buttons.append(new_tab_sel_button) + #end def add_tab + + def display_tab(self, new_tab_name): + """displays the indicated tab""" + print "start of display_tab, len(self.tab_names):",len(self.tab_names) + tab_no=-1 + #if len(self.tab_names)>1: + for i in range(len(self.tab_names)): + print "display_tab, new_tab_name:",new_tab_name," self.tab_names[i]:", self.tab_names[i] + if (new_tab_name==self.tab_names[i]): + tab_no=i + #self.tab_buttons[i].set_active(False) + #else: + # tab_no=0 + + #current_tab_no=-1 + for i in range(len(self.tab_names)): + if self.current_tab==self.tabs[i]: + #self.tab_buttons[i].set_active(False) + pass + + if tab_no==-1: + raise fpdb_simple.FpdbError("invalid tab_no") + else: + self.main_vbox.remove(self.current_tab) + #self.current_tab.destroy() + self.current_tab=self.tabs[tab_no] + self.main_vbox.add(self.current_tab) + self.tab_buttons[tab_no].set_active(True) + self.current_tab.show() + #end def display_tab + + def delete_event(self, widget, event, data=None): + return False + #end def delete_event + + def destroy(self, widget, data=None): + self.quit(widget, data) + #end def destroy + + def dia_about(self, widget, data): + print "todo: implement dia_about" + #end def dia_about + + def dia_create_del_database(self, widget, data): + print "todo: implement dia_create_del_database" + obtain_global_lock() + #end def dia_create_del_database + + def dia_create_del_user(self, widget, data): + print "todo: implement dia_create_del_user" + obtain_global_lock() + #end def dia_create_del_user + + def dia_database_stats(self, widget, data): + print "todo: implement dia_database_stats" + #end def dia_database_stats + + def dia_delete_db_parts(self, widget, data): + print "todo: implement dia_delete_db_parts" + obtain_global_lock() + #end def dia_delete_db_parts + + def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None): + print "todo: implement dia_edit_profile" + obtain_global_lock() + #end def dia_edit_profile + + def dia_export_db(self, widget, data): + print "todo: implement dia_export_db" + obtain_global_lock() + #end def dia_export_db + + def dia_get_db_root_credentials(self): + """obtains db root credentials from user""" + print "todo: implement dia_get_db_root_credentials" +# user, pw=None, None +# +# dialog=gtk.Dialog(title="DB Credentials needed", parent=None, flags=0, +# buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,"Connect and recreate",gtk.RESPONSE_OK)) +# +# label_warning1=gtk.Label("Please enter credentials for a database user for "+self.host+" that has permissions to create a database.") +# +# +# label_user=gtk.Label("Username") +# dialog.vbox.add(label_user) +# label_user.show() +# +# response=dialog.run() +# dialog.destroy() +# return (user, pw, response) + #end def dia_get_db_root_credentials + + def dia_import_db(self, widget, data): + print "todo: implement dia_import_db" + obtain_global_lock() + #end def dia_import_db + + def dia_licensing(self, widget, data): + print "todo: implement dia_licensing" + #end def dia_licensing + + def dia_load_profile(self, widget, data): + """Dialogue to select a file to load a profile from""" + obtain_global_lock() + chooser = gtk.FileChooserDialog(title="Please select a profile file to load", + action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) + chooser.set_filename(self.profile) + + response = chooser.run() + chooser.destroy() + if response == gtk.RESPONSE_OK: + self.load_profile(chooser.get_filename()) + elif response == gtk.RESPONSE_CANCEL: + print 'User cancelled loading profile' + #end def dia_load_profile + + def dia_recreate_tables(self, widget, data): + """Dialogue that asks user to confirm that he wants to delete and recreate the tables""" + self.obtain_global_lock() + dia_confirm=gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, + buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables") + diastring=("Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted.") + dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted + + response=dia_confirm.run() + dia_confirm.destroy() + if response == gtk.RESPONSE_YES: + self.db.recreate_tables() + elif response == gtk.RESPONSE_NO: + print 'User cancelled recreating tables' + #end def dia_recreate_tables + + def dia_regression_test(self, widget, data): + print "todo: implement dia_regression_test" + self.obtain_global_lock() + #end def dia_regression_test + + def dia_save_profile(self, widget, data): + print "todo: implement dia_save_profile" + #end def dia_save_profile + + def dia_setup_wizard(self, path): + print "todo: implement setup wizard" + print "setup wizard not implemented - please create the default configuration file:", path + sys.exit(1) + #end def dia_setup_wizard + + def get_menu(self, window): + """returns the menu for this program""" + accel_group = gtk.AccelGroup() + self.item_factory = gtk.ItemFactory(gtk.MenuBar, "
", accel_group) + self.item_factory.create_items(self.menu_items) + window.add_accel_group(accel_group) + return self.item_factory.get_widget("
") + #end def get_menu + + def load_default_profile(self): + """Loads the defaut profile""" + defaultpath=os.path.expanduser("~") + if not defaultpath.endswith(os.sep):#todo: check if this is needed in *nix, if not delete it + defaultpath+=(os.sep) + + if (os.sep=="\\"):#ie. if Windows use application data folder + defaultpath+=("Application Data"+os.sep) + else:#ie. if real OS prefix fpdb with a . as it is convention + defaultpath+="." + defaultpath+=("fpdb"+os.sep+"profiles"+os.sep+"default.conf") + + if os.path.exists(defaultpath): + self.load_profile(defaultpath) + else: + self.dia_setup_wizard(path=defaultpath) + #end def load_default_profile + + def load_profile(self, filename): + """Loads profile from the provided path name. also see load_default_profile""" + self.obtain_global_lock() + file=open(filename, "rU") + lines=file.readlines() + print "Opened and read profile file", filename + self.profile=filename + + self.bulk_import_default_path="/work/poker-histories/wine-ps/" #/todo: move this to .conf + + found_backend, found_host, found_database, found_user, found_password=False, False, False, False, False + for i in range(len(lines)): + if lines[i].startswith("backend="): + backend=int(lines[i][8:-1]) + found_backend=True + elif lines[i].startswith("host="): + host=lines[i][5:-1] + #self.host=host + found_host=True + elif lines[i].startswith("database="): + database=lines[i][9:-1] + #self.database=database + found_database=True + elif lines[i].startswith("user="): + user=lines[i][5:-1] + found_user=True + elif lines[i].startswith("password="): + password=lines[i][9:-1] + found_password=True + + if not found_backend: + raise fpdb_simple.FpdbError("failed to read backend from settings file:"+filename) + elif not found_host: + raise fpdb_simple.FpdbError("failed to read host from settings file:"+filename) + elif not found_database: + raise fpdb_simple.FpdbError("failed to read database from settings file:"+filename) + elif not found_user: + raise fpdb_simple.FpdbError("failed to read user from settings file:"+filename) + elif not found_password: + raise fpdb_simple.FpdbError("failed to read password from settings file:"+filename) + + if self.db!=None: + self.db.disconnect() + + self.db = fpdb_db.fpdb_db() + self.db.connect(backend, host, database, user, password) + #end def load_profile + + def not_implemented(self): + print "todo: called unimplemented menu entry"#remove this once more entries are implemented + #end def not_implemented + + def obtain_global_lock(self): + print "todo: implement obtain_global_lock" + #end def obtain_global_lock + + def quit(self, widget, data): + print "Quitting normally" + #check if current settings differ from profile, if so offer to save or abort + self.db.disconnect() + gtk.main_quit() + #end def quit_cliecked + + def release_global_lock(self): + print "todo: implement release_global_lock" + #end def release_global_lock + + def tab_abbreviations(self, widget, data): + print "todo: implement tab_abbreviations" + #end def tab_abbreviations + + def tab_auto_import(self, widget, data): + print "todo: implement tab_auto_import" + #end def tab_auto_import + + def tab_bulk_import(self, widget, data): + """opens a tab for bulk importing""" + print "start of tab_bulk_import" + new_import_thread=import_threaded.import_threaded(self.db, self.bulk_import_default_path) + self.threads.append(new_import_thread) + bulk_tab=new_import_thread.get_vbox() + self.add_and_display_tab(bulk_tab, "bulk import") + #end def tab_bulk_import + + def tab_main_help(self, widget, data): + """Displays a tab with the main fpdb help screen""" + print "start of tab_main_help" + mh_tab=gtk.Label("""Welcome to Fpdb +blabla todo make this read a file for the helptext +blabla +blabla""") + self.add_and_display_tab(mh_tab, "main help") + #end def tab_main_help + + def tab_table_viewer(self, widget, data): + """opens a table viewer tab""" + print "start of tab_table_viewer" + new_tv_thread=table_viewer.table_viewer(self.db) + self.threads.append(new_tv_thread) + tv_tab=new_tv_thread.get_vbox() + self.add_and_display_tab(tv_tab, "table viewer") + #end def tab_table_viewer + + def __init__(self): + self.threads=[] + self.db=None + self.load_default_profile() + + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.connect("delete_event", self.delete_event) + self.window.connect("destroy", self.destroy) + self.window.set_title("Free Poker DB") + self.window.set_border_width(1) + self.window.set_size_request(600,400) + self.window.set_resizable(True) + + self.menu_items = ( + ( "/_Main", None, None, 0, "" ), + ( "/Main/_Load Profile", "L", self.dia_load_profile, 0, None ), + ( "/Main/_Edit Profile (todo)", "E", self.dia_edit_profile, 0, None ), + ( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ), + ( "/Main/sep1", None, None, 0, "" ), + ( "/Main/_Quit", "Q", self.quit, 0, None ), + ( "/_Import", None, None, 0, "" ), + ( "/Import/_Import Files and Directories", "I", self.tab_bulk_import, 0, None ), + ( "/Import/_Auto Import (todo)", "A", self.tab_auto_import, 0, None ), + ( "/Import/Auto _Rating (todo)", "R", self.not_implemented, 0, None ), + ( "/_Viewers", None, None, 0, "" ), + ( "/Viewers/_Graphs (todo)", None, self.not_implemented, 0, None ), + ( "/Viewers/H_and Replayer (todo)", None, self.not_implemented, 0, None ), + ( "/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ), + ( "/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), + ( "/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), + ( "/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), + ( "/Viewers/Poker_table Viewer", "T", self.tab_table_viewer, 0, None ), + #( "/Viewers/Tourney Replayer + #( "/H_UD", None, None, 0, "" ), + ( "/_Database", None, None, 0, "" ), + ( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ), + ( "/Database/Create or Delete _User (todo)", None, self.dia_create_del_user, 0, None ), + ( "/Database/Create or Recreate _Tables", None, self.dia_recreate_tables, 0, None ), + ( "/Database/_Statistics (todo)", None, self.dia_database_stats, 0, None ), + ( "/D_ebugging", None, None, 0, "" ), + ( "/Debugging/_Delete Parts of Database (todo)", None, self.dia_delete_db_parts, 0, None ), + ( "/Debugging/_Export DB (todo)", None, self.dia_export_db, 0, None ), + ( "/Debugging/_Import DB (todo)", None, self.dia_import_db, 0, None ), + ( "/Debugging/_Regression test (todo)", None, self.dia_regression_test, 0, None ), + ( "/_Help", None, None, 0, "" ), + ( "/Help/_Main Help", "H", self.tab_main_help, 0, None ), + ( "/Help/_Abbrevations (todo)", None, self.tab_abbreviations, 0, None ), + ( "/Help/sep1", None, None, 0, "" ), + ( "/Help/A_bout (todo)", None, self.dia_about, 0, None ), + ( "/Help/_License and Copying (todo)", None, self.dia_licensing, 0, None ) + ) + + self.main_vbox = gtk.VBox(False, 1) + self.main_vbox.set_border_width(1) + self.window.add(self.main_vbox) + self.main_vbox.show() + + menubar = self.get_menu(self.window) + self.main_vbox.pack_start(menubar, False, True, 0) + menubar.show() + #done menubar + + self.tabs=[] + self.tab_names=[] + self.tab_buttons=[] + self.tab_box = gtk.HBox(False,1) + self.main_vbox.pack_start(self.tab_box, False, True, 0) + self.tab_box.show() + #done tab bar + + self.current_tab = gtk.VBox(False,1) + self.current_tab.set_border_width(1) + self.main_vbox.add(self.current_tab) + self.current_tab.show() + + self.tab_main_help(None, None) + + self.status_bar = gtk.Label("Status: Connected to "+self.db.get_backend_name()+" database named "+self.db.database+" on host "+self.db.host) + self.main_vbox.pack_end(self.status_bar, False, True, 0) + self.status_bar.show() + + self.window.show() + #end def __init__ + + def main(self): + gtk.main() + return 0 + #end def main + +if __name__ == "__main__": + me = fpdb() + me.main() diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py new file mode 100755 index 00000000..40b01661 --- /dev/null +++ b/pyfpdb/fpdb_db.py @@ -0,0 +1,270 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import os +import fpdb_simple + +class fpdb_db: + def __init__(self): + """Simple constructor, doesnt really do anything""" + self.db=None + self.cursor=None + self.MYSQL=1 + self.MYSQL_INNODB=2 + self.PGSQL=3 + #end def __init__ + + def connect(self, backend, host, database, user, password): + """Connects a database with the given parameters""" + self.backend=backend + self.host=host + self.database=database + self.user=user + self.password=password + #print "fpdb_db.connect, password:",password,"/end" + if backend==self.MYSQL or backend==self.MYSQL_INNODB: + import MySQLdb + print "fpdb_db.connect, host:", host, " user:", user, " passwd:", password, " db:", database + self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) + elif backend==self.PGSQL: + import pgdb + self.db = pgdb.connect(dsn=host+":"+database, user='postgres', password=password) + else: + raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) + self.cursor=self.db.cursor() + #end def connect + + def create_table(self, string): + """creates a table for the given string + The string should the name of the table followed by the column list + in brackets as if it were an SQL command. Do NOT include the "CREATE TABLES" + bit at the beginning nor the ";" or ENGINE= at the end""" + string="CREATE TABLE "+string + if (self.backend==self.MYSQL_INNODB): + string+=" ENGINE=INNODB" + string+=";" + #print "create_table, string:", string + self.cursor.execute(string) + self.db.commit() + #end def create_table + + def disconnect(self, due_to_error=False): + """Disconnects the DB""" + if due_to_error: + self.db.rollback() + else: + self.db.commit() + self.cursor.close() + self.db.close() + #end def disconnect + + def reconnect(self, due_to_error=False): + """Reconnects the DB""" + print "started fpdb_db.reconnect" + self.disconnect(due_to_error) + self.connect(self.backend, self.host, self.database, self.user, self.password) + #end def disconnect + + def drop_tables(self): + """Drops the fpdb tables from the current db""" + self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + self.cursor.execute("DROP TABLE IF EXISTS autorates;") + self.cursor.execute("DROP TABLE IF EXISTS board_cards;") + self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") + self.cursor.execute("DROP TABLE IF EXISTS hands_players;") + self.cursor.execute("DROP TABLE IF EXISTS hands;") + self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") + self.cursor.execute("DROP TABLE IF EXISTS tourneys;") + self.cursor.execute("DROP TABLE IF EXISTS players;") + self.cursor.execute("DROP TABLE IF EXISTS gametypes;") + self.cursor.execute("DROP TABLE IF EXISTS sites;") + self.db.commit() + #end def drop_tables + + def get_backend_name(self): + """Returns the name of the currently used backend""" + if self.backend==1: + return "MySQL normal" + elif self.backend==2: + return "MySQL InnoDB" + elif self.backend==3: + return "PostgreSQL" + #end def get_backend_name + + def get_db_info(self): + return (self.host, self.database, self.user, self.password) + #end def get_db_info + + def recreate_tables(self): + """(Re-)creates the tables of the current DB""" + self.drop_tables() + + self.create_table("""sites ( + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + name varchar(32), + currency char(3))""") + + self.create_table("""gametypes ( + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + site_id SMALLINT UNSIGNED, FOREIGN KEY (site_id) REFERENCES sites(id), + type char(4), + category varchar(9), + limit_type char(2), + small_blind int, + big_blind int, + small_bet int, + big_bet int)""") + + self.create_table("""players ( + id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + name VARCHAR(32) CHARACTER SET utf8, + site_id SMALLINT UNSIGNED, FOREIGN KEY (site_id) REFERENCES sites(id), + comment text, + comment_ts DATETIME)""") + + self.create_table("""autorates ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + player_id INT UNSIGNED, FOREIGN KEY (player_id) REFERENCES players(id), + gametype_id SMALLINT UNSIGNED, FOREIGN KEY (gametype_id) REFERENCES gametypes(id), + description varchar(50), + short_desc char(8), + rating_time DATETIME, + hand_count int)""") + + self.create_table("""hands ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + site_hand_no bigint, + gametype_id SMALLINT UNSIGNED, FOREIGN KEY (gametype_id) REFERENCES gametypes(id), + hand_start DATETIME, + seats smallint, + comment text, + comment_ts DATETIME)""") + + self.create_table("""board_cards ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, + PRIMARY KEY (id), + hand_id BIGINT UNSIGNED, + FOREIGN KEY (hand_id) REFERENCES hands(id), + card1_value smallint, + card1_suit char(1), + card2_value smallint, + card2_suit char(1), + card3_value smallint, + card3_suit char(1), + card4_value smallint, + card4_suit char(1), + card5_value smallint, + card5_suit char(1))""") + + self.create_table("""tourneys ( + id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + site_id SMALLINT UNSIGNED, FOREIGN KEY (site_id) REFERENCES sites(id), + site_tourney_no BIGINT, + buyin INT, + fee INT, + knockout INT, + entries INT, + prizepool INT, + start_time DATETIME, + comment TEXT, + comment_ts DATETIME)""") + + self.create_table("""tourneys_players ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + tourney_id INT UNSIGNED, FOREIGN KEY (tourney_id) REFERENCES tourneys(id), + player_id INT UNSIGNED, FOREIGN KEY (player_id) REFERENCES players(id), + payin_amount INT, + rank INT, + winnings INT, + comment TEXT, + comment_ts DATETIME)""") + + self.create_table("""hands_players ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, + PRIMARY KEY (id), + hand_id BIGINT UNSIGNED, + FOREIGN KEY (hand_id) REFERENCES hands(id), + player_id INT UNSIGNED, + FOREIGN KEY (player_id) REFERENCES players(id), + player_startcash int, + position char(1), + ante int, + + card1_value smallint, + card1_suit char(1), + card2_value smallint, + card2_suit char(1), + card3_value smallint, + card3_suit char(1), + card4_value smallint, + card4_suit char(1), + card5_value smallint, + card5_suit char(1), + card6_value smallint, + card6_suit char(1), + card7_value smallint, + card7_suit char(1), + + winnings int, + rake int, + comment text, + comment_ts DATETIME, + + tourneys_players_id BIGINT UNSIGNED, + FOREIGN KEY (tourneys_players_id) REFERENCES tourneys_players(id))""") + + self.create_table("""hands_actions ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, + PRIMARY KEY (id), + hand_player_id BIGINT UNSIGNED, + FOREIGN KEY (hand_player_id) REFERENCES hands_players(id), + street SMALLINT, + action_no SMALLINT, + action CHAR(5), + amount INT, + comment TEXT, + comment_ts DATETIME)""") + + self.create_table("""HudDataHoldemOmaha ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES gametypes(id), + playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES players(id), + activeSeats SMALLINT, + HDs INT, + VPIP INT, + PFR INT, + PF3B4B INT, + sawFlop INT, + sawTurn INT, + sawRiver INT, + sawShowdown INT, + raisedFlop INT, + raisedTurn INT, + raisedRiver INT, + otherRaisedFlop INT, + otherRaisedFlopFold INT, + otherRaisedTurn INT, + otherRaisedTurnFold INT, + otherRaisedRiver INT, + otherRaisedRiverFold INT)""") + + self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") + self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") + self.db.commit() + print "finished recreating tables" + #end def recreate_tables +#end class fpdb_db diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py new file mode 100755 index 00000000..5d89ac76 --- /dev/null +++ b/pyfpdb/fpdb_import.py @@ -0,0 +1,192 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +#see status.txt for site/games support info + +import sys +import MySQLdb +#import pgdb +import math +import os +import datetime +import fpdb_simple +import fpdb_parse_logic +from optparse import OptionParser + + +def import_file(server, database, user, password, inputFile): + self.server=server + self.database=database + self.user=user + self.password=password + self.inputFile=inputFile + import_file_dict(self) + +def import_file_dict(options): + last_read_hand=0 + if (options.inputFile=="stdin"): + inputFile=sys.stdin + else: + inputFile=open(options.inputFile, "rU") + + #connect to DB + db = MySQLdb.connect(host = options.server, user = options.user, + passwd = options.password, db = options.database) + cursor = db.cursor() + + if (not options.quiet): + print "Opened file", options.inputFile, "and connected to MySQL on", options.server + + line=inputFile.readline() + site=fpdb_simple.recogniseSite(line) + category=fpdb_simple.recogniseCategory(line) + inputFile.seek(0) + lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) + + startpos=0 + stored=0 #counter + duplicates=0 #counter + partial=0 #counter + errors=0 #counter + + for i in range (len(lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method + if (len(lines[i])<2): + endpos=i + hand=lines[startpos:endpos] + + if (len(hand[0])<2): + hand=hand[1:] + + cancelled=False + damaged=False + if (site=="ftp"): + for i in range (len(hand)): + if (hand[i].endswith(" has been canceled")): #this is their typo. this is a typo, right? + cancelled=True + + seat1=hand[i].find("Seat ") #todo: make this recover by skipping this line + if (seat1!=-1): + if (hand[i].find("Seat ", seat1+3)!=-1): + damaged=True + + if (len(hand)<3): + pass + #todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work. + elif (hand[0].endswith(" (partial)")): #partial hand - do nothing + partial+=1 + elif (hand[1].find("Seat")==-1 and hand[2].find("Seat")==-1 and hand[3].find("Seat")==-1):#todo: should this be or instead of and? + partial+=1 + elif (cancelled or damaged): + partial+=1 + else: #normal processing + fpdb_simple.filterAnteBlindFold(site,hand) + hand=fpdb_simple.filterCrap(site, hand) + + try: + if (category=="holdem" or category=="omahahi" or category=="omahahilo" or category=="razz" or category=="studhi" or category=="studhilo"): + if (category=="razz" or category=="studhi" or category=="studhilo"): + raise fpdb_simple.FpdbError ("stud/razz currently out of order") + last_read_hand=fpdb_parse_logic.mainParser(db, cursor, site, category, hand) + db.commit() + else: + raise fpdb_simple.FpdbError ("invalid category") + + stored+=1 + db.commit() + except fpdb_simple.DuplicateError: + duplicates+=1 + except (ValueError), fe: + errors+=1 + print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." + print "Filename:",options.inputFile + print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" + print hand[0] + + if (options.failOnError): + db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + inputFile.close() + cursor.close() + db.close() + raise + except (fpdb_simple.FpdbError), fe: + errors+=1 + print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." + print "Filename:",options.inputFile + print "Here is the first line so you can identify it." + print hand[0] + #fe.printStackTrace() #todo: get stacktrace + db.rollback() + + if (options.failOnError): + db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + inputFile.close() + cursor.close() + db.close() + raise + if (options.minPrint!=0): + if ((stored+duplicates+partial+errors)%options.minPrint==0): + print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors + + if (options.handCount!=0): + if ((stored+duplicates+partial+errors)>=options.handCount): + if (not options.quiet): + print "quitting due to reaching the amount of hands to be imported" + print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors + sys.exit(0) + startpos=endpos + print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors + + if stored==0 and duplicates>0: + for line_no in range(len(lines)): + if lines[line_no].find("Game #")!=-1: + final_game_line=lines[line_no] + last_read_hand=fpdb_simple.parseSiteHandNo(final_game_line) + #todo: this will cause return of an unstored hand number if the last hadn was error or partial + db.commit() + inputFile.close() + cursor.close() + db.close() + return last_read_hand + + +if __name__ == "__main__": + failOnError=False + quiet=False + + #process CLI parameters + parser = OptionParser() + parser.add_option("-c", "--handCount", default="0", type="int", + help="Number of hands to import (default 0 means unlimited)") + parser.add_option("-d", "--database", default="fpdb", help="The MySQL database to use (default fpdb)") + parser.add_option("-e", "--errorFile", default="failed.txt", + help="File to store failed hands into. (default: failed.txt) Not implemented.") + parser.add_option("-f", "--inputFile", "--file", "--inputfile", default="stdin", + help="The file you want to import (remember to use quotes if necessary)") + parser.add_option("-m", "--minPrint", "--status", default="50", type="int", + help="How often to print a one-line status report (0 means never, default is 50)") + parser.add_option("-p", "--password", help="The password for the MySQL user") + parser.add_option("-q", "--quiet", action="store_true", + help="If this is passed it doesn't print a total at the end nor the opening line. Note that this purposely does NOT change --minPrint") + parser.add_option("-s", "--server", default="localhost", + help="Hostname/IP of the MySQL server (default localhost)") + parser.add_option("-u", "--user", default="fpdb", help="The MySQL username (default fpdb)") + parser.add_option("-x", "--failOnError", action="store_true", + help="If this option is passed it quits when it encounters any error") + + (options, sys.argv) = parser.parse_args() + + import_file_dict(options) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py new file mode 100644 index 00000000..6dc57212 --- /dev/null +++ b/pyfpdb/fpdb_parse_logic.py @@ -0,0 +1,152 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +#methods that are specific to holdem but not trivial + +import fpdb_simple +import fpdb_save_to_db + +#parses a holdem hand +def mainParser(db, cursor, site, category, hand): + #print "hand:",hand + #part 0: create the empty arrays + lineTypes=[] #char, valid values: header, name, cards, action, win, rake, ignore + lineStreets=[] #char, valid values: (predeal, preflop, flop, turn, river) + + cardValues, cardSuits, boardValues, boardSuits=[],[],[],[] + antes, actionTypes, actionAmounts, seatLines, winnings, rakes=[],[],[],[],[],[] + + #part 1: read hand no and check for duplicate + siteHandNo=fpdb_simple.parseSiteHandNo(hand[0]) + handStartTime=fpdb_simple.parseHandStartTime(hand[0], site) + siteID=fpdb_simple.recogniseSiteID(cursor, site) + + isTourney=fpdb_simple.isTourney(hand[0]) + gametypeID=fpdb_simple.recogniseGametypeID(cursor, hand[0], siteID, category, isTourney) + if isTourney: + if site!="ps": + raise fpdb_simple.FpdbError("tourneys are only supported on PS right now") + siteTourneyNo=fpdb_simple.parseTourneyNo(hand[0]) + buyin=fpdb_simple.parseBuyin(hand[0]) + #print "Buyin:", buyin + fee=fpdb_simple.parseFee(hand[0]) + #print "Fee:", fee + entries=-1 #todo: parse this + prizepool=-1 #todo: parse this + tourneyStartTime=handStartTime #todo: read tourney start time + #print "gametypeID:",gametypeID + fpdb_simple.isAlreadyInDB(cursor, gametypeID, siteHandNo) + + #part 2: classify lines by type (e.g. cards, action, win, sectionchange) and street + fpdb_simple.classifyLines(hand, category, lineTypes, lineStreets) + #for i in range (len(hand)): + # print "i:",i,"lineTypes[i]:",lineTypes[i],"hand[i]:",hand[i] + + #part 3: read basic player info + #3a read player names, startcashes + for i in range (len(hand)): #todo: use maxseats+1 here. + if (lineTypes[i]=="name"): + seatLines.append(hand[i]) + #print "seatLines:",seatLines + #print "hand:",hand + names=fpdb_simple.parseNames(seatLines) + #print "names:",names + playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID) + startCashes=fpdb_simple.parseCashes(seatLines, site) + + fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts) + + #3b remove people who sitout before cards are dealt (e.g. sitout instead of paying blinds) + #PS doesnt have a nameline for sitouts so for PS this can be skipped. havent tried FTP yet + + #3c read positions + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + positions = fpdb_simple.parsePositions (hand, names) + + #part 4: take appropriate action for each line based on linetype + for i in range(len(hand)): + if (lineTypes[i]=="cards"): + #print "hand[i]:",hand[i] + fpdb_simple.parseCardLine (site, category, lineStreets[i], hand[i], names, cardValues, cardSuits, boardValues, boardSuits) + #print "cardValues:",cardValues + elif (lineTypes[i]=="action"): + fpdb_simple.parseActionLine (hand[i], lineStreets[i], names, actionTypes, actionAmounts, site) + elif (lineTypes[i]=="win"): + fpdb_simple.parseWinLine (hand[i], site, names, winnings, isTourney) + elif (lineTypes[i]=="rake"): + if isTourney: + totalRake=0 + else: + totalRake=fpdb_simple.parseRake(hand[i]) + fpdb_simple.splitRake(winnings, rakes, totalRake) + elif (lineTypes[i]=="header" or lineTypes[i]=="rake" or lineTypes[i]=="name" or lineTypes[i]=="ignore"): + pass + elif (lineTypes[i]=="ante"): + fpdb_simple.parseAnteLine(hand[i], site, names, antes) + else: + raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i]) + #print "end of part4 cardValues:",cardValues,"cardSuits:",cardSuits + + #part 5: final preparations, then call fpdb_save_to_db.saveHoldem with + # the arrays as they are - that file will fill them. + fpdb_simple.convertCardValues(cardValues) + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + fpdb_simple.convertCardValuesBoard(boardValues) + fpdb_simple.convertBlindBet(actionTypes, actionAmounts) + fpdb_simple.checkPositions(positions) + + cursor.execute("SELECT limit_type FROM gametypes WHERE id=%s",(gametypeID, )) + limit_type=cursor.fetchone()[0] #todo: remove this unnecessary database access + fpdb_simple.convert3B4B(site, category, limit_type, actionTypes, actionAmounts) + + hands_players_flags=fpdb_simple.calculate_hands_players_flags(playerIDs, category, actionTypes) + + if isTourney: + payin_amounts=fpdb_simple.calcPayin(len(names), buyin, fee) + ranks=[] + for i in range (len(names)): + ranks.append(0) + knockout=0 + entries=-1 + prizepool=-1 + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, + knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, + siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, + startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, + actionTypes, actionAmounts, hands_players_flags) + elif (category=="razz" or category=="studhi" or category=="studhilo"): + result = fpdb_save_to_db.tourney_stud(cursor, category, siteTourneyNo, buyin, fee, + knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, + siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, + startCashes, antes, cardValues, cardSuits, winnings, rakes, + actionTypes, actionAmounts, hands_players_flags) + else: + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, + handStartTime, names, playerIDs, startCashes, positions, cardValues, + cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, hands_players_flags) + elif (category=="razz" or category=="studhi" or category=="studhilo"): + result = fpdb_save_to_db.ring_stud(cursor, category, siteHandNo, gametypeID, + handStartTime, names, playerIDs, startCashes, antes, cardValues, + cardSuits, winnings, rakes, actionTypes, actionAmounts, hands_players_flags) + else: + raise fpdb_simple.FpdbError ("unrecognised category") + db.commit() + return result +#end def mainParser + diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py new file mode 100644 index 00000000..990ae09c --- /dev/null +++ b/pyfpdb/fpdb_save_to_db.py @@ -0,0 +1,123 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +#This file contains methods to store hands into the db. decides to move this +#into a seperate file since its ugly, fairly long and just generally in the way. + +import fpdb_simple + +#stores a stud/razz hand into the database +def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, + names, player_ids, start_cashes, antes, card_values, card_suits, + winnings, rakes, action_types, action_amounts, hands_players_flags): + fpdb_simple.fillCardArrays(len(names), 7, card_values, card_suits) + + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + + hands_players_ids=fpdb_simple.store_hands_players_stud(cursor, hands_id, player_ids, + start_cashes, antes, card_values, card_suits, winnings, rakes) + + fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + return site_hand_no +#end def ring_stud + +#stores a holdem/omaha hand into the database +def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, + names, player_ids, start_cashes, positions, card_values, card_suits, + board_values, board_suits, winnings, rakes, action_types, action_amounts, hands_players_flags): + #fill up the two player card arrays + if (category=="holdem"): + fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) + elif (category=="omahahi" or category=="omahahilo"): + fpdb_simple.fillCardArrays(len(names), 4, card_values, card_suits) + else: + raise fpdb_simple.FpdbError ("invalid category: category") + + fpdb_simple.fill_board_cards(board_values, board_suits) + + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + + hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, + start_cashes, positions, card_values, card_suits, winnings, rakes) + + fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + + fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) + + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + return site_hand_no +#end def ring_holdem_omaha + +def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, + tourney_start, payin_amounts, ranks, #end of tourney specific params + site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, + start_cashes, positions, card_values, card_suits, + board_values, board_suits, winnings, rakes, + action_types, action_amounts, hands_players_flags): +#stores a tourney stud/razz hand into the database + #fill up the two player card arrays + if (category=="holdem"): + fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) + elif (category=="omahahi" or category=="omahahilo"): + fpdb_simple.fillCardArrays(len(names), 4, card_values, card_suits) + else: + raise fpdb_simple.FpdbError ("invalid category: category") + + fpdb_simple.fill_board_cards(board_values, board_suits) + + tourney_id=fpdb_simple.store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start) + + tourneys_players_ids=fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings) + + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + + hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, + start_cashes, positions, card_values, card_suits, winnings, rakes, tourneys_players_ids) + + fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + + fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) + + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + return site_hand_no +#end def tourney_holdem_omaha + +def tourney_stud(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, + tourney_start, payin_amounts, ranks, #end of tourney specific params + site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, + start_cashes, antes, card_values, card_suits, winnings, rakes, + action_types, action_amounts, hands_players_flags): +#stores a tourney stud/razz hand into the database + fpdb_simple.fillCardArrays(len(names), 7, card_values, card_suits) + + tourney_id=fpdb_simple.store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start) + + tourneys_players_ids=fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings) + + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + + hands_players_ids=fpdb_simple.store_hands_players_stud_tourney(cursor, hands_id, player_ids, + start_cashes, antes, card_values, card_suits, winnings, rakes, tourneys_players_ids) + + fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + return site_hand_no +#end def tourney_stud + diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py new file mode 100644 index 00000000..a18918fe --- /dev/null +++ b/pyfpdb/fpdb_simple.py @@ -0,0 +1,1345 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +#This file contains simple functions for fpdb + +import datetime + +class DuplicateError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class FpdbError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +#returns an array of the total money paid. intending to add rebuys/addons here +def calcPayin(count, buyin, fee): + result=[] + for i in range(count): + result.append (buyin+fee) + return result +#end def calcPayin + +def checkPositions(positions): + """verifies that these positions are valid""" + for i in range (len(positions)): + pos=positions[i] + try:#todo: use type recognition instead of error + if (len(pos)!=1): + raise FpdbError("invalid position found in checkPositions. i: "+str(i)+" position: "+pos) #dont need to str() here + except TypeError:#->not string->is int->fine + pass + + if (pos!="B" and pos!="S" and pos!=0 and pos!=1 and pos!=2 and pos!=3 and pos!=4 and pos!=5 and pos!=6 and pos!=7): + raise FpdbError("invalid position found in checkPositions. i: "+str(i)+" position: "+str(pos)) +#end def fpdb_simple.checkPositions + +#classifies each line for further processing in later code. Manipulates the passed arrays. +def classifyLines(hand, category, lineTypes, lineStreets): + currentStreet="predeal" + done=False #set this to true once we reach the last relevant line (the summary, except rake, is all repeats) + for i in range (len(hand)): + if (done): + if (hand[i].find("[")==-1 or hand[i].find("mucked [")==-1): + lineTypes.append("ignore") + else: #it's storing a mucked card + lineTypes.append("cards") + elif (hand[i].startswith("Dealt to")): + lineTypes.append("cards") + elif (i==0): + lineTypes.append("header") + elif (hand[i].startswith("Seat ") and ((hand[i].find("in chips")!=-1) or (hand[i].find("($")!=-1))): + lineTypes.append("name") + elif (isActionLine(hand[i])): + lineTypes.append("action") + if (hand[i].find(" posts ")!=-1 or hand[i].find(" posts the ")!=-1):#need to set this here so the "action" of posting blinds is registered properly + currentStreet="preflop" + elif (isWinLine(hand[i])): + lineTypes.append("win") + elif (hand[i].startswith("Total pot ") and hand[i].find("Rake")!=-1): + lineTypes.append("rake") + done=True + elif (hand[i]=="*** SHOW DOWN ***" or hand[i]=="*** SUMMARY ***"): + lineTypes.append("ignore") + #print "in classifyLine, showdown or summary" + elif (hand[i].find(" antes ")!=-1 or hand[i].find(" posts the ante ")!=-1): + lineTypes.append("ante") + elif (hand[i].startswith("*** FLOP *** [")): + lineTypes.append("cards") + currentStreet="flop" + elif (hand[i].startswith("*** TURN *** [")): + lineTypes.append("cards") + currentStreet="turn" + elif (hand[i].startswith("*** RIVER *** [")): + lineTypes.append("cards") + currentStreet="river" + elif (hand[i].startswith("*** 3")): + lineTypes.append("ignore") + currentStreet=3 + elif (hand[i].startswith("*** 4")): + lineTypes.append("ignore") + currentStreet=4 + elif (hand[i].startswith("*** 5")): + lineTypes.append("ignore") + currentStreet=5 + elif (hand[i].startswith("*** 6")): + lineTypes.append("ignore") + currentStreet=6 + elif (hand[i].startswith("*** 7") or hand[i]=="*** RIVER ***"): + lineTypes.append("ignore") + currentStreet=7 + elif (hand[i].find(" shows [")!=-1): + lineTypes.append("cards") + else: + raise FpdbError("unrecognised linetype in:"+hand[i]) + lineStreets.append(currentStreet) +#end def classifyLines + +#calculates the actual bet amounts in the given amount array and changes it accordingly. +def convert3B4B(site, category, limit_type, actionTypes, actionAmounts): + #print "convert3B4B: actionTypes:", actionTypes + #print "convert3B4B: actionAmounts pre_Convert",actionAmounts + for i in range (len(actionTypes)): + for j in range (len(actionTypes[i])): + bets=[] + for k in range (len(actionTypes[i][j])): + if (actionTypes[i][j][k]=="bet"): + bets.append((i,j,k)) + if (len(bets)==2): + #print "len(bets) 2 or higher, need to correct it. bets:",bets,"len:",len(bets) + amount2=actionAmounts[bets[1][0]][bets[1][1]][bets[1][2]] + amount1=actionAmounts[bets[0][0]][bets[0][1]][bets[0][2]] + actionAmounts[bets[1][0]][bets[1][1]][bets[1][2]]=amount2-amount1 + elif (len(bets)>2): + fail=True + #todo: run correction for below + if (site=="ps" and category=="holdem" and limit_type=="nl" and len(bets)==3): + fail=False + if (site=="ftp" and category=="omahahi" and limit_type=="pl" and len(bets)==3): + fail=False + + if fail: + print "len(bets)>2 in convert3B4B, i didnt think this is possible. i:",i,"j:",j,"k:",k + print "actionTypes:",actionTypes + raise FpdbError ("too many bets in convert3B4B") + #print "actionAmounts postConvert",actionAmounts +#end def convert3B4B(actionTypes, actionAmounts) + +#Corrects the bet amount if the player had to pay blinds +def convertBlindBet(actionTypes, actionAmounts): + i=0#setting street to pre-flop + for j in range (len(actionTypes[i])):#playerloop + blinds=[] + bets=[] + for k in range (len(actionTypes[i][j])): + if (actionTypes[i][j][k]=="blind"): + blinds.append((i,j,k)) + + if (len(blinds)>0 and actionTypes[i][j][k]=="bet"): + bets.append((i,j,k)) + if (len(bets)==1): + blind_amount=actionAmounts[blinds[0][0]][blinds[0][1]][blinds[0][2]] + bet_amount=actionAmounts[bets[0][0]][bets[0][1]][bets[0][2]] + actionAmounts[bets[0][0]][bets[0][1]][bets[0][2]]=bet_amount-blind_amount +#end def convertBlindBet + +#converts the strings in the given array to ints (changes the passed array, no returning). see table design for conversion details +#todo: make this use convertCardValuesBoard +def convertCardValues(arr): + for i in range (len(arr)): + for j in range (len(arr[i])): + if (arr[i][j]=="A"): + arr[i][j]=14 + elif (arr[i][j]=="K"): + arr[i][j]=13 + elif (arr[i][j]=="Q"): + arr[i][j]=12 + elif (arr[i][j]=="J"): + arr[i][j]=11 + elif (arr[i][j]=="T"): + arr[i][j]=10 + else: + arr[i][j]=int(arr[i][j]) +#end def convertCardValues + +#converts the strings in the given array to ints (changes the passed array, no returning). see table design for conversion details +def convertCardValuesBoard(arr): + for i in range (len(arr)): + if (arr[i]=="A"): + arr[i]=14 + elif (arr[i]=="K"): + arr[i]=13 + elif (arr[i]=="Q"): + arr[i]=12 + elif (arr[i]=="J"): + arr[i]=11 + elif (arr[i]=="T"): + arr[i]=10 + else: + arr[i]=int(arr[i]) +#end def convertCardValuesBoard + +#this creates the 2D/3D arrays. manipulates the passed arrays instead of returning. +def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, action_amounts): + for i in range(seats):#create second dimension arrays + tmp=[] + card_values.append(tmp) + tmp=[] + card_suits.append(tmp) + antes.append(0) + winnings.append(0) + rakes.append(0) + + for i in range (8): + #build the first dimension array, for streets + #todo: 0-2 will of course be left empty, get rid of this nicely using consts + tmp=[] + action_types.append(tmp) + tmp=[] + action_amounts.append(tmp) + for j in range (seats): #second dimension arrays: players + tmp=[] + action_types[i].append(tmp) + tmp=[] + action_amounts[i].append(tmp) + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + pass + elif (category=="razz" or category=="studhi" or category=="studhilo"):#need to fill card arrays. + for i in range(seats): + for j in range (7): + card_values[i].append(0) + card_suits[i].append("x") + else: + raise FpdbError("invalid category") +#end def createArrays + +def fill_board_cards(board_values, board_suits): +#fill up the two board card arrays + while (len(board_values)<5): + board_values.append(0) + board_suits.append("x") +#end def fill_board_cards + +def fillCardArrays(player_count, card_count, card_values, card_suits): +#fills up the two card arrays + #print "fillCardArrays, player_count:", player_count," card_count:",card_count + #print "card_values:",card_values + for i in range (player_count): + while (len(card_values[i])=1): + raise DuplicateError ("dupl") +#end isAlreadyInDB + +#returns whether the passed topline indicates a tournament or not +def isTourney(topline): + if (topline.find("Tournament")!=-1): + return True + else: + return False +#end def isTourney + +#returns boolean whether the passed line is a win line +def isWinLine(line): + if (line.find("wins the pot")!=-1): + return True + elif (line.find("ties for the high pot")!=-1): + return True + elif (line.find("ties for the high main pot")!=-1): + return True + elif (line.find("ties for the high side pot")!=-1): + return True + elif (line.find("ties for the low pot")!=-1): + return True + elif (line.find("ties for the low main pot")!=-1): + return True + elif (line.find("ties for the low side pot")!=-1): + return True + elif (line.find("ties for the main pot")!=-1): #for ftp tied main pot of split pot + return True + elif (line.find("ties for the pot")!=-1): #for ftp tie + return True + elif (line.find("ties for the side pot")!=-1): #for ftp tied split pots + return True + elif (line.find("wins side pot #")!=-1): #for ftp multi split pots + return True + elif (line.find("wins the low main pot")!=-1): + return True + elif (line.find("wins the low pot")!=-1): + return True + elif (line.find("wins the low side pot")!=-1): + return True + elif (line.find("wins the high main pot")!=-1): + return True + elif (line.find("wins the high pot")!=-1): + return True + elif (line.find("wins the high side pot")!=-1): + return True + elif (line.find("wins the main pot")!=-1): + return True + elif (line.find("wins the side pot")!=-1): #for ftp split pots + return True + elif (line.find("collected")!=-1): + return True + else: + return False #not raising error here, any unknown line wouldve been detected in isActionLine already +#end def isWinLine + +#returns the amount of cash/chips put into the put in the given action line +def parseActionAmount(line, atype, site): + if (line.endswith(" and is all-in")): + line=line[:-14] + elif (line.endswith(", and is all in")): + line=line[:-15] + + if line.endswith(", and is capped"):#ideally we should recognise this as an all-in if category is capXl + line=line[:-15] + if line.endswith(" and is capped"): + line=line[:-14] + + + if (atype=="fold"): + amount=0 + elif (atype=="check"): + amount=0 + elif (atype=="unbet" and site=="ftp"): + pos1=line.find("$")+1 + pos2=line.find(" returned to") + amount=float2int(line[pos1:pos2]) + elif (atype=="unbet" and site=="ps"): + #print "ps unbet, line:",line + pos1=line.find("$")+1 + if pos1==0: + pos1=line.find("(")+1 + pos2=line.find(")") + amount=float2int(line[pos1:pos2]) + elif (atype=="bet" and site=="ps" and line.find(": raises $")!=-1 and line.find("to $")!=-1): + pos=line.find("to $")+4 + amount=float2int(line[pos:]) + else: + pos=line.rfind("$")+1 + if pos!=0: + amount=float2int(line[pos:]) + else: + #print "line:"+line+"EOL" + pos=line.rfind(" ")+1 + #print "pos:",pos + #print "pos of 20:", line.find("20") + amount=int(line[pos:]) + return amount +#end def parseActionAmount + +#doesnt return anything, simply changes the passed arrays action_types and +# action_amounts. For stud this expects numeric streets (3-7), for +# holdem/omaha it expects predeal, preflop, flop, turn or river +def parseActionLine(line, street, names, action_types, action_amounts, site): + #print "parseActionLine, line:",line + #this only applies to stud + if (street<3): + text="invalid street ("+str(street)+") for line: "+line + raise FpdbError(text) + + if (street=="predeal" or street=="preflop"): + street=0 + elif (street=="flop"): + street=1 + elif (street=="turn"): + street=2 + elif (street=="river"): + street=3 + + atype=parseActionType(line) + playerno=recognisePlayerNo(line, names, atype) + amount=parseActionAmount(line, atype, site) + + action_types[street][playerno].append(atype) + action_amounts[street][playerno].append(amount) +#end def parseActionLine + +#returns the action type code (see table design) of the given action line +def parseActionType(line): + if (line.startswith("Uncalled bet")): + return "unbet" + elif (line.endswith("folds")): + return "fold" + elif (line.endswith("checks")): + return "check" + elif (line.find("calls")!=-1): + return "call" + elif (line.find("brings in for")!=-1): + return "blind" + elif (line.find("completes it to")!=-1): + return "bet" + #todo: what if someone completes instead of bringing in? + elif (line.find(" posts $")!=-1): + return "blind" + elif (line.find(" posts a dead ")!=-1): + return "blind" + elif (line.find(": posts small blind ")!=-1): + return "blind" + elif (line.find(" posts the small blind of $")!=-1): + return "blind" + elif (line.find(": posts big blind ")!=-1): + return "blind" + elif (line.find(" posts the big blind of $")!=-1): + return "blind" + elif (line.find(": posts small & big blinds $")!=-1): + return "blind" + #todo: seperately record voluntary blind payments made to join table out of turn + elif (line.find("bets")!=-1): + return "bet" + elif (line.find("raises")!=-1): + return "bet" + else: + raise FpdbError ("failed to recognise actiontype in parseActionLine in: "+line) +#end def parseActionType + +#parses the ante out of the given line and checks which player paid it, updates antes accordingly. +def parseAnteLine(line, site, names, antes): + for i in range(len(names)): + if (line.startswith(names[i].encode("latin-1"))): #found the ante'er + pos=line.rfind("$")+1 + if pos!=0: #found $, so must be ring + antes[i]+=float2int(line[pos:]) + else: + pos=line.rfind(" ")+1 + antes[i]+=int(line[pos:]) +#end def parseAntes + +#returns the buyin of a tourney in cents +def parseBuyin(topline): + pos1=topline.find("$")+1 + pos2=topline.find("+") + return float2int(topline[pos1:pos2]) +#end def parseBuyin + +#parses a card line and changes the passed arrays accordingly +#todo: reorganise this messy method +def parseCardLine(site, category, street, line, names, cardValues, cardSuits, boardValues, boardSuits): + if (line.startswith("Dealt to ") or line.find(" shows [")!=-1 or line.find("mucked [")!=-1): + playerNo=recognisePlayerNo(line, names, "card") #anything but unbet will be ok for that string + + pos=line.rfind("[")+1 + if (category=="holdem"): + for i in (pos, pos+3): + cardValues[playerNo].append(line[i:i+1]) + cardSuits[playerNo].append(line[i+1:i+2]) + if (len(cardValues[playerNo])!=2): + if cardValues[playerNo][0]==cardValues[playerNo][2] and cardSuits[playerNo][1]==cardSuits[playerNo][3]: #two tests will do + cardValues[playerNo]=cardValues[playerNo][0:2] + cardSuits[playerNo]=cardSuits[playerNo][0:2] + else: + print "line:",line,"cardValues[playerNo]:",cardValues[playerNo] + raise FpdbError("read too many/too few holecards in parseCardLine") + elif (category=="omahahi" or category=="omahahilo"): + for i in (pos, pos+3, pos+6, pos+9): + cardValues[playerNo].append(line[i:i+1]) + cardSuits[playerNo].append(line[i+1:i+2]) + if (len(cardValues[playerNo])!=4): + if cardValues[playerNo][0]==cardValues[playerNo][4] and cardSuits[playerNo][3]==cardSuits[playerNo][7]: #two tests will do + cardValues[playerNo]=cardValues[playerNo][0:4] + cardSuits[playerNo]=cardSuits[playerNo][0:4] + else: + print "line:",line,"cardValues[playerNo]:",cardValues[playerNo] + raise FpdbError("read too many/too few holecards in parseCardLine") + elif (category=="razz" or category=="studhi" or category=="studhilo"): + if (line.find("shows")==-1): + cardValues[playerNo][street-1]=line[pos:pos+1] + cardSuits[playerNo][street-1]=line[pos+1:pos+2] + else: + cardValues[playerNo][0]=line[pos:pos+1] + cardSuits[playerNo][0]=line[pos+1:pos+2] + pos+=3 + cardValues[playerNo][1]=line[pos:pos+1] + cardSuits[playerNo][1]=line[pos+1:pos+2] + if street==7: + pos+=15 + cardValues[playerNo][6]=line[pos:pos+1] + cardSuits[playerNo][6]=line[pos+1:pos+2] + else: + print "line:",line,"street:",street + raise FpdbError("invalid category") + #print "end of parseCardLine/playercards, cardValues:",cardValues + elif (line.startswith("*** FLOP ***")): + pos=line.find("[")+1 + for i in (pos, pos+3, pos+6): + boardValues.append(line[i:i+1]) + boardSuits.append(line[i+1:i+2]) + #print boardValues + elif (line.startswith("*** TURN ***") or line.startswith("*** RIVER ***")): + pos=line.find("[")+1 + pos=line.find("[", pos+1)+1 + boardValues.append(line[pos:pos+1]) + boardSuits.append(line[pos+1:pos+2]) + #print boardValues + else: + raise FpdbError ("unrecognised line:"+line) +#end def parseCardLine + +#parses the start cash of each player out of the given lines and returns them as an array +def parseCashes(lines, site): + result = [] + for i in range (len(lines)): + pos1=lines[i].rfind("($")+2 + if pos1==1: #for tourneys - it's 1 instead of -1 due to adding 2 above + pos1=lines[i].rfind("(")+1 + #print "parseCashes, lines[i]:",lines[i] + #print "parseCashes, pos1:",pos1 + if (site=="ftp"): + pos2=lines[i].rfind(")") + elif (site=="ps"): + #print "in parseCashes, line:", lines[i] + pos2=lines[i].find(" in chips") + #print "in parseCashes, line:", lines[i], "pos1:",pos1,"pos2:",pos2 + result.append(float2int(lines[i][pos1:pos2])) + return result +#end def parseCashes + +#returns the buyin of a tourney in cents +def parseFee(topline): + pos1=topline.find("$")+1 + pos1=topline.find("$",pos1)+1 + pos2=topline.find(" ", pos1) + return float2int(topline[pos1:pos2]) +#end def parsefee + +#returns a datetime object with the starttime indicated in the given topline +def parseHandStartTime(topline, site): + #convert x:13:35 to 0x:13:35 + counter=0 + while (True): + pos=topline.find(" "+str(counter)+":") + if (pos!=-1): + topline=topline[0:pos+1]+"0"+topline[pos+1:] + counter+=1 + if counter==10: break + + if site=="ftp": + pos = topline.find(" ", len(topline)-26)+1 + tmp = topline[pos:] + #print "year:", tmp[14:18], "month", tmp[19:21], "day", tmp[22:24], "hour", tmp[0:2], "minute", tmp[3:5], "second", tmp[6:8] + result = datetime.datetime(int(tmp[14:18]), int(tmp[19:21]), int(tmp[22:24]), int(tmp[0:2]), int(tmp[3:5]), int(tmp[6:8])) + elif site=="ps": + tmp=topline[-30:] + #print "parsehandStartTime, tmp:", tmp + pos = tmp.find("-")+2 + tmp = tmp[pos:] + #print "year:", tmp[0:4], "month", tmp[5:7], "day", tmp[8:10], "hour", tmp[13:15], "minute", tmp[16:18], "second", tmp[19:21] + result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[13:15]), int(tmp[16:18]), int(tmp[19:21])) + else: + raise FpdbError("invalid site in parseHandStartTime") + + if site=="ftp" or site=="ps": #these use US ET + result+=datetime.timedelta(hours=5) + + return result +#end def parseHandStartTime + +#parses the names out of the given lines and returns them as an array +def parseNames(lines): + result = [] + for i in range (len(lines)): + pos1=lines[i].find(":")+2 + pos2=lines[i].rfind("(")-1 + tmp=lines[i][pos1:pos2] + #print "parseNames, tmp original:",tmp + tmp=unicode(tmp,"latin-1") + #print "parseNames, tmp after unicode latin-1 conversion:",tmp + result.append(tmp) + return result +#end def parseNames + +#returns an array with the positions of the respective players +def parsePositions (hand, names): + #prep array + positions=[] + for i in range(len(names)): + positions.append(-1) + + #find blinds + sb,bb=-1,-1 + for i in range (len(hand)): + if (sb==-1 and hand[i].find("small blind")!=-1 and hand[i].find("dead small blind")==-1): + sb=hand[i] + #print "sb:",sb + if (bb==-1 and hand[i].find("big blind")!=-1 and hand[i].find("dead big blind")==-1): + bb=hand[i] + #print "bb:",bb + + #identify blinds + #print "parsePositions before recognising sb/bb. names:",names + sbExists=True + if (sb!=-1): + sb=recognisePlayerNo(sb, names, "bet") + else: + sbExists=False + if (bb!=-1): + bb=recognisePlayerNo(bb, names, "bet") + + #write blinds into array + if (sbExists): + positions[sb]="S" + positions[bb]="B" + + #fill up rest of array + if (sbExists): + arraypos=sb-1 + else: + arraypos=bb-1 + distFromBtn=0 + while (arraypos>=0 and arraypos != bb): + #print "parsePositions first while, arraypos:",arraypos,"positions:",positions + positions[arraypos]=distFromBtn + arraypos-=1 + distFromBtn+=1 + + arraypos=len(names)-1 + if (bb!=0 or (bb==0 and sbExists==False)): + while (arraypos>bb): + positions[arraypos]=distFromBtn + arraypos-=1 + distFromBtn+=1 + + for i in range (len(names)): + if positions[i]==-1: + print "parsePositions names:",names + print "result:",positions + raise FpdbError ("failed to read positions") + return positions +#end def parsePositions + +#simply parses the rake amount and returns it as an int +def parseRake(line): + pos=line.find("Rake")+6 + rake=float2int(line[pos:]) + return rake +#end def parseRake + +def parseSiteHandNo(topline): + """returns the hand no assigned by the poker site""" + pos1=topline.find("#")+1 + pos2=topline.find(":") + return topline[pos1:pos2] +#end def parseHandSiteNo + +#returns the hand no assigned by the poker site +def parseTourneyNo(topline): + pos1=topline.find("Tournament #")+12 + pos2=topline.find(",", pos1) + #print "parseTourneyNo pos1:",pos1," pos2:",pos2, " result:",topline[pos1:pos2] + return topline[pos1:pos2] +#end def parseTourneyNo + +#parses a win/collect line. manipulates the passed array winnings, no explicit return +def parseWinLine(line, site, names, winnings, isTourney): + #print "parseWinLine: line:",line + for i in range(len(names)): + if (line.startswith(names[i].encode("latin-1"))): #found a winner + if isTourney: + pos1=line.rfind("collected ")+11 + if (site=="ftp"): + pos2=line.find(")", pos1) + elif (site=="ps"): + pos2=line.find(" ", pos1) + winnings[i]+=int(line[pos1:pos2]) + else: + pos1=line.rfind("$")+1 + if (site=="ftp"): + pos2=line.find(")", pos1) + elif (site=="ps"): + pos2=line.find(" ", pos1) + winnings[i]+=float2int(line[pos1:pos2]) +#end def parseWinLine + +#returns the category (as per database) string for the given line +def recogniseCategory(line): + if (line.find("Razz")!=-1): + return "razz" + elif (line.find("Hold'em")!=-1): + return "holdem" + elif (line.find("Omaha")!=-1 and line.find("Hi/Lo")==-1 and line.find("H/L")==-1): + return "omahahi" + elif (line.find("Omaha")!=-1 and (line.find("Hi/Lo")!=-1 or line.find("H/L")!=-1)): + return "omahahilo" + elif (line.find("Stud")!=-1 and line.find("Hi/Lo")==-1 and line.find("H/L")==-1): + return "studhi" + elif (line.find("Stud")!=-1 and (line.find("Hi/Lo")!=-1 or line.find("H/L")!=-1)): + return "studhilo" + else: + raise FpdbError("failed to recognise category, line:"+line) +#end def recogniseCategory + +#returns the int for the gametype_id for the given line +def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: this method is messy + #if (topline.find("HORSE")!=-1): + # raise FpdbError("recogniseGametypeID: HORSE is not yet supported.") + + #note: the below variable names small_bet and big_bet are misleading, in NL/PL they mean small/big blind + if isTourney: + type="tour" + pos1=topline.find("(")+1 + if (topline[pos1]=="H" or topline[pos1]=="O" or topline[pos1]=="R" or topline[pos1]=="S" or topline[pos1+2]=="C"): + pos1=topline.find("(", pos1)+1 + pos2=topline.find("/", pos1) + small_bet=int(topline[pos1:pos2]) + else: + type="ring" + pos1=topline.find("$")+1 + pos2=topline.find("/$") + small_bet=float2int(topline[pos1:pos2]) + + pos1=pos2+2 + if isTourney: + pos1-=1 + if (site_id==1): #ftp + pos2=topline.find(" ", pos1) + elif (site_id==2): #ps + pos2=topline.find(")") + + if pos2<=pos1: + pos2=topline.find(")", pos1) + + + if isTourney: + big_bet=int(topline[pos1:pos2]) + else: + big_bet=float2int(topline[pos1:pos2]) + + if (topline.find("No Limit")!=-1): + limit_type="nl" + if (topline.find("Cap No")!=-1): + limit_type="cn" + elif (topline.find("Pot Limit")!=-1): + limit_type="pl" + if (topline.find("Cap Pot")!=-1): + limit_type="cp" + else: + limit_type="fl" + + #print "recogniseGametypeID small_bet/blind:",small_bet,"big bet/blind:", big_bet,"limit type:",limit_type + if (limit_type=="fl"): + cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_bet=%s AND big_bet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + else: + cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_blind=%s AND big_blind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + result=cursor.fetchone() + #print "tried SELECTing gametypes.id, result:",result + + try: + len(result) + except TypeError: + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + max_seats=10 + else: + max_seats=8 + + if (limit_type=="fl"): + big_blind=small_bet #todo: read this + small_blind=big_blind/2 #todo: read this + cursor.execute("""INSERT INTO gametypes + (site_id, type, category, limit_type, small_blind, big_blind, small_bet, big_bet) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, category, limit_type, small_blind, big_blind, small_bet, big_bet)) + cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_bet=%s AND big_bet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + else: + cursor.execute("""INSERT INTO gametypes + (site_id, type, category, limit_type, small_blind, big_blind, small_bet, big_bet) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, category, limit_type, small_bet, big_bet, 0, 0))#remember, for these bet means blind + cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_blind=%s AND big_blind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + + result=cursor.fetchone() + #print "created new gametypes.id:",result + + return result[0] +#end def recogniseGametypeID + +#returns the SQL ids of the names given in an array +def recognisePlayerIDs(cursor, names, site_id): + result = [] + for i in range (len(names)): + cursor.execute ("SELECT id FROM players WHERE name=%s", (names[i],)) + tmp=cursor.fetchall() + if (len(tmp)==0): #new player + cursor.execute ("INSERT INTO players (name, site_id) VALUES (%s, %s)", (names[i], site_id)) + #print "Number of players rows inserted: %d" % cursor.rowcount + cursor.execute ("SELECT id FROM players WHERE name=%s", (names[i],)) + tmp=cursor.fetchall() + #print "recognisePlayerIDs, names[i]:",names[i],"tmp:",tmp + result.append(tmp[0][0]) + return result +#end def recognisePlayerIDs + +#recognises the name in the given line and returns its array position in the given array +def recognisePlayerNo(line, names, atype): + #print "recogniseplayerno, names:",names + for i in range (len(names)): + if (atype=="unbet"): + if (line.endswith(names[i].encode("latin-1"))): + return (i) + elif (line.startswith("Dealt to ")): + #print "recognisePlayerNo, card precut, line:",line + tmp=line[9:] + #print "recognisePlayerNo, card postcut, tmp:",tmp + if (tmp.startswith(names[i].encode("latin-1"))): + return (i) + elif (line.startswith("Seat ")): + if (line.startswith("Seat 10")): + tmp=line[9:] + else: + tmp=line[8:] + + if (tmp.startswith(names[i].encode("latin-1"))): + return (i) + else: + if (line.startswith(names[i].encode("latin-1"))): + return (i) + #if we're here we mustve failed + raise FpdbError ("failed to recognise player in: "+line+" atype:"+atype) +#end def recognisePlayerNo + +#returns the site abbreviation for the given site +def recogniseSite(line): + if (line.startswith("Full Tilt Poker")): + return "ftp" + elif (line.startswith("PokerStars")): + return "ps" + else: + raise FpdbError("failed to recognise site, line:"+line) +#end def recogniseSite + +#returns the ID of the given site +def recogniseSiteID(cursor, site): + if (site=="ftp"): + cursor.execute("SELECT id FROM sites WHERE name = ('Full Tilt Poker')") + elif (site=="ps"): + cursor.execute("SELECT id FROM sites WHERE name = ('PokerStars')") + return cursor.fetchall()[0][0] +#end def recogniseSiteID + +#removes trailing \n from the given array +def removeTrailingEOL(arr): + for i in range(len(arr)): + if (arr[i].endswith("\n")): + #print "arr[i] before removetrailingEOL:", arr[i] + arr[i]=arr[i][:-1] + #print "arr[i] after removetrailingEOL:", arr[i] + return arr +#end def removeTrailingEOL + +#splits the rake according to the proportion of pot won. manipulates the second passed array. +def splitRake(winnings, rakes, totalRake): + winnercnt=0 + totalWin=0 + for i in range(len(winnings)): + if winnings[i]!=0: + winnercnt+=1 + totalWin+=winnings[i] + firstWinner=i + if winnercnt==1: + rakes[firstWinner]=totalRake + else: + totalWin=float(totalWin) + for i in range(len(winnings)): + if winnings[i]!=0: + winPortion=winnings[i]/totalWin + rakes[i]=totalRake*winPortion +#end def splitRake + +def storeActions(cursor, hands_players_ids, action_types, action_amounts): +#stores into table hands_actions + for i in range (len(action_types)): #iterate through streets + for j in range (len(action_types[i])): #iterate through names + for k in range (len(action_types[i][j])): #iterate through individual actions of that player on that street + cursor.execute ("INSERT INTO hands_actions (hand_player_id, street, action_no, action, amount) VALUES (%s, %s, %s, %s, %s)", (hands_players_ids[j], i, k, action_types[i][j][k], action_amounts[i][j][k])) +#end def storeActions + +def store_board_cards(cursor, hands_id, board_values, board_suits): +#stores into table board_cards + cursor.execute (""" + INSERT INTO board_cards (hand_id, card1_value, card1_suit, + card2_value, card2_suit, card3_value, card3_suit, card4_value, card4_suit, + card5_value, card5_suit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + (hands_id, board_values[0], board_suits[0], board_values[1], board_suits[1], + board_values[2], board_suits[2], board_values[3], board_suits[3], + board_values[4], board_suits[4])) +#end def store_board_cards + +def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names): +#stores into table hands + cursor.execute ("INSERT INTO hands (site_hand_no, gametype_id, hand_start, seats) VALUES (%s, %s, %s, %s)", (site_hand_no, gametype_id, hand_start_time, len(names))) + #todo: find a better way of doing this... + cursor.execute("SELECT id FROM hands WHERE site_hand_no=%s AND gametype_id=%s", (site_hand_no, gametype_id)) + return cursor.fetchall()[0][0] +#end def storeHands + +def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, + start_cashes, positions, card_values, card_suits, winnings, rakes): + result=[] + if (category=="holdem"): + for i in range (len(player_ids)): + cursor.execute (""" + INSERT INTO hands_players + (hand_id, player_id, player_startcash, position, + card1_value, card1_suit, card2_value, card2_suit, winnings, rake) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + (hands_id, player_ids[i], start_cashes[i], positions[i], + card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], + winnings[i], rakes[i])) + cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + result.append(cursor.fetchall()[0][0]) + elif (category=="omahahi" or category=="omahahilo"): + for i in range (len(player_ids)): + cursor.execute (""" + INSERT INTO hands_players + (hand_id, player_id, player_startcash, position, + card1_value, card1_suit, card2_value, card2_suit, + card3_value, card3_suit, card4_value, card4_suit, winnings, rake) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + (hands_id, player_ids[i], start_cashes[i], positions[i], + card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], + card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], + winnings[i], rakes[i])) + cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + result.append(cursor.fetchall()[0][0]) + else: + raise FpdbError("invalid category") + return result +#end def store_hands_players_holdem_omaha + +def store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, + card_values, card_suits, winnings, rakes): +#stores hands_players rows for stud/razz games. returns an array of the resulting IDs + result=[] + for i in range (len(player_ids)): + cursor.execute ("""INSERT INTO hands_players + (hand_id, player_id, player_startcash, ante, + card1_value, card1_suit, card2_value, card2_suit, + card3_value, card3_suit, card4_value, card4_suit, + card5_value, card5_suit, card6_value, card6_suit, + card7_value, card7_suit, winnings, rake) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + %s, %s, %s, %s)""", + (hands_id, player_ids[i], start_cashes[i], antes[i], + card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], + card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], + card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5], + card_values[i][6], card_suits[i][6], winnings[i], rakes[i])) + cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + result.append(cursor.fetchall()[0][0]) + return result +#end def store_hands_players_stud + +def store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, start_cashes, + positions, card_values, card_suits, winnings, rakes, tourneys_players_ids): +#stores hands_players for tourney holdem/omaha hands + result=[] + for i in range (len(player_ids)): + if len(card_values[0])==2: + cursor.execute ("""INSERT INTO hands_players + (hand_id, player_id, player_startcash,position, + card1_value, card1_suit, card2_value, card2_suit, + winnings, rake, tourneys_players_id) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + (hands_id, player_ids[i], start_cashes[i], positions[i], + card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], + winnings[i], rakes[i], tourneys_players_ids[i])) + elif len(card_values[0])==4: + cursor.execute ("""INSERT INTO hands_players + (hand_id, player_id, player_startcash,position, + card1_value, card1_suit, card2_value, card2_suit, + card3_value, card3_suit, card4_value, card4_suit, + winnings, rake, tourneys_players_id) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + (hands_id, player_ids[i], start_cashes[i], positions[i], + card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], + card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], + winnings[i], rakes[i], tourneys_players_ids[i])) + else: + raise FpdbError ("invalid card_values length:"+str(len(card_values[0]))) + cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + result.append(cursor.fetchall()[0][0]) + + return result +#end def store_hands_players_holdem_omaha_tourney + +def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, + antes, card_values, card_suits, winnings, rakes, tourneys_players_ids): +#stores hands_players for tourney stud/razz hands + result=[] + for i in range (len(player_ids)): + cursor.execute ("""INSERT INTO hands_players + (hand_id, player_id, player_startcash, ante, + card1_value, card1_suit, card2_value, card2_suit, + card3_value, card3_suit, card4_value, card4_suit, + card5_value, card5_suit, card6_value, card6_suit, + card7_value, card7_suit, winnings, rake, tourneys_players_id) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s)""", + (hands_id, player_ids[i], start_cashes[i], antes[i], + card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], + card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], + card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5], + card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i])) + cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + result.append(cursor.fetchall()[0][0]) + return result +#end def store_hands_players_stud_tourney + +def calculateHudImport(player_ids, category, action_types): + VPIP=[] + PFR=[] + PF3B4B=[] + sawFlop=[] + sawTurn=[] + sawRiver=[] + sawShowdown=[] + raisedFlop=[] + raisedTurn=[] + raisedRiver=[] + otherRaisedFlop=[] + otherRaisedFlopFold=[] + otherRaisedTurn=[] + otherRaisedTurnFold=[] + otherRaisedRiver=[] + otherRaisedRiverFold=[] + for player in range (len(player_ids)): + myVPIP=False + myPFR=False + myPF3B4B=False + mySawFlop=False + mySawTurn=False + mySawRiver=False + mySawShowdown=False + myRaisedFlop=False + myRaisedTurn=False + myRaisedRiver=False + myOtherRaisedFlop=False + myOtherRaisedFlopFold=False + myOtherRaisedTurn=False + myOtherRaisedTurnFold=False + myOtherRaisedRiver=False + myOtherRaisedRiverFold=False + + street=0 + pfRaiseCount=0 + for count in range (len(action_types[street][player])):#finally individual actions + currentAction=action_types[street][player][count] + if currentAction!="bet": + pfRaiseCount++ + if (currentAction=="bet" or currentAction=="call"): + myVPIP=True + if pfRaiseCount>=1: + myPFR=True + if pfRaiseCount>=2:#todo: this doesnt catch all 3B4B + myPF3B4B=True + + #todo: flop, turn, river, SD + + VPIP.append(myVPIP) + PFR.append(myPFR) + PF3B4B.append(myPF3B4B) + sawFlop.append(mySawFlop) + sawTurn.append(mySawTurn) + sawRiver.append(mySawRiver) + sawShowdown.append(mySawShowdown) + raisedFlop.append(myRaisedFlop) + raisedTurn.append(myRaisedTurn) + raisedRiver.append(myRaisedRiver) + otherRaisedFlop.append(myOtherRaisedFlop) + otherRaisedFlopFold.append(myOtherRaisedFlopFold) + otherRaisedTurn.append(myOtherRaisedTurn) + otherRaisedTurnFold.append(myOtherRaisedTurnFold) + otherRaisedRiver.append(myOtherRaisedRiver) + otherRaisedRiverFold.append(myOtherRaisedRiverFold) + + result={'VPIP':VPIP} + result['PFR']=PFR + result['PF3B4B']=PF3B4B + result['sawFlop']=sawFlop + result['sawTurn']=sawTurn + result['sawRiver']=sawRiver + result['sawShowdown']=sawShowdown + result['raisedFlop']=raisedFlop + result['otherRaisedFlop']=otherRaisedFlop + result['otherRaisedFlopFold']=otherRaisedFlopFold + result['raisedTurn']=raisedTurn + result['otherRaisedTurn']=otherRaisedTurn + result['otherRaisedTurnFold']=otherRaisedTurnFold + result['raisedRiver']=raisedRiver + result['otherRaisedRiver']=otherRaisedRiver + result['otherRaisedRiverFold']=otherRaisedRiverFold + return result +#end def calculate_hands_players_flags + +def store_hands_players_flags(cursor, category, hand_player_ids, hands_players_flags): + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + for i in range (len(hand_player_ids)): + cursor.execute("""INSERT INTO hands_players_flags (hand_player_id, folded_on, street0_vpi, street0_raise, street1_raise, street2_raise, street3_raise) VALUES (%s, %s, %s, %s, %s, %s, %s)""", (hand_player_ids[i], hands_players_flags['folded_on'][i], hands_players_flags['street0_vpi'][i], hands_players_flags['street0_raise'][i], hands_players_flags['street1_raise'][i], hands_players_flags['street2_raise'][i], hands_players_flags['street3_raise'][i])) + else: + for i in range (len(hand_player_ids)): + cursor.execute("""INSERT INTO hands_players_flags (hand_player_id, folded_on, street0_vpi, street0_raise, street1_raise, street2_raise, street3_raise, street4_raise) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (hand_player_ids[i], hands_players_flags['folded_on'][i], hands_players_flags['street0_vpi'][i], hands_players_flags['street0_raise'][i], hands_players_flags['street1_raise'][i], hands_players_flags['street2_raise'][i], hands_players_flags['street3_raise'][i], hands_players_flags['street4_raise'][i])) +#end def store_hands_players_flags(cursor, hands_players_ids, hands_players_flags) + +def store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time): + cursor.execute("SELECT id FROM tourneys WHERE site_tourney_no=%s AND site_id=%s", (site_tourney_no, site_id)) + tmp=cursor.fetchone() + #print "tried SELECTing tourneys.id, result:",tmp + + try: + len(tmp) + except TypeError: + cursor.execute("""INSERT INTO tourneys + (site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time)) + cursor.execute("SELECT id FROM tourneys WHERE site_tourney_no=%s AND site_id=%s", (site_tourney_no, site_id)) + tmp=cursor.fetchone() + #print "created new tourneys.id:",tmp + return tmp[0] +#end def store_tourneys + +def store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings): + result=[] + #print "in store_tourneys_players. tourney_id:",tourney_id + #print "player_ids:",player_ids + #print "payin_amounts:",payin_amounts + #print "ranks:",ranks + #print "winnings:",winnings + for i in range (len(player_ids)): + cursor.execute("SELECT id FROM tourneys_players WHERE tourney_id=%s AND player_id=%s", (tourney_id, player_ids[i])) + tmp=cursor.fetchone() + #print "tried SELECTing tourneys_players.id:",tmp + + try: + len(tmp) + except TypeError: + cursor.execute("""INSERT INTO tourneys_players + (tourney_id, player_id, payin_amount, rank, winnings) VALUES (%s, %s, %s, %s, %s)""", + (tourney_id, player_ids[i], payin_amounts[i], ranks[i], winnings[i])) + + cursor.execute("SELECT id FROM tourneys_players WHERE tourney_id=%s AND player_id=%s", + (tourney_id, player_ids[i])) + tmp=cursor.fetchone() + #print "created new tourneys_players.id:",tmp + result.append(tmp[0]) + return result diff --git a/pyfpdb/import_threaded.py b/pyfpdb/import_threaded.py new file mode 100755 index 00000000..74bb5878 --- /dev/null +++ b/pyfpdb/import_threaded.py @@ -0,0 +1,153 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import threading +import fpdb_simple +import fpdb_import +import pygtk +pygtk.require('2.0') +import gtk +import os #todo: remove this once import_dir is in fpdb_import + +class import_threaded (threading.Thread): + def import_dir(self): + """imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it""" + self.path=self.inputFile + for file in os.listdir(self.path): + if os.path.isdir(file): + pass + else: + self.inputFile=self.path+os.sep+file + fpdb_import.import_file_dict(self) + print "import_dir done" + + def load_clicked(self, widget, data=None): + self.inputFile=self.chooser.get_filename() + + self.handCount=self.hand_count_tbuffer.get_text(self.hand_count_tbuffer.get_start_iter(), self.hand_count_tbuffer.get_end_iter()) + if (self.handCount=="unlimited" or self.handCount=="Unlimited"): + self.handCount=0 + else: + self.handCount=int(self.handCount) + + self.errorFile="failed.txt" + + self.minPrint=self.min_print_tbuffer.get_text(self.min_print_tbuffer.get_start_iter(), self.min_print_tbuffer.get_end_iter()) + if (self.minPrint=="never" or self.minPrint=="Never"): + self.minPrint=0 + else: + self.minPrint=int(self.minPrint) + + self.quiet=self.info_tbuffer.get_text(self.info_tbuffer.get_start_iter(), self.info_tbuffer.get_end_iter()) + if (self.quiet=="yes"): + self.quiet=False + else: + self.quiet=True + + self.failOnError=self.fail_error_tbuffer.get_text(self.fail_error_tbuffer.get_start_iter(), self.fail_error_tbuffer.get_end_iter()) + if (self.failOnError=="no"): + self.failOnError=False + else: + self.failOnError=True + + self.server, self.database, self.user, self.password=self.db.get_db_info() + + if os.path.isdir(self.inputFile): + self.import_dir() + else: + fpdb_import.import_file_dict(self) + + def get_vbox(self): + """returns the vbox of this thread""" + return self.vbox + #end def get_vbox + + def run (self): + print "todo: implement bulk import thread" + #end def run + + def __init__(self, db, initial_path="/work/poker-histories/wine-ps/"): + self.db=db + + self.vbox=gtk.VBox(False,1) + self.vbox.show() + + self.chooser = gtk.FileChooserWidget() + self.chooser.set_filename(initial_path) + #chooser.set_default_response(gtk.RESPONSE_OK) + #self.filesel.ok_button.connect_object("clicked", gtk.Widget.destroy, self.filesel) + self.vbox.add(self.chooser) + self.chooser.show() + + + self.settings_hbox = gtk.HBox(False, 0) + self.vbox.pack_end(self.settings_hbox, False, True, 0) + self.settings_hbox.show() + + self.hand_count_label = gtk.Label("Hands to import per file") + self.settings_hbox.add(self.hand_count_label) + self.hand_count_label.show() + + self.hand_count_tbuffer=gtk.TextBuffer() + self.hand_count_tbuffer.set_text("unlimited") + self.hand_count_tview=gtk.TextView(self.hand_count_tbuffer) + self.settings_hbox.add(self.hand_count_tview) + self.hand_count_tview.show() + + self.min_hands_label = gtk.Label("Status every") + self.settings_hbox.add(self.min_hands_label) + self.min_hands_label.show() + + self.min_print_tbuffer=gtk.TextBuffer() + self.min_print_tbuffer.set_text("never") + self.min_print_tview=gtk.TextView(self.min_print_tbuffer) + self.settings_hbox.add(self.min_print_tview) + self.min_print_tview.show() + + + self.toggles_hbox = gtk.HBox(False, 0) + self.vbox.pack_end(self.toggles_hbox, False, True, 0) + self.toggles_hbox.show() + + self.info_label = gtk.Label("Print start/end info:") + self.toggles_hbox.add(self.info_label) + self.info_label.show() + + self.info_tbuffer=gtk.TextBuffer() + self.info_tbuffer.set_text("yes") + self.info_tview=gtk.TextView(self.info_tbuffer) + self.toggles_hbox.add(self.info_tview) + self.info_tview.show() + + self.fail_error_label = gtk.Label("Fail on error:") + self.toggles_hbox.add(self.fail_error_label) + self.fail_error_label.show() + + self.fail_error_tbuffer=gtk.TextBuffer() + self.fail_error_tbuffer.set_text("no") + self.fail_error_tview=gtk.TextView(self.fail_error_tbuffer) + self.toggles_hbox.add(self.fail_error_tview) + self.fail_error_tview.show() + + self.load_button = gtk.Button("Import") #todo: rename variables to import too + self.load_button.connect("clicked", self.load_clicked, "Import clicked") + self.toggles_hbox.add(self.load_button) + self.load_button.show() + + threading.Thread.__init__ ( self ) + print "initialised new bulk import thread (not actually a thread yet)" +#end class import_threaded diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py new file mode 100755 index 00000000..a7e02982 --- /dev/null +++ b/pyfpdb/table_viewer.py @@ -0,0 +1,271 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import threading +import pygtk +pygtk.require('2.0') +import gtk +import os +import MySQLdb +import fpdb_import +import fpdb_db + +class table_viewer (threading.Thread): + def browse_clicked(self, widget, data): + """runs when user clicks browse on tv tab""" + print "start of table_viewer.browser_clicked" + current_path=self.filename_tbuffer.get_text(self.filename_tbuffer.get_start_iter(), self.filename_tbuffer.get_end_iter()) + + dia_chooser = gtk.FileChooserDialog(title="Please choose the file for which you want to open the Table Viewer", + action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) + #dia_chooser.set_current_folder(pathname) + dia_chooser.set_filename(current_path) + #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import + + response = dia_chooser.run() + if response == gtk.RESPONSE_OK: + #print dia_chooser.get_filename(), 'selected' + self.filename_tbuffer.set_text(dia_chooser.get_filename()) + elif response == gtk.RESPONSE_CANCEL: + print 'Closed, no files selected' + dia_chooser.destroy() + #end def table_viewer.browse_clicked + + def prepare_data(self): + """prepares the data for display by refresh_clicked, returns a 2D array""" + print "start of prepare_data" + arr=[] + #first prepare the header row + if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): + tmp=("Name", "Hands", "VPIP", "PFR", "AF", "FF", "AT", "FT", "AR", "FR") + streets=(1,2,3) + else: + tmp=("Name", "Hands", "VPI3", "A3", "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7") + streets=(4,5,6,7)#todo: change this once table has been changed + arr.append(tmp) + + #then the data rows + for i in range(len(self.player_names)): + tmp=[] + tmp.append(self.player_names[i][0]) + + self.cursor.execute("""SELECT DISTINCT hands.id FROM hands + INNER JOIN hands_players ON hands_players.hand_id = hands.id + WHERE hands.gametype_id=%s AND hands_players.player_id=%s""", (self.gametype_id, self.player_ids[i][0])) + hand_count=self.cursor.rowcount + tmp.append(str(hand_count)) + + self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players + INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id + INNER JOIN hands ON hands_players.hand_id = hands.id + WHERE hands.gametype_id=%s AND hands_players.player_id=%s + AND street0_vpi=True""", (self.gametype_id, self.player_ids[i][0])) + vpi_count=self.cursor.rowcount + vpi_percent=int(vpi_count/float(hand_count)*100) + tmp.append(str(vpi_percent)) + + + self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players + INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id + INNER JOIN hands ON hands_players.hand_id = hands.id + WHERE hands.gametype_id=%s AND hands_players.player_id=%s + AND street0_raise=True""", (self.gametype_id, self.player_ids[i][0])) + raise_count=self.cursor.rowcount + raise_percent=int(raise_count/float(hand_count)*100) + tmp.append(str(raise_percent)) + + ######start of flop/4th street###### + hand_count + + play_counts=[] + raise_counts=[] + fold_counts=[] + self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players + INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id + INNER JOIN hands ON hands_players.hand_id = hands.id + WHERE hands.gametype_id=%s AND hands_players.player_id=%s + AND folded_on=0""", (self.gametype_id, self.player_ids[i][0])) + preflop_fold_count=self.cursor.rowcount + last_play_count=hand_count-preflop_fold_count + play_counts.append(last_play_count) + + for street in streets: + self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players + INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id + INNER JOIN hands ON hands_players.hand_id = hands.id + WHERE hands.gametype_id=%s AND hands_players.player_id=%s + AND folded_on="""+str(street), (self.gametype_id, self.player_ids[i][0])) + fold_count=self.cursor.rowcount + fold_counts.append(fold_count) + last_play_count-=fold_count + play_counts.append(last_play_count) + + self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players + INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id + INNER JOIN hands ON hands_players.hand_id = hands.id + WHERE hands.gametype_id=%s AND hands_players.player_id=%s + AND street"""+str(street)+"_raise=True""", (self.gametype_id, self.player_ids[i][0])) + raise_counts.append(self.cursor.rowcount) + + for street in range (len(streets)): + if play_counts[street]>0: + raise_percent=int(raise_counts[street]/float(play_counts[street])*100) + tmp.append(str(raise_percent)) + fold_percent=int(fold_counts[street]/float(play_counts[street])*100) + tmp.append(str(fold_percent)) + else: + tmp.append("n/a") + tmp.append("n/a") + + arr.append(tmp) + return arr + #end def table_viewer.prepare_data + + def refresh_clicked(self, widget, data): + """runs when user clicks refresh""" + if self.debug: print "start of table_viewer.refresh_clicked" + arr=self.prepare_data() + + try: self.data_table.destroy() + except AttributeError: pass + self.data_table=gtk.Table(rows=len(arr), columns=len(arr[0]), homogeneous=False) + self.main_vbox.pack_start(self.data_table) + self.data_table.show() + + for row in range(len(arr)): + for column in range (len(arr[row])): + new_label=gtk.Label(arr[row][column]) + self.data_table.attach(child=new_label, left_attach=column, right_attach=column+1, top_attach=row, bottom_attach=row+1) + new_label.show() + #end def table_viewer.refresh_clicked + + def read_names_clicked(self, widget, data): + """runs when user clicks read names""" + print "start of table_viewer.read_names_clicked" + print "self.last_read_hand:",self.last_read_hand + self.db.reconnect() + self.cursor=self.db.cursor + self.cursor.execute("""SELECT id FROM hands WHERE site_hand_no=%s""", (self.last_read_hand)) + hands_id_tmp=self.db.cursor.fetchone() + print "tmp:",hands_id_tmp + self.hands_id=hands_id_tmp[0] + + self.db.cursor.execute("SELECT gametype_id FROM hands WHERE id=%s", (self.hands_id, )) + self.gametype_id=self.db.cursor.fetchone()[0] + self.cursor.execute("SELECT category FROM gametypes WHERE id=%s", (self.gametype_id, )) + self.category=self.db.cursor.fetchone()[0] + print "self.gametype_id", self.gametype_id," category:", self.category, " self.hands_id:", self.hands_id + + self.db.cursor.execute("""SELECT DISTINCT players.id FROM hands_players + INNER JOIN players ON hands_players.player_id=players.id + WHERE hand_id=%s""", (self.hands_id, )) + self.player_ids=self.db.cursor.fetchall() + print "self.player_ids:",self.player_ids + + self.db.cursor.execute("""SELECT DISTINCT players.name FROM hands_players + INNER JOIN players ON hands_players.player_id=players.id + WHERE hand_id=%s""", (self.hands_id, )) + self.player_names=self.db.cursor.fetchall() + print "self.player_names:",self.player_names + #end def table_viewer.read_names_clicked + + def import_clicked(self, widget, data): + """runs when user clicks import""" + print "start of table_viewer.import_clicked" + self.inputFile=self.filename_tbuffer.get_text(self.filename_tbuffer.get_start_iter(), self.filename_tbuffer.get_end_iter()) + + self.server=self.db.host + self.database=self.db.database + self.user=self.db.user + self.password=self.db.password + + self.quiet=False + self.failOnError=False + self.minPrint=0 + self.handCount=0 + + self.last_read_hand=fpdb_import.import_file_dict(self) + #end def table_viewer.import_clicked + + def all_clicked(self, widget, data): + """runs when user clicks all""" + print "start of table_viewer.all_clicked" + self.import_clicked(widget, data) + self.read_names_clicked(widget, data) + self.refresh_clicked(widget, data) + #end def table_viewer.all_clicked + + def get_vbox(self): + """returns the vbox of this thread""" + return self.main_vbox + #end def get_vbox + + def __init__(self, db, debug=True): + """Constructor for table_viewer""" + self.debug=debug + if self.debug: print "start of table_viewer constructor" + self.db=db + self.cursor=db.cursor + + self.main_vbox = gtk.VBox(False, 0) + self.main_vbox.show() + + self.settings_hbox = gtk.HBox(False, 0) + self.main_vbox.pack_end(self.settings_hbox, False, True, 0) + self.settings_hbox.show() + + self.filename_label = gtk.Label("Path of history file") + self.settings_hbox.add(self.filename_label) + self.filename_label.show() + + self.filename_tbuffer=gtk.TextBuffer() + self.filename_tbuffer.set_text("/home/sycamore/ps-history/HH20080726 Meliboea - $0.10-$0.20 - Limit Hold'em.txt") + self.filename_tview=gtk.TextView(self.filename_tbuffer) + self.settings_hbox.add(self.filename_tview) + self.filename_tview.show() + + self.browse_button=gtk.Button("Browse...") + self.browse_button.connect("clicked", self.browse_clicked, "Browse clicked") + self.settings_hbox.add(self.browse_button) + self.browse_button.show() + + + self.button_hbox = gtk.HBox(False, 0) + self.main_vbox.pack_end(self.button_hbox, False, True, 0) + self.button_hbox.show() + + #self.import_button = gtk.Button("Import") + #self.import_button.connect("clicked", self.import_clicked, "Import clicked") + #self.button_hbox.add(self.import_button) + #self.import_button.show() + + #self.read_names_button = gtk.Button("Read Names") + #self.read_names_button.connect("clicked", self.read_names_clicked, "Read clicked") + #self.button_hbox.add(self.read_names_button) + #self.read_names_button.show() + + #self.refresh_button = gtk.Button("Show/Refresh data") + #self.refresh_button.connect("clicked", self.refresh_clicked, "Refresh clicked") + #self.button_hbox.add(self.refresh_button) + #self.refresh_button.show() + + self.all_button = gtk.Button("Import&Read&Refresh") + self.all_button.connect("clicked", self.all_clicked, "All clicked") + self.button_hbox.add(self.all_button) + self.all_button.show() + #end of table_viewer.__init__ diff --git a/testdata/ftp-omaha-hi-pl-ring-001-005.txt b/testdata/ftp-omaha-hi-pl-ring-001-005.txt new file mode 100644 index 00000000..b2b4ebb6 --- /dev/null +++ b/testdata/ftp-omaha-hi-pl-ring-001-005.txt @@ -0,0 +1,271 @@ +Full Tilt Poker Game #6929537410: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:15:44 ET - 2008/06/22 +Seat 1: player16 ($94.90) +Seat 2: player25 ($147) +Seat 3: player18 ($62.80) +Seat 4: player19 ($136.55) +Seat 5: play-er26 ($56.05) +Seat 6: player21 ($252.95) +Seat 7: player22 ($200) +Seat 8: player23 ($162.50) +Seat 9: player24 ($270.70) +player24 posts the small blind of $0.50 +player16 posts the big blind of $1 +player22 posts $1 +The button is in seat #8 +*** HOLE CARDS *** +player25 folds +player25 stands up +player18 folds +player19 folds +play-er26 folds +player21 folds +player22 checks +player23 calls $1 +player17 adds $100 +player24 calls $0.50 +player16 checks +*** FLOP *** [4s Kc 8s] +player24 has 15 seconds left to act +player24 checks +player16 checks +player22 checks +player23 checks +*** TURN *** [4s Kc 8s] [6s] +player24 checks +player16 checks +player22 checks +player23 bets $4 +player24 calls $4 +player16 folds +player22 folds +*** RIVER *** [4s Kc 8s 6s] [Qc] +player24 checks +player23 checks +*** SHOW DOWN *** +player23 shows [Td 5s 3d Js] a flush, Jack high +player24 mucks +player23 wins the pot ($11.40) with a flush, Jack high +*** SUMMARY *** +Total pot $12 | Rake $0.60 +Board: [4s Kc 8s 6s Qc] +Seat 1: player16 (big blind) folded on the Turn +Seat 2: player25 didn't bet (folded) +Seat 3: player18 didn't bet (folded) +Seat 4: player19 didn't bet (folded) +Seat 5: play-er26 didn't bet (folded) +Seat 6: player21 didn't bet (folded) +Seat 7: player22 folded on the Turn +Seat 8: player23 (button) collected ($11.40) +Seat 9: player24 (small blind) mucked + + + +Full Tilt Poker Game #6929553738: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:17:06 ET - 2008/06/22 +Seat 1: player16 ($93.90) +Seat 2: player17 ($100) +Seat 3: player18 ($62.80) +Seat 4: player19 ($136.55) +Seat 5: play-er26 ($56.05) +Seat 6: player21 ($252.95) +Seat 7: player22 ($199) +Seat 8: player23 ($168.90) +Seat 9: player24 ($265.70) +player16 posts the small blind of $0.50 +player17 posts the big blind of $1 +The button is in seat #9 +*** HOLE CARDS *** +player18 folds +play-er26 stands up +player19 raises to $2 +play-er26 folds +player21 calls $2 +player22 has 15 seconds left to act +player22 folds +player23 folds +player24 folds +player16 calls $1.50 +player17 calls $1 +*** FLOP *** [Jc 4c Kc] +player16 checks +player17 checks +player19 checks +player21 checks +*** TURN *** [Jc 4c Kc] [7h] +player16 checks +player17 checks +player19 bets $3.50 +player21 folds +player16 folds +player17 calls $3.50 +*** RIVER *** [Jc 4c Kc 7h] [8s] +player17 checks +player19 has 15 seconds left to act +player19 bets $10 +player17 calls $10 +*** SHOW DOWN *** +player19 shows [4s Tc As Ac] a flush, Ace high +player17 mucks +player19 wins the pot ($33.25) with a flush, Ace high +*** SUMMARY *** +Total pot $35 | Rake $1.75 +Board: [Jc 4c Kc 7h 8s] +Seat 1: player16 (small blind) folded on the Turn +Seat 2: player17 (big blind) mucked +Seat 3: player18 didn't bet (folded) +Seat 4: player19 collected ($33.25) +Seat 5: play-er26 didn't bet (folded) +Seat 6: player21 folded on the Turn +Seat 7: player22 didn't bet (folded) +Seat 8: player23 didn't bet (folded) +Seat 9: player24 (button) didn't bet (folded) + + + +Full Tilt Poker Game #6929572212: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:18:40 ET - 2008/06/22 +Seat 1: player16 ($91.90) +Seat 2: player17 ($84.50) +Seat 3: player18 ($62.80) +Seat 4: player19 ($154.30) +Seat 6: player21 ($250.95) +Seat 7: player22 ($199) +Seat 8: player23 ($168.90) +Seat 9: player24 ($265.70) +player17 posts the small blind of $0.50 +player18 posts the big blind of $1 +The button is in seat #1 +*** HOLE CARDS *** +player19 folds +player21 folds +player20 adds $50 +player22 folds +player23 folds +player24 folds +player20 is sitting out +player16 raises to $2 +player17 folds +player18 folds +Uncalled bet of $1 returned to player16 +player16 mucks +player16 wins the pot ($2.50) +*** SUMMARY *** +Total pot $2.50 | Rake $0 +Seat 1: player16 (button) collected ($2.50), mucked +Seat 2: player17 (small blind) folded before the Flop +Seat 3: player18 (big blind) folded before the Flop +Seat 4: player19 didn't bet (folded) +Seat 6: player21 didn't bet (folded) +Seat 7: player22 didn't bet (folded) +Seat 8: player23 didn't bet (folded) +Seat 9: player24 didn't bet (folded) + + + +Full Tilt Poker Game #6929576743: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:19:03 ET - 2008/06/22 +Seat 1: player16 ($93.40) +Seat 2: player17 ($84) +Seat 3: player18 ($61.80) +Seat 4: player19 ($154.30) +Seat 5: player20 ($50), is sitting out +Seat 6: player21 ($250.95) +Seat 7: player22 ($199) +Seat 8: player23 ($168.90) +Seat 9: player24 ($265.70) +player18 posts the small blind of $0.50 +player19 posts the big blind of $1 +The button is in seat #2 +*** HOLE CARDS *** +player20 has returned +player21 calls $1 +player22 folds +player23 calls $1 +player24 calls $1 +player16 raises to $4 +player17 folds +player18 folds +player19 folds +player21 folds +player23 folds +player17 is sitting out +player24 has 15 seconds left to act +player24 calls $3 +*** FLOP *** [Tc 9s 7h] +player24 checks +player16 has 15 seconds left to act +player16 bets $8 +player24 folds +Uncalled bet of $8 returned to player16 +player16 mucks +player16 wins the pot ($10.95) +*** SUMMARY *** +Total pot $11.50 | Rake $0.55 +Board: [Tc 9s 7h] +Seat 1: player16 collected ($10.95), mucked +Seat 2: player17 (button) didn't bet (folded) +Seat 3: player18 (small blind) folded before the Flop +Seat 4: player19 (big blind) folded before the Flop +Seat 5: player20 is sitting out +Seat 6: player21 folded before the Flop +Seat 7: player22 didn't bet (folded) +Seat 8: player23 folded before the Flop +Seat 9: player24 folded on the Flop + + + +Full Tilt Poker Game #6929587483: Table Green (deep) - $0.50/$1 - Pot Limit Omaha Hi - 17:19:57 ET - 2008/06/22 +Seat 1: player16 ($100.35) +Seat 2: player17 ($84), is sitting out +Seat 3: player18 ($61.30) +Seat 4: player19 ($153.30) +Seat 5: player20 ($50) +Seat 6: player21 ($249.95) +Seat 7: player22 ($199) +Seat 8: player23 ($167.90) +Seat 9: player24 ($261.70) +player19 posts the small blind of $0.50 +player20 posts the big blind of $1 +The button is in seat #3 +*** HOLE CARDS *** +player21 folds +player22 folds +player21 stands up +player23 calls $1 +player24 calls $1 +player16 folds +player18 folds +player19 calls $0.50 +player20 checks +*** FLOP *** [Jd Td 2c] +roguern adds $100 +player19 bets $3 +player20 folds +player23 folds +player24 has 15 seconds left to act +player24 raises to $11 +player19 raises to $37 +player24 raises to $115 +player19 raises to $152.30, and is all in +player24 calls $37.30 +player19 shows [Jc Jh 7s 5h] +player24 shows [Kh Ad 6h Qd] +*** TURN *** [Jd Td 2c] [As] +*** RIVER *** [Jd Td 2c As] [8s] +player19 shows three of a kind, Jacks +player24 shows a straight, Ace high +player24 wins the pot ($305.60) with a straight, Ace high +player19 is sitting out +*** SUMMARY *** +Total pot $308.60 | Rake $3 +Board: [Jd Td 2c As 8s] +Seat 1: player16 didn't bet (folded) +Seat 2: player17 is sitting out +Seat 3: player18 (button) didn't bet (folded) +Seat 4: player19 (small blind) showed [Jc Jh 7s 5h] and lost with three of a kind, Jacks +Seat 5: player20 (big blind) folded on the Flop +Seat 6: player21 didn't bet (folded) +Seat 7: player22 didn't bet (folded) +Seat 8: player23 folded on the Flop +Seat 9: player24 showed [Kh Ad 6h Qd] and won ($305.60) with a straight, Ace high + + + + diff --git a/testdata/ftp-stud-hilo-ring-001.txt b/testdata/ftp-stud-hilo-ring-001.txt new file mode 100644 index 00000000..e23cb335 --- /dev/null +++ b/testdata/ftp-stud-hilo-ring-001.txt @@ -0,0 +1,61 @@ +Full Tilt Poker Game #6367428246: Table Mountain Mesa - $15/$30 Ante $3 - Limit Stud H/L - 23:47:38 ET - 2008/05/10 +Seat 1: Player_8 ($446), is sitting out +Seat 2: Play er9 ($303.50) +Seat 3: P layer10 ($613), is sitting out +Seat 4: Player_11 ($164) +Seat 5: Player1 2 ($543.50), is sitting out +Seat 6: Player13 ($912.50) +Seat 7: Player14 ($430), is sitting out +Seat 8: Player15 ($531.50) +Player15 antes $3 +Player_11 antes $3 +Player13 antes $3 +Play er9 antes $3 +*** 3RD STREET *** +Dealt to Play er9 [2s] +Dealt to Player_11 [3c] +Dealt to Player13 [8c] +Dealt to Player15 [Jc] +Play er9 is low with [2s] +Play er9 brings in for $5 +Player_11 folds +Player13 completes it to $15 +Player15 folds +Play er9 calls $10 +*** 4TH STREET *** +Dealt to Play er9 [2s] [6c] +Dealt to Player13 [8c] [5h] +Player13 bets $15 +Play er9 calls $15 +*** 5TH STREET *** +Dealt to Play er9 [2s 6c] [Ac] +Dealt to Player13 [8c 5h] [Ah] +Player13 bets $30 +Play er9 calls $30 +*** 6TH STREET *** +Dealt to Play er9 [2s 6c Ac] [2c] +Dealt to Player13 [8c 5h Ah] [Jd] +Play er9 bets $30 +Player13 calls $30 +*** 7TH STREET *** +Play er9 bets $30 +Player13 calls $30 +*** SHOW DOWN *** +Play er9 shows [5c 4h 2s 6c Ac 2c 2h] three of a kind, Twos, for high and 6,5,4,2,A, for low +Player13 mucks +Play er9 wins the high pot ($125) with three of a kind, Twos +Play er9 wins the low pot ($125) with 6,5,4,2,A +*** SUMMARY *** +Total pot $252 | Rake $2 +Seat 1: Player_8 is sitting out +Seat 2: Play er9 collected ($250) +Seat 3: P layer10 is sitting out +Seat 4: Player_11 folded on 3rd St. +Seat 5: Player1 2 is sitting out +Seat 6: Player13 mucked +Seat 7: Player14 is sitting out +Seat 8: Player15 folded on 3rd St. + + + + diff --git a/testdata/ftp.6367428246.expected.txt b/testdata/ftp.6367428246.expected.txt new file mode 100644 index 00000000..01ca224a --- /dev/null +++ b/testdata/ftp.6367428246.expected.txt @@ -0,0 +1,38 @@ +Connected to MySQL on localhost. Print Hand Utility +options.site: Full Tilt Poker site_id: 1 + +From Table sites +==================== +site_name: Full Tilt Poker + +From Table gametypes +==================== +type: category: studhilo limit_type: +sb: bb: sbet: bbet: + +From Table hands +================ +site_hand_no: 6367428246 hand_start: 2008-05-11 04:47:38 seat_count: 4 + +From Table hands_players +======================== +player_name:Play er9 player_startcash:30350 ante:300 cards:5c 4h 2s 6c Ac 2c 2h winnings:25000 rake:200 +player_name:Player_11 player_startcash:16400 ante:300 cards:?? ?? 3c ?? ?? ?? ?? winnings:0 rake:0 +player_name:Player13 player_startcash:91250 ante:300 cards:?? ?? 8c 5h Ah Jd ?? winnings:0 rake:0 +player_name:Player15 player_startcash:53150 ante:300 cards:?? ?? Jc ?? ?? ?? ?? winnings:0 rake:0 + +From Table hands_actions +======================== +player_name:Play er9 actionCount:0 street:3 streetActionNo:0 action:blind amount:500 +player_name:Play er9 actionCount:1 street:3 streetActionNo:1 action:call amount:1000 +player_name:Play er9 actionCount:2 street:4 streetActionNo:0 action:call amount:1500 +player_name:Play er9 actionCount:3 street:5 streetActionNo:0 action:call amount:3000 +player_name:Play er9 actionCount:4 street:6 streetActionNo:0 action:bet amount:3000 +player_name:Play er9 actionCount:5 street:7 streetActionNo:0 action:bet amount:3000 +player_name:Player_11 actionCount:0 street:3 streetActionNo:0 action:fold amount:0 +player_name:Player13 actionCount:0 street:3 streetActionNo:0 action:bet amount:1500 +player_name:Player13 actionCount:1 street:4 streetActionNo:0 action:bet amount:1500 +player_name:Player13 actionCount:2 street:5 streetActionNo:0 action:bet amount:3000 +player_name:Player13 actionCount:3 street:6 streetActionNo:0 action:call amount:3000 +player_name:Player13 actionCount:4 street:7 streetActionNo:0 action:call amount:3000 +player_name:Player15 actionCount:0 street:3 streetActionNo:0 action:fold amount:0 diff --git a/testdata/ftp.6929537410.expected.txt b/testdata/ftp.6929537410.expected.txt new file mode 100644 index 00000000..448ab95e --- /dev/null +++ b/testdata/ftp.6929537410.expected.txt @@ -0,0 +1,46 @@ +Connected to MySQL on localhost. Print Hand Utility +options.site: Full Tilt Poker site_id: 1 +From Table hands +================ +site_hand_no: 6929537410 hand_start: 2008-06-22 22:15:44 seat_count: 9 category: omahahi +Board cards: 4s Kc 8s 6s Qc + +From Table hands_players +======================== +player_name:player16 player_startcash:9490 position:BB cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player25 player_startcash:14700 position:6 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player18 player_startcash:6280 position:5 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player19 player_startcash:13655 position:4 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:play-er26 player_startcash:5605 position:3 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player21 player_startcash:25295 position:2 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player22 player_startcash:20000 position:1 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player23 player_startcash:16250 position:Btn cards:Td 5s 3d Js winnings:1140 rake:60 +player_name:player24 player_startcash:27070 position:SB cards:?? ?? ?? ?? winnings:0 rake:0 + +From Table hands_actions +======================== +player_name:player16 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:100 +player_name:player16 actionCount:1 street:Preflop streetActionNo:1 action:check amount:0 +player_name:player16 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:player16 actionCount:3 street:Turn streetActionNo:0 action:check amount:0 +player_name:player16 actionCount:4 street:Turn streetActionNo:1 action:fold amount:0 +player_name:player25 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player18 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player19 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:play-er26 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player21 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player22 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:100 +player_name:player22 actionCount:1 street:Preflop streetActionNo:1 action:check amount:0 +player_name:player22 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:player22 actionCount:3 street:Turn streetActionNo:0 action:check amount:0 +player_name:player22 actionCount:4 street:Turn streetActionNo:1 action:fold amount:0 +player_name:player23 actionCount:0 street:Preflop streetActionNo:0 action:call amount:100 +player_name:player23 actionCount:1 street:Flop streetActionNo:0 action:check amount:0 +player_name:player23 actionCount:2 street:Turn streetActionNo:0 action:bet amount:400 +player_name:player23 actionCount:3 street:River streetActionNo:0 action:check amount:0 +player_name:player24 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:50 +player_name:player24 actionCount:1 street:Preflop streetActionNo:1 action:call amount:50 +player_name:player24 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:player24 actionCount:3 street:Turn streetActionNo:0 action:check amount:0 +player_name:player24 actionCount:4 street:Turn streetActionNo:1 action:call amount:400 +player_name:player24 actionCount:5 street:River streetActionNo:0 action:check amount:0 diff --git a/testdata/ftp.6929553738.expected.txt b/testdata/ftp.6929553738.expected.txt new file mode 100644 index 00000000..0999a08d --- /dev/null +++ b/testdata/ftp.6929553738.expected.txt @@ -0,0 +1,45 @@ +Connected to MySQL on localhost. Print Hand Utility +options.site: Full Tilt Poker site_id: 1 +From Table hands +================ +site_hand_no: 6929553738 hand_start: 2008-06-22 22:17:06 seat_count: 9 category: omahahi +Board cards: Jc 4c Kc 7h 8s + +From Table hands_players +======================== +player_name:player16 player_startcash:9390 position:SB cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player17 player_startcash:10000 position:BB cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player18 player_startcash:6280 position:6 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player19 player_startcash:13655 position:5 off Btn cards:4s Tc As Ac winnings:3325 rake:175 +player_name:play-er26 player_startcash:5605 position:4 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player21 player_startcash:25295 position:3 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player22 player_startcash:19900 position:2 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player23 player_startcash:16890 position:1 off Btn cards:?? ?? ?? ?? winnings:0 rake:0 +player_name:player24 player_startcash:26570 position:Btn cards:?? ?? ?? ?? winnings:0 rake:0 + +From Table hands_actions +======================== +player_name:player16 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:50 +player_name:player16 actionCount:1 street:Preflop streetActionNo:1 action:call amount:150 +player_name:player16 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:player16 actionCount:3 street:Turn streetActionNo:0 action:check amount:0 +player_name:player16 actionCount:4 street:Turn streetActionNo:1 action:fold amount:0 +player_name:player17 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:100 +player_name:player17 actionCount:1 street:Preflop streetActionNo:1 action:call amount:100 +player_name:player17 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:player17 actionCount:3 street:Turn streetActionNo:0 action:check amount:0 +player_name:player17 actionCount:4 street:Turn streetActionNo:1 action:call amount:350 +player_name:player17 actionCount:5 street:River streetActionNo:0 action:check amount:0 +player_name:player17 actionCount:6 street:River streetActionNo:1 action:call amount:1000 +player_name:player18 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player19 actionCount:0 street:Preflop streetActionNo:0 action:bet amount:200 +player_name:player19 actionCount:1 street:Flop streetActionNo:0 action:check amount:0 +player_name:player19 actionCount:2 street:Turn streetActionNo:0 action:bet amount:350 +player_name:player19 actionCount:3 street:River streetActionNo:0 action:bet amount:1000 +player_name:play-er26 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player21 actionCount:0 street:Preflop streetActionNo:0 action:call amount:200 +player_name:player21 actionCount:1 street:Flop streetActionNo:0 action:check amount:0 +player_name:player21 actionCount:2 street:Turn streetActionNo:0 action:fold amount:0 +player_name:player22 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player23 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:player24 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 diff --git a/testdata/mysql-dump.sql b/testdata/mysql-dump.sql new file mode 100644 index 00000000..7f92c777 --- /dev/null +++ b/testdata/mysql-dump.sql @@ -0,0 +1,376 @@ +-- MySQL dump 10.11 +-- +-- Host: localhost Database: fpdb +-- ------------------------------------------------------ +-- Server version 5.0.54-log + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `autorates` +-- + +DROP TABLE IF EXISTS `autorates`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `autorates` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `player_id` int(10) unsigned default NULL, + `gametype_id` smallint(5) unsigned default NULL, + `description` varchar(50) default NULL, + `short_desc` char(8) default NULL, + `rating_time` datetime default NULL, + `hand_count` int(11) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `player_id` (`player_id`), + KEY `gametype_id` (`gametype_id`), + CONSTRAINT `autorates_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`), + CONSTRAINT `autorates_ibfk_2` FOREIGN KEY (`gametype_id`) REFERENCES `gametypes` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `autorates` +-- + +LOCK TABLES `autorates` WRITE; +/*!40000 ALTER TABLE `autorates` DISABLE KEYS */; +/*!40000 ALTER TABLE `autorates` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `board_cards` +-- + +DROP TABLE IF EXISTS `board_cards`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `board_cards` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `hand_id` bigint(20) unsigned default NULL, + `card1_value` smallint(6) default NULL, + `card1_suit` char(1) default NULL, + `card2_value` smallint(6) default NULL, + `card2_suit` char(1) default NULL, + `card3_value` smallint(6) default NULL, + `card3_suit` char(1) default NULL, + `card4_value` smallint(6) default NULL, + `card4_suit` char(1) default NULL, + `card5_value` smallint(6) default NULL, + `card5_suit` char(1) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `hand_id` (`hand_id`), + CONSTRAINT `board_cards_ibfk_1` FOREIGN KEY (`hand_id`) REFERENCES `hands` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `board_cards` +-- + +LOCK TABLES `board_cards` WRITE; +/*!40000 ALTER TABLE `board_cards` DISABLE KEYS */; +INSERT INTO `board_cards` VALUES (1,1,12,'d',10,'h',11,'s',2,'s',7,'s'),(2,2,10,'h',11,'d',3,'c',7,'c',4,'s'),(3,3,4,'h',9,'s',14,'d',12,'c',13,'s'),(4,5,4,'s',13,'c',8,'s',6,'s',12,'c'),(5,6,11,'c',4,'c',13,'c',7,'h',8,'s'),(6,7,0,'x',0,'x',0,'x',0,'x',0,'x'),(7,8,10,'c',9,'s',7,'h',0,'x',0,'x'),(8,9,11,'d',10,'d',2,'c',14,'s',8,'s'); +/*!40000 ALTER TABLE `board_cards` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `gametypes` +-- + +DROP TABLE IF EXISTS `gametypes`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `gametypes` ( + `id` smallint(5) unsigned NOT NULL auto_increment, + `site_id` smallint(5) unsigned default NULL, + `type` char(4) default NULL, + `category` varchar(9) default NULL, + `limit_type` char(2) default NULL, + `max_seats` smallint(6) default NULL, + `small_blind` int(11) default NULL, + `big_blind` int(11) default NULL, + `small_bet` int(11) default NULL, + `big_bet` int(11) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `site_id` (`site_id`), + CONSTRAINT `gametypes_ibfk_1` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `gametypes` +-- + +LOCK TABLES `gametypes` WRITE; +/*!40000 ALTER TABLE `gametypes` DISABLE KEYS */; +INSERT INTO `gametypes` VALUES (1,1,'ring','holdem','fl',10,10,25,25,50),(2,1,'ring','holdem','fl',10,25,50,50,100),(3,1,'ring','holdem','fl',10,50,100,100,200),(4,1,'ring','holdem','fl',10,100,200,200,400),(5,1,'ring','holdem','fl',10,150,300,300,600),(6,1,'ring','holdem','fl',10,250,500,500,1000),(7,1,'ring','holdem','fl',10,400,800,800,1600),(8,1,'ring','holdem','fl',10,500,1000,1000,2000),(9,1,'ring','holdem','fl',10,1000,1500,1500,3000),(10,1,'ring','holdem','fl',10,1500,3000,3000,6000),(11,1,'ring','omahahi','pl',10,5,10,0,0),(12,1,'ring','omahahi','pl',10,10,25,0,0),(13,1,'ring','omahahi','pl',10,25,50,0,0),(14,1,'ring','omahahi','pl',10,50,100,0,0),(15,1,'ring','omahahi','pl',10,100,200,0,0),(16,1,'ring','omahahi','pl',10,200,400,0,0),(17,1,'ring','omahahi','pl',10,1000,2000,0,0),(18,1,'ring','omahahi','cp',10,2500,5000,0,0),(19,1,'ring','omahahilo','fl',10,10,25,25,50),(20,1,'ring','omahahilo','fl',10,50,100,100,200),(21,1,'ring','omahahilo','fl',10,150,300,300,600),(22,1,'ring','omahahilo','fl',10,250,500,500,1000),(23,1,'ring','omahahilo','pl',10,10,25,0,0),(24,1,'ring','omahahilo','pl',10,50,100,0,0),(25,1,'ring','omahahilo','pl',10,100,200,0,0),(26,1,'ring','omahahilo','nl',10,5,10,0,0),(27,1,'ring','omahahilo','nl',10,50,100,0,0),(28,1,'ring','omahahilo','nl',10,100,200,0,0),(29,1,'ring','razz','fl',8,0,0,25,50),(30,1,'ring','razz','fl',8,0,0,50,100),(31,1,'ring','razz','fl',8,0,0,100,200),(32,1,'ring','razz','fl',8,0,0,200,400),(33,1,'ring','razz','fl',8,0,0,300,600),(34,1,'ring','razz','fl',8,0,0,500,1000),(35,1,'ring','razz','fl',8,0,0,800,1600),(36,1,'ring','razz','fl',8,0,0,1500,3000),(37,1,'ring','razz','fl',8,0,0,2000,4000),(38,1,'ring','razz','fl',8,0,0,3000,6000),(39,1,'ring','razz','fl',8,0,0,10000,20000),(40,1,'ring','studhi','fl',8,0,0,25,50),(41,1,'ring','studhi','fl',8,0,0,200,400),(42,1,'ring','studhi','fl',8,0,0,300,600),(43,1,'ring','studhi','fl',8,0,0,500,1000),(44,1,'ring','studhilo','fl',8,0,0,50,100),(45,1,'ring','studhilo','fl',8,0,0,100,200),(46,1,'ring','studhilo','fl',8,0,0,1500,3000),(47,2,'ring','holdem','fl',10,1,2,2,4),(48,2,'ring','holdem','fl',10,2,5,5,10),(49,2,'ring','holdem','fl',10,5,10,10,20),(50,2,'ring','holdem','fl',10,10,25,25,50),(51,2,'ring','holdem','nl',10,1,2,0,0),(52,2,'ring','holdem','nl',10,2,5,0,0),(53,2,'ring','holdem','nl',10,5,10,0,0),(54,2,'ring','omahahi','pl',10,1,2,0,0),(55,2,'ring','omahahi','pl',10,10,25,0,0),(56,2,'ring','omahahilo','fl',10,1,2,2,4),(57,2,'ring','omahahilo','pl',10,1,2,0,0),(58,2,'ring','razz','fl',8,0,0,50,100),(59,2,'tour','razz','fl',8,0,0,10,20),(60,2,'ring','studhi','fl',8,0,0,4,8),(61,2,'ring','studhi','fl',8,0,0,10,20),(62,2,'ring','studhilo','fl',8,0,0,4,8),(63,2,'ring','studhilo','fl',8,0,0,10,20),(64,2,'ring','studhilo','fl',8,0,0,25,50),(65,2,'ring','studhilo','fl',8,0,0,50,100); +/*!40000 ALTER TABLE `gametypes` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `hands` +-- + +DROP TABLE IF EXISTS `hands`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `hands` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `site_hand_no` bigint(20) default NULL, + `gametype_id` smallint(5) unsigned default NULL, + `hand_start` datetime default NULL, + `seats` smallint(6) default NULL, + `comment` text, + `comment_ts` datetime default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `gametype_id` (`gametype_id`), + CONSTRAINT `hands_ibfk_1` FOREIGN KEY (`gametype_id`) REFERENCES `gametypes` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `hands` +-- + +LOCK TABLES `hands` WRITE; +/*!40000 ALTER TABLE `hands` DISABLE KEYS */; +INSERT INTO `hands` VALUES (1,14519394979,47,'2008-01-13 05:22:15',7,NULL,NULL),(2,14519420999,47,'2008-01-13 05:23:43',7,NULL,NULL),(3,14519433154,47,'2008-01-13 05:24:25',7,NULL,NULL),(4,6367428246,46,'2008-05-11 04:47:38',4,NULL,NULL),(5,6929537410,14,'2008-06-22 22:15:44',9,NULL,NULL),(6,6929553738,14,'2008-06-22 22:17:06',9,NULL,NULL),(7,6929572212,14,'2008-06-22 22:18:40',8,NULL,NULL),(8,6929576743,14,'2008-06-22 22:19:03',8,NULL,NULL),(9,6929587483,14,'2008-06-22 22:19:57',8,NULL,NULL); +/*!40000 ALTER TABLE `hands` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `hands_actions` +-- + +DROP TABLE IF EXISTS `hands_actions`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `hands_actions` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `hand_player_id` bigint(20) unsigned default NULL, + `street` smallint(6) default NULL, + `action_no` smallint(6) default NULL, + `action` char(5) default NULL, + `amount` int(11) default NULL, + `comment` text, + `comment_ts` datetime default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `hand_player_id` (`hand_player_id`), + CONSTRAINT `hands_actions_ibfk_1` FOREIGN KEY (`hand_player_id`) REFERENCES `hands_players` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=181 DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `hands_actions` +-- + +LOCK TABLES `hands_actions` WRITE; +/*!40000 ALTER TABLE `hands_actions` DISABLE KEYS */; +INSERT INTO `hands_actions` VALUES (1,1,0,0,'call',4,NULL,NULL),(2,2,0,0,'blind',1,NULL,NULL),(3,2,0,1,'call',3,NULL,NULL),(4,3,0,0,'blind',2,NULL,NULL),(5,3,0,1,'fold',0,NULL,NULL),(6,4,0,0,'bet',4,NULL,NULL),(7,5,0,0,'fold',0,NULL,NULL),(8,6,0,0,'fold',0,NULL,NULL),(9,7,0,0,'fold',0,NULL,NULL),(10,1,1,0,'call',2,NULL,NULL),(11,2,1,0,'check',0,NULL,NULL),(12,2,1,1,'call',2,NULL,NULL),(13,4,1,0,'bet',2,NULL,NULL),(14,1,2,0,'call',4,NULL,NULL),(15,2,2,0,'check',0,NULL,NULL),(16,2,2,1,'call',4,NULL,NULL),(17,4,2,0,'bet',4,NULL,NULL),(18,1,3,0,'fold',0,NULL,NULL),(19,2,3,0,'check',0,NULL,NULL),(20,2,3,1,'fold',0,NULL,NULL),(21,4,3,0,'bet',4,NULL,NULL),(22,8,0,0,'fold',0,NULL,NULL),(23,9,0,0,'call',2,NULL,NULL),(24,10,0,0,'fold',0,NULL,NULL),(25,11,0,0,'blind',1,NULL,NULL),(26,11,0,1,'call',1,NULL,NULL),(27,12,0,0,'blind',2,NULL,NULL),(28,12,0,1,'check',0,NULL,NULL),(29,13,0,0,'fold',0,NULL,NULL),(30,14,0,0,'fold',0,NULL,NULL),(31,9,1,0,'call',2,NULL,NULL),(32,11,1,0,'check',0,NULL,NULL),(33,11,1,1,'call',2,NULL,NULL),(34,12,1,0,'bet',2,NULL,NULL),(35,9,2,0,'bet',8,NULL,NULL),(36,11,2,0,'check',0,NULL,NULL),(37,11,2,1,'fold',0,NULL,NULL),(38,12,2,0,'bet',4,NULL,NULL),(39,12,2,1,'call',4,NULL,NULL),(40,9,3,0,'bet',4,NULL,NULL),(41,12,3,0,'check',0,NULL,NULL),(42,12,3,1,'call',4,NULL,NULL),(43,15,0,0,'fold',0,NULL,NULL),(44,16,0,0,'fold',0,NULL,NULL),(45,17,0,0,'call',2,NULL,NULL),(46,17,0,1,'call',2,NULL,NULL),(47,18,0,0,'call',2,NULL,NULL),(48,18,0,1,'call',2,NULL,NULL),(49,19,0,0,'blind',1,NULL,NULL),(50,19,0,1,'bet',3,NULL,NULL),(51,20,0,0,'blind',2,NULL,NULL),(52,20,0,1,'call',2,NULL,NULL),(53,21,0,0,'fold',0,NULL,NULL),(54,17,1,0,'bet',2,NULL,NULL),(55,17,1,1,'bet',4,NULL,NULL),(56,17,1,2,'call',2,NULL,NULL),(57,18,1,0,'bet',4,NULL,NULL),(58,18,1,1,'bet',4,NULL,NULL),(59,19,1,0,'check',0,NULL,NULL),(60,19,1,1,'fold',0,NULL,NULL),(61,20,1,0,'check',0,NULL,NULL),(62,20,1,1,'fold',0,NULL,NULL),(63,17,2,0,'bet',4,NULL,NULL),(64,17,2,1,'bet',8,NULL,NULL),(65,17,2,2,'call',4,NULL,NULL),(66,18,2,0,'bet',8,NULL,NULL),(67,18,2,1,'bet',8,NULL,NULL),(68,17,3,0,'bet',4,NULL,NULL),(69,17,3,1,'bet',8,NULL,NULL),(70,17,3,2,'call',4,NULL,NULL),(71,18,3,0,'bet',8,NULL,NULL),(72,18,3,1,'bet',8,NULL,NULL),(73,22,3,0,'blind',500,NULL,NULL),(74,22,3,1,'call',1000,NULL,NULL),(75,23,3,0,'fold',0,NULL,NULL),(76,24,3,0,'bet',1500,NULL,NULL),(77,25,3,0,'fold',0,NULL,NULL),(78,22,4,0,'call',1500,NULL,NULL),(79,24,4,0,'bet',1500,NULL,NULL),(80,22,5,0,'call',3000,NULL,NULL),(81,24,5,0,'bet',3000,NULL,NULL),(82,22,6,0,'bet',3000,NULL,NULL),(83,24,6,0,'call',3000,NULL,NULL),(84,22,7,0,'bet',3000,NULL,NULL),(85,24,7,0,'call',3000,NULL,NULL),(86,26,0,0,'blind',100,NULL,NULL),(87,26,0,1,'check',0,NULL,NULL),(88,27,0,0,'fold',0,NULL,NULL),(89,28,0,0,'fold',0,NULL,NULL),(90,29,0,0,'fold',0,NULL,NULL),(91,30,0,0,'fold',0,NULL,NULL),(92,31,0,0,'fold',0,NULL,NULL),(93,32,0,0,'blind',100,NULL,NULL),(94,32,0,1,'check',0,NULL,NULL),(95,33,0,0,'call',100,NULL,NULL),(96,34,0,0,'blind',50,NULL,NULL),(97,34,0,1,'call',50,NULL,NULL),(98,26,1,0,'check',0,NULL,NULL),(99,32,1,0,'check',0,NULL,NULL),(100,33,1,0,'check',0,NULL,NULL),(101,34,1,0,'check',0,NULL,NULL),(102,26,2,0,'check',0,NULL,NULL),(103,26,2,1,'fold',0,NULL,NULL),(104,32,2,0,'check',0,NULL,NULL),(105,32,2,1,'fold',0,NULL,NULL),(106,33,2,0,'bet',400,NULL,NULL),(107,34,2,0,'check',0,NULL,NULL),(108,34,2,1,'call',400,NULL,NULL),(109,33,3,0,'check',0,NULL,NULL),(110,34,3,0,'check',0,NULL,NULL),(111,35,0,0,'blind',50,NULL,NULL),(112,35,0,1,'call',150,NULL,NULL),(113,36,0,0,'blind',100,NULL,NULL),(114,36,0,1,'call',100,NULL,NULL),(115,37,0,0,'fold',0,NULL,NULL),(116,38,0,0,'bet',200,NULL,NULL),(117,39,0,0,'fold',0,NULL,NULL),(118,40,0,0,'call',200,NULL,NULL),(119,41,0,0,'fold',0,NULL,NULL),(120,42,0,0,'fold',0,NULL,NULL),(121,43,0,0,'fold',0,NULL,NULL),(122,35,1,0,'check',0,NULL,NULL),(123,36,1,0,'check',0,NULL,NULL),(124,38,1,0,'check',0,NULL,NULL),(125,40,1,0,'check',0,NULL,NULL),(126,35,2,0,'check',0,NULL,NULL),(127,35,2,1,'fold',0,NULL,NULL),(128,36,2,0,'check',0,NULL,NULL),(129,36,2,1,'call',350,NULL,NULL),(130,38,2,0,'bet',350,NULL,NULL),(131,40,2,0,'fold',0,NULL,NULL),(132,36,3,0,'check',0,NULL,NULL),(133,36,3,1,'call',1000,NULL,NULL),(134,38,3,0,'bet',1000,NULL,NULL),(135,44,0,0,'bet',200,NULL,NULL),(136,44,0,1,'unbet',100,NULL,NULL),(137,45,0,0,'blind',50,NULL,NULL),(138,45,0,1,'fold',0,NULL,NULL),(139,46,0,0,'blind',100,NULL,NULL),(140,46,0,1,'fold',0,NULL,NULL),(141,47,0,0,'fold',0,NULL,NULL),(142,48,0,0,'fold',0,NULL,NULL),(143,49,0,0,'fold',0,NULL,NULL),(144,50,0,0,'fold',0,NULL,NULL),(145,51,0,0,'fold',0,NULL,NULL),(146,52,0,0,'bet',400,NULL,NULL),(147,53,0,0,'fold',0,NULL,NULL),(148,54,0,0,'blind',50,NULL,NULL),(149,54,0,1,'fold',0,NULL,NULL),(150,55,0,0,'blind',100,NULL,NULL),(151,55,0,1,'fold',0,NULL,NULL),(152,56,0,0,'call',100,NULL,NULL),(153,56,0,1,'fold',0,NULL,NULL),(154,57,0,0,'fold',0,NULL,NULL),(155,58,0,0,'call',100,NULL,NULL),(156,58,0,1,'fold',0,NULL,NULL),(157,59,0,0,'call',100,NULL,NULL),(158,59,0,1,'call',300,NULL,NULL),(159,52,1,0,'bet',800,NULL,NULL),(160,52,1,1,'unbet',800,NULL,NULL),(161,59,1,0,'check',0,NULL,NULL),(162,59,1,1,'fold',0,NULL,NULL),(163,60,0,0,'fold',0,NULL,NULL),(164,61,0,0,'fold',0,NULL,NULL),(165,62,0,0,'blind',50,NULL,NULL),(166,62,0,1,'call',50,NULL,NULL),(167,63,0,0,'blind',100,NULL,NULL),(168,63,0,1,'check',0,NULL,NULL),(169,64,0,0,'fold',0,NULL,NULL),(170,65,0,0,'fold',0,NULL,NULL),(171,66,0,0,'call',100,NULL,NULL),(172,67,0,0,'call',100,NULL,NULL),(173,62,1,0,'bet',300,NULL,NULL),(174,62,1,1,'bet',3400,NULL,NULL),(175,62,1,2,'bet',15230,NULL,NULL),(176,63,1,0,'fold',0,NULL,NULL),(177,66,1,0,'fold',0,NULL,NULL),(178,67,1,0,'bet',1100,NULL,NULL),(179,67,1,1,'bet',10400,NULL,NULL),(180,67,1,2,'call',3730,NULL,NULL); +/*!40000 ALTER TABLE `hands_actions` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `hands_players` +-- + +DROP TABLE IF EXISTS `hands_players`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `hands_players` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `hand_id` bigint(20) unsigned default NULL, + `player_id` int(10) unsigned default NULL, + `player_startcash` int(11) default NULL, + `position` char(1) default NULL, + `ante` int(11) default NULL, + `card1_value` smallint(6) default NULL, + `card1_suit` char(1) default NULL, + `card2_value` smallint(6) default NULL, + `card2_suit` char(1) default NULL, + `card3_value` smallint(6) default NULL, + `card3_suit` char(1) default NULL, + `card4_value` smallint(6) default NULL, + `card4_suit` char(1) default NULL, + `card5_value` smallint(6) default NULL, + `card5_suit` char(1) default NULL, + `card6_value` smallint(6) default NULL, + `card6_suit` char(1) default NULL, + `card7_value` smallint(6) default NULL, + `card7_suit` char(1) default NULL, + `winnings` int(11) default NULL, + `rake` int(11) default NULL, + `comment` text, + `comment_ts` datetime default NULL, + `tourneys_players_id` bigint(20) unsigned default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `hand_id` (`hand_id`), + KEY `player_id` (`player_id`), + KEY `tourneys_players_id` (`tourneys_players_id`), + CONSTRAINT `hands_players_ibfk_1` FOREIGN KEY (`hand_id`) REFERENCES `hands` (`id`), + CONSTRAINT `hands_players_ibfk_2` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`), + CONSTRAINT `hands_players_ibfk_3` FOREIGN KEY (`tourneys_players_id`) REFERENCES `tourneys_players` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `hands_players` +-- + +LOCK TABLES `hands_players` WRITE; +/*!40000 ALTER TABLE `hands_players` DISABLE KEYS */; +INSERT INTO `hands_players` VALUES (1,1,1,75,'0',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(2,1,2,59,'S',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(3,1,3,147,'B',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(4,1,4,198,'4',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,31,1,NULL,NULL,NULL),(5,1,5,122,'3',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(6,1,6,48,'2',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(7,1,7,139,'1',NULL,10,'s',11,'h',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(8,2,1,65,'2',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(9,2,2,49,'1',NULL,8,'s',9,'s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,35,1,NULL,NULL,NULL),(10,2,3,179,'0',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(11,2,4,205,'S',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(12,2,5,118,'B',NULL,12,'h',11,'s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(13,2,6,34,'4',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(14,2,7,135,'3',NULL,8,'d',5,'d',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(15,3,1,65,'3',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(16,3,2,68,'2',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(17,3,3,179,'1',NULL,14,'h',9,'d',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,92,4,NULL,NULL,NULL),(18,3,4,201,'0',NULL,14,'c',10,'d',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(19,3,5,102,'S',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(20,3,6,34,'B',NULL,0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(21,3,7,135,'4',NULL,7,'c',11,'h',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(22,4,8,30350,NULL,300,5,'c',4,'h',2,'s',6,'c',14,'c',2,'c',2,'h',25000,200,NULL,NULL,NULL),(23,4,9,16400,NULL,300,0,'x',0,'x',3,'c',0,'x',0,'x',0,'x',0,'x',0,0,NULL,NULL,NULL),(24,4,10,91250,NULL,300,0,'x',0,'x',8,'c',5,'h',14,'h',11,'d',0,'x',0,0,NULL,NULL,NULL),(25,4,11,53150,NULL,300,0,'x',0,'x',11,'c',0,'x',0,'x',0,'x',0,'x',0,0,NULL,NULL,NULL),(26,5,12,9490,'B',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(27,5,13,14700,'6',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(28,5,14,6280,'5',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(29,5,15,13655,'4',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(30,5,16,5605,'3',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(31,5,17,25295,'2',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(32,5,18,20000,'1',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(33,5,19,16250,'0',NULL,10,'d',5,'s',3,'d',11,'s',NULL,NULL,NULL,NULL,NULL,NULL,1140,60,NULL,NULL,NULL),(34,5,20,27070,'S',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(35,6,12,9390,'S',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(36,6,21,10000,'B',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(37,6,14,6280,'6',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(38,6,15,13655,'5',NULL,4,'s',10,'c',14,'s',14,'c',NULL,NULL,NULL,NULL,NULL,NULL,3325,175,NULL,NULL,NULL),(39,6,16,5605,'4',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(40,6,17,25295,'3',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(41,6,18,19900,'2',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(42,6,19,16890,'1',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(43,6,20,26570,'0',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(44,7,12,9190,'0',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,250,0,NULL,NULL,NULL),(45,7,21,8450,'S',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(46,7,14,6280,'B',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(47,7,15,15430,'5',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(48,7,17,25095,'4',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(49,7,18,19900,'3',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(50,7,19,16890,'2',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(51,7,20,26570,'1',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(52,8,12,9340,'1',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,1095,55,NULL,NULL,NULL),(53,8,21,8400,'0',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(54,8,14,6180,'S',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(55,8,15,15430,'B',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(56,8,17,25095,'5',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(57,8,18,19900,'4',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(58,8,19,16890,'3',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(59,8,20,26570,'2',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(60,9,12,10035,'1',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(61,9,14,6130,'0',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(62,9,15,15330,'S',NULL,11,'c',11,'h',7,'s',5,'h',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(63,9,22,5000,'B',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(64,9,17,24995,'5',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(65,9,18,19900,'4',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(66,9,19,16790,'3',NULL,0,'x',0,'x',0,'x',0,'x',NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL),(67,9,20,26170,'2',NULL,13,'h',14,'d',6,'h',12,'d',NULL,NULL,NULL,NULL,NULL,NULL,30560,300,NULL,NULL,NULL); +/*!40000 ALTER TABLE `hands_players` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `players` +-- + +DROP TABLE IF EXISTS `players`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `players` ( + `id` int(10) unsigned NOT NULL auto_increment, + `name` varchar(32) default NULL, + `site_id` smallint(5) unsigned default NULL, + `comment` text, + `comment_ts` datetime default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `site_id` (`site_id`), + CONSTRAINT `players_ibfk_1` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `players` +-- + +LOCK TABLES `players` WRITE; +/*!40000 ALTER TABLE `players` DISABLE KEYS */; +INSERT INTO `players` VALUES (1,'Player_1',2,NULL,NULL),(2,'Player_2',2,NULL,NULL),(3,'Player_3',2,NULL,NULL),(4,'Player_4',2,NULL,NULL),(5,'Player_5',2,NULL,NULL),(6,'Player_6',2,NULL,NULL),(7,'Player_7',2,NULL,NULL),(8,'Play er9',1,NULL,NULL),(9,'Player_11',1,NULL,NULL),(10,'Player13',1,NULL,NULL),(11,'Player15',1,NULL,NULL),(12,'player16',1,NULL,NULL),(13,'player25',1,NULL,NULL),(14,'player18',1,NULL,NULL),(15,'player19',1,NULL,NULL),(16,'play-er26',1,NULL,NULL),(17,'player21',1,NULL,NULL),(18,'player22',1,NULL,NULL),(19,'player23',1,NULL,NULL),(20,'player24',1,NULL,NULL),(21,'player17',1,NULL,NULL),(22,'player20',1,NULL,NULL); +/*!40000 ALTER TABLE `players` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `sites` +-- + +DROP TABLE IF EXISTS `sites`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `sites` ( + `id` smallint(5) unsigned NOT NULL auto_increment, + `name` varchar(32) default NULL, + `currency` char(3) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `sites` +-- + +LOCK TABLES `sites` WRITE; +/*!40000 ALTER TABLE `sites` DISABLE KEYS */; +INSERT INTO `sites` VALUES (1,'Full Tilt Poker','USD'),(2,'PokerStars','USD'); +/*!40000 ALTER TABLE `sites` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `tourneys` +-- + +DROP TABLE IF EXISTS `tourneys`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `tourneys` ( + `id` int(10) unsigned NOT NULL auto_increment, + `gametype_id` smallint(5) unsigned default NULL, + `site_tourney_no` bigint(20) default NULL, + `buyin` int(11) default NULL, + `fee` int(11) default NULL, + `knockout` int(11) default NULL, + `entries` int(11) default NULL, + `prizepool` int(11) default NULL, + `start_time` datetime default NULL, + `comment` text, + `comment_ts` datetime default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `gametype_id` (`gametype_id`), + CONSTRAINT `tourneys_ibfk_1` FOREIGN KEY (`gametype_id`) REFERENCES `gametypes` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `tourneys` +-- + +LOCK TABLES `tourneys` WRITE; +/*!40000 ALTER TABLE `tourneys` DISABLE KEYS */; +/*!40000 ALTER TABLE `tourneys` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `tourneys_players` +-- + +DROP TABLE IF EXISTS `tourneys_players`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `tourneys_players` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `tourney_id` int(10) unsigned default NULL, + `player_id` int(10) unsigned default NULL, + `payin_amount` int(11) default NULL, + `rank` int(11) default NULL, + `winnings` int(11) default NULL, + `comment` text, + `comment_ts` datetime default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `tourney_id` (`tourney_id`), + KEY `player_id` (`player_id`), + CONSTRAINT `tourneys_players_ibfk_1` FOREIGN KEY (`tourney_id`) REFERENCES `tourneys` (`id`), + CONSTRAINT `tourneys_players_ibfk_2` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SET character_set_client = @saved_cs_client; + +-- +-- Dumping data for table `tourneys_players` +-- + +LOCK TABLES `tourneys_players` WRITE; +/*!40000 ALTER TABLE `tourneys_players` DISABLE KEYS */; +/*!40000 ALTER TABLE `tourneys_players` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2008-07-12 18:42:27 diff --git a/testdata/ps-holdem-ring-001to003.txt b/testdata/ps-holdem-ring-001to003.txt new file mode 100644 index 00000000..9afd29ef --- /dev/null +++ b/testdata/ps-holdem-ring-001to003.txt @@ -0,0 +1,169 @@ +PokerStars Game #14519394979: Hold'em Limit ($0.02/$0.04) - 2008/01/13 - 00:22:15 (ET) +Table 'Merope' 10-max Seat #1 is the button +Seat 1: Player_1 ($0.75 in chips) +Seat 3: Player_2 ($0.59 in chips) +Seat 4: Player_3 ($1.47 in chips) +Seat 6: Player_4 ($1.98 in chips) +Seat 7: Player_5 ($1.22 in chips) +Seat 8: Player_6 ($0.48 in chips) +Seat 9: Player_7 ($1.39 in chips) +Player_2: posts small blind $0.01 +Player_3: posts big blind $0.02 +*** HOLE CARDS *** +Dealt to Player_7 [Ts Jh] +Player_4: raises $0.02 to $0.04 +Player_5: folds +Player_6: folds +Player_7: folds +Player_1: calls $0.04 +Player_2: calls $0.03 +Player_3: folds +*** FLOP *** [Qd Th Js] +Player_2: checks +Player_4: bets $0.02 +Player_1: calls $0.02 +Player_2: calls $0.02 +*** TURN *** [Qd Th Js] [2s] +Player_2: checks +Player_4: bets $0.04 +Player_1: calls $0.04 +Player_2: calls $0.04 +*** RIVER *** [Qd Th Js 2s] [7s] +Player_2: checks +Player_4: bets $0.04 +Player_1: folds +Player_2: folds +Player_4 collected $0.31 from pot +*** SUMMARY *** +Total pot $0.32 | Rake $0.01 +Board [Qd Th Js 2s 7s] +Seat 1: Player_1 (button) folded on the River +Seat 3: Player_2 (small blind) folded on the River +Seat 4: Player_3 (big blind) folded before Flop +Seat 6: Player_4 collected ($0.31) +Seat 7: Player_5 folded before Flop (didn't bet) +Seat 8: Player_6 folded before Flop (didn't bet) +Seat 9: Player_7 folded before Flop (didn't bet) + + + +PokerStars Game #14519420999: Hold'em Limit ($0.02/$0.04) - 2008/01/13 - 00:23:43 (ET) +Table 'Merope' 10-max Seat #4 is the button +Seat 1: Player_1 ($0.65 in chips) +Seat 3: Player_2 ($0.49 in chips) +Seat 4: Player_3 ($1.79 in chips) +Seat 6: Player_4 ($2.05 in chips) +Seat 7: Player_5 ($1.18 in chips) +Seat 8: Player_6 ($0.34 in chips) +Seat 9: Player_7 ($1.35 in chips) +wakked13 will be allowed to play after the button +Player_4: posts small blind $0.01 +Player_5: posts big blind $0.02 +*** HOLE CARDS *** +Dealt to Player_7 [8d 5d] +Player_6 said, "vn" +Player_6: folds +Player_7: folds +Player_1: folds +Player_2: calls $0.02 +Player_3: folds +Player_4: calls $0.01 +Player_5: checks +*** FLOP *** [Th Jd 3c] +Player_3 said, "ty" +Player_4: checks +Player_5: bets $0.02 +Player_2: calls $0.02 +Player_4: calls $0.02 +*** TURN *** [Th Jd 3c] [7c] +Player_4: checks +Player_5: bets $0.04 +Player_2: raises $0.04 to $0.08 +Player_4: folds +Player_5: calls $0.04 +*** RIVER *** [Th Jd 3c 7c] [4s] +Player_5: checks +Player_2: bets $0.04 +Player_5: calls $0.04 +*** SHOW DOWN *** +Player_2: shows [8s 9s] (a straight, Seven to Jack) +Player_5: mucks hand +Player_2 collected $0.35 from pot +*** SUMMARY *** +Total pot $0.36 | Rake $0.01 +Board [Th Jd 3c 7c 4s] +Seat 1: Player_1 folded before Flop (didn't bet) +Seat 3: Player_2 showed [8s 9s] and won ($0.35) with a straight, Seven to Jack +Seat 4: Player_3 (button) folded before Flop (didn't bet) +Seat 6: Player_4 (small blind) folded on the Turn +Seat 7: Player_5 (big blind) mucked [Qh Js] +Seat 8: Player_6 folded before Flop (didn't bet) +Seat 9: Player_7 folded before Flop (didn't bet) + + + +PokerStars Game #14519433154: Hold'em Limit ($0.02/$0.04) - 2008/01/13 - 00:24:25 (ET) +Table 'Merope' 10-max Seat #6 is the button +Seat 1: Player_1 ($0.65 in chips) +Seat 3: Player_2 ($0.68 in chips) +Seat 4: Player_3 ($1.79 in chips) +Seat 6: Player_4 ($2.01 in chips) +Seat 7: Player_5 ($1.02 in chips) +Seat 8: Player_6 ($0.34 in chips) +Seat 9: Player_7 ($1.35 in chips) +Player_5: posts small blind $0.01 +Player_6: posts big blind $0.02 +wakked13: sits out +*** HOLE CARDS *** +Dealt to Player_7 [7c Jh] +Player_7: folds +Player_1: folds +Player_2: folds +Player_3: calls $0.02 +Player_4: calls $0.02 +Player_5: raises $0.02 to $0.04 +Player_6: calls $0.02 +Player_3: calls $0.02 +Player_4: calls $0.02 +*** FLOP *** [4h 9s Ad] +Player_5: checks +Player_6: checks +Player_3: bets $0.02 +Player_4: raises $0.02 to $0.04 +Player_5: folds +Player_6: folds +Player_3: raises $0.02 to $0.06 +Player_4: raises $0.02 to $0.08 +Betting is capped +Player_3: calls $0.02 +*** TURN *** [4h 9s Ad] [Qc] +Player_3: bets $0.04 +Player_4: raises $0.04 to $0.08 +Player_3: raises $0.04 to $0.12 +Player_4: raises $0.04 to $0.16 +Betting is capped +Player_3: calls $0.04 +*** RIVER *** [4h 9s Ad Qc] [Ks] +Player_3: bets $0.04 +Player_4: raises $0.04 to $0.08 +Player_3: raises $0.04 to $0.12 +Player_4: raises $0.04 to $0.16 +Betting is capped +Player_3: calls $0.04 +*** SHOW DOWN *** +Player_4: shows [Ac Td] (a pair of Aces) +Player_3: shows [Ah 9d] (two pair, Aces and Nines) +Player_3 collected $0.92 from pot +*** SUMMARY *** +Total pot $0.96 | Rake $0.04 +Board [4h 9s Ad Qc Ks] +Seat 1: Player_1 folded before Flop (didn't bet) +Seat 3: Player_2 folded before Flop (didn't bet) +Seat 4: Player_3 showed [Ah 9d] and won ($0.92) with two pair, Aces and Nines +Seat 6: Player_4 (button) showed [Ac Td] and lost with a pair of Aces +Seat 7: Player_5 (small blind) folded on the Flop +Seat 8: Player_6 (big blind) folded on the Flop +Seat 9: Player_7 folded before Flop (didn't bet) + + + diff --git a/testdata/ps.14519394979.expected.txt b/testdata/ps.14519394979.expected.txt new file mode 100644 index 00000000..6bcaa016 --- /dev/null +++ b/testdata/ps.14519394979.expected.txt @@ -0,0 +1,40 @@ +Connected to MySQL on localhost. Print Hand Utility +options.site: PokerStars site_id: 2 +From Table hands +================ +site_hand_no: 14519394979 hand_start: 2008-01-13 05:22:15 seat_count: 7 category: holdem +Board cards: Qd Th Js 2s 7s + +From Table hands_players +======================== +player_name:Player_1 player_startcash:75 position:Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_2 player_startcash:59 position:SB cards:?? ?? winnings:0 rake:0 +player_name:Player_3 player_startcash:147 position:BB cards:?? ?? winnings:0 rake:0 +player_name:Player_4 player_startcash:198 position:4 off Btn cards:?? ?? winnings:31 rake:1 +player_name:Player_5 player_startcash:122 position:3 off Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_6 player_startcash:48 position:2 off Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_7 player_startcash:139 position:1 off Btn cards:Ts Jh winnings:0 rake:0 + +From Table hands_actions +======================== +player_name:Player_1 actionCount:0 street:Preflop streetActionNo:0 action:call amount:4 +player_name:Player_1 actionCount:1 street:Flop streetActionNo:0 action:call amount:2 +player_name:Player_1 actionCount:2 street:Turn streetActionNo:0 action:call amount:4 +player_name:Player_1 actionCount:3 street:River streetActionNo:0 action:fold amount:0 +player_name:Player_2 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:1 +player_name:Player_2 actionCount:1 street:Preflop streetActionNo:1 action:call amount:3 +player_name:Player_2 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:Player_2 actionCount:3 street:Flop streetActionNo:1 action:call amount:2 +player_name:Player_2 actionCount:4 street:Turn streetActionNo:0 action:check amount:0 +player_name:Player_2 actionCount:5 street:Turn streetActionNo:1 action:call amount:4 +player_name:Player_2 actionCount:6 street:River streetActionNo:0 action:check amount:0 +player_name:Player_2 actionCount:7 street:River streetActionNo:1 action:fold amount:0 +player_name:Player_3 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:2 +player_name:Player_3 actionCount:1 street:Preflop streetActionNo:1 action:fold amount:0 +player_name:Player_4 actionCount:0 street:Preflop streetActionNo:0 action:bet amount:4 +player_name:Player_4 actionCount:1 street:Flop streetActionNo:0 action:bet amount:2 +player_name:Player_4 actionCount:2 street:Turn streetActionNo:0 action:bet amount:4 +player_name:Player_4 actionCount:3 street:River streetActionNo:0 action:bet amount:4 +player_name:Player_5 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_6 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_7 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 diff --git a/testdata/ps.14519420999.expected.txt b/testdata/ps.14519420999.expected.txt new file mode 100644 index 00000000..3be7f2b8 --- /dev/null +++ b/testdata/ps.14519420999.expected.txt @@ -0,0 +1,40 @@ +Connected to MySQL on localhost. Print Hand Utility +options.site: PokerStars site_id: 2 +From Table hands +================ +site_hand_no: 14519420999 hand_start: 2008-01-13 05:23:43 seat_count: 7 category: holdem +Board cards: Th Jd 3c 7c 4s + +From Table hands_players +======================== +player_name:Player_1 player_startcash:65 position:2 off Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_2 player_startcash:49 position:1 off Btn cards:8s 9s winnings:35 rake:1 +player_name:Player_3 player_startcash:179 position:Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_4 player_startcash:205 position:SB cards:?? ?? winnings:0 rake:0 +player_name:Player_5 player_startcash:118 position:BB cards:Qh Js winnings:0 rake:0 +player_name:Player_6 player_startcash:34 position:4 off Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_7 player_startcash:135 position:3 off Btn cards:8d 5d winnings:0 rake:0 + +From Table hands_actions +======================== +player_name:Player_1 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_2 actionCount:0 street:Preflop streetActionNo:0 action:call amount:2 +player_name:Player_2 actionCount:1 street:Flop streetActionNo:0 action:call amount:2 +player_name:Player_2 actionCount:2 street:Turn streetActionNo:0 action:bet amount:8 +player_name:Player_2 actionCount:3 street:River streetActionNo:0 action:bet amount:4 +player_name:Player_3 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_4 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:1 +player_name:Player_4 actionCount:1 street:Preflop streetActionNo:1 action:call amount:1 +player_name:Player_4 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:Player_4 actionCount:3 street:Flop streetActionNo:1 action:call amount:2 +player_name:Player_4 actionCount:4 street:Turn streetActionNo:0 action:check amount:0 +player_name:Player_4 actionCount:5 street:Turn streetActionNo:1 action:fold amount:0 +player_name:Player_5 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:2 +player_name:Player_5 actionCount:1 street:Preflop streetActionNo:1 action:check amount:0 +player_name:Player_5 actionCount:2 street:Flop streetActionNo:0 action:bet amount:2 +player_name:Player_5 actionCount:3 street:Turn streetActionNo:0 action:bet amount:4 +player_name:Player_5 actionCount:4 street:Turn streetActionNo:1 action:call amount:4 +player_name:Player_5 actionCount:5 street:River streetActionNo:0 action:check amount:0 +player_name:Player_5 actionCount:6 street:River streetActionNo:1 action:call amount:4 +player_name:Player_6 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_7 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 diff --git a/testdata/ps.14519433154.expected.txt b/testdata/ps.14519433154.expected.txt new file mode 100644 index 00000000..764d3d8e --- /dev/null +++ b/testdata/ps.14519433154.expected.txt @@ -0,0 +1,49 @@ +Connected to MySQL on localhost. Print Hand Utility +options.site: PokerStars site_id: 2 +From Table hands +================ +site_hand_no: 14519433154 hand_start: 2008-01-13 05:24:25 seat_count: 7 category: holdem +Board cards: 4h 9s Ad Qc Ks + +From Table hands_players +======================== +player_name:Player_1 player_startcash:65 position:3 off Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_2 player_startcash:68 position:2 off Btn cards:?? ?? winnings:0 rake:0 +player_name:Player_3 player_startcash:179 position:1 off Btn cards:Ah 9d winnings:92 rake:4 +player_name:Player_4 player_startcash:201 position:Btn cards:Ac Td winnings:0 rake:0 +player_name:Player_5 player_startcash:102 position:SB cards:?? ?? winnings:0 rake:0 +player_name:Player_6 player_startcash:34 position:BB cards:?? ?? winnings:0 rake:0 +player_name:Player_7 player_startcash:135 position:4 off Btn cards:7c Jh winnings:0 rake:0 + +From Table hands_actions +======================== +player_name:Player_1 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_2 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_3 actionCount:0 street:Preflop streetActionNo:0 action:call amount:2 +player_name:Player_3 actionCount:1 street:Preflop streetActionNo:1 action:call amount:2 +player_name:Player_3 actionCount:2 street:Flop streetActionNo:0 action:bet amount:2 +player_name:Player_3 actionCount:3 street:Flop streetActionNo:1 action:bet amount:4 +player_name:Player_3 actionCount:4 street:Flop streetActionNo:2 action:call amount:2 +player_name:Player_3 actionCount:5 street:Turn streetActionNo:0 action:bet amount:4 +player_name:Player_3 actionCount:6 street:Turn streetActionNo:1 action:bet amount:8 +player_name:Player_3 actionCount:7 street:Turn streetActionNo:2 action:call amount:4 +player_name:Player_3 actionCount:8 street:River streetActionNo:0 action:bet amount:4 +player_name:Player_3 actionCount:9 street:River streetActionNo:1 action:bet amount:8 +player_name:Player_3 actionCount:10 street:River streetActionNo:2 action:call amount:4 +player_name:Player_4 actionCount:0 street:Preflop streetActionNo:0 action:call amount:2 +player_name:Player_4 actionCount:1 street:Preflop streetActionNo:1 action:call amount:2 +player_name:Player_4 actionCount:2 street:Flop streetActionNo:0 action:bet amount:4 +player_name:Player_4 actionCount:3 street:Flop streetActionNo:1 action:bet amount:4 +player_name:Player_4 actionCount:4 street:Turn streetActionNo:0 action:bet amount:8 +player_name:Player_4 actionCount:5 street:Turn streetActionNo:1 action:bet amount:8 +player_name:Player_4 actionCount:6 street:River streetActionNo:0 action:bet amount:8 +player_name:Player_4 actionCount:7 street:River streetActionNo:1 action:bet amount:8 +player_name:Player_5 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:1 +player_name:Player_5 actionCount:1 street:Preflop streetActionNo:1 action:bet amount:3 +player_name:Player_5 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:Player_5 actionCount:3 street:Flop streetActionNo:1 action:fold amount:0 +player_name:Player_6 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:2 +player_name:Player_6 actionCount:1 street:Preflop streetActionNo:1 action:call amount:2 +player_name:Player_6 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 +player_name:Player_6 actionCount:3 street:Flop streetActionNo:1 action:fold amount:0 +player_name:Player_7 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 diff --git a/utils/dump_db_basedata.py b/utils/dump_db_basedata.py new file mode 100755 index 00000000..f796c8ea --- /dev/null +++ b/utils/dump_db_basedata.py @@ -0,0 +1,38 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import sys +import MySQLdb + +db = MySQLdb.connect("localhost", "fpdb", sys.argv[1], "fpdb") +cursor = db.cursor() +print "Connected to MySQL on localhost. Printing dev-supplied base data:" + +cursor.execute("SELECT * FROM sites") +print "Sites" +print "=====" +print cursor.fetchall() + +cursor.execute("SELECT * FROM gametypes") +print "Gametypes" +print "=========" +result=cursor.fetchall() +for i in range (len(result)): + print result[i] + +cursor.close() +db.close() diff --git a/utils/fpdb_util_lib.py b/utils/fpdb_util_lib.py new file mode 100644 index 00000000..9afb9fb4 --- /dev/null +++ b/utils/fpdb_util_lib.py @@ -0,0 +1,79 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import sys + +def cards2String(arr): + if (len(arr)%2!=0): + print "TODO: raise error, cards2String failed, uneven length of arr" + sys.exit(1) + result = "" + for i in range (len(arr)/2): + if arr[i*2]==0: + result+="??" + else: + if arr[i*2]==14: + result+="A" + elif arr[i*2]==13: + result+="K" + elif arr[i*2]==12: + result+="Q" + elif arr[i*2]==11: + result+="J" + elif arr[i*2]==10: + result+="T" + elif (arr[i*2]>=2 and arr[i*2]<=9): + result+=str(arr[i*2]) + else: + print "TODO: raise error, cards2String failed, arr[i*2]:",arr[i*2] + sys.exit(1) + result+=arr[i*2+1] + result+=" " + return result[:-1] + +def id_to_player_name(cursor, id): + cursor.execute("SELECT name FROM players WHERE id=%s", (id, )) + return cursor.fetchone()[0] + +def position2String(pos): + if pos=="B": + return "BB" + elif pos=="S": + return "SB" + elif pos=="0": + return "Btn" + else: + return (pos+" off Btn") + +def street_int2String(category, street): + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + if street==0: + return "Preflop" + elif street==1: + return "Flop" + elif street==2: + return "Turn" + elif street==3: + return "River" + else: + print "TODO: raise error, fpdb_util_lib.py street_int2String invalid street no" + sys.exit(1) + elif (category=="razz" or category=="studhi" or category=="studhilo"): + return str(street) + else: + print "TODO: raise error, fpdb_util_lib.py street_int2String invalid category" + sys.exit(1) diff --git a/utils/get_db_stats.py b/utils/get_db_stats.py new file mode 100755 index 00000000..79f3b0fa --- /dev/null +++ b/utils/get_db_stats.py @@ -0,0 +1,83 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import sys +import MySQLdb + +db = MySQLdb.connect("localhost", "fpdb", sys.argv[1], "fpdb") +cursor = db.cursor() +print "Connected to MySQL on localhost. Printing summary stats:" + +cursor.execute("SELECT id FROM players") +print "Players:",cursor.rowcount +cursor.execute("SELECT id FROM autorates") +print "Autorates:",cursor.rowcount + +cursor.execute("SELECT id FROM sites") +print "Sites:",cursor.rowcount +cursor.execute("SELECT id FROM gametypes") +print "Gametypes:",cursor.rowcount + +cursor.execute("SELECT id FROM hands") +print "Total Hands:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.type='ring'") +print "Hands, Ring:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.type='stt'") +print "Hands, STT:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.type='mtt'") +print "Hands, MTT:",cursor.rowcount + +print "" +print "Hands per category and type" +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.limit_type='cn'") +print "Hands, Cap No Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.limit_type='cp'") +print "Hands, Cap Pot Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='holdem' AND gametypes.limit_type='nl'") +print "Hands, Holdem No Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='holdem' AND gametypes.limit_type='pl'") +print "Hands, Holdem Pot Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='holdem' AND gametypes.limit_type='fl'") +print "Hands, Holdem Fixed Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahi' AND gametypes.limit_type='nl'") +print "Hands, Omaha Hi No Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahi' AND gametypes.limit_type='pl'") +print "Hands, Omaha Hi Pot Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahi' AND gametypes.limit_type='fl'") +print "Hands, Omaha Hi Fixed Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahilo' AND gametypes.limit_type='nl'") +print "Hands, Omaha Hi/Lo No Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahilo' AND gametypes.limit_type='pl'") +print "Hands, Omaha Hi/Lo Pot Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahilo' AND gametypes.limit_type='fl'") +print "Hands, Omaha Hi/Lo Fixed Limit:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='razz'") +print "Hands, Razz:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='studhi'") +print "Hands, Stud Hi:",cursor.rowcount +cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='studhilo'") +print "Hands, Stud Hi/Lo:",cursor.rowcount +print "" +cursor.execute("SELECT id FROM board_cards") +print "Board_cards:",cursor.rowcount +cursor.execute("SELECT id FROM hands_players") +print "Hands_players:",cursor.rowcount +cursor.execute("SELECT id FROM hands_actions") +print "Hands_actions:",cursor.rowcount + +cursor.close() +db.close() diff --git a/utils/print_hand.py b/utils/print_hand.py new file mode 100755 index 00000000..1ccb3a15 --- /dev/null +++ b/utils/print_hand.py @@ -0,0 +1,169 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +#This is intended mostly for regression testing + +import sys +import MySQLdb +from optparse import OptionParser +import fpdb_util_lib as ful + +parser = OptionParser() +parser.add_option("-n", "--hand_number", "--hand", type="int", + help="Number of the hand to print") +parser.add_option("-p", "--password", help="The password for the MySQL user") +parser.add_option("-s", "--site", default="PokerStars", + help="Name of the site (as written in the history files)") + +(options, sys.argv) = parser.parse_args() + +if options.hand_number==None or options.site==None: + print "please supply a hand number and site name. TODO: make this work" + +db = MySQLdb.connect("localhost", "fpdb", options.password, "fpdb") +cursor = db.cursor() +print "Connected to MySQL on localhost. Print Hand Utility" + +cursor.execute("SELECT id FROM sites WHERE name=%s", (options.site,)) +site_id=cursor.fetchone()[0] +print "options.site:",options.site,"site_id:",site_id + +cursor.execute("""SELECT hands.* FROM hands INNER JOIN gametypes +ON hands.gametype_id = gametypes.id WHERE gametypes.site_id=%s AND hands.site_hand_no=%s""", +(site_id, options.hand_number)) +hands_result=cursor.fetchone() +gametype_id=hands_result[2] +site_hand_no=options.hand_number +hand_id=hands_result[0] +hand_start=hands_result[3] +seat_count=hands_result[4] + + +print "" +print "From Table gametypes" +print "====================" + +cursor.execute("""SELECT type, category, limit_type FROM gametypes WHERE id=%s""", + (gametype_id, )) +type_etc=cursor.fetchone() +type=type_etc[0] +category=type_etc[1] +limit_type=type_etc[2] +print "type:", type, " category:", category, " limit_type:", limit_type + +gt_string="" +do_bets=False +if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + cursor.execute("SELECT small_blind FROM gametypes WHERE id=%s", (gametype_id, )) + sb=cursor.fetchone()[0] + cursor.execute("SELECT big_blind FROM gametypes WHERE id=%s", (gametype_id, )) + bb=cursor.fetchone()[0] + gt_string=("sb: "+str(sb)+" bb: "+str(bb)) + if (limit_type=="fl"): + do_bets=True +elif (category=="razz" or category=="studhi" or category=="studhilo"): + do_bets=True + +if do_bets: + cursor.execute("SELECT small_bet FROM gametypes WHERE id=%s", (gametype_id, )) + sbet=cursor.fetchone()[0] + cursor.execute("SELECT big_bet FROM gametypes WHERE id=%s", (gametype_id, )) + bbet=cursor.fetchone()[0] + gt_string+=(" sbet: "+str(sbet)+" bbet: "+str(bbet)) +print gt_string + +if type=="ring": + pass +elif type=="tour": + #cursor.execute("SELECT tourneys_players_id FROM hands + cursor.execute("""SELECT DISTINCT tourneys_players.id + FROM hands JOIN hands_players ON hands_players.hand_id=hands.id + JOIN tourneys_players ON hands_players.tourneys_players_id=tourneys_players.id + WHERE hands.id=%s""", (hand_id,)) + hands_players_ids=cursor.fetchall() + print "dbg hands_players_ids:",hands_players_ids + + print "" + print "From Table tourneys" + print "===================" + print "TODO" + + + print "" + print "From Table tourneys_players" + print "===========================" + print "TODO" +else: + print "invalid type:",type + sys.exit(1) + + +print "" +print "From Table hands" +print "================" + +print "site_hand_no:",site_hand_no,"hand_start:",hand_start,"seat_count:",seat_count +#,"hand_id:",hand_id,"gametype_id:",gametype_id + +if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + cursor.execute("""SELECT * FROM board_cards WHERE hand_id=%s""",(hand_id, )) + bc=cursor.fetchone() + print "Board cards:", ful.cards2String(bc[2:]) + + +print "" +print "From Table hands_players" +print "========================" +cursor.execute("""SELECT * FROM hands_players WHERE hand_id=%s""",(hand_id, )) +hands_players=cursor.fetchall() +player_names=[] +for i in range (len(hands_players)): + line=hands_players[i][2:] + player_names.append(ful.id_to_player_name(cursor, line[0])) + printstr="player_name:"+player_names[i]+" player_startcash:"+str(line[1]) + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + printstr+=" position:"+ful.position2String(line[2])+" cards:" + if (category=="holdem"): + printstr+=ful.cards2String(line[4:8]) + else: + printstr+=ful.cards2String(line[4:12]) + elif (category=="razz" or category=="studhi" or category=="studhilo"): + printstr+=" ante:"+str(line[3])+" cards:" + printstr+=ful.cards2String(line[4:18]) + else: + print "TODO: raise error, print_hand.py" + sys.exit(1) + printstr+=" winnings:"+str(line[18])+" rake:"+str(line[19]) + print printstr + + +print "" +print "From Table hands_actions" +print "========================" +for i in range (len(hands_players)): + cursor.execute("""SELECT * FROM hands_actions WHERE hand_player_id=%s""",(hands_players[i][0], )) + hands_actions=cursor.fetchall() + for j in range (len(hands_actions)): + line=hands_actions[j][2:] + printstr="player_name:"+player_names[i]+" actionCount:"+str(j) + printstr+=" street:"+ful.street_int2String(category, line[0])+" streetActionNo:"+str(line[1])+" action:"+line[2] + printstr+=" amount:"+str(line[3]) + print printstr + +cursor.close() +db.close() +sys.exit(0) diff --git a/utils/regression-test.sh b/utils/regression-test.sh new file mode 100755 index 00000000..96c86a3c --- /dev/null +++ b/utils/regression-test.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +rm ../testdata/*.found.txt +../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x +../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x +../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ftp-stud-hilo-ring-001.txt -x +../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ftp-omaha-hi-pl-ring-001-005.txt -x + +echo "it should've reported first that it stored 3, then that it had 3 duplicates," +echo " then 1 stored, then 5 stored" + +./print_hand.py -p$1 --hand=14519394979 > ../testdata/ps.14519394979.found.txt && colordiff ../testdata/ps.14519394979.found.txt ../testdata/ps.14519394979.expected.txt +./print_hand.py -p$1 --hand=14519420999 > ../testdata/ps.14519420999.found.txt && colordiff ../testdata/ps.14519420999.found.txt ../testdata/ps.14519420999.expected.txt +./print_hand.py -p$1 --hand=14519433154 > ../testdata/ps.14519433154.found.txt && colordiff ../testdata/ps.14519433154.found.txt ../testdata/ps.14519433154.expected.txt + +./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ../testdata/ftp.6367428246.found.txt && colordiff ../testdata/ftp.6367428246.found.txt ../testdata/ftp.6367428246.expected.txt + +./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929537410 > ../testdata/ftp.6929537410.found.txt && colordiff ../testdata/ftp.6929537410.found.txt ../testdata/ftp.6929537410.expected.txt +./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929553738 > ../testdata/ftp.6929553738.found.txt && colordiff ../testdata/ftp.6929553738.found.txt ../testdata/ftp.6929553738.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929572212 > ../testdata/ftp.6929572212.found.txt && colordiff ../testdata/ftp.6929572212.found.txt ../testdata/ftp.6929572212.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929576743 > ../testdata/ftp.6929576743.found.txt && colordiff ../testdata/ftp.6929576743.found.txt ../testdata/ftp.6929576743.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929587483 > ../testdata/ftp.6929587483.found.txt && colordiff ../testdata/ftp.6929587483.found.txt ../testdata/ftp.6929587483.expected.txt + +echo "if everything was printed as expected this worked" +echo "todo: this doesnt verify correct gametype detection" From 67c2b3fe04011d682f04314b7c54a34ec4a9552d Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 07:29:53 +0100 Subject: [PATCH 002/262] git2 - import now stores the first few fields into a new line. little fix on load_profile - still doesnt work tho --- docs/known-bugs-and-planned-features.txt | 9 ++-- prepare-git.sh | 2 +- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_parse_logic.py | 10 ++-- pyfpdb/fpdb_save_to_db.py | 16 +++--- pyfpdb/fpdb_simple.py | 63 +++++++++++++++++++++--- 6 files changed, 77 insertions(+), 25 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 51d6f7a2..178db304 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,10 +3,13 @@ todolist (db=database, imp=importer, tv=tableviewer) before beta =========== current speedup attempt todo: -holdem in fpdb_simple done - now update remaining files, test, then add back postflop and stud functionality +finish hud store todos in fpdb_simple +update tv +test +add back postflop and stud functionality -import fails on stud/razz -tv doesnt display street 3-5 in stud/razz i think +change save_to_db into one method and probably move into parse_logic +fix load profile Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu find out if i can SQL for the rowcount, rather than select a field and then just take the rowcount. this might bring a significant performance improvement make a quick benchmark of mysql and postgresql: import of my whole db, some tableviewer refreshes with and without updated file diff --git a/prepare-git.sh b/prepare-git.sh index 44007d5f..7865c4f9 100755 --- a/prepare-git.sh +++ b/prepare-git.sh @@ -17,5 +17,5 @@ rm testdata/*.found.txt rm utils/*.pyc -rm fpdb-python/*.pyc +rm pyfpdb/*.pyc git-add--interactive diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 2645c7b1..b0bc8f58 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -159,7 +159,7 @@ class fpdb: def dia_load_profile(self, widget, data): """Dialogue to select a file to load a profile from""" - obtain_global_lock() + self.obtain_global_lock() chooser = gtk.FileChooserDialog(title="Please select a profile file to load", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 6dc57212..b7aad5d5 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -113,7 +113,7 @@ def mainParser(db, cursor, site, category, hand): limit_type=cursor.fetchone()[0] #todo: remove this unnecessary database access fpdb_simple.convert3B4B(site, category, limit_type, actionTypes, actionAmounts) - hands_players_flags=fpdb_simple.calculate_hands_players_flags(playerIDs, category, actionTypes) + hudImportData=fpdb_simple.calculateHudImport(playerIDs, category, actionTypes) if isTourney: payin_amounts=fpdb_simple.calcPayin(len(names), buyin, fee) @@ -128,22 +128,22 @@ def mainParser(db, cursor, site, category, hand): knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, - actionTypes, actionAmounts, hands_players_flags) + actionTypes, actionAmounts, hudImportData) elif (category=="razz" or category=="studhi" or category=="studhilo"): result = fpdb_save_to_db.tourney_stud(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, cardSuits, winnings, rakes, - actionTypes, actionAmounts, hands_players_flags) + actionTypes, actionAmounts, hudImportData) else: if (category=="holdem" or category=="omahahi" or category=="omahahilo"): result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, - cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, hands_players_flags) + cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, hudImportData) elif (category=="razz" or category=="studhi" or category=="studhilo"): result = fpdb_save_to_db.ring_stud(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, - cardSuits, winnings, rakes, actionTypes, actionAmounts, hands_players_flags) + cardSuits, winnings, rakes, actionTypes, actionAmounts, hudImportData) else: raise fpdb_simple.FpdbError ("unrecognised category") db.commit() diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index 990ae09c..1dd656bf 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -23,7 +23,7 @@ import fpdb_simple #stores a stud/razz hand into the database def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, antes, card_values, card_suits, - winnings, rakes, action_types, action_amounts, hands_players_flags): + winnings, rakes, action_types, action_amounts, hudImportData): fpdb_simple.fillCardArrays(len(names), 7, card_values, card_suits) hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) @@ -31,7 +31,7 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, hands_players_ids=fpdb_simple.store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes) - fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) return site_hand_no @@ -40,7 +40,7 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, #stores a holdem/omaha hand into the database def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, - board_values, board_suits, winnings, rakes, action_types, action_amounts, hands_players_flags): + board_values, board_suits, winnings, rakes, action_types, action_amounts, hudImportData): #fill up the two player card arrays if (category=="holdem"): fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) @@ -56,7 +56,7 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes) - fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + fpdb_simple.storeHudData(cursor, category, gametype_id, player_ids, hudImportData) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) @@ -69,7 +69,7 @@ def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, - action_types, action_amounts, hands_players_flags): + action_types, action_amounts, hudImportData): #stores a tourney stud/razz hand into the database #fill up the two player card arrays if (category=="holdem"): @@ -90,7 +90,7 @@ def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, tourneys_players_ids) - fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) @@ -102,7 +102,7 @@ def tourney_stud(cursor, category, site_tourney_no, buyin, fee, knockout, entrie tourney_start, payin_amounts, ranks, #end of tourney specific params site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, - action_types, action_amounts, hands_players_flags): + action_types, action_amounts, hudImportData): #stores a tourney stud/razz hand into the database fpdb_simple.fillCardArrays(len(names), 7, card_values, card_suits) @@ -115,7 +115,7 @@ def tourney_stud(cursor, category, site_tourney_no, buyin, fee, knockout, entrie hands_players_ids=fpdb_simple.store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, tourneys_players_ids) - fpdb_simple.store_hands_players_flags(cursor, category, hands_players_ids, hands_players_flags) + fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) return site_hand_no diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index a18918fe..34240bad 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1246,7 +1246,7 @@ def calculateHudImport(player_ids, category, action_types): for count in range (len(action_types[street][player])):#finally individual actions currentAction=action_types[street][player][count] if currentAction!="bet": - pfRaiseCount++ + pfRaiseCount+=1 if (currentAction=="bet" or currentAction=="call"): myVPIP=True if pfRaiseCount>=1: @@ -1290,15 +1290,64 @@ def calculateHudImport(player_ids, category, action_types): result['otherRaisedRiver']=otherRaisedRiver result['otherRaisedRiverFold']=otherRaisedRiverFold return result -#end def calculate_hands_players_flags +#end def calculateHudImport -def store_hands_players_flags(cursor, category, hand_player_ids, hands_players_flags): +def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - for i in range (len(hand_player_ids)): - cursor.execute("""INSERT INTO hands_players_flags (hand_player_id, folded_on, street0_vpi, street0_raise, street1_raise, street2_raise, street3_raise) VALUES (%s, %s, %s, %s, %s, %s, %s)""", (hand_player_ids[i], hands_players_flags['folded_on'][i], hands_players_flags['street0_vpi'][i], hands_players_flags['street0_raise'][i], hands_players_flags['street1_raise'][i], hands_players_flags['street2_raise'][i], hands_players_flags['street3_raise'][i])) + for player in range (len(playerIds)): + cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s", (gametypeId, playerIds[player], len(playerIds))) + row=cursor.fetchone() + print "gametypeId:", gametypeId, "playerIds[player]",playerIds[player], "len(playerIds):",len(playerIds), "row:",row + + try: len(row) + except TypeError: + row=[] + + if (len(row)==0): + print "new huddata row" + doInsert=True + row=[] + row.append(0)#blank for id + row.append(gametypeId) + row.append(playerIds[player]) + row.append(len(playerIds))#seats + row.append(0)#HDs + for i in range(len(hudImportData)): + row.append(0) + else: + doInsert=False + newrow=[] + for i in range(len(row)): + newrow.append(row[i]) + row=newrow + + row[4]+=1 #HDs + if hudImportData['VPIP'][player]: row[5]+=1 + if hudImportData['PFR'][player]: row[6]+=1 + if hudImportData['PF3B4B'][player]: row[7]+=1 + if hudImportData['sawFlop'][player]: row[8]+=1 + if hudImportData['sawTurn'][player]: row[9]+=1 + if hudImportData['sawRiver'][player]: row[10]+=1 + if hudImportData['sawShowdown'][player]: row[11]+=1 + if hudImportData['raisedFlop'][player]: row[12]+=1 + if hudImportData['raisedTurn'][player]: row[13]+=1 + if hudImportData['raisedRiver'][player]: row[14]+=1 + if hudImportData['otherRaisedFlop'][player]: row[15]+=1 + if hudImportData['otherRaisedFlopFold'][player]: row[16]+=1 + if hudImportData['otherRaisedTurn'][player]: row[17]+=1 + if hudImportData['otherRaisedTurnFold'][player]: row[18]+=1 + if hudImportData['otherRaisedRiver'][player]: row[19]+=1 + if hudImportData['otherRaisedRiverFold'][player]: row[20]+=1 + + if doInsert: + print "playerid before insert:",row[2] + cursor.execute("""INSERT INTO HudDataHoldemOmaha + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20])) + else: + print "todo: store updated line" else: - for i in range (len(hand_player_ids)): - cursor.execute("""INSERT INTO hands_players_flags (hand_player_id, folded_on, street0_vpi, street0_raise, street1_raise, street2_raise, street3_raise, street4_raise) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (hand_player_ids[i], hands_players_flags['folded_on'][i], hands_players_flags['street0_vpi'][i], hands_players_flags['street0_raise'][i], hands_players_flags['street1_raise'][i], hands_players_flags['street2_raise'][i], hands_players_flags['street3_raise'][i], hands_players_flags['street4_raise'][i])) + raise FpdbError("todo") #end def store_hands_players_flags(cursor, hands_players_ids, hands_players_flags) def store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time): From 6659dc6b9d669249c0aca2378b780a800b332425 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 08:01:26 +0100 Subject: [PATCH 003/262] git3 - it stores and updates hud data lines for holdem/omaha ring games :) --- pyfpdb/fpdb_simple.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 34240bad..a1f9ca5e 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1345,7 +1345,10 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20])) else: - print "todo: store updated line" + print "storing updated hud data line" + cursor.execute("""UPDATE HudDataHoldemOmaha + SET HDs=%s, VPIP=%s, PFR=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[1], row[2], row[3])) else: raise FpdbError("todo") #end def store_hands_players_flags(cursor, hands_players_ids, hands_players_flags) From b829b3b266dd5e3f85c25d9e3be08998966d6030 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 08:31:15 +0100 Subject: [PATCH 004/262] git4 - tv displays up to flop. --- docs/known-bugs-and-planned-features.txt | 8 +- pyfpdb/table_viewer.py | 93 ++++++------------------ 2 files changed, 23 insertions(+), 78 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 178db304..cc61a721 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,12 +2,8 @@ todolist (db=database, imp=importer, tv=tableviewer) before beta =========== -current speedup attempt todo: -finish hud store todos in fpdb_simple -update tv -test -add back postflop and stud functionality - +add back postflop and stud functionality to hud and import-HudData +take into account count of opportunities for 3B/4B PF. change save_to_db into one method and probably move into parse_logic fix load profile Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index a7e02982..8c46479f 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -25,6 +25,14 @@ import fpdb_import import fpdb_db class table_viewer (threading.Thread): + def hudDivide (self, a, b): + if b==0: + return "n/a" + else: + return str(int((a/float(b))*100)) + return "todo" + #end def hudDivide + def browse_clicked(self, widget, data): """runs when user clicks browse on tv tab""" print "start of table_viewer.browser_clicked" @@ -52,85 +60,26 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "AF", "FF", "AT", "FT", "AR", "FR") - streets=(1,2,3) + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR") else: - tmp=("Name", "Hands", "VPI3", "A3", "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7") - streets=(4,5,6,7)#todo: change this once table has been changed + raise fpdb_simple.FpdbError("todo reimplement stud") + tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7") arr.append(tmp) #then the data rows - for i in range(len(self.player_names)): + for player in range(len(self.player_names)): tmp=[] - tmp.append(self.player_names[i][0]) + tmp.append(self.player_names[player][0]) - self.cursor.execute("""SELECT DISTINCT hands.id FROM hands - INNER JOIN hands_players ON hands_players.hand_id = hands.id - WHERE hands.gametype_id=%s AND hands_players.player_id=%s""", (self.gametype_id, self.player_ids[i][0])) - hand_count=self.cursor.rowcount - tmp.append(str(hand_count)) + self.cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s", (self.gametype_id, self.player_ids[player][0], len(self.player_names))) + row=self.cursor.fetchone() - self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players - INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id - INNER JOIN hands ON hands_players.hand_id = hands.id - WHERE hands.gametype_id=%s AND hands_players.player_id=%s - AND street0_vpi=True""", (self.gametype_id, self.player_ids[i][0])) - vpi_count=self.cursor.rowcount - vpi_percent=int(vpi_count/float(hand_count)*100) - tmp.append(str(vpi_percent)) - - - self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players - INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id - INNER JOIN hands ON hands_players.hand_id = hands.id - WHERE hands.gametype_id=%s AND hands_players.player_id=%s - AND street0_raise=True""", (self.gametype_id, self.player_ids[i][0])) - raise_count=self.cursor.rowcount - raise_percent=int(raise_count/float(hand_count)*100) - tmp.append(str(raise_percent)) - - ######start of flop/4th street###### - hand_count - - play_counts=[] - raise_counts=[] - fold_counts=[] - self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players - INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id - INNER JOIN hands ON hands_players.hand_id = hands.id - WHERE hands.gametype_id=%s AND hands_players.player_id=%s - AND folded_on=0""", (self.gametype_id, self.player_ids[i][0])) - preflop_fold_count=self.cursor.rowcount - last_play_count=hand_count-preflop_fold_count - play_counts.append(last_play_count) - - for street in streets: - self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players - INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id - INNER JOIN hands ON hands_players.hand_id = hands.id - WHERE hands.gametype_id=%s AND hands_players.player_id=%s - AND folded_on="""+str(street), (self.gametype_id, self.player_ids[i][0])) - fold_count=self.cursor.rowcount - fold_counts.append(fold_count) - last_play_count-=fold_count - play_counts.append(last_play_count) - - self.cursor.execute("""SELECT DISTINCT hands.id FROM hands_players - INNER JOIN hands_players_flags ON hands_players.id = hands_players_flags.hand_player_id - INNER JOIN hands ON hands_players.hand_id = hands.id - WHERE hands.gametype_id=%s AND hands_players.player_id=%s - AND street"""+str(street)+"_raise=True""", (self.gametype_id, self.player_ids[i][0])) - raise_counts.append(self.cursor.rowcount) - - for street in range (len(streets)): - if play_counts[street]>0: - raise_percent=int(raise_counts[street]/float(play_counts[street])*100) - tmp.append(str(raise_percent)) - fold_percent=int(fold_counts[street]/float(play_counts[street])*100) - tmp.append(str(fold_percent)) - else: - tmp.append("n/a") - tmp.append("n/a") + tmp.append(str(row[4]))#Hands + tmp.append(self.hudDivide(row[5],row[4])) #VPIP + tmp.append(self.hudDivide(row[6],row[4])) #PFR + tmp.append(self.hudDivide(row[7],row[4])) #PF3B4B + tmp.append(self.hudDivide(row[12],row[8])) #AF + tmp.append(self.hudDivide(row[15],row[16])) #FF arr.append(tmp) return arr From 02d928fb1e5d49d233a86aff01008815e7634c7b Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 12:54:54 +0100 Subject: [PATCH 005/262] git5 - tv now displays all postflop rounds for holdem/omaha again (but with placeholder info as not calculating base data for that in importer yet) added extra field to table to facilitate 3B calculations. --- pyfpdb/fpdb_db.py | 2 ++ pyfpdb/fpdb_simple.py | 70 ++++++++++++++++++++++-------------------- pyfpdb/table_viewer.py | 10 ++++-- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 40b01661..dcc4a8fa 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -82,6 +82,7 @@ class fpdb_db: def drop_tables(self): """Drops the fpdb tables from the current db""" self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + #self.cursor.execute("DROP TABLE IF EXISTS hands_players_flags;") self.cursor.execute("DROP TABLE IF EXISTS autorates;") self.cursor.execute("DROP TABLE IF EXISTS board_cards;") self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") @@ -247,6 +248,7 @@ class fpdb_db: HDs INT, VPIP INT, PFR INT, + PFOtherRaisedBefore INT, PF3B4B INT, sawFlop INT, sawTurn INT, diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index a1f9ca5e..bfd21716 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1207,8 +1207,10 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, #end def store_hands_players_stud_tourney def calculateHudImport(player_ids, category, action_types): + """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" VPIP=[] PFR=[] + PFOtherRaisedBefore=[] PF3B4B=[] sawFlop=[] sawTurn=[] @@ -1226,20 +1228,21 @@ def calculateHudImport(player_ids, category, action_types): for player in range (len(player_ids)): myVPIP=False myPFR=False + myPFOtherRaisedBefore=False #todo myPF3B4B=False - mySawFlop=False - mySawTurn=False - mySawRiver=False - mySawShowdown=False - myRaisedFlop=False - myRaisedTurn=False - myRaisedRiver=False - myOtherRaisedFlop=False - myOtherRaisedFlopFold=False - myOtherRaisedTurn=False - myOtherRaisedTurnFold=False - myOtherRaisedRiver=False - myOtherRaisedRiverFold=False + mySawFlop=False #todo + mySawTurn=False #todo + mySawRiver=False #todo + mySawShowdown=False #todo + myRaisedFlop=False #todo + myRaisedTurn=False #todo + myRaisedRiver=False #todo + myOtherRaisedFlop=False #todo + myOtherRaisedFlopFold=False #todo + myOtherRaisedTurn=False #todo + myOtherRaisedTurnFold=False #todo + myOtherRaisedRiver=False #todo + myOtherRaisedRiverFold=False #todo street=0 pfRaiseCount=0 @@ -1254,10 +1257,9 @@ def calculateHudImport(player_ids, category, action_types): if pfRaiseCount>=2:#todo: this doesnt catch all 3B4B myPF3B4B=True - #todo: flop, turn, river, SD - VPIP.append(myVPIP) PFR.append(myPFR) + PFOtherRaisedBefore.append(myPFOtherRaisedBefore) PF3B4B.append(myPF3B4B) sawFlop.append(mySawFlop) sawTurn.append(mySawTurn) @@ -1275,6 +1277,7 @@ def calculateHudImport(player_ids, category, action_types): result={'VPIP':VPIP} result['PFR']=PFR + result['PFOtherRaisedBefore']=PFOtherRaisedBefore result['PF3B4B']=PF3B4B result['sawFlop']=sawFlop result['sawTurn']=sawTurn @@ -1324,31 +1327,32 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): row[4]+=1 #HDs if hudImportData['VPIP'][player]: row[5]+=1 if hudImportData['PFR'][player]: row[6]+=1 - if hudImportData['PF3B4B'][player]: row[7]+=1 - if hudImportData['sawFlop'][player]: row[8]+=1 - if hudImportData['sawTurn'][player]: row[9]+=1 - if hudImportData['sawRiver'][player]: row[10]+=1 - if hudImportData['sawShowdown'][player]: row[11]+=1 - if hudImportData['raisedFlop'][player]: row[12]+=1 - if hudImportData['raisedTurn'][player]: row[13]+=1 - if hudImportData['raisedRiver'][player]: row[14]+=1 - if hudImportData['otherRaisedFlop'][player]: row[15]+=1 - if hudImportData['otherRaisedFlopFold'][player]: row[16]+=1 - if hudImportData['otherRaisedTurn'][player]: row[17]+=1 - if hudImportData['otherRaisedTurnFold'][player]: row[18]+=1 - if hudImportData['otherRaisedRiver'][player]: row[19]+=1 - if hudImportData['otherRaisedRiverFold'][player]: row[20]+=1 + if hudImportData['PFOtherRaisedBefore'][player]: row[7]+=1 + if hudImportData['PF3B4B'][player]: row[8]+=1 + if hudImportData['sawFlop'][player]: row[9]+=1 + if hudImportData['sawTurn'][player]: row[10]+=1 + if hudImportData['sawRiver'][player]: row[11]+=1 + if hudImportData['sawShowdown'][player]: row[12]+=1 + if hudImportData['raisedFlop'][player]: row[13]+=1 + if hudImportData['raisedTurn'][player]: row[14]+=1 + if hudImportData['raisedRiver'][player]: row[15]+=1 + if hudImportData['otherRaisedFlop'][player]: row[16]+=1 + if hudImportData['otherRaisedFlopFold'][player]: row[17]+=1 + if hudImportData['otherRaisedTurn'][player]: row[18]+=1 + if hudImportData['otherRaisedTurnFold'][player]: row[19]+=1 + if hudImportData['otherRaisedRiver'][player]: row[20]+=1 + if hudImportData['otherRaisedRiverFold'][player]: row[21]+=1 if doInsert: print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20])) + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PFOtherRaisedBefore, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21])) else: print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha - SET HDs=%s, VPIP=%s, PFR=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s - WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[1], row[2], row[3])) + SET HDs=%s, VPIP=%s, PFR=%s, PFOtherRaisedBefore=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[1], row[2], row[3])) else: raise FpdbError("todo") #end def store_hands_players_flags(cursor, hands_players_ids, hands_players_flags) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 8c46479f..e37a4d9d 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -77,9 +77,13 @@ class table_viewer (threading.Thread): tmp.append(str(row[4]))#Hands tmp.append(self.hudDivide(row[5],row[4])) #VPIP tmp.append(self.hudDivide(row[6],row[4])) #PFR - tmp.append(self.hudDivide(row[7],row[4])) #PF3B4B - tmp.append(self.hudDivide(row[12],row[8])) #AF - tmp.append(self.hudDivide(row[15],row[16])) #FF + tmp.append(self.hudDivide(row[8],row[4])) #PF3B4B + tmp.append(self.hudDivide(row[13],row[9])) #AF + tmp.append(self.hudDivide(row[16],row[17])) #FF + tmp.append(self.hudDivide(row[14],row[10])) #AT + tmp.append(self.hudDivide(row[18],row[19])) #FT + tmp.append(self.hudDivide(row[15],row[11])) #AR + tmp.append(self.hudDivide(row[20],row[21])) #FR arr.append(tmp) return arr From b377fd08c6151d34cb8918999684d41d0486cb81 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 15:54:13 +0100 Subject: [PATCH 006/262] git6 - it displays (in brackets behind the percentage) how many hands AF/FF/AT/FT/AR/FR is based on commented out some prints split off a "before alpha release" section off the known-bugs list --- docs/known-bugs-and-planned-features.txt | 36 ++++++++++---------- pyfpdb/fpdb_simple.py | 42 +++++++++++++++++------- pyfpdb/table_viewer.py | 14 ++++---- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index cc61a721..d3519287 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,40 +1,40 @@ todolist (db=database, imp=importer, tv=tableviewer) -before beta +before alpha =========== add back postflop and stud functionality to hud and import-HudData -take into account count of opportunities for 3B/4B PF. -change save_to_db into one method and probably move into parse_logic +change action_no to be total for this street rather than just for one player +properly read 3B/4B percentage fix load profile -Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu -find out if i can SQL for the rowcount, rather than select a field and then just take the rowcount. this might bring a significant performance improvement -make a quick benchmark of mysql and postgresql: import of my whole db, some tableviewer refreshes with and without updated file fix tv browse button size tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem tourney bug: fails recognisePlayer tourney bug: fails with tuple error in recogniseplayerid -verify at least 2 or 3 sng hands db+imp+tv WtSD (went to showdown) db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) db+imp+tv WwSF (Won when seen flop - partial taken into account) -db+imp+tv steal blind from btn, co, lmp. fold SB/BB/BI to steal -remove unused flags fields -update install instructions -setup wizard -change action_no to be total for this street rather than just for one player +config wizard catch index error, type error, file not found error -add field for if a game was mixed +update install instructions implement error file in importer +remove mysql/myisam support. + + +before beta +=========== +make bulk importer display a grand total in the GUI +change save_to_db into one method and probably move into parse_logic +Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu +make a quick benchmark of mysql and postgresql: import of my whole db, some tableviewer refreshes with and without updated file +db+imp+tv steal blind from btn, co, lmp. fold SB/BB/BI to steal Make tab and enter work as sensible in GUIs and implement Ctrl+Q, Ctrl+X and Alt+F4 for close. use profile file for bulk import and table viewer settings and pathes -fold% also counts rounds where nobody raised handle errors properly, in particular wrt to SQL rollback. -remove mysql/myisam support. check that we read sitout correctly in: Full Tilt Poker Game #6150325318: Table Bogside setup database, database-user and permission from GUI. update prepare-git to check for license header and copyright. - change/expand print_hand to cover everything new and update verified hands' .found file +verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== @@ -42,13 +42,13 @@ make tv work with ftp e.g. by making importer return site as well (easy) make the gui display errors log file move directory import code from gui to backend -convert fpdb_import to not require passing "self" +convert fpdb_import to not require passing "self", generally clean the parameter passing (tedious general stability improvement for unusual playernames): change all the str.find so they dont accidentially count player names containing the searched phrase. e.g. with rfind. Doesn't handle Daylight Saving Time (I don't think at least) Need to store if someone goes all-in, particularly for better NL/PL support. verify at least 3 hands per category per site per limit_type (when cap then do 2 normal and one 1 capped) incl tv display put lines in tv to make it easier to read -speed up so that refresh takes no more than 10 seks on my P3M-800 +speed up so that refresh after importing my whole DB takes no more than 10 seks on my P3M-800 (a quick run on git5+ indicates this is ok now), or 5 with remote DB select range of stakes and sng/mtt values and types for tv change "for i" to more sensible var name instead of i change stud street storage from 3-7 to 0-4 throughout diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index bfd21716..f8bf95eb 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1208,6 +1208,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, def calculateHudImport(player_ids, category, action_types): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" + #setup subarrays of the result dictionary. VPIP=[] PFR=[] PFOtherRaisedBefore=[] @@ -1226,6 +1227,7 @@ def calculateHudImport(player_ids, category, action_types): otherRaisedRiver=[] otherRaisedRiverFold=[] for player in range (len(player_ids)): + #set default values myVPIP=False myPFR=False myPFOtherRaisedBefore=False #todo @@ -1243,20 +1245,37 @@ def calculateHudImport(player_ids, category, action_types): myOtherRaisedTurnFold=False #todo myOtherRaisedRiver=False #todo myOtherRaisedRiverFold=False #todo - + + #calculate preflop values street=0 - pfRaiseCount=0 + heroPfRaiseCount=0 for count in range (len(action_types[street][player])):#finally individual actions currentAction=action_types[street][player][count] if currentAction!="bet": - pfRaiseCount+=1 + heroPfRaiseCount+=1 if (currentAction=="bet" or currentAction=="call"): myVPIP=True - if pfRaiseCount>=1: + if heroPfRaiseCount>=1: myPFR=True - if pfRaiseCount>=2:#todo: this doesnt catch all 3B4B + if heroPfRaiseCount>=2:#todo: this doesnt catch all 3B4B myPF3B4B=True + #calculate saw* values + if (len(action_types[1][player])>0): + mySawFlop=True + if (len(action_types[2][player])>0): + mySawTurn=True + if (len(action_types[3][player])>0): + mySawRiver=True + for count in range (len(action_types[3][player])): + if action_types[3][player][count]=="fold": + mySawShowdown=True + + #print "todo: finish boolean recognition" + + + + #add each value to the appropriate array VPIP.append(myVPIP) PFR.append(myPFR) PFOtherRaisedBefore.append(myPFOtherRaisedBefore) @@ -1274,7 +1293,8 @@ def calculateHudImport(player_ids, category, action_types): otherRaisedTurnFold.append(myOtherRaisedTurnFold) otherRaisedRiver.append(myOtherRaisedRiver) otherRaisedRiverFold.append(myOtherRaisedRiverFold) - + + #add each array to the to-be-returned dictionary result={'VPIP':VPIP} result['PFR']=PFR result['PFOtherRaisedBefore']=PFOtherRaisedBefore @@ -1300,14 +1320,14 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): for player in range (len(playerIds)): cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s", (gametypeId, playerIds[player], len(playerIds))) row=cursor.fetchone() - print "gametypeId:", gametypeId, "playerIds[player]",playerIds[player], "len(playerIds):",len(playerIds), "row:",row + #print "gametypeId:", gametypeId, "playerIds[player]",playerIds[player], "len(playerIds):",len(playerIds), "row:",row try: len(row) except TypeError: row=[] if (len(row)==0): - print "new huddata row" + #print "new huddata row" doInsert=True row=[] row.append(0)#blank for id @@ -1344,18 +1364,18 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if hudImportData['otherRaisedRiverFold'][player]: row[21]+=1 if doInsert: - print "playerid before insert:",row[2] + #print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PFOtherRaisedBefore, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21])) else: - print "storing updated hud data line" + #print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha SET HDs=%s, VPIP=%s, PFR=%s, PFOtherRaisedBefore=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[1], row[2], row[3])) else: raise FpdbError("todo") -#end def store_hands_players_flags(cursor, hands_players_ids, hands_players_flags) +#end def storeHudData def store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time): cursor.execute("SELECT id FROM tourneys WHERE site_tourney_no=%s AND site_id=%s", (site_tourney_no, site_id)) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index e37a4d9d..7b94b5f3 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -77,13 +77,13 @@ class table_viewer (threading.Thread): tmp.append(str(row[4]))#Hands tmp.append(self.hudDivide(row[5],row[4])) #VPIP tmp.append(self.hudDivide(row[6],row[4])) #PFR - tmp.append(self.hudDivide(row[8],row[4])) #PF3B4B - tmp.append(self.hudDivide(row[13],row[9])) #AF - tmp.append(self.hudDivide(row[16],row[17])) #FF - tmp.append(self.hudDivide(row[14],row[10])) #AT - tmp.append(self.hudDivide(row[18],row[19])) #FT - tmp.append(self.hudDivide(row[15],row[11])) #AR - tmp.append(self.hudDivide(row[20],row[21])) #FR + tmp.append(self.hudDivide(row[8],row[7])) #PF3B4B + tmp.append(self.hudDivide(row[13],row[9])+" ("+str(row[9])+")") #AF + tmp.append(self.hudDivide(row[17],row[16])+" ("+str(row[16])+")") #FF + tmp.append(self.hudDivide(row[14],row[10])+" ("+str(row[10])+")") #AT + tmp.append(self.hudDivide(row[19],row[18])+" ("+str(row[18])+")") #FT + tmp.append(self.hudDivide(row[15],row[11])+" ("+str(row[11])+")") #AR + tmp.append(self.hudDivide(row[21],row[20])+" ("+str(row[20])+")") #FR arr.append(tmp) return arr From d95bc6dff6b50094d5f1b3c782284454d36aa779 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 19:57:33 +0100 Subject: [PATCH 007/262] git7 - calculates&displays flop stuff. I wouldnt go as far as claiming that "it works", but it runs without compiler/VM-caught error and it produces numbers - good enough till i get around to updating that damned print_hands --- docs/known-bugs-and-planned-features.txt | 12 ++++++----- pyfpdb/fpdb_simple.py | 27 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index d3519287..bcbac127 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,17 +2,18 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha =========== -add back postflop and stud functionality to hud and import-HudData +add back turn, river and stud functionality to hud and import-HudData +db+imp+tv WtSD (went to showdown) +db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) +db+imp+tv WwSF (Won when seen flop - partial taken into account) change action_no to be total for this street rather than just for one player + properly read 3B/4B percentage fix load profile fix tv browse button size tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem tourney bug: fails recognisePlayer tourney bug: fails with tuple error in recogniseplayerid -db+imp+tv WtSD (went to showdown) -db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) -db+imp+tv WwSF (Won when seen flop - partial taken into account) config wizard catch index error, type error, file not found error update install instructions @@ -22,6 +23,7 @@ remove mysql/myisam support. before beta =========== +change stud street storage from 3-7 to 0-4 throughout make bulk importer display a grand total in the GUI change save_to_db into one method and probably move into parse_logic Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu @@ -38,6 +40,7 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== +cut down action_types array size to appropriate length make tv work with ftp e.g. by making importer return site as well (easy) make the gui display errors log file @@ -51,7 +54,6 @@ put lines in tv to make it easier to read speed up so that refresh after importing my whole DB takes no more than 10 seks on my P3M-800 (a quick run on git5+ indicates this is ok now), or 5 with remote DB select range of stakes and sng/mtt values and types for tv change "for i" to more sensible var name instead of i -change stud street storage from 3-7 to 0-4 throughout in all importer: stop doing if site=="ftp", make class constants for site_id instead recognise somewhere if a file is still active and if so keep it open and only read new hands rather than detecting dupes gentoo ebuild diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index f8bf95eb..0510ebc6 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1271,9 +1271,34 @@ def calculateHudImport(player_ids, category, action_types): if action_types[3][player][count]=="fold": mySawShowdown=True - #print "todo: finish boolean recognition" + #flop stuff + street=1 + if mySawFlop: + for count in range(len(action_types[street][player])): + if action_types[street][player][count]=="bet": + myRaisedFlop=True + + for otherPlayer in range (len(player_ids)): + if player==otherPlayer or myOtherRaisedFlop: + pass + else: + for countOther in range (len(action_types[street][otherPlayer])): + if action_types[street][otherPlayer][countOther]=="bet": + myOtherRaisedFlop=True + for countOtherFold in range (len(action_types[street][otherPlayer])): + if action_types[street][otherPlayer][countOtherFold]=="fold": + myOtherRaisedFlopFold=True + #turn stuff: todo + for count in range(len(action_types[2][player])): + if action_types[2][player][count]=="bet": + myRaisedTurn=True + #river stuff: todo + for count in range(len(action_types[3][player])): + if action_types[3][player][count]=="bet": + myRaisedRiver=True + #add each value to the appropriate array VPIP.append(myVPIP) From 7af8bb922a38d2f19d773faf58d1397dff260f62 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 21:51:32 +0100 Subject: [PATCH 008/262] git8 - updated the 3 ps testdatas and regression test --- pyfpdb/fpdb.py | 1 + pyfpdb/fpdb_db.py | 3 +-- testdata/ps.14519394979.expected.txt | 8 +++++++- testdata/ps.14519420999.expected.txt | 8 +++++++- testdata/ps.14519433154.expected.txt | 8 +++++++- utils/regression-test.sh | 19 +++++++++---------- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index b0bc8f58..b13166ab 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -111,6 +111,7 @@ class fpdb: def dia_database_stats(self, widget, data): print "todo: implement dia_database_stats" + #string=fpdb_db.getDbStats(db, cursor) #end def dia_database_stats def dia_delete_db_parts(self, widget, data): diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index dcc4a8fa..62506f0d 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -23,7 +23,6 @@ class fpdb_db: """Simple constructor, doesnt really do anything""" self.db=None self.cursor=None - self.MYSQL=1 self.MYSQL_INNODB=2 self.PGSQL=3 #end def __init__ @@ -36,7 +35,7 @@ class fpdb_db: self.user=user self.password=password #print "fpdb_db.connect, password:",password,"/end" - if backend==self.MYSQL or backend==self.MYSQL_INNODB: + if backend==self.MYSQL_INNODB: import MySQLdb print "fpdb_db.connect, host:", host, " user:", user, " passwd:", password, " db:", database self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) diff --git a/testdata/ps.14519394979.expected.txt b/testdata/ps.14519394979.expected.txt index 6bcaa016..3c5f86d8 100644 --- a/testdata/ps.14519394979.expected.txt +++ b/testdata/ps.14519394979.expected.txt @@ -1,8 +1,14 @@ Connected to MySQL on localhost. Print Hand Utility options.site: PokerStars site_id: 2 + +From Table gametypes +==================== +type: ring category: holdem limit_type: fl +sb: 1 bb: 2 sbet: 2 bbet: 4 + From Table hands ================ -site_hand_no: 14519394979 hand_start: 2008-01-13 05:22:15 seat_count: 7 category: holdem +site_hand_no: 14519394979 hand_start: 2008-01-13 05:22:15 seat_count: 7 Board cards: Qd Th Js 2s 7s From Table hands_players diff --git a/testdata/ps.14519420999.expected.txt b/testdata/ps.14519420999.expected.txt index 3be7f2b8..5c1de6de 100644 --- a/testdata/ps.14519420999.expected.txt +++ b/testdata/ps.14519420999.expected.txt @@ -1,8 +1,14 @@ Connected to MySQL on localhost. Print Hand Utility options.site: PokerStars site_id: 2 + +From Table gametypes +==================== +type: ring category: holdem limit_type: fl +sb: 1 bb: 2 sbet: 2 bbet: 4 + From Table hands ================ -site_hand_no: 14519420999 hand_start: 2008-01-13 05:23:43 seat_count: 7 category: holdem +site_hand_no: 14519420999 hand_start: 2008-01-13 05:23:43 seat_count: 7 Board cards: Th Jd 3c 7c 4s From Table hands_players diff --git a/testdata/ps.14519433154.expected.txt b/testdata/ps.14519433154.expected.txt index 764d3d8e..81000579 100644 --- a/testdata/ps.14519433154.expected.txt +++ b/testdata/ps.14519433154.expected.txt @@ -1,8 +1,14 @@ Connected to MySQL on localhost. Print Hand Utility options.site: PokerStars site_id: 2 + +From Table gametypes +==================== +type: ring category: holdem limit_type: fl +sb: 1 bb: 2 sbet: 2 bbet: 4 + From Table hands ================ -site_hand_no: 14519433154 hand_start: 2008-01-13 05:24:25 seat_count: 7 category: holdem +site_hand_no: 14519433154 hand_start: 2008-01-13 05:24:25 seat_count: 7 Board cards: 4h 9s Ad Qc Ks From Table hands_players diff --git a/utils/regression-test.sh b/utils/regression-test.sh index 96c86a3c..252db6ce 100755 --- a/utils/regression-test.sh +++ b/utils/regression-test.sh @@ -16,25 +16,24 @@ #agpl-3.0.txt in the docs folder of the package. rm ../testdata/*.found.txt -../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x -../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x -../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ftp-stud-hilo-ring-001.txt -x -../fpdb-python/fpdb_import.py -p$1 --file=../testdata/ftp-omaha-hi-pl-ring-001-005.txt -x +../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x +../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x +#../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ftp-stud-hilo-ring-001.txt -x +#../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ftp-omaha-hi-pl-ring-001-005.txt -x -echo "it should've reported first that it stored 3, then that it had 3 duplicates," -echo " then 1 stored, then 5 stored" +echo "it should've reported first that it stored 3, then that it had 3 duplicates" +#echo " then 1 stored, then 5 stored" ./print_hand.py -p$1 --hand=14519394979 > ../testdata/ps.14519394979.found.txt && colordiff ../testdata/ps.14519394979.found.txt ../testdata/ps.14519394979.expected.txt ./print_hand.py -p$1 --hand=14519420999 > ../testdata/ps.14519420999.found.txt && colordiff ../testdata/ps.14519420999.found.txt ../testdata/ps.14519420999.expected.txt ./print_hand.py -p$1 --hand=14519433154 > ../testdata/ps.14519433154.found.txt && colordiff ../testdata/ps.14519433154.found.txt ../testdata/ps.14519433154.expected.txt -./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ../testdata/ftp.6367428246.found.txt && colordiff ../testdata/ftp.6367428246.found.txt ../testdata/ftp.6367428246.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ../testdata/ftp.6367428246.found.txt && colordiff ../testdata/ftp.6367428246.found.txt ../testdata/ftp.6367428246.expected.txt -./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929537410 > ../testdata/ftp.6929537410.found.txt && colordiff ../testdata/ftp.6929537410.found.txt ../testdata/ftp.6929537410.expected.txt -./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929553738 > ../testdata/ftp.6929553738.found.txt && colordiff ../testdata/ftp.6929553738.found.txt ../testdata/ftp.6929553738.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929537410 > ../testdata/ftp.6929537410.found.txt && colordiff ../testdata/ftp.6929537410.found.txt ../testdata/ftp.6929537410.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929553738 > ../testdata/ftp.6929553738.found.txt && colordiff ../testdata/ftp.6929553738.found.txt ../testdata/ftp.6929553738.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929572212 > ../testdata/ftp.6929572212.found.txt && colordiff ../testdata/ftp.6929572212.found.txt ../testdata/ftp.6929572212.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929576743 > ../testdata/ftp.6929576743.found.txt && colordiff ../testdata/ftp.6929576743.found.txt ../testdata/ftp.6929576743.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929587483 > ../testdata/ftp.6929587483.found.txt && colordiff ../testdata/ftp.6929587483.found.txt ../testdata/ftp.6929587483.expected.txt echo "if everything was printed as expected this worked" -echo "todo: this doesnt verify correct gametype detection" From 2ed82c58ee56954cadf1683ce17d9c63775d37b4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 4 Aug 2008 23:03:13 +0100 Subject: [PATCH 009/262] git9 - (tables slightly changed) started PrintPlayerFlag - it does the command line parsing and prints the input' data and the selected ids. Changed tabledesign type possibilities from cash/tour to ring/tour to match importer. if anyone has code broken by this send it in and ill fix it. also cleaned the html a bit more. --- docs/known-bugs-and-planned-features.txt | 10 +++- docs/tabledesign.html | 70 ++++++------------------ utils/PrintPlayerFlags.py | 61 +++++++++++++++++++++ utils/regression-test.sh | 4 ++ 4 files changed, 91 insertions(+), 54 deletions(-) create mode 100755 utils/PrintPlayerFlags.py diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index bcbac127..93cae5ba 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,8 +1,12 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha -=========== -add back turn, river and stud functionality to hud and import-HudData +============ +PrintPlayerFlag +imp/tv bug: PFR is blatantly crazy +imp(?) bug: handcount is only about 1/2 of what it should be +verify at least one PrintPlayerFlags +and stud functionality to hud and import-HudData db+imp+tv WtSD (went to showdown) db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) db+imp+tv WwSF (Won when seen flop - partial taken into account) @@ -17,12 +21,14 @@ tourney bug: fails with tuple error in recogniseplayerid config wizard catch index error, type error, file not found error update install instructions +split python requirements, get deep links for windows DL for everything implement error file in importer remove mysql/myisam support. before beta =========== +file permission script, use games group change stud street storage from 3-7 to 0-4 throughout make bulk importer display a grand total in the GUI change save_to_db into one method and probably move into parse_logic diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 3cfa5c48..018ae40b 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -7,42 +7,21 @@

see commit comments for version info

Direct suggestions, praise and animal names to steffen@sycamoretest.info

-

TODO clean all the crap out of this like i did in HudData

-

I decided to be generous on the sizes of the types - if computing -experience shows one thing then its that it will come back to bite -you in the ass if you save 2 bits in the wrong place. If performance -and/or db size are too bad we can still shrink some fields.

-

Relationships -are noted in the comment (need to double check that all are listed)

-

If -you want more comments or if anything is confusing or bad let me -know.

-


-

-

All -money/cash amounts are stored in cents/pennies/whatever (e.g. $4.27 -would be stored a 427). Chips are stored as-is (e.g. 3675 chips would -be stored as 3675).

-


-

-

Support -for ringgames in Holdem, Omaha, Razz and Stud complete. Support for -SnG/MTT is alpha

-


-

-

Notes -on use/editing:

-

Any -change to this must be carried to to the table creation code unless it -is clearly noted in appropriate places.

-

If the code (in particular the importer) and this document disagree then this document is to be considered authorative. Please report such mismatches to steffen@sycamoretest.org or through sourceforge.

+

TODO clean all the crap out of this like i did in HudData, line39 onwards

+

I decided to be generous on the sizes of the types - if computing experience shows one thing then its that it will come back to bite you in the ass if you save 2 bits in the wrong place. If performance and/or db size are too bad we can still shrink some fields.

+

Relationships are noted in the comment (need to double check that all are listed)

+

If you want more comments or if anything is confusing or bad let me know.

+

All money/cash amounts are stored in cents/pennies/whatever (e.g. $4.27 would be stored a 427). Chips are stored as-is (e.g. 3675 chips would be stored as 3675).

+

Support for ringgames in Holdem, Omaha, Razz and Stud complete. Support for SnG/MTT is alpha

+

Notes on use/editing:

+

Any change to this must be carried to to the table creation code in fpdb_db.py or at least an entry to known bugs is to be made.

+

If the code (in particular the importer) and this document disagree then this document is to be considered authorative. Please report such mismatches to steffen@sycamoretest.org or through an assembla ticket.

License
Trademarks of third parties have been used under Fair Use or similar laws.
Copyright 2008 Steffen Jobbagy-Felso
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 as published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license can be found in fdl-1.2.txt
The program itself is licensed under AGPLv3, see agpl-3.0.txt

-

See -readme.txt for copying

+

See readme.txt for copying


Table @@ -54,16 +33,9 @@ players

Comment

- -

id

- - -

int

- - -


-

- +

id

+

int

+


@@ -266,18 +238,12 @@ gametypes

+

type

+

char(4)

-

type

- - -

char(4)

- - -

valid - entries:

-

cash - - aka ringgames

-

tour - tournament

+

valid entries:

+

ring - ringgames aka cash games

+

tour - tournament incl SnG

diff --git a/utils/PrintPlayerFlags.py b/utils/PrintPlayerFlags.py new file mode 100755 index 00000000..c77a6296 --- /dev/null +++ b/utils/PrintPlayerFlags.py @@ -0,0 +1,61 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +#This is intended mostly for regression testing + +import sys +import MySQLdb +from optparse import OptionParser +import fpdb_util_lib as ful + +parser = OptionParser() +parser.add_option("-b", "--bigblind", default="2", type="int", help="big blinds in cent") +parser.add_option("-c", "--cat", "--category", default="holdem", help="Category, e.g. holdem or studhilo") +parser.add_option("-g", "--gameType", default="ring", help="Whether its a ringgame (ring) or a tournament (tour)") +parser.add_option("-l", "--limit", "--limitType", default="fl", help="Limit Type, one of: nl, pl, fl, cn, cp") +parser.add_option("-n", "--name", "--playername", default="Player_1", help="Name of the player to print") +parser.add_option("-p", "--password", help="The password for the MySQL user") +parser.add_option("-s", "--site", default="PokerStars", help="Name of the site (as written in the history files)") + +(options, sys.argv) = parser.parse_args() + +db = MySQLdb.connect("localhost", "fpdb", options.password, "fpdb") +cursor = db.cursor() +print "Connected to MySQL on localhost. Print Player Flags Utility" + + +print "Basic Data" +print "==========" +print "bigblind:",options.bigblind, "category:",options.cat, "limitType:", options.limit, "name:", options.name, "gameType:", options.gameType, "site:", options.site + +cursor.execute("SELECT id FROM sites WHERE name=%s", (options.site,)) +siteId=cursor.fetchone()[0] + +cursor.execute("SELECT id FROM gametypes WHERE big_blind=%s AND category=%s AND site_id=%s AND limit_type=%s AND type=%s", (options.bigblind, options.cat, siteId, options.limit, options.gameType)) +gametypeId=cursor.fetchone()[0] + +cursor.execute("SELECT id FROM players WHERE name=%s", (options.name,)) +playerId=cursor.fetchone()[0] + +print "siteId:", siteId, "gametypeId:", gametypeId, "playerId:", playerId + + + + +cursor.close() +db.close() +sys.exit(0) diff --git a/utils/regression-test.sh b/utils/regression-test.sh index 252db6ce..c4011f1f 100755 --- a/utils/regression-test.sh +++ b/utils/regression-test.sh @@ -15,6 +15,8 @@ #In the "official" distribution you can find the license in #agpl-3.0.txt in the docs folder of the package. +echo "Please note for this to really work you need to work on an empty database" + rm ../testdata/*.found.txt ../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x ../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x @@ -28,6 +30,8 @@ echo "it should've reported first that it stored 3, then that it had 3 duplicate ./print_hand.py -p$1 --hand=14519420999 > ../testdata/ps.14519420999.found.txt && colordiff ../testdata/ps.14519420999.found.txt ../testdata/ps.14519420999.expected.txt ./print_hand.py -p$1 --hand=14519433154 > ../testdata/ps.14519433154.found.txt && colordiff ../testdata/ps.14519433154.found.txt ../testdata/ps.14519433154.expected.txt +./PrintPlayerFlags.py -p$1 > ../testdata/ps-flags-3hands.found.txt && colordiff ../testdata/ps-flags-3hands.found.txt ../testdata/ps-flags-3hands.expected.txt + #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ../testdata/ftp.6367428246.found.txt && colordiff ../testdata/ftp.6367428246.found.txt ../testdata/ftp.6367428246.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929537410 > ../testdata/ftp.6929537410.found.txt && colordiff ../testdata/ftp.6929537410.found.txt ../testdata/ftp.6929537410.expected.txt From 84b3851cb5dc5aa700c1779175349bff5a385f90 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 5 Aug 2008 00:14:17 +0100 Subject: [PATCH 010/262] git10 - added field to table design that i had neglected to document. apologies. renamed it as well to be more useful, its now PF3B4BChance. more table design cleaning. finished PrintPlayerFlags and renamed to the more appropriate PrintPlayerHudData fixed: imp/tv bug: PFR is blatantly crazy moved the scripts that do regression testing into the testdata directory and renamed that into regression-test --- docs/known-bugs-and-planned-features.txt | 14 ++++-- docs/tabledesign.html | 43 ++++++----------- prepare-git.sh | 4 +- pyfpdb/fpdb_db.py | 2 +- pyfpdb/fpdb_simple.py | 16 +++---- .../PrintPlayerHudData.py | 47 +++++++++++++++++-- {utils => regression-test}/fpdb_util_lib.py | 0 .../ftp-omaha-hi-pl-ring-001-005.txt | 0 .../ftp-stud-hilo-ring-001.txt | 0 .../ftp.6367428246.expected.txt | 0 .../ftp.6929537410.expected.txt | 0 .../ftp.6929553738.expected.txt | 0 {testdata => regression-test}/mysql-dump.sql | 0 {utils => regression-test}/print_hand.py | 0 .../ps-holdem-ring-001to003.txt | 0 .../ps.14519394979.expected.txt | 0 .../ps.14519420999.expected.txt | 0 .../ps.14519433154.expected.txt | 0 regression-test/regression-test.sh | 43 +++++++++++++++++ utils/regression-test.sh | 43 ----------------- 20 files changed, 120 insertions(+), 92 deletions(-) rename utils/PrintPlayerFlags.py => regression-test/PrintPlayerHudData.py (61%) rename {utils => regression-test}/fpdb_util_lib.py (100%) rename {testdata => regression-test}/ftp-omaha-hi-pl-ring-001-005.txt (100%) rename {testdata => regression-test}/ftp-stud-hilo-ring-001.txt (100%) rename {testdata => regression-test}/ftp.6367428246.expected.txt (100%) rename {testdata => regression-test}/ftp.6929537410.expected.txt (100%) rename {testdata => regression-test}/ftp.6929553738.expected.txt (100%) rename {testdata => regression-test}/mysql-dump.sql (100%) rename {utils => regression-test}/print_hand.py (100%) rename {testdata => regression-test}/ps-holdem-ring-001to003.txt (100%) rename {testdata => regression-test}/ps.14519394979.expected.txt (100%) rename {testdata => regression-test}/ps.14519420999.expected.txt (100%) rename {testdata => regression-test}/ps.14519433154.expected.txt (100%) create mode 100755 regression-test/regression-test.sh delete mode 100755 utils/regression-test.sh diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 93cae5ba..6df5603f 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,11 +2,13 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ -PrintPlayerFlag -imp/tv bug: PFR is blatantly crazy -imp(?) bug: handcount is only about 1/2 of what it should be +PrintPlayerFlag - the actual flags verify at least one PrintPlayerFlags -and stud functionality to hud and import-HudData +in tv, select from hud table using named fields rather than the current * + +tv: add in brackets number of hands on which 3B4B is based +imp/tv bug: handcount is only about 1/2 of what it should be +add stud functionality back to imp/tv db+imp+tv WtSD (went to showdown) db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) db+imp+tv WwSF (Won when seen flop - partial taken into account) @@ -24,7 +26,7 @@ update install instructions split python requirements, get deep links for windows DL for everything implement error file in importer remove mysql/myisam support. - +ideally HUD before beta =========== @@ -46,6 +48,7 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== +HTMLify docs and validate them cut down action_types array size to appropriate length make tv work with ftp e.g. by making importer return site as well (easy) make the gui display errors @@ -72,6 +75,7 @@ HTMLify docs can wait till 1.x ================= finish cleaning tabledesign html code +rearrange huddata fields It treats fold due to disconnect as voluntary fold which is not ideal auto-import check for unnecessary db.commit() diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 018ae40b..497d7fa4 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -38,27 +38,14 @@ players


- -

name

- - -

varchar(32)

- - -


-

- +

name

+

varchar(32)

+


- -

site_id

- - -

smallint

- - -

references sites.id

- +

site_id

+

smallint

+

references sites.id

@@ -73,16 +60,9 @@ players

- -

comment_ts

- - -

datetime (in UTC)

- - -


-

- +

comment_ts

+

datetime (in UTC)

+



@@ -798,6 +778,11 @@ far less relevant.

int

number of hands where player raised before flop

+ +

PF3B4BChance

+

int

+

number of hands where player had chance to 3B or 4B

+

PF3B4B

int

diff --git a/prepare-git.sh b/prepare-git.sh index 7865c4f9..08dd0a5f 100755 --- a/prepare-git.sh +++ b/prepare-git.sh @@ -15,7 +15,7 @@ #In the "official" distribution you can find the license in #agpl-3.0.txt in the docs folder of the package. -rm testdata/*.found.txt -rm utils/*.pyc +rm regression-test/*.found.txt +rm regression-test/*.pyc rm pyfpdb/*.pyc git-add--interactive diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 62506f0d..cf74dd35 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -247,7 +247,7 @@ class fpdb_db: HDs INT, VPIP INT, PFR INT, - PFOtherRaisedBefore INT, + PF3B4BChance INT, PF3B4B INT, sawFlop INT, sawTurn INT, diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 0510ebc6..e2204ec4 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1211,7 +1211,7 @@ def calculateHudImport(player_ids, category, action_types): #setup subarrays of the result dictionary. VPIP=[] PFR=[] - PFOtherRaisedBefore=[] + PF3B4BChance=[] PF3B4B=[] sawFlop=[] sawTurn=[] @@ -1230,7 +1230,7 @@ def calculateHudImport(player_ids, category, action_types): #set default values myVPIP=False myPFR=False - myPFOtherRaisedBefore=False #todo + myPF3B4BChance=False #todo myPF3B4B=False mySawFlop=False #todo mySawTurn=False #todo @@ -1251,7 +1251,7 @@ def calculateHudImport(player_ids, category, action_types): heroPfRaiseCount=0 for count in range (len(action_types[street][player])):#finally individual actions currentAction=action_types[street][player][count] - if currentAction!="bet": + if currentAction=="bet": heroPfRaiseCount+=1 if (currentAction=="bet" or currentAction=="call"): myVPIP=True @@ -1303,7 +1303,7 @@ def calculateHudImport(player_ids, category, action_types): #add each value to the appropriate array VPIP.append(myVPIP) PFR.append(myPFR) - PFOtherRaisedBefore.append(myPFOtherRaisedBefore) + PF3B4BChance.append(PF3B4BChance) PF3B4B.append(myPF3B4B) sawFlop.append(mySawFlop) sawTurn.append(mySawTurn) @@ -1322,7 +1322,7 @@ def calculateHudImport(player_ids, category, action_types): #add each array to the to-be-returned dictionary result={'VPIP':VPIP} result['PFR']=PFR - result['PFOtherRaisedBefore']=PFOtherRaisedBefore + result['PF3B4BChance']=PF3B4BChance result['PF3B4B']=PF3B4B result['sawFlop']=sawFlop result['sawTurn']=sawTurn @@ -1372,7 +1372,7 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): row[4]+=1 #HDs if hudImportData['VPIP'][player]: row[5]+=1 if hudImportData['PFR'][player]: row[6]+=1 - if hudImportData['PFOtherRaisedBefore'][player]: row[7]+=1 + if hudImportData['PF3B4BChance'][player]: row[7]+=1 if hudImportData['PF3B4B'][player]: row[8]+=1 if hudImportData['sawFlop'][player]: row[9]+=1 if hudImportData['sawTurn'][player]: row[10]+=1 @@ -1391,12 +1391,12 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if doInsert: #print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PFOtherRaisedBefore, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21])) else: #print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha - SET HDs=%s, VPIP=%s, PFR=%s, PFOtherRaisedBefore=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s + SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[1], row[2], row[3])) else: raise FpdbError("todo") diff --git a/utils/PrintPlayerFlags.py b/regression-test/PrintPlayerHudData.py similarity index 61% rename from utils/PrintPlayerFlags.py rename to regression-test/PrintPlayerHudData.py index c77a6296..1c3f4de9 100755 --- a/utils/PrintPlayerFlags.py +++ b/regression-test/PrintPlayerHudData.py @@ -25,6 +25,7 @@ import fpdb_util_lib as ful parser = OptionParser() parser.add_option("-b", "--bigblind", default="2", type="int", help="big blinds in cent") parser.add_option("-c", "--cat", "--category", default="holdem", help="Category, e.g. holdem or studhilo") +parser.add_option("-e", "--seats", default="7", type="int", help="number of active seats") parser.add_option("-g", "--gameType", default="ring", help="Whether its a ringgame (ring) or a tournament (tour)") parser.add_option("-l", "--limit", "--limitType", default="fl", help="Limit Type, one of: nl, pl, fl, cn, cp") parser.add_option("-n", "--name", "--playername", default="Player_1", help="Name of the player to print") @@ -37,7 +38,7 @@ db = MySQLdb.connect("localhost", "fpdb", options.password, "fpdb") cursor = db.cursor() print "Connected to MySQL on localhost. Print Player Flags Utility" - +print "" print "Basic Data" print "==========" print "bigblind:",options.bigblind, "category:",options.cat, "limitType:", options.limit, "name:", options.name, "gameType:", options.gameType, "site:", options.site @@ -51,11 +52,49 @@ gametypeId=cursor.fetchone()[0] cursor.execute("SELECT id FROM players WHERE name=%s", (options.name,)) playerId=cursor.fetchone()[0] -print "siteId:", siteId, "gametypeId:", gametypeId, "playerId:", playerId +cursor.execute("SELECT id FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s",(gametypeId, playerId, options.seats)) +hudDataId=cursor.fetchone()[0] + +print "siteId:", siteId, "gametypeId:", gametypeId, "playerId:", playerId, "hudDataId:", hudDataId + +print "" +print "HUD Raw Hand Counts" +print "===================" + +cursor.execute ("SELECT HDs, VPIP, PFR, PF3B4BChance, PF3B4B FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "HDs:",fields[0] +print "VPIP:",fields[1] +print "PFR:",fields[2] +print "PF3B4BChance:",fields[3] +print "PF3B4B:",fields[4] +print "" + +cursor.execute ("SELECT sawFlop, sawTurn, sawRiver, sawShowdown FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "sawFlop:",fields[0] +print "sawTurn:",fields[1] +print "sawRiver:",fields[2] +print "sawShowdown:",fields[3] +print "" + +cursor.execute ("SELECT raisedFlop, raisedTurn, raisedRiver FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "raisedFlop:",fields[0] +print "raisedTurn:",fields[1] +print "raisedRiver:",fields[2] +print "" + +cursor.execute ("SELECT otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "otherRaisedFlop:",fields[0] +print "otherRaisedFlopFold:",fields[1] +print "otherRaisedTurn:",fields[2] +print "otherRaisedTurnFold:",fields[3] +print "otherRaisedRiver:",fields[4] +print "otherRaisedRiverFold:",fields[5] - - cursor.close() db.close() sys.exit(0) diff --git a/utils/fpdb_util_lib.py b/regression-test/fpdb_util_lib.py similarity index 100% rename from utils/fpdb_util_lib.py rename to regression-test/fpdb_util_lib.py diff --git a/testdata/ftp-omaha-hi-pl-ring-001-005.txt b/regression-test/ftp-omaha-hi-pl-ring-001-005.txt similarity index 100% rename from testdata/ftp-omaha-hi-pl-ring-001-005.txt rename to regression-test/ftp-omaha-hi-pl-ring-001-005.txt diff --git a/testdata/ftp-stud-hilo-ring-001.txt b/regression-test/ftp-stud-hilo-ring-001.txt similarity index 100% rename from testdata/ftp-stud-hilo-ring-001.txt rename to regression-test/ftp-stud-hilo-ring-001.txt diff --git a/testdata/ftp.6367428246.expected.txt b/regression-test/ftp.6367428246.expected.txt similarity index 100% rename from testdata/ftp.6367428246.expected.txt rename to regression-test/ftp.6367428246.expected.txt diff --git a/testdata/ftp.6929537410.expected.txt b/regression-test/ftp.6929537410.expected.txt similarity index 100% rename from testdata/ftp.6929537410.expected.txt rename to regression-test/ftp.6929537410.expected.txt diff --git a/testdata/ftp.6929553738.expected.txt b/regression-test/ftp.6929553738.expected.txt similarity index 100% rename from testdata/ftp.6929553738.expected.txt rename to regression-test/ftp.6929553738.expected.txt diff --git a/testdata/mysql-dump.sql b/regression-test/mysql-dump.sql similarity index 100% rename from testdata/mysql-dump.sql rename to regression-test/mysql-dump.sql diff --git a/utils/print_hand.py b/regression-test/print_hand.py similarity index 100% rename from utils/print_hand.py rename to regression-test/print_hand.py diff --git a/testdata/ps-holdem-ring-001to003.txt b/regression-test/ps-holdem-ring-001to003.txt similarity index 100% rename from testdata/ps-holdem-ring-001to003.txt rename to regression-test/ps-holdem-ring-001to003.txt diff --git a/testdata/ps.14519394979.expected.txt b/regression-test/ps.14519394979.expected.txt similarity index 100% rename from testdata/ps.14519394979.expected.txt rename to regression-test/ps.14519394979.expected.txt diff --git a/testdata/ps.14519420999.expected.txt b/regression-test/ps.14519420999.expected.txt similarity index 100% rename from testdata/ps.14519420999.expected.txt rename to regression-test/ps.14519420999.expected.txt diff --git a/testdata/ps.14519433154.expected.txt b/regression-test/ps.14519433154.expected.txt similarity index 100% rename from testdata/ps.14519433154.expected.txt rename to regression-test/ps.14519433154.expected.txt diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh new file mode 100755 index 00000000..cd8fa5df --- /dev/null +++ b/regression-test/regression-test.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +echo "Please note for this to really work you need to work on an empty database" + +rm *.found.txt +../pyfpdb/fpdb_import.py -p$1 --file=ps-holdem-ring-001to003.txt -x +../pyfpdb/fpdb_import.py -p$1 --file=ps-holdem-ring-001to003.txt -x +#../pyfpdb/fpdb_import.py -p$1 --file=ftp-stud-hilo-ring-001.txt -x +#../pyfpdb/fpdb_import.py -p$1 --file=ftp-omaha-hi-pl-ring-001-005.txt -x + +echo "it should've reported first that it stored 3, then that it had 3 duplicates" +#echo " then 1 stored, then 5 stored" + +./print_hand.py -p$1 --hand=14519394979 > ps.14519394979.found.txt && colordiff ps.14519394979.found.txt ps.14519394979.expected.txt +./print_hand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt +./print_hand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt + +./PrintPlayerFlags.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt + +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ftp.6367428246.found.txt && colordiff ftp.6367428246.found.txt ftp.6367428246.expected.txt + +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929537410 > ftp.6929537410.found.txt && colordiff ftp.6929537410.found.txt ftp.6929537410.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929553738 > ftp.6929553738.found.txt && colordiff ftp.6929553738.found.txt ftp.6929553738.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929572212 > ftp.6929572212.found.txt && colordiff ftp.6929572212.found.txt ftp.6929572212.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929576743 > ftp.6929576743.found.txt && colordiff ftp.6929576743.found.txt ftp.6929576743.expected.txt +#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929587483 > ftp.6929587483.found.txt && colordiff ftp.6929587483.found.txt ftp.6929587483.expected.txt + +echo "if everything was printed as expected this worked" diff --git a/utils/regression-test.sh b/utils/regression-test.sh deleted file mode 100755 index c4011f1f..00000000 --- a/utils/regression-test.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh - -#Copyright 2008 Steffen Jobbagy-Felso -#This program is free software: you can redistribute it and/or modify -#it under the terms of the GNU Affero General Public License as published by -#the Free Software Foundation, version 3 of the License. -# -#This program is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -#GNU General Public License for more details. -# -#You should have received a copy of the GNU Affero General Public License -#along with this program. If not, see . -#In the "official" distribution you can find the license in -#agpl-3.0.txt in the docs folder of the package. - -echo "Please note for this to really work you need to work on an empty database" - -rm ../testdata/*.found.txt -../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x -../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ps-holdem-ring-001to003.txt -x -#../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ftp-stud-hilo-ring-001.txt -x -#../pyfpdb/fpdb_import.py -p$1 --file=../testdata/ftp-omaha-hi-pl-ring-001-005.txt -x - -echo "it should've reported first that it stored 3, then that it had 3 duplicates" -#echo " then 1 stored, then 5 stored" - -./print_hand.py -p$1 --hand=14519394979 > ../testdata/ps.14519394979.found.txt && colordiff ../testdata/ps.14519394979.found.txt ../testdata/ps.14519394979.expected.txt -./print_hand.py -p$1 --hand=14519420999 > ../testdata/ps.14519420999.found.txt && colordiff ../testdata/ps.14519420999.found.txt ../testdata/ps.14519420999.expected.txt -./print_hand.py -p$1 --hand=14519433154 > ../testdata/ps.14519433154.found.txt && colordiff ../testdata/ps.14519433154.found.txt ../testdata/ps.14519433154.expected.txt - -./PrintPlayerFlags.py -p$1 > ../testdata/ps-flags-3hands.found.txt && colordiff ../testdata/ps-flags-3hands.found.txt ../testdata/ps-flags-3hands.expected.txt - -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ../testdata/ftp.6367428246.found.txt && colordiff ../testdata/ftp.6367428246.found.txt ../testdata/ftp.6367428246.expected.txt - -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929537410 > ../testdata/ftp.6929537410.found.txt && colordiff ../testdata/ftp.6929537410.found.txt ../testdata/ftp.6929537410.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929553738 > ../testdata/ftp.6929553738.found.txt && colordiff ../testdata/ftp.6929553738.found.txt ../testdata/ftp.6929553738.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929572212 > ../testdata/ftp.6929572212.found.txt && colordiff ../testdata/ftp.6929572212.found.txt ../testdata/ftp.6929572212.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929576743 > ../testdata/ftp.6929576743.found.txt && colordiff ../testdata/ftp.6929576743.found.txt ../testdata/ftp.6929576743.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929587483 > ../testdata/ftp.6929587483.found.txt && colordiff ../testdata/ftp.6929587483.found.txt ../testdata/ftp.6929587483.expected.txt - -echo "if everything was printed as expected this worked" From fb7a8ba9a2dea7aa3ea547b14c5926251ae69880 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 5 Aug 2008 19:55:50 +0100 Subject: [PATCH 011/262] git11 - documentation improved. tv shows 3B4B number of hands in brackets. set correct permissions on docs --- docs/filelist.txt | 0 docs/known-bugs-and-planned-features.txt | 3 - docs/readme-dev.txt | 0 docs/readme-overview.txt | 38 ++++++----- docs/release-notes.txt | 0 docs/requirements.txt | 0 docs/status.txt | 65 ++++++++++--------- .../LibFpdbImport.pm | 0 .../LibFpdbShared.pm | 0 {perlfpdb => ignore-me_perl6}/LibFpdbView.pm | 0 .../RunFpdbCLI.perl6 | 0 pyfpdb/fpdb_simple.py | 2 +- pyfpdb/table_viewer.py | 2 +- 13 files changed, 58 insertions(+), 52 deletions(-) mode change 100755 => 100644 docs/filelist.txt mode change 100755 => 100644 docs/readme-dev.txt mode change 100755 => 100644 docs/readme-overview.txt mode change 100755 => 100644 docs/release-notes.txt mode change 100755 => 100644 docs/requirements.txt mode change 100755 => 100644 docs/status.txt rename {perlfpdb => ignore-me_perl6}/LibFpdbImport.pm (100%) rename {perlfpdb => ignore-me_perl6}/LibFpdbShared.pm (100%) rename {perlfpdb => ignore-me_perl6}/LibFpdbView.pm (100%) rename {perlfpdb => ignore-me_perl6}/RunFpdbCLI.perl6 (100%) diff --git a/docs/filelist.txt b/docs/filelist.txt old mode 100755 new mode 100644 diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 6df5603f..5d4d31cd 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,11 +2,8 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ -PrintPlayerFlag - the actual flags verify at least one PrintPlayerFlags in tv, select from hud table using named fields rather than the current * - -tv: add in brackets number of hands on which 3B4B is based imp/tv bug: handcount is only about 1/2 of what it should be add stud functionality back to imp/tv db+imp+tv WtSD (went to showdown) diff --git a/docs/readme-dev.txt b/docs/readme-dev.txt old mode 100755 new mode 100644 diff --git a/docs/readme-overview.txt b/docs/readme-overview.txt old mode 100755 new mode 100644 index b0711dc5..56e97f14 --- a/docs/readme-overview.txt +++ b/docs/readme-overview.txt @@ -1,16 +1,23 @@ Summary ======= -A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Support Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future +A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future -Longer summary -============== -This program is poker tracking software, a class of utilities that will record and track every little detail of your poker sessions. Want to know how many times player Y has raised before the flop at your current stakes? This program will tell you - allowing you to make much more accurate reads (poker slang for "[educated] guessing your opponents hand") and as a result better decisions. Of course in poker the end result in $ is never guaranteed, but this software will make it much easier for you. -The software currently supports importing and processing hand history files for: -- Holdem, Omaha (Hi and Hi/Lo), Stud (Hi and Hi/Lo) and Razz (I am not aware of any modern software that supports more than Holdem) -- No Limit, Pot Limit, Fixed Limit and Full Tilt's CapNL and CapPL -- Any stakes -- Cash games, Sit and Gos and Multi Table Tourneys -- All the above from PokerStars, all the above except SnG/Tourney for Full Tilt +Contact +======= +mail: steffen(at)sycamoretest.info +jabber/xmpp/Google Talk: as above +ICQ: 7806355 +MSN: steffenjf@gmx.de (don't email that) + +You can also create tickets on our assembla page at todo get link + +But you could send all my hand histories to yourself! +===================================================== +At the end of the day this comes down to a question of trust, but unlike Windows and the poker client software you don't have to trust fpdb blindly. You can: +- Verify the source code yourself. +- Convince or pay someone to verify the source code for you. +- Use a personal firewall to completely block fpdb from the Internet +- (for the uber-paranoid) Get yourself the virtualisation software VirtualBox, set up a VM (virtual machine) to run fpdb but run the poker software on your real PC. Then cut the VM off the Internet, fpdb doesn't need it. If you have a PC made in the last few years this should run fast enough as well. Note that most Windows licenses do NOT permit you to use two Windows installations at once, even if they are on the same PC. Installing ========== @@ -45,27 +52,26 @@ Why Free Software? ================== This program is released under the terms of the free/libre software license AGPL3 as released by the FSF. The AGPL3 protects your rights and those of the wider community. As Richard Stallman, one of the founders of the free software movement, put it: "Free software is a matter of liberty, not price. To understand the concept, you should think of free as in free speech, not as in free beer." (well, it is both really, like the right to vote used to be free) -For example, a "pirated" copy of proprietary software X is free of charge, but you don't actually have a legal right to use it, you don't have any possibility to fix its bugs and you certainly don't have any legal right to share it with your friends. You also won't be able to get support, often not even security fixes. Actually, even if you pay hundreds of pounds for your program they deny your right to fix their errors for them. Imagine buying a car where you're not permitted (under threat of jail) to replace parts.. +For example, a "pirated" copy of proprietary software X is free of charge, but you don't actually have a legal right to use it, you don't have any possibility to fix its bugs and you certainly don't have any legal right to share it with your friends. You also won't be able to get support, often not even security fixes. Actually, even if you pay hundreds of pounds for your program they deny your right to fix their errors for them. Imagine buying a car where you're not permitted (under threat of jail) to replace broken parts.. With free/libre software (also known as open source software, or short FOSS or FLOSS) on the other hand you get all these freedoms: -(note: the legally binding terms are in the license text, this is merely an amateur summary so normal people don't have to read pages of silly legalese) +(note: the legally binding terms are in the license text, this is merely an amateur summary so normal people don't have to read pages of legalese) -Freedom 0: The freedom to use: To run the program, for any purpose. +Freedom 0: The freedom to use: To run the program, for any purpose. Free of Charge. Freedom 1: The freedom to study and help yourself. This freedom guarantees your right to study and learn from the source code of the program, and to fix it if it is broken. If you're not a programmer yourself the developers will generally be happy to fix it for you. Failing that you can always pay someone from the money you saved on not having to pay for it. Freedom 2: The freedom to be a decent human being and help your neighbour: I don't threaten you with criminal charges or jail time if you share with your friends and neighbours, subject to the very modest restrictions of the AGPL3. Freedom 3: The freedom to improve the program and release your improvements to the public (or parts thereof) so that the whole community benefits. Note that you are PERMITTED, but not REQUIRED to distribute your changes. If you do distribute your changes you must do so under the terms of the AGPL3 however. Note that this is the license - I retain full copyright over my code, including the right to change the license for future versions. I do not intend to do this however. In any case, any version I released under AGPL3 remains available under that license forever, or more accurately until my copyright expires at which point it goes into the public domain. -I reject the concept of software patents as a crime and under the European Patent Agreement software patents - even if you mislabel them as "computer-implemented inventions" or whatever - are explicitly prohibited. This view has been uphelp by numerous courts including the highest German appeals courts for patent matters (BGH-Zivilsenat?). -I therefore do not recognise the validity or applicability of any software patent. I realise that the European Patent Office completely ignores the laws and treaties that govern its operation but that means that EPO management are criminals, not that software patents are valid. +I reject the concept of software patents as a crime and under the European Patent Agreement software patents - even if you mislabel them as "computer-implemented inventions" or whatever - are explicitly prohibited. Can I get/use this under a different license? ============================================= The short answer: Maybe. The long one: As detailed, I fully support what the FSF does and aims to achieve with the GPL. However, I realise that many free software developers don't object to closed source, some don't even object to closed source profiteering of their charity, and I don't think I have any right to go and tell them they're wrong. So if anyone wishes to use all or part of my code in another open source project with an AGPL3-incompatible license then let me know and we should be able to come to an agreement. -If you wish to use all or part of this in closed source let me know how much that is worth to you :) +If you wish to use all or part of this in closed source let me know how much that is worth to you, I support free software but at the end of the day you can't pay rent with code ;) Disclaimer, License of this Document diff --git a/docs/release-notes.txt b/docs/release-notes.txt old mode 100755 new mode 100644 diff --git a/docs/requirements.txt b/docs/requirements.txt old mode 100755 new mode 100644 diff --git a/docs/status.txt b/docs/status.txt old mode 100755 new mode 100644 index fb7c0f3c..1a27c912 --- a/docs/status.txt +++ b/docs/status.txt @@ -1,52 +1,52 @@ -For all support please note that the tables will almost certainly be changed without keeping backwards compatibility. Therefore you should keep your history files after import so you can re-import if necessary. +For all support please note that the tables WILL be changed, almost certainly without keeping backwards compatibility. Therefore you should keep your history files after import so you can re-import when necessary. Should you lose history files and need a "database updater" let me know. If support for another site/game would encourage you to help with this software please let me know at steffen@sycamoretest.info. -Note for git100 until further notice (not long though): deactivated all import of stud/razz pending update of that code for the new caching tables. +IMPORTANT: git10 and below contain at least one significant bug that cause wrong data to be displayed. I wrote a new query script for testing so I can track this down. -Database -======== -Holdem, Omaha Hi/HiLo, Razz and Stud Hi/HiLo in NL/PL/FL are supported for ring games and tourneys. Actual storage tables are nearly final. Support/cache tables for improved viewer speed are in heavy flux. +Holdem/Omaha ring games on PokerStars +====================================== +Works, quite well tested, works in table viewer and is useful now. Functionality is quite limited so far though. This includes the holdem/omaha parts of HORSE, HOSE and any other mixed game -Importer (imp) -============== -Ringgames on FTP and PS: -Holdem, Omaha Hi/HiLo, Razz and Stud Hi/HiLo in NL/PL/FL/CapNL/CapPL: beta - This should be pretty much done, I imported 30k hands. - Bug reports welcome, please send the hand that caused the error. +Stud/Razz ring games on PokerStars +================================== +Broken - used to work, hardly tested. Needs to be updated to new infrastructure using a HudData table rather than hands_players_flags. Will fix this shortly. -Tourney support: on PS alpha, on FTP incomplete, see svn comments. +A note on No Limit and Pot Limit +================================ +Fpdb fully supports NL/PL, however currently only as far as it does limit games. So it'll tell you how often a player has bet, but not how much. But the mate I'm writing this for doesnt play NL/PL (and neither do I) so volunteers would be most welcome. -Mixed Games (e.g. HORSE): alpha - -GUI for importer +Tournaments/SnGs ================ -alpha -Most functionality still missing, but thats a backend problem not really a GUI thing. +Broken - used to work, hardly tested. Needs to be updated to new infrastructure.. Not sure when I'll fix this, volunteers welcome :) +Independent of the current broken state at the moment tourney support would only show information as if a tourney is a ring game - but it's not. If you play tournaments and would like to help improving this let me know, I probably won't bother. -CLI/automated testing interface -=============================== -Simple DB Statistics Script -> migrate to GUI -Simple player stats report script -> migrate to GUI -Hand printout script -> badly needs updating -Simple regression testing script done. Need to updating existing data and create (much) more. +Full Tilt Poker +=============== +Not sure - it should import them, but I believe the table viewer won't work. Should only take minutes to fix this so I'll probably do it soon. -Table Viewer (tv) -================= -pre-alpha -Currently the importer fills a flags table, and tv selects an id field from that and does some things to create the data. Thinking of instead or additionally creating a count table that imp would also directly access. Imp would increment the values for each player that tv currently has to select for - implementing this now, as of git100. -I know tv is rather low on functionality so far, but as soon as I've finished the above update adding more will be easy and quick. +Other Sites +=========== +Patches/Volunteers welcome. + +GUI +=== +Alpha - the infrastructure stands and is fast enough, now "just" need to add more functionality. HUD === -? +Not started - volunteers welcome. I found a *nix utility based on xlib that does half the work for this (most of the other half is already in the table viewer), see http://www.roard.com/docs/cookbook/cbsu19.html. There's also a python implementation of xlib which should run even on Windows. Packaging ========= Gentoo GNU/Linux: I'll make ebuilds, if you want one now let me know. -Other free/libre systems (e.g. other GNU/Linux-Distros, *BSD): Let me know what you use (OS/Distribution+version, arch) and I'll make a package. +Other free/libre systems (e.g. other GNU/Linux-Distros, *BSD, and I'd love to help in getting this to work on any Hurd based system): Let me know what you use (Distribution, version and CPU type) and I'll make a package. Windows/MacOSX: Manual installation of dependencies for now, integrated installer would be nice - volunteers welcome, but I'll do it eventually in any case. +Regression Testing +================== +The query scripts are done-ish, need to verify more data so it can be used in automated regression testing. + Legal ===== General: This will offer more or less the same kind of functionality as HM and PT so there shouldn't be any problem on any site that allows tracking. @@ -54,9 +54,12 @@ PS: I have asked them and they said that due to the fast changing nature of pre-release software they don't review that. However, looking at their posted guidelines we're fine FTP: I sent them an email and what I described there was ok for them. +I will write them again for "certification" on release of 1.0 + +To avoid any misunderstandings, PS and FTP obviously do not sponsor, endorse, or support fpdb. They allow using it, that's all. Any problems with fpdb should be reported to us, not the poker site. A note on viewing/tracking mucked hands. Many people will say this is cheating. I agree. However, I don't make the rules on these sites. The sites that store mucked hands into the history file clearly think that being able to see other people's mucked hands is ok. This software - like HM and PT - merely takes info provided by the poker site and makes it more easily accessible. -Apparently PS has now turned this off. +Apparently PS has now turned this off :) License ======= diff --git a/perlfpdb/LibFpdbImport.pm b/ignore-me_perl6/LibFpdbImport.pm similarity index 100% rename from perlfpdb/LibFpdbImport.pm rename to ignore-me_perl6/LibFpdbImport.pm diff --git a/perlfpdb/LibFpdbShared.pm b/ignore-me_perl6/LibFpdbShared.pm similarity index 100% rename from perlfpdb/LibFpdbShared.pm rename to ignore-me_perl6/LibFpdbShared.pm diff --git a/perlfpdb/LibFpdbView.pm b/ignore-me_perl6/LibFpdbView.pm similarity index 100% rename from perlfpdb/LibFpdbView.pm rename to ignore-me_perl6/LibFpdbView.pm diff --git a/perlfpdb/RunFpdbCLI.perl6 b/ignore-me_perl6/RunFpdbCLI.perl6 similarity index 100% rename from perlfpdb/RunFpdbCLI.perl6 rename to ignore-me_perl6/RunFpdbCLI.perl6 diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index e2204ec4..1b04c9ea 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1303,7 +1303,7 @@ def calculateHudImport(player_ids, category, action_types): #add each value to the appropriate array VPIP.append(myVPIP) PFR.append(myPFR) - PF3B4BChance.append(PF3B4BChance) + PF3B4BChance.append(myPF3B4BChance) PF3B4B.append(myPF3B4B) sawFlop.append(mySawFlop) sawTurn.append(mySawTurn) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 7b94b5f3..ca788bf5 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -77,7 +77,7 @@ class table_viewer (threading.Thread): tmp.append(str(row[4]))#Hands tmp.append(self.hudDivide(row[5],row[4])) #VPIP tmp.append(self.hudDivide(row[6],row[4])) #PFR - tmp.append(self.hudDivide(row[8],row[7])) #PF3B4B + tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B tmp.append(self.hudDivide(row[13],row[9])+" ("+str(row[9])+")") #AF tmp.append(self.hudDivide(row[17],row[16])+" ("+str(row[16])+")") #FF tmp.append(self.hudDivide(row[14],row[10])+" ("+str(row[10])+")") #AT From 5ee559474143d9077109cd0dd0d01a7f0f0f25f8 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 6 Aug 2008 21:09:29 +0100 Subject: [PATCH 012/262] git12 - added percent sign to tv display removed debug prints that im not going to touch for a while. --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/fpdb.py | 16 +++++++-------- pyfpdb/fpdb_db.py | 4 ++-- pyfpdb/table_viewer.py | 26 ++++++++++++------------ 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 5d4d31cd..86bafb1a 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,6 +2,7 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ +colour in tv verify at least one PrintPlayerFlags in tv, select from hud table using named fields rather than the current * imp/tv bug: handcount is only about 1/2 of what it should be diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index b13166ab..e4d2458b 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -30,7 +30,7 @@ import table_viewer class fpdb: def tab_clicked(self, widget, tab_name): """called when a tab button is clicked to activate that tab""" - print "start of tab_clicked" + #print "start of tab_clicked" self.display_tab(tab_name) #end def tab_clicked @@ -42,7 +42,7 @@ class fpdb: def add_tab(self, new_tab, new_tab_name): """adds a tab, namely creates the button and displays it and appends all the relevant arrays""" - print "start of add_tab" + #print "start of add_tab" for i in self.tab_names: #todo: check this is valid if i==new_tab_name: raise fpdb_simple.FpdbError("duplicate tab_name not permitted") @@ -59,11 +59,11 @@ class fpdb: def display_tab(self, new_tab_name): """displays the indicated tab""" - print "start of display_tab, len(self.tab_names):",len(self.tab_names) + #print "start of display_tab, len(self.tab_names):",len(self.tab_names) tab_no=-1 #if len(self.tab_names)>1: for i in range(len(self.tab_names)): - print "display_tab, new_tab_name:",new_tab_name," self.tab_names[i]:", self.tab_names[i] + #print "display_tab, new_tab_name:",new_tab_name," self.tab_names[i]:", self.tab_names[i] if (new_tab_name==self.tab_names[i]): tab_no=i #self.tab_buttons[i].set_active(False) @@ -309,7 +309,7 @@ class fpdb: def tab_bulk_import(self, widget, data): """opens a tab for bulk importing""" - print "start of tab_bulk_import" + #print "start of tab_bulk_import" new_import_thread=import_threaded.import_threaded(self.db, self.bulk_import_default_path) self.threads.append(new_import_thread) bulk_tab=new_import_thread.get_vbox() @@ -318,7 +318,7 @@ class fpdb: def tab_main_help(self, widget, data): """Displays a tab with the main fpdb help screen""" - print "start of tab_main_help" + #print "start of tab_main_help" mh_tab=gtk.Label("""Welcome to Fpdb blabla todo make this read a file for the helptext blabla @@ -328,7 +328,7 @@ blabla""") def tab_table_viewer(self, widget, data): """opens a table viewer tab""" - print "start of tab_table_viewer" + #print "start of tab_table_viewer" new_tv_thread=table_viewer.table_viewer(self.db) self.threads.append(new_tv_thread) tv_tab=new_tv_thread.get_vbox() @@ -343,7 +343,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB") + self.window.set_title("Free Poker DB - version: pre-alpha, git12") self.window.set_border_width(1) self.window.set_size_request(600,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index cf74dd35..1cd3e4cc 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -37,7 +37,7 @@ class fpdb_db: #print "fpdb_db.connect, password:",password,"/end" if backend==self.MYSQL_INNODB: import MySQLdb - print "fpdb_db.connect, host:", host, " user:", user, " passwd:", password, " db:", database + #print "fpdb_db.connect, host:", host, " user:", user, " passwd:", password, " db:", database self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) elif backend==self.PGSQL: import pgdb @@ -73,7 +73,7 @@ class fpdb_db: def reconnect(self, due_to_error=False): """Reconnects the DB""" - print "started fpdb_db.reconnect" + #print "started fpdb_db.reconnect" self.disconnect(due_to_error) self.connect(self.backend, self.host, self.database, self.user, self.password) #end def disconnect diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index ca788bf5..596fd11a 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -29,13 +29,13 @@ class table_viewer (threading.Thread): if b==0: return "n/a" else: - return str(int((a/float(b))*100)) + return str(int((a/float(b))*100))+"%" return "todo" #end def hudDivide def browse_clicked(self, widget, data): """runs when user clicks browse on tv tab""" - print "start of table_viewer.browser_clicked" + #print "start of table_viewer.browser_clicked" current_path=self.filename_tbuffer.get_text(self.filename_tbuffer.get_start_iter(), self.filename_tbuffer.get_end_iter()) dia_chooser = gtk.FileChooserDialog(title="Please choose the file for which you want to open the Table Viewer", @@ -56,7 +56,7 @@ class table_viewer (threading.Thread): def prepare_data(self): """prepares the data for display by refresh_clicked, returns a 2D array""" - print "start of prepare_data" + #print "start of prepare_data" arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): @@ -91,7 +91,7 @@ class table_viewer (threading.Thread): def refresh_clicked(self, widget, data): """runs when user clicks refresh""" - if self.debug: print "start of table_viewer.refresh_clicked" + #print "start of table_viewer.refresh_clicked" arr=self.prepare_data() try: self.data_table.destroy() @@ -109,37 +109,37 @@ class table_viewer (threading.Thread): def read_names_clicked(self, widget, data): """runs when user clicks read names""" - print "start of table_viewer.read_names_clicked" - print "self.last_read_hand:",self.last_read_hand + #print "start of table_viewer.read_names_clicked" + #print "self.last_read_hand:",self.last_read_hand self.db.reconnect() self.cursor=self.db.cursor self.cursor.execute("""SELECT id FROM hands WHERE site_hand_no=%s""", (self.last_read_hand)) hands_id_tmp=self.db.cursor.fetchone() - print "tmp:",hands_id_tmp + #print "tmp:",hands_id_tmp self.hands_id=hands_id_tmp[0] self.db.cursor.execute("SELECT gametype_id FROM hands WHERE id=%s", (self.hands_id, )) self.gametype_id=self.db.cursor.fetchone()[0] self.cursor.execute("SELECT category FROM gametypes WHERE id=%s", (self.gametype_id, )) self.category=self.db.cursor.fetchone()[0] - print "self.gametype_id", self.gametype_id," category:", self.category, " self.hands_id:", self.hands_id + #print "self.gametype_id", self.gametype_id," category:", self.category, " self.hands_id:", self.hands_id self.db.cursor.execute("""SELECT DISTINCT players.id FROM hands_players INNER JOIN players ON hands_players.player_id=players.id WHERE hand_id=%s""", (self.hands_id, )) self.player_ids=self.db.cursor.fetchall() - print "self.player_ids:",self.player_ids + #print "self.player_ids:",self.player_ids self.db.cursor.execute("""SELECT DISTINCT players.name FROM hands_players INNER JOIN players ON hands_players.player_id=players.id WHERE hand_id=%s""", (self.hands_id, )) self.player_names=self.db.cursor.fetchall() - print "self.player_names:",self.player_names + #print "self.player_names:",self.player_names #end def table_viewer.read_names_clicked def import_clicked(self, widget, data): """runs when user clicks import""" - print "start of table_viewer.import_clicked" + #print "start of table_viewer.import_clicked" self.inputFile=self.filename_tbuffer.get_text(self.filename_tbuffer.get_start_iter(), self.filename_tbuffer.get_end_iter()) self.server=self.db.host @@ -157,7 +157,7 @@ class table_viewer (threading.Thread): def all_clicked(self, widget, data): """runs when user clicks all""" - print "start of table_viewer.all_clicked" + #print "start of table_viewer.all_clicked" self.import_clicked(widget, data) self.read_names_clicked(widget, data) self.refresh_clicked(widget, data) @@ -171,7 +171,7 @@ class table_viewer (threading.Thread): def __init__(self, db, debug=True): """Constructor for table_viewer""" self.debug=debug - if self.debug: print "start of table_viewer constructor" + #print "start of table_viewer constructor" self.db=db self.cursor=db.cursor From 9ca400b4abbe63f5de84bec74311a55d092004de Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 6 Aug 2008 21:57:02 +0100 Subject: [PATCH 013/262] git13 - tv now displays the data over a range of activeSeats. see table_viewer.py lines 75-84 for ranges. added version string to GUI made GUI a bit wider to accomodate wider t --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/fpdb.py | 4 +-- pyfpdb/table_viewer.py | 32 ++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 86bafb1a..be64ebaf 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -86,6 +86,7 @@ benchmark properly on mysql innodb, mysql myisam, postgresql, sqlite, more? rename things like this: ClassName.methodName and variableName. do this on tables too. update codingstyle CLI (not ncurses, proper CLI) equivalent for fpdb.py optimise/simplify storing by creating the SQL statements depending on hand rather than calling different methods +make range of activeSeats configurable for tv/hud License ======= diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index e4d2458b..30f306a0 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,9 +343,9 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git12") + self.window.set_title("Free Poker DB - version: pre-alpha, git13") self.window.set_border_width(1) - self.window.set_size_request(600,400) + self.window.set_size_request(700,400) self.window.set_resizable(True) self.menu_items = ( diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 596fd11a..958dddea 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -71,8 +71,36 @@ class table_viewer (threading.Thread): tmp=[] tmp.append(self.player_names[player][0]) - self.cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s", (self.gametype_id, self.player_ids[player][0], len(self.player_names))) - row=self.cursor.fetchone() + seatCount=len(self.player_names) + if seatCount>=8: + minSeats,maxSeats=7,10 + elif seatCount==7: + minSeats,maxSeats=6,9 + elif seatCount==6 or seatCount==5: + minSeats,maxSeats=seatCount-1,seatCount+1 + elif seatCount==4: + minSeats,maxSeats=4,5 + elif seatCount==2 or seatCount==3: + minSeats,maxSeats=seatCount,seatCount + else: + fpdb_simple.FpdbError("invalid seatCount") + + self.cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats>=%s AND activeSeats<=%s", (self.gametype_id, self.player_ids[player][0], minSeats, maxSeats)) + rows=self.cursor.fetchall() + + row=[] + for field_no in range(len(rows[0])): + row.append(rows[0][field_no]) + + for row_no in range(len(rows)): + if row_no==0: + pass + else: + for field_no in range(len(rows[row_no])): + if field_no<=3: + pass + else: + row[field_no]+=rows[row_no][field_no] tmp.append(str(row[4]))#Hands tmp.append(self.hudDivide(row[5],row[4])) #VPIP From 39f5b9095c237ffa38f483ebe3f280c83119abb8 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 7 Aug 2008 00:17:51 +0100 Subject: [PATCH 014/262] git14 - finished one .expected for the HUD data table. seems to reveal a total of 5 errors - ouch changed range of tv-activeSeats on user feedback - will do this occasionally until i make it configurable "fix": "imp/tv bug: handcount is only about 1/2 of what it should be" -> this was NOT a bug. I was just a bit silly and didn't connect the dots from activating a filter by number of active players and the sudden drop of hands. --- docs/known-bugs-and-planned-features.txt | 27 ++++++++++-------- pyfpdb/fpdb.py | 2 +- pyfpdb/table_viewer.py | 2 +- regression-test/ps-flags-3hands.expected.txt | 30 ++++++++++++++++++++ regression-test/regression-test.sh | 2 +- 5 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 regression-test/ps-flags-3hands.expected.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index be64ebaf..0e3b56c5 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,10 +2,11 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ -colour in tv -verify at least one PrintPlayerFlags -in tv, select from hud table using named fields rather than the current * -imp/tv bug: handcount is only about 1/2 of what it should be +verify at least two PrintPlayerFlags (one of them with 10+ hands) +fix fold % in tv +optionally show single postflop agg/fold rate + +colour in tv, or at least seperation lines add stud functionality back to imp/tv db+imp+tv WtSD (went to showdown) db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) @@ -13,21 +14,22 @@ db+imp+tv WwSF (Won when seen flop - partial taken into account) change action_no to be total for this street rather than just for one player properly read 3B/4B percentage -fix load profile fix tv browse button size -tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem -tourney bug: fails recognisePlayer -tourney bug: fails with tuple error in recogniseplayerid -config wizard catch index error, type error, file not found error -update install instructions +update install instructions, include how to adapt default config and where to put it split python requirements, get deep links for windows DL for everything implement error file in importer -remove mysql/myisam support. -ideally HUD before beta =========== +in tv, select from hud table using named fields rather than the current * +remove remains of mysql/myisam support. +tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem +tourney bug: fails recognisePlayer +tourney bug: fails with tuple error in recogniseplayerid +fix load profile +HUD +config wizard file permission script, use games group change stud street storage from 3-7 to 0-4 throughout make bulk importer display a grand total in the GUI @@ -46,6 +48,7 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== +move version into seperate file HTMLify docs and validate them cut down action_types array size to appropriate length make tv work with ftp e.g. by making importer return site as well (easy) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 30f306a0..5b30882c 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,7 +343,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git13") + self.window.set_title("Free Poker DB - version: pre-alpha, git14") self.window.set_border_width(1) self.window.set_size_request(700,400) self.window.set_resizable(True) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 958dddea..58280909 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -75,7 +75,7 @@ class table_viewer (threading.Thread): if seatCount>=8: minSeats,maxSeats=7,10 elif seatCount==7: - minSeats,maxSeats=6,9 + minSeats,maxSeats=6,10 elif seatCount==6 or seatCount==5: minSeats,maxSeats=seatCount-1,seatCount+1 elif seatCount==4: diff --git a/regression-test/ps-flags-3hands.expected.txt b/regression-test/ps-flags-3hands.expected.txt new file mode 100644 index 00000000..cf491638 --- /dev/null +++ b/regression-test/ps-flags-3hands.expected.txt @@ -0,0 +1,30 @@ +Connected to MySQL on localhost. Print Player Flags Utility + +Basic Data +========== +bigblind: 2 category: holdem limitType: fl name: Player_1 gameType: ring site: PokerStars +siteId: 2 gametypeId: 1 playerId: 1 hudDataId: 1 + +HUD Raw Hand Counts +=================== +HDs: 3 +VPIP: 1 +PFR: 0 +PF3B4BChance: 1 +PF3B4B: 0 + +sawFlop: 1 +sawTurn: 1 +sawRiver: 1 +sawShowdown: 0 + +raisedFlop: 0 +raisedTurn: 0 +raisedRiver: 0 + +otherRaisedFlop: 1 +otherRaisedFlopFold: 0 +otherRaisedTurn: 1 +otherRaisedTurnFold: 0 +otherRaisedRiver: 1 +otherRaisedRiverFold: 1 diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index cd8fa5df..309e0469 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -30,7 +30,7 @@ echo "it should've reported first that it stored 3, then that it had 3 duplicate ./print_hand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt ./print_hand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt -./PrintPlayerFlags.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt +./PrintPlayerHudData.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ftp.6367428246.found.txt && colordiff ftp.6367428246.found.txt ftp.6367428246.expected.txt From 490af1acafef41dec205341bd885ed6c592158cb Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 7 Aug 2008 11:08:50 +0100 Subject: [PATCH 015/262] git15 - fixed bug that it read sawShowdown wrong. fixed postflop fold importing. fairly certain importer is running correctly now :) also added SD/F, the percentage of the time the user saw the showdown when they saw the flop. I believe this is normally called WtSD, but this is clearer. widened main GUI a bit more the todolist is still growing hehe --- docs/known-bugs-and-planned-features.txt | 38 +++++------ docs/status.txt | 8 +-- pyfpdb/fpdb.py | 4 +- pyfpdb/fpdb_simple.py | 80 ++++++++++++++++-------- pyfpdb/table_viewer.py | 8 +-- 5 files changed, 82 insertions(+), 56 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 0e3b56c5..376f1591 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,26 +2,27 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ -verify at least two PrintPlayerFlags (one of them with 10+ hands) -fix fold % in tv -optionally show single postflop agg/fold rate - -colour in tv, or at least seperation lines -add stud functionality back to imp/tv -db+imp+tv WtSD (went to showdown) -db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) -db+imp+tv WwSF (Won when seen flop - partial taken into account) -change action_no to be total for this street rather than just for one player - -properly read 3B/4B percentage +verify PrintPlayerFlags for one player on at least 10 hands fix tv browse button size +optionally show single postflop agg/fold rate +seperation lines for tv +fix default pathes up to sensible ones catch index error, type error, file not found error update install instructions, include how to adapt default config and where to put it split python requirements, get deep links for windows DL for everything +license info + +db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) +db+imp+tv WwSF (Won when seen flop - partial taken into account) +change action_no to be total for this street rather than just for one player. change .expected.txt files accordingly. +calculate 3B/4B percentage (depends on above, currently its useless) +auto-import implement error file in importer before beta =========== +change to use different colours according to classification. +add stud, razz and tourney back to imp/tv but with less seperate codepathes in tv, select from hud table using named fields rather than the current * remove remains of mysql/myisam support. tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem @@ -37,21 +38,21 @@ change save_to_db into one method and probably move into parse_logic Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu make a quick benchmark of mysql and postgresql: import of my whole db, some tableviewer refreshes with and without updated file db+imp+tv steal blind from btn, co, lmp. fold SB/BB/BI to steal +db+imp+tv cb/2nd/3rd barrel, fold to them. Make tab and enter work as sensible in GUIs and implement Ctrl+Q, Ctrl+X and Alt+F4 for close. use profile file for bulk import and table viewer settings and pathes handle errors properly, in particular wrt to SQL rollback. check that we read sitout correctly in: Full Tilt Poker Game #6150325318: Table Bogside setup database, database-user and permission from GUI. update prepare-git to check for license header and copyright. -change/expand print_hand to cover everything new and update verified hands' .found file verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== move version into seperate file +make option to use "traditional" labels, e.g. WtSD instead of SD/F HTMLify docs and validate them cut down action_types array size to appropriate length -make tv work with ftp e.g. by making importer return site as well (easy) make the gui display errors log file move directory import code from gui to backend @@ -61,24 +62,23 @@ Doesn't handle Daylight Saving Time (I don't think at least) Need to store if someone goes all-in, particularly for better NL/PL support. verify at least 3 hands per category per site per limit_type (when cap then do 2 normal and one 1 capped) incl tv display put lines in tv to make it easier to read -speed up so that refresh after importing my whole DB takes no more than 10 seks on my P3M-800 (a quick run on git5+ indicates this is ok now), or 5 with remote DB +ensure that refresh still takes no more than 10 seks on my P3M-800 (a quick run on git15 indicates this is ok now), or 5 with remote DB select range of stakes and sng/mtt values and types for tv change "for i" to more sensible var name instead of i -in all importer: stop doing if site=="ftp", make class constants for site_id instead recognise somewhere if a file is still active and if so keep it open and only read new hands rather than detecting dupes gentoo ebuild separate all gui and all processing into files that are named accordingly ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. why do we have to reconnect in tv.read_names_clicked? -HUD for PokerStars -HTMLify docs can wait till 1.x ================= +positional stats in HUD +return full ftp functionality +in all importer: stop doing if site=="ftp", make class constants for site_id instead finish cleaning tabledesign html code rearrange huddata fields It treats fold due to disconnect as voluntary fold which is not ideal -auto-import check for unnecessary db.commit() aliases Probably PartyPoker for all or most supported games diff --git a/docs/status.txt b/docs/status.txt index 1a27c912..0de3ba43 100644 --- a/docs/status.txt +++ b/docs/status.txt @@ -18,16 +18,16 @@ Fpdb fully supports NL/PL, however currently only as far as it does limit games. Tournaments/SnGs ================ -Broken - used to work, hardly tested. Needs to be updated to new infrastructure.. Not sure when I'll fix this, volunteers welcome :) -Independent of the current broken state at the moment tourney support would only show information as if a tourney is a ring game - but it's not. If you play tournaments and would like to help improving this let me know, I probably won't bother. +Broken - used to work, hardly tested. Needs to be updated to new infrastructure.. Will probably fix this quite soon. +Independent of the current brokenness the tourney support would only show information as if a tourney is a ring game - but it's not. If you play tournaments and would like to help improving this let me know, I probably won't bother. Full Tilt Poker =============== -Not sure - it should import them, but I believe the table viewer won't work. Should only take minutes to fix this so I'll probably do it soon. +Not sure - it should import them, but I believe the table viewer won't work. Would be easy to fix though, volunteers welcome Other Sites =========== -Patches/Volunteers welcome. +Patches/Volunteers welcome. Should be quite easy. GUI === diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 5b30882c..a472368a 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,9 +343,9 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git14") + self.window.set_title("Free Poker DB - version: pre-alpha, git15") self.window.set_border_width(1) - self.window.set_size_request(700,400) + self.window.set_size_request(750,400) self.window.set_resizable(True) self.menu_items = ( diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 1b04c9ea..4249c86f 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1230,21 +1230,21 @@ def calculateHudImport(player_ids, category, action_types): #set default values myVPIP=False myPFR=False - myPF3B4BChance=False #todo + myPF3B4BChance=False myPF3B4B=False - mySawFlop=False #todo - mySawTurn=False #todo - mySawRiver=False #todo - mySawShowdown=False #todo - myRaisedFlop=False #todo - myRaisedTurn=False #todo - myRaisedRiver=False #todo - myOtherRaisedFlop=False #todo - myOtherRaisedFlopFold=False #todo - myOtherRaisedTurn=False #todo - myOtherRaisedTurnFold=False #todo - myOtherRaisedRiver=False #todo - myOtherRaisedRiverFold=False #todo + mySawFlop=False + mySawTurn=False + mySawRiver=False + mySawShowdown=False + myRaisedFlop=False + myRaisedTurn=False + myRaisedRiver=False + myOtherRaisedFlop=False + myOtherRaisedFlopFold=False + myOtherRaisedTurn=False + myOtherRaisedTurnFold=False + myOtherRaisedRiver=False + myOtherRaisedRiverFold=False #calculate preflop values street=0 @@ -1257,7 +1257,7 @@ def calculateHudImport(player_ids, category, action_types): myVPIP=True if heroPfRaiseCount>=1: myPFR=True - if heroPfRaiseCount>=2:#todo: this doesnt catch all 3B4B + if heroPfRaiseCount>=2: myPF3B4B=True #calculate saw* values @@ -1267,9 +1267,10 @@ def calculateHudImport(player_ids, category, action_types): mySawTurn=True if (len(action_types[3][player])>0): mySawRiver=True + mySawShowdown=True for count in range (len(action_types[3][player])): if action_types[3][player][count]=="fold": - mySawShowdown=True + mySawShowdown=False #flop stuff street=1 @@ -1285,21 +1286,46 @@ def calculateHudImport(player_ids, category, action_types): for countOther in range (len(action_types[street][otherPlayer])): if action_types[street][otherPlayer][countOther]=="bet": myOtherRaisedFlop=True - for countOtherFold in range (len(action_types[street][otherPlayer])): - if action_types[street][otherPlayer][countOtherFold]=="fold": + for countOtherFold in range (len(action_types[street][player])): + if action_types[street][player][countOtherFold]=="fold": myOtherRaisedFlopFold=True - #turn stuff: todo - for count in range(len(action_types[2][player])): - if action_types[2][player][count]=="bet": - myRaisedTurn=True + #turn stuff - copy of flop with different vars + street=2 + if mySawTurn: + for count in range(len(action_types[street][player])): + if action_types[street][player][count]=="bet": + myRaisedTurn=True + + for otherPlayer in range (len(player_ids)): + if player==otherPlayer or myOtherRaisedTurn: + pass + else: + for countOther in range (len(action_types[street][otherPlayer])): + if action_types[street][otherPlayer][countOther]=="bet": + myOtherRaisedTurn=True + for countOtherFold in range (len(action_types[street][player])): + if action_types[street][player][countOtherFold]=="fold": + myOtherRaisedTurnFold=True - #river stuff: todo - for count in range(len(action_types[3][player])): - if action_types[3][player][count]=="bet": - myRaisedRiver=True + #turn stuff - copy of flop with different vars + street=3 + if mySawRiver: + for count in range(len(action_types[street][player])): + if action_types[street][player][count]=="bet": + myRaisedRiver=True + + for otherPlayer in range (len(player_ids)): + if player==otherPlayer or myOtherRaisedRiver: + pass + else: + for countOther in range (len(action_types[street][otherPlayer])): + if action_types[street][otherPlayer][countOther]=="bet": + myOtherRaisedRiver=True + for countOtherFold in range (len(action_types[street][player])): + if action_types[street][player][countOtherFold]=="fold": + myOtherRaisedRiverFold=True - #add each value to the appropriate array VPIP.append(myVPIP) PFR.append(myPFR) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 58280909..7065cafd 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -30,7 +30,6 @@ class table_viewer (threading.Thread): return "n/a" else: return str(int((a/float(b))*100))+"%" - return "todo" #end def hudDivide def browse_clicked(self, widget, data): @@ -60,10 +59,10 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F") else: - raise fpdb_simple.FpdbError("todo reimplement stud") - tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7") + raise fpdb_simple.FpdbError("reimplement stud") + tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7", "SD/4") arr.append(tmp) #then the data rows @@ -112,6 +111,7 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[19],row[18])+" ("+str(row[18])+")") #FT tmp.append(self.hudDivide(row[15],row[11])+" ("+str(row[11])+")") #AR tmp.append(self.hudDivide(row[21],row[20])+" ("+str(row[20])+")") #FR + tmp.append(self.hudDivide(row[12],row[9])+" ("+str(row[9])+")") #SD/F arr.append(tmp) return arr From 0a9c2cb292efb048366f1bdd5233acedee741169 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 7 Aug 2008 12:14:53 +0100 Subject: [PATCH 016/262] git16 - fixed tv browse button size --- docs/known-bugs-and-planned-features.txt | 11 +++++------ pyfpdb/fpdb.py | 2 +- pyfpdb/table_viewer.py | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 376f1591..d0d8d8fe 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,24 +3,23 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ verify PrintPlayerFlags for one player on at least 10 hands -fix tv browse button size -optionally show single postflop agg/fold rate -seperation lines for tv fix default pathes up to sensible ones catch index error, type error, file not found error update install instructions, include how to adapt default config and where to put it split python requirements, get deep links for windows DL for everything -license info +GUI license info db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) db+imp+tv WwSF (Won when seen flop - partial taken into account) change action_no to be total for this street rather than just for one player. change .expected.txt files accordingly. calculate 3B/4B percentage (depends on above, currently its useless) -auto-import -implement error file in importer before beta =========== +optionally show single postflop agg/fold rate +seperation lines for tv +auto-import +implement error file in importer change to use different colours according to classification. add stud, razz and tourney back to imp/tv but with less seperate codepathes in tv, select from hud table using named fields rather than the current * diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index a472368a..b3730545 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,7 +343,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git15") + self.window.set_title("Free Poker DB - version: pre-alpha, git16") self.window.set_border_width(1) self.window.set_size_request(750,400) self.window.set_resizable(True) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 7065cafd..d50d0397 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -211,18 +211,18 @@ class table_viewer (threading.Thread): self.settings_hbox.show() self.filename_label = gtk.Label("Path of history file") - self.settings_hbox.add(self.filename_label) + self.settings_hbox.pack_start(self.filename_label, False, False) self.filename_label.show() self.filename_tbuffer=gtk.TextBuffer() self.filename_tbuffer.set_text("/home/sycamore/ps-history/HH20080726 Meliboea - $0.10-$0.20 - Limit Hold'em.txt") self.filename_tview=gtk.TextView(self.filename_tbuffer) - self.settings_hbox.add(self.filename_tview) + self.settings_hbox.pack_start(self.filename_tview, True, True, padding=5) self.filename_tview.show() self.browse_button=gtk.Button("Browse...") self.browse_button.connect("clicked", self.browse_clicked, "Browse clicked") - self.settings_hbox.add(self.browse_button) + self.settings_hbox.pack_start(self.browse_button, False, False) self.browse_button.show() From 33e085cf8809990b9ad59371db316d646d73ceb4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 7 Aug 2008 16:08:23 +0100 Subject: [PATCH 017/262] git17 - added fields to db+imp+tv: Won $ when seen flop and Won $ at Showdown. Seem to work fine, will verify properly later. REIMPORT is necessary after this update. cleaned table design a bit more removed actionCount from print_hand - this is useless. need to update regression-test/*.expected.txt accordingly --- docs/known-bugs-and-planned-features.txt | 5 +- docs/tabledesign.html | 68 ++++++++------------- pyfpdb/fpdb.py | 4 +- pyfpdb/fpdb_db.py | 4 +- pyfpdb/fpdb_parse_logic.py | 5 +- pyfpdb/fpdb_simple.py | 30 +++++++-- pyfpdb/table_viewer.py | 4 +- regression-test/PrintPlayerHudData.py | 6 ++ regression-test/ftp.6367428246.expected.txt | 2 + regression-test/ftp.6929537410.expected.txt | 2 + regression-test/ftp.6929553738.expected.txt | 2 + regression-test/print_hand.py | 2 +- 12 files changed, 77 insertions(+), 57 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index d0d8d8fe..582cad9c 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,17 +2,16 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ -verify PrintPlayerFlags for one player on at least 10 hands fix default pathes up to sensible ones catch index error, type error, file not found error update install instructions, include how to adapt default config and where to put it split python requirements, get deep links for windows DL for everything GUI license info -db+imp+tv W$SD (won $ when seeing showdown - partial win counts partially here) -db+imp+tv WwSF (Won when seen flop - partial taken into account) change action_no to be total for this street rather than just for one player. change .expected.txt files accordingly. calculate 3B/4B percentage (depends on above, currently its useless) +update regression testing to take into account everything new +add fpdb version string into db to detect outdated db format. before beta =========== diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 497d7fa4..5ad16bdc 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -269,52 +269,25 @@ gametypes

- -

small_blind

- - -

int

- - -


-

+

small_blind

+

int

+


+ + +

big_blind

+

int

+


- -

big_blind

- - -

int

- - -


-

- +

small_bet

+

int

+


- -

small_bet

- - -

int

- - -


-

- - - - -

big_bet

- - -

int

- - -


-

- +

big_bet

+

int

+



@@ -746,7 +719,7 @@ far less relevant.

id

bigint

-

+


gametypeId

@@ -853,6 +826,17 @@ far less relevant.

int

number of hands where someone else raised River and the player folded

+ +

wonWhenSeenFlop

+

float

+

How many hands the player won after seeing the flop - this can be a "partial win" if the pot is split.
+ To be completely clear, this stores a hand count, NOT a money amount.

+ + +

wonAtSD

+

float

+

As wonPostFlop, but for showdown.

+

Table hands_actions

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index b3730545..25d35251 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,9 +343,9 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git16") + self.window.set_title("Free Poker DB - version: pre-alpha, git17") self.window.set_border_width(1) - self.window.set_size_request(750,400) + self.window.set_size_request(800,400) self.window.set_resizable(True) self.menu_items = ( diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 1cd3e4cc..7e6f5970 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -261,7 +261,9 @@ class fpdb_db: otherRaisedTurn INT, otherRaisedTurnFold INT, otherRaisedRiver INT, - otherRaisedRiverFold INT)""") + otherRaisedRiverFold INT, + wonWhenSeenFlop FLOAT, + wonAtSD FLOAT)""") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index b7aad5d5..1842d924 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -113,7 +113,10 @@ def mainParser(db, cursor, site, category, hand): limit_type=cursor.fetchone()[0] #todo: remove this unnecessary database access fpdb_simple.convert3B4B(site, category, limit_type, actionTypes, actionAmounts) - hudImportData=fpdb_simple.calculateHudImport(playerIDs, category, actionTypes) + totalWinnings=0 + for i in range(len(winnings)): + totalWinnings+=winnings[i] + hudImportData=fpdb_simple.calculateHudImport(playerIDs, category, actionTypes, winnings, totalWinnings) if isTourney: payin_amounts=fpdb_simple.calcPayin(len(names), buyin, fee) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 4249c86f..79c4cb3f 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1206,7 +1206,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, return result #end def store_hands_players_stud_tourney -def calculateHudImport(player_ids, category, action_types): +def calculateHudImport(player_ids, category, action_types, winnings, totalWinnings): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" #setup subarrays of the result dictionary. VPIP=[] @@ -1226,6 +1226,9 @@ def calculateHudImport(player_ids, category, action_types): otherRaisedTurnFold=[] otherRaisedRiver=[] otherRaisedRiverFold=[] + wonWhenSeenFlop=[] + wonAtSD=[] + for player in range (len(player_ids)): #set default values myVPIP=False @@ -1245,6 +1248,8 @@ def calculateHudImport(player_ids, category, action_types): myOtherRaisedTurnFold=False myOtherRaisedRiver=False myOtherRaisedRiverFold=False + myWonWhenSeenFlop=0.0 + myWonAtSD=0.0 #calculate preflop values street=0 @@ -1325,7 +1330,14 @@ def calculateHudImport(player_ids, category, action_types): for countOtherFold in range (len(action_types[street][player])): if action_types[street][player][countOtherFold]=="fold": myOtherRaisedRiverFold=True - + + if winnings[player]!=0: + if mySawFlop: + myWonWhenSeenFlop=winnings[player]/float(totalWinnings) + #print "myWonWhenSeenFlop:",myWonWhenSeenFlop + if mySawShowdown: + myWonAtSD=myWonWhenSeenFlop + #add each value to the appropriate array VPIP.append(myVPIP) PFR.append(myPFR) @@ -1344,6 +1356,8 @@ def calculateHudImport(player_ids, category, action_types): otherRaisedTurnFold.append(myOtherRaisedTurnFold) otherRaisedRiver.append(myOtherRaisedRiver) otherRaisedRiverFold.append(myOtherRaisedRiverFold) + wonWhenSeenFlop.append(myWonWhenSeenFlop) + wonAtSD.append(myWonAtSD) #add each array to the to-be-returned dictionary result={'VPIP':VPIP} @@ -1363,6 +1377,8 @@ def calculateHudImport(player_ids, category, action_types): result['raisedRiver']=raisedRiver result['otherRaisedRiver']=otherRaisedRiver result['otherRaisedRiverFold']=otherRaisedRiverFold + result['wonWhenSeenFlop']=wonWhenSeenFlop + result['wonAtSD']=wonAtSD return result #end def calculateHudImport @@ -1413,17 +1429,19 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if hudImportData['otherRaisedTurnFold'][player]: row[19]+=1 if hudImportData['otherRaisedRiver'][player]: row[20]+=1 if hudImportData['otherRaisedRiverFold'][player]: row[21]+=1 + if hudImportData['wonWhenSeenFlop'][player]!=0.0: row[22]+=hudImportData['wonWhenSeenFlop'][player] + if hudImportData['wonAtSD'][player]!=0.0: row[23]+=hudImportData['wonAtSD'][player] if doInsert: #print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21])) + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23])) else: #print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha - SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s - WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[1], row[2], row[3])) + SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[1], row[2], row[3])) else: raise FpdbError("todo") #end def storeHudData diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index d50d0397..87ce855c 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -59,7 +59,7 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wSF", "W$@SD") else: raise fpdb_simple.FpdbError("reimplement stud") tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7", "SD/4") @@ -112,6 +112,8 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[15],row[11])+" ("+str(row[11])+")") #AR tmp.append(self.hudDivide(row[21],row[20])+" ("+str(row[20])+")") #FR tmp.append(self.hudDivide(row[12],row[9])+" ("+str(row[9])+")") #SD/F + tmp.append(self.hudDivide(row[22],row[9])+" ("+str(row[9])+")") #W$wSF + tmp.append(self.hudDivide(row[23],row[12])+" ("+str(row[12])+")") #W$@SD arr.append(tmp) return arr diff --git a/regression-test/PrintPlayerHudData.py b/regression-test/PrintPlayerHudData.py index 1c3f4de9..99be5a4e 100755 --- a/regression-test/PrintPlayerHudData.py +++ b/regression-test/PrintPlayerHudData.py @@ -93,6 +93,12 @@ print "otherRaisedTurn:",fields[2] print "otherRaisedTurnFold:",fields[3] print "otherRaisedRiver:",fields[4] print "otherRaisedRiverFold:",fields[5] +print "" + +cursor.execute ("SELECT wonWhenSeenFlop, wonAtSD FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "wonWhenSeenFlop:",fields[0] +print "wonAtSD:",fields[1] cursor.close() diff --git a/regression-test/ftp.6367428246.expected.txt b/regression-test/ftp.6367428246.expected.txt index 01ca224a..2e88061c 100644 --- a/regression-test/ftp.6367428246.expected.txt +++ b/regression-test/ftp.6367428246.expected.txt @@ -1,3 +1,5 @@ +This file is outdated! + Connected to MySQL on localhost. Print Hand Utility options.site: Full Tilt Poker site_id: 1 diff --git a/regression-test/ftp.6929537410.expected.txt b/regression-test/ftp.6929537410.expected.txt index 448ab95e..6d558420 100644 --- a/regression-test/ftp.6929537410.expected.txt +++ b/regression-test/ftp.6929537410.expected.txt @@ -1,3 +1,5 @@ +This file is outdated! + Connected to MySQL on localhost. Print Hand Utility options.site: Full Tilt Poker site_id: 1 From Table hands diff --git a/regression-test/ftp.6929553738.expected.txt b/regression-test/ftp.6929553738.expected.txt index 0999a08d..b758cbfc 100644 --- a/regression-test/ftp.6929553738.expected.txt +++ b/regression-test/ftp.6929553738.expected.txt @@ -1,3 +1,5 @@ +This file is outdated! + Connected to MySQL on localhost. Print Hand Utility options.site: Full Tilt Poker site_id: 1 From Table hands diff --git a/regression-test/print_hand.py b/regression-test/print_hand.py index 1ccb3a15..b558ed96 100755 --- a/regression-test/print_hand.py +++ b/regression-test/print_hand.py @@ -159,7 +159,7 @@ for i in range (len(hands_players)): hands_actions=cursor.fetchall() for j in range (len(hands_actions)): line=hands_actions[j][2:] - printstr="player_name:"+player_names[i]+" actionCount:"+str(j) + printstr="player_name:"+player_names[i] printstr+=" street:"+ful.street_int2String(category, line[0])+" streetActionNo:"+str(line[1])+" action:"+line[2] printstr+=" amount:"+str(line[3]) print printstr From bf691fe9e753b2af26e3e0642697df3b9e0c103e Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 8 Aug 2008 00:07:00 +0100 Subject: [PATCH 018/262] git18 - tv now has background colours to make it far more legible --- docs/known-bugs-and-planned-features.txt | 2 +- docs/tabledesign.html | 2 +- pyfpdb/fpdb.py | 4 ++-- pyfpdb/table_viewer.py | 14 +++++++++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 582cad9c..2abd5dbe 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -5,6 +5,7 @@ before alpha fix default pathes up to sensible ones catch index error, type error, file not found error update install instructions, include how to adapt default config and where to put it +add instructions how to reimport split python requirements, get deep links for windows DL for everything GUI license info @@ -16,7 +17,6 @@ add fpdb version string into db to detect outdated db format. before beta =========== optionally show single postflop agg/fold rate -seperation lines for tv auto-import implement error file in importer change to use different colours according to classification. diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 5ad16bdc..0d8ff626 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -835,7 +835,7 @@ far less relevant.

wonAtSD

float

-

As wonPostFlop, but for showdown.

+

As wonWhenSeenFlop, but for showdown.

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 25d35251..50ff4512 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,9 +343,9 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git17") + self.window.set_title("Free Poker DB - version: pre-alpha, git18") self.window.set_border_width(1) - self.window.set_size_request(800,400) + self.window.set_size_request(950,400) self.window.set_resizable(True) self.menu_items = ( diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 87ce855c..ebdf4d76 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -132,8 +132,20 @@ class table_viewer (threading.Thread): for row in range(len(arr)): for column in range (len(arr[row])): + eventBox=gtk.EventBox() new_label=gtk.Label(arr[row][column]) - self.data_table.attach(child=new_label, left_attach=column, right_attach=column+1, top_attach=row, bottom_attach=row+1) + if row%2==0: # + bg_col="white" + if column==0 or (column>=5 and column<=10): + bg_col="lightgrey" + else: + bg_col="lightgrey" + if column==0 or (column>=5 and column<=10): + bg_col="grey" + eventBox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bg_col)) + eventBox.add(new_label) + self.data_table.attach(child=eventBox, left_attach=column, right_attach=column+1, top_attach=row, bottom_attach=row+1) + eventBox.show() new_label.show() #end def table_viewer.refresh_clicked From 0e84dceb1f1bbb3050b0c886bd528b1f5969789d Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 8 Aug 2008 22:03:43 +0100 Subject: [PATCH 019/262] git19 (REIMPORT needed) - updated everything to use new action counting method -> it half works, but fails to store (or print) a substantial proportion of the action_nos removed bunch of commented prints from fpdb_parse --- docs/known-bugs-and-planned-features.txt | 11 +- docs/tabledesign.html | 130 ++++-------------- prepare-git.sh | 1 + pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_parse_logic.py | 28 ++-- pyfpdb/fpdb_save_to_db.py | 4 +- pyfpdb/fpdb_simple.py | 33 +++-- regression-test/fpdb_util_lib.py | 6 +- .../ftp-omaha-hi-pl-ring-001-005.txt | 0 .../ftp-stud-hilo-ring-001.txt | 0 .../ftp.6367428246.expected.txt | 0 .../ftp.6929537410.expected.txt | 0 .../ftp.6929553738.expected.txt | 0 regression-test/ps.14519394979.expected.txt | 42 +++--- regression-test/ps.14519420999.expected.txt | 42 +++--- regression-test/ps.14519433154.expected.txt | 60 ++++---- 16 files changed, 147 insertions(+), 212 deletions(-) rename regression-test/{ => known-broken}/ftp-omaha-hi-pl-ring-001-005.txt (100%) rename regression-test/{ => known-broken}/ftp-stud-hilo-ring-001.txt (100%) rename regression-test/{ => known-broken}/ftp.6367428246.expected.txt (100%) rename regression-test/{ => known-broken}/ftp.6929537410.expected.txt (100%) rename regression-test/{ => known-broken}/ftp.6929553738.expected.txt (100%) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 2abd5dbe..44220752 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -9,14 +9,14 @@ add instructions how to reimport split python requirements, get deep links for windows DL for everything GUI license info -change action_no to be total for this street rather than just for one player. change .expected.txt files accordingly. -calculate 3B/4B percentage (depends on above, currently its useless) -update regression testing to take into account everything new -add fpdb version string into db to detect outdated db format. +calculate 3B/4B percentage +add fpdb version string into db to detect outdated db format and importer bugs +update regression testing to take into account everything new, make sure it passes all tests before beta =========== optionally show single postflop agg/fold rate +change definition of bet to exclude bring in? auto-import implement error file in importer change to use different colours according to classification. @@ -30,7 +30,7 @@ fix load profile HUD config wizard file permission script, use games group -change stud street storage from 3-7 to 0-4 throughout +change stud street storage from 3-7 to 0-4 throughout (possibly best way is to just shrink the holding array in fpdb_simple.createArrays make bulk importer display a grand total in the GUI change save_to_db into one method and probably move into parse_logic Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu @@ -47,6 +47,7 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== +In many places there are unnecessary database accesses or it regenerates information it already had before or just generally does things in obscenely inefficient ways. It's great to have a total of 3 CPUs with a combined 5.6GHz working for you isn't it... ;) In any case, these should be optimised to leave more power for other things, such as dropping to lower power states move version into seperate file make option to use "traditional" labels, e.g. WtSD instead of SD/F HTMLify docs and validate them diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 0d8ff626..dff5e003 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -840,131 +840,61 @@ far less relevant.

Table hands_actions

-

Did -separate this into an extra table because it makes SELECTing across -different streets so much easier. Also the space saving will be very -large.

+

Did separate this into an extra table because it makes SELECTing across different streets so much easier. Also the space saving will be very large.

- - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + +

Field Name

-

Type

-
-

Comment

-

Type

Comment

-

id

-
-

bigint

-
-


-

-

id

bigint


-

hand_player_id

-
-

bigint

-
-

references - hands_players.id

-

hand_player_id

bigint

references hands_players.id

-

street

-
-

smallint

-
-

street - number, 0-3 (preflop, flop, turn, river) for holdem/omaha or 0-4 - for razz/stud

-

-1 for seen showdown

-

street

smallint

street number, 0-3 (preflop, flop, turn, river) for holdem/omaha or 0-4 for razz/stud

-1 for seen showdown

-

action_no

-
-

smallint

-
-

action number, 1-4

-

action_no

smallint

action number, this is counted from zero for each street but across all players (e.g. in a heads up where the SB calls and the BB raises and the SB calls again would have numbers 0 and 1 for blinds, 2 and 4 for call and 3 for bet)
+ Note that the blinds are counted as an action, so if the SB stays in the hand it'll always be action #0

-

action

-
-

char(5)

-
-

bet - stands for bring in, complete, bet, double bet, raise and double - raise, since they all - technically - do the same thing. unbet is - used for when an uncalled bet is returned.

-

Other valid values: blind - call check fold

-

action

char(5)

Bet stands for bring in, complete, bet, double bet, raise and double raise, since they all - technically - do the same thing. Unbet is used for when an uncalled bet is returned.

+

Other valid values: blind call check fold

-

amount

-
-

int

-
-

amount put into the middle - for this action

-

amount

int

amount put into the middle for this action

-

comment

-
-

text

-
-


-

-

comment

text


-

comment_ts

-
-

datetime (in UTC)

-
-


-

-

comment_ts

datetime (in UTC)


-


-

+


Tournament Tables


Table tourneys

- - - - - - + + + - - - + + + - - - + + +

Field name

diff --git a/prepare-git.sh b/prepare-git.sh index 08dd0a5f..91b008e9 100755 --- a/prepare-git.sh +++ b/prepare-git.sh @@ -18,4 +18,5 @@ rm regression-test/*.found.txt rm regression-test/*.pyc rm pyfpdb/*.pyc + git-add--interactive diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 50ff4512..cf9b99ae 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,7 +343,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git18") + self.window.set_title("Free Poker DB - version: pre-alpha, git19") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 1842d924..c024e7c7 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -28,7 +28,7 @@ def mainParser(db, cursor, site, category, hand): lineStreets=[] #char, valid values: (predeal, preflop, flop, turn, river) cardValues, cardSuits, boardValues, boardSuits=[],[],[],[] - antes, actionTypes, actionAmounts, seatLines, winnings, rakes=[],[],[],[],[],[] + antes, actionTypes, actionAmounts, actionNos, seatLines, winnings, rakes=[], [],[],[],[],[],[] #part 1: read hand no and check for duplicate siteHandNo=fpdb_simple.parseSiteHandNo(hand[0]) @@ -42,49 +42,36 @@ def mainParser(db, cursor, site, category, hand): raise fpdb_simple.FpdbError("tourneys are only supported on PS right now") siteTourneyNo=fpdb_simple.parseTourneyNo(hand[0]) buyin=fpdb_simple.parseBuyin(hand[0]) - #print "Buyin:", buyin fee=fpdb_simple.parseFee(hand[0]) - #print "Fee:", fee entries=-1 #todo: parse this prizepool=-1 #todo: parse this tourneyStartTime=handStartTime #todo: read tourney start time - #print "gametypeID:",gametypeID fpdb_simple.isAlreadyInDB(cursor, gametypeID, siteHandNo) #part 2: classify lines by type (e.g. cards, action, win, sectionchange) and street fpdb_simple.classifyLines(hand, category, lineTypes, lineStreets) - #for i in range (len(hand)): - # print "i:",i,"lineTypes[i]:",lineTypes[i],"hand[i]:",hand[i] #part 3: read basic player info #3a read player names, startcashes for i in range (len(hand)): #todo: use maxseats+1 here. if (lineTypes[i]=="name"): seatLines.append(hand[i]) - #print "seatLines:",seatLines - #print "hand:",hand names=fpdb_simple.parseNames(seatLines) - #print "names:",names playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID) startCashes=fpdb_simple.parseCashes(seatLines, site) - fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts) + fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts, actionNos) - #3b remove people who sitout before cards are dealt (e.g. sitout instead of paying blinds) - #PS doesnt have a nameline for sitouts so for PS this can be skipped. havent tried FTP yet - - #3c read positions + #3b read positions if (category=="holdem" or category=="omahahi" or category=="omahahilo"): positions = fpdb_simple.parsePositions (hand, names) #part 4: take appropriate action for each line based on linetype for i in range(len(hand)): if (lineTypes[i]=="cards"): - #print "hand[i]:",hand[i] fpdb_simple.parseCardLine (site, category, lineStreets[i], hand[i], names, cardValues, cardSuits, boardValues, boardSuits) - #print "cardValues:",cardValues elif (lineTypes[i]=="action"): - fpdb_simple.parseActionLine (hand[i], lineStreets[i], names, actionTypes, actionAmounts, site) + fpdb_simple.parseActionLine (site, hand[i], lineStreets[i], names, actionTypes, actionAmounts, actionNos) elif (lineTypes[i]=="win"): fpdb_simple.parseWinLine (hand[i], site, names, winnings, isTourney) elif (lineTypes[i]=="rake"): @@ -99,7 +86,6 @@ def mainParser(db, cursor, site, category, hand): fpdb_simple.parseAnteLine(hand[i], site, names, antes) else: raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i]) - #print "end of part4 cardValues:",cardValues,"cardSuits:",cardSuits #part 5: final preparations, then call fpdb_save_to_db.saveHoldem with # the arrays as they are - that file will fill them. @@ -110,7 +96,7 @@ def mainParser(db, cursor, site, category, hand): fpdb_simple.checkPositions(positions) cursor.execute("SELECT limit_type FROM gametypes WHERE id=%s",(gametypeID, )) - limit_type=cursor.fetchone()[0] #todo: remove this unnecessary database access + limit_type=cursor.fetchone()[0] fpdb_simple.convert3B4B(site, category, limit_type, actionTypes, actionAmounts) totalWinnings=0 @@ -119,6 +105,7 @@ def mainParser(db, cursor, site, category, hand): hudImportData=fpdb_simple.calculateHudImport(playerIDs, category, actionTypes, winnings, totalWinnings) if isTourney: + raise fpdb_simple.FpdbError ("tourneys are currently broken") payin_amounts=fpdb_simple.calcPayin(len(names), buyin, fee) ranks=[] for i in range (len(names)): @@ -142,8 +129,9 @@ def mainParser(db, cursor, site, category, hand): if (category=="holdem" or category=="omahahi" or category=="omahahilo"): result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, - cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, hudImportData) + cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData) elif (category=="razz" or category=="studhi" or category=="studhilo"): + raise fpdb_simple.FpdbError ("stud/razz are currently broken") result = fpdb_save_to_db.ring_stud(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, cardSuits, winnings, rakes, actionTypes, actionAmounts, hudImportData) diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index 1dd656bf..a8040cf3 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -40,7 +40,7 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, #stores a holdem/omaha hand into the database def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, - board_values, board_suits, winnings, rakes, action_types, action_amounts, hudImportData): + board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData): #fill up the two player card arrays if (category=="holdem"): fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) @@ -60,7 +60,7 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) return site_hand_no #end def ring_holdem_omaha diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 79c4cb3f..49e35853 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -199,7 +199,7 @@ def convertCardValuesBoard(arr): #end def convertCardValuesBoard #this creates the 2D/3D arrays. manipulates the passed arrays instead of returning. -def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, action_amounts): +def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, action_amounts, actionNos): for i in range(seats):#create second dimension arrays tmp=[] card_values.append(tmp) @@ -208,19 +208,26 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake antes.append(0) winnings.append(0) rakes.append(0) - - for i in range (8): - #build the first dimension array, for streets - #todo: 0-2 will of course be left empty, get rid of this nicely using consts + + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + streetCount=4 + else: + streetCount=8 + + for i in range(streetCount): #build the first dimension array, for streets tmp=[] action_types.append(tmp) tmp=[] action_amounts.append(tmp) + tmp=[] + actionNos.append(tmp) for j in range (seats): #second dimension arrays: players tmp=[] action_types[i].append(tmp) tmp=[] action_amounts[i].append(tmp) + tmp=[] + actionNos[i].append(tmp) if (category=="holdem" or category=="omahahi" or category=="omahahilo"): pass elif (category=="razz" or category=="studhi" or category=="studhilo"):#need to fill card arrays. @@ -555,8 +562,7 @@ def parseActionAmount(line, atype, site): #doesnt return anything, simply changes the passed arrays action_types and # action_amounts. For stud this expects numeric streets (3-7), for # holdem/omaha it expects predeal, preflop, flop, turn or river -def parseActionLine(line, street, names, action_types, action_amounts, site): - #print "parseActionLine, line:",line +def parseActionLine(site, line, street, names, action_types, action_amounts, actionNos): #this only applies to stud if (street<3): text="invalid street ("+str(street)+") for line: "+line @@ -571,12 +577,19 @@ def parseActionLine(line, street, names, action_types, action_amounts, site): elif (street=="river"): street=3 + nextActionNo=0 + for player in range(len(actionNos[street])): + for count in range(len(actionNos[street][player])): + if actionNos[street][player][count]>=nextActionNo: + nextActionNo=actionNos[street][player][count]+1 + atype=parseActionType(line) playerno=recognisePlayerNo(line, names, atype) amount=parseActionAmount(line, atype, site) action_types[street][playerno].append(atype) action_amounts[street][playerno].append(amount) + actionNos[street][playerno].append(nextActionNo) #end def parseActionLine #returns the action type code (see table design) of the given action line @@ -1066,12 +1079,14 @@ def splitRake(winnings, rakes, totalRake): rakes[i]=totalRake*winPortion #end def splitRake -def storeActions(cursor, hands_players_ids, action_types, action_amounts): +def storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos): #stores into table hands_actions + #print "start of storeActions, actionNos:",actionNos + #print " action_amounts:",action_amounts for i in range (len(action_types)): #iterate through streets for j in range (len(action_types[i])): #iterate through names for k in range (len(action_types[i][j])): #iterate through individual actions of that player on that street - cursor.execute ("INSERT INTO hands_actions (hand_player_id, street, action_no, action, amount) VALUES (%s, %s, %s, %s, %s)", (hands_players_ids[j], i, k, action_types[i][j][k], action_amounts[i][j][k])) + cursor.execute ("INSERT INTO hands_actions (hand_player_id, street, action_no, action, amount) VALUES (%s, %s, %s, %s, %s)", (hands_players_ids[j], i, actionNos[i][j][k], action_types[i][j][k], action_amounts[i][j][k])) #end def storeActions def store_board_cards(cursor, hands_id, board_values, board_suits): diff --git a/regression-test/fpdb_util_lib.py b/regression-test/fpdb_util_lib.py index 9afb9fb4..565f7cbd 100644 --- a/regression-test/fpdb_util_lib.py +++ b/regression-test/fpdb_util_lib.py @@ -64,11 +64,11 @@ def street_int2String(category, street): if street==0: return "Preflop" elif street==1: - return "Flop" + return "Flop " elif street==2: - return "Turn" + return "Turn " elif street==3: - return "River" + return "River " else: print "TODO: raise error, fpdb_util_lib.py street_int2String invalid street no" sys.exit(1) diff --git a/regression-test/ftp-omaha-hi-pl-ring-001-005.txt b/regression-test/known-broken/ftp-omaha-hi-pl-ring-001-005.txt similarity index 100% rename from regression-test/ftp-omaha-hi-pl-ring-001-005.txt rename to regression-test/known-broken/ftp-omaha-hi-pl-ring-001-005.txt diff --git a/regression-test/ftp-stud-hilo-ring-001.txt b/regression-test/known-broken/ftp-stud-hilo-ring-001.txt similarity index 100% rename from regression-test/ftp-stud-hilo-ring-001.txt rename to regression-test/known-broken/ftp-stud-hilo-ring-001.txt diff --git a/regression-test/ftp.6367428246.expected.txt b/regression-test/known-broken/ftp.6367428246.expected.txt similarity index 100% rename from regression-test/ftp.6367428246.expected.txt rename to regression-test/known-broken/ftp.6367428246.expected.txt diff --git a/regression-test/ftp.6929537410.expected.txt b/regression-test/known-broken/ftp.6929537410.expected.txt similarity index 100% rename from regression-test/ftp.6929537410.expected.txt rename to regression-test/known-broken/ftp.6929537410.expected.txt diff --git a/regression-test/ftp.6929553738.expected.txt b/regression-test/known-broken/ftp.6929553738.expected.txt similarity index 100% rename from regression-test/ftp.6929553738.expected.txt rename to regression-test/known-broken/ftp.6929553738.expected.txt diff --git a/regression-test/ps.14519394979.expected.txt b/regression-test/ps.14519394979.expected.txt index 3c5f86d8..962ec68c 100644 --- a/regression-test/ps.14519394979.expected.txt +++ b/regression-test/ps.14519394979.expected.txt @@ -23,24 +23,24 @@ player_name:Player_7 player_startcash:139 position:1 off Btn cards:Ts Jh winning From Table hands_actions ======================== -player_name:Player_1 actionCount:0 street:Preflop streetActionNo:0 action:call amount:4 -player_name:Player_1 actionCount:1 street:Flop streetActionNo:0 action:call amount:2 -player_name:Player_1 actionCount:2 street:Turn streetActionNo:0 action:call amount:4 -player_name:Player_1 actionCount:3 street:River streetActionNo:0 action:fold amount:0 -player_name:Player_2 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:1 -player_name:Player_2 actionCount:1 street:Preflop streetActionNo:1 action:call amount:3 -player_name:Player_2 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 -player_name:Player_2 actionCount:3 street:Flop streetActionNo:1 action:call amount:2 -player_name:Player_2 actionCount:4 street:Turn streetActionNo:0 action:check amount:0 -player_name:Player_2 actionCount:5 street:Turn streetActionNo:1 action:call amount:4 -player_name:Player_2 actionCount:6 street:River streetActionNo:0 action:check amount:0 -player_name:Player_2 actionCount:7 street:River streetActionNo:1 action:fold amount:0 -player_name:Player_3 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:2 -player_name:Player_3 actionCount:1 street:Preflop streetActionNo:1 action:fold amount:0 -player_name:Player_4 actionCount:0 street:Preflop streetActionNo:0 action:bet amount:4 -player_name:Player_4 actionCount:1 street:Flop streetActionNo:0 action:bet amount:2 -player_name:Player_4 actionCount:2 street:Turn streetActionNo:0 action:bet amount:4 -player_name:Player_4 actionCount:3 street:River streetActionNo:0 action:bet amount:4 -player_name:Player_5 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 -player_name:Player_6 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 -player_name:Player_7 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_1 street:Preflop streetActionNo:6 action:call amount:4 +player_name:Player_1 street:Flop streetActionNo:2 action:call amount:2 +player_name:Player_1 street:Turn streetActionNo:2 action:call amount:4 +player_name:Player_1 street:River streetActionNo:2 action:fold amount:0 +player_name:Player_2 street:Preflop streetActionNo:0 action:blind amount:1 +player_name:Player_2 street:Preflop streetActionNo:7 action:call amount:3 +player_name:Player_2 street:Flop streetActionNo:0 action:check amount:0 +player_name:Player_2 street:Flop streetActionNo:3 action:call amount:2 +player_name:Player_2 street:Turn streetActionNo:0 action:check amount:0 +player_name:Player_2 street:Turn streetActionNo:3 action:call amount:4 +player_name:Player_2 street:River streetActionNo:0 action:check amount:0 +player_name:Player_2 street:River streetActionNo:3 action:fold amount:0 +player_name:Player_3 street:Preflop streetActionNo:1 action:blind amount:2 +player_name:Player_3 street:Preflop streetActionNo:8 action:fold amount:0 +player_name:Player_4 street:Preflop streetActionNo:2 action:bet amount:4 +player_name:Player_4 street:Flop streetActionNo:1 action:bet amount:2 +player_name:Player_4 street:Turn streetActionNo:1 action:bet amount:4 +player_name:Player_4 street:River streetActionNo:1 action:bet amount:4 +player_name:Player_5 street:Preflop streetActionNo:3 action:fold amount:0 +player_name:Player_6 street:Preflop streetActionNo:4 action:fold amount:0 +player_name:Player_7 street:Preflop streetActionNo:5 action:fold amount:0 diff --git a/regression-test/ps.14519420999.expected.txt b/regression-test/ps.14519420999.expected.txt index 5c1de6de..ba18560f 100644 --- a/regression-test/ps.14519420999.expected.txt +++ b/regression-test/ps.14519420999.expected.txt @@ -23,24 +23,24 @@ player_name:Player_7 player_startcash:135 position:3 off Btn cards:8d 5d winning From Table hands_actions ======================== -player_name:Player_1 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 -player_name:Player_2 actionCount:0 street:Preflop streetActionNo:0 action:call amount:2 -player_name:Player_2 actionCount:1 street:Flop streetActionNo:0 action:call amount:2 -player_name:Player_2 actionCount:2 street:Turn streetActionNo:0 action:bet amount:8 -player_name:Player_2 actionCount:3 street:River streetActionNo:0 action:bet amount:4 -player_name:Player_3 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 -player_name:Player_4 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:1 -player_name:Player_4 actionCount:1 street:Preflop streetActionNo:1 action:call amount:1 -player_name:Player_4 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 -player_name:Player_4 actionCount:3 street:Flop streetActionNo:1 action:call amount:2 -player_name:Player_4 actionCount:4 street:Turn streetActionNo:0 action:check amount:0 -player_name:Player_4 actionCount:5 street:Turn streetActionNo:1 action:fold amount:0 -player_name:Player_5 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:2 -player_name:Player_5 actionCount:1 street:Preflop streetActionNo:1 action:check amount:0 -player_name:Player_5 actionCount:2 street:Flop streetActionNo:0 action:bet amount:2 -player_name:Player_5 actionCount:3 street:Turn streetActionNo:0 action:bet amount:4 -player_name:Player_5 actionCount:4 street:Turn streetActionNo:1 action:call amount:4 -player_name:Player_5 actionCount:5 street:River streetActionNo:0 action:check amount:0 -player_name:Player_5 actionCount:6 street:River streetActionNo:1 action:call amount:4 -player_name:Player_6 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 -player_name:Player_7 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_1 street:Preflop streetActionNo:4 action:fold amount:0 +player_name:Player_2 street:Preflop streetActionNo:5 action:call amount:2 +player_name:Player_2 street:Flop streetActionNo:2 action:call amount:2 +player_name:Player_2 street:Turn streetActionNo:2 action:bet amount:8 +player_name:Player_2 street:River streetActionNo:1 action:bet amount:4 +player_name:Player_3 street:Preflop streetActionNo:6 action:fold amount:0 +player_name:Player_4 street:Preflop streetActionNo:0 action:blind amount:1 +player_name:Player_4 street:Preflop streetActionNo:7 action:call amount:1 +player_name:Player_4 street:Flop streetActionNo:0 action:check amount:0 +player_name:Player_4 street:Flop streetActionNo:3 action:call amount:2 +player_name:Player_4 street:Turn streetActionNo:0 action:check amount:0 +player_name:Player_4 street:Turn streetActionNo:3 action:fold amount:0 +player_name:Player_5 street:Preflop streetActionNo:1 action:blind amount:2 +player_name:Player_5 street:Preflop streetActionNo:8 action:check amount:0 +player_name:Player_5 street:Flop streetActionNo:1 action:bet amount:2 +player_name:Player_5 street:Turn streetActionNo:1 action:bet amount:4 +player_name:Player_5 street:Turn streetActionNo:4 action:call amount:4 +player_name:Player_5 street:River streetActionNo:0 action:check amount:0 +player_name:Player_5 street:River streetActionNo:2 action:call amount:4 +player_name:Player_6 street:Preflop streetActionNo:2 action:fold amount:0 +player_name:Player_7 street:Preflop streetActionNo:3 action:fold amount:0 diff --git a/regression-test/ps.14519433154.expected.txt b/regression-test/ps.14519433154.expected.txt index 81000579..6e739a5a 100644 --- a/regression-test/ps.14519433154.expected.txt +++ b/regression-test/ps.14519433154.expected.txt @@ -23,33 +23,33 @@ player_name:Player_7 player_startcash:135 position:4 off Btn cards:7c Jh winning From Table hands_actions ======================== -player_name:Player_1 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 -player_name:Player_2 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 -player_name:Player_3 actionCount:0 street:Preflop streetActionNo:0 action:call amount:2 -player_name:Player_3 actionCount:1 street:Preflop streetActionNo:1 action:call amount:2 -player_name:Player_3 actionCount:2 street:Flop streetActionNo:0 action:bet amount:2 -player_name:Player_3 actionCount:3 street:Flop streetActionNo:1 action:bet amount:4 -player_name:Player_3 actionCount:4 street:Flop streetActionNo:2 action:call amount:2 -player_name:Player_3 actionCount:5 street:Turn streetActionNo:0 action:bet amount:4 -player_name:Player_3 actionCount:6 street:Turn streetActionNo:1 action:bet amount:8 -player_name:Player_3 actionCount:7 street:Turn streetActionNo:2 action:call amount:4 -player_name:Player_3 actionCount:8 street:River streetActionNo:0 action:bet amount:4 -player_name:Player_3 actionCount:9 street:River streetActionNo:1 action:bet amount:8 -player_name:Player_3 actionCount:10 street:River streetActionNo:2 action:call amount:4 -player_name:Player_4 actionCount:0 street:Preflop streetActionNo:0 action:call amount:2 -player_name:Player_4 actionCount:1 street:Preflop streetActionNo:1 action:call amount:2 -player_name:Player_4 actionCount:2 street:Flop streetActionNo:0 action:bet amount:4 -player_name:Player_4 actionCount:3 street:Flop streetActionNo:1 action:bet amount:4 -player_name:Player_4 actionCount:4 street:Turn streetActionNo:0 action:bet amount:8 -player_name:Player_4 actionCount:5 street:Turn streetActionNo:1 action:bet amount:8 -player_name:Player_4 actionCount:6 street:River streetActionNo:0 action:bet amount:8 -player_name:Player_4 actionCount:7 street:River streetActionNo:1 action:bet amount:8 -player_name:Player_5 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:1 -player_name:Player_5 actionCount:1 street:Preflop streetActionNo:1 action:bet amount:3 -player_name:Player_5 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 -player_name:Player_5 actionCount:3 street:Flop streetActionNo:1 action:fold amount:0 -player_name:Player_6 actionCount:0 street:Preflop streetActionNo:0 action:blind amount:2 -player_name:Player_6 actionCount:1 street:Preflop streetActionNo:1 action:call amount:2 -player_name:Player_6 actionCount:2 street:Flop streetActionNo:0 action:check amount:0 -player_name:Player_6 actionCount:3 street:Flop streetActionNo:1 action:fold amount:0 -player_name:Player_7 actionCount:0 street:Preflop streetActionNo:0 action:fold amount:0 +player_name:Player_1 street:Preflop streetActionNo:3 action:fold amount:0 +player_name:Player_2 street:Preflop streetActionNo:4 action:fold amount:0 +player_name:Player_3 street:Preflop streetActionNo:5 action:call amount:2 +player_name:Player_3 street:Preflop streetActionNo:9 action:call amount:2 +player_name:Player_3 street:Flop streetActionNo:2 action:bet amount:2 +player_name:Player_3 street:Flop streetActionNo:6 action:bet amount:4 +player_name:Player_3 street:Flop streetActionNo:8 action:call amount:2 +player_name:Player_3 street:Turn streetActionNo:0 action:bet amount:4 +player_name:Player_3 street:Turn streetActionNo:2 action:bet amount:8 +player_name:Player_3 street:Turn streetActionNo:4 action:call amount:4 +player_name:Player_3 street:River streetActionNo:0 action:bet amount:4 +player_name:Player_3 street:River streetActionNo:2 action:bet amount:8 +player_name:Player_3 street:River streetActionNo:4 action:call amount:4 +player_name:Player_4 street:Preflop streetActionNo:6 action:call amount:2 +player_name:Player_4 street:Preflop streetActionNo:10 action:call amount:2 +player_name:Player_4 street:Flop streetActionNo:3 action:bet amount:4 +player_name:Player_4 street:Flop streetActionNo:7 action:bet amount:4 +player_name:Player_4 street:Turn streetActionNo:1 action:bet amount:8 +player_name:Player_4 street:Turn streetActionNo:3 action:bet amount:8 +player_name:Player_4 street:River streetActionNo:1 action:bet amount:8 +player_name:Player_4 street:River streetActionNo:3 action:bet amount:8 +player_name:Player_5 street:Preflop streetActionNo:0 action:blind amount:1 +player_name:Player_5 street:Preflop streetActionNo:7 action:bet amount:3 +player_name:Player_5 street:Flop streetActionNo:0 action:check amount:0 +player_name:Player_5 street:Flop streetActionNo:4 action:fold amount:0 +player_name:Player_6 street:Preflop streetActionNo:1 action:blind amount:2 +player_name:Player_6 street:Preflop streetActionNo:8 action:call amount:2 +player_name:Player_6 street:Flop streetActionNo:1 action:check amount:0 +player_name:Player_6 street:Flop streetActionNo:5 action:fold amount:0 +player_name:Player_7 street:Preflop streetActionNo:2 action:fold amount:0 From 3d82fd4f6a3042c14e27367bd5b58fb4a5c9a2db Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sat, 9 Aug 2008 18:53:07 +0100 Subject: [PATCH 020/262] git20 - made differently arranged array for actiontypes, used that to calculate 3B/4B percentage --- docs/known-bugs-and-planned-features.txt | 18 +++++----- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_parse_logic.py | 11 +++--- pyfpdb/fpdb_simple.py | 43 ++++++++++++++++++------ 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 44220752..3c0186ab 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -9,18 +9,22 @@ add instructions how to reimport split python requirements, get deep links for windows DL for everything GUI license info -calculate 3B/4B percentage add fpdb version string into db to detect outdated db format and importer bugs update regression testing to take into account everything new, make sure it passes all tests +implement error file in importer + +next +==== +ST, CB, 2B, 3B, fold to these +optionally show single postflop agg/fold rate +auto-import +use different colours according to classification. +add stud, razz and tourney back to imp/tv but with less seperate codepathes +table with data for graphs for SD/F, W$wSF, W$@SD before beta =========== -optionally show single postflop agg/fold rate change definition of bet to exclude bring in? -auto-import -implement error file in importer -change to use different colours according to classification. -add stud, razz and tourney back to imp/tv but with less seperate codepathes in tv, select from hud table using named fields rather than the current * remove remains of mysql/myisam support. tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem @@ -35,8 +39,6 @@ make bulk importer display a grand total in the GUI change save_to_db into one method and probably move into parse_logic Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu make a quick benchmark of mysql and postgresql: import of my whole db, some tableviewer refreshes with and without updated file -db+imp+tv steal blind from btn, co, lmp. fold SB/BB/BI to steal -db+imp+tv cb/2nd/3rd barrel, fold to them. Make tab and enter work as sensible in GUIs and implement Ctrl+Q, Ctrl+X and Alt+F4 for close. use profile file for bulk import and table viewer settings and pathes handle errors properly, in particular wrt to SQL rollback. diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index cf9b99ae..9f6e7c10 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,7 +343,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git19") + self.window.set_title("Free Poker DB - version: pre-alpha, git20") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index c024e7c7..646081f2 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -27,8 +27,7 @@ def mainParser(db, cursor, site, category, hand): lineTypes=[] #char, valid values: header, name, cards, action, win, rake, ignore lineStreets=[] #char, valid values: (predeal, preflop, flop, turn, river) - cardValues, cardSuits, boardValues, boardSuits=[],[],[],[] - antes, actionTypes, actionAmounts, actionNos, seatLines, winnings, rakes=[], [],[],[],[],[],[] + cardValues, cardSuits, boardValues, boardSuits, antes, actionTypes, actionAmounts, actionNos, actionTypeByNo, seatLines, winnings, rakes=[], [],[],[],[],[],[],[],[],[],[],[] #part 1: read hand no and check for duplicate siteHandNo=fpdb_simple.parseSiteHandNo(hand[0]) @@ -60,8 +59,8 @@ def mainParser(db, cursor, site, category, hand): playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID) startCashes=fpdb_simple.parseCashes(seatLines, site) - fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts, actionNos) - + fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts, actionNos, actionTypeByNo) + #3b read positions if (category=="holdem" or category=="omahahi" or category=="omahahilo"): positions = fpdb_simple.parsePositions (hand, names) @@ -71,7 +70,7 @@ def mainParser(db, cursor, site, category, hand): if (lineTypes[i]=="cards"): fpdb_simple.parseCardLine (site, category, lineStreets[i], hand[i], names, cardValues, cardSuits, boardValues, boardSuits) elif (lineTypes[i]=="action"): - fpdb_simple.parseActionLine (site, hand[i], lineStreets[i], names, actionTypes, actionAmounts, actionNos) + fpdb_simple.parseActionLine (site, hand[i], lineStreets[i], playerIDs, names, actionTypes, actionAmounts, actionNos, actionTypeByNo) elif (lineTypes[i]=="win"): fpdb_simple.parseWinLine (hand[i], site, names, winnings, isTourney) elif (lineTypes[i]=="rake"): @@ -102,7 +101,7 @@ def mainParser(db, cursor, site, category, hand): totalWinnings=0 for i in range(len(winnings)): totalWinnings+=winnings[i] - hudImportData=fpdb_simple.calculateHudImport(playerIDs, category, actionTypes, winnings, totalWinnings) + hudImportData=fpdb_simple.calculateHudImport(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings) if isTourney: raise fpdb_simple.FpdbError ("tourneys are currently broken") diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 49e35853..a7610a8c 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -199,7 +199,7 @@ def convertCardValuesBoard(arr): #end def convertCardValuesBoard #this creates the 2D/3D arrays. manipulates the passed arrays instead of returning. -def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, action_amounts, actionNos): +def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, action_amounts, actionNos, actionTypeByNo): for i in range(seats):#create second dimension arrays tmp=[] card_values.append(tmp) @@ -212,7 +212,7 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake if (category=="holdem" or category=="omahahi" or category=="omahahilo"): streetCount=4 else: - streetCount=8 + streetCount=5 for i in range(streetCount): #build the first dimension array, for streets tmp=[] @@ -221,6 +221,8 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake action_amounts.append(tmp) tmp=[] actionNos.append(tmp) + tmp=[] + actionTypeByNo.append(tmp) for j in range (seats): #second dimension arrays: players tmp=[] action_types[i].append(tmp) @@ -562,7 +564,7 @@ def parseActionAmount(line, atype, site): #doesnt return anything, simply changes the passed arrays action_types and # action_amounts. For stud this expects numeric streets (3-7), for # holdem/omaha it expects predeal, preflop, flop, turn or river -def parseActionLine(site, line, street, names, action_types, action_amounts, actionNos): +def parseActionLine(site, line, street, playerIDs, names, action_types, action_amounts, actionNos, actionTypeByNo): #this only applies to stud if (street<3): text="invalid street ("+str(street)+") for line: "+line @@ -590,6 +592,8 @@ def parseActionLine(site, line, street, names, action_types, action_amounts, act action_types[street][playerno].append(atype) action_amounts[street][playerno].append(amount) actionNos[street][playerno].append(nextActionNo) + tmp=(playerIDs[playerno], atype) + actionTypeByNo[street].append(tmp) #end def parseActionLine #returns the action type code (see table design) of the given action line @@ -1221,7 +1225,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, return result #end def store_hands_players_stud_tourney -def calculateHudImport(player_ids, category, action_types, winnings, totalWinnings): +def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winnings, totalWinnings): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" #setup subarrays of the result dictionary. VPIP=[] @@ -1244,6 +1248,12 @@ def calculateHudImport(player_ids, category, action_types, winnings, totalWinnin wonWhenSeenFlop=[] wonAtSD=[] + firstPfRaise=-1 + for i in range(len(actionTypeByNo[0])): + if actionTypeByNo[0][i][1]=="bet": + firstPfRaise=i + break + for player in range (len(player_ids)): #set default values myVPIP=False @@ -1266,20 +1276,31 @@ def calculateHudImport(player_ids, category, action_types, winnings, totalWinnin myWonWhenSeenFlop=0.0 myWonAtSD=0.0 - #calculate preflop values + #calculate VPIP and PFR street=0 heroPfRaiseCount=0 for count in range (len(action_types[street][player])):#finally individual actions currentAction=action_types[street][player][count] if currentAction=="bet": - heroPfRaiseCount+=1 + myPFR=True if (currentAction=="bet" or currentAction=="call"): myVPIP=True - if heroPfRaiseCount>=1: - myPFR=True - if heroPfRaiseCount>=2: - myPF3B4B=True - + + #PF3B4BChance and PF3B4B + pfFold=-1 + pfRaise=-1 + if firstPfRaise!=-1: + for i in range(len(actionTypeByNo[0])): + if actionTypeByNo[0][i][0]==player_ids[player]: + if actionTypeByNo[0][i][1]=="bet" and pfRaise==-1 and i>firstPfRaise: + pfRaise=i + if actionTypeByNo[0][i][1]=="fold" and pfFold==-1: + pfFold=i + if pfFold==-1 or pfFold>firstPfRaise: + myPF3B4BChance=True + if pfRaise>firstPfRaise: + myPF3B4B=True + #calculate saw* values if (len(action_types[1][player])>0): mySawFlop=True From b0dd505627c4d60f81a4e1849b772c0d78f7330f Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 10 Aug 2008 02:52:05 +0100 Subject: [PATCH 021/262] git21 - added fpdb version into db to detect outdated db format. added fields for steals to DB and placeholder inserts to importer. lots of doc updates split pygtk requirements, cleaned table design HTML more noticed its really slow now - might be because I'm running it over LAN. will figure this out shortly --- docs/abbreviations.txt | 25 +- docs/codingstyle.txt | 4 +- docs/filelist.txt | 19 +- docs/install-in-gentoo.txt | 2 +- docs/install-in-windows.txt | 13 +- docs/known-bugs-and-planned-features.txt | 31 +- docs/readme-dev.txt | 12 +- docs/readme-overview.txt | 16 +- docs/readme-user.txt | 27 +- docs/release-notes.txt | 48 ++- docs/requirements.txt | 29 +- docs/tabledesign.html | 375 +++++-------------- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 26 +- pyfpdb/fpdb_simple.py | 38 +- pyfpdb/table_viewer.py | 2 +- regression-test/ps-flags-3hands.expected.txt | 10 + 17 files changed, 296 insertions(+), 383 deletions(-) diff --git a/docs/abbreviations.txt b/docs/abbreviations.txt index 9e7edd16..115baaa8 100644 --- a/docs/abbreviations.txt +++ b/docs/abbreviations.txt @@ -1,6 +1,5 @@ -In utility output and HUD -========================= -CLI=Command Line Interface (Shell, Terminal, "DOS-window") +HUD/table viewer +================ A3-7=3rd-7th street Complete/Raise percentage AF=Flop Bet/Raise percentage AT=River Bet/Raise percentage @@ -9,17 +8,25 @@ F3-7=3rd-7th street Fold percentage FF=Flop Fold percentage FR=River Fold percentage FT=Turn Fold percentage -FTP=Full Tilt Poker -GUI=Graphical User Interface (normal interface with buttons and menus) HD=Hands -HUD=Heads-Up Display (shows stats directly in the poker software) -MTT=Multi Table Tournament +PF3B4B=Pre Flop 3Bet or 4Bet PFR=Pre Flop Raise -PS=PokerStars -SnG=Sit and Go +SD/F=Showdown/Flop=WtSD=How often player went to showdown when he saw the flop +W$wsF=Won $ when he saw flop +W$@SD=Won $ at showdown VPI3=Voluntary Put In on 3rd Street (ie. call+complete+raise) VPIP=Voluntary Put In Preflop (ie. call+raise) +Other +===== +CLI=Command Line Interface (Shell, Terminal, "DOS-window") +FTP=Full Tilt Poker +GUI=Graphical User Interface (normal interface with buttons and menus) +HUD=Heads-Up Display (shows stats directly in the poker software) +PS=PokerStars +MTT=Multi Table Tournament +SnG=Sit and Go + License ======= Trademarks of third parties have been used under Fair Use or similar laws. diff --git a/docs/codingstyle.txt b/docs/codingstyle.txt index 2fc578e9..31c113f9 100644 --- a/docs/codingstyle.txt +++ b/docs/codingstyle.txt @@ -1,6 +1,6 @@ This is just a loose collection of things so far, but might as well make a start :) -A word on wrapping: Please avoid making manual line breaks, the computer can and therefore should do it. Whether people use a phone or a 40" super-uber-HD screen, they should be allowed to use as much of it as they wish to. +A word on wrapping: Please avoid making manual line breaks, the computer can and therefore should do it. Whether people use a phone or a 40" super-uber-HD screen, they should be allowed to use as much of it as they wish/can. Incidentially, if anyone knows how to activate line wrap in Eclipse I'd really appreciate if you let me know. Comments (or prints) with todo are things that are missing, bugs, or just messy code. @@ -11,6 +11,8 @@ If you don't mind make names in java style, ie.: Classes, files or tables like this: MyClassName Methods and variables like this: myMethodName +I'll eventually change all my code to that style, too. + License ======= Trademarks of third parties have been used under Fair Use or similar laws. diff --git a/docs/filelist.txt b/docs/filelist.txt index bf65ca39..15e9361a 100644 --- a/docs/filelist.txt +++ b/docs/filelist.txt @@ -1,13 +1,13 @@ -todo: update filelist.txt +This is partially outdated File list ========= .: docs/ Documentation files -importer/ Directory with the importer (in python) +pyfpdb/ The main program (in python) setup/ Directory with files for setting up this program -testdata/ Directory with test data -utils/ A good abbreviation for "all the crap that doesn't fit in anywhere else" +regression-test/ Directory with test data, query scripts (in python) and the regression test script (in bash) +utils/ A couple of things that will migrate to the main prog soon viewer/ Directory with the GUI (in Java) ./docs: @@ -20,12 +20,13 @@ filelist.txt This file howto-import.txt Instructions on how to run the importer install-in-gentoo.txt Installation instructions for Gentoo GNU/Linux install-in-windows.txt Installation instructions for Windows -readme.txt This file -status.txt Details of support for poker sites -tabledesign.odt The table design master (OpenDocument file, MS Office needs plugin) -tabledesign.html Table design copy (HTML file) +readme-dev.txt Some notes, pointers and such for developers or anyone else interested in changing fpdb +readme-overview.txt Some general info about this program - read that first +readme-user.txt Instructions on how to use fpdb +status.txt Details of support for poker types and sites +tabledesign.html Table design with comments -./importer: +./pyfpdb: fpdb.py The main GUI. This is what the user will start and use to access the other things. fpdb_import.py Main import program. Calls methods in the other files. Takes one hand history file as input. This is the file diff --git a/docs/install-in-gentoo.txt b/docs/install-in-gentoo.txt index b9f646e2..684ccacd 100644 --- a/docs/install-in-gentoo.txt +++ b/docs/install-in-gentoo.txt @@ -28,7 +28,7 @@ Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edi 3. Guided installation steps Run the GUI as described in readme-user and click the menu database -> recreate tables -That's it! +That's it! Now see readme-user.txt for usage instructions. License diff --git a/docs/install-in-windows.txt b/docs/install-in-windows.txt index b7939f94..69267fb8 100644 --- a/docs/install-in-windows.txt +++ b/docs/install-in-windows.txt @@ -13,17 +13,18 @@ As of this writing the latest binary version is 5.0.51b whilst the latest versio At the end make sure you activate that you want to configure it now. Use the advanced/detailed config. Leave everything as default unless stated below, or unless you have reason not to. Make sure to DEACTIVATE TCP/IP networking, unless you want that and know how to secure it - Activate "include bin directory in windows PATH" Set a root password. Note that this is not the account/pw that fpdb will use. Once finished it shold confirm "service started successfully" +Then configure a user and create a database. + 2. Install python Go to http://www.python.org/download/ and get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts. 3. Install the Python-DBAPI package for MySQL: -Go to http://sourceforge.net/project/showfiles.php?group_id=22307 and get the latest version of MySQL-python-1.2.2.win32-py2.5.exe +Go to http://sourceforge.net/project/showfiles.php?group_id=22307 and get the latest version of MySQL-python-*.win32-py2.5.exe Double click to install. @@ -49,13 +50,7 @@ Right click on "My Computer" ("Arbeitsplatz" in German Windows) on the Desktop o Now edit the file, in particular you will always have to type in the correct password (insecure, I know) and if you differ from the default setup you may need to change host, database or user. -8. Open a shell (aka command prompt, aka DOS window) and go to the fpdb-python folder and run fpdb.py, e.g.: -c: -cd \fpdb\fpdb-python -python fpdb.py - -You can run this by double click, but then any error message would be lost. - +8. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to. When the program started open the menu Database and click "Create or Recreate Tables". That's it! Now you can use the bulk importer and the table viewer, more's coming. See readme-user.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 3c0186ab..c7488f17 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,27 +3,32 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ fix default pathes up to sensible ones -catch index error, type error, file not found error -update install instructions, include how to adapt default config and where to put it -add instructions how to reimport -split python requirements, get deep links for windows DL for everything +get deep links for windows DL for everything GUI license info - -add fpdb version string into db to detect outdated db format and importer bugs update regression testing to take into account everything new, make sure it passes all tests -implement error file in importer next ==== -ST, CB, 2B, 3B, fold to these -optionally show single postflop agg/fold rate +3B/4B might not be recognised nor counted as chance if someone raised after player called. +fill steal fields correctly, add to tester and tv +CB, 2nd/3rd Barrel, fold to these + +separate all gui and all processing into files that are named accordingly +ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. +figure out what slowed it down so much between git19 and git21 (8/9aug) auto-import +why do we have to reconnect in tv.read_names_clicked? +implement error file in importer +catch index error, type error, file not found error +finish updating filelist +optionally show single postflop agg/fold rate use different colours according to classification. -add stud, razz and tourney back to imp/tv but with less seperate codepathes table with data for graphs for SD/F, W$wSF, W$@SD before beta =========== +add stud, razz and tourney back to imp/tv but with less seperate codepathes +offer not storing db password change definition of bet to exclude bring in? in tv, select from hud table using named fields rather than the current * remove remains of mysql/myisam support. @@ -49,7 +54,8 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== -In many places there are unnecessary database accesses or it regenerates information it already had before or just generally does things in obscenely inefficient ways. It's great to have a total of 3 CPUs with a combined 5.6GHz working for you isn't it... ;) In any case, these should be optimised to leave more power for other things, such as dropping to lower power states +In many places there are unnecessary database accesses or it regenerates information it already had before or just generally does things in obscenely inefficient ways. Optimise this +multi-select in bulk importer move version into seperate file make option to use "traditional" labels, e.g. WtSD instead of SD/F HTMLify docs and validate them @@ -68,9 +74,6 @@ select range of stakes and sng/mtt values and types for tv change "for i" to more sensible var name instead of i recognise somewhere if a file is still active and if so keep it open and only read new hands rather than detecting dupes gentoo ebuild -separate all gui and all processing into files that are named accordingly -ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. -why do we have to reconnect in tv.read_names_clicked? can wait till 1.x ================= diff --git a/docs/readme-dev.txt b/docs/readme-dev.txt index ee578f46..6a346dda 100644 --- a/docs/readme-dev.txt +++ b/docs/readme-dev.txt @@ -10,7 +10,8 @@ What to do? - There's a list of various bugs, deficiencies and important missing features in known_bugs_and_planned_features.txt. - In the main GUI there's various menu points marked with todo - all of these will have to be done eventually. -If you want to take a look at coding-style.txt and git-instructions.txt or feel free to send patches or even just changed file in whatever code layout or naming convention you like best. I will, of course, still give you full credit. +If you want to take a look at coding-style.txt. +Ideally use git (see git-instructions.txt for some commands) and let me know where to pull from, alternatively feel free to send patches or even just changed file in whatever code layout or naming convention you like best. I will, of course, still give you full credit. Contact/Communication ===================== @@ -18,16 +19,17 @@ Please see readme-overview Dependencies ============ -Since all real OS' have easy built in handling for dependencies feel free to add requirements on new libraries etc. Unfortunately due to the reality of the online poker market (namely the complete absence of clients for free/libre systems) it doesn't make sense to write this without supporting Windows so all dependencies must have a source-compatible Windows version. Please ensure to list any new deps in requirements.txt or let me know. +Since all real OSs have easy built in handling for dependencies feel free to add requirements on new libraries etc. Unfortunately due to the reality of the online poker market (namely the complete absence of clients for free/libre systems) it doesn't make sense to write this without supporting Windows so all dependencies must have a source-compatible Windows version. Please ensure to list any new deps in requirements.txt or let me know. Code/File/Class Structure ========================= Basically the code runs like this fpdb.py -> bulk importer tab (import_threaded.py) -> fpdb_import.py -> fpdb_parse_logic.py -> fpdb_save_to_db.py - -> table viewer tab (table_viewer.py) +or +fpdb.py -> table viewer tab (table_viewer.py) (todo: -> libTableViewer) -All files can call the simple methods that I just collected in fpdb_simple.py, in essence to reduce the other files mostly to high-level calls that direct the execution flow. +All files call the simple methods that I just collected in fpdb_simple.py, to abstract the other files off the nitty gritty details as I was learning python. I'm currently working on (amongst other things) integrating everything into the fpdb.py GUI with a view to allow easy creation of a CLI client, too. Also see filelist.txt. @@ -48,7 +50,7 @@ Preferred: Where possible simple text-based formats, e.g. plain text (with Unix Also good: Other free and open formats, e.g. ODF. -Not good: Any format that doesn't have full documentation freely and publicly available with a full license for anyone to implement it. Sadly, Microsoft has chosen not fulfil these requirements for ISO MS OOXML to become a truly open standard. +Not good: Any format that doesn't have full documentation freely and publicly available with a proper license for anyone to implement it. Sadly, Microsoft has chosen not fulfil these requirements for ISO MS OOXML to become a truly open standard. License (of this file) ======= diff --git a/docs/readme-overview.txt b/docs/readme-overview.txt index 56e97f14..5d610325 100644 --- a/docs/readme-overview.txt +++ b/docs/readme-overview.txt @@ -17,7 +17,7 @@ At the end of the day this comes down to a question of trust, but unlike Windows - Verify the source code yourself. - Convince or pay someone to verify the source code for you. - Use a personal firewall to completely block fpdb from the Internet -- (for the uber-paranoid) Get yourself the virtualisation software VirtualBox, set up a VM (virtual machine) to run fpdb but run the poker software on your real PC. Then cut the VM off the Internet, fpdb doesn't need it. If you have a PC made in the last few years this should run fast enough as well. Note that most Windows licenses do NOT permit you to use two Windows installations at once, even if they are on the same PC. +- (for the uber-paranoid) Get yourself the free virtualisation software VirtualBox, set up a VM (virtual machine) to run fpdb but run the poker software on your real PC. Then cut the VM off the Internet, fpdb doesn't need it. If you have a PC made in the last few years this should run fast enough as well. Note that most Windows licenses do NOT permit you to use two Windows installations at once, even if they are on the same PC. Installing ========== @@ -42,11 +42,11 @@ its too slow let me know and I'll see what I can do :) Contact ======= -Please contact me directly using one of the below (quickest response will be by jabber/xmpp/google talk) until I have setup Savannah. -mail: steffen(at)sycamoretest.info -jabber/xmpp/Google Talk: as above -ICQ: 7806355 -MSN: steffenjf@gmx.de (don't email that) +- email steffen@sycamoretest.info +- jabber/xmpp/google talk to steffen@sycamoretest.info +- create a ticket in the ticketing system of assembla +- ICQ 7806355 +- MSN steffenjf@gmx.de (don't email here) Why Free Software? ================== @@ -58,8 +58,8 @@ With free/libre software (also known as open source software, or short FOSS or F (note: the legally binding terms are in the license text, this is merely an amateur summary so normal people don't have to read pages of legalese) Freedom 0: The freedom to use: To run the program, for any purpose. Free of Charge. -Freedom 1: The freedom to study and help yourself. This freedom guarantees your right to study and learn from the source code of the program, and to fix it if it is broken. If you're not a programmer yourself the developers will generally be happy to fix it for you. Failing that you can always pay someone from the money you saved on not having to pay for it. -Freedom 2: The freedom to be a decent human being and help your neighbour: I don't threaten you with criminal charges or jail time if you share with your friends and neighbours, subject to the very modest restrictions of the AGPL3. +Freedom 1: The freedom to study and help yourself. This freedom guarantees your right to study and learn from the source code of the program, and to fix it if it is broken. If you're not a programmer yourself the developers will generally be happy to fix it for you, often even for free. Failing that you can always pay someone from the money you saved on not having to pay for it. +Freedom 2: The freedom to be a decent human being and help your neighbour: I don't threaten you with lawsuits or jail time if you share with your friends and neighbours, subject to the very modest restrictions of the AGPL3. Freedom 3: The freedom to improve the program and release your improvements to the public (or parts thereof) so that the whole community benefits. Note that you are PERMITTED, but not REQUIRED to distribute your changes. If you do distribute your changes you must do so under the terms of the AGPL3 however. Note that this is the license - I retain full copyright over my code, including the right to change the license for future versions. I do not intend to do this however. In any case, any version I released under AGPL3 remains available under that license forever, or more accurately until my copyright expires at which point it goes into the public domain. diff --git a/docs/readme-user.txt b/docs/readme-user.txt index 7ae81cb6..6d3d1f76 100644 --- a/docs/readme-user.txt +++ b/docs/readme-user.txt @@ -2,31 +2,32 @@ Before you do this make sure you setup the dependencies, the database, user, tab Running it ========== -If you have python setup properly you can execute it by double clicking fpdb-python/fpdb.py. +If you have python setup properly you can execute it by double clicking pyfpdb/fpdb.py. -Note however that all error messages are currently only printed if you call it from a shell. It'll be much easier to diagnose possible problems (which are likely in alpha stage) if you run it from a shell. To do that open a shell (aka command prompt aka DOS window aka terminal) and run the following commands. -For *nix, e.g. if its in /home/sycamore/fpdb/: -cd /home/sycamore/fpdb/fpdb-python +Note however that all error messages are currently only printed if you call it from a shell. It'll be much easier to diagnose possible problems (which are likely in alpha stage) if you run it from a shell. In Windows XP it seems to automatically open a shell window with the fpdb window where you can see the command line output. + +In Linux/MacOS/*BSD, e.g. if its in /home/sycamore/fpdb/, do this: +cd /home/sycamore/fpdb/pyfpdb python fpdb.py -In Windows, e.g. if its in C:\Program Files\fpdb -C: -cd "\Program Files\fpdb\fpdb-python\" -python fpdb.py That will start the main GUI. -Have a look at the menues, the stuff that is marked todo is not yet implemented. +Have a look at the menus, the stuff that is marked todo is not yet implemented. The main things are the bulk importer and the table viewer. To use the importer open it from the menu (import files and directories). You can set a few options at the bottom, then select a folder or single file in the main are and click Import. Please report any errors by one of the contacts listed in readme-overview.txt. Currently this will block the interface, but you can open another instance of this program e.g. if you wanna play whilst a big import is running. -To use the table viewer open it from the menu, select the hand history file of the table you're at, and click the Import&Read&Refresh button. The abbreviations there are explained in abbreviations.txt, but feel free to ask. - Please check the output at the shell for errors, if there are any please get in touch by one of the methods listed in readme-overview.txt -Please let us know whether you like this software :) -Contacts are listed in readme-overview or email steffen@sycamoretest.info +Table Viewer (tv) +================= +To use the table viewer open it from the menu, select the hand history file of the table you're at, and click the Import&Read&Refresh button. The abbreviations there are explained in abbreviations.txt, but feel free to ask. Note that most poker software will only create the file once the first hand you payed to play is finished. +In each column there is either just the number (hand count for current stake, range of seats and type of game) or a percentage and the number of hands that this percentage is based on. For example, in W$@SD (won $ at shodown) the number in brackets is how many showdowns that player has seen. + +Reimporting +=========== +Currently on most updates a reimport of the whole database is required. To do this open fpdb, click the menu Database and select Create/Recreate tables. Then import all your history files again. License ======= diff --git a/docs/release-notes.txt b/docs/release-notes.txt index 58d800b0..f8de5bcb 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -1,30 +1,27 @@ -v0.01(alpha) draft (ie. minimum requirements for alpha1) +alpha1 draft (ie. minimum requirements for alpha1) Hi everyone, -we are proud to announce the first release of our new poker tracking software fpdb (freepokerdb, very imaginative I know ;) ). You may wonder why we bothered when now with HM and PT3 there are at least two excellent packages to choose from. +I am proud to announce the first release of my new poker tracking software fpdb (freepokerdb, very imaginative I know ;) ). You may wonder why I bothered when now with HM and PT3 there are at least two excellent packages to choose from. Three main reasons: -1. Fpdb is free/libre open source software. In short, this means you don't depend on us if sth. is wrong or you want something more in this program as you can freely change it yourself. You also don't have to pay anything for it. If you like it and think we deserve to be paid contact us :) -2. HM and PT3 only support holdem. Fpdb (initially) supports Holdem, Omaha, Razz and Stud including Hi/Lo versions. -3. HM and PT3 run on Windows only, and for me at least did not work in wine even after installing Mono. Fpdb runs natively on any plattform that has the required software, which will cover roundabout 99.9% of PCs that are in use ;) +1. Fpdb is free/libre open source software. In short, this means you don't depend on me if sth. is wrong or you want something more in this program as you can freely change it yourself. You also don't have to pay anything for it. If you like it and think I deserve to be paid just drop me a mail ;) +2. HM and PT3 only support holdem. Fpdb (initially) supports Holdem and Omaha, with Stud and Razz mostly implemented +3. HM and PT3 run on Windows only, and for me at least did not work in wine even after installing Mono. Fpdb runs natively on any plattform that has the required software, which will cover roundabout 99.9% of PCs. You still need to run Windows or wine to run the actual poker client though. -4. Fpdb won't irritate you with copy prevention measures, e.g. HM will require re-activation after some types of partition change. To be fair I should add that the support is fast, friendly and helpful. Nevertheless I just don't appreciate being hassled AFTER I pay. +4. Fpdb won't irritate you with copy prevention measures, e.g. HM will require re-activation after some types of partition change. To be fair I should add that the support is fast, friendly and helpful. Nevertheless I for one just don't appreciate being hassled AFTER I pay. -This is alpha1, as the name indicates it is still at a very early stage. The importer and database are nearing completion but the GUI in particular is not very functional yet and the HUD is missing altogether. However the difficult bit (getting it all to work nicely together) for v1 is done, now we "just" need to add lots and lots of options, testing and output. +This is alpha1, as the name indicates it is still at a very early stage. The importer and database are nearing completion but the GUI in particular is not very functional yet and the HUD is missing alltogether. Except for the HUD most of the infrastructure is in place though, now I "just" need to add all the bells and whistles and tune it. Current feature list: Interface ========= - Central interface programs with tabs (similar to Azureus classic) -- Follows convention on how things are arranged and what they look like. +- Follows (ish) convention on how things are arranged and what they look like. - Works equally in *nix and Windows (tested on Gentoo GNU/Linux, MacOSX and WinXP) -- Command line interface planned -- DB setup from inside the GUI - Bulk importer for single files, multiple files, or directories (incl recursion) - Auto-importer -- Multi-threaded - running the importer doesn't freeze the view tabs and they don't freeze each other either (blocker for beta) -- Profiles (to store different settings) -- HUD (would be nice for alpha but not a blocker for release) +- Profiles (to store different settings - profile path currently hardcoded as the load_profile function is broken) +- Interface freezes whilst importing, but if you want to start using it whilst a big import is running just start another instance. Backend, Distribution ===================== @@ -33,26 +30,27 @@ Backend, Distribution Site/Game Support ================= -- Initially only full support for PS, ring games also work in FTP. -- Supports Holdem, Omaha, Razz and Stud including Hi/Lo split where applicable +- Initially only full support for PS, FTP will be supported soon +- Supports Holdem, Omaha Hi and Omaha Hi/Lo - Supports No Limit, Pot Limit, Fixed Limit NL, Cap NL and Cap PL Note that currently it does not display extra stats for NL/PL so usefulness is limited for these limit types. Suggestions welcome, I don't play these. -- Supports ring/cash games as well as SnG and MTT tourneys +- Supports ring/cash games, SnG/MTT will be supported soon -- Tableviewer (tv) interface to the database. The application is currently single-threaded (though the backend DB doesn't have to be) but I will fix that. Until then you can just open the interface twice, once for import and once for tv. Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are: - - VPIP, PFR, Steal from SB, Steal from BTN, Steal from CO, Avg Steal - - average bet size for NL/PL (blocker for beta) - - 3/4B, 2B, Raise and Fold % on flop, turn and river. - - Aggr % if everyone folded to player in final position of round - - Number of hands this is based on. - - Equivalent functions for Razz/Stud +- Tableviewer (tv) interface to the database. The application is currently single-threaded but I will fix that, in the meantime just start the interface multiple times. Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are: + - VPIP, PFR and Preflop 3B/4B (3B/4B is not quite correct I think) + - Raise and Fold % on flop, turn and river. Fold only counts hands when someone raised. + - Number of hands this is based on. + - SD/F (aka WtSD, proportion of hands where player went to showdown after seeing flop) + - W$wSF (Won $ when seen Flop) + - W$@SD (Won $ at showdown) + For all stats it also displays how many hands this particular is based on - You can edit/add whatever you like, it's all python and SQL. The code should be fairly straightforward I think and I put some notes into readme-dev.txt but feel free to ask. -If you can live with alpha/beta software please give this a go and send any feedback, feature requests, bug reports and animal names to steffen@sycamoretest.info. Of course support and feature requests with patches or payment offers will be prioritised. +If you can live with alpha software please give this a go and send any feedback, feature requests/suggestions, bug reports and animal names to steffen@sycamoretest.info or pick one of the contact methods listed in readme-overview.txt or reply to this post. -IMPORTANT: The database format will undergo more changes and at this point I am not planning to write a converter so please keep your history files so you can re-import when necessary. Independent of this you should always keep the original raw files in a save place with any tracking software. +IMPORTANT: The database format will undergo more changes and at this point I am not planning to write a converter so please keep your history files so you can re-import when necessary. Independent of this you should always keep the original raw files in a safe place with any tracking software. License diff --git a/docs/requirements.txt b/docs/requirements.txt index a28b3ff7..7f066744 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -6,6 +6,8 @@ If you can be bothered please do contact your poker site(s) and ask them to rele Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least). +If you use a package management system (e.g. if you have GNU/Linux or *BSD) just check that you have mysql, mysql-python and pygtk or postgresql, pygresql and pygtk. Your package manager will take care of the rest for you. + Make new entries in this format: X. Program Name @@ -89,15 +91,36 @@ c. Project Webpage d. License LGPL2 -7. PyGTK +7. PyGObject +============ +a. Optional? + Required. +b. Required Version and Why + ? +c. Project Webpage + main: http://www.pygtk.org +d. License + LGPL2.1 + +8. PyCairo +========== +a. Optional? + Required. +b. Required Version and Why + ? +c. Project Webpage + main: http://www.pygtk.org +d. License + LGPL2.1 + +9. PyGTK ======== a. Optional? Required. b. Required Version and Why - I use 2.12.0 but it should run with 2.10. That is needed as I used AccelMap. + ? c. Project Webpage main: http://www.pygtk.org - Note for Windows: Due to the lack of package management you have to manually get PyGTK's dependencies (PyCairo and PyGobject). d. License LGPL2.1 diff --git a/docs/tabledesign.html b/docs/tabledesign.html index dff5e003..9b66579c 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -22,8 +22,19 @@ Copyright 2008 Steffen Jobbagy-Felso
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 as published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license can be found in fdl-1.2.txt
The program itself is licensed under AGPLv3, see agpl-3.0.txt

See readme.txt for copying

-


-

+

Table settings

+ + + + + + + + + + + +

Field name

Type

Comment

version

smallint

the git version of the database (ie. table design changes and major bugfixes require a bump)

Table players

@@ -48,16 +59,9 @@ players

- - - + + + @@ -65,75 +69,35 @@ players

references sites.id

-

comment

-
-

text

-
-


-

-

comment

text


comment_ts


-


-

+


Table autorates

-

An -autorating is a computer-"recognised" label/category for a -player. Examples could include "Calling Station" if a -player has <20% each for aggression and folding postflop. Or -"Tight-Aggressive/Aggressive" for players with <20% -VPIP, >10% PFR and >40% postflop aggression.

+

An autorating is a computer-"recognised" label/category for a player. Examples could include "Calling Station" if a player has <20% each for aggression and folding postflop. Or "Tight-Aggressive/Aggressive" for players with <20% VPIP, >10% PFR and >40% postflop aggression.

- - - - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -166,23 +129,13 @@ VPIP, >10% PFR and >40% postflop aggression.

int

-

Field name

-
-

Type

-
-

Comment

-

Field name

Type

Comment

-

id

-
-

bigint

-
-


-

-

id

bigint


-

player_id

-
-

int

-
-

references players.id

-

player_id

int

references players.id

-

gametype_id

-
-

smallint

-
-

references gametypes.id

-

gametype_id

smallint

references gametypes.id

-

description

-
-

varchar(50)

-
-

autorating description

-

description

varchar(50)

autorating description

@@ -143,8 +107,7 @@ VPIP, >10% PFR and >40% postflop aggression.

char(8)

-

short description e.g. for - display in HUD

+

short description e.g. for display in HUD

-

number of hands rating is - based on

+

number of hands rating is based on

-


-

-


-

-


-

-

Table -gametypes

+


+

Table gametypes

- - -

Field name

@@ -290,70 +243,33 @@ gametypes


-


-

-

Table -sites

+


+

Table sites

- - - - - - + + + - - - + + + - - - + + + - - - + + +
-

Field name

-
-

Type

-
-

Comment

-

Field name

Type

Comment

-

id

-
-

smallint

-
-


-

-

id

smallint


-

name

-
-

varchar(32)

-
-


-

-

name

varchar(32)


-

currency

-
-

char(3)

-
-

currency code, e.g. USD, - GBP, EUR

-

currency

char(3)

currency code, e.g. USD, GBP, EUR

-


-

-

Table -hands

+


+

Table hands

- - -

Field Name

@@ -519,145 +435,24 @@ board_cards


-


-

-

-
-

-

Table -hands_players

-

cardX: -can be 1 through 7, one for each card. In holdem/omaha this stores -the hole cards so 3-7 or 5-7 are empty

-

I -did not separate this into an extra table because I felt the lost -space is not sufficiently large. Also the benefit for searching is -far less relevant.

+

Table hands_players

+

cardX: can be 1 through 7, one for each card. In holdem/omaha this stores the hole cards so 3-7 or 5-7 are empty

+

I did not separate this into an extra table because I felt the lost space is not sufficiently large. Also the benefit for searching is far less relevant.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-

Field Name

-
-

Type

-
-

Comment

-
-

id

-
-

bigint

-
-


-

-
-

hand_id

-
-

bigint

-
-

references hands_stud.id

-
-

player_id

-
-

int

-
-

references players.id

-
-

player_startcash

-
-

int

-
-


-

-
-

position

-
-

char(1)

-
-

BB=B, - SB=S, Button=0, Cutoff=1, etc.

-

This is used in - holdem/omaha only.

-
-

ante

-
-

int

-
-

note: for cash this could - be boolean, but in tourneys you may enter a hand with less than - the full ante

-
-

cardX_value

-
-

smallint

-
-

2-10=2-10, - J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x

-

see note above table

-
-

cardX_suit

-
-

char(1)

-
-

h=hearts, s=spades, - d=diamonds, c=clubs, unknown/no card=x

-
-

winnings

-
-

int

-
-

winnings in this hand - (bets, antes, etc. are NOT deducted, but rake already is)

-

Field Name

Type

Comment

id

bigint


hand_id

bigint

references hands_stud.id

player_id

int

references players.id

player_startcash

int


position

char(1)

BB=B, SB=S, Button=0, Cutoff=1, etc.

+

This is used in holdem/omaha only.

ante

int

note: for cash this couldbe boolean, but in tourneys you may enter a hand with less thanthe full ante

cardX_value

smallint

2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x

+

see note above table

cardX_suit

char(1)

h=hearts, s=spades, d=diamonds, c=clubs, unknown/no card=x

winnings

int

winnings in this hand (bets, antes, etc. are NOT deducted, but rake already is)

rake

@@ -837,6 +632,36 @@ far less relevant.

float

As wonWhenSeenFlop, but for showdown.

stealAttemptChance

int

Player was in CO, BTN or SB and nobody has called yet

stealAttempted

int

Player took a chance per the above condition

foldBbToStealChance

int

Somebody tried to steal BB from player

foldedBbToSteal

int

Player folded BB to steal attempt

foldSbToStealChance

int

Somebody tried to steal SB from player

foldedSbToSteal

int

Player folded SB to steal attempt

Table hands_actions

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 9f6e7c10..a1705200 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,7 +343,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git20") + self.window.set_title("Free Poker DB - version: pre-alpha, git21") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 7e6f5970..dc95a957 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -34,10 +34,8 @@ class fpdb_db: self.database=database self.user=user self.password=password - #print "fpdb_db.connect, password:",password,"/end" if backend==self.MYSQL_INNODB: import MySQLdb - #print "fpdb_db.connect, host:", host, " user:", user, " passwd:", password, " db:", database self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) elif backend==self.PGSQL: import pgdb @@ -45,6 +43,13 @@ class fpdb_db: else: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) self.cursor=self.db.cursor() + #try: + # self.cursor.execute("SELECT * FROM settings") + # settings=self.cursor.fetchone() + # if settings[0]!=21: + # print "outdated database version - please recreate tables" + #except:# _mysql_exceptions.ProgrammingError: + # print "failed to read settings table - please recreate tables" #end def connect def create_table(self, string): @@ -80,8 +85,9 @@ class fpdb_db: def drop_tables(self): """Drops the fpdb tables from the current db""" + self.cursor.execute("DROP TABLE IF EXISTS settings;") + self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - #self.cursor.execute("DROP TABLE IF EXISTS hands_players_flags;") self.cursor.execute("DROP TABLE IF EXISTS autorates;") self.cursor.execute("DROP TABLE IF EXISTS board_cards;") self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") @@ -113,6 +119,9 @@ class fpdb_db: """(Re-)creates the tables of the current DB""" self.drop_tables() + self.create_table("""settings ( + version SMALLINT)""") + self.create_table("""sites ( id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), name varchar(32), @@ -263,9 +272,16 @@ class fpdb_db: otherRaisedRiver INT, otherRaisedRiverFold INT, wonWhenSeenFlop FLOAT, - wonAtSD FLOAT)""") + wonAtSD FLOAT, + stealAttemptChance INT, + stealAttempted INT, + foldBbToStealChance INT, + foldedBbToSteal INT, + foldSbToStealChance INT, + foldedSbToSteal INT)""") - self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") + self.cursor.execute("INSERT INTO settings VALUES (21);") + self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() print "finished recreating tables" diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index a7610a8c..d8ee4500 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1247,6 +1247,12 @@ def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winni otherRaisedRiverFold=[] wonWhenSeenFlop=[] wonAtSD=[] + stealAttemptChance=[] + stealAttempted=[] + foldBbToStealChance=[] + foldedBbToSteal=[] + foldSbToStealChance=[] + foldedSbToSteal=[] firstPfRaise=-1 for i in range(len(actionTypeByNo[0])): @@ -1275,6 +1281,12 @@ def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winni myOtherRaisedRiverFold=False myWonWhenSeenFlop=0.0 myWonAtSD=0.0 + myStealAttemptChance=False + myStealAttempted=False + myFoldBbToStealChance=False + myFoldedBbToSteal=False + myFoldSbToStealChance=False + myFoldedSbToSteal=False #calculate VPIP and PFR street=0 @@ -1394,6 +1406,12 @@ def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winni otherRaisedRiverFold.append(myOtherRaisedRiverFold) wonWhenSeenFlop.append(myWonWhenSeenFlop) wonAtSD.append(myWonAtSD) + stealAttemptChance.append(myStealAttemptChance) + stealAttempted.append(myStealAttempted) + foldBbToStealChance.append(myFoldBbToStealChance) + foldedBbToSteal.append(myFoldedBbToSteal) + foldSbToStealChance.append(myFoldSbToStealChance) + foldedSbToSteal.append(myFoldedSbToSteal) #add each array to the to-be-returned dictionary result={'VPIP':VPIP} @@ -1415,6 +1433,12 @@ def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winni result['otherRaisedRiverFold']=otherRaisedRiverFold result['wonWhenSeenFlop']=wonWhenSeenFlop result['wonAtSD']=wonAtSD + result['stealAttemptChance']=stealAttemptChance + result['stealAttempted']=stealAttempted + result['foldBbToStealChance']=foldBbToStealChance + result['foldedBbToSteal']=foldedBbToSteal + result['foldSbToStealChance']=foldSbToStealChance + result['foldedSbToSteal']=foldedSbToSteal return result #end def calculateHudImport @@ -1467,17 +1491,23 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if hudImportData['otherRaisedRiverFold'][player]: row[21]+=1 if hudImportData['wonWhenSeenFlop'][player]!=0.0: row[22]+=hudImportData['wonWhenSeenFlop'][player] if hudImportData['wonAtSD'][player]!=0.0: row[23]+=hudImportData['wonAtSD'][player] + if hudImportData['stealAttemptChance'][player]: row[24]+=1 + if hudImportData['stealAttempted'][player]: row[25]+=1 + if hudImportData['foldBbToStealChance'][player]: row[26]+=1 + if hudImportData['foldedBbToSteal'][player]: row[27]+=1 + if hudImportData['foldSbToStealChance'][player]: row[28]+=1 + if hudImportData['foldedSbToSteal'][player]: row[29]+=1 if doInsert: #print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23])) + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29])) else: #print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha - SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s - WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[1], row[2], row[3])) + SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s, stealAttemptChance=%s, stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[1], row[2], row[3])) else: raise FpdbError("todo") #end def storeHudData diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index ebdf4d76..0f0dd71d 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -59,7 +59,7 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wSF", "W$@SD") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wsF", "W$@SD") else: raise fpdb_simple.FpdbError("reimplement stud") tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7", "SD/4") diff --git a/regression-test/ps-flags-3hands.expected.txt b/regression-test/ps-flags-3hands.expected.txt index cf491638..d3af95d0 100644 --- a/regression-test/ps-flags-3hands.expected.txt +++ b/regression-test/ps-flags-3hands.expected.txt @@ -28,3 +28,13 @@ otherRaisedTurn: 1 otherRaisedTurnFold: 0 otherRaisedRiver: 1 otherRaisedRiverFold: 1 + +wonWhenSeenFlop: +wonAtSD: + +stealAttemptChance: +stealAttempted: +foldBbToStealChance: +foldedBbToSteal: +foldSbToStealChance: +foldedSbToSteal: From e56e071b7c147e072669b23fcfd7f16956ea2ae4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 10 Aug 2008 03:26:51 +0100 Subject: [PATCH 022/262] git22 - now shows single postflop agg/fold rate - can be changed by editing fpdb. may flip this arbitrarily, will export it to profile soon --- docs/known-bugs-and-planned-features.txt | 2 +- pyfpdb/fpdb.py | 2 +- pyfpdb/table_viewer.py | 24 ++++++++++++++++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index c7488f17..df741292 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -21,7 +21,7 @@ why do we have to reconnect in tv.read_names_clicked? implement error file in importer catch index error, type error, file not found error finish updating filelist -optionally show single postflop agg/fold rate +export combinedPostflop from table_viewer.prepareData to profile use different colours according to classification. table with data for graphs for SD/F, W$wSF, W$@SD diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index a1705200..c0846f60 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,7 +343,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git21") + self.window.set_title("Free Poker DB - version: pre-alpha, git22") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 0f0dd71d..2d1b9299 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -57,9 +57,12 @@ class table_viewer (threading.Thread): """prepares the data for display by refresh_clicked, returns a 2D array""" #print "start of prepare_data" arr=[] + combinedPostflop=True #todo: export as option #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wsF", "W$@SD") + if (combinedPostflop): + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "F-R Aggr", "F-R Fold", "SD/F", "W$wsF", "W$@SD") else: raise fpdb_simple.FpdbError("reimplement stud") tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7", "SD/4") @@ -105,12 +108,21 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[5],row[4])) #VPIP tmp.append(self.hudDivide(row[6],row[4])) #PFR tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B - tmp.append(self.hudDivide(row[13],row[9])+" ("+str(row[9])+")") #AF - tmp.append(self.hudDivide(row[17],row[16])+" ("+str(row[16])+")") #FF - tmp.append(self.hudDivide(row[14],row[10])+" ("+str(row[10])+")") #AT - tmp.append(self.hudDivide(row[19],row[18])+" ("+str(row[18])+")") #FT - tmp.append(self.hudDivide(row[15],row[11])+" ("+str(row[11])+")") #AR - tmp.append(self.hudDivide(row[21],row[20])+" ("+str(row[20])+")") #FR + if (combinedPostflop): + aggCount=row[13]+row[14]+row[15] + handCount=row[9]+row[10]+row[11] + foldCount=row[17]+row[19]+row[21] + otherRaiseCount=row[16]+row[18]+row[20] + tmp.append(self.hudDivide(aggCount,handCount)+" ("+str(handCount)+")") #Agg + tmp.append(self.hudDivide(foldCount,otherRaiseCount)+" ("+str(otherRaiseCount)+")") #FF + else: + tmp.append(self.hudDivide(row[13],row[9])+" ("+str(row[9])+")") #AF + tmp.append(self.hudDivide(row[17],row[16])+" ("+str(row[16])+")") #FF + tmp.append(self.hudDivide(row[14],row[10])+" ("+str(row[10])+")") #AT + tmp.append(self.hudDivide(row[19],row[18])+" ("+str(row[18])+")") #FT + tmp.append(self.hudDivide(row[15],row[11])+" ("+str(row[11])+")") #AR + tmp.append(self.hudDivide(row[21],row[20])+" ("+str(row[20])+")") #FR + tmp.append(self.hudDivide(row[12],row[9])+" ("+str(row[9])+")") #SD/F tmp.append(self.hudDivide(row[22],row[9])+" ("+str(row[9])+")") #W$wSF tmp.append(self.hudDivide(row[23],row[12])+" ("+str(row[12])+")") #W$@SD From 488af76f8e824c635932781893dbf8147786b144 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 10 Aug 2008 03:34:45 +0100 Subject: [PATCH 023/262] git23 - added references to readme-user and agpl to main screen --- pyfpdb/fpdb.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index c0846f60..05321821 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -319,10 +319,9 @@ class fpdb: def tab_main_help(self, widget, data): """Displays a tab with the main fpdb help screen""" #print "start of tab_main_help" - mh_tab=gtk.Label("""Welcome to Fpdb -blabla todo make this read a file for the helptext -blabla -blabla""") + mh_tab=gtk.Label("""Welcome to Fpdb! +For howto information please see docs/readme-user.txt +This program is licensed under the AGPL3, see docs/agpl-3.0.txt""") self.add_and_display_tab(mh_tab, "main help") #end def tab_main_help @@ -343,7 +342,7 @@ blabla""") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git22") + self.window.set_title("Free Poker DB - version: pre-alpha, git23") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) From 9b737612d01ffe0bd60e5235c86acc76893ae1a7 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 11 Aug 2008 00:44:46 +0100 Subject: [PATCH 024/262] git24 - changed config file format to match new way of passing around settings inside the code - will use this to facilitate various code cleanups and a generalised method of dealing with settings got deep links for windows DL for everything tv option of combined postflop is now exported to profile --- docs/abbreviations.txt | 2 + docs/default.conf | 12 ++--- docs/install-in-windows.txt | 21 +++++---- docs/known-bugs-and-planned-features.txt | 6 +-- docs/requirements.txt | 9 ++-- pyfpdb/fpdb.py | 59 ++++++++++-------------- pyfpdb/table_viewer.py | 10 ++-- 7 files changed, 56 insertions(+), 63 deletions(-) diff --git a/docs/abbreviations.txt b/docs/abbreviations.txt index 115baaa8..fd02a6b1 100644 --- a/docs/abbreviations.txt +++ b/docs/abbreviations.txt @@ -11,6 +11,8 @@ FT=Turn Fold percentage HD=Hands PF3B4B=Pre Flop 3Bet or 4Bet PFR=Pre Flop Raise +Postf A=Postflop (ie. flop+turn+river) Aggression% +Postf F=Postflop Fold % SD/F=Showdown/Flop=WtSD=How often player went to showdown when he saw the flop W$wsF=Won $ when he saw flop W$@SD=Won $ at showdown diff --git a/docs/default.conf b/docs/default.conf index 05bdb743..909548bc 100644 --- a/docs/default.conf +++ b/docs/default.conf @@ -1,6 +1,6 @@ -backend=2 -host=localhost -database=fpdb -user=fpdb -password=enterYourPwHere - +db-backend=2 +db-host=localhost +db-databaseName=fpdb +db-user=fpdb +db-password=enterYourPwHere +tv-combinedPostflop=True diff --git a/docs/install-in-windows.txt b/docs/install-in-windows.txt index 69267fb8..91ca35e2 100644 --- a/docs/install-in-windows.txt +++ b/docs/install-in-windows.txt @@ -3,12 +3,17 @@ made them in XP Pro, if you discover any differences or problems please let me k Also see the other install-in-*.txt files. The length of these instructions is due to MS refusal to provide any kind of package management. -For some packages I've given direct(ish) download links here, for the remainder check requirements.txt. +Here are direct download links from 10Aug2008: +http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.67-win32.zip/from/pick#mirrors +http://www.python.org/ftp/python/2.5.2/python-2.5.2.msi +http://downloads.sourceforge.net/mysql-python/MySQL-python-1.2.2.win32-py2.5.exe?modtime=1173863337&big_mirror=0 +http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.12/gtk+-bundle-2.12.11.zip +http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.4/pycairo-1.4.12-1.win32-py2.5.exe +http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.14/pygobject-2.14.1-1.win32-py2.5.exe +http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.12/pygtk-2.12.1-2.win32-py2.5.exe 1a. Install MySQL and do its basic setup -Download Windows ZIP/Setup.exe from http://dev.mysql.com/downloads/mysql/5.0.html#win32 -Note that there is a link to skip registration/login. -As of this writing the latest binary version is 5.0.51b whilst the latest version is 5.0.56... Windows. +- Download Windows ZIP/Setup.exe - Unzip the archive, execute the setup file At the end make sure you activate that you want to configure it now. Use the advanced/detailed config. Leave everything as default unless stated below, or unless you have reason not to. @@ -20,12 +25,11 @@ Once finished it shold confirm "service started successfully" Then configure a user and create a database. 2. Install python -Go to http://www.python.org/download/ and get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts. +Get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts. 3. Install the Python-DBAPI package for MySQL: -Go to http://sourceforge.net/project/showfiles.php?group_id=22307 and get the latest version of MySQL-python-*.win32-py2.5.exe -Double click to install. +Get the package and double click to install. 4. In MySQL create a new database fpdb and a user by the same name. Set a password. I did this in webmin. Then set permissions for that user to: Select | Insert | Update | Delete | Create | Drop @@ -36,8 +40,7 @@ To use it, create some empty folder like c:\gtk . Using either Windows Explorer's built-in zip file management, or the command-line unzip.exe from ftp://tug.ctan.org/tex-archive/tools/zip/info-zip/WIN32/unz552xN.exe -unzip this bundle. (But you presumably already did that, as you are -reading this file.) +unzip this bundle. Then add the bin folder to your PATH. Make sure you have no other versions of GTK+ in PATH. diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index df741292..cf25cf51 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,13 +3,12 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ fix default pathes up to sensible ones -get deep links for windows DL for everything -GUI license info update regression testing to take into account everything new, make sure it passes all tests +3B/4B might not be recognised nor counted as chance if someone raised after player called. +add to install the sql commands to create DB etc. next ==== -3B/4B might not be recognised nor counted as chance if someone raised after player called. fill steal fields correctly, add to tester and tv CB, 2nd/3rd Barrel, fold to these @@ -21,7 +20,6 @@ why do we have to reconnect in tv.read_names_clicked? implement error file in importer catch index error, type error, file not found error finish updating filelist -export combinedPostflop from table_viewer.prepareData to profile use different colours according to classification. table with data for graphs for SD/F, W$wSF, W$@SD diff --git a/docs/requirements.txt b/docs/requirements.txt index 7f066744..8ba9053f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -7,7 +7,6 @@ If you can be bothered please do contact your poker site(s) and ask them to rele Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least). If you use a package management system (e.g. if you have GNU/Linux or *BSD) just check that you have mysql, mysql-python and pygtk or postgresql, pygresql and pygtk. Your package manager will take care of the rest for you. - Make new entries in this format: X. Program Name @@ -91,8 +90,8 @@ c. Project Webpage d. License LGPL2 -7. PyGObject -============ +7. PyCairo +========== a. Optional? Required. b. Required Version and Why @@ -102,8 +101,8 @@ c. Project Webpage d. License LGPL2.1 -8. PyCairo -========== +8. PyGObject +============ a. Optional? Required. b. Required Version and Why diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 05321821..7a8bd7fa 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -241,43 +241,33 @@ class fpdb: self.profile=filename self.bulk_import_default_path="/work/poker-histories/wine-ps/" #/todo: move this to .conf - - found_backend, found_host, found_database, found_user, found_password=False, False, False, False, False + self.settings={'db-host':"localhost", 'db-backend':2, 'db-databaseName':"fpdb", 'db-user':"fpdb"} + if (os.sep=="/"): + self.settings['os']="linuxmac" + else: + self.settings['os']="windows" for i in range(len(lines)): - if lines[i].startswith("backend="): - backend=int(lines[i][8:-1]) - found_backend=True - elif lines[i].startswith("host="): - host=lines[i][5:-1] - #self.host=host - found_host=True - elif lines[i].startswith("database="): - database=lines[i][9:-1] - #self.database=database - found_database=True - elif lines[i].startswith("user="): - user=lines[i][5:-1] - found_user=True - elif lines[i].startswith("password="): - password=lines[i][9:-1] - found_password=True - - if not found_backend: - raise fpdb_simple.FpdbError("failed to read backend from settings file:"+filename) - elif not found_host: - raise fpdb_simple.FpdbError("failed to read host from settings file:"+filename) - elif not found_database: - raise fpdb_simple.FpdbError("failed to read database from settings file:"+filename) - elif not found_user: - raise fpdb_simple.FpdbError("failed to read user from settings file:"+filename) - elif not found_password: - raise fpdb_simple.FpdbError("failed to read password from settings file:"+filename) + if lines[i].startswith("db-backend="): + self.settings['db-backend']=int(lines[i][11:-1]) + elif lines[i].startswith("db-host="): + self.settings['db-host']=lines[i][8:-1] + elif lines[i].startswith("db-databaseName="): + self.settings['db-database']=lines[i][16:-1] + elif lines[i].startswith("db-user="): + self.settings['db-user']=lines[i][8:-1] + elif lines[i].startswith("db-password="): + self.settings['db-password']=lines[i][12:-1] + elif lines[i].startswith("tv-combinedPostflop="): + if lines[i].find("True")!=-1: + self.settings['tv-combinedPostflop']=True + else: + self.settings['tv-combinedPostflop']=False if self.db!=None: self.db.disconnect() self.db = fpdb_db.fpdb_db() - self.db.connect(backend, host, database, user, password) + self.db.connect(self.settings['db-backend'], self.settings['db-host'], self.settings['db-databaseName'], self.settings['db-user'], self.settings['db-password']) #end def load_profile def not_implemented(self): @@ -320,15 +310,16 @@ class fpdb: """Displays a tab with the main fpdb help screen""" #print "start of tab_main_help" mh_tab=gtk.Label("""Welcome to Fpdb! -For howto information please see docs/readme-user.txt -This program is licensed under the AGPL3, see docs/agpl-3.0.txt""") +For howto information please see docs"""+os.sep+"""readme-user.txt +The abbrevations in the table viewer are explained in docs"""+os.sep+"""abbrevations.txt +This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.add_and_display_tab(mh_tab, "main help") #end def tab_main_help def tab_table_viewer(self, widget, data): """opens a table viewer tab""" #print "start of tab_table_viewer" - new_tv_thread=table_viewer.table_viewer(self.db) + new_tv_thread=table_viewer.table_viewer(self.db, self.settings) self.threads.append(new_tv_thread) tv_tab=new_tv_thread.get_vbox() self.add_and_display_tab(tv_tab, "table viewer") diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 2d1b9299..0f6ba8a8 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -57,12 +57,11 @@ class table_viewer (threading.Thread): """prepares the data for display by refresh_clicked, returns a 2D array""" #print "start of prepare_data" arr=[] - combinedPostflop=True #todo: export as option #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wsF", "W$@SD") - if (combinedPostflop): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "F-R Aggr", "F-R Fold", "SD/F", "W$wsF", "W$@SD") + if self.settings['tv-combinedPostflop']: + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "Postf A", "Postf F", "SD/F", "W$wsF", "W$@SD") else: raise fpdb_simple.FpdbError("reimplement stud") tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7", "SD/4") @@ -108,7 +107,7 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[5],row[4])) #VPIP tmp.append(self.hudDivide(row[6],row[4])) #PFR tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B - if (combinedPostflop): + if self.settings['tv-combinedPostflop']: aggCount=row[13]+row[14]+row[15] handCount=row[9]+row[10]+row[11] foldCount=row[17]+row[19]+row[21] @@ -222,12 +221,13 @@ class table_viewer (threading.Thread): return self.main_vbox #end def get_vbox - def __init__(self, db, debug=True): + def __init__(self, db, settings, debug=True): """Constructor for table_viewer""" self.debug=debug #print "start of table_viewer constructor" self.db=db self.cursor=db.cursor + self.settings=settings self.main_vbox = gtk.VBox(False, 0) self.main_vbox.show() From 47bf90d71c9620480cf18f87c97e26e959cfd3d6 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 11 Aug 2008 01:29:08 +0100 Subject: [PATCH 025/262] git25 - default path of importer and table viewer are now plattform dependent defaults that'll work on default setups but can also be changed using the profile file --- docs/default.conf | 2 ++ docs/known-bugs-and-planned-features.txt | 3 ++- pyfpdb/fpdb.py | 19 ++++++++++++++++--- pyfpdb/table_viewer.py | 2 +- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/default.conf b/docs/default.conf index 909548bc..dadf6290 100644 --- a/docs/default.conf +++ b/docs/default.conf @@ -4,3 +4,5 @@ db-databaseName=fpdb db-user=fpdb db-password=enterYourPwHere tv-combinedPostflop=True +bulkImport-defaultPath=default +tv-defaultPath=default diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index cf25cf51..a09d0338 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -5,7 +5,8 @@ before alpha fix default pathes up to sensible ones update regression testing to take into account everything new, make sure it passes all tests 3B/4B might not be recognised nor counted as chance if someone raised after player called. -add to install the sql commands to create DB etc. +add to install the sql commands to create DB etc. +test everything actually works in windows too next ==== diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 7a8bd7fa..24631aae 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -240,12 +240,19 @@ class fpdb: print "Opened and read profile file", filename self.profile=filename - self.bulk_import_default_path="/work/poker-histories/wine-ps/" #/todo: move this to .conf self.settings={'db-host':"localhost", 'db-backend':2, 'db-databaseName':"fpdb", 'db-user':"fpdb"} if (os.sep=="/"): self.settings['os']="linuxmac" else: self.settings['os']="windows" + + if self.settings['os']=="windows": + self.settings['bulkImport-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\filename.txt" + self.settings['tv-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\filename.txt" + else: + self.settings['bulkImport-defaultPath'] = os.path.expanduser("~") + "/.wine/drive_c/Program Files/PokerStars/HandHistory/filename.txt" + self.settings['tv-defaultPath'] = os.path.expanduser("~")+"/.wine/drive_c/Program Files/PokerStars/HandHistory/filename.txt" + for i in range(len(lines)): if lines[i].startswith("db-backend="): self.settings['db-backend']=int(lines[i][11:-1]) @@ -262,6 +269,12 @@ class fpdb: self.settings['tv-combinedPostflop']=True else: self.settings['tv-combinedPostflop']=False + elif lines[i].startswith("bulkImport-defaultPath="): + if lines[i][23:-1]!="default": + self.settings['bulkImport-defaultPath']=lines[i][23:-1] + elif lines[i].startswith("tv-defaultPath="): + if lines[i][15:-1]!="default": + self.settings['tv-defaultPath']=lines[i][15:-1] if self.db!=None: self.db.disconnect() @@ -300,7 +313,7 @@ class fpdb: def tab_bulk_import(self, widget, data): """opens a tab for bulk importing""" #print "start of tab_bulk_import" - new_import_thread=import_threaded.import_threaded(self.db, self.bulk_import_default_path) + new_import_thread=import_threaded.import_threaded(self.db, self.settings['bulkImport-defaultPath']) self.threads.append(new_import_thread) bulk_tab=new_import_thread.get_vbox() self.add_and_display_tab(bulk_tab, "bulk import") @@ -333,7 +346,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git23") + self.window.set_title("Free Poker DB - version: pre-alpha, git25") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 0f6ba8a8..c8d01b87 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -241,7 +241,7 @@ class table_viewer (threading.Thread): self.filename_label.show() self.filename_tbuffer=gtk.TextBuffer() - self.filename_tbuffer.set_text("/home/sycamore/ps-history/HH20080726 Meliboea - $0.10-$0.20 - Limit Hold'em.txt") + self.filename_tbuffer.set_text(self.settings['tv-defaultPath']) self.filename_tview=gtk.TextView(self.filename_tbuffer) self.settings_hbox.pack_start(self.filename_tview, True, True, padding=5) self.filename_tview.show() From 09f233d6e443a69853d1a30b94e369024047158e Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 11 Aug 2008 17:39:13 +0100 Subject: [PATCH 026/262] git26 - add to install the sql commands to create DB etc. fixed little bug in load_profile introduced by recent improvements there updated regression testing to take into account everything new, made sure it passes all tests more table design cleaning various doc updates added create-release.sh to automate release creation --- create-release.sh | 31 +++ docs/install-in-gentoo.txt | 28 +- docs/install-in-windows.txt | 25 +- docs/known-bugs-and-planned-features.txt | 10 +- docs/readme-dev.txt | 8 +- docs/readme-overview.txt | 26 +- docs/release-notes.txt | 47 ++-- docs/tabledesign.html | 259 +++++-------------- pyfpdb/fpdb.py | 5 +- pyfpdb/fpdb_db.py | 1 + regression-test/ps-flags-3hands.expected.txt | 16 +- 11 files changed, 203 insertions(+), 253 deletions(-) create mode 100755 create-release.sh diff --git a/create-release.sh b/create-release.sh new file mode 100755 index 00000000..1d1d0aaf --- /dev/null +++ b/create-release.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +rm regression-test/*.found.txt +rm regression-test/*.pyc +rm pyfpdb/*.pyc + +mkdir fpdb-$1 +cp -R docs fpdb-$1/ +cp -R pyfpdb fpdb-$1/ +cp -R regression-test fpdb-$1/ +cp -R utils fpdb-$1/ +cd fpdb-$1 +zip -r ../fpdb-$1.zip * +tar -cf - * | bzip2 > ../fpdb-$1.tar.bz2 +cd .. +rm -r fpdb-$1 diff --git a/docs/install-in-gentoo.txt b/docs/install-in-gentoo.txt index 684ccacd..12e20067 100644 --- a/docs/install-in-gentoo.txt +++ b/docs/install-in-gentoo.txt @@ -21,11 +21,33 @@ rc-update add postgresql default emerge --config mysql The --config step will ask you for the mysql root user - set this securely, we will create a seperate account for fpdb -Create a user and a database, the default names are fpdb but you can choose whatever you want to. I did this in webmin, but it will be added to the GUI as well. Then set permissions for that user to: Select | Insert | Update | Delete | Create | Drop +3. Create a mysql user and a database +Now open a shell (aka command prompt aka DOS window): +Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open. -Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edit it according to what you configured just now, in particular you will definitely have to put in the password you configured. YES, THIS IS INSECURE. +Type (replacing yourPassword with the root password for MySQL you specified during installation): +mysql --user=root --password=yourPassword -3. Guided installation steps +It should say something like this: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 4 +Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1 + +Type 'help;' or '\h' for help. Type '\c' to clear the buffer. + +mysql> + + +Now create the actual database. The default name is fpdb, I recommend you keep it. Type this: +CREATE DATABASE fpdb; + +Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password): +GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION; + +Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edit it according to what you configured just now, in particular you will definitely have to put in the password you configured. I know this is insecure, will fix it before stable release. + + +4. Guided installation steps Run the GUI as described in readme-user and click the menu database -> recreate tables That's it! Now see readme-user.txt for usage instructions. diff --git a/docs/install-in-windows.txt b/docs/install-in-windows.txt index 91ca35e2..d9b27768 100644 --- a/docs/install-in-windows.txt +++ b/docs/install-in-windows.txt @@ -22,7 +22,30 @@ http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.12/pygtk-2.12.1-2.win32-py Once finished it shold confirm "service started successfully" -Then configure a user and create a database. +1b. MySQL database and user setup +Now open a shell (aka command prompt aka DOS window): +Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open. + +Type (replacing yourPassword with the root password for MySQL you specified during installation): +mysql --user=root --password=yourPassword + +It should say something like this: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 4 +Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1 + +Type 'help;' or '\h' for help. Type '\c' to clear the buffer. + +mysql> + + +Now create the actual database. The default name is fpdb, I recommend you keep it. Type this: +CREATE DATABASE fpdb; + +Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password): +GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION; + +Exit mysql by pressing Ctrl+D 2. Install python Get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts. diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index a09d0338..f9e18080 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,14 +2,13 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ -fix default pathes up to sensible ones -update regression testing to take into account everything new, make sure it passes all tests -3B/4B might not be recognised nor counted as chance if someone raised after player called. -add to install the sql commands to create DB etc. test everything actually works in windows too +verify link in release notes next ==== +3B/4B might not be recognised nor counted as chance if someone raised after player called. +uncomment version checking fill steal fields correctly, add to tester and tv CB, 2nd/3rd Barrel, fold to these @@ -27,6 +26,7 @@ table with data for graphs for SD/F, W$wSF, W$@SD before beta =========== add stud, razz and tourney back to imp/tv but with less seperate codepathes +move prepare-git.sh and create-release.sh to utils offer not storing db password change definition of bet to exclude bring in? in tv, select from hud table using named fields rather than the current * @@ -34,7 +34,7 @@ remove remains of mysql/myisam support. tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem tourney bug: fails recognisePlayer tourney bug: fails with tuple error in recogniseplayerid -fix load profile +fix GUI's load profile HUD config wizard file permission script, use games group diff --git a/docs/readme-dev.txt b/docs/readme-dev.txt index 6a346dda..fe611a17 100644 --- a/docs/readme-dev.txt +++ b/docs/readme-dev.txt @@ -1,7 +1,7 @@ Hi, -This document is to serve as a first point of contact for current and prospective developers with: -a) organisational/legal things -b) introduction into the code structure +This document is to serve as a little overview (later: full technical doc) for current and prospective developers with: +a) introduction into the code structure +b) organisational/legal things What to do? =========== @@ -42,7 +42,7 @@ Copyright/Licensing =================== Copyright by default is handled on a per-file basis. If you send in a patch or make a commit to an existing file it is done on the understanding that you transfer all rights (as far as legally possible in your jurisdiction) to the current copyright holder of that file, unless otherwise stated. If you create a new file please ensure to include a copyright and license statement. -The licenses used by this project are the AGPL3 for code and FDL1.2 for documentation. Why AGPL3? As far as I know it is currently the strongest copyleft license. +The licenses used by this project are the AGPL3 for code and FDL1.2 for documentation. See readme-overview.txt for reasons and if you wish to use fpdb with different licensing. Preferred File Formats ====================== diff --git a/docs/readme-overview.txt b/docs/readme-overview.txt index 5d610325..825a61de 100644 --- a/docs/readme-overview.txt +++ b/docs/readme-overview.txt @@ -1,16 +1,20 @@ Summary ======= -A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future +A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future. Some of this is not yet working though, please see status.txt and known-bugs-and-planned-features.txt Contact ======= +Please note that this project has two hostings: one at assembla.com which holds are version control "central tree" (irrelevant for users) and everything else at http://sourceforge.net/projects/fpdb/. + +The best means of contact are the sourceforge page: Use the bug, feature request or patch functions or just post in the forum. + +Alternatively feel free to contact me directly: + mail: steffen(at)sycamoretest.info jabber/xmpp/Google Talk: as above ICQ: 7806355 MSN: steffenjf@gmx.de (don't email that) -You can also create tickets on our assembla page at todo get link - But you could send all my hand histories to yourself! ===================================================== At the end of the day this comes down to a question of trust, but unlike Windows and the poker client software you don't have to trust fpdb blindly. You can: @@ -40,14 +44,6 @@ As for hardware, my main test machine is a Pentium 3-M 800 with 256 RAM and Gent program will have to work on that. If you run an even more ancient machine and its too slow let me know and I'll see what I can do :) -Contact -======= -- email steffen@sycamoretest.info -- jabber/xmpp/google talk to steffen@sycamoretest.info -- create a ticket in the ticketing system of assembla -- ICQ 7806355 -- MSN steffenjf@gmx.de (don't email here) - Why Free Software? ================== This program is released under the terms of the free/libre software license AGPL3 as released by the FSF. The AGPL3 protects your rights and those of the wider community. As Richard Stallman, one of the founders of the free software movement, put it: "Free software is a matter of liberty, not price. To understand the concept, you should think of free as in free speech, not as in free beer." (well, it is both really, like the right to vote used to be free) @@ -70,12 +66,12 @@ Can I get/use this under a different license? ============================================= The short answer: Maybe. The long one: As detailed, I fully support what the FSF does and aims to achieve with the GPL. However, I realise that many free software developers don't object to closed source, some don't even object to closed source profiteering of their charity, and I don't think I have any right to go and tell them they're wrong. -So if anyone wishes to use all or part of my code in another open source project with an AGPL3-incompatible license then let me know and we should be able to come to an agreement. -If you wish to use all or part of this in closed source let me know how much that is worth to you, I support free software but at the end of the day you can't pay rent with code ;) +So if anyone wishes to use all or part of my code in another free software/open source project with an AGPL3-incompatible license such as BSD then let me know and we'll figure out a solution that makes everyone happy. +If you wish to use all or part of this in closed source let me know how much if anything that is worth to you and I'm sure we'll be able to reach an agreement. Note that you are NOT permitted to just use fpdb code in closed source development whether in-house or by an independent software developer, you will NEED an additionally agreement with me to get fpdb under different licensing conditions. -Disclaimer, License of this Document -==================================== +License of this Document +======================== The views expressed in this document are those of Steffen Jobbagy-Felso, other members of the fpdb team and external contributors may or may not agree. Trademarks of third parties have been used under Fair Use or similar laws. diff --git a/docs/release-notes.txt b/docs/release-notes.txt index f8de5bcb..ac8668b8 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -1,27 +1,24 @@ -alpha1 draft (ie. minimum requirements for alpha1) +alpha1 Hi everyone, I am proud to announce the first release of my new poker tracking software fpdb (freepokerdb, very imaginative I know ;) ). You may wonder why I bothered when now with HM and PT3 there are at least two excellent packages to choose from. -Three main reasons: +Four main reasons: 1. Fpdb is free/libre open source software. In short, this means you don't depend on me if sth. is wrong or you want something more in this program as you can freely change it yourself. You also don't have to pay anything for it. If you like it and think I deserve to be paid just drop me a mail ;) -2. HM and PT3 only support holdem. Fpdb (initially) supports Holdem and Omaha, with Stud and Razz mostly implemented -3. HM and PT3 run on Windows only, and for me at least did not work in wine even after installing Mono. Fpdb runs natively on any plattform that has the required software, which will cover roundabout 99.9% of PCs. +2. HM and PT3 only support holdem. Fpdb initially supports Holdem and Omaha, with Stud and Razz mostly implemented and coming soon. +3. HM and PT3 run on Windows only, and for me at least did not work in wine even after installing Mono. Fpdb runs on any plattform that has the required software, which will cover roundabout 99.9% of PCs. Currently tested plattforms are Windows XP-x86 and Gentoo GNU/Linux-amd64 as well as -x86. You still need to run Windows or wine to run the actual poker client though. -4. Fpdb won't irritate you with copy prevention measures, e.g. HM will require re-activation after some types of partition change. To be fair I should add that the support is fast, friendly and helpful. Nevertheless I for one just don't appreciate being hassled AFTER I pay. +4. Fpdb won't irritate you with copy prevention measures. -This is alpha1, as the name indicates it is still at a very early stage. The importer and database are nearing completion but the GUI in particular is not very functional yet and the HUD is missing alltogether. Except for the HUD most of the infrastructure is in place though, now I "just" need to add all the bells and whistles and tune it. +To install it go to https://sourceforge.net/projects/showfiles.php?group_id=226872 and download the zip or tar.bz2, unpack it, and follow the instructions in docs/install-in-* for your operating system (e.g. docs\install-in-windows.txt). Sourceforge lists the release files as source files, not as binary executables - this is correct, python will automagically compile as and when required. -Current feature list: +This is alpha1, as the name indicates it is still at a very early stage. The importer and database are nearing completion but the GUI in particular is not very functional yet and the HUD is missing alltogether. -Interface -========= -- Central interface programs with tabs (similar to Azureus classic) -- Follows (ish) convention on how things are arranged and what they look like. -- Works equally in *nix and Windows (tested on Gentoo GNU/Linux, MacOSX and WinXP) -- Bulk importer for single files, multiple files, or directories (incl recursion) -- Auto-importer -- Profiles (to store different settings - profile path currently hardcoded as the load_profile function is broken) -- Interface freezes whilst importing, but if you want to start using it whilst a big import is running just start another instance. +If anyone wishes to help with development that would be very very welcome and I've put a few notes in docs/readme-dev.txt in the download for what you could do. Or just start coding and set me the patches :) +If you're not a programmer and you're not interested in learning it you can still help simply by trying it out and sending bug reports and feature requests. To avoid unrealistic expectations I'd like to state that it'll be a long time until fpdb reaches feature parity to established paid-for closed source software. + +Feature List (short now, growing fast ;) ): + +You can edit/add whatever you like, it's all python and SQL. The code should be fairly straightforward I think and I put some notes into readme-dev.txt but feel free to ask. Backend, Distribution ===================== @@ -30,27 +27,29 @@ Backend, Distribution Site/Game Support ================= -- Initially only full support for PS, FTP will be supported soon -- Supports Holdem, Omaha Hi and Omaha Hi/Lo +- Initially only full support for PS, FTP coming soon +- Supports Holdem, Omaha Hi and Omaha Hi/Lo. Stud and Razz coming soon. - Supports No Limit, Pot Limit, Fixed Limit NL, Cap NL and Cap PL Note that currently it does not display extra stats for NL/PL so usefulness is limited for these limit types. Suggestions welcome, I don't play these. -- Supports ring/cash games, SnG/MTT will be supported soon +- Supports ring/cash games, SnG/MTT coming soon - -- Tableviewer (tv) interface to the database. The application is currently single-threaded but I will fix that, in the meantime just start the interface multiple times. Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are: +Tableviewer (tv) +=========== +Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are: - VPIP, PFR and Preflop 3B/4B (3B/4B is not quite correct I think) - - Raise and Fold % on flop, turn and river. Fold only counts hands when someone raised. + - Raise and Fold % on flop, turn and river. Fold only counts hands when someone raised. This can be displayed per street or as one combined value each for aggression and folding. - Number of hands this is based on. - SD/F (aka WtSD, proportion of hands where player went to showdown after seeing flop) - W$wSF (Won $ when seen Flop) - W$@SD (Won $ at showdown) For all stats it also displays how many hands this particular is based on -- You can edit/add whatever you like, it's all python and SQL. The code should be fairly straightforward I think and I put some notes into readme-dev.txt but feel free to ask. If you can live with alpha software please give this a go and send any feedback, feature requests/suggestions, bug reports and animal names to steffen@sycamoretest.info or pick one of the contact methods listed in readme-overview.txt or reply to this post. -IMPORTANT: The database format will undergo more changes and at this point I am not planning to write a converter so please keep your history files so you can re-import when necessary. Independent of this you should always keep the original raw files in a safe place with any tracking software. +IMPORTANT: The database format WILL undergo more changes and at this point I am not planning to write a converter so please keep your history files so you can re-import when necessary. Independent of this you should always keep the original raw files in a safe place with any tracking software. Should you however end up loosing your files somehow let me know and I'll try to help. + + License diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 9b66579c..df22378b 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -5,17 +5,15 @@ Free Poker DB Tabledesign -

see commit comments for version info

-

Direct suggestions, praise and animal names to steffen@sycamoretest.info

-

TODO clean all the crap out of this like i did in HudData, line39 onwards

+

See commit comments for version info, see status.txt for status.

+

Direct suggestions, praise and animal names to steffen@sycamoretest.info or check readme-overview.txt for more contacts

I decided to be generous on the sizes of the types - if computing experience shows one thing then its that it will come back to bite you in the ass if you save 2 bits in the wrong place. If performance and/or db size are too bad we can still shrink some fields.

Relationships are noted in the comment (need to double check that all are listed)

If you want more comments or if anything is confusing or bad let me know.

All money/cash amounts are stored in cents/pennies/whatever (e.g. $4.27 would be stored a 427). Chips are stored as-is (e.g. 3675 chips would be stored as 3675).

-

Support for ringgames in Holdem, Omaha, Razz and Stud complete. Support for SnG/MTT is alpha

Notes on use/editing:

Any change to this must be carried to to the table creation code in fpdb_db.py or at least an entry to known bugs is to be made.

-

If the code (in particular the importer) and this document disagree then this document is to be considered authorative. Please report such mismatches to steffen@sycamoretest.org or through an assembla ticket.

+

If the code (in particular the importer) and this document disagree then this document is to be considered authorative.

License
Trademarks of third parties have been used under Fair Use or similar laws.
Copyright 2008 Steffen Jobbagy-Felso
@@ -100,126 +98,65 @@ autorates

autorating description

-

short_desc

-
-

char(8)

-
-

short description e.g. for display in HUD

-

short_desc

char(8)

short description e.g. for display in HUD

-

rating_time

-
-

datetime (in UTC)

-
-

timestamp of rating

-

rating_time

datetime (in UTC)

timestamp of rating

-

hand_count

-
-

int

-
-

number of hands rating is based on

-

hand_count

int

number of hands rating is based on


Table gametypes

- - - + + + - - - + + + - - - + + + - + - - - + + + - - - + + + @@ -229,8 +166,7 @@ autorates

- + @@ -271,109 +207,50 @@ autorates

Table hands

-

Field name

-
-

Type

-
-

Comment

-

Field name

Type

Comment

-

id

-
-

smallint

-
-


-

-

id

smallint


-

site_id

-
-

smallint

-
-

references sites.id

-

site_id

smallint

references sites.id

type

char(4)

-

valid entries:

-

ring - ringgames aka cash games

-

tour - tournament incl SnG

-

valid entries:
+ ring - ringgames aka cash games
+ tour - tournament incl SnG

-

category

-
-

varchar(9)

-
-

valid - entries:

-

holdem=Texas - Hold'em

-

omahahi=Omaha - High only

-

omahahilo=Omaha - 8 or better

-

razz=Razz

-

studhi=7 - Card Stud High only

-

studhl=7 Card Stud 8 or - better

-

category

varchar(9)

valid entries:
+ holdem=Texas Hold'em
+ omahahi=Omaha High only
+ omahahilo=Omaha 8 or better
+ razz=Razz
+ studhi=7 Card Stud High only
+ studhl=7 Card Stud 8 orbetter

-

limit_type

-
-

char(2)

-
-

nl=No - Limit

-

cn=Cap - No Limit

-

pl=Pot - Limit

-

cp=Cap - Pot Limit

-

fl=Fixed Limit

-

limit_type

char(2)

nl=No Limit
+ cn=Cap No Limit
+ pl=Pot Limit
+ cp=Cap Pot Limit
+ fl=Fixed Limit

small_blind

big_blind

int


-


small_bet

- - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + +
-

Field Name

-
-

Type

-
-

Comment

-

Field Name

Type

Comment

-

id

-
-

bigint

-
-


-

-

id

bigint


-

site_hand_no

-
-

bigint

-
-

the site's hand number

-

site_hand_no

bigint

the site's hand number

-

gametype_id

-
-

smallint

-
-

references gametypes.id

-

gametype_id

smallint

references gametypes.id

-

hand_start

-
-

datetime (in UTC)

-
-

start date&time of the - hand

-

hand_start

datetime (in UTC)

start date&time of the hand

-

seats

-
-

smallint

-
-

number of used seats (ie. - that got dealt cards)

-

seats

smallint

number of used seats (ie. that got dealt cards)

-

comment

-
-

text

-
-


-

-

comment

text


-

comment_ts

-
-

datetime (in UTC)

-
-


-

-

comment_ts

datetime (in UTC)


-


-

-

Table -board_cards

-

cardX --> can be 1 through 5

+


+

Table board_cards

+

cardX -> can be 1 through 5

- - - + @@ -539,6 +540,37 @@ autorates

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Field Name

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 24631aae..4bc0235e 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -259,7 +259,7 @@ class fpdb: elif lines[i].startswith("db-host="): self.settings['db-host']=lines[i][8:-1] elif lines[i].startswith("db-databaseName="): - self.settings['db-database']=lines[i][16:-1] + self.settings['db-databaseName']=lines[i][16:-1] elif lines[i].startswith("db-user="): self.settings['db-user']=lines[i][8:-1] elif lines[i].startswith("db-password="): @@ -280,6 +280,7 @@ class fpdb: self.db.disconnect() self.db = fpdb_db.fpdb_db() + #print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName'] self.db.connect(self.settings['db-backend'], self.settings['db-host'], self.settings['db-databaseName'], self.settings['db-user'], self.settings['db-password']) #end def load_profile @@ -346,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git25") + self.window.set_title("Free Poker DB - version: pre-alpha, git26") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index dc95a957..5a247950 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -34,6 +34,7 @@ class fpdb_db: self.database=database self.user=user self.password=password + #print "fpdb_db.connect, database:",database if backend==self.MYSQL_INNODB: import MySQLdb self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) diff --git a/regression-test/ps-flags-3hands.expected.txt b/regression-test/ps-flags-3hands.expected.txt index d3af95d0..4cd66f1c 100644 --- a/regression-test/ps-flags-3hands.expected.txt +++ b/regression-test/ps-flags-3hands.expected.txt @@ -29,12 +29,12 @@ otherRaisedTurnFold: 0 otherRaisedRiver: 1 otherRaisedRiverFold: 1 -wonWhenSeenFlop: -wonAtSD: +wonWhenSeenFlop: 0.0 +wonAtSD: 0.0 -stealAttemptChance: -stealAttempted: -foldBbToStealChance: -foldedBbToSteal: -foldSbToStealChance: -foldedSbToSteal: +stealAttemptChance: not yet implemented +stealAttempted: not yet implemented +foldBbToStealChance: not yet implemented +foldedBbToSteal: not yet implemented +foldSbToStealChance: not yet implemented +foldedSbToSteal: not yet implemented From 436bfc26f26bc8451f94150cb4080624f0fab5c1 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 11 Aug 2008 23:03:30 +0100 Subject: [PATCH 027/262] git27 - changed version identifier to alpha1. yay :) --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/fpdb.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index f9e18080..41d045a7 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,6 +3,7 @@ todolist (db=database, imp=importer, tv=tableviewer) before alpha ============ test everything actually works in windows too +expand instructions for profile file verify link in release notes next diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 4bc0235e..3ffc284a 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: pre-alpha, git26") + self.window.set_title("Free Poker DB - version: alpha1, git27") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) From 6d7bdc347c64a122141409dead0cec731cfd6b76 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 13 Aug 2008 03:07:44 +0100 Subject: [PATCH 028/262] git28 - added test hands for steal and cb uncommented db version checking - it works started implementing storing steals filling steal attempts and attempt chances fields now, but not verified correctness nor am i displaying it --- docs/known-bugs-and-planned-features.txt | 44 +++++++------ docs/release-notes.txt | 5 +- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 16 ++--- pyfpdb/fpdb_parse_logic.py | 2 +- pyfpdb/fpdb_simple.py | 60 +++++++++++++++++- regression-test/ps-flags-3hands.expected.txt | 12 ++-- ...ng-001to003.txt => ps-lhe-ring-3hands.txt} | 0 .../ps-lhe-ring-call-3B-preflop-cb-no2b.txt | 62 +++++++++++++++++++ ...ps-lhe-ring-successful-steal-by-cutoff.txt | 42 +++++++++++++ regression-test/regression-test.sh | 4 +- 11 files changed, 205 insertions(+), 44 deletions(-) rename regression-test/{ps-holdem-ring-001to003.txt => ps-lhe-ring-3hands.txt} (100%) create mode 100644 regression-test/ps-lhe-ring-call-3B-preflop-cb-no2b.txt create mode 100644 regression-test/ps-lhe-ring-successful-steal-by-cutoff.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 41d045a7..fcf965a5 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,31 +1,35 @@ todolist (db=database, imp=importer, tv=tableviewer) +Everything is subject to change. -before alpha -============ -test everything actually works in windows too -expand instructions for profile file -verify link in release notes - -next -==== -3B/4B might not be recognised nor counted as chance if someone raised after player called. -uncomment version checking -fill steal fields correctly, add to tester and tv +before alpha2 +============= +fill steal reaction fields +add all steal fields to tester and tv CB, 2nd/3rd Barrel, fold to these - -separate all gui and all processing into files that are named accordingly -ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. -figure out what slowed it down so much between git19 and git21 (8/9aug) +printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt auto-import -why do we have to reconnect in tv.read_names_clicked? -implement error file in importer -catch index error, type error, file not found error +seperate and improve instructions for update +verify link in release notes +split install instructions into OS-specific and OS-independent section. expand release creator to concatenate. +expand instructions for profile file, again, the release-creator will cat it. +delete old mailing list and create fpdb-announce finish updating filelist -use different colours according to classification. -table with data for graphs for SD/F, W$wSF, W$@SD +return sng support before beta =========== +SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug +show database version error in GUI +anonymiser script to generate testdata without making a dozen find&replace all... +separate all gui and all processing into files that are named accordingly +ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. +figure out what slowed it down so much between git19 and git21 (8/9aug) +why do we have to reconnect in tv.read_names_clicked? +implement error file in importer +catch index error, type error, file not found error +use different colours according to classification. +table with data for graphs for SD/F, W$wSF, W$@SD + add stud, razz and tourney back to imp/tv but with less seperate codepathes move prepare-git.sh and create-release.sh to utils offer not storing db password diff --git a/docs/release-notes.txt b/docs/release-notes.txt index ac8668b8..a9bf00ed 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -13,7 +13,7 @@ To install it go to https://sourceforge.net/projects/showfiles.php?group_id=2268 This is alpha1, as the name indicates it is still at a very early stage. The importer and database are nearing completion but the GUI in particular is not very functional yet and the HUD is missing alltogether. -If anyone wishes to help with development that would be very very welcome and I've put a few notes in docs/readme-dev.txt in the download for what you could do. Or just start coding and set me the patches :) +If anyone wishes to help with development that would be very very welcome and I've put a few notes in docs/readme-dev.txt in the download for what you could do. Or just start coding and send me the patches :) If you're not a programmer and you're not interested in learning it you can still help simply by trying it out and sending bug reports and feature requests. To avoid unrealistic expectations I'd like to state that it'll be a long time until fpdb reaches feature parity to established paid-for closed source software. Feature List (short now, growing fast ;) ): @@ -45,10 +45,9 @@ Tv takes a history filename and loads the appropriate players' stats and display For all stats it also displays how many hands this particular is based on -If you can live with alpha software please give this a go and send any feedback, feature requests/suggestions, bug reports and animal names to steffen@sycamoretest.info or pick one of the contact methods listed in readme-overview.txt or reply to this post. - IMPORTANT: The database format WILL undergo more changes and at this point I am not planning to write a converter so please keep your history files so you can re-import when necessary. Independent of this you should always keep the original raw files in a safe place with any tracking software. Should you however end up loosing your files somehow let me know and I'll try to help. +Please send any feedback, feature requests/suggestions, bug reports and animal names to steffen@sycamoretest.info, pick one of the contact methods listed in readme-overview.txt, send me a PM here or reply to this post. diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 3ffc284a..d194ad88 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1, git27") + self.window.set_title("Free Poker DB - version: alpha1+, git28") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 5a247950..071ac49c 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -44,13 +44,13 @@ class fpdb_db: else: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) self.cursor=self.db.cursor() - #try: - # self.cursor.execute("SELECT * FROM settings") - # settings=self.cursor.fetchone() - # if settings[0]!=21: - # print "outdated database version - please recreate tables" - #except:# _mysql_exceptions.ProgrammingError: - # print "failed to read settings table - please recreate tables" + try: + self.cursor.execute("SELECT * FROM settings") + settings=self.cursor.fetchone() + if settings[0]!=28: + print "outdated database version - please recreate tables" + except:# _mysql_exceptions.ProgrammingError: + print "failed to read settings table - please recreate tables" #end def connect def create_table(self, string): @@ -281,7 +281,7 @@ class fpdb_db: foldSbToStealChance INT, foldedSbToSteal INT)""") - self.cursor.execute("INSERT INTO settings VALUES (21);") + self.cursor.execute("INSERT INTO settings VALUES (28);") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 646081f2..617f2393 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -101,7 +101,7 @@ def mainParser(db, cursor, site, category, hand): totalWinnings=0 for i in range(len(winnings)): totalWinnings+=winnings[i] - hudImportData=fpdb_simple.calculateHudImport(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings) + hudImportData=fpdb_simple.generateHudData(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) if isTourney: raise fpdb_simple.FpdbError ("tourneys are currently broken") diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index d8ee4500..2aca4a82 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1225,7 +1225,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, return result #end def store_hands_players_stud_tourney -def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winnings, totalWinnings): +def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings, totalWinnings, positions): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" #setup subarrays of the result dictionary. VPIP=[] @@ -1254,12 +1254,41 @@ def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winni foldSbToStealChance=[] foldedSbToSteal=[] - firstPfRaise=-1 + firstPfRaiseByNo=-1 + firstPfRaiserId=-1 + firstPfRaiserNo=-1 + firstPfCallByNo=-1 + firstPfCallerId=-1 for i in range(len(actionTypeByNo[0])): if actionTypeByNo[0][i][1]=="bet": - firstPfRaise=i + firstPfRaiseByNo=i + firstPfRaiserId=actionTypeByNo[0][i][0] + for j in range(len(player_ids)): + if player_ids[j]==firstPfRaiserId: + firstPfRaiserNo=j + break + break + for i in range(len(actionTypeByNo[0])): + if actionTypeByNo[0][i][1]=="call": + firstPfCallByNo=i + firstPfCallerId=actionTypeByNo[0][i][0] break + cutoffId=-1 + buttonId=-1 + sbId=-1 + bbId=-1 + for player in range(len(positions)): + if positions==1: + cutoffId=player_ids[player] + if positions==0: + buttonId=player_ids[player] + if positions=='S': + sbId=player_ids[player] + if positions=='B': + bbId=player_ids[player] + + #run a loop for each player preparing the actual values that will be commited to SQL for player in range (len(player_ids)): #set default values myVPIP=False @@ -1313,6 +1342,31 @@ def calculateHudImport(player_ids, category, action_types, actionTypeByNo, winni if pfRaise>firstPfRaise: myPF3B4B=True + #myStealAttemptChance myStealAttempted myFoldBbToStealChance myFoldedBbToSteal myFoldSbToStealChance myFoldedSbToSteal + #steal calculations + if len(player_ids)>=5: #no point otherwise + if positions[player]==1: + if firstPfRaiserId==player_ids[player]: + myStealAttemptChance=True + myStealAttempted=True + elif firstPfRaiserId==buttonId or firstPfRaiserId==sbId or firstPfRaiserId==bbId or firstPfRaiserId==-1: + myStealAttemptChance=True + if positions[player]==0: + if firstPfRaiserId==player_ids[player]: + myStealAttemptChance=True + myStealAttempted=True + elif firstPfRaiserId==sbId or firstPfRaiserId==bbId or firstPfRaiserId==-1: + myStealAttemptChance=True + if positions[player]==S: + if firstPfRaiserId==player_ids[player]: + myStealAttemptChance=True + myStealAttempted=True + elif firstPfRaiserId==bbId or firstPfRaiserId==-1: + myStealAttemptChance=True + if positions[player]==B: + pass + + #calculate saw* values if (len(action_types[1][player])>0): mySawFlop=True diff --git a/regression-test/ps-flags-3hands.expected.txt b/regression-test/ps-flags-3hands.expected.txt index 4cd66f1c..9f7b2e14 100644 --- a/regression-test/ps-flags-3hands.expected.txt +++ b/regression-test/ps-flags-3hands.expected.txt @@ -32,9 +32,9 @@ otherRaisedRiverFold: 1 wonWhenSeenFlop: 0.0 wonAtSD: 0.0 -stealAttemptChance: not yet implemented -stealAttempted: not yet implemented -foldBbToStealChance: not yet implemented -foldedBbToSteal: not yet implemented -foldSbToStealChance: not yet implemented -foldedSbToSteal: not yet implemented +stealAttemptChance: 0 +stealAttempted: 0 +foldBbToStealChance: 0 +foldedBbToSteal: 0 +foldSbToStealChance: 0 +foldedSbToSteal: 0 diff --git a/regression-test/ps-holdem-ring-001to003.txt b/regression-test/ps-lhe-ring-3hands.txt similarity index 100% rename from regression-test/ps-holdem-ring-001to003.txt rename to regression-test/ps-lhe-ring-3hands.txt diff --git a/regression-test/ps-lhe-ring-call-3B-preflop-cb-no2b.txt b/regression-test/ps-lhe-ring-call-3B-preflop-cb-no2b.txt new file mode 100644 index 00000000..797839a9 --- /dev/null +++ b/regression-test/ps-lhe-ring-call-3B-preflop-cb-no2b.txt @@ -0,0 +1,62 @@ +PokerStars Game #19546605871: Hold'em Limit ($0.25/$0.50) - 2008/08/11 - 20:15:41 (ET) +Table 'Pyxis' 10-max Seat #1 is the button +Seat 1: player10 ($7.75 in chips) +Seat 2: player1 ($11.55 in chips) +Seat 3: player2 ($8.25 in chips) +Seat 4: player3 ($0.90 in chips) +Seat 5: player4 ($10003.50 in chips) +Seat 6: player5 ($13.50 in chips) +Seat 7: player6 ($8 in chips) +Seat 8: player7 ($11.80 in chips) +Seat 9: player8 ($11.05 in chips) +Seat 10: player9 ($11.85 in chips) +player1: posts small blind $0.10 +player1 said, "little holy water on the river lol" +player2: posts big blind $0.25 +*** HOLE CARDS *** +Dealt to player6 [Ad Ts] +player3: calls $0.25 +player4: folds +player5: raises $0.25 to $0.50 +player6: calls $0.50 +player7: folds +player8: folds +player9: folds +player10: folds +player1: folds +player2: folds +player3: raises $0.25 to $0.75 +player5: calls $0.25 +player6: calls $0.25 +*** FLOP *** [Js 5d 4c] +player3: bets $0.15 and is all-in +player5: calls $0.15 +player6: calls $0.15 +*** TURN *** [Js 5d 4c] [4s] +player5: bets $0.50 +player6: calls $0.50 +*** RIVER *** [Js 5d 4c 4s] [6d] +player5: bets $0.50 +player6: calls $0.50 +*** SHOW DOWN *** +player5: shows [Qc Ac] (a pair of Fours) +player6: mucks hand +player5 collected $1.90 from side pot +player3: shows [9c 7c] (a pair of Fours - lower kicker) +player5 collected $2.95 from main pot +*** SUMMARY *** +Total pot $5.05 Main pot $2.95. Side pot $1.90. | Rake $0.20 +Board [Js 5d 4c 4s 6d] +Seat 1: player10 (button) folded before Flop (didn't bet) +Seat 2: player1 (small blind) folded before Flop +Seat 3: player2 (big blind) folded before Flop +Seat 4: player3 showed [9c 7c] and lost with a pair of Fours +Seat 5: player4 folded before Flop (didn't bet) +Seat 6: player5 showed [Qc Ac] and won ($4.85) with a pair of Fours +Seat 7: player6 mucked [Ad Ts] +Seat 8: player7 folded before Flop (didn't bet) +Seat 9: player8 folded before Flop (didn't bet) +Seat 10: player9 folded before Flop (didn't bet) + + + diff --git a/regression-test/ps-lhe-ring-successful-steal-by-cutoff.txt b/regression-test/ps-lhe-ring-successful-steal-by-cutoff.txt new file mode 100644 index 00000000..3d402cef --- /dev/null +++ b/regression-test/ps-lhe-ring-successful-steal-by-cutoff.txt @@ -0,0 +1,42 @@ +PokerStars Game #19546637866: Hold'em Limit ($0.25/$0.50) - 2008/08/11 - 20:17:04 (ET) +Table 'Pyxis' 10-max Seat #2 is the button +Seat 1: player1 ($7.75 in chips) +Seat 2: player2 ($11.45 in chips) +Seat 5: player3 ($10003.50 in chips) +Seat 6: player4 ($16.45 in chips) +Seat 7: player5 ($6.10 in chips) +Seat 8: player6 ($11.80 in chips) +Seat 9: player7 ($11.05 in chips) +Seat 10: player8 ($11.85 in chips) +player9 leaves the table +player10: is sitting out +player10 leaves the table +player3: posts small blind $0.10 +player4: posts big blind $0.25 +*** HOLE CARDS *** +Dealt to player5 [Jh 5d] +Mac Fun K joins the table at seat #4 +player5: folds +player6: folds +player7: folds +player8: folds +player1: raises $0.25 to $0.50 +player2: folds +player3: folds +player4: folds +Uncalled bet ($0.25) returned to player1 +player1 collected $0.60 from pot +player1: doesn't show hand +*** SUMMARY *** +Total pot $0.60 | Rake $0 +Seat 1: player1 collected ($0.60) +Seat 2: player2 (button) folded before Flop (didn't bet) +Seat 5: player3 (small blind) folded before Flop +Seat 6: player4 (big blind) folded before Flop +Seat 7: player5 folded before Flop (didn't bet) +Seat 8: player6 folded before Flop (didn't bet) +Seat 9: player7 folded before Flop (didn't bet) +Seat 10: player8 folded before Flop (didn't bet) + + + diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index 309e0469..0982958e 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -18,8 +18,8 @@ echo "Please note for this to really work you need to work on an empty database" rm *.found.txt -../pyfpdb/fpdb_import.py -p$1 --file=ps-holdem-ring-001to003.txt -x -../pyfpdb/fpdb_import.py -p$1 --file=ps-holdem-ring-001to003.txt -x +../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x +../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x #../pyfpdb/fpdb_import.py -p$1 --file=ftp-stud-hilo-ring-001.txt -x #../pyfpdb/fpdb_import.py -p$1 --file=ftp-omaha-hi-pl-ring-001-005.txt -x From 6d61e1e6c6d79ad1d5e1e1a3fd917eb17ad82eef Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 13 Aug 2008 03:29:24 +0100 Subject: [PATCH 029/262] git29 - it displays ST correctly (well, for the one steal I checked that is) --- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_simple.py | 12 ++++++------ pyfpdb/table_viewer.py | 6 ++++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index d194ad88..b54550ea 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git28") + self.window.set_title("Free Poker DB - version: alpha1+, git29") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2aca4a82..bd89b872 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1330,16 +1330,16 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #PF3B4BChance and PF3B4B pfFold=-1 pfRaise=-1 - if firstPfRaise!=-1: + if firstPfRaiseByNo!=-1: for i in range(len(actionTypeByNo[0])): if actionTypeByNo[0][i][0]==player_ids[player]: - if actionTypeByNo[0][i][1]=="bet" and pfRaise==-1 and i>firstPfRaise: + if actionTypeByNo[0][i][1]=="bet" and pfRaise==-1 and i>firstPfRaiseByNo: pfRaise=i if actionTypeByNo[0][i][1]=="fold" and pfFold==-1: pfFold=i - if pfFold==-1 or pfFold>firstPfRaise: + if pfFold==-1 or pfFold>firstPfRaiseByNo: myPF3B4BChance=True - if pfRaise>firstPfRaise: + if pfRaise>firstPfRaiseByNo: myPF3B4B=True #myStealAttemptChance myStealAttempted myFoldBbToStealChance myFoldedBbToSteal myFoldSbToStealChance myFoldedSbToSteal @@ -1357,13 +1357,13 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStealAttempted=True elif firstPfRaiserId==sbId or firstPfRaiserId==bbId or firstPfRaiserId==-1: myStealAttemptChance=True - if positions[player]==S: + if positions[player]=='S': if firstPfRaiserId==player_ids[player]: myStealAttemptChance=True myStealAttempted=True elif firstPfRaiserId==bbId or firstPfRaiserId==-1: myStealAttemptChance=True - if positions[player]==B: + if positions[player]=='B': pass diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index c8d01b87..9c0b9af8 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -59,9 +59,9 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wsF", "W$@SD") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wsF", "W$@SD") if self.settings['tv-combinedPostflop']: - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "Postf A", "Postf F", "SD/F", "W$wsF", "W$@SD") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "Postf A", "Postf F", "SD/F", "W$wsF", "W$@SD") else: raise fpdb_simple.FpdbError("reimplement stud") tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7", "SD/4") @@ -107,6 +107,8 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[5],row[4])) #VPIP tmp.append(self.hudDivide(row[6],row[4])) #PFR tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B + tmp.append(self.hudDivide(row[25],row[24])+" ("+str(row[24])+")") #ST + if self.settings['tv-combinedPostflop']: aggCount=row[13]+row[14]+row[15] handCount=row[9]+row[10]+row[11] From 2a0f73646cc8439c7ce5e8493f8b2112d21a90dd Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 13 Aug 2008 04:13:56 +0100 Subject: [PATCH 030/262] git30 - --- docs/known-bugs-and-planned-features.txt | 12 ++++--- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 4 +-- pyfpdb/fpdb_simple.py | 42 ++++++++++++++++++------ 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index fcf965a5..fcb3b1f3 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,7 @@ Everything is subject to change. before alpha2 ============= -fill steal reaction fields -add all steal fields to tester and tv +add steal reaction to tv CB, 2nd/3rd Barrel, fold to these printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt auto-import @@ -16,11 +15,14 @@ delete old mailing list and create fpdb-announce finish updating filelist return sng support -before beta -=========== +alpha3 +====== +anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no by running no SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug show database version error in GUI -anonymiser script to generate testdata without making a dozen find&replace all... + +before beta +=========== separate all gui and all processing into files that are named accordingly ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. figure out what slowed it down so much between git19 and git21 (8/9aug) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index b54550ea..ab26977e 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git29") + self.window.set_title("Free Poker DB - version: alpha1+, git30") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 071ac49c..23c6b287 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM settings") settings=self.cursor.fetchone() - if settings[0]!=28: + if settings[0]!=30: print "outdated database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -281,7 +281,7 @@ class fpdb_db: foldSbToStealChance INT, foldedSbToSteal INT)""") - self.cursor.execute("INSERT INTO settings VALUES (28);") + self.cursor.execute("INSERT INTO settings VALUES (30);") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index bd89b872..a65ea744 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1287,6 +1287,8 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings sbId=player_ids[player] if positions=='B': bbId=player_ids[player] + + someoneStole=False #run a loop for each player preparing the actual values that will be commited to SQL for player in range (len(player_ids)): @@ -1312,10 +1314,6 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myWonAtSD=0.0 myStealAttemptChance=False myStealAttempted=False - myFoldBbToStealChance=False - myFoldedBbToSteal=False - myFoldSbToStealChance=False - myFoldedSbToSteal=False #calculate VPIP and PFR street=0 @@ -1365,8 +1363,10 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStealAttemptChance=True if positions[player]=='B': pass - - + + if myStealAttempted: + someoneStole=True + #calculate saw* values if (len(action_types[1][player])>0): mySawFlop=True @@ -1462,10 +1462,6 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings wonAtSD.append(myWonAtSD) stealAttemptChance.append(myStealAttemptChance) stealAttempted.append(myStealAttempted) - foldBbToStealChance.append(myFoldBbToStealChance) - foldedBbToSteal.append(myFoldedBbToSteal) - foldSbToStealChance.append(myFoldSbToStealChance) - foldedSbToSteal.append(myFoldedSbToSteal) #add each array to the to-be-returned dictionary result={'VPIP':VPIP} @@ -1489,10 +1485,36 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings result['wonAtSD']=wonAtSD result['stealAttemptChance']=stealAttemptChance result['stealAttempted']=stealAttempted + + #after having calculated the above we now do second level calculations, so far just steal attempts. + for player in range (len(player_ids)): + myFoldBbToStealChance=False + myFoldedBbToSteal=False + myFoldSbToStealChance=False + myFoldedSbToSteal=False + + if someoneStole and (positions[player]=='B' or positions[player]=='S') and firstPfRaiserId!=player_ids[player]: + street=0 + for count in range (len(action_types[street][player])):#finally individual actions + if positions[player]=='B': + myFoldBbToStealChance=True + if action_types[street][player][count]=="fold": + myFoldedBbToSteal=True + if positions[player]=='S': + myFoldSbToStealChance=True + if action_types[street][player][count]=="fold": + myFoldedSbToSteal=True + + + foldBbToStealChance.append(myFoldBbToStealChance) + foldedBbToSteal.append(myFoldedBbToSteal) + foldSbToStealChance.append(myFoldSbToStealChance) + foldedSbToSteal.append(myFoldedSbToSteal) result['foldBbToStealChance']=foldBbToStealChance result['foldedBbToSteal']=foldedBbToSteal result['foldSbToStealChance']=foldSbToStealChance result['foldedSbToSteal']=foldedSbToSteal + return result #end def calculateHudImport From aecfa6d61b23e25f865d793fa776439ed4039f3e Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 13 Aug 2008 04:22:22 +0100 Subject: [PATCH 031/262] git31 - last git added generating the reaction to steal data. this git adds em to tv. On the tested hand this works correctly :) --- docs/known-bugs-and-planned-features.txt | 6 ++++-- pyfpdb/fpdb.py | 2 +- pyfpdb/table_viewer.py | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index fcb3b1f3..6f02aabf 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,8 +1,8 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change. -before alpha2 -============= +alpha2 +====== add steal reaction to tv CB, 2nd/3rd Barrel, fold to these printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt @@ -14,6 +14,7 @@ expand instructions for profile file, again, the release-creator will cat it. delete old mailing list and create fpdb-announce finish updating filelist return sng support +update abbreviations.txt alpha3 ====== @@ -23,6 +24,7 @@ show database version error in GUI before beta =========== +optionally combine FB/FS separate all gui and all processing into files that are named accordingly ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. figure out what slowed it down so much between git19 and git21 (8/9aug) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index ab26977e..2e8038f7 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git30") + self.window.set_title("Free Poker DB - version: alpha1+, git31") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 9c0b9af8..33af6a78 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -59,12 +59,11 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "AF", "FF", "AT", "FT", "AR", "FR", "SD/F", "W$wsF", "W$@SD") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "FS", "FB", "AF", "FF", "AT", "FT", "AR", "FR", "WtSD", "W$wsF", "W$SD") if self.settings['tv-combinedPostflop']: - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "Postf A", "Postf F", "SD/F", "W$wsF", "W$@SD") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "FS", "FB", "Postf A", "Postf F", "WtSD", "W$wsF", "W$SD") else: raise fpdb_simple.FpdbError("reimplement stud") - tmp=("Name", "Hands", "VPI3", "A3", "3B4B_3" "A4", "F4", "A5", "F5", "A6", "F6", "A7", "F7", "SD/4") arr.append(tmp) #then the data rows @@ -108,6 +107,8 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[6],row[4])) #PFR tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B tmp.append(self.hudDivide(row[25],row[24])+" ("+str(row[24])+")") #ST + tmp.append(self.hudDivide(row[29],row[28])+" ("+str(row[28])+")") #FS + tmp.append(self.hudDivide(row[27],row[26])+" ("+str(row[26])+")") #FB if self.settings['tv-combinedPostflop']: aggCount=row[13]+row[14]+row[15] From 61d0857a4c867807b26b57533f6d9c4bbac9324b Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 13 Aug 2008 06:50:30 +0100 Subject: [PATCH 032/262] git33 - added fields for CB/2B/3B to table design, table creation and tv. importer fills it with placeholder data renamed ebuild from v0.01 alpha to v1.0 alpha as I won't be using normal version numbers before 1.0 --- docs/known-bugs-and-planned-features.txt | 10 ++-- docs/release-notes.txt | 8 ++- docs/status.txt | 9 +--- docs/tabledesign.html | 32 ++++++++++++ packaging/fpdb-1.0_alpha1_p27.ebuild | 37 ++++++++++++++ pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 14 +++-- pyfpdb/fpdb_simple.py | 65 +++++++++++++++++++++--- pyfpdb/table_viewer.py | 12 ++++- 9 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 packaging/fpdb-1.0_alpha1_p27.ebuild diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 6f02aabf..675a8c4b 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,7 @@ Everything is subject to change. alpha2 ====== -add steal reaction to tv -CB, 2nd/3rd Barrel, fold to these +CB, 2nd/3rd Barrel, fold to these -> actually calculate them printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt auto-import seperate and improve instructions for update @@ -15,6 +14,10 @@ delete old mailing list and create fpdb-announce finish updating filelist return sng support update abbreviations.txt +fix up bg colours in tv +ebuild symlink doesnt work +more automation in the ebuild, update install-in-gentoo.txt, set permissions in it +gentoo ebuild: USE dedicated, USE postgresql, copy docs alpha3 ====== @@ -24,7 +27,7 @@ show database version error in GUI before beta =========== -optionally combine FB/FS +optionally combine FB/FS and CB/2B/3B separate all gui and all processing into files that are named accordingly ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. figure out what slowed it down so much between git19 and git21 (8/9aug) @@ -81,7 +84,6 @@ ensure that refresh still takes no more than 10 seks on my P3M-800 (a quick run select range of stakes and sng/mtt values and types for tv change "for i" to more sensible var name instead of i recognise somewhere if a file is still active and if so keep it open and only read new hands rather than detecting dupes -gentoo ebuild can wait till 1.x ================= diff --git a/docs/release-notes.txt b/docs/release-notes.txt index a9bf00ed..714d9342 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -1,4 +1,10 @@ +alpha2 +====== +Main new features: +Now reads and displays steal attempts and reaction to them + alpha1 +====== Hi everyone, I am proud to announce the first release of my new poker tracking software fpdb (freepokerdb, very imaginative I know ;) ). You may wonder why I bothered when now with HM and PT3 there are at least two excellent packages to choose from. @@ -9,7 +15,7 @@ Four main reasons: You still need to run Windows or wine to run the actual poker client though. 4. Fpdb won't irritate you with copy prevention measures. -To install it go to https://sourceforge.net/projects/showfiles.php?group_id=226872 and download the zip or tar.bz2, unpack it, and follow the instructions in docs/install-in-* for your operating system (e.g. docs\install-in-windows.txt). Sourceforge lists the release files as source files, not as binary executables - this is correct, python will automagically compile as and when required. +To install it go to https://sourceforge.net/project/showfiles.php?group_id=226872 and download the zip or tar.bz2, unpack it, and follow the instructions in docs/install-in-* for your operating system (e.g. docs\install-in-windows.txt). Sourceforge lists the release files as source files, not as binary executables - this is correct, python will automagically compile as and when required. This is alpha1, as the name indicates it is still at a very early stage. The importer and database are nearing completion but the GUI in particular is not very functional yet and the HUD is missing alltogether. diff --git a/docs/status.txt b/docs/status.txt index 0de3ba43..5aae18bc 100644 --- a/docs/status.txt +++ b/docs/status.txt @@ -12,18 +12,13 @@ Stud/Razz ring games on PokerStars ================================== Broken - used to work, hardly tested. Needs to be updated to new infrastructure using a HudData table rather than hands_players_flags. Will fix this shortly. -A note on No Limit and Pot Limit -================================ -Fpdb fully supports NL/PL, however currently only as far as it does limit games. So it'll tell you how often a player has bet, but not how much. But the mate I'm writing this for doesnt play NL/PL (and neither do I) so volunteers would be most welcome. - Tournaments/SnGs ================ -Broken - used to work, hardly tested. Needs to be updated to new infrastructure.. Will probably fix this quite soon. -Independent of the current brokenness the tourney support would only show information as if a tourney is a ring game - but it's not. If you play tournaments and would like to help improving this let me know, I probably won't bother. +Broken - used to work, hardly tested. Needs to be updated to new infrastructure.. Will probably fix this shortly Full Tilt Poker =============== -Not sure - it should import them, but I believe the table viewer won't work. Would be easy to fix though, volunteers welcome +Not sure - it should import them, but I believe the table viewer won't work. Would be easy to fix I think, volunteers welcome. This used to work in a pre-alpha but basically was left behind during updates to the other stuff. Other Sites =========== diff --git a/docs/tabledesign.html b/docs/tabledesign.html index df22378b..8f7b19ee 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -509,6 +509,7 @@ autorates

float

As wonWhenSeenFlop, but for showdown.

stealAttemptChance

int

int

Player folded SB to steal attempt

contBetChance

int

Player had chance to make continuation bet

contBetDone

int

Player used chance to make continuation bet

secondBarrelChance

int

Player had chance to make second barrel bet

secondBarrelDone

int

Player used chance to make second barrel bet

thirdBarrelChance

int

Player had chance to make third barrel bet

thirdBarrelDone

int

Player used chance to make third barrel bet

Table hands_actions

diff --git a/packaging/fpdb-1.0_alpha1_p27.ebuild b/packaging/fpdb-1.0_alpha1_p27.ebuild new file mode 100644 index 00000000..e0ec86f6 --- /dev/null +++ b/packaging/fpdb-1.0_alpha1_p27.ebuild @@ -0,0 +1,37 @@ +# Copyright 1999-2008 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha1_p27.ebuild,v 1.0 2008/08/13 05:45:00 jer Exp $ + +NEED_PYTHON=2.3 + +#inherit distutils + +MY_P="fpdb-${PV}" +DESCRIPTION="A database program to track your online poker games" +HOMEPAGE="https://sourceforge.net/projects/fpdb/" +SRC_URI="mirror://sourceforge/fpdb/fpdb-alpha1-git27.tar.bz2" + +LICENSE="AGPL-3" +SLOT="0" +KEYWORDS="~amd64 ~x86" +#note: I would be very surprised if this doesnt work on other architectures, please send me your experiences +IUSE="" + +RDEPEND="virtual/mysql + dev-python/mysql-python + >=x11-libs/gtk+-2.10 + dev-python/pygtk" +DEPEND="${RDEPEND}" + +src_install() { + DIRINST="${D}usr/share/games/fpdb/" + mkdir -p "${DIRINST}" + cp -R * "${DIRINST}" || die + + DIRBIN="${D}usr/bin/games/" + mkdir -p "${DIRBIN}" + #echo "dirs" + #echo "${DIRINST}pyfpdb/fpdb.py" + #echo + ln -s "${DIRINST}pyfpdb/fpdb.py" "${DIRBIN}/fpdb.py" || die +} diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 2e8038f7..e61cbffb 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git31") + self.window.set_title("Free Poker DB - version: alpha1+, git33") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 23c6b287..221d0714 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM settings") settings=self.cursor.fetchone() - if settings[0]!=30: + if settings[0]!=33: print "outdated database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -274,14 +274,22 @@ class fpdb_db: otherRaisedRiverFold INT, wonWhenSeenFlop FLOAT, wonAtSD FLOAT, + stealAttemptChance INT, stealAttempted INT, foldBbToStealChance INT, foldedBbToSteal INT, foldSbToStealChance INT, - foldedSbToSteal INT)""") + foldedSbToSteal INT, - self.cursor.execute("INSERT INTO settings VALUES (30);") + contBetChance INT, + contBetDone INT, + secondBarrelChance INT, + secondBarrelDone INT, + thirdBarrelChance INT, + thirdBarrelDone INT)""") + + self.cursor.execute("INSERT INTO settings VALUES (33);") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index a65ea744..986a9eb2 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1249,10 +1249,6 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings wonAtSD=[] stealAttemptChance=[] stealAttempted=[] - foldBbToStealChance=[] - foldedBbToSteal=[] - foldSbToStealChance=[] - foldedSbToSteal=[] firstPfRaiseByNo=-1 firstPfRaiserId=-1 @@ -1487,6 +1483,10 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings result['stealAttempted']=stealAttempted #after having calculated the above we now do second level calculations, so far just steal attempts. + foldBbToStealChance=[] + foldedBbToSteal=[] + foldSbToStealChance=[] + foldedSbToSteal=[] for player in range (len(player_ids)): myFoldBbToStealChance=False myFoldedBbToSteal=False @@ -1515,6 +1515,48 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings result['foldSbToStealChance']=foldSbToStealChance result['foldedSbToSteal']=foldedSbToSteal + #now CB/2B/3B + contBetChance=[] + contBetDone=[] + for player in range (len(player_ids)): + myContBetChance=False + myContBetDone=False + + #calc CB + + contBetChance.append(myContBetChance) + contBetDone.append(myContBetDone) + result['contBetChance']=contBetDone + result['contBetDone']=contBetDone + + #now 2B + secondBarrelChance=[] + secondBarrelDone=[] + for player in range (len(player_ids)): + mySecondBarrelChance=False + mySecondBarrelDone=False + + #calc 2b + + secondBarrelChance.append(mySecondBarrelChance) + secondBarrelDone.append(mySecondBarrelDone) + result['secondBarrelChance']=secondBarrelDone + result['secondBarrelDone']=secondBarrelDone + + #now 3B + thirdBarrelChance=[] + thirdBarrelDone=[] + for player in range (len(player_ids)): + myThirdBarrelChance=False + myThirdBarrelDone=False + + #calc 3b + + thirdBarrelChance.append(myThirdBarrelChance) + thirdBarrelDone.append(myThirdBarrelDone) + result['thirdBarrelChance']=thirdBarrelDone + result['thirdBarrelDone']=thirdBarrelDone + return result #end def calculateHudImport @@ -1574,16 +1616,23 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if hudImportData['foldSbToStealChance'][player]: row[28]+=1 if hudImportData['foldedSbToSteal'][player]: row[29]+=1 + if hudImportData['contBetChance'][player]: row[30]+=1 + if hudImportData['contBetDone'][player]: row[31]+=1 + if hudImportData['secondBarrelChance'][player]: row[32]+=1 + if hudImportData['secondBarrelDone'][player]: row[33]+=1 + if hudImportData['thirdBarrelChance'][player]: row[34]+=1 + if hudImportData['thirdBarrelDone'][player]: row[35]+=1 + if doInsert: #print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29])) + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, contBetChance, contBetDone, secondBarrelChance, secondBarrelDone, thirdBarrelChance, thirdBarrelDone) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35])) else: #print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha - SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s, stealAttemptChance=%s, stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s - WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[1], row[2], row[3])) + SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s, stealAttemptChance=%s, stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s, contBetChance=%s, contBetDone=%s, secondBarrelChance=%s, secondBarrelDone=%s, thirdBarrelChance=%s, thirdBarrelDone=%s + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[1], row[2], row[3])) else: raise FpdbError("todo") #end def storeHudData diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 33af6a78..c68341e2 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -59,9 +59,12 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "FS", "FB", "AF", "FF", "AT", "FT", "AR", "FR", "WtSD", "W$wsF", "W$SD") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "FS", "FB", "CB", "2B", "3B") if self.settings['tv-combinedPostflop']: - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "FS", "FB", "Postf A", "Postf F", "WtSD", "W$wsF", "W$SD") + tmp+=("Postf A", "Postf F") + else: + tmp+=("AF", "FF", "AT", "FT", "AR", "FR") + tmp+=("WtSD", "W$wsF", "W$SD") else: raise fpdb_simple.FpdbError("reimplement stud") arr.append(tmp) @@ -106,10 +109,15 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[5],row[4])) #VPIP tmp.append(self.hudDivide(row[6],row[4])) #PFR tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B + tmp.append(self.hudDivide(row[25],row[24])+" ("+str(row[24])+")") #ST tmp.append(self.hudDivide(row[29],row[28])+" ("+str(row[28])+")") #FS tmp.append(self.hudDivide(row[27],row[26])+" ("+str(row[26])+")") #FB + tmp.append(self.hudDivide(row[31],row[30])+" ("+str(row[30])+")") #CB + tmp.append(self.hudDivide(row[33],row[32])+" ("+str(row[32])+")") #2B + tmp.append(self.hudDivide(row[35],row[34])+" ("+str(row[34])+")") #3B + if self.settings['tv-combinedPostflop']: aggCount=row[13]+row[14]+row[15] handCount=row[9]+row[10]+row[11] From e56a65b4c907b05be7f51d5a9a81d99fe27f1eef Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 13 Aug 2008 08:11:20 +0100 Subject: [PATCH 033/262] git34 - reading CB/2B/3B now. Some doc updates --- docs/known-bugs-and-planned-features.txt | 23 ++++++++++++----------- docs/status.txt | 10 ++++++++-- packaging/fpdb-1.0_alpha1_p27.ebuild | 2 +- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 2 +- pyfpdb/fpdb_simple.py | 23 ++++++++++++++++------- 6 files changed, 39 insertions(+), 23 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 675a8c4b..f2662710 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,9 +1,8 @@ todolist (db=database, imp=importer, tv=tableviewer) -Everything is subject to change. +Everything is subject to change and especially the order will often change. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. -alpha2 +alpha2 (release by 17Aug) ====== -CB, 2nd/3rd Barrel, fold to these -> actually calculate them printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt auto-import seperate and improve instructions for update @@ -15,18 +14,21 @@ finish updating filelist return sng support update abbreviations.txt fix up bg colours in tv -ebuild symlink doesnt work -more automation in the ebuild, update install-in-gentoo.txt, set permissions in it -gentoo ebuild: USE dedicated, USE postgresql, copy docs +ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config alpha3 ====== -anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no by running no +check-raise/call-raise on all streets +anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no +table with data for graphs for SD/F, W$wSF, W$@SD SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug -show database version error in GUI +show database version error in GUI and use fpdb_db class const for it, add it to title +split hud data generation into separate for loops and make it more efficient +fix bug that sawFlop/Turn/River gets miscalculated if someone is allin - might as well add all-in recognition for this before beta =========== +gentoo ebuild: USE postgresql optionally combine FB/FS and CB/2B/3B separate all gui and all processing into files that are named accordingly ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. @@ -35,7 +37,6 @@ why do we have to reconnect in tv.read_names_clicked? implement error file in importer catch index error, type error, file not found error use different colours according to classification. -table with data for graphs for SD/F, W$wSF, W$@SD add stud, razz and tourney back to imp/tv but with less seperate codepathes move prepare-git.sh and create-release.sh to utils @@ -65,9 +66,9 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== +recognise&handle all-in for CB etc. (mostly useful for NL/PL, in limit I would argue missing a chance due to lack of money is the same as missing it due to sneezing and clicking the wrong button) In many places there are unnecessary database accesses or it regenerates information it already had before or just generally does things in obscenely inefficient ways. Optimise this multi-select in bulk importer -move version into seperate file make option to use "traditional" labels, e.g. WtSD instead of SD/F HTMLify docs and validate them cut down action_types array size to appropriate length @@ -99,7 +100,7 @@ Probably PartyPoker for all or most supported games repair hands where the seat lines are missing, happens when observing at FTP flags for storing the reason for winning (best hi, tie for best low, etc.) to DB. not sure actually if this is such a good idea remember that there can be multiple reasons for the same player in the same hand windows integrated installer -benchmark properly on mysql innodb, mysql myisam, postgresql, sqlite, more? +benchmark properly on mysql innodb, postgresql, more? rename things like this: ClassName.methodName and variableName. do this on tables too. update codingstyle CLI (not ncurses, proper CLI) equivalent for fpdb.py optimise/simplify storing by creating the SQL statements depending on hand rather than calling different methods diff --git a/docs/status.txt b/docs/status.txt index 5aae18bc..3ab5fbf8 100644 --- a/docs/status.txt +++ b/docs/status.txt @@ -2,7 +2,7 @@ For all support please note that the tables WILL be changed, almost certainly wi If support for another site/game would encourage you to help with this software please let me know at steffen@sycamoretest.info. -IMPORTANT: git10 and below contain at least one significant bug that cause wrong data to be displayed. I wrote a new query script for testing so I can track this down. +IMPORTANT: There appears to be a bug in at least up to git34 in reading WtSD/W$wsF/W$SD Holdem/Omaha ring games on PokerStars ====================================== @@ -18,7 +18,7 @@ Broken - used to work, hardly tested. Needs to be updated to new infrastructure. Full Tilt Poker =============== -Not sure - it should import them, but I believe the table viewer won't work. Would be easy to fix I think, volunteers welcome. This used to work in a pre-alpha but basically was left behind during updates to the other stuff. +Might work - used to work and was very well test. It should import them (except HudData), but I believe the table viewer won't work. Would be easy to fix I think, volunteers welcome. This used to work in a pre-alpha but basically was left behind during updates to the other stuff. Other Sites =========== @@ -42,6 +42,12 @@ Regression Testing ================== The query scripts are done-ish, need to verify more data so it can be used in automated regression testing. +Database Backends +================= +- MySQL InnoDB is the default +- PostgreSQL should work but I haven't tried it. If it doesn't please let me know. +- MySQL ISAM won't be supported since it is not transactional + Legal ===== General: This will offer more or less the same kind of functionality as HM and PT so there shouldn't be any problem on any site that allows tracking. diff --git a/packaging/fpdb-1.0_alpha1_p27.ebuild b/packaging/fpdb-1.0_alpha1_p27.ebuild index e0ec86f6..773e8522 100644 --- a/packaging/fpdb-1.0_alpha1_p27.ebuild +++ b/packaging/fpdb-1.0_alpha1_p27.ebuild @@ -14,7 +14,7 @@ SRC_URI="mirror://sourceforge/fpdb/fpdb-alpha1-git27.tar.bz2" LICENSE="AGPL-3" SLOT="0" KEYWORDS="~amd64 ~x86" -#note: I would be very surprised if this doesnt work on other architectures, please send me your experiences +#note: this should work on other architectures too, please send me your experiences IUSE="" RDEPEND="virtual/mysql diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index e61cbffb..ec57bb93 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git33") + self.window.set_title("Free Poker DB - version: alpha1+, git34") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 221d0714..b9d5534c 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -289,7 +289,7 @@ class fpdb_db: thirdBarrelChance INT, thirdBarrelDone INT)""") - self.cursor.execute("INSERT INTO settings VALUES (33);") + self.cursor.execute("INSERT INTO settings VALUES (34);") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 986a9eb2..91f647a7 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1495,7 +1495,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings if someoneStole and (positions[player]=='B' or positions[player]=='S') and firstPfRaiserId!=player_ids[player]: street=0 - for count in range (len(action_types[street][player])):#finally individual actions + for count in range (len(action_types[street][player])):#individual actions if positions[player]=='B': myFoldBbToStealChance=True if action_types[street][player][count]=="fold": @@ -1522,11 +1522,14 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myContBetChance=False myContBetDone=False - #calc CB + if PFR[player]: + myContBetChance=True + if raisedFlop[player]: + myContBetDone=True contBetChance.append(myContBetChance) contBetDone.append(myContBetDone) - result['contBetChance']=contBetDone + result['contBetChance']=contBetChance result['contBetDone']=contBetDone #now 2B @@ -1536,11 +1539,14 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings mySecondBarrelChance=False mySecondBarrelDone=False - #calc 2b + if contBetDone[player]: + mySecondBarrelChance=True + if raisedTurn[player]: + mySecondBarrelDone=True secondBarrelChance.append(mySecondBarrelChance) secondBarrelDone.append(mySecondBarrelDone) - result['secondBarrelChance']=secondBarrelDone + result['secondBarrelChance']=secondBarrelChance result['secondBarrelDone']=secondBarrelDone #now 3B @@ -1550,11 +1556,14 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myThirdBarrelChance=False myThirdBarrelDone=False - #calc 3b + if secondBarrelDone[player]: + myThirdBarrelChance=True + if raisedRiver[player]: + myThirdBarrelDone=True thirdBarrelChance.append(myThirdBarrelChance) thirdBarrelDone.append(myThirdBarrelDone) - result['thirdBarrelChance']=thirdBarrelDone + result['thirdBarrelChance']=thirdBarrelChance result['thirdBarrelDone']=thirdBarrelDone return result From 341b24a2e090474375ec6a3f7f8ccb1e2b638294 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 15 Aug 2008 01:45:40 +0100 Subject: [PATCH 034/262] git35 - changed table and field names to match my naming convention to stabilise downstream-facing api. --- docs/known-bugs-and-planned-features.txt | 1 + docs/tabledesign.html | 379 +++++++++-------------- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 209 +++++++------ pyfpdb/fpdb_parse_logic.py | 2 +- pyfpdb/fpdb_simple.py | 108 ++++--- pyfpdb/table_viewer.py | 18 +- 7 files changed, 321 insertions(+), 398 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index f2662710..83c476a2 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,6 +3,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== +move version into seperate file for fpdb gui and db printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt auto-import seperate and improve instructions for update diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 8f7b19ee..b5144967 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -20,7 +20,7 @@ Copyright 2008 Steffen Jobbagy-Felso
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 as published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license can be found in fdl-1.2.txt
The program itself is licensed under AGPLv3, see agpl-3.0.txt

See readme.txt for copying

-

Table settings

+

Table Settings

@@ -33,8 +33,7 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

Field name

the git version of the database (ie. table design changes and major bugfixes require a bump)

-

Table -players

+

Table Players

@@ -52,9 +51,9 @@ players

- + - + @@ -62,14 +61,13 @@ players

- +

Field name


site_id

siteId

smallint

references sites.id

references Sites.id

comment


comment_ts

commentTs

datetime (in UTC)



-

Table -autorates

+

Table Autorates

An autorating is a computer-"recognised" label/category for a player. Examples could include "Calling Station" if a player has <20% each for aggression and folding postflop. Or "Tight-Aggressive/Aggressive" for players with <20% VPIP, >10% PFR and >40% postflop aggression.

@@ -83,14 +81,14 @@ autorates

- + - + - + - + @@ -98,23 +96,23 @@ autorates

- + - + - +


player_id

playerId

int

references players.id

references Players.id

gametype_id

gametypeId

smallint

references gametypes.id

references Gametypes.id

description

autorating description

short_desc

shortDesc

char(8)

short description e.g. for display in HUD

rating_time

ratingTime

datetime (in UTC)

timestamp of rating

hand_count

handCount

int

number of hands rating is based on


-

Table gametypes

+

Table Gametypes

@@ -127,7 +125,7 @@ autorates

- + @@ -150,7 +148,7 @@ autorates

studhl=7 Card Stud 8 orbetter

- + - + - + - + - +

Field name


site_id

siteId

smallint

references sites.id

limit_type

limitType

char(2)

nl=No Limit
cn=Cap No Limit
@@ -159,28 +157,28 @@ autorates

fl=Fixed Limit

small_blind

smallBlind

int


big_blind

bigBlind

int


small_bet

smallBet

int


big_bet

bigBet

int



-

Table sites

+

Table Sites

@@ -204,7 +202,7 @@ autorates

Field name


-

Table hands

+

Table Hands

@@ -217,17 +215,17 @@ autorates

- + - + - + @@ -242,145 +240,119 @@ autorates

- +

Field Name


site_hand_no

siteHandNo

bigint

the site's hand number

gametype_id

gametypeId

smallint

references gametypes.id

hand_start

handStart

datetime (in UTC)

start date&time of the hand


comment_ts

commentTs

datetime (in UTC)



-

Table board_cards

+

Table BoardCards

cardX -> can be 1 through 5

- - - + + + - - - + + + - - - + + + - - - + + + - - - + + +
-

Field Name

-
-

Type

-
-

Comment

-

Field Name

Type

Comment

-

id

-
-

bigint

-
-


-

-

id

bigint


-

hand_id

-
-

bigint

-
-

the site's hand number

-

handId

bigint

the site's hand number

-

cardX_value

-
-

smallint

-
-

2-10=2-10, J=11, Q=12, - K=13, A=14 (even in razz), unknown/no card=x

-

cardXValue

smallint

2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x

-

cardX_suit

-
-

char(1)

-
-

h=hearts, s=spades, - d=diamonds, c=clubs, unknown/no card=x

-

cardXSuit

char(1)

h=hearts, s=spades, d=diamonds, c=clubs, unknown/no card=x

-


-

-

Table hands_players

+


+

Table HandsPlayers

cardX: can be 1 through 7, one for each card. In holdem/omaha this stores the hole cards so 3-7 or 5-7 are empty

I did not separate this into an extra table because I felt the lost space is not sufficiently large. Also the benefit for searching is far less relevant.

- - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - +

This is used in holdem/omaha only.

+ + + + + + + + + - - - - - - +

see note above table

- - - + + + - - - + + + - - - + + + + + + + + + + + + + + + + + +

Field Name

Type

Comment

id

bigint


hand_id

bigint

references hands_stud.id

player_id

int

references players.id

player_startcash

int


position

char(1)

Field Name

Type

+

Comment

id

bigint


handId

bigint

references Hands.id

playerId

int

references Players.id

startCash

int


position

char(1)

BB=B, SB=S, Button=0, Cutoff=1, etc.

-

This is used in holdem/omaha only.

ante

int

note: for cash this couldbe boolean, but in tourneys you may enter a hand with less thanthe full ante

cardX_value

smallint

ante

int

note: for cash this could be boolean, but in tourneys you may enter a hand with less than the full ante

cardXValue

smallint

2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x

-

see note above table

cardX_suit

char(1)

h=hearts, s=spades, d=diamonds, c=clubs, unknown/no card=x

winnings

int

winnings in this hand (bets, antes, etc. are NOT deducted, but rake already is)

-

rake

-
-

int

-
-

rake for this player for - this hand

-
-

comment

-
-

text

-
-


-

-

cardXSuit

char(1)

h=hearts, s=spades, d=diamonds, c=clubs, unknown/no card=x

-

comment_ts

-
-

datetime (in UTC)

-
-


-

-

winnings

int

winnings in this hand (bets, antes, etc. are NOT deducted, but rake already is)

-

tourneys_players_id

-
-

bigint

-
-

references - tourneys_players.id

-

rake

int

rake for this player for this hand

comment

text


commentTs

datetime (in UTC)


tourneysPlayersId

bigint

references TourneysPlayers.id

-


-

+


Table HudDataHoldemOmaha

@@ -573,7 +545,7 @@ autorates

-

Table hands_actions

+

Table HandsActions

Did separate this into an extra table because it makes SELECTing across different streets so much easier. Also the space saving will be very large.

@@ -587,9 +559,9 @@ autorates

- + - + @@ -597,7 +569,7 @@ autorates

- + @@ -619,7 +591,7 @@ autorates

- + @@ -627,53 +599,27 @@ autorates


Tournament Tables


-

Table tourneys

+

Table Tourneys


hand_player_id

handPlayerId

bigint

references hands_players.id

references HandsPlayers.id

street

street number, 0-3 (preflop, flop, turn, river) for holdem/omaha or 0-4 for razz/stud

-1 for seen showdown

action_no

actionNo

smallint

action number, this is counted from zero for each street but across all players (e.g. in a heads up where the SB calls and the BB raises and the SB calls again would have numbers 0 and 1 for blinds, 2 and 4 for call and 3 for bet)
Note that the blinds are counted as an action, so if the SB stays in the hand it'll always be action #0


comment_ts

commentTs

datetime (in UTC)


- - - + + + - - - + + + - - - + + + - - - + + + @@ -737,7 +682,7 @@ autorates

-

Field name

-
-

Type

-
-

Comment

-

Field name

Type

Comment

-

id

-
-

int

-
-


-

-

id

int


-

site_id

-
-

smallint

-
-

References sites.id

-

siteId

smallint

References Sites.id

-

site_tourney_no

-
-

bigint

-
-


-

-

siteTourneyNo

bigint


@@ -683,8 +629,7 @@ autorates

int

-

Buy-in in cents. Without - rebuy/add-on

+

Buy-in in cents. Without rebuy/add-on

-

start_time

+

startTime

datetime (in UTC)

@@ -760,7 +705,7 @@ autorates

-

comment_ts

+

commentTs

datetime (in UTC)

@@ -773,67 +718,43 @@ autorates


-

Table -tourneys_players

+

Table TourneysPlayers

- - - - - - + + + + + + + + + + + + + - - - - - - - - - - @@ -855,8 +776,7 @@ tourneys_players

signed int

@@ -873,14 +793,13 @@ tourneys_players

-

Field Name

-
-

Type

-
-

Comment

-

Field Name

Type

Comment

id

bigint


tourneyId

int

References Tourneys.id

-

id

-
-

bigint

-
-


-

-
-

tourney_id

+

playerId

int

-

References tourneys.id

+

References Players.id

-

player_id

+

payinAmount

int

-

References players.id

-
-

payin_amount

-
-

int

-
-

Buyin, fee, rebuys and - add-ons

+

Buyin, fee, rebuys and add-ons

-

Winnings (not profit) by - this player, -1 if unknown.

+

Winnings (not profit) by this player, -1 if unknown.

-

comment_ts

+

commentTs

datetime (in UTC)

-


-

+


diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index ec57bb93..e48bac7d 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -347,7 +347,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git34") + self.window.set_title("Free Poker DB - version: alpha1+, git35") self.window.set_border_width(1) self.window.set_size_request(950,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index b9d5534c..cc817678 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -45,9 +45,9 @@ class fpdb_db: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) self.cursor=self.db.cursor() try: - self.cursor.execute("SELECT * FROM settings") + self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=33: + if settings[0]!=35: print "outdated database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -86,30 +86,43 @@ class fpdb_db: def drop_tables(self): """Drops the fpdb tables from the current db""" - self.cursor.execute("DROP TABLE IF EXISTS settings;") - + #todo: run the below if current db is git34 or lower + #self.cursor.execute("DROP TABLE IF EXISTS settings;") + #self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + #self.cursor.execute("DROP TABLE IF EXISTS autorates;") + #self.cursor.execute("DROP TABLE IF EXISTS board_cards;") + #self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") + #self.cursor.execute("DROP TABLE IF EXISTS hands_players;") + #self.cursor.execute("DROP TABLE IF EXISTS hands;") + #self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") + #self.cursor.execute("DROP TABLE IF EXISTS tourneys;") + #self.cursor.execute("DROP TABLE IF EXISTS players;") + #self.cursor.execute("DROP TABLE IF EXISTS gametypes;") + #self.cursor.execute("DROP TABLE IF EXISTS sites;") + + self.cursor.execute("DROP TABLE IF EXISTS Settings;") self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - self.cursor.execute("DROP TABLE IF EXISTS autorates;") - self.cursor.execute("DROP TABLE IF EXISTS board_cards;") - self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") - self.cursor.execute("DROP TABLE IF EXISTS hands_players;") - self.cursor.execute("DROP TABLE IF EXISTS hands;") - self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") - self.cursor.execute("DROP TABLE IF EXISTS tourneys;") - self.cursor.execute("DROP TABLE IF EXISTS players;") - self.cursor.execute("DROP TABLE IF EXISTS gametypes;") - self.cursor.execute("DROP TABLE IF EXISTS sites;") + self.cursor.execute("DROP TABLE IF EXISTS Autorates;") + self.cursor.execute("DROP TABLE IF EXISTS BoardCards;") + self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") + self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") + self.cursor.execute("DROP TABLE IF EXISTS Hands;") + self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") + self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") + self.cursor.execute("DROP TABLE IF EXISTS Players;") + self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") + self.cursor.execute("DROP TABLE IF EXISTS Sites;") self.db.commit() #end def drop_tables def get_backend_name(self): """Returns the name of the currently used backend""" - if self.backend==1: - return "MySQL normal" - elif self.backend==2: + if self.backend==2: return "MySQL InnoDB" elif self.backend==3: return "PostgreSQL" + else: + raise fpdb_simple.FpdbError("invalid backend") #end def get_backend_name def get_db_info(self): @@ -120,139 +133,131 @@ class fpdb_db: """(Re-)creates the tables of the current DB""" self.drop_tables() - self.create_table("""settings ( + self.create_table("""Settings ( version SMALLINT)""") - self.create_table("""sites ( + self.create_table("""Sites ( id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), name varchar(32), currency char(3))""") - self.create_table("""gametypes ( + self.create_table("""Gametypes ( id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - site_id SMALLINT UNSIGNED, FOREIGN KEY (site_id) REFERENCES sites(id), + siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), type char(4), category varchar(9), - limit_type char(2), - small_blind int, - big_blind int, - small_bet int, - big_bet int)""") + limitType char(2), + smallBlind int, + bigBlind int, + smallBet int, + bigBet int)""") - self.create_table("""players ( + self.create_table("""Players ( id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), name VARCHAR(32) CHARACTER SET utf8, - site_id SMALLINT UNSIGNED, FOREIGN KEY (site_id) REFERENCES sites(id), + siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), comment text, - comment_ts DATETIME)""") + commentTs DATETIME)""") - self.create_table("""autorates ( + self.create_table("""Autorates ( id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - player_id INT UNSIGNED, FOREIGN KEY (player_id) REFERENCES players(id), - gametype_id SMALLINT UNSIGNED, FOREIGN KEY (gametype_id) REFERENCES gametypes(id), + playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), + gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), description varchar(50), - short_desc char(8), - rating_time DATETIME, - hand_count int)""") + shortDesc char(8), + ratingTime DATETIME, + handCount int)""") - self.create_table("""hands ( + self.create_table("""Hands ( id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - site_hand_no bigint, - gametype_id SMALLINT UNSIGNED, FOREIGN KEY (gametype_id) REFERENCES gametypes(id), - hand_start DATETIME, + siteHandNo bigint, + gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + handStart DATETIME, seats smallint, comment text, - comment_ts DATETIME)""") + commentTs DATETIME)""") - self.create_table("""board_cards ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, - PRIMARY KEY (id), - hand_id BIGINT UNSIGNED, - FOREIGN KEY (hand_id) REFERENCES hands(id), - card1_value smallint, - card1_suit char(1), - card2_value smallint, - card2_suit char(1), - card3_value smallint, - card3_suit char(1), - card4_value smallint, - card4_suit char(1), - card5_value smallint, - card5_suit char(1))""") + self.create_table("""BoardCards ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + handId BIGINT UNSIGNED, FOREIGN KEY (handId) REFERENCES Hands(id), + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1))""") - self.create_table("""tourneys ( + self.create_table("""Tourneys ( id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - site_id SMALLINT UNSIGNED, FOREIGN KEY (site_id) REFERENCES sites(id), - site_tourney_no BIGINT, + siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), + siteTourneyNo BIGINT, buyin INT, fee INT, knockout INT, entries INT, prizepool INT, - start_time DATETIME, + startTime DATETIME, comment TEXT, - comment_ts DATETIME)""") + commentTs DATETIME)""") - self.create_table("""tourneys_players ( + self.create_table("""TourneysPlayers ( id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - tourney_id INT UNSIGNED, FOREIGN KEY (tourney_id) REFERENCES tourneys(id), - player_id INT UNSIGNED, FOREIGN KEY (player_id) REFERENCES players(id), - payin_amount INT, + tourneyId INT UNSIGNED, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), + playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), + payinAmount INT, rank INT, winnings INT, comment TEXT, - comment_ts DATETIME)""") + commentTs DATETIME)""") - self.create_table("""hands_players ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, - PRIMARY KEY (id), - hand_id BIGINT UNSIGNED, - FOREIGN KEY (hand_id) REFERENCES hands(id), - player_id INT UNSIGNED, - FOREIGN KEY (player_id) REFERENCES players(id), - player_startcash int, + self.create_table("""HandsPlayers ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + handId BIGINT UNSIGNED, FOREIGN KEY (handId) REFERENCES Hands(id), + playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), + startCash int, position char(1), ante int, - card1_value smallint, - card1_suit char(1), - card2_value smallint, - card2_suit char(1), - card3_value smallint, - card3_suit char(1), - card4_value smallint, - card4_suit char(1), - card5_value smallint, - card5_suit char(1), - card6_value smallint, - card6_suit char(1), - card7_value smallint, - card7_suit char(1), + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1), + card6Value smallint, + card6Suit char(1), + card7Value smallint, + card7Suit char(1), winnings int, rake int, comment text, - comment_ts DATETIME, + commentTs DATETIME, - tourneys_players_id BIGINT UNSIGNED, - FOREIGN KEY (tourneys_players_id) REFERENCES tourneys_players(id))""") + tourneysPlayersId BIGINT UNSIGNED, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id))""") - self.create_table("""hands_actions ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, - PRIMARY KEY (id), - hand_player_id BIGINT UNSIGNED, - FOREIGN KEY (hand_player_id) REFERENCES hands_players(id), + self.create_table("""HandsActions ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + handPlayerId BIGINT UNSIGNED, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), street SMALLINT, - action_no SMALLINT, + actionNo SMALLINT, action CHAR(5), amount INT, comment TEXT, - comment_ts DATETIME)""") + commentTs DATETIME)""") self.create_table("""HudDataHoldemOmaha ( id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES gametypes(id), - playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES players(id), + gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), activeSeats SMALLINT, HDs INT, VPIP INT, @@ -289,9 +294,9 @@ class fpdb_db: thirdBarrelChance INT, thirdBarrelDone INT)""") - self.cursor.execute("INSERT INTO settings VALUES (34);") - self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") - self.cursor.execute("INSERT INTO sites VALUES (DEFAULT, \"PokerStars\", 'USD');") + self.cursor.execute("INSERT INTO Settings VALUES (35);") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() print "finished recreating tables" #end def recreate_tables diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 617f2393..c8efc2e2 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -94,7 +94,7 @@ def mainParser(db, cursor, site, category, hand): fpdb_simple.convertBlindBet(actionTypes, actionAmounts) fpdb_simple.checkPositions(positions) - cursor.execute("SELECT limit_type FROM gametypes WHERE id=%s",(gametypeID, )) + cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, )) limit_type=cursor.fetchone()[0] fpdb_simple.convert3B4B(site, category, limit_type, actionTypes, actionAmounts) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 91f647a7..d661421b 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -457,7 +457,7 @@ def isActionLine(line): #returns whether this is a duplicate def isAlreadyInDB(cursor, gametypeID, siteHandNo): - cursor.execute ("SELECT id FROM hands WHERE gametype_id=%s AND site_hand_no=%s", (gametypeID, siteHandNo)) + cursor.execute ("SELECT id FROM Hands WHERE gametypeId=%s AND siteHandNo=%s", (gametypeID, siteHandNo)) result=cursor.fetchall() if (len(result)>=1): raise DuplicateError ("dupl") @@ -958,9 +958,9 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th #print "recogniseGametypeID small_bet/blind:",small_bet,"big bet/blind:", big_bet,"limit type:",limit_type if (limit_type=="fl"): - cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_bet=%s AND big_bet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBet=%s AND bigBet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) else: - cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_blind=%s AND big_blind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBlind=%s AND bigBlind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) result=cursor.fetchone() #print "tried SELECTing gametypes.id, result:",result @@ -975,15 +975,15 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th if (limit_type=="fl"): big_blind=small_bet #todo: read this small_blind=big_blind/2 #todo: read this - cursor.execute("""INSERT INTO gametypes - (site_id, type, category, limit_type, small_blind, big_blind, small_bet, big_bet) + cursor.execute("""INSERT INTO Gametypes + (siteId, type, category, limitType, smallBlind, bigBlind, smallBet, bigBet) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, category, limit_type, small_blind, big_blind, small_bet, big_bet)) - cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_bet=%s AND big_bet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBet=%s AND bigBet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) else: - cursor.execute("""INSERT INTO gametypes - (site_id, type, category, limit_type, small_blind, big_blind, small_bet, big_bet) + cursor.execute("""INSERT INTO Gametypes + (siteId, type, category, limitType, smallBlind, bigBlind, smallBet, bigBet) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, category, limit_type, small_bet, big_bet, 0, 0))#remember, for these bet means blind - cursor.execute ("SELECT id FROM gametypes WHERE site_id=%s AND type=%s AND category=%s AND limit_type=%s AND small_blind=%s AND big_blind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) + cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBlind=%s AND bigBlind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) result=cursor.fetchone() #print "created new gametypes.id:",result @@ -995,12 +995,12 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th def recognisePlayerIDs(cursor, names, site_id): result = [] for i in range (len(names)): - cursor.execute ("SELECT id FROM players WHERE name=%s", (names[i],)) + cursor.execute ("SELECT id FROM Players WHERE name=%s", (names[i],)) tmp=cursor.fetchall() if (len(tmp)==0): #new player - cursor.execute ("INSERT INTO players (name, site_id) VALUES (%s, %s)", (names[i], site_id)) + cursor.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)", (names[i], site_id)) #print "Number of players rows inserted: %d" % cursor.rowcount - cursor.execute ("SELECT id FROM players WHERE name=%s", (names[i],)) + cursor.execute ("SELECT id FROM Players WHERE name=%s", (names[i],)) tmp=cursor.fetchall() #print "recognisePlayerIDs, names[i]:",names[i],"tmp:",tmp result.append(tmp[0][0]) @@ -1048,9 +1048,9 @@ def recogniseSite(line): #returns the ID of the given site def recogniseSiteID(cursor, site): if (site=="ftp"): - cursor.execute("SELECT id FROM sites WHERE name = ('Full Tilt Poker')") + cursor.execute("SELECT id FROM Sites WHERE name = ('Full Tilt Poker')") elif (site=="ps"): - cursor.execute("SELECT id FROM sites WHERE name = ('PokerStars')") + cursor.execute("SELECT id FROM Sites WHERE name = ('PokerStars')") return cursor.fetchall()[0][0] #end def recogniseSiteID @@ -1090,15 +1090,14 @@ def storeActions(cursor, hands_players_ids, action_types, action_amounts, action for i in range (len(action_types)): #iterate through streets for j in range (len(action_types[i])): #iterate through names for k in range (len(action_types[i][j])): #iterate through individual actions of that player on that street - cursor.execute ("INSERT INTO hands_actions (hand_player_id, street, action_no, action, amount) VALUES (%s, %s, %s, %s, %s)", (hands_players_ids[j], i, actionNos[i][j][k], action_types[i][j][k], action_amounts[i][j][k])) + cursor.execute ("INSERT INTO HandsActions (handPlayerId, street, actionNo, action, amount) VALUES (%s, %s, %s, %s, %s)", (hands_players_ids[j], i, actionNos[i][j][k], action_types[i][j][k], action_amounts[i][j][k])) #end def storeActions def store_board_cards(cursor, hands_id, board_values, board_suits): #stores into table board_cards - cursor.execute (""" - INSERT INTO board_cards (hand_id, card1_value, card1_suit, - card2_value, card2_suit, card3_value, card3_suit, card4_value, card4_suit, - card5_value, card5_suit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + cursor.execute ("""INSERT INTO BoardCards (handId, card1Value, card1Suit, + card2Value, card2Suit, card3Value, card3Suit, card4Value, card4Suit, + card5Value, card5Suit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, board_values[0], board_suits[0], board_values[1], board_suits[1], board_values[2], board_suits[2], board_values[3], board_suits[3], board_values[4], board_suits[4])) @@ -1106,9 +1105,9 @@ def store_board_cards(cursor, hands_id, board_values, board_suits): def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names): #stores into table hands - cursor.execute ("INSERT INTO hands (site_hand_no, gametype_id, hand_start, seats) VALUES (%s, %s, %s, %s)", (site_hand_no, gametype_id, hand_start_time, len(names))) + cursor.execute ("INSERT INTO Hands (siteHandNo, gametypeId, handStart, seats) VALUES (%s, %s, %s, %s)", (site_hand_no, gametype_id, hand_start_time, len(names))) #todo: find a better way of doing this... - cursor.execute("SELECT id FROM hands WHERE site_hand_no=%s AND gametype_id=%s", (site_hand_no, gametype_id)) + cursor.execute("SELECT id FROM Hands WHERE siteHandNo=%s AND gametypeId=%s", (site_hand_no, gametype_id)) return cursor.fetchall()[0][0] #end def storeHands @@ -1118,22 +1117,21 @@ def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, if (category=="holdem"): for i in range (len(player_ids)): cursor.execute (""" - INSERT INTO hands_players - (hand_id, player_id, player_startcash, position, - card1_value, card1_suit, card2_value, card2_suit, winnings, rake) + INSERT INTO HandsPlayers + (handId, playerId, startCash, position, + card1Value, card1Suit, card2Value, card2Suit, winnings, rake) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], winnings[i], rakes[i])) - cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId=%s", (hands_id, player_ids[i])) result.append(cursor.fetchall()[0][0]) elif (category=="omahahi" or category=="omahahilo"): for i in range (len(player_ids)): - cursor.execute (""" - INSERT INTO hands_players - (hand_id, player_id, player_startcash, position, - card1_value, card1_suit, card2_value, card2_suit, - card3_value, card3_suit, card4_value, card4_suit, winnings, rake) + cursor.execute ("""INSERT INTO HandsPlayers + (handId, playerId, startCash, position, + card1Value, card1Suit, card2Value, card2Suit, + card3Value, card3Suit, card4Value, card4Suit, winnings, rake) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], @@ -1151,12 +1149,12 @@ def store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, #stores hands_players rows for stud/razz games. returns an array of the resulting IDs result=[] for i in range (len(player_ids)): - cursor.execute ("""INSERT INTO hands_players - (hand_id, player_id, player_startcash, ante, - card1_value, card1_suit, card2_value, card2_suit, - card3_value, card3_suit, card4_value, card4_suit, - card5_value, card5_suit, card6_value, card6_suit, - card7_value, card7_suit, winnings, rake) + cursor.execute ("""INSERT INTO HandsPlayers + (handId, playerId, startCash, ante, + card1Value, card1Suit, card2Value, card2Suit, + card3Value, card3Suit, card4Value, card4Suit, + card5Value, card5Suit, card6Value, card6Suit, + card7Value, card7Suit, winnings, rake) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], antes[i], @@ -1175,20 +1173,20 @@ def store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, start result=[] for i in range (len(player_ids)): if len(card_values[0])==2: - cursor.execute ("""INSERT INTO hands_players - (hand_id, player_id, player_startcash,position, - card1_value, card1_suit, card2_value, card2_suit, - winnings, rake, tourneys_players_id) + cursor.execute ("""INSERT INTO HandsPlayers + (handId, playerId, startCash, position, + card1Value, card1Suit, card2Value, card2Suit, + winnings, rake, tourneysPlayersId) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], winnings[i], rakes[i], tourneys_players_ids[i])) elif len(card_values[0])==4: - cursor.execute ("""INSERT INTO hands_players - (hand_id, player_id, player_startcash,position, - card1_value, card1_suit, card2_value, card2_suit, - card3_value, card3_suit, card4_value, card4_suit, - winnings, rake, tourneys_players_id) + cursor.execute ("""INSERT INTO HandsPlayers + (handId, playerId, startCash, position, + card1Value, card1Suit, card2Value, card2Suit, + card3Value, card3Suit, card4Value, card4Suit, + winnings, rake, tourneysPlayersId) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], @@ -1196,7 +1194,7 @@ def store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, start winnings[i], rakes[i], tourneys_players_ids[i])) else: raise FpdbError ("invalid card_values length:"+str(len(card_values[0]))) - cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId=%s", (hands_id, player_ids[i])) result.append(cursor.fetchall()[0][0]) return result @@ -1207,7 +1205,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, #stores hands_players for tourney stud/razz hands result=[] for i in range (len(player_ids)): - cursor.execute ("""INSERT INTO hands_players + cursor.execute ("""INSERT INTO HandsPlayers (hand_id, player_id, player_startcash, ante, card1_value, card1_suit, card2_value, card2_suit, card3_value, card3_suit, card4_value, card4_suit, @@ -1647,17 +1645,17 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): #end def storeHudData def store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time): - cursor.execute("SELECT id FROM tourneys WHERE site_tourney_no=%s AND site_id=%s", (site_tourney_no, site_id)) + cursor.execute("SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND siteId=%s", (site_tourney_no, site_id)) tmp=cursor.fetchone() #print "tried SELECTing tourneys.id, result:",tmp try: len(tmp) except TypeError: - cursor.execute("""INSERT INTO tourneys - (site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time) + cursor.execute("""INSERT INTO Tourneys + (siteId, siteTourneyNo, buyin, fee, knockout, entries, prizepool, startTime) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time)) - cursor.execute("SELECT id FROM tourneys WHERE site_tourney_no=%s AND site_id=%s", (site_tourney_no, site_id)) + cursor.execute("SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND siteId=%s", (site_tourney_no, site_id)) tmp=cursor.fetchone() #print "created new tourneys.id:",tmp return tmp[0] @@ -1671,18 +1669,18 @@ def store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, #print "ranks:",ranks #print "winnings:",winnings for i in range (len(player_ids)): - cursor.execute("SELECT id FROM tourneys_players WHERE tourney_id=%s AND player_id=%s", (tourney_id, player_ids[i])) + cursor.execute("SELECT id FROM TourneysPlayers WHERE tourneyId=%s AND playerId=%s", (tourney_id, player_ids[i])) tmp=cursor.fetchone() #print "tried SELECTing tourneys_players.id:",tmp try: len(tmp) except TypeError: - cursor.execute("""INSERT INTO tourneys_players - (tourney_id, player_id, payin_amount, rank, winnings) VALUES (%s, %s, %s, %s, %s)""", + cursor.execute("""INSERT INTO TourneysPlayers + (tourneyId, playerId, payinAmount, rank, winnings) VALUES (%s, %s, %s, %s, %s)""", (tourney_id, player_ids[i], payin_amounts[i], ranks[i], winnings[i])) - cursor.execute("SELECT id FROM tourneys_players WHERE tourney_id=%s AND player_id=%s", + cursor.execute("SELECT id FROM TourneysPlayers WHERE tourneyId=%s AND playerId=%s", (tourney_id, player_ids[i])) tmp=cursor.fetchone() #print "created new tourneys_players.id:",tmp diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index c68341e2..47f37fce 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -177,26 +177,26 @@ class table_viewer (threading.Thread): #print "self.last_read_hand:",self.last_read_hand self.db.reconnect() self.cursor=self.db.cursor - self.cursor.execute("""SELECT id FROM hands WHERE site_hand_no=%s""", (self.last_read_hand)) + self.cursor.execute("""SELECT id FROM Hands WHERE siteHandNo=%s""", (self.last_read_hand)) hands_id_tmp=self.db.cursor.fetchone() #print "tmp:",hands_id_tmp self.hands_id=hands_id_tmp[0] - self.db.cursor.execute("SELECT gametype_id FROM hands WHERE id=%s", (self.hands_id, )) + self.db.cursor.execute("SELECT gametypeId FROM Hands WHERE id=%s", (self.hands_id, )) self.gametype_id=self.db.cursor.fetchone()[0] - self.cursor.execute("SELECT category FROM gametypes WHERE id=%s", (self.gametype_id, )) + self.cursor.execute("SELECT category FROM Gametypes WHERE id=%s", (self.gametype_id, )) self.category=self.db.cursor.fetchone()[0] #print "self.gametype_id", self.gametype_id," category:", self.category, " self.hands_id:", self.hands_id - self.db.cursor.execute("""SELECT DISTINCT players.id FROM hands_players - INNER JOIN players ON hands_players.player_id=players.id - WHERE hand_id=%s""", (self.hands_id, )) + self.db.cursor.execute("""SELECT DISTINCT Players.id FROM HandsPlayers + INNER JOIN Players ON HandsPlayers.playerId=Players.id + WHERE handId=%s""", (self.hands_id, )) self.player_ids=self.db.cursor.fetchall() #print "self.player_ids:",self.player_ids - self.db.cursor.execute("""SELECT DISTINCT players.name FROM hands_players - INNER JOIN players ON hands_players.player_id=players.id - WHERE hand_id=%s""", (self.hands_id, )) + self.db.cursor.execute("""SELECT DISTINCT Players.name FROM HandsPlayers + INNER JOIN Players ON HandsPlayers.playerId=Players.id + WHERE handId=%s""", (self.hands_id, )) self.player_names=self.db.cursor.fetchall() #print "self.player_names:",self.player_names #end def table_viewer.read_names_clicked From ff2e75cb6b9b05f3d3bbad920c35cfeac6da1db3 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 15 Aug 2008 02:45:19 +0100 Subject: [PATCH 035/262] git36 - implement tv-combinedStealFold and tv-combined2B3B --- docs/default.conf | 2 ++ docs/known-bugs-and-planned-features.txt | 2 ++ pyfpdb/fpdb.py | 20 +++++++++++++-- pyfpdb/fpdb_db.py | 24 +++++++++--------- pyfpdb/table_viewer.py | 32 ++++++++++++++++++++---- 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/docs/default.conf b/docs/default.conf index dadf6290..d2e095d8 100644 --- a/docs/default.conf +++ b/docs/default.conf @@ -3,6 +3,8 @@ db-host=localhost db-databaseName=fpdb db-user=fpdb db-password=enterYourPwHere +tv-combinedStealFold=True +tv-combined2B3B=True tv-combinedPostflop=True bulkImport-defaultPath=default tv-defaultPath=default diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 83c476a2..1e94e9be 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,6 +3,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== +make table drops depending on previous db version move version into seperate file for fpdb gui and db printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt auto-import @@ -30,6 +31,7 @@ fix bug that sawFlop/Turn/River gets miscalculated if someone is allin - might a before beta =========== gentoo ebuild: USE postgresql +skins optionally combine FB/FS and CB/2B/3B separate all gui and all processing into files that are named accordingly ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index e48bac7d..1cdbf39e 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -245,6 +245,8 @@ class fpdb: self.settings['os']="linuxmac" else: self.settings['os']="windows" + self.settings['tv-combinedStealFold']=True + self.settings['tv-combined2B3B']=True if self.settings['os']=="windows": self.settings['bulkImport-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\filename.txt" @@ -269,12 +271,26 @@ class fpdb: self.settings['tv-combinedPostflop']=True else: self.settings['tv-combinedPostflop']=False + elif lines[i].startswith("tv-combinedStealFold="): + if lines[i].find("True")!=-1: + self.settings['tv-combinedStealFold']=True + else: + self.settings['tv-combinedStealFold']=False + elif lines[i].startswith("tv-combined2B3B="): + if lines[i].find("True")!=-1: + self.settings['tv-combined2B3B']=True + else: + self.settings['tv-combined2B3B']=False elif lines[i].startswith("bulkImport-defaultPath="): if lines[i][23:-1]!="default": self.settings['bulkImport-defaultPath']=lines[i][23:-1] elif lines[i].startswith("tv-defaultPath="): if lines[i][15:-1]!="default": self.settings['tv-defaultPath']=lines[i][15:-1] + elif lines[i].startswith("#"): + pass #comment - dont parse + else: + raise fpdb_simple.FpdbError("invalid line in profile file: "+lines[i]) if self.db!=None: self.db.disconnect() @@ -347,9 +363,9 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git35") + self.window.set_title("Free Poker DB - version: alpha1+, git36") self.window.set_border_width(1) - self.window.set_size_request(950,400) + self.window.set_size_request(1020,400) self.window.set_resizable(True) self.menu_items = ( diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index cc817678..b065b43b 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -87,18 +87,18 @@ class fpdb_db: def drop_tables(self): """Drops the fpdb tables from the current db""" #todo: run the below if current db is git34 or lower - #self.cursor.execute("DROP TABLE IF EXISTS settings;") - #self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - #self.cursor.execute("DROP TABLE IF EXISTS autorates;") - #self.cursor.execute("DROP TABLE IF EXISTS board_cards;") - #self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") - #self.cursor.execute("DROP TABLE IF EXISTS hands_players;") - #self.cursor.execute("DROP TABLE IF EXISTS hands;") - #self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") - #self.cursor.execute("DROP TABLE IF EXISTS tourneys;") - #self.cursor.execute("DROP TABLE IF EXISTS players;") - #self.cursor.execute("DROP TABLE IF EXISTS gametypes;") - #self.cursor.execute("DROP TABLE IF EXISTS sites;") + self.cursor.execute("DROP TABLE IF EXISTS settings;") + self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + self.cursor.execute("DROP TABLE IF EXISTS autorates;") + self.cursor.execute("DROP TABLE IF EXISTS board_cards;") + self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") + self.cursor.execute("DROP TABLE IF EXISTS hands_players;") + self.cursor.execute("DROP TABLE IF EXISTS hands;") + self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") + self.cursor.execute("DROP TABLE IF EXISTS tourneys;") + self.cursor.execute("DROP TABLE IF EXISTS players;") + self.cursor.execute("DROP TABLE IF EXISTS gametypes;") + self.cursor.execute("DROP TABLE IF EXISTS sites;") self.cursor.execute("DROP TABLE IF EXISTS Settings;") self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 47f37fce..e4686a17 100755 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -59,11 +59,25 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST", "FS", "FB", "CB", "2B", "3B") + tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST") + + if self.settings['tv-combinedStealFold']: + tmp+=("FSB", ) + else: + tmp+=("FS", "FB") + + tmp+=("CB", ) + + if self.settings['tv-combined2B3B']: + tmp+=("23B", ) + else: + tmp+=("2B", "3B") + if self.settings['tv-combinedPostflop']: tmp+=("Postf A", "Postf F") else: tmp+=("AF", "FF", "AT", "FT", "AR", "FR") + tmp+=("WtSD", "W$wsF", "W$SD") else: raise fpdb_simple.FpdbError("reimplement stud") @@ -111,12 +125,20 @@ class table_viewer (threading.Thread): tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B tmp.append(self.hudDivide(row[25],row[24])+" ("+str(row[24])+")") #ST - tmp.append(self.hudDivide(row[29],row[28])+" ("+str(row[28])+")") #FS - tmp.append(self.hudDivide(row[27],row[26])+" ("+str(row[26])+")") #FB + + if self.settings['tv-combinedStealFold']: + tmp.append(self.hudDivide(row[29]+row[27],row[28]+row[26])+" ("+str(row[28]+row[26])+")") #FSB + else: + tmp.append(self.hudDivide(row[29],row[28])+" ("+str(row[28])+")") #FS + tmp.append(self.hudDivide(row[27],row[26])+" ("+str(row[26])+")") #FB tmp.append(self.hudDivide(row[31],row[30])+" ("+str(row[30])+")") #CB - tmp.append(self.hudDivide(row[33],row[32])+" ("+str(row[32])+")") #2B - tmp.append(self.hudDivide(row[35],row[34])+" ("+str(row[34])+")") #3B + + if self.settings['tv-combined2B3B']: + tmp.append(self.hudDivide(row[33]+row[35],row[32]+row[34])+" ("+str(row[32]+row[34])+")") #23B + else: + tmp.append(self.hudDivide(row[33],row[32])+" ("+str(row[32])+")") #2B + tmp.append(self.hudDivide(row[35],row[34])+" ("+str(row[34])+")") #3B if self.settings['tv-combinedPostflop']: aggCount=row[13]+row[14]+row[15] From 732edf9e698686ff87308e28e070b3b6f1a52a37 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 15 Aug 2008 02:53:37 +0100 Subject: [PATCH 036/262] git37 - make table drops depending on previous db version --- docs/known-bugs-and-planned-features.txt | 6 +-- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 65 ++++++++++++++---------- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 1e94e9be..ced9c7cd 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,9 +3,8 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -make table drops depending on previous db version +make HudData tables positional move version into seperate file for fpdb gui and db -printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt auto-import seperate and improve instructions for update verify link in release notes @@ -18,6 +17,8 @@ update abbreviations.txt fix up bg colours in tv ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config +printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt + alpha3 ====== check-raise/call-raise on all streets @@ -91,7 +92,6 @@ recognise somewhere if a file is still active and if so keep it open and only re can wait till 1.x ================= -positional stats in HUD return full ftp functionality in all importer: stop doing if site=="ftp", make class constants for site_id instead finish cleaning tabledesign html code diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 1cdbf39e..6bca690e 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -363,7 +363,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git36") + self.window.set_title("Free Poker DB - version: alpha1+, git37") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index b065b43b..b7d07370 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -86,32 +86,45 @@ class fpdb_db: def drop_tables(self): """Drops the fpdb tables from the current db""" - #todo: run the below if current db is git34 or lower - self.cursor.execute("DROP TABLE IF EXISTS settings;") - self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - self.cursor.execute("DROP TABLE IF EXISTS autorates;") - self.cursor.execute("DROP TABLE IF EXISTS board_cards;") - self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") - self.cursor.execute("DROP TABLE IF EXISTS hands_players;") - self.cursor.execute("DROP TABLE IF EXISTS hands;") - self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") - self.cursor.execute("DROP TABLE IF EXISTS tourneys;") - self.cursor.execute("DROP TABLE IF EXISTS players;") - self.cursor.execute("DROP TABLE IF EXISTS gametypes;") - self.cursor.execute("DROP TABLE IF EXISTS sites;") - - self.cursor.execute("DROP TABLE IF EXISTS Settings;") - self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - self.cursor.execute("DROP TABLE IF EXISTS Autorates;") - self.cursor.execute("DROP TABLE IF EXISTS BoardCards;") - self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") - self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") - self.cursor.execute("DROP TABLE IF EXISTS Hands;") - self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") - self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") - self.cursor.execute("DROP TABLE IF EXISTS Players;") - self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") - self.cursor.execute("DROP TABLE IF EXISTS Sites;") + oldDbVersion=0 + try: + self.cursor.execute("SELECT * FROM settings") #for alpha1 + oldDbVersion=self.cursor.fetchone()[0] + except:# _mysql_exceptions.ProgrammingError: + pass + try: + self.cursor.execute("SELECT * FROM Settings") + oldDbVersion=self.cursor.fetchone()[0] + except:# _mysql_exceptions.ProgrammingError: + pass + + if oldDbVersion<=34: + self.cursor.execute("DROP TABLE IF EXISTS settings;") + self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + self.cursor.execute("DROP TABLE IF EXISTS autorates;") + self.cursor.execute("DROP TABLE IF EXISTS board_cards;") + self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") + self.cursor.execute("DROP TABLE IF EXISTS hands_players;") + self.cursor.execute("DROP TABLE IF EXISTS hands;") + self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") + self.cursor.execute("DROP TABLE IF EXISTS tourneys;") + self.cursor.execute("DROP TABLE IF EXISTS players;") + self.cursor.execute("DROP TABLE IF EXISTS gametypes;") + self.cursor.execute("DROP TABLE IF EXISTS sites;") + else: + self.cursor.execute("DROP TABLE IF EXISTS Settings;") + self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + self.cursor.execute("DROP TABLE IF EXISTS Autorates;") + self.cursor.execute("DROP TABLE IF EXISTS BoardCards;") + self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") + self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") + self.cursor.execute("DROP TABLE IF EXISTS Hands;") + self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") + self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") + self.cursor.execute("DROP TABLE IF EXISTS Players;") + self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") + self.cursor.execute("DROP TABLE IF EXISTS Sites;") + self.db.commit() #end def drop_tables From 423753de179901d2fddfd6b1ef556e1f82aec536 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 15 Aug 2008 03:32:27 +0100 Subject: [PATCH 037/262] git38 - make HudData tables positional (sf request 2052124) --- docs/known-bugs-and-planned-features.txt | 4 +- docs/tabledesign.html | 183 ++++++----------------- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 34 +++-- pyfpdb/fpdb_simple.py | 29 +++- 5 files changed, 94 insertions(+), 158 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index ced9c7cd..9fde4b0a 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,7 +3,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -make HudData tables positional +fold to CB/2B/3B move version into seperate file for fpdb gui and db auto-import seperate and improve instructions for update @@ -17,6 +17,7 @@ update abbreviations.txt fix up bg colours in tv ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config +verify positionalness of HudData printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt alpha3 @@ -31,6 +32,7 @@ fix bug that sawFlop/Turn/River gets miscalculated if someone is allin - might a before beta =========== +optionally make tv positional gentoo ebuild: USE postgresql skins optionally combine FB/FS and CB/2B/3B diff --git a/docs/tabledesign.html b/docs/tabledesign.html index b5144967..30cd86cc 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -543,6 +543,11 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

int

Player used chance to make third barrel bet

+ +

position

+

char(1)

+

Position for which this row applies. In this table this can be B(BB), S(SB), D(Dealer/Button), C(Cutoff), M(Middle - the 3 before cutoff) or E (Early - the 3 before Middle)

+

Table HandsActions

@@ -622,102 +627,47 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


- -

buyin

- - -

int

- - -

Buy-in in cents. Without rebuy/add-on

- +

buyin

+

int

+

Buy-in in cents. Without rebuy/add-on

- -

fee

- - -

int

- - -


-

- +

fee

+

int

+


- -

knockout

- - -

int

- - -


-

- +

knockout

+

int

+


- -

entries

- - -

int

- - -

-1 if unknown

- +

entries

+

int

+

-1 if unknown

- -

prizepool

- - -

int

- - -

Need - this as separate field to support rebuy/addon

-

-1 if unknown

- +

prizepool

+

int

+

Need this as separate field to support rebuy/addon

-1 if unknown

- -

startTime

- - -

datetime (in UTC)

- - -

Empty if unknown

- +

startTime

+

datetime (in UTC)

+

Empty if unknown

- -

comment

- - -

text

- - -


-

- +

comment

+

text

+


- -

commentTs

- - -

datetime (in UTC)

- - -


-

- +

commentTs

+

datetime (in UTC)

+


-


-

+


Table TourneysPlayers

@@ -736,71 +686,34 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

- - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + +

References Tourneys.id

-

playerId

-
-

int

-
-

References Players.id

-

playerId

int

References Players.id

-

payinAmount

-
-

int

-
-

Buyin, fee, rebuys and add-ons

-

payinAmount

int

Buyin, fee, rebuys and add-ons

-

rank

-
-

int

-
-

Finishing rank

-

rank

int

Finishing rank

-

winnings

-
-

signed int

-
-

Winnings (not profit) by this player, -1 if unknown.

-

winnings

signed int

Winnings (not profit) by this player, -1 if unknown.

-

comment

-
-

text

-
-


-

-

comment

text


-

commentTs

-
-

datetime (in UTC)

-
-


-

commentTs

datetime (in UTC)


diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 6bca690e..e8a6f822 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -363,7 +363,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git37") + self.window.set_title("Free Poker DB - version: alpha1+, git38") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index b7d07370..13e63568 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=35: + if settings[0]!=38: print "outdated database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -111,19 +111,19 @@ class fpdb_db: self.cursor.execute("DROP TABLE IF EXISTS players;") self.cursor.execute("DROP TABLE IF EXISTS gametypes;") self.cursor.execute("DROP TABLE IF EXISTS sites;") - else: - self.cursor.execute("DROP TABLE IF EXISTS Settings;") - self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - self.cursor.execute("DROP TABLE IF EXISTS Autorates;") - self.cursor.execute("DROP TABLE IF EXISTS BoardCards;") - self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") - self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") - self.cursor.execute("DROP TABLE IF EXISTS Hands;") - self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") - self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") - self.cursor.execute("DROP TABLE IF EXISTS Players;") - self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") - self.cursor.execute("DROP TABLE IF EXISTS Sites;") + + self.cursor.execute("DROP TABLE IF EXISTS Settings;") + self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + self.cursor.execute("DROP TABLE IF EXISTS Autorates;") + self.cursor.execute("DROP TABLE IF EXISTS BoardCards;") + self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") + self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") + self.cursor.execute("DROP TABLE IF EXISTS Hands;") + self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") + self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") + self.cursor.execute("DROP TABLE IF EXISTS Players;") + self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") + self.cursor.execute("DROP TABLE IF EXISTS Sites;") self.db.commit() #end def drop_tables @@ -305,9 +305,11 @@ class fpdb_db: secondBarrelChance INT, secondBarrelDone INT, thirdBarrelChance INT, - thirdBarrelDone INT)""") + thirdBarrelDone INT, - self.cursor.execute("INSERT INTO Settings VALUES (35);") + position CHAR(1))""") + + self.cursor.execute("INSERT INTO Settings VALUES (38);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index d661421b..2d29a39d 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1247,6 +1247,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings wonAtSD=[] stealAttemptChance=[] stealAttempted=[] + hudDataPositions=[] firstPfRaiseByNo=-1 firstPfRaiserId=-1 @@ -1456,6 +1457,21 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings wonAtSD.append(myWonAtSD) stealAttemptChance.append(myStealAttemptChance) stealAttempted.append(myStealAttempted) + pos=positions[player] + if pos=='B': + hudDataPositions.append('B') + elif pos=='S': + hudDataPositions.append('S') + elif pos==0: + hudDataPositions.append('D') + elif pos==1: + hudDataPositions.append('C') + elif pos>=2 and pos<=4: + hudDataPositions.append('M') + elif pos>=5 and pos<=7: + hudDataPositions.append('L') + else: + raise FpdbError("invalid position") #add each array to the to-be-returned dictionary result={'VPIP':VPIP} @@ -1564,13 +1580,14 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings result['thirdBarrelChance']=thirdBarrelChance result['thirdBarrelDone']=thirdBarrelDone + result['position']=hudDataPositions return result #end def calculateHudImport def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if (category=="holdem" or category=="omahahi" or category=="omahahilo"): for player in range (len(playerIds)): - cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s", (gametypeId, playerIds[player], len(playerIds))) + cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s", (gametypeId, playerIds[player], len(playerIds), hudImportData['position'][player])) row=cursor.fetchone() #print "gametypeId:", gametypeId, "playerIds[player]",playerIds[player], "len(playerIds):",len(playerIds), "row:",row @@ -1589,6 +1606,7 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): row.append(0)#HDs for i in range(len(hudImportData)): row.append(0) + else: doInsert=False newrow=[] @@ -1629,17 +1647,18 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if hudImportData['secondBarrelDone'][player]: row[33]+=1 if hudImportData['thirdBarrelChance'][player]: row[34]+=1 if hudImportData['thirdBarrelDone'][player]: row[35]+=1 - + row[36]=hudImportData['position'][player] + if doInsert: #print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, contBetChance, contBetDone, secondBarrelChance, secondBarrelDone, thirdBarrelChance, thirdBarrelDone) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35])) + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, contBetChance, contBetDone, secondBarrelChance, secondBarrelDone, thirdBarrelChance, thirdBarrelDone, position) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[36])) else: #print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s, stealAttemptChance=%s, stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s, contBetChance=%s, contBetDone=%s, secondBarrelChance=%s, secondBarrelDone=%s, thirdBarrelChance=%s, thirdBarrelDone=%s - WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[1], row[2], row[3])) + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[1], row[2], row[3], row[36])) else: raise FpdbError("todo") #end def storeHudData From 701a824ac674086fd1e675254ada6ec9274f1da7 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 15 Aug 2008 04:28:51 +0100 Subject: [PATCH 038/262] git39 - started to reactivate tourney support - split tourneys table in design and recreate code, but still need to update the code --- docs/known-bugs-and-planned-features.txt | 1 - docs/tabledesign.html | 58 +++++++++++++++++------- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 14 ++++-- pyfpdb/fpdb_parse_logic.py | 12 ++--- pyfpdb/fpdb_save_to_db.py | 17 +++---- 6 files changed, 62 insertions(+), 42 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 9fde4b0a..1304efe1 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -12,7 +12,6 @@ split install instructions into OS-specific and OS-independent section. expand r expand instructions for profile file, again, the release-creator will cat it. delete old mailing list and create fpdb-announce finish updating filelist -return sng support update abbreviations.txt fix up bg colours in tv ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 30cd86cc..8d9a31a9 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -617,30 +617,15 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


-

siteId

+

tourneyGametypeId

smallint

-

References Sites.id

+

References TourneyGametypes.id

siteTourneyNo

bigint


- -

buyin

-

int

-

Buy-in in cents. Without rebuy/add-on

- - -

fee

-

int

-


- - -

knockout

-

int

-


-

entries

int

@@ -668,6 +653,45 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


+

Table TourneyGametypes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Field name

Type

Comment

id

int


siteId

smallint

References Sites.id

buyin

int

Buy-in in cents. Without rebuy/add-on

fee

int


knockout

int


rebuyOrAddon

boolean

Whether rebuys or add-ons are possible

+


Table TourneysPlayers

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index e8a6f822..b3fef587 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -363,7 +363,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git38") + self.window.set_title("Free Poker DB - version: alpha1+, git39") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 13e63568..f83c9dbb 100755 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=38: + if settings[0]!=39: print "outdated database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -119,6 +119,7 @@ class fpdb_db: self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") self.cursor.execute("DROP TABLE IF EXISTS Hands;") + self.cursor.execute("DROP TABLE IF EXISTS TourneysGametypes;") self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") self.cursor.execute("DROP TABLE IF EXISTS Players;") @@ -204,13 +205,18 @@ class fpdb_db: card5Value smallint, card5Suit char(1))""") - self.create_table("""Tourneys ( + self.create_table("""TourneysGametypes ( id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), - siteTourneyNo BIGINT, buyin INT, fee INT, knockout INT, + rebuyOrAddon BOOLEAN)""") + + self.create_table("""Tourneys ( + id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id), + siteTourneyNo BIGINT, entries INT, prizepool INT, startTime DATETIME, @@ -309,7 +315,7 @@ class fpdb_db: position CHAR(1))""") - self.cursor.execute("INSERT INTO Settings VALUES (38);") + self.cursor.execute("INSERT INTO Settings VALUES (39);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index c8efc2e2..d69eeae0 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -113,12 +113,10 @@ def mainParser(db, cursor, site, category, hand): entries=-1 prizepool=-1 if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, - knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, - siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, - startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, - actionTypes, actionAmounts, hudImportData) + result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, + siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData) elif (category=="razz" or category=="studhi" or category=="studhilo"): + raise fpdb_simple.FpdbError ("stud/razz are currently broken") result = fpdb_save_to_db.tourney_stud(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, @@ -126,9 +124,7 @@ def mainParser(db, cursor, site, category, hand): actionTypes, actionAmounts, hudImportData) else: if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, - handStartTime, names, playerIDs, startCashes, positions, cardValues, - cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData) + result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData) elif (category=="razz" or category=="studhi" or category=="studhilo"): raise fpdb_simple.FpdbError ("stud/razz are currently broken") result = fpdb_save_to_db.ring_stud(cursor, category, siteHandNo, gametypeID, diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index a8040cf3..a3c95733 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -37,10 +37,9 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, return site_hand_no #end def ring_stud -#stores a holdem/omaha hand into the database -def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, - names, player_ids, start_cashes, positions, card_values, card_suits, - board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData): +def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData): + """stores a holdem/omaha hand into the database""" + #fill up the two player card arrays if (category=="holdem"): fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) @@ -64,13 +63,9 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti return site_hand_no #end def ring_holdem_omaha -def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, - tourney_start, payin_amounts, ranks, #end of tourney specific params - site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, - start_cashes, positions, card_values, card_suits, - board_values, board_suits, winnings, rakes, - action_types, action_amounts, hudImportData): -#stores a tourney stud/razz hand into the database +def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, #end of tourney specific params + site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData): + """stores a tourney holdem/omaha hand into the database""" #fill up the two player card arrays if (category=="holdem"): fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) From 16be2b39f97af3ec014ac30ade9a861b94279e60 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sat, 16 Aug 2008 07:16:50 +0100 Subject: [PATCH 039/262] git40 - fixed little bug in table creation and added tourneyGametypeId to the cache to make tourney caching more sensible. --- docs/install-in-windows.txt | 2 +- docs/tabledesign.html | 9 +++++++-- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 11 ++++++----- pyfpdb/import_threaded.py | 0 pyfpdb/table_viewer.py | 2 +- 6 files changed, 16 insertions(+), 10 deletions(-) mode change 100755 => 100644 pyfpdb/fpdb_db.py mode change 100755 => 100644 pyfpdb/import_threaded.py mode change 100755 => 100644 pyfpdb/table_viewer.py diff --git a/docs/install-in-windows.txt b/docs/install-in-windows.txt index d9b27768..96a2524e 100644 --- a/docs/install-in-windows.txt +++ b/docs/install-in-windows.txt @@ -24,7 +24,7 @@ Once finished it shold confirm "service started successfully" 1b. MySQL database and user setup Now open a shell (aka command prompt aka DOS window): -Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open. +Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A window with a black background should open. Type (replacing yourPassword with the root password for MySQL you specified during installation): mysql --user=root --password=yourPassword diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 8d9a31a9..0f22247c 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -547,6 +547,11 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

+ + + + +

position

char(1)

Position for which this row applies. In this table this can be B(BB), S(SB), D(Dealer/Button), C(Cutoff), M(Middle - the 3 before cutoff) or E (Early - the 3 before Middle)

tourneysGametypeId

smallint

References TourneysGametypes.id

@@ -617,9 +622,9 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


-

tourneyGametypeId

+

tourneysGametypeId

smallint

-

References TourneyGametypes.id

+

References TourneysGametypes.id

siteTourneyNo

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index b3fef587..6946d2cc 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -363,7 +363,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git39") + self.window.set_title("Free Poker DB - version: alpha1+, git40") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py old mode 100755 new mode 100644 index f83c9dbb..688c6673 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=39: + if settings[0]!=40: print "outdated database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -119,11 +119,11 @@ class fpdb_db: self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") self.cursor.execute("DROP TABLE IF EXISTS Hands;") - self.cursor.execute("DROP TABLE IF EXISTS TourneysGametypes;") self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") self.cursor.execute("DROP TABLE IF EXISTS Players;") self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") + self.cursor.execute("DROP TABLE IF EXISTS TourneysGametypes;") self.cursor.execute("DROP TABLE IF EXISTS Sites;") self.db.commit() @@ -206,7 +206,7 @@ class fpdb_db: card5Suit char(1))""") self.create_table("""TourneysGametypes ( - id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), buyin INT, fee INT, @@ -313,9 +313,10 @@ class fpdb_db: thirdBarrelChance INT, thirdBarrelDone INT, - position CHAR(1))""") + position CHAR(1), + tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id))""") - self.cursor.execute("INSERT INTO Settings VALUES (39);") + self.cursor.execute("INSERT INTO Settings VALUES (40);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.db.commit() diff --git a/pyfpdb/import_threaded.py b/pyfpdb/import_threaded.py old mode 100755 new mode 100644 diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py old mode 100755 new mode 100644 index e4686a17..6afae5d9 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -59,7 +59,7 @@ class table_viewer (threading.Thread): arr=[] #first prepare the header row if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): - tmp=("Name", "Hands", "VPIP", "PFR", "PF3B4B", "ST") + tmp=("Name", "HDs", "VPIP", "PFR", "PF3B4B", "ST") if self.settings['tv-combinedStealFold']: tmp+=("FSB", ) From 0f032bcc5ee617d0e65d1ac0a52b55d34ba56a6a Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sat, 16 Aug 2008 22:03:27 +0100 Subject: [PATCH 040/262] git41 - added a bunch of new cache fields containing placeholder data. okay totalProfit should actually be filled correctly already :) --- docs/known-bugs-and-planned-features.txt | 15 ++-- docs/readme-dev.txt | 3 +- docs/tabledesign.html | 70 +++++++++++++++++++ pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 25 +++++-- pyfpdb/fpdb_simple.py | 89 ++++++++++++++++++++++-- pyfpdb/table_viewer.py | 5 ++ 7 files changed, 193 insertions(+), 16 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 1304efe1..af68054d 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,6 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -fold to CB/2B/3B -move version into seperate file for fpdb gui and db auto-import seperate and improve instructions for update verify link in release notes @@ -13,15 +11,16 @@ expand instructions for profile file, again, the release-creator will cat it. delete old mailing list and create fpdb-announce finish updating filelist update abbreviations.txt -fix up bg colours in tv ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config -verify positionalness of HudData -printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt alpha3 ====== -check-raise/call-raise on all streets +fix up bg colours in tv +fill the fold to CB/2B/3B cache fields +verify the totalProfit cache field +fill the check-/call-raise cache fields +move version into seperate file for fpdb gui and db anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no table with data for graphs for SD/F, W$wSF, W$@SD SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug @@ -29,8 +28,12 @@ show database version error in GUI and use fpdb_db class const for it, add it to split hud data generation into separate for loops and make it more efficient fix bug that sawFlop/Turn/River gets miscalculated if someone is allin - might as well add all-in recognition for this +verify positionalness of HudData +printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt + before beta =========== +change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands optionally make tv positional gentoo ebuild: USE postgresql skins diff --git a/docs/readme-dev.txt b/docs/readme-dev.txt index fe611a17..25e013bd 100644 --- a/docs/readme-dev.txt +++ b/docs/readme-dev.txt @@ -15,11 +15,12 @@ Ideally use git (see git-instructions.txt for some commands) and let me know whe Contact/Communication ===================== +If you start working on something please open a bug or feature request at sf to avoid someone else from doing the same Please see readme-overview Dependencies ============ -Since all real OSs have easy built in handling for dependencies feel free to add requirements on new libraries etc. Unfortunately due to the reality of the online poker market (namely the complete absence of clients for free/libre systems) it doesn't make sense to write this without supporting Windows so all dependencies must have a source-compatible Windows version. Please ensure to list any new deps in requirements.txt or let me know. +Please let me know before you add any new dependencies and ensure that they are source-compatible between *nix and Windows Code/File/Class Structure ========================= diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 0f22247c..33030de0 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -543,16 +543,86 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

int

Player used chance to make third barrel bet

+

position

char(1)

Position for which this row applies. In this table this can be B(BB), S(SB), D(Dealer/Button), C(Cutoff), M(Middle - the 3 before cutoff) or E (Early - the 3 before Middle)

+

tourneysGametypeId

smallint

References TourneysGametypes.id

+ + +

foldToContBetChance

+

int

+

Player had chance to fold to continuation bet

+ +

foldToContBetDone

+

int

+

Player used chance to fold to continuation bet

+ + +

foldToSecondBarrelChance

+

int

+

Player had chance to fold to second barrel bet

+ + +

foldToSecondBarrelDone

+

int

+

Player used chance to fold to second barrel bet

+ + +

foldToThirdBarrelChance

+

int

+

Player had chance to fold to third barrel bet

+ + +

foldToThirdBarrelDone

+

int

+

Player used chance to fold to third barrel bet

+ + + +

totalProfit

+

int

+

how much money in cents the player made

+ + + +

flopCheckCallRaiseChance

+

int

+

How often player had the chance to do a check-raise or a call-raise on the flop

+ + +

flopCheckCallRaiseDone

+

int

+

How often player used the chance to do a check-raise or a call-raise on the flop

+ + +

turnCheckCallRaiseChance

+

int

+

How often player had the chance to do a check-raise or a call-raise on the turn

+ + +

turnCheckCallRaiseDone

+

int

+

How often player used the chance to do a check-raise or a call-raise on the turn

+ + +

riverCheckCallRaiseChance

+

int

+

How often player had the chance to do a check-raise or a call-raise on the river

+ + +

riverCheckCallRaiseDone

+

int

+

How often player used the chance to do a check-raise or a call-raise on the river

+ +

Table HandsActions

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 6946d2cc..65dd7c64 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -363,7 +363,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git40") + self.window.set_title("Free Poker DB - version: alpha1+, git41") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 688c6673..43fa2702 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,8 +47,8 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=40: - print "outdated database version - please recreate tables" + if settings[0]!=41: + print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" #end def connect @@ -314,11 +314,28 @@ class fpdb_db: thirdBarrelDone INT, position CHAR(1), - tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id))""") + tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id), - self.cursor.execute("INSERT INTO Settings VALUES (40);") + foldToContBetChance INT, + foldToContBetDone INT, + foldToSecondBarrelChance INT, + foldToSecondBarrelDone INT, + foldToThirdBarrelChance INT, + foldToThirdBarrelDone INT, + + totalProfit INT, + + flopCheckCallRaiseChance INT, + flopCheckCallRaiseDone INT, + turnCheckCallRaiseChance INT, + turnCheckCallRaiseDone INT, + riverCheckCallRaiseChance INT, + riverCheckCallRaiseDone INT)""") + + self.cursor.execute("INSERT INTO Settings VALUES (41);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") + self.cursor.execute("INSERT INTO TourneysGametypes (id) VALUES (DEFAULT);") self.db.commit() print "finished recreating tables" #end def recreate_tables diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2d29a39d..329db3d2 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1581,6 +1581,71 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings result['thirdBarrelDone']=thirdBarrelDone result['position']=hudDataPositions + + + foldToContBetChance=[] + foldToContBetDone=[] + foldToSecondBarrelChance=[] + foldToSecondBarrelDone=[] + foldToThirdBarrelChance=[] + foldToThirdBarrelDone=[] + + totalProfit=[] + + flopCheckCallRaiseChance=[] + flopCheckCallRaiseDone=[] + turnCheckCallRaiseChance=[] + turnCheckCallRaiseDone=[] + riverCheckCallRaiseChance=[] + riverCheckCallRaiseDone=[] + for player in range (len(player_ids)): + myFoldToContBetChance=False + myFoldToContBetDone=False + myFoldToSecondBarrelChance=False + myFoldToSecondBarrelDone=False + myFoldToThirdBarrelChance=False + myFoldToThirdBarrelDone=False + + myTotalProfit=0 + + myFlopCheckCallRaiseChance=False + myFlopCheckCallRaiseDone=False + myTurnCheckCallRaiseChance=False + myTurnCheckCallRaiseDone=False + myRiverCheckCallRaiseChance=False + myRiverCheckCallRaiseDone=False + + foldToContBetChance.append(myFoldToContBetChance) + foldToContBetDone.append(myFoldToContBetDone) + foldToSecondBarrelChance.append(myFoldToSecondBarrelChance) + foldToSecondBarrelDone.append(myFoldToSecondBarrelDone) + foldToThirdBarrelChance.append(myFoldToThirdBarrelChance) + foldToThirdBarrelDone.append(myFoldToThirdBarrelDone) + + totalProfit.append(myTotalProfit) + + flopCheckCallRaiseChance.append(myFlopCheckCallRaiseChance) + flopCheckCallRaiseDone.append(myFlopCheckCallRaiseDone) + turnCheckCallRaiseChance.append(myTurnCheckCallRaiseChance) + turnCheckCallRaiseDone.append(myTurnCheckCallRaiseDone) + riverCheckCallRaiseChance.append(myRiverCheckCallRaiseChance) + riverCheckCallRaiseDone.append(myRiverCheckCallRaiseDone) + + result['foldToContBetChance']=foldToContBetChance + result['foldToContBetDone']=foldToContBetDone + result['foldToSecondBarrelChance']=foldToSecondBarrelChance + result['foldToSecondBarrelDone']=foldToSecondBarrelDone + result['foldToThirdBarrelChance']=foldToThirdBarrelChance + result['foldToThirdBarrelDone']=foldToThirdBarrelDone + + result['totalProfit']=totalProfit + + result['flopCheckCallRaiseChance']=flopCheckCallRaiseChance + result['flopCheckCallRaiseDone']=flopCheckCallRaiseDone + result['turnCheckCallRaiseChance']=turnCheckCallRaiseChance + result['turnCheckCallRaiseDone']=turnCheckCallRaiseDone + result['riverCheckCallRaiseChance']=riverCheckCallRaiseChance + result['riverCheckCallRaiseDone']=riverCheckCallRaiseDone return result #end def calculateHudImport @@ -1649,16 +1714,32 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): if hudImportData['thirdBarrelDone'][player]: row[35]+=1 row[36]=hudImportData['position'][player] + if hudImportData['foldToContBetChance'][player]: row[37]+=1 + if hudImportData['foldToContBetDone'][player]: row[38]+=1 + if hudImportData['foldToSecondBarrelChance'][player]: row[39]+=1 + if hudImportData['foldToSecondBarrelDone'][player]: row[40]+=1 + if hudImportData['foldToThirdBarrelChance'][player]: row[41]+=1 + if hudImportData['foldToThirdBarrelDone'][player]: row[42]+=1 + + row[43]+=hudImportData['totalProfit'][player] + + if hudImportData['flopCheckCallRaiseChance'][player]: row[44]+=1 + if hudImportData['flopCheckCallRaiseDone'][player]: row[45]+=1 + if hudImportData['turnCheckCallRaiseChance'][player]: row[46]+=1 + if hudImportData['turnCheckCallRaiseDone'][player]: row[47]+=1 + if hudImportData['riverCheckCallRaiseChance'][player]: row[48]+=1 + if hudImportData['riverCheckCallRaiseDone'][player]: row[49]+=1 + if doInsert: #print "playerid before insert:",row[2] cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, contBetChance, contBetDone, secondBarrelChance, secondBarrelDone, thirdBarrelChance, thirdBarrelDone, position) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[36])) + (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, contBetChance, contBetDone, secondBarrelChance, secondBarrelDone, thirdBarrelChance, thirdBarrelDone, position, tourneysGametypeId, foldToContBetChance, foldToContBetDone, foldToSecondBarrelChance, foldToSecondBarrelDone, foldToThirdBarrelChance, foldToThirdBarrelDone, totalProfit, flopCheckCallRaiseChance, flopCheckCallRaiseDone, turnCheckCallRaiseChance, turnCheckCallRaiseDone, riverCheckCallRaiseChance, riverCheckCallRaiseDone) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[36], 1, row[37], row[38], row[39], row[40], row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49])) else: #print "storing updated hud data line" cursor.execute("""UPDATE HudDataHoldemOmaha - SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s, stealAttemptChance=%s, stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s, contBetChance=%s, contBetDone=%s, secondBarrelChance=%s, secondBarrelDone=%s, thirdBarrelChance=%s, thirdBarrelDone=%s - WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[1], row[2], row[3], row[36])) + SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s, stealAttemptChance=%s, stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s, contBetChance=%s, contBetDone=%s, secondBarrelChance=%s, secondBarrelDone=%s, thirdBarrelChance=%s, thirdBarrelDone=%s, tourneysGametypeId=%s, foldToContBetChance=%s, foldToContBetDone=%s, foldToSecondBarrelChance=%s, foldToSecondBarrelDone=%s, foldToThirdBarrelChance=%s, foldToThirdBarrelDone=%s, totalProfit=%s, flopCheckCallRaiseChance=%s, flopCheckCallRaiseDone=%s, turnCheckCallRaiseChance=%s, turnCheckCallRaiseDone=%s, riverCheckCallRaiseChance=%s, riverCheckCallRaiseDone=%s + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], 1, row[37], row[38], row[39], row[40], row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[1], row[2], row[3], row[36])) else: raise FpdbError("todo") #end def storeHudData diff --git a/pyfpdb/table_viewer.py b/pyfpdb/table_viewer.py index 6afae5d9..164e47b6 100644 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/table_viewer.py @@ -117,6 +117,7 @@ class table_viewer (threading.Thread): if field_no<=3: pass else: + #print "in prep data, row_no:",row_no,"field_no:",field_no row[field_no]+=rows[row_no][field_no] tmp.append(str(row[4]))#Hands @@ -186,6 +187,10 @@ class table_viewer (threading.Thread): bg_col="lightgrey" if column==0 or (column>=5 and column<=10): bg_col="grey" + #style = eventBox.get_style() + #style.font.height=8 + #eventBox.set_style(style) + eventBox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bg_col)) eventBox.add(new_label) self.data_table.attach(child=eventBox, left_attach=column, right_attach=column+1, top_attach=row, bottom_attach=row+1) From e9d8b685ec0cc9dcfd57d355746f0e3d05e9e1b8 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 17 Aug 2008 01:48:03 +0100 Subject: [PATCH 041/262] p42 - started implementing autoimport. renamed some files to match the future more precise splitting of processing and frontend code changing from gitX to pX to match Gentoo portage's convention. changed table design on feedback from ray but not yet the actual code - more coming shortly --- docs/default.conf | 2 +- docs/known-bugs-and-planned-features.txt | 6 +- docs/tabledesign.html | 15 +++ pyfpdb/GuiAutoImport.py | 102 ++++++++++++++++++ .../{import_threaded.py => GuiBulkImport.py} | 2 +- pyfpdb/{table_viewer.py => GuiTableViewer.py} | 4 +- pyfpdb/fpdb.py | 31 +++--- 7 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 pyfpdb/GuiAutoImport.py rename pyfpdb/{import_threaded.py => GuiBulkImport.py} (99%) rename pyfpdb/{table_viewer.py => GuiTableViewer.py} (99%) diff --git a/docs/default.conf b/docs/default.conf index d2e095d8..531f30da 100644 --- a/docs/default.conf +++ b/docs/default.conf @@ -7,4 +7,4 @@ tv-combinedStealFold=True tv-combined2B3B=True tv-combinedPostflop=True bulkImport-defaultPath=default -tv-defaultPath=default +hud-defaultPath=default diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index af68054d..edf1469a 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,7 +3,8 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -auto-import +implement gametypes.base/hiLo + seperate and improve instructions for update verify link in release notes split install instructions into OS-specific and OS-independent section. expand release creator to concatenate. @@ -16,6 +17,7 @@ ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo. alpha3 ====== +export settings[hud-defaultInterval] to conf fix up bg colours in tv fill the fold to CB/2B/3B cache fields verify the totalProfit cache field @@ -33,7 +35,7 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring before beta =========== -change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands +?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? optionally make tv positional gentoo ebuild: USE postgresql skins diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 33030de0..46b905a7 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -136,6 +136,13 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

ring - ringgames aka cash games
tour - tournament incl SnG

+ +

base

+

char(4)

+

The underlying structure. valid entries:
+ hold - Holdem and Omaha
+ stud - Stud and Razz

+

category

varchar(9)

@@ -156,6 +163,14 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

cp=Cap Pot Limit
fl=Fixed Limit

+ +

hiLo

+

char(1)

+

Whether the game is hi, lo or both. Valid Entries:
+ h - High only
+ l - Low only
+ s - Hi/Lo Split

+

smallBlind

int

diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py new file mode 100644 index 00000000..e0bf4e98 --- /dev/null +++ b/pyfpdb/GuiAutoImport.py @@ -0,0 +1,102 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import threading +import pygtk +pygtk.require('2.0') +import gtk +import fpdb_import + +class GuiAutoImport (threading.Thread): + def browseClicked(self, widget, data): + """runs when user clicks browse on auto import tab""" + #print "start of GuiAutoImport.browseClicked" + current_path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) + + dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", + action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) + #dia_chooser.set_current_folder(pathname) + dia_chooser.set_filename(current_path) + #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import + + response = dia_chooser.run() + if response == gtk.RESPONSE_OK: + #print dia_chooser.get_filename(), 'selected' + self.pathTBuffer.set_text(dia_chooser.get_filename()) + elif response == gtk.RESPONSE_CANCEL: + print 'Closed, no files selected' + dia_chooser.destroy() + #end def GuiAutoImport.browseClicked + + def startClicked(self, widget, data): + """runs when user clicks start on auto import tab""" + print "implement GuiAutoImport.startClicked" + #end def GuiAutoImport.browseClicked + + def get_vbox(self): + """returns the vbox of this thread""" + return self.mainVBox + #end def get_vbox + + def __init__(self, settings, debug=True): + """Constructor for GuiAutoImport""" + self.settings=settings + + self.mainVBox=gtk.VBox(False,1) + self.mainVBox.show() + + self.settingsHBox = gtk.HBox(False, 0) + self.mainVBox.pack_start(self.settingsHBox, False, True, 0) + self.settingsHBox.show() + + self.intervalLabel = gtk.Label("Interval (ie. break) between imports in seconds:") + self.settingsHBox.pack_start(self.intervalLabel) + self.intervalLabel.show() + + self.intervalTBuffer=gtk.TextBuffer() + self.intervalTBuffer.set_text(str(self.settings['hud-defaultInterval'])) + self.intervalTView=gtk.TextView(self.intervalTBuffer) + self.settingsHBox.pack_start(self.intervalTView) + self.intervalTView.show() + + + self.pathHBox = gtk.HBox(False, 0) + self.mainVBox.pack_start(self.pathHBox, False, True, 0) + self.pathHBox.show() + + self.pathLabel = gtk.Label("Path to auto-import:") + self.pathHBox.pack_start(self.pathLabel, False, False, 0) + self.pathLabel.show() + + self.pathTBuffer=gtk.TextBuffer() + self.pathTBuffer.set_text(self.settings['hud-defaultPath']) + self.pathTView=gtk.TextView(self.pathTBuffer) + self.pathHBox.pack_start(self.pathTView, False, True, 0) + self.pathTView.show() + + self.browseButton=gtk.Button("Browse...") + self.browseButton.connect("clicked", self.browseClicked, "Browse clicked") + self.pathHBox.pack_end(self.browseButton, False, False, 0) + self.browseButton.show() + + + self.startButton=gtk.Button("Start Autoimport") + self.startButton.connect("clicked", self.startClicked, "start clicked") + self.mainVBox.add(self.startButton) + self.startButton.show() + #end of GuiAutoImport.__init__ diff --git a/pyfpdb/import_threaded.py b/pyfpdb/GuiBulkImport.py similarity index 99% rename from pyfpdb/import_threaded.py rename to pyfpdb/GuiBulkImport.py index 74bb5878..279337c8 100644 --- a/pyfpdb/import_threaded.py +++ b/pyfpdb/GuiBulkImport.py @@ -23,7 +23,7 @@ pygtk.require('2.0') import gtk import os #todo: remove this once import_dir is in fpdb_import -class import_threaded (threading.Thread): +class GuiBulkImport (threading.Thread): def import_dir(self): """imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it""" self.path=self.inputFile diff --git a/pyfpdb/table_viewer.py b/pyfpdb/GuiTableViewer.py similarity index 99% rename from pyfpdb/table_viewer.py rename to pyfpdb/GuiTableViewer.py index 164e47b6..345333a0 100644 --- a/pyfpdb/table_viewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -24,7 +24,7 @@ import MySQLdb import fpdb_import import fpdb_db -class table_viewer (threading.Thread): +class GuiTableViewer (threading.Thread): def hudDivide (self, a, b): if b==0: return "n/a" @@ -279,7 +279,7 @@ class table_viewer (threading.Thread): self.filename_label.show() self.filename_tbuffer=gtk.TextBuffer() - self.filename_tbuffer.set_text(self.settings['tv-defaultPath']) + self.filename_tbuffer.set_text(self.settings['hud-defaultPath']) self.filename_tview=gtk.TextView(self.filename_tbuffer) self.settings_hbox.pack_start(self.filename_tview, True, True, padding=5) self.filename_tview.show() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 65dd7c64..2bb9fa42 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -24,8 +24,9 @@ import gtk import fpdb_db import fpdb_simple -import import_threaded -import table_viewer +import GuiBulkImport +import GuiTableViewer +import GuiAutoImport class fpdb: def tab_clicked(self, widget, tab_name): @@ -250,10 +251,12 @@ class fpdb: if self.settings['os']=="windows": self.settings['bulkImport-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\filename.txt" - self.settings['tv-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\filename.txt" + self.settings['hud-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\" else: self.settings['bulkImport-defaultPath'] = os.path.expanduser("~") + "/.wine/drive_c/Program Files/PokerStars/HandHistory/filename.txt" - self.settings['tv-defaultPath'] = os.path.expanduser("~")+"/.wine/drive_c/Program Files/PokerStars/HandHistory/filename.txt" + self.settings['hud-defaultPath'] = os.path.expanduser("~")+"/.wine/drive_c/Program Files/PokerStars/HandHistory/" + + self.settings['hud-defaultInterval']=30 for i in range(len(lines)): if lines[i].startswith("db-backend="): @@ -284,9 +287,9 @@ class fpdb: elif lines[i].startswith("bulkImport-defaultPath="): if lines[i][23:-1]!="default": self.settings['bulkImport-defaultPath']=lines[i][23:-1] - elif lines[i].startswith("tv-defaultPath="): + elif lines[i].startswith("hud-defaultPath="): if lines[i][15:-1]!="default": - self.settings['tv-defaultPath']=lines[i][15:-1] + self.settings['hud-defaultPath']=lines[i][16:-1] elif lines[i].startswith("#"): pass #comment - dont parse else: @@ -324,16 +327,20 @@ class fpdb: #end def tab_abbreviations def tab_auto_import(self, widget, data): - print "todo: implement tab_auto_import" + """opens the auto import tab""" + new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings) + self.threads.append(new_aimp_thread) + aimp_tab=new_aimp_thread.get_vbox() + self.add_and_display_tab(aimp_tab, "Auto Import") #end def tab_auto_import def tab_bulk_import(self, widget, data): """opens a tab for bulk importing""" #print "start of tab_bulk_import" - new_import_thread=import_threaded.import_threaded(self.db, self.settings['bulkImport-defaultPath']) + new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings['bulkImport-defaultPath']) self.threads.append(new_import_thread) bulk_tab=new_import_thread.get_vbox() - self.add_and_display_tab(bulk_tab, "bulk import") + self.add_and_display_tab(bulk_tab, "Bulk Import") #end def tab_bulk_import def tab_main_help(self, widget, data): @@ -349,7 +356,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") def tab_table_viewer(self, widget, data): """opens a table viewer tab""" #print "start of tab_table_viewer" - new_tv_thread=table_viewer.table_viewer(self.db, self.settings) + new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings) self.threads.append(new_tv_thread) tv_tab=new_tv_thread.get_vbox() self.add_and_display_tab(tv_tab, "table viewer") @@ -363,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, git41") + self.window.set_title("Free Poker DB - version: alpha1+, p42") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) @@ -376,7 +383,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") ( "/Main/sep1", None, None, 0, "" ), ( "/Main/_Quit", "Q", self.quit, 0, None ), ( "/_Import", None, None, 0, "" ), - ( "/Import/_Import Files and Directories", "I", self.tab_bulk_import, 0, None ), + ( "/Import/_Bulk Import", "B", self.tab_bulk_import, 0, None ), ( "/Import/_Auto Import (todo)", "A", self.tab_auto_import, 0, None ), ( "/Import/Auto _Rating (todo)", "R", self.not_implemented, 0, None ), ( "/_Viewers", None, None, 0, "" ), From 9531c8d85b4db19d43741a525540a7eff8dfdd28 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 17 Aug 2008 02:46:23 +0100 Subject: [PATCH 042/262] p43 - implemented gametypes.base/.hiLo and more updates to table design --- docs/known-bugs-and-planned-features.txt | 7 +++++-- docs/tabledesign.html | 23 +++++++++++++++++++---- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 21 +++++++++++++-------- pyfpdb/fpdb_simple.py | 17 +++++++++++++---- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index edf1469a..df8a9fce 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,9 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -implement gametypes.base/hiLo +implement hands.tableName/importTime HandsPlayers.seatNo +change tabledesign VALIGN seperate and improve instructions for update verify link in release notes split install instructions into OS-specific and OS-independent section. expand release creator to concatenate. @@ -17,6 +18,7 @@ ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo. alpha3 ====== +store raw hand in db export settings[hud-defaultInterval] to conf fix up bg colours in tv fill the fold to CB/2B/3B cache fields @@ -35,8 +37,9 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring before beta =========== +maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? -optionally make tv positional +rakeback/frequent player points gentoo ebuild: USE postgresql skins optionally combine FB/FS and CB/2B/3B diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 46b905a7..cff04483 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -229,6 +229,11 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

bigint


+ +

tableName

+

varchar(20)

+

The site's name for the current table

+

siteHandNo

bigint

@@ -244,6 +249,11 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

datetime (in UTC)

start date&time of the hand

+ +

importTime

+

datetime (in UTC)

+

date&time of import of this hand

+

seats

smallint

@@ -322,8 +332,13 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

position

char(1)

-

BB=B, SB=S, Button=0, Cutoff=1, etc.

-

This is used in holdem/omaha only.

+

BB=B, SB=S, Button=0, Cutoff=1, etc.
+ This is used in holdem/omaha only.

+ + +

seatNo

+

smallint

+

The seat in which the person was sitting - necessary for HUD

ante

@@ -333,8 +348,8 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

cardXValue

smallint

-

2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x

-

see note above table

+

2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x
+ see note above table

cardXSuit

diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 2bb9fa42..ec3a4405 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p42") + self.window.set_title("Free Poker DB - version: alpha1+, p43") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 43fa2702..4be539e5 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=41: + if settings[0]!=43: print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -159,8 +159,10 @@ class fpdb_db: id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), type char(4), + base char(4), category varchar(9), limitType char(2), + hiLo char(1), smallBlind int, bigBlind int, smallBet int, @@ -184,11 +186,13 @@ class fpdb_db: self.create_table("""Hands ( id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - siteHandNo bigint, + tableName VARCHAR(20), + siteHandNo BIGINT, gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), handStart DATETIME, - seats smallint, - comment text, + importTime DATETIME, + seats SMALLINT, + comment TEXT, commentTs DATETIME)""") self.create_table("""BoardCards ( @@ -237,9 +241,10 @@ class fpdb_db: id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), handId BIGINT UNSIGNED, FOREIGN KEY (handId) REFERENCES Hands(id), playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), - startCash int, - position char(1), - ante int, + startCash INT, + position CHAR(1), + seatNo SMALLINT, + ante INT, card1Value smallint, card1Suit char(1), @@ -332,7 +337,7 @@ class fpdb_db: riverCheckCallRaiseChance INT, riverCheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (41);") + self.cursor.execute("INSERT INTO Settings VALUES (43);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneysGametypes (id) VALUES (DEFAULT);") diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 329db3d2..012d2b90 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -967,22 +967,31 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th try: len(result) except TypeError: - if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + if category=="holdem" or category=="omahahi" or category=="omahahilo": max_seats=10 + base="hold" else: max_seats=8 + base="stud" + + if category=="holdem" or category=="omahahi" or category=="studhi": + hiLo='h' + elif category=="razz": + hiLo='l' + else: + hiLo='s' if (limit_type=="fl"): big_blind=small_bet #todo: read this small_blind=big_blind/2 #todo: read this cursor.execute("""INSERT INTO Gametypes - (siteId, type, category, limitType, smallBlind, bigBlind, smallBet, bigBet) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, category, limit_type, small_blind, big_blind, small_bet, big_bet)) + (siteId, type, base, category, limitType, hiLo, smallBlind, bigBlind, smallBet, bigBet) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, base, category, limit_type, hiLo, small_blind, big_blind, small_bet, big_bet)) cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBet=%s AND bigBet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) else: cursor.execute("""INSERT INTO Gametypes (siteId, type, category, limitType, smallBlind, bigBlind, smallBet, bigBet) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, category, limit_type, small_bet, big_bet, 0, 0))#remember, for these bet means blind + VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, base, category, limit_type, hiLo, small_bet, big_bet, 0, 0))#remember, for these bet means blind cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBlind=%s AND bigBlind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) result=cursor.fetchone() From 18ff57027f1502166b4a3cd3a67470fb3e16963e Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 17 Aug 2008 03:18:42 +0100 Subject: [PATCH 043/262] p44 - implement hands.tableName/importTime --- docs/known-bugs-and-planned-features.txt | 2 +- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 4 ++-- pyfpdb/fpdb_parse_logic.py | 8 ++++++-- pyfpdb/fpdb_save_to_db.py | 4 ++-- pyfpdb/fpdb_simple.py | 25 +++++++++++++++++------- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index df8a9fce..76b74c78 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,7 +3,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -implement hands.tableName/importTime HandsPlayers.seatNo + HandsPlayers.seatNo change tabledesign VALIGN seperate and improve instructions for update diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index ec3a4405..9eb0eed8 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p43") + self.window.set_title("Free Poker DB - version: alpha1+, p44") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 4be539e5..bef32616 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=43: + if settings[0]!=44: print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -337,7 +337,7 @@ class fpdb_db: riverCheckCallRaiseChance INT, riverCheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (43);") + self.cursor.execute("INSERT INTO Settings VALUES (44);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneysGametypes (id) VALUES (DEFAULT);") diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index d69eeae0..ee98ac4a 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -83,6 +83,10 @@ def mainParser(db, cursor, site, category, hand): pass elif (lineTypes[i]=="ante"): fpdb_simple.parseAnteLine(hand[i], site, names, antes) + elif (lineTypes[i]=="table"): + result=fpdb_simple.parseTableLine(hand[i]) + maxSeats=result['maxSeats'] + tableName=result['tableName'] else: raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i]) @@ -114,7 +118,7 @@ def mainParser(db, cursor, site, category, hand): prizepool=-1 if (category=="holdem" or category=="omahahi" or category=="omahahilo"): result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, - siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData) + siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName) elif (category=="razz" or category=="studhi" or category=="studhilo"): raise fpdb_simple.FpdbError ("stud/razz are currently broken") result = fpdb_save_to_db.tourney_stud(cursor, category, siteTourneyNo, buyin, fee, @@ -124,7 +128,7 @@ def mainParser(db, cursor, site, category, hand): actionTypes, actionAmounts, hudImportData) else: if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData) + result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName) elif (category=="razz" or category=="studhi" or category=="studhilo"): raise fpdb_simple.FpdbError ("stud/razz are currently broken") result = fpdb_save_to_db.ring_stud(cursor, category, siteHandNo, gametypeID, diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index a3c95733..09ff1e66 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -37,7 +37,7 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, return site_hand_no #end def ring_stud -def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData): +def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName): """stores a holdem/omaha hand into the database""" #fill up the two player card arrays @@ -50,7 +50,7 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti fpdb_simple.fill_board_cards(board_values, board_suits) - hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName) hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 012d2b90..32592e2d 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -109,6 +109,8 @@ def classifyLines(hand, category, lineTypes, lineStreets): currentStreet=7 elif (hand[i].find(" shows [")!=-1): lineTypes.append("cards") + elif (hand[i].startswith("Table '")): + lineTypes.append("table") else: raise FpdbError("unrecognised linetype in:"+hand[i]) lineStreets.append(currentStreet) @@ -340,8 +342,8 @@ def filterCrap(site, hand): toRemove.append(hand[i]) elif (hand[i].find(" is low with [")!=-1): toRemove.append(hand[i]) - elif (hand[i].find("-max Seat #")!=-1 and hand[i].find(" is the button")!=-1): - toRemove.append(hand[i]) + #elif (hand[i].find("-max Seat #")!=-1 and hand[i].find(" is the button")!=-1): + # toRemove.append(hand[i]) elif (hand[i].endswith(" mucks")): toRemove.append(hand[i]) elif (hand[i].endswith(": mucks hand")): @@ -860,7 +862,18 @@ def parseSiteHandNo(topline): pos1=topline.find("#")+1 pos2=topline.find(":") return topline[pos1:pos2] -#end def parseHandSiteNo +#end def parseSiteHandNo + +def parseTableLine(line): + """returns a dictionary with maxSeats and tableName""" + pos1=line.find('\'')+1 + pos2=line.find('\'', pos1) + #print "table:",line[pos1:pos2] + pos3=pos2+2 + pos4=line.find("-max") + #print "seats:",line[pos3:pos4] + return {'maxSeats':int(line[pos3:pos4]), 'tableName':line[pos1:pos2]} +#end def parseTableLine #returns the hand no assigned by the poker site def parseTourneyNo(topline): @@ -968,10 +981,8 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th len(result) except TypeError: if category=="holdem" or category=="omahahi" or category=="omahahilo": - max_seats=10 base="hold" else: - max_seats=8 base="stud" if category=="holdem" or category=="omahahi" or category=="studhi": @@ -1112,9 +1123,9 @@ def store_board_cards(cursor, hands_id, board_values, board_suits): board_values[4], board_suits[4])) #end def store_board_cards -def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names): +def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName): #stores into table hands - cursor.execute ("INSERT INTO Hands (siteHandNo, gametypeId, handStart, seats) VALUES (%s, %s, %s, %s)", (site_hand_no, gametype_id, hand_start_time, len(names))) + cursor.execute ("INSERT INTO Hands (siteHandNo, gametypeId, handStart, seats, tableName, importTime) VALUES (%s, %s, %s, %s, %s, %s)", (site_hand_no, gametype_id, hand_start_time, len(names), tableName, datetime.datetime.today())) #todo: find a better way of doing this... cursor.execute("SELECT id FROM Hands WHERE siteHandNo=%s AND gametypeId=%s", (site_hand_no, gametype_id)) return cursor.fetchall()[0][0] From 579b44450ce770f2230d3daa13e3e8789916dc72 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 17 Aug 2008 04:44:53 +0100 Subject: [PATCH 044/262] p45 - implemented HandsPlayers.seatNo --- docs/known-bugs-and-planned-features.txt | 4 +-- pyfpdb/fpdb.py | 6 ++--- pyfpdb/fpdb_db.py | 4 +-- pyfpdb/fpdb_parse_logic.py | 8 +++--- pyfpdb/fpdb_save_to_db.py | 5 ++-- pyfpdb/fpdb_simple.py | 34 ++++++++++++------------ 6 files changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 76b74c78..d48a2a49 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,7 +3,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== - HandsPlayers.seatNo +implement HandsPlayers.seatNo change tabledesign VALIGN seperate and improve instructions for update @@ -14,7 +14,7 @@ delete old mailing list and create fpdb-announce finish updating filelist update abbreviations.txt ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config - +add minimal instructions for developing to git-instructions alpha3 ====== diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 9eb0eed8..77ea08a1 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -350,7 +350,7 @@ class fpdb: For howto information please see docs"""+os.sep+"""readme-user.txt The abbrevations in the table viewer are explained in docs"""+os.sep+"""abbrevations.txt This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") - self.add_and_display_tab(mh_tab, "main help") + self.add_and_display_tab(mh_tab, "Help") #end def tab_main_help def tab_table_viewer(self, widget, data): @@ -359,7 +359,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings) self.threads.append(new_tv_thread) tv_tab=new_tv_thread.get_vbox() - self.add_and_display_tab(tv_tab, "table viewer") + self.add_and_display_tab(tv_tab, "Table Viewer") #end def tab_table_viewer def __init__(self): @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p44") + self.window.set_title("Free Poker DB - version: alpha1+, p45") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index bef32616..3fb62f30 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=44: + if settings[0]!=45: print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -337,7 +337,7 @@ class fpdb_db: riverCheckCallRaiseChance INT, riverCheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (44);") + self.cursor.execute("INSERT INTO Settings VALUES (45);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneysGametypes (id) VALUES (DEFAULT);") diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index ee98ac4a..8912fc07 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -57,7 +57,9 @@ def mainParser(db, cursor, site, category, hand): seatLines.append(hand[i]) names=fpdb_simple.parseNames(seatLines) playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID) - startCashes=fpdb_simple.parseCashes(seatLines, site) + tmp=fpdb_simple.parseCashesAndSeatNos(seatLines, site) + startCashes=tmp['startCashes'] + seatNos=tmp['seatNos'] fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts, actionNos, actionTypeByNo) @@ -118,7 +120,7 @@ def mainParser(db, cursor, site, category, hand): prizepool=-1 if (category=="holdem" or category=="omahahi" or category=="omahahilo"): result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, - siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName) + siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) elif (category=="razz" or category=="studhi" or category=="studhilo"): raise fpdb_simple.FpdbError ("stud/razz are currently broken") result = fpdb_save_to_db.tourney_stud(cursor, category, siteTourneyNo, buyin, fee, @@ -128,7 +130,7 @@ def mainParser(db, cursor, site, category, hand): actionTypes, actionAmounts, hudImportData) else: if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName) + result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) elif (category=="razz" or category=="studhi" or category=="studhilo"): raise fpdb_simple.FpdbError ("stud/razz are currently broken") result = fpdb_save_to_db.ring_stud(cursor, category, siteHandNo, gametypeID, diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index 09ff1e66..1526049b 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -37,7 +37,7 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, return site_hand_no #end def ring_stud -def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName): +def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a holdem/omaha hand into the database""" #fill up the two player card arrays @@ -52,8 +52,7 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName) - hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, - start_cashes, positions, card_values, card_suits, winnings, rakes) + hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos) fpdb_simple.storeHudData(cursor, category, gametype_id, player_ids, hudImportData) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 32592e2d..89a90a7b 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -718,24 +718,24 @@ def parseCardLine(site, category, street, line, names, cardValues, cardSuits, bo raise FpdbError ("unrecognised line:"+line) #end def parseCardLine -#parses the start cash of each player out of the given lines and returns them as an array -def parseCashes(lines, site): - result = [] +def parseCashesAndSeatNos(lines, site): + """parses the startCashes and seatNos of each player out of the given lines and returns them as a dictionary of two arrays""" + cashes = [] + seatNos = [] for i in range (len(lines)): + pos2=lines[i].rfind(":") + seatNos.append(int(lines[i][5:pos2])) + pos1=lines[i].rfind("($")+2 if pos1==1: #for tourneys - it's 1 instead of -1 due to adding 2 above pos1=lines[i].rfind("(")+1 - #print "parseCashes, lines[i]:",lines[i] - #print "parseCashes, pos1:",pos1 if (site=="ftp"): pos2=lines[i].rfind(")") elif (site=="ps"): - #print "in parseCashes, line:", lines[i] pos2=lines[i].find(" in chips") - #print "in parseCashes, line:", lines[i], "pos1:",pos1,"pos2:",pos2 - result.append(float2int(lines[i][pos1:pos2])) - return result -#end def parseCashes + cashes.append(float2int(lines[i][pos1:pos2])) + return {'startCashes':cashes, 'seatNos':seatNos} +#end def parseCashesAndSeatNos #returns the buyin of a tourney in cents def parseFee(topline): @@ -1132,18 +1132,18 @@ def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableN #end def storeHands def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, - start_cashes, positions, card_values, card_suits, winnings, rakes): + start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos): result=[] if (category=="holdem"): for i in range (len(player_ids)): cursor.execute (""" INSERT INTO HandsPlayers (handId, playerId, startCash, position, - card1Value, card1Suit, card2Value, card2Suit, winnings, rake) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + card1Value, card1Suit, card2Value, card2Suit, winnings, rake, seatNo) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], - winnings[i], rakes[i])) + winnings[i], rakes[i], seatNos[i])) cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId=%s", (hands_id, player_ids[i])) result.append(cursor.fetchall()[0][0]) elif (category=="omahahi" or category=="omahahilo"): @@ -1151,12 +1151,12 @@ def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, cursor.execute ("""INSERT INTO HandsPlayers (handId, playerId, startCash, position, card1Value, card1Suit, card2Value, card2Suit, - card3Value, card3Suit, card4Value, card4Suit, winnings, rake) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + card3Value, card3Suit, card4Value, card4Suit, winnings, rake, seatNo) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], - winnings[i], rakes[i])) + winnings[i], rakes[i], seatNo[i])) cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) result.append(cursor.fetchall()[0][0]) else: From 5a462037c36854562c050e8767740a228720f9e9 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 17 Aug 2008 05:28:26 +0100 Subject: [PATCH 045/262] p46 - auto-importer kinda works - it does what its supposed to, but freezes the interface. you can however just start the interface a second time. anyone know how to fix this? --- docs/known-bugs-and-planned-features.txt | 4 ++-- pyfpdb/GuiAutoImport.py | 25 +++++++++++++++++++++++- pyfpdb/GuiBulkImport.py | 4 ++-- pyfpdb/fpdb.py | 2 +- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index d48a2a49..87daac0c 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,6 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -implement HandsPlayers.seatNo - change tabledesign VALIGN seperate and improve instructions for update verify link in release notes @@ -18,6 +16,7 @@ add minimal instructions for developing to git-instructions alpha3 ====== +(steffen) finish bringing back tourney store raw hand in db export settings[hud-defaultInterval] to conf fix up bg colours in tv @@ -37,6 +36,7 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring before beta =========== +stop auto-importer from freezing interface maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? rakeback/frequent player points diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index e0bf4e98..585e72ae 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -19,6 +19,8 @@ import threading import pygtk pygtk.require('2.0') import gtk +import os +import time import fpdb_import class GuiAutoImport (threading.Thread): @@ -45,7 +47,19 @@ class GuiAutoImport (threading.Thread): def startClicked(self, widget, data): """runs when user clicks start on auto import tab""" - print "implement GuiAutoImport.startClicked" + + self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) + for file in os.listdir(self.path): + if os.path.isdir(file): + print "AutoImport is not recursive - please select the final directory in which the history files are" + else: + self.inputFile=self.path+os.sep+file + fpdb_import.import_file_dict(self) + print "GuiBulkImport.import_dir done" + + interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) + time.sleep(interval) + self.startClicked(widget,data) #end def GuiAutoImport.browseClicked def get_vbox(self): @@ -57,6 +71,15 @@ class GuiAutoImport (threading.Thread): """Constructor for GuiAutoImport""" self.settings=settings + self.server=settings['db-host'] + self.user=settings['db-user'] + self.password=settings['db-password'] + self.database=settings['db-databaseName'] + self.quiet=False + self.failOnError=False + self.minPrint=30 + self.handCount=0 + self.mainVBox=gtk.VBox(False,1) self.mainVBox.show() diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 279337c8..1db64ba0 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -29,11 +29,11 @@ class GuiBulkImport (threading.Thread): self.path=self.inputFile for file in os.listdir(self.path): if os.path.isdir(file): - pass + print "BulkImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file fpdb_import.import_file_dict(self) - print "import_dir done" + print "GuiBulkImport.import_dir done" def load_clicked(self, widget, data=None): self.inputFile=self.chooser.get_filename() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 77ea08a1..26c985fc 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p45") + self.window.set_title("Free Poker DB - version: alpha1+, p46") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From ea5db15e10e14281dfcb31fdae5497dc994bd9eb Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 17 Aug 2008 07:02:01 +0100 Subject: [PATCH 046/262] p47 - added website courtesy of ectomorfo --- docs/known-bugs-and-planned-features.txt | 1 - website/config.php | 16 + website/contact.php | 29 + website/default.conf | 8 + website/docs-abreviations.php | 67 +++ website/docs-git-instructions.php | 51 ++ website/docs-install-gentoo.php | 87 +++ website/docs-install-windows.php | 115 ++++ website/docs-overview.php | 86 +++ website/docs-requirements.php | 162 ++++++ website/docs-usage.php | 66 +++ website/docs.php | 38 ++ website/features.php | 49 ++ website/footer.php | 7 + website/fpdb.png | Bin 0 -> 13014 bytes website/header.php | 19 + website/index.php | 24 + website/license.php | 689 +++++++++++++++++++++++ website/screenshots.php | 27 + website/sidebar.php | 11 + website/style.css | 99 ++++ 21 files changed, 1650 insertions(+), 1 deletion(-) create mode 100644 website/config.php create mode 100644 website/contact.php create mode 100644 website/default.conf create mode 100644 website/docs-abreviations.php create mode 100644 website/docs-git-instructions.php create mode 100644 website/docs-install-gentoo.php create mode 100644 website/docs-install-windows.php create mode 100644 website/docs-overview.php create mode 100644 website/docs-requirements.php create mode 100644 website/docs-usage.php create mode 100644 website/docs.php create mode 100644 website/features.php create mode 100644 website/footer.php create mode 100644 website/fpdb.png create mode 100644 website/header.php create mode 100644 website/index.php create mode 100644 website/license.php create mode 100644 website/screenshots.php create mode 100644 website/sidebar.php create mode 100644 website/style.css diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 87daac0c..cf42560c 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -36,7 +36,6 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring before beta =========== -stop auto-importer from freezing interface maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? rakeback/frequent player points diff --git a/website/config.php b/website/config.php new file mode 100644 index 00000000..403b9cf2 --- /dev/null +++ b/website/config.php @@ -0,0 +1,16 @@ + diff --git a/website/contact.php b/website/contact.php new file mode 100644 index 00000000..ce12d333 --- /dev/null +++ b/website/contact.php @@ -0,0 +1,29 @@ + + +
+ +

Contact

+ +

The best means of contact are the sourceforge page: Use the bug, feature request or patch functions or just post in the forum.

+ +

Alternatively feel free to contact me directly:

+ +

mail: steffen(at)sycamoretest.info
+jabber/xmpp/Google Talk: as above
+ICQ: 7806355
+MSN: steffenjf(at)gmx.de (don't email that)

+ +
+ + diff --git a/website/default.conf b/website/default.conf new file mode 100644 index 00000000..dadf6290 --- /dev/null +++ b/website/default.conf @@ -0,0 +1,8 @@ +db-backend=2 +db-host=localhost +db-databaseName=fpdb +db-user=fpdb +db-password=enterYourPwHere +tv-combinedPostflop=True +bulkImport-defaultPath=default +tv-defaultPath=default diff --git a/website/docs-abreviations.php b/website/docs-abreviations.php new file mode 100644 index 00000000..dfc67387 --- /dev/null +++ b/website/docs-abreviations.php @@ -0,0 +1,67 @@ + + +
+ +

Abreviations

+ +

HUD/table viewer
+================
+A3-7=3rd-7th street Complete/Raise percentage
+AF=Flop Bet/Raise percentage
+AT=River Bet/Raise percentage
+AR=Turn Bet/Raise percentage
+F3-7=3rd-7th street Fold percentage
+FF=Flop Fold percentage
+FR=River Fold percentage
+FT=Turn Fold percentage
+HD=Hands
+PF3B4B=Pre Flop 3Bet or 4Bet
+PFR=Pre Flop Raise
+Postf A=Postflop (ie. flop+turn+river) Aggression%
+Postf F=Postflop Fold %
+SD/F=Showdown/Flop=WtSD=How often player went to showdown when he saw the flop
+W$wsF=Won $ when he saw flop
+W$@SD=Won $ at showdown
+VPI3=Voluntary Put In on 3rd Street (ie. call+complete+raise)
+VPIP=Voluntary Put In Preflop (ie. call+raise)
+
+Other
+=====
+CLI=Command Line Interface (Shell, Terminal, "DOS-window")
+FTP=Full Tilt Poker
+GUI=Graphical User Interface (normal interface with buttons and menus)
+HUD=Heads-Up Display (shows stats directly in the poker software)
+PS=PokerStars
+MTT=Multi Table Tournament
+SnG=Sit and Go
+
+License
+=======
+Trademarks of third parties have been used under Fair Use or similar laws.
+
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License,
+Version 1.2 as published by the Free Software Foundation; with
+no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
+Texts. A copy of the license can be found in fdl-1.2.txt
+
+The program itself is licensed under AGPLv3, see agpl-3.0.txt
+

+ +
+ + diff --git a/website/docs-git-instructions.php b/website/docs-git-instructions.php new file mode 100644 index 00000000..3f4e3074 --- /dev/null +++ b/website/docs-git-instructions.php @@ -0,0 +1,51 @@ + + +
+ +

Git Instructions

+ +

Hi, welcome to my minimal git guide for fpdb devs!
+I'll expand this on request, if you have any questions just send me a mail at steffen(at)sycamoretest.info.

+ +How to make a local git commit
+==============================
+go to the root of your fpdb directory and type:
+git-add--interactive
+If you added any new files press a and Enter, then type the number of your new file and press Enter twice. If you made any changes to existing files press u and enter. If you want to commit all changes press * and Enter twice. Press q to leave git-add--interactive.
+Then create a file for your commit message (I call it since_last_commit.txt) but don't add this to the repository. In the first line of this file put a summary of your changes. If you wish to you can also add in a revision number. My tree (the "central" or "official" repository) uses the format gitX where X is a running number, e.g. git91 is followed by git92. Then give some details of your changes, try to mention anything non-trivial and definitely any user-visible bug fixes. If the table design has been changed that has to be mentioned in the first line.
+Then run this:
+git-commit -F since_last_commit.txt
+
+todo: how to pull/push changes to/from me
+todo: git-diff, git-rm, git-mv
+
+License
+=======
+Trademarks of third parties have been used under Fair Use or similar laws.
+
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License,
+Version 1.2 as published by the Free Software Foundation; with
+no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
+Texts. A copy of the license can be found in fdl-1.2.txt
+
+The program itself is licensed under AGPLv3, see agpl-3.0.txt

+ + +
+ + diff --git a/website/docs-install-gentoo.php b/website/docs-install-gentoo.php new file mode 100644 index 00000000..448630fb --- /dev/null +++ b/website/docs-install-gentoo.php @@ -0,0 +1,87 @@ + + +
+ +

Installing in Gentoo Linux

+ +

Last checked: 3 Aug 2008, git99

+ +These instructions are for Gentoo GNU/Linux, but if you adapt the steps installing and starting stuff it should work on any other OS as well.

+ +1. Install everything. Check if anything is already installed and if it is remove it from the command.

+ +For mysql:
+emerge mysql mysql-python pygtk -av
+/etc/init.d/mysql start
+rc-update add mysql default

+ +For postgresql:
+emerge postgresql pygresql pygtk
+/etc/init.d/postgresql start
+rc-update add postgresql default
+ +

+2. Manual configuration steps
+
+emerge --config mysql
+The --config step will ask you for the mysql root user - set this securely, we will create a seperate account for fpdb
+

+3. Create a mysql user and a database
+Now open a shell (aka command prompt aka DOS window):
+Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open.

+ +Type (replacing yourPassword with the root password for MySQL you specified during installation):
+mysql --user=root --password=yourPassword

+ +It should say something like this:
+Welcome to the MySQL monitor. Commands end with ; or \g.
+Your MySQL connection id is 4
+Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1
+
+Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+
+mysql>
+
+Now create the actual database. The default name is fpdb, I recommend you keep it. Type this:
+CREATE DATABASE fpdb;
+ +Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password):
+GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;

+ +Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edit it according to what you configured just now, in particular you will definitely have to put in the password you configured. I know this is insecure, will fix it before stable release.
+
+4. Guided installation steps
+Run the GUI as described in readme-user and click the menu database -> recreate tables
+
+That's it! Now see readme-user.txt for usage instructions.
+
+License
+=======
+Trademarks of third parties have been used under Fair Use or similar laws.
+
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License,
+Version 1.2 as published by the Free Software Foundation; with
+no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
+Texts. A copy of the license can be found in fdl-1.2.txt
+
+The program itself is licensed under AGPLv3, see agpl-3.0.txt

+ + +
+ + diff --git a/website/docs-install-windows.php b/website/docs-install-windows.php new file mode 100644 index 00000000..547a8aba --- /dev/null +++ b/website/docs-install-windows.php @@ -0,0 +1,115 @@ + + +
+ +

Installing in Windows

+ +

These instructions are for 32/64bit Windows NT/2k/XP/2k3/Vista/2k8. Well, in principle. I made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP. Also see the installation pages for other plataforms.
+
+The length of these instructions is due to MS refusal to provide any kind of package management.
+
+Here are direct download links from 10Aug2008:
+http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.67-win32.zip/from/pick#mirrors
+http://www.python.org/ftp/python/2.5.2/python-2.5.2.msi
+http://downloads.sourceforge.net/mysql-python/MySQL-python-1.2.2.win32-py2.5.exe?modtime=1173863337&big_mirror=0
+http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.12/gtk+-bundle-2.12.11.zip
+http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.4/pycairo-1.4.12-1.win32-py2.5.exe
+http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.14/pygobject-2.14.1-1.win32-py2.5.exe
+http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.12/pygtk-2.12.1-2.win32-py2.5.exe
+
+1a. Install MySQL and do its basic setup
+- Download Windows ZIP/Setup.exe
+- Unzip the archive, execute the setup file
+ At the end make sure you activate that you want to configure it now.
+ Use the advanced/detailed config. Leave everything as default unless stated below, or unless you have reason not to.
+ Make sure to DEACTIVATE TCP/IP networking, unless you want that and know how to secure it
+ Set a root password. Note that this is not the account/pw that fpdb will use.
+
+Once finished it shold confirm "service started successfully"
+
+1b. MySQL database and user setup
+Now open a shell (aka command prompt aka DOS window):
+Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open.
+
+Type (replacing yourPassword with the root password for MySQL you specified during installation):
+mysql --user=root --password=yourPassword
+
+It should say something like this:
+Welcome to the MySQL monitor. Commands end with ; or \g.
+Your MySQL connection id is 4
+Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1
+
+Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+
+mysql>
+
+Now create the actual database. The default name is fpdb, I recommend you keep it. Type this:
+CREATE DATABASE fpdb;
+
+Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password):
+GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;
+
+Exit mysql by pressing Ctrl+D
+
+2. Install python
+Get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts.
+
+3. Install the Python-DBAPI package for MySQL:
+Get the package and double click to install.
+
+4. In MySQL create a new database fpdb and a user by the same name. Set a password. I did this in webmin. Then set permissions for that user to: Select | Insert | Update | Delete | Create | Drop
+
+5. Time for GTK+ - here's the instructions from their bundle
+
+To use it, create some empty folder like c:\gtk . Using either
+Windows Explorer's built-in zip file management, or the command-line
+unzip.exe from
+ftp://tug.ctan.org/tex-archive/tools/zip/info-zip/WIN32/unz552xN.exe
+unzip this bundle.
+
+Then add the bin folder to your PATH. Make sure you have no other
+versions of GTK+ in PATH.
+To do that:
+Right click on "My Computer" ("Arbeitsplatz" in German Windows) on the Desktop or in (Windows) Explorer. Select Properties. Then click on the tab Advanced and then you should see Environment Variables. Simply append GTK's bin folder to the existing PATH (make sure to put a ; between the old PATH and GTK's folder to seperate the entries in this list).
+
+6. Install pycairo, pygobject and pygtk with double click.
+
+7. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf
+
+Now edit the file, in particular you will always have to type in the correct password (insecure, I know) and if you differ from the default setup you may need to change host, database or user.
+
+8. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to.
+When the program started open the menu Database and click "Create or Recreate Tables".
+
+That's it! Now you can use the bulk importer and the table viewer, more's coming. See readme-user.txt
+
+License
+=======
+Trademarks of third parties have been used under Fair Use or similar laws.
+
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License,
+Version 1.2 as published by the Free Software Foundation; with
+no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
+Texts. A copy of the license can be found in fdl-1.2.txt
+
+The program itself is licensed under AGPLv3, see agpl-3.0.txt

+ + +
+ + diff --git a/website/docs-overview.php b/website/docs-overview.php new file mode 100644 index 00000000..dedc957e --- /dev/null +++ b/website/docs-overview.php @@ -0,0 +1,86 @@ + + +
+ +

Overview

+

+Summary
+=======
+A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future. Some of this is not yet working though, please see status.txt and known-bugs-and-planned-features.txt
+
+But you could send all my hand histories to yourself!
+=====================================================
+At the end of the day this comes down to a question of trust, but unlike Windows and the poker client software you don't have to trust fpdb blindly. You can:
+- Verify the source code yourself.
+- Convince or pay someone to verify the source code for you.
+- Use a personal firewall to completely block fpdb from the Internet
+- (for the uber-paranoid) Get yourself the free virtualisation software VirtualBox, set up a VM (virtual machine) to run fpdb but run the poker software on your real PC. Then cut the VM off the Internet, fpdb doesn't need it. If you have a PC made in the last few years this should run fast enough as well. Note that most Windows licenses do NOT permit you to use two Windows installations at once, even if they are on the same PC.
+
+Requirements
+============
+Software requirements are listed in requirements.txt
+As for hardware, my main test machine is a Pentium 3-M 800 with 256 RAM and Gentoo GNU/Linux
+(running the poker client through what most people will call emulation). So this
+program will have to work on that. If you run an even more ancient machine and
+its too slow let me know and I'll see what I can do :)
+
+Why Free Software?
+==================
+This program is released under the terms of the free/libre software license AGPL3 as released by the FSF. The AGPL3 protects your rights and those of the wider community. As Richard Stallman, one of the founders of the free software movement, put it: "Free software is a matter of liberty, not price. To understand the concept, you should think of free as in free speech, not as in free beer." (well, it is both really, like the right to vote used to be free)
+
+For example, a "pirated" copy of proprietary software X is free of charge, but you don't actually have a legal right to use it, you don't have any possibility to fix its bugs and you certainly don't have any legal right to share it with your friends. You also won't be able to get support, often not even security fixes. Actually, even if you pay hundreds of pounds for your program they deny your right to fix their errors for them. Imagine buying a car where you're not permitted (under threat of jail) to replace broken parts..
+
+With free/libre software (also known as open source software, or short FOSS or FLOSS) on the other hand you get all these freedoms:
+(note: the legally binding terms are in the license text, this is merely an amateur summary so normal people don't have to read pages of legalese)
+
+Freedom 0: The freedom to use: To run the program, for any purpose. Free of Charge.
+Freedom 1: The freedom to study and help yourself. This freedom guarantees your right to study and learn from the source code of the program, and to fix it if it is broken. If you're not a programmer yourself the developers will generally be happy to fix it for you, often even for free. Failing that you can always pay someone from the money you saved on not having to pay for it.
+Freedom 2: The freedom to be a decent human being and help your neighbour: I don't threaten you with lawsuits or jail time if you share with your friends and neighbours, subject to the very modest restrictions of the AGPL3.
+Freedom 3: The freedom to improve the program and release your improvements to the public (or parts thereof) so that the whole community benefits. Note that you are PERMITTED, but not REQUIRED to distribute your changes. If you do distribute your changes you must do so under the terms of the AGPL3 however.
+
+Note that this is the license - I retain full copyright over my code, including the right to change the license for future versions. I do not intend to do this however. In any case, any version I released under AGPL3 remains available under that license forever, or more accurately until my copyright expires at which point it goes into the public domain.
+
+I reject the concept of software patents as a crime and under the European Patent Agreement software patents - even if you mislabel them as "computer-implemented inventions" or whatever - are explicitly prohibited.
+
+Can I get/use this under a different license?
+=============================================
+The short answer: Maybe.
+The long one: As detailed, I fully support what the FSF does and aims to achieve with the GPL. However, I realise that many free software developers don't object to closed source, some don't even object to closed source profiteering of their charity, and I don't think I have any right to go and tell them they're wrong.
+So if anyone wishes to use all or part of my code in another free software/open source project with an AGPL3-incompatible license such as BSD then let me know and we'll figure out a solution that makes everyone happy.
+If you wish to use all or part of this in closed source let me know how much if anything that is worth to you and I'm sure we'll be able to reach an agreement. Note that you are NOT permitted to just use fpdb code in closed source development whether in-house or by an independent software developer, you will NEED an additionally agreement with me to get fpdb under different licensing conditions.
+
+
+License of this Document
+========================
+The views expressed in this document are those of Steffen Jobbagy-Felso, other members of the fpdb team and external contributors may or may not agree.
+
+Trademarks of third parties have been used under Fair Use or similar laws.
+
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License,
+Version 1.2 as published by the Free Software Foundation; with
+no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
+Texts. A copy of the license can be found in fdl-1.2.txt
+
+The program itself is licensed under AGPLv3, see agpl-3.0.txt
+

+ + + +
+ + diff --git a/website/docs-requirements.php b/website/docs-requirements.php new file mode 100644 index 00000000..a8c20dd5 --- /dev/null +++ b/website/docs-requirements.php @@ -0,0 +1,162 @@ + + +
+ +

Requirements

+

+I recommend using a free/libre operating system, meaning a GNU/Linux distribution or a BSD variant (e.g. Gentoo GNU/Linux or OpenBSD) for ethical and practical reasons. Would you buy a car where you're prohibited from opening the bonnet under threat of jail? If the answer is no you should by the same logic not use closed source software for real money Poker :)
+
+Unfortunately you will always need one piece of unfree software: The poker client itself. Although not a direct dependency of fpdb you obviously will have a hard time putting this to productive use without running some poker client. As far as I know, only unfree clients are available. If you know better please let me know ASAP!
+
+If you can be bothered please do contact your poker site(s) and ask them to release free/libre clients, even if it is only for Windows. But lets be realistic, the chance of a positive answer is very low.
+
+Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least).
+
+If you use a package management system (e.g. if you have GNU/Linux or *BSD) just check that you have mysql, mysql-python and pygtk or postgresql, pygresql and pygtk. Your package manager will take care of the rest for you.
+
+Make new entries in this format:
+X. Program Name
+===============
+a. Optional?
+b. Required Version and Why
+c. Project Webpage
+d. License
+
+1. MySQL
+========
+a. Optional?
+ Choose MySQL or PostgreSQL
+b. Required Version and Why
+ At least 3.23 required due to mysql-python.
+ I use 5.0.54 and 5.0.60-r1 (GNU/Linux) and 5.0.51b (Windows).
+c. Project Webpage
+ http://www.mysql.com
+d. License
+ GPL2
+
+2. PostgreSQL
+=============
+a. Optional?
+ Choose MySQL or PostgreSQL
+b. Required Version and Why
+ I use 8.0.15 (GNU/Linux) and 8.3.3 (Windows) but I am not aware of any incompatibilities
+ with older or newer versions, pls report success/failure.
+c. Project Webpage
+ http://www.postgresql.org
+d. License
+ BSD License
+
+3. mysql-python
+===============
+a. Optional?
+ Required if you want to use MySQL backend
+b. Required Version and Why
+ I use 1.2.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
+c. Project Webpage
+ http://sourceforge.net/projects/mysql-python/
+d. License
+ SF lists GNU General Public License (GPL), Python License (CNRI Python License), Zope Public License.
+ Project states GPL without version in Pkg-info.
+
+4. pygresql
+===========
+a. Optional?
+ Required if you want to use PostgreSQL backend
+b. Required Version and Why
+ I use 3.6.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
+c. Project Webpage
+ http://www.pygresql.org/
+d. License
+ http://www.pygresql.org/readme.html#copyright-notice (BSD License?)
+ Summary: "Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose, without fee, and without a written agreement
+ is hereby granted[...]" plus Disclaimer.
+
+5. Python
+=========
+a. Optional?
+ Required.
+b. Required Version and Why
+ I use 2.4.4 and 2.5.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
+c. Project Webpage
+ http://www.python.org
+d. License
+ Python License
+
+6. GTK+ and dependencies
+=======
+a. Optional?
+ Required.
+b. Required Version and Why
+ I use 2.12.9 but it should run with 2.10 or higher. That is needed as I used MessageDialog updates
+c. Project Webpage
+ Main: http://www.gtk.org/
+ API spec: http://library.gnome.org/devel/gtk/2.12/
+ Windows DLs (get the bundle unless you know what you're doing): http://www.gtk.org/download-windows.html
+d. License
+ LGPL2
+
+7. PyCairo
+==========
+a. Optional?
+ Required.
+b. Required Version and Why
+ ?
+c. Project Webpage
+ main: http://www.pygtk.org
+d. License
+ LGPL2.1
+
+8. PyGObject
+============
+a. Optional?
+ Required.
+b. Required Version and Why
+ ?
+c. Project Webpage
+ main: http://www.pygtk.org
+d. License
+ LGPL2.1
+
+9. PyGTK
+========
+a. Optional?
+ Required.
+b. Required Version and Why
+ ?
+c. Project Webpage
+ main: http://www.pygtk.org
+d. License
+ LGPL2.1
+
+License (of this file)
+=======
+Trademarks of third parties have been used under Fair Use or similar laws.
+
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License,
+Version 1.2 as published by the Free Software Foundation; with
+no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
+Texts. A copy of the license can be found in fdl-1.2.txt
+
+The program itself is licensed under AGPLv3, see agpl-3.0.txt

+ + + +
+ + diff --git a/website/docs-usage.php b/website/docs-usage.php new file mode 100644 index 00000000..7d23da2c --- /dev/null +++ b/website/docs-usage.php @@ -0,0 +1,66 @@ + + +
+ +

Usage instructions

+ +

Before you do this make sure you setup the dependencies, the database, user, tables and config file.
+
+Running it
+==========
+If you have python setup properly you can execute it by double clicking pyfpdb/fpdb.py.
+
+Note however that all error messages are currently only printed if you call it from a shell. It'll be much easier to diagnose possible problems (which are likely in alpha stage) if you run it from a shell. In Windows XP it seems to automatically open a shell window with the fpdb window where you can see the command line output.
+
+In Linux/MacOS/*BSD, e.g. if its in /home/sycamore/fpdb/, do this:
+cd /home/sycamore/fpdb/pyfpdb
+python fpdb.py
+
+That will start the main GUI.
+
+Have a look at the menus, the stuff that is marked todo is not yet implemented.
+
+The main things are the bulk importer and the table viewer. To use the importer open it from the menu (import files and directories). You can set a few options at the bottom, then select a folder or single file in the main are and click Import. Please report any errors by one of the contacts listed in readme-overview.txt.
+Currently this will block the interface, but you can open another instance of this program e.g. if you wanna play whilst a big import is running.
+
+Please check the output at the shell for errors, if there are any please get in touch by one of the methods listed in readme-overview.txt
+
+Table Viewer (tv)
+=================v +To use the table viewer open it from the menu, select the hand history file of the table you're at, and click the Import&Read&Refresh button. The abbreviations there are explained in abbreviations.txt, but feel free to ask. Note that most poker software will only create the file once the first hand you payed to play is finished.
+In each column there is either just the number (hand count for current stake, range of seats and type of game) or a percentage and the number of hands that this percentage is based on. For example, in W$@SD (won $ at shodown) the number in brackets is how many showdowns that player has seen.
+
+Reimporting
+===========
+Currently on most updates a reimport of the whole database is required. To do this open fpdb, click the menu Database and select Create/Recreate tables. Then import all your history files again.
+
+License
+======= +Trademarks of third parties have been used under Fair Use or similar laws.
+
+Copyright 2008 Steffen Jobbagy-Felso
+Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License,
+Version 1.2 as published by the Free Software Foundation; with
+no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
+Texts. A copy of the license can be found in fdl-1.2.txt
+
+The program itself is licensed under AGPLv3, see agpl-3.0.txt

+ + +
+ + diff --git a/website/docs.php b/website/docs.php new file mode 100644 index 00000000..190bb779 --- /dev/null +++ b/website/docs.php @@ -0,0 +1,38 @@ + + +
+ +

Documentation

+ + + + + + + +
+ + diff --git a/website/features.php b/website/features.php new file mode 100644 index 00000000..fa10ead6 --- /dev/null +++ b/website/features.php @@ -0,0 +1,49 @@ + + +
+ +

Features

+ +

Backend, Distribution
+=====================
+- Choice of MySQL/InnoDB or PostgreSQL. (not tested on PostgreSQL)
+- It is possible to run the database on one PC, the importer on another, and then access the database with the table viewer or HUD from a third PC. (note: do NOT do this unencrypted over an untrusted network like your employer's LAN or the Internet!)
+
+Site/Game Support
+=================
+- Initially only full support for PS, FTP coming soon
+- Supports Holdem, Omaha Hi and Omaha Hi/Lo. Stud and Razz coming soon.
+ +- Supports No Limit, Pot Limit, Fixed Limit NL, Cap NL and Cap PL
+ Note that currently it does not display extra stats for NL/PL so usefulness is limited for these limit types. Suggestions welcome, I don't play these.
+- Supports ring/cash games, SnG/MTT coming soon
+
+Tableviewer (tv)
+===========
+Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are:
+ - VPIP, PFR and Preflop 3B/4B (3B/4B is not quite correct I think)
+ + - Raise and Fold % on flop, turn and river. Fold only counts hands when someone raised. This can be displayed per street or as one combined value each for aggression and folding.
+ - Number of hands this is based on.
+ - SD/F (aka WtSD, proportion of hands where player went to showdown after seeing flop)
+ - W$wSF (Won $ when seen Flop)
+ - W$@SD (Won $ at showdown)
+ For all stats it also displays how many hands this particular is based on

+ + +
+ + diff --git a/website/footer.php b/website/footer.php new file mode 100644 index 00000000..e0e74b50 --- /dev/null +++ b/website/footer.php @@ -0,0 +1,7 @@ + + + + + + + diff --git a/website/fpdb.png b/website/fpdb.png new file mode 100644 index 0000000000000000000000000000000000000000..5081614bd254db5c38d13421d99c8350349a8b5d GIT binary patch literal 13014 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 + + + + + + fpdb - <?php echo (empty($PAGE_TITLE) ? 'freepokerdb' : $PAGE_TITLE) ?> + + + + + + + +
+ + diff --git a/website/index.php b/website/index.php new file mode 100644 index 00000000..ce3108a2 --- /dev/null +++ b/website/index.php @@ -0,0 +1,24 @@ + + +
+ +

Welcome!

+ +

fpdb is a database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future. The software is currently in alpha status, which means some of the features are not working yet. As it's open source you're free to add any feature you like or modify the existing ones to fit your needs.

+ +

To see what fpdb can do, go to the features page. If you're ready to test it, take a look at the documentation.

+ +
+ + diff --git a/website/license.php b/website/license.php new file mode 100644 index 00000000..246d6f55 --- /dev/null +++ b/website/license.php @@ -0,0 +1,689 @@ + + +
+ +

License

+ +
+
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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, either version 3 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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
+
+
+
+ +
+ + diff --git a/website/screenshots.php b/website/screenshots.php new file mode 100644 index 00000000..7d666b24 --- /dev/null +++ b/website/screenshots.php @@ -0,0 +1,27 @@ + + +
+ +

Screenshots

+ +

Importing hands:

+
+

Table viewer:

+ + +
+ + diff --git a/website/sidebar.php b/website/sidebar.php new file mode 100644 index 00000000..76dc6f44 --- /dev/null +++ b/website/sidebar.php @@ -0,0 +1,11 @@ + diff --git a/website/style.css b/website/style.css new file mode 100644 index 00000000..65137a8c --- /dev/null +++ b/website/style.css @@ -0,0 +1,99 @@ +body { + margin: 0px; + padding: 0px; + background-color: #fff; + font-family: "Verdana", "Arial", sans-serif; + font-size: 13px; + text-align: center; + color: #333; +} + +a { + text-decoration: none; + color: #336984; +} + +a:hover { + color: #e00000; +} + +img { + border: 0px; +} + +h1 { + font-size: 23px; +} + +li { + padding: 3px; +} + + +/* ------------------------------------------------- */ +#wrapper { + text-align: left; + margin: auto; +} + + +/* ------------------------------------------------- */ +#header { + padding: 20px 20px 10px 20px; + background-color: #8BB9D1; + border-bottom: 10px solid #4690B5; + color: #fff; + font-size: 31px; +} + +#logo a { + font-size: 32px; + color: #5C5C49; +} + + +/* ------------------------------------------------- */ +#main { + margin-left: 210px; + margin-right: 15px; +} + + +/* ------------------------------------------------- */ +#sidebar { + background-color: #E8F1F6; + width: 180px; + padding: 10px; + float: left; +} + +#sidebar ul { + padding: 0px; + margin: 0px; + list-style-position: inside; +} + +#sidebar ul li { + border-bottom: 1px solid #D1E3EC; + padding: 3px; +} + +#sidebar ul li:hover { + background-color: #fff; +} + +#sidebar a { + text-decoration: none; +} + + +/* ------------------------------------------------- */ +#footer { + text-align: center; + margin-top: 20px; + background-color: #F6F6FD; + padding: 10px; + border-top: 3px solid #DADAF6; + font-size: 11px; + clear: both; +} From 5612ee663787999f913f79bf03c36f4f9c309769 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 17 Aug 2008 13:13:42 +0100 Subject: [PATCH 047/262] p48 - removed some redundant bracketed values from tv renamed cache fields and added more to be flexible for stud style games. note that tv is not updated yet added forums and bugtracker to website sidebar --- docs/known-bugs-and-planned-features.txt | 8 +- docs/requirements.txt | 139 ------ docs/tabledesign.html | 208 ++++---- pyfpdb/GuiTableViewer.py | 4 +- pyfpdb/fpdb.py | 4 +- pyfpdb/fpdb_db.py | 97 ++-- pyfpdb/fpdb_save_to_db.py | 2 +- pyfpdb/fpdb_simple.py | 576 +++++++++++++---------- website/sidebar.php | 2 + 9 files changed, 531 insertions(+), 509 deletions(-) delete mode 100644 docs/requirements.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index cf42560c..1e209316 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,19 +3,21 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== +finish Cache generalisation from line 1814 onwards +add sf.net logo to webpage change tabledesign VALIGN seperate and improve instructions for update -verify link in release notes split install instructions into OS-specific and OS-independent section. expand release creator to concatenate. expand instructions for profile file, again, the release-creator will cat it. -delete old mailing list and create fpdb-announce +add instructions for mailing list to contacts finish updating filelist -update abbreviations.txt ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config add minimal instructions for developing to git-instructions alpha3 ====== +make sure totalProfit shows actual profit rather than winnings. +update abbreviations.txt (steffen) finish bringing back tourney store raw hand in db export settings[hud-defaultInterval] to conf diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 8ba9053f..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,139 +0,0 @@ -I recommend using a free/libre operating system, meaning a GNU/Linux distribution or a BSD variant (e.g. Gentoo GNU/Linux or OpenBSD) for ethical and practical reasons. Would you buy a car where you're prohibited from opening the bonnet under threat of jail? If the answer is no you should by the same logic not use closed source software for real money Poker :) - -Unfortunately you will always need one piece of unfree software: The poker client itself. Although not a direct dependency of fpdb you obviously will have a hard time putting this to productive use without running some poker client. As far as I know, only unfree clients are available. If you know better please let me know ASAP! - -If you can be bothered please do contact your poker site(s) and ask them to release free/libre clients, even if it is only for Windows. But lets be realistic, the chance of a positive answer is very low. - -Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least). - -If you use a package management system (e.g. if you have GNU/Linux or *BSD) just check that you have mysql, mysql-python and pygtk or postgresql, pygresql and pygtk. Your package manager will take care of the rest for you. - -Make new entries in this format: -X. Program Name -=============== -a. Optional? -b. Required Version and Why -c. Project Webpage -d. License - -1. MySQL -======== -a. Optional? - Choose MySQL or PostgreSQL -b. Required Version and Why - At least 3.23 required due to mysql-python. - I use 5.0.54 and 5.0.60-r1 (GNU/Linux) and 5.0.51b (Windows). -c. Project Webpage - http://www.mysql.com -d. License - GPL2 - -2. PostgreSQL -============= -a. Optional? - Choose MySQL or PostgreSQL -b. Required Version and Why - I use 8.0.15 (GNU/Linux) and 8.3.3 (Windows) but I am not aware of any incompatibilities - with older or newer versions, pls report success/failure. -c. Project Webpage - http://www.postgresql.org -d. License - BSD License - -3. mysql-python -=============== -a. Optional? - Required if you want to use MySQL backend -b. Required Version and Why - I use 1.2.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure. -c. Project Webpage - http://sourceforge.net/projects/mysql-python/ -d. License - SF lists GNU General Public License (GPL), Python License (CNRI Python License), Zope Public License. - Project states GPL without version in Pkg-info. - -4. pygresql -=========== -a. Optional? - Required if you want to use PostgreSQL backend -b. Required Version and Why - I use 3.6.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure. -c. Project Webpage - http://www.pygresql.org/ -d. License - http://www.pygresql.org/readme.html#copyright-notice (BSD License?) - Summary: "Permission to use, copy, modify, and distribute this software and its - documentation for any purpose, without fee, and without a written agreement - is hereby granted[...]" plus Disclaimer. - -5. Python -========= -a. Optional? - Required. -b. Required Version and Why - I use 2.4.4 and 2.5.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure. -c. Project Webpage - http://www.python.org -d. License - Python License - -6. GTK+ and dependencies -======= -a. Optional? - Required. -b. Required Version and Why - I use 2.12.9 but it should run with 2.10 or higher. That is needed as I used MessageDialog updates -c. Project Webpage - Main: http://www.gtk.org/ - API spec: http://library.gnome.org/devel/gtk/2.12/ - Windows DLs (get the bundle unless you know what you're doing): http://www.gtk.org/download-windows.html -d. License - LGPL2 - -7. PyCairo -========== -a. Optional? - Required. -b. Required Version and Why - ? -c. Project Webpage - main: http://www.pygtk.org -d. License - LGPL2.1 - -8. PyGObject -============ -a. Optional? - Required. -b. Required Version and Why - ? -c. Project Webpage - main: http://www.pygtk.org -d. License - LGPL2.1 - -9. PyGTK -======== -a. Optional? - Required. -b. Required Version and Why - ? -c. Project Webpage - main: http://www.pygtk.org -d. License - LGPL2.1 - - - -License (of this file) -======= -Trademarks of third parties have been used under Fair Use or similar laws. - -Copyright 2008 Steffen Jobbagy-Felso -Permission is granted to copy, distribute and/or modify this -document under the terms of the GNU Free Documentation License, -Version 1.2 as published by the Free Software Foundation; with -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover -Texts. A copy of the license can be found in fdl-1.2.txt - -The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/tabledesign.html b/docs/tabledesign.html index cff04483..679c277d 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -383,7 +383,7 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


-

Table HudDataHoldemOmaha

+

Table HudCache

@@ -410,45 +410,61 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

+ + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + + + + + + @@ -456,60 +472,75 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + - - + @@ -544,76 +575,85 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

- + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + - - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + @@ -623,34 +663,44 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

- + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + +

Field Name

smallint

range 2-10

position

char(1)

Position for which this row applies. In this table this can be B(BB), S(SB), D(Dealer/Button), C(Cutoff), M(Middle - the 3 before cutoff) or E (Early - the 3 before Middle)

tourneysGametypeId

smallint

References TourneysGametypes.id

HDs

int

number of hands this player played in this gametype with this number of seats

VPIP

street0VPI

int

number of hands where player paid to see flop

PFR

street0Aggr

int

number of hands where player raised before flop

PF3B4BChance

street0_3B4BChance

int

number of hands where player had chance to 3B or 4B

PF3B4B

street0_3B4BDone

int

number of hands where player 3bet/4bet before flop

sawFlop

street1Seen

int

number of hands where player saw flop

number of hands where player saw flop/street4

sawTurn

street2Seen

int

number of hands where player saw turn

number of hands where player saw turn/street5

sawRiver

street3Seen

int

number of hands where player saw river

number of hands where player saw river/street6

street4Seen

int

number of hands where player saw street7

sawShowdown

number of hands where player saw showdown

raisedFlop

street1Aggr

int

number of hands where player raised flop

number of hands where player raised flop/street4

raisedTurn

street2Aggr

int

number of hands where player raised turn

number of hands where player raised turn/street5

raisedRiver

street3Aggr

int

number of hands where player raised river

number of hands where player raised river/street6

otherRaisedFlop

street4Aggr

int

number of hands where someone else raised flop

number of hands where player raised street7

otherRaisedFlopFold

otherRaisedStreet1

int

number of hands where someone else raised flop and the player folded

number of hands where someone else raised flop/street4

otherRaisedTurn

otherRaisedStreet2

int

number of hands where someone else raised Turn

number of hands where someone else raised turn/street5

otherRaisedTurnFold

otherRaisedStreet3

int

number of hands where someone else raised Turn and the player folded

number of hands where someone else raised river/street6

otherRaisedRiver

otherRaisedStreet4

int

number of hands where someone else raised River

number of hands where someone else raised street7

otherRaisedRiverFold

foldToOtherRaisedStreet1

int

number of hands where someone else raised River and the player folded

number of hands where someone else raised flop/street4 and the player folded

wonWhenSeenFlop

foldToOtherRaisedStreet2

int

number of hands where someone else raised Turn/street5 and the player folded

foldToOtherRaisedStreet3

int

number of hands where someone else raised River/street6 and the player folded

foldToOtherRaisedStreet4

int

number of hands where someone else raised street7 and the player folded

wonWhenSeenStreet1

float

How many hands the player won after seeing the flop - this can be a "partial win" if the pot is split.
+

How many hands the player won after seeing the flop/street4 - this can be a "partial win" if the pot is split.
To be completely clear, this stores a hand count, NOT a money amount.

wonAtSD

float

As wonWhenSeenFlop, but for showdown.

As wonWhenSeenStreet1, but for showdown.

contBetChance

street1CBChance

int

Player had chance to make continuation bet

Player had chance to make continuation bet on flop/street4

contBetDone

street1CBDone

int

Player used chance to make continuation bet

Player used chance to make continuation bet on flop/street4

secondBarrelChance

street2CBChance

int

Player had chance to make second barrel bet

Player had chance to make continuation bet on turn/street5

secondBarrelDone

street2CBDone

int

Player used chance to make second barrel bet

Player used chance to make continuation bet on turn/street5

thirdBarrelChance

street3CBChance

int

Player had chance to make third barrel bet

Player had chance to make continuation bet on river/street6

thirdBarrelDone

street3CBDone

int

Player used chance to make third barrel bet

Player used chance to make continuation bet on river/street6

street4CBChance

int

Player had chance to make continuation bet on street7

street4CBDone

int

Player used chance to make continuation bet on street7

position

char(1)

Position for which this row applies. In this table this can be B(BB), S(SB), D(Dealer/Button), C(Cutoff), M(Middle - the 3 before cutoff) or E (Early - the 3 before Middle)

tourneysGametypeId

smallint

References TourneysGametypes.id

foldToContBetChance

foldToStreet1CBChance

int

Player had chance to fold to continuation bet

Player had chance to fold to continuation bet on this street

foldToContBetDone

foldToStreet1CBDone

int

Player used chance to fold to continuation bet

Player used chance to fold to continuation bet on this street

foldToSecondBarrelChance

foldToStreet2CBChance

int

Player had chance to fold to second barrel bet

Player had chance to fold to continuation bet on this street

foldToSecondBarrelDone

foldToStreet2CBDone

int

Player used chance to fold to second barrel bet

Player used chance to fold to continuation bet on this street

foldToThirdBarrelChance

foldToStreet3CBChance

int

Player had chance to fold to third barrel bet

Player had chance to fold to continuation bet on this street

foldToThirdBarrelDone

foldToStreet3CBDone

int

Player used chance to fold to third barrel bet

Player used chance to fold to continuation bet on this street

foldToStreet4CBChance

int

Player had chance to fold to continuation bet on this street

foldToStreet4CBDone

int

Player used chance to fold to continuation bet on this street

flopCheckCallRaiseChance

street1CheckCallRaiseChance

int

How often player had the chance to do a check-raise or a call-raise on the flop

How often player had the chance to do a check-raise or a call-raise on this street

flopCheckCallRaiseDone

street1CheckCallRaiseDone

int

How often player used the chance to do a check-raise or a call-raise on the flop

How often player used the chance to do a check-raise or a call-raise on this street

turnCheckCallRaiseChance

street2CheckCallRaiseChance

int

How often player had the chance to do a check-raise or a call-raise on the turn

How often player had the chance to do a check-raise or a call-raise on this street

turnCheckCallRaiseDone

street2CheckCallRaiseDone

int

How often player used the chance to do a check-raise or a call-raise on the turn

How often player used the chance to do a check-raise or a call-raise on this street

riverCheckCallRaiseChance

street3CheckCallRaiseChance

int

How often player had the chance to do a check-raise or a call-raise on the river

How often player had the chance to do a check-raise or a call-raise on this street

riverCheckCallRaiseDone

street3CheckCallRaiseDone

int

How often player used the chance to do a check-raise or a call-raise on the river

How often player used the chance to do a check-raise or a call-raise on this street

street4CheckCallRaiseChance

int

How often player had the chance to do a check-raise or a call-raise on this street

street4CheckCallRaiseDone

int

How often player used the chance to do a check-raise or a call-raise on this street

diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 345333a0..75be8426 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -156,8 +156,8 @@ class GuiTableViewer (threading.Thread): tmp.append(self.hudDivide(row[15],row[11])+" ("+str(row[11])+")") #AR tmp.append(self.hudDivide(row[21],row[20])+" ("+str(row[20])+")") #FR - tmp.append(self.hudDivide(row[12],row[9])+" ("+str(row[9])+")") #SD/F - tmp.append(self.hudDivide(row[22],row[9])+" ("+str(row[9])+")") #W$wSF + tmp.append(self.hudDivide(row[12],row[9])) #WtSD + tmp.append(self.hudDivide(row[22],row[9])) #W$wSF tmp.append(self.hudDivide(row[23],row[12])+" ("+str(row[12])+")") #W$@SD arr.append(tmp) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 26c985fc..5e79fff8 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -293,7 +293,7 @@ class fpdb: elif lines[i].startswith("#"): pass #comment - dont parse else: - raise fpdb_simple.FpdbError("invalid line in profile file: "+lines[i]) + raise fpdb_simple.FpdbError("invalid line in profile file: "+lines[i]+" if you don't know what to do just remove it from "+filename) if self.db!=None: self.db.disconnect() @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p46") + self.window.set_title("Free Poker DB - version: alpha1+, p48") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 3fb62f30..b5b08b58 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=45: + if settings[0]!=48: print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -112,8 +112,11 @@ class fpdb_db: self.cursor.execute("DROP TABLE IF EXISTS gametypes;") self.cursor.execute("DROP TABLE IF EXISTS sites;") + if oldDbVersion>34 and oldDbVersion<=45: + self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + self.cursor.execute("DROP TABLE IF EXISTS Settings;") - self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") + self.cursor.execute("DROP TABLE IF EXISTS HudCache;") self.cursor.execute("DROP TABLE IF EXISTS Autorates;") self.cursor.execute("DROP TABLE IF EXISTS BoardCards;") self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") @@ -278,30 +281,37 @@ class fpdb_db: comment TEXT, commentTs DATETIME)""") - self.create_table("""HudDataHoldemOmaha ( + self.create_table("""HudCache ( id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), activeSeats SMALLINT, + position CHAR(1), + tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id), + HDs INT, - VPIP INT, - PFR INT, - PF3B4BChance INT, - PF3B4B INT, - sawFlop INT, - sawTurn INT, - sawRiver INT, + street0VPI INT, + street0Aggr INT, + street0_3B4BChance INT, + street0_3B4BDone INT, + street1Seen INT, + street2Seen INT, + street3Seen INT, + street4Seen INT, sawShowdown INT, - raisedFlop INT, - raisedTurn INT, - raisedRiver INT, - otherRaisedFlop INT, - otherRaisedFlopFold INT, - otherRaisedTurn INT, - otherRaisedTurnFold INT, - otherRaisedRiver INT, - otherRaisedRiverFold INT, - wonWhenSeenFlop FLOAT, + street1Aggr INT, + street2Aggr INT, + street3Aggr INT, + street4Aggr INT, + otherRaisedStreet1 INT, + otherRaisedStreet2 INT, + otherRaisedStreet3 INT, + otherRaisedStreet4 INT, + foldToOtherRaisedStreet1 INT, + foldToOtherRaisedStreet2 INT, + foldToOtherRaisedStreet3 INT, + foldToOtherRaisedStreet4 INT, + wonWhenSeenStreet1 FLOAT, wonAtSD FLOAT, stealAttemptChance INT, @@ -311,33 +321,36 @@ class fpdb_db: foldSbToStealChance INT, foldedSbToSteal INT, - contBetChance INT, - contBetDone INT, - secondBarrelChance INT, - secondBarrelDone INT, - thirdBarrelChance INT, - thirdBarrelDone INT, + street1CBChance INT, + street1CBDone INT, + street2CBChance INT, + street2CBDone INT, + street3CBChance INT, + street3CBDone INT, + street4CBChance INT, + street4CBDone INT, - position CHAR(1), - tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id), - - foldToContBetChance INT, - foldToContBetDone INT, - foldToSecondBarrelChance INT, - foldToSecondBarrelDone INT, - foldToThirdBarrelChance INT, - foldToThirdBarrelDone INT, + foldToStreet1CBChance INT, + foldToStreet1CBDone INT, + foldToStreet2CBChance INT, + foldToStreet2CBDone INT, + foldToStreet3CBChance INT, + foldToStreet3CBDone INT, + foldToStreet4CBChance INT, + foldToStreet4CBDone INT, totalProfit INT, - flopCheckCallRaiseChance INT, - flopCheckCallRaiseDone INT, - turnCheckCallRaiseChance INT, - turnCheckCallRaiseDone INT, - riverCheckCallRaiseChance INT, - riverCheckCallRaiseDone INT)""") + street1CheckCallRaiseChance INT, + street1CheckCallRaiseDone INT, + street2CheckCallRaiseChance INT, + street2CheckCallRaiseDone INT, + street3CheckCallRaiseChance INT, + street3CheckCallRaiseDone INT, + street4CheckCallRaiseChance INT, + street4CheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (45);") + self.cursor.execute("INSERT INTO Settings VALUES (48);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneysGametypes (id) VALUES (DEFAULT);") diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index 1526049b..65a77710 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -54,7 +54,7 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos) - fpdb_simple.storeHudData(cursor, category, gametype_id, player_ids, hudImportData) + fpdb_simple.storeHudCache(cursor, category, gametype_id, player_ids, hudImportData) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 89a90a7b..e7f2faec 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1246,24 +1246,29 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings, totalWinnings, positions): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" #setup subarrays of the result dictionary. - VPIP=[] - PFR=[] - PF3B4BChance=[] - PF3B4B=[] - sawFlop=[] - sawTurn=[] - sawRiver=[] + street0VPI=[] + street0Aggr=[] + street0_3B4BChance=[] + street0_3B4BDone=[] + street1Seen=[] + street2Seen=[] + street3Seen=[] + street4Seen=[] sawShowdown=[] - raisedFlop=[] - raisedTurn=[] - raisedRiver=[] - otherRaisedFlop=[] - otherRaisedFlopFold=[] - otherRaisedTurn=[] - otherRaisedTurnFold=[] - otherRaisedRiver=[] - otherRaisedRiverFold=[] - wonWhenSeenFlop=[] + street1Aggr=[] + street2Aggr=[] + street3Aggr=[] + street4Aggr=[] + otherRaisedStreet1=[] + otherRaisedStreet2=[] + otherRaisedStreet3=[] + otherRaisedStreet4=[] + foldToOtherRaisedStreet1=[] + foldToOtherRaisedStreet2=[] + foldToOtherRaisedStreet3=[] + foldToOtherRaisedStreet4=[] + wonWhenSeenStreet1=[] + wonAtSD=[] stealAttemptChance=[] stealAttempted=[] @@ -1308,24 +1313,28 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #run a loop for each player preparing the actual values that will be commited to SQL for player in range (len(player_ids)): #set default values - myVPIP=False - myPFR=False - myPF3B4BChance=False - myPF3B4B=False - mySawFlop=False - mySawTurn=False - mySawRiver=False + myStreet0VPI=False + myStreet0Aggr=False + myStreet0_3B4BChance=False + myStreet0_3B4BDone=False + myStreet1Seen=False + myStreet2Seen=False + myStreet3Seen=False + myStreet4Seen=False mySawShowdown=False - myRaisedFlop=False - myRaisedTurn=False - myRaisedRiver=False - myOtherRaisedFlop=False - myOtherRaisedFlopFold=False - myOtherRaisedTurn=False - myOtherRaisedTurnFold=False - myOtherRaisedRiver=False - myOtherRaisedRiverFold=False - myWonWhenSeenFlop=0.0 + myStreet1Aggr=False + myStreet2Aggr=False + myStreet3Aggr=False + myStreet4Aggr=False + myOtherRaisedStreet1=False + myOtherRaisedStreet2=False + myOtherRaisedStreet3=False + myOtherRaisedStreet4=False + myFoldToOtherRaisedStreet1=False + myFoldToOtherRaisedStreet2=False + myFoldToOtherRaisedStreet3=False + myFoldToOtherRaisedStreet4=False + myWonWhenSeenStreet1=0.0 myWonAtSD=0.0 myStealAttemptChance=False myStealAttempted=False @@ -1336,9 +1345,9 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings for count in range (len(action_types[street][player])):#finally individual actions currentAction=action_types[street][player][count] if currentAction=="bet": - myPFR=True + myStreet0Aggr=True if (currentAction=="bet" or currentAction=="call"): - myVPIP=True + myStreet0VPI=True #PF3B4BChance and PF3B4B pfFold=-1 @@ -1351,11 +1360,10 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings if actionTypeByNo[0][i][1]=="fold" and pfFold==-1: pfFold=i if pfFold==-1 or pfFold>firstPfRaiseByNo: - myPF3B4BChance=True + myStreet0_3B4BChance=True if pfRaise>firstPfRaiseByNo: - myPF3B4B=True + myStreet0_3B4BDone=True - #myStealAttemptChance myStealAttempted myFoldBbToStealChance myFoldedBbToSteal myFoldSbToStealChance myFoldedSbToSteal #steal calculations if len(player_ids)>=5: #no point otherwise if positions[player]==1: @@ -1384,11 +1392,11 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #calculate saw* values if (len(action_types[1][player])>0): - mySawFlop=True + myStreet1Seen=True if (len(action_types[2][player])>0): - mySawTurn=True + myStreet2Seen=True if (len(action_types[3][player])>0): - mySawRiver=True + myStreet3Seen=True mySawShowdown=True for count in range (len(action_types[3][player])): if action_types[3][player][count]=="fold": @@ -1396,84 +1404,87 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #flop stuff street=1 - if mySawFlop: + if myStreet1Seen: for count in range(len(action_types[street][player])): if action_types[street][player][count]=="bet": - myRaisedFlop=True + myStreet1Aggr=True for otherPlayer in range (len(player_ids)): - if player==otherPlayer or myOtherRaisedFlop: + if player==otherPlayer: pass else: for countOther in range (len(action_types[street][otherPlayer])): if action_types[street][otherPlayer][countOther]=="bet": - myOtherRaisedFlop=True + myOtherRaisedStreet1=True for countOtherFold in range (len(action_types[street][player])): if action_types[street][player][countOtherFold]=="fold": - myOtherRaisedFlopFold=True + myFoldToOtherRaisedStreet1=True #turn stuff - copy of flop with different vars street=2 - if mySawTurn: + if myStreet2Seen: for count in range(len(action_types[street][player])): if action_types[street][player][count]=="bet": - myRaisedTurn=True + myStreet2Aggr=True for otherPlayer in range (len(player_ids)): - if player==otherPlayer or myOtherRaisedTurn: + if player==otherPlayer: pass else: for countOther in range (len(action_types[street][otherPlayer])): if action_types[street][otherPlayer][countOther]=="bet": - myOtherRaisedTurn=True + myOtherRaisedStreet2=True for countOtherFold in range (len(action_types[street][player])): if action_types[street][player][countOtherFold]=="fold": - myOtherRaisedTurnFold=True + myFoldToOtherRaisedStreet2=True - #turn stuff - copy of flop with different vars + #river stuff - copy of flop with different vars street=3 - if mySawRiver: + if myStreet3Seen: for count in range(len(action_types[street][player])): if action_types[street][player][count]=="bet": - myRaisedRiver=True + myStreet3Aggr=True for otherPlayer in range (len(player_ids)): - if player==otherPlayer or myOtherRaisedRiver: + if player==otherPlayer: pass else: for countOther in range (len(action_types[street][otherPlayer])): if action_types[street][otherPlayer][countOther]=="bet": - myOtherRaisedRiver=True + myOtherRaisedStreet3=True for countOtherFold in range (len(action_types[street][player])): if action_types[street][player][countOtherFold]=="fold": - myOtherRaisedRiverFold=True + myFoldToOtherRaisedStreet3=True if winnings[player]!=0: - if mySawFlop: - myWonWhenSeenFlop=winnings[player]/float(totalWinnings) - #print "myWonWhenSeenFlop:",myWonWhenSeenFlop + if myStreet1Seen: + myWonWhenSeenStreet1=winnings[player]/float(totalWinnings) if mySawShowdown: - myWonAtSD=myWonWhenSeenFlop + myWonAtSD=myWonWhenSeenStreet1 #add each value to the appropriate array - VPIP.append(myVPIP) - PFR.append(myPFR) - PF3B4BChance.append(myPF3B4BChance) - PF3B4B.append(myPF3B4B) - sawFlop.append(mySawFlop) - sawTurn.append(mySawTurn) - sawRiver.append(mySawRiver) + street0VPI.append(myStreet0VPI) + street0Aggr.append(myStreet0Aggr) + street0_3B4BChance.append(myStreet0_3B4BChance) + street0_3B4BDone.append(myStreet0_3B4BDone) + street1Seen.append(myStreet1Seen) + street2Seen.append(myStreet2Seen) + street3Seen.append(myStreet3Seen) + street4Seen.append(myStreet4Seen) sawShowdown.append(mySawShowdown) - raisedFlop.append(myRaisedFlop) - raisedTurn.append(myRaisedTurn) - raisedRiver.append(myRaisedRiver) - otherRaisedFlop.append(myOtherRaisedFlop) - otherRaisedFlopFold.append(myOtherRaisedFlopFold) - otherRaisedTurn.append(myOtherRaisedTurn) - otherRaisedTurnFold.append(myOtherRaisedTurnFold) - otherRaisedRiver.append(myOtherRaisedRiver) - otherRaisedRiverFold.append(myOtherRaisedRiverFold) - wonWhenSeenFlop.append(myWonWhenSeenFlop) + street1Aggr.append(myStreet1Aggr) + street2Aggr.append(myStreet2Aggr) + street3Aggr.append(myStreet3Aggr) + street4Aggr.append(myStreet4Aggr) + otherRaisedStreet1.append(myOtherRaisedStreet1) + otherRaisedStreet2.append(myOtherRaisedStreet2) + otherRaisedStreet3.append(myOtherRaisedStreet3) + otherRaisedStreet4.append(myOtherRaisedStreet4) + foldToOtherRaisedStreet1.append(myFoldToOtherRaisedStreet1) + foldToOtherRaisedStreet2.append(myFoldToOtherRaisedStreet2) + foldToOtherRaisedStreet3.append(myFoldToOtherRaisedStreet3) + foldToOtherRaisedStreet4.append(myFoldToOtherRaisedStreet4) + wonWhenSeenStreet1.append(myWonWhenSeenStreet1) wonAtSD.append(myWonAtSD) stealAttemptChance.append(myStealAttemptChance) stealAttempted.append(myStealAttempted) @@ -1494,29 +1505,34 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings raise FpdbError("invalid position") #add each array to the to-be-returned dictionary - result={'VPIP':VPIP} - result['PFR']=PFR - result['PF3B4BChance']=PF3B4BChance - result['PF3B4B']=PF3B4B - result['sawFlop']=sawFlop - result['sawTurn']=sawTurn - result['sawRiver']=sawRiver + result={'street0VPI':street0VPI} + result['street0Aggr']=street0Aggr + result['street0_3B4BChance']=street0_3B4BChance + result['street0_3B4BDone']=street0_3B4BDone + result['street1Seen']=street1Seen + result['street2Seen']=street2Seen + result['street3Seen']=street3Seen + result['street4Seen']=street4Seen result['sawShowdown']=sawShowdown - result['raisedFlop']=raisedFlop - result['otherRaisedFlop']=otherRaisedFlop - result['otherRaisedFlopFold']=otherRaisedFlopFold - result['raisedTurn']=raisedTurn - result['otherRaisedTurn']=otherRaisedTurn - result['otherRaisedTurnFold']=otherRaisedTurnFold - result['raisedRiver']=raisedRiver - result['otherRaisedRiver']=otherRaisedRiver - result['otherRaisedRiverFold']=otherRaisedRiverFold - result['wonWhenSeenFlop']=wonWhenSeenFlop + + result['street1Aggr']=street1Aggr + result['otherRaisedStreet1']=otherRaisedStreet1 + result['foldToOtherRaisedStreet1']=foldToOtherRaisedStreet1 + result['street2Aggr']=street2Aggr + result['otherRaisedStreet2']=otherRaisedStreet2 + result['foldToOtherRaisedStreet2']=foldToOtherRaisedStreet2 + result['street3Aggr']=street3Aggr + result['otherRaisedStreet3']=otherRaisedStreet3 + result['foldToOtherRaisedStreet3']=foldToOtherRaisedStreet3 + result['street4Aggr']=street4Aggr + result['otherRaisedStreet4']=otherRaisedStreet4 + result['foldToOtherRaisedStreet4']=foldToOtherRaisedStreet4 + result['wonWhenSeenStreet1']=wonWhenSeenStreet1 result['wonAtSD']=wonAtSD result['stealAttemptChance']=stealAttemptChance result['stealAttempted']=stealAttempted - #after having calculated the above we now do second level calculations, so far just steal attempts. + #now the various steal values foldBbToStealChance=[] foldedBbToSteal=[] foldSbToStealChance=[] @@ -1549,130 +1565,158 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings result['foldSbToStealChance']=foldSbToStealChance result['foldedSbToSteal']=foldedSbToSteal - #now CB/2B/3B - contBetChance=[] - contBetDone=[] + #now CB + street1CBChance=[] + street1CBDone=[] for player in range (len(player_ids)): - myContBetChance=False - myContBetDone=False + myStreet1CBChance=False + myStreet1CBDone=False - if PFR[player]: - myContBetChance=True - if raisedFlop[player]: - myContBetDone=True + if street0VPI[player]: + myStreet1CBChance=True + if street1Aggr[player]: + myStreet1CBDone=True - contBetChance.append(myContBetChance) - contBetDone.append(myContBetDone) - result['contBetChance']=contBetChance - result['contBetDone']=contBetDone + street1CBChance.append(myStreet1CBChance) + street1CBDone.append(myStreet1CBDone) + result['street1CBChance']=street1CBChance + result['street1CBDone']=street1CBDone #now 2B - secondBarrelChance=[] - secondBarrelDone=[] + street2CBChance=[] + street2CBDone=[] for player in range (len(player_ids)): - mySecondBarrelChance=False - mySecondBarrelDone=False + myStreet2CBChance=False + myStreet2CBDone=False - if contBetDone[player]: - mySecondBarrelChance=True - if raisedTurn[player]: - mySecondBarrelDone=True + if street1CBDone[player]: + myStreet2CBChance=True + if street2Aggr[player]: + myStreet2CBDone=True - secondBarrelChance.append(mySecondBarrelChance) - secondBarrelDone.append(mySecondBarrelDone) - result['secondBarrelChance']=secondBarrelChance - result['secondBarrelDone']=secondBarrelDone + street2CBChance.append(myStreet2CBChance) + street2CBDone.append(myStreet2CBDone) + result['street2CBChance']=street2CBChance + result['street2CBDone']=street2CBDone #now 3B - thirdBarrelChance=[] - thirdBarrelDone=[] + street3CBChance=[] + street3CBDone=[] for player in range (len(player_ids)): - myThirdBarrelChance=False - myThirdBarrelDone=False + myStreet3CBChance=False + myStreet3CBDone=False - if secondBarrelDone[player]: - myThirdBarrelChance=True - if raisedRiver[player]: - myThirdBarrelDone=True + if street2CBDone[player]: + myStreet3CBChance=True + if street3Aggr[player]: + myStreet3CBDone=True - thirdBarrelChance.append(myThirdBarrelChance) - thirdBarrelDone.append(myThirdBarrelDone) - result['thirdBarrelChance']=thirdBarrelChance - result['thirdBarrelDone']=thirdBarrelDone + street3CBChance.append(myStreet3CBChance) + street3CBDone.append(myStreet3CBDone) + result['street3CBChance']=street3CBChance + result['street3CBDone']=street3CBDone + #4B - todo, implement for stud/razz + street4CBChance=[] + street4CBDone=[] + for player in range (len(player_ids)): + myStreet4CBChance=False + myStreet4CBDone=False + + street4CBChance.append(myStreet4CBChance) + street4CBDone.append(myStreet4CBDone) + result['street4CBChance']=street4CBChance + result['street4CBDone']=street4CBDone + + result['position']=hudDataPositions - - foldToContBetChance=[] - foldToContBetDone=[] - foldToSecondBarrelChance=[] - foldToSecondBarrelDone=[] - foldToThirdBarrelChance=[] - foldToThirdBarrelDone=[] + foldToStreet1CBChance=[] + foldToStreet1CBDone=[] + foldToStreet2CBChance=[] + foldToStreet2CBDone=[] + foldToStreet3CBChance=[] + foldToStreet3CBDone=[] + foldToStreet4CBChance=[] + foldToStreet4CBDone=[] totalProfit=[] - flopCheckCallRaiseChance=[] - flopCheckCallRaiseDone=[] - turnCheckCallRaiseChance=[] - turnCheckCallRaiseDone=[] - riverCheckCallRaiseChance=[] - riverCheckCallRaiseDone=[] + street1CheckCallRaiseChance=[] + street1CheckCallRaiseDone=[] + street2CheckCallRaiseChance=[] + street2CheckCallRaiseDone=[] + street3CheckCallRaiseChance=[] + street3CheckCallRaiseDone=[] + street4CheckCallRaiseChance=[] + street4CheckCallRaiseDone=[] for player in range (len(player_ids)): - myFoldToContBetChance=False - myFoldToContBetDone=False - myFoldToSecondBarrelChance=False - myFoldToSecondBarrelDone=False - myFoldToThirdBarrelChance=False - myFoldToThirdBarrelDone=False + myFoldToStreet1CBChance=False + myFoldToStreet1CBDone=False + myFoldToStreet2CBChance=False + myFoldToStreet2CBDone=False + myFoldToStreet3CBChance=False + myFoldToStreet3CBDone=False + myFoldToStreet4CBChance=False + myFoldToStreet4CBDone=False myTotalProfit=0 - myFlopCheckCallRaiseChance=False - myFlopCheckCallRaiseDone=False - myTurnCheckCallRaiseChance=False - myTurnCheckCallRaiseDone=False - myRiverCheckCallRaiseChance=False - myRiverCheckCallRaiseDone=False + myStreet1CheckCallRaiseChance=False + myStreet1CheckCallRaiseDone=False + myStreet2CheckCallRaiseChance=False + myStreet2CheckCallRaiseDone=False + myStreet3CheckCallRaiseChance=False + myStreet3CheckCallRaiseDone=False + myStreet4CheckCallRaiseChance=False + myStreet4CheckCallRaiseDone=False - foldToContBetChance.append(myFoldToContBetChance) - foldToContBetDone.append(myFoldToContBetDone) - foldToSecondBarrelChance.append(myFoldToSecondBarrelChance) - foldToSecondBarrelDone.append(myFoldToSecondBarrelDone) - foldToThirdBarrelChance.append(myFoldToThirdBarrelChance) - foldToThirdBarrelDone.append(myFoldToThirdBarrelDone) + foldToStreet1CBChance.append(myFoldToStreet1CBChance) + foldToStreet1CBDone.append(myFoldToStreet1CBDone) + foldToStreet2CBChance.append(myFoldToStreet2CBChance) + foldToStreet2CBDone.append(myFoldToStreet2CBDone) + foldToStreet3CBChance.append(myFoldToStreet3CBChance) + foldToStreet3CBDone.append(myFoldToStreet3CBDone) + foldToStreet4CBChance.append(myFoldToStreet4CBChance) + foldToStreet4CBDone.append(myFoldToStreet4CBDone) totalProfit.append(myTotalProfit) - flopCheckCallRaiseChance.append(myFlopCheckCallRaiseChance) - flopCheckCallRaiseDone.append(myFlopCheckCallRaiseDone) - turnCheckCallRaiseChance.append(myTurnCheckCallRaiseChance) - turnCheckCallRaiseDone.append(myTurnCheckCallRaiseDone) - riverCheckCallRaiseChance.append(myRiverCheckCallRaiseChance) - riverCheckCallRaiseDone.append(myRiverCheckCallRaiseDone) + street1CheckCallRaiseChance.append(myStreet1CheckCallRaiseChance) + street1CheckCallRaiseDone.append(myStreet1CheckCallRaiseDone) + street2CheckCallRaiseChance.append(myStreet2CheckCallRaiseChance) + street2CheckCallRaiseDone.append(myStreet2CheckCallRaiseDone) + street3CheckCallRaiseChance.append(myStreet3CheckCallRaiseChance) + street3CheckCallRaiseDone.append(myStreet3CheckCallRaiseDone) + street4CheckCallRaiseChance.append(myStreet4CheckCallRaiseChance) + street4CheckCallRaiseDone.append(myStreet4CheckCallRaiseDone) - result['foldToContBetChance']=foldToContBetChance - result['foldToContBetDone']=foldToContBetDone - result['foldToSecondBarrelChance']=foldToSecondBarrelChance - result['foldToSecondBarrelDone']=foldToSecondBarrelDone - result['foldToThirdBarrelChance']=foldToThirdBarrelChance - result['foldToThirdBarrelDone']=foldToThirdBarrelDone + result['foldToStreet1CBChance']=foldToStreet1CBChance + result['foldToStreet1CBDone']=foldToStreet1CBDone + result['foldToStreet2CBChance']=foldToStreet2CBChance + result['foldToStreet2CBDone']=foldToStreet2CBDone + result['foldToStreet3CBChance']=foldToStreet3CBChance + result['foldToStreet3CBDone']=foldToStreet3CBDone + result['foldToStreet4CBChance']=foldToStreet4CBChance + result['foldToStreet4CBDone']=foldToStreet4CBDone result['totalProfit']=totalProfit - result['flopCheckCallRaiseChance']=flopCheckCallRaiseChance - result['flopCheckCallRaiseDone']=flopCheckCallRaiseDone - result['turnCheckCallRaiseChance']=turnCheckCallRaiseChance - result['turnCheckCallRaiseDone']=turnCheckCallRaiseDone - result['riverCheckCallRaiseChance']=riverCheckCallRaiseChance - result['riverCheckCallRaiseDone']=riverCheckCallRaiseDone + result['street1CheckCallRaiseChance']=street1CheckCallRaiseChance + result['street1CheckCallRaiseDone']=street1CheckCallRaiseDone + result['street2CheckCallRaiseChance']=street2CheckCallRaiseChance + result['street2CheckCallRaiseDone']=street2CheckCallRaiseDone + result['street3CheckCallRaiseChance']=street3CheckCallRaiseChance + result['street3CheckCallRaiseDone']=street3CheckCallRaiseDone + result['street4CheckCallRaiseChance']=street4CheckCallRaiseChance + result['street4CheckCallRaiseDone']=street4CheckCallRaiseDone return result #end def calculateHudImport -def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): +def storeHudCache(cursor, category, gametypeId, playerIds, hudImportData): if (category=="holdem" or category=="omahahi" or category=="omahahilo"): for player in range (len(playerIds)): - cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s", (gametypeId, playerIds[player], len(playerIds), hudImportData['position'][player])) + cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s", (gametypeId, playerIds[player], len(playerIds), hudImportData['position'][player])) row=cursor.fetchone() #print "gametypeId:", gametypeId, "playerIds[player]",playerIds[player], "len(playerIds):",len(playerIds), "row:",row @@ -1688,8 +1732,7 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): row.append(gametypeId) row.append(playerIds[player]) row.append(len(playerIds))#seats - row.append(0)#HDs - for i in range(len(hudImportData)): + for i in range(len(hudImportData)+2): row.append(0) else: @@ -1698,71 +1741,122 @@ def storeHudData(cursor, category, gametypeId, playerIds, hudImportData): for i in range(len(row)): newrow.append(row[i]) row=newrow - - row[4]+=1 #HDs - if hudImportData['VPIP'][player]: row[5]+=1 - if hudImportData['PFR'][player]: row[6]+=1 - if hudImportData['PF3B4BChance'][player]: row[7]+=1 - if hudImportData['PF3B4B'][player]: row[8]+=1 - if hudImportData['sawFlop'][player]: row[9]+=1 - if hudImportData['sawTurn'][player]: row[10]+=1 - if hudImportData['sawRiver'][player]: row[11]+=1 - if hudImportData['sawShowdown'][player]: row[12]+=1 - if hudImportData['raisedFlop'][player]: row[13]+=1 - if hudImportData['raisedTurn'][player]: row[14]+=1 - if hudImportData['raisedRiver'][player]: row[15]+=1 - if hudImportData['otherRaisedFlop'][player]: row[16]+=1 - if hudImportData['otherRaisedFlopFold'][player]: row[17]+=1 - if hudImportData['otherRaisedTurn'][player]: row[18]+=1 - if hudImportData['otherRaisedTurnFold'][player]: row[19]+=1 - if hudImportData['otherRaisedRiver'][player]: row[20]+=1 - if hudImportData['otherRaisedRiverFold'][player]: row[21]+=1 - if hudImportData['wonWhenSeenFlop'][player]!=0.0: row[22]+=hudImportData['wonWhenSeenFlop'][player] - if hudImportData['wonAtSD'][player]!=0.0: row[23]+=hudImportData['wonAtSD'][player] - if hudImportData['stealAttemptChance'][player]: row[24]+=1 - if hudImportData['stealAttempted'][player]: row[25]+=1 - if hudImportData['foldBbToStealChance'][player]: row[26]+=1 - if hudImportData['foldedBbToSteal'][player]: row[27]+=1 - if hudImportData['foldSbToStealChance'][player]: row[28]+=1 - if hudImportData['foldedSbToSteal'][player]: row[29]+=1 - if hudImportData['contBetChance'][player]: row[30]+=1 - if hudImportData['contBetDone'][player]: row[31]+=1 - if hudImportData['secondBarrelChance'][player]: row[32]+=1 - if hudImportData['secondBarrelDone'][player]: row[33]+=1 - if hudImportData['thirdBarrelChance'][player]: row[34]+=1 - if hudImportData['thirdBarrelDone'][player]: row[35]+=1 - row[36]=hudImportData['position'][player] + row[4]=hudImportData['position'][player] + row[5]=1 #tourneysGametypeId + row[6]+=1 #HDs + if hudImportData['street0VPI'][player]: row[7]+=1 + if hudImportData['street0Aggr'][player]: row[8]+=1 + if hudImportData['street0_3B4BChance'][player]: row[9]+=1 + if hudImportData['street0_3B4BDone'][player]: row[10]+=1 + if hudImportData['street1Seen'][player]: row[11]+=1 + if hudImportData['street2Seen'][player]: row[12]+=1 + if hudImportData['street3Seen'][player]: row[13]+=1 + if hudImportData['street4Seen'][player]: row[14]+=1 + if hudImportData['sawShowdown'][player]: row[15]+=1 + if hudImportData['street1Aggr'][player]: row[16]+=1 + if hudImportData['street2Aggr'][player]: row[17]+=1 + if hudImportData['street3Aggr'][player]: row[18]+=1 + if hudImportData['street4Aggr'][player]: row[19]+=1 + if hudImportData['otherRaisedStreet1'][player]: row[20]+=1 + if hudImportData['otherRaisedStreet2'][player]: row[21]+=1 + if hudImportData['otherRaisedStreet3'][player]: row[22]+=1 + if hudImportData['otherRaisedStreet4'][player]: row[23]+=1 + if hudImportData['foldToOtherRaisedStreet1'][player]: row[24]+=1 + if hudImportData['foldToOtherRaisedStreet2'][player]: row[25]+=1 + if hudImportData['foldToOtherRaisedStreet3'][player]: row[26]+=1 + if hudImportData['foldToOtherRaisedStreet4'][player]: row[27]+=1 + if hudImportData['wonWhenSeenStreet1'][player]!=0.0: row[28]+=hudImportData['wonWhenSeenStreet1'][player] + if hudImportData['wonAtSD'][player]!=0.0: row[29]+=hudImportData['wonAtSD'][player] + if hudImportData['stealAttemptChance'][player]: row[30]+=1 + if hudImportData['stealAttempted'][player]: row[31]+=1 + if hudImportData['foldBbToStealChance'][player]: row[32]+=1 + if hudImportData['foldedBbToSteal'][player]: row[33]+=1 + if hudImportData['foldSbToStealChance'][player]: row[34]+=1 + if hudImportData['foldedSbToSteal'][player]: row[35]+=1 - if hudImportData['foldToContBetChance'][player]: row[37]+=1 - if hudImportData['foldToContBetDone'][player]: row[38]+=1 - if hudImportData['foldToSecondBarrelChance'][player]: row[39]+=1 - if hudImportData['foldToSecondBarrelDone'][player]: row[40]+=1 - if hudImportData['foldToThirdBarrelChance'][player]: row[41]+=1 - if hudImportData['foldToThirdBarrelDone'][player]: row[42]+=1 + if hudImportData['street1CBChance'][player]: row[36]+=1 + if hudImportData['street1CBDone'][player]: row[37]+=1 + if hudImportData['street2CBChance'][player]: row[38]+=1 + if hudImportData['street2CBDone'][player]: row[39]+=1 + if hudImportData['street3CBChance'][player]: row[40]+=1 + if hudImportData['street3CBDone'][player]: row[41]+=1 + if hudImportData['street4CBChance'][player]: row[42]+=1 + if hudImportData['street4CBDone'][player]: row[43]+=1 + + if hudImportData['foldToStreet1CBChance'][player]: row[44]+=1 + if hudImportData['foldToStreet1CBDone'][player]: row[45]+=1 + if hudImportData['foldToStreet2CBChance'][player]: row[46]+=1 + if hudImportData['foldToStreet2CBDone'][player]: row[47]+=1 + if hudImportData['foldToStreet3CBChance'][player]: row[48]+=1 + if hudImportData['foldToStreet3CBDone'][player]: row[49]+=1 + if hudImportData['foldToStreet4CBChance'][player]: row[50]+=1 + if hudImportData['foldToStreet4CBDone'][player]: row[51]+=1 - row[43]+=hudImportData['totalProfit'][player] + row[52]+=hudImportData['totalProfit'][player] - if hudImportData['flopCheckCallRaiseChance'][player]: row[44]+=1 - if hudImportData['flopCheckCallRaiseDone'][player]: row[45]+=1 - if hudImportData['turnCheckCallRaiseChance'][player]: row[46]+=1 - if hudImportData['turnCheckCallRaiseDone'][player]: row[47]+=1 - if hudImportData['riverCheckCallRaiseChance'][player]: row[48]+=1 - if hudImportData['riverCheckCallRaiseDone'][player]: row[49]+=1 + if hudImportData['street1CheckCallRaiseChance'][player]: row[53]+=1 + if hudImportData['street1CheckCallRaiseDone'][player]: row[54]+=1 + if hudImportData['street2CheckCallRaiseChance'][player]: row[55]+=1 + if hudImportData['street2CheckCallRaiseDone'][player]: row[56]+=1 + if hudImportData['street3CheckCallRaiseChance'][player]: row[57]+=1 + if hudImportData['street3CheckCallRaiseDone'][player]: row[58]+=1 + if hudImportData['street4CheckCallRaiseChance'][player]: row[59]+=1 + if hudImportData['street4CheckCallRaiseDone'][player]: row[60]+=1 if doInsert: #print "playerid before insert:",row[2] - cursor.execute("""INSERT INTO HudDataHoldemOmaha - (gametypeId, playerId, activeSeats, HDs, VPIP, PFR, PF3B4BChance, PF3B4B, sawFlop, sawTurn, sawRiver, sawShowdown, raisedFlop, raisedTurn, raisedRiver, otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold, wonWhenSeenFlop, wonAtSD, stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, contBetChance, contBetDone, secondBarrelChance, secondBarrelDone, thirdBarrelChance, thirdBarrelDone, position, tourneysGametypeId, foldToContBetChance, foldToContBetDone, foldToSecondBarrelChance, foldToSecondBarrelDone, foldToThirdBarrelChance, foldToThirdBarrelDone, totalProfit, flopCheckCallRaiseChance, flopCheckCallRaiseDone, turnCheckCallRaiseChance, turnCheckCallRaiseDone, riverCheckCallRaiseChance, riverCheckCallRaiseDone) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[36], 1, row[37], row[38], row[39], row[40], row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49])) + cursor.execute("""INSERT INTO HudCache + (gametypeId, playerId, activeSeats, position, tourneysGametypeId, + HDs, street0VPI, street0Aggr, street0_3B4BChance, street0_3B4BDone, + street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown, + street1Aggr, street2Aggr, street3Aggr, street4Aggr, otherRaisedStreet1, + otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4, foldToOtherRaisedStreet1, foldToOtherRaisedStreet2, + foldToOtherRaisedStreet3, foldToOtherRaisedStreet4, wonWhenSeenStreet1, wonAtSD, stealAttemptChance, + stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal, + street1CBChance, street1CBDone, street2CBChance, street2CBDone, street3CBChance, + street3CBDone, street4CBChance, street4CBDone, foldToStreet1CBChance, foldToStreet1CBDone, + foldToStreet2CBChance, foldToStreet2CBDone, foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance, + foldToStreet4CBDone, totalProfit, street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance, + street2CheckCallRaiseDone, street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone) + VALUES (%s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s)""", (row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40], row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50], row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60])) else: #print "storing updated hud data line" - cursor.execute("""UPDATE HudDataHoldemOmaha - SET HDs=%s, VPIP=%s, PFR=%s, PF3B4BChance=%s, PF3B4B=%s, sawFlop=%s, sawTurn=%s, sawRiver=%s, sawShowdown=%s, raisedFlop=%s, raisedTurn=%s, raisedRiver=%s, otherRaisedFlop=%s, otherRaisedFlopFold=%s, otherRaisedTurn=%s, otherRaisedTurnFold=%s, otherRaisedRiver=%s, otherRaisedRiverFold=%s, wonWhenSeenFlop=%s, wonAtSD=%s, stealAttemptChance=%s, stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s, contBetChance=%s, contBetDone=%s, secondBarrelChance=%s, secondBarrelDone=%s, thirdBarrelChance=%s, thirdBarrelDone=%s, tourneysGametypeId=%s, foldToContBetChance=%s, foldToContBetDone=%s, foldToSecondBarrelChance=%s, foldToSecondBarrelDone=%s, foldToThirdBarrelChance=%s, foldToThirdBarrelDone=%s, totalProfit=%s, flopCheckCallRaiseChance=%s, flopCheckCallRaiseDone=%s, turnCheckCallRaiseChance=%s, turnCheckCallRaiseDone=%s, riverCheckCallRaiseChance=%s, riverCheckCallRaiseDone=%s - WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s""", (row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20], row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30], row[31], row[32], row[33], row[34], row[35], 1, row[37], row[38], row[39], row[40], row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[1], row[2], row[3], row[36])) + cursor.execute("""UPDATE HudCache + SET HDs=%s, street0VPI=%s, street0Aggr=%s, street0_3B4BChance=%s, street0_3B4BDone=%s, + street1Seen=%s, street2Seen=%s, street3Seen=%s, street4Seen=%s, sawShowdown=%s, + street1Aggr=%s, street2Aggr=%s, street3Aggr=%s, street4Aggr=%s, otherRaisedStreet1=%s, + otherRaisedStreet2=%s, otherRaisedStreet3=%s, otherRaisedStreet4=%s, foldToOtherRaisedStreet1=%s, foldToOtherRaisedStreet2=%s, + foldToOtherRaisedStreet3=%s, foldToOtherRaisedStreet4=%s, wonWhenSeenStreet1=%s, wonAtSD=%s, stealAttemptChance=%s, + stealAttempted=%s, foldBbToStealChance=%s, foldedBbToSteal=%s, foldSbToStealChance=%s, foldedSbToSteal=%s, + street1CBChance=%s, street1CBDone=%s, street2CBChance=%s, street2CBDone=%s, street3CBChance=%s, + street3CBDone=%s, street4CBChance=%s, street4CBDone=%s, foldToStreet1CBChance=%s, foldToStreet1CBDone=%s, + foldToStreet2CBChance=%s, foldToStreet2CBDone=%s, foldToStreet3CBChance=%s, foldToStreet3CBDone=%s, foldToStreet4CBChance=%s, + foldToStreet4CBDone=%s, totalProfit=%s, street1CheckCallRaiseChance=%s, street1CheckCallRaiseDone=%s, street2CheckCallRaiseChance=%s, + street2CheckCallRaiseDone=%s, street3CheckCallRaiseChance=%s, street3CheckCallRaiseDone=%s, street4CheckCallRaiseChance=%s, street4CheckCallRaiseDone=%s + WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s AND tourneysGametypeId=%s""", (row[6], row[7], row[8], row[9], row[10], + row[11], row[12], row[13], row[14], row[15], + row[16], row[17], row[18], row[19], row[20], + row[21], row[22], row[23], row[24], row[25], + row[26], row[27], row[28], row[29], row[30], + row[31], row[32], row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40], + row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50], + row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60], + row[1], row[2], row[3], row[4], row[5])) else: raise FpdbError("todo") -#end def storeHudData +#end def storeHudCache def store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time): cursor.execute("SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND siteId=%s", (site_tourney_no, site_id)) diff --git a/website/sidebar.php b/website/sidebar.php index 76dc6f44..7bc7f60d 100644 --- a/website/sidebar.php +++ b/website/sidebar.php @@ -7,5 +7,7 @@
  • Documentation
  • License
  • Contact
  • +
  • Forums
  • +
  • Bug/Request Tracker
  • From 1bfb744fc3b70cba4e8069c06cdce7abfbee8478 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 02:02:02 +0100 Subject: [PATCH 048/262] p49 - corrected duplication of db and user creation in windows install --- docs/install-in-windows.txt | 22 +++++++++++----------- docs/known-bugs-and-planned-features.txt | 7 ++++--- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/install-in-windows.txt b/docs/install-in-windows.txt index 96a2524e..32fdc51f 100644 --- a/docs/install-in-windows.txt +++ b/docs/install-in-windows.txt @@ -1,9 +1,9 @@ These instructions are for 32/64bit Windows NT/2k/XP/2k3/Vista/2k8. Well, in principle. I -made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP. +made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP. Also see the other install-in-*.txt files. -The length of these instructions is due to MS refusal to provide any kind of package management. +The length of these instructions is due to MS refusal to provide any meaningful package management, but an installer will be made at some point. -Here are direct download links from 10Aug2008: +Here are direct download links, last checked 10Aug2008: http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.67-win32.zip/from/pick#mirrors http://www.python.org/ftp/python/2.5.2/python-2.5.2.msi http://downloads.sourceforge.net/mysql-python/MySQL-python-1.2.2.win32-py2.5.exe?modtime=1173863337&big_mirror=0 @@ -16,9 +16,9 @@ http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.12/pygtk-2.12.1-2.win32-py - Download Windows ZIP/Setup.exe - Unzip the archive, execute the setup file At the end make sure you activate that you want to configure it now. - Use the advanced/detailed config. Leave everything as default unless stated below, or unless you have reason not to. + Use the advanced/detailed config. Leave everything as default unless stated below, or unless you know what you're doing. Make sure to DEACTIVATE TCP/IP networking, unless you want that and know how to secure it - Set a root password. Note that this is not the account/pw that fpdb will use. + Set a root password. Note that this is NOT the account/pw that fpdb will use. Also note that this is NOT a system account - you can't login to Windows with this, it is only for the database. Once finished it shold confirm "service started successfully" @@ -55,9 +55,7 @@ Get the latest Windows installer. As of this writing that is 2.5.2. Double click Get the package and double click to install. -4. In MySQL create a new database fpdb and a user by the same name. Set a password. I did this in webmin. Then set permissions for that user to: Select | Insert | Update | Delete | Create | Drop - -5. Time for GTK+ - here's the instructions from their bundle +4. Time for GTK+ - here's the instructions from their bundle To use it, create some empty folder like c:\gtk . Using either Windows Explorer's built-in zip file management, or the command-line @@ -70,17 +68,19 @@ versions of GTK+ in PATH. To do that: Right click on "My Computer" ("Arbeitsplatz" in German Windows) on the Desktop or in (Windows) Explorer. Select Properties. Then click on the tab Advanced and then you should see Environment Variables. Simply append GTK's bin folder to the existing PATH (make sure to put a ; between the old PATH and GTK's folder to seperate the entries in this list). -6. Install pycairo, pygobject and pygtk with double click. +5. Install pycairo, pygobject and pygtk with double click. -7. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf +6. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf Now edit the file, in particular you will always have to type in the correct password (insecure, I know) and if you differ from the default setup you may need to change host, database or user. -8. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to. +7. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to. When the program started open the menu Database and click "Create or Recreate Tables". That's it! Now you can use the bulk importer and the table viewer, more's coming. See readme-user.txt +A word on privelege separation: fpdb should not require root/Administrator rights to run. If it does it is a bug or serious misconfiguration, please let us know. + License ======= Trademarks of third parties have been used under Fair Use or similar laws. diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 1e209316..2aa4e4f2 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,16 +3,17 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -finish Cache generalisation from line 1814 onwards +add maxSeats to hands add sf.net logo to webpage change tabledesign VALIGN seperate and improve instructions for update -split install instructions into OS-specific and OS-independent section. expand release creator to concatenate. -expand instructions for profile file, again, the release-creator will cat it. +split install instructions into OS-specific and OS-independent section +expand instructions for profile file add instructions for mailing list to contacts finish updating filelist ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config add minimal instructions for developing to git-instructions +re-run existing regression tests alpha3 ====== From 6a5d1d6332591828b080ed5ae1687454e0f6e7c4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 02:45:06 +0100 Subject: [PATCH 049/262] p50 - added Hands.maxSeats --- docs/tabledesign.html | 5 +++++ pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 5 +++-- pyfpdb/fpdb_save_to_db.py | 2 +- pyfpdb/fpdb_simple.py | 4 ++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 679c277d..27145846 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -259,6 +259,11 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    smallint

    number of used seats (ie. that got dealt cards)

    + +

    maxSeats

    +

    smallint

    +

    number of available seats

    +

    comment

    text

    diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 5e79fff8..b2a52c5d 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p48") + self.window.set_title("Free Poker DB - version: alpha1+, p50") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index b5b08b58..06eb1005 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=48: + if settings[0]!=50: print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -195,6 +195,7 @@ class fpdb_db: handStart DATETIME, importTime DATETIME, seats SMALLINT, + maxSeats SMALLINT, comment TEXT, commentTs DATETIME)""") @@ -350,7 +351,7 @@ class fpdb_db: street4CheckCallRaiseChance INT, street4CheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (48);") + self.cursor.execute("INSERT INTO Settings VALUES (50);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneysGametypes (id) VALUES (DEFAULT);") diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index 65a77710..d9ca0076 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -50,7 +50,7 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti fpdb_simple.fill_board_cards(board_values, board_suits) - hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName) + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index e7f2faec..602b1852 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1123,9 +1123,9 @@ def store_board_cards(cursor, hands_id, board_values, board_suits): board_values[4], board_suits[4])) #end def store_board_cards -def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName): +def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats): #stores into table hands - cursor.execute ("INSERT INTO Hands (siteHandNo, gametypeId, handStart, seats, tableName, importTime) VALUES (%s, %s, %s, %s, %s, %s)", (site_hand_no, gametype_id, hand_start_time, len(names), tableName, datetime.datetime.today())) + cursor.execute ("INSERT INTO Hands (siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats) VALUES (%s, %s, %s, %s, %s, %s, %s)", (site_hand_no, gametype_id, hand_start_time, len(names), tableName, datetime.datetime.today(), maxSeats)) #todo: find a better way of doing this... cursor.execute("SELECT id FROM Hands WHERE siteHandNo=%s AND gametypeId=%s", (site_hand_no, gametype_id)) return cursor.fetchall()[0][0] From 6eb42cd05ebc211b7070e4ca702f36b2a8c7eb4f Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 03:56:59 +0100 Subject: [PATCH 050/262] p51 - added to git instructions updated tv to use new HudCache --- docs/git-instructions.txt | 27 ------------ docs/known-bugs-and-planned-features.txt | 7 ++-- pyfpdb/GuiTableViewer.py | 52 ++++++++++++------------ pyfpdb/fpdb.py | 2 +- website/docs-git-instructions.php | 38 +++++++++++------ 5 files changed, 55 insertions(+), 71 deletions(-) delete mode 100644 docs/git-instructions.txt diff --git a/docs/git-instructions.txt b/docs/git-instructions.txt deleted file mode 100644 index 1d455453..00000000 --- a/docs/git-instructions.txt +++ /dev/null @@ -1,27 +0,0 @@ -Hi, welcome to my minimal git guide for fpdb devs! -I'll expand this on request, if you have any questions just send me a mail at steffen@sycamoretest.info. - -How to make a local git commit -============================== -go to the root of your fpdb directory and type: -git-add--interactive -If you added any new files press a and Enter, then type the number of your new file and press Enter twice. If you made any changes to existing files press u and enter. If you want to commit all changes press * and Enter twice. Press q to leave git-add--interactive. -Then create a file for your commit message (I call it since_last_commit.txt) but don't add this to the repository. In the first line of this file put a summary of your changes. If you wish to you can also add in a revision number. My tree (the "central" or "official" repository) uses the format gitX where X is a running number, e.g. git91 is followed by git92. Then give some details of your changes, try to mention anything non-trivial and definitely any user-visible bug fixes. If the table design has been changed that has to be mentioned in the first line. -Then run this: -git-commit -F since_last_commit.txt - -todo: how to pull/push changes to/from me -todo: git-diff, git-rm, git-mv - -License -======= -Trademarks of third parties have been used under Fair Use or similar laws. - -Copyright 2008 Steffen Jobbagy-Felso -Permission is granted to copy, distribute and/or modify this -document under the terms of the GNU Free Documentation License, -Version 1.2 as published by the Free Software Foundation; with -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover -Texts. A copy of the license can be found in fdl-1.2.txt - -The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 2aa4e4f2..ecb4ead3 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,20 +3,19 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -add maxSeats to hands +change config file to windows line endings add sf.net logo to webpage change tabledesign VALIGN seperate and improve instructions for update -split install instructions into OS-specific and OS-independent section expand instructions for profile file add instructions for mailing list to contacts -finish updating filelist ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config -add minimal instructions for developing to git-instructions re-run existing regression tests alpha3 ====== +finish updating filelist +finish todos in git instructions make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt (steffen) finish bringing back tourney diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 75be8426..0fb29a28 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -102,7 +102,7 @@ class GuiTableViewer (threading.Thread): else: fpdb_simple.FpdbError("invalid seatCount") - self.cursor.execute("SELECT * FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats>=%s AND activeSeats<=%s", (self.gametype_id, self.player_ids[player][0], minSeats, maxSeats)) + self.cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats>=%s AND activeSeats<=%s", (self.gametype_id, self.player_ids[player][0], minSeats, maxSeats)) rows=self.cursor.fetchall() row=[] @@ -120,45 +120,45 @@ class GuiTableViewer (threading.Thread): #print "in prep data, row_no:",row_no,"field_no:",field_no row[field_no]+=rows[row_no][field_no] - tmp.append(str(row[4]))#Hands - tmp.append(self.hudDivide(row[5],row[4])) #VPIP - tmp.append(self.hudDivide(row[6],row[4])) #PFR - tmp.append(self.hudDivide(row[8],row[7])+" ("+str(row[7])+")") #PF3B4B + tmp.append(str(row[6]))#Hands + tmp.append(self.hudDivide(row[7],row[6])) #VPIP + tmp.append(self.hudDivide(row[8],row[6])) #PFR + tmp.append(self.hudDivide(row[10],row[9])+" ("+str(row[9])+")") #PF3B4B - tmp.append(self.hudDivide(row[25],row[24])+" ("+str(row[24])+")") #ST + tmp.append(self.hudDivide(row[31],row[30])+" ("+str(row[30])+")") #ST if self.settings['tv-combinedStealFold']: - tmp.append(self.hudDivide(row[29]+row[27],row[28]+row[26])+" ("+str(row[28]+row[26])+")") #FSB + tmp.append(self.hudDivide(row[35]+row[33],row[34]+row[32])+" ("+str(row[34]+row[32])+")") #FSB else: - tmp.append(self.hudDivide(row[29],row[28])+" ("+str(row[28])+")") #FS - tmp.append(self.hudDivide(row[27],row[26])+" ("+str(row[26])+")") #FB + tmp.append(self.hudDivide(row[35],row[34])+" ("+str(row[34])+")") #FS + tmp.append(self.hudDivide(row[33],row[32])+" ("+str(row[32])+")") #FB - tmp.append(self.hudDivide(row[31],row[30])+" ("+str(row[30])+")") #CB + tmp.append(self.hudDivide(row[37],row[36])+" ("+str(row[36])+")") #CB if self.settings['tv-combined2B3B']: - tmp.append(self.hudDivide(row[33]+row[35],row[32]+row[34])+" ("+str(row[32]+row[34])+")") #23B + tmp.append(self.hudDivide(row[39]+row[41],row[38]+row[40])+" ("+str(row[38]+row[40])+")") #23B else: - tmp.append(self.hudDivide(row[33],row[32])+" ("+str(row[32])+")") #2B - tmp.append(self.hudDivide(row[35],row[34])+" ("+str(row[34])+")") #3B + tmp.append(self.hudDivide(row[39],row[38])+" ("+str(row[38])+")") #2B + tmp.append(self.hudDivide(row[41],row[40])+" ("+str(row[40])+")") #3B if self.settings['tv-combinedPostflop']: - aggCount=row[13]+row[14]+row[15] - handCount=row[9]+row[10]+row[11] - foldCount=row[17]+row[19]+row[21] - otherRaiseCount=row[16]+row[18]+row[20] + aggCount=row[16]+row[17]+row[18] + handCount=row[11]+row[12]+row[13] + foldCount=row[24]+row[25]+row[26] + otherRaiseCount=row[20]+row[21]+row[22] tmp.append(self.hudDivide(aggCount,handCount)+" ("+str(handCount)+")") #Agg tmp.append(self.hudDivide(foldCount,otherRaiseCount)+" ("+str(otherRaiseCount)+")") #FF else: - tmp.append(self.hudDivide(row[13],row[9])+" ("+str(row[9])+")") #AF - tmp.append(self.hudDivide(row[17],row[16])+" ("+str(row[16])+")") #FF - tmp.append(self.hudDivide(row[14],row[10])+" ("+str(row[10])+")") #AT - tmp.append(self.hudDivide(row[19],row[18])+" ("+str(row[18])+")") #FT - tmp.append(self.hudDivide(row[15],row[11])+" ("+str(row[11])+")") #AR - tmp.append(self.hudDivide(row[21],row[20])+" ("+str(row[20])+")") #FR + tmp.append(self.hudDivide(row[16],row[11])+" ("+str(row[11])+")") #AF + tmp.append(self.hudDivide(row[24],row[20])+" ("+str(row[20])+")") #FF + tmp.append(self.hudDivide(row[17],row[12])+" ("+str(row[12])+")") #AT + tmp.append(self.hudDivide(row[25],row[21])+" ("+str(row[21])+")") #FT + tmp.append(self.hudDivide(row[18],row[13])+" ("+str(row[13])+")") #AR + tmp.append(self.hudDivide(row[26],row[22])+" ("+str(row[22])+")") #FR - tmp.append(self.hudDivide(row[12],row[9])) #WtSD - tmp.append(self.hudDivide(row[22],row[9])) #W$wSF - tmp.append(self.hudDivide(row[23],row[12])+" ("+str(row[12])+")") #W$@SD + tmp.append(self.hudDivide(row[15],row[11])) #WtSD + tmp.append(self.hudDivide(row[28],row[11])) #W$wSF + tmp.append(self.hudDivide(row[29],row[15])+" ("+str(row[15])+")") #W$@SD arr.append(tmp) return arr diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index b2a52c5d..fa9bc510 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p50") + self.window.set_title("Free Poker DB - version: alpha1+, p51") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/website/docs-git-instructions.php b/website/docs-git-instructions.php index 3f4e3074..9975f808 100644 --- a/website/docs-git-instructions.php +++ b/website/docs-git-instructions.php @@ -14,23 +14,35 @@ require SITE_PATH.'sidebar.php';

    Git Instructions

    Hi, welcome to my minimal git guide for fpdb devs!
    -I'll expand this on request, if you have any questions just send me a mail at steffen(at)sycamoretest.info.

    +You can use a git version just as user as well of course, but as there are generally hardly tested it is not advised.
    +I'll expand this on request, if you have any questions just send me a mail at steffen(at)sycamoretest.info. There's also a bunch of instructions at http://www.assembla.com/spaces/fpdb/trac_git_tool

    -How to make a local git commit
    -==============================
    -go to the root of your fpdb directory and type:
    +

    0. Getting it

    +

    To get git for gentoo just do emerge git -av
    +To get it for Windows go to http://code.google.com/p/msysgit/downloads/list and install it. +

    1. Cloning the fpdb git tree

    +

    Just create a new directory (lets say ~/fpdb/ ), go into it and type:
    +git clone git://git.assembla.com/fpdb.git

    +

    2. Making your changes

    +

    You can use whatever you want to do edit the files. I personally use nedit and occassionally Eclipse.

    +

    3. Making a (local) commit

    +

    Unlike in svn you don't need to be online to make your commits. First we need to tell git what to commit, so go to the root of your fpdb directory and type:
    git-add--interactive
    -If you added any new files press a and Enter, then type the number of your new file and press Enter twice. If you made any changes to existing files press u and enter. If you want to commit all changes press * and Enter twice. Press q to leave git-add--interactive.
    -Then create a file for your commit message (I call it since_last_commit.txt) but don't add this to the repository. In the first line of this file put a summary of your changes. If you wish to you can also add in a revision number. My tree (the "central" or "official" repository) uses the format gitX where X is a running number, e.g. git91 is followed by git92. Then give some details of your changes, try to mention anything non-trivial and definitely any user-visible bug fixes. If the table design has been changed that has to be mentioned in the first line.
    +Now press u and enter. It will display a list of all changed files. If you want to commit all files just press * and enter twice to return to the main menu. If you want to commit only certain ones press the number of the file and enter and repeat until you have all the files. Then press enter again to return to the main menu.
    +If you added any new files press a and Enter, then type the number of your new file and press Enter twice. Press q to leave git-add--interactive.
    +Now create a file for your commit message (I call it since_last_commit.txt) but don't add this to the repository. In the first line of this file put a summary of your changes. Then give some details of your changes, try to mention anything non-trivial and definitely any user-visible bug fixes.
    Then run this:
    git-commit -F since_last_commit.txt
    -
    -todo: how to pull/push changes to/from me
    -todo: git-diff, git-rm, git-mv
    -
    -License
    -=======
    -Trademarks of third parties have been used under Fair Use or similar laws.
    +

    4a. Pushing the changes to your own public git tree

    +

    Do this OR 4b, not both.
    +todo

    +

    4b. Preparing changeset for emailing/uploading

    +

    Do this OR 4a, not both.
    +todo

    +

    5. Pulling updates from the main tree

    +

    todo

    +

    License

    +

    Trademarks of third parties have been used under Fair Use or similar laws.

    Copyright 2008 Steffen Jobbagy-Felso
    Permission is granted to copy, distribute and/or modify this
    From 8fb57d372974732eefaaf3964725aaa4a0d484b4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 05:58:41 +0100 Subject: [PATCH 051/262] p52 - much progress on tourneys and some minor stuff and a change of tables migrated install-in-windows to website renamed table tourneysgametypes to tourneytypes --- docs/install-in-windows.txt | 95 ------------------------ docs/known-bugs-and-planned-features.txt | 4 +- docs/tabledesign.html | 10 +-- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 16 ++-- pyfpdb/fpdb_parse_logic.py | 13 ++-- pyfpdb/fpdb_save_to_db.py | 26 ++----- pyfpdb/fpdb_simple.py | 39 ++++++++-- website/docs-install-windows.php | 23 +++--- 9 files changed, 71 insertions(+), 157 deletions(-) delete mode 100644 docs/install-in-windows.txt diff --git a/docs/install-in-windows.txt b/docs/install-in-windows.txt deleted file mode 100644 index 32fdc51f..00000000 --- a/docs/install-in-windows.txt +++ /dev/null @@ -1,95 +0,0 @@ -These instructions are for 32/64bit Windows NT/2k/XP/2k3/Vista/2k8. Well, in principle. I -made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP. -Also see the other install-in-*.txt files. -The length of these instructions is due to MS refusal to provide any meaningful package management, but an installer will be made at some point. - -Here are direct download links, last checked 10Aug2008: -http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.67-win32.zip/from/pick#mirrors -http://www.python.org/ftp/python/2.5.2/python-2.5.2.msi -http://downloads.sourceforge.net/mysql-python/MySQL-python-1.2.2.win32-py2.5.exe?modtime=1173863337&big_mirror=0 -http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.12/gtk+-bundle-2.12.11.zip -http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.4/pycairo-1.4.12-1.win32-py2.5.exe -http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.14/pygobject-2.14.1-1.win32-py2.5.exe -http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.12/pygtk-2.12.1-2.win32-py2.5.exe - -1a. Install MySQL and do its basic setup -- Download Windows ZIP/Setup.exe -- Unzip the archive, execute the setup file - At the end make sure you activate that you want to configure it now. - Use the advanced/detailed config. Leave everything as default unless stated below, or unless you know what you're doing. - Make sure to DEACTIVATE TCP/IP networking, unless you want that and know how to secure it - Set a root password. Note that this is NOT the account/pw that fpdb will use. Also note that this is NOT a system account - you can't login to Windows with this, it is only for the database. - -Once finished it shold confirm "service started successfully" - -1b. MySQL database and user setup -Now open a shell (aka command prompt aka DOS window): -Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A window with a black background should open. - -Type (replacing yourPassword with the root password for MySQL you specified during installation): -mysql --user=root --password=yourPassword - -It should say something like this: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 4 -Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1 - -Type 'help;' or '\h' for help. Type '\c' to clear the buffer. - -mysql> - - -Now create the actual database. The default name is fpdb, I recommend you keep it. Type this: -CREATE DATABASE fpdb; - -Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password): -GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION; - -Exit mysql by pressing Ctrl+D - -2. Install python -Get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts. - - -3. Install the Python-DBAPI package for MySQL: -Get the package and double click to install. - - -4. Time for GTK+ - here's the instructions from their bundle - -To use it, create some empty folder like c:\gtk . Using either -Windows Explorer's built-in zip file management, or the command-line -unzip.exe from -ftp://tug.ctan.org/tex-archive/tools/zip/info-zip/WIN32/unz552xN.exe -unzip this bundle. - -Then add the bin folder to your PATH. Make sure you have no other -versions of GTK+ in PATH. -To do that: -Right click on "My Computer" ("Arbeitsplatz" in German Windows) on the Desktop or in (Windows) Explorer. Select Properties. Then click on the tab Advanced and then you should see Environment Variables. Simply append GTK's bin folder to the existing PATH (make sure to put a ; between the old PATH and GTK's folder to seperate the entries in this list). - -5. Install pycairo, pygobject and pygtk with double click. - -6. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf - -Now edit the file, in particular you will always have to type in the correct password (insecure, I know) and if you differ from the default setup you may need to change host, database or user. - -7. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to. -When the program started open the menu Database and click "Create or Recreate Tables". - -That's it! Now you can use the bulk importer and the table viewer, more's coming. See readme-user.txt - -A word on privelege separation: fpdb should not require root/Administrator rights to run. If it does it is a bug or serious misconfiguration, please let us know. - -License -======= -Trademarks of third parties have been used under Fair Use or similar laws. - -Copyright 2008 Steffen Jobbagy-Felso -Permission is granted to copy, distribute and/or modify this -document under the terms of the GNU Free Documentation License, -Version 1.2 as published by the Free Software Foundation; with -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover -Texts. A copy of the license can be found in fdl-1.2.txt - -The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index ecb4ead3..635d01cc 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -4,8 +4,6 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== change config file to windows line endings -add sf.net logo to webpage -change tabledesign VALIGN seperate and improve instructions for update expand instructions for profile file add instructions for mailing list to contacts @@ -14,6 +12,8 @@ re-run existing regression tests alpha3 ====== +add sf.net logo to webpage +change tabledesign VALIGN finish updating filelist finish todos in git instructions make sure totalProfit shows actual profit rather than winnings. diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 27145846..3a89ef9b 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -421,9 +421,9 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    Position for which this row applies. In this table this can be B(BB), S(SB), D(Dealer/Button), C(Cutoff), M(Middle - the 3 before cutoff) or E (Early - the 3 before Middle)

    -

    tourneysGametypeId

    +

    tourneyTypeId

    smallint

    -

    References TourneysGametypes.id

    +

    References TourneyTypes.id

    @@ -777,9 +777,9 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


    -

    tourneysGametypeId

    +

    tourneyTypeId

    smallint

    -

    References TourneysGametypes.id

    +

    References TourneyTypes.id

    siteTourneyNo

    @@ -813,7 +813,7 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


    -

    Table TourneyGametypes

    +

    Table TourneyTypes

    diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index fa9bc510..a2c59f84 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p51") + self.window.set_title("Free Poker DB - version: alpha1+, p52") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 06eb1005..0fa0dbc7 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=50: + if settings[0]!=52: print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -126,7 +126,9 @@ class fpdb_db: self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") self.cursor.execute("DROP TABLE IF EXISTS Players;") self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") - self.cursor.execute("DROP TABLE IF EXISTS TourneysGametypes;") + if oldDbVersion>45 and oldDbVersion<=51: + self.cursor.execute("DROP TABLE IF EXISTS TourneysGametypes;") + self.cursor.execute("DROP TABLE IF EXISTS TourneyTypes;") self.cursor.execute("DROP TABLE IF EXISTS Sites;") self.db.commit() @@ -213,7 +215,7 @@ class fpdb_db: card5Value smallint, card5Suit char(1))""") - self.create_table("""TourneysGametypes ( + self.create_table("""TourneyTypes ( id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), buyin INT, @@ -223,7 +225,7 @@ class fpdb_db: self.create_table("""Tourneys ( id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id), + tourneyTypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), siteTourneyNo BIGINT, entries INT, prizepool INT, @@ -288,7 +290,7 @@ class fpdb_db: playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), activeSeats SMALLINT, position CHAR(1), - tourneysGametypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneysGametypeId) REFERENCES TourneysGametypes(id), + tourneyTypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), HDs INT, street0VPI INT, @@ -351,10 +353,10 @@ class fpdb_db: street4CheckCallRaiseChance INT, street4CheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (50);") + self.cursor.execute("INSERT INTO Settings VALUES (52);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") - self.cursor.execute("INSERT INTO TourneysGametypes (id) VALUES (DEFAULT);") + self.cursor.execute("INSERT INTO TourneyTypes (id) VALUES (DEFAULT);") self.db.commit() print "finished recreating tables" #end def recreate_tables diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 8912fc07..aa644a0c 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -44,7 +44,11 @@ def mainParser(db, cursor, site, category, hand): fee=fpdb_simple.parseFee(hand[0]) entries=-1 #todo: parse this prizepool=-1 #todo: parse this + knockout=0 tourneyStartTime=handStartTime #todo: read tourney start time + rebuyOrAddon=fpdb_simple.isRebuyOrAddon(hand[0]) + + tourneyTypeId=fpdb_simple.recogniseTourneyTypeId(cursor, siteID, buyin, fee, knockout, rebuyOrAddon) fpdb_simple.isAlreadyInDB(cursor, gametypeID, siteHandNo) #part 2: classify lines by type (e.g. cards, action, win, sectionchange) and street @@ -110,16 +114,13 @@ def mainParser(db, cursor, site, category, hand): hudImportData=fpdb_simple.generateHudData(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) if isTourney: - raise fpdb_simple.FpdbError ("tourneys are currently broken") - payin_amounts=fpdb_simple.calcPayin(len(names), buyin, fee) ranks=[] for i in range (len(names)): ranks.append(0) - knockout=0 - entries=-1 - prizepool=-1 + payin_amounts=fpdb_simple.calcPayin(len(names), buyin, fee) + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, + result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) elif (category=="razz" or category=="studhi" or category=="studhilo"): raise fpdb_simple.FpdbError ("stud/razz are currently broken") diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index d9ca0076..be5e35bd 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -39,15 +39,7 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a holdem/omaha hand into the database""" - - #fill up the two player card arrays - if (category=="holdem"): - fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) - elif (category=="omahahi" or category=="omahahilo"): - fpdb_simple.fillCardArrays(len(names), 4, card_values, card_suits) - else: - raise fpdb_simple.FpdbError ("invalid category: category") - + fpdb_simple.fillCardArrays(len(names), category, card_values, card_suits) fpdb_simple.fill_board_cards(board_values, board_suits) hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) @@ -62,21 +54,13 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti return site_hand_no #end def ring_holdem_omaha -def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, #end of tourney specific params - site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData): +def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId, siteId, #end of tourney specific params + site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a tourney holdem/omaha hand into the database""" - #fill up the two player card arrays - if (category=="holdem"): - fpdb_simple.fillCardArrays(len(names), 2, card_values, card_suits) - elif (category=="omahahi" or category=="omahahilo"): - fpdb_simple.fillCardArrays(len(names), 4, card_values, card_suits) - else: - raise fpdb_simple.FpdbError ("invalid category: category") - + fpdb_simple.fillCardArrays(len(names), category, card_values, card_suits) fpdb_simple.fill_board_cards(board_values, board_suits) - tourney_id=fpdb_simple.store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start) - + tourney_id=fpdb_simple.store_tourneys(cursor, siteId, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start) tourneys_players_ids=fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings) hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 602b1852..8416bcbb 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -250,12 +250,17 @@ def fill_board_cards(board_values, board_suits): board_suits.append("x") #end def fill_board_cards -def fillCardArrays(player_count, card_count, card_values, card_suits): -#fills up the two card arrays - #print "fillCardArrays, player_count:", player_count," card_count:",card_count - #print "card_values:",card_values +def fillCardArrays(player_count, category, card_values, card_suits): + """fills up the two card arrays""" + if (category=="holdem"): + cardCount=2 + elif (category=="omahahi" or category=="omahahilo"): + cardCount=4 + else: + raise fpdb_simple.FpdbError ("invalid category: category") + for i in range (player_count): - while (len(card_values[i])Installing in Windows -

    These instructions are for 32/64bit Windows NT/2k/XP/2k3/Vista/2k8. Well, in principle. I made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP. Also see the installation pages for other plataforms.
    -
    -The length of these instructions is due to MS refusal to provide any kind of package management.
    -
    -Here are direct download links from 10Aug2008:
    +

    These instructions are for 32/64bit Windows NT/2k/XP/2k3/Vista/2k8. Well, in principle. I +made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP.
    +The length of these instructions is due to MS refusal to provide any meaningful package management, but an installer will be made at some point.

    +

    Here are direct download links, last checked on 10Aug2008:
    http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.67-win32.zip/from/pick#mirrors
    http://www.python.org/ftp/python/2.5.2/python-2.5.2.msi
    http://downloads.sourceforge.net/mysql-python/MySQL-python-1.2.2.win32-py2.5.exe?modtime=1173863337&big_mirror=0
    @@ -38,7 +37,7 @@ Once finished it shold confirm "service started successfully"

    1b. MySQL database and user setup
    Now open a shell (aka command prompt aka DOS window):
    -Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open.
    +Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A window with a black background should open.

    Type (replacing yourPassword with the root password for MySQL you specified during installation):
    mysql --user=root --password=yourPassword
    @@ -66,9 +65,7 @@ Get the latest Windows installer. As of this writing that is 2.5.2. Double click 3. Install the Python-DBAPI package for MySQL:
    Get the package and double click to install.

    -4. In MySQL create a new database fpdb and a user by the same name. Set a password. I did this in webmin. Then set permissions for that user to: Select | Insert | Update | Delete | Create | Drop
    -
    -5. Time for GTK+ - here's the instructions from their bundle
    +4. Time for GTK+ - here's the instructions from their bundle

    To use it, create some empty folder like c:\gtk . Using either
    Windows Explorer's built-in zip file management, or the command-line
    @@ -81,17 +78,17 @@ versions of GTK+ in PATH.
    To do that:
    Right click on "My Computer" ("Arbeitsplatz" in German Windows) on the Desktop or in (Windows) Explorer. Select Properties. Then click on the tab Advanced and then you should see Environment Variables. Simply append GTK's bin folder to the existing PATH (make sure to put a ; between the old PATH and GTK's folder to seperate the entries in this list).

    -6. Install pycairo, pygobject and pygtk with double click.
    +5. Install pycairo, pygobject and pygtk with double click.

    -7. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf
    +6. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf

    Now edit the file, in particular you will always have to type in the correct password (insecure, I know) and if you differ from the default setup you may need to change host, database or user.

    -8. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to.
    +7. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to.
    When the program started open the menu Database and click "Create or Recreate Tables".

    That's it! Now you can use the bulk importer and the table viewer, more's coming. See readme-user.txt
    -
    +A word on privelege separation: fpdb should not require root/Administrator rights to run. If it does it is a bug or serious misconfiguration, please let us know.
    License
    =======
    Trademarks of third parties have been used under Fair Use or similar laws.
    From de845d4e90561dbe8589f0e57f17525769bf8eb5 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 06:27:37 +0100 Subject: [PATCH 052/262] p53 - more progress on tourneys --- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 4 ++-- pyfpdb/fpdb_save_to_db.py | 13 ++++++------- pyfpdb/fpdb_simple.py | 30 ++++++++++++++---------------- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index a2c59f84..b10aef04 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p52") + self.window.set_title("Free Poker DB - version: alpha1+, p53") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 0fa0dbc7..d65c1a7f 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -47,7 +47,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=52: + if settings[0]!=53: print "outdated or too new database version - please recreate tables" except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" @@ -353,7 +353,7 @@ class fpdb_db: street4CheckCallRaiseChance INT, street4CheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (52);") + self.cursor.execute("INSERT INTO Settings VALUES (53);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneyTypes (id) VALUES (DEFAULT);") diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index be5e35bd..a5016904 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -54,25 +54,24 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti return site_hand_no #end def ring_holdem_omaha -def tourney_holdem_omaha(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId, siteId, #end of tourney specific params +def tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId, siteId, #end of tourney specific params site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a tourney holdem/omaha hand into the database""" fpdb_simple.fillCardArrays(len(names), category, card_values, card_suits) fpdb_simple.fill_board_cards(board_values, board_suits) - tourney_id=fpdb_simple.store_tourneys(cursor, siteId, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start) + tourney_id=fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start) tourneys_players_ids=fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings) - hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) - hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, - start_cashes, positions, card_values, card_suits, winnings, rakes, tourneys_players_ids) + hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha_tourney(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids) - fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) + fpdb_simple.storeHudCache(cursor, category, gametype_id, player_ids, hudImportData) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) return site_hand_no #end def tourney_holdem_omaha diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 8416bcbb..2e2dbb2a 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1155,8 +1155,7 @@ def storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableN return cursor.fetchall()[0][0] #end def storeHands -def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, - start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos): +def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos): result=[] if (category=="holdem"): for i in range (len(player_ids)): @@ -1211,8 +1210,7 @@ def store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, return result #end def store_hands_players_stud -def store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, start_cashes, - positions, card_values, card_suits, winnings, rakes, tourneys_players_ids): +def store_hands_players_holdem_omaha_tourney(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids): #stores hands_players for tourney holdem/omaha hands result=[] for i in range (len(player_ids)): @@ -1220,22 +1218,22 @@ def store_hands_players_holdem_omaha_tourney(cursor, hands_id, player_ids, start cursor.execute ("""INSERT INTO HandsPlayers (handId, playerId, startCash, position, card1Value, card1Suit, card2Value, card2Suit, - winnings, rake, tourneysPlayersId) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + winnings, rake, tourneysPlayersId, seatNo) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], - winnings[i], rakes[i], tourneys_players_ids[i])) + winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i])) elif len(card_values[0])==4: cursor.execute ("""INSERT INTO HandsPlayers (handId, playerId, startCash, position, card1Value, card1Suit, card2Value, card2Suit, card3Value, card3Suit, card4Value, card4Suit, - winnings, rake, tourneysPlayersId) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + winnings, rake, tourneysPlayersId, seatNo) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], - winnings[i], rakes[i], tourneys_players_ids[i])) + winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i])) else: raise FpdbError ("invalid card_values length:"+str(len(card_values[0]))) cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId=%s", (hands_id, player_ids[i])) @@ -1882,18 +1880,18 @@ def storeHudCache(cursor, category, gametypeId, playerIds, hudImportData): raise FpdbError("todo") #end def storeHudCache -def store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time): - cursor.execute("SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND siteId=%s", (site_tourney_no, site_id)) +def store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime): + cursor.execute("SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND tourneyTypeId=%s", (siteTourneyNo, tourneyTypeId)) tmp=cursor.fetchone() #print "tried SELECTing tourneys.id, result:",tmp try: len(tmp) - except TypeError: + except TypeError:#means we have to create new one cursor.execute("""INSERT INTO Tourneys - (siteId, siteTourneyNo, buyin, fee, knockout, entries, prizepool, startTime) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, start_time)) - cursor.execute("SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND siteId=%s", (site_tourney_no, site_id)) + (tourneyTypeId, siteTourneyNo, entries, prizepool, startTime) + VALUES (%s, %s, %s, %s, %s)""", (tourneyTypeId, siteTourneyNo, entries, prizepool, startTime)) + cursor.execute("SELECT id FROM Tourneys WHERE siteTourneyNo=%s AND tourneyTypeId=%s", (siteTourneyNo, tourneyTypeId)) tmp=cursor.fetchone() #print "created new tourneys.id:",tmp return tmp[0] From f6d596d2ed69f56e0fd3719abbe8ee6fd35d53fa Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 06:51:18 +0100 Subject: [PATCH 053/262] p54 - fixed bug that caused everything but FL to fail --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_simple.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 635d01cc..e2efbe1d 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -13,6 +13,7 @@ re-run existing regression tests alpha3 ====== add sf.net logo to webpage +separate db table design version and last bugfix in importer change tabledesign VALIGN finish updating filelist finish todos in git instructions diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index b10aef04..d7fbaef8 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p53") + self.window.set_title("Free Poker DB - version: alpha1+, p54") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2e2dbb2a..bd329734 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1011,8 +1011,8 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBet=%s AND bigBet=%s", (site_id, type, category, limit_type, small_bet, big_bet)) else: cursor.execute("""INSERT INTO Gametypes - (siteId, type, category, limitType, smallBlind, bigBlind, smallBet, bigBet) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, base, category, limit_type, hiLo, small_bet, big_bet, 0, 0))#remember, for these bet means blind + (siteId, type, base, category, limitType, hiLo, smallBlind, bigBlind, smallBet, bigBet) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, base, category, limit_type, hiLo, small_bet, big_bet, 0, 0))#remember, for these bet means blind cursor.execute ("SELECT id FROM Gametypes WHERE siteId=%s AND type=%s AND category=%s AND limitType=%s AND smallBlind=%s AND bigBlind=%s", (site_id, type, category, limit_type, small_bet, big_bet)) result=cursor.fetchone() From b546868e102c1549d4b52c76fc610108ef70e1d8 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 07:43:05 +0100 Subject: [PATCH 054/262] p55 - three bugfixes fixed bug that it filtered sitouts in tourneys fixed bug that it didnt handle if some joined (tourney) out of hand fixed bug that it didnt handle : in player name --- docs/known-bugs-and-planned-features.txt | 4 +--- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_import.py | 6 ++++-- pyfpdb/fpdb_simple.py | 19 +++++++++++++------ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index e2efbe1d..87123cfa 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -53,15 +53,13 @@ implement error file in importer catch index error, type error, file not found error use different colours according to classification. -add stud, razz and tourney back to imp/tv but with less seperate codepathes +add stud, razz back to imp/tv but with less seperate codepathes move prepare-git.sh and create-release.sh to utils offer not storing db password change definition of bet to exclude bring in? in tv, select from hud table using named fields rather than the current * remove remains of mysql/myisam support. tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem -tourney bug: fails recognisePlayer -tourney bug: fails with tuple error in recogniseplayerid fix GUI's load profile HUD config wizard diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index d7fbaef8..e0e2c3f6 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p54") + self.window.set_title("Free Poker DB - version: alpha1+, p55") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 5d89ac76..b6fcffd7 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -93,8 +93,10 @@ def import_file_dict(options): elif (cancelled or damaged): partial+=1 else: #normal processing - fpdb_simple.filterAnteBlindFold(site,hand) - hand=fpdb_simple.filterCrap(site, hand) + isTourney=fpdb_simple.isTourney(hand[0]) + if not isTourney: + fpdb_simple.filterAnteBlindFold(site,hand) + hand=fpdb_simple.filterCrap(site, hand, isTourney) try: if (category=="holdem" or category=="omahahi" or category=="omahahilo" or category=="razz" or category=="studhi" or category=="studhilo"): diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index bd329734..ef132da8 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -305,7 +305,7 @@ def filterAnteBlindFold(site,hand): #end def filterAnteFold #removes useless lines as well as trailing spaces -def filterCrap(site, hand): +def filterCrap(site, hand, isTourney): #remove one trailing space at end of line for i in range (len(hand)): if (hand[i][-1]==' '): @@ -317,6 +317,8 @@ def filterCrap(site, hand): for i in range (len(hand)): if (hand[i].startswith("Board [")): toRemove.append(hand[i]) + elif (hand[i].find(" out of hand ")!=-1): + hand[i]=hand[i][:-56] elif (hand[i]=="*** HOLE CARDS ***"): toRemove.append(hand[i]) elif (hand[i].endswith("has been disconnected")): @@ -374,10 +376,6 @@ def filterCrap(site, hand): toRemove.append(hand[i]) elif (hand[i].endswith("was removed from the table for failing to post")): toRemove.append(hand[i]) - elif (hand[i].endswith("is sitting out")): - toRemove.append(hand[i]) - elif (hand[i].endswith(": sits out")): - toRemove.append(hand[i]) elif (hand[i].find("joins the table at seat ")!=-1): toRemove.append(hand[i]) elif (hand[i].endswith(" sits down")): @@ -401,6 +399,15 @@ def filterCrap(site, hand): toRemove.append(hand[i]) elif (hand[i].find(": ")!=-1 and site=="ftp" and hand[i].find("Seat ")==-1 and hand[i].find(": Table")==-1): #filter ftp chat toRemove.append(hand[i]) + if isTourney: + if (hand[i].endswith(" is sitting out") and (not hand[i].startswith("Seat "))): + toRemove.append(hand[i]) + else: + if (hand[i].endswith(": sits out")): + toRemove.append(hand[i]) + elif (hand[i].endswith(" is sitting out")): + toRemove.append(hand[i]) + for i in range (len(toRemove)): #print "removing in filterCr:",toRemove[i] @@ -733,7 +740,7 @@ def parseCashesAndSeatNos(lines, site): cashes = [] seatNos = [] for i in range (len(lines)): - pos2=lines[i].rfind(":") + pos2=lines[i].find(":") seatNos.append(int(lines[i][5:pos2])) pos1=lines[i].rfind("($")+2 From 660530a166484feb3b607d74968c2cd427455415 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 09:05:22 +0100 Subject: [PATCH 055/262] p56 - various fixes to get FTP working again fixed in prev commit: sometimes truncuates position on store in tourneys --- docs/known-bugs-and-planned-features.txt | 4 ++-- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_parse_logic.py | 8 ++++--- pyfpdb/fpdb_simple.py | 27 +++++++++++++++++------- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 87123cfa..966c22df 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -12,6 +12,7 @@ re-run existing regression tests alpha3 ====== +ftp: read maxSeats add sf.net logo to webpage separate db table design version and last bugfix in importer change tabledesign VALIGN @@ -56,10 +57,9 @@ use different colours according to classification. add stud, razz back to imp/tv but with less seperate codepathes move prepare-git.sh and create-release.sh to utils offer not storing db password -change definition of bet to exclude bring in? +change definition of bet to exclude bring in in tv, select from hud table using named fields rather than the current * remove remains of mysql/myisam support. -tourney bug: sometimes truncuates position on store -> possibly indicates much bigger problem fix GUI's load profile HUD config wizard diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index e0e2c3f6..8fedf631 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -370,7 +370,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p55") + self.window.set_title("Free Poker DB - version: alpha1+, p56") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index aa644a0c..8ab7fe2e 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -90,11 +90,13 @@ def mainParser(db, cursor, site, category, hand): elif (lineTypes[i]=="ante"): fpdb_simple.parseAnteLine(hand[i], site, names, antes) elif (lineTypes[i]=="table"): - result=fpdb_simple.parseTableLine(hand[i]) - maxSeats=result['maxSeats'] - tableName=result['tableName'] + tableResult=fpdb_simple.parseTableLine(site, hand[i]) else: raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i]) + if site=="ftp": + tableResult=fpdb_simple.parseTableLine(site, hand[0]) + maxSeats=tableResult['maxSeats'] + tableName=tableResult['tableName'] #part 5: final preparations, then call fpdb_save_to_db.saveHoldem with # the arrays as they are - that file will fill them. diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index ef132da8..2307e8f0 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -19,6 +19,9 @@ import datetime +PS=1 +FTP=2 + class DuplicateError(Exception): def __init__(self, value): self.value = value @@ -881,15 +884,23 @@ def parseSiteHandNo(topline): return topline[pos1:pos2] #end def parseSiteHandNo -def parseTableLine(line): +def parseTableLine(site, line): """returns a dictionary with maxSeats and tableName""" - pos1=line.find('\'')+1 - pos2=line.find('\'', pos1) - #print "table:",line[pos1:pos2] - pos3=pos2+2 - pos4=line.find("-max") - #print "seats:",line[pos3:pos4] - return {'maxSeats':int(line[pos3:pos4]), 'tableName':line[pos1:pos2]} + if site=="ps": + pos1=line.find('\'')+1 + pos2=line.find('\'', pos1) + #print "table:",line[pos1:pos2] + pos3=pos2+2 + pos4=line.find("-max") + #print "seats:",line[pos3:pos4] + return {'maxSeats':int(line[pos3:pos4]), 'tableName':line[pos1:pos2]} + elif site=="ftp": + pos1=line.find("Table ")+6 + pos2=line.find("-")-1 + #print "table:",line[pos1:pos2]+"end" + return {'maxSeats':9, 'tableName':line[pos1:pos2]} + else: + raise FpdbError("invalid site ID") #end def parseTableLine #returns the hand no assigned by the poker site From 928e7262f4a90336dc11edc4c1601adb02c79baa Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 15:41:34 +0100 Subject: [PATCH 056/262] p57 - added optional call to hud into importer changed config file to windows line endings started updating print_hand to new tables --- docs/default.conf | 21 +++---- docs/known-bugs-and-planned-features.txt | 6 +- pyfpdb/GuiAutoImport.py | 2 +- pyfpdb/GuiBulkImport.py | 2 +- pyfpdb/fpdb.py | 8 ++- pyfpdb/fpdb_import.py | 20 +++---- regression-test/print_hand.py | 73 +++++++++++++----------- 7 files changed, 75 insertions(+), 57 deletions(-) diff --git a/docs/default.conf b/docs/default.conf index 531f30da..031b8fb7 100644 --- a/docs/default.conf +++ b/docs/default.conf @@ -1,10 +1,11 @@ -db-backend=2 -db-host=localhost -db-databaseName=fpdb -db-user=fpdb -db-password=enterYourPwHere -tv-combinedStealFold=True -tv-combined2B3B=True -tv-combinedPostflop=True -bulkImport-defaultPath=default -hud-defaultPath=default +db-backend=2 +db-host=localhost +db-databaseName=fpdb +db-user=fpdb +db-password=enterYourPwHere +imp-callFpdbHud=True +tv-combinedStealFold=True +tv-combined2B3B=True +tv-combinedPostflop=True +bulkImport-defaultPath=default +hud-defaultPath=default diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 966c22df..9851e6e6 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,15 +3,17 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== -change config file to windows line endings seperate and improve instructions for update -expand instructions for profile file +update status or make a support matrix table for website add instructions for mailing list to contacts ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config re-run existing regression tests alpha3 ====== +export imp-callFpdbHud to .conf +make it work with postgres +expand instructions for profile file ftp: read maxSeats add sf.net logo to webpage separate db table design version and last bugfix in importer diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 585e72ae..3d345c29 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -54,7 +54,7 @@ class GuiAutoImport (threading.Thread): print "AutoImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file - fpdb_import.import_file_dict(self) + fpdb_import.import_file_dict(self, self.settings) print "GuiBulkImport.import_dir done" interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 1db64ba0..7b8aa7ab 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -32,7 +32,7 @@ class GuiBulkImport (threading.Thread): print "BulkImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file - fpdb_import.import_file_dict(self) + fpdb_import.import_file_dict(self, self.settings) print "GuiBulkImport.import_dir done" def load_clicked(self, widget, data=None): diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 8fedf631..06756328 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -248,6 +248,7 @@ class fpdb: self.settings['os']="windows" self.settings['tv-combinedStealFold']=True self.settings['tv-combined2B3B']=True + self.settings['imp-callFpdbHud']=True if self.settings['os']=="windows": self.settings['bulkImport-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\filename.txt" @@ -269,6 +270,11 @@ class fpdb: self.settings['db-user']=lines[i][8:-1] elif lines[i].startswith("db-password="): self.settings['db-password']=lines[i][12:-1] + elif lines[i].startswith("imp-callFpdbHud="): + if lines[i].find("True")!=-1: + self.settings['imp-callFpdbHud']=True + else: + self.settings['imp-callFpdbHud']=False elif lines[i].startswith("tv-combinedPostflop="): if lines[i].find("True")!=-1: self.settings['tv-combinedPostflop']=True @@ -370,7 +376,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p56") + self.window.set_title("Free Poker DB - version: alpha1+, p57") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index b6fcffd7..38f2cf04 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -34,9 +34,10 @@ def import_file(server, database, user, password, inputFile): self.user=user self.password=password self.inputFile=inputFile - import_file_dict(self) + self.settings={'imp-callFpdbHud':False} + import_file_dict(self, settings) -def import_file_dict(options): +def import_file_dict(options, settings): last_read_hand=0 if (options.inputFile=="stdin"): inputFile=sys.stdin @@ -99,15 +100,14 @@ def import_file_dict(options): hand=fpdb_simple.filterCrap(site, hand, isTourney) try: - if (category=="holdem" or category=="omahahi" or category=="omahahilo" or category=="razz" or category=="studhi" or category=="studhilo"): - if (category=="razz" or category=="studhi" or category=="studhilo"): - raise fpdb_simple.FpdbError ("stud/razz currently out of order") - last_read_hand=fpdb_parse_logic.mainParser(db, cursor, site, category, hand) - db.commit() - else: - raise fpdb_simple.FpdbError ("invalid category") - + if (category=="razz" or category=="studhi" or category=="studhilo"): + raise fpdb_simple.FpdbError ("stud/razz currently out of order") + last_read_hand=fpdb_parse_logic.mainParser(db, cursor, site, category, hand) + db.commit() + stored+=1 + if settings['imp-callFpdbHud']: + print "call to HUD here" db.commit() except fpdb_simple.DuplicateError: duplicates+=1 diff --git a/regression-test/print_hand.py b/regression-test/print_hand.py index b558ed96..fa73e9b3 100755 --- a/regression-test/print_hand.py +++ b/regression-test/print_hand.py @@ -23,7 +23,7 @@ from optparse import OptionParser import fpdb_util_lib as ful parser = OptionParser() -parser.add_option("-n", "--hand_number", "--hand", type="int", +parser.add_option("-n", "--handNumber", "--hand", type="int", help="Number of the hand to print") parser.add_option("-p", "--password", help="The password for the MySQL user") parser.add_option("-s", "--site", default="PokerStars", @@ -31,52 +31,61 @@ parser.add_option("-s", "--site", default="PokerStars", (options, sys.argv) = parser.parse_args() -if options.hand_number==None or options.site==None: +if options.handNumber==None or options.site==None: print "please supply a hand number and site name. TODO: make this work" db = MySQLdb.connect("localhost", "fpdb", options.password, "fpdb") cursor = db.cursor() print "Connected to MySQL on localhost. Print Hand Utility" -cursor.execute("SELECT id FROM sites WHERE name=%s", (options.site,)) -site_id=cursor.fetchone()[0] -print "options.site:",options.site,"site_id:",site_id +cursor.execute("SELECT id FROM Sites WHERE name=%s", (options.site,)) +siteId=cursor.fetchone()[0] +print "options.site:",options.site,"siteId:",siteId -cursor.execute("""SELECT hands.* FROM hands INNER JOIN gametypes -ON hands.gametype_id = gametypes.id WHERE gametypes.site_id=%s AND hands.site_hand_no=%s""", -(site_id, options.hand_number)) -hands_result=cursor.fetchone() -gametype_id=hands_result[2] -site_hand_no=options.hand_number -hand_id=hands_result[0] -hand_start=hands_result[3] -seat_count=hands_result[4] +print "" +print "From Table Hands" +print "================" + +cursor.execute("""SELECT Hands.* FROM Hands INNER JOIN Gametypes +ON Hands.gametypeId = Gametypes.id WHERE Gametypes.siteId=%s AND Hands.siteHandNo=%s""", +(siteId, options.handNumber)) +handsResult=cursor.fetchone() +handId=handsResult[0] +tableName=handsResult[1] +siteHandNo=options.handNumber +gametypeId=handsResult[3] +handStart=handsResult[4] +#skip importTime +seats=handsResult[6] +maxSeats=handsResult[7] +print "handId:", handId, " tableName:", tableName, " siteHandNo:", siteHandNo, " gametypeId:", gametypeId, " handStart:", handStart, " seats:", seats, " maxSeats:", maxSeats print "" -print "From Table gametypes" +print "From Table Gametypes" print "====================" -cursor.execute("""SELECT type, category, limit_type FROM gametypes WHERE id=%s""", - (gametype_id, )) -type_etc=cursor.fetchone() -type=type_etc[0] -category=type_etc[1] -limit_type=type_etc[2] -print "type:", type, " category:", category, " limit_type:", limit_type +cursor.execute("""SELECT type, base, category, limitType, hiLo FROM Gametypes WHERE id=%s""", (gametypeId, )) +typeEtc=cursor.fetchone() +type=typeEtc[0] +base=typeEtc[1] +category=typeEtc[2] +limitType=typeEtc[3] +hiLo=typeEtc[4] +print "type:", type, " base:", base, " category:", category, " limitType:", limitType, " hiLo:", hiLo -gt_string="" -do_bets=False -if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - cursor.execute("SELECT small_blind FROM gametypes WHERE id=%s", (gametype_id, )) +gtString="" +doBets=False +if base=="hold": + cursor.execute("SELECT smallBlind FROM Gametypes WHERE id=%s", (gametypeId, )) sb=cursor.fetchone()[0] - cursor.execute("SELECT big_blind FROM gametypes WHERE id=%s", (gametype_id, )) + cursor.execute("SELECT bigBlind FROM Gametypes WHERE id=%s", (gametypeId, )) bb=cursor.fetchone()[0] - gt_string=("sb: "+str(sb)+" bb: "+str(bb)) - if (limit_type=="fl"): - do_bets=True -elif (category=="razz" or category=="studhi" or category=="studhilo"): - do_bets=True + gtString=("sb: "+str(sb)+" bb: "+str(bb)) + if (limitType=="fl"): + doBets=True +elif base=="stud": + doBets=True if do_bets: cursor.execute("SELECT small_bet FROM gametypes WHERE id=%s", (gametype_id, )) From 999eac4019c58191ac23a20a3cee23083f6a96db Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 19:42:30 +0100 Subject: [PATCH 057/262] p58 - --- docs/known-bugs-and-planned-features.txt | 2 +- pyfpdb/GuiBulkImport.py | 5 +++-- pyfpdb/GuiTableViewer.py | 2 +- pyfpdb/fpdb.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 9851e6e6..fec10458 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,6 +3,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== +make windows use correct language version of Appdata, e.g. Anwendungdaten seperate and improve instructions for update update status or make a support matrix table for website add instructions for mailing list to contacts @@ -11,7 +12,6 @@ re-run existing regression tests alpha3 ====== -export imp-callFpdbHud to .conf make it work with postgres expand instructions for profile file ftp: read maxSeats diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 7b8aa7ab..f6583cac 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -80,14 +80,15 @@ class GuiBulkImport (threading.Thread): print "todo: implement bulk import thread" #end def run - def __init__(self, db, initial_path="/work/poker-histories/wine-ps/"): + def __init__(self, db, settings): self.db=db + self.settings=settings self.vbox=gtk.VBox(False,1) self.vbox.show() self.chooser = gtk.FileChooserWidget() - self.chooser.set_filename(initial_path) + self.chooser.set_filename(self.settings['bulkImport-defaultPath']) #chooser.set_default_response(gtk.RESPONSE_OK) #self.filesel.ok_button.connect_object("clicked", gtk.Widget.destroy, self.filesel) self.vbox.add(self.chooser) diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 0fb29a28..e67355e6 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -243,7 +243,7 @@ class GuiTableViewer (threading.Thread): self.minPrint=0 self.handCount=0 - self.last_read_hand=fpdb_import.import_file_dict(self) + self.last_read_hand=fpdb_import.import_file_dict(self, self.settings) #end def table_viewer.import_clicked def all_clicked(self, widget, data): diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 06756328..c7aacd45 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -343,7 +343,7 @@ class fpdb: def tab_bulk_import(self, widget, data): """opens a tab for bulk importing""" #print "start of tab_bulk_import" - new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings['bulkImport-defaultPath']) + new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings) self.threads.append(new_import_thread) bulk_tab=new_import_thread.get_vbox() self.add_and_display_tab(bulk_tab, "Bulk Import") From c716dfd35eb9adbadfafc46cd6d07897c1fe19f9 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 18 Aug 2008 23:53:25 +0100 Subject: [PATCH 058/262] p58 - added HUD from ray fpdb_parse now returns hand id rather than site hand no --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/Cards01.png | Bin 0 -> 78351 bytes pyfpdb/Configuration.py | 211 ++++++++++++++++++ pyfpdb/Database.py | 156 ++++++++++++++ pyfpdb/GuiAutoImport.py | 2 +- pyfpdb/HUD_config.xml.example | 120 +++++++++++ pyfpdb/HUD_main.py | 94 ++++++++ pyfpdb/HandHistory.py | 194 +++++++++++++++++ pyfpdb/Hud.py | 189 ++++++++++++++++ pyfpdb/Mucked.py | 241 +++++++++++++++++++++ pyfpdb/SQL.py | 262 +++++++++++++++++++++++ pyfpdb/Stats.py | 186 ++++++++++++++++ pyfpdb/Tables.py | 135 ++++++++++++ pyfpdb/fpdb_import.py | 8 +- pyfpdb/fpdb_save_to_db.py | 9 +- 15 files changed, 1798 insertions(+), 10 deletions(-) create mode 100644 pyfpdb/Cards01.png create mode 100644 pyfpdb/Configuration.py create mode 100644 pyfpdb/Database.py create mode 100644 pyfpdb/HUD_config.xml.example create mode 100755 pyfpdb/HUD_main.py create mode 100644 pyfpdb/HandHistory.py create mode 100644 pyfpdb/Hud.py create mode 100644 pyfpdb/Mucked.py create mode 100644 pyfpdb/SQL.py create mode 100644 pyfpdb/Stats.py create mode 100644 pyfpdb/Tables.py diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index fec10458..f6ae60dc 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -4,6 +4,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== make windows use correct language version of Appdata, e.g. Anwendungdaten +stop bulk importer from executing HUD hook seperate and improve instructions for update update status or make a support matrix table for website add instructions for mailing list to contacts diff --git a/pyfpdb/Cards01.png b/pyfpdb/Cards01.png new file mode 100644 index 0000000000000000000000000000000000000000..2c64fc9e756a7d6b42c5dfb266c3a8a9d03c6f3d GIT binary patch literal 78351 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

    kWJ$iiI|uNS6ouFw^0IhVcnVp!6N3 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 Jr(_fLe*jAO1TX*q literal 0 HcmV?d00001 diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py new file mode 100644 index 00000000..2ade6bee --- /dev/null +++ b/pyfpdb/Configuration.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +"""Configuration.py + +Handles HUD configuration files. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## + +# Standard Library modules +import xml.dom.minidom +from xml.dom.minidom import Node + +class Layout: + def __init__(self, max): + self.max = int(max) + self.location = [] + for i in range(self.max + 1): self.location.append(None) + + def __str__(self): + temp = " Layout = %d max, width= %d, height = %d, fav_seat = %d\n" % (self.max, self.width, self.height, self.fav_seat) + temp = temp + " Locations = " + for i in range(1, len(self.location)): + temp = temp + "(%d,%d)" % self.location[i] + + return temp + +class Site: + def __init__(self, node): + self.site_name = node.getAttribute("site_name") + self.table_finder = node.getAttribute("table_finder") + self.screen_name = node.getAttribute("screen_name") + self.site_path = node.getAttribute("site_path") + self.HH_path = node.getAttribute("HH_path") + self.decoder = node.getAttribute("decoder") + self.layout = {} + + for layout_node in node.getElementsByTagName('layout'): + max = int( layout_node.getAttribute('max') ) + lo = Layout(max) + lo.fav_seat = int( layout_node.getAttribute('fav_seat') ) + lo.width = int( layout_node.getAttribute('width') ) + lo.height = int( layout_node.getAttribute('height') ) + + for location_node in layout_node.getElementsByTagName('location'): + lo.location[int( location_node.getAttribute('seat') )] = (int( location_node.getAttribute('x') ), int( location_node.getAttribute('y'))) + + self.layout[lo.max] = lo + + def __str__(self): + temp = "Site = " + self.site_name + "\n" + for key in dir(self): + if key.startswith('__'): continue + if key == 'layout': continue + value = getattr(self, key) + if callable(value): continue + temp = temp + ' ' + key + " = " + value + "\n" + + for layout in self.layout: + temp = temp + "%s" % self.layout[layout] + + return temp + +class Stat: + def __init__(self): + pass + + def __str__(self): + temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click) + return temp + +class Game: + def __init__(self, node): + self.game_name = node.getAttribute("game_name") + self.db = node.getAttribute("db") + self.rows = int( node.getAttribute("rows") ) + self.cols = int( node.getAttribute("cols") ) + + self.stats = {} + for stat_node in node.getElementsByTagName('stat'): + stat = Stat() + stat.stat_name = stat_node.getAttribute("stat_name") + stat.row = int( stat_node.getAttribute("row") ) + stat.col = int( stat_node.getAttribute("col") ) + stat.tip = stat_node.getAttribute("tip") + stat.click = stat_node.getAttribute("click") + + self.stats[stat.stat_name] = stat + + def __str__(self): + temp = "Game = " + self.game_name + "\n" + temp = temp + " db = %s\n" % self.db + temp = temp + " rows = %d\n" % self.rows + temp = temp + " cols = %d\n" % self.cols + + for stat in self.stats.keys(): + temp = temp + "%s" % self.stats[stat] + + return temp + +class Database: + def __init__(self, node): + self.db_name = node.getAttribute("db_name") + self.db_server = node.getAttribute("db_server") + self.db_ip = node.getAttribute("db_ip") + self.db_user = node.getAttribute("db_user") + self.db_type = node.getAttribute("db_type") + self.db_pass = node.getAttribute("db_pass") + + def __str__(self): + temp = 'Database = ' + self.db_name + '\n' + for key in dir(self): + if key.startswith('__'): continue + value = getattr(self, key) + if callable(value): continue + temp = temp + ' ' + key + " = " + value + "\n" + return temp + +class Mucked: + def __init__(self, node): + self.name = node.getAttribute("mw_name") + self.cards = node.getAttribute("deck") + self.card_wd = node.getAttribute("card_wd") + self.card_ht = node.getAttribute("card_ht") + self.rows = node.getAttribute("rows") + self.cols = node.getAttribute("cols") + self.format = node.getAttribute("stud") + print "mw name = " + self.name + + def __str__(self): + temp = 'Mucked = ' + self.name + "\n" + for key in dir(self): + if key.startswith('__'): continue + value = getattr(self, key) + if callable(value): continue + temp = temp + ' ' + key + " = " + value + "\n" + return temp + +class Config: + def __init__(self, file = 'HUD_config.xml'): + + doc = xml.dom.minidom.parse(file) + + self.supported_sites = {} + self.supported_games = {} + self.supported_databases = {} + self.mucked_windows = {} + +# s_sites = doc.getElementsByTagName("supported_sites") + for site_node in doc.getElementsByTagName("site"): + site = Site(node = site_node) + self.supported_sites[site.site_name] = site + + s_games = doc.getElementsByTagName("supported_games") + for game_node in doc.getElementsByTagName("game"): + game = Game(node = game_node) + self.supported_games[game.game_name] = game + + s_dbs = doc.getElementsByTagName("supported_databases") + for db_node in doc.getElementsByTagName("database"): + db = Database(node = db_node) + self.supported_databases[db.db_name] = db + + s_dbs = doc.getElementsByTagName("mucked_windows") + for mw_node in doc.getElementsByTagName("mw"): + mw = Mucked(node = mw_node) + self.mucked_windows[mw.name] = mw + +if __name__== "__main__": + c = Config() + + print "\n----------- SUPPORTED SITES -----------" + for s in c.supported_sites.keys(): + print c.supported_sites[s] + + print "----------- END SUPPORTED SITES -----------" + + + print "\n----------- SUPPORTED GAMES -----------" + for game in c.supported_games.keys(): + print c.supported_games[game] + + print "----------- END SUPPORTED GAMES -----------" + + + print "\n----------- SUPPORTED DATABASES -----------" + for db in c.supported_databases.keys(): + print c.supported_databases[db] + + print "----------- END SUPPORTED DATABASES -----------" + + print "\n----------- MUCKED WINDOW FORMATS -----------" + for w in c.mucked_windows.keys(): + print c.mucked_windows[w] + + print "----------- END MUCKED WINDOW FORMATS -----------" diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py new file mode 100644 index 00000000..0d6db697 --- /dev/null +++ b/pyfpdb/Database.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +"""Database.py + +Create and manage the database objects. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# postmaster -D /var/lib/pgsql/data + +# Standard Library modules + +# pyGTK modules + +# FreePokerTools modules +import Configuration +import SQL + +# pgdb database module for posgres via DB-API +#import pgdb +# pgdb uses pyformat. is that fixed or an option? + +# mysql bindings +import MySQLdb + +class Database: + def __init__(self, c, db_name, game): + print "db_name = " + db_name + if c.supported_databases[db_name].db_server == 'postgresql': + self.connection = pgdb.connect(dsn = c.supported_databases[db_name].db_ip, + user = c.supported_databases[db_name].db_user, + password = c.supported_databases[db_name].db_pass, + database = c.supported_databases[db_name].db_name) + + elif c.supported_databases[db_name].db_server == 'mysql': + self.connection = MySQLdb.connect(host = c.supported_databases[db_name].db_ip, + user = c.supported_databases[db_name].db_user, + passwd = c.supported_databases[db_name].db_pass, + db = c.supported_databases[db_name].db_name) + + else: + print "Database not recognized." + return(0) + + self.type = c.supported_databases[db_name].db_type + self.sql = SQL.Sql(game = game, type = self.type) + + def close(self): + self.connection.close + + def get_table_name(self, hand_id): + c = self.connection.cursor() + c.execute(self.sql.query['get_table_name'], (hand_id)) + row = c.fetchone() + return row + + def get_last_hand(self): + c = self.connection.cursor() + c.execute(self.sql.query['get_last_hand']) + row = c.fetchone() + return row[0] + + def get_xml(self, hand_id): + c = self.connection.cursor() + c.execute(self.sql.query['get_xml'], (hand_id)) + row = c.fetchone() + return row[0] + + def get_recent_hands(self, last_hand): + c = self.connection.cursor() + c.execute(self.sql.query['get_recent_hands'], {'last_hand': last_hand}) + return c.fetchall() + + def get_hand_info(self, new_hand_id): + c = self.connection.cursor() + c.execute(self.sql.query['get_hand_info'], new_hand_id) + return c.fetchall() + + def get_cards(self, hand): + c = self.connection.cursor() + c.execute(self.sql.query['get_cards'], hand) + colnames = [desc[0] for desc in c.description] + cards = {} + for row in c.fetchall(): + s_dict = {} + for name, val in zip(colnames, row): + s_dict[name] = val + cards[s_dict['seat_number']] = s_dict + return (cards) + + def get_stats_from_hand(self, hand, hero): + c = self.connection.cursor() + +# get the players in the hand and their seats + c.execute(self.sql.query['get_players_from_hand'], (hand)) + names = {} + seats = {} + for row in c.fetchall(): + names[row[0]] = row[2] + seats[row[0]] = row[1] + +# now get the stats + c.execute(self.sql.query['get_stats_from_hand'], (hand, hand)) + colnames = [desc[0] for desc in c.description] + stat_dict = {} + for row in c.fetchall(): + t_dict = {} + for name, val in zip(colnames, row): + t_dict[name] = val +# print t_dict + t_dict['screen_name'] = names[t_dict['player_id']] + t_dict['seat'] = seats[t_dict['player_id']] + stat_dict[t_dict['player_id']] = t_dict + return stat_dict + + def get_player_id(self, config, site, player_name): + print "site = %s, player name = %s" % (site, player_name) + c = self.connection.cursor() + c.execute(self.sql.query['get_player_id'], {'player': player_name, 'site': site}) + row = c.fetchone() + return row[0] + +if __name__=="__main__": + c = Configuration.Config() + + db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem +# db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz +# db_connection = Database(c, 'ptracks', 'razz') # postgres + print "database connection object = ", db_connection.connection + print "database type = ", db_connection.type + + h = db_connection.get_last_hand() + print "last hand = ", h + + hero = db_connection.get_player_id(c, 'PokerStars', 'nutOmatic') + print "nutOmatic is id_player = %d" % hero + + stat_dict = db_connection.get_stats_from_hand(h, hero) + for p in stat_dict.keys(): + print p, " ", stat_dict[p] + db_connection.close diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 3d345c29..af34ad58 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -55,7 +55,7 @@ class GuiAutoImport (threading.Thread): else: self.inputFile=self.path+os.sep+file fpdb_import.import_file_dict(self, self.settings) - print "GuiBulkImport.import_dir done" + print "GuiAutoImport.import_dir done" interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) time.sleep(interval) diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example new file mode 100644 index 00000000..7d76688f --- /dev/null +++ b/pyfpdb/HUD_config.xml.example @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py new file mode 100755 index 00000000..1f71625b --- /dev/null +++ b/pyfpdb/HUD_main.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +"""Hud_main.py + +Main for FreePokerTools HUD. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# to do kill window on my seat +# to do adjust for preferred seat +# to do allow window resizing +# to do hud to echo, but ignore non numbers +# to do kill a hud +# to do no hud window for hero +# to do single click to display detailed stats +# to do things to add to config.xml +# to do font and size + +# Standard Library modules +import sys +import os + +# pyGTK modules +import pygtk +import gtk +import gobject + +# FreePokerTools modules +import Configuration +import Database +import Tables +import Hud + +# global dict for keeping the huds +hud_dict = {} +db_connection = 0; +config = 0; + +def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() + +def process_new_hand(source, condition): +# there is a new hand_id to be processed +# read the hand_id from stdin and strip whitespace + new_hand_id = sys.stdin.readline() + new_hand_id = new_hand_id.rstrip() + db_connection = Database.Database(config, 'fpdb', 'holdem') + + (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) +# if a hud for this table exists, just update it + if hud_dict.has_key(table_name): + hud_dict[table_name].update(new_hand_id, db_connection, config) +# otherwise create a new hud + else: + table_windows = Tables.discover(config) + for t in table_windows.keys(): + if table_windows[t].name == table_name: + hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_connection) + hud_dict[table_name].create(new_hand_id, config) + hud_dict[table_name].update(new_hand_id, db_connection, config) + break +# print "table name \"%s\" not identified, no hud created" % (table_name) + return(1) + +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() + + config = Configuration.Config() + + db_connection = Database.Database(config, 'fpdb', 'holdem') + + s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) + + gtk.main() diff --git a/pyfpdb/HandHistory.py b/pyfpdb/HandHistory.py new file mode 100644 index 00000000..dd2fa427 --- /dev/null +++ b/pyfpdb/HandHistory.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +"""HandHistory.py + +Parses HandHistory xml files and returns requested objects. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## +# Standard Library modules +import xml.dom.minidom +from xml.dom.minidom import Node + +class HandHistory: + def __init__(self, xml_string, elements = ('ALL')): + + doc = xml.dom.minidom.parseString(xml_string) + if elements == ('ALL'): + elements = ('BETTING', 'AWARDS', 'POSTS', 'PLAYERS', 'GAME') + + if 'BETTING' in elements: + self.BETTING = Betting(doc.getElementsByTagName('BETTING')[0]) + if 'AWARDS' in elements: + self.AWARDS = Awards (doc.getElementsByTagName('AWARDS')[0]) + if 'POSTS' in elements: + self.POSTS = Posts (doc.getElementsByTagName('POSTS')[0]) + if 'GAME' in elements: + self.GAME = Game (doc.getElementsByTagName('GAME')[0]) + if 'PLAYERS' in elements: + self.PLAYERS = {} + p_n = doc.getElementsByTagName('PLAYERS')[0] + for p in p_n.getElementsByTagName('PLAYER'): + a_player = Player(p) + self.PLAYERS[a_player.name] = a_player + +class Player: + def __init__(self, node): + self.name = node.getAttribute('NAME') + self.seat = node.getAttribute('SEAT') + self.stack = node.getAttribute('STACK') + self.showed_hand = node.getAttribute('SHOWED_HAND') + self.cards = node.getAttribute('CARDS') + self.allin = node.getAttribute('ALLIN') + self.sitting_out = node.getAttribute('SITTING_OUT') + self.hand = node.getAttribute('HAND') + self.start_cards = node.getAttribute('START_CARDS') + + if self.allin == '' or \ + self.allin == '0' or \ + self.allin.upper() == 'FALSE': self.allin = False + else: self.allin = True + + if self.sitting_out == '' or \ + self.sitting_out == '0' or \ + self.sitting_out.upper() == 'FALSE': self.sitting_out = False + else: self.sitting_out = True + + def __str__(self): + temp = "%s\n seat = %s\n stack = %s\n cards = %s\n" % \ + (self.name, self.seat, self.stack, self.cards) + temp = temp + " showed_hand = %s\n allin = %s\n" % \ + (self.showed_hand, self.allin) + temp = temp + " hand = %s\n start_cards = %s\n" % \ + (self.hand, self.start_cards) + return temp + +class Awards: + def __init__(self, node): + self.awards = [] # just an array of award objects + for a in node.getElementsByTagName('AWARD'): + self.awards.append(Award(a)) + + def __str__(self): + temp = "" + for a in self.awards: + temp = temp + "%s\n" % (a) + return temp + +class Award: + def __init__(self, node): + self.player = node.getAttribute('PLAYER') + self.amount = node.getAttribute('AMOUNT') + self.pot = node.getAttribute('POT') + + def __str__(self): + return self.player + " won " + self.amount + " from " + self.pot + +class Game: + def __init__(self, node): + print node + self.tags = {} + for tag in ( ('GAME_NAME', 'game_name'), ('MAX', 'max'), ('HIGHLOW', 'high_low'), + ('STRUCTURE', 'structure'), ('MIXED', 'mixed') ): + L = node.getElementsByTagName(tag[0]) + if (not L): continue + print L + for node2 in L: + title = "" + for node3 in node2.childNodes: + if (node3.nodeType == Node.TEXT_NODE): + title +=node3.data + self.tags[tag[1]] = title + + def __str__(self): + return "%s %s %s, (%s max), %s" % (self.tags['structure'], + self.tags['game_name'], + self.tags['game_name'], + self.tags['max'], + self.tags['game_name']) + +class Posts: + def __init__(self, node): + self.posts = [] # just an array of post objects + for p in node.getElementsByTagName('POST'): + self.posts.append(Post(p)) + + def __str__(self): + temp = "" + for p in self.posts: + temp = temp + "%s\n" % (p) + return temp + +class Post: + def __init__(self, node): + self.player = node.getAttribute('PLAYER') + self.amount = node.getAttribute('AMOUNT') + self.posted = node.getAttribute('POSTED') + self.live = node.getAttribute('LIVE') + + def __str__(self): + return ("%s posted %s %s %s") % (self.player, self.amount, self.posted, self.live) + +class Betting: + def __init__(self, node): + self.rounds = [] # a Betting object is just an array of rounds + for r in node.getElementsByTagName('ROUND'): + self.rounds.append(Round(r)) + + def __str__(self): + temp = "" + for r in self.rounds: + temp = temp + "%s\n" % (r) + return temp + +class Round: + def __init__(self, node): + self.name = node.getAttribute('ROUND_NAME') + self.action = [] + for a in node.getElementsByTagName('ACTION'): + self.action.append(Action(a)) + + def __str__(self): + temp = self.name + "\n" + for a in self.action: + temp = temp + " %s\n" % (a) + return temp + +class Action: + def __init__(self, node): + self.player = node.getAttribute('PLAYER') + self.action = node.getAttribute('ACT') + self.amount = node.getAttribute('AMOUNT') + self.allin = node.getAttribute('ALLIN') + + def __str__(self): + return self.player + " " + self.action + " " + self.amount + " " + self.allin + +if __name__== "__main__": + file = open('test.xml', 'r') + xml_string = file.read() + file.close() + + print xml_string + "\n\n\n" + h = HandHistory(xml_string, ('ALL')) + print h.GAME + print h.POSTS + print h.BETTING + print h.AWARDS + + for p in h.PLAYERS.keys(): + print h.PLAYERS[p] \ No newline at end of file diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py new file mode 100644 index 00000000..9b35f283 --- /dev/null +++ b/pyfpdb/Hud.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +"""Hud.py + +Create and manage the hud overlays. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## +# Standard Library modules + +# pyGTK modules +import pygtk +import gtk +import pango +import gobject + +# FreePokerTools modules +import Tables # needed for testing only +import Configuration +import Stats +import Mucked +import Database + +class Hud: + + def __init__(self, table, max, poker_game, config, db_connection): + self.table = table + self.config = config + self.poker_game = poker_game + self.max = max + self.db_connection = db_connection + + self.stat_windows = {} + self.font = pango.FontDescription("Sans 8") + + + def create(self, hand, config): +# update this hud, to the stats and players as of "hand" +# hand is the hand id of the most recent hand played at this table +# +# this method also manages the creating and destruction of stat +# windows via calls to the Stat_Window class + for i in range(1, self.max + 1): + (x, y) = config.supported_sites[self.table.site].layout[self.max].location[i] + self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], + table = self.table, + x = x, + y = y, + seat = i, + player_id = 'fake', + font = self.font) + + self.stats = [['', '', ''], ['', '', '']] + for stat in config.supported_games[self.poker_game].stats.keys(): + self.stats[config.supported_games[self.poker_game].stats[stat].row] \ + [config.supported_games[self.poker_game].stats[stat].col] = \ + config.supported_games[self.poker_game].stats[stat].stat_name + +# self.mucked_window = gtk.Window() +# self.m = Mucked.Mucked(self.mucked_window, self.db_connection) +# self.mucked_window.show_all() + + def update(self, hand, db, config): + stat_dict = db.get_stats_from_hand(hand, 3) + for s in stat_dict.keys(): + for r in range(0, 2): + for c in range(0, 3): + number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) + self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(number[1]) + tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \ + number[3] + ", " + number[4] + Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip) +# self.m.update(hand) + +class Stat_Window: + + def button_press_cb(self, widget, event, *args): +# 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. + if event.button == 1: # left button event + if event.type == gtk.gdk.BUTTON_PRESS: # left button single click + if self.sb_click > 0: return + self.sb_click = gobject.timeout_add(250, self.single_click) + elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click + if self.sb_click > 0: + gobject.source_remove(self.sb_click) + self.sb_click = 0 + self.double_click(widget, event, *args) + + if event.button == 2: # middle button event + print "middle button clicked" + + if event.button == 3: # right button event + print "right button clicked" + + def single_click(self): +# Callback from the timeout in the single-click finding part of the +# button press call back. This needs to be modified to get all the +# arguments from the call. + print "left button clicked" + self.sb_click = 0 + return False + + def double_click(self, widget, event, *args): + self.toggle_decorated(widget) + + def toggle_decorated(self, widget): + top = widget.get_toplevel() + (x, y) = top.get_position() + + if top.get_decorated(): + top.set_decorated(0) + top.move(x, y) + else: + top.set_decorated(1) + top.move(x, y) + + def __init__(self, game, table, seat, x, y, player_id, font): + self.game = game + self.table = table + self.x = x + table.x + self.y = y + table.y + self.player_id = player_id + self.sb_click = 0 + + self.window = gtk.Window() + self.window.set_decorated(0) + self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.window.set_title("%s" % seat) + + self.grid = gtk.Table(rows = self.game.rows, columns = self.game.cols, homogeneous = False) + self.window.add(self.grid) + + self.e_box = [] + self.frame = [] + self.label = [] + for r in range(self.game.rows): + self.e_box.append([]) + self.label.append([]) + for c in range(self.game.cols): + self.e_box[r].append( gtk.EventBox() ) + Stats.do_tip(self.e_box[r][c], 'farts') + self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 1, ypadding = 1) + self.label[r].append( gtk.Label('xxx') ) + self.e_box[r][c].add(self.label[r][c]) + self.e_box[r][c].connect("button_press_event", self.button_press_cb) +# font = pango.FontDescription("Sans 8") + self.label[r][c].modify_font(font) + self.window.realize + self.window.move(self.x, self.y) + self.window.set_keep_above(1) + self.window.show_all() + +def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() + +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) + db = Database.Database(c, 'PTrackSv2', 'razz') + + for t in tables: + win = Hud(t, 8, c, db) +# t.get_details() + win.update(8300, db, c) + + gtk.main() diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py new file mode 100644 index 00000000..a1f76f87 --- /dev/null +++ b/pyfpdb/Mucked.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python +"""Mucked.py + +Mucked cards display for FreePokerTools HUD. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# to do +# problem with hand 30586 + +# Standard Library modules +import sys +import os +import string +import xml.dom.minidom +from xml.dom.minidom import Node + +# pyGTK modules +import pygtk +import gtk +import gobject + +# FreePokerTools modules +import Configuration +import Database +import Tables +import Hud +import Mucked +import HandHistory + +class Mucked: + def __init__(self, parent, db_connection): + + self.parent = parent #this is the parent of the mucked cards widget + self.db_connection = db_connection + + self.vbox = gtk.VBox() + self.parent.add(self.vbox) + + self.mucked_list = MuckedList (self.vbox, db_connection) + self.mucked_cards = MuckedCards(self.vbox, db_connection) + self.mucked_list.mucked_cards = self.mucked_cards + + def update(self, new_hand_id): + self.mucked_list.update(new_hand_id) + +class MuckedList: + def __init__(self, parent, db_connection): + + self.parent = parent + self.db_connection = db_connection + +# set up a scrolled window to hold the listbox + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) + self.parent.add(self.scrolled_window) + +# create a ListStore to use as the model + self.liststore = gtk.ListStore(str, str, str) + self.treeview = gtk.TreeView(self.liststore) + self.tvcolumn0 = gtk.TreeViewColumn('HandID') + self.tvcolumn1 = gtk.TreeViewColumn('Cards') + self.tvcolumn2 = gtk.TreeViewColumn('Net') + +# add tvcolumn to treeview + self.treeview.append_column(self.tvcolumn0) + self.treeview.append_column(self.tvcolumn1) + self.treeview.append_column(self.tvcolumn2) + +# create a CellRendererText to render the data + self.cell = gtk.CellRendererText() + + # add the cell to the tvcolumn and allow it to expand + self.tvcolumn0.pack_start(self.cell, True) + self.tvcolumn1.pack_start(self.cell, True) + self.tvcolumn2.pack_start(self.cell, True) + self.tvcolumn0.add_attribute(self.cell, 'text', 0) + self.tvcolumn1.add_attribute(self.cell, 'text', 1) + self.tvcolumn2.add_attribute(self.cell, 'text', 2) +# resize the cols if nec + self.tvcolumn0.set_resizable(True) + self.treeview.connect("row-activated", self.activated_event) + + self.scrolled_window.add_with_viewport(self.treeview) + + def activated_event(self, path, column, data=None): + sel = self.treeview.get_selection() + (model, iter) = sel.get_selected() + self.mucked_cards.update(model.get_value(iter, 0)) + + def update(self, new_hand_id): + info_row = self.db_connection.get_hand_info(new_hand_id) + iter = self.liststore.append(info_row[0]) + sel = self.treeview.get_selection() + sel.select_iter(iter) + + vadj = self.scrolled_window.get_vadjustment() + vadj.set_value(vadj.upper) + self.mucked_cards.update(new_hand_id) + +class MuckedCards: + def __init__(self, parent, db_connection): + + self.parent = parent #this is the parent of the mucked cards widget + self.db_connection = db_connection + + self.card_images = self.get_card_images() + self.seen_cards = {} + self.grid_contents = {} + self.eb = {} + + self.rows = 8 + self.cols = 7 + self.grid = gtk.Table(self.rows, self.cols + 4, homogeneous = False) + + for r in range(0, self.rows): + for c in range(0, self.cols): + self.seen_cards[(c, r)] = gtk.image_new_from_pixbuf(self.card_images[('B', 'S')]) + self.eb[(c, r)]= gtk.EventBox() + +# set up the contents for the cells + for r in range(0, self.rows): + self.grid_contents[( 0, r)] = gtk.Label("%d" % (r + 1)) + self.grid_contents[( 1, r)] = gtk.Label("player %d" % (r + 1)) + self.grid_contents[( 4, r)] = gtk.Label("-") + self.grid_contents[( 9, r)] = gtk.Label("-") + self.grid_contents[( 2, r)] = self.eb[( 0, r)] + self.grid_contents[( 3, r)] = self.eb[( 1, r)] + self.grid_contents[( 5, r)] = self.eb[( 2, r)] + self.grid_contents[( 6, r)] = self.eb[( 3, r)] + self.grid_contents[( 7, r)] = self.eb[( 4, r)] + self.grid_contents[( 8, r)] = self.eb[( 5, r)] + self.grid_contents[(10, r)] = self.eb[( 6, r)] + for c in range(0, self.cols): + self.eb[(c, r)].add(self.seen_cards[(c, r)]) + +# add the cell contents to the table + for c in range(0, self.cols + 4): + for r in range(0, self.rows): + self.grid.attach(self.grid_contents[(c, r)], c, c+1, r, r+1, xpadding = 1, ypadding = 1) + + self.parent.add(self.grid) + + def update(self, new_hand_id): + cards = self.db_connection.get_cards(new_hand_id) + self.clear() + + for c in cards.keys(): + self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name']) + + for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'), + (4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')): + if not cards[c][i[1]] == "": + self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \ + set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])]) + + xml_text = self.db_connection.get_xml(new_hand_id) + hh = HandHistory.HandHistory(xml_text, ('BETTING')) + +# action in tool tips for 3rd street cards + tip = "%s" % hh.BETTING.rounds[0] + for c in (0, 1, 2): + for r in range(0, self.rows): + self.eb[(c, r)].set_tooltip_text(tip) + +# action in tools tips for later streets + round_to_col = (0, 3, 4, 5, 6) + for round in range(1, len(hh.BETTING.rounds)): + tip = "%s" % hh.BETTING.rounds[round] + for r in range(0, self.rows): + self.eb[(round_to_col[round], r)].set_tooltip_text(tip) + + def split_cards(self, card): + return (card[0], card[1].upper()) + + def clear(self): + for r in range(0, self.rows): + self.grid_contents[(1, r)].set_text(" ") + for c in range(0, 7): + self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')]) + self.eb[(c, r)].set_tooltip_text('') + def get_card_images(self): + card_images = {} + suits = ('S', 'H', 'D', 'C') + ranks = ('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'B') + pb = gtk.gdk.pixbuf_new_from_file("Cards01.png") + + for j in range(0, 14): + for i in range(0, 4): + temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42) + pb.copy_area(30*j, 42*i, 30, 42, temp_pb, 0, 0) + card_images[(ranks[j], suits[i])] = temp_pb + return(card_images) + +# cards are 30 wide x 42 high + +if __name__== "__main__": + + def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() # used only for testing + + def process_new_hand(source, condition): #callback from stdin watch -- testing only +# there is a new hand_id to be processed +# just read it and pass it to update + new_hand_id = sys.stdin.readline() + new_hand_id = new_hand_id.rstrip() # remove trailing whitespace + m.update(new_hand_id) + return(True) + + print "system = %s" % (os.name) + + config = Configuration.Config() + db_connection = Database.Database(config, 'PTrackSv2', 'razz') + + main_window = gtk.Window() + main_window.set_keep_above(True) + main_window.connect("destroy", destroy) + + m = Mucked(main_window, db_connection) + main_window.show_all() + + s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) + + gtk.main() + diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py new file mode 100644 index 00000000..ffd02ea6 --- /dev/null +++ b/pyfpdb/SQL.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +"""SQL.py + +Set up all of the SQL statements for a given game and database type. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# Standard Library modules + +# pyGTK modules + +# FreePokerTools modules + +class Sql: + + def __init__(self, game = 'holdem', type = 'PT3'): + self.query = {} + + if game == 'razz' and type == 'ptracks': + + self.query['get_table_name'] = "select table_name from game where game_id = %s" + + self.query['get_last_hand'] = "select max(game_id) from game" + + self.query['get_recent_hands'] = "select game_id from game where game_id > %(last_hand)d" + + self.query['get_xml'] = "select xml from hand_history where game_id = %s" + + self.query['get_player_id'] = """ + select player_id from players + where screen_name = %(player)s + """ + + self.query['get_hand_info'] = """ + SELECT + game_id, + CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, + total_won-total_bet AS net + FROM game_players + WHERE game_id = %s AND player_id = 3 + """ + + self.query['get_cards'] = """ + select + seat_number, + screen_name, + hole_card_1, + hole_card_2, + hole_card_3, + hole_card_4, + hole_card_5, + hole_card_6, + hole_card_7 + from game_players, players + where game_id = %s and game_players.player_id = players.player_id + order by seat_number + """ + + self.query['get_stats_from_hand'] = """ + SELECT player_id, + count(*) AS n, + sum(pre_fourth_raise_n) AS pfr, + sum(fourth_raise_n) AS raise_n_2, + sum(fourth_ck_raise_n) AS cr_n_2, + sum(fifth_bet_raise_n) AS br_n_3, + sum(fifth_bet_ck_raise_n) AS cr_n_3, + sum(sixth_bet_raise_n) AS br_n_4, + sum(sixth_bet_ck_raise_n) AS cr_n_4, + sum(river_bet_raise_n) AS br_n_5, + sum(river_bet_ck_raise_n) AS cr_n_5, + sum(went_to_showdown_n) AS sd, + sum(saw_fourth_n) AS saw_f, + sum(raised_first_pf) AS first_pfr, + sum(vol_put_money_in_pot) AS vpip, + sum(limp_with_prev_callers) AS limp_w_callers, + + sum(ppossible_actions) AS poss_a_pf, + sum(pfold) AS fold_pf, + sum(pcheck) AS check_pf, + sum(praise) AS raise_pf, + sum(pcall) AS raise_pf, + sum(limp_call_reraise_pf) AS limp_call_pf, + + sum(pfr_check) AS check_after_raise, + sum(pfr_call) AS call_after_raise, + sum(pfr_fold) AS fold_after_raise, + sum(pfr_bet) AS bet_after_raise, + sum(pfr_raise) AS raise_after_raise, + sum(folded_to_river_bet) AS fold_to_r_bet, + + sum(fpossible_actions) AS poss_a_2, + sum(ffold) AS fold_2, + sum(fcheck) AS check_2, + sum(fbet) AS bet_2, + sum(fraise) AS raise_2, + sum(fcall) AS raise_2, + + sum(fifpossible_actions) AS poss_a_3, + sum(fiffold) AS fold_3, + sum(fifcheck) AS check_3, + sum(fifbet) AS bet_3, + sum(fifraise) AS raise_3, + sum(fifcall) AS call_3, + + sum(spossible_actions) AS poss_a_4, + sum(sfold) AS fold_4, + sum(scheck) AS check_4, + sum(sbet) AS bet_4, + sum(sraise) AS raise_4, + sum(scall) AS call_4, + + sum(rpossible_actions) AS poss_a_5, + sum(rfold) AS fold_5, + sum(rcheck) AS check_5, + sum(rbet) AS bet_5, + sum(rraise) AS raise_5, + sum(rcall) AS call_5, + + sum(cold_call_pf) AS cc_pf, + sum(saw_fifth_n) AS saw_3, + sum(saw_sixth_n) AS saw_4, + sum(saw_river_n) AS saw_5 + FROM game_players + WHERE player_id in + (SELECT player_id FROM game_players + WHERE game_id = %s AND NOT player_id = %s) + GROUP BY player_id + """ +# alternate form of WHERE for above +# WHERE game_id = %(hand)d AND NOT player_id = %(hero)d) +# WHERE game_id = %s AND NOT player_id = %s) + + self.query['get_players_from_hand'] = """ + SELECT game_players.player_id, seat_number, screen_name + FROM game_players INNER JOIN players ON (game_players.player_id = players.player_id) + WHERE game_id = %s + """ + + if game == 'holdem' and type == 'PT3': + + self.query['get_last_hand'] = "select max(id_hand) from holdem_hand_summary" + + self.query['get_player_id'] = """ + select id_player from player, lookup_sites + where player_name = %(player)s + and site_name = %(site)s + and player.id_site = lookup_sites.id_site + """ + + self.query['get_stats_from_hand'] = """ + select id_player AS player_id, + count(*) AS n, + sum(CAST (flg_vpip AS integer)) as vpip, + sum(CAST (flg_p_first_raise AS integer)) as p_first_raise, + sum(CAST (flg_f_saw AS integer)) as f_saw, + sum(CAST (flg_p_open AS integer)) as p_open, + sum(CAST (flg_p_limp AS integer)) as p_limp, + sum(CAST (flg_p_fold AS integer)) as p_fold, + sum(CAST (flg_p_ccall AS integer)) as p_ccall, + sum(CAST (flg_f_bet AS integer)) as f_bet, + sum(CAST (flg_f_first_raise AS integer)) as f_first_raise, + sum(CAST (flg_f_check AS integer)) as f_check, + sum(CAST (flg_f_check_raise AS integer)) as f_check_raise, + sum(CAST (flg_f_fold AS integer)) as f_fold, + sum(CAST (flg_f_saw AS integer)) as f_saw, + sum(CAST (flg_t_bet AS integer)) as t_bet, + sum(CAST (flg_t_first_raise AS integer)) as t_first_raise, + sum(CAST (flg_t_check AS integer)) as t_check, + sum(CAST (flg_t_check_raise AS integer)) as t_check_raise, + sum(CAST (flg_t_fold AS integer)) as t_fold, + sum(CAST (flg_t_saw AS integer)) as t_saw, + sum(CAST (flg_r_bet AS integer)) as r_bet, + sum(CAST (flg_r_first_raise AS integer)) as r_first_raise, + sum(CAST (flg_r_check AS integer)) as r_check, + sum(CAST (flg_r_check_raise AS integer)) as r_check_raise, + sum(CAST (flg_r_fold AS integer)) as r_fold, + sum(CAST (flg_r_saw AS integer)) as r_saw, + sum(CAST (flg_sb_steal_fold AS integer)) as sb_steal_fold, + sum(CAST (flg_bb_steal_fold AS integer)) as bb_steal_fold, + sum(CAST (flg_blind_def_opp AS integer)) as blind_def_opp, + sum(CAST (flg_steal_att AS integer)) as steal_att, + sum(CAST (flg_steal_opp AS integer)) as steal_opp, + sum(CAST (flg_blind_k AS integer)) as blind_k, + sum(CAST (flg_showdown AS integer)) as showdown, + + sum(CAST (flg_p_squeeze AS integer)) as p_squeeze, + sum(CAST (flg_p_squeeze_opp AS integer)) as p_squeeze_opp, + sum(CAST (flg_p_squeeze_def_opp AS integer)) as p_squeeze_def_opp, + + sum(CAST (flg_f_cbet AS integer)) as f_cbet, + sum(CAST (flg_f_cbet_opp AS integer)) as f_cbet_opp, + sum(CAST (flg_f_cbet_def_opp AS integer)) as f_cbet_def_opp + + from holdem_hand_player_statistics + where id_hand = %(hand)d and not id_player = %(hero)d + group by id_player + """ + + if game == 'holdem' and type == 'fpdb': + + self.query['get_last_hand'] = "select max(id) from Hands" + + self.query['get_player_id'] = """ + select Players.id AS player_id from Players, Sites + where Players.name = %(player)s + and Sites.name = %(site)s + and Players.SiteId = Sites.id + """ + + self.query['get_stats_from_hand'] = """ + SELECT HudCache.playerId AS player_id, + sum(HDs) AS n, + sum(street0Aggr) AS pfr, + sum(street0VPI) AS vpip, + sum(sawShowdown) AS sd, + sum(wonAtSD) AS wmsd, + sum(street1Seen) AS saw_f, + sum(totalProfit) AS net + FROM HudCache, Hands + WHERE HudCache.PlayerId in + (SELECT PlayerId FROM HandsPlayers + WHERE handId = %s) + AND Hands.id = %s + AND Hands.gametypeId = HudCache.gametypeId + GROUP BY HudCache.PlayerId + """ + + self.query['get_players_from_hand'] = """ + SELECT HandsPlayers.playerId, seatNo, name + FROM HandsPlayers INNER JOIN Players ON (HandsPlayers.playerId = Players.id) + WHERE handId = %s + """ + + self.query['get_table_name'] = """ + select tableName, maxSeats, category + from Hands,Gametypes + where Hands.id = %s + and Gametypes.id = Hands.gametypeId + """ + +if __name__== "__main__": +# just print the default queries and exit + s = Sql(game = 'razz', type = 'ptracks') + for key in s.query: + print "For query " + key + ", sql =" + print s.query[key] diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py new file mode 100644 index 00000000..edde911d --- /dev/null +++ b/pyfpdb/Stats.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python + +"""Manage collecting and formatting of stats and tooltips. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## +# Standard Library modules + +# pyGTK modules +import pygtk +import gtk + +# FreePokerTools modules +import Configuration +import Database + +def do_tip(widget, tip): + widget.set_tooltip_text(tip) + +def do_stat(stat_dict, player = 24, stat = 'vpip'): + return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) +# OK, for reference the tuple returned by the stat is: +# 0 - The stat, raw, no formating, eg 0.33333333 +# 1 - formatted stat with appropriate precision and punctuation, eg 33% +# 2 - formatted stat with appropriate precision, punctuation and a hint, eg v=33% +# 3 - same as #2 except name of stat instead of hint, eg vpip=33% +# 4 - the calculation that got the stat, eg 9/27 +# 5 - the name of the stat, useful for a tooltip, eg vpip + +########################################### +# functions that return individual stats +def vpip(stat_dict, player): + stat = 0.0 + try: + stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'v=%3.1f' % (100*stat) + '%', + 'vpip=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']), + 'vpip' + ) + except: return (stat, + '%3.1f' % (0) + '%', + 'w=%3.1f' % (0) + '%', + 'wtsd=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'wtsd' + ) + +def pfr(stat_dict, player): + stat = 0.0 + try: + stat = float(stat_dict[player]['pfr'])/float(stat_dict[player]['n']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'p=%3.1f' % (100*stat) + '%', + 'pfr=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']), + 'pfr' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'w=%3.1f' % (0) + '%', + 'wtsd=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'wtsd' + ) + +def wtsd(stat_dict, player): + stat = 0.0 + try: + stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'w=%3.1f' % (100*stat) + '%', + 'wtsd=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['sd'], stat_dict[player]['saw_f']), + 'wtsd' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'w=%3.1f' % (0) + '%', + 'wtsd=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'wtsd' + ) + + +def saw_f(stat_dict, player): + try: + num = float(stat_dict[player]['saw_f']) + den = float(stat_dict[player]['n']) + stat = num/den + return (stat, + '%3.1f' % (100*stat) + '%', + 'sf=%3.1f' % (100*stat) + '%', + 'saw_f=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['saw_f'], stat_dict[player]['n']), + 'saw_f' + ) + except: + stat = 0.0 + num = 0 + den = 0 + return (stat, + '%3.1f' % (stat) + '%', + 'sf=%3.1f' % (stat) + '%', + 'saw_f=%3.1f' % (stat) + '%', + '(%d/%d)' % (num, den), + 'saw_f' + ) + +def n(stat_dict, player): + try: + return (stat_dict[player]['n'], + '%d' % (stat_dict[player]['n']), + 'n=%d' % (stat_dict[player]['n']), + 'n=%d' % (stat_dict[player]['n']), + '(%d)' % (stat_dict[player]['n']), + 'number hands seen' + ) + except: + return (stat_dict[player][0], + '%d' % (stat_dict[player][0]), + 'n=%d' % (stat_dict[player][0]), + 'n=%d' % (stat_dict[player][0]), + '(%d)' % (stat_dict[player][0]), + 'number hands seen' + ) + +def fold_f(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['fold_2']/stat_dict[player]['saw_f'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff=%3.1f' % (100*stat) + '%', + 'fold_f=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['fold_2'], stat_dict[player]['saw_f']), + 'folded fourth' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff=%3.1f' % (0) + '%', + 'fold_f=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'folded fourth' + ) + + +if __name__=="__main__": + c = Configuration.Config() + db_connection = Database.Database(c, 'fpdb', 'holdem') + h = db_connection.get_last_hand() + stat_dict = db_connection.get_stats_from_hand(h, 0) + + for player in stat_dict.keys(): + print "player = ", player, do_stat(stat_dict, player = player, stat = 'vpip') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'pfr') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'wtsd') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'saw_f') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'n') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f') + + db_connection.close + diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py new file mode 100644 index 00000000..6c53a590 --- /dev/null +++ b/pyfpdb/Tables.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +"""Discover_Tables.py + +Inspects the currently open windows and finds those of interest to us--that is +poker table windows from supported sites. Returns a list +of Table_Window objects representing the windows found. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## + +# Standard Library modules +import os +import re + +# FreePokerTools modules +import Configuration + +class Table_Window: + def __str__(self): +# __str__ method for testing + temp = 'TableWindow object\n' + temp = temp + " name = %s\n site = %s\n number = %s\n title = %s\n" % (self.name, self.site, self.number, self.title) + temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) + temp = temp + " width = %d\n height = %d\n x = %d\n y = %d\n" % (self.width, self.height, self.x, self.y) + if getattr(self, 'tournament', 0): + temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table) + return temp + + def get_details(table): + table.game = 'razz' + table.max = 8 + table.struture = 'limit' + table.tournament = 0 + +def discover(c): + tables = {} + for listing in os.popen('xwininfo -root -tree').readlines(): + if re.search('Lobby', listing): continue + if re.search('Instant Hand History', listing): continue + if not re.search('Logged In as ', listing): continue + for s in c.supported_sites.keys(): + if re.search(c.supported_sites[s].table_finder, listing): + mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) + if mo.group(2) == '(has no name)': continue + if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup + tw = Table_Window() + tw.site = c.supported_sites[s].site_name + tw.number = mo.group(1) + tw.title = mo.group(2) + tw.width = int( mo.group(3) ) + tw.height = int( mo.group(4) ) + tw.x = int (mo.group(5) ) + tw.y = int (mo.group(6) ) + tw.title = re.sub('\"', '', tw.title) +# this rather ugly hack makes my fake table used for debugging work + if tw.title == "PokerStars.py": continue + +# use this eval thingie to call the title bar decoder specified in the config file + eval("%s(tw)" % c.supported_sites[s].decoder) + tables[tw.name] = tw + return tables + +def pokerstars_decode_table(tw): +# extract the table name OR the tournament number and table name from the title +# other info in title is redundant with data in the database + title_bits = re.split(' - ', tw.title) + name = title_bits[0] + mo = re.search('Tournament (\d+) Table (\d+)', name) + if mo: + tw.tournament = int( mo.group(1) ) + tw.table = int( mo.group(2) ) + tw.name = name + else: + tw.tournament = None + for pattern in [' no all-in', ' fast', ',']: + name = re.sub(pattern, '', name) + name = re.sub('\s+$', '', name) + tw.name = name + + mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball)', tw.title) + +#Traceback (most recent call last): +# File "/home/fatray/razz-poker-productio/HUD_main.py", line 41, in process_new_hand +# table_windows = Tables.discover(config) +# File "/home/fatray/razz-poker-productio/Tables.py", line 58, in discover +# eval("%s(tw)" % c.supported_sites[s].decoder) +# File "", line 1, in +# File "/home/fatray/razz-poker-productio/Tables.py", line 80, in pokerstars_decode_table +# tw.game = mo.group(1).lower() +#AttributeError: 'NoneType' object has no attribute 'group' +# +#This problem happens with observed windows!! + + tw.game = mo.group(1).lower() + tw.game = re.sub('\'', '', tw.game) + tw.game = re.sub('h/l', 'hi/lo', tw.game) + + mo = re.search('(No Limit|Pot Limit)', tw.title) + if mo: + tw.structure = mo.group(1).lower() + else: + tw.structure = 'limit' + + tw.max = None + if tw.game in ('razz', 'stud', 'stud hi/lo'): + tw.max = 8 + elif tw.game in ('5-card draw', 'triple draw 2-7 lowball'): + tw.max = 6 + elif tw.game == 'holdem': + pass + elif tw.game in ('omaha', 'omaha hi/lo'): + pass + +if __name__=="__main__": + c = Configuration.Config() + tables = discover(c) + + for t in tables.keys(): + print tables[t] \ No newline at end of file diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 38f2cf04..6bee055c 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -102,12 +102,12 @@ def import_file_dict(options, settings): try: if (category=="razz" or category=="studhi" or category=="studhilo"): raise fpdb_simple.FpdbError ("stud/razz currently out of order") - last_read_hand=fpdb_parse_logic.mainParser(db, cursor, site, category, hand) + handsId=fpdb_parse_logic.mainParser(db, cursor, site, category, hand) db.commit() stored+=1 if settings['imp-callFpdbHud']: - print "call to HUD here" + print "call to HUD here. handsId:",handsId db.commit() except fpdb_simple.DuplicateError: duplicates+=1 @@ -156,13 +156,13 @@ def import_file_dict(options, settings): for line_no in range(len(lines)): if lines[line_no].find("Game #")!=-1: final_game_line=lines[line_no] - last_read_hand=fpdb_simple.parseSiteHandNo(final_game_line) + handsId=fpdb_simple.parseSiteHandNo(final_game_line) #todo: this will cause return of an unstored hand number if the last hadn was error or partial db.commit() inputFile.close() cursor.close() db.close() - return last_read_hand + return handsId if __name__ == "__main__": diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index a5016904..a3ec95a5 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -34,7 +34,7 @@ def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) - return site_hand_no + return hands_id #end def ring_stud def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): @@ -51,7 +51,7 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) - return site_hand_no + return hands_id #end def ring_holdem_omaha def tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId, siteId, #end of tourney specific params @@ -72,7 +72,7 @@ def tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) - return site_hand_no + return hands_id #end def tourney_holdem_omaha def tourney_stud(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, @@ -95,6 +95,5 @@ def tourney_stud(cursor, category, site_tourney_no, buyin, fee, knockout, entrie fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) - return site_hand_no + return hands_id #end def tourney_stud - From eff5c9cc6e1f18c451981f6e8f6a3bf5805e5f82 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 19 Aug 2008 00:18:17 +0100 Subject: [PATCH 059/262] p59 - hud update from ray --- pyfpdb/HUD_config.xml.example | 57 ++--------------------------------- pyfpdb/HUD_main.py | 5 +++ 2 files changed, 7 insertions(+), 55 deletions(-) diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index 7d76688f..ca650270 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -2,7 +2,7 @@ - + @@ -45,51 +45,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -100,17 +55,9 @@ - - - - - - - - - + diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 1f71625b..99ad9f5c 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -79,6 +79,11 @@ def process_new_hand(source, condition): return(1) if __name__== "__main__": + + if not os.name == 'posix': + print "This version of the HUD only works with Linux or compatible.\nHUD exiting." + sys.exit() + main_window = gtk.Window() main_window.connect("destroy", destroy) label = gtk.Label('Fake main window, blah blah, blah\nblah, blah') From d73a4614f35e2d9df729816747b37d7e82a242bc Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 19 Aug 2008 23:38:01 +0100 Subject: [PATCH 060/262] p60 - patches from ray - autoimport now doesnt block GUI and calls HUD. --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/GuiAutoImport.py | 25 ++++++++++++++++++++---- pyfpdb/fpdb_import.py | 2 ++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index f6ae60dc..06ae9033 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,6 +3,7 @@ Everything is subject to change and especially the order will often change. Patc alpha2 (release by 17Aug) ====== +set hud stats row1: hands, VPIP, PFR, PF3B/4B, ST row2: Aggr% (postflop), Fold% (postflop), W$sF, WtSD, W$@SD make windows use correct language version of Appdata, e.g. Anwendungdaten stop bulk importer from executing HUD hook seperate and improve instructions for update diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index af34ad58..42f5bf6d 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -16,9 +16,12 @@ #agpl-3.0.txt in the docs folder of the package. import threading +import subprocess + import pygtk pygtk.require('2.0') import gtk +import gobject import os import time import fpdb_import @@ -45,21 +48,35 @@ class GuiAutoImport (threading.Thread): dia_chooser.destroy() #end def GuiAutoImport.browseClicked + def do_import(self): + """Callback for timer to do an import iteration.""" + fpdb_import.import_file_dict(self, self.settings) + return(1) + def startClicked(self, widget, data): """runs when user clicks start on auto import tab""" - + +# Check to see if we have an open file handle to the HUD and open one if we do not. +# bufsize = 1 means unbuffered +# We need to close this file handle sometime. + try: #uhhh, I don't this this is the best way to check for the existence of an attr + getattr(self, "pipe_to_hud") + except AttributeError: + cwd = os.getcwd() + command = os.path.join(cwd, 'HUD_main.py') + self.pipe_to_hud = subprocess.Popen(command, bufsize = 1, stdin = subprocess.PIPE) + self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) for file in os.listdir(self.path): if os.path.isdir(file): print "AutoImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file - fpdb_import.import_file_dict(self, self.settings) + self.do_import() print "GuiAutoImport.import_dir done" interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) - time.sleep(interval) - self.startClicked(widget,data) + gobject.timeout_add(interval*1000, self.do_import) #end def GuiAutoImport.browseClicked def get_vbox(self): diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 6bee055c..82c7d968 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -108,6 +108,8 @@ def import_file_dict(options, settings): stored+=1 if settings['imp-callFpdbHud']: print "call to HUD here. handsId:",handsId +# pipe the Hands.id out to the HUD + options.pipe_to_hud.stdin.write("%s\n" % (handsId)) db.commit() except fpdb_simple.DuplicateError: duplicates+=1 From 0b4e7c66cbaf0f0bb32a2b5be5ed32576ae97d43 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 20 Aug 2008 17:48:54 +0100 Subject: [PATCH 061/262] p61- corrected blocker error in win install instructions - thanks Ryan Hayward for pointing this out :) --- website/docs-install-windows.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs-install-windows.php b/website/docs-install-windows.php index b1827ac7..6bf3236a 100644 --- a/website/docs-install-windows.php +++ b/website/docs-install-windows.php @@ -30,7 +30,7 @@ The length of these instructions is due to MS refusal to provide any meaningful - Unzip the archive, execute the setup file
    At the end make sure you activate that you want to configure it now.
    Use the advanced/detailed config. Leave everything as default unless stated below, or unless you have reason not to.
    - Make sure to DEACTIVATE TCP/IP networking, unless you want that and know how to secure it
    + Make sure to ACTIVATE TCP/IP networking, but you should block external connections (ie. anything not from localhost) to MySQL unless you're running the DB on a different machine.
    Set a root password. Note that this is not the account/pw that fpdb will use.

    Once finished it shold confirm "service started successfully"
    From 66d938accccb5b20bd2e1aa96a69346148c84b54 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 20 Aug 2008 20:29:08 +0100 Subject: [PATCH 062/262] p62 - major patch from ray with new stats in HUD added db todo file with the things that are left for the db --- docs/db-todo.txt | 22 ++ docs/known-bugs-and-planned-features.txt | 3 + docs/tabledesign.html | 4 +- pyfpdb/Configuration.py | 1 - pyfpdb/Database.py | 1 - pyfpdb/Hud.py | 11 +- pyfpdb/Mucked.py | 2 - pyfpdb/SQL.py | 95 ++---- pyfpdb/Stats.py | 418 ++++++++++++++++++++++- 9 files changed, 479 insertions(+), 78 deletions(-) create mode 100644 docs/db-todo.txt diff --git a/docs/db-todo.txt b/docs/db-todo.txt new file mode 100644 index 00000000..9d8c0042 --- /dev/null +++ b/docs/db-todo.txt @@ -0,0 +1,22 @@ +stats to add +============ +*CC,TotalColdCalls,TimesFacingRaisePreflop +**Folded to 3-Bet,FoldedToThreeBetPreflop,FacedThreeBetPreflop +Called 3-Bet,CalledThreeBetPreflop,FacedThreeBetPreflop +*Winning %,wonhand,TotalHands +**Flop Aggression Factor,timesaggressiveflop,timescalledflop +**Turn Aggression Factor,timesaggressiveturn,timescalledturn +**River Aggression Factor,timesaggressiveriver,timescalledriver +scoopedPot (useful for hi/lo split games) + +couldSqueeze, didSqueeze, facedSqueeze, calledSqueeze -> if (player raises from EP) and (someone after him calls) and (someone after that goes all in) +Donk Bet Turn,DidDonkBetTurn,CouldDonkBetTurn -> EP raises, LP reraises, EP calls. If EP bets out after the flop, it is considered a donk bet +Float Turn,DidFloatTurn,CouldFloatTurn -> + +other +===== +look at PT3 and HM tables see if i forgot anything +store all-in somewhere +add somewhere: mixed flag to denote that the game is part of a mixed game rotation + +add tables for single draw (5card draw, 2-7 single draw, badugi) and tripple draw (2-7 tripple draw) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 06ae9033..612da0dc 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,8 +1,10 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and especially the order will often change. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. +Please also see db-todo.txt alpha2 (release by 17Aug) ====== +auto import only runs on one file per start set hud stats row1: hands, VPIP, PFR, PF3B/4B, ST row2: Aggr% (postflop), Fold% (postflop), W$sF, WtSD, W$@SD make windows use correct language version of Appdata, e.g. Anwendungdaten stop bulk importer from executing HUD hook @@ -11,6 +13,7 @@ update status or make a support matrix table for website add instructions for mailing list to contacts ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config re-run existing regression tests +move conf file out of profiles folder alpha3 ====== diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 3a89ef9b..5411d230 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -434,7 +434,9 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - + diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 2ade6bee..849d595a 100644 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -140,7 +140,6 @@ class Mucked: self.rows = node.getAttribute("rows") self.cols = node.getAttribute("cols") self.format = node.getAttribute("stud") - print "mw name = " + self.name def __str__(self): temp = 'Mucked = ' + self.name + "\n" diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 0d6db697..8d10e8a1 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -40,7 +40,6 @@ import MySQLdb class Database: def __init__(self, c, db_name, game): - print "db_name = " + db_name if c.supported_databases[db_name].db_server == 'postgresql': self.connection = pgdb.connect(dsn = c.supported_databases[db_name].db_ip, user = c.supported_databases[db_name].db_user, diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 9b35f283..6ac1c763 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -65,7 +65,10 @@ class Hud: player_id = 'fake', font = self.font) - self.stats = [['', '', ''], ['', '', '']] + self.stats = [] + for i in range(0, config.supported_games[self.poker_game].rows + 1): + row_list = [''] * config.supported_games[self.poker_game].cols + self.stats.append(row_list) for stat in config.supported_games[self.poker_game].stats.keys(): self.stats[config.supported_games[self.poker_game].stats[stat].row] \ [config.supported_games[self.poker_game].stats[stat].col] = \ @@ -78,8 +81,10 @@ class Hud: def update(self, hand, db, config): stat_dict = db.get_stats_from_hand(hand, 3) for s in stat_dict.keys(): - for r in range(0, 2): - for c in range(0, 3): +# for r in range(0, 2): +# for c in range(0, 3): + for r in range(0, config.supported_games[self.poker_game].rows): + for c in range(0, config.supported_games[self.poker_game].cols): number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(number[1]) tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \ diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py index a1f76f87..292fd58d 100644 --- a/pyfpdb/Mucked.py +++ b/pyfpdb/Mucked.py @@ -223,8 +223,6 @@ if __name__== "__main__": m.update(new_hand_id) return(True) - print "system = %s" % (os.name) - config = Configuration.Config() db_connection = Database.Database(config, 'PTrackSv2', 'razz') diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index ffd02ea6..4e22f270 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -152,67 +152,7 @@ class Sql: WHERE game_id = %s """ - if game == 'holdem' and type == 'PT3': - - self.query['get_last_hand'] = "select max(id_hand) from holdem_hand_summary" - - self.query['get_player_id'] = """ - select id_player from player, lookup_sites - where player_name = %(player)s - and site_name = %(site)s - and player.id_site = lookup_sites.id_site - """ - - self.query['get_stats_from_hand'] = """ - select id_player AS player_id, - count(*) AS n, - sum(CAST (flg_vpip AS integer)) as vpip, - sum(CAST (flg_p_first_raise AS integer)) as p_first_raise, - sum(CAST (flg_f_saw AS integer)) as f_saw, - sum(CAST (flg_p_open AS integer)) as p_open, - sum(CAST (flg_p_limp AS integer)) as p_limp, - sum(CAST (flg_p_fold AS integer)) as p_fold, - sum(CAST (flg_p_ccall AS integer)) as p_ccall, - sum(CAST (flg_f_bet AS integer)) as f_bet, - sum(CAST (flg_f_first_raise AS integer)) as f_first_raise, - sum(CAST (flg_f_check AS integer)) as f_check, - sum(CAST (flg_f_check_raise AS integer)) as f_check_raise, - sum(CAST (flg_f_fold AS integer)) as f_fold, - sum(CAST (flg_f_saw AS integer)) as f_saw, - sum(CAST (flg_t_bet AS integer)) as t_bet, - sum(CAST (flg_t_first_raise AS integer)) as t_first_raise, - sum(CAST (flg_t_check AS integer)) as t_check, - sum(CAST (flg_t_check_raise AS integer)) as t_check_raise, - sum(CAST (flg_t_fold AS integer)) as t_fold, - sum(CAST (flg_t_saw AS integer)) as t_saw, - sum(CAST (flg_r_bet AS integer)) as r_bet, - sum(CAST (flg_r_first_raise AS integer)) as r_first_raise, - sum(CAST (flg_r_check AS integer)) as r_check, - sum(CAST (flg_r_check_raise AS integer)) as r_check_raise, - sum(CAST (flg_r_fold AS integer)) as r_fold, - sum(CAST (flg_r_saw AS integer)) as r_saw, - sum(CAST (flg_sb_steal_fold AS integer)) as sb_steal_fold, - sum(CAST (flg_bb_steal_fold AS integer)) as bb_steal_fold, - sum(CAST (flg_blind_def_opp AS integer)) as blind_def_opp, - sum(CAST (flg_steal_att AS integer)) as steal_att, - sum(CAST (flg_steal_opp AS integer)) as steal_opp, - sum(CAST (flg_blind_k AS integer)) as blind_k, - sum(CAST (flg_showdown AS integer)) as showdown, - - sum(CAST (flg_p_squeeze AS integer)) as p_squeeze, - sum(CAST (flg_p_squeeze_opp AS integer)) as p_squeeze_opp, - sum(CAST (flg_p_squeeze_def_opp AS integer)) as p_squeeze_def_opp, - - sum(CAST (flg_f_cbet AS integer)) as f_cbet, - sum(CAST (flg_f_cbet_opp AS integer)) as f_cbet_opp, - sum(CAST (flg_f_cbet_def_opp AS integer)) as f_cbet_def_opp - - from holdem_hand_player_statistics - where id_hand = %(hand)d and not id_player = %(hero)d - group by id_player - """ - - if game == 'holdem' and type == 'fpdb': + if type == 'fpdb': self.query['get_last_hand'] = "select max(id) from Hands" @@ -231,6 +171,39 @@ class Sql: sum(sawShowdown) AS sd, sum(wonAtSD) AS wmsd, sum(street1Seen) AS saw_f, + sum(stealAttemptChance) AS steal_opp, + sum(stealAttempted) AS steal, + sum(foldedSbToSteal) AS SBnotDef, + sum(foldedBbToSteal) AS BBnotDef, + sum(foldBbToStealChance) AS SBstolen, + sum(foldBbToStealChance) AS BBstolen, + sum(street0_3B4BChance) AS TB_opp_0, + sum(street0_3B4BDone) AS TB_0, + sum(street1Seen) AS saw_1, + sum(street2Seen) AS saw_2, + sum(street3Seen) AS saw_3, + sum(street4Seen) AS saw_4, + sum(street1Aggr) AS aggr_1, + sum(street2Aggr) AS aggr_2, + sum(street3Aggr) AS aggr_3, + sum(street4Aggr) AS aggr_4, + sum(otherRaisedStreet1) AS was_raised_1, + sum(otherRaisedStreet2) AS was_raised_2, + sum(otherRaisedStreet3) AS was_raised_3, + sum(otherRaisedStreet4) AS was_raised_4, + sum(foldToOtherRaisedStreet1) AS f_freq_1, + sum(foldToOtherRaisedStreet1) AS f_freq_2, + sum(foldToOtherRaisedStreet1) AS f_freq_3, + sum(foldToOtherRaisedStreet1) AS f_freq_4, + sum(wonWhenSeenStreet1) AS w_w_s_1, + sum(street1CBChance) AS CB_opp_1, + sum(street2CBChance) AS CB_opp_2, + sum(street3CBChance) AS CB_opp_3, + sum(street4CBChance) AS CB_opp_4, + sum(street1CBDone) AS CB_1, + sum(street2CBDone) AS CB_2, + sum(street3CBDone) AS CB_3, + sum(street4CBDone) AS CB_4, sum(totalProfit) AS net FROM HudCache, Hands WHERE HudCache.PlayerId in diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py index edde911d..4f989898 100644 --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -20,6 +20,28 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## + +# How to write a new stat: +# 1 You can see a listing of all the raw stats (e.g., from the HudCache table) +# by running Database.py as a stand along program. You need to combine +# those raw stats to get stats to present to the HUD. If you need more +# information than is in the HudCache table, then you have to write SQL. +# 2 The raw stats seen when you run Database.py are available in the Stats.py +# in the stat_dict dict. For example the number of vpips would be +# stat_dict[player]['vpip']. So the % vpip is +# float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']). You can see how the +# keys of stat_dict relate to the column names in HudCache by inspecting +# the proper section of the SQL.py module. +# 3 You have to write a small function for each stat you want to add. See +# the vpip() function for example. This function has to be protected from +# exceptions, using something like the try:/except: paragraphs in vpip. +# 4 The name of the function has to be the same as the of the stat used +# in the config file. +# 5 The stat functions have a peculiar return value, which is outlined in +# the do_stat function. This format is useful for tool tips and maybe +# other stuff. +# 6 For each stat you make add a line to the __main__ function to test it. + # Standard Library modules # pyGTK modules @@ -78,10 +100,10 @@ def pfr(stat_dict, player): except: return (stat, '%3.1f' % (0) + '%', - 'w=%3.1f' % (0) + '%', - 'wtsd=%3.1f' % (0) + '%', + 'p=%3.1f' % (0) + '%', + 'pfr=%3.1f' % (0) + '%', '(%d/%d)' % (0, 0), - 'wtsd' + 'pfr' ) def wtsd(stat_dict, player): @@ -93,7 +115,7 @@ def wtsd(stat_dict, player): 'w=%3.1f' % (100*stat) + '%', 'wtsd=%3.1f' % (100*stat) + '%', '(%d/%d)' % (stat_dict[player]['sd'], stat_dict[player]['saw_f']), - 'wtsd' + '% went to showdown' ) except: return (stat, @@ -101,9 +123,30 @@ def wtsd(stat_dict, player): 'w=%3.1f' % (0) + '%', 'wtsd=%3.1f' % (0) + '%', '(%d/%d)' % (0, 0), - 'wtsd' + '% went to showdown' ) +def wmsd(stat_dict, player): + stat = 0.0 + try: + stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'w=%3.1f' % (100*stat) + '%', + 'wmsd=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['wmsd'], stat_dict[player]['sd']), + '% won money at showdown' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'w=%3.1f' % (0) + '%', + 'wmsd=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% won money at showdown' + ) + + def saw_f(stat_dict, player): try: @@ -156,7 +199,7 @@ def fold_f(stat_dict, player): 'ff=%3.1f' % (100*stat) + '%', 'fold_f=%3.1f' % (100*stat) + '%', '(%d/%d)' % (stat_dict[player]['fold_2'], stat_dict[player]['saw_f']), - 'folded fourth' + 'folded flop/4th' ) except: return (stat, @@ -164,11 +207,350 @@ def fold_f(stat_dict, player): 'ff=%3.1f' % (0) + '%', 'fold_f=%3.1f' % (0) + '%', '(%d/%d)' % (0, 0), - 'folded fourth' + 'folded flop/4th' ) +def steal(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['steal']/stat_dict[player]['steal_opp'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'st=%3.1f' % (100*stat) + '%', + 'steal=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['steal'], stat_dict[player]['steal_opp']), + '% steal attempted' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'st=%3.1f' % (0) + '%', + 'steal=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% steal attempted' + ) -if __name__=="__main__": +def f_SB_steal(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['SBnotDef']/stat_dict[player]['SBstolen'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'fSB=%3.1f' % (100*stat) + '%', + 'fSB_s=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['SBnotDef'], stat_dict[player]['SBstolen']), + '% folded SB to steal' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'fSB=%3.1f' % (0) + '%', + 'fSB_s=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% folded SB to steal' + ) + +def f_BB_steal(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['BBnotDef']/stat_dict[player]['BBstolen'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'fBB=%3.1f' % (100*stat) + '%', + 'fBB_s=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['BBnotDef'], stat_dict[player]['BBstolen']), + '% folded BB to steal' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'fBB=%3.1f' % (0) + '%', + 'fBB_s=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% folded BB to steal' + ) + +def three_B_0(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['TB_0']/stat_dict[player]['TB_opp_0'] + return (stat, + '%3.1f' % (100*stat) + '%', + '3B=%3.1f' % (100*stat) + '%', + '3B_pf=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['TB_0'], stat_dict[player]['TB_opp_0']), + '% 3/4 Bet preflop/3rd' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + '3B=%3.1f' % (0) + '%', + '3B_pf=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% 3/4 Bet preflop/3rd' + ) + +def WMsF(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['w_w_s_1']/stat_dict[player]['saw_f'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'wf=%3.1f' % (100*stat) + '%', + 'w_w_f=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['w_w_s_1'], stat_dict[player]['saw_f']), + '% won$/saw flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'wf=%3.1f' % (0) + '%', + 'w_w_f=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% won$/saw flop/4th' + ) + +def a_freq_1(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['aggr_1']/stat_dict[player]['saw_f'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'a1=%3.1f' % (100*stat) + '%', + 'a_fq_1=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_f']), + 'Aggression Freq flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a1=%3.1f' % (0) + '%', + 'a_fq_1=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq flop/4th' + ) + +def a_freq_2(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['aggr_2']/stat_dict[player]['saw_2'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'a2=%3.1f' % (100*stat) + '%', + 'a_fq_2=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_2'], stat_dict[player]['saw_2']), + 'Aggression Freq turn/5th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a2=%3.1f' % (0) + '%', + 'a_fq_2=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq turn/5th' + ) + +def a_freq_3(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['aggr_3']/stat_dict[player]['saw_3'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'a3=%3.1f' % (100*stat) + '%', + 'a_fq_3=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_1']), + 'Aggression Freq river/6th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a3=%3.1f' % (0) + '%', + 'a_fq_3=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq river/6th' + ) + +def a_freq_4(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['aggr_4']/stat_dict[player]['saw_4'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'a4=%3.1f' % (100*stat) + '%', + 'a_fq_4=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_4'], stat_dict[player]['saw_4']), + 'Aggression Freq 7th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a1=%3.1f' % (0) + '%', + 'a_fq_1=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq flop/4th' + ) + +def cb_1(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['CB_1']/stat_dict[player]['CB_opp_1'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb1=%3.1f' % (100*stat) + '%', + 'cb_1=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_1'], stat_dict[player]['CB_opp_1']), + '% continuation bet flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb1=%3.1f' % (0) + '%', + 'cb_1=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet flop/4th' + ) + +def cb_2(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['CB_2']/stat_dict[player]['CB_opp_2'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb2=%3.1f' % (100*stat) + '%', + 'cb_2=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_2'], stat_dict[player]['CB_opp_2']), + '% continuation bet turn/5th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb2=%3.1f' % (0) + '%', + 'cb_2=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet turn/5th' + ) + +def cb_3(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['CB_3']/stat_dict[player]['CB_opp_3'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb3=%3.1f' % (100*stat) + '%', + 'cb_3=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_3'], stat_dict[player]['CB_opp_3']), + '% continuation bet river/6th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb3=%3.1f' % (0) + '%', + 'cb_3=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet river/6th' + ) + +def cb_4(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['CB_4']/stat_dict[player]['CB_opp_4'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb4=%3.1f' % (100*stat) + '%', + 'cb_4=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_4'], stat_dict[player]['CB_opp_4']), + '% continuation bet 7th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb4=%3.1f' % (0) + '%', + 'cb_4=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet 7th' + ) + +def ffreq_1(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['f_freq_1']/stat_dict[player]['was_raised_1'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff1=%3.1f' % (100*stat) + '%', + 'ff_1=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_1'], stat_dict[player]['was_raised_1']), + '% fold frequency flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff1=%3.1f' % (0) + '%', + 'ff_1=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency flop/4th' + ) + +def ffreq_2(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['f_freq_2']/stat_dict[player]['was_raised_2'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff2=%3.1f' % (100*stat) + '%', + 'ff_2=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_2'], stat_dict[player]['was_raised_2']), + '% fold frequency turn/5th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff2=%3.1f' % (0) + '%', + 'ff_2=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency turn/5th' + ) + +def ffreq_3(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['f_freq_3']/stat_dict[player]['was_raised_3'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff3=%3.1f' % (100*stat) + '%', + 'ff_3=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_3'], stat_dict[player]['was_raised_3']), + '% fold frequency river/6th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff3=%3.1f' % (0) + '%', + 'ff_3=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency river/6th' + ) + +def ffreq_4(stat_dict, player): + stat = 0.0 + try: + stat = stat_dict[player]['f_freq_4']/stat_dict[player]['was_raised_4'] + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff4=%3.1f' % (100*stat) + '%', + 'ff_4=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_4'], stat_dict[player]['was_raised_4']), + '% fold frequency 7th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff4=%3.1f' % (0) + '%', + 'ff_4=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency 7th' + ) + +if __name__== "__main__": c = Configuration.Config() db_connection = Database.Database(c, 'fpdb', 'holdem') h = db_connection.get_last_hand() @@ -181,6 +563,24 @@ if __name__=="__main__": print "player = ", player, do_stat(stat_dict, player = player, stat = 'saw_f') print "player = ", player, do_stat(stat_dict, player = player, stat = 'n') print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f') - + print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B_0') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_1') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_2') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_3') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_4') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_1') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_2') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_3') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_4') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_1') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_2') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4') + db_connection.close From 7c50ddd542b7ab4e1dbca156767a58afaae34acd Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 20 Aug 2008 22:10:29 +0100 Subject: [PATCH 063/262] p63 - fixed slight error causing wrong info in fold flop/turn/river in HUD --- docs/known-bugs-and-planned-features.txt | 2 +- pyfpdb/SQL.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 612da0dc..81748231 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -5,7 +5,7 @@ Please also see db-todo.txt alpha2 (release by 17Aug) ====== auto import only runs on one file per start -set hud stats row1: hands, VPIP, PFR, PF3B/4B, ST row2: Aggr% (postflop), Fold% (postflop), W$sF, WtSD, W$@SD +set hud stats default row1: hands, VPIP, PFR, PF3B/4B, ST row2: Aggr% (postflop), Fold% (postflop), W$sF, WtSD, W$@SD make windows use correct language version of Appdata, e.g. Anwendungdaten stop bulk importer from executing HUD hook seperate and improve instructions for update diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index 4e22f270..e1af9c9b 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -192,9 +192,9 @@ class Sql: sum(otherRaisedStreet3) AS was_raised_3, sum(otherRaisedStreet4) AS was_raised_4, sum(foldToOtherRaisedStreet1) AS f_freq_1, - sum(foldToOtherRaisedStreet1) AS f_freq_2, - sum(foldToOtherRaisedStreet1) AS f_freq_3, - sum(foldToOtherRaisedStreet1) AS f_freq_4, + sum(foldToOtherRaisedStreet2) AS f_freq_2, + sum(foldToOtherRaisedStreet3) AS f_freq_3, + sum(foldToOtherRaisedStreet4) AS f_freq_4, sum(wonWhenSeenStreet1) AS w_w_s_1, sum(street1CBChance) AS CB_opp_1, sum(street2CBChance) AS CB_opp_2, From 6689054cc70c738deab09871d4548353a36cb395 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 20 Aug 2008 22:14:38 +0100 Subject: [PATCH 064/262] p64 - made default interval for auto import a more sensible 10s --- pyfpdb/fpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index c7aacd45..8894068b 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -257,7 +257,7 @@ class fpdb: self.settings['bulkImport-defaultPath'] = os.path.expanduser("~") + "/.wine/drive_c/Program Files/PokerStars/HandHistory/filename.txt" self.settings['hud-defaultPath'] = os.path.expanduser("~")+"/.wine/drive_c/Program Files/PokerStars/HandHistory/" - self.settings['hud-defaultInterval']=30 + self.settings['hud-defaultInterval']=10 for i in range(len(lines)): if lines[i].startswith("db-backend="): From 6879815320b55cc21b65242160ae7bead5fb2c61 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 20 Aug 2008 22:47:43 +0100 Subject: [PATCH 065/262] p64 - fixed bug that HUD displayed wrong raw data for aggr river/6th even though it used the right numbers for calculating the % value attempted to fix W$wsF in HUD but didnt work made default interval for auto import a more sensible 10s --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/Stats.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 81748231..2feb12ac 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -4,6 +4,7 @@ Please also see db-todo.txt alpha2 (release by 17Aug) ====== +W$wsF doesnt work in HUD auto import only runs on one file per start set hud stats default row1: hands, VPIP, PFR, PF3B/4B, ST row2: Aggr% (postflop), Fold% (postflop), W$sF, WtSD, W$@SD make windows use correct language version of Appdata, e.g. Anwendungdaten diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py index 4f989898..96ddd5ad 100644 --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -293,12 +293,12 @@ def three_B_0(stat_dict, player): def WMsF(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['w_w_s_1']/stat_dict[player]['saw_f'] + stat = stat_dict[player]['w_w_s_1']/stat_dict[player]['saw_1'] return (stat, '%3.1f' % (100*stat) + '%', 'wf=%3.1f' % (100*stat) + '%', 'w_w_f=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['w_w_s_1'], stat_dict[player]['saw_f']), + '(%d/%d)' % (stat_dict[player]['w_w_s_1'], stat_dict[player]['saw_1']), '% won$/saw flop/4th' ) except: @@ -358,7 +358,7 @@ def a_freq_3(stat_dict, player): '%3.1f' % (100*stat) + '%', 'a3=%3.1f' % (100*stat) + '%', 'a_fq_3=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_1']), + '(%d/%d)' % (stat_dict[player]['aggr_3'], stat_dict[player]['saw_3']), 'Aggression Freq river/6th' ) except: From 76448ce06f71e84183f6b8cda136d2bfd5c081d4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 22 Aug 2008 20:32:20 +0100 Subject: [PATCH 066/262] p65 - fixed minor bugs in the various calls to fpdb_import bulk import works on single files again table viewer works again fixed CLI import --- docs/known-bugs-and-planned-features.txt | 10 ++++++---- pyfpdb/GuiBulkImport.py | 2 +- pyfpdb/GuiTableViewer.py | 8 ++------ pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_import.py | 9 +++++---- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 2feb12ac..70485fe5 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,9 +2,10 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and especially the order will often change. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha2 (release by 17Aug) +alpha2 (release 22Aug) ====== -W$wsF doesnt work in HUD +make windows not call fpdb hud +W$wsF doesnt work in HUD (p64 may have made this worse) auto import only runs on one file per start set hud stats default row1: hands, VPIP, PFR, PF3B/4B, ST row2: Aggr% (postflop), Fold% (postflop), W$sF, WtSD, W$@SD make windows use correct language version of Appdata, e.g. Anwendungdaten @@ -16,8 +17,10 @@ ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo. re-run existing regression tests move conf file out of profiles folder -alpha3 +alpha3 (release by 31Aug) ====== +store raw hand in db +write reimport function using the raw hand field make it work with postgres expand instructions for profile file ftp: read maxSeats @@ -29,7 +32,6 @@ finish todos in git instructions make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt (steffen) finish bringing back tourney -store raw hand in db export settings[hud-defaultInterval] to conf fix up bg colours in tv fill the fold to CB/2B/3B cache fields diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index f6583cac..4d91c893 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -69,7 +69,7 @@ class GuiBulkImport (threading.Thread): if os.path.isdir(self.inputFile): self.import_dir() else: - fpdb_import.import_file_dict(self) + fpdb_import.import_file_dict(self, self.settings) def get_vbox(self): """returns the vbox of this thread""" diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index e67355e6..866b042d 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -201,13 +201,9 @@ class GuiTableViewer (threading.Thread): def read_names_clicked(self, widget, data): """runs when user clicks read names""" #print "start of table_viewer.read_names_clicked" - #print "self.last_read_hand:",self.last_read_hand self.db.reconnect() self.cursor=self.db.cursor - self.cursor.execute("""SELECT id FROM Hands WHERE siteHandNo=%s""", (self.last_read_hand)) - hands_id_tmp=self.db.cursor.fetchone() - #print "tmp:",hands_id_tmp - self.hands_id=hands_id_tmp[0] + self.hands_id=self.last_read_hand_id self.db.cursor.execute("SELECT gametypeId FROM Hands WHERE id=%s", (self.hands_id, )) self.gametype_id=self.db.cursor.fetchone()[0] @@ -243,7 +239,7 @@ class GuiTableViewer (threading.Thread): self.minPrint=0 self.handCount=0 - self.last_read_hand=fpdb_import.import_file_dict(self, self.settings) + self.last_read_hand_id=fpdb_import.import_file_dict(self, self.settings, False) #end def table_viewer.import_clicked def all_clicked(self, widget, data): diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 8894068b..6a196799 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -376,7 +376,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p57") + self.window.set_title("Free Poker DB - version: alpha1+, p65") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 82c7d968..82e2413a 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -37,7 +37,7 @@ def import_file(server, database, user, password, inputFile): self.settings={'imp-callFpdbHud':False} import_file_dict(self, settings) -def import_file_dict(options, settings): +def import_file_dict(options, settings, callHud=True): last_read_hand=0 if (options.inputFile=="stdin"): inputFile=sys.stdin @@ -106,7 +106,7 @@ def import_file_dict(options, settings): db.commit() stored+=1 - if settings['imp-callFpdbHud']: + if settings['imp-callFpdbHud'] and callHud: print "call to HUD here. handsId:",handsId # pipe the Hands.id out to the HUD options.pipe_to_hud.stdin.write("%s\n" % (handsId)) @@ -192,5 +192,6 @@ if __name__ == "__main__": help="If this option is passed it quits when it encounters any error") (options, sys.argv) = parser.parse_args() - - import_file_dict(options) + + settings={'imp-callFpdbHud':False} + import_file_dict(options, settings, False) From 07f8220808fc572d0e7ec6e13613a400117e29e6 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 22 Aug 2008 21:10:32 +0100 Subject: [PATCH 067/262] p66 - mostly HUD improvements from ray HUD now doesnt display a taskbar window for each stat window HUD w$wsf works more HUD improvements made windows not call fpdb hud moved conf file out of profiles folder --- docs/known-bugs-and-planned-features.txt | 20 ++-- pyfpdb/Hud.py | 14 ++- pyfpdb/SQL.py | 112 ++++++++++++++--------- pyfpdb/Stats.py | 57 +++++++----- pyfpdb/fpdb.py | 4 +- pyfpdb/fpdb_import.py | 4 +- 6 files changed, 129 insertions(+), 82 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 70485fe5..bdc21599 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -4,23 +4,19 @@ Please also see db-todo.txt alpha2 (release 22Aug) ====== -make windows not call fpdb hud -W$wsF doesnt work in HUD (p64 may have made this worse) -auto import only runs on one file per start -set hud stats default row1: hands, VPIP, PFR, PF3B/4B, ST row2: Aggr% (postflop), Fold% (postflop), W$sF, WtSD, W$@SD -make windows use correct language version of Appdata, e.g. Anwendungdaten -stop bulk importer from executing HUD hook seperate and improve instructions for update -update status or make a support matrix table for website -add instructions for mailing list to contacts -ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config re-run existing regression tests -move conf file out of profiles folder +update website for dropping profile subfolder alpha3 (release by 31Aug) ====== -store raw hand in db -write reimport function using the raw hand field +ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config +update status or make a support matrix table for website +add instructions for mailing list to contacts +auto import only runs on one file per start +make windows use correct language version of Appdata, e.g. Anwendungdaten +make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) +store raw hand in db and write reimport function using the raw hand field make it work with postgres expand instructions for profile file ftp: read maxSeats diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 6ac1c763..2e0c7cdd 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -147,7 +147,9 @@ class Stat_Window: self.window = gtk.Window() self.window.set_decorated(0) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.window.set_keep_above(1) self.window.set_title("%s" % seat) + self.window.set_property("skip-taskbar-hint", True) self.grid = gtk.Table(rows = self.game.rows, columns = self.game.cols, homogeneous = False) self.window.add(self.grid) @@ -169,7 +171,6 @@ class Stat_Window: self.label[r][c].modify_font(font) self.window.realize self.window.move(self.x, self.y) - self.window.set_keep_above(1) self.window.show_all() def destroy(*args): # call back for terminating the main eventloop @@ -184,7 +185,16 @@ if __name__== "__main__": c = Configuration.Config() tables = Tables.discover(c) - db = Database.Database(c, 'PTrackSv2', 'razz') + db = Database.Database(c, 'fpdb', 'holdem') + + for attr in dir(Stats): + if attr.startswith('__'): continue + if attr == 'Configuration' or attr == 'Database': continue + if attr == 'GInitiallyUnowned': continue +# print Stats.attr.__doc__ + + print Stats.vpip.__doc__ + for t in tables: win = Hud(t, 8, c, db) diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index e1af9c9b..ffec6c65 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -32,6 +32,11 @@ class Sql: def __init__(self, game = 'holdem', type = 'PT3'): self.query = {} +############################################################################ +# +# Support for the ptracks database, a cut down PT2 stud database. +# You can safely ignore this unless you are me. +# if game == 'razz' and type == 'ptracks': self.query['get_table_name'] = "select table_name from game where game_id = %s" @@ -151,7 +156,10 @@ class Sql: FROM game_players INNER JOIN players ON (game_players.player_id = players.player_id) WHERE game_id = %s """ - + +###############################################################################3 +# Support for the Free Poker DataBase = fpdb http://fpdb.sourceforge.net/ +# if type == 'fpdb': self.query['get_last_hand'] = "select max(id) from Hands" @@ -164,47 +172,67 @@ class Sql: """ self.query['get_stats_from_hand'] = """ - SELECT HudCache.playerId AS player_id, - sum(HDs) AS n, - sum(street0Aggr) AS pfr, - sum(street0VPI) AS vpip, - sum(sawShowdown) AS sd, - sum(wonAtSD) AS wmsd, - sum(street1Seen) AS saw_f, - sum(stealAttemptChance) AS steal_opp, - sum(stealAttempted) AS steal, - sum(foldedSbToSteal) AS SBnotDef, - sum(foldedBbToSteal) AS BBnotDef, - sum(foldBbToStealChance) AS SBstolen, - sum(foldBbToStealChance) AS BBstolen, - sum(street0_3B4BChance) AS TB_opp_0, - sum(street0_3B4BDone) AS TB_0, - sum(street1Seen) AS saw_1, - sum(street2Seen) AS saw_2, - sum(street3Seen) AS saw_3, - sum(street4Seen) AS saw_4, - sum(street1Aggr) AS aggr_1, - sum(street2Aggr) AS aggr_2, - sum(street3Aggr) AS aggr_3, - sum(street4Aggr) AS aggr_4, - sum(otherRaisedStreet1) AS was_raised_1, - sum(otherRaisedStreet2) AS was_raised_2, - sum(otherRaisedStreet3) AS was_raised_3, - sum(otherRaisedStreet4) AS was_raised_4, - sum(foldToOtherRaisedStreet1) AS f_freq_1, - sum(foldToOtherRaisedStreet2) AS f_freq_2, - sum(foldToOtherRaisedStreet3) AS f_freq_3, - sum(foldToOtherRaisedStreet4) AS f_freq_4, - sum(wonWhenSeenStreet1) AS w_w_s_1, - sum(street1CBChance) AS CB_opp_1, - sum(street2CBChance) AS CB_opp_2, - sum(street3CBChance) AS CB_opp_3, - sum(street4CBChance) AS CB_opp_4, - sum(street1CBDone) AS CB_1, - sum(street2CBDone) AS CB_2, - sum(street3CBDone) AS CB_3, - sum(street4CBDone) AS CB_4, - sum(totalProfit) AS net + SELECT HudCache.playerId AS player_id, + HudCache.gametypeId AS gametypeId, + activeSeats AS n_active, + position AS position, + HudCache.tourneyTypeId AS tourneyTypeId, + sum(HDs) AS n, + sum(street0VPI) AS vpip, + sum(street0Aggr) AS pfr, + sum(street0_3B4BChance) AS TB_opp_0, + sum(street0_3B4BDone) AS TB_0, + sum(street1Seen) AS saw_f, + sum(street1Seen) AS saw_1, + sum(street2Seen) AS saw_2, + sum(street3Seen) AS saw_3, + sum(street4Seen) AS saw_4, + sum(sawShowdown) AS sd, + sum(street1Aggr) AS aggr_1, + sum(street2Aggr) AS aggr_2, + sum(street3Aggr) AS aggr_3, + sum(street4Aggr) AS aggr_4, + sum(otherRaisedStreet1) AS was_raised_1, + sum(otherRaisedStreet2) AS was_raised_2, + sum(otherRaisedStreet3) AS was_raised_3, + sum(otherRaisedStreet4) AS was_raised_4, + sum(foldToOtherRaisedStreet1) AS f_freq_1, + sum(foldToOtherRaisedStreet2) AS f_freq_2, + sum(foldToOtherRaisedStreet3) AS f_freq_3, + sum(foldToOtherRaisedStreet4) AS f_freq_4, + sum(wonWhenSeenStreet1) AS w_w_s_1, + sum(wonAtSD) AS wmsd, + sum(stealAttemptChance) AS steal_opp, + sum(stealAttempted) AS steal, + sum(foldBbToStealChance) AS SBstolen, + sum(foldedBbToSteal) AS BBnotDef, + sum(foldBbToStealChance) AS BBstolen, + sum(foldedSbToSteal) AS SBnotDef, + sum(street1CBChance) AS CB_opp_1, + sum(street1CBDone) AS CB_1, + sum(street2CBChance) AS CB_opp_2, + sum(street2CBDone) AS CB_2, + sum(street3CBChance) AS CB_opp_3, + sum(street3CBDone) AS CB_3, + sum(street4CBChance) AS CB_opp_4, + sum(street4CBDone) AS CB_4, + sum(foldToStreet1CBChance) AS f_cb_opp_1, + sum(foldToStreet1CBDone) AS f_cb_1, + sum(foldToStreet2CBChance) AS f_cb_opp_2, + sum(foldToStreet2CBDone) AS f_cb_2, + sum(foldToStreet3CBChance) AS f_cb_opp_3, + sum(foldToStreet3CBDone) AS f_cb_3, + sum(foldToStreet4CBChance) AS f_cb_opp_4, + sum(foldToStreet4CBDone) AS f_cb_4, + sum(totalProfit) AS net, + sum(street1CheckCallRaiseChance) AS ccr_opp_1, + sum(street1CheckCallRaiseDone) AS ccr_1, + sum(street2CheckCallRaiseChance) AS ccr_opp_2, + sum(street2CheckCallRaiseDone) AS ccr_2, + sum(street3CheckCallRaiseChance) AS ccr_opp_3, + sum(street3CheckCallRaiseDone) AS ccr_3, + sum(street4CheckCallRaiseChance) AS ccr_opp_4, + sum(street4CheckCallRaiseDone) AS ccr_4 FROM HudCache, Hands WHERE HudCache.PlayerId in (SELECT PlayerId FROM HandsPlayers diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py index 96ddd5ad..b52378cb 100644 --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -55,6 +55,10 @@ import Database def do_tip(widget, tip): widget.set_tooltip_text(tip) +def list_stats(): + for key in dir(): + print key + def do_stat(stat_dict, player = 24, stat = 'vpip'): return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) # OK, for reference the tuple returned by the stat is: @@ -68,6 +72,9 @@ def do_stat(stat_dict, player = 24, stat = 'vpip'): ########################################### # functions that return individual stats def vpip(stat_dict, player): + """ + Voluntarily put $ in the pot + """ stat = 0.0 try: stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']) @@ -146,8 +153,6 @@ def wmsd(stat_dict, player): '% won money at showdown' ) - - def saw_f(stat_dict, player): try: num = float(stat_dict[player]['saw_f']) @@ -193,7 +198,7 @@ def n(stat_dict, player): def fold_f(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['fold_2']/stat_dict[player]['saw_f'] + stat = float(stat_dict[player]['fold_2'])/fold(stat_dict[player]['saw_f']) return (stat, '%3.1f' % (100*stat) + '%', 'ff=%3.1f' % (100*stat) + '%', @@ -213,7 +218,7 @@ def fold_f(stat_dict, player): def steal(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['steal']/stat_dict[player]['steal_opp'] + stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp']) return (stat, '%3.1f' % (100*stat) + '%', 'st=%3.1f' % (100*stat) + '%', @@ -233,7 +238,7 @@ def steal(stat_dict, player): def f_SB_steal(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['SBnotDef']/stat_dict[player]['SBstolen'] + stat = float(stat_dict[player]['SBnotDef'])/float(stat_dict[player]['SBstolen']) return (stat, '%3.1f' % (100*stat) + '%', 'fSB=%3.1f' % (100*stat) + '%', @@ -253,7 +258,7 @@ def f_SB_steal(stat_dict, player): def f_BB_steal(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['BBnotDef']/stat_dict[player]['BBstolen'] + stat = float(stat_dict[player]['BBnotDef'])/float(stat_dict[player]['BBstolen']) return (stat, '%3.1f' % (100*stat) + '%', 'fBB=%3.1f' % (100*stat) + '%', @@ -273,7 +278,7 @@ def f_BB_steal(stat_dict, player): def three_B_0(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['TB_0']/stat_dict[player]['TB_opp_0'] + stat = float(stat_dict[player]['TB_0'])/float(stat_dict[player]['TB_opp_0']) return (stat, '%3.1f' % (100*stat) + '%', '3B=%3.1f' % (100*stat) + '%', @@ -293,12 +298,12 @@ def three_B_0(stat_dict, player): def WMsF(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['w_w_s_1']/stat_dict[player]['saw_1'] + stat = float(stat_dict[player]['w_w_s_1'])/float(stat_dict[player]['saw_1']) return (stat, '%3.1f' % (100*stat) + '%', 'wf=%3.1f' % (100*stat) + '%', 'w_w_f=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['w_w_s_1'], stat_dict[player]['saw_1']), + '(%d/%d)' % (stat_dict[player]['w_w_s_1'], stat_dict[player]['saw_f']), '% won$/saw flop/4th' ) except: @@ -313,7 +318,7 @@ def WMsF(stat_dict, player): def a_freq_1(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['aggr_1']/stat_dict[player]['saw_f'] + stat = float(stat_dict[player]['aggr_1'])/float(stat_dict[player]['saw_f']) return (stat, '%3.1f' % (100*stat) + '%', 'a1=%3.1f' % (100*stat) + '%', @@ -333,7 +338,7 @@ def a_freq_1(stat_dict, player): def a_freq_2(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['aggr_2']/stat_dict[player]['saw_2'] + stat = float(stat_dict[player]['aggr_2'])/float(stat_dict[player]['saw_2']) return (stat, '%3.1f' % (100*stat) + '%', 'a2=%3.1f' % (100*stat) + '%', @@ -353,12 +358,12 @@ def a_freq_2(stat_dict, player): def a_freq_3(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['aggr_3']/stat_dict[player]['saw_3'] + stat = float(stat_dict[player]['aggr_3'])/float(stat_dict[player]['saw_3']) return (stat, '%3.1f' % (100*stat) + '%', 'a3=%3.1f' % (100*stat) + '%', 'a_fq_3=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['aggr_3'], stat_dict[player]['saw_3']), + '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_1']), 'Aggression Freq river/6th' ) except: @@ -373,7 +378,7 @@ def a_freq_3(stat_dict, player): def a_freq_4(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['aggr_4']/stat_dict[player]['saw_4'] + stat = float(stat_dict[player]['aggr_4'])/float(stat_dict[player]['saw_4']) return (stat, '%3.1f' % (100*stat) + '%', 'a4=%3.1f' % (100*stat) + '%', @@ -393,7 +398,7 @@ def a_freq_4(stat_dict, player): def cb_1(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['CB_1']/stat_dict[player]['CB_opp_1'] + stat = float(stat_dict[player]['CB_1'])/float(stat_dict[player]['CB_opp_1']) return (stat, '%3.1f' % (100*stat) + '%', 'cb1=%3.1f' % (100*stat) + '%', @@ -413,7 +418,7 @@ def cb_1(stat_dict, player): def cb_2(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['CB_2']/stat_dict[player]['CB_opp_2'] + stat = float(stat_dict[player]['CB_2'])/float(stat_dict[player]['CB_opp_2']) return (stat, '%3.1f' % (100*stat) + '%', 'cb2=%3.1f' % (100*stat) + '%', @@ -433,7 +438,7 @@ def cb_2(stat_dict, player): def cb_3(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['CB_3']/stat_dict[player]['CB_opp_3'] + stat = float(stat_dict[player]['CB_3'])/float(stat_dict[player]['CB_opp_3']) return (stat, '%3.1f' % (100*stat) + '%', 'cb3=%3.1f' % (100*stat) + '%', @@ -453,7 +458,7 @@ def cb_3(stat_dict, player): def cb_4(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['CB_4']/stat_dict[player]['CB_opp_4'] + stat = float(stat_dict[player]['CB_4'])/float(stat_dict[player]['CB_opp_4']) return (stat, '%3.1f' % (100*stat) + '%', 'cb4=%3.1f' % (100*stat) + '%', @@ -473,7 +478,7 @@ def cb_4(stat_dict, player): def ffreq_1(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['f_freq_1']/stat_dict[player]['was_raised_1'] + stat = float(stat_dict[player]['f_freq_1'])/float(stat_dict[player]['was_raised_1']) return (stat, '%3.1f' % (100*stat) + '%', 'ff1=%3.1f' % (100*stat) + '%', @@ -493,7 +498,7 @@ def ffreq_1(stat_dict, player): def ffreq_2(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['f_freq_2']/stat_dict[player]['was_raised_2'] + stat = float(stat_dict[player]['f_freq_2'])/float(stat_dict[player]['was_raised_2']) return (stat, '%3.1f' % (100*stat) + '%', 'ff2=%3.1f' % (100*stat) + '%', @@ -513,7 +518,7 @@ def ffreq_2(stat_dict, player): def ffreq_3(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['f_freq_3']/stat_dict[player]['was_raised_3'] + stat = float(stat_dict[player]['f_freq_3'])/float(stat_dict[player]['was_raised_3']) return (stat, '%3.1f' % (100*stat) + '%', 'ff3=%3.1f' % (100*stat) + '%', @@ -533,7 +538,7 @@ def ffreq_3(stat_dict, player): def ffreq_4(stat_dict, player): stat = 0.0 try: - stat = stat_dict[player]['f_freq_4']/stat_dict[player]['was_raised_4'] + stat = float(stat_dict[player]['f_freq_4'])/float(stat_dict[player]['was_raised_4']) return (stat, '%3.1f' % (100*stat) + '%', 'ff4=%3.1f' % (100*stat) + '%', @@ -582,5 +587,13 @@ if __name__== "__main__": print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3') print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4') +# print "\n\nLegal stats:" +# for attr in dir(): +# if attr.startswith('__'): continue +# if attr == 'Configuration' or attr == 'Database': continue +# if attr == 'GInitiallyUnowned': continue +# print attr.__doc__ +# +# print vpip.__doc__ db_connection.close diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 6a196799..27f3d259 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -225,7 +225,7 @@ class fpdb: defaultpath+=("Application Data"+os.sep) else:#ie. if real OS prefix fpdb with a . as it is convention defaultpath+="." - defaultpath+=("fpdb"+os.sep+"profiles"+os.sep+"default.conf") + defaultpath+=("fpdb"+os.sep+os.sep+"default.conf") if os.path.exists(defaultpath): self.load_profile(defaultpath) @@ -376,7 +376,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p65") + self.window.set_title("Free Poker DB - version: alpha1+, p66") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 82e2413a..94cc98f0 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -106,8 +106,8 @@ def import_file_dict(options, settings, callHud=True): db.commit() stored+=1 - if settings['imp-callFpdbHud'] and callHud: - print "call to HUD here. handsId:",handsId + if settings['imp-callFpdbHud'] and callHud and os.sep=='/': + #print "call to HUD here. handsId:",handsId # pipe the Hands.id out to the HUD options.pipe_to_hud.stdin.write("%s\n" % (handsId)) db.commit() From 1b4696b26861efd89506fe9f521a9425e6515c74 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 22 Aug 2008 22:39:43 +0100 Subject: [PATCH 068/262] p67 - finished updating print_hand and renamed it to PrintHand. also updated the related .expected files and thereby checked that the importer (except cache) still works correctly. --- docs/known-bugs-and-planned-features.txt | 3 +- pyfpdb/fpdb.py | 2 +- .../{print_hand.py => PrintHand.py} | 79 ++++++++------- regression-test/fpdb_util_lib.py | 5 +- regression-test/ps.14519394979.expected.txt | 79 +++++++-------- regression-test/ps.14519420999.expected.txt | 79 +++++++-------- regression-test/ps.14519433154.expected.txt | 97 ++++++++++--------- regression-test/regression-test.sh | 10 +- 8 files changed, 181 insertions(+), 173 deletions(-) rename regression-test/{print_hand.py => PrintHand.py} (69%) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index bdc21599..78cee90a 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -5,11 +5,12 @@ Please also see db-todo.txt alpha2 (release 22Aug) ====== seperate and improve instructions for update -re-run existing regression tests +PrintPlayerHudData update website for dropping profile subfolder alpha3 (release by 31Aug) ====== +specify NOT NULL on almost all table columns ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config update status or make a support matrix table for website add instructions for mailing list to contacts diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 27f3d259..ebbe9901 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -376,7 +376,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p66") + self.window.set_title("Free Poker DB - version: alpha1+, p67") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/regression-test/print_hand.py b/regression-test/PrintHand.py similarity index 69% rename from regression-test/print_hand.py rename to regression-test/PrintHand.py index fa73e9b3..e29414ba 100755 --- a/regression-test/print_hand.py +++ b/regression-test/PrintHand.py @@ -87,34 +87,34 @@ if base=="hold": elif base=="stud": doBets=True -if do_bets: - cursor.execute("SELECT small_bet FROM gametypes WHERE id=%s", (gametype_id, )) +if doBets: + cursor.execute("SELECT smallBet FROM Gametypes WHERE id=%s", (gametypeId, )) sbet=cursor.fetchone()[0] - cursor.execute("SELECT big_bet FROM gametypes WHERE id=%s", (gametype_id, )) + cursor.execute("SELECT bigBet FROM Gametypes WHERE id=%s", (gametypeId, )) bbet=cursor.fetchone()[0] - gt_string+=(" sbet: "+str(sbet)+" bbet: "+str(bbet)) -print gt_string + gtString+=(" sbet: "+str(sbet)+" bbet: "+str(bbet)) +print gtString if type=="ring": pass elif type=="tour": #cursor.execute("SELECT tourneys_players_id FROM hands - cursor.execute("""SELECT DISTINCT tourneys_players.id - FROM hands JOIN hands_players ON hands_players.hand_id=hands.id - JOIN tourneys_players ON hands_players.tourneys_players_id=tourneys_players.id - WHERE hands.id=%s""", (hand_id,)) - hands_players_ids=cursor.fetchall() - print "dbg hands_players_ids:",hands_players_ids + cursor.execute("""SELECT DISTINCT TourneysPlayers.id + FROM Hands JOIN HandsPlayers ON HandsPlayers.handId=Hands.id + JOIN TourneysPlayers ON HandsPlayers.tourneysPlayersId=TourneysPlayers.id + WHERE Hands.id=%s""", (hand_id,)) + handsPlayersIds=cursor.fetchall() + print "dbg hands_players_ids:",handsPlayersIds print "" - print "From Table tourneys" + print "From Table Tourneys" print "===================" print "TODO" print "" - print "From Table tourneys_players" - print "===========================" + print "From Table TourneysPlayers" + print "==========================" print "TODO" else: print "invalid type:",type @@ -122,53 +122,50 @@ else: print "" -print "From Table hands" -print "================" - -print "site_hand_no:",site_hand_no,"hand_start:",hand_start,"seat_count:",seat_count -#,"hand_id:",hand_id,"gametype_id:",gametype_id +print "From Table BoardCards" +print "=====================" if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - cursor.execute("""SELECT * FROM board_cards WHERE hand_id=%s""",(hand_id, )) + cursor.execute("""SELECT * FROM BoardCards WHERE handId=%s""",(handId, )) bc=cursor.fetchone() print "Board cards:", ful.cards2String(bc[2:]) print "" -print "From Table hands_players" -print "========================" -cursor.execute("""SELECT * FROM hands_players WHERE hand_id=%s""",(hand_id, )) -hands_players=cursor.fetchall() -player_names=[] -for i in range (len(hands_players)): - line=hands_players[i][2:] - player_names.append(ful.id_to_player_name(cursor, line[0])) - printstr="player_name:"+player_names[i]+" player_startcash:"+str(line[1]) +print "From Table HandsPlayers" +print "=======================" +cursor.execute("""SELECT * FROM HandsPlayers WHERE handId=%s""",(handId, )) +handsPlayers=cursor.fetchall() +playerNames=[] +for i in range (len(handsPlayers)): + line=handsPlayers[i][2:] + playerNames.append(ful.id_to_player_name(cursor, line[0])) + printstr="playerName:"+playerNames[i]+" playerStartcash:"+str(line[1]) if (category=="holdem" or category=="omahahi" or category=="omahahilo"): printstr+=" position:"+ful.position2String(line[2])+" cards:" if (category=="holdem"): - printstr+=ful.cards2String(line[4:8]) + printstr+=ful.cards2String(line[5:9]) else: - printstr+=ful.cards2String(line[4:12]) + printstr+=ful.cards2String(line[5:13]) elif (category=="razz" or category=="studhi" or category=="studhilo"): printstr+=" ante:"+str(line[3])+" cards:" - printstr+=ful.cards2String(line[4:18]) + printstr+=ful.cards2String(line[5:19]) else: print "TODO: raise error, print_hand.py" sys.exit(1) - printstr+=" winnings:"+str(line[18])+" rake:"+str(line[19]) + printstr+=" winnings:"+str(line[19])+" rake:"+str(line[20]) print printstr print "" -print "From Table hands_actions" -print "========================" -for i in range (len(hands_players)): - cursor.execute("""SELECT * FROM hands_actions WHERE hand_player_id=%s""",(hands_players[i][0], )) - hands_actions=cursor.fetchall() - for j in range (len(hands_actions)): - line=hands_actions[j][2:] - printstr="player_name:"+player_names[i] +print "From Table HandsActions" +print "=======================" +for i in range (len(handsPlayers)): + cursor.execute("""SELECT * FROM HandsActions WHERE handPlayerId=%s""",(handsPlayers[i][0], )) + handsActions=cursor.fetchall() + for j in range (len(handsActions)): + line=handsActions[j][2:] + printstr="playerName:"+playerNames[i] printstr+=" street:"+ful.street_int2String(category, line[0])+" streetActionNo:"+str(line[1])+" action:"+line[2] printstr+=" amount:"+str(line[3]) print printstr diff --git a/regression-test/fpdb_util_lib.py b/regression-test/fpdb_util_lib.py index 565f7cbd..a450a453 100644 --- a/regression-test/fpdb_util_lib.py +++ b/regression-test/fpdb_util_lib.py @@ -39,14 +39,15 @@ def cards2String(arr): elif (arr[i*2]>=2 and arr[i*2]<=9): result+=str(arr[i*2]) else: - print "TODO: raise error, cards2String failed, arr[i*2]:",arr[i*2] + print "TODO: raise error, cards2String failed, arr[i*2]:", arr[i*2], "len(arr):", len(arr) + print "arr:",arr sys.exit(1) result+=arr[i*2+1] result+=" " return result[:-1] def id_to_player_name(cursor, id): - cursor.execute("SELECT name FROM players WHERE id=%s", (id, )) + cursor.execute("SELECT name FROM Players WHERE id=%s", (id, )) return cursor.fetchone()[0] def position2String(pos): diff --git a/regression-test/ps.14519394979.expected.txt b/regression-test/ps.14519394979.expected.txt index 962ec68c..c1eb98d7 100644 --- a/regression-test/ps.14519394979.expected.txt +++ b/regression-test/ps.14519394979.expected.txt @@ -1,46 +1,49 @@ Connected to MySQL on localhost. Print Hand Utility -options.site: PokerStars site_id: 2 +options.site: PokerStars siteId: 2 -From Table gametypes +From Table Hands +================ +handId: 1 tableName: Merope siteHandNo: 14519394979 gametypeId: 1 handStart: 2008-01-13 05:22:15 seats: 7 maxSeats: 10 + +From Table Gametypes ==================== -type: ring category: holdem limit_type: fl +type: ring base: hold category: holdem limitType: fl hiLo: h sb: 1 bb: 2 sbet: 2 bbet: 4 -From Table hands -================ -site_hand_no: 14519394979 hand_start: 2008-01-13 05:22:15 seat_count: 7 +From Table BoardCards +===================== Board cards: Qd Th Js 2s 7s -From Table hands_players -======================== -player_name:Player_1 player_startcash:75 position:Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_2 player_startcash:59 position:SB cards:?? ?? winnings:0 rake:0 -player_name:Player_3 player_startcash:147 position:BB cards:?? ?? winnings:0 rake:0 -player_name:Player_4 player_startcash:198 position:4 off Btn cards:?? ?? winnings:31 rake:1 -player_name:Player_5 player_startcash:122 position:3 off Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_6 player_startcash:48 position:2 off Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_7 player_startcash:139 position:1 off Btn cards:Ts Jh winnings:0 rake:0 +From Table HandsPlayers +======================= +playerName:Player_1 playerStartcash:75 position:Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_2 playerStartcash:59 position:SB cards:?? ?? winnings:0 rake:0 +playerName:Player_3 playerStartcash:147 position:BB cards:?? ?? winnings:0 rake:0 +playerName:Player_4 playerStartcash:198 position:4 off Btn cards:?? ?? winnings:31 rake:1 +playerName:Player_5 playerStartcash:122 position:3 off Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_6 playerStartcash:48 position:2 off Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_7 playerStartcash:139 position:1 off Btn cards:Ts Jh winnings:0 rake:0 -From Table hands_actions -======================== -player_name:Player_1 street:Preflop streetActionNo:6 action:call amount:4 -player_name:Player_1 street:Flop streetActionNo:2 action:call amount:2 -player_name:Player_1 street:Turn streetActionNo:2 action:call amount:4 -player_name:Player_1 street:River streetActionNo:2 action:fold amount:0 -player_name:Player_2 street:Preflop streetActionNo:0 action:blind amount:1 -player_name:Player_2 street:Preflop streetActionNo:7 action:call amount:3 -player_name:Player_2 street:Flop streetActionNo:0 action:check amount:0 -player_name:Player_2 street:Flop streetActionNo:3 action:call amount:2 -player_name:Player_2 street:Turn streetActionNo:0 action:check amount:0 -player_name:Player_2 street:Turn streetActionNo:3 action:call amount:4 -player_name:Player_2 street:River streetActionNo:0 action:check amount:0 -player_name:Player_2 street:River streetActionNo:3 action:fold amount:0 -player_name:Player_3 street:Preflop streetActionNo:1 action:blind amount:2 -player_name:Player_3 street:Preflop streetActionNo:8 action:fold amount:0 -player_name:Player_4 street:Preflop streetActionNo:2 action:bet amount:4 -player_name:Player_4 street:Flop streetActionNo:1 action:bet amount:2 -player_name:Player_4 street:Turn streetActionNo:1 action:bet amount:4 -player_name:Player_4 street:River streetActionNo:1 action:bet amount:4 -player_name:Player_5 street:Preflop streetActionNo:3 action:fold amount:0 -player_name:Player_6 street:Preflop streetActionNo:4 action:fold amount:0 -player_name:Player_7 street:Preflop streetActionNo:5 action:fold amount:0 +From Table HandsActions +======================= +playerName:Player_1 street:Preflop streetActionNo:6 action:call amount:4 +playerName:Player_1 street:Flop streetActionNo:2 action:call amount:2 +playerName:Player_1 street:Turn streetActionNo:2 action:call amount:4 +playerName:Player_1 street:River streetActionNo:2 action:fold amount:0 +playerName:Player_2 street:Preflop streetActionNo:0 action:blind amount:1 +playerName:Player_2 street:Preflop streetActionNo:7 action:call amount:3 +playerName:Player_2 street:Flop streetActionNo:0 action:check amount:0 +playerName:Player_2 street:Flop streetActionNo:3 action:call amount:2 +playerName:Player_2 street:Turn streetActionNo:0 action:check amount:0 +playerName:Player_2 street:Turn streetActionNo:3 action:call amount:4 +playerName:Player_2 street:River streetActionNo:0 action:check amount:0 +playerName:Player_2 street:River streetActionNo:3 action:fold amount:0 +playerName:Player_3 street:Preflop streetActionNo:1 action:blind amount:2 +playerName:Player_3 street:Preflop streetActionNo:8 action:fold amount:0 +playerName:Player_4 street:Preflop streetActionNo:2 action:bet amount:4 +playerName:Player_4 street:Flop streetActionNo:1 action:bet amount:2 +playerName:Player_4 street:Turn streetActionNo:1 action:bet amount:4 +playerName:Player_4 street:River streetActionNo:1 action:bet amount:4 +playerName:Player_5 street:Preflop streetActionNo:3 action:fold amount:0 +playerName:Player_6 street:Preflop streetActionNo:4 action:fold amount:0 +playerName:Player_7 street:Preflop streetActionNo:5 action:fold amount:0 diff --git a/regression-test/ps.14519420999.expected.txt b/regression-test/ps.14519420999.expected.txt index ba18560f..3f42bb0a 100644 --- a/regression-test/ps.14519420999.expected.txt +++ b/regression-test/ps.14519420999.expected.txt @@ -1,46 +1,49 @@ Connected to MySQL on localhost. Print Hand Utility -options.site: PokerStars site_id: 2 +options.site: PokerStars siteId: 2 -From Table gametypes +From Table Hands +================ +handId: 2 tableName: Merope siteHandNo: 14519420999 gametypeId: 1 handStart: 2008-01-13 05:23:43 seats: 7 maxSeats: 10 + +From Table Gametypes ==================== -type: ring category: holdem limit_type: fl +type: ring base: hold category: holdem limitType: fl hiLo: h sb: 1 bb: 2 sbet: 2 bbet: 4 -From Table hands -================ -site_hand_no: 14519420999 hand_start: 2008-01-13 05:23:43 seat_count: 7 +From Table BoardCards +===================== Board cards: Th Jd 3c 7c 4s -From Table hands_players -======================== -player_name:Player_1 player_startcash:65 position:2 off Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_2 player_startcash:49 position:1 off Btn cards:8s 9s winnings:35 rake:1 -player_name:Player_3 player_startcash:179 position:Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_4 player_startcash:205 position:SB cards:?? ?? winnings:0 rake:0 -player_name:Player_5 player_startcash:118 position:BB cards:Qh Js winnings:0 rake:0 -player_name:Player_6 player_startcash:34 position:4 off Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_7 player_startcash:135 position:3 off Btn cards:8d 5d winnings:0 rake:0 +From Table HandsPlayers +======================= +playerName:Player_1 playerStartcash:65 position:2 off Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_2 playerStartcash:49 position:1 off Btn cards:8s 9s winnings:35 rake:1 +playerName:Player_3 playerStartcash:179 position:Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_4 playerStartcash:205 position:SB cards:?? ?? winnings:0 rake:0 +playerName:Player_5 playerStartcash:118 position:BB cards:Qh Js winnings:0 rake:0 +playerName:Player_6 playerStartcash:34 position:4 off Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_7 playerStartcash:135 position:3 off Btn cards:8d 5d winnings:0 rake:0 -From Table hands_actions -======================== -player_name:Player_1 street:Preflop streetActionNo:4 action:fold amount:0 -player_name:Player_2 street:Preflop streetActionNo:5 action:call amount:2 -player_name:Player_2 street:Flop streetActionNo:2 action:call amount:2 -player_name:Player_2 street:Turn streetActionNo:2 action:bet amount:8 -player_name:Player_2 street:River streetActionNo:1 action:bet amount:4 -player_name:Player_3 street:Preflop streetActionNo:6 action:fold amount:0 -player_name:Player_4 street:Preflop streetActionNo:0 action:blind amount:1 -player_name:Player_4 street:Preflop streetActionNo:7 action:call amount:1 -player_name:Player_4 street:Flop streetActionNo:0 action:check amount:0 -player_name:Player_4 street:Flop streetActionNo:3 action:call amount:2 -player_name:Player_4 street:Turn streetActionNo:0 action:check amount:0 -player_name:Player_4 street:Turn streetActionNo:3 action:fold amount:0 -player_name:Player_5 street:Preflop streetActionNo:1 action:blind amount:2 -player_name:Player_5 street:Preflop streetActionNo:8 action:check amount:0 -player_name:Player_5 street:Flop streetActionNo:1 action:bet amount:2 -player_name:Player_5 street:Turn streetActionNo:1 action:bet amount:4 -player_name:Player_5 street:Turn streetActionNo:4 action:call amount:4 -player_name:Player_5 street:River streetActionNo:0 action:check amount:0 -player_name:Player_5 street:River streetActionNo:2 action:call amount:4 -player_name:Player_6 street:Preflop streetActionNo:2 action:fold amount:0 -player_name:Player_7 street:Preflop streetActionNo:3 action:fold amount:0 +From Table HandsActions +======================= +playerName:Player_1 street:Preflop streetActionNo:4 action:fold amount:0 +playerName:Player_2 street:Preflop streetActionNo:5 action:call amount:2 +playerName:Player_2 street:Flop streetActionNo:2 action:call amount:2 +playerName:Player_2 street:Turn streetActionNo:2 action:bet amount:8 +playerName:Player_2 street:River streetActionNo:1 action:bet amount:4 +playerName:Player_3 street:Preflop streetActionNo:6 action:fold amount:0 +playerName:Player_4 street:Preflop streetActionNo:0 action:blind amount:1 +playerName:Player_4 street:Preflop streetActionNo:7 action:call amount:1 +playerName:Player_4 street:Flop streetActionNo:0 action:check amount:0 +playerName:Player_4 street:Flop streetActionNo:3 action:call amount:2 +playerName:Player_4 street:Turn streetActionNo:0 action:check amount:0 +playerName:Player_4 street:Turn streetActionNo:3 action:fold amount:0 +playerName:Player_5 street:Preflop streetActionNo:1 action:blind amount:2 +playerName:Player_5 street:Preflop streetActionNo:8 action:check amount:0 +playerName:Player_5 street:Flop streetActionNo:1 action:bet amount:2 +playerName:Player_5 street:Turn streetActionNo:1 action:bet amount:4 +playerName:Player_5 street:Turn streetActionNo:4 action:call amount:4 +playerName:Player_5 street:River streetActionNo:0 action:check amount:0 +playerName:Player_5 street:River streetActionNo:2 action:call amount:4 +playerName:Player_6 street:Preflop streetActionNo:2 action:fold amount:0 +playerName:Player_7 street:Preflop streetActionNo:3 action:fold amount:0 diff --git a/regression-test/ps.14519433154.expected.txt b/regression-test/ps.14519433154.expected.txt index 6e739a5a..2754cd4d 100644 --- a/regression-test/ps.14519433154.expected.txt +++ b/regression-test/ps.14519433154.expected.txt @@ -1,55 +1,58 @@ Connected to MySQL on localhost. Print Hand Utility -options.site: PokerStars site_id: 2 +options.site: PokerStars siteId: 2 -From Table gametypes +From Table Hands +================ +handId: 3 tableName: Merope siteHandNo: 14519433154 gametypeId: 1 handStart: 2008-01-13 05:24:25 seats: 7 maxSeats: 10 + +From Table Gametypes ==================== -type: ring category: holdem limit_type: fl +type: ring base: hold category: holdem limitType: fl hiLo: h sb: 1 bb: 2 sbet: 2 bbet: 4 -From Table hands -================ -site_hand_no: 14519433154 hand_start: 2008-01-13 05:24:25 seat_count: 7 +From Table BoardCards +===================== Board cards: 4h 9s Ad Qc Ks -From Table hands_players -======================== -player_name:Player_1 player_startcash:65 position:3 off Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_2 player_startcash:68 position:2 off Btn cards:?? ?? winnings:0 rake:0 -player_name:Player_3 player_startcash:179 position:1 off Btn cards:Ah 9d winnings:92 rake:4 -player_name:Player_4 player_startcash:201 position:Btn cards:Ac Td winnings:0 rake:0 -player_name:Player_5 player_startcash:102 position:SB cards:?? ?? winnings:0 rake:0 -player_name:Player_6 player_startcash:34 position:BB cards:?? ?? winnings:0 rake:0 -player_name:Player_7 player_startcash:135 position:4 off Btn cards:7c Jh winnings:0 rake:0 +From Table HandsPlayers +======================= +playerName:Player_1 playerStartcash:65 position:3 off Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_2 playerStartcash:68 position:2 off Btn cards:?? ?? winnings:0 rake:0 +playerName:Player_3 playerStartcash:179 position:1 off Btn cards:Ah 9d winnings:92 rake:4 +playerName:Player_4 playerStartcash:201 position:Btn cards:Ac Td winnings:0 rake:0 +playerName:Player_5 playerStartcash:102 position:SB cards:?? ?? winnings:0 rake:0 +playerName:Player_6 playerStartcash:34 position:BB cards:?? ?? winnings:0 rake:0 +playerName:Player_7 playerStartcash:135 position:4 off Btn cards:7c Jh winnings:0 rake:0 -From Table hands_actions -======================== -player_name:Player_1 street:Preflop streetActionNo:3 action:fold amount:0 -player_name:Player_2 street:Preflop streetActionNo:4 action:fold amount:0 -player_name:Player_3 street:Preflop streetActionNo:5 action:call amount:2 -player_name:Player_3 street:Preflop streetActionNo:9 action:call amount:2 -player_name:Player_3 street:Flop streetActionNo:2 action:bet amount:2 -player_name:Player_3 street:Flop streetActionNo:6 action:bet amount:4 -player_name:Player_3 street:Flop streetActionNo:8 action:call amount:2 -player_name:Player_3 street:Turn streetActionNo:0 action:bet amount:4 -player_name:Player_3 street:Turn streetActionNo:2 action:bet amount:8 -player_name:Player_3 street:Turn streetActionNo:4 action:call amount:4 -player_name:Player_3 street:River streetActionNo:0 action:bet amount:4 -player_name:Player_3 street:River streetActionNo:2 action:bet amount:8 -player_name:Player_3 street:River streetActionNo:4 action:call amount:4 -player_name:Player_4 street:Preflop streetActionNo:6 action:call amount:2 -player_name:Player_4 street:Preflop streetActionNo:10 action:call amount:2 -player_name:Player_4 street:Flop streetActionNo:3 action:bet amount:4 -player_name:Player_4 street:Flop streetActionNo:7 action:bet amount:4 -player_name:Player_4 street:Turn streetActionNo:1 action:bet amount:8 -player_name:Player_4 street:Turn streetActionNo:3 action:bet amount:8 -player_name:Player_4 street:River streetActionNo:1 action:bet amount:8 -player_name:Player_4 street:River streetActionNo:3 action:bet amount:8 -player_name:Player_5 street:Preflop streetActionNo:0 action:blind amount:1 -player_name:Player_5 street:Preflop streetActionNo:7 action:bet amount:3 -player_name:Player_5 street:Flop streetActionNo:0 action:check amount:0 -player_name:Player_5 street:Flop streetActionNo:4 action:fold amount:0 -player_name:Player_6 street:Preflop streetActionNo:1 action:blind amount:2 -player_name:Player_6 street:Preflop streetActionNo:8 action:call amount:2 -player_name:Player_6 street:Flop streetActionNo:1 action:check amount:0 -player_name:Player_6 street:Flop streetActionNo:5 action:fold amount:0 -player_name:Player_7 street:Preflop streetActionNo:2 action:fold amount:0 +From Table HandsActions +======================= +playerName:Player_1 street:Preflop streetActionNo:3 action:fold amount:0 +playerName:Player_2 street:Preflop streetActionNo:4 action:fold amount:0 +playerName:Player_3 street:Preflop streetActionNo:5 action:call amount:2 +playerName:Player_3 street:Preflop streetActionNo:9 action:call amount:2 +playerName:Player_3 street:Flop streetActionNo:2 action:bet amount:2 +playerName:Player_3 street:Flop streetActionNo:6 action:bet amount:4 +playerName:Player_3 street:Flop streetActionNo:8 action:call amount:2 +playerName:Player_3 street:Turn streetActionNo:0 action:bet amount:4 +playerName:Player_3 street:Turn streetActionNo:2 action:bet amount:8 +playerName:Player_3 street:Turn streetActionNo:4 action:call amount:4 +playerName:Player_3 street:River streetActionNo:0 action:bet amount:4 +playerName:Player_3 street:River streetActionNo:2 action:bet amount:8 +playerName:Player_3 street:River streetActionNo:4 action:call amount:4 +playerName:Player_4 street:Preflop streetActionNo:6 action:call amount:2 +playerName:Player_4 street:Preflop streetActionNo:10 action:call amount:2 +playerName:Player_4 street:Flop streetActionNo:3 action:bet amount:4 +playerName:Player_4 street:Flop streetActionNo:7 action:bet amount:4 +playerName:Player_4 street:Turn streetActionNo:1 action:bet amount:8 +playerName:Player_4 street:Turn streetActionNo:3 action:bet amount:8 +playerName:Player_4 street:River streetActionNo:1 action:bet amount:8 +playerName:Player_4 street:River streetActionNo:3 action:bet amount:8 +playerName:Player_5 street:Preflop streetActionNo:0 action:blind amount:1 +playerName:Player_5 street:Preflop streetActionNo:7 action:bet amount:3 +playerName:Player_5 street:Flop streetActionNo:0 action:check amount:0 +playerName:Player_5 street:Flop streetActionNo:4 action:fold amount:0 +playerName:Player_6 street:Preflop streetActionNo:1 action:blind amount:2 +playerName:Player_6 street:Preflop streetActionNo:8 action:call amount:2 +playerName:Player_6 street:Flop streetActionNo:1 action:check amount:0 +playerName:Player_6 street:Flop streetActionNo:5 action:fold amount:0 +playerName:Player_7 street:Preflop streetActionNo:2 action:fold amount:0 diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index 0982958e..b72092b5 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -15,7 +15,7 @@ #In the "official" distribution you can find the license in #agpl-3.0.txt in the docs folder of the package. -echo "Please note for this to really work you need to work on an empty database" +echo "Please note for this to work you need to work on an empty database, otherwise some info (the id fields) will be off" rm *.found.txt ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x @@ -26,11 +26,11 @@ rm *.found.txt echo "it should've reported first that it stored 3, then that it had 3 duplicates" #echo " then 1 stored, then 5 stored" -./print_hand.py -p$1 --hand=14519394979 > ps.14519394979.found.txt && colordiff ps.14519394979.found.txt ps.14519394979.expected.txt -./print_hand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt -./print_hand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt +./PrintHand.py -p$1 --hand=14519394979 > ps.14519394979.found.txt && colordiff ps.14519394979.found.txt ps.14519394979.expected.txt +./PrintHand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt +./PrintHand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt -./PrintPlayerHudData.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt +#./PrintPlayerHudData.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ftp.6367428246.found.txt && colordiff ftp.6367428246.found.txt ftp.6367428246.expected.txt From 60bdfb64f3c1c2f3eff4f6d0901f9b2b2b0ebbf7 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sat, 23 Aug 2008 01:16:35 +0100 Subject: [PATCH 069/262] p68 - updated PrintPlayerHudData. this is alpha2 --- docs/known-bugs-and-planned-features.txt | 6 +- docs/release-notes.txt | 22 +++++++- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 3 + regression-test/PrintPlayerHudData.py | 58 +++++++++++--------- regression-test/ps-flags-3hands.expected.txt | 38 +++++++------ regression-test/regression-test.sh | 2 +- 7 files changed, 80 insertions(+), 51 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 78cee90a..90e6fa0d 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,15 +1,16 @@ todolist (db=database, imp=importer, tv=tableviewer) -Everything is subject to change and especially the order will often change. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. +Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt alpha2 (release 22Aug) ====== seperate and improve instructions for update -PrintPlayerHudData update website for dropping profile subfolder alpha3 (release by 31Aug) ====== +anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no +update PrintPlayerHudData to take position and all the new fields into account specify NOT NULL on almost all table columns ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config update status or make a support matrix table for website @@ -35,7 +36,6 @@ fill the fold to CB/2B/3B cache fields verify the totalProfit cache field fill the check-/call-raise cache fields move version into seperate file for fpdb gui and db -anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no table with data for graphs for SD/F, W$wSF, W$@SD SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug show database version error in GUI and use fpdb_db class const for it, add it to title diff --git a/docs/release-notes.txt b/docs/release-notes.txt index 714d9342..f4616142 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -1,7 +1,25 @@ alpha2 ====== -Main new features: -Now reads and displays steal attempts and reaction to them +Hi everyone, +we are proud to announce the second alpha release of fpdb, the free/libre open source poker tracker. The biggest highlight is the HUD for Linux made by Ray (and perhaps Mac, not sure). But for everyone fpdb now also supports PS tournaments (SnG and MTT) and FTP ring games. There is also a new auto-importer though it currently only runs on one file per instance (though you can open multiple instances of fpdb). +Fpdb also parses alot of new situations into the cache table for the HUD, most of which are already available through the Linux HUD. There have also been a large number of bugfixes for all kinds of things. For full details of the changes see the git commit comments. + +The website will be updated shortly but in the meantime please note that the configuration file now resides in ~/.fpdb/default.conf on Linux/Mac and in C:\Documents and Settings\Application Data\fpdb\default.conf on Windows (you have to translate the Documents and Settings, but not the Application data bit). Apart from that just follow the instructions on the site. I will be updating/improving the fpdb ebuild for Gentoo next week, please send me a PM or email if you would like a package for another Linux/BSD. + +To use the HUD in Linux simply run the auto importer whilst having the table open. You will also have to rename HUD_config.xml.example to HUD_config.xml and edit the database parameters in there in addition to what is written in the install guide. + +If you've been using fpdb alpha1 just delete the fpdb folder, follow the installation guide except for installing the dependencies, then run fpdb and run recreate tables. Note that you will have to reimport ALL your history files. + +For external devs (in particular Youre Toast): The table design has gone through some changes, though most of it was just renaming to make a consistent naming scheme and to make it more flexible towards stud/razz type games. I do not expect to having to make any future changes to the existing fields but I cannot yet guarantee that. There will be some additional fields in particular in the HudCache table. Please let me know if you need any further fields. + + +Any questions and comments please post here, send me a PM or email, post a feature request or bug report on sourceforge or use any of the other means of contact listed on our webpage. + +Enjoy, +The fpdb team + +PS: I personally will be away Sun-Tue so I probably won't be responding to anything but feel free to message me anyways, but if you post here or at SF someone else might be able to help. + alpha1 ====== diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index ebbe9901..9757ee2e 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -376,7 +376,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha1+, p67") + self.window.set_title("Free Poker DB - version: alpha2, p68") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index d65c1a7f..f10b52a8 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -297,15 +297,18 @@ class fpdb_db: street0Aggr INT, street0_3B4BChance INT, street0_3B4BDone INT, + street1Seen INT, street2Seen INT, street3Seen INT, street4Seen INT, sawShowdown INT, + street1Aggr INT, street2Aggr INT, street3Aggr INT, street4Aggr INT, + otherRaisedStreet1 INT, otherRaisedStreet2 INT, otherRaisedStreet3 INT, diff --git a/regression-test/PrintPlayerHudData.py b/regression-test/PrintPlayerHudData.py index 99be5a4e..be5be0d0 100755 --- a/regression-test/PrintPlayerHudData.py +++ b/regression-test/PrintPlayerHudData.py @@ -43,16 +43,16 @@ print "Basic Data" print "==========" print "bigblind:",options.bigblind, "category:",options.cat, "limitType:", options.limit, "name:", options.name, "gameType:", options.gameType, "site:", options.site -cursor.execute("SELECT id FROM sites WHERE name=%s", (options.site,)) +cursor.execute("SELECT id FROM Sites WHERE name=%s", (options.site,)) siteId=cursor.fetchone()[0] -cursor.execute("SELECT id FROM gametypes WHERE big_blind=%s AND category=%s AND site_id=%s AND limit_type=%s AND type=%s", (options.bigblind, options.cat, siteId, options.limit, options.gameType)) +cursor.execute("SELECT id FROM Gametypes WHERE bigBlind=%s AND category=%s AND siteId=%s AND limitType=%s AND type=%s", (options.bigblind, options.cat, siteId, options.limit, options.gameType)) gametypeId=cursor.fetchone()[0] -cursor.execute("SELECT id FROM players WHERE name=%s", (options.name,)) +cursor.execute("SELECT id FROM Players WHERE name=%s", (options.name,)) playerId=cursor.fetchone()[0] -cursor.execute("SELECT id FROM HudDataHoldemOmaha WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s",(gametypeId, playerId, options.seats)) +cursor.execute("SELECT id FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s",(gametypeId, playerId, options.seats)) hudDataId=cursor.fetchone()[0] print "siteId:", siteId, "gametypeId:", gametypeId, "playerId:", playerId, "hudDataId:", hudDataId @@ -61,43 +61,47 @@ print "" print "HUD Raw Hand Counts" print "===================" -cursor.execute ("SELECT HDs, VPIP, PFR, PF3B4BChance, PF3B4B FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +cursor.execute ("SELECT HDs, street0VPI, street0Aggr, street0_3B4BChance, street0_3B4BDone FROM HudCache WHERE id=%s", (hudDataId,)) fields=cursor.fetchone() print "HDs:",fields[0] -print "VPIP:",fields[1] -print "PFR:",fields[2] -print "PF3B4BChance:",fields[3] -print "PF3B4B:",fields[4] +print "street0VPI:",fields[1] +print "street0Aggr:",fields[2] +print "street0_3B4BChance:",fields[3] +print "street0_3B4BDone:",fields[4] print "" -cursor.execute ("SELECT sawFlop, sawTurn, sawRiver, sawShowdown FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +cursor.execute ("SELECT street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown FROM HudCache WHERE id=%s", (hudDataId,)) fields=cursor.fetchone() -print "sawFlop:",fields[0] -print "sawTurn:",fields[1] -print "sawRiver:",fields[2] -print "sawShowdown:",fields[3] +print "street1Seen:",fields[0] +print "street2Seen:",fields[1] +print "street3Seen:",fields[2] +print "street4Seen:",fields[3] +print "sawShowdown:",fields[4] print "" -cursor.execute ("SELECT raisedFlop, raisedTurn, raisedRiver FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +cursor.execute ("SELECT street1Aggr, street2Aggr, street3Aggr, street4Aggr FROM HudCache WHERE id=%s", (hudDataId,)) fields=cursor.fetchone() -print "raisedFlop:",fields[0] -print "raisedTurn:",fields[1] -print "raisedRiver:",fields[2] +print "street1Aggr:",fields[0] +print "street2Aggr:",fields[1] +print "street3Aggr:",fields[2] +print "street4Aggr:",fields[3] print "" -cursor.execute ("SELECT otherRaisedFlop, otherRaisedFlopFold, otherRaisedTurn, otherRaisedTurnFold, otherRaisedRiver, otherRaisedRiverFold FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +cursor.execute ("SELECT otherRaisedStreet1, otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4, foldToOtherRaisedStreet1, foldToOtherRaisedStreet2, foldToOtherRaisedStreet3, foldToOtherRaisedStreet4 FROM HudCache WHERE id=%s", (hudDataId,)) fields=cursor.fetchone() -print "otherRaisedFlop:",fields[0] -print "otherRaisedFlopFold:",fields[1] -print "otherRaisedTurn:",fields[2] -print "otherRaisedTurnFold:",fields[3] -print "otherRaisedRiver:",fields[4] -print "otherRaisedRiverFold:",fields[5] +print "otherRaisedStreet1:",fields[0] +print "otherRaisedStreet2:",fields[1] +print "otherRaisedStreet3:",fields[2] +print "otherRaisedStreet4:",fields[3] +print "foldToOtherRaisedStreet1:",fields[4] +print "foldToOtherRaisedStreet2:",fields[5] +print "foldToOtherRaisedStreet3:",fields[6] +print "foldToOtherRaisedStreet4:",fields[7] print "" -cursor.execute ("SELECT wonWhenSeenFlop, wonAtSD FROM HudDataHoldemOmaha WHERE id=%s", (hudDataId,)) +cursor.execute ("SELECT wonWhenSeenStreet1, wonAtSD FROM HudCache WHERE id=%s", (hudDataId,)) fields=cursor.fetchone() -print "wonWhenSeenFlop:",fields[0] +print "wonWhenSeenStreet1:",fields[0] print "wonAtSD:",fields[1] diff --git a/regression-test/ps-flags-3hands.expected.txt b/regression-test/ps-flags-3hands.expected.txt index 9f7b2e14..f6224735 100644 --- a/regression-test/ps-flags-3hands.expected.txt +++ b/regression-test/ps-flags-3hands.expected.txt @@ -8,28 +8,32 @@ siteId: 2 gametypeId: 1 playerId: 1 hudDataId: 1 HUD Raw Hand Counts =================== HDs: 3 -VPIP: 1 -PFR: 0 -PF3B4BChance: 1 -PF3B4B: 0 +street0VPI: 1 +street0Aggr: 0 +street0_3B4BChance: 1 +street0_3B4BDone: 0 -sawFlop: 1 -sawTurn: 1 -sawRiver: 1 +street1Seen: 1 +street2Seen: 1 +street3Seen: 1 +street4Seen: 0 sawShowdown: 0 -raisedFlop: 0 -raisedTurn: 0 -raisedRiver: 0 +street1Aggr: 0 +street2Aggr: 0 +street3Aggr: 0 +street4Aggr: 0 -otherRaisedFlop: 1 -otherRaisedFlopFold: 0 -otherRaisedTurn: 1 -otherRaisedTurnFold: 0 -otherRaisedRiver: 1 -otherRaisedRiverFold: 1 +otherRaisedStreet1: 1 +otherRaisedStreet2: 1 +otherRaisedStreet3: 1 +otherRaisedStreet4: 0 +foldToOtherRaisedStreet1: 0 +foldToOtherRaisedStreet2: 0 +foldToOtherRaisedStreet3: 1 +foldToOtherRaisedStreet4: 0 -wonWhenSeenFlop: 0.0 +wonWhenSeenStreet1: 0.0 wonAtSD: 0.0 stealAttemptChance: 0 diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index b72092b5..5e0cfb9d 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -30,7 +30,7 @@ echo "it should've reported first that it stored 3, then that it had 3 duplicate ./PrintHand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt ./PrintHand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt -#./PrintPlayerHudData.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt +./PrintPlayerHudData.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ftp.6367428246.found.txt && colordiff ftp.6367428246.found.txt ftp.6367428246.expected.txt From 700ce6c8f5f862e998fc072aaf225a8f0823ac2a Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sat, 23 Aug 2008 03:54:14 +0100 Subject: [PATCH 070/262] p69 - added position to PrintPlayerHudData and removed the existing .expected file as its useless basically. updated it to include new fields. --- docs/known-bugs-and-planned-features.txt | 11 ++--- docs/release-notes.txt | 10 ++-- regression-test/PrintPlayerHudData.py | 49 +++++++++++++++++++- regression-test/ps-flags-3hands.expected.txt | 44 ------------------ 4 files changed, 59 insertions(+), 55 deletions(-) delete mode 100644 regression-test/ps-flags-3hands.expected.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 90e6fa0d..21ca84d6 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,19 +2,16 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha2 (release 22Aug) -====== -seperate and improve instructions for update -update website for dropping profile subfolder - alpha3 (release by 31Aug) ====== +update website for dropping profile subfolder and add dedicated update page +update status or make a support matrix table for website +add instructions for mailing list to contacts + anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no update PrintPlayerHudData to take position and all the new fields into account specify NOT NULL on almost all table columns ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config -update status or make a support matrix table for website -add instructions for mailing list to contacts auto import only runs on one file per start make windows use correct language version of Appdata, e.g. Anwendungdaten make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) diff --git a/docs/release-notes.txt b/docs/release-notes.txt index f4616142..c8ff9444 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -1,16 +1,18 @@ alpha2 ====== Hi everyone, -we are proud to announce the second alpha release of fpdb, the free/libre open source poker tracker. The biggest highlight is the HUD for Linux made by Ray (and perhaps Mac, not sure). But for everyone fpdb now also supports PS tournaments (SnG and MTT) and FTP ring games. There is also a new auto-importer though it currently only runs on one file per instance (though you can open multiple instances of fpdb). +we are proud to announce the second alpha release of fpdb, the free/libre open source poker tracker. The biggest highlight is the HUD for Linux made by Ray (and perhaps Mac, not sure). But for everyone fpdb now also supports PS tournaments (SnG and MTT) and FTP ring games. There is also a new auto-importer though it currently only runs on one file per instance (but you can open multiple instances of fpdb). Fpdb also parses alot of new situations into the cache table for the HUD, most of which are already available through the Linux HUD. There have also been a large number of bugfixes for all kinds of things. For full details of the changes see the git commit comments. -The website will be updated shortly but in the meantime please note that the configuration file now resides in ~/.fpdb/default.conf on Linux/Mac and in C:\Documents and Settings\Application Data\fpdb\default.conf on Windows (you have to translate the Documents and Settings, but not the Application data bit). Apart from that just follow the instructions on the site. I will be updating/improving the fpdb ebuild for Gentoo next week, please send me a PM or email if you would like a package for another Linux/BSD. +You can download fpdb at https://sourceforge.net/project/showfiles.php?group_id=226872 + +Our website at http://fpdb.sourceforge.net/ will be updated shortly but in the meantime please note that the configuration file now resides in ~/.fpdb/default.conf on Linux/Mac and in C:\Documents and Settings\Application Data\fpdb\default.conf on Windows (on non-English Windows you have to translate the Documents and Settings, but not the Application data bit). Apart from that just follow the instructions on the site. I will be updating/improving the fpdb ebuild for Gentoo next week, please send me a PM or email if you would like a package for another Linux/BSD. To use the HUD in Linux simply run the auto importer whilst having the table open. You will also have to rename HUD_config.xml.example to HUD_config.xml and edit the database parameters in there in addition to what is written in the install guide. If you've been using fpdb alpha1 just delete the fpdb folder, follow the installation guide except for installing the dependencies, then run fpdb and run recreate tables. Note that you will have to reimport ALL your history files. -For external devs (in particular Youre Toast): The table design has gone through some changes, though most of it was just renaming to make a consistent naming scheme and to make it more flexible towards stud/razz type games. I do not expect to having to make any future changes to the existing fields but I cannot yet guarantee that. There will be some additional fields in particular in the HudCache table. Please let me know if you need any further fields. +For external devs (in particular Youre Toast): The table design has gone through some changes, though most of it was just renaming to make a consistent naming scheme and to make it more flexible towards stud/razz type games. I do not expect to having to make any future changes to the existing fields but I cannot yet guarantee that. There will be some additional fields in particular in the HudCache table. Please let me know if you need any further fields. I realise my naming breaks the "database designer's convention" but for this project I feel ignoring this convention for the benefit of consistent naming between Python and SQL is ok. Any questions and comments please post here, send me a PM or email, post a feature request or bug report on sourceforge or use any of the other means of contact listed on our webpage. @@ -20,6 +22,8 @@ The fpdb team PS: I personally will be away Sun-Tue so I probably won't be responding to anything but feel free to message me anyways, but if you post here or at SF someone else might be able to help. +PPS: If you wanna know EXACTLY how this project is moving along follow the git tree over at [url]http://www.assembla.com/spaces/fpdb/trac_git_tool[/url] + alpha1 ====== diff --git a/regression-test/PrintPlayerHudData.py b/regression-test/PrintPlayerHudData.py index be5be0d0..351472b1 100755 --- a/regression-test/PrintPlayerHudData.py +++ b/regression-test/PrintPlayerHudData.py @@ -29,6 +29,7 @@ parser.add_option("-e", "--seats", default="7", type="int", help="number of acti parser.add_option("-g", "--gameType", default="ring", help="Whether its a ringgame (ring) or a tournament (tour)") parser.add_option("-l", "--limit", "--limitType", default="fl", help="Limit Type, one of: nl, pl, fl, cn, cp") parser.add_option("-n", "--name", "--playername", default="Player_1", help="Name of the player to print") +parser.add_option("-o", "--position", default="B", help="Position, can be B, S, or a number between 0 and 7") parser.add_option("-p", "--password", help="The password for the MySQL user") parser.add_option("-s", "--site", default="PokerStars", help="Name of the site (as written in the history files)") @@ -52,7 +53,7 @@ gametypeId=cursor.fetchone()[0] cursor.execute("SELECT id FROM Players WHERE name=%s", (options.name,)) playerId=cursor.fetchone()[0] -cursor.execute("SELECT id FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s",(gametypeId, playerId, options.seats)) +cursor.execute("SELECT id FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s",(gametypeId, playerId, options.seats, options.position)) hudDataId=cursor.fetchone()[0] print "siteId:", siteId, "gametypeId:", gametypeId, "playerId:", playerId, "hudDataId:", hudDataId @@ -104,6 +105,52 @@ fields=cursor.fetchone() print "wonWhenSeenStreet1:",fields[0] print "wonAtSD:",fields[1] +cursor.execute ("SELECT stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal FROM HudCache WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "stealAttemptChance:",fields[0] +print "stealAttempted:",fields[1] +print "foldBbToStealChance:",fields[2] +print "foldedBbToSteal:",fields[3] +print "foldSbToStealChance:",fields[4] +print "foldedSbToSteal:",fields[5] + +cursor.execute ("SELECT street1CBChance, street1CBDone, street2CBChance, street2CBDone, street3CBChance, street3CBDone, street4CBChance, street4CBDone FROM HudCache WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "street1CBChance:",fields[0] +print "street1CBDone:",fields[1] +print "street2CBChance:",fields[2] +print "street2CBDone:",fields[3] +print "street3CBChance:",fields[4] +print "street3CBDone:",fields[5] +print "street4CBChance:",fields[6] +print "street4CBDone:",fields[7] + +cursor.execute ("SELECT foldToStreet1CBChance, foldToStreet1CBDone, foldToStreet2CBChance, foldToStreet2CBDone, foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance, foldToStreet4CBDone FROM HudCache WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "foldToStreet1CBChance:",fields[0] +print "foldToStreet1CBDone:",fields[1] +print "foldToStreet2CBChance:",fields[2] +print "foldToStreet2CBDone:",fields[3] +print "foldToStreet3CBChance:",fields[4] +print "foldToStreet3CBDone:",fields[5] +print "foldToStreet4CBChance:",fields[6] +print "foldToStreet4CBDone:",fields[7] + +cursor.execute ("SELECT totalProfit FROM HudCache WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "totalProfit:",fields[0] + +cursor.execute ("SELECT street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance, street2CheckCallRaiseDone, street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone FROM HudCache WHERE id=%s", (hudDataId,)) +fields=cursor.fetchone() +print "street1CheckCallRaiseChance:",fields[0] +print "street1CheckCallRaiseDone:",fields[1] +print "street2CheckCallRaiseChance:",fields[2] +print "street2CheckCallRaiseDone:",fields[3] +print "street3CheckCallRaiseChance:",fields[4] +print "street3CheckCallRaiseDone:",fields[5] +print "street4CheckCallRaiseChance:",fields[6] +print "street4CheckCallRaiseDone:",fields[7] + cursor.close() db.close() diff --git a/regression-test/ps-flags-3hands.expected.txt b/regression-test/ps-flags-3hands.expected.txt deleted file mode 100644 index f6224735..00000000 --- a/regression-test/ps-flags-3hands.expected.txt +++ /dev/null @@ -1,44 +0,0 @@ -Connected to MySQL on localhost. Print Player Flags Utility - -Basic Data -========== -bigblind: 2 category: holdem limitType: fl name: Player_1 gameType: ring site: PokerStars -siteId: 2 gametypeId: 1 playerId: 1 hudDataId: 1 - -HUD Raw Hand Counts -=================== -HDs: 3 -street0VPI: 1 -street0Aggr: 0 -street0_3B4BChance: 1 -street0_3B4BDone: 0 - -street1Seen: 1 -street2Seen: 1 -street3Seen: 1 -street4Seen: 0 -sawShowdown: 0 - -street1Aggr: 0 -street2Aggr: 0 -street3Aggr: 0 -street4Aggr: 0 - -otherRaisedStreet1: 1 -otherRaisedStreet2: 1 -otherRaisedStreet3: 1 -otherRaisedStreet4: 0 -foldToOtherRaisedStreet1: 0 -foldToOtherRaisedStreet2: 0 -foldToOtherRaisedStreet3: 1 -foldToOtherRaisedStreet4: 0 - -wonWhenSeenStreet1: 0.0 -wonAtSD: 0.0 - -stealAttemptChance: 0 -stealAttempted: 0 -foldBbToStealChance: 0 -foldedBbToSteal: 0 -foldSbToStealChance: 0 -foldedSbToSteal: 0 From 0b96d49b230268e911411f7ef6f9a2045efd664e Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 31 Aug 2008 03:06:24 +0100 Subject: [PATCH 071/262] p70 - it displays GUI error message for missing config file --- docs/known-bugs-and-planned-features.txt | 46 ++++++++++++------------ pyfpdb/fpdb.py | 25 ++++++++++--- pyfpdb/fpdb_import.py | 2 +- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 21ca84d6..2a93f965 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -4,46 +4,46 @@ Please also see db-todo.txt alpha3 (release by 31Aug) ====== -update website for dropping profile subfolder and add dedicated update page +add dedicated update page update status or make a support matrix table for website add instructions for mailing list to contacts +(fixed by ray) auto import only runs on one file per start +find correct sf logo link +show database version error in GUI and use fpdb_db class const for it, add it to title + +hud displayed 0/3 but a 2 digit percentage for W$SD? anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no -update PrintPlayerHudData to take position and all the new fields into account specify NOT NULL on almost all table columns -ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config -auto import only runs on one file per start -make windows use correct language version of Appdata, e.g. Anwendungdaten -make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) store raw hand in db and write reimport function using the raw hand field -make it work with postgres -expand instructions for profile file +make windows use correct language version of Appdata, e.g. Anwendungdaten. http://mail.python.org/pipermail/python-list/2005-September/341702.html + +ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config ftp: read maxSeats -add sf.net logo to webpage -separate db table design version and last bugfix in importer -change tabledesign VALIGN -finish updating filelist -finish todos in git instructions make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt (steffen) finish bringing back tourney export settings[hud-defaultInterval] to conf -fix up bg colours in tv -fill the fold to CB/2B/3B cache fields -verify the totalProfit cache field -fill the check-/call-raise cache fields -move version into seperate file for fpdb gui and db +fill the fold to CB/2B/3B, check-/call-raise cache fields table with data for graphs for SD/F, W$wSF, W$@SD -SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug -show database version error in GUI and use fpdb_db class const for it, add it to title -split hud data generation into separate for loops and make it more efficient -fix bug that sawFlop/Turn/River gets miscalculated if someone is allin - might as well add all-in recognition for this -verify positionalness of HudData printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt before beta =========== +separate db table design version and last bugfix in importer +change tabledesign VALIGN +finish updating filelist +finish todos in git instructions +fix up bg colours in tv +move version into seperate file for fpdb gui and db +SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug +create little test script for people to run to verify successful installation of pydeps +split hud data generation into separate for loops and make it more efficient +fix bug that sawFlop/Turn/River gets miscalculated if someone is allin - might as well add all-in recognition for this +make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) +make it work with postgres +expand instructions for profile file maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? rakeback/frequent player points diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 9757ee2e..ff561cc5 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -200,11 +200,26 @@ class fpdb: print "todo: implement dia_save_profile" #end def dia_save_profile - def dia_setup_wizard(self, path): + def diaSetupWizard(self, path): print "todo: implement setup wizard" - print "setup wizard not implemented - please create the default configuration file:", path + print "setup wizard not implemented - please create the default configuration file:", path + diaSetupWizard = gtk.Dialog(title="Fatal Error - Config File Missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) + + label = gtk.Label("Please copy the config file from the docs folder to:") + diaSetupWizard.vbox.add(label) + label.show() + + label = gtk.Label(path) + diaSetupWizard.vbox.add(label) + label.show() + + label = gtk.Label("and edit it according to the install documentation at http://fpdb.sourceforge.net") + diaSetupWizard.vbox.add(label) + label.show() + + response = diaSetupWizard.run() sys.exit(1) - #end def dia_setup_wizard + #end def diaSetupWizard def get_menu(self, window): """returns the menu for this program""" @@ -225,12 +240,12 @@ class fpdb: defaultpath+=("Application Data"+os.sep) else:#ie. if real OS prefix fpdb with a . as it is convention defaultpath+="." - defaultpath+=("fpdb"+os.sep+os.sep+"default.conf") + defaultpath+=("fpdb"+os.sep+"default.conf") if os.path.exists(defaultpath): self.load_profile(defaultpath) else: - self.dia_setup_wizard(path=defaultpath) + self.diaSetupWizard(path=defaultpath) #end def load_default_profile def load_profile(self, filename): diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 94cc98f0..9522abc6 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -108,7 +108,7 @@ def import_file_dict(options, settings, callHud=True): stored+=1 if settings['imp-callFpdbHud'] and callHud and os.sep=='/': #print "call to HUD here. handsId:",handsId -# pipe the Hands.id out to the HUD + #pipe the Hands.id out to the HUD options.pipe_to_hud.stdin.write("%s\n" % (handsId)) db.commit() except fpdb_simple.DuplicateError: From a289f7b151df3e9b4da57e2d43ece0a804cbbf4b Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 31 Aug 2008 21:22:36 +0100 Subject: [PATCH 072/262] p71 - fixed guibulkimport. thought I'd already done that, but there you go.. added 4B detection to HudCache filling added primitive filling code for foldToStreetXCB, this'll not be correct for all cases yet --- docs/known-bugs-and-planned-features.txt | 12 +-- pyfpdb/GuiBulkImport.py | 4 +- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_simple.py | 108 +++++++++++++++++------ 4 files changed, 89 insertions(+), 37 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 2a93f965..18159b13 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,17 +2,14 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha3 (release by 31Aug) +alpha3 (release 1Sep?) ====== -add dedicated update page -update status or make a support matrix table for website -add instructions for mailing list to contacts (fixed by ray) auto import only runs on one file per start find correct sf logo link +hud displayed 0/3 but a 2 digit percentage for W$SD? show database version error in GUI and use fpdb_db class const for it, add it to title -hud displayed 0/3 but a 2 digit percentage for W$SD? anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no specify NOT NULL on almost all table columns store raw hand in db and write reimport function using the raw hand field @@ -24,13 +21,16 @@ make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt (steffen) finish bringing back tourney export settings[hud-defaultInterval] to conf -fill the fold to CB/2B/3B, check-/call-raise cache fields +fill check-/call-raise cache fields +fix foldToCB cache fields to catch everything properly table with data for graphs for SD/F, W$wSF, W$@SD printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt before beta =========== +add dedicated update page +update status or make a support matrix table for website separate db table design version and last bugfix in importer change tabledesign VALIGN finish updating filelist diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 4d91c893..0a6bd10d 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -32,7 +32,7 @@ class GuiBulkImport (threading.Thread): print "BulkImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file - fpdb_import.import_file_dict(self, self.settings) + fpdb_import.import_file_dict(self, self.settings, False) print "GuiBulkImport.import_dir done" def load_clicked(self, widget, data=None): @@ -69,7 +69,7 @@ class GuiBulkImport (threading.Thread): if os.path.isdir(self.inputFile): self.import_dir() else: - fpdb_import.import_file_dict(self, self.settings) + fpdb_import.import_file_dict(self, self.settings, False) def get_vbox(self): """returns the vbox of this thread""" diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index ff561cc5..0210ef78 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -391,7 +391,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha2, p68") + self.window.set_title("Free Poker DB - version: alpha2+, p71") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2307e8f0..a1b6aca1 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1608,6 +1608,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #now CB street1CBChance=[] street1CBDone=[] + someoneDidStreet1CB=False for player in range (len(player_ids)): myStreet1CBChance=False myStreet1CBDone=False @@ -1616,6 +1617,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet1CBChance=True if street1Aggr[player]: myStreet1CBDone=True + someoneDidStreet1CB=True street1CBChance.append(myStreet1CBChance) street1CBDone.append(myStreet1CBDone) @@ -1625,6 +1627,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #now 2B street2CBChance=[] street2CBDone=[] + someoneDidStreet2CB=False for player in range (len(player_ids)): myStreet2CBChance=False myStreet2CBDone=False @@ -1633,6 +1636,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet2CBChance=True if street2Aggr[player]: myStreet2CBDone=True + someoneDidStreet2CB=True street2CBChance.append(myStreet2CBChance) street2CBDone.append(myStreet2CBDone) @@ -1642,6 +1646,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #now 3B street3CBChance=[] street3CBDone=[] + someoneDidStreet3CB=False for player in range (len(player_ids)): myStreet3CBChance=False myStreet3CBDone=False @@ -1650,19 +1655,27 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet3CBChance=True if street3Aggr[player]: myStreet3CBDone=True + someoneDidStreet3CB=True street3CBChance.append(myStreet3CBChance) street3CBDone.append(myStreet3CBDone) result['street3CBChance']=street3CBChance result['street3CBDone']=street3CBDone - #4B - todo, implement for stud/razz + #and 4B street4CBChance=[] street4CBDone=[] + someoneDidStreet4CB=False for player in range (len(player_ids)): myStreet4CBChance=False myStreet4CBDone=False + if street3CBDone[player]: + myStreet4CBChance=True + if street4Aggr[player]: + myStreet4CBDone=True + someoneDidStreet4CB=True + street4CBChance.append(myStreet4CBChance) street4CBDone.append(myStreet4CBDone) result['street4CBChance']=street4CBChance @@ -1680,6 +1693,72 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings foldToStreet4CBChance=[] foldToStreet4CBDone=[] + for player in range (len(player_ids)): + myFoldToStreet1CBChance=False + myFoldToStreet1CBDone=False + if someoneDidStreet1CB: + if street1CBDone[player]: + pass + elif street1Seen[player]: + myFoldToStreet1CBChance=True + if foldToOtherRaisedStreet1[player]: + myFoldToStreet1CBDone=True + + foldToStreet1CBChance.append(myFoldToStreet1CBChance) + foldToStreet1CBDone.append(myFoldToStreet1CBDone) + + for player in range (len(player_ids)): + myFoldToStreet2CBChance=False + myFoldToStreet2CBDone=False + if someoneDidStreet2CB: + if street2CBDone[player]: + pass + elif street2Seen[player]: + myFoldToStreet2CBChance=True + if foldToOtherRaisedStreet2[player]: + myFoldToStreet2CBDone=True + + foldToStreet2CBChance.append(myFoldToStreet2CBChance) + foldToStreet2CBDone.append(myFoldToStreet2CBDone) + + for player in range (len(player_ids)): + myFoldToStreet3CBChance=False + myFoldToStreet3CBDone=False + if someoneDidStreet3CB: + if street3CBDone[player]: + pass + elif street3Seen[player]: + myFoldToStreet3CBChance=True + if foldToOtherRaisedStreet3[player]: + myFoldToStreet3CBDone=True + + foldToStreet3CBChance.append(myFoldToStreet3CBChance) + foldToStreet3CBDone.append(myFoldToStreet3CBDone) + + for player in range (len(player_ids)): + myFoldToStreet4CBChance=False + myFoldToStreet4CBDone=False + if someoneDidStreet4CB: + if street4CBDone[player]: + pass + elif street4Seen[player]: + myFoldToStreet4CBChance=True + if foldToOtherRaisedStreet4[player]: + myFoldToStreet4CBDone=True + + foldToStreet4CBChance.append(myFoldToStreet4CBChance) + foldToStreet4CBDone.append(myFoldToStreet4CBDone) + + result['foldToStreet1CBChance']=foldToStreet1CBChance + result['foldToStreet1CBDone']=foldToStreet1CBDone + result['foldToStreet2CBChance']=foldToStreet2CBChance + result['foldToStreet2CBDone']=foldToStreet2CBDone + result['foldToStreet3CBChance']=foldToStreet3CBChance + result['foldToStreet3CBDone']=foldToStreet3CBDone + result['foldToStreet4CBChance']=foldToStreet4CBChance + result['foldToStreet4CBDone']=foldToStreet4CBDone + + totalProfit=[] street1CheckCallRaiseChance=[] @@ -1691,15 +1770,6 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings street4CheckCallRaiseChance=[] street4CheckCallRaiseDone=[] for player in range (len(player_ids)): - myFoldToStreet1CBChance=False - myFoldToStreet1CBDone=False - myFoldToStreet2CBChance=False - myFoldToStreet2CBDone=False - myFoldToStreet3CBChance=False - myFoldToStreet3CBDone=False - myFoldToStreet4CBChance=False - myFoldToStreet4CBDone=False - myTotalProfit=0 myStreet1CheckCallRaiseChance=False @@ -1711,15 +1781,6 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet4CheckCallRaiseChance=False myStreet4CheckCallRaiseDone=False - foldToStreet1CBChance.append(myFoldToStreet1CBChance) - foldToStreet1CBDone.append(myFoldToStreet1CBDone) - foldToStreet2CBChance.append(myFoldToStreet2CBChance) - foldToStreet2CBDone.append(myFoldToStreet2CBDone) - foldToStreet3CBChance.append(myFoldToStreet3CBChance) - foldToStreet3CBDone.append(myFoldToStreet3CBDone) - foldToStreet4CBChance.append(myFoldToStreet4CBChance) - foldToStreet4CBDone.append(myFoldToStreet4CBDone) - totalProfit.append(myTotalProfit) street1CheckCallRaiseChance.append(myStreet1CheckCallRaiseChance) @@ -1730,15 +1791,6 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings street3CheckCallRaiseDone.append(myStreet3CheckCallRaiseDone) street4CheckCallRaiseChance.append(myStreet4CheckCallRaiseChance) street4CheckCallRaiseDone.append(myStreet4CheckCallRaiseDone) - - result['foldToStreet1CBChance']=foldToStreet1CBChance - result['foldToStreet1CBDone']=foldToStreet1CBDone - result['foldToStreet2CBChance']=foldToStreet2CBChance - result['foldToStreet2CBDone']=foldToStreet2CBDone - result['foldToStreet3CBChance']=foldToStreet3CBChance - result['foldToStreet3CBDone']=foldToStreet3CBDone - result['foldToStreet4CBChance']=foldToStreet4CBChance - result['foldToStreet4CBDone']=foldToStreet4CBDone result['totalProfit']=totalProfit From 37e111067bd33227486dd8d58806d96d65a255e7 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 31 Aug 2008 23:37:40 +0100 Subject: [PATCH 073/262] p72 - redid foldToCB, this might be correct now but havent verified --- docs/known-bugs-and-planned-features.txt | 1 - pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_parse_logic.py | 7 +- pyfpdb/fpdb_simple.py | 93 ++++++++++++------------ 4 files changed, 52 insertions(+), 51 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 18159b13..fad9b4ae 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -22,7 +22,6 @@ update abbreviations.txt (steffen) finish bringing back tourney export settings[hud-defaultInterval] to conf fill check-/call-raise cache fields -fix foldToCB cache fields to catch everything properly table with data for graphs for SD/F, W$wSF, W$@SD printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 0210ef78..327cf2e2 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -391,7 +391,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha2+, p71") + self.window.set_title("Free Poker DB - version: alpha2+, p72") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 8ab7fe2e..f439d1c5 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -70,13 +70,16 @@ def mainParser(db, cursor, site, category, hand): #3b read positions if (category=="holdem" or category=="omahahi" or category=="omahahilo"): positions = fpdb_simple.parsePositions (hand, names) + base="hold" + else: + base="stud" #part 4: take appropriate action for each line based on linetype for i in range(len(hand)): if (lineTypes[i]=="cards"): fpdb_simple.parseCardLine (site, category, lineStreets[i], hand[i], names, cardValues, cardSuits, boardValues, boardSuits) elif (lineTypes[i]=="action"): - fpdb_simple.parseActionLine (site, hand[i], lineStreets[i], playerIDs, names, actionTypes, actionAmounts, actionNos, actionTypeByNo) + fpdb_simple.parseActionLine (site, base, hand[i], lineStreets[i], playerIDs, names, actionTypes, actionAmounts, actionNos, actionTypeByNo) elif (lineTypes[i]=="win"): fpdb_simple.parseWinLine (hand[i], site, names, winnings, isTourney) elif (lineTypes[i]=="rake"): @@ -113,7 +116,7 @@ def mainParser(db, cursor, site, category, hand): totalWinnings=0 for i in range(len(winnings)): totalWinnings+=winnings[i] - hudImportData=fpdb_simple.generateHudData(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) + hudImportData=fpdb_simple.generateHudCacheData(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) if isTourney: ranks=[] diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index a1b6aca1..b420bd26 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -586,7 +586,7 @@ def parseActionAmount(line, atype, site): #doesnt return anything, simply changes the passed arrays action_types and # action_amounts. For stud this expects numeric streets (3-7), for # holdem/omaha it expects predeal, preflop, flop, turn or river -def parseActionLine(site, line, street, playerIDs, names, action_types, action_amounts, actionNos, actionTypeByNo): +def parseActionLine(site, base, line, street, playerIDs, names, action_types, action_amounts, actionNos, actionTypeByNo): #this only applies to stud if (street<3): text="invalid street ("+str(street)+") for line: "+line @@ -1283,7 +1283,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, return result #end def store_hands_players_stud_tourney -def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings, totalWinnings, positions): +def generateHudCacheData(player_ids, category, action_types, actionTypeByNo, winnings, totalWinnings, positions): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" #setup subarrays of the result dictionary. street0VPI=[] @@ -1608,7 +1608,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #now CB street1CBChance=[] street1CBDone=[] - someoneDidStreet1CB=False + didStreet1CB=[] for player in range (len(player_ids)): myStreet1CBChance=False myStreet1CBDone=False @@ -1617,7 +1617,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet1CBChance=True if street1Aggr[player]: myStreet1CBDone=True - someoneDidStreet1CB=True + didStreet1CB.append(player_ids[player]) street1CBChance.append(myStreet1CBChance) street1CBDone.append(myStreet1CBDone) @@ -1627,7 +1627,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #now 2B street2CBChance=[] street2CBDone=[] - someoneDidStreet2CB=False + didStreet2CB=[] for player in range (len(player_ids)): myStreet2CBChance=False myStreet2CBDone=False @@ -1636,7 +1636,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet2CBChance=True if street2Aggr[player]: myStreet2CBDone=True - someoneDidStreet2CB=True + didStreet2CB.append(player_ids[player]) street2CBChance.append(myStreet2CBChance) street2CBDone.append(myStreet2CBDone) @@ -1646,7 +1646,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #now 3B street3CBChance=[] street3CBDone=[] - someoneDidStreet3CB=False + didStreet3CB=[] for player in range (len(player_ids)): myStreet3CBChance=False myStreet3CBDone=False @@ -1655,7 +1655,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet3CBChance=True if street3Aggr[player]: myStreet3CBDone=True - someoneDidStreet3CB=True + didStreet3CB.append(player_ids[player]) street3CBChance.append(myStreet3CBChance) street3CBDone.append(myStreet3CBDone) @@ -1665,7 +1665,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings #and 4B street4CBChance=[] street4CBDone=[] - someoneDidStreet4CB=False + didStreet4CB=[] for player in range (len(player_ids)): myStreet4CBChance=False myStreet4CBDone=False @@ -1674,7 +1674,7 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings myStreet4CBChance=True if street4Aggr[player]: myStreet4CBDone=True - someoneDidStreet4CB=True + didStreet4CB.append(player_ids[player]) street4CBChance.append(myStreet4CBChance) street4CBDone.append(myStreet4CBDone) @@ -1696,59 +1696,38 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings for player in range (len(player_ids)): myFoldToStreet1CBChance=False myFoldToStreet1CBDone=False - if someoneDidStreet1CB: - if street1CBDone[player]: - pass - elif street1Seen[player]: - myFoldToStreet1CBChance=True - if foldToOtherRaisedStreet1[player]: - myFoldToStreet1CBDone=True - foldToStreet1CBChance.append(myFoldToStreet1CBChance) foldToStreet1CBDone.append(myFoldToStreet1CBDone) - - for player in range (len(player_ids)): + myFoldToStreet2CBChance=False myFoldToStreet2CBDone=False - if someoneDidStreet2CB: - if street2CBDone[player]: - pass - elif street2Seen[player]: - myFoldToStreet2CBChance=True - if foldToOtherRaisedStreet2[player]: - myFoldToStreet2CBDone=True - foldToStreet2CBChance.append(myFoldToStreet2CBChance) foldToStreet2CBDone.append(myFoldToStreet2CBDone) - for player in range (len(player_ids)): myFoldToStreet3CBChance=False myFoldToStreet3CBDone=False - if someoneDidStreet3CB: - if street3CBDone[player]: - pass - elif street3Seen[player]: - myFoldToStreet3CBChance=True - if foldToOtherRaisedStreet3[player]: - myFoldToStreet3CBDone=True - foldToStreet3CBChance.append(myFoldToStreet3CBChance) foldToStreet3CBDone.append(myFoldToStreet3CBDone) - for player in range (len(player_ids)): myFoldToStreet4CBChance=False myFoldToStreet4CBDone=False - if someoneDidStreet4CB: - if street4CBDone[player]: - pass - elif street4Seen[player]: - myFoldToStreet4CBChance=True - if foldToOtherRaisedStreet4[player]: - myFoldToStreet4CBDone=True - foldToStreet4CBChance.append(myFoldToStreet4CBChance) foldToStreet4CBDone.append(myFoldToStreet4CBDone) + + print "actionTypeByNo:", actionTypeByNo + + if len(didStreet1CB)>=1: + generateFoldToCB(1, player_ids, didStreet1CB, street1CBDone, foldToStreet1CBChance, foldToStreet1CBDone, actionTypeByNo) + if len(didStreet2CB)>=1: + generateFoldToCB(2, player_ids, didStreet2CB, street2CBDone, foldToStreet2CBChance, foldToStreet2CBDone, actionTypeByNo) + + if len(didStreet3CB)>=1: + generateFoldToCB(3, player_ids, didStreet3CB, street3CBDone, foldToStreet3CBChance, foldToStreet3CBDone, actionTypeByNo) + + if len(didStreet4CB)>=1: + generateFoldToCB(4, player_ids, didStreet4CB, street4CBDone, foldToStreet4CBChance, foldToStreet4CBDone, actionTypeByNo) + result['foldToStreet1CBChance']=foldToStreet1CBChance result['foldToStreet1CBDone']=foldToStreet1CBDone result['foldToStreet2CBChance']=foldToStreet2CBChance @@ -1803,7 +1782,27 @@ def generateHudData(player_ids, category, action_types, actionTypeByNo, winnings result['street4CheckCallRaiseChance']=street4CheckCallRaiseChance result['street4CheckCallRaiseDone']=street4CheckCallRaiseDone return result -#end def calculateHudImport +#end def generateHudCacheData + +def generateFoldToCB(street, playerIDs, didStreetCB, streetCBDone, foldToStreetCBChance, foldToStreetCBDone, actionTypeByNo): + """fills the passed foldToStreetCB* arrays appropriately depending on the given street""" + print "beginning of generateFoldToCB, street:", street, "len(actionTypeByNo):", len(actionTypeByNo) + print "len(actionTypeByNo[street]):",len(actionTypeByNo[street]) + firstCBReaction=0 + for action in range(len(actionTypeByNo[street])): + if actionTypeByNo[street][action][1]=="bet": + for player in didStreetCB: + if player==actionTypeByNo[street][action][0] and firstCBReaction==0: + firstCBReaction=action+1 + break + + for action in actionTypeByNo[street][firstCBReaction:]: + for player in range(len(playerIDs)): + if playerIDs[player]==action[0]: + foldToStreetCBChance[player]=True + if action[1]=="fold": + foldToStreetCBDone[player]=True +#end def generateFoldToCB def storeHudCache(cursor, category, gametypeId, playerIds, hudImportData): if (category=="holdem" or category=="omahahi" or category=="omahahilo"): From 6fc13a970b56c7a8d9b8e19bac54396fee3d25e0 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 1 Sep 2008 01:00:36 +0100 Subject: [PATCH 074/262] p73 - updated and much improved ebuild --- create-release.sh | 4 +- docs/known-bugs-and-planned-features.txt | 5 +- packaging/fpdb-1.0_alpha1_p27.ebuild | 37 --------------- packaging/fpdb-1.0_alpha2_p68.ebuild | 59 ++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 41 deletions(-) delete mode 100644 packaging/fpdb-1.0_alpha1_p27.ebuild create mode 100644 packaging/fpdb-1.0_alpha2_p68.ebuild diff --git a/create-release.sh b/create-release.sh index 1d1d0aaf..d4b7bfad 100755 --- a/create-release.sh +++ b/create-release.sh @@ -25,7 +25,7 @@ cp -R pyfpdb fpdb-$1/ cp -R regression-test fpdb-$1/ cp -R utils fpdb-$1/ cd fpdb-$1 -zip -r ../fpdb-$1.zip * -tar -cf - * | bzip2 > ../fpdb-$1.tar.bz2 +zip -r ../fpdb-1.0_$1.zip * +tar -cf - * | bzip2 > ../fpdb-1.0_$1.tar.bz2 cd .. rm -r fpdb-$1 diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index fad9b4ae..ac6e15c0 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -6,16 +6,15 @@ alpha3 (release 1Sep?) ====== (fixed by ray) auto import only runs on one file per start find correct sf logo link -hud displayed 0/3 but a 2 digit percentage for W$SD? show database version error in GUI and use fpdb_db class const for it, add it to title +update install-in-gentoo.txt and install-in-ubuntu.txt anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no specify NOT NULL on almost all table columns store raw hand in db and write reimport function using the raw hand field make windows use correct language version of Appdata, e.g. Anwendungdaten. http://mail.python.org/pipermail/python-list/2005-September/341702.html -ebuild: symlink doesnt work, USE gtk, more automation, update install-in-gentoo.txt, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, print notice about install-in-gentoo.txt and mysql --config ftp: read maxSeats make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt @@ -28,6 +27,8 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring before beta =========== +ebuild: USE gtk, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, git-ebuild, get it into sunrise +make hud display W$SD etc as fraction. add dedicated update page update status or make a support matrix table for website separate db table design version and last bugfix in importer diff --git a/packaging/fpdb-1.0_alpha1_p27.ebuild b/packaging/fpdb-1.0_alpha1_p27.ebuild deleted file mode 100644 index 773e8522..00000000 --- a/packaging/fpdb-1.0_alpha1_p27.ebuild +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 1999-2008 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha1_p27.ebuild,v 1.0 2008/08/13 05:45:00 jer Exp $ - -NEED_PYTHON=2.3 - -#inherit distutils - -MY_P="fpdb-${PV}" -DESCRIPTION="A database program to track your online poker games" -HOMEPAGE="https://sourceforge.net/projects/fpdb/" -SRC_URI="mirror://sourceforge/fpdb/fpdb-alpha1-git27.tar.bz2" - -LICENSE="AGPL-3" -SLOT="0" -KEYWORDS="~amd64 ~x86" -#note: this should work on other architectures too, please send me your experiences -IUSE="" - -RDEPEND="virtual/mysql - dev-python/mysql-python - >=x11-libs/gtk+-2.10 - dev-python/pygtk" -DEPEND="${RDEPEND}" - -src_install() { - DIRINST="${D}usr/share/games/fpdb/" - mkdir -p "${DIRINST}" - cp -R * "${DIRINST}" || die - - DIRBIN="${D}usr/bin/games/" - mkdir -p "${DIRBIN}" - #echo "dirs" - #echo "${DIRINST}pyfpdb/fpdb.py" - #echo - ln -s "${DIRINST}pyfpdb/fpdb.py" "${DIRBIN}/fpdb.py" || die -} diff --git a/packaging/fpdb-1.0_alpha2_p68.ebuild b/packaging/fpdb-1.0_alpha2_p68.ebuild new file mode 100644 index 00000000..30304eeb --- /dev/null +++ b/packaging/fpdb-1.0_alpha2_p68.ebuild @@ -0,0 +1,59 @@ +# Copyright 1999-2008 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha2_p68.ebuild,v 1.0 2008/08/31 23:00:00 steffen@sycamoretest.info Exp $ + +NEED_PYTHON=2.3 + +#inherit distutils + +MY_P="fpdb-${PV}" +DESCRIPTION="A database program to track your online poker games" +HOMEPAGE="https://sourceforge.net/projects/fpdb/" +#SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2" +SRC_URI="mirror://sourceforge/fpdb/fpdb-alpha2-p68.tar.bz2" + +LICENSE="AGPL-3" +SLOT="0" +KEYWORDS="~amd64 ~x86" +#note: this should work on other architectures too, please send me your experiences +IUSE="" + +RDEPEND="virtual/mysql + dev-python/mysql-python + >=x11-libs/gtk+-2.10 + dev-python/pygtk" +DEPEND="${RDEPEND}" + +src_install() { + DIRINST="${D}usr/share/games/fpdb/" + mkdir -p "${DIRINST}" + cp -R * "${DIRINST}" || die + + DIRBIN="${D}usr/games/bin/" + mkdir -p "${DIRBIN}" + echo "pathes" + echo "${DIRINST}pyfpdb/fpdb.py" + echo "${DIRBIN}fpdb.py" + echo + echo "cd /usr/share/games/fpdb/pyfpdb/ && python fpdb.py" > "${DIRBIN}fpdb" || die + chmod 755 "${DIRBIN}fpdb" || die +} + +#src_test() { +#} + +pkg_postinst() { + elog "Fpdb has been installed and can be called by executing /usr/games/bin/fpdb.py" + elog "You need to perform a couple more steps manually." + elog "Please also make sure you followed instructions from previous emerges, in particular make sure you configured mysql and set a root pw for it" + elog "Now run this command to connect to MySQL: mysql --user=root --password=yourPassword" + elog "In the mysql command line interface you need to type these two lines (make sure you get the ; at the end)" + elog "In the second line replace \"newPassword\" with a password of your choice" + elog "CREATE DATABASE fpdb;" + elog "GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;" + elog "Finally copy the default config file from ${DIRINST}docs/default.conf to ~/.fpdb/ for every user that is to use fpdb." + elog "You will need to edit the default.conf, in particular you need to replace the password with what you entered in the \"GRANT ALL...\"" + elog "Finally run the GUI and click the menu database -> recreate tables" + elog "That's it! See our webpage at http://fpdb.sourceforge.net for more documentation" + elog " " +} From 817c227dcb5360f2b98bada84dbf4e328eef6e35 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 1 Sep 2008 05:41:58 +0100 Subject: [PATCH 075/262] p74 - mostly recovered razz/stud support removed obselete gentoo install instructions, added ubuntu instructions --- docs/install-in-gentoo.txt | 67 ------------------------ docs/install-in-ubuntu.txt | 46 ++++++++++++++++ docs/known-bugs-and-planned-features.txt | 31 ++++++----- pyfpdb/fpdb_import.py | 2 - pyfpdb/fpdb_parse_logic.py | 45 +++++++++------- pyfpdb/fpdb_save_to_db.py | 24 ++++----- pyfpdb/fpdb_simple.py | 39 ++++++-------- 7 files changed, 117 insertions(+), 137 deletions(-) delete mode 100644 docs/install-in-gentoo.txt create mode 100644 docs/install-in-ubuntu.txt diff --git a/docs/install-in-gentoo.txt b/docs/install-in-gentoo.txt deleted file mode 100644 index 12e20067..00000000 --- a/docs/install-in-gentoo.txt +++ /dev/null @@ -1,67 +0,0 @@ -Last checked: 3 Aug 2008, git99 - -These instructions are for Gentoo GNU/Linux, but if you adapt the steps -installing and starting stuff it should work on any other OS as well. - -1. Install everything. Check if anything is already installed and if it is remove it from the command. - -For mysql: -emerge mysql mysql-python pygtk -av -/etc/init.d/mysql start -rc-update add mysql default - -For postgresql: -emerge postgresql pygresql pygtk -/etc/init.d/postgresql start -rc-update add postgresql default - - -2. Manual configuration steps - -emerge --config mysql -The --config step will ask you for the mysql root user - set this securely, we will create a seperate account for fpdb - -3. Create a mysql user and a database -Now open a shell (aka command prompt aka DOS window): -Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open. - -Type (replacing yourPassword with the root password for MySQL you specified during installation): -mysql --user=root --password=yourPassword - -It should say something like this: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 4 -Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1 - -Type 'help;' or '\h' for help. Type '\c' to clear the buffer. - -mysql> - - -Now create the actual database. The default name is fpdb, I recommend you keep it. Type this: -CREATE DATABASE fpdb; - -Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password): -GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION; - -Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edit it according to what you configured just now, in particular you will definitely have to put in the password you configured. I know this is insecure, will fix it before stable release. - - -4. Guided installation steps -Run the GUI as described in readme-user and click the menu database -> recreate tables - -That's it! Now see readme-user.txt for usage instructions. - - -License -======= -Trademarks of third parties have been used under Fair Use or similar laws. - -Copyright 2008 Steffen Jobbagy-Felso -Permission is granted to copy, distribute and/or modify this -document under the terms of the GNU Free Documentation License, -Version 1.2 as published by the Free Software Foundation; with -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover -Texts. A copy of the license can be found in fdl-1.2.txt - -The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/install-in-ubuntu.txt b/docs/install-in-ubuntu.txt new file mode 100644 index 00000000..b3843eca --- /dev/null +++ b/docs/install-in-ubuntu.txt @@ -0,0 +1,46 @@ +Last checked: 1 Sep, p74 + +These instructions are for any Ubuntu Linux, but except for the name of the package management software it should be identical on other Linux distributions. + +Using the package manager (I think it's called synaptics) install the dependencies: mysql, python, mysql-python, pygtk +I don't use ubuntu but I'd imagine it will at some point prompt you for a MySQL root password enter one and when it's done installing everything open a shell/terminal and type this: +mysql --user=root --password=YourMysqlRootPassword +It should say something like this: + +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 4 +Server version: 5.0.60-log Ubuntu Linux mysql-5.0.60-r1 + +Type 'help;' or '\h' for help. Type '\c' to clear the buffer. + +mysql> + +Now type this: +CREATE DATABASE fpdb; + +Next you need to create a user. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password): +GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION; + +Then download one of the alpha2 archives from the sourceforge page at https://sourceforge.net/projects/fpdb/ and unpack it into a folder of your choice. + +Next create a directory called .fpdb (the . at the beginning is important) in your home directory, e.g. /home/steffen/.fpdb +Then copy the default.conf file from the docs directory of the archive into this new folder. +Finally edit default.conf with any texteditor and enter the fpdb password you entered above (in the GRANT ALL... command). + +This should be it, try double clicking pyfpdb/fpdb.py or open it from a shell with python /path/to/fpdb/pyfpdb/fpdb.py + +Now see readme-user.txt for usage instructions. + + +License +======= +Trademarks of third parties have been used under Fair Use or similar laws. + +Copyright 2008 Steffen Jobbagy-Felso +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.2 as published by the Free Software Foundation; with +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license can be found in fdl-1.2.txt + +The program itself is licensed under AGPLv3, see agpl-3.0.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index ac6e15c0..3e7ad0cc 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,13 +2,16 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha3 (release 1Sep?) +alpha3 (release 1-2Sep?) ====== (fixed by ray) auto import only runs on one file per start find correct sf logo link show database version error in GUI and use fpdb_db class const for it, add it to title -update install-in-gentoo.txt and install-in-ubuntu.txt +update install-in-gentoo on website +update ebuild and ubuntu guide for HUD_config.xml +implement stud HudCache +implement storeHudCache for stud base anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no specify NOT NULL on almost all table columns @@ -18,23 +21,29 @@ make windows use correct language version of Appdata, e.g. Anwendungdaten. http: ftp: read maxSeats make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt -(steffen) finish bringing back tourney export settings[hud-defaultInterval] to conf fill check-/call-raise cache fields -table with data for graphs for SD/F, W$wSF, W$@SD printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt -before beta -=========== -ebuild: USE gtk, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, git-ebuild, get it into sunrise -make hud display W$SD etc as fraction. -add dedicated update page -update status or make a support matrix table for website +alpha4 (release 8Sep?) +====== +Everything that didn't make it into alpha3 +Import draw (maybe without HudCache for a start) +table with data for graphs for SD/F, W$wSF, W$@SD separate db table design version and last bugfix in importer change tabledesign VALIGN finish updating filelist finish todos in git instructions +debian/ubuntu package + +before beta +=========== +finish bringing back tourney +ebuild: USE gtk, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, git-ebuild, get it into sunrise +make hud display W$SD etc as fraction. +add dedicated update page +update status or make a support matrix table for website fix up bg colours in tv move version into seperate file for fpdb gui and db SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug @@ -58,7 +67,6 @@ implement error file in importer catch index error, type error, file not found error use different colours according to classification. -add stud, razz back to imp/tv but with less seperate codepathes move prepare-git.sh and create-release.sh to utils offer not storing db password change definition of bet to exclude bring in @@ -68,7 +76,6 @@ fix GUI's load profile HUD config wizard file permission script, use games group -change stud street storage from 3-7 to 0-4 throughout (possibly best way is to just shrink the holding array in fpdb_simple.createArrays make bulk importer display a grand total in the GUI change save_to_db into one method and probably move into parse_logic Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 9522abc6..f53f6ed1 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -100,8 +100,6 @@ def import_file_dict(options, settings, callHud=True): hand=fpdb_simple.filterCrap(site, hand, isTourney) try: - if (category=="razz" or category=="studhi" or category=="studhilo"): - raise fpdb_simple.FpdbError ("stud/razz currently out of order") handsId=fpdb_parse_logic.mainParser(db, cursor, site, category, hand) db.commit() diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index f439d1c5..a33f040a 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -22,7 +22,10 @@ import fpdb_save_to_db #parses a holdem hand def mainParser(db, cursor, site, category, hand): - #print "hand:",hand + if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + base="hold" + else: + base="stud" #part 0: create the empty arrays lineTypes=[] #char, valid values: header, name, cards, action, win, rake, ignore lineStreets=[] #char, valid values: (predeal, preflop, flop, turn, river) @@ -53,7 +56,7 @@ def mainParser(db, cursor, site, category, hand): #part 2: classify lines by type (e.g. cards, action, win, sectionchange) and street fpdb_simple.classifyLines(hand, category, lineTypes, lineStreets) - + #part 3: read basic player info #3a read player names, startcashes for i in range (len(hand)): #todo: use maxseats+1 here. @@ -68,11 +71,8 @@ def mainParser(db, cursor, site, category, hand): fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts, actionNos, actionTypeByNo) #3b read positions - if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + if base=="hold": positions = fpdb_simple.parsePositions (hand, names) - base="hold" - else: - base="stud" #part 4: take appropriate action for each line based on linetype for i in range(len(hand)): @@ -104,7 +104,7 @@ def mainParser(db, cursor, site, category, hand): #part 5: final preparations, then call fpdb_save_to_db.saveHoldem with # the arrays as they are - that file will fill them. fpdb_simple.convertCardValues(cardValues) - if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + if base=="hold": fpdb_simple.convertCardValuesBoard(boardValues) fpdb_simple.convertBlindBet(actionTypes, actionAmounts) fpdb_simple.checkPositions(positions) @@ -116,7 +116,12 @@ def mainParser(db, cursor, site, category, hand): totalWinnings=0 for i in range(len(winnings)): totalWinnings+=winnings[i] - hudImportData=fpdb_simple.generateHudCacheData(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) + + if base=="hold": + hudImportData=fpdb_simple.generateHudCacheData(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) + else: + print "todo: stud HudCache" + hudImportData=None if isTourney: ranks=[] @@ -124,24 +129,24 @@ def mainParser(db, cursor, site, category, hand): ranks.append(0) payin_amounts=fpdb_simple.calcPayin(len(names), buyin, fee) - if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - result = fpdb_save_to_db.tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteID, + if base=="hold": + result = fpdb_save_to_db.tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) - elif (category=="razz" or category=="studhi" or category=="studhilo"): - raise fpdb_simple.FpdbError ("stud/razz are currently broken") - result = fpdb_save_to_db.tourney_stud(cursor, category, siteTourneyNo, buyin, fee, + elif base=="stud": + result = fpdb_save_to_db.tourney_stud(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, cardSuits, winnings, rakes, - actionTypes, actionAmounts, hudImportData) + actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) + else: + raise fpdb_simple.FpdbError ("unrecognised category") else: - if (category=="holdem" or category=="omahahi" or category=="omahahilo"): - result = fpdb_save_to_db.ring_holdem_omaha(cursor, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) - elif (category=="razz" or category=="studhi" or category=="studhilo"): - raise fpdb_simple.FpdbError ("stud/razz are currently broken") - result = fpdb_save_to_db.ring_stud(cursor, category, siteHandNo, gametypeID, + if base=="hold": + result = fpdb_save_to_db.ring_holdem_omaha(cursor, base, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) + elif base=="stud": + result = fpdb_save_to_db.ring_stud(cursor, base, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, - cardSuits, winnings, rakes, actionTypes, actionAmounts, hudImportData) + cardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) else: raise fpdb_simple.FpdbError ("unrecognised category") db.commit() diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index a3ec95a5..dccbcba7 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -21,25 +21,23 @@ import fpdb_simple #stores a stud/razz hand into the database -def ring_stud(cursor, category, site_hand_no, gametype_id, hand_start_time, - names, player_ids, start_cashes, antes, card_values, card_suits, - winnings, rakes, action_types, action_amounts, hudImportData): - fpdb_simple.fillCardArrays(len(names), 7, card_values, card_suits) +def ring_stud(cursor, base, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): + fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) - hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) hands_players_ids=fpdb_simple.store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes) - fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) + fpdb_simple.storeHudCache(cursor, category, gametype_id, player_ids, hudImportData) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) return hands_id #end def ring_stud -def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): +def ring_holdem_omaha(cursor, base, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a holdem/omaha hand into the database""" - fpdb_simple.fillCardArrays(len(names), category, card_values, card_suits) + fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) fpdb_simple.fill_board_cards(board_values, board_suits) hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) @@ -54,10 +52,10 @@ def ring_holdem_omaha(cursor, category, site_hand_no, gametype_id, hand_start_ti return hands_id #end def ring_holdem_omaha -def tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId, siteId, #end of tourney specific params +def tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId, siteId, #end of tourney specific params site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a tourney holdem/omaha hand into the database""" - fpdb_simple.fillCardArrays(len(names), category, card_values, card_suits) + fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) fpdb_simple.fill_board_cards(board_values, board_suits) tourney_id=fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start) @@ -75,13 +73,13 @@ def tourney_holdem_omaha(cursor, category, siteTourneyNo, buyin, fee, knockout, return hands_id #end def tourney_holdem_omaha -def tourney_stud(cursor, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, +def tourney_stud(cursor, base, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, #end of tourney specific params site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, action_types, action_amounts, hudImportData): #stores a tourney stud/razz hand into the database - fpdb_simple.fillCardArrays(len(names), 7, card_values, card_suits) + fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) tourney_id=fpdb_simple.store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index b420bd26..55210b82 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -97,19 +97,19 @@ def classifyLines(hand, category, lineTypes, lineStreets): currentStreet="river" elif (hand[i].startswith("*** 3")): lineTypes.append("ignore") - currentStreet=3 + currentStreet=0 elif (hand[i].startswith("*** 4")): lineTypes.append("ignore") - currentStreet=4 + currentStreet=1 elif (hand[i].startswith("*** 5")): lineTypes.append("ignore") - currentStreet=5 + currentStreet=2 elif (hand[i].startswith("*** 6")): lineTypes.append("ignore") - currentStreet=6 + currentStreet=3 elif (hand[i].startswith("*** 7") or hand[i]=="*** RIVER ***"): lineTypes.append("ignore") - currentStreet=7 + currentStreet=4 elif (hand[i].find(" shows [")!=-1): lineTypes.append("cards") elif (hand[i].startswith("Table '")): @@ -119,10 +119,8 @@ def classifyLines(hand, category, lineTypes, lineStreets): lineStreets.append(currentStreet) #end def classifyLines -#calculates the actual bet amounts in the given amount array and changes it accordingly. def convert3B4B(site, category, limit_type, actionTypes, actionAmounts): - #print "convert3B4B: actionTypes:", actionTypes - #print "convert3B4B: actionAmounts pre_Convert",actionAmounts + """calculates the actual bet amounts in the given amount array and changes it accordingly.""" for i in range (len(actionTypes)): for j in range (len(actionTypes[i])): bets=[] @@ -253,14 +251,16 @@ def fill_board_cards(board_values, board_suits): board_suits.append("x") #end def fill_board_cards -def fillCardArrays(player_count, category, card_values, card_suits): +def fillCardArrays(player_count, base, category, card_values, card_suits): """fills up the two card arrays""" if (category=="holdem"): cardCount=2 elif (category=="omahahi" or category=="omahahilo"): cardCount=4 + elif base=="stud": + cardCount=7 else: - raise fpdb_simple.FpdbError ("invalid category: category") + raise fpdb_simple.FpdbError ("invalid category:", category) for i in range (player_count): while (len(card_values[i])=1: generateFoldToCB(1, player_ids, didStreet1CB, street1CBDone, foldToStreet1CBChance, foldToStreet1CBDone, actionTypeByNo) @@ -1786,8 +1779,8 @@ def generateHudCacheData(player_ids, category, action_types, actionTypeByNo, win def generateFoldToCB(street, playerIDs, didStreetCB, streetCBDone, foldToStreetCBChance, foldToStreetCBDone, actionTypeByNo): """fills the passed foldToStreetCB* arrays appropriately depending on the given street""" - print "beginning of generateFoldToCB, street:", street, "len(actionTypeByNo):", len(actionTypeByNo) - print "len(actionTypeByNo[street]):",len(actionTypeByNo[street]) + #print "beginning of generateFoldToCB, street:", street, "len(actionTypeByNo):", len(actionTypeByNo) + #print "len(actionTypeByNo[street]):",len(actionTypeByNo[street]) firstCBReaction=0 for action in range(len(actionTypeByNo[street])): if actionTypeByNo[street][action][1]=="bet": @@ -1946,7 +1939,7 @@ def storeHudCache(cursor, category, gametypeId, playerIds, hudImportData): row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60], row[1], row[2], row[3], row[4], row[5])) else: - raise FpdbError("todo") + print "todo: implement storeHudCache for stud base" #end def storeHudCache def store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime): From c963599b762e2ddf7c7bc7c925d11a381ee12e30 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 1 Sep 2008 16:18:01 +0100 Subject: [PATCH 076/262] p75 - show database version error in GUI --- docs/known-bugs-and-planned-features.txt | 36 +++++++----------------- pyfpdb/fpdb.py | 19 ++++++++++++- pyfpdb/fpdb_db.py | 7 +++-- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 3e7ad0cc..52e42302 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -7,13 +7,12 @@ alpha3 (release 1-2Sep?) (fixed by ray) auto import only runs on one file per start find correct sf logo link -show database version error in GUI and use fpdb_db class const for it, add it to title +windows integrated installer update install-in-gentoo on website update ebuild and ubuntu guide for HUD_config.xml implement stud HudCache implement storeHudCache for stud base -anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no specify NOT NULL on almost all table columns store raw hand in db and write reimport function using the raw hand field make windows use correct language version of Appdata, e.g. Anwendungdaten. http://mail.python.org/pipermail/python-list/2005-September/341702.html @@ -28,6 +27,8 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring alpha4 (release 8Sep?) ====== +change to savannah? +anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no Everything that didn't make it into alpha3 Import draw (maybe without HudCache for a start) table with data for graphs for SD/F, W$wSF, W$@SD @@ -49,31 +50,25 @@ move version into seperate file for fpdb gui and db SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug create little test script for people to run to verify successful installation of pydeps split hud data generation into separate for loops and make it more efficient -fix bug that sawFlop/Turn/River gets miscalculated if someone is allin - might as well add all-in recognition for this +fix bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin - might as well add all-in recognition for this make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) make it work with postgres expand instructions for profile file maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? rakeback/frequent player points -gentoo ebuild: USE postgresql skins -optionally combine FB/FS and CB/2B/3B separate all gui and all processing into files that are named accordingly ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. -figure out what slowed it down so much between git19 and git21 (8/9aug) why do we have to reconnect in tv.read_names_clicked? implement error file in importer catch index error, type error, file not found error -use different colours according to classification. +HUD: use different colours according to classification. move prepare-git.sh and create-release.sh to utils offer not storing db password change definition of bet to exclude bring in -in tv, select from hud table using named fields rather than the current * -remove remains of mysql/myisam support. fix GUI's load profile -HUD config wizard file permission script, use games group make bulk importer display a grand total in the GUI @@ -90,42 +85,31 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== -recognise&handle all-in for CB etc. (mostly useful for NL/PL, in limit I would argue missing a chance due to lack of money is the same as missing it due to sneezing and clicking the wrong button) +make DB version error offer reimport, recreation and continue. In many places there are unnecessary database accesses or it regenerates information it already had before or just generally does things in obscenely inefficient ways. Optimise this multi-select in bulk importer -make option to use "traditional" labels, e.g. WtSD instead of SD/F -HTMLify docs and validate them cut down action_types array size to appropriate length make the gui display errors log file move directory import code from gui to backend convert fpdb_import to not require passing "self", generally clean the parameter passing -(tedious general stability improvement for unusual playernames): change all the str.find so they dont accidentially count player names containing the searched phrase. e.g. with rfind. Doesn't handle Daylight Saving Time (I don't think at least) -Need to store if someone goes all-in, particularly for better NL/PL support. -verify at least 3 hands per category per site per limit_type (when cap then do 2 normal and one 1 capped) incl tv display -put lines in tv to make it easier to read ensure that refresh still takes no more than 10 seks on my P3M-800 (a quick run on git15 indicates this is ok now), or 5 with remote DB -select range of stakes and sng/mtt values and types for tv -change "for i" to more sensible var name instead of i +select range of stakes and sng/mtt values and types for hud recognise somewhere if a file is still active and if so keep it open and only read new hands rather than detecting dupes +return full ftp functionality can wait till 1.x ================= -return full ftp functionality in all importer: stop doing if site=="ftp", make class constants for site_id instead -finish cleaning tabledesign html code -rearrange huddata fields It treats fold due to disconnect as voluntary fold which is not ideal check for unnecessary db.commit() aliases -Probably PartyPoker for all or most supported games repair hands where the seat lines are missing, happens when observing at FTP flags for storing the reason for winning (best hi, tie for best low, etc.) to DB. not sure actually if this is such a good idea remember that there can be multiple reasons for the same player in the same hand -windows integrated installer benchmark properly on mysql innodb, postgresql, more? -rename things like this: ClassName.methodName and variableName. do this on tables too. update codingstyle -CLI (not ncurses, proper CLI) equivalent for fpdb.py +rename things like this: ClassName.methodName and variableName. update codingstyle +CLI (not ncurses, normal CLI) equivalent for fpdb.py optimise/simplify storing by creating the SQL statements depending on hand rather than calling different methods make range of activeSeats configurable for tv/hud diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 327cf2e2..2b203057 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -322,6 +322,23 @@ class fpdb: self.db = fpdb_db.fpdb_db() #print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName'] self.db.connect(self.settings['db-backend'], self.settings['db-host'], self.settings['db-databaseName'], self.settings['db-user'], self.settings['db-password']) + if self.db.wrongDbVersion: + diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) + + label = gtk.Label("An invalid DB version or missing tables have been detected.") + diaDbVersionWarning.vbox.add(label) + label.show() + + label = gtk.Label("This error is not necessarily fatal but it is strongly recommended that you recreate the tables by using the Database menu.") + diaDbVersionWarning.vbox.add(label) + label.show() + + label = gtk.Label("Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc.") + diaDbVersionWarning.vbox.add(label) + label.show() + + response = diaDbVersionWarning.run() + diaDbVersionWarning.destroy() #end def load_profile def not_implemented(self): @@ -391,7 +408,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha2+, p72") + self.window.set_title("Free Poker DB - version: alpha2+, p75") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index f10b52a8..5017d13b 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -44,13 +44,16 @@ class fpdb_db: else: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) self.cursor=self.db.cursor() + self.wrongDbVersion=False try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=53: + if settings[0]!=75: print "outdated or too new database version - please recreate tables" + self.wrongDbVersion=True except:# _mysql_exceptions.ProgrammingError: print "failed to read settings table - please recreate tables" + self.wrongDbVersion=True #end def connect def create_table(self, string): @@ -356,7 +359,7 @@ class fpdb_db: street4CheckCallRaiseChance INT, street4CheckCallRaiseDone INT)""") - self.cursor.execute("INSERT INTO Settings VALUES (53);") + self.cursor.execute("INSERT INTO Settings VALUES (75);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneyTypes (id) VALUES (DEFAULT);") From 54ff7b71f1d059a3d97d5d44c671a766c36234ae Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 1 Sep 2008 17:32:18 +0100 Subject: [PATCH 077/262] p76 - specify NOT NULL on almost all table columns to increase DB resilience against importer errors little bugfix to make omaha work again --- docs/known-bugs-and-planned-features.txt | 1 - pyfpdb/fpdb.py | 7 +- pyfpdb/fpdb_db.py | 284 ++++++++++++----------- pyfpdb/fpdb_simple.py | 4 +- 4 files changed, 148 insertions(+), 148 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 52e42302..c6ef129a 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -13,7 +13,6 @@ update ebuild and ubuntu guide for HUD_config.xml implement stud HudCache implement storeHudCache for stud base -specify NOT NULL on almost all table columns store raw hand in db and write reimport function using the raw hand field make windows use correct language version of Appdata, e.g. Anwendungdaten. http://mail.python.org/pipermail/python-list/2005-September/341702.html diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 2b203057..730fc929 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -385,8 +385,7 @@ class fpdb: """Displays a tab with the main fpdb help screen""" #print "start of tab_main_help" mh_tab=gtk.Label("""Welcome to Fpdb! -For howto information please see docs"""+os.sep+"""readme-user.txt -The abbrevations in the table viewer are explained in docs"""+os.sep+"""abbrevations.txt +For documentation please visit our website at http://fpdb.sourceforge.net/ or check the docs directory in the fpdb folder. This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.add_and_display_tab(mh_tab, "Help") #end def tab_main_help @@ -408,14 +407,14 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha2+, p75") + self.window.set_title("Free Poker DB - version: alpha2+, p76") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) self.menu_items = ( ( "/_Main", None, None, 0, "" ), - ( "/Main/_Load Profile", "L", self.dia_load_profile, 0, None ), + ( "/Main/_Load Profile (broken)", "L", self.dia_load_profile, 0, None ), ( "/Main/_Edit Profile (todo)", "E", self.dia_edit_profile, 0, None ), ( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ), ( "/Main/sep1", None, None, 0, "" ), diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 5017d13b..1b9f8778 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -48,7 +48,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=75: + if settings[0]!=76: print "outdated or too new database version - please recreate tables" self.wrongDbVersion=True except:# _mysql_exceptions.ProgrammingError: @@ -156,109 +156,110 @@ class fpdb_db: self.drop_tables() self.create_table("""Settings ( - version SMALLINT)""") + version SMALLINT NOT NULL)""") self.create_table("""Sites ( - id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - name varchar(32), - currency char(3))""") + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + name varchar(32) NOT NULL, + currency char(3) NOT NULL)""") self.create_table("""Gametypes ( - id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), - type char(4), - base char(4), - category varchar(9), - limitType char(2), - hiLo char(1), + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), + type char(4) NOT NULL, + base char(4) NOT NULL, + category varchar(9) NOT NULL, + limitType char(2) NOT NULL, + hiLo char(1) NOT NULL, smallBlind int, bigBlind int, - smallBet int, - bigBet int)""") + smallBet int NOT NULL, + bigBet int NOT NULL)""") + #NOT NULL not set for small/bigBlind as they are not existent in all games self.create_table("""Players ( - id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - name VARCHAR(32) CHARACTER SET utf8, - siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), + id INT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + name VARCHAR(32) CHARACTER SET utf8 NOT NULL, + siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), comment text, commentTs DATETIME)""") self.create_table("""Autorates ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), - gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - description varchar(50), - shortDesc char(8), - ratingTime DATETIME, - handCount int)""") + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + description varchar(50) NOT NULL, + shortDesc char(8) NOT NULL, + ratingTime DATETIME NOT NULL, + handCount int NOT NULL)""") self.create_table("""Hands ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - tableName VARCHAR(20), - siteHandNo BIGINT, - gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - handStart DATETIME, - importTime DATETIME, - seats SMALLINT, - maxSeats SMALLINT, + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + tableName VARCHAR(20) NOT NULL, + siteHandNo BIGINT NOT NULL, + gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + handStart DATETIME NOT NULL, + importTime DATETIME NOT NULL, + seats SMALLINT NOT NULL, + maxSeats SMALLINT NOT NULL, comment TEXT, commentTs DATETIME)""") self.create_table("""BoardCards ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - handId BIGINT UNSIGNED, FOREIGN KEY (handId) REFERENCES Hands(id), - card1Value smallint, - card1Suit char(1), - card2Value smallint, - card2Suit char(1), - card3Value smallint, - card3Suit char(1), - card4Value smallint, - card4Suit char(1), - card5Value smallint, - card5Suit char(1))""") + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + handId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handId) REFERENCES Hands(id), + card1Value smallint NOT NULL, + card1Suit char(1) NOT NULL, + card2Value smallint NOT NULL, + card2Suit char(1) NOT NULL, + card3Value smallint NOT NULL, + card3Suit char(1) NOT NULL, + card4Value smallint NOT NULL, + card4Suit char(1) NOT NULL, + card5Value smallint NOT NULL, + card5Suit char(1) NOT NULL)""") self.create_table("""TourneyTypes ( - id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - siteId SMALLINT UNSIGNED, FOREIGN KEY (siteId) REFERENCES Sites(id), - buyin INT, - fee INT, - knockout INT, - rebuyOrAddon BOOLEAN)""") + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), + buyin INT NOT NULL, + fee INT NOT NULL, + knockout INT NOT NULL, + rebuyOrAddon BOOLEAN NOT NULL)""") self.create_table("""Tourneys ( - id INT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - tourneyTypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), - siteTourneyNo BIGINT, - entries INT, - prizepool INT, - startTime DATETIME, + id INT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + siteTourneyNo BIGINT NOT NULL, + entries INT NOT NULL, + prizepool INT NOT NULL, + startTime DATETIME NOT NULL, comment TEXT, commentTs DATETIME)""") self.create_table("""TourneysPlayers ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - tourneyId INT UNSIGNED, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), - playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), - payinAmount INT, - rank INT, - winnings INT, + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + tourneyId INT UNSIGNED NOT NULL, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + payinAmount INT NOT NULL, + rank INT NOT NULL, + winnings INT NOT NULL, comment TEXT, commentTs DATETIME)""") self.create_table("""HandsPlayers ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - handId BIGINT UNSIGNED, FOREIGN KEY (handId) REFERENCES Hands(id), - playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), - startCash INT, + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + handId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handId) REFERENCES Hands(id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + startCash INT NOT NULL, position CHAR(1), - seatNo SMALLINT, + seatNo SMALLINT NOT NULL, ante INT, - card1Value smallint, - card1Suit char(1), - card2Value smallint, - card2Suit char(1), + card1Value smallint NOT NULL, + card1Suit char(1) NOT NULL, + card2Value smallint NOT NULL, + card2Suit char(1) NOT NULL, card3Value smallint, card3Suit char(1), card4Value smallint, @@ -270,99 +271,100 @@ class fpdb_db: card7Value smallint, card7Suit char(1), - winnings int, - rake int, + winnings int NOT NULL, + rake int NOT NULL, comment text, commentTs DATETIME, tourneysPlayersId BIGINT UNSIGNED, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id))""") + #NOT NULL not set on cards 3-7 as they dont exist in all games self.create_table("""HandsActions ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - handPlayerId BIGINT UNSIGNED, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), - street SMALLINT, - actionNo SMALLINT, - action CHAR(5), - amount INT, + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + handPlayerId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), + street SMALLINT NOT NULL, + actionNo SMALLINT NOT NULL, + action CHAR(5) NOT NULL, + amount INT NOT NULL, comment TEXT, commentTs DATETIME)""") self.create_table("""HudCache ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT, PRIMARY KEY (id), - gametypeId SMALLINT UNSIGNED, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - playerId INT UNSIGNED, FOREIGN KEY (playerId) REFERENCES Players(id), - activeSeats SMALLINT, + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + activeSeats SMALLINT NOT NULL, position CHAR(1), - tourneyTypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), - HDs INT, - street0VPI INT, - street0Aggr INT, - street0_3B4BChance INT, - street0_3B4BDone INT, + HDs INT NOT NULL, + street0VPI INT NOT NULL, + street0Aggr INT NOT NULL, + street0_3B4BChance INT NOT NULL, + street0_3B4BDone INT NOT NULL, - street1Seen INT, - street2Seen INT, - street3Seen INT, - street4Seen INT, - sawShowdown INT, + street1Seen INT NOT NULL, + street2Seen INT NOT NULL, + street3Seen INT NOT NULL, + street4Seen INT NOT NULL, + sawShowdown INT NOT NULL, - street1Aggr INT, - street2Aggr INT, - street3Aggr INT, - street4Aggr INT, + street1Aggr INT NOT NULL, + street2Aggr INT NOT NULL, + street3Aggr INT NOT NULL, + street4Aggr INT NOT NULL, - otherRaisedStreet1 INT, - otherRaisedStreet2 INT, - otherRaisedStreet3 INT, - otherRaisedStreet4 INT, - foldToOtherRaisedStreet1 INT, - foldToOtherRaisedStreet2 INT, - foldToOtherRaisedStreet3 INT, - foldToOtherRaisedStreet4 INT, - wonWhenSeenStreet1 FLOAT, - wonAtSD FLOAT, + otherRaisedStreet1 INT NOT NULL, + otherRaisedStreet2 INT NOT NULL, + otherRaisedStreet3 INT NOT NULL, + otherRaisedStreet4 INT NOT NULL, + foldToOtherRaisedStreet1 INT NOT NULL, + foldToOtherRaisedStreet2 INT NOT NULL, + foldToOtherRaisedStreet3 INT NOT NULL, + foldToOtherRaisedStreet4 INT NOT NULL, + wonWhenSeenStreet1 FLOAT NOT NULL, + wonAtSD FLOAT NOT NULL, - stealAttemptChance INT, - stealAttempted INT, - foldBbToStealChance INT, - foldedBbToSteal INT, - foldSbToStealChance INT, - foldedSbToSteal INT, + stealAttemptChance INT NOT NULL, + stealAttempted INT NOT NULL, + foldBbToStealChance INT NOT NULL, + foldedBbToSteal INT NOT NULL, + foldSbToStealChance INT NOT NULL, + foldedSbToSteal INT NOT NULL, - street1CBChance INT, - street1CBDone INT, - street2CBChance INT, - street2CBDone INT, - street3CBChance INT, - street3CBDone INT, - street4CBChance INT, - street4CBDone INT, + street1CBChance INT NOT NULL, + street1CBDone INT NOT NULL, + street2CBChance INT NOT NULL, + street2CBDone INT NOT NULL, + street3CBChance INT NOT NULL, + street3CBDone INT NOT NULL, + street4CBChance INT NOT NULL, + street4CBDone INT NOT NULL, - foldToStreet1CBChance INT, - foldToStreet1CBDone INT, - foldToStreet2CBChance INT, - foldToStreet2CBDone INT, - foldToStreet3CBChance INT, - foldToStreet3CBDone INT, - foldToStreet4CBChance INT, - foldToStreet4CBDone INT, + foldToStreet1CBChance INT NOT NULL, + foldToStreet1CBDone INT NOT NULL, + foldToStreet2CBChance INT NOT NULL, + foldToStreet2CBDone INT NOT NULL, + foldToStreet3CBChance INT NOT NULL, + foldToStreet3CBDone INT NOT NULL, + foldToStreet4CBChance INT NOT NULL, + foldToStreet4CBDone INT NOT NULL, - totalProfit INT, + totalProfit INT NOT NULL, - street1CheckCallRaiseChance INT, - street1CheckCallRaiseDone INT, - street2CheckCallRaiseChance INT, - street2CheckCallRaiseDone INT, - street3CheckCallRaiseChance INT, - street3CheckCallRaiseDone INT, - street4CheckCallRaiseChance INT, - street4CheckCallRaiseDone INT)""") + street1CheckCallRaiseChance INT NOT NULL, + street1CheckCallRaiseDone INT NOT NULL, + street2CheckCallRaiseChance INT NOT NULL, + street2CheckCallRaiseDone INT NOT NULL, + street3CheckCallRaiseChance INT NOT NULL, + street3CheckCallRaiseDone INT NOT NULL, + street4CheckCallRaiseChance INT NOT NULL, + street4CheckCallRaiseDone INT NOT NULL)""") - self.cursor.execute("INSERT INTO Settings VALUES (75);") + self.cursor.execute("INSERT INTO Settings VALUES (76);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") - self.cursor.execute("INSERT INTO TourneyTypes (id) VALUES (DEFAULT);") + self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") self.db.commit() print "finished recreating tables" #end def recreate_tables diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 55210b82..923c2053 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1192,8 +1192,8 @@ def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, sta (hands_id, player_ids[i], start_cashes[i], positions[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], - winnings[i], rakes[i], seatNo[i])) - cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + winnings[i], rakes[i], seatNos[i])) + cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId=%s", (hands_id, player_ids[i])) result.append(cursor.fetchall()[0][0]) else: raise FpdbError("invalid category") From 4976dc742cc42dab1c4ae68e26a212d2b6066133 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 1 Sep 2008 17:54:48 +0100 Subject: [PATCH 078/262] p77 - make windows use correct language version of Appdata folder, e.g. Anwendungdaten in german --- docs/known-bugs-and-planned-features.txt | 4 +--- pyfpdb/fpdb.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index c6ef129a..e4e43af7 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -14,8 +14,6 @@ implement stud HudCache implement storeHudCache for stud base store raw hand in db and write reimport function using the raw hand field -make windows use correct language version of Appdata, e.g. Anwendungdaten. http://mail.python.org/pipermail/python-list/2005-September/341702.html - ftp: read maxSeats make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt @@ -35,7 +33,7 @@ separate db table design version and last bugfix in importer change tabledesign VALIGN finish updating filelist finish todos in git instructions -debian/ubuntu package +debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html before beta =========== diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 730fc929..1a0ffaa6 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -237,8 +237,8 @@ class fpdb: defaultpath+=(os.sep) if (os.sep=="\\"):#ie. if Windows use application data folder - defaultpath+=("Application Data"+os.sep) - else:#ie. if real OS prefix fpdb with a . as it is convention + defaultpath=os.environ["APPDATA"]+os.sep + else:#ie. if POSIX OS prefix fpdb with a . defaultpath+="." defaultpath+=("fpdb"+os.sep+"default.conf") From 2c251d94f27302ebb1a0ef77f126cd7292bec0d2 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 2 Sep 2008 01:00:43 +0100 Subject: [PATCH 079/262] p78 - implemented stud HudCache generation and storing --- docs/known-bugs-and-planned-features.txt | 3 +- pyfpdb/fpdb.py | 32 ++--- pyfpdb/fpdb_parse_logic.py | 5 +- pyfpdb/fpdb_save_to_db.py | 10 +- pyfpdb/fpdb_simple.py | 152 +++++++++++++---------- 5 files changed, 107 insertions(+), 95 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index e4e43af7..7ff766cb 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -10,8 +10,6 @@ find correct sf logo link windows integrated installer update install-in-gentoo on website update ebuild and ubuntu guide for HUD_config.xml -implement stud HudCache -implement storeHudCache for stud base store raw hand in db and write reimport function using the raw hand field ftp: read maxSeats @@ -25,6 +23,7 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring alpha4 (release 8Sep?) ====== change to savannah? +implement steal and positions in stud anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no Everything that didn't make it into alpha3 Import draw (maybe without HudCache for a start) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 1a0ffaa6..def44534 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -407,7 +407,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha2+, p76") + self.window.set_title("Free Poker DB - version: alpha2+, p78") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) @@ -417,22 +417,22 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") ( "/Main/_Load Profile (broken)", "L", self.dia_load_profile, 0, None ), ( "/Main/_Edit Profile (todo)", "E", self.dia_edit_profile, 0, None ), ( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ), - ( "/Main/sep1", None, None, 0, "" ), - ( "/Main/_Quit", "Q", self.quit, 0, None ), - ( "/_Import", None, None, 0, "" ), - ( "/Import/_Bulk Import", "B", self.tab_bulk_import, 0, None ), - ( "/Import/_Auto Import (todo)", "A", self.tab_auto_import, 0, None ), - ( "/Import/Auto _Rating (todo)", "R", self.not_implemented, 0, None ), - ( "/_Viewers", None, None, 0, "" ), - ( "/Viewers/_Graphs (todo)", None, self.not_implemented, 0, None ), - ( "/Viewers/H_and Replayer (todo)", None, self.not_implemented, 0, None ), - ( "/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ), - ( "/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), - ( "/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), - ( "/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), - ( "/Viewers/Poker_table Viewer", "T", self.tab_table_viewer, 0, None ), + ("/Main/sep1", None, None, 0, "" ), + ("/Main/_Quit", "Q", self.quit, 0, None ), + ("/_Import", None, None, 0, "" ), + ("/Import/_Bulk Import", "B", self.tab_bulk_import, 0, None ), + ("/Import/_Auto Import and HUD", "A", self.tab_auto_import, 0, None ), + ("/Import/Auto _Rating (todo)", "R", self.not_implemented, 0, None ), + ("/_Viewers", None, None, 0, "" ), + ("/_Viewers/_Auto Import and HUD", "A", self.tab_auto_import, 0, None ), + ("/Viewers/_Graphs (todo)", None, self.not_implemented, 0, None ), + ("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ), + ("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ), + ("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), + ("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), + ("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), + ("/Viewers/Poker_table Viewer", "T", self.tab_table_viewer, 0, None ), #( "/Viewers/Tourney Replayer - #( "/H_UD", None, None, 0, "" ), ( "/_Database", None, None, 0, "" ), ( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ), ( "/Database/Create or Delete _User (todo)", None, self.dia_create_del_user, 0, None ), diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index a33f040a..6696f64e 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -118,10 +118,9 @@ def mainParser(db, cursor, site, category, hand): totalWinnings+=winnings[i] if base=="hold": - hudImportData=fpdb_simple.generateHudCacheData(playerIDs, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) + hudImportData=fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) else: - print "todo: stud HudCache" - hudImportData=None + hudImportData=fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes, actionTypeByNo, winnings, totalWinnings, None) if isTourney: ranks=[] diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index dccbcba7..c07fd225 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -27,9 +27,9 @@ def ring_stud(cursor, base, category, site_hand_no, gametype_id, hand_start_time hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) hands_players_ids=fpdb_simple.store_hands_players_stud(cursor, hands_id, player_ids, - start_cashes, antes, card_values, card_suits, winnings, rakes) + start_cashes, antes, card_values, card_suits, winnings, rakes, seatNos) - fpdb_simple.storeHudCache(cursor, category, gametype_id, player_ids, hudImportData) + fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) return hands_id @@ -44,7 +44,7 @@ def ring_holdem_omaha(cursor, base, category, site_hand_no, gametype_id, hand_st hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos) - fpdb_simple.storeHudCache(cursor, category, gametype_id, player_ids, hudImportData) + fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) @@ -65,7 +65,7 @@ def tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knoc hands_players_ids=fpdb_simple.store_hands_players_holdem_omaha_tourney(cursor, category, hands_id, player_ids, start_cashes, positions, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids) - fpdb_simple.storeHudCache(cursor, category, gametype_id, player_ids, hudImportData) + fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData) fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) @@ -90,7 +90,7 @@ def tourney_stud(cursor, base, category, site_tourney_no, buyin, fee, knockout, hands_players_ids=fpdb_simple.store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, tourneys_players_ids) - fpdb_simple.storeHudData(cursor, category, player_ids, hudImportData) + fpdb_simple.storeHudData(cursor, base, category, player_ids, hudImportData) fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) return hands_id diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 923c2053..1c854008 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1201,7 +1201,7 @@ def store_hands_players_holdem_omaha(cursor, category, hands_id, player_ids, sta #end def store_hands_players_holdem_omaha def store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, - card_values, card_suits, winnings, rakes): + card_values, card_suits, winnings, rakes, seatNos): #stores hands_players rows for stud/razz games. returns an array of the resulting IDs result=[] for i in range (len(player_ids)): @@ -1210,14 +1210,14 @@ def store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, card1Value, card1Suit, card2Value, card2Suit, card3Value, card3Suit, card4Value, card4Suit, card5Value, card5Suit, card6Value, card6Suit, - card7Value, card7Suit, winnings, rake) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + card7Value, card7Suit, winnings, rake, seatNo) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], antes[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5], - card_values[i][6], card_suits[i][6], winnings[i], rakes[i])) + card_values[i][6], card_suits[i][6], winnings[i], rakes[i], seatNos[i])) cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId=%s", (hands_id, player_ids[i])) result.append(cursor.fetchall()[0][0]) return result @@ -1278,7 +1278,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, return result #end def store_hands_players_stud_tourney -def generateHudCacheData(player_ids, category, action_types, actionTypeByNo, winnings, totalWinnings, positions): +def generateHudCacheData(player_ids, base, category, action_types, actionTypeByNo, winnings, totalWinnings, positions): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" #setup subarrays of the result dictionary. street0VPI=[] @@ -1333,15 +1333,16 @@ def generateHudCacheData(player_ids, category, action_types, actionTypeByNo, win buttonId=-1 sbId=-1 bbId=-1 - for player in range(len(positions)): - if positions==1: - cutoffId=player_ids[player] - if positions==0: - buttonId=player_ids[player] - if positions=='S': - sbId=player_ids[player] - if positions=='B': - bbId=player_ids[player] + if base=="hold": + for player in range(len(positions)): + if positions==1: + cutoffId=player_ids[player] + if positions==0: + buttonId=player_ids[player] + if positions=='S': + sbId=player_ids[player] + if positions=='B': + bbId=player_ids[player] someoneStole=False @@ -1400,30 +1401,31 @@ def generateHudCacheData(player_ids, category, action_types, actionTypeByNo, win myStreet0_3B4BDone=True #steal calculations - if len(player_ids)>=5: #no point otherwise - if positions[player]==1: - if firstPfRaiserId==player_ids[player]: - myStealAttemptChance=True - myStealAttempted=True - elif firstPfRaiserId==buttonId or firstPfRaiserId==sbId or firstPfRaiserId==bbId or firstPfRaiserId==-1: - myStealAttemptChance=True - if positions[player]==0: - if firstPfRaiserId==player_ids[player]: - myStealAttemptChance=True - myStealAttempted=True - elif firstPfRaiserId==sbId or firstPfRaiserId==bbId or firstPfRaiserId==-1: - myStealAttemptChance=True - if positions[player]=='S': - if firstPfRaiserId==player_ids[player]: - myStealAttemptChance=True - myStealAttempted=True - elif firstPfRaiserId==bbId or firstPfRaiserId==-1: - myStealAttemptChance=True - if positions[player]=='B': - pass + if base=="hold": + if len(player_ids)>=5: #no point otherwise + if positions[player]==1: + if firstPfRaiserId==player_ids[player]: + myStealAttemptChance=True + myStealAttempted=True + elif firstPfRaiserId==buttonId or firstPfRaiserId==sbId or firstPfRaiserId==bbId or firstPfRaiserId==-1: + myStealAttemptChance=True + if positions[player]==0: + if firstPfRaiserId==player_ids[player]: + myStealAttemptChance=True + myStealAttempted=True + elif firstPfRaiserId==sbId or firstPfRaiserId==bbId or firstPfRaiserId==-1: + myStealAttemptChance=True + if positions[player]=='S': + if firstPfRaiserId==player_ids[player]: + myStealAttemptChance=True + myStealAttempted=True + elif firstPfRaiserId==bbId or firstPfRaiserId==-1: + myStealAttemptChance=True + if positions[player]=='B': + pass - if myStealAttempted: - someoneStole=True + if myStealAttempted: + someoneStole=True #calculate saw* values if (len(action_types[1][player])>0): @@ -1523,21 +1525,25 @@ def generateHudCacheData(player_ids, category, action_types, actionTypeByNo, win wonAtSD.append(myWonAtSD) stealAttemptChance.append(myStealAttemptChance) stealAttempted.append(myStealAttempted) - pos=positions[player] - if pos=='B': - hudDataPositions.append('B') - elif pos=='S': - hudDataPositions.append('S') - elif pos==0: - hudDataPositions.append('D') - elif pos==1: - hudDataPositions.append('C') - elif pos>=2 and pos<=4: - hudDataPositions.append('M') - elif pos>=5 and pos<=7: - hudDataPositions.append('L') - else: - raise FpdbError("invalid position") + if base=="hold": + pos=positions[player] + if pos=='B': + hudDataPositions.append('B') + elif pos=='S': + hudDataPositions.append('S') + elif pos==0: + hudDataPositions.append('D') + elif pos==1: + hudDataPositions.append('C') + elif pos>=2 and pos<=4: + hudDataPositions.append('M') + elif pos>=5 and pos<=7: + hudDataPositions.append('L') + else: + raise FpdbError("invalid position") + elif base=="stud": + #todo: stud positions and steals + pass #add each array to the to-be-returned dictionary result={'street0VPI':street0VPI} @@ -1578,17 +1584,18 @@ def generateHudCacheData(player_ids, category, action_types, actionTypeByNo, win myFoldSbToStealChance=False myFoldedSbToSteal=False - if someoneStole and (positions[player]=='B' or positions[player]=='S') and firstPfRaiserId!=player_ids[player]: - street=0 - for count in range (len(action_types[street][player])):#individual actions - if positions[player]=='B': - myFoldBbToStealChance=True - if action_types[street][player][count]=="fold": - myFoldedBbToSteal=True - if positions[player]=='S': - myFoldSbToStealChance=True - if action_types[street][player][count]=="fold": - myFoldedSbToSteal=True + if base=="hold": + if someoneStole and (positions[player]=='B' or positions[player]=='S') and firstPfRaiserId!=player_ids[player]: + street=0 + for count in range (len(action_types[street][player])):#individual actions + if positions[player]=='B': + myFoldBbToStealChance=True + if action_types[street][player][count]=="fold": + myFoldedBbToSteal=True + if positions[player]=='S': + myFoldSbToStealChance=True + if action_types[street][player][count]=="fold": + myFoldedSbToSteal=True foldBbToStealChance.append(myFoldBbToStealChance) @@ -1797,10 +1804,14 @@ def generateFoldToCB(street, playerIDs, didStreetCB, streetCBDone, foldToStreetC foldToStreetCBDone[player]=True #end def generateFoldToCB -def storeHudCache(cursor, category, gametypeId, playerIds, hudImportData): - if (category=="holdem" or category=="omahahi" or category=="omahahilo"): +def storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData): +# if (category=="holdem" or category=="omahahi" or category=="omahahilo"): + for player in range (len(playerIds)): - cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s", (gametypeId, playerIds[player], len(playerIds), hudImportData['position'][player])) + if base=="hold": + cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s", (gametypeId, playerIds[player], len(playerIds), hudImportData['position'][player])) + else: + cursor.execute("SELECT * FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s", (gametypeId, playerIds[player], len(playerIds))) row=cursor.fetchone() #print "gametypeId:", gametypeId, "playerIds[player]",playerIds[player], "len(playerIds):",len(playerIds), "row:",row @@ -1826,7 +1837,10 @@ def storeHudCache(cursor, category, gametypeId, playerIds, hudImportData): newrow.append(row[i]) row=newrow - row[4]=hudImportData['position'][player] + if base=="hold": + row[4]=hudImportData['position'][player] + else: + row[4]=0 row[5]=1 #tourneysGametypeId row[6]+=1 #HDs if hudImportData['street0VPI'][player]: row[7]+=1 @@ -1938,8 +1952,8 @@ def storeHudCache(cursor, category, gametypeId, playerIds, hudImportData): row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50], row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60], row[1], row[2], row[3], row[4], row[5])) - else: - print "todo: implement storeHudCache for stud base" +# else: +# print "todo: implement storeHudCache for stud base" #end def storeHudCache def store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime): From 7b133313319eaa8eaf77d919d06d0d4bd15b7576 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 15 Sep 2008 21:31:55 +0100 Subject: [PATCH 080/262] p80 - a great many updates from Ray --- create-release.sh | 2 + docs/known-bugs-and-planned-features.txt | 22 +- pyfpdb/Configuration.py | 72 +++++- pyfpdb/Database.py | 48 +++- pyfpdb/GuiAutoImport.py | 87 +++++-- pyfpdb/HUD_main.py | 76 ++++-- pyfpdb/Hud.py | 301 ++++++++++++++++++++--- pyfpdb/Mucked.py | 12 +- pyfpdb/SQL.py | 37 ++- pyfpdb/Stats.py | 67 +++-- pyfpdb/Tables.py | 102 +++++++- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 25 +- pyfpdb/fpdb_import.py | 21 +- pyfpdb/fpdb_simple.py | 9 +- pyfpdb/schema.postgres.sql | 217 ++++++++++++++++ 16 files changed, 964 insertions(+), 136 deletions(-) mode change 100644 => 100755 pyfpdb/Configuration.py mode change 100644 => 100755 pyfpdb/Hud.py mode change 100755 => 100644 pyfpdb/fpdb_import.py create mode 100644 pyfpdb/schema.postgres.sql diff --git a/create-release.sh b/create-release.sh index d4b7bfad..a4fa00d5 100755 --- a/create-release.sh +++ b/create-release.sh @@ -22,6 +22,8 @@ rm pyfpdb/*.pyc mkdir fpdb-$1 cp -R docs fpdb-$1/ cp -R pyfpdb fpdb-$1/ +rm fpdb-$1/HUD_config.* +cp pyfpdb/HUD_config.xml.example fpdb-$1/ cp -R regression-test fpdb-$1/ cp -R utils fpdb-$1/ cd fpdb-$1 diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 7ff766cb..6d1c2689 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,9 +2,8 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha3 (release 1-2Sep?) +alpha3 (release 15Sep) ====== -(fixed by ray) auto import only runs on one file per start find correct sf logo link windows integrated installer @@ -20,7 +19,7 @@ fill check-/call-raise cache fields printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt -alpha4 (release 8Sep?) +alpha4 (release 22-29Sep) ====== change to savannah? implement steal and positions in stud @@ -33,9 +32,25 @@ change tabledesign VALIGN finish updating filelist finish todos in git instructions debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html +howto remote DB +move all user docs to webpage before beta =========== +No Full Tilt support in HUD +No river stats for stud games +hole/board cards are not correctly stored in the db for stud games +HORSE (and presumably other mixed games) hand history files not handled correctly +HUD stat windows are too big on Windows +HUD task bar entries on Windows won't go away +Some MTTs won't import (rebuys??) +Many STTs won't import +MTT/STT not tested in HUD +HUD stats not aggregated +Player names with non-Latin chars throw warnings in HUD +HUD doesn't start when fpdb is started from the Windows "Start Menu" +Exiting HUD on Windows doesn't properly clean up stat windows + finish bringing back tourney ebuild: USE gtk, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, git-ebuild, get it into sunrise make hud display W$SD etc as fraction. @@ -49,6 +64,7 @@ split hud data generation into separate for loops and make it more efficient fix bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin - might as well add all-in recognition for this make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) make it work with postgres +gentoo ebuild: USE postgresql expand instructions for profile file maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py old mode 100644 new mode 100755 index 849d595a..55b8ed2b --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -23,6 +23,7 @@ Handles HUD configuration files. ######################################################################## # Standard Library modules +import shutil import xml.dom.minidom from xml.dom.minidom import Node @@ -38,7 +39,7 @@ class Layout: for i in range(1, len(self.location)): temp = temp + "(%d,%d)" % self.location[i] - return temp + return temp + "\n" class Site: def __init__(self, node): @@ -81,7 +82,7 @@ class Stat: pass def __str__(self): - temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click) + temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s, popup = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click, self.popup) return temp class Game: @@ -99,6 +100,7 @@ class Game: stat.col = int( stat_node.getAttribute("col") ) stat.tip = stat_node.getAttribute("tip") stat.click = stat_node.getAttribute("click") + stat.popup = stat_node.getAttribute("popup") self.stats[stat.stat_name] = stat @@ -150,15 +152,31 @@ class Mucked: temp = temp + ' ' + key + " = " + value + "\n" return temp +class Popup: + def __init__(self, node): + self.name = node.getAttribute("pu_name") + self.pu_stats = [] + for stat_node in node.getElementsByTagName('pu_stat'): + self.pu_stats.append(stat_node.getAttribute("pu_stat_name")) + + def __str__(self): + temp = "Popup = " + self.name + "\n" + for stat in self.pu_stats: + temp = temp + " " + stat + return temp + "\n" + class Config: def __init__(self, file = 'HUD_config.xml'): doc = xml.dom.minidom.parse(file) + self.doc = doc + self.file = file self.supported_sites = {} self.supported_games = {} self.supported_databases = {} self.mucked_windows = {} + self.popup_windows = {} # s_sites = doc.getElementsByTagName("supported_sites") for site_node in doc.getElementsByTagName("site"): @@ -180,6 +198,47 @@ class Config: mw = Mucked(node = mw_node) self.mucked_windows[mw.name] = mw + s_dbs = doc.getElementsByTagName("popup_windows") + for pu_node in doc.getElementsByTagName("pu"): + pu = Popup(node = pu_node) + self.popup_windows[pu.name] = pu + + def get_site_node(self, site): + for site_node in self.doc.getElementsByTagName("site"): + if site_node.getAttribute("site_name") == site: + return site_node + + def get_layout_node(self, site_node, layout): + for layout_node in site_node.getElementsByTagName("layout"): + if int( layout_node.getAttribute("max") ) == int( layout ): + return layout_node + + def get_location_node(self, layout_node, seat): + for location_node in layout_node.getElementsByTagName("location"): + if int( location_node.getAttribute("seat") ) == int( seat ): + return location_node + + def save(self, file = None): + if not file == None: + f = open(file, 'w') + self.doc.writexml(f) + f.close() + else: + shutil.move(self.file, self.file+".backup") + f = open(self.file, 'w') + self.doc.writexml(f) + f.close + + def edit_layout(self, site_name, max, width = None, height = None, + fav_seat = None, locations = None): + site_node = self.get_site_node(site_name) + layout_node = self.get_layout_node(site_node, max) + for i in range(1, max + 1): + location_node = self.get_location_node(layout_node, i) + location_node.setAttribute("x", str( locations[i-1][0] )) + location_node.setAttribute("y", str( locations[i-1][1] )) + self.supported_sites[site_name].layout[max].location[i] = ( locations[i-1][0], locations[i-1][1] ) + if __name__== "__main__": c = Config() @@ -208,3 +267,12 @@ if __name__== "__main__": print c.mucked_windows[w] print "----------- END MUCKED WINDOW FORMATS -----------" + + print "\n----------- POPUP WINDOW FORMATS -----------" + for w in c.popup_windows.keys(): + print c.popup_windows[w] + + print "----------- END MUCKED WINDOW FORMATS -----------" + + c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) + c.save(file="testout.xml") \ No newline at end of file diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 8d10e8a1..d634e134 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -24,6 +24,7 @@ Create and manage the database objects. # postmaster -D /var/lib/pgsql/data # Standard Library modules +import sys # pyGTK modules @@ -32,7 +33,7 @@ import Configuration import SQL # pgdb database module for posgres via DB-API -#import pgdb +import psycopg2 # pgdb uses pyformat. is that fixed or an option? # mysql bindings @@ -41,7 +42,7 @@ import MySQLdb class Database: def __init__(self, c, db_name, game): if c.supported_databases[db_name].db_server == 'postgresql': - self.connection = pgdb.connect(dsn = c.supported_databases[db_name].db_ip, + self.connection = psycopg2.connect(host = c.supported_databases[db_name].db_ip, user = c.supported_databases[db_name].db_user, password = c.supported_databases[db_name].db_pass, database = c.supported_databases[db_name].db_name) @@ -59,12 +60,12 @@ class Database: self.type = c.supported_databases[db_name].db_type self.sql = SQL.Sql(game = game, type = self.type) - def close(self): - self.connection.close + def close_connection(self): + self.connection.close() def get_table_name(self, hand_id): c = self.connection.cursor() - c.execute(self.sql.query['get_table_name'], (hand_id)) + c.execute(self.sql.query['get_table_name'], (hand_id, )) row = c.fetchone() return row @@ -90,7 +91,21 @@ class Database: c.execute(self.sql.query['get_hand_info'], new_hand_id) return c.fetchall() +# def get_cards(self, hand): +# this version is for the PTrackSv2 db +# c = self.connection.cursor() +# c.execute(self.sql.query['get_cards'], hand) +# colnames = [desc[0] for desc in c.description] +# cards = {} +# for row in c.fetchall(): +# s_dict = {} +# for name, val in zip(colnames, row): +# s_dict[name] = val +# cards[s_dict['seat_number']] = s_dict +# return (cards) + def get_cards(self, hand): +# this version is for the fpdb db c = self.connection.cursor() c.execute(self.sql.query['get_cards'], hand) colnames = [desc[0] for desc in c.description] @@ -101,12 +116,14 @@ class Database: s_dict[name] = val cards[s_dict['seat_number']] = s_dict return (cards) - - def get_stats_from_hand(self, hand, hero): + + def get_stats_from_hand(self, hand, player_id = False): c = self.connection.cursor() + if not player_id: player_id = "%" # get the players in the hand and their seats - c.execute(self.sql.query['get_players_from_hand'], (hand)) +# c.execute(self.sql.query['get_players_from_hand'], (hand, player_id)) + c.execute(self.sql.query['get_players_from_hand'], (hand, )) names = {} seats = {} for row in c.fetchall(): @@ -114,6 +131,7 @@ class Database: seats[row[0]] = row[1] # now get the stats +# c.execute(self.sql.query['get_stats_from_hand'], (hand, hand, player_id)) c.execute(self.sql.query['get_stats_from_hand'], (hand, hand)) colnames = [desc[0] for desc in c.description] stat_dict = {} @@ -137,7 +155,8 @@ class Database: if __name__=="__main__": c = Configuration.Config() - db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem +# db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem + db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem # db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz # db_connection = Database(c, 'ptracks', 'razz') # postgres print "database connection object = ", db_connection.connection @@ -149,7 +168,16 @@ if __name__=="__main__": hero = db_connection.get_player_id(c, 'PokerStars', 'nutOmatic') print "nutOmatic is id_player = %d" % hero + stat_dict = db_connection.get_stats_from_hand(h) + for p in stat_dict.keys(): + print p, " ", stat_dict[p] + + print "nutOmatics stats:" stat_dict = db_connection.get_stats_from_hand(h, hero) for p in stat_dict.keys(): print p, " ", stat_dict[p] - db_connection.close + + db_connection.close_connection + + print "press enter to continue" + sys.stdin.readline() diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 42f5bf6d..5081022f 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -33,12 +33,12 @@ class GuiAutoImport (threading.Thread): current_path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", - action=gtk.FILE_CHOOSER_ACTION_OPEN, + action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) #dia_chooser.set_current_folder(pathname) dia_chooser.set_filename(current_path) #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import - + response = dia_chooser.run() if response == gtk.RESPONSE_OK: #print dia_chooser.get_filename(), 'selected' @@ -50,8 +50,18 @@ class GuiAutoImport (threading.Thread): def do_import(self): """Callback for timer to do an import iteration.""" - fpdb_import.import_file_dict(self, self.settings) - return(1) + for file in os.listdir(self.path): + if os.path.isdir(file): + print "AutoImport is not recursive - please select the final directory in which the history files are" + else: + self.inputFile = os.path.join(self.path, file) + stat_info = os.stat(self.inputFile) + if not self.import_files.has_key(self.inputFile) or stat_info.st_mtime > self.import_files[self.inputFile]: + fpdb_import.import_file_dict(self, self.settings, callHud = True) + self.import_files[self.inputFile] = stat_info.st_mtime + + print "GuiAutoImport.import_dir done" + return True def startClicked(self, widget, data): """runs when user clicks start on auto import tab""" @@ -59,24 +69,48 @@ class GuiAutoImport (threading.Thread): # Check to see if we have an open file handle to the HUD and open one if we do not. # bufsize = 1 means unbuffered # We need to close this file handle sometime. + +# TODO: Allow for importing from multiple dirs - REB 29AUG2008 +# As presently written this function does nothing if there is already a pipe open. +# That is not correct. It should open another dir for importing while piping the +# results to the same pipe. This means that self.path should be a a list of dirs +# to watch. try: #uhhh, I don't this this is the best way to check for the existence of an attr getattr(self, "pipe_to_hud") except AttributeError: - cwd = os.getcwd() - command = os.path.join(cwd, 'HUD_main.py') - self.pipe_to_hud = subprocess.Popen(command, bufsize = 1, stdin = subprocess.PIPE) - - self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) - for file in os.listdir(self.path): - if os.path.isdir(file): - print "AutoImport is not recursive - please select the final directory in which the history files are" + if os.name == 'nt': + command = "python HUD_main.py" + " %s" % (self.database) + bs = 0 # windows is not happy with line buffing here + self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, + universal_newlines=True) else: - self.inputFile=self.path+os.sep+file - self.do_import() - print "GuiAutoImport.import_dir done" + cwd = os.getcwd() + command = os.path.join(cwd, 'HUD_main.py') + bs = 1 + self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, + universal_newlines=True) +# self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, +# universal_newlines=True) +# command = command + " %s" % (self.database) +# print "command = ", command +# self.pipe_to_hud = os.popen(command, 'w') + self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) + +# Iniitally populate the self.import_files dict, which keeps mtimes for the files watched + + self.import_files = {} + for file in os.listdir(self.path): + if os.path.isdir(file): + pass # skip subdirs for now + else: + inputFile = os.path.join(self.path, file) + stat_info = os.stat(inputFile) + self.import_files[inputFile] = stat_info.st_mtime + + self.do_import() - interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) - gobject.timeout_add(interval*1000, self.do_import) + interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) + gobject.timeout_add(interval*1000, self.do_import) #end def GuiAutoImport.browseClicked def get_vbox(self): @@ -140,3 +174,22 @@ class GuiAutoImport (threading.Thread): self.mainVBox.add(self.startButton) self.startButton.show() #end of GuiAutoImport.__init__ +if __name__== "__main__": + def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() + + settings = {} + settings['db-host'] = "192.168.1.100" + settings['db-user'] = "mythtv" + settings['db-password'] = "mythtv" + settings['db-databaseName'] = "fpdb" + settings['hud-defaultInterval'] = 10 + settings['hud-defaultPath'] = 'C:/Program Files/PokerStars/HandHistory/nutOmatic' + settings['imp-callFpdbHud'] = True + + i = GuiAutoImport(settings) + main_window = gtk.Window() + main_window.connect("destroy", destroy) + main_window.add(i.mainVBox) + main_window.show() + gtk.main() diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 99ad9f5c..15bbd034 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -26,15 +26,17 @@ Main for FreePokerTools HUD. # to do adjust for preferred seat # to do allow window resizing # to do hud to echo, but ignore non numbers -# to do kill a hud # to do no hud window for hero -# to do single click to display detailed stats # to do things to add to config.xml # to do font and size +# to do bg and fg color +# to do opacity # Standard Library modules import sys import os +import thread +import Queue # pyGTK modules import pygtk @@ -49,19 +51,23 @@ import Hud # global dict for keeping the huds hud_dict = {} + db_connection = 0; config = 0; def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() - -def process_new_hand(source, condition): + +def process_new_hand(new_hand_id, db_name): # there is a new hand_id to be processed # read the hand_id from stdin and strip whitespace - new_hand_id = sys.stdin.readline() - new_hand_id = new_hand_id.rstrip() - db_connection = Database.Database(config, 'fpdb', 'holdem') + global hud_dict + for h in hud_dict.keys(): + if hud_dict[h].deleted: + del(hud_dict[h]) + + db_connection = Database.Database(config, db_name, 'temp') (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) # if a hud for this table exists, just update it if hud_dict.has_key(table_name): @@ -71,29 +77,61 @@ def process_new_hand(source, condition): table_windows = Tables.discover(config) for t in table_windows.keys(): if table_windows[t].name == table_name: - hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_connection) + hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_name) hud_dict[table_name].create(new_hand_id, config) hud_dict[table_name].update(new_hand_id, db_connection, config) break # print "table name \"%s\" not identified, no hud created" % (table_name) - return(1) + db_connection.close_connection() + return(1) + +def check_stdin(db_name): + try: + hand_no = dataQueue.get(block=False) + process_new_hand(hand_no, db_name) + except: + pass + + return True + +def read_stdin(source, condition, db_name): + new_hand_id = sys.stdin.readline() + process_new_hand(new_hand_id, db_name) + return True + +def producer(): # This is the thread function + while True: + hand_no = sys.stdin.readline() # reads stdin + dataQueue.put(hand_no) # and puts result on the queue if __name__== "__main__": - - if not os.name == 'posix': - print "This version of the HUD only works with Linux or compatible.\nHUD exiting." + print "HUD_main starting" + + try: + db_name = sys.argv[1] + except: + db_name = 'fpdb-p' + print "Using db name = ", db_name + + config = Configuration.Config() +# db_connection = Database.Database(config, 'fpdb', 'holdem') + + if os.name == 'posix': + s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, read_stdin, db_name) + elif os.name == 'nt': + dataQueue = Queue.Queue() # shared global. infinite size + gobject.threads_init() # this is required + thread.start_new_thread(producer, ()) # starts the thread + gobject.timeout_add(1000, check_stdin, db_name) + else: + print "Sorry your operating system is not supported." sys.exit() main_window = gtk.Window() main_window.connect("destroy", destroy) - label = gtk.Label('Fake main window, blah blah, blah\nblah, blah') + label = gtk.Label('Closing this window will exit from the HUD.') main_window.add(label) + main_window.set_title("HUD Main Window") main_window.show_all() - config = Configuration.Config() - - db_connection = Database.Database(config, 'fpdb', 'holdem') - - s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) - gtk.main() diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py old mode 100644 new mode 100755 index 2e0c7cdd..debfaa67 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -22,6 +22,7 @@ Create and manage the hud overlays. ######################################################################## # Standard Library modules +import os # pyGTK modules import pygtk @@ -29,26 +30,85 @@ import gtk import pango import gobject +# win32 modules -- only imported on windows systems +if os.name == 'nt': + import win32gui + import win32con + # FreePokerTools modules import Tables # needed for testing only import Configuration import Stats import Mucked import Database +import HUD_main class Hud: - def __init__(self, table, max, poker_game, config, db_connection): + def __init__(self, table, max, poker_game, config, db_name): self.table = table self.config = config self.poker_game = poker_game self.max = max - self.db_connection = db_connection + self.db_name = db_name + self.deleted = False self.stat_windows = {} + self.popup_windows = {} self.font = pango.FontDescription("Sans 8") - +# Set up a main window for this this instance of the HUD + self.main_window = gtk.Window() +# self.window.set_decorated(0) + self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.main_window.set_keep_above(1) + self.main_window.set_title(table.name) + self.main_window.connect("destroy", self.kill_hud) + + self.ebox = gtk.EventBox() + self.label = gtk.Label("Close this window to\nkill the HUD for\n %s" % (table.name)) + self.main_window.add(self.ebox) + self.ebox.add(self.label) + self.main_window.move(self.table.x, self.table.y) + +# A popup window for the main window + self.menu = gtk.Menu() + self.item1 = gtk.MenuItem('Kill this HUD') + self.menu.append(self.item1) + self.item1.connect("activate", self.kill_hud) + self.item1.show() + self.item2 = gtk.MenuItem('Save Layout') + self.menu.append(self.item2) + self.item2.connect("activate", self.save_layout) + self.item2.show() + self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) + + self.main_window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.main_window) + + def on_button_press(self, widget, event): + if event.button == 3: + widget.popup(None, None, None, event.button, event.time) + return True + return False + + def kill_hud(self, args): + for k in self.stat_windows.keys(): + self.stat_windows[k].window.destroy() + self.main_window.destroy() + self.deleted = True + + def save_layout(self, *args): + new_layout = [] + for sw in self.stat_windows: + loc = self.stat_windows[sw].window.get_position() + new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) + new_layout.append(new_loc) + print new_layout + self.config.edit_layout(self.table.site, self.table.max, locations = new_layout) + self.config.save() + def create(self, hand, config): # update this hud, to the stats and players as of "hand" # hand is the hand id of the most recent hand played at this table @@ -58,6 +118,7 @@ class Hud: for i in range(1, self.max + 1): (x, y) = config.supported_sites[self.table.site].layout[self.max].location[i] self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], + parent = self, table = self.table, x = x, y = y, @@ -79,10 +140,10 @@ class Hud: # self.mucked_window.show_all() def update(self, hand, db, config): - stat_dict = db.get_stats_from_hand(hand, 3) + self.hand = hand # this is the last hand, so it is available later + stat_dict = db.get_stats_from_hand(hand) for s in stat_dict.keys(): -# for r in range(0, 2): -# for c in range(0, 3): + self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] for r in range(0, config.supported_games[self.poker_game].rows): for c in range(0, config.supported_games[self.poker_game].cols): number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) @@ -91,7 +152,33 @@ class Hud: number[3] + ", " + number[4] Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip) # self.m.update(hand) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + class Stat_Window: def button_press_cb(self, widget, event, *args): @@ -101,7 +188,7 @@ class Stat_Window: if event.button == 1: # left button event if event.type == gtk.gdk.BUTTON_PRESS: # left button single click if self.sb_click > 0: return - self.sb_click = gobject.timeout_add(250, self.single_click) + self.sb_click = gobject.timeout_add(250, self.single_click, widget) elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click if self.sb_click > 0: gobject.source_remove(self.sb_click) @@ -109,17 +196,20 @@ class Stat_Window: self.double_click(widget, event, *args) if event.button == 2: # middle button event - print "middle button clicked" + pass +# print "middle button clicked" if event.button == 3: # right button event - print "right button clicked" + pass +# print "right button clicked" - def single_click(self): + def single_click(self, widget): # Callback from the timeout in the single-click finding part of the # button press call back. This needs to be modified to get all the # arguments from the call. - print "left button clicked" +# print "left button clicked" self.sb_click = 0 + Popup_window(widget, self) return False def double_click(self, widget, event, *args): @@ -136,13 +226,14 @@ class Stat_Window: top.set_decorated(1) top.move(x, y) - def __init__(self, game, table, seat, x, y, player_id, font): - self.game = game - self.table = table - self.x = x + table.x - self.y = y + table.y - self.player_id = player_id - self.sb_click = 0 + def __init__(self, parent, game, table, seat, x, y, player_id, font): + self.parent = parent # Hud object that this stat window belongs to + self.game = game # Configuration object for the curren + self.table = table # Table object where this is going + self.x = x + table.x # table.x and y are the location of the table + self.y = y + table.y # x and y are the location relative to table.x & y + self.player_id = player_id # looks like this isn't used ;) + self.sb_click = 0 # used to figure out button clicks self.window = gtk.Window() self.window.set_decorated(0) @@ -163,7 +254,7 @@ class Stat_Window: for c in range(self.game.cols): self.e_box[r].append( gtk.EventBox() ) Stats.do_tip(self.e_box[r][c], 'farts') - self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 1, ypadding = 1) + self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) self.label[r].append( gtk.Label('xxx') ) self.e_box[r][c].add(self.label[r][c]) self.e_box[r][c].connect("button_press_event", self.button_press_cb) @@ -172,10 +263,173 @@ class Stat_Window: self.window.realize self.window.move(self.x, self.y) self.window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.window) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() + +class Popup_window: + def __init__(self, parent, stat_window): + self.sb_click = 0 + +# create the popup window + self.window = gtk.Window() + self.window.set_decorated(0) + self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.window.set_keep_above(1) + self.window.set_title("popup") + self.window.set_property("skip-taskbar-hint", True) + self.window.set_transient_for(parent.get_toplevel()) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.ebox = gtk.EventBox() + self.ebox.connect("button_press_event", self.button_press_cb) + self.lab = gtk.Label("stuff\nstuff\nstuff") + +# need an event box so we can respond to clicks + self.window.add(self.ebox) + self.ebox.add(self.lab) + self.window.realize + +# figure out the row, col address of the click that activated the popup + row = 0 + col = 0 + for r in range(0, stat_window.game.rows): + for c in range(0, stat_window.game.cols): + if stat_window.e_box[r][c] == parent: + row = r + col = c + break + +# figure out what popup format we're using + popup_format = "default" + for stat in stat_window.game.stats.keys(): + if stat_window.game.stats[stat].row == row and stat_window.game.stats[stat].col == col: + popup_format = stat_window.game.stats[stat].popup + break + +# get the list of stats to be presented from the config + stat_list = [] + for w in stat_window.parent.config.popup_windows.keys(): + if w == popup_format: + stat_list = stat_window.parent.config.popup_windows[w].pu_stats + break + +# get a database connection + db_connection = Database.Database(stat_window.parent.config, stat_window.parent.db_name, 'temp') + +# calculate the stat_dict and then create the text for the pu +# stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand, stat_window.player_id) + stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand) + db_connection.close_connection() + + pu_text = "" + for s in stat_list: + number = Stats.do_stat(stat_dict, player = int(stat_window.player_id), stat = s) + pu_text += number[3] + "\n" + + self.lab.set_text(pu_text) + self.window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.window) + + def button_press_cb(self, widget, event, *args): +# This handles all callbacks from button presses on the event boxes in +# the popup windows. There is a bit of an ugly kludge to separate single- +# and double-clicks. This is the same code as in the Stat_window class + if event.button == 1: # left button event + if event.type == gtk.gdk.BUTTON_PRESS: # left button single click + if self.sb_click > 0: return + self.sb_click = gobject.timeout_add(250, self.single_click, widget) + elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click + if self.sb_click > 0: + gobject.source_remove(self.sb_click) + self.sb_click = 0 + self.double_click(widget, event, *args) + + if event.button == 2: # middle button event + pass +# print "middle button clicked" + + if event.button == 3: # right button event + pass +# print "right button clicked" + + def single_click(self, widget): +# Callback from the timeout in the single-click finding part of the +# button press call back. This needs to be modified to get all the +# arguments from the call. + self.sb_click = 0 + self.window.destroy() + return False + + def double_click(self, widget, event, *args): + self.toggle_decorated(widget) + + def toggle_decorated(self, widget): + top = widget.get_toplevel() + (x, y) = top.get_position() + + if top.get_decorated(): + top.set_decorated(0) + top.move(x, y) + else: + top.set_decorated(1) + top.move(x, y) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + if __name__== "__main__": main_window = gtk.Window() main_window.connect("destroy", destroy) @@ -187,15 +441,6 @@ if __name__== "__main__": tables = Tables.discover(c) db = Database.Database(c, 'fpdb', 'holdem') - for attr in dir(Stats): - if attr.startswith('__'): continue - if attr == 'Configuration' or attr == 'Database': continue - if attr == 'GInitiallyUnowned': continue -# print Stats.attr.__doc__ - - print Stats.vpip.__doc__ - - for t in tables: win = Hud(t, 8, c, db) # t.get_details() diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py index 292fd58d..0af06c65 100644 --- a/pyfpdb/Mucked.py +++ b/pyfpdb/Mucked.py @@ -105,7 +105,8 @@ class MuckedList: self.mucked_cards.update(model.get_value(iter, 0)) def update(self, new_hand_id): - info_row = self.db_connection.get_hand_info(new_hand_id) +# info_row = self.db_connection.get_hand_info(new_hand_id) + info_row = ((new_hand_id, "xxxx", 0), ) iter = self.liststore.append(info_row[0]) sel = self.treeview.get_selection() sel.select_iter(iter) @@ -156,11 +157,15 @@ class MuckedCards: self.grid.attach(self.grid_contents[(c, r)], c, c+1, r, r+1, xpadding = 1, ypadding = 1) self.parent.add(self.grid) - + + def translate_cards(self, old_cards): + pass + def update(self, new_hand_id): cards = self.db_connection.get_cards(new_hand_id) self.clear() + cards = self.translate_cards(cards) for c in cards.keys(): self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name']) @@ -224,7 +229,7 @@ if __name__== "__main__": return(True) config = Configuration.Config() - db_connection = Database.Database(config, 'PTrackSv2', 'razz') + db_connection = Database.Database(config, 'fpdb', '') main_window = gtk.Window() main_window.set_keep_above(True) @@ -236,4 +241,3 @@ if __name__== "__main__": s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) gtk.main() - diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index ffec6c65..072d478e 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -60,7 +60,7 @@ class Sql: FROM game_players WHERE game_id = %s AND player_id = 3 """ - + self.query['get_cards'] = """ select seat_number, @@ -173,10 +173,6 @@ class Sql: self.query['get_stats_from_hand'] = """ SELECT HudCache.playerId AS player_id, - HudCache.gametypeId AS gametypeId, - activeSeats AS n_active, - position AS position, - HudCache.tourneyTypeId AS tourneyTypeId, sum(HDs) AS n, sum(street0VPI) AS vpip, sum(street0Aggr) AS pfr, @@ -241,12 +237,18 @@ class Sql: AND Hands.gametypeId = HudCache.gametypeId GROUP BY HudCache.PlayerId """ +# AND PlayerId LIKE %s +# HudCache.gametypeId AS gametypeId, +# activeSeats AS n_active, +# position AS position, +# HudCache.tourneyTypeId AS tourneyTypeId, self.query['get_players_from_hand'] = """ SELECT HandsPlayers.playerId, seatNo, name FROM HandsPlayers INNER JOIN Players ON (HandsPlayers.playerId = Players.id) WHERE handId = %s """ +# WHERE handId = %s AND Players.id LIKE %s self.query['get_table_name'] = """ select tableName, maxSeats, category @@ -255,6 +257,31 @@ class Sql: and Gametypes.id = Hands.gametypeId """ + self.query['get_cards'] = """ + select + seatNo AS seat_number, + name AS screen_name, + card1Value, card1Suit, + card2Value, card2Suit, + card3Value, card3Suit, + card4Value, card4Suit, + card5Value, card5Suit, + card6Value, card6Suit, + card7Value, card7Suit + from HandsPlayers, Players + where handID = %s and HandsPlayers.playerId = Players.id + order by seatNo + """ + +# self.query['get_hand_info'] = """ +# SELECT +# game_id, +# CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, +# total_won-total_bet AS net +# FROM game_players +# WHERE game_id = %s AND player_id = 3 +# """ + if __name__== "__main__": # just print the default queries and exit s = Sql(game = 'razz', type = 'ptracks') diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py index b52378cb..3531c017 100644 --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -43,6 +43,7 @@ # 6 For each stat you make add a line to the __main__ function to test it. # Standard Library modules +#import sys # pyGTK modules import pygtk @@ -55,10 +56,6 @@ import Database def do_tip(widget, tip): widget.set_tooltip_text(tip) -def list_stats(): - for key in dir(): - print key - def do_stat(stat_dict, player = 24, stat = 'vpip'): return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) # OK, for reference the tuple returned by the stat is: @@ -72,9 +69,7 @@ def do_stat(stat_dict, player = 24, stat = 'vpip'): ########################################### # functions that return individual stats def vpip(stat_dict, player): - """ - Voluntarily put $ in the pot - """ + """ Voluntarily put $ in the pot.""" stat = 0.0 try: stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']) @@ -94,6 +89,7 @@ def vpip(stat_dict, player): ) def pfr(stat_dict, player): + """ Preflop (3rd street) raise.""" stat = 0.0 try: stat = float(stat_dict[player]['pfr'])/float(stat_dict[player]['n']) @@ -114,6 +110,7 @@ def pfr(stat_dict, player): ) def wtsd(stat_dict, player): + """ Went to SD when saw flop/4th.""" stat = 0.0 try: stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f']) @@ -134,6 +131,7 @@ def wtsd(stat_dict, player): ) def wmsd(stat_dict, player): + """ Won $ at showdown.""" stat = 0.0 try: stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd']) @@ -141,7 +139,7 @@ def wmsd(stat_dict, player): '%3.1f' % (100*stat) + '%', 'w=%3.1f' % (100*stat) + '%', 'wmsd=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['wmsd'], stat_dict[player]['sd']), + '(%f5.0/%d)' % (stat_dict[player]['wmsd'], stat_dict[player]['sd']), '% won money at showdown' ) except: @@ -154,6 +152,7 @@ def wmsd(stat_dict, player): ) def saw_f(stat_dict, player): + """ Saw flop/4th.""" try: num = float(stat_dict[player]['saw_f']) den = float(stat_dict[player]['n']) @@ -178,6 +177,7 @@ def saw_f(stat_dict, player): ) def n(stat_dict, player): + """ Number of hands played.""" try: return (stat_dict[player]['n'], '%d' % (stat_dict[player]['n']), @@ -187,15 +187,16 @@ def n(stat_dict, player): 'number hands seen' ) except: - return (stat_dict[player][0], - '%d' % (stat_dict[player][0]), - 'n=%d' % (stat_dict[player][0]), - 'n=%d' % (stat_dict[player][0]), - '(%d)' % (stat_dict[player][0]), + return (0, + '%d' % (0), + 'n=%d' % (0), + 'n=%d' % (0), + '(%d)' % (0), 'number hands seen' ) def fold_f(stat_dict, player): + """ Folded flop/4th.""" stat = 0.0 try: stat = float(stat_dict[player]['fold_2'])/fold(stat_dict[player]['saw_f']) @@ -216,6 +217,7 @@ def fold_f(stat_dict, player): ) def steal(stat_dict, player): + """ Steal %.""" stat = 0.0 try: stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp']) @@ -236,6 +238,7 @@ def steal(stat_dict, player): ) def f_SB_steal(stat_dict, player): + """ Folded SB to steal.""" stat = 0.0 try: stat = float(stat_dict[player]['SBnotDef'])/float(stat_dict[player]['SBstolen']) @@ -256,6 +259,7 @@ def f_SB_steal(stat_dict, player): ) def f_BB_steal(stat_dict, player): + """ Folded BB to steal.""" stat = 0.0 try: stat = float(stat_dict[player]['BBnotDef'])/float(stat_dict[player]['BBstolen']) @@ -276,6 +280,7 @@ def f_BB_steal(stat_dict, player): ) def three_B_0(stat_dict, player): + """ Three bet preflop/3rd.""" stat = 0.0 try: stat = float(stat_dict[player]['TB_0'])/float(stat_dict[player]['TB_opp_0']) @@ -296,6 +301,7 @@ def three_B_0(stat_dict, player): ) def WMsF(stat_dict, player): + """ Won $ when saw flop/4th.""" stat = 0.0 try: stat = float(stat_dict[player]['w_w_s_1'])/float(stat_dict[player]['saw_1']) @@ -316,6 +322,7 @@ def WMsF(stat_dict, player): ) def a_freq_1(stat_dict, player): + """ Flop/4th aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_1'])/float(stat_dict[player]['saw_f']) @@ -336,6 +343,7 @@ def a_freq_1(stat_dict, player): ) def a_freq_2(stat_dict, player): + """ Turn/5th aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_2'])/float(stat_dict[player]['saw_2']) @@ -356,6 +364,7 @@ def a_freq_2(stat_dict, player): ) def a_freq_3(stat_dict, player): + """ River/6th aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_3'])/float(stat_dict[player]['saw_3']) @@ -376,6 +385,7 @@ def a_freq_3(stat_dict, player): ) def a_freq_4(stat_dict, player): + """ 7th street aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_4'])/float(stat_dict[player]['saw_4']) @@ -389,13 +399,14 @@ def a_freq_4(stat_dict, player): except: return (stat, '%3.1f' % (0) + '%', - 'a1=%3.1f' % (0) + '%', - 'a_fq_1=%3.1f' % (0) + '%', + 'a4=%3.1f' % (0) + '%', + 'a_fq_4=%3.1f' % (0) + '%', '(%d/%d)' % (0, 0), 'Aggression Freq flop/4th' ) def cb_1(stat_dict, player): + """ Flop continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_1'])/float(stat_dict[player]['CB_opp_1']) @@ -416,6 +427,7 @@ def cb_1(stat_dict, player): ) def cb_2(stat_dict, player): + """ Turn continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_2'])/float(stat_dict[player]['CB_opp_2']) @@ -436,6 +448,7 @@ def cb_2(stat_dict, player): ) def cb_3(stat_dict, player): + """ River continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_3'])/float(stat_dict[player]['CB_opp_3']) @@ -456,6 +469,7 @@ def cb_3(stat_dict, player): ) def cb_4(stat_dict, player): + """ 7th street continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_4'])/float(stat_dict[player]['CB_opp_4']) @@ -476,6 +490,7 @@ def cb_4(stat_dict, player): ) def ffreq_1(stat_dict, player): + """ Flop/4th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_1'])/float(stat_dict[player]['was_raised_1']) @@ -496,6 +511,7 @@ def ffreq_1(stat_dict, player): ) def ffreq_2(stat_dict, player): + """ Turn/5th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_2'])/float(stat_dict[player]['was_raised_2']) @@ -516,6 +532,7 @@ def ffreq_2(stat_dict, player): ) def ffreq_3(stat_dict, player): + """ River/6th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_3'])/float(stat_dict[player]['was_raised_3']) @@ -536,6 +553,7 @@ def ffreq_3(stat_dict, player): ) def ffreq_4(stat_dict, player): + """ 7th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_4'])/float(stat_dict[player]['was_raised_4']) @@ -559,7 +577,7 @@ if __name__== "__main__": c = Configuration.Config() db_connection = Database.Database(c, 'fpdb', 'holdem') h = db_connection.get_last_hand() - stat_dict = db_connection.get_stats_from_hand(h, 0) + stat_dict = db_connection.get_stats_from_hand(h) for player in stat_dict.keys(): print "player = ", player, do_stat(stat_dict, player = player, stat = 'vpip') @@ -587,13 +605,14 @@ if __name__== "__main__": print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3') print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4') -# print "\n\nLegal stats:" -# for attr in dir(): -# if attr.startswith('__'): continue -# if attr == 'Configuration' or attr == 'Database': continue -# if attr == 'GInitiallyUnowned': continue -# print attr.__doc__ -# -# print vpip.__doc__ + print "\n\nLegal stats:" + for attr in dir(): + if attr.startswith('__'): continue + if attr in ("Configuration", "Database", "GInitiallyUnowned", "gtk", "pygtk", + "player", "c", "db_connection", "do_stat", "do_tip", "stat_dict", + "h"): continue + print attr, eval("%s.__doc__" % (attr)) +# print " " % (attr) + db_connection.close diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index 6c53a590..d7d9533d 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -26,8 +26,14 @@ of Table_Window objects representing the windows found. # Standard Library modules import os +import sys import re +# Win32 modules + +if os.name == 'nt': + import win32gui + # FreePokerTools modules import Configuration @@ -49,8 +55,24 @@ class Table_Window: table.tournament = 0 def discover(c): + if os.name == 'posix': + tables = discover_posix(c) + return tables + elif os.name == 'nt': + tables = discover_nt(c) + return tables + elif ox.name == 'mac': + tables = discover_mac(c) + return tables + else: tables = {} + + return(tables) + +def discover_posix(c): + """ Poker client table window finder for posix/Linux = XWindows.""" tables = {} for listing in os.popen('xwininfo -root -tree').readlines(): +# xwininfo -root -tree -id 0xnnnnn gets the info on a single window if re.search('Lobby', listing): continue if re.search('Instant Hand History', listing): continue if not re.search('Logged In as ', listing): continue @@ -75,6 +97,68 @@ def discover(c): eval("%s(tw)" % c.supported_sites[s].decoder) tables[tw.name] = tw return tables +# +# The discover_xx functions query the system and report on the poker clients +# currently displayed on the screen. The discover_posix should give you +# some idea how to support other systems. +# +# discover_xx() returns a dict of TableWindow objects--one TableWindow +# object for each poker client table on the screen. +# +# Each TableWindow object must have the following attributes correctly populated: +# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site +# name specified in the config file. +# tw.number = This is the system id number for the client table window in the +# format that the system presents it. +# tw.title = The full title from the window title bar. +# tw.width, tw.height = The width and height of the window in pixels. This is +# the internal width and height, not including the title bar and +# window borders. +# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative +# to the top left of the display screen. This also does not include the +# title bar and window borders. To put it another way, this is the +# screen location of (0, 0) in the working window. + +def win_enum_handler(hwnd, titles): + titles[hwnd] = win32gui.GetWindowText(hwnd) + +def child_enum_handler(hwnd, children): + print hwnd, win32.GetWindowRect(hwnd) + +def discover_nt(c): + """ Poker client table window finder for Windows.""" +# +# I cannot figure out how to get the inside dimensions of the poker table +# windows. So I just assume all borders are 3 thick and all title bars +# are 29 high. No doubt this will be off when used with certain themes. +# + b_width = 3 + tb_height = 29 + titles = {} + tables = {} + win32gui.EnumWindows(win_enum_handler, titles) + for hwnd in titles.keys(): + if re.search('Logged In as', titles[hwnd]) and not re.search('Lobby', titles[hwnd]): + tw = Table_Window() +# tw.site = c.supported_sites[s].site_name + tw.number = hwnd + (x, y, width, height) = win32gui.GetWindowRect(hwnd) + tw.title = titles[hwnd] + tw.width = int( width ) - 2*b_width + tw.height = int( height ) - b_width - tb_height + tw.x = int( x ) + b_width + tw.y = int( y ) + tb_height + eval("%s(tw)" % "pokerstars_decode_table") + tw.site = "PokerStars" + + + tables[tw.name] = tw + return tables + +def discover_mac(c): + """ Poker client table window finder for Macintosh.""" + tables = {} + return tables def pokerstars_decode_table(tw): # extract the table name OR the tournament number and table name from the title @@ -95,18 +179,6 @@ def pokerstars_decode_table(tw): mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball)', tw.title) -#Traceback (most recent call last): -# File "/home/fatray/razz-poker-productio/HUD_main.py", line 41, in process_new_hand -# table_windows = Tables.discover(config) -# File "/home/fatray/razz-poker-productio/Tables.py", line 58, in discover -# eval("%s(tw)" % c.supported_sites[s].decoder) -# File "", line 1, in -# File "/home/fatray/razz-poker-productio/Tables.py", line 80, in pokerstars_decode_table -# tw.game = mo.group(1).lower() -#AttributeError: 'NoneType' object has no attribute 'group' -# -#This problem happens with observed windows!! - tw.game = mo.group(1).lower() tw.game = re.sub('\'', '', tw.game) tw.game = re.sub('h/l', 'hi/lo', tw.game) @@ -132,4 +204,8 @@ if __name__=="__main__": tables = discover(c) for t in tables.keys(): - print tables[t] \ No newline at end of file + print "t = ", t + print tables[t] + + print "press enter to continue" + sys.stdin.readline() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index def44534..878ca119 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -407,7 +407,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha2+, p78") + self.window.set_title("Free Poker DB - version: alpha3, p80") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 1b9f8778..4ff5f621 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -25,6 +25,7 @@ class fpdb_db: self.cursor=None self.MYSQL_INNODB=2 self.PGSQL=3 + self.SQLITE=4 #end def __init__ def connect(self, backend, host, database, user, password): @@ -39,8 +40,10 @@ class fpdb_db: import MySQLdb self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) elif backend==self.PGSQL: - import pgdb - self.db = pgdb.connect(dsn=host+":"+database, user='postgres', password=password) +# import pgdb +# self.db = pgdb.connect(dsn=host+":"+database, user='postgres', password=password) + import psycopg2 + self.db = psycopg2.connect(host = host, user = user, password = password, database = database) else: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) self.cursor=self.db.cursor() @@ -153,6 +156,24 @@ class fpdb_db: def recreate_tables(self): """(Re-)creates the tables of the current DB""" + + if self.backend == 3: +# postgresql + print "recreating tables in postgres db" + schema_file = open('schema.postgres.sql', 'r') + schema = schema_file.read() + schema_file.close() + curse = self.db.cursor() +# curse.executemany(schema, [1, 2]) + for sql in schema.split(';'): + sql = sql.rstrip() + if sql == '': + continue + curse.execute(sql) + self.db.commit() + curse.close() + return + self.drop_tables() self.create_table("""Settings ( diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py old mode 100755 new mode 100644 index f53f6ed1..149b4c7b --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -19,6 +19,7 @@ import sys import MySQLdb +import psycopg2 #import pgdb import math import os @@ -37,7 +38,7 @@ def import_file(server, database, user, password, inputFile): self.settings={'imp-callFpdbHud':False} import_file_dict(self, settings) -def import_file_dict(options, settings, callHud=True): +def import_file_dict(options, settings, callHud=False): last_read_hand=0 if (options.inputFile=="stdin"): inputFile=sys.stdin @@ -45,8 +46,16 @@ def import_file_dict(options, settings, callHud=True): inputFile=open(options.inputFile, "rU") #connect to DB - db = MySQLdb.connect(host = options.server, user = options.user, + if options.settings['db-backend'] == 2: + db = MySQLdb.connect(host = options.server, user = options.user, passwd = options.password, db = options.database) + elif options.settings['db-backend'] == 3: + db = psycopg2.connect(host = options.server, user = options.user, + password = options.password, database = options.database) + elif options.settings['db-backend'] == 4: + pass + else: + pass cursor = db.cursor() if (not options.quiet): @@ -104,11 +113,13 @@ def import_file_dict(options, settings, callHud=True): db.commit() stored+=1 - if settings['imp-callFpdbHud'] and callHud and os.sep=='/': + db.commit() +# if settings['imp-callFpdbHud'] and callHud and os.sep=='/': + if settings['imp-callFpdbHud'] and callHud: #print "call to HUD here. handsId:",handsId #pipe the Hands.id out to the HUD - options.pipe_to_hud.stdin.write("%s\n" % (handsId)) - db.commit() +# options.pipe_to_hud.write("%s" % (handsId) + os.linesep) + options.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) except fpdb_simple.DuplicateError: duplicates+=1 except (ValueError), fe: diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 1c854008..dcfac57b 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -18,6 +18,7 @@ #This file contains simple functions for fpdb import datetime +import re PS=1 FTP=2 @@ -781,8 +782,10 @@ def parseHandStartTime(topline, site): #print "parsehandStartTime, tmp:", tmp pos = tmp.find("-")+2 tmp = tmp[pos:] - #print "year:", tmp[0:4], "month", tmp[5:7], "day", tmp[8:10], "hour", tmp[13:15], "minute", tmp[16:18], "second", tmp[19:21] - result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[13:15]), int(tmp[16:18]), int(tmp[19:21])) + if re.search('\(ET\)', tmp): # the old datetime format at ps + result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[13:15]), int(tmp[16:18]), int(tmp[19:21])) + else: # new format + result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[11:13]), int(tmp[14:16]), int(tmp[17:19])) else: raise FpdbError("invalid site in parseHandStartTime") @@ -1951,7 +1954,7 @@ def storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData): row[31], row[32], row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40], row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50], row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60], - row[1], row[2], row[3], row[4], row[5])) + row[1], row[2], row[3], str(row[4]), row[5])) # else: # print "todo: implement storeHudCache for stud base" #end def storeHudCache diff --git a/pyfpdb/schema.postgres.sql b/pyfpdb/schema.postgres.sql new file mode 100644 index 00000000..74653ba6 --- /dev/null +++ b/pyfpdb/schema.postgres.sql @@ -0,0 +1,217 @@ + +DROP TABLE IF EXISTS Settings CASCADE; +CREATE TABLE Settings (version SMALLINT); + +DROP TABLE IF EXISTS Sites CASCADE; +CREATE TABLE Sites ( + id SERIAL UNIQUE, PRIMARY KEY (id), + name varchar(32), + currency char(3)); + +DROP TABLE IF EXISTS Gametypes CASCADE; +CREATE TABLE Gametypes ( + id SERIAL UNIQUE, PRIMARY KEY (id), + siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), + type char(4), + base char(4), + category varchar(9), + limitType char(2), + hiLo char(1), + smallBlind int, + bigBlind int, + smallBet int, + bigBet int); + +DROP TABLE IF EXISTS Players CASCADE; +CREATE TABLE Players ( + id SERIAL UNIQUE, PRIMARY KEY (id), + name VARCHAR(32), + siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), + comment text, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS Autorates CASCADE; +CREATE TABLE Autorates ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + description varchar(50), + shortDesc char(8), + ratingTime timestamp without time zone, + handCount int); + +DROP TABLE IF EXISTS Hands CASCADE; +CREATE TABLE Hands ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + tableName VARCHAR(20), + siteHandNo BIGINT, + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + handStart timestamp without time zone, + importTime timestamp without time zone, + seats SMALLINT, + maxSeats SMALLINT, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS BoardCards CASCADE; +CREATE TABLE BoardCards ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1)); + +DROP TABLE IF EXISTS TourneyTypes CASCADE; +CREATE TABLE TourneyTypes ( + id SERIAL, PRIMARY KEY (id), + siteId INT, FOREIGN KEY (siteId) REFERENCES Sites(id), + buyin INT, + fee INT, + knockout INT, + rebuyOrAddon BOOLEAN); + +DROP TABLE IF EXISTS Tourneys CASCADE; +CREATE TABLE Tourneys ( + id SERIAL UNIQUE, PRIMARY KEY (id), + tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + siteTourneyNo BIGINT, + entries INT, + prizepool INT, + startTime timestamp without time zone, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS TourneysPlayers CASCADE; +CREATE TABLE TourneysPlayers ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + tourneyId INT, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + payinAmount INT, + rank INT, + winnings INT, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS HandsPlayers CASCADE; +CREATE TABLE HandsPlayers ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + startCash INT, + position CHAR(1), + seatNo SMALLINT, + ante INT, + + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1), + card6Value smallint, + card6Suit char(1), + card7Value smallint, + card7Suit char(1), + + winnings int, + rake int, + comment text, + commentTs timestamp without time zone, + tourneysPlayersId BIGINT, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id)); + +DROP TABLE IF EXISTS HandsActions CASCADE; +CREATE TABLE HandsActions ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handPlayerId BIGINT, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), + street SMALLINT, + actionNo SMALLINT, + action CHAR(5), + amount INT, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS HudCache CASCADE; +CREATE TABLE HudCache ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + activeSeats SMALLINT, + position CHAR(1), + tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + + HDs INT, + street0VPI INT, + street0Aggr INT, + street0_3B4BChance INT, + street0_3B4BDone INT, + street1Seen INT, + street2Seen INT, + street3Seen INT, + street4Seen INT, + sawShowdown INT, + street1Aggr INT, + street2Aggr INT, + street3Aggr INT, + street4Aggr INT, + otherRaisedStreet1 INT, + otherRaisedStreet2 INT, + otherRaisedStreet3 INT, + otherRaisedStreet4 INT, + foldToOtherRaisedStreet1 INT, + foldToOtherRaisedStreet2 INT, + foldToOtherRaisedStreet3 INT, + foldToOtherRaisedStreet4 INT, + wonWhenSeenStreet1 FLOAT, + wonAtSD FLOAT, + + stealAttemptChance INT, + stealAttempted INT, + foldBbToStealChance INT, + foldedBbToSteal INT, + foldSbToStealChance INT, + foldedSbToSteal INT, + + street1CBChance INT, + street1CBDone INT, + street2CBChance INT, + street2CBDone INT, + street3CBChance INT, + street3CBDone INT, + street4CBChance INT, + street4CBDone INT, + + foldToStreet1CBChance INT, + foldToStreet1CBDone INT, + foldToStreet2CBChance INT, + foldToStreet2CBDone INT, + foldToStreet3CBChance INT, + foldToStreet3CBDone INT, + foldToStreet4CBChance INT, + foldToStreet4CBDone INT, + + totalProfit INT, + + street1CheckCallRaiseChance INT, + street1CheckCallRaiseDone INT, + street2CheckCallRaiseChance INT, + street2CheckCallRaiseDone INT, + street3CheckCallRaiseChance INT, + street3CheckCallRaiseDone INT, + street4CheckCallRaiseChance INT, + street4CheckCallRaiseDone INT); + +INSERT INTO Settings VALUES (76); +INSERT INTO Sites ("name", currency) VALUES ('Full Tilt Poker', 'USD'); +INSERT INTO Sites ("name", currency) VALUES ('PokerStars', 'USD'); +INSERT INTO TourneyTypes (buyin, fee, knockout, rebuyOrAddon) VALUES (0, 0, 0, FALSE); From 092f68e259efd58a6cb18551d0fc500609d2cbc8 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 16 Sep 2008 03:14:59 +0100 Subject: [PATCH 081/262] p81 - fixed create release script, updated ebuild --- create-release.sh | 11 +++-- docs/known-bugs-and-planned-features.txt | 7 ++- packaging/fpdb-1.0_alpha3_p80.ebuild | 59 ++++++++++++++++++++++++ pyfpdb/fpdb.py | 2 +- 4 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 packaging/fpdb-1.0_alpha3_p80.ebuild diff --git a/create-release.sh b/create-release.sh index a4fa00d5..5f30ed25 100755 --- a/create-release.sh +++ b/create-release.sh @@ -22,12 +22,15 @@ rm pyfpdb/*.pyc mkdir fpdb-$1 cp -R docs fpdb-$1/ cp -R pyfpdb fpdb-$1/ -rm fpdb-$1/HUD_config.* -cp pyfpdb/HUD_config.xml.example fpdb-$1/ +rm fpdb-$1/pyfpdb/HUD_config.* +cp pyfpdb/HUD_config.xml.example fpdb-$1/pyfpdb/ cp -R regression-test fpdb-$1/ cp -R utils fpdb-$1/ + cd fpdb-$1 -zip -r ../fpdb-1.0_$1.zip * -tar -cf - * | bzip2 > ../fpdb-1.0_$1.tar.bz2 +zip -r ../../fpdb-1.0_$1.zip * +tar -cf - * | bzip2 >> ../../fpdb-1.0_$1.tar.bz2 cd .. rm -r fpdb-$1 + +echo "Please ensure the files are named fpdb-1.0_alpha*_p*.*" diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 6d1c2689..fc961226 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,9 +2,11 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha3 (release 15Sep) +alpha4 (release 22-29Sep) ====== +newsletter&mailing list find correct sf logo link +fix HUD config location and update release script accordingly windows integrated installer update install-in-gentoo on website @@ -18,9 +20,6 @@ export settings[hud-defaultInterval] to conf fill check-/call-raise cache fields printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt - -alpha4 (release 22-29Sep) -====== change to savannah? implement steal and positions in stud anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no diff --git a/packaging/fpdb-1.0_alpha3_p80.ebuild b/packaging/fpdb-1.0_alpha3_p80.ebuild new file mode 100644 index 00000000..0abc47b7 --- /dev/null +++ b/packaging/fpdb-1.0_alpha3_p80.ebuild @@ -0,0 +1,59 @@ +# Copyright 1999-2008 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha2_p68.ebuild,v 1.0 2008/08/31 23:00:00 steffen@sycamoretest.info Exp $ + +NEED_PYTHON=2.3 + +#inherit distutils + +MY_P="fpdb-${PV}" +DESCRIPTION="A database program to track your online poker games" +HOMEPAGE="https://sourceforge.net/projects/fpdb/" +#SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2" +SRC_URI="mirror://sourceforge/fpdb/fpdb-1.0_alpha3-p80.tar.bz2" + +LICENSE="AGPL-3" +SLOT="0" +KEYWORDS="~amd64 ~x86" +#note: this should work on other architectures too, please send me your experiences +IUSE="" + +RDEPEND="virtual/mysql + dev-python/mysql-python + >=x11-libs/gtk+-2.10 + dev-python/pygtk" +DEPEND="${RDEPEND}" + +src_install() { + DIRINST="${D}usr/share/games/fpdb/" + mkdir -p "${DIRINST}" + cp -R * "${DIRINST}" || die + + DIRBIN="${D}usr/games/bin/" + mkdir -p "${DIRBIN}" + #echo "pathes" + #echo "${DIRINST}pyfpdb/fpdb.py" + #echo "${DIRBIN}fpdb.py" + #echo + echo "cd /usr/share/games/fpdb/pyfpdb/ && python fpdb.py" > "${DIRBIN}fpdb" || die + chmod 755 "${DIRBIN}fpdb" || die +} + +#src_test() { +#} + +pkg_postinst() { + elog "Fpdb has been installed and can be called by executing /usr/games/bin/fpdb" + elog "You need to perform a couple more steps manually." + elog "Please also make sure you followed instructions from previous emerges, in particular make sure you configured mysql and set a root pw for it" + elog "Now run this command to connect to MySQL: mysql --user=root --password=yourPassword" + elog "In the mysql command line interface you need to type these two lines (make sure you get the ; at the end)" + elog "In the second line replace \"newPassword\" with a password of your choice" + elog "CREATE DATABASE fpdb;" + elog "GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;" + elog "Finally copy the default config file from ${DIRINST}docs/default.conf to ~/.fpdb/ for every user that is to use fpdb." + elog "You will need to edit the default.conf, in particular you need to replace the password with what you entered in the \"GRANT ALL...\"" + elog "Finally run the GUI and click the menu database -> recreate tables" + elog "That's it! See our webpage at http://fpdb.sourceforge.net for more documentation" + elog " " +} diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 878ca119..140f3edb 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -407,7 +407,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha3, p80") + self.window.set_title("Free Poker DB - version: alpha3+, p81") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From cd212af029cc2063ed655c9c74a08c4e246d73d6 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 16 Sep 2008 22:19:50 +0100 Subject: [PATCH 082/262] p82 - made import of SQL interface libraries into try-except loop to facilitate choosing between mysql and pgsql --- docs/known-bugs-and-planned-features.txt | 22 +++++++++++----------- pyfpdb/Database.py | 8 +++++--- pyfpdb/GuiTableViewer.py | 18 +++++++++++++++++- pyfpdb/fpdb_db.py | 3 --- pyfpdb/fpdb_import.py | 20 +++++++++++++++++--- 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index fc961226..830c4297 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -5,6 +5,8 @@ Please also see db-todo.txt alpha4 (release 22-29Sep) ====== newsletter&mailing list +update requirements to include new pgsql interface lib and rename to dependencies.txt +ebuild: support pgsql find correct sf logo link fix HUD config location and update release script accordingly @@ -13,7 +15,7 @@ update install-in-gentoo on website update ebuild and ubuntu guide for HUD_config.xml store raw hand in db and write reimport function using the raw hand field -ftp: read maxSeats +ftp: read maxSeats rather than making an assumption make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt export settings[hud-defaultInterval] to conf @@ -23,39 +25,38 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring change to savannah? implement steal and positions in stud anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no -Everything that didn't make it into alpha3 Import draw (maybe without HudCache for a start) table with data for graphs for SD/F, W$wSF, W$@SD separate db table design version and last bugfix in importer -change tabledesign VALIGN +change tabledesign VALIGN and add link to webpage finish updating filelist finish todos in git instructions debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html howto remote DB move all user docs to webpage +contributor list on webpage +finish bringing back tourney +No river stats for stud games +hole/board cards are not correctly stored in the db for stud games +HORSE (and presumably other mixed games) hand history files not handled correctly +Some MTTs won't import (rebuys??) +Many STTs won't import before beta =========== No Full Tilt support in HUD -No river stats for stud games -hole/board cards are not correctly stored in the db for stud games -HORSE (and presumably other mixed games) hand history files not handled correctly HUD stat windows are too big on Windows HUD task bar entries on Windows won't go away -Some MTTs won't import (rebuys??) -Many STTs won't import MTT/STT not tested in HUD HUD stats not aggregated Player names with non-Latin chars throw warnings in HUD HUD doesn't start when fpdb is started from the Windows "Start Menu" Exiting HUD on Windows doesn't properly clean up stat windows -finish bringing back tourney ebuild: USE gtk, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, git-ebuild, get it into sunrise make hud display W$SD etc as fraction. add dedicated update page update status or make a support matrix table for website -fix up bg colours in tv move version into seperate file for fpdb gui and db SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug create little test script for people to run to verify successful installation of pydeps @@ -63,7 +64,6 @@ split hud data generation into separate for loops and make it more efficient fix bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin - might as well add all-in recognition for this make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) make it work with postgres -gentoo ebuild: USE postgresql expand instructions for profile file maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index d634e134..8e9f8eab 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -32,12 +32,14 @@ import sys import Configuration import SQL +try: # pgdb database module for posgres via DB-API -import psycopg2 + import psycopg2 # pgdb uses pyformat. is that fixed or an option? - # mysql bindings -import MySQLdb + import MySQLdb +except: + pass class Database: def __init__(self, c, db_name, game): diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 866b042d..3714138e 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -20,7 +20,23 @@ import pygtk pygtk.require('2.0') import gtk import os -import MySQLdb + +try: + import MySQLdb +except: + diaSQLLibMissing = gtk.Dialog(title="Fatal Error - SQL interface library missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) + + label = gtk.Label("Please note that the table viewer only works with MySQL, if you use PostgreSQL this error is expected.") + diaSQLLibMissing.vbox.add(label) + label.show() + + label = gtk.Label("Since the HUD now runs on all supported plattforms I do not see any point in table viewer anymore, if you disagree please send a message to steffen@sycamoretest.info") + diaSQLLibMissing.vbox.add(label) + label.show() + + response = diaSQLLibMissing.run() + #sys.exit(1) + import fpdb_import import fpdb_db diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 4ff5f621..1468d8d2 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -35,13 +35,10 @@ class fpdb_db: self.database=database self.user=user self.password=password - #print "fpdb_db.connect, database:",database if backend==self.MYSQL_INNODB: import MySQLdb self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) elif backend==self.PGSQL: -# import pgdb -# self.db = pgdb.connect(dsn=host+":"+database, user='postgres', password=password) import psycopg2 self.db = psycopg2.connect(host = host, user = user, password = password, database = database) else: diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 149b4c7b..0689009e 100644 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -18,9 +18,19 @@ #see status.txt for site/games support info import sys -import MySQLdb -import psycopg2 -#import pgdb + +try: + import MySQLdb + mysqlLibFound=True +except: + pass + +try: + import psycopg2 + pgsqlLibFound=True +except: + pass + import math import os import datetime @@ -47,9 +57,13 @@ def import_file_dict(options, settings, callHud=False): #connect to DB if options.settings['db-backend'] == 2: + if not mysqlLibFound: + raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") db = MySQLdb.connect(host = options.server, user = options.user, passwd = options.password, db = options.database) elif options.settings['db-backend'] == 3: + if not pgsqlLibFound: + raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") db = psycopg2.connect(host = options.server, user = options.user, password = options.password, database = options.database) elif options.settings['db-backend'] == 4: From 7dcf2d9bbccd8b21e159485b23712d50c2af8c0d Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 17 Sep 2008 01:21:55 +0100 Subject: [PATCH 083/262] p83 - corrected silly mistake i made in p82 --- pyfpdb/Database.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 8e9f8eab..6006a246 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -35,8 +35,10 @@ import SQL try: # pgdb database module for posgres via DB-API import psycopg2 -# pgdb uses pyformat. is that fixed or an option? +except: + pass # mysql bindings +try: import MySQLdb except: pass From 54a229b26fe569e4e73f7ee0902f013793c6c9aa Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 17 Sep 2008 01:43:04 +0100 Subject: [PATCH 084/262] p83 - corrected silly mistake i made in p82 --- pyfpdb/HUD_config.xml.example | 95 +++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index ca650270..649493fa 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -2,7 +2,7 @@ - + @@ -22,16 +22,16 @@ - - - + + + - - + + @@ -48,20 +48,83 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - From 656356d2996a4ec6aca9b75f759877c7942b95a6 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 17 Sep 2008 01:44:40 +0100 Subject: [PATCH 085/262] p84 - included updated HUD_config.xml.example (was in the last commit actually) --- pyfpdb/fpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 140f3edb..da426dfb 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -407,7 +407,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha3+, p81") + self.window.set_title("Free Poker DB - version: alpha3+, p84") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 1a008b1ac2542ce679e202275409a87c819f0367 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 17 Sep 2008 02:33:46 +0100 Subject: [PATCH 086/262] p85 - improved PS timestamp parsing using regex from Carl Gherardi --- pyfpdb/fpdb_simple.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index dcfac57b..30fe52c1 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -782,10 +782,12 @@ def parseHandStartTime(topline, site): #print "parsehandStartTime, tmp:", tmp pos = tmp.find("-")+2 tmp = tmp[pos:] - if re.search('\(ET\)', tmp): # the old datetime format at ps - result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[13:15]), int(tmp[16:18]), int(tmp[19:21])) - else: # new format - result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[11:13]), int(tmp[14:16]), int(tmp[17:19])) + #Need to match either + # 2008/09/07 06:23:14 ET or + # 2008/08/17 - 01:14:43 (ET) + m = re.match('(?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\- ]+(?P
    [0-9]{2}):(?P[0-9]{2}):(?P[0-9]{2})',tmp) + #print "year:", int(m.group('YEAR')), "month", int(m.group('MON')), "day", int(m.group('DAY')), "hour", int(m.group('HR')), "minute", int(m.group('MIN')), "second", int(m.group('SEC')) + result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC'))) else: raise FpdbError("invalid site in parseHandStartTime") From 672d2d70afdb4aed3673d7b2c99d11f4fd3c3a05 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 18 Sep 2008 01:23:38 +0100 Subject: [PATCH 087/262] p86 - ftp: read maxSeats rather than making an assumption. included new ebuild this time but obviously untried (can only try it after making the file release..). removed old ebuilds. --- docs/known-bugs-and-planned-features.txt | 6 +- packaging/fpdb-1.0_alpha2_p68.ebuild | 59 ------------------- ..._p80.ebuild => fpdb-1.0_alpha4_p86.ebuild} | 4 +- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_parse_logic.py | 4 +- pyfpdb/fpdb_simple.py | 17 +++++- 6 files changed, 22 insertions(+), 70 deletions(-) delete mode 100644 packaging/fpdb-1.0_alpha2_p68.ebuild rename packaging/{fpdb-1.0_alpha3_p80.ebuild => fpdb-1.0_alpha4_p86.ebuild} (95%) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 830c4297..86453039 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,12 +2,11 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha4 (release 22-29Sep) +alpha4 (release 25Sep-2Oct) ====== newsletter&mailing list update requirements to include new pgsql interface lib and rename to dependencies.txt ebuild: support pgsql -find correct sf logo link fix HUD config location and update release script accordingly windows integrated installer @@ -15,7 +14,6 @@ update install-in-gentoo on website update ebuild and ubuntu guide for HUD_config.xml store raw hand in db and write reimport function using the raw hand field -ftp: read maxSeats rather than making an assumption make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt export settings[hud-defaultInterval] to conf @@ -44,6 +42,8 @@ Many STTs won't import before beta =========== +make linux use /etc/fpdb for config first, then ~/.fpdb. +FTP file with only one partial hand causes error No Full Tilt support in HUD HUD stat windows are too big on Windows HUD task bar entries on Windows won't go away diff --git a/packaging/fpdb-1.0_alpha2_p68.ebuild b/packaging/fpdb-1.0_alpha2_p68.ebuild deleted file mode 100644 index 30304eeb..00000000 --- a/packaging/fpdb-1.0_alpha2_p68.ebuild +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 1999-2008 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha2_p68.ebuild,v 1.0 2008/08/31 23:00:00 steffen@sycamoretest.info Exp $ - -NEED_PYTHON=2.3 - -#inherit distutils - -MY_P="fpdb-${PV}" -DESCRIPTION="A database program to track your online poker games" -HOMEPAGE="https://sourceforge.net/projects/fpdb/" -#SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2" -SRC_URI="mirror://sourceforge/fpdb/fpdb-alpha2-p68.tar.bz2" - -LICENSE="AGPL-3" -SLOT="0" -KEYWORDS="~amd64 ~x86" -#note: this should work on other architectures too, please send me your experiences -IUSE="" - -RDEPEND="virtual/mysql - dev-python/mysql-python - >=x11-libs/gtk+-2.10 - dev-python/pygtk" -DEPEND="${RDEPEND}" - -src_install() { - DIRINST="${D}usr/share/games/fpdb/" - mkdir -p "${DIRINST}" - cp -R * "${DIRINST}" || die - - DIRBIN="${D}usr/games/bin/" - mkdir -p "${DIRBIN}" - echo "pathes" - echo "${DIRINST}pyfpdb/fpdb.py" - echo "${DIRBIN}fpdb.py" - echo - echo "cd /usr/share/games/fpdb/pyfpdb/ && python fpdb.py" > "${DIRBIN}fpdb" || die - chmod 755 "${DIRBIN}fpdb" || die -} - -#src_test() { -#} - -pkg_postinst() { - elog "Fpdb has been installed and can be called by executing /usr/games/bin/fpdb.py" - elog "You need to perform a couple more steps manually." - elog "Please also make sure you followed instructions from previous emerges, in particular make sure you configured mysql and set a root pw for it" - elog "Now run this command to connect to MySQL: mysql --user=root --password=yourPassword" - elog "In the mysql command line interface you need to type these two lines (make sure you get the ; at the end)" - elog "In the second line replace \"newPassword\" with a password of your choice" - elog "CREATE DATABASE fpdb;" - elog "GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;" - elog "Finally copy the default config file from ${DIRINST}docs/default.conf to ~/.fpdb/ for every user that is to use fpdb." - elog "You will need to edit the default.conf, in particular you need to replace the password with what you entered in the \"GRANT ALL...\"" - elog "Finally run the GUI and click the menu database -> recreate tables" - elog "That's it! See our webpage at http://fpdb.sourceforge.net for more documentation" - elog " " -} diff --git a/packaging/fpdb-1.0_alpha3_p80.ebuild b/packaging/fpdb-1.0_alpha4_p86.ebuild similarity index 95% rename from packaging/fpdb-1.0_alpha3_p80.ebuild rename to packaging/fpdb-1.0_alpha4_p86.ebuild index 0abc47b7..839f578e 100644 --- a/packaging/fpdb-1.0_alpha3_p80.ebuild +++ b/packaging/fpdb-1.0_alpha4_p86.ebuild @@ -9,8 +9,8 @@ NEED_PYTHON=2.3 MY_P="fpdb-${PV}" DESCRIPTION="A database program to track your online poker games" HOMEPAGE="https://sourceforge.net/projects/fpdb/" -#SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2" -SRC_URI="mirror://sourceforge/fpdb/fpdb-1.0_alpha3-p80.tar.bz2" +SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2" +#SRC_URI="mirror://sourceforge/fpdb/fpdb-1.0_alpha3-p80.tar.bz2" LICENSE="AGPL-3" SLOT="0" diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index da426dfb..94e1165e 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -407,7 +407,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha3+, p84") + self.window.set_title("Free Poker DB - version: alpha4, p86") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 6696f64e..a79b9de4 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -93,11 +93,11 @@ def mainParser(db, cursor, site, category, hand): elif (lineTypes[i]=="ante"): fpdb_simple.parseAnteLine(hand[i], site, names, antes) elif (lineTypes[i]=="table"): - tableResult=fpdb_simple.parseTableLine(site, hand[i]) + tableResult=fpdb_simple.parseTableLine(site, base, hand[i]) else: raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i]) if site=="ftp": - tableResult=fpdb_simple.parseTableLine(site, hand[0]) + tableResult=fpdb_simple.parseTableLine(site, base, hand[0]) maxSeats=tableResult['maxSeats'] tableName=tableResult['tableName'] diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 30fe52c1..aed7cf1d 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -884,7 +884,7 @@ def parseSiteHandNo(topline): return topline[pos1:pos2] #end def parseSiteHandNo -def parseTableLine(site, line): +def parseTableLine(site, base, line): """returns a dictionary with maxSeats and tableName""" if site=="ps": pos1=line.find('\'')+1 @@ -897,8 +897,19 @@ def parseTableLine(site, line): elif site=="ftp": pos1=line.find("Table ")+6 pos2=line.find("-")-1 - #print "table:",line[pos1:pos2]+"end" - return {'maxSeats':9, 'tableName':line[pos1:pos2]} + if base=="hold": + maxSeats=9 + elif base=="stud": + maxSeats=8 + + if line.find("6 max")!=-1: + maxSeats=6 + elif line.find("4 max")!=-1: + maxSeats=4 + elif line.find("heads up")!=-1: + maxSeats=2 + + return {'maxSeats':maxSeats, 'tableName':line[pos1:pos2]} else: raise FpdbError("invalid site ID") #end def parseTableLine From 009161d55b0a1b0c5b20fc714fe9167b5b9bc6f7 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sat, 20 Sep 2008 05:56:16 +0100 Subject: [PATCH 088/262] p87 - added profit graph kindly sent by Carl Gherardi, it's got fixed player id and is using the wrong table fields right now, will fix that tomorrow or so. note that this adds a new dependency, matplotlib, but I put it into a try except loop to avoid load failure added subfolder gentoo to packaging folder and added it to release script --- create-release.sh | 1 + docs/known-bugs-and-planned-features.txt | 4 +- .../{ => gentoo}/fpdb-1.0_alpha4_p86.ebuild | 0 pyfpdb/GuiGraphViewer.py | 93 +++++++++++++++++++ pyfpdb/fpdb.py | 14 ++- 5 files changed, 108 insertions(+), 4 deletions(-) rename packaging/{ => gentoo}/fpdb-1.0_alpha4_p86.ebuild (100%) create mode 100644 pyfpdb/GuiGraphViewer.py diff --git a/create-release.sh b/create-release.sh index 5f30ed25..975cffd3 100755 --- a/create-release.sh +++ b/create-release.sh @@ -21,6 +21,7 @@ rm pyfpdb/*.pyc mkdir fpdb-$1 cp -R docs fpdb-$1/ +cp -R packaging fpdb-$1/ cp -R pyfpdb fpdb-$1/ rm fpdb-$1/pyfpdb/HUD_config.* cp pyfpdb/HUD_config.xml.example fpdb-$1/pyfpdb/ diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 86453039..5e768b49 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,11 +1,11 @@ -todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== +graph: fixed player id and using the wrong table fields, update dependencies.txt newsletter&mailing list -update requirements to include new pgsql interface lib and rename to dependencies.txt +update requirements to include new pgsql interface lib ebuild: support pgsql fix HUD config location and update release script accordingly diff --git a/packaging/fpdb-1.0_alpha4_p86.ebuild b/packaging/gentoo/fpdb-1.0_alpha4_p86.ebuild similarity index 100% rename from packaging/fpdb-1.0_alpha4_p86.ebuild rename to packaging/gentoo/fpdb-1.0_alpha4_p86.ebuild diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py new file mode 100644 index 00000000..ed673c75 --- /dev/null +++ b/pyfpdb/GuiGraphViewer.py @@ -0,0 +1,93 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import threading +import pygtk +pygtk.require('2.0') +import gtk +import os + +try: + from matplotlib.figure import Figure + from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas + from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar + from numpy import arange, sin, pi +except: + print "Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib." + +try: + import MySQLdb +except: + diaSQLLibMissing = gtk.Dialog(title="Fatal Error - SQL interface library missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) + + label = gtk.Label("Please note that the table viewer only works with MySQL, if you use PostgreSQL this error is expected.") + diaSQLLibMissing.vbox.add(label) + label.show() + + label = gtk.Label("Since the HUD now runs on all supported plattforms I do not see any point in table viewer anymore, if you disagree please send a message to steffen@sycamoretest.info") + diaSQLLibMissing.vbox.add(label) + label.show() + + response = diaSQLLibMissing.run() + #sys.exit(1) + +import fpdb_import +import fpdb_db + +class GuiGraphViewer (threading.Thread): + def get_vbox(self): + """returns the vbox of this thread""" + return self.main_vbox + #end def get_vbox + + def __init__(self, db, settings, debug=True): + """Constructor for table_viewer""" + self.debug=debug + #print "start of table_viewer constructor" + self.db=db + self.cursor=db.cursor + self.settings=settings + + self.main_vbox = gtk.VBox(False, 0) + self.main_vbox.show() + + self.fig = Figure(figsize=(5,4), dpi=100) + self.ax = self.fig.add_subplot(111) +# x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +# y = [2.7, 2.8, 31.4, 38.1, 58.0, 76.2, 100.5, 130.0, 149.3, 180.0] + + self.db.reconnect() + self.cursor=self.db.cursor + + self.db.cursor.execute("SELECT handId, winnings FROM HandsPlayers WHERE playerId=1 ORDER BY handId") + + self.results = self.db.cursor.fetchall() + +# x=map(lambda x:float(x[0]),self.results) + y=map(lambda x:float(x[1]),self.results) + line = range(len(y)) + + for i in range(len(y)): + line[i] = y[i] + line[i-1] + + self.ax.plot(line,) + + self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea + self.main_vbox.pack_start(self.canvas) + self.canvas.show() + + #end of table_viewer.__init__ diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 94e1165e..307c074d 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -27,6 +27,7 @@ import fpdb_simple import GuiBulkImport import GuiTableViewer import GuiAutoImport +import GuiGraphViewer class fpdb: def tab_clicked(self, widget, tab_name): @@ -399,6 +400,15 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.add_and_display_tab(tv_tab, "Table Viewer") #end def tab_table_viewer + def tabGraphViewer(self, widget, data): + """opens a graph viewer tab""" + #print "start of tabGraphViewer" + new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings) + self.threads.append(new_gv_thread) + gv_tab=new_gv_thread.get_vbox() + self.add_and_display_tab(gv_tab, "Graphs") + #end def tabGraphViewer + def __init__(self): self.threads=[] self.db=None @@ -407,7 +417,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha4, p86") + self.window.set_title("Free Poker DB - version: alpha4+, p87 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) @@ -425,7 +435,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") ("/Import/Auto _Rating (todo)", "R", self.not_implemented, 0, None ), ("/_Viewers", None, None, 0, "" ), ("/_Viewers/_Auto Import and HUD", "A", self.tab_auto_import, 0, None ), - ("/Viewers/_Graphs (todo)", None, self.not_implemented, 0, None ), + ("/Viewers/_Graphs", None, self.tabGraphViewer, 0, None ), ("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ), ("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), From 8117193b4f3d19eb52849e198a5f73e56c662c37 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 21 Sep 2008 14:24:43 +0100 Subject: [PATCH 089/262] p88 - graph viewer now takes player name from GUI rather than hardcoded player id --- docs/known-bugs-and-planned-features.txt | 3 +- pyfpdb/GuiGraphViewer.py | 97 +++++++++++++----------- pyfpdb/fpdb.py | 4 +- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 5e768b49..3ee7d983 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,7 +3,8 @@ Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== -graph: fixed player id and using the wrong table fields, update dependencies.txt +graph: using the wrong table fields, update dependencies.txt, select site from drop down +check we're reading mucked cards from PS newsletter&mailing list update requirements to include new pgsql interface lib ebuild: support pgsql diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index ed673c75..bf46c17d 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -29,31 +29,44 @@ try: except: print "Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib." -try: - import MySQLdb -except: - diaSQLLibMissing = gtk.Dialog(title="Fatal Error - SQL interface library missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) - - label = gtk.Label("Please note that the table viewer only works with MySQL, if you use PostgreSQL this error is expected.") - diaSQLLibMissing.vbox.add(label) - label.show() - - label = gtk.Label("Since the HUD now runs on all supported plattforms I do not see any point in table viewer anymore, if you disagree please send a message to steffen@sycamoretest.info") - diaSQLLibMissing.vbox.add(label) - label.show() - - response = diaSQLLibMissing.run() - #sys.exit(1) - import fpdb_import import fpdb_db class GuiGraphViewer (threading.Thread): def get_vbox(self): """returns the vbox of this thread""" - return self.main_vbox + return self.mainVBox #end def get_vbox + def showClicked(self, widget, data): + name=self.nameTBuffer.get_text(self.nameTBuffer.get_start_iter(), self.nameTBuffer.get_end_iter()) + + self.fig = Figure(figsize=(5,4), dpi=100) + self.ax = self.fig.add_subplot(111) +# x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +# y = [2.7, 2.8, 31.4, 38.1, 58.0, 76.2, 100.5, 130.0, 149.3, 180.0] + + #self.db.reconnect() + + self.cursor.execute("SELECT handId, winnings FROM HandsPlayers INNER JOIN Players ON HandsPlayers.playerId = Players.id WHERE Players.name = %s ORDER BY handId", (name, )) + + self.results = self.db.cursor.fetchall() + +# x=map(lambda x:float(x[0]),self.results) + y=map(lambda x:float(x[1]),self.results) + line = range(len(y)) + + for i in range(len(y)): + line[i] = y[i] + line[i-1] + + self.ax.plot(line,) + + self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea + self.mainVBox.pack_start(self.canvas) + self.canvas.show() + + + def __init__(self, db, settings, debug=True): """Constructor for table_viewer""" self.debug=debug @@ -62,32 +75,26 @@ class GuiGraphViewer (threading.Thread): self.cursor=db.cursor self.settings=settings - self.main_vbox = gtk.VBox(False, 0) - self.main_vbox.show() + self.mainVBox = gtk.VBox(False, 0) + self.mainVBox.show() - self.fig = Figure(figsize=(5,4), dpi=100) - self.ax = self.fig.add_subplot(111) -# x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -# y = [2.7, 2.8, 31.4, 38.1, 58.0, 76.2, 100.5, 130.0, 149.3, 180.0] - - self.db.reconnect() - self.cursor=self.db.cursor - - self.db.cursor.execute("SELECT handId, winnings FROM HandsPlayers WHERE playerId=1 ORDER BY handId") - - self.results = self.db.cursor.fetchall() - -# x=map(lambda x:float(x[0]),self.results) - y=map(lambda x:float(x[1]),self.results) - line = range(len(y)) - - for i in range(len(y)): - line[i] = y[i] + line[i-1] - - self.ax.plot(line,) - - self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea - self.main_vbox.pack_start(self.canvas) - self.canvas.show() - - #end of table_viewer.__init__ + self.settingsHBox = gtk.HBox(False, 0) + self.mainVBox.pack_start(self.settingsHBox, False, True, 0) + self.settingsHBox.show() + + self.nameLabel = gtk.Label("Name of the player to be graphed:") + self.settingsHBox.pack_start(self.nameLabel) + self.nameLabel.show() + + self.nameTBuffer=gtk.TextBuffer() + self.nameTBuffer.set_text("name") + self.nameTView=gtk.TextView(self.nameTBuffer) + self.settingsHBox.pack_start(self.nameTView) + self.nameTView.show() + + self.showButton=gtk.Button("Show/Refresh") + self.showButton.connect("clicked", self.showClicked, "show clicked") + self.settingsHBox.add(self.showButton) + self.showButton.show() + + #end of GuiGraphViewer.__init__ diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 307c074d..42c653a5 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -417,7 +417,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha4+, p87 or higher") + self.window.set_title("Free Poker DB - version: alpha4+, p88 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) @@ -435,7 +435,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") ("/Import/Auto _Rating (todo)", "R", self.not_implemented, 0, None ), ("/_Viewers", None, None, 0, "" ), ("/_Viewers/_Auto Import and HUD", "A", self.tab_auto_import, 0, None ), - ("/Viewers/_Graphs", None, self.tabGraphViewer, 0, None ), + ("/Viewers/_Graphs", "G", self.tabGraphViewer, 0, None ), ("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ), ("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), From 7ed7db3791d5b61ae68c6357478aa5efd60d0ee2 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 21 Sep 2008 22:21:09 +0100 Subject: [PATCH 090/262] p89 - graph now sorts by siteHandNo rather than handId and takes into account expenditure. --- docs/known-bugs-and-planned-features.txt | 3 ++- pyfpdb/GuiGraphViewer.py | 32 ++++++++++++++++++------ pyfpdb/fpdb.py | 2 +- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 3ee7d983..4ccb14f8 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,7 +3,8 @@ Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== -graph: using the wrong table fields, update dependencies.txt, select site from drop down +graph: update dependencies.txt, select site from drop down +print a "press any key" thing after we print the traceback. That way it is easy for them to see the error message. check we're reading mucked cards from PS newsletter&mailing list update requirements to include new pgsql interface lib diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index bf46c17d..5880fb70 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -46,14 +46,32 @@ class GuiGraphViewer (threading.Thread): # x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # y = [2.7, 2.8, 31.4, 38.1, 58.0, 76.2, 100.5, 130.0, 149.3, 180.0] - #self.db.reconnect() + self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers + INNER JOIN Players ON HandsPlayers.playerId = Players.id + INNER JOIN Hands ON Hands.id = HandsPlayers.handId + WHERE Players.name = %s ORDER BY siteHandNo""", (name, )) + winnings = self.db.cursor.fetchall() + + + + + #print "winnings:",winnings + #print "" + #print "spent:",spent + + profit=range(len(winnings)) + for i in profit: + self.cursor.execute("""SELECT SUM(amount) FROM HandsActions + INNER JOIN HandsPlayers ON HandsActions.handPlayerId = HandsPlayers.id + INNER JOIN Players ON HandsPlayers.playerId = Players.id + WHERE Players.name = %s AND HandsPlayers.handId = %s""", (name, winnings[i][0])) + spent = self.db.cursor.fetchone() + + profit[i]=(i, winnings[i][1]-spent[0]) + - self.cursor.execute("SELECT handId, winnings FROM HandsPlayers INNER JOIN Players ON HandsPlayers.playerId = Players.id WHERE Players.name = %s ORDER BY handId", (name, )) - - self.results = self.db.cursor.fetchall() - -# x=map(lambda x:float(x[0]),self.results) - y=map(lambda x:float(x[1]),self.results) +# x=map(lambda x:float(x[0]), results) + y=map(lambda x:float(x[1]), profit) line = range(len(y)) for i in range(len(y)): diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 42c653a5..dce926fa 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -417,7 +417,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha4+, p88 or higher") + self.window.set_title("Free Poker DB - version: alpha4+, p89 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 8c6cecb8f72c6ffc67118db1ba5b43d71fb332f3 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 21 Sep 2008 23:38:22 +0100 Subject: [PATCH 091/262] p90 - release script renames HUD_config.xml.example so user doesnt have to fixed a couple of stupid errors where i used the wrong siteID somehow graph now lets you pick beteen PS and FTP --- create-release.sh | 2 +- docs/known-bugs-and-planned-features.txt | 3 ++- pyfpdb/GuiGraphViewer.py | 30 ++++++++++++++++++++---- pyfpdb/fpdb_parse_logic.py | 1 + pyfpdb/fpdb_simple.py | 15 ++++++++---- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/create-release.sh b/create-release.sh index 975cffd3..bdc4333a 100755 --- a/create-release.sh +++ b/create-release.sh @@ -24,7 +24,7 @@ cp -R docs fpdb-$1/ cp -R packaging fpdb-$1/ cp -R pyfpdb fpdb-$1/ rm fpdb-$1/pyfpdb/HUD_config.* -cp pyfpdb/HUD_config.xml.example fpdb-$1/pyfpdb/ +cp pyfpdb/HUD_config.xml.example fpdb-$1/pyfpdb/HUD_config.xml cp -R regression-test fpdb-$1/ cp -R utils fpdb-$1/ diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 4ccb14f8..f7bd5e19 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,9 @@ Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== -graph: update dependencies.txt, select site from drop down +graph: update dependencies.txt, select site from drop down, doesnt remove old graph on refresh print a "press any key" thing after we print the traceback. That way it is easy for them to see the error message. +reading small blind wrong for PS 25/50ct check we're reading mucked cards from PS newsletter&mailing list update requirements to include new pgsql interface lib diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 5880fb70..ccb8638f 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -41,15 +41,27 @@ class GuiGraphViewer (threading.Thread): def showClicked(self, widget, data): name=self.nameTBuffer.get_text(self.nameTBuffer.get_start_iter(), self.nameTBuffer.get_end_iter()) + site=self.siteTBuffer.get_text(self.siteTBuffer.get_start_iter(), self.siteTBuffer.get_end_iter()) + + if site=="PS": + site=1 + elif site=="FTP": + site=2 + else: + print "invalid text in site selection in graph, defaulting to PS" + site=1 + #print "site:", site + self.fig = Figure(figsize=(5,4), dpi=100) self.ax = self.fig.add_subplot(111) # x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # y = [2.7, 2.8, 31.4, 38.1, 58.0, 76.2, 100.5, 130.0, 149.3, 180.0] - self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers + self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers INNER JOIN Players ON HandsPlayers.playerId = Players.id INNER JOIN Hands ON Hands.id = HandsPlayers.handId - WHERE Players.name = %s ORDER BY siteHandNo""", (name, )) + WHERE Players.name = %s AND Players.siteId = %s + ORDER BY siteHandNo""", (name, site)) winnings = self.db.cursor.fetchall() @@ -64,7 +76,7 @@ class GuiGraphViewer (threading.Thread): self.cursor.execute("""SELECT SUM(amount) FROM HandsActions INNER JOIN HandsPlayers ON HandsActions.handPlayerId = HandsPlayers.id INNER JOIN Players ON HandsPlayers.playerId = Players.id - WHERE Players.name = %s AND HandsPlayers.handId = %s""", (name, winnings[i][0])) + WHERE Players.name = %s AND HandsPlayers.handId = %s AND Players.siteId = %s""", (name, winnings[i][0], site)) spent = self.db.cursor.fetchone() profit[i]=(i, winnings[i][1]-spent[0]) @@ -110,9 +122,19 @@ class GuiGraphViewer (threading.Thread): self.settingsHBox.pack_start(self.nameTView) self.nameTView.show() + self.siteLabel = gtk.Label("Site (PS or FTP):") + self.settingsHBox.pack_start(self.siteLabel) + self.siteLabel.show() + + self.siteTBuffer=gtk.TextBuffer() + self.siteTBuffer.set_text("PS") + self.siteTView=gtk.TextView(self.siteTBuffer) + self.settingsHBox.pack_start(self.siteTView) + self.siteTView.show() + self.showButton=gtk.Button("Show/Refresh") self.showButton.connect("clicked", self.showClicked, "show clicked") - self.settingsHBox.add(self.showButton) + self.settingsHBox.pack_start(self.showButton) self.showButton.show() #end of GuiGraphViewer.__init__ diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index a79b9de4..e7ec42e9 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -36,6 +36,7 @@ def mainParser(db, cursor, site, category, hand): siteHandNo=fpdb_simple.parseSiteHandNo(hand[0]) handStartTime=fpdb_simple.parseHandStartTime(hand[0], site) siteID=fpdb_simple.recogniseSiteID(cursor, site) + #print "parse logic, siteID:",siteID,"site:",site isTourney=fpdb_simple.isTourney(hand[0]) gametypeID=fpdb_simple.recogniseGametypeID(cursor, hand[0], siteID, category, isTourney) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index aed7cf1d..80a75f64 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -983,14 +983,15 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th pos1=pos2+2 if isTourney: pos1-=1 - if (site_id==1): #ftp + if (site_id==2): #ftp pos2=topline.find(" ", pos1) - elif (site_id==2): #ps + elif (site_id==1): #ps pos2=topline.find(")") if pos2<=pos1: pos2=topline.find(")", pos1) - + + #pos2-=1 if isTourney: big_bet=int(topline[pos1:pos2]) @@ -1121,9 +1122,13 @@ def recogniseSite(line): #returns the ID of the given site def recogniseSiteID(cursor, site): if (site=="ftp"): - cursor.execute("SELECT id FROM Sites WHERE name = ('Full Tilt Poker')") + return 2 + #cursor.execute("SELECT id FROM Sites WHERE name = ('Full Tilt Poker')") elif (site=="ps"): - cursor.execute("SELECT id FROM Sites WHERE name = ('PokerStars')") + return 1 + #cursor.execute("SELECT id FROM Sites WHERE name = ('PokerStars')") + else: + raise FpdbError("invalid site in recogniseSiteID: "+site) return cursor.fetchall()[0][0] #end def recogniseSiteID From 7ed7a2b88cff7a041cef97996f82ed267ee77ef9 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 22 Sep 2008 03:31:33 +0100 Subject: [PATCH 092/262] p91 - patch to HUD table detection from carl --- docs/known-bugs-and-planned-features.txt | 3 ++- pyfpdb/GuiGraphViewer.py | 23 ++++------------------- pyfpdb/Tables.py | 2 +- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index f7bd5e19..c6959172 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,9 @@ Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== -graph: update dependencies.txt, select site from drop down, doesnt remove old graph on refresh +graph: update dependencies.txt, doesnt remove old graph on refresh print a "press any key" thing after we print the traceback. That way it is easy for them to see the error message. +pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. reading small blind wrong for PS 25/50ct check we're reading mucked cards from PS newsletter&mailing list diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index ccb8638f..f81d231d 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -50,12 +50,9 @@ class GuiGraphViewer (threading.Thread): else: print "invalid text in site selection in graph, defaulting to PS" site=1 - #print "site:", site self.fig = Figure(figsize=(5,4), dpi=100) self.ax = self.fig.add_subplot(111) -# x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -# y = [2.7, 2.8, 31.4, 38.1, 58.0, 76.2, 100.5, 130.0, 149.3, 180.0] self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers INNER JOIN Players ON HandsPlayers.playerId = Players.id @@ -63,14 +60,7 @@ class GuiGraphViewer (threading.Thread): WHERE Players.name = %s AND Players.siteId = %s ORDER BY siteHandNo""", (name, site)) winnings = self.db.cursor.fetchall() - - - - - #print "winnings:",winnings - #print "" - #print "spent:",spent - + profit=range(len(winnings)) for i in profit: self.cursor.execute("""SELECT SUM(amount) FROM HandsActions @@ -78,11 +68,8 @@ class GuiGraphViewer (threading.Thread): INNER JOIN Players ON HandsPlayers.playerId = Players.id WHERE Players.name = %s AND HandsPlayers.handId = %s AND Players.siteId = %s""", (name, winnings[i][0], site)) spent = self.db.cursor.fetchone() - profit[i]=(i, winnings[i][1]-spent[0]) - -# x=map(lambda x:float(x[0]), results) y=map(lambda x:float(x[1]), profit) line = range(len(y)) @@ -94,13 +81,12 @@ class GuiGraphViewer (threading.Thread): self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea self.mainVBox.pack_start(self.canvas) self.canvas.show() + #end of def showClicked - - def __init__(self, db, settings, debug=True): - """Constructor for table_viewer""" + """Constructor for GraphViewer""" self.debug=debug - #print "start of table_viewer constructor" + #print "start of GraphViewer constructor" self.db=db self.cursor=db.cursor self.settings=settings @@ -136,5 +122,4 @@ class GuiGraphViewer (threading.Thread): self.showButton.connect("clicked", self.showClicked, "show clicked") self.settingsHBox.pack_start(self.showButton) self.showButton.show() - #end of GuiGraphViewer.__init__ diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index d7d9533d..41db0645 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -172,7 +172,7 @@ def pokerstars_decode_table(tw): tw.name = name else: tw.tournament = None - for pattern in [' no all-in', ' fast', ',']: + for pattern in [' no all-in', ' fast', ',', ' 50BB min']: name = re.sub(pattern, '', name) name = re.sub('\s+$', '', name) tw.name = name From 8364d998f21d145953fb10ec7e2fb1312c2aa280 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 22 Sep 2008 03:34:28 +0100 Subject: [PATCH 093/262] p92 - patch from carl to fix utils/get_db_stats --- utils/get_db_stats.py | 50 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/utils/get_db_stats.py b/utils/get_db_stats.py index 79f3b0fa..182baa38 100755 --- a/utils/get_db_stats.py +++ b/utils/get_db_stats.py @@ -22,61 +22,61 @@ db = MySQLdb.connect("localhost", "fpdb", sys.argv[1], "fpdb") cursor = db.cursor() print "Connected to MySQL on localhost. Printing summary stats:" -cursor.execute("SELECT id FROM players") +cursor.execute("SELECT id FROM Players") print "Players:",cursor.rowcount -cursor.execute("SELECT id FROM autorates") +cursor.execute("SELECT id FROM Autorates") print "Autorates:",cursor.rowcount -cursor.execute("SELECT id FROM sites") +cursor.execute("SELECT id FROM Sites") print "Sites:",cursor.rowcount -cursor.execute("SELECT id FROM gametypes") +cursor.execute("SELECT id FROM Gametypes") print "Gametypes:",cursor.rowcount -cursor.execute("SELECT id FROM hands") +cursor.execute("SELECT id FROM Hands") print "Total Hands:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.type='ring'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.type='ring'") print "Hands, Ring:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.type='stt'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.type='stt'") print "Hands, STT:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.type='mtt'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.type='mtt'") print "Hands, MTT:",cursor.rowcount print "" print "Hands per category and type" -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.limit_type='cn'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.limitType='cn'") print "Hands, Cap No Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.limit_type='cp'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.limitType='cp'") print "Hands, Cap Pot Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='holdem' AND gametypes.limit_type='nl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='holdem' AND Gametypes.limitType='nl'") print "Hands, Holdem No Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='holdem' AND gametypes.limit_type='pl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='holdem' AND Gametypes.limitType='pl'") print "Hands, Holdem Pot Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='holdem' AND gametypes.limit_type='fl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='holdem' AND Gametypes.limitType='fl'") print "Hands, Holdem Fixed Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahi' AND gametypes.limit_type='nl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='omahahi' AND Gametypes.limitType='nl'") print "Hands, Omaha Hi No Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahi' AND gametypes.limit_type='pl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='omahahi' AND Gametypes.limitType='pl'") print "Hands, Omaha Hi Pot Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahi' AND gametypes.limit_type='fl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='omahahi' AND Gametypes.limitType='fl'") print "Hands, Omaha Hi Fixed Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahilo' AND gametypes.limit_type='nl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='omahahilo' AND Gametypes.limitType='nl'") print "Hands, Omaha Hi/Lo No Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahilo' AND gametypes.limit_type='pl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='omahahilo' AND Gametypes.limitType='pl'") print "Hands, Omaha Hi/Lo Pot Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='omahahilo' AND gametypes.limit_type='fl'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='omahahilo' AND Gametypes.limitType='fl'") print "Hands, Omaha Hi/Lo Fixed Limit:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='razz'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='razz'") print "Hands, Razz:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='studhi'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='studhi'") print "Hands, Stud Hi:",cursor.rowcount -cursor.execute("SELECT hands.id FROM hands INNER JOIN gametypes ON hands.gametype_id = gametypes.id WHERE gametypes.category='studhilo'") +cursor.execute("SELECT Hands.id FROM Hands INNER JOIN Gametypes ON Hands.gametypeId = Gametypes.id WHERE Gametypes.category='studhilo'") print "Hands, Stud Hi/Lo:",cursor.rowcount print "" -cursor.execute("SELECT id FROM board_cards") +cursor.execute("SELECT id FROM BoardCards") print "Board_cards:",cursor.rowcount -cursor.execute("SELECT id FROM hands_players") +cursor.execute("SELECT id FROM HandsPlayers") print "Hands_players:",cursor.rowcount -cursor.execute("SELECT id FROM hands_actions") +cursor.execute("SELECT id FROM HandsActions") print "Hands_actions:",cursor.rowcount cursor.close() From 4e8a09ff84c113e6c930cbc41018c80a0e3758ac Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 22 Sep 2008 22:12:03 +0100 Subject: [PATCH 094/262] p93 - unbet now stores a negative amount since that's what it is --- docs/tabledesign.html | 2 +- pyfpdb/GuiGraphViewer.py | 4 ++-- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_simple.py | 3 +++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 5411d230..8d283f64 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -744,7 +744,7 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index f81d231d..3d4be24f 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -57,7 +57,7 @@ class GuiGraphViewer (threading.Thread): self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers INNER JOIN Players ON HandsPlayers.playerId = Players.id INNER JOIN Hands ON Hands.id = HandsPlayers.handId - WHERE Players.name = %s AND Players.siteId = %s + WHERE Players.name = %s AND Players.siteId = %s AND tourneysPlayersId is NULL ORDER BY siteHandNo""", (name, site)) winnings = self.db.cursor.fetchall() @@ -66,7 +66,7 @@ class GuiGraphViewer (threading.Thread): self.cursor.execute("""SELECT SUM(amount) FROM HandsActions INNER JOIN HandsPlayers ON HandsActions.handPlayerId = HandsPlayers.id INNER JOIN Players ON HandsPlayers.playerId = Players.id - WHERE Players.name = %s AND HandsPlayers.handId = %s AND Players.siteId = %s""", (name, winnings[i][0], site)) + WHERE Players.name = %s AND HandsPlayers.handId = %s AND Players.siteId = %s AND tourneysPlayersId is NULL""", (name, winnings[i][0], site)) spent = self.db.cursor.fetchone() profit[i]=(i, winnings[i][1]-spent[0]) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index dce926fa..fafa019d 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -417,7 +417,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha4+, p89 or higher") + self.window.set_title("Free Poker DB - version: alpha4+, p93 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 80a75f64..f8fa6524 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -581,6 +581,9 @@ def parseActionAmount(line, atype, site): #print "pos:",pos #print "pos of 20:", line.find("20") amount=int(line[pos:]) + + if atype=="unbet": + amount*=-1 return amount #end def parseActionAmount From 915c948eee663ac3404b78337129d8e596d108da Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 22 Sep 2008 22:48:12 +0100 Subject: [PATCH 095/262] p94 - it now skips rather than dies on tourney summaries --- pyfpdb/fpdb_import.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 0689009e..f4fe56ab 100644 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -76,6 +76,14 @@ def import_file_dict(options, settings, callHud=False): print "Opened file", options.inputFile, "and connected to MySQL on", options.server line=inputFile.readline() + + if line.find("Tournament Summary")!=-1: + print "TODO: implement importing tournament summaries" + inputFile.close() + cursor.close() + db.close() + return 0 + site=fpdb_simple.recogniseSite(line) category=fpdb_simple.recogniseCategory(line) inputFile.seek(0) From 7db2a471b8398c8132075e03d77060f3cc55075c Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 23 Sep 2008 00:39:37 +0100 Subject: [PATCH 096/262] p95 - graph viewer didnt work, fixed it --- pyfpdb/GuiGraphViewer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 3d4be24f..58d79945 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -57,7 +57,7 @@ class GuiGraphViewer (threading.Thread): self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers INNER JOIN Players ON HandsPlayers.playerId = Players.id INNER JOIN Hands ON Hands.id = HandsPlayers.handId - WHERE Players.name = %s AND Players.siteId = %s AND tourneysPlayersId is NULL + WHERE Players.name = %s AND Players.siteId = %s AND (tourneysPlayersId IS NULL) ORDER BY siteHandNo""", (name, site)) winnings = self.db.cursor.fetchall() @@ -66,7 +66,7 @@ class GuiGraphViewer (threading.Thread): self.cursor.execute("""SELECT SUM(amount) FROM HandsActions INNER JOIN HandsPlayers ON HandsActions.handPlayerId = HandsPlayers.id INNER JOIN Players ON HandsPlayers.playerId = Players.id - WHERE Players.name = %s AND HandsPlayers.handId = %s AND Players.siteId = %s AND tourneysPlayersId is NULL""", (name, winnings[i][0], site)) + WHERE Players.name = %s AND HandsPlayers.handId = %s AND Players.siteId = %s AND (tourneysPlayersId IS NULL)""", (name, winnings[i][0], site)) spent = self.db.cursor.fetchone() profit[i]=(i, winnings[i][1]-spent[0]) From 276f6ba262e36ded2728a0bb4eff849d36b2633b Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 23 Sep 2008 10:17:53 -0400 Subject: [PATCH 097/262] Initial load of git repo. --- Configuration.py | 278 +++++++++++++++++++++ Database.py | 183 ++++++++++++++ HUD_config.xml | 175 ++++++++++++++ HUD_main.py | 137 +++++++++++ HandHistory.py | 194 +++++++++++++++ Hud.py | 449 ++++++++++++++++++++++++++++++++++ Mucked.py | 243 +++++++++++++++++++ SQL.py | 290 ++++++++++++++++++++++ Stats.py | 618 +++++++++++++++++++++++++++++++++++++++++++++++ Tables.py | 211 ++++++++++++++++ 10 files changed, 2778 insertions(+) create mode 100644 Configuration.py create mode 100644 Database.py create mode 100644 HUD_config.xml create mode 100755 HUD_main.py create mode 100644 HandHistory.py create mode 100755 Hud.py create mode 100644 Mucked.py create mode 100644 SQL.py create mode 100644 Stats.py create mode 100644 Tables.py diff --git a/Configuration.py b/Configuration.py new file mode 100644 index 00000000..55b8ed2b --- /dev/null +++ b/Configuration.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python +"""Configuration.py + +Handles HUD configuration files. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## + +# Standard Library modules +import shutil +import xml.dom.minidom +from xml.dom.minidom import Node + +class Layout: + def __init__(self, max): + self.max = int(max) + self.location = [] + for i in range(self.max + 1): self.location.append(None) + + def __str__(self): + temp = " Layout = %d max, width= %d, height = %d, fav_seat = %d\n" % (self.max, self.width, self.height, self.fav_seat) + temp = temp + " Locations = " + for i in range(1, len(self.location)): + temp = temp + "(%d,%d)" % self.location[i] + + return temp + "\n" + +class Site: + def __init__(self, node): + self.site_name = node.getAttribute("site_name") + self.table_finder = node.getAttribute("table_finder") + self.screen_name = node.getAttribute("screen_name") + self.site_path = node.getAttribute("site_path") + self.HH_path = node.getAttribute("HH_path") + self.decoder = node.getAttribute("decoder") + self.layout = {} + + for layout_node in node.getElementsByTagName('layout'): + max = int( layout_node.getAttribute('max') ) + lo = Layout(max) + lo.fav_seat = int( layout_node.getAttribute('fav_seat') ) + lo.width = int( layout_node.getAttribute('width') ) + lo.height = int( layout_node.getAttribute('height') ) + + for location_node in layout_node.getElementsByTagName('location'): + lo.location[int( location_node.getAttribute('seat') )] = (int( location_node.getAttribute('x') ), int( location_node.getAttribute('y'))) + + self.layout[lo.max] = lo + + def __str__(self): + temp = "Site = " + self.site_name + "\n" + for key in dir(self): + if key.startswith('__'): continue + if key == 'layout': continue + value = getattr(self, key) + if callable(value): continue + temp = temp + ' ' + key + " = " + value + "\n" + + for layout in self.layout: + temp = temp + "%s" % self.layout[layout] + + return temp + +class Stat: + def __init__(self): + pass + + def __str__(self): + temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s, popup = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click, self.popup) + return temp + +class Game: + def __init__(self, node): + self.game_name = node.getAttribute("game_name") + self.db = node.getAttribute("db") + self.rows = int( node.getAttribute("rows") ) + self.cols = int( node.getAttribute("cols") ) + + self.stats = {} + for stat_node in node.getElementsByTagName('stat'): + stat = Stat() + stat.stat_name = stat_node.getAttribute("stat_name") + stat.row = int( stat_node.getAttribute("row") ) + stat.col = int( stat_node.getAttribute("col") ) + stat.tip = stat_node.getAttribute("tip") + stat.click = stat_node.getAttribute("click") + stat.popup = stat_node.getAttribute("popup") + + self.stats[stat.stat_name] = stat + + def __str__(self): + temp = "Game = " + self.game_name + "\n" + temp = temp + " db = %s\n" % self.db + temp = temp + " rows = %d\n" % self.rows + temp = temp + " cols = %d\n" % self.cols + + for stat in self.stats.keys(): + temp = temp + "%s" % self.stats[stat] + + return temp + +class Database: + def __init__(self, node): + self.db_name = node.getAttribute("db_name") + self.db_server = node.getAttribute("db_server") + self.db_ip = node.getAttribute("db_ip") + self.db_user = node.getAttribute("db_user") + self.db_type = node.getAttribute("db_type") + self.db_pass = node.getAttribute("db_pass") + + def __str__(self): + temp = 'Database = ' + self.db_name + '\n' + for key in dir(self): + if key.startswith('__'): continue + value = getattr(self, key) + if callable(value): continue + temp = temp + ' ' + key + " = " + value + "\n" + return temp + +class Mucked: + def __init__(self, node): + self.name = node.getAttribute("mw_name") + self.cards = node.getAttribute("deck") + self.card_wd = node.getAttribute("card_wd") + self.card_ht = node.getAttribute("card_ht") + self.rows = node.getAttribute("rows") + self.cols = node.getAttribute("cols") + self.format = node.getAttribute("stud") + + def __str__(self): + temp = 'Mucked = ' + self.name + "\n" + for key in dir(self): + if key.startswith('__'): continue + value = getattr(self, key) + if callable(value): continue + temp = temp + ' ' + key + " = " + value + "\n" + return temp + +class Popup: + def __init__(self, node): + self.name = node.getAttribute("pu_name") + self.pu_stats = [] + for stat_node in node.getElementsByTagName('pu_stat'): + self.pu_stats.append(stat_node.getAttribute("pu_stat_name")) + + def __str__(self): + temp = "Popup = " + self.name + "\n" + for stat in self.pu_stats: + temp = temp + " " + stat + return temp + "\n" + +class Config: + def __init__(self, file = 'HUD_config.xml'): + + doc = xml.dom.minidom.parse(file) + + self.doc = doc + self.file = file + self.supported_sites = {} + self.supported_games = {} + self.supported_databases = {} + self.mucked_windows = {} + self.popup_windows = {} + +# s_sites = doc.getElementsByTagName("supported_sites") + for site_node in doc.getElementsByTagName("site"): + site = Site(node = site_node) + self.supported_sites[site.site_name] = site + + s_games = doc.getElementsByTagName("supported_games") + for game_node in doc.getElementsByTagName("game"): + game = Game(node = game_node) + self.supported_games[game.game_name] = game + + s_dbs = doc.getElementsByTagName("supported_databases") + for db_node in doc.getElementsByTagName("database"): + db = Database(node = db_node) + self.supported_databases[db.db_name] = db + + s_dbs = doc.getElementsByTagName("mucked_windows") + for mw_node in doc.getElementsByTagName("mw"): + mw = Mucked(node = mw_node) + self.mucked_windows[mw.name] = mw + + s_dbs = doc.getElementsByTagName("popup_windows") + for pu_node in doc.getElementsByTagName("pu"): + pu = Popup(node = pu_node) + self.popup_windows[pu.name] = pu + + def get_site_node(self, site): + for site_node in self.doc.getElementsByTagName("site"): + if site_node.getAttribute("site_name") == site: + return site_node + + def get_layout_node(self, site_node, layout): + for layout_node in site_node.getElementsByTagName("layout"): + if int( layout_node.getAttribute("max") ) == int( layout ): + return layout_node + + def get_location_node(self, layout_node, seat): + for location_node in layout_node.getElementsByTagName("location"): + if int( location_node.getAttribute("seat") ) == int( seat ): + return location_node + + def save(self, file = None): + if not file == None: + f = open(file, 'w') + self.doc.writexml(f) + f.close() + else: + shutil.move(self.file, self.file+".backup") + f = open(self.file, 'w') + self.doc.writexml(f) + f.close + + def edit_layout(self, site_name, max, width = None, height = None, + fav_seat = None, locations = None): + site_node = self.get_site_node(site_name) + layout_node = self.get_layout_node(site_node, max) + for i in range(1, max + 1): + location_node = self.get_location_node(layout_node, i) + location_node.setAttribute("x", str( locations[i-1][0] )) + location_node.setAttribute("y", str( locations[i-1][1] )) + self.supported_sites[site_name].layout[max].location[i] = ( locations[i-1][0], locations[i-1][1] ) + +if __name__== "__main__": + c = Config() + + print "\n----------- SUPPORTED SITES -----------" + for s in c.supported_sites.keys(): + print c.supported_sites[s] + + print "----------- END SUPPORTED SITES -----------" + + + print "\n----------- SUPPORTED GAMES -----------" + for game in c.supported_games.keys(): + print c.supported_games[game] + + print "----------- END SUPPORTED GAMES -----------" + + + print "\n----------- SUPPORTED DATABASES -----------" + for db in c.supported_databases.keys(): + print c.supported_databases[db] + + print "----------- END SUPPORTED DATABASES -----------" + + print "\n----------- MUCKED WINDOW FORMATS -----------" + for w in c.mucked_windows.keys(): + print c.mucked_windows[w] + + print "----------- END MUCKED WINDOW FORMATS -----------" + + print "\n----------- POPUP WINDOW FORMATS -----------" + for w in c.popup_windows.keys(): + print c.popup_windows[w] + + print "----------- END MUCKED WINDOW FORMATS -----------" + + c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) + c.save(file="testout.xml") \ No newline at end of file diff --git a/Database.py b/Database.py new file mode 100644 index 00000000..d634e134 --- /dev/null +++ b/Database.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python +"""Database.py + +Create and manage the database objects. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# postmaster -D /var/lib/pgsql/data + +# Standard Library modules +import sys + +# pyGTK modules + +# FreePokerTools modules +import Configuration +import SQL + +# pgdb database module for posgres via DB-API +import psycopg2 +# pgdb uses pyformat. is that fixed or an option? + +# mysql bindings +import MySQLdb + +class Database: + def __init__(self, c, db_name, game): + if c.supported_databases[db_name].db_server == 'postgresql': + self.connection = psycopg2.connect(host = c.supported_databases[db_name].db_ip, + user = c.supported_databases[db_name].db_user, + password = c.supported_databases[db_name].db_pass, + database = c.supported_databases[db_name].db_name) + + elif c.supported_databases[db_name].db_server == 'mysql': + self.connection = MySQLdb.connect(host = c.supported_databases[db_name].db_ip, + user = c.supported_databases[db_name].db_user, + passwd = c.supported_databases[db_name].db_pass, + db = c.supported_databases[db_name].db_name) + + else: + print "Database not recognized." + return(0) + + self.type = c.supported_databases[db_name].db_type + self.sql = SQL.Sql(game = game, type = self.type) + + def close_connection(self): + self.connection.close() + + def get_table_name(self, hand_id): + c = self.connection.cursor() + c.execute(self.sql.query['get_table_name'], (hand_id, )) + row = c.fetchone() + return row + + def get_last_hand(self): + c = self.connection.cursor() + c.execute(self.sql.query['get_last_hand']) + row = c.fetchone() + return row[0] + + def get_xml(self, hand_id): + c = self.connection.cursor() + c.execute(self.sql.query['get_xml'], (hand_id)) + row = c.fetchone() + return row[0] + + def get_recent_hands(self, last_hand): + c = self.connection.cursor() + c.execute(self.sql.query['get_recent_hands'], {'last_hand': last_hand}) + return c.fetchall() + + def get_hand_info(self, new_hand_id): + c = self.connection.cursor() + c.execute(self.sql.query['get_hand_info'], new_hand_id) + return c.fetchall() + +# def get_cards(self, hand): +# this version is for the PTrackSv2 db +# c = self.connection.cursor() +# c.execute(self.sql.query['get_cards'], hand) +# colnames = [desc[0] for desc in c.description] +# cards = {} +# for row in c.fetchall(): +# s_dict = {} +# for name, val in zip(colnames, row): +# s_dict[name] = val +# cards[s_dict['seat_number']] = s_dict +# return (cards) + + def get_cards(self, hand): +# this version is for the fpdb db + c = self.connection.cursor() + c.execute(self.sql.query['get_cards'], hand) + colnames = [desc[0] for desc in c.description] + cards = {} + for row in c.fetchall(): + s_dict = {} + for name, val in zip(colnames, row): + s_dict[name] = val + cards[s_dict['seat_number']] = s_dict + return (cards) + + def get_stats_from_hand(self, hand, player_id = False): + c = self.connection.cursor() + + if not player_id: player_id = "%" +# get the players in the hand and their seats +# c.execute(self.sql.query['get_players_from_hand'], (hand, player_id)) + c.execute(self.sql.query['get_players_from_hand'], (hand, )) + names = {} + seats = {} + for row in c.fetchall(): + names[row[0]] = row[2] + seats[row[0]] = row[1] + +# now get the stats +# c.execute(self.sql.query['get_stats_from_hand'], (hand, hand, player_id)) + c.execute(self.sql.query['get_stats_from_hand'], (hand, hand)) + colnames = [desc[0] for desc in c.description] + stat_dict = {} + for row in c.fetchall(): + t_dict = {} + for name, val in zip(colnames, row): + t_dict[name] = val +# print t_dict + t_dict['screen_name'] = names[t_dict['player_id']] + t_dict['seat'] = seats[t_dict['player_id']] + stat_dict[t_dict['player_id']] = t_dict + return stat_dict + + def get_player_id(self, config, site, player_name): + print "site = %s, player name = %s" % (site, player_name) + c = self.connection.cursor() + c.execute(self.sql.query['get_player_id'], {'player': player_name, 'site': site}) + row = c.fetchone() + return row[0] + +if __name__=="__main__": + c = Configuration.Config() + +# db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem + db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem +# db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz +# db_connection = Database(c, 'ptracks', 'razz') # postgres + print "database connection object = ", db_connection.connection + print "database type = ", db_connection.type + + h = db_connection.get_last_hand() + print "last hand = ", h + + hero = db_connection.get_player_id(c, 'PokerStars', 'nutOmatic') + print "nutOmatic is id_player = %d" % hero + + stat_dict = db_connection.get_stats_from_hand(h) + for p in stat_dict.keys(): + print p, " ", stat_dict[p] + + print "nutOmatics stats:" + stat_dict = db_connection.get_stats_from_hand(h, hero) + for p in stat_dict.keys(): + print p, " ", stat_dict[p] + + db_connection.close_connection + + print "press enter to continue" + sys.stdin.readline() diff --git a/HUD_config.xml b/HUD_config.xml new file mode 100644 index 00000000..518e2af6 --- /dev/null +++ b/HUD_config.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HUD_main.py b/HUD_main.py new file mode 100755 index 00000000..15bbd034 --- /dev/null +++ b/HUD_main.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +"""Hud_main.py + +Main for FreePokerTools HUD. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# to do kill window on my seat +# to do adjust for preferred seat +# to do allow window resizing +# to do hud to echo, but ignore non numbers +# to do no hud window for hero +# to do things to add to config.xml +# to do font and size +# to do bg and fg color +# to do opacity + +# Standard Library modules +import sys +import os +import thread +import Queue + +# pyGTK modules +import pygtk +import gtk +import gobject + +# FreePokerTools modules +import Configuration +import Database +import Tables +import Hud + +# global dict for keeping the huds +hud_dict = {} + +db_connection = 0; +config = 0; + +def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() + +def process_new_hand(new_hand_id, db_name): +# there is a new hand_id to be processed +# read the hand_id from stdin and strip whitespace + global hud_dict + + for h in hud_dict.keys(): + if hud_dict[h].deleted: + del(hud_dict[h]) + + db_connection = Database.Database(config, db_name, 'temp') + (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) +# if a hud for this table exists, just update it + if hud_dict.has_key(table_name): + hud_dict[table_name].update(new_hand_id, db_connection, config) +# otherwise create a new hud + else: + table_windows = Tables.discover(config) + for t in table_windows.keys(): + if table_windows[t].name == table_name: + hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_name) + hud_dict[table_name].create(new_hand_id, config) + hud_dict[table_name].update(new_hand_id, db_connection, config) + break +# print "table name \"%s\" not identified, no hud created" % (table_name) + db_connection.close_connection() + return(1) + +def check_stdin(db_name): + try: + hand_no = dataQueue.get(block=False) + process_new_hand(hand_no, db_name) + except: + pass + + return True + +def read_stdin(source, condition, db_name): + new_hand_id = sys.stdin.readline() + process_new_hand(new_hand_id, db_name) + return True + +def producer(): # This is the thread function + while True: + hand_no = sys.stdin.readline() # reads stdin + dataQueue.put(hand_no) # and puts result on the queue + +if __name__== "__main__": + print "HUD_main starting" + + try: + db_name = sys.argv[1] + except: + db_name = 'fpdb-p' + print "Using db name = ", db_name + + config = Configuration.Config() +# db_connection = Database.Database(config, 'fpdb', 'holdem') + + if os.name == 'posix': + s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, read_stdin, db_name) + elif os.name == 'nt': + dataQueue = Queue.Queue() # shared global. infinite size + gobject.threads_init() # this is required + thread.start_new_thread(producer, ()) # starts the thread + gobject.timeout_add(1000, check_stdin, db_name) + else: + print "Sorry your operating system is not supported." + sys.exit() + + main_window = gtk.Window() + main_window.connect("destroy", destroy) + label = gtk.Label('Closing this window will exit from the HUD.') + main_window.add(label) + main_window.set_title("HUD Main Window") + main_window.show_all() + + gtk.main() diff --git a/HandHistory.py b/HandHistory.py new file mode 100644 index 00000000..dd2fa427 --- /dev/null +++ b/HandHistory.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +"""HandHistory.py + +Parses HandHistory xml files and returns requested objects. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## +# Standard Library modules +import xml.dom.minidom +from xml.dom.minidom import Node + +class HandHistory: + def __init__(self, xml_string, elements = ('ALL')): + + doc = xml.dom.minidom.parseString(xml_string) + if elements == ('ALL'): + elements = ('BETTING', 'AWARDS', 'POSTS', 'PLAYERS', 'GAME') + + if 'BETTING' in elements: + self.BETTING = Betting(doc.getElementsByTagName('BETTING')[0]) + if 'AWARDS' in elements: + self.AWARDS = Awards (doc.getElementsByTagName('AWARDS')[0]) + if 'POSTS' in elements: + self.POSTS = Posts (doc.getElementsByTagName('POSTS')[0]) + if 'GAME' in elements: + self.GAME = Game (doc.getElementsByTagName('GAME')[0]) + if 'PLAYERS' in elements: + self.PLAYERS = {} + p_n = doc.getElementsByTagName('PLAYERS')[0] + for p in p_n.getElementsByTagName('PLAYER'): + a_player = Player(p) + self.PLAYERS[a_player.name] = a_player + +class Player: + def __init__(self, node): + self.name = node.getAttribute('NAME') + self.seat = node.getAttribute('SEAT') + self.stack = node.getAttribute('STACK') + self.showed_hand = node.getAttribute('SHOWED_HAND') + self.cards = node.getAttribute('CARDS') + self.allin = node.getAttribute('ALLIN') + self.sitting_out = node.getAttribute('SITTING_OUT') + self.hand = node.getAttribute('HAND') + self.start_cards = node.getAttribute('START_CARDS') + + if self.allin == '' or \ + self.allin == '0' or \ + self.allin.upper() == 'FALSE': self.allin = False + else: self.allin = True + + if self.sitting_out == '' or \ + self.sitting_out == '0' or \ + self.sitting_out.upper() == 'FALSE': self.sitting_out = False + else: self.sitting_out = True + + def __str__(self): + temp = "%s\n seat = %s\n stack = %s\n cards = %s\n" % \ + (self.name, self.seat, self.stack, self.cards) + temp = temp + " showed_hand = %s\n allin = %s\n" % \ + (self.showed_hand, self.allin) + temp = temp + " hand = %s\n start_cards = %s\n" % \ + (self.hand, self.start_cards) + return temp + +class Awards: + def __init__(self, node): + self.awards = [] # just an array of award objects + for a in node.getElementsByTagName('AWARD'): + self.awards.append(Award(a)) + + def __str__(self): + temp = "" + for a in self.awards: + temp = temp + "%s\n" % (a) + return temp + +class Award: + def __init__(self, node): + self.player = node.getAttribute('PLAYER') + self.amount = node.getAttribute('AMOUNT') + self.pot = node.getAttribute('POT') + + def __str__(self): + return self.player + " won " + self.amount + " from " + self.pot + +class Game: + def __init__(self, node): + print node + self.tags = {} + for tag in ( ('GAME_NAME', 'game_name'), ('MAX', 'max'), ('HIGHLOW', 'high_low'), + ('STRUCTURE', 'structure'), ('MIXED', 'mixed') ): + L = node.getElementsByTagName(tag[0]) + if (not L): continue + print L + for node2 in L: + title = "" + for node3 in node2.childNodes: + if (node3.nodeType == Node.TEXT_NODE): + title +=node3.data + self.tags[tag[1]] = title + + def __str__(self): + return "%s %s %s, (%s max), %s" % (self.tags['structure'], + self.tags['game_name'], + self.tags['game_name'], + self.tags['max'], + self.tags['game_name']) + +class Posts: + def __init__(self, node): + self.posts = [] # just an array of post objects + for p in node.getElementsByTagName('POST'): + self.posts.append(Post(p)) + + def __str__(self): + temp = "" + for p in self.posts: + temp = temp + "%s\n" % (p) + return temp + +class Post: + def __init__(self, node): + self.player = node.getAttribute('PLAYER') + self.amount = node.getAttribute('AMOUNT') + self.posted = node.getAttribute('POSTED') + self.live = node.getAttribute('LIVE') + + def __str__(self): + return ("%s posted %s %s %s") % (self.player, self.amount, self.posted, self.live) + +class Betting: + def __init__(self, node): + self.rounds = [] # a Betting object is just an array of rounds + for r in node.getElementsByTagName('ROUND'): + self.rounds.append(Round(r)) + + def __str__(self): + temp = "" + for r in self.rounds: + temp = temp + "%s\n" % (r) + return temp + +class Round: + def __init__(self, node): + self.name = node.getAttribute('ROUND_NAME') + self.action = [] + for a in node.getElementsByTagName('ACTION'): + self.action.append(Action(a)) + + def __str__(self): + temp = self.name + "\n" + for a in self.action: + temp = temp + " %s\n" % (a) + return temp + +class Action: + def __init__(self, node): + self.player = node.getAttribute('PLAYER') + self.action = node.getAttribute('ACT') + self.amount = node.getAttribute('AMOUNT') + self.allin = node.getAttribute('ALLIN') + + def __str__(self): + return self.player + " " + self.action + " " + self.amount + " " + self.allin + +if __name__== "__main__": + file = open('test.xml', 'r') + xml_string = file.read() + file.close() + + print xml_string + "\n\n\n" + h = HandHistory(xml_string, ('ALL')) + print h.GAME + print h.POSTS + print h.BETTING + print h.AWARDS + + for p in h.PLAYERS.keys(): + print h.PLAYERS[p] \ No newline at end of file diff --git a/Hud.py b/Hud.py new file mode 100755 index 00000000..debfaa67 --- /dev/null +++ b/Hud.py @@ -0,0 +1,449 @@ +#!/usr/bin/env python +"""Hud.py + +Create and manage the hud overlays. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## +# Standard Library modules +import os + +# pyGTK modules +import pygtk +import gtk +import pango +import gobject + +# win32 modules -- only imported on windows systems +if os.name == 'nt': + import win32gui + import win32con + +# FreePokerTools modules +import Tables # needed for testing only +import Configuration +import Stats +import Mucked +import Database +import HUD_main + +class Hud: + + def __init__(self, table, max, poker_game, config, db_name): + self.table = table + self.config = config + self.poker_game = poker_game + self.max = max + self.db_name = db_name + self.deleted = False + + self.stat_windows = {} + self.popup_windows = {} + self.font = pango.FontDescription("Sans 8") + +# Set up a main window for this this instance of the HUD + self.main_window = gtk.Window() +# self.window.set_decorated(0) + self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.main_window.set_keep_above(1) + self.main_window.set_title(table.name) + self.main_window.connect("destroy", self.kill_hud) + + self.ebox = gtk.EventBox() + self.label = gtk.Label("Close this window to\nkill the HUD for\n %s" % (table.name)) + self.main_window.add(self.ebox) + self.ebox.add(self.label) + self.main_window.move(self.table.x, self.table.y) + +# A popup window for the main window + self.menu = gtk.Menu() + self.item1 = gtk.MenuItem('Kill this HUD') + self.menu.append(self.item1) + self.item1.connect("activate", self.kill_hud) + self.item1.show() + self.item2 = gtk.MenuItem('Save Layout') + self.menu.append(self.item2) + self.item2.connect("activate", self.save_layout) + self.item2.show() + self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) + + self.main_window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.main_window) + + def on_button_press(self, widget, event): + if event.button == 3: + widget.popup(None, None, None, event.button, event.time) + return True + return False + + def kill_hud(self, args): + for k in self.stat_windows.keys(): + self.stat_windows[k].window.destroy() + self.main_window.destroy() + self.deleted = True + + def save_layout(self, *args): + new_layout = [] + for sw in self.stat_windows: + loc = self.stat_windows[sw].window.get_position() + new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) + new_layout.append(new_loc) + print new_layout + self.config.edit_layout(self.table.site, self.table.max, locations = new_layout) + self.config.save() + + def create(self, hand, config): +# update this hud, to the stats and players as of "hand" +# hand is the hand id of the most recent hand played at this table +# +# this method also manages the creating and destruction of stat +# windows via calls to the Stat_Window class + for i in range(1, self.max + 1): + (x, y) = config.supported_sites[self.table.site].layout[self.max].location[i] + self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], + parent = self, + table = self.table, + x = x, + y = y, + seat = i, + player_id = 'fake', + font = self.font) + + self.stats = [] + for i in range(0, config.supported_games[self.poker_game].rows + 1): + row_list = [''] * config.supported_games[self.poker_game].cols + self.stats.append(row_list) + for stat in config.supported_games[self.poker_game].stats.keys(): + self.stats[config.supported_games[self.poker_game].stats[stat].row] \ + [config.supported_games[self.poker_game].stats[stat].col] = \ + config.supported_games[self.poker_game].stats[stat].stat_name + +# self.mucked_window = gtk.Window() +# self.m = Mucked.Mucked(self.mucked_window, self.db_connection) +# self.mucked_window.show_all() + + def update(self, hand, db, config): + self.hand = hand # this is the last hand, so it is available later + stat_dict = db.get_stats_from_hand(hand) + for s in stat_dict.keys(): + self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] + for r in range(0, config.supported_games[self.poker_game].rows): + for c in range(0, config.supported_games[self.poker_game].cols): + number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) + self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(number[1]) + tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \ + number[3] + ", " + number[4] + Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip) +# self.m.update(hand) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + +class Stat_Window: + + def button_press_cb(self, widget, event, *args): +# 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. + if event.button == 1: # left button event + if event.type == gtk.gdk.BUTTON_PRESS: # left button single click + if self.sb_click > 0: return + self.sb_click = gobject.timeout_add(250, self.single_click, widget) + elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click + if self.sb_click > 0: + gobject.source_remove(self.sb_click) + self.sb_click = 0 + self.double_click(widget, event, *args) + + if event.button == 2: # middle button event + pass +# print "middle button clicked" + + if event.button == 3: # right button event + pass +# print "right button clicked" + + def single_click(self, widget): +# Callback from the timeout in the single-click finding part of the +# button press call back. This needs to be modified to get all the +# arguments from the call. +# print "left button clicked" + self.sb_click = 0 + Popup_window(widget, self) + return False + + def double_click(self, widget, event, *args): + self.toggle_decorated(widget) + + def toggle_decorated(self, widget): + top = widget.get_toplevel() + (x, y) = top.get_position() + + if top.get_decorated(): + top.set_decorated(0) + top.move(x, y) + else: + top.set_decorated(1) + top.move(x, y) + + def __init__(self, parent, game, table, seat, x, y, player_id, font): + self.parent = parent # Hud object that this stat window belongs to + self.game = game # Configuration object for the curren + self.table = table # Table object where this is going + self.x = x + table.x # table.x and y are the location of the table + self.y = y + table.y # x and y are the location relative to table.x & y + self.player_id = player_id # looks like this isn't used ;) + self.sb_click = 0 # used to figure out button clicks + + self.window = gtk.Window() + self.window.set_decorated(0) + self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.window.set_keep_above(1) + self.window.set_title("%s" % seat) + self.window.set_property("skip-taskbar-hint", True) + + self.grid = gtk.Table(rows = self.game.rows, columns = self.game.cols, homogeneous = False) + self.window.add(self.grid) + + self.e_box = [] + self.frame = [] + self.label = [] + for r in range(self.game.rows): + self.e_box.append([]) + self.label.append([]) + for c in range(self.game.cols): + self.e_box[r].append( gtk.EventBox() ) + Stats.do_tip(self.e_box[r][c], 'farts') + self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) + self.label[r].append( gtk.Label('xxx') ) + self.e_box[r][c].add(self.label[r][c]) + self.e_box[r][c].connect("button_press_event", self.button_press_cb) +# font = pango.FontDescription("Sans 8") + self.label[r][c].modify_font(font) + self.window.realize + self.window.move(self.x, self.y) + self.window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.window) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + +def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() + +class Popup_window: + def __init__(self, parent, stat_window): + self.sb_click = 0 + +# create the popup window + self.window = gtk.Window() + self.window.set_decorated(0) + self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.window.set_keep_above(1) + self.window.set_title("popup") + self.window.set_property("skip-taskbar-hint", True) + self.window.set_transient_for(parent.get_toplevel()) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + + self.ebox = gtk.EventBox() + self.ebox.connect("button_press_event", self.button_press_cb) + self.lab = gtk.Label("stuff\nstuff\nstuff") + +# need an event box so we can respond to clicks + self.window.add(self.ebox) + self.ebox.add(self.lab) + self.window.realize + +# figure out the row, col address of the click that activated the popup + row = 0 + col = 0 + for r in range(0, stat_window.game.rows): + for c in range(0, stat_window.game.cols): + if stat_window.e_box[r][c] == parent: + row = r + col = c + break + +# figure out what popup format we're using + popup_format = "default" + for stat in stat_window.game.stats.keys(): + if stat_window.game.stats[stat].row == row and stat_window.game.stats[stat].col == col: + popup_format = stat_window.game.stats[stat].popup + break + +# get the list of stats to be presented from the config + stat_list = [] + for w in stat_window.parent.config.popup_windows.keys(): + if w == popup_format: + stat_list = stat_window.parent.config.popup_windows[w].pu_stats + break + +# get a database connection + db_connection = Database.Database(stat_window.parent.config, stat_window.parent.db_name, 'temp') + +# calculate the stat_dict and then create the text for the pu +# stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand, stat_window.player_id) + stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand) + db_connection.close_connection() + + pu_text = "" + for s in stat_list: + number = Stats.do_stat(stat_dict, player = int(stat_window.player_id), stat = s) + pu_text += number[3] + "\n" + + self.lab.set_text(pu_text) + self.window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.window) + + def button_press_cb(self, widget, event, *args): +# This handles all callbacks from button presses on the event boxes in +# the popup windows. There is a bit of an ugly kludge to separate single- +# and double-clicks. This is the same code as in the Stat_window class + if event.button == 1: # left button event + if event.type == gtk.gdk.BUTTON_PRESS: # left button single click + if self.sb_click > 0: return + self.sb_click = gobject.timeout_add(250, self.single_click, widget) + elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click + if self.sb_click > 0: + gobject.source_remove(self.sb_click) + self.sb_click = 0 + self.double_click(widget, event, *args) + + if event.button == 2: # middle button event + pass +# print "middle button clicked" + + if event.button == 3: # right button event + pass +# print "right button clicked" + + def single_click(self, widget): +# Callback from the timeout in the single-click finding part of the +# button press call back. This needs to be modified to get all the +# arguments from the call. + self.sb_click = 0 + self.window.destroy() + return False + + def double_click(self, widget, event, *args): + self.toggle_decorated(widget) + + def toggle_decorated(self, widget): + top = widget.get_toplevel() + (x, y) = top.get_position() + + if top.get_decorated(): + top.set_decorated(0) + top.move(x, y) + else: + top.set_decorated(1) + top.move(x, y) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + +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) + db = Database.Database(c, 'fpdb', 'holdem') + + for t in tables: + win = Hud(t, 8, c, db) +# t.get_details() + win.update(8300, db, c) + + gtk.main() diff --git a/Mucked.py b/Mucked.py new file mode 100644 index 00000000..0af06c65 --- /dev/null +++ b/Mucked.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python +"""Mucked.py + +Mucked cards display for FreePokerTools HUD. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# to do +# problem with hand 30586 + +# Standard Library modules +import sys +import os +import string +import xml.dom.minidom +from xml.dom.minidom import Node + +# pyGTK modules +import pygtk +import gtk +import gobject + +# FreePokerTools modules +import Configuration +import Database +import Tables +import Hud +import Mucked +import HandHistory + +class Mucked: + def __init__(self, parent, db_connection): + + self.parent = parent #this is the parent of the mucked cards widget + self.db_connection = db_connection + + self.vbox = gtk.VBox() + self.parent.add(self.vbox) + + self.mucked_list = MuckedList (self.vbox, db_connection) + self.mucked_cards = MuckedCards(self.vbox, db_connection) + self.mucked_list.mucked_cards = self.mucked_cards + + def update(self, new_hand_id): + self.mucked_list.update(new_hand_id) + +class MuckedList: + def __init__(self, parent, db_connection): + + self.parent = parent + self.db_connection = db_connection + +# set up a scrolled window to hold the listbox + self.scrolled_window = gtk.ScrolledWindow() + self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) + self.parent.add(self.scrolled_window) + +# create a ListStore to use as the model + self.liststore = gtk.ListStore(str, str, str) + self.treeview = gtk.TreeView(self.liststore) + self.tvcolumn0 = gtk.TreeViewColumn('HandID') + self.tvcolumn1 = gtk.TreeViewColumn('Cards') + self.tvcolumn2 = gtk.TreeViewColumn('Net') + +# add tvcolumn to treeview + self.treeview.append_column(self.tvcolumn0) + self.treeview.append_column(self.tvcolumn1) + self.treeview.append_column(self.tvcolumn2) + +# create a CellRendererText to render the data + self.cell = gtk.CellRendererText() + + # add the cell to the tvcolumn and allow it to expand + self.tvcolumn0.pack_start(self.cell, True) + self.tvcolumn1.pack_start(self.cell, True) + self.tvcolumn2.pack_start(self.cell, True) + self.tvcolumn0.add_attribute(self.cell, 'text', 0) + self.tvcolumn1.add_attribute(self.cell, 'text', 1) + self.tvcolumn2.add_attribute(self.cell, 'text', 2) +# resize the cols if nec + self.tvcolumn0.set_resizable(True) + self.treeview.connect("row-activated", self.activated_event) + + self.scrolled_window.add_with_viewport(self.treeview) + + def activated_event(self, path, column, data=None): + sel = self.treeview.get_selection() + (model, iter) = sel.get_selected() + self.mucked_cards.update(model.get_value(iter, 0)) + + def update(self, new_hand_id): +# info_row = self.db_connection.get_hand_info(new_hand_id) + info_row = ((new_hand_id, "xxxx", 0), ) + iter = self.liststore.append(info_row[0]) + sel = self.treeview.get_selection() + sel.select_iter(iter) + + vadj = self.scrolled_window.get_vadjustment() + vadj.set_value(vadj.upper) + self.mucked_cards.update(new_hand_id) + +class MuckedCards: + def __init__(self, parent, db_connection): + + self.parent = parent #this is the parent of the mucked cards widget + self.db_connection = db_connection + + self.card_images = self.get_card_images() + self.seen_cards = {} + self.grid_contents = {} + self.eb = {} + + self.rows = 8 + self.cols = 7 + self.grid = gtk.Table(self.rows, self.cols + 4, homogeneous = False) + + for r in range(0, self.rows): + for c in range(0, self.cols): + self.seen_cards[(c, r)] = gtk.image_new_from_pixbuf(self.card_images[('B', 'S')]) + self.eb[(c, r)]= gtk.EventBox() + +# set up the contents for the cells + for r in range(0, self.rows): + self.grid_contents[( 0, r)] = gtk.Label("%d" % (r + 1)) + self.grid_contents[( 1, r)] = gtk.Label("player %d" % (r + 1)) + self.grid_contents[( 4, r)] = gtk.Label("-") + self.grid_contents[( 9, r)] = gtk.Label("-") + self.grid_contents[( 2, r)] = self.eb[( 0, r)] + self.grid_contents[( 3, r)] = self.eb[( 1, r)] + self.grid_contents[( 5, r)] = self.eb[( 2, r)] + self.grid_contents[( 6, r)] = self.eb[( 3, r)] + self.grid_contents[( 7, r)] = self.eb[( 4, r)] + self.grid_contents[( 8, r)] = self.eb[( 5, r)] + self.grid_contents[(10, r)] = self.eb[( 6, r)] + for c in range(0, self.cols): + self.eb[(c, r)].add(self.seen_cards[(c, r)]) + +# add the cell contents to the table + for c in range(0, self.cols + 4): + for r in range(0, self.rows): + self.grid.attach(self.grid_contents[(c, r)], c, c+1, r, r+1, xpadding = 1, ypadding = 1) + + self.parent.add(self.grid) + + def translate_cards(self, old_cards): + pass + + def update(self, new_hand_id): + cards = self.db_connection.get_cards(new_hand_id) + self.clear() + + cards = self.translate_cards(cards) + for c in cards.keys(): + self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name']) + + for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'), + (4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')): + if not cards[c][i[1]] == "": + self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \ + set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])]) + + xml_text = self.db_connection.get_xml(new_hand_id) + hh = HandHistory.HandHistory(xml_text, ('BETTING')) + +# action in tool tips for 3rd street cards + tip = "%s" % hh.BETTING.rounds[0] + for c in (0, 1, 2): + for r in range(0, self.rows): + self.eb[(c, r)].set_tooltip_text(tip) + +# action in tools tips for later streets + round_to_col = (0, 3, 4, 5, 6) + for round in range(1, len(hh.BETTING.rounds)): + tip = "%s" % hh.BETTING.rounds[round] + for r in range(0, self.rows): + self.eb[(round_to_col[round], r)].set_tooltip_text(tip) + + def split_cards(self, card): + return (card[0], card[1].upper()) + + def clear(self): + for r in range(0, self.rows): + self.grid_contents[(1, r)].set_text(" ") + for c in range(0, 7): + self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')]) + self.eb[(c, r)].set_tooltip_text('') + def get_card_images(self): + card_images = {} + suits = ('S', 'H', 'D', 'C') + ranks = ('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'B') + pb = gtk.gdk.pixbuf_new_from_file("Cards01.png") + + for j in range(0, 14): + for i in range(0, 4): + temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42) + pb.copy_area(30*j, 42*i, 30, 42, temp_pb, 0, 0) + card_images[(ranks[j], suits[i])] = temp_pb + return(card_images) + +# cards are 30 wide x 42 high + +if __name__== "__main__": + + def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() # used only for testing + + def process_new_hand(source, condition): #callback from stdin watch -- testing only +# there is a new hand_id to be processed +# just read it and pass it to update + new_hand_id = sys.stdin.readline() + new_hand_id = new_hand_id.rstrip() # remove trailing whitespace + m.update(new_hand_id) + return(True) + + config = Configuration.Config() + db_connection = Database.Database(config, 'fpdb', '') + + main_window = gtk.Window() + main_window.set_keep_above(True) + main_window.connect("destroy", destroy) + + m = Mucked(main_window, db_connection) + main_window.show_all() + + s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) + + gtk.main() diff --git a/SQL.py b/SQL.py new file mode 100644 index 00000000..072d478e --- /dev/null +++ b/SQL.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python +"""SQL.py + +Set up all of the SQL statements for a given game and database type. +""" +# Copyright 2008, Ray E. Barker +# +# 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 + +######################################################################## + +# Standard Library modules + +# pyGTK modules + +# FreePokerTools modules + +class Sql: + + def __init__(self, game = 'holdem', type = 'PT3'): + self.query = {} + +############################################################################ +# +# Support for the ptracks database, a cut down PT2 stud database. +# You can safely ignore this unless you are me. +# + if game == 'razz' and type == 'ptracks': + + self.query['get_table_name'] = "select table_name from game where game_id = %s" + + self.query['get_last_hand'] = "select max(game_id) from game" + + self.query['get_recent_hands'] = "select game_id from game where game_id > %(last_hand)d" + + self.query['get_xml'] = "select xml from hand_history where game_id = %s" + + self.query['get_player_id'] = """ + select player_id from players + where screen_name = %(player)s + """ + + self.query['get_hand_info'] = """ + SELECT + game_id, + CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, + total_won-total_bet AS net + FROM game_players + WHERE game_id = %s AND player_id = 3 + """ + + self.query['get_cards'] = """ + select + seat_number, + screen_name, + hole_card_1, + hole_card_2, + hole_card_3, + hole_card_4, + hole_card_5, + hole_card_6, + hole_card_7 + from game_players, players + where game_id = %s and game_players.player_id = players.player_id + order by seat_number + """ + + self.query['get_stats_from_hand'] = """ + SELECT player_id, + count(*) AS n, + sum(pre_fourth_raise_n) AS pfr, + sum(fourth_raise_n) AS raise_n_2, + sum(fourth_ck_raise_n) AS cr_n_2, + sum(fifth_bet_raise_n) AS br_n_3, + sum(fifth_bet_ck_raise_n) AS cr_n_3, + sum(sixth_bet_raise_n) AS br_n_4, + sum(sixth_bet_ck_raise_n) AS cr_n_4, + sum(river_bet_raise_n) AS br_n_5, + sum(river_bet_ck_raise_n) AS cr_n_5, + sum(went_to_showdown_n) AS sd, + sum(saw_fourth_n) AS saw_f, + sum(raised_first_pf) AS first_pfr, + sum(vol_put_money_in_pot) AS vpip, + sum(limp_with_prev_callers) AS limp_w_callers, + + sum(ppossible_actions) AS poss_a_pf, + sum(pfold) AS fold_pf, + sum(pcheck) AS check_pf, + sum(praise) AS raise_pf, + sum(pcall) AS raise_pf, + sum(limp_call_reraise_pf) AS limp_call_pf, + + sum(pfr_check) AS check_after_raise, + sum(pfr_call) AS call_after_raise, + sum(pfr_fold) AS fold_after_raise, + sum(pfr_bet) AS bet_after_raise, + sum(pfr_raise) AS raise_after_raise, + sum(folded_to_river_bet) AS fold_to_r_bet, + + sum(fpossible_actions) AS poss_a_2, + sum(ffold) AS fold_2, + sum(fcheck) AS check_2, + sum(fbet) AS bet_2, + sum(fraise) AS raise_2, + sum(fcall) AS raise_2, + + sum(fifpossible_actions) AS poss_a_3, + sum(fiffold) AS fold_3, + sum(fifcheck) AS check_3, + sum(fifbet) AS bet_3, + sum(fifraise) AS raise_3, + sum(fifcall) AS call_3, + + sum(spossible_actions) AS poss_a_4, + sum(sfold) AS fold_4, + sum(scheck) AS check_4, + sum(sbet) AS bet_4, + sum(sraise) AS raise_4, + sum(scall) AS call_4, + + sum(rpossible_actions) AS poss_a_5, + sum(rfold) AS fold_5, + sum(rcheck) AS check_5, + sum(rbet) AS bet_5, + sum(rraise) AS raise_5, + sum(rcall) AS call_5, + + sum(cold_call_pf) AS cc_pf, + sum(saw_fifth_n) AS saw_3, + sum(saw_sixth_n) AS saw_4, + sum(saw_river_n) AS saw_5 + FROM game_players + WHERE player_id in + (SELECT player_id FROM game_players + WHERE game_id = %s AND NOT player_id = %s) + GROUP BY player_id + """ +# alternate form of WHERE for above +# WHERE game_id = %(hand)d AND NOT player_id = %(hero)d) +# WHERE game_id = %s AND NOT player_id = %s) + + self.query['get_players_from_hand'] = """ + SELECT game_players.player_id, seat_number, screen_name + FROM game_players INNER JOIN players ON (game_players.player_id = players.player_id) + WHERE game_id = %s + """ + +###############################################################################3 +# Support for the Free Poker DataBase = fpdb http://fpdb.sourceforge.net/ +# + if type == 'fpdb': + + self.query['get_last_hand'] = "select max(id) from Hands" + + self.query['get_player_id'] = """ + select Players.id AS player_id from Players, Sites + where Players.name = %(player)s + and Sites.name = %(site)s + and Players.SiteId = Sites.id + """ + + self.query['get_stats_from_hand'] = """ + SELECT HudCache.playerId AS player_id, + sum(HDs) AS n, + sum(street0VPI) AS vpip, + sum(street0Aggr) AS pfr, + sum(street0_3B4BChance) AS TB_opp_0, + sum(street0_3B4BDone) AS TB_0, + sum(street1Seen) AS saw_f, + sum(street1Seen) AS saw_1, + sum(street2Seen) AS saw_2, + sum(street3Seen) AS saw_3, + sum(street4Seen) AS saw_4, + sum(sawShowdown) AS sd, + sum(street1Aggr) AS aggr_1, + sum(street2Aggr) AS aggr_2, + sum(street3Aggr) AS aggr_3, + sum(street4Aggr) AS aggr_4, + sum(otherRaisedStreet1) AS was_raised_1, + sum(otherRaisedStreet2) AS was_raised_2, + sum(otherRaisedStreet3) AS was_raised_3, + sum(otherRaisedStreet4) AS was_raised_4, + sum(foldToOtherRaisedStreet1) AS f_freq_1, + sum(foldToOtherRaisedStreet2) AS f_freq_2, + sum(foldToOtherRaisedStreet3) AS f_freq_3, + sum(foldToOtherRaisedStreet4) AS f_freq_4, + sum(wonWhenSeenStreet1) AS w_w_s_1, + sum(wonAtSD) AS wmsd, + sum(stealAttemptChance) AS steal_opp, + sum(stealAttempted) AS steal, + sum(foldBbToStealChance) AS SBstolen, + sum(foldedBbToSteal) AS BBnotDef, + sum(foldBbToStealChance) AS BBstolen, + sum(foldedSbToSteal) AS SBnotDef, + sum(street1CBChance) AS CB_opp_1, + sum(street1CBDone) AS CB_1, + sum(street2CBChance) AS CB_opp_2, + sum(street2CBDone) AS CB_2, + sum(street3CBChance) AS CB_opp_3, + sum(street3CBDone) AS CB_3, + sum(street4CBChance) AS CB_opp_4, + sum(street4CBDone) AS CB_4, + sum(foldToStreet1CBChance) AS f_cb_opp_1, + sum(foldToStreet1CBDone) AS f_cb_1, + sum(foldToStreet2CBChance) AS f_cb_opp_2, + sum(foldToStreet2CBDone) AS f_cb_2, + sum(foldToStreet3CBChance) AS f_cb_opp_3, + sum(foldToStreet3CBDone) AS f_cb_3, + sum(foldToStreet4CBChance) AS f_cb_opp_4, + sum(foldToStreet4CBDone) AS f_cb_4, + sum(totalProfit) AS net, + sum(street1CheckCallRaiseChance) AS ccr_opp_1, + sum(street1CheckCallRaiseDone) AS ccr_1, + sum(street2CheckCallRaiseChance) AS ccr_opp_2, + sum(street2CheckCallRaiseDone) AS ccr_2, + sum(street3CheckCallRaiseChance) AS ccr_opp_3, + sum(street3CheckCallRaiseDone) AS ccr_3, + sum(street4CheckCallRaiseChance) AS ccr_opp_4, + sum(street4CheckCallRaiseDone) AS ccr_4 + FROM HudCache, Hands + WHERE HudCache.PlayerId in + (SELECT PlayerId FROM HandsPlayers + WHERE handId = %s) + AND Hands.id = %s + AND Hands.gametypeId = HudCache.gametypeId + GROUP BY HudCache.PlayerId + """ +# AND PlayerId LIKE %s +# HudCache.gametypeId AS gametypeId, +# activeSeats AS n_active, +# position AS position, +# HudCache.tourneyTypeId AS tourneyTypeId, + + self.query['get_players_from_hand'] = """ + SELECT HandsPlayers.playerId, seatNo, name + FROM HandsPlayers INNER JOIN Players ON (HandsPlayers.playerId = Players.id) + WHERE handId = %s + """ +# WHERE handId = %s AND Players.id LIKE %s + + self.query['get_table_name'] = """ + select tableName, maxSeats, category + from Hands,Gametypes + where Hands.id = %s + and Gametypes.id = Hands.gametypeId + """ + + self.query['get_cards'] = """ + select + seatNo AS seat_number, + name AS screen_name, + card1Value, card1Suit, + card2Value, card2Suit, + card3Value, card3Suit, + card4Value, card4Suit, + card5Value, card5Suit, + card6Value, card6Suit, + card7Value, card7Suit + from HandsPlayers, Players + where handID = %s and HandsPlayers.playerId = Players.id + order by seatNo + """ + +# self.query['get_hand_info'] = """ +# SELECT +# game_id, +# CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, +# total_won-total_bet AS net +# FROM game_players +# WHERE game_id = %s AND player_id = 3 +# """ + +if __name__== "__main__": +# just print the default queries and exit + s = Sql(game = 'razz', type = 'ptracks') + for key in s.query: + print "For query " + key + ", sql =" + print s.query[key] diff --git a/Stats.py b/Stats.py new file mode 100644 index 00000000..3531c017 --- /dev/null +++ b/Stats.py @@ -0,0 +1,618 @@ +#!/usr/bin/env python + +"""Manage collecting and formatting of stats and tooltips. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## + +# How to write a new stat: +# 1 You can see a listing of all the raw stats (e.g., from the HudCache table) +# by running Database.py as a stand along program. You need to combine +# those raw stats to get stats to present to the HUD. If you need more +# information than is in the HudCache table, then you have to write SQL. +# 2 The raw stats seen when you run Database.py are available in the Stats.py +# in the stat_dict dict. For example the number of vpips would be +# stat_dict[player]['vpip']. So the % vpip is +# float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']). You can see how the +# keys of stat_dict relate to the column names in HudCache by inspecting +# the proper section of the SQL.py module. +# 3 You have to write a small function for each stat you want to add. See +# the vpip() function for example. This function has to be protected from +# exceptions, using something like the try:/except: paragraphs in vpip. +# 4 The name of the function has to be the same as the of the stat used +# in the config file. +# 5 The stat functions have a peculiar return value, which is outlined in +# the do_stat function. This format is useful for tool tips and maybe +# other stuff. +# 6 For each stat you make add a line to the __main__ function to test it. + +# Standard Library modules +#import sys + +# pyGTK modules +import pygtk +import gtk + +# FreePokerTools modules +import Configuration +import Database + +def do_tip(widget, tip): + widget.set_tooltip_text(tip) + +def do_stat(stat_dict, player = 24, stat = 'vpip'): + return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) +# OK, for reference the tuple returned by the stat is: +# 0 - The stat, raw, no formating, eg 0.33333333 +# 1 - formatted stat with appropriate precision and punctuation, eg 33% +# 2 - formatted stat with appropriate precision, punctuation and a hint, eg v=33% +# 3 - same as #2 except name of stat instead of hint, eg vpip=33% +# 4 - the calculation that got the stat, eg 9/27 +# 5 - the name of the stat, useful for a tooltip, eg vpip + +########################################### +# functions that return individual stats +def vpip(stat_dict, player): + """ Voluntarily put $ in the pot.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'v=%3.1f' % (100*stat) + '%', + 'vpip=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']), + 'vpip' + ) + except: return (stat, + '%3.1f' % (0) + '%', + 'w=%3.1f' % (0) + '%', + 'wtsd=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'wtsd' + ) + +def pfr(stat_dict, player): + """ Preflop (3rd street) raise.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['pfr'])/float(stat_dict[player]['n']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'p=%3.1f' % (100*stat) + '%', + 'pfr=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']), + 'pfr' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'p=%3.1f' % (0) + '%', + 'pfr=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'pfr' + ) + +def wtsd(stat_dict, player): + """ Went to SD when saw flop/4th.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'w=%3.1f' % (100*stat) + '%', + 'wtsd=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['sd'], stat_dict[player]['saw_f']), + '% went to showdown' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'w=%3.1f' % (0) + '%', + 'wtsd=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% went to showdown' + ) + +def wmsd(stat_dict, player): + """ Won $ at showdown.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'w=%3.1f' % (100*stat) + '%', + 'wmsd=%3.1f' % (100*stat) + '%', + '(%f5.0/%d)' % (stat_dict[player]['wmsd'], stat_dict[player]['sd']), + '% won money at showdown' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'w=%3.1f' % (0) + '%', + 'wmsd=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% won money at showdown' + ) + +def saw_f(stat_dict, player): + """ Saw flop/4th.""" + try: + num = float(stat_dict[player]['saw_f']) + den = float(stat_dict[player]['n']) + stat = num/den + return (stat, + '%3.1f' % (100*stat) + '%', + 'sf=%3.1f' % (100*stat) + '%', + 'saw_f=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['saw_f'], stat_dict[player]['n']), + 'saw_f' + ) + except: + stat = 0.0 + num = 0 + den = 0 + return (stat, + '%3.1f' % (stat) + '%', + 'sf=%3.1f' % (stat) + '%', + 'saw_f=%3.1f' % (stat) + '%', + '(%d/%d)' % (num, den), + 'saw_f' + ) + +def n(stat_dict, player): + """ Number of hands played.""" + try: + return (stat_dict[player]['n'], + '%d' % (stat_dict[player]['n']), + 'n=%d' % (stat_dict[player]['n']), + 'n=%d' % (stat_dict[player]['n']), + '(%d)' % (stat_dict[player]['n']), + 'number hands seen' + ) + except: + return (0, + '%d' % (0), + 'n=%d' % (0), + 'n=%d' % (0), + '(%d)' % (0), + 'number hands seen' + ) + +def fold_f(stat_dict, player): + """ Folded flop/4th.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['fold_2'])/fold(stat_dict[player]['saw_f']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff=%3.1f' % (100*stat) + '%', + 'fold_f=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['fold_2'], stat_dict[player]['saw_f']), + 'folded flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff=%3.1f' % (0) + '%', + 'fold_f=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'folded flop/4th' + ) + +def steal(stat_dict, player): + """ Steal %.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'st=%3.1f' % (100*stat) + '%', + 'steal=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['steal'], stat_dict[player]['steal_opp']), + '% steal attempted' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'st=%3.1f' % (0) + '%', + 'steal=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% steal attempted' + ) + +def f_SB_steal(stat_dict, player): + """ Folded SB to steal.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['SBnotDef'])/float(stat_dict[player]['SBstolen']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'fSB=%3.1f' % (100*stat) + '%', + 'fSB_s=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['SBnotDef'], stat_dict[player]['SBstolen']), + '% folded SB to steal' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'fSB=%3.1f' % (0) + '%', + 'fSB_s=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% folded SB to steal' + ) + +def f_BB_steal(stat_dict, player): + """ Folded BB to steal.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['BBnotDef'])/float(stat_dict[player]['BBstolen']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'fBB=%3.1f' % (100*stat) + '%', + 'fBB_s=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['BBnotDef'], stat_dict[player]['BBstolen']), + '% folded BB to steal' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'fBB=%3.1f' % (0) + '%', + 'fBB_s=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% folded BB to steal' + ) + +def three_B_0(stat_dict, player): + """ Three bet preflop/3rd.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['TB_0'])/float(stat_dict[player]['TB_opp_0']) + return (stat, + '%3.1f' % (100*stat) + '%', + '3B=%3.1f' % (100*stat) + '%', + '3B_pf=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['TB_0'], stat_dict[player]['TB_opp_0']), + '% 3/4 Bet preflop/3rd' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + '3B=%3.1f' % (0) + '%', + '3B_pf=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% 3/4 Bet preflop/3rd' + ) + +def WMsF(stat_dict, player): + """ Won $ when saw flop/4th.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['w_w_s_1'])/float(stat_dict[player]['saw_1']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'wf=%3.1f' % (100*stat) + '%', + 'w_w_f=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['w_w_s_1'], stat_dict[player]['saw_f']), + '% won$/saw flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'wf=%3.1f' % (0) + '%', + 'w_w_f=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% won$/saw flop/4th' + ) + +def a_freq_1(stat_dict, player): + """ Flop/4th aggression frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['aggr_1'])/float(stat_dict[player]['saw_f']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'a1=%3.1f' % (100*stat) + '%', + 'a_fq_1=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_f']), + 'Aggression Freq flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a1=%3.1f' % (0) + '%', + 'a_fq_1=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq flop/4th' + ) + +def a_freq_2(stat_dict, player): + """ Turn/5th aggression frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['aggr_2'])/float(stat_dict[player]['saw_2']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'a2=%3.1f' % (100*stat) + '%', + 'a_fq_2=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_2'], stat_dict[player]['saw_2']), + 'Aggression Freq turn/5th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a2=%3.1f' % (0) + '%', + 'a_fq_2=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq turn/5th' + ) + +def a_freq_3(stat_dict, player): + """ River/6th aggression frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['aggr_3'])/float(stat_dict[player]['saw_3']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'a3=%3.1f' % (100*stat) + '%', + 'a_fq_3=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_1']), + 'Aggression Freq river/6th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a3=%3.1f' % (0) + '%', + 'a_fq_3=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq river/6th' + ) + +def a_freq_4(stat_dict, player): + """ 7th street aggression frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['aggr_4'])/float(stat_dict[player]['saw_4']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'a4=%3.1f' % (100*stat) + '%', + 'a_fq_4=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['aggr_4'], stat_dict[player]['saw_4']), + 'Aggression Freq 7th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'a4=%3.1f' % (0) + '%', + 'a_fq_4=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + 'Aggression Freq flop/4th' + ) + +def cb_1(stat_dict, player): + """ Flop continuation bet.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['CB_1'])/float(stat_dict[player]['CB_opp_1']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb1=%3.1f' % (100*stat) + '%', + 'cb_1=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_1'], stat_dict[player]['CB_opp_1']), + '% continuation bet flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb1=%3.1f' % (0) + '%', + 'cb_1=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet flop/4th' + ) + +def cb_2(stat_dict, player): + """ Turn continuation bet.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['CB_2'])/float(stat_dict[player]['CB_opp_2']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb2=%3.1f' % (100*stat) + '%', + 'cb_2=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_2'], stat_dict[player]['CB_opp_2']), + '% continuation bet turn/5th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb2=%3.1f' % (0) + '%', + 'cb_2=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet turn/5th' + ) + +def cb_3(stat_dict, player): + """ River continuation bet.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['CB_3'])/float(stat_dict[player]['CB_opp_3']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb3=%3.1f' % (100*stat) + '%', + 'cb_3=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_3'], stat_dict[player]['CB_opp_3']), + '% continuation bet river/6th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb3=%3.1f' % (0) + '%', + 'cb_3=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet river/6th' + ) + +def cb_4(stat_dict, player): + """ 7th street continuation bet.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['CB_4'])/float(stat_dict[player]['CB_opp_4']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'cb4=%3.1f' % (100*stat) + '%', + 'cb_4=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['CB_4'], stat_dict[player]['CB_opp_4']), + '% continuation bet 7th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'cb4=%3.1f' % (0) + '%', + 'cb_4=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% continuation bet 7th' + ) + +def ffreq_1(stat_dict, player): + """ Flop/4th fold frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['f_freq_1'])/float(stat_dict[player]['was_raised_1']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff1=%3.1f' % (100*stat) + '%', + 'ff_1=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_1'], stat_dict[player]['was_raised_1']), + '% fold frequency flop/4th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff1=%3.1f' % (0) + '%', + 'ff_1=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency flop/4th' + ) + +def ffreq_2(stat_dict, player): + """ Turn/5th fold frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['f_freq_2'])/float(stat_dict[player]['was_raised_2']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff2=%3.1f' % (100*stat) + '%', + 'ff_2=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_2'], stat_dict[player]['was_raised_2']), + '% fold frequency turn/5th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff2=%3.1f' % (0) + '%', + 'ff_2=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency turn/5th' + ) + +def ffreq_3(stat_dict, player): + """ River/6th fold frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['f_freq_3'])/float(stat_dict[player]['was_raised_3']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff3=%3.1f' % (100*stat) + '%', + 'ff_3=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_3'], stat_dict[player]['was_raised_3']), + '% fold frequency river/6th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff3=%3.1f' % (0) + '%', + 'ff_3=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency river/6th' + ) + +def ffreq_4(stat_dict, player): + """ 7th fold frequency.""" + stat = 0.0 + try: + stat = float(stat_dict[player]['f_freq_4'])/float(stat_dict[player]['was_raised_4']) + return (stat, + '%3.1f' % (100*stat) + '%', + 'ff4=%3.1f' % (100*stat) + '%', + 'ff_4=%3.1f' % (100*stat) + '%', + '(%d/%d)' % (stat_dict[player]['f_freq_4'], stat_dict[player]['was_raised_4']), + '% fold frequency 7th' + ) + except: + return (stat, + '%3.1f' % (0) + '%', + 'ff4=%3.1f' % (0) + '%', + 'ff_4=%3.1f' % (0) + '%', + '(%d/%d)' % (0, 0), + '% fold frequency 7th' + ) + +if __name__== "__main__": + c = Configuration.Config() + db_connection = Database.Database(c, 'fpdb', 'holdem') + h = db_connection.get_last_hand() + stat_dict = db_connection.get_stats_from_hand(h) + + for player in stat_dict.keys(): + print "player = ", player, do_stat(stat_dict, player = player, stat = 'vpip') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'pfr') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'wtsd') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'saw_f') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'n') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B_0') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_1') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_2') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_3') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_4') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_1') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_2') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_3') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_4') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_1') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_2') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3') + print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4') + + print "\n\nLegal stats:" + for attr in dir(): + if attr.startswith('__'): continue + if attr in ("Configuration", "Database", "GInitiallyUnowned", "gtk", "pygtk", + "player", "c", "db_connection", "do_stat", "do_tip", "stat_dict", + "h"): continue + print attr, eval("%s.__doc__" % (attr)) +# print " " % (attr) + + db_connection.close + diff --git a/Tables.py b/Tables.py new file mode 100644 index 00000000..d7d9533d --- /dev/null +++ b/Tables.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +"""Discover_Tables.py + +Inspects the currently open windows and finds those of interest to us--that is +poker table windows from supported sites. Returns a list +of Table_Window objects representing the windows found. +""" +# Copyright 2008, Ray E. Barker + +# +# 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 + +######################################################################## + +# Standard Library modules +import os +import sys +import re + +# Win32 modules + +if os.name == 'nt': + import win32gui + +# FreePokerTools modules +import Configuration + +class Table_Window: + def __str__(self): +# __str__ method for testing + temp = 'TableWindow object\n' + temp = temp + " name = %s\n site = %s\n number = %s\n title = %s\n" % (self.name, self.site, self.number, self.title) + temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) + temp = temp + " width = %d\n height = %d\n x = %d\n y = %d\n" % (self.width, self.height, self.x, self.y) + if getattr(self, 'tournament', 0): + temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table) + return temp + + def get_details(table): + table.game = 'razz' + table.max = 8 + table.struture = 'limit' + table.tournament = 0 + +def discover(c): + if os.name == 'posix': + tables = discover_posix(c) + return tables + elif os.name == 'nt': + tables = discover_nt(c) + return tables + elif ox.name == 'mac': + tables = discover_mac(c) + return tables + else: tables = {} + + return(tables) + +def discover_posix(c): + """ Poker client table window finder for posix/Linux = XWindows.""" + tables = {} + for listing in os.popen('xwininfo -root -tree').readlines(): +# xwininfo -root -tree -id 0xnnnnn gets the info on a single window + if re.search('Lobby', listing): continue + if re.search('Instant Hand History', listing): continue + if not re.search('Logged In as ', listing): continue + for s in c.supported_sites.keys(): + if re.search(c.supported_sites[s].table_finder, listing): + mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) + if mo.group(2) == '(has no name)': continue + if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup + tw = Table_Window() + tw.site = c.supported_sites[s].site_name + tw.number = mo.group(1) + tw.title = mo.group(2) + tw.width = int( mo.group(3) ) + tw.height = int( mo.group(4) ) + tw.x = int (mo.group(5) ) + tw.y = int (mo.group(6) ) + tw.title = re.sub('\"', '', tw.title) +# this rather ugly hack makes my fake table used for debugging work + if tw.title == "PokerStars.py": continue + +# use this eval thingie to call the title bar decoder specified in the config file + eval("%s(tw)" % c.supported_sites[s].decoder) + tables[tw.name] = tw + return tables +# +# The discover_xx functions query the system and report on the poker clients +# currently displayed on the screen. The discover_posix should give you +# some idea how to support other systems. +# +# discover_xx() returns a dict of TableWindow objects--one TableWindow +# object for each poker client table on the screen. +# +# Each TableWindow object must have the following attributes correctly populated: +# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site +# name specified in the config file. +# tw.number = This is the system id number for the client table window in the +# format that the system presents it. +# tw.title = The full title from the window title bar. +# tw.width, tw.height = The width and height of the window in pixels. This is +# the internal width and height, not including the title bar and +# window borders. +# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative +# to the top left of the display screen. This also does not include the +# title bar and window borders. To put it another way, this is the +# screen location of (0, 0) in the working window. + +def win_enum_handler(hwnd, titles): + titles[hwnd] = win32gui.GetWindowText(hwnd) + +def child_enum_handler(hwnd, children): + print hwnd, win32.GetWindowRect(hwnd) + +def discover_nt(c): + """ Poker client table window finder for Windows.""" +# +# I cannot figure out how to get the inside dimensions of the poker table +# windows. So I just assume all borders are 3 thick and all title bars +# are 29 high. No doubt this will be off when used with certain themes. +# + b_width = 3 + tb_height = 29 + titles = {} + tables = {} + win32gui.EnumWindows(win_enum_handler, titles) + for hwnd in titles.keys(): + if re.search('Logged In as', titles[hwnd]) and not re.search('Lobby', titles[hwnd]): + tw = Table_Window() +# tw.site = c.supported_sites[s].site_name + tw.number = hwnd + (x, y, width, height) = win32gui.GetWindowRect(hwnd) + tw.title = titles[hwnd] + tw.width = int( width ) - 2*b_width + tw.height = int( height ) - b_width - tb_height + tw.x = int( x ) + b_width + tw.y = int( y ) + tb_height + eval("%s(tw)" % "pokerstars_decode_table") + tw.site = "PokerStars" + + + tables[tw.name] = tw + return tables + +def discover_mac(c): + """ Poker client table window finder for Macintosh.""" + tables = {} + return tables + +def pokerstars_decode_table(tw): +# extract the table name OR the tournament number and table name from the title +# other info in title is redundant with data in the database + title_bits = re.split(' - ', tw.title) + name = title_bits[0] + mo = re.search('Tournament (\d+) Table (\d+)', name) + if mo: + tw.tournament = int( mo.group(1) ) + tw.table = int( mo.group(2) ) + tw.name = name + else: + tw.tournament = None + for pattern in [' no all-in', ' fast', ',']: + name = re.sub(pattern, '', name) + name = re.sub('\s+$', '', name) + tw.name = name + + mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball)', tw.title) + + tw.game = mo.group(1).lower() + tw.game = re.sub('\'', '', tw.game) + tw.game = re.sub('h/l', 'hi/lo', tw.game) + + mo = re.search('(No Limit|Pot Limit)', tw.title) + if mo: + tw.structure = mo.group(1).lower() + else: + tw.structure = 'limit' + + tw.max = None + if tw.game in ('razz', 'stud', 'stud hi/lo'): + tw.max = 8 + elif tw.game in ('5-card draw', 'triple draw 2-7 lowball'): + tw.max = 6 + elif tw.game == 'holdem': + pass + elif tw.game in ('omaha', 'omaha hi/lo'): + pass + +if __name__=="__main__": + c = Configuration.Config() + tables = discover(c) + + for t in tables.keys(): + print "t = ", t + print tables[t] + + print "press enter to continue" + sys.stdin.readline() From 32ac9bdaac1e73c3282b8883f7720047cbfa3496 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 23 Sep 2008 10:23:15 -0400 Subject: [PATCH 098/262] Small patch from Carl to allow for 50BB tables on Stars --- Tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tables.py b/Tables.py index d7d9533d..41db0645 100644 --- a/Tables.py +++ b/Tables.py @@ -172,7 +172,7 @@ def pokerstars_decode_table(tw): tw.name = name else: tw.tournament = None - for pattern in [' no all-in', ' fast', ',']: + for pattern in [' no all-in', ' fast', ',', ' 50BB min']: name = re.sub(pattern, '', name) name = re.sub('\s+$', '', name) tw.name = name From 2bfe67ade2410a346a3a98a0a7106587f2f10aa1 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 23 Sep 2008 20:33:47 -0400 Subject: [PATCH 099/262] inserted HUD_config.xml.example -->supports Stars and Tilt --- HUD_config.xml | 175 ----------------------------------------- HUD_config.xml.example | 146 ++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 175 deletions(-) delete mode 100644 HUD_config.xml create mode 100644 HUD_config.xml.example diff --git a/HUD_config.xml b/HUD_config.xml deleted file mode 100644 index 518e2af6..00000000 --- a/HUD_config.xml +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HUD_config.xml.example b/HUD_config.xml.example new file mode 100644 index 00000000..423787c5 --- /dev/null +++ b/HUD_config.xml.example @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6fcdda9dbbc5f8abfb8fd3b07ff1e93ad1bf53ab Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 23 Sep 2008 20:37:43 -0400 Subject: [PATCH 100/262] added Full Tilt support for Linux users --- Tables.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Tables.py b/Tables.py index 41db0645..d300b942 100644 --- a/Tables.py +++ b/Tables.py @@ -42,7 +42,7 @@ class Table_Window: # __str__ method for testing temp = 'TableWindow object\n' temp = temp + " name = %s\n site = %s\n number = %s\n title = %s\n" % (self.name, self.site, self.number, self.title) - temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) +# temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) temp = temp + " width = %d\n height = %d\n x = %d\n y = %d\n" % (self.width, self.height, self.x, self.y) if getattr(self, 'tournament', 0): temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table) @@ -75,9 +75,11 @@ def discover_posix(c): # xwininfo -root -tree -id 0xnnnnn gets the info on a single window if re.search('Lobby', listing): continue if re.search('Instant Hand History', listing): continue - if not re.search('Logged In as ', listing): continue + if not re.search('Logged In as ', listing, re.IGNORECASE): continue for s in c.supported_sites.keys(): if re.search(c.supported_sites[s].table_finder, listing): + print "listing = ", listing + print "found ", c.supported_sites[s].site_name mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) if mo.group(2) == '(has no name)': continue if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup @@ -90,12 +92,12 @@ def discover_posix(c): tw.x = int (mo.group(5) ) tw.y = int (mo.group(6) ) tw.title = re.sub('\"', '', tw.title) -# this rather ugly hack makes my fake table used for debugging work - if tw.title == "PokerStars.py": continue # use this eval thingie to call the title bar decoder specified in the config file eval("%s(tw)" % c.supported_sites[s].decoder) tables[tw.name] = tw + print "found table named ", tw.name + print tw return tables # # The discover_xx functions query the system and report on the poker clients @@ -199,6 +201,18 @@ def pokerstars_decode_table(tw): elif tw.game in ('omaha', 'omaha hi/lo'): pass +def fulltilt_decode_table(tw): +# extract the table name OR the tournament number and table name from the title +# other info in title is redundant with data in the database + title_bits = re.split(' - ', tw.title) + name = title_bits[0] + tw.tournament = None +# for pattern in [r' (6 max)', r' (heads up)', r' (deep)', r' (deep hu)', r' (deep 6)', +# r' (2)', r' (edu)', r' (edu, 6 max)', r' (6)' ]: +# name = re.sub(pattern, '', name) + (tw.name, trash) = name.split(r' (', 1) + tw.name = tw.name.rstrip() + if __name__=="__main__": c = Configuration.Config() tables = discover(c) From d8eadd1d178fca92edcc388fa8b4beee916e9394 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 23 Sep 2008 20:57:23 -0400 Subject: [PATCH 101/262] added support for Full Tilt for Windows users --- Tables.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Tables.py b/Tables.py index d300b942..f2b229d0 100644 --- a/Tables.py +++ b/Tables.py @@ -140,9 +140,8 @@ def discover_nt(c): tables = {} win32gui.EnumWindows(win_enum_handler, titles) for hwnd in titles.keys(): - if re.search('Logged In as', titles[hwnd]) and not re.search('Lobby', titles[hwnd]): + if re.search('Logged In as', titles[hwnd], re.IGNORECASE) and not re.search('Lobby', titles[hwnd]): tw = Table_Window() -# tw.site = c.supported_sites[s].site_name tw.number = hwnd (x, y, width, height) = win32gui.GetWindowRect(hwnd) tw.title = titles[hwnd] @@ -150,10 +149,17 @@ def discover_nt(c): tw.height = int( height ) - b_width - tb_height tw.x = int( x ) + b_width tw.y = int( y ) + tb_height - eval("%s(tw)" % "pokerstars_decode_table") - tw.site = "PokerStars" + if re.search('Logged In as', titles[hwnd]): + tw.site = "PokerStars" + elif re.search('Logged In As', titles[hwnd]): + tw.site = "Full Tilt" + else: + tw.site = "Unknown" - + if not tw.site == "Unknown": + eval("%s(tw)" % c.supported_sites[s].decoder) + else: + tw.name = "Unknown" tables[tw.name] = tw return tables From a0cd7e5940d4499666b5c228748e254a7d1d2941 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 03:08:12 +0100 Subject: [PATCH 102/262] p96 - updated webpage here in git to latest online version - from now on pls git website changes again --- docs/known-bugs-and-planned-features.txt | 3 +- website/config.php | 16 - website/contact.php | 56 +- website/docs-abreviations.php | 132 +-- website/docs-benchmarks.php | 23 + website/docs-git-instructions.php | 124 +- website/docs-hudHowTo.php | 169 +++ website/docs-install-gentoo.php | 172 ++- website/docs-install-windows.php | 315 +++-- website/docs-overview.php | 170 ++- website/docs-requirements.php | 322 +++-- website/docs-usage.php | 130 +- website/docs.php | 75 +- website/features.php | 96 +- website/footer.php | 8 +- website/header.php | 36 +- website/img/00.mySqlWebsite1.jpg | Bin 0 -> 10881 bytes website/img/01.mySqlWebsite2.jpg | Bin 0 -> 17420 bytes website/img/02.mySqlSetup1.jpg | Bin 0 -> 13621 bytes website/img/03.mySqlSetup3.jpg | Bin 0 -> 10370 bytes website/img/04.mySqlSetup5.jpg | Bin 0 -> 10364 bytes website/img/05.mySqlSetup7.jpg | Bin 0 -> 17188 bytes website/img/06.mySqlConfig1.jpg | Bin 0 -> 17465 bytes website/img/07.mySqlConfig2.jpg | Bin 0 -> 12647 bytes website/img/08.mySqlConfig4.jpg | Bin 0 -> 16770 bytes website/img/09.mySqlConfig7.jpg | Bin 0 -> 16282 bytes website/img/10.mySqlConfig8.jpg | Bin 0 -> 15379 bytes website/img/11.mySqlConfig10.jpg | Bin 0 -> 9384 bytes website/img/12.mySqlConfig11.jpg | Bin 0 -> 9311 bytes website/img/13.run.jpg | Bin 0 -> 5010 bytes website/img/14.shellCdToMySql.jpg | Bin 0 -> 13520 bytes website/img/15.shellMySqlRootLogin.jpg | Bin 0 -> 11586 bytes website/img/16.shellMySqlPrompt.jpg | Bin 0 -> 8986 bytes website/img/17.shellMySqlCreateDB.jpg | Bin 0 -> 4986 bytes website/img/18.shellMySqlCreateUser.jpg | Bin 0 -> 20457 bytes website/img/19.shellMySqlUserCreated.jpg | Bin 0 -> 7472 bytes website/img/20.pythonInst.jpg | Bin 0 -> 12024 bytes website/img/21.pythonInst4.jpg | Bin 0 -> 5839 bytes website/img/22.mySqlPythonInst1.jpg | Bin 0 -> 13313 bytes website/img/23.shellMkDirGtk.jpg | Bin 0 -> 9544 bytes website/img/24.setGtkPath1.jpg | Bin 0 -> 14898 bytes website/img/25.setGtkPath2.jpg | Bin 0 -> 17477 bytes website/img/26.setGtkPath3.jpg | Bin 0 -> 14195 bytes website/img/27.pycairoInst.jpg | Bin 0 -> 11334 bytes website/img/28.pygobjectInst.jpg | Bin 0 -> 11875 bytes website/img/29.pyGtkInst.jpg | Bin 0 -> 12495 bytes website/img/30.shellMkDirProfiles.jpg | Bin 0 -> 16895 bytes website/img/31.editDbProfile.jpg | Bin 0 -> 9934 bytes website/img/32.startFpdb.jpg | Bin 0 -> 22452 bytes website/img/docs.HudHowTo1.png | Bin 0 -> 29883 bytes website/img/docs.HudHowTo2.png | Bin 0 -> 6339 bytes website/img/docs.HudHowTo3.png | Bin 0 -> 345650 bytes website/img/docs.HudHowTo4.png | Bin 0 -> 5546 bytes website/img/docs.HudHowTo5.png | Bin 0 -> 1804 bytes website/img/docs.HudHowTo6.png | Bin 0 -> 3449 bytes website/img/docs.HudHowTo7.png | Bin 0 -> 11620 bytes website/index.php | 48 +- website/license.php | 1376 +++++++++++----------- website/screenshots.php | 49 +- website/sidebar.php | 26 +- website/style.css | 151 +-- 61 files changed, 1843 insertions(+), 1654 deletions(-) delete mode 100644 website/config.php create mode 100644 website/docs-benchmarks.php create mode 100644 website/docs-hudHowTo.php create mode 100644 website/img/00.mySqlWebsite1.jpg create mode 100644 website/img/01.mySqlWebsite2.jpg create mode 100644 website/img/02.mySqlSetup1.jpg create mode 100644 website/img/03.mySqlSetup3.jpg create mode 100644 website/img/04.mySqlSetup5.jpg create mode 100644 website/img/05.mySqlSetup7.jpg create mode 100644 website/img/06.mySqlConfig1.jpg create mode 100644 website/img/07.mySqlConfig2.jpg create mode 100644 website/img/08.mySqlConfig4.jpg create mode 100644 website/img/09.mySqlConfig7.jpg create mode 100644 website/img/10.mySqlConfig8.jpg create mode 100644 website/img/11.mySqlConfig10.jpg create mode 100644 website/img/12.mySqlConfig11.jpg create mode 100644 website/img/13.run.jpg create mode 100644 website/img/14.shellCdToMySql.jpg create mode 100644 website/img/15.shellMySqlRootLogin.jpg create mode 100644 website/img/16.shellMySqlPrompt.jpg create mode 100644 website/img/17.shellMySqlCreateDB.jpg create mode 100644 website/img/18.shellMySqlCreateUser.jpg create mode 100644 website/img/19.shellMySqlUserCreated.jpg create mode 100644 website/img/20.pythonInst.jpg create mode 100644 website/img/21.pythonInst4.jpg create mode 100644 website/img/22.mySqlPythonInst1.jpg create mode 100644 website/img/23.shellMkDirGtk.jpg create mode 100644 website/img/24.setGtkPath1.jpg create mode 100644 website/img/25.setGtkPath2.jpg create mode 100644 website/img/26.setGtkPath3.jpg create mode 100644 website/img/27.pycairoInst.jpg create mode 100644 website/img/28.pygobjectInst.jpg create mode 100644 website/img/29.pyGtkInst.jpg create mode 100644 website/img/30.shellMkDirProfiles.jpg create mode 100644 website/img/31.editDbProfile.jpg create mode 100644 website/img/32.startFpdb.jpg create mode 100644 website/img/docs.HudHowTo1.png create mode 100644 website/img/docs.HudHowTo2.png create mode 100644 website/img/docs.HudHowTo3.png create mode 100644 website/img/docs.HudHowTo4.png create mode 100644 website/img/docs.HudHowTo5.png create mode 100644 website/img/docs.HudHowTo6.png create mode 100644 website/img/docs.HudHowTo7.png diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index c6959172..3e227448 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -13,7 +13,7 @@ update requirements to include new pgsql interface lib ebuild: support pgsql fix HUD config location and update release script accordingly -windows integrated installer +update website for windows installer update install-in-gentoo on website update ebuild and ubuntu guide for HUD_config.xml @@ -43,6 +43,7 @@ hole/board cards are not correctly stored in the db for stud games HORSE (and presumably other mixed games) hand history files not handled correctly Some MTTs won't import (rebuys??) Many STTs won't import +redirect stderr before beta =========== diff --git a/website/config.php b/website/config.php deleted file mode 100644 index 403b9cf2..00000000 --- a/website/config.php +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/website/contact.php b/website/contact.php index ce12d333..e289ba3c 100644 --- a/website/contact.php +++ b/website/contact.php @@ -1,29 +1,27 @@ - - -
    - -

    Contact

    - -

    The best means of contact are the sourceforge page: Use the bug, feature request or patch functions or just post in the forum.

    - -

    Alternatively feel free to contact me directly:

    - -

    mail: steffen(at)sycamoretest.info
    -jabber/xmpp/Google Talk: as above
    -ICQ: 7806355
    -MSN: steffenjf(at)gmx.de (don't email that)

    - -
    - - + + +
    + +

    Contact

    + +

    The best means of contact are the sourceforge page: Use the bug, feature request or patch functions or just post in the forum.

    + +

    Alternatively feel free to contact me directly:

    + +

    mail: steffen(at)sycamoretest.info
    +jabber/xmpp/Google Talk: as above
    +ICQ: 7806355
    +MSN: steffenjf(at)gmx.de (don't email that)

    + +
    + + diff --git a/website/docs-abreviations.php b/website/docs-abreviations.php index dfc67387..0463f2be 100644 --- a/website/docs-abreviations.php +++ b/website/docs-abreviations.php @@ -1,67 +1,65 @@ - - -
    - -

    Abreviations

    - -

    HUD/table viewer
    -================
    -A3-7=3rd-7th street Complete/Raise percentage
    -AF=Flop Bet/Raise percentage
    -AT=River Bet/Raise percentage
    -AR=Turn Bet/Raise percentage
    -F3-7=3rd-7th street Fold percentage
    -FF=Flop Fold percentage
    -FR=River Fold percentage
    -FT=Turn Fold percentage
    -HD=Hands
    -PF3B4B=Pre Flop 3Bet or 4Bet
    -PFR=Pre Flop Raise
    -Postf A=Postflop (ie. flop+turn+river) Aggression%
    -Postf F=Postflop Fold %
    -SD/F=Showdown/Flop=WtSD=How often player went to showdown when he saw the flop
    -W$wsF=Won $ when he saw flop
    -W$@SD=Won $ at showdown
    -VPI3=Voluntary Put In on 3rd Street (ie. call+complete+raise)
    -VPIP=Voluntary Put In Preflop (ie. call+raise)
    -
    -Other
    -=====
    -CLI=Command Line Interface (Shell, Terminal, "DOS-window")
    -FTP=Full Tilt Poker
    -GUI=Graphical User Interface (normal interface with buttons and menus)
    -HUD=Heads-Up Display (shows stats directly in the poker software)
    -PS=PokerStars
    -MTT=Multi Table Tournament
    -SnG=Sit and Go
    -
    -License
    -=======
    -Trademarks of third parties have been used under Fair Use or similar laws.
    -
    -Copyright 2008 Steffen Jobbagy-Felso
    -Permission is granted to copy, distribute and/or modify this
    -document under the terms of the GNU Free Documentation License,
    -Version 1.2 as published by the Free Software Foundation; with
    -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    -Texts. A copy of the license can be found in fdl-1.2.txt
    -
    -The program itself is licensed under AGPLv3, see agpl-3.0.txt
    -

    - -
    - - + + +
    + +

    Abreviations

    + +

    HUD/table viewer
    +================
    +A3-7=3rd-7th street Complete/Raise percentage
    +AF=Flop Bet/Raise percentage
    +AT=River Bet/Raise percentage
    +AR=Turn Bet/Raise percentage
    +F3-7=3rd-7th street Fold percentage
    +FF=Flop Fold percentage
    +FR=River Fold percentage
    +FT=Turn Fold percentage
    +HD=Hands
    +PF3B4B=Pre Flop 3Bet or 4Bet
    +PFR=Pre Flop Raise
    +Postf A=Postflop (ie. flop+turn+river) Aggression%
    +Postf F=Postflop Fold %
    +SD/F=Showdown/Flop=WtSD=How often player went to showdown when he saw the flop
    +W$wsF=Won $ when he saw flop
    +W$@SD=Won $ at showdown
    +VPI3=Voluntary Put In on 3rd Street (ie. call+complete+raise)
    +VPIP=Voluntary Put In Preflop (ie. call+raise)
    +
    +Other
    +=====
    +CLI=Command Line Interface (Shell, Terminal, "DOS-window")
    +FTP=Full Tilt Poker
    +GUI=Graphical User Interface (normal interface with buttons and menus)
    +HUD=Heads-Up Display (shows stats directly in the poker software)
    +PS=PokerStars
    +MTT=Multi Table Tournament
    +SnG=Sit and Go
    +
    +License
    +=======
    +Trademarks of third parties have been used under Fair Use or similar laws.
    +
    +Copyright 2008 Steffen Jobbagy-Felso
    +Permission is granted to copy, distribute and/or modify this
    +document under the terms of the GNU Free Documentation License,
    +Version 1.2 as published by the Free Software Foundation; with
    +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    +Texts. A copy of the license can be found in fdl-1.2.txt
    +
    +The program itself is licensed under AGPLv3, see agpl-3.0.txt
    +

    + +
    + + diff --git a/website/docs-benchmarks.php b/website/docs-benchmarks.php new file mode 100644 index 00000000..a3629827 --- /dev/null +++ b/website/docs-benchmarks.php @@ -0,0 +1,23 @@ + + +
    + +

    Benchmarks

    + +

    Check this page from time to time and they will come.

    + + +
    + + diff --git a/website/docs-git-instructions.php b/website/docs-git-instructions.php index 9975f808..be2de79d 100644 --- a/website/docs-git-instructions.php +++ b/website/docs-git-instructions.php @@ -1,63 +1,61 @@ - - -
    - -

    Git Instructions

    - -

    Hi, welcome to my minimal git guide for fpdb devs!
    -You can use a git version just as user as well of course, but as there are generally hardly tested it is not advised.
    -I'll expand this on request, if you have any questions just send me a mail at steffen(at)sycamoretest.info. There's also a bunch of instructions at http://www.assembla.com/spaces/fpdb/trac_git_tool

    - -

    0. Getting it

    -

    To get git for gentoo just do emerge git -av
    -To get it for Windows go to http://code.google.com/p/msysgit/downloads/list and install it. -

    1. Cloning the fpdb git tree

    -

    Just create a new directory (lets say ~/fpdb/ ), go into it and type:
    -git clone git://git.assembla.com/fpdb.git

    -

    2. Making your changes

    -

    You can use whatever you want to do edit the files. I personally use nedit and occassionally Eclipse.

    -

    3. Making a (local) commit

    -

    Unlike in svn you don't need to be online to make your commits. First we need to tell git what to commit, so go to the root of your fpdb directory and type:
    -git-add--interactive
    -Now press u and enter. It will display a list of all changed files. If you want to commit all files just press * and enter twice to return to the main menu. If you want to commit only certain ones press the number of the file and enter and repeat until you have all the files. Then press enter again to return to the main menu.
    -If you added any new files press a and Enter, then type the number of your new file and press Enter twice. Press q to leave git-add--interactive.
    -Now create a file for your commit message (I call it since_last_commit.txt) but don't add this to the repository. In the first line of this file put a summary of your changes. Then give some details of your changes, try to mention anything non-trivial and definitely any user-visible bug fixes.
    -Then run this:
    -git-commit -F since_last_commit.txt
    -

    4a. Pushing the changes to your own public git tree

    -

    Do this OR 4b, not both.
    -todo

    -

    4b. Preparing changeset for emailing/uploading

    -

    Do this OR 4a, not both.
    -todo

    -

    5. Pulling updates from the main tree

    -

    todo

    -

    License

    -

    Trademarks of third parties have been used under Fair Use or similar laws.
    -
    -Copyright 2008 Steffen Jobbagy-Felso
    -Permission is granted to copy, distribute and/or modify this
    -document under the terms of the GNU Free Documentation License,
    -Version 1.2 as published by the Free Software Foundation; with
    -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    -Texts. A copy of the license can be found in fdl-1.2.txt
    -
    -The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - - -
    - - + + +
    + +

    Git Instructions

    + +

    Hi, welcome to my minimal git guide for fpdb devs!
    +You can use a git version just as user as well of course, but as there are generally hardly tested it is not advised.
    +I'll expand this on request, if you have any questions just send me a mail at steffen(at)sycamoretest.info. There's also a bunch of instructions at http://www.assembla.com/spaces/fpdb/trac_git_tool

    + +

    0. Getting it

    +

    To get git for gentoo just do emerge git -av
    +To get it for Windows go to http://code.google.com/p/msysgit/downloads/list and install it. +

    1. Cloning the fpdb git tree

    +

    Just create a new directory (lets say ~/fpdb/ ), go into it and type:
    +git clone git://git.assembla.com/fpdb.git

    +

    2. Making your changes

    +

    You can use whatever you want to do edit the files. I personally use nedit and occassionally Eclipse.

    +

    3. Making a (local) commit

    +

    Unlike in svn you don't need to be online to make your commits. First we need to tell git what to commit, so go to the root of your fpdb directory and type:
    +git-add--interactive
    +Now press u and enter. It will display a list of all changed files. If you want to commit all files just press * and enter twice to return to the main menu. If you want to commit only certain ones press the number of the file and enter and repeat until you have all the files. Then press enter again to return to the main menu.
    +If you added any new files press a and Enter, then type the number of your new file and press Enter twice. Press q to leave git-add--interactive.
    +Now create a file for your commit message (I call it since_last_commit.txt) but don't add this to the repository. In the first line of this file put a summary of your changes. Then give some details of your changes, try to mention anything non-trivial and definitely any user-visible bug fixes.
    +Then run this:
    +git-commit -F since_last_commit.txt
    +

    4a. Pushing the changes to your own public git tree

    +

    Do this OR 4b, not both.
    +todo

    +

    4b. Preparing changeset for emailing/uploading

    +

    Do this OR 4a, not both.
    +todo

    +

    5. Pulling updates from the main tree

    +

    todo

    +

    License

    +

    Trademarks of third parties have been used under Fair Use or similar laws.
    +
    +Copyright 2008 Steffen Jobbagy-Felso
    +Permission is granted to copy, distribute and/or modify this
    +document under the terms of the GNU Free Documentation License,
    +Version 1.2 as published by the Free Software Foundation; with
    +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    +Texts. A copy of the license can be found in fdl-1.2.txt
    +
    +The program itself is licensed under AGPLv3, see agpl-3.0.txt

    + + +
    + + diff --git a/website/docs-hudHowTo.php b/website/docs-hudHowTo.php new file mode 100644 index 00000000..cf501c44 --- /dev/null +++ b/website/docs-hudHowTo.php @@ -0,0 +1,169 @@ + + +
    + +

    How To Use the HUD

    + +

    3 September, 2008

    + +

    fpdb version alpha 3

    + +

    Initial Configuration

    + +

    Install and configure the import/tracker program as detailed elsewhere. You should have this line in your default.conf file.

    + +imp-callFpdbHud=True + +

    When you downloaded fpdb you got an example HUD configuration file named HUD_config.xml.

    + +

    1. Open the subdirectory where you installed fpdb.

    + +

    2. Make a backup copy of HUD_config.xml.

    + +

    3. Check that the db_user and db_pass parameters are the ones you specified during database setup. This line is near the bottom of your config file and specifies the database parameters to be used.

    + +<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="fpdb" db_type="fpdb"></database> + +

    This should allow you to use the HUD with the stats and layout in the example configuration file.

    + +

    Running the HUD

    + +

    1. Open fpdb and select Auto Import from the menu. The fpdb main screen will change to show the autoimport dialog.

    + +Image of HUD + +

    Check that the path is pointing to your hand history subdirectory, if it is not, then click the browse button and select it. ?You can also adjust the interval between imports. Smaller intervals will get your updated HUD data more often, but might cause lag. If you experience lag, increase the interval.

    + +

    2. Click "Start Autoimport" to start the import. fpdb will automatically start the HUD. When the HUD starts it will open a HUD main window.

    + +Image of HUD + +

    2. This window currently has no purpose other than providing a close button that will cause the HUD to exit.

    + +

    3. Play a hand of poker (good luck). A few seconds after the completion of the hand the stat windows should overlay the poker client window.

    + +Image of HUD + +

    You will also see a small main window for each table that has a HUD. Clicking the close button on that window will kill the HUD stat windows for that table. The stat windows will not go away automatically when you close the table.

    + + + +

    4. Adjust the positions of the stat windows. By default, the stat windows are created without decorations (title bar, border, etc.). Double clicking on a stat will add the decorations to that stat window. You can then use the title bar to move the window and double click again to make the decorations disappear.

    + + + + + +

    5. So play some poker: raise, bet, float, get all-in. You can find out what each stat is by hovering the mouse over the stat and looking at the tool tip. The tool tip also has the name of the player that the stat corresponds to, so it is useful in figuring out which stat window goes where if your windows are not in the right place. You can also get a pop up window with additional stats by single clicking on a stat.

    + + + +

    These windows do not automatically update when a new hand is imported, but they can be moved around the same way the stat window are moved. Single clicking anywhere on the popup will make it disappear.

    + +

    Configuring Stat Layouts

    + +

    OK, back to the HUD_config.xml file--saving a backup would be a good idea. Before you ask, yes, there will be a neat configuration function in the HUD, to make this quicker and easier. We thought you would prefer to have the HUD now rather than wait for us to write the configuration code.

    + +

    1. Open your HUD_config.xml file in you text editor and scroll down to site entry for the layout you want to configure. For example, if you want to change a layout for Pokerstars, find the line that starts like this.

    + +<site site_name="PokerStars" ... + +

    Below that line you will find several blocks of lines defining the stat layouts for tables with the various numbers of seats. For example the layout for 6-seated tables looks like this:

    + +<layout max="6" width="792" height="546" fav_seat="0>
    +<location seat="1" x="681" y="119"> </location>
    +<location seat="2" x="681" y="301"> </location>
    +<location seat="3" x="487" y="369"> </location>
    +<location seat="4" x="226" y="369"> </location>
    +<location seat="5" x="0" y="301"> </location>
    +<location seat="6" x="0" y="119"> </location>
    +</layout>
    + +

    The first line of this layout specifies that it is for a 6-max (max="6") table that has been sized to 792 x 546 (width="792" height="546"). The fav_seat parameter is not used at this time.

    + +

    The next 6 lines specify where the stat windows are placed on the poker client window. The x and y positions are measured from the inside upper left of the poker client window. That is x = 0, y = 0 would be the first usable pixel to the right of the window border and below the title bar.

    + +

    So if you are using the layout in the example above and decide that the stat window for seat 3 is being place 9 pixels too high, you would change the line for seat="3" to be:

    + +<location seat="3" x="487" y="378"> </location> + +

    If you use smaller or larger client windows you should correct the width and height parameters so that they are up-to-date when automatic resizing is implemented.

    + +

    Configuring the Stats Shown in the stat windows

    + +

    The definition of the stat window stats is in the "supported games" paragraph of the HUD_config.xml file. For example:

    + +<game game_name="studhilo" db="fpdb" rows="2" cols="3">
    +<stat row="0" col="0" stat_name="vpip" tip="tip1" click="tog_decorate" popup="default"> </stat>
    +<stat row="0" col="1" stat_name="pfr" tip="tip1" click="tog_decorate" popup="default"> </stat>
    +<stat row="0" col="2" stat_name="ffreq_1" tip="tip1" click="tog_decorate" popup="default"> </stat>
    +<stat row="1" col="0" stat_name="n" tip="tip1" click="tog_decorate" popup="default"> </stat>
    +<stat row="1" col="1" stat_name="wtsd" tip="tip1" click="tog_decorate" popup="default"> </stat>
    +<stat row="1" col="2" stat_name="wmsd" tip="tip1" click="tog_decorate" popup="default"> </stat>
    +</game>
    + +

    The first line specifies the game that this stat paragraph is used for (game = "studhilo") and the number of rows and columns in the stat window. In this case we have specified 2 rows and 3 columns so we can have 2x3 = 6 stats. Rows and columns are numbered from 0, so the 3 columns are numbered 0, 1, and 2.

    + +

    The subsequent lines in the stat paragraph specify which stats are displayed in the various parts of the window. In this example, vpip is displayed in col 0, row 0.

    + +

    So to create stat windows with 4 columns of 2 rows you would change the cols parameter in the first line to cols = "4" and add 2 additional rows to specify the stats for row 2, col 3 and row 1, col 3.

    + +

    The click and tip parameters in this paragraph are not currently used. The popup parameter is explained in the next section.

    + +

    Configuring Popup Windows

    + +

    Each stat location can display a different popup window when clicked. In the example just above, each of the stats has the "default" popup specified. You can see the definition of the default popup by scrolling farther down in your config file. It should look like this.

    + +<popup_windows>
    +<pu pu_name="default">
    +<pu_stat pu_stat_name = "n"> </pu_stat>
    +<pu_stat pu_stat_name = "vpip"> </pu_stat>
    +<pu_stat pu_stat_name = "pfr"> </pu_stat>
    +...
    +<pu_stat pu_stat_name = "ffreq_4"> </pu_stat>
    +</pu>
    +</popup_windows>
    + +

    You can create a new popup by making a new pu elelment, with a new name and a new list of stats. You then specify that popup name in the popup parameter in one or more of your stats.

    + +

    Currently Supported Stats

    + +
    +
    a_freq_1
    Flop/4th aggression frequency.
    +
    a_freq_2
    Turn/5th aggression frequency.
    +
    a_freq_3
    River/6th aggression frequency.
    +
    a_freq_4
    7th street aggression frequency.
    +
    cb_1
    Flop continuation bet.
    +
    cb_2
    Turn continuation bet.
    +
    cb_3
    River continuation bet.
    +
    cb_4
    7th street continuation bet.
    +
    f_BB_steal
    Folded BB to steal.
    +
    f_SB_steal
    Folded SB to steal.
    +
    ffreq_1
    Flop/4th fold frequency.
    +
    ffreq_2
    Turn/5th fold frequency.
    +
    ffreq_3
    River/6th fold frequency.
    +
    ffreq_4
    7th fold frequency.
    +
    n
    Number of hands played.
    +
    pfr
    Preflop (3rd street) raise.
    +
    saw_f
    Saw flop/4th.
    +
    steal
    Steal %.
    +
    three_B_0
    Three bet preflop/3rd.
    +
    vpip
    Voluntarily put $ in the pot.
    +
    wmsd
    Won $ at showdown.
    +
    wtsd
    Went to SD when saw flop/4th.
    +
    WMsF
    Won $ when saw flop/4th.
    +
    + + + \ No newline at end of file diff --git a/website/docs-install-gentoo.php b/website/docs-install-gentoo.php index 448630fb..43d2f02b 100644 --- a/website/docs-install-gentoo.php +++ b/website/docs-install-gentoo.php @@ -1,87 +1,85 @@ - - -
    - -

    Installing in Gentoo Linux

    - -

    Last checked: 3 Aug 2008, git99

    - -These instructions are for Gentoo GNU/Linux, but if you adapt the steps installing and starting stuff it should work on any other OS as well.

    - -1. Install everything. Check if anything is already installed and if it is remove it from the command.

    - -For mysql:
    -emerge mysql mysql-python pygtk -av
    -/etc/init.d/mysql start
    -rc-update add mysql default

    - -For postgresql:
    -emerge postgresql pygresql pygtk
    -/etc/init.d/postgresql start
    -rc-update add postgresql default
    - -

    -2. Manual configuration steps
    -
    -emerge --config mysql
    -The --config step will ask you for the mysql root user - set this securely, we will create a seperate account for fpdb
    -

    -3. Create a mysql user and a database
    -Now open a shell (aka command prompt aka DOS window):
    -Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open.

    - -Type (replacing yourPassword with the root password for MySQL you specified during installation):
    -mysql --user=root --password=yourPassword

    - -It should say something like this:
    -Welcome to the MySQL monitor. Commands end with ; or \g.
    -Your MySQL connection id is 4
    -Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1
    -
    -Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
    -
    -mysql>
    -
    -Now create the actual database. The default name is fpdb, I recommend you keep it. Type this:
    -CREATE DATABASE fpdb;
    - -Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password):
    -GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;

    - -Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edit it according to what you configured just now, in particular you will definitely have to put in the password you configured. I know this is insecure, will fix it before stable release.
    -
    -4. Guided installation steps
    -Run the GUI as described in readme-user and click the menu database -> recreate tables
    -
    -That's it! Now see readme-user.txt for usage instructions.
    -
    -License
    -=======
    -Trademarks of third parties have been used under Fair Use or similar laws.
    -
    -Copyright 2008 Steffen Jobbagy-Felso
    -Permission is granted to copy, distribute and/or modify this
    -document under the terms of the GNU Free Documentation License,
    -Version 1.2 as published by the Free Software Foundation; with
    -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    -Texts. A copy of the license can be found in fdl-1.2.txt
    -
    -The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - - -
    - - + + +
    + +

    Installing in Gentoo Linux

    + +

    Last checked: 3 Aug 2008, git99

    + +These instructions are for Gentoo GNU/Linux, but if you adapt the steps installing and starting stuff it should work on any other OS as well.

    + +1. Install everything. Check if anything is already installed and if it is remove it from the command.

    + +For mysql:
    +emerge mysql mysql-python pygtk -av
    +/etc/init.d/mysql start
    +rc-update add mysql default

    + +For postgresql:
    +emerge postgresql pygresql pygtk
    +/etc/init.d/postgresql start
    +rc-update add postgresql default
    + +

    +2. Manual configuration steps
    +
    +emerge --config mysql
    +The --config step will ask you for the mysql root user - set this securely, we will create a seperate account for fpdb
    +

    +3. Create a mysql user and a database
    +Now open a shell (aka command prompt aka DOS window):
    +Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A windows with a black background should open.

    + +Type (replacing yourPassword with the root password for MySQL you specified during installation):
    +mysql --user=root --password=yourPassword

    + +It should say something like this:
    +Welcome to the MySQL monitor. Commands end with ; or \g.
    +Your MySQL connection id is 4
    +Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1
    +
    +Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
    +
    +mysql>
    +
    +Now create the actual database. The default name is fpdb, I recommend you keep it. Type this:
    +CREATE DATABASE fpdb;
    + +Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password):
    +GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;

    + +Copy the .conf file from this directory to ~/.fpdb/profiles/default.conf and edit it according to what you configured just now, in particular you will definitely have to put in the password you configured. I know this is insecure, will fix it before stable release.
    +
    +4. Guided installation steps
    +Run the GUI as described in readme-user and click the menu database -> recreate tables
    +
    +That's it! Now see readme-user.txt for usage instructions.
    +
    +License
    +=======
    +Trademarks of third parties have been used under Fair Use or similar laws.
    +
    +Copyright 2008 Steffen Jobbagy-Felso
    +Permission is granted to copy, distribute and/or modify this
    +document under the terms of the GNU Free Documentation License,
    +Version 1.2 as published by the Free Software Foundation; with
    +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    +Texts. A copy of the license can be found in fdl-1.2.txt
    +
    +The program itself is licensed under AGPLv3, see agpl-3.0.txt

    + + +
    + + diff --git a/website/docs-install-windows.php b/website/docs-install-windows.php index 6bf3236a..8141c294 100644 --- a/website/docs-install-windows.php +++ b/website/docs-install-windows.php @@ -1,112 +1,203 @@ - - -
    - -

    Installing in Windows

    - -

    These instructions are for 32/64bit Windows NT/2k/XP/2k3/Vista/2k8. Well, in principle. I -made them in XP Pro, if you discover any differences or problems please let me know. If you're still on Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP.
    -The length of these instructions is due to MS refusal to provide any meaningful package management, but an installer will be made at some point.

    -

    Here are direct download links, last checked on 10Aug2008:
    -http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.67-win32.zip/from/pick#mirrors
    -http://www.python.org/ftp/python/2.5.2/python-2.5.2.msi
    -http://downloads.sourceforge.net/mysql-python/MySQL-python-1.2.2.win32-py2.5.exe?modtime=1173863337&big_mirror=0
    -http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.12/gtk+-bundle-2.12.11.zip
    -http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.4/pycairo-1.4.12-1.win32-py2.5.exe
    -http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.14/pygobject-2.14.1-1.win32-py2.5.exe
    -http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.12/pygtk-2.12.1-2.win32-py2.5.exe
    -
    -1a. Install MySQL and do its basic setup
    -- Download Windows ZIP/Setup.exe
    -- Unzip the archive, execute the setup file
    - At the end make sure you activate that you want to configure it now.
    - Use the advanced/detailed config. Leave everything as default unless stated below, or unless you have reason not to.
    - Make sure to ACTIVATE TCP/IP networking, but you should block external connections (ie. anything not from localhost) to MySQL unless you're running the DB on a different machine.
    - Set a root password. Note that this is not the account/pw that fpdb will use.
    -
    -Once finished it shold confirm "service started successfully"
    -
    -1b. MySQL database and user setup
    -Now open a shell (aka command prompt aka DOS window):
    -Click Start, then Run. In the opening window type "cmd" (without the inverted commas) and then click OK. A window with a black background should open.
    -
    -Type (replacing yourPassword with the root password for MySQL you specified during installation):
    -mysql --user=root --password=yourPassword
    -
    -It should say something like this:
    -Welcome to the MySQL monitor. Commands end with ; or \g.
    -Your MySQL connection id is 4
    -Server version: 5.0.60-log Gentoo Linux mysql-5.0.60-r1
    -
    -Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
    -
    -mysql>
    -
    -Now create the actual database. The default name is fpdb, I recommend you keep it. Type this:
    -CREATE DATABASE fpdb;
    -
    -Next you need to create a user. I recommend you use the default fpdb. Type this (replacing newPassword with the password you want the fpdb user to have - this can, but for security shouldn't, be the same as the root mysql password):
    -GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;
    -
    -Exit mysql by pressing Ctrl+D
    -
    -2. Install python
    -Get the latest Windows installer. As of this writing that is 2.5.2. Double click the .msi file to start installation and follow the prompts.
    -
    -3. Install the Python-DBAPI package for MySQL:
    -Get the package and double click to install.
    -
    -4. Time for GTK+ - here's the instructions from their bundle
    -
    -To use it, create some empty folder like c:\gtk . Using either
    -Windows Explorer's built-in zip file management, or the command-line
    -unzip.exe from
    -ftp://tug.ctan.org/tex-archive/tools/zip/info-zip/WIN32/unz552xN.exe
    -unzip this bundle.
    -
    -Then add the bin folder to your PATH. Make sure you have no other
    -versions of GTK+ in PATH.
    -To do that:
    -Right click on "My Computer" ("Arbeitsplatz" in German Windows) on the Desktop or in (Windows) Explorer. Select Properties. Then click on the tab Advanced and then you should see Environment Variables. Simply append GTK's bin folder to the existing PATH (make sure to put a ; between the old PATH and GTK's folder to seperate the entries in this list).
    -
    -5. Install pycairo, pygobject and pygtk with double click.
    -
    -6. Copy the default.conf from the docs folder to the appropriate folder in your system, e.g. C:\Documents and Settings\Nick\Application Data\fpdb\profiles\default.conf
    -
    -Now edit the file, in particular you will always have to type in the correct password (insecure, I know) and if you differ from the default setup you may need to change host, database or user.
    -
    -7. Double click fpdb.py in the pyfpdb folder of where you downloaded/unpacked it to.
    -When the program started open the menu Database and click "Create or Recreate Tables".
    -
    -That's it! Now you can use the bulk importer and the table viewer, more's coming. See readme-user.txt
    -A word on privelege separation: fpdb should not require root/Administrator rights to run. If it does it is a bug or serious misconfiguration, please let us know.
    -License
    -=======
    -Trademarks of third parties have been used under Fair Use or similar laws.
    -
    -Copyright 2008 Steffen Jobbagy-Felso
    -Permission is granted to copy, distribute and/or modify this
    -document under the terms of the GNU Free Documentation License,
    -Version 1.2 as published by the Free Software Foundation; with
    -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    -Texts. A copy of the license can be found in fdl-1.2.txt
    -
    -The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - - -
    - - + + +
    + +

    Installing in Windows

    + +
    +

    These instructions were made with Windows XP. They should also work with Windows NT / 2000 / 2003 / Vista and 2008. Please report any differences to gmic at users.sourceforge.net. +

    If you're still using Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP.

    +

    An Installer will be made at some point.

    + +
    +windows install guide screenshot +

    1. Click here to open the MySQL Download Page. Click on the "No, thanks..." link to see the download links for the MySQL installer.

    +
    + +
    +windows install guide screenshot +

    2. Click on one of the HTTP or FTP download links to download the zip file (mysql-5.0.67-win32.zip).

    +
    + +
    +windows install guide screenshot +

    3. Unzip the setup file to a folder of your choice and double-click on it. On the welcome screen click "Next".

    +
    + +
    +windows install guide screenshot +

    4. As the setup type "Typical" should be selected. Then click "Next". On the following screen click "Install" and installation will begin.

    +
    + +
    +windows install guide screenshot +

    5. Before the installation ends an ad for MySQL Enterprise edition will appear. Just click "Next" two times.

    +
    + +
    +windows install guide screenshot +

    6. Now make sure that "Configure the MySQL Server now" is checked and click "Finish".

    +
    + +
    +windows install guide screenshot +

    7. You are now looking at the MySQL Configuration Wizard. Click "Next".

    +
    + +
    +windows install guide screenshot +

    8. Make sure "Detailed Configuration" is selected. Then click "Next. Now "Developer machine" should be selected. Click "Next".

    +
    + +
    +windows install guide screenshot +

    9. On this screen "Multifunctional Database should be selected. Click "Next". On the next screen (InnoDB Tablespace) just click "Next".

    +
    + +
    +windows install guide screenshot +

    10. Now "Decision Support" should be selected. Click "Next". Now make sure "Enable TCP/IP Networking" IS selected. Then click "Next".

    +
    + +
    +windows install guide screenshot +

    11. Here "Standard Character Set" should be selected. Click "Next". Now make sure "Install As Windows Service" is selected.

    +
    + +
    +windows install guide screenshot +

    12. Now choose a root password. This will NOT be the password for your poker database. Click "Next".

    +
    + +
    +windows install guide screenshot +

    13. On this last screen of the Configuration Wizard just click "Execute." A few success messages will appear. Click "Finish".

    +
    + +
    +windows install guide screenshot +

    14. Now click the Windows Start Button and then click "Run". Click into the white space of the new window, type cmd and hit ENTER.

    +
    + +
    +windows install guide screenshot +

    15. In the newly appeared console window type cd "%PROGRAMFILES%\MySQL\MySQL Server 5.0\bin" and hit ENTER.

    +
    + +
    +windows install guide screenshot +

    16. Type mysql --user=root --password=yourPassword and hit ENTER (replace yourPassword with your chosen root password).

    +
    + +
    +windows install guide screenshot +

    17. A few lines followed by mysql> will appear. This is the MySQL command prompt.

    +
    + +
    +windows install guide screenshot +

    18. We will now create your poker database. Type CREATE DATABASE fpdb; and hit ENTER. "Query OK" says we were successful.

    +
    + +
    +windows install guide screenshot +

    19. Type the following, replace newPassword with a password of your choice and hit ENTER:

    +

    GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;

    +
    + +
    +windows install guide screenshot +

    20. Again it says "Query OK". Type exit and hit ENTER to exit the MySQL prompt. Leave this window open. We will need it later.

    +
    + +
    +windows install guide screenshot +

    21. Click here, save the file python-2.5.2.msi where you want and double-click on it. In case of a warning window click "Execute".

    +
    + +
    +windows install guide screenshot +

    22.Click "Next" three times. Python will install. Then click finish.

    +
    + +
    +windows install guide screenshot +

    23. Click here, save MySQL-python-1.2.2.win32-py2.5.exe to a folder of your choice and double click it. In case of a warning window click "Execute". Click "Next" three times. The Python API for MySQL will install. Click "Finish".

    +
    + +
    +windows install guide screenshot +

    24. In the console window (which we left open) now type: mkdir c:\gtk and hit ENTER. Leave the window open again, we'll need it.

    +

    Now click here and save the gtk zip file gtk+-bundle-2.12.11.zip to a folder of your choice. Unzip its contents to C:\gtk

    +
    + +
    +windows install guide screenshot +

    25. Now right-click "My Computer" (on your Desktop) and click on "Properties". Now click on the tab "Advanced".

    +
    + +
    +windows install guide screenshot +

    26. Click the button "Environ Variables". In the lower list of the new window click on "Path" (possibly you need to scroll).

    +
    + +
    +windows install guide screenshot +

    27.Now click on the"Edit" button and a new window will pop up. To the value of the variable append ;C:\gtk\bin Click Ok three times.

    +
    + +
    +windows install guide screenshot +

    28. Click here, save the file pycairo-1.4.12-1.win32-py2.5.exe to a folder of your choice and double click on it. In case of a warning window click "Execute". Now click "Next" three times. The pycairo graphics library API for Python will install. Click "Finish".

    +
    + +
    +windows install guide screenshot +

    29. Click here, save the file pygobject-2.14.1-1.win32-py2.5.exe to a folder of your choice and double click on it. In case of a warning click "Execute". Now click "Next" three times. The Python Gobject API will install. Click "Finish".

    +
    + +
    +windows install guide screenshot +

    30. Click here, save the file pygtk-2.12.1-2.win32-py2.5.exe to a folder of your choice and double click on it. In case of a warning click "Execute". Now click "Next" three times. The Python API for Gtk+ will install. Click "Finish".

    +
    + +
    windows install guide screenshot +

    31. In the console window now type: mkdir "%homepath%\Application Data\fpdb" and hit ENTER. Copy the file "default.conf" from the docs folder of your fpdb git to the directory C:\%homepath%\Application Data\fpdb\.

    +
    + +
    +windows install guide screenshot +

    32. Now open the file "default.conf" in WordPad (Start > Programs > Accessoirs > WordPad) and replace the password in the dbpassword line with your chosen password for the fpdb user.

    +
    + +
    +windows install guide screenshot +

    33. Now start FPDB by double-clicking on the file fpdb.py in the folder fpdb. A console window should open up and shortly thereafter the fpdb application window should be visible. Click on the menu "Database" and select "Create or Recreate Tables".

    +
    +

    Congratulations! Your fpdb installation is complete! Now you can use the bulk importer to import your hands into fpdb. +

    +
    + +

    A word on privelege separation: fpdb should not require root/Administrator rights to run. If it does it is a bug or serious misconfiguration, please let us know.

    +

    License

    +

    Trademarks of third parties have been used under Fair Use or similar laws.

    +

    Copyright 2008 Steffen Jobbagy-Felso

    +

    Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 as published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license can be found in fdl-1.2.txt. The program itself is licensed under AGPLv3, see agpl-3.0.txt

    + + + + + diff --git a/website/docs-overview.php b/website/docs-overview.php index dedc957e..3d2781d2 100644 --- a/website/docs-overview.php +++ b/website/docs-overview.php @@ -1,86 +1,84 @@ - - -
    - -

    Overview

    -

    -Summary
    -=======
    -A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future. Some of this is not yet working though, please see status.txt and known-bugs-and-planned-features.txt
    -
    -But you could send all my hand histories to yourself!
    -=====================================================
    -At the end of the day this comes down to a question of trust, but unlike Windows and the poker client software you don't have to trust fpdb blindly. You can:
    -- Verify the source code yourself.
    -- Convince or pay someone to verify the source code for you.
    -- Use a personal firewall to completely block fpdb from the Internet
    -- (for the uber-paranoid) Get yourself the free virtualisation software VirtualBox, set up a VM (virtual machine) to run fpdb but run the poker software on your real PC. Then cut the VM off the Internet, fpdb doesn't need it. If you have a PC made in the last few years this should run fast enough as well. Note that most Windows licenses do NOT permit you to use two Windows installations at once, even if they are on the same PC.
    -
    -Requirements
    -============
    -Software requirements are listed in requirements.txt
    -As for hardware, my main test machine is a Pentium 3-M 800 with 256 RAM and Gentoo GNU/Linux
    -(running the poker client through what most people will call emulation). So this
    -program will have to work on that. If you run an even more ancient machine and
    -its too slow let me know and I'll see what I can do :)
    -
    -Why Free Software?
    -==================
    -This program is released under the terms of the free/libre software license AGPL3 as released by the FSF. The AGPL3 protects your rights and those of the wider community. As Richard Stallman, one of the founders of the free software movement, put it: "Free software is a matter of liberty, not price. To understand the concept, you should think of free as in free speech, not as in free beer." (well, it is both really, like the right to vote used to be free)
    -
    -For example, a "pirated" copy of proprietary software X is free of charge, but you don't actually have a legal right to use it, you don't have any possibility to fix its bugs and you certainly don't have any legal right to share it with your friends. You also won't be able to get support, often not even security fixes. Actually, even if you pay hundreds of pounds for your program they deny your right to fix their errors for them. Imagine buying a car where you're not permitted (under threat of jail) to replace broken parts..
    -
    -With free/libre software (also known as open source software, or short FOSS or FLOSS) on the other hand you get all these freedoms:
    -(note: the legally binding terms are in the license text, this is merely an amateur summary so normal people don't have to read pages of legalese)
    -
    -Freedom 0: The freedom to use: To run the program, for any purpose. Free of Charge.
    -Freedom 1: The freedom to study and help yourself. This freedom guarantees your right to study and learn from the source code of the program, and to fix it if it is broken. If you're not a programmer yourself the developers will generally be happy to fix it for you, often even for free. Failing that you can always pay someone from the money you saved on not having to pay for it.
    -Freedom 2: The freedom to be a decent human being and help your neighbour: I don't threaten you with lawsuits or jail time if you share with your friends and neighbours, subject to the very modest restrictions of the AGPL3.
    -Freedom 3: The freedom to improve the program and release your improvements to the public (or parts thereof) so that the whole community benefits. Note that you are PERMITTED, but not REQUIRED to distribute your changes. If you do distribute your changes you must do so under the terms of the AGPL3 however.
    -
    -Note that this is the license - I retain full copyright over my code, including the right to change the license for future versions. I do not intend to do this however. In any case, any version I released under AGPL3 remains available under that license forever, or more accurately until my copyright expires at which point it goes into the public domain.
    -
    -I reject the concept of software patents as a crime and under the European Patent Agreement software patents - even if you mislabel them as "computer-implemented inventions" or whatever - are explicitly prohibited.
    -
    -Can I get/use this under a different license?
    -=============================================
    -The short answer: Maybe.
    -The long one: As detailed, I fully support what the FSF does and aims to achieve with the GPL. However, I realise that many free software developers don't object to closed source, some don't even object to closed source profiteering of their charity, and I don't think I have any right to go and tell them they're wrong.
    -So if anyone wishes to use all or part of my code in another free software/open source project with an AGPL3-incompatible license such as BSD then let me know and we'll figure out a solution that makes everyone happy.
    -If you wish to use all or part of this in closed source let me know how much if anything that is worth to you and I'm sure we'll be able to reach an agreement. Note that you are NOT permitted to just use fpdb code in closed source development whether in-house or by an independent software developer, you will NEED an additionally agreement with me to get fpdb under different licensing conditions.
    -
    -
    -License of this Document
    -========================
    -The views expressed in this document are those of Steffen Jobbagy-Felso, other members of the fpdb team and external contributors may or may not agree.
    -
    -Trademarks of third parties have been used under Fair Use or similar laws.
    -
    -Copyright 2008 Steffen Jobbagy-Felso
    -Permission is granted to copy, distribute and/or modify this
    -document under the terms of the GNU Free Documentation License,
    -Version 1.2 as published by the Free Software Foundation; with
    -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    -Texts. A copy of the license can be found in fdl-1.2.txt
    -
    -The program itself is licensed under AGPLv3, see agpl-3.0.txt
    -

    - - - -
    - - + + +
    + +

    Overview

    +

    +Summary
    +=======
    +A database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future. Some of this is not yet working though, please see status.txt and known-bugs-and-planned-features.txt
    +
    +But you could send all my hand histories to yourself!
    +=====================================================
    +At the end of the day this comes down to a question of trust, but unlike Windows and the poker client software you don't have to trust fpdb blindly. You can:
    +- Verify the source code yourself.
    +- Convince or pay someone to verify the source code for you.
    +- Use a personal firewall to completely block fpdb from the Internet
    +- (for the uber-paranoid) Get yourself the free virtualisation software VirtualBox, set up a VM (virtual machine) to run fpdb but run the poker software on your real PC. Then cut the VM off the Internet, fpdb doesn't need it. If you have a PC made in the last few years this should run fast enough as well. Note that most Windows licenses do NOT permit you to use two Windows installations at once, even if they are on the same PC.
    +
    +Requirements
    +============
    +Software requirements are listed in requirements.txt
    +As for hardware, my main test machine is a Pentium 3-M 800 with 256 RAM and Gentoo GNU/Linux
    +(running the poker client through what most people will call emulation). So this
    +program will have to work on that. If you run an even more ancient machine and
    +its too slow let me know and I'll see what I can do :)
    +
    +Why Free Software?
    +==================
    +This program is released under the terms of the free/libre software license AGPL3 as released by the FSF. The AGPL3 protects your rights and those of the wider community. As Richard Stallman, one of the founders of the free software movement, put it: "Free software is a matter of liberty, not price. To understand the concept, you should think of free as in free speech, not as in free beer." (well, it is both really, like the right to vote used to be free)
    +
    +For example, a "pirated" copy of proprietary software X is free of charge, but you don't actually have a legal right to use it, you don't have any possibility to fix its bugs and you certainly don't have any legal right to share it with your friends. You also won't be able to get support, often not even security fixes. Actually, even if you pay hundreds of pounds for your program they deny your right to fix their errors for them. Imagine buying a car where you're not permitted (under threat of jail) to replace broken parts..
    +
    +With free/libre software (also known as open source software, or short FOSS or FLOSS) on the other hand you get all these freedoms:
    +(note: the legally binding terms are in the license text, this is merely an amateur summary so normal people don't have to read pages of legalese)
    +
    +Freedom 0: The freedom to use: To run the program, for any purpose. Free of Charge.
    +Freedom 1: The freedom to study and help yourself. This freedom guarantees your right to study and learn from the source code of the program, and to fix it if it is broken. If you're not a programmer yourself the developers will generally be happy to fix it for you, often even for free. Failing that you can always pay someone from the money you saved on not having to pay for it.
    +Freedom 2: The freedom to be a decent human being and help your neighbour: I don't threaten you with lawsuits or jail time if you share with your friends and neighbours, subject to the very modest restrictions of the AGPL3.
    +Freedom 3: The freedom to improve the program and release your improvements to the public (or parts thereof) so that the whole community benefits. Note that you are PERMITTED, but not REQUIRED to distribute your changes. If you do distribute your changes you must do so under the terms of the AGPL3 however.
    +
    +Note that this is the license - I retain full copyright over my code, including the right to change the license for future versions. I do not intend to do this however. In any case, any version I released under AGPL3 remains available under that license forever, or more accurately until my copyright expires at which point it goes into the public domain.
    +
    +I reject the concept of software patents as a crime and under the European Patent Agreement software patents - even if you mislabel them as "computer-implemented inventions" or whatever - are explicitly prohibited.
    +
    +Can I get/use this under a different license?
    +=============================================
    +The short answer: Maybe.
    +The long one: As detailed, I fully support what the FSF does and aims to achieve with the GPL. However, I realise that many free software developers don't object to closed source, some don't even object to closed source profiteering of their charity, and I don't think I have any right to go and tell them they're wrong.
    +So if anyone wishes to use all or part of my code in another free software/open source project with an AGPL3-incompatible license such as BSD then let me know and we'll figure out a solution that makes everyone happy.
    +If you wish to use all or part of this in closed source let me know how much if anything that is worth to you and I'm sure we'll be able to reach an agreement. Note that you are NOT permitted to just use fpdb code in closed source development whether in-house or by an independent software developer, you will NEED an additionally agreement with me to get fpdb under different licensing conditions.
    +
    +
    +License of this Document
    +========================
    +The views expressed in this document are those of Steffen Jobbagy-Felso, other members of the fpdb team and external contributors may or may not agree.
    +
    +Trademarks of third parties have been used under Fair Use or similar laws.
    +
    +Copyright 2008 Steffen Jobbagy-Felso
    +Permission is granted to copy, distribute and/or modify this
    +document under the terms of the GNU Free Documentation License,
    +Version 1.2 as published by the Free Software Foundation; with
    +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    +Texts. A copy of the license can be found in fdl-1.2.txt
    +
    +The program itself is licensed under AGPLv3, see agpl-3.0.txt
    +

    + + + +
    + + diff --git a/website/docs-requirements.php b/website/docs-requirements.php index a8c20dd5..537e4921 100644 --- a/website/docs-requirements.php +++ b/website/docs-requirements.php @@ -1,162 +1,160 @@ - - -
    - -

    Requirements

    -

    -I recommend using a free/libre operating system, meaning a GNU/Linux distribution or a BSD variant (e.g. Gentoo GNU/Linux or OpenBSD) for ethical and practical reasons. Would you buy a car where you're prohibited from opening the bonnet under threat of jail? If the answer is no you should by the same logic not use closed source software for real money Poker :)
    -
    -Unfortunately you will always need one piece of unfree software: The poker client itself. Although not a direct dependency of fpdb you obviously will have a hard time putting this to productive use without running some poker client. As far as I know, only unfree clients are available. If you know better please let me know ASAP!
    -
    -If you can be bothered please do contact your poker site(s) and ask them to release free/libre clients, even if it is only for Windows. But lets be realistic, the chance of a positive answer is very low.
    -
    -Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least).
    -
    -If you use a package management system (e.g. if you have GNU/Linux or *BSD) just check that you have mysql, mysql-python and pygtk or postgresql, pygresql and pygtk. Your package manager will take care of the rest for you.
    -
    -Make new entries in this format:
    -X. Program Name
    -===============
    -a. Optional?
    -b. Required Version and Why
    -c. Project Webpage
    -d. License
    -
    -1. MySQL
    -========
    -a. Optional?
    - Choose MySQL or PostgreSQL
    -b. Required Version and Why
    - At least 3.23 required due to mysql-python.
    - I use 5.0.54 and 5.0.60-r1 (GNU/Linux) and 5.0.51b (Windows).
    -c. Project Webpage
    - http://www.mysql.com
    -d. License
    - GPL2
    -
    -2. PostgreSQL
    -=============
    -a. Optional?
    - Choose MySQL or PostgreSQL
    -b. Required Version and Why
    - I use 8.0.15 (GNU/Linux) and 8.3.3 (Windows) but I am not aware of any incompatibilities
    - with older or newer versions, pls report success/failure.
    -c. Project Webpage
    - http://www.postgresql.org
    -d. License
    - BSD License
    -
    -3. mysql-python
    -===============
    -a. Optional?
    - Required if you want to use MySQL backend
    -b. Required Version and Why
    - I use 1.2.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    -c. Project Webpage
    - http://sourceforge.net/projects/mysql-python/
    -d. License
    - SF lists GNU General Public License (GPL), Python License (CNRI Python License), Zope Public License.
    - Project states GPL without version in Pkg-info.
    -
    -4. pygresql
    -===========
    -a. Optional?
    - Required if you want to use PostgreSQL backend
    -b. Required Version and Why
    - I use 3.6.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    -c. Project Webpage
    - http://www.pygresql.org/
    -d. License
    - http://www.pygresql.org/readme.html#copyright-notice (BSD License?)
    - Summary: "Permission to use, copy, modify, and distribute this software and its
    - documentation for any purpose, without fee, and without a written agreement
    - is hereby granted[...]" plus Disclaimer.
    -
    -5. Python
    -=========
    -a. Optional?
    - Required.
    -b. Required Version and Why
    - I use 2.4.4 and 2.5.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    -c. Project Webpage
    - http://www.python.org
    -d. License
    - Python License
    -
    -6. GTK+ and dependencies
    -=======
    -a. Optional?
    - Required.
    -b. Required Version and Why
    - I use 2.12.9 but it should run with 2.10 or higher. That is needed as I used MessageDialog updates
    -c. Project Webpage
    - Main: http://www.gtk.org/
    - API spec: http://library.gnome.org/devel/gtk/2.12/
    - Windows DLs (get the bundle unless you know what you're doing): http://www.gtk.org/download-windows.html
    -d. License
    - LGPL2
    -
    -7. PyCairo
    -==========
    -a. Optional?
    - Required.
    -b. Required Version and Why
    - ?
    -c. Project Webpage
    - main: http://www.pygtk.org
    -d. License
    - LGPL2.1
    -
    -8. PyGObject
    -============
    -a. Optional?
    - Required.
    -b. Required Version and Why
    - ?
    -c. Project Webpage
    - main: http://www.pygtk.org
    -d. License
    - LGPL2.1
    -
    -9. PyGTK
    -========
    -a. Optional?
    - Required.
    -b. Required Version and Why
    - ?
    -c. Project Webpage
    - main: http://www.pygtk.org
    -d. License
    - LGPL2.1
    -
    -License (of this file)
    -=======
    -Trademarks of third parties have been used under Fair Use or similar laws.
    -
    -Copyright 2008 Steffen Jobbagy-Felso
    -Permission is granted to copy, distribute and/or modify this
    -document under the terms of the GNU Free Documentation License,
    -Version 1.2 as published by the Free Software Foundation; with
    -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    -Texts. A copy of the license can be found in fdl-1.2.txt
    -
    -The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - - - -
    - - + + +
    + +

    Requirements

    +

    +I recommend using a free/libre operating system, meaning a GNU/Linux distribution or a BSD variant (e.g. Gentoo GNU/Linux or OpenBSD) for ethical and practical reasons. Would you buy a car where you're prohibited from opening the bonnet under threat of jail? If the answer is no you should by the same logic not use closed source software for real money Poker :)
    +
    +Unfortunately you will always need one piece of unfree software: The poker client itself. Although not a direct dependency of fpdb you obviously will have a hard time putting this to productive use without running some poker client. As far as I know, only unfree clients are available. If you know better please let me know ASAP!
    +
    +If you can be bothered please do contact your poker site(s) and ask them to release free/libre clients, even if it is only for Windows. But lets be realistic, the chance of a positive answer is very low.
    +
    +Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least).
    +
    +If you use a package management system (e.g. if you have GNU/Linux or *BSD) just check that you have mysql, mysql-python and pygtk or postgresql, pygresql and pygtk. Your package manager will take care of the rest for you.
    +
    +Make new entries in this format:
    +X. Program Name
    +===============
    +a. Optional?
    +b. Required Version and Why
    +c. Project Webpage
    +d. License
    +
    +1. MySQL
    +========
    +a. Optional?
    + Choose MySQL or PostgreSQL
    +b. Required Version and Why
    + At least 3.23 required due to mysql-python.
    + I use 5.0.54 and 5.0.60-r1 (GNU/Linux) and 5.0.51b (Windows).
    +c. Project Webpage
    + http://www.mysql.com
    +d. License
    + GPL2
    +
    +2. PostgreSQL
    +=============
    +a. Optional?
    + Choose MySQL or PostgreSQL
    +b. Required Version and Why
    + I use 8.0.15 (GNU/Linux) and 8.3.3 (Windows) but I am not aware of any incompatibilities
    + with older or newer versions, pls report success/failure.
    +c. Project Webpage
    + http://www.postgresql.org
    +d. License
    + BSD License
    +
    +3. mysql-python
    +===============
    +a. Optional?
    + Required if you want to use MySQL backend
    +b. Required Version and Why
    + I use 1.2.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    +c. Project Webpage
    + http://sourceforge.net/projects/mysql-python/
    +d. License
    + SF lists GNU General Public License (GPL), Python License (CNRI Python License), Zope Public License.
    + Project states GPL without version in Pkg-info.
    +
    +4. pygresql
    +===========
    +a. Optional?
    + Required if you want to use PostgreSQL backend
    +b. Required Version and Why
    + I use 3.6.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    +c. Project Webpage
    + http://www.pygresql.org/
    +d. License
    + http://www.pygresql.org/readme.html#copyright-notice (BSD License?)
    + Summary: "Permission to use, copy, modify, and distribute this software and its
    + documentation for any purpose, without fee, and without a written agreement
    + is hereby granted[...]" plus Disclaimer.
    +
    +5. Python
    +=========
    +a. Optional?
    + Required.
    +b. Required Version and Why
    + I use 2.4.4 and 2.5.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    +c. Project Webpage
    + http://www.python.org
    +d. License
    + Python License
    +
    +6. GTK+ and dependencies
    +=======
    +a. Optional?
    + Required.
    +b. Required Version and Why
    + I use 2.12.9 but it should run with 2.10 or higher. That is needed as I used MessageDialog updates
    +c. Project Webpage
    + Main: http://www.gtk.org/
    + API spec: http://library.gnome.org/devel/gtk/2.12/
    + Windows DLs (get the bundle unless you know what you're doing): http://www.gtk.org/download-windows.html
    +d. License
    + LGPL2
    +
    +7. PyCairo
    +==========
    +a. Optional?
    + Required.
    +b. Required Version and Why
    + ?
    +c. Project Webpage
    + main: http://www.pygtk.org
    +d. License
    + LGPL2.1
    +
    +8. PyGObject
    +============
    +a. Optional?
    + Required.
    +b. Required Version and Why
    + ?
    +c. Project Webpage
    + main: http://www.pygtk.org
    +d. License
    + LGPL2.1
    +
    +9. PyGTK
    +========
    +a. Optional?
    + Required.
    +b. Required Version and Why
    + ?
    +c. Project Webpage
    + main: http://www.pygtk.org
    +d. License
    + LGPL2.1
    +
    +License (of this file)
    +=======
    +Trademarks of third parties have been used under Fair Use or similar laws.
    +
    +Copyright 2008 Steffen Jobbagy-Felso
    +Permission is granted to copy, distribute and/or modify this
    +document under the terms of the GNU Free Documentation License,
    +Version 1.2 as published by the Free Software Foundation; with
    +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    +Texts. A copy of the license can be found in fdl-1.2.txt
    +
    +The program itself is licensed under AGPLv3, see agpl-3.0.txt

    + + + +
    + + diff --git a/website/docs-usage.php b/website/docs-usage.php index 7d23da2c..a9132f2c 100644 --- a/website/docs-usage.php +++ b/website/docs-usage.php @@ -1,66 +1,64 @@ - - -
    - -

    Usage instructions

    - -

    Before you do this make sure you setup the dependencies, the database, user, tables and config file.
    -
    -Running it
    -==========
    -If you have python setup properly you can execute it by double clicking pyfpdb/fpdb.py.
    -
    -Note however that all error messages are currently only printed if you call it from a shell. It'll be much easier to diagnose possible problems (which are likely in alpha stage) if you run it from a shell. In Windows XP it seems to automatically open a shell window with the fpdb window where you can see the command line output.
    -
    -In Linux/MacOS/*BSD, e.g. if its in /home/sycamore/fpdb/, do this:
    -cd /home/sycamore/fpdb/pyfpdb
    -python fpdb.py
    -
    -That will start the main GUI.
    -
    -Have a look at the menus, the stuff that is marked todo is not yet implemented.
    -
    -The main things are the bulk importer and the table viewer. To use the importer open it from the menu (import files and directories). You can set a few options at the bottom, then select a folder or single file in the main are and click Import. Please report any errors by one of the contacts listed in readme-overview.txt.
    -Currently this will block the interface, but you can open another instance of this program e.g. if you wanna play whilst a big import is running.
    -
    -Please check the output at the shell for errors, if there are any please get in touch by one of the methods listed in readme-overview.txt
    -
    -Table Viewer (tv)
    -=================v -To use the table viewer open it from the menu, select the hand history file of the table you're at, and click the Import&Read&Refresh button. The abbreviations there are explained in abbreviations.txt, but feel free to ask. Note that most poker software will only create the file once the first hand you payed to play is finished.
    -In each column there is either just the number (hand count for current stake, range of seats and type of game) or a percentage and the number of hands that this percentage is based on. For example, in W$@SD (won $ at shodown) the number in brackets is how many showdowns that player has seen.
    -
    -Reimporting
    -===========
    -Currently on most updates a reimport of the whole database is required. To do this open fpdb, click the menu Database and select Create/Recreate tables. Then import all your history files again.
    -
    -License
    -======= -Trademarks of third parties have been used under Fair Use or similar laws.
    -
    -Copyright 2008 Steffen Jobbagy-Felso
    -Permission is granted to copy, distribute and/or modify this
    -document under the terms of the GNU Free Documentation License,
    -Version 1.2 as published by the Free Software Foundation; with
    -no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    -Texts. A copy of the license can be found in fdl-1.2.txt
    -
    -The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - - -
    - - + + +
    + +

    Usage instructions

    + +

    Before you do this make sure you setup the dependencies, the database, user, tables and config file.
    +
    +Running it
    +==========
    +If you have python setup properly you can execute it by double clicking pyfpdb/fpdb.py.
    +
    +Note however that all error messages are currently only printed if you call it from a shell. It'll be much easier to diagnose possible problems (which are likely in alpha stage) if you run it from a shell. In Windows XP it seems to automatically open a shell window with the fpdb window where you can see the command line output.
    +
    +In Linux/MacOS/*BSD, e.g. if its in /home/sycamore/fpdb/, do this:
    +cd /home/sycamore/fpdb/pyfpdb
    +python fpdb.py
    +
    +That will start the main GUI.
    +
    +Have a look at the menus, the stuff that is marked todo is not yet implemented.
    +
    +The main things are the bulk importer and the table viewer. To use the importer open it from the menu (import files and directories). You can set a few options at the bottom, then select a folder or single file in the main are and click Import. Please report any errors by one of the contacts listed in readme-overview.txt.
    +Currently this will block the interface, but you can open another instance of this program e.g. if you wanna play whilst a big import is running.
    +
    +Please check the output at the shell for errors, if there are any please get in touch by one of the methods listed in readme-overview.txt
    +
    +Table Viewer (tv)
    +=================v +To use the table viewer open it from the menu, select the hand history file of the table you're at, and click the Import&Read&Refresh button. The abbreviations there are explained in abbreviations.txt, but feel free to ask. Note that most poker software will only create the file once the first hand you payed to play is finished.
    +In each column there is either just the number (hand count for current stake, range of seats and type of game) or a percentage and the number of hands that this percentage is based on. For example, in W$@SD (won $ at shodown) the number in brackets is how many showdowns that player has seen.
    +
    +Reimporting
    +===========
    +Currently on most updates a reimport of the whole database is required. To do this open fpdb, click the menu Database and select Create/Recreate tables. Then import all your history files again.
    +
    +License
    +======= +Trademarks of third parties have been used under Fair Use or similar laws.
    +
    +Copyright 2008 Steffen Jobbagy-Felso
    +Permission is granted to copy, distribute and/or modify this
    +document under the terms of the GNU Free Documentation License,
    +Version 1.2 as published by the Free Software Foundation; with
    +no Invariant Sections, no Front-Cover Texts, and with no Back-Cover
    +Texts. A copy of the license can be found in fdl-1.2.txt
    +
    +The program itself is licensed under AGPLv3, see agpl-3.0.txt

    + + +
    + + diff --git a/website/docs.php b/website/docs.php index 190bb779..d0749d68 100644 --- a/website/docs.php +++ b/website/docs.php @@ -1,38 +1,37 @@ - - -
    - -

    Documentation

    - - - - - - - -
    - - + + +
    + +

    Documentation

    + + + + + + + +
    + + diff --git a/website/features.php b/website/features.php index fa10ead6..e1df9f1e 100644 --- a/website/features.php +++ b/website/features.php @@ -1,49 +1,47 @@ - - -
    - -

    Features

    - -

    Backend, Distribution
    -=====================
    -- Choice of MySQL/InnoDB or PostgreSQL. (not tested on PostgreSQL)
    -- It is possible to run the database on one PC, the importer on another, and then access the database with the table viewer or HUD from a third PC. (note: do NOT do this unencrypted over an untrusted network like your employer's LAN or the Internet!)
    -
    -Site/Game Support
    -=================
    -- Initially only full support for PS, FTP coming soon
    -- Supports Holdem, Omaha Hi and Omaha Hi/Lo. Stud and Razz coming soon.
    - -- Supports No Limit, Pot Limit, Fixed Limit NL, Cap NL and Cap PL
    - Note that currently it does not display extra stats for NL/PL so usefulness is limited for these limit types. Suggestions welcome, I don't play these.
    -- Supports ring/cash games, SnG/MTT coming soon
    -
    -Tableviewer (tv)
    -===========
    -Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are:
    - - VPIP, PFR and Preflop 3B/4B (3B/4B is not quite correct I think)
    - - - Raise and Fold % on flop, turn and river. Fold only counts hands when someone raised. This can be displayed per street or as one combined value each for aggression and folding.
    - - Number of hands this is based on.
    - - SD/F (aka WtSD, proportion of hands where player went to showdown after seeing flop)
    - - W$wSF (Won $ when seen Flop)
    - - W$@SD (Won $ at showdown)
    - For all stats it also displays how many hands this particular is based on

    - - -
    - - + + +
    + +

    Features

    + +

    Backend, Distribution
    +=====================
    +- Choice of MySQL/InnoDB or PostgreSQL. (not tested on PostgreSQL)
    +- It is possible to run the database on one PC, the importer on another, and then access the database with the table viewer or HUD from a third PC. (note: do NOT do this unencrypted over an untrusted network like your employer's LAN or the Internet!)
    +
    +Site/Game Support
    +=================
    +- Initially only full support for PS, FTP coming soon
    +- Supports Holdem, Omaha Hi and Omaha Hi/Lo. Stud and Razz coming soon.
    + +- Supports No Limit, Pot Limit, Fixed Limit NL, Cap NL and Cap PL
    + Note that currently it does not display extra stats for NL/PL so usefulness is limited for these limit types. Suggestions welcome, I don't play these.
    +- Supports ring/cash games, SnG/MTT coming soon
    +
    +Tableviewer (tv)
    +===========
    +Tv takes a history filename and loads the appropriate players' stats and displays them in a tabular format. These stats currently are:
    + - VPIP, PFR and Preflop 3B/4B (3B/4B is not quite correct I think)
    + + - Raise and Fold % on flop, turn and river. Fold only counts hands when someone raised. This can be displayed per street or as one combined value each for aggression and folding.
    + - Number of hands this is based on.
    + - SD/F (aka WtSD, proportion of hands where player went to showdown after seeing flop)
    + - W$wSF (Won $ when seen Flop)
    + - W$@SD (Won $ at showdown)
    + For all stats it also displays how many hands this particular is based on

    + + +
    + + diff --git a/website/footer.php b/website/footer.php index e0e74b50..abe37b45 100644 --- a/website/footer.php +++ b/website/footer.php @@ -1,7 +1 @@ - - -
    - - - - + diff --git a/website/header.php b/website/header.php index b0cc6d7c..df88b6cb 100644 --- a/website/header.php +++ b/website/header.php @@ -1,19 +1,17 @@ - - - - - - - fpdb - <?php echo (empty($PAGE_TITLE) ? 'freepokerdb' : $PAGE_TITLE) ?> - - - - - - - -
    - - + + + + + + + fpdb - The free poker database - <?php echo ($PAGE_TITLE) ?> + + + + + + + + diff --git a/website/img/00.mySqlWebsite1.jpg b/website/img/00.mySqlWebsite1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2b83b68abb85775bd73f11ec0df8f058fb339cdc GIT binary patch literal 10881 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 UKSR`m)^+vw0Mnl(uKT6`05l8z=Kufz literal 0 HcmV?d00001 diff --git a/website/img/01.mySqlWebsite2.jpg b/website/img/01.mySqlWebsite2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..458a9e904315d9e6dd934ece551ecc3fcfaeacd7 GIT binary patch literal 17420 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 literal 0 HcmV?d00001 diff --git a/website/img/03.mySqlSetup3.jpg b/website/img/03.mySqlSetup3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..295306f3664c1637de7d4a8ed98d792c4bab5503 GIT binary patch literal 10370 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 literal 0 HcmV?d00001 diff --git a/website/img/04.mySqlSetup5.jpg b/website/img/04.mySqlSetup5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a236687c1dc4cdc83f519c62b2a470ca0a62614d GIT binary patch literal 10364 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

    o*vGR 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 literal 0 HcmV?d00001 diff --git a/website/img/06.mySqlConfig1.jpg b/website/img/06.mySqlConfig1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eaa0252343ed7bf3627e98e5365ebd882c681dde GIT binary patch literal 17465 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 literal 0 HcmV?d00001 diff --git a/website/img/07.mySqlConfig2.jpg b/website/img/07.mySqlConfig2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5ee23448b76d877f1ccd2ddf0274678d9627d627 GIT binary patch literal 12647 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 literal 0 HcmV?d00001 diff --git a/website/img/08.mySqlConfig4.jpg b/website/img/08.mySqlConfig4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..36a0aaea042284312675ab269734cc25fc7d4ea5 GIT binary patch literal 16770 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 S*gxa{WEcN`aAyl2TmJ=cP9ZV? literal 0 HcmV?d00001 diff --git a/website/img/09.mySqlConfig7.jpg b/website/img/09.mySqlConfig7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae39d7dc5004107560a80634c51ddd61c40e6872 GIT binary patch literal 16282 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 literal 0 HcmV?d00001 diff --git a/website/img/11.mySqlConfig10.jpg b/website/img/11.mySqlConfig10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7bd1066c1d70ffadfddea44c8f8d09f0b8d18fe8 GIT binary patch literal 9384 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 JgY$LqKLFP`z_kDX literal 0 HcmV?d00001 diff --git a/website/img/12.mySqlConfig11.jpg b/website/img/12.mySqlConfig11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..74ed5781f6349a75e4d0e247f4a470bdc2022b25 GIT binary patch literal 9311 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 literal 0 HcmV?d00001 diff --git a/website/img/13.run.jpg b/website/img/13.run.jpg new file mode 100644 index 0000000000000000000000000000000000000000..71f66ee4a271ee0b8fad61af5a7291e6330b69c6 GIT binary patch literal 5010 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 literal 0 HcmV?d00001 diff --git a/website/img/14.shellCdToMySql.jpg b/website/img/14.shellCdToMySql.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6c0b104f4da2adeba9835ae1c091789e2a1796d3 GIT binary patch literal 13520 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%0DE>}pj3Wzr%O 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 literal 0 HcmV?d00001 diff --git a/website/img/17.shellMySqlCreateDB.jpg b/website/img/17.shellMySqlCreateDB.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05c654d7c29664185b70a7db572972d02aa804f8 GIT binary patch literal 4986 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 FzW@b(zuW)- literal 0 HcmV?d00001 diff --git a/website/img/20.pythonInst.jpg b/website/img/20.pythonInst.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cc9c518d1585635de1834cbf83a970ac86bdb824 GIT binary patch literal 12024 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 literal 0 HcmV?d00001 diff --git a/website/img/21.pythonInst4.jpg b/website/img/21.pythonInst4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7c5d25824f1f31a2c95d596f77ce95dd67d30bc6 GIT binary patch literal 5839 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 literal 0 HcmV?d00001 diff --git a/website/img/22.mySqlPythonInst1.jpg b/website/img/22.mySqlPythonInst1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f74f01a5d5762ac4376bce52c18a3a0e0a92ef0f GIT binary patch literal 13313 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

    xEYwo{q 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 E2Oeu%o&W#< literal 0 HcmV?d00001 diff --git a/website/img/23.shellMkDirGtk.jpg b/website/img/23.shellMkDirGtk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb8317cc450cf0908c94b315fe3b774012328106 GIT binary patch literal 9544 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 Z-T>!&Jtg#p>e-+Eb3px%ORe5k{s)0d^xpsg literal 0 HcmV?d00001 diff --git a/website/img/25.setGtkPath2.jpg b/website/img/25.setGtkPath2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d88a8382d9d7d1dd137b9b70fdff820c431d4e25 GIT binary patch literal 17477 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 literal 0 HcmV?d00001 diff --git a/website/img/26.setGtkPath3.jpg b/website/img/26.setGtkPath3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..29439d2e14d0b762dd3ba76ae5683e73857766f5 GIT binary patch literal 14195 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 mz8ON$W}YySgyS{N1p literal 0 HcmV?d00001 diff --git a/website/img/27.pycairoInst.jpg b/website/img/27.pycairoInst.jpg new file mode 100644 index 0000000000000000000000000000000000000000..71eb8e8d51730b2fa5e5c142c7f9e7210bb68e4e GIT binary patch literal 11334 zcmeI2RZ!f|m*{6;aCdhJ?k+(GcV~d$F2OB01b2eF2M;iV6WoHk2X}&71lauc<^J#1 z-mP1^b>Hs!*0;{XvFd*4?(^w>U3uLGU@OTf$^n2tAVA^o2YB595Q2T}Z2$mORR9YB z06_lx|CQHufXqK$|0DS)$^R(9Kf$lP032ih6MO_bkQM-k1BAx`z77J&06+j7{NGXh zFF{5@LPbMB{A(1$2Ef6=0pURK2na~<2!CA=kr0ppAb2==cQ$g?K%hX0>foU)KQW z@c)Ft;{YT8vs=W&K_Nt$72NT(mc;#x|0ngU$du|CW1uTQ;^!3$<@GDz)Kpuf`yu{$ zi>cwzW%?)F!jy*D){(|vL&_^)62@C|WH-He6Z>Lw{t8HDR~h6~SHC{e4{E>TJP!0U z!7zR~x4W#Fw0l;psvfrbSly$4e5`*38CM_V8go1n40(+6I4v z+r6o$KK;`9%pv?oKI+am;1zJzGq+bb=o<{4l;h52I`(O%Tw5;0pLqv?Y@B?AP7N%W zqR+qBJf>}udHneo?~#AWvS3O4FL7#FMk?px;HycA!}osTv#k>U;`txyB}RSV{!!<5 zt%`6^_Wz6hKcB|&$Ag89xs%16|EU z3)|!$B+jqoICp(Hsn;F#Q|IHe^i=F6&vIucD-CBA>me#Q^4mS-YX9m&HDwi5dbk2B zbi`bmtu>C4a;GUWDoUbz9v|}yW;{~+Bx2^C6!Aw?@r3mFc{6)@>V5w( zch*#sDtCIwoce&0k3`*iKs+lQ1-_+G9o?5KZ0`+-8<@P4uuDMnLsWHH3+^6=(8o8j z_y)IJzg3EeLRh0QvZwSp8K+6O(~qt;k0W!5HnUP{mXy3!ibTY8oVrD)*fVQg$OPY# z?uxTDIKAD;ASI^Px8e{{XlII}YPg@tW&&I#*bj&-v1_aVw;@NhpJy;Vogc`?#s}0$ zKDY5@gYIlk86)m`&u_61WVf3fH zvOx%WbH_&7!)%%$+gBs|hMZ>we{^&iY7|!tFX|}VWxg&UE!85vfTJ%qJ3Pi9Tf})B zKRC{t=aH~>Cu>G_XQ{(VF~{lfNaPN^nQBgx4Xw%Ux?BQy?d4B9LK~&05$9hwsF2}v z#2rENpLre89EHUK5NK`6JyRrTgsB&B^nh7nJ˧kG(wB_}OQHE#_?=TOJIRtB#E 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 E0nHo^a{vGU literal 0 HcmV?d00001 diff --git a/website/img/28.pygobjectInst.jpg b/website/img/28.pygobjectInst.jpg new file mode 100644 index 0000000000000000000000000000000000000000..87fb4cb726a1bc752a41d3db38f70d3f0e3a9e51 GIT binary patch literal 11875 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 IBHq{j8{-fQH~;_u literal 0 HcmV?d00001 diff --git a/website/img/29.pyGtkInst.jpg b/website/img/29.pyGtkInst.jpg new file mode 100644 index 0000000000000000000000000000000000000000..593bacf41baebcdc4c8f198f4b9a2e074afda0ae GIT binary patch literal 12495 zcmb_?Wl&sAx9$uxgZtnf+&#Fv2Z!J?xH}~1;2Kt1P$)N-6cq{5F~+{uTGtM z&v)g=z2~l7-Tl<=UcFa!b@%Fi_Ig=<*#=-M$tlVKfIuKX;qL=@*#Qv9`r26o0II40 zW&i+y{5SoTmotQj@=GrO2N}Q!3IhSD0SGui5DxHV5I_n50)P0z}009UFKoCG6R73>O-)cl82mk~|MnJ(QAOsTO>7}4T7jTJb zU-JuidN(!G(9tt^wK4K(B=!B(ruh3&00IK&Z~mVGL_k4827wVFsDJAfaR5LN0s


    #USV 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 L|Nb)QW#xYWU4Ix0 literal 0 HcmV?d00001 diff --git a/website/img/30.shellMkDirProfiles.jpg b/website/img/30.shellMkDirProfiles.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c5e25a196d9263440de2d745eff2aa636bef2072 GIT binary patch literal 16895 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

    _JE==uDxq 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 Tw%$Dm_B2yt{68!%FFyeNL literal 0 HcmV?d00001 diff --git a/website/img/docs.HudHowTo4.png b/website/img/docs.HudHowTo4.png new file mode 100644 index 0000000000000000000000000000000000000000..2610a30a5d44faf07ac47dcdbe1351a790c4ea15 GIT binary patch literal 5546 zcmV;b6; zd3Y1m`o}+$bcfQ?ri5-3TCi9s3Y4;mfGBRrqQC_OQN*f%pcfGk*{%Yz$>N3rihv;M z72%2~f+#;l#DYs%TgpSYHQ3+u-e6ijlmI!%>aZ2bH0Rx{-S&V=6`P*45(yZ{YUr#fgV%>|(i~ z{Xlo`R7Ir(lCFN*qPhoZ$o8$pNkRL8sKz3D00@$E5gmPK9ZD}hr^@`LQBsyL6ch@x zZJH_V4WG-(3kiJtg5mqenxQbi1plq{GIk&HSs_xKzgB$>ZQB@ zL^f8U5a3*~Xw$yESkVWRlf6?bxtOf{N~g+@lZDXXKJfIhna$5PfH6Mj*-$Fs*C@y& zm|XGI5?&slv0_I53$MQeUg3oOD1Q&`Yw5?2nH8kuYHDpiCcu+HQC>(g{O%+3{a+Ob zK;KAT;)6X=2m;&EOF3I)xp8duP}S(?P)^RD9_rtXyZiS?P$*C+6bOO>g+f4~(7)@y zD^Li6AdxX%lxevZbq+Kdu z^??jlr`H5~<@5x@=PqAg=8L`|T>13`U4JYCLBWWw;f!w=Nc*NL~l+PXowH;LXtcYfSUuAq~=x7I?#)fN-d(5AyW{w zc8_1xkmp=L34-ACOM$Gf$4*`Ia&*k;5Q?z(By&1=lb>^eppdAVx~s~oBO~}@SOlJc zMpH?K?8VLD0jwC-o@N`4@XAH=M%~w6#ktGn6pK0pNyn679hu+88%Y*9kzG#nX1*Yn z*V`OKQFXu_weNvMQMp7DZ-ql;?AgCipyRhoN^S}9AuX?h4nbZ3oURI{v2Z7lsiT1cjm|ScIA&L#Ro|D34HGkHU6cpYJW=wGlx~RX3-Z)^Qw!hhSgi*&U5CjMah-7{nZ)9Yic;{C> z&ehSlNiae{SiSa8f}I{JKE#LaVU18KAlO@=pj;%_TS-!U6GRDq&MYP6isggui2Xfh z3J}G{bZq8JYHm3IZJK&17NsuDYuHSpS+~@PoVxO1J>D1fa2ha|G$MneCY;N)aUeR5U!gY1OU> zd@#NnA5`V>T$fv~b0|ceP5XT!f+*JLDA!5km1!u|NTgOBL|+dDeIoq$BdgeD`vQO@ zB1;l-Ra;7u2eKq0>$KRq=H}381A`+@A|N0rEDUYNJ(O`k$+Es43#&Y1q{?DuWoua( z>cy3;Q=G3fos3%aRnj6{)fATUUC?MSTqBU>syB(`zl;u$6LTDAabym)?`%FjBmMF7 zRuJf;Bt5^9m;g@-DnvwCX5ZO-tL5$47l8gTfyAr4Ihj++O+h}$z^R;4;#EFKq91$D z7FrFe>K@P*uFs(qfyebUZA&_K?aF24*iiQGx@6{C_4oBfB9jC4 zYlzEL zlFY%2#hkcOX0fcY>VVj?fB0e{hc6ZaFflONaOz4a zZGwGC{;LQ9xV3p>(sD}asP^SdZYihpEDx#5`kF&ooOpB9yP#j~2~|B7$W?PFI{_u? z$U1$5malqHT&6*g&9?)*vGok&lWxLOfVmTTFb-cp-{ZW!HO=Ba8K;%xoMhgq24)U2GP{e1m{Z^`H# zN7AjK#E1A3E=UOCC7W}Q@6&VW5fzA^hj|Set&X407g#M{J*jncj`q{JT>8ZX^IdwL zLuKsQKb2j?$2u*dBy%RO+>pLIHJkJt{ji^&S5`e6ux#H>tO%;Hi{;9omUf*rrcLS& zLPLV}1_Z(AT^~RO<3`&KpUI*i?q2Zla;mOtO=V~t%KVG;0OcsKx3}w3f(%(BSPmIOI9Gsu4g?+Fq?$qyZ=z z^sAV9608b}hHt+DEpV)Gx2Aex`ksypL~hHt-KF1`GdsmD2p zz6WX8_U*-r;J(AfOhJW$O#`LvOPll`WDysZYHr+E$EAQ$sq63F zwYtEbf7iKbyGP;Kx%^ z>e!RNx1PG9E&|vrph_<8;P&nh@>O9JufF#gza$^xt1TGe2!?d+^?&a|f^$G94kXB%h= ze_(A!5hK2R8cUi0->^nzX}hQ1!}M)R7OnY&J8o8Sa{p$YoHBxoem}8pWHbOzJusfo zsqe6R)vYvA>bRJGl;6{8?tVG&>OdxZ?$5HMT6I;=c2?MG&<`i!_{0$;5d$JmFraQv$=X8G*-W=ueY+7t#IA7^i*ueB1+UNNLB1yZ(}*7BCecxyxp&K9R`(7wY(F@=H$y*(x{x>X|cYDUNZ_Z-Ae_}2f8NVUsd`ye@7f3#T zo+xjnNjd`l+=jed$(mcPgMfBa?0Tb{Fxe67U7-W_Pss+Hl@ zao}fYO8=x(bnU$B@MbkPqI&wZL)2Wbn9r7P7VX{&xB5`+1mI@KxHY z!!n*G#ROz4mm_TXW{KA05-1$7isI~i^8Ie7o4+Sdc2cqI>r8^Sq|vmL-U-4<$pQc8t z#=us$@ZhMYShQ+A`*!?`}9J9^;8Jx>fpkWHQy`| z(E3@ViZoVj`k0VTPXf?xLVMER|B02U1#}tL+G06dcn98zk3!(1b2T?h6kq(%@Y&IJ zuB~sD+S8e13>H>-vp!#f-|4sM`RGh`A57t5Ru*YT53**#Bzle9W0F_t8OFOK+pzud z(QMxLJDFJ**|&8uBiE&J_ndhENVJVk!mY)+L+Ol}k4v>!^ZFqZWTXT#_z>_2vn^Cy31+L*01+Z|}< zy70|XdpcUT!H2688n+n2k3XwfwBT)Kj2lX3UJ1TIVI=gro5gGUm>KB5jGDKF|5VNX1;LBPR}M3G;#$sM$^)N3;=A6(`rQj^cI2fm*r=Y*vQ<-f zZSp1X^rvOp4%|C*rp*R;ed#ldeqlIEGV>8-+2RD<7LNF4iAQ>=`S|m*Jada)2JO|P zC%yb3+3P{t_?QpdjxhB7|B;e3j?s7bz#}A<@e8-Yn3~(@?X`1V_-3s=9jzO#TIIPf z4!dy$_r+m1^@cm3+|()VfO1o(xC6>fo#GBCH+70xK-tp7Tz&Q2kutVuhypTaxcu{4 z?z^ocVPVbx@%vhhQ$3Wcg0XAam__I3pi-+zDb-p`Pc7F`cy_wQJXfbzmr<$JgftH& zJUW)HcRaw91#37{YX9cg$&bB4@0n{j{QI8`PHg8YPMw9j5d~uapB?&*IA0~@B?YAY z`6KI=E~3|l|MK61JLnc@{aFqB3M<)sZ-U*cVK$L~zhnv!EA^&d=H`$-&FJrN%(*S73Y8zG!4!n@gFqu2#dXkwf%NftTtqJG7I1QjMyLvh5z;a#2RBP22@4vFFew31V#Pk z^25?GSa=kY!&DEG)`rWZ%}cqnN^~~nKX37J+sA=1NZM@1tUk=N1E~zH65d6G4rKMl zeZ&u*2Ggwz?*Q&?f0JqVTgQK>6M9c5;*vi(TB@Z}V^6c~miAlJW|Fiem4RChGrCHw z7CvGcn~#6R(0Q|AszyXC7G+}`{3rfbG&NU6`W`wn_{(hHS zTvwi6Q!ED5=mD|J*twB|FAbu5up>e{*Rdu#tF7=ZJgOzF8rAe1MT#ahwJ7zEPb|Dj z4Jzr10Pt}@)R(y{6Q!q$c7Eo)lXV;ytJ(UoT+djXl#gEaHLu)L#;{%^__e^vzu>Td zD?+&12=DCS6R5v?XZuyp<<8uB8c&}XV(iE~L~SNB*PcS%eXdihFK5MS4G}BHMakpK zcQda2(K&=qAAO#0l24JHok{Yy8yUO&IB}0H zGi#LGoGVN z`!~2;iK{|5SA}F0y{VLhC86#)G6+Oa#N?c1IkUE;tnV`b&5Nn z+|(&{1FHDKLR4xs%_BRKqcwdnLPg#VgCymArS*T)zFsjF~yTt}Q#ktdv~(Equr!J2yHG2UOJH#mE^e z*;93~IOn%noc6qpsjW>Pz_5GjJxpJBoQcan;mF~idE>b|**JRyPi#43l5fcDWTw3N z8HW%3K%&2wUQD+xoe7IRBz&OGhRSU~3JQ|v19 zk1!)?7@a%B(K0%kR&9HuEj#Y?PFuq#cnD92`}6+XLjaUr+Q`S3%a}Ox?~@xhG@QEY zrD^O85)`d?Y|eNZx1C8>e@}`{9$oI5fXq=b-Gci^a_o+Ov+9G{sGBxMiIy(id(~LK zT0ZH5?DxsJiL9OYB6+%BIkw;(8Z{rqXtkgHf^GmO0}8=|`Qu{gyDWu~JBFBT)(gmH zvF1n)@7!MVFXi?GWO^~(t9+f)h=E2A2ECef=L$JCH;I5MJH;9AnQV{rQgW^ zWq;z-?g2#HvxESt3)6l~=h%)6IxpU2zmOXPPM_Fp|H2MVojAerMDx{?s{%4d#dLQE z`Y>h9E;6#RICk)VBn^At#LlGlK|C{O6X}0tk$U7?o*TKw+1*3w70%+@)a*>|0UnB@bOqyOd8599TOS9Y&WCkZ83YE_0{xv=?gZ_ zzMoyIrqH8HClY%PVAk6^@M~MsV=eh)A=w|zAhA;d!~eaS-b=Qb*ckcI8e)#Ep;uyO z22EH_&xM~lyL$ling1}F!c3Ya&bKQEc4LZLVCSaRxCM4@>J4{5xv5j!0p+GnF_}ZT saZ@YYb0{};jebomH#{wqo9mPR2d1wn7r}9hV*mgE07*qoM6N<$g5*dgk^lez literal 0 HcmV?d00001 diff --git a/website/img/docs.HudHowTo5.png b/website/img/docs.HudHowTo5.png new file mode 100644 index 0000000000000000000000000000000000000000..29e42675ec5b3f7e6841d73c32d65f07065de3af GIT binary patch literal 1804 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 b=f(d3M73-9ffxsL00000NkvXXu0mjfEeX)g literal 0 HcmV?d00001 diff --git a/website/img/docs.HudHowTo7.png b/website/img/docs.HudHowTo7.png new file mode 100644 index 0000000000000000000000000000000000000000..e3bada614b7b574b76221979425bcfed001911d7 GIT binary patch literal 11620 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 - -

    - - + + +
    + +

    Welcome!

    + +

    fpdb is a database program to track your online poker games, the behaviour of the other players and your winnings/losses. Supports Holdem, Omaha, Stud and Razz for cash games as well as SnG and MTT tournaments with more possibly coming in the future. The software is currently in alpha status, which means some of the features are not working yet. As it's open source you're free to add any feature you like or modify the existing ones to fit your needs.

    + +

    To see what fpdb can do, go to the features page. If you're ready to test it, take a look at the documentation.

    + +
    + + diff --git a/website/license.php b/website/license.php index 246d6f55..d7c3fa9b 100644 --- a/website/license.php +++ b/website/license.php @@ -1,689 +1,687 @@ - - -
    - -

    License

    - -
    -
    -                    GNU AFFERO GENERAL PUBLIC LICENSE
    -                       Version 3, 19 November 2007
    -
    - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
    - Everyone is permitted to copy and distribute verbatim copies
    - of this license document, but changing it is not allowed.
    -
    -                            Preamble
    -
    -  The GNU Affero General Public License is a free, copyleft license for
    -software and other kinds of works, specifically designed to ensure
    -cooperation with the community in the case of network server software.
    -
    -  The licenses for most software and other practical works are designed
    -to take away your freedom to share and change the works.  By contrast,
    -our General Public Licenses are intended to guarantee your freedom to
    -share and change all versions of a program--to make sure it remains free
    -software for all its users.
    -
    -  When we speak of free software, we are referring to freedom, not
    -price.  Our General Public Licenses are designed to make sure that you
    -have the freedom to distribute copies of free software (and charge for
    -them if you wish), that you receive source code or can get it if you
    -want it, that you can change the software or use pieces of it in new
    -free programs, and that you know you can do these things.
    -
    -  Developers that use our General Public Licenses protect your rights
    -with two steps: (1) assert copyright on the software, and (2) offer
    -you this License which gives you legal permission to copy, distribute
    -and/or modify the software.
    -
    -  A secondary benefit of defending all users' freedom is that
    -improvements made in alternate versions of the program, if they
    -receive widespread use, become available for other developers to
    -incorporate.  Many developers of free software are heartened and
    -encouraged by the resulting cooperation.  However, in the case of
    -software used on network servers, this result may fail to come about.
    -The GNU General Public License permits making a modified version and
    -letting the public access it on a server without ever releasing its
    -source code to the public.
    -
    -  The GNU Affero General Public License is designed specifically to
    -ensure that, in such cases, the modified source code becomes available
    -to the community.  It requires the operator of a network server to
    -provide the source code of the modified version running there to the
    -users of that server.  Therefore, public use of a modified version, on
    -a publicly accessible server, gives the public access to the source
    -code of the modified version.
    -
    -  An older license, called the Affero General Public License and
    -published by Affero, was designed to accomplish similar goals.  This is
    -a different license, not a version of the Affero GPL, but Affero has
    -released a new version of the Affero GPL which permits relicensing under
    -this license.
    -
    -  The precise terms and conditions for copying, distribution and
    -modification follow.
    -
    -                       TERMS AND CONDITIONS
    -
    -  0. Definitions.
    -
    -  "This License" refers to version 3 of the GNU Affero General Public License.
    -
    -  "Copyright" also means copyright-like laws that apply to other kinds of
    -works, such as semiconductor masks.
    -
    -  "The Program" refers to any copyrightable work licensed under this
    -License.  Each licensee is addressed as "you".  "Licensees" and
    -"recipients" may be individuals or organizations.
    -
    -  To "modify" a work means to copy from or adapt all or part of the work
    -in a fashion requiring copyright permission, other than the making of an
    -exact copy.  The resulting work is called a "modified version" of the
    -earlier work or a work "based on" the earlier work.
    -
    -  A "covered work" means either the unmodified Program or a work based
    -on the Program.
    -
    -  To "propagate" a work means to do anything with it that, without
    -permission, would make you directly or secondarily liable for
    -infringement under applicable copyright law, except executing it on a
    -computer or modifying a private copy.  Propagation includes copying,
    -distribution (with or without modification), making available to the
    -public, and in some countries other activities as well.
    -
    -  To "convey" a work means any kind of propagation that enables other
    -parties to make or receive copies.  Mere interaction with a user through
    -a computer network, with no transfer of a copy, is not conveying.
    -
    -  An interactive user interface displays "Appropriate Legal Notices"
    -to the extent that it includes a convenient and prominently visible
    -feature that (1) displays an appropriate copyright notice, and (2)
    -tells the user that there is no warranty for the work (except to the
    -extent that warranties are provided), that licensees may convey the
    -work under this License, and how to view a copy of this License.  If
    -the interface presents a list of user commands or options, such as a
    -menu, a prominent item in the list meets this criterion.
    -
    -  1. Source Code.
    -
    -  The "source code" for a work means the preferred form of the work
    -for making modifications to it.  "Object code" means any non-source
    -form of a work.
    -
    -  A "Standard Interface" means an interface that either is an official
    -standard defined by a recognized standards body, or, in the case of
    -interfaces specified for a particular programming language, one that
    -is widely used among developers working in that language.
    -
    -  The "System Libraries" of an executable work include anything, other
    -than the work as a whole, that (a) is included in the normal form of
    -packaging a Major Component, but which is not part of that Major
    -Component, and (b) serves only to enable use of the work with that
    -Major Component, or to implement a Standard Interface for which an
    -implementation is available to the public in source code form.  A
    -"Major Component", in this context, means a major essential component
    -(kernel, window system, and so on) of the specific operating system
    -(if any) on which the executable work runs, or a compiler used to
    -produce the work, or an object code interpreter used to run it.
    -
    -  The "Corresponding Source" for a work in object code form means all
    -the source code needed to generate, install, and (for an executable
    -work) run the object code and to modify the work, including scripts to
    -control those activities.  However, it does not include the work's
    -System Libraries, or general-purpose tools or generally available free
    -programs which are used unmodified in performing those activities but
    -which are not part of the work.  For example, Corresponding Source
    -includes interface definition files associated with source files for
    -the work, and the source code for shared libraries and dynamically
    -linked subprograms that the work is specifically designed to require,
    -such as by intimate data communication or control flow between those
    -subprograms and other parts of the work.
    -
    -  The Corresponding Source need not include anything that users
    -can regenerate automatically from other parts of the Corresponding
    -Source.
    -
    -  The Corresponding Source for a work in source code form is that
    -same work.
    -
    -  2. Basic Permissions.
    -
    -  All rights granted under this License are granted for the term of
    -copyright on the Program, and are irrevocable provided the stated
    -conditions are met.  This License explicitly affirms your unlimited
    -permission to run the unmodified Program.  The output from running a
    -covered work is covered by this License only if the output, given its
    -content, constitutes a covered work.  This License acknowledges your
    -rights of fair use or other equivalent, as provided by copyright law.
    -
    -  You may make, run and propagate covered works that you do not
    -convey, without conditions so long as your license otherwise remains
    -in force.  You may convey covered works to others for the sole purpose
    -of having them make modifications exclusively for you, or provide you
    -with facilities for running those works, provided that you comply with
    -the terms of this License in conveying all material for which you do
    -not control copyright.  Those thus making or running the covered works
    -for you must do so exclusively on your behalf, under your direction
    -and control, on terms that prohibit them from making any copies of
    -your copyrighted material outside their relationship with you.
    -
    -  Conveying under any other circumstances is permitted solely under
    -the conditions stated below.  Sublicensing is not allowed; section 10
    -makes it unnecessary.
    -
    -  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
    -
    -  No covered work shall be deemed part of an effective technological
    -measure under any applicable law fulfilling obligations under article
    -11 of the WIPO copyright treaty adopted on 20 December 1996, or
    -similar laws prohibiting or restricting circumvention of such
    -measures.
    -
    -  When you convey a covered work, you waive any legal power to forbid
    -circumvention of technological measures to the extent such circumvention
    -is effected by exercising rights under this License with respect to
    -the covered work, and you disclaim any intention to limit operation or
    -modification of the work as a means of enforcing, against the work's
    -users, your or third parties' legal rights to forbid circumvention of
    -technological measures.
    -
    -  4. Conveying Verbatim Copies.
    -
    -  You may convey verbatim copies of the Program's source code as you
    -receive it, in any medium, provided that you conspicuously and
    -appropriately publish on each copy an appropriate copyright notice;
    -keep intact all notices stating that this License and any
    -non-permissive terms added in accord with section 7 apply to the code;
    -keep intact all notices of the absence of any warranty; and give all
    -recipients a copy of this License along with the Program.
    -
    -  You may charge any price or no price for each copy that you convey,
    -and you may offer support or warranty protection for a fee.
    -
    -  5. Conveying Modified Source Versions.
    -
    -  You may convey a work based on the Program, or the modifications to
    -produce it from the Program, in the form of source code under the
    -terms of section 4, provided that you also meet all of these conditions:
    -
    -    a) The work must carry prominent notices stating that you modified
    -    it, and giving a relevant date.
    -
    -    b) The work must carry prominent notices stating that it is
    -    released under this License and any conditions added under section
    -    7.  This requirement modifies the requirement in section 4 to
    -    "keep intact all notices".
    -
    -    c) You must license the entire work, as a whole, under this
    -    License to anyone who comes into possession of a copy.  This
    -    License will therefore apply, along with any applicable section 7
    -    additional terms, to the whole of the work, and all its parts,
    -    regardless of how they are packaged.  This License gives no
    -    permission to license the work in any other way, but it does not
    -    invalidate such permission if you have separately received it.
    -
    -    d) If the work has interactive user interfaces, each must display
    -    Appropriate Legal Notices; however, if the Program has interactive
    -    interfaces that do not display Appropriate Legal Notices, your
    -    work need not make them do so.
    -
    -  A compilation of a covered work with other separate and independent
    -works, which are not by their nature extensions of the covered work,
    -and which are not combined with it such as to form a larger program,
    -in or on a volume of a storage or distribution medium, is called an
    -"aggregate" if the compilation and its resulting copyright are not
    -used to limit the access or legal rights of the compilation's users
    -beyond what the individual works permit.  Inclusion of a covered work
    -in an aggregate does not cause this License to apply to the other
    -parts of the aggregate.
    -
    -  6. Conveying Non-Source Forms.
    -
    -  You may convey a covered work in object code form under the terms
    -of sections 4 and 5, provided that you also convey the
    -machine-readable Corresponding Source under the terms of this License,
    -in one of these ways:
    -
    -    a) Convey the object code in, or embodied in, a physical product
    -    (including a physical distribution medium), accompanied by the
    -    Corresponding Source fixed on a durable physical medium
    -    customarily used for software interchange.
    -
    -    b) Convey the object code in, or embodied in, a physical product
    -    (including a physical distribution medium), accompanied by a
    -    written offer, valid for at least three years and valid for as
    -    long as you offer spare parts or customer support for that product
    -    model, to give anyone who possesses the object code either (1) a
    -    copy of the Corresponding Source for all the software in the
    -    product that is covered by this License, on a durable physical
    -    medium customarily used for software interchange, for a price no
    -    more than your reasonable cost of physically performing this
    -    conveying of source, or (2) access to copy the
    -    Corresponding Source from a network server at no charge.
    -
    -    c) Convey individual copies of the object code with a copy of the
    -    written offer to provide the Corresponding Source.  This
    -    alternative is allowed only occasionally and noncommercially, and
    -    only if you received the object code with such an offer, in accord
    -    with subsection 6b.
    -
    -    d) Convey the object code by offering access from a designated
    -    place (gratis or for a charge), and offer equivalent access to the
    -    Corresponding Source in the same way through the same place at no
    -    further charge.  You need not require recipients to copy the
    -    Corresponding Source along with the object code.  If the place to
    -    copy the object code is a network server, the Corresponding Source
    -    may be on a different server (operated by you or a third party)
    -    that supports equivalent copying facilities, provided you maintain
    -    clear directions next to the object code saying where to find the
    -    Corresponding Source.  Regardless of what server hosts the
    -    Corresponding Source, you remain obligated to ensure that it is
    -    available for as long as needed to satisfy these requirements.
    -
    -    e) Convey the object code using peer-to-peer transmission, provided
    -    you inform other peers where the object code and Corresponding
    -    Source of the work are being offered to the general public at no
    -    charge under subsection 6d.
    -
    -  A separable portion of the object code, whose source code is excluded
    -from the Corresponding Source as a System Library, need not be
    -included in conveying the object code work.
    -
    -  A "User Product" is either (1) a "consumer product", which means any
    -tangible personal property which is normally used for personal, family,
    -or household purposes, or (2) anything designed or sold for incorporation
    -into a dwelling.  In determining whether a product is a consumer product,
    -doubtful cases shall be resolved in favor of coverage.  For a particular
    -product received by a particular user, "normally used" refers to a
    -typical or common use of that class of product, regardless of the status
    -of the particular user or of the way in which the particular user
    -actually uses, or expects or is expected to use, the product.  A product
    -is a consumer product regardless of whether the product has substantial
    -commercial, industrial or non-consumer uses, unless such uses represent
    -the only significant mode of use of the product.
    -
    -  "Installation Information" for a User Product means any methods,
    -procedures, authorization keys, or other information required to install
    -and execute modified versions of a covered work in that User Product from
    -a modified version of its Corresponding Source.  The information must
    -suffice to ensure that the continued functioning of the modified object
    -code is in no case prevented or interfered with solely because
    -modification has been made.
    -
    -  If you convey an object code work under this section in, or with, or
    -specifically for use in, a User Product, and the conveying occurs as
    -part of a transaction in which the right of possession and use of the
    -User Product is transferred to the recipient in perpetuity or for a
    -fixed term (regardless of how the transaction is characterized), the
    -Corresponding Source conveyed under this section must be accompanied
    -by the Installation Information.  But this requirement does not apply
    -if neither you nor any third party retains the ability to install
    -modified object code on the User Product (for example, the work has
    -been installed in ROM).
    -
    -  The requirement to provide Installation Information does not include a
    -requirement to continue to provide support service, warranty, or updates
    -for a work that has been modified or installed by the recipient, or for
    -the User Product in which it has been modified or installed.  Access to a
    -network may be denied when the modification itself materially and
    -adversely affects the operation of the network or violates the rules and
    -protocols for communication across the network.
    -
    -  Corresponding Source conveyed, and Installation Information provided,
    -in accord with this section must be in a format that is publicly
    -documented (and with an implementation available to the public in
    -source code form), and must require no special password or key for
    -unpacking, reading or copying.
    -
    -  7. Additional Terms.
    -
    -  "Additional permissions" are terms that supplement the terms of this
    -License by making exceptions from one or more of its conditions.
    -Additional permissions that are applicable to the entire Program shall
    -be treated as though they were included in this License, to the extent
    -that they are valid under applicable law.  If additional permissions
    -apply only to part of the Program, that part may be used separately
    -under those permissions, but the entire Program remains governed by
    -this License without regard to the additional permissions.
    -
    -  When you convey a copy of a covered work, you may at your option
    -remove any additional permissions from that copy, or from any part of
    -it.  (Additional permissions may be written to require their own
    -removal in certain cases when you modify the work.)  You may place
    -additional permissions on material, added by you to a covered work,
    -for which you have or can give appropriate copyright permission.
    -
    -  Notwithstanding any other provision of this License, for material you
    -add to a covered work, you may (if authorized by the copyright holders of
    -that material) supplement the terms of this License with terms:
    -
    -    a) Disclaiming warranty or limiting liability differently from the
    -    terms of sections 15 and 16 of this License; or
    -
    -    b) Requiring preservation of specified reasonable legal notices or
    -    author attributions in that material or in the Appropriate Legal
    -    Notices displayed by works containing it; or
    -
    -    c) Prohibiting misrepresentation of the origin of that material, or
    -    requiring that modified versions of such material be marked in
    -    reasonable ways as different from the original version; or
    -
    -    d) Limiting the use for publicity purposes of names of licensors or
    -    authors of the material; or
    -
    -    e) Declining to grant rights under trademark law for use of some
    -    trade names, trademarks, or service marks; or
    -
    -    f) Requiring indemnification of licensors and authors of that
    -    material by anyone who conveys the material (or modified versions of
    -    it) with contractual assumptions of liability to the recipient, for
    -    any liability that these contractual assumptions directly impose on
    -    those licensors and authors.
    -
    -  All other non-permissive additional terms are considered "further
    -restrictions" within the meaning of section 10.  If the Program as you
    -received it, or any part of it, contains a notice stating that it is
    -governed by this License along with a term that is a further
    -restriction, you may remove that term.  If a license document contains
    -a further restriction but permits relicensing or conveying under this
    -License, you may add to a covered work material governed by the terms
    -of that license document, provided that the further restriction does
    -not survive such relicensing or conveying.
    -
    -  If you add terms to a covered work in accord with this section, you
    -must place, in the relevant source files, a statement of the
    -additional terms that apply to those files, or a notice indicating
    -where to find the applicable terms.
    -
    -  Additional terms, permissive or non-permissive, may be stated in the
    -form of a separately written license, or stated as exceptions;
    -the above requirements apply either way.
    -
    -  8. Termination.
    -
    -  You may not propagate or modify a covered work except as expressly
    -provided under this License.  Any attempt otherwise to propagate or
    -modify it is void, and will automatically terminate your rights under
    -this License (including any patent licenses granted under the third
    -paragraph of section 11).
    -
    -  However, if you cease all violation of this License, then your
    -license from a particular copyright holder is reinstated (a)
    -provisionally, unless and until the copyright holder explicitly and
    -finally terminates your license, and (b) permanently, if the copyright
    -holder fails to notify you of the violation by some reasonable means
    -prior to 60 days after the cessation.
    -
    -  Moreover, your license from a particular copyright holder is
    -reinstated permanently if the copyright holder notifies you of the
    -violation by some reasonable means, this is the first time you have
    -received notice of violation of this License (for any work) from that
    -copyright holder, and you cure the violation prior to 30 days after
    -your receipt of the notice.
    -
    -  Termination of your rights under this section does not terminate the
    -licenses of parties who have received copies or rights from you under
    -this License.  If your rights have been terminated and not permanently
    -reinstated, you do not qualify to receive new licenses for the same
    -material under section 10.
    -
    -  9. Acceptance Not Required for Having Copies.
    -
    -  You are not required to accept this License in order to receive or
    -run a copy of the Program.  Ancillary propagation of a covered work
    -occurring solely as a consequence of using peer-to-peer transmission
    -to receive a copy likewise does not require acceptance.  However,
    -nothing other than this License grants you permission to propagate or
    -modify any covered work.  These actions infringe copyright if you do
    -not accept this License.  Therefore, by modifying or propagating a
    -covered work, you indicate your acceptance of this License to do so.
    -
    -  10. Automatic Licensing of Downstream Recipients.
    -
    -  Each time you convey a covered work, the recipient automatically
    -receives a license from the original licensors, to run, modify and
    -propagate that work, subject to this License.  You are not responsible
    -for enforcing compliance by third parties with this License.
    -
    -  An "entity transaction" is a transaction transferring control of an
    -organization, or substantially all assets of one, or subdividing an
    -organization, or merging organizations.  If propagation of a covered
    -work results from an entity transaction, each party to that
    -transaction who receives a copy of the work also receives whatever
    -licenses to the work the party's predecessor in interest had or could
    -give under the previous paragraph, plus a right to possession of the
    -Corresponding Source of the work from the predecessor in interest, if
    -the predecessor has it or can get it with reasonable efforts.
    -
    -  You may not impose any further restrictions on the exercise of the
    -rights granted or affirmed under this License.  For example, you may
    -not impose a license fee, royalty, or other charge for exercise of
    -rights granted under this License, and you may not initiate litigation
    -(including a cross-claim or counterclaim in a lawsuit) alleging that
    -any patent claim is infringed by making, using, selling, offering for
    -sale, or importing the Program or any portion of it.
    -
    -  11. Patents.
    -
    -  A "contributor" is a copyright holder who authorizes use under this
    -License of the Program or a work on which the Program is based.  The
    -work thus licensed is called the contributor's "contributor version".
    -
    -  A contributor's "essential patent claims" are all patent claims
    -owned or controlled by the contributor, whether already acquired or
    -hereafter acquired, that would be infringed by some manner, permitted
    -by this License, of making, using, or selling its contributor version,
    -but do not include claims that would be infringed only as a
    -consequence of further modification of the contributor version.  For
    -purposes of this definition, "control" includes the right to grant
    -patent sublicenses in a manner consistent with the requirements of
    -this License.
    -
    -  Each contributor grants you a non-exclusive, worldwide, royalty-free
    -patent license under the contributor's essential patent claims, to
    -make, use, sell, offer for sale, import and otherwise run, modify and
    -propagate the contents of its contributor version.
    -
    -  In the following three paragraphs, a "patent license" is any express
    -agreement or commitment, however denominated, not to enforce a patent
    -(such as an express permission to practice a patent or covenant not to
    -sue for patent infringement).  To "grant" such a patent license to a
    -party means to make such an agreement or commitment not to enforce a
    -patent against the party.
    -
    -  If you convey a covered work, knowingly relying on a patent license,
    -and the Corresponding Source of the work is not available for anyone
    -to copy, free of charge and under the terms of this License, through a
    -publicly available network server or other readily accessible means,
    -then you must either (1) cause the Corresponding Source to be so
    -available, or (2) arrange to deprive yourself of the benefit of the
    -patent license for this particular work, or (3) arrange, in a manner
    -consistent with the requirements of this License, to extend the patent
    -license to downstream recipients.  "Knowingly relying" means you have
    -actual knowledge that, but for the patent license, your conveying the
    -covered work in a country, or your recipient's use of the covered work
    -in a country, would infringe one or more identifiable patents in that
    -country that you have reason to believe are valid.
    -
    -  If, pursuant to or in connection with a single transaction or
    -arrangement, you convey, or propagate by procuring conveyance of, a
    -covered work, and grant a patent license to some of the parties
    -receiving the covered work authorizing them to use, propagate, modify
    -or convey a specific copy of the covered work, then the patent license
    -you grant is automatically extended to all recipients of the covered
    -work and works based on it.
    -
    -  A patent license is "discriminatory" if it does not include within
    -the scope of its coverage, prohibits the exercise of, or is
    -conditioned on the non-exercise of one or more of the rights that are
    -specifically granted under this License.  You may not convey a covered
    -work if you are a party to an arrangement with a third party that is
    -in the business of distributing software, under which you make payment
    -to the third party based on the extent of your activity of conveying
    -the work, and under which the third party grants, to any of the
    -parties who would receive the covered work from you, a discriminatory
    -patent license (a) in connection with copies of the covered work
    -conveyed by you (or copies made from those copies), or (b) primarily
    -for and in connection with specific products or compilations that
    -contain the covered work, unless you entered into that arrangement,
    -or that patent license was granted, prior to 28 March 2007.
    -
    -  Nothing in this License shall be construed as excluding or limiting
    -any implied license or other defenses to infringement that may
    -otherwise be available to you under applicable patent law.
    -
    -  12. No Surrender of Others' Freedom.
    -
    -  If conditions are imposed on you (whether by court order, agreement or
    -otherwise) that contradict the conditions of this License, they do not
    -excuse you from the conditions of this License.  If you cannot convey a
    -covered work so as to satisfy simultaneously your obligations under this
    -License and any other pertinent obligations, then as a consequence you may
    -not convey it at all.  For example, if you agree to terms that obligate you
    -to collect a royalty for further conveying from those to whom you convey
    -the Program, the only way you could satisfy both those terms and this
    -License would be to refrain entirely from conveying the Program.
    -
    -  13. Remote Network Interaction; Use with the GNU General Public License.
    -
    -  Notwithstanding any other provision of this License, if you modify the
    -Program, your modified version must prominently offer all users
    -interacting with it remotely through a computer network (if your version
    -supports such interaction) an opportunity to receive the Corresponding
    -Source of your version by providing access to the Corresponding Source
    -from a network server at no charge, through some standard or customary
    -means of facilitating copying of software.  This Corresponding Source
    -shall include the Corresponding Source for any work covered by version 3
    -of the GNU General Public License that is incorporated pursuant to the
    -following paragraph.
    -
    -  Notwithstanding any other provision of this License, you have
    -permission to link or combine any covered work with a work licensed
    -under version 3 of the GNU General Public License into a single
    -combined work, and to convey the resulting work.  The terms of this
    -License will continue to apply to the part which is the covered work,
    -but the work with which it is combined will remain governed by version
    -3 of the GNU General Public License.
    -
    -  14. Revised Versions of this License.
    -
    -  The Free Software Foundation may publish revised and/or new versions of
    -the GNU Affero General Public License from time to time.  Such new versions
    -will be similar in spirit to the present version, but may differ in detail to
    -address new problems or concerns.
    -
    -  Each version is given a distinguishing version number.  If the
    -Program specifies that a certain numbered version of the GNU Affero General
    -Public License "or any later version" applies to it, you have the
    -option of following the terms and conditions either of that numbered
    -version or of any later version published by the Free Software
    -Foundation.  If the Program does not specify a version number of the
    -GNU Affero General Public License, you may choose any version ever published
    -by the Free Software Foundation.
    -
    -  If the Program specifies that a proxy can decide which future
    -versions of the GNU Affero General Public License can be used, that proxy's
    -public statement of acceptance of a version permanently authorizes you
    -to choose that version for the Program.
    -
    -  Later license versions may give you additional or different
    -permissions.  However, no additional obligations are imposed on any
    -author or copyright holder as a result of your choosing to follow a
    -later version.
    -
    -  15. Disclaimer of Warranty.
    -
    -  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
    -APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
    -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
    -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
    -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    -PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
    -IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
    -ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
    -
    -  16. Limitation of Liability.
    -
    -  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
    -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
    -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
    -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
    -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
    -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
    -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
    -SUCH DAMAGES.
    -
    -  17. Interpretation of Sections 15 and 16.
    -
    -  If the disclaimer of warranty and limitation of liability provided
    -above cannot be given local legal effect according to their terms,
    -reviewing courts shall apply local law that most closely approximates
    -an absolute waiver of all civil liability in connection with the
    -Program, unless a warranty or assumption of liability accompanies a
    -copy of the Program in return for a fee.
    -
    -                     END OF TERMS AND CONDITIONS
    -
    -            How to Apply These Terms to Your New Programs
    -
    -  If you develop a new program, and you want it to be of the greatest
    -possible use to the public, the best way to achieve this is to make it
    -free software which everyone can redistribute and change under these terms.
    -
    -  To do so, attach the following notices to the program.  It is safest
    -to attach them to the start of each source file to most effectively
    -state the exclusion of warranty; and each file should have at least
    -the "copyright" line and a pointer to where the full notice is found.
    -
    -    <one line to give the program's name and a brief idea of what it does.>
    -    Copyright (C) <year>  <name of author>
    -
    -    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, either version 3 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 Affero General Public License for more details.
    -
    -    You should have received a copy of the GNU Affero General Public License
    -    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    -
    -Also add information on how to contact you by electronic and paper mail.
    -
    -  If your software can interact with users remotely through a computer
    -network, you should also make sure that it provides a way for users to
    -get its source.  For example, if your program is a web application, its
    -interface could display a "Source" link that leads users to an archive
    -of the code.  There are many ways you could offer source, and different
    -solutions will be better for different programs; see section 13 for the
    -specific requirements.
    -
    -  You should also get your employer (if you work as a programmer) or school,
    -if any, to sign a "copyright disclaimer" for the program, if necessary.
    -For more information on this, and how to apply and follow the GNU AGPL, see
    -<http://www.gnu.org/licenses/>.
    -
    -
    -
    - -
    - - + + +
    + +

    License

    + +
    +
    +                    GNU AFFERO GENERAL PUBLIC LICENSE
    +                       Version 3, 19 November 2007
    +
    + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
    + Everyone is permitted to copy and distribute verbatim copies
    + of this license document, but changing it is not allowed.
    +
    +                            Preamble
    +
    +  The GNU Affero General Public License is a free, copyleft license for
    +software and other kinds of works, specifically designed to ensure
    +cooperation with the community in the case of network server software.
    +
    +  The licenses for most software and other practical works are designed
    +to take away your freedom to share and change the works.  By contrast,
    +our General Public Licenses are intended to guarantee your freedom to
    +share and change all versions of a program--to make sure it remains free
    +software for all its users.
    +
    +  When we speak of free software, we are referring to freedom, not
    +price.  Our General Public Licenses are designed to make sure that you
    +have the freedom to distribute copies of free software (and charge for
    +them if you wish), that you receive source code or can get it if you
    +want it, that you can change the software or use pieces of it in new
    +free programs, and that you know you can do these things.
    +
    +  Developers that use our General Public Licenses protect your rights
    +with two steps: (1) assert copyright on the software, and (2) offer
    +you this License which gives you legal permission to copy, distribute
    +and/or modify the software.
    +
    +  A secondary benefit of defending all users' freedom is that
    +improvements made in alternate versions of the program, if they
    +receive widespread use, become available for other developers to
    +incorporate.  Many developers of free software are heartened and
    +encouraged by the resulting cooperation.  However, in the case of
    +software used on network servers, this result may fail to come about.
    +The GNU General Public License permits making a modified version and
    +letting the public access it on a server without ever releasing its
    +source code to the public.
    +
    +  The GNU Affero General Public License is designed specifically to
    +ensure that, in such cases, the modified source code becomes available
    +to the community.  It requires the operator of a network server to
    +provide the source code of the modified version running there to the
    +users of that server.  Therefore, public use of a modified version, on
    +a publicly accessible server, gives the public access to the source
    +code of the modified version.
    +
    +  An older license, called the Affero General Public License and
    +published by Affero, was designed to accomplish similar goals.  This is
    +a different license, not a version of the Affero GPL, but Affero has
    +released a new version of the Affero GPL which permits relicensing under
    +this license.
    +
    +  The precise terms and conditions for copying, distribution and
    +modification follow.
    +
    +                       TERMS AND CONDITIONS
    +
    +  0. Definitions.
    +
    +  "This License" refers to version 3 of the GNU Affero General Public License.
    +
    +  "Copyright" also means copyright-like laws that apply to other kinds of
    +works, such as semiconductor masks.
    +
    +  "The Program" refers to any copyrightable work licensed under this
    +License.  Each licensee is addressed as "you".  "Licensees" and
    +"recipients" may be individuals or organizations.
    +
    +  To "modify" a work means to copy from or adapt all or part of the work
    +in a fashion requiring copyright permission, other than the making of an
    +exact copy.  The resulting work is called a "modified version" of the
    +earlier work or a work "based on" the earlier work.
    +
    +  A "covered work" means either the unmodified Program or a work based
    +on the Program.
    +
    +  To "propagate" a work means to do anything with it that, without
    +permission, would make you directly or secondarily liable for
    +infringement under applicable copyright law, except executing it on a
    +computer or modifying a private copy.  Propagation includes copying,
    +distribution (with or without modification), making available to the
    +public, and in some countries other activities as well.
    +
    +  To "convey" a work means any kind of propagation that enables other
    +parties to make or receive copies.  Mere interaction with a user through
    +a computer network, with no transfer of a copy, is not conveying.
    +
    +  An interactive user interface displays "Appropriate Legal Notices"
    +to the extent that it includes a convenient and prominently visible
    +feature that (1) displays an appropriate copyright notice, and (2)
    +tells the user that there is no warranty for the work (except to the
    +extent that warranties are provided), that licensees may convey the
    +work under this License, and how to view a copy of this License.  If
    +the interface presents a list of user commands or options, such as a
    +menu, a prominent item in the list meets this criterion.
    +
    +  1. Source Code.
    +
    +  The "source code" for a work means the preferred form of the work
    +for making modifications to it.  "Object code" means any non-source
    +form of a work.
    +
    +  A "Standard Interface" means an interface that either is an official
    +standard defined by a recognized standards body, or, in the case of
    +interfaces specified for a particular programming language, one that
    +is widely used among developers working in that language.
    +
    +  The "System Libraries" of an executable work include anything, other
    +than the work as a whole, that (a) is included in the normal form of
    +packaging a Major Component, but which is not part of that Major
    +Component, and (b) serves only to enable use of the work with that
    +Major Component, or to implement a Standard Interface for which an
    +implementation is available to the public in source code form.  A
    +"Major Component", in this context, means a major essential component
    +(kernel, window system, and so on) of the specific operating system
    +(if any) on which the executable work runs, or a compiler used to
    +produce the work, or an object code interpreter used to run it.
    +
    +  The "Corresponding Source" for a work in object code form means all
    +the source code needed to generate, install, and (for an executable
    +work) run the object code and to modify the work, including scripts to
    +control those activities.  However, it does not include the work's
    +System Libraries, or general-purpose tools or generally available free
    +programs which are used unmodified in performing those activities but
    +which are not part of the work.  For example, Corresponding Source
    +includes interface definition files associated with source files for
    +the work, and the source code for shared libraries and dynamically
    +linked subprograms that the work is specifically designed to require,
    +such as by intimate data communication or control flow between those
    +subprograms and other parts of the work.
    +
    +  The Corresponding Source need not include anything that users
    +can regenerate automatically from other parts of the Corresponding
    +Source.
    +
    +  The Corresponding Source for a work in source code form is that
    +same work.
    +
    +  2. Basic Permissions.
    +
    +  All rights granted under this License are granted for the term of
    +copyright on the Program, and are irrevocable provided the stated
    +conditions are met.  This License explicitly affirms your unlimited
    +permission to run the unmodified Program.  The output from running a
    +covered work is covered by this License only if the output, given its
    +content, constitutes a covered work.  This License acknowledges your
    +rights of fair use or other equivalent, as provided by copyright law.
    +
    +  You may make, run and propagate covered works that you do not
    +convey, without conditions so long as your license otherwise remains
    +in force.  You may convey covered works to others for the sole purpose
    +of having them make modifications exclusively for you, or provide you
    +with facilities for running those works, provided that you comply with
    +the terms of this License in conveying all material for which you do
    +not control copyright.  Those thus making or running the covered works
    +for you must do so exclusively on your behalf, under your direction
    +and control, on terms that prohibit them from making any copies of
    +your copyrighted material outside their relationship with you.
    +
    +  Conveying under any other circumstances is permitted solely under
    +the conditions stated below.  Sublicensing is not allowed; section 10
    +makes it unnecessary.
    +
    +  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
    +
    +  No covered work shall be deemed part of an effective technological
    +measure under any applicable law fulfilling obligations under article
    +11 of the WIPO copyright treaty adopted on 20 December 1996, or
    +similar laws prohibiting or restricting circumvention of such
    +measures.
    +
    +  When you convey a covered work, you waive any legal power to forbid
    +circumvention of technological measures to the extent such circumvention
    +is effected by exercising rights under this License with respect to
    +the covered work, and you disclaim any intention to limit operation or
    +modification of the work as a means of enforcing, against the work's
    +users, your or third parties' legal rights to forbid circumvention of
    +technological measures.
    +
    +  4. Conveying Verbatim Copies.
    +
    +  You may convey verbatim copies of the Program's source code as you
    +receive it, in any medium, provided that you conspicuously and
    +appropriately publish on each copy an appropriate copyright notice;
    +keep intact all notices stating that this License and any
    +non-permissive terms added in accord with section 7 apply to the code;
    +keep intact all notices of the absence of any warranty; and give all
    +recipients a copy of this License along with the Program.
    +
    +  You may charge any price or no price for each copy that you convey,
    +and you may offer support or warranty protection for a fee.
    +
    +  5. Conveying Modified Source Versions.
    +
    +  You may convey a work based on the Program, or the modifications to
    +produce it from the Program, in the form of source code under the
    +terms of section 4, provided that you also meet all of these conditions:
    +
    +    a) The work must carry prominent notices stating that you modified
    +    it, and giving a relevant date.
    +
    +    b) The work must carry prominent notices stating that it is
    +    released under this License and any conditions added under section
    +    7.  This requirement modifies the requirement in section 4 to
    +    "keep intact all notices".
    +
    +    c) You must license the entire work, as a whole, under this
    +    License to anyone who comes into possession of a copy.  This
    +    License will therefore apply, along with any applicable section 7
    +    additional terms, to the whole of the work, and all its parts,
    +    regardless of how they are packaged.  This License gives no
    +    permission to license the work in any other way, but it does not
    +    invalidate such permission if you have separately received it.
    +
    +    d) If the work has interactive user interfaces, each must display
    +    Appropriate Legal Notices; however, if the Program has interactive
    +    interfaces that do not display Appropriate Legal Notices, your
    +    work need not make them do so.
    +
    +  A compilation of a covered work with other separate and independent
    +works, which are not by their nature extensions of the covered work,
    +and which are not combined with it such as to form a larger program,
    +in or on a volume of a storage or distribution medium, is called an
    +"aggregate" if the compilation and its resulting copyright are not
    +used to limit the access or legal rights of the compilation's users
    +beyond what the individual works permit.  Inclusion of a covered work
    +in an aggregate does not cause this License to apply to the other
    +parts of the aggregate.
    +
    +  6. Conveying Non-Source Forms.
    +
    +  You may convey a covered work in object code form under the terms
    +of sections 4 and 5, provided that you also convey the
    +machine-readable Corresponding Source under the terms of this License,
    +in one of these ways:
    +
    +    a) Convey the object code in, or embodied in, a physical product
    +    (including a physical distribution medium), accompanied by the
    +    Corresponding Source fixed on a durable physical medium
    +    customarily used for software interchange.
    +
    +    b) Convey the object code in, or embodied in, a physical product
    +    (including a physical distribution medium), accompanied by a
    +    written offer, valid for at least three years and valid for as
    +    long as you offer spare parts or customer support for that product
    +    model, to give anyone who possesses the object code either (1) a
    +    copy of the Corresponding Source for all the software in the
    +    product that is covered by this License, on a durable physical
    +    medium customarily used for software interchange, for a price no
    +    more than your reasonable cost of physically performing this
    +    conveying of source, or (2) access to copy the
    +    Corresponding Source from a network server at no charge.
    +
    +    c) Convey individual copies of the object code with a copy of the
    +    written offer to provide the Corresponding Source.  This
    +    alternative is allowed only occasionally and noncommercially, and
    +    only if you received the object code with such an offer, in accord
    +    with subsection 6b.
    +
    +    d) Convey the object code by offering access from a designated
    +    place (gratis or for a charge), and offer equivalent access to the
    +    Corresponding Source in the same way through the same place at no
    +    further charge.  You need not require recipients to copy the
    +    Corresponding Source along with the object code.  If the place to
    +    copy the object code is a network server, the Corresponding Source
    +    may be on a different server (operated by you or a third party)
    +    that supports equivalent copying facilities, provided you maintain
    +    clear directions next to the object code saying where to find the
    +    Corresponding Source.  Regardless of what server hosts the
    +    Corresponding Source, you remain obligated to ensure that it is
    +    available for as long as needed to satisfy these requirements.
    +
    +    e) Convey the object code using peer-to-peer transmission, provided
    +    you inform other peers where the object code and Corresponding
    +    Source of the work are being offered to the general public at no
    +    charge under subsection 6d.
    +
    +  A separable portion of the object code, whose source code is excluded
    +from the Corresponding Source as a System Library, need not be
    +included in conveying the object code work.
    +
    +  A "User Product" is either (1) a "consumer product", which means any
    +tangible personal property which is normally used for personal, family,
    +or household purposes, or (2) anything designed or sold for incorporation
    +into a dwelling.  In determining whether a product is a consumer product,
    +doubtful cases shall be resolved in favor of coverage.  For a particular
    +product received by a particular user, "normally used" refers to a
    +typical or common use of that class of product, regardless of the status
    +of the particular user or of the way in which the particular user
    +actually uses, or expects or is expected to use, the product.  A product
    +is a consumer product regardless of whether the product has substantial
    +commercial, industrial or non-consumer uses, unless such uses represent
    +the only significant mode of use of the product.
    +
    +  "Installation Information" for a User Product means any methods,
    +procedures, authorization keys, or other information required to install
    +and execute modified versions of a covered work in that User Product from
    +a modified version of its Corresponding Source.  The information must
    +suffice to ensure that the continued functioning of the modified object
    +code is in no case prevented or interfered with solely because
    +modification has been made.
    +
    +  If you convey an object code work under this section in, or with, or
    +specifically for use in, a User Product, and the conveying occurs as
    +part of a transaction in which the right of possession and use of the
    +User Product is transferred to the recipient in perpetuity or for a
    +fixed term (regardless of how the transaction is characterized), the
    +Corresponding Source conveyed under this section must be accompanied
    +by the Installation Information.  But this requirement does not apply
    +if neither you nor any third party retains the ability to install
    +modified object code on the User Product (for example, the work has
    +been installed in ROM).
    +
    +  The requirement to provide Installation Information does not include a
    +requirement to continue to provide support service, warranty, or updates
    +for a work that has been modified or installed by the recipient, or for
    +the User Product in which it has been modified or installed.  Access to a
    +network may be denied when the modification itself materially and
    +adversely affects the operation of the network or violates the rules and
    +protocols for communication across the network.
    +
    +  Corresponding Source conveyed, and Installation Information provided,
    +in accord with this section must be in a format that is publicly
    +documented (and with an implementation available to the public in
    +source code form), and must require no special password or key for
    +unpacking, reading or copying.
    +
    +  7. Additional Terms.
    +
    +  "Additional permissions" are terms that supplement the terms of this
    +License by making exceptions from one or more of its conditions.
    +Additional permissions that are applicable to the entire Program shall
    +be treated as though they were included in this License, to the extent
    +that they are valid under applicable law.  If additional permissions
    +apply only to part of the Program, that part may be used separately
    +under those permissions, but the entire Program remains governed by
    +this License without regard to the additional permissions.
    +
    +  When you convey a copy of a covered work, you may at your option
    +remove any additional permissions from that copy, or from any part of
    +it.  (Additional permissions may be written to require their own
    +removal in certain cases when you modify the work.)  You may place
    +additional permissions on material, added by you to a covered work,
    +for which you have or can give appropriate copyright permission.
    +
    +  Notwithstanding any other provision of this License, for material you
    +add to a covered work, you may (if authorized by the copyright holders of
    +that material) supplement the terms of this License with terms:
    +
    +    a) Disclaiming warranty or limiting liability differently from the
    +    terms of sections 15 and 16 of this License; or
    +
    +    b) Requiring preservation of specified reasonable legal notices or
    +    author attributions in that material or in the Appropriate Legal
    +    Notices displayed by works containing it; or
    +
    +    c) Prohibiting misrepresentation of the origin of that material, or
    +    requiring that modified versions of such material be marked in
    +    reasonable ways as different from the original version; or
    +
    +    d) Limiting the use for publicity purposes of names of licensors or
    +    authors of the material; or
    +
    +    e) Declining to grant rights under trademark law for use of some
    +    trade names, trademarks, or service marks; or
    +
    +    f) Requiring indemnification of licensors and authors of that
    +    material by anyone who conveys the material (or modified versions of
    +    it) with contractual assumptions of liability to the recipient, for
    +    any liability that these contractual assumptions directly impose on
    +    those licensors and authors.
    +
    +  All other non-permissive additional terms are considered "further
    +restrictions" within the meaning of section 10.  If the Program as you
    +received it, or any part of it, contains a notice stating that it is
    +governed by this License along with a term that is a further
    +restriction, you may remove that term.  If a license document contains
    +a further restriction but permits relicensing or conveying under this
    +License, you may add to a covered work material governed by the terms
    +of that license document, provided that the further restriction does
    +not survive such relicensing or conveying.
    +
    +  If you add terms to a covered work in accord with this section, you
    +must place, in the relevant source files, a statement of the
    +additional terms that apply to those files, or a notice indicating
    +where to find the applicable terms.
    +
    +  Additional terms, permissive or non-permissive, may be stated in the
    +form of a separately written license, or stated as exceptions;
    +the above requirements apply either way.
    +
    +  8. Termination.
    +
    +  You may not propagate or modify a covered work except as expressly
    +provided under this License.  Any attempt otherwise to propagate or
    +modify it is void, and will automatically terminate your rights under
    +this License (including any patent licenses granted under the third
    +paragraph of section 11).
    +
    +  However, if you cease all violation of this License, then your
    +license from a particular copyright holder is reinstated (a)
    +provisionally, unless and until the copyright holder explicitly and
    +finally terminates your license, and (b) permanently, if the copyright
    +holder fails to notify you of the violation by some reasonable means
    +prior to 60 days after the cessation.
    +
    +  Moreover, your license from a particular copyright holder is
    +reinstated permanently if the copyright holder notifies you of the
    +violation by some reasonable means, this is the first time you have
    +received notice of violation of this License (for any work) from that
    +copyright holder, and you cure the violation prior to 30 days after
    +your receipt of the notice.
    +
    +  Termination of your rights under this section does not terminate the
    +licenses of parties who have received copies or rights from you under
    +this License.  If your rights have been terminated and not permanently
    +reinstated, you do not qualify to receive new licenses for the same
    +material under section 10.
    +
    +  9. Acceptance Not Required for Having Copies.
    +
    +  You are not required to accept this License in order to receive or
    +run a copy of the Program.  Ancillary propagation of a covered work
    +occurring solely as a consequence of using peer-to-peer transmission
    +to receive a copy likewise does not require acceptance.  However,
    +nothing other than this License grants you permission to propagate or
    +modify any covered work.  These actions infringe copyright if you do
    +not accept this License.  Therefore, by modifying or propagating a
    +covered work, you indicate your acceptance of this License to do so.
    +
    +  10. Automatic Licensing of Downstream Recipients.
    +
    +  Each time you convey a covered work, the recipient automatically
    +receives a license from the original licensors, to run, modify and
    +propagate that work, subject to this License.  You are not responsible
    +for enforcing compliance by third parties with this License.
    +
    +  An "entity transaction" is a transaction transferring control of an
    +organization, or substantially all assets of one, or subdividing an
    +organization, or merging organizations.  If propagation of a covered
    +work results from an entity transaction, each party to that
    +transaction who receives a copy of the work also receives whatever
    +licenses to the work the party's predecessor in interest had or could
    +give under the previous paragraph, plus a right to possession of the
    +Corresponding Source of the work from the predecessor in interest, if
    +the predecessor has it or can get it with reasonable efforts.
    +
    +  You may not impose any further restrictions on the exercise of the
    +rights granted or affirmed under this License.  For example, you may
    +not impose a license fee, royalty, or other charge for exercise of
    +rights granted under this License, and you may not initiate litigation
    +(including a cross-claim or counterclaim in a lawsuit) alleging that
    +any patent claim is infringed by making, using, selling, offering for
    +sale, or importing the Program or any portion of it.
    +
    +  11. Patents.
    +
    +  A "contributor" is a copyright holder who authorizes use under this
    +License of the Program or a work on which the Program is based.  The
    +work thus licensed is called the contributor's "contributor version".
    +
    +  A contributor's "essential patent claims" are all patent claims
    +owned or controlled by the contributor, whether already acquired or
    +hereafter acquired, that would be infringed by some manner, permitted
    +by this License, of making, using, or selling its contributor version,
    +but do not include claims that would be infringed only as a
    +consequence of further modification of the contributor version.  For
    +purposes of this definition, "control" includes the right to grant
    +patent sublicenses in a manner consistent with the requirements of
    +this License.
    +
    +  Each contributor grants you a non-exclusive, worldwide, royalty-free
    +patent license under the contributor's essential patent claims, to
    +make, use, sell, offer for sale, import and otherwise run, modify and
    +propagate the contents of its contributor version.
    +
    +  In the following three paragraphs, a "patent license" is any express
    +agreement or commitment, however denominated, not to enforce a patent
    +(such as an express permission to practice a patent or covenant not to
    +sue for patent infringement).  To "grant" such a patent license to a
    +party means to make such an agreement or commitment not to enforce a
    +patent against the party.
    +
    +  If you convey a covered work, knowingly relying on a patent license,
    +and the Corresponding Source of the work is not available for anyone
    +to copy, free of charge and under the terms of this License, through a
    +publicly available network server or other readily accessible means,
    +then you must either (1) cause the Corresponding Source to be so
    +available, or (2) arrange to deprive yourself of the benefit of the
    +patent license for this particular work, or (3) arrange, in a manner
    +consistent with the requirements of this License, to extend the patent
    +license to downstream recipients.  "Knowingly relying" means you have
    +actual knowledge that, but for the patent license, your conveying the
    +covered work in a country, or your recipient's use of the covered work
    +in a country, would infringe one or more identifiable patents in that
    +country that you have reason to believe are valid.
    +
    +  If, pursuant to or in connection with a single transaction or
    +arrangement, you convey, or propagate by procuring conveyance of, a
    +covered work, and grant a patent license to some of the parties
    +receiving the covered work authorizing them to use, propagate, modify
    +or convey a specific copy of the covered work, then the patent license
    +you grant is automatically extended to all recipients of the covered
    +work and works based on it.
    +
    +  A patent license is "discriminatory" if it does not include within
    +the scope of its coverage, prohibits the exercise of, or is
    +conditioned on the non-exercise of one or more of the rights that are
    +specifically granted under this License.  You may not convey a covered
    +work if you are a party to an arrangement with a third party that is
    +in the business of distributing software, under which you make payment
    +to the third party based on the extent of your activity of conveying
    +the work, and under which the third party grants, to any of the
    +parties who would receive the covered work from you, a discriminatory
    +patent license (a) in connection with copies of the covered work
    +conveyed by you (or copies made from those copies), or (b) primarily
    +for and in connection with specific products or compilations that
    +contain the covered work, unless you entered into that arrangement,
    +or that patent license was granted, prior to 28 March 2007.
    +
    +  Nothing in this License shall be construed as excluding or limiting
    +any implied license or other defenses to infringement that may
    +otherwise be available to you under applicable patent law.
    +
    +  12. No Surrender of Others' Freedom.
    +
    +  If conditions are imposed on you (whether by court order, agreement or
    +otherwise) that contradict the conditions of this License, they do not
    +excuse you from the conditions of this License.  If you cannot convey a
    +covered work so as to satisfy simultaneously your obligations under this
    +License and any other pertinent obligations, then as a consequence you may
    +not convey it at all.  For example, if you agree to terms that obligate you
    +to collect a royalty for further conveying from those to whom you convey
    +the Program, the only way you could satisfy both those terms and this
    +License would be to refrain entirely from conveying the Program.
    +
    +  13. Remote Network Interaction; Use with the GNU General Public License.
    +
    +  Notwithstanding any other provision of this License, if you modify the
    +Program, your modified version must prominently offer all users
    +interacting with it remotely through a computer network (if your version
    +supports such interaction) an opportunity to receive the Corresponding
    +Source of your version by providing access to the Corresponding Source
    +from a network server at no charge, through some standard or customary
    +means of facilitating copying of software.  This Corresponding Source
    +shall include the Corresponding Source for any work covered by version 3
    +of the GNU General Public License that is incorporated pursuant to the
    +following paragraph.
    +
    +  Notwithstanding any other provision of this License, you have
    +permission to link or combine any covered work with a work licensed
    +under version 3 of the GNU General Public License into a single
    +combined work, and to convey the resulting work.  The terms of this
    +License will continue to apply to the part which is the covered work,
    +but the work with which it is combined will remain governed by version
    +3 of the GNU General Public License.
    +
    +  14. Revised Versions of this License.
    +
    +  The Free Software Foundation may publish revised and/or new versions of
    +the GNU Affero General Public License from time to time.  Such new versions
    +will be similar in spirit to the present version, but may differ in detail to
    +address new problems or concerns.
    +
    +  Each version is given a distinguishing version number.  If the
    +Program specifies that a certain numbered version of the GNU Affero General
    +Public License "or any later version" applies to it, you have the
    +option of following the terms and conditions either of that numbered
    +version or of any later version published by the Free Software
    +Foundation.  If the Program does not specify a version number of the
    +GNU Affero General Public License, you may choose any version ever published
    +by the Free Software Foundation.
    +
    +  If the Program specifies that a proxy can decide which future
    +versions of the GNU Affero General Public License can be used, that proxy's
    +public statement of acceptance of a version permanently authorizes you
    +to choose that version for the Program.
    +
    +  Later license versions may give you additional or different
    +permissions.  However, no additional obligations are imposed on any
    +author or copyright holder as a result of your choosing to follow a
    +later version.
    +
    +  15. Disclaimer of Warranty.
    +
    +  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
    +APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
    +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
    +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
    +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    +PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
    +IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
    +ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
    +
    +  16. Limitation of Liability.
    +
    +  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
    +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
    +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
    +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
    +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
    +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
    +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
    +SUCH DAMAGES.
    +
    +  17. Interpretation of Sections 15 and 16.
    +
    +  If the disclaimer of warranty and limitation of liability provided
    +above cannot be given local legal effect according to their terms,
    +reviewing courts shall apply local law that most closely approximates
    +an absolute waiver of all civil liability in connection with the
    +Program, unless a warranty or assumption of liability accompanies a
    +copy of the Program in return for a fee.
    +
    +                     END OF TERMS AND CONDITIONS
    +
    +            How to Apply These Terms to Your New Programs
    +
    +  If you develop a new program, and you want it to be of the greatest
    +possible use to the public, the best way to achieve this is to make it
    +free software which everyone can redistribute and change under these terms.
    +
    +  To do so, attach the following notices to the program.  It is safest
    +to attach them to the start of each source file to most effectively
    +state the exclusion of warranty; and each file should have at least
    +the "copyright" line and a pointer to where the full notice is found.
    +
    +    <one line to give the program's name and a brief idea of what it does.>
    +    Copyright (C) <year>  <name of author>
    +
    +    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, either version 3 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 Affero General Public License for more details.
    +
    +    You should have received a copy of the GNU Affero General Public License
    +    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    +
    +Also add information on how to contact you by electronic and paper mail.
    +
    +  If your software can interact with users remotely through a computer
    +network, you should also make sure that it provides a way for users to
    +get its source.  For example, if your program is a web application, its
    +interface could display a "Source" link that leads users to an archive
    +of the code.  There are many ways you could offer source, and different
    +solutions will be better for different programs; see section 13 for the
    +specific requirements.
    +
    +  You should also get your employer (if you work as a programmer) or school,
    +if any, to sign a "copyright disclaimer" for the program, if necessary.
    +For more information on this, and how to apply and follow the GNU AGPL, see
    +<http://www.gnu.org/licenses/>.
    +
    +
    +
    + +
    + + diff --git a/website/screenshots.php b/website/screenshots.php index 7d666b24..dbc7ff1c 100644 --- a/website/screenshots.php +++ b/website/screenshots.php @@ -1,27 +1,22 @@ - - -
    - -

    Screenshots

    - -

    Importing hands:

    -
    -

    Table viewer:

    - - -
    - - + + +

    Screenshots

    + +

    Importing hands:

    +

    Table viewer:

    +

    HUD:

    + +
    + + diff --git a/website/sidebar.php b/website/sidebar.php index 7bc7f60d..06bd6ca4 100644 --- a/website/sidebar.php +++ b/website/sidebar.php @@ -1,13 +1,13 @@ - + diff --git a/website/style.css b/website/style.css index 65137a8c..d12f4779 100644 --- a/website/style.css +++ b/website/style.css @@ -1,99 +1,52 @@ -body { - margin: 0px; - padding: 0px; - background-color: #fff; - font-family: "Verdana", "Arial", sans-serif; - font-size: 13px; - text-align: center; - color: #333; -} - -a { - text-decoration: none; - color: #336984; -} - -a:hover { - color: #e00000; -} - -img { - border: 0px; -} - -h1 { - font-size: 23px; -} - -li { - padding: 3px; -} - - -/* ------------------------------------------------- */ -#wrapper { - text-align: left; - margin: auto; -} - - -/* ------------------------------------------------- */ -#header { - padding: 20px 20px 10px 20px; - background-color: #8BB9D1; - border-bottom: 10px solid #4690B5; - color: #fff; - font-size: 31px; -} - -#logo a { - font-size: 32px; - color: #5C5C49; -} - - -/* ------------------------------------------------- */ -#main { - margin-left: 210px; - margin-right: 15px; -} - - -/* ------------------------------------------------- */ -#sidebar { - background-color: #E8F1F6; - width: 180px; - padding: 10px; - float: left; -} - -#sidebar ul { - padding: 0px; - margin: 0px; - list-style-position: inside; -} - -#sidebar ul li { - border-bottom: 1px solid #D1E3EC; - padding: 3px; -} - -#sidebar ul li:hover { - background-color: #fff; -} - -#sidebar a { - text-decoration: none; -} - - -/* ------------------------------------------------- */ -#footer { - text-align: center; - margin-top: 20px; - background-color: #F6F6FD; - padding: 10px; - border-top: 3px solid #DADAF6; - font-size: 11px; - clear: both; -} +body {margin: 0px; padding: 0px; background-color: #fff; font-family: "Verdana", "Arial", sans-serif; font-size: 13px; color: #333;} + +a {text-decoration: none; color: #336984;} + +a:hover {color: #e00000;} + +img {border: 0px;} + +h1 {font-size: 23px;} + +li {padding: 3px;} + + +/* ------------------------------------------------- */ +#header { + padding: 20px 20px 10px 20px; + background-color: #8BB9D1; + border-bottom: 10px solid #4690B5; + color: #fff; + font-size: 31px; +} + +#logo a { + font-size: 32px; + color: #5C5C49; +} + + +/* ------------------------------------------------- */ +#main { margin-left: 210px; margin-right: 15px; } +.winInst div { width: 906px; } +.screenshot { background-color: #E8F1F6; padding: 0px; margin: 10px auto; padding: 10px; } +.screenshot img { padding: 2px; border: 1px solid #87aade; } +.screenshot p { margin: 0; } +#screens { float: left; margin: 1em; text-align: center; } + +/* ------------------------------------------------- */ +#sidebar {background-color: #E8F1F6; width: 180px; padding: 10px; float: left;} + +#sidebar ul {padding: 0px; margin: 0px; list-style-position: inside;} + +#sidebar ul li {border-bottom: 1px solid #D1E3EC; padding: 3px;} + +#sidebar ul li:hover {background-color: #fff;} + +#sidebar a {text-decoration: none;} + +dl dt { float: left; width: 200px; font-weight: bold; } + + +/* ------------------------------------------------- */ +#footer { clear: both; text-align: right; padding: 1em 2em; } From c4f6ac42a093da8a94d0b004d66fe3fa65dc463a Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 03:51:47 +0100 Subject: [PATCH 103/262] p97 - new banner from Bar Nuthin and updated requirements from me --- docs/known-bugs-and-planned-features.txt | 5 +- website/docs-requirements.php | 100 +++++++++++++---------- website/fpdb.png | Bin 13014 -> 26584 bytes 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 3e227448..1e954d4f 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,13 +3,12 @@ Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== -graph: update dependencies.txt, doesnt remove old graph on refresh +graph: doesnt remove old graph on refresh print a "press any key" thing after we print the traceback. That way it is easy for them to see the error message. pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. reading small blind wrong for PS 25/50ct check we're reading mucked cards from PS newsletter&mailing list -update requirements to include new pgsql interface lib ebuild: support pgsql fix HUD config location and update release script accordingly @@ -43,7 +42,7 @@ hole/board cards are not correctly stored in the db for stud games HORSE (and presumably other mixed games) hand history files not handled correctly Some MTTs won't import (rebuys??) Many STTs won't import -redirect stderr +redirect stderr: http://diveintopython.org/scripts_and_streams/stdin_stdout_stderr.html before beta =========== diff --git a/website/docs-requirements.php b/website/docs-requirements.php index 537e4921..166424af 100644 --- a/website/docs-requirements.php +++ b/website/docs-requirements.php @@ -13,24 +13,21 @@ require 'sidebar.php';

    I recommend using a free/libre operating system, meaning a GNU/Linux distribution or a BSD variant (e.g. Gentoo GNU/Linux or OpenBSD) for ethical and practical reasons. Would you buy a car where you're prohibited from opening the bonnet under threat of jail? If the answer is no you should by the same logic not use closed source software for real money Poker :)

    -Unfortunately you will always need one piece of unfree software: The poker client itself. Although not a direct dependency of fpdb you obviously will have a hard time putting this to productive use without running some poker client. As far as I know, only unfree clients are available. If you know better please let me know ASAP!
    +Unfortunately you will always need one piece of unfree software: The poker client itself. Although not a direct dependency of fpdb you obviously will have a hard time putting this to productive use without running some poker client. As far as I know, only unfree clients are available. If you know better please let me know!

    -If you can be bothered please do contact your poker site(s) and ask them to release free/libre clients, even if it is only for Windows. But lets be realistic, the chance of a positive answer is very low.
    +If you can be bothered please do contact your poker site(s) and ask them to release free/libre clients, even if it is only for Windows. But lets be realistic, the chance of a positive answer is very low. Also, even unfree Linux client would of course be a great step forward

    -Before I start the list a note on the databases, as of git96 I have yet to try using this with PostgreSQL, but if I'm not mistaken it should actually work by now (the stuff in fpdb-python at least).
    +In Windows use of the environment installer is recommended, pls see our sf download page. For Gentoo Linux we have an ebuild and for Ubuntu Linux we have (partial) instructions. If you use a different Linux or a BSD and have trouble please IM, email or post in the forums. Fpdb has been reported to work on MacOSX, but installation of the requirements is relatively painful. Any instructions for people to use would be much appreciated.

    -If you use a package management system (e.g. if you have GNU/Linux or *BSD) just check that you have mysql, mysql-python and pygtk or postgresql, pygresql and pygtk. Your package manager will take care of the rest for you.
    -
    Make new entries in this format:
    -X. Program Name
    -===============
    +Program Name
    a. Optional?
    b. Required Version and Why
    c. Project Webpage
    -d. License
    -
    -1. MySQL
    -========
    +d. License

    +

    Database backend - MySQL

    +

    These two are required if you want to use MySQL as backend, which is the recommended choice due to lack of testing and polish of PostgreSQL support.

    +

    MySQL
    a. Optional?
    Choose MySQL or PostgreSQL
    b. Required Version and Why
    @@ -41,8 +38,20 @@ c. Project Webpage
    d. License
    GPL2

    -2. PostgreSQL
    -=============
    +mysql-python
    +a. Optional?
    + Required if you want to use MySQL backend
    +b. Required Version and Why
    + I use 1.2.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    +c. Project Webpage
    + http://sourceforge.net/projects/mysql-python/
    +d. License
    + SF lists GNU General Public License (GPL), Python License (CNRI Python License), Zope Public License.
    + Project states GPL without version in Pkg-info.

    + +

    Database backend - PostgreSQL

    +

    These two are required if you want to use PostgreSQL as backend

    +

    PostgreSQL
    a. Optional?
    Choose MySQL or PostgreSQL
    b. Required Version and Why
    @@ -53,34 +62,18 @@ c. Project Webpage
    d. License
    BSD License

    -3. mysql-python
    -===============
    -a. Optional?
    - Required if you want to use MySQL backend
    -b. Required Version and Why
    - I use 1.2.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    -c. Project Webpage
    - http://sourceforge.net/projects/mysql-python/
    -d. License
    - SF lists GNU General Public License (GPL), Python License (CNRI Python License), Zope Public License.
    - Project states GPL without version in Pkg-info.
    -
    -4. pygresql
    -===========
    +psycopg
    a. Optional?
    Required if you want to use PostgreSQL backend
    b. Required Version and Why
    - I use 3.6.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    + I use 2.0.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    c. Project Webpage
    - http://www.pygresql.org/
    + http://initd.org/projects/psycopg2
    d. License
    - http://www.pygresql.org/readme.html#copyright-notice (BSD License?)
    - Summary: "Permission to use, copy, modify, and distribute this software and its
    - documentation for any purpose, without fee, and without a written agreement
    - is hereby granted[...]" plus Disclaimer.
    + GPL2 according to Gentoo's ebuilds

    -5. Python
    -=========
    +

    Required for everyone

    +

    Python
    a. Optional?
    Required.
    b. Required Version and Why
    @@ -90,8 +83,7 @@ c. Project Webpage
    d. License
    Python License

    -6. GTK+ and dependencies
    -=======
    +

    GTK+ and dependencies
    a. Optional?
    Required.
    b. Required Version and Why
    @@ -103,8 +95,7 @@ c. Project Webpage
    d. License
    LGPL2

    -7. PyCairo
    -==========
    +PyCairo
    a. Optional?
    Required.
    b. Required Version and Why
    @@ -114,8 +105,7 @@ c. Project Webpage
    d. License
    LGPL2.1

    -8. PyGObject
    -============
    +PyGObject
    a. Optional?
    Required.
    b. Required Version and Why
    @@ -125,8 +115,7 @@ c. Project Webpage
    d. License
    LGPL2.1

    -9. PyGTK
    -========
    +PyGTK
    a. Optional?
    Required.
    b. Required Version and Why
    @@ -134,8 +123,29 @@ b. Required Version and Why
    c. Project Webpage
    main: http://www.pygtk.org
    d. License
    - LGPL2.1
    -
    + LGPL2.1

    +

    Requirements for the graphing function

    +

    These are only required if you wish to use the graphing function, and fpdb will otherwise function without them

    +

    Numpy
    +a. Optional?
    + Optional.
    +b. Required Version and Why
    + I use 1.0.4 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    +c. Project Webpage
    + http://numeric.scipy.org/
    +d. License
    + BSD according to Gentoo's ebuild
    +
    +matplotlib
    +a. Optional?
    + Optional.
    +b. Required Version and Why
    + I use 0.91.2 but I am not aware of any incompatibilities with older or newer versions, pls report success/failure.
    +c. Project Webpage
    + http://matplotlib.sourceforge.net/
    +d. License
    + BSD according to Gentoo's ebuild

    + License (of this file)
    =======
    Trademarks of third parties have been used under Fair Use or similar laws.
    diff --git a/website/fpdb.png b/website/fpdb.png index 5081614bd254db5c38d13421d99c8350349a8b5d..24de154556b9472b2de85c7007625f8a8c0c0f76 100644 GIT binary patch literal 26584 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 $E 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 Date: Wed, 24 Sep 2008 03:56:44 +0100 Subject: [PATCH 104/262] p98 - redirect stderr --- docs/known-bugs-and-planned-features.txt | 1 - pyfpdb/fpdb.py | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 1e954d4f..73b99de6 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -42,7 +42,6 @@ hole/board cards are not correctly stored in the db for stud games HORSE (and presumably other mixed games) hand history files not handled correctly Some MTTs won't import (rebuys??) Many STTs won't import -redirect stderr: http://diveintopython.org/scripts_and_streams/stdin_stdout_stderr.html before beta =========== diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index fafa019d..683d719b 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -18,6 +18,9 @@ import os import sys +errorfile = open('error.log', 'w') +sys.stderr = errorfile + import pygtk pygtk.require('2.0') import gtk From 6304790f3810cb227201cc65e6d14fa0e66573af Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 03:58:14 +0100 Subject: [PATCH 105/262] p98b - redirect stderr --- pyfpdb/fpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 683d719b..85a294d3 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -18,7 +18,7 @@ import os import sys -errorfile = open('error.log', 'w') +errorfile = open('fpdb-error.log', 'w') sys.stderr = errorfile import pygtk From fef324b854d26457114087d3d16f6b5184ef362a Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 04:01:38 +0100 Subject: [PATCH 106/262] p99 - graphing now removes old graph on refresh --- docs/known-bugs-and-planned-features.txt | 2 -- pyfpdb/GuiGraphViewer.py | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 73b99de6..ea960f3f 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,8 +3,6 @@ Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== -graph: doesnt remove old graph on refresh -print a "press any key" thing after we print the traceback. That way it is easy for them to see the error message. pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. reading small blind wrong for PS 25/50ct check we're reading mucked cards from PS diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 58d79945..e2f62fae 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -39,6 +39,9 @@ class GuiGraphViewer (threading.Thread): #end def get_vbox def showClicked(self, widget, data): + try: self.canvas.destroy() + except AttributeError: pass + name=self.nameTBuffer.get_text(self.nameTBuffer.get_start_iter(), self.nameTBuffer.get_end_iter()) site=self.siteTBuffer.get_text(self.siteTBuffer.get_start_iter(), self.siteTBuffer.get_end_iter()) From ba1af89897fd4f6ecc20534ef77f189334a8e659 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 04:22:59 +0100 Subject: [PATCH 107/262] p100 - updated contacts with mailing list and how to subscribe to new downloads --- docs/known-bugs-and-planned-features.txt | 10 +++++----- website/contact.php | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index ea960f3f..17ef57f4 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -6,15 +6,14 @@ alpha4 (release 25Sep-2Oct) pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. reading small blind wrong for PS 25/50ct check we're reading mucked cards from PS -newsletter&mailing list ebuild: support pgsql fix HUD config location and update release script accordingly -update website for windows installer -update install-in-gentoo on website -update ebuild and ubuntu guide for HUD_config.xml +(michael) update website for windows installer +(steffen) update install-in-gentoo on website +(steffen) update ebuild and ubuntu guide for HUD_config.xml -store raw hand in db and write reimport function using the raw hand field +(steffen) store raw hand in db and write reimport function using the raw hand field make sure totalProfit shows actual profit rather than winnings. update abbreviations.txt export settings[hud-defaultInterval] to conf @@ -43,6 +42,7 @@ Many STTs won't import before beta =========== +validate webpage make linux use /etc/fpdb for config first, then ~/.fpdb. FTP file with only one partial hand causes error No Full Tilt support in HUD diff --git a/website/contact.php b/website/contact.php index e289ba3c..55d29da6 100644 --- a/website/contact.php +++ b/website/contact.php @@ -9,14 +9,16 @@ require 'sidebar.php';

    Contact

    -

    The best means of contact are the sourceforge page: Use the bug, feature request or patch functions or just post in the forum.

    +

    The best means of contact are on the sourceforge page: Use the bug, feature request or patch functions or just post in the forum. The forum requires a sourceforge account, but bug, feature request and patches don't seem to.

    +

    We also have a mailing list, to subscribe please click here. This is used for development purposes and you can also ask for help using the mailing list.

    +

    To be informed of fpdb updates please visit the sf download page and click on the little envelope with a plus sign in the line of the package that you want be informed off. For Windows user that will be both, Linux users should only subscribe to fpdb itself. This requires a sourceforge account, sorry.

    Alternatively feel free to contact me directly:

    mail: steffen(at)sycamoretest.info
    -jabber/xmpp/Google Talk: as above
    +jabber/xmpp/Google Talk: steffen-laptop-temp(at)sycamoretest.info (don't email that)
    ICQ: 7806355
    -MSN: steffenjf(at)gmx.de (don't email that)

    +MSN: steffen(at)sycamoretest.info

    MRd+!342YZf-Ca>xM}8}(fB$N5cvv$49m;9O4O=m> z)6O|2zqYH`3bht=3Oj?nC#Mt$d9Fs08$;Aw13IIbS-?LUt8Y#C)Y< zil15i#6?{w^uL#u;$sx6R$Z{*TuDE|wS(s3OV*8AkJZ3;6=SsNHH zuz6GzB9H%i-ujXBQ&3}s9RvQn78wZ0E-;Al@$vX)@ZO;KG^vJ{>l@sVC-MM%3v>5# zslfVa-GJVJk9`qacog$t+e$*fPLp6FSkArWMjD#>x-o&piKY@8Bvq3;-5O?ZS2@4x&ewH-MVB@0n%k$P{ zl0;3y^m}h$#B`A0rjmSW+-+0aadLTgL;G@LeBFsFyXaC@ydfgEBmR`qtczN{*Aezg z@wvbCO3D7!D>X?9F@7TS;L%>GaQtS}@2yhNVWX{5pH!Go$aO2ASUgi2R|T$9CtiJU z{rdI0HH4#d71(Dgp2G8D<^Vla&`Pb|K|31cZQ7jS`>kcn`;h88M&`o(oC1>9OA_-W zd-ZN0b<~HBNfDBm0&{EFy~s0t=ID)G%F{5`AQAvVAk!zva|!s}fF=ZnhOD=Sz}T0#BU$8gpWJz|A0Y zcp)55uhwTYrowJ8cuhRXe3)>NrqByE(Qk+oR~pkULf)bTj|+R7dcr8LjGE%On0sk6 zBb#IRlq~74zn7W^7U~9hRE`=}0uD*NMqx(|DH}+E9c*X7;_Z|o4gyX^&H-echOFeOeWyco_0GIe z)IL(h+{b7AVL)d{-f3c;mmaV##~|;Wj(pa>+_DP#{aj|LtrQ^>aP`V$bczB`-bpK3 z^$|&>^i%~51-!vQf0c^o11(|mK8w|AZhtX9 zRcO;4d?@d!dprE+gIc=VVx|){L}!3yqwT~SMFu{GnQlNIn^<=d*rZ%ay(di;8>>H1NHpfsk#yBV9}(1Ib3gW37QuLBu{Mb0bKzB1vpW z2eU|;Zv8|$jcPw#cd1*FXt_oA9+NIjqnEd#)oaT9Zx~Z>P02i6Kx4Jo^;3YTg&vIc z!r7D*TQc76&+R8*9PolSH7YN5J$ZAbknF~}aS#{Z|B)amyqXv- zPi@Aj%u#F#8&an2U}q|z!$C4RUJLal5~6Fh2d>%*@jaiJj}P#{Rsz;Hhal<8b74Kh zn0b5<$EP52hyAP2c_!$XEvNpKe(pyOt#z=@hrJFiMb=%?cR__UK)R87lu0dwSDnSU z06yi{?CbadLZ^JfNzw~DC)i1Bhu#tM1l`6>mrecn%y>!vN0eajAbnM!J=`cZ2%M6a z)haH$SXP*E1tEPq&WR?%CX=aJVj}Z z#c^4+D| zY}qwYNZXRHV8O8^5Y3VMFkHi6*ay@zZEkgZ+!|0sm8Mo`tulA1fEH=^*_5QNwH9|~ z1AuQ={~Fn>bA|j-ti4~Gy;sHo9V7}tn>#6kX=n%RkZHr6SP%GU#2oKJwW8B1 zqA$tGR1?v)!=TCjyynJ|ZZ%RHBMaX_-d5!Hyl1-*IzQCxp3H{BhLjc0N+o+V&Fpka zku|EVp-{5#hhV=|dnEm=M|=C7tV-Zxf&8QVvFs8lqC6cW zFmhG0qu$%rmV-idY^FLqT73z~)MpD}wTy&r2?`?KD)Sb-)}_Rpsub7X>N$i}{`PP| z5E0Gd8M}&5^yGPYlH)FW|%s$Xsfe@eCvb2A){mAT(ae`m#6KiMlj>ce2IzF0rTBF9LMay@mmca5~uj9oCs^ zd=hO$^MqshV=&zaRQRK=fJwbANrv`udP^Scy`2jZH6sVF6wywN%c)DlpF9-$f`(US ziv&8$i3IkJ{+gZBCh81cXZzaD*(u0{lH6`Bh>=rwV|6VR`mRoCGM&zP@LGbz#u$}s zurh+I8;qw+A5ewNtYt52Eoal21$tNDXg#p@#~(A{W>KdS4k}seXcQn1jT z4pZ&;I_Ez=DEI?I+%|3U>$-Kr%$qk2FJK;mje=EZHIl`^mfMv&A#aqvxV;fMv7;@w z?hP#^A#_P5{dY}^-V51Y84Wvbi)GNGoONU`Tr&r-JnC-a(o|!L*Oq%qe`Y=hh`%_M5V3O6to^?7l=2_5G z2#A7)SR(An;gOnugC$P=FV!cWfwXN@4`cbGTRav?lpoQ)0iUY(;#K`eQ?y{@<=pVp}L)1YXL9}AypAfVRo{l(t6e%C}}M7EHh1SPb6 z=Oqno%eBO3tqk+t08${=P8>|pRRA&wHO$yY{|9E^z*XD|Dr!6=&oW;}t4 zr)PK+MYJL_FrQ|l-m{Yl1aiZHC!ms~h6^<9M z7l?p`GEeCN$isFD9-Cu903~QHak8C2NJxaUB299~fVOII1|mk6k6%Mc5GtJhURW@A zXhOz7piD@_G%xq2suvjVfpFS|{D#<(uin(X75v2O zD^%;LPZdYxRp|qeFplj72Vm1vaJ&vrQ9M5If$jUf$N46)MxI&q7$lO}=dHT}^3z1Q z5CTt@POzdc5+>108Vt+jZsnOFK6qE4B@Gmka{hBmy|~4bGOLQ71aATdjOK5o(|@=7 z|F8}Ww+afo{{vt36RLkP!9X=q<)2AuA(w+sTE3896{}=<#3VZhiZR~Xb|ce6A$Xch zJ1J$Al1o7`8#`v@s1tni0t3JGldZbHVT(#n zBPRcHN*fDq&@o#<5IML*g>XZ~xxPyuuS(uKm=;#WANrrBsLl5FNive zA1&?fxII)IzX%CTy!~unoK)dAVBsfrKE-z?`J$Gc<$64oJ@Xc~+`c_TP>wf;Fvt|w z>#Igf6F<9K-Pxe>CMdl5B0KcC`5n9D#(9?4LrkStr0iDb(A$J9`oMo$SLAeBU`0GO zqR@6G$+Veu6;T>H=i>`|qXGHK&gp>06uM{L1)@8X$wjX6!E70Gp~I37mGENFVCVc$ zN`7qTED?1jvg=ljwy$4%LZfz0bo>6iK;|-?%C0lRrp?FqBSvCK5mO{|wR5g+Mr;cb zkKSADL6!GpP2KRm)BjHgUL#_r)DWgmAszG5cUW9(U;E%^I&Q-!h<#_W;;wAHU|{+y z;S;Zuw&I4tSg}P=Og+Bv2ectL2ewr(-f~jLp8Bw_oL2WjntqSn^I*ejypH>J3U2TG EZ)Z3~T>t<8 literal 0 HcmV?d00001 diff --git a/website/img/31.editDbProfile.jpg b/website/img/31.editDbProfile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db20e795d1643b982e243e8751bbd4c8e540cac8 GIT binary patch literal 9934 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 literal 0 HcmV?d00001 diff --git a/website/img/docs.HudHowTo2.png b/website/img/docs.HudHowTo2.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e88564338efc62771bf953cc626e14d14720e7 GIT binary patch literal 6339 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 z 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 z%0D3Tx)ZQ41IsuV4L^b`(JMAwc+Qsm?q67~SPkn~oW^=U8<7&0bU(#K`5aavKl;V- zla1??_^k|G{C0JAFXJ?V#Vf9gI5A}HANjyx*Bs6y6Q3WTa6R0yDvT1KV30BtgYbei0$Mz+&BEN zdIyji{*mKxn{dO{vg&%bpabU5 z2ZiYvsg7b%0oj?}cR{0pyBLB)&c}?ytEJ}s>fto(*$01#kd$(o&7sqi=o?zNF~M~A zX-Oz&Z5>WWts^1<3168B@%=gBGM25i2Y2)CZsKq1ZWXjM`vSvWsk_rF&5OVQvlGbf zl33Zvq2NdI@Gwqxt)T%ws>);S;w&1saE-8{O3H#|3DxjO%y`Bj+Wfq0hT%GtqlqV# zqfz^4!8fi#f?Vp8cDSf<)U`YqJ%?iSk)!hcmBlYJV@PMOFJ!?BGJ9lxJ(du65gT)E z9D24(9JANy*UEW?C;Xc0cn|RMxdw0J|g8J5Rri~jsAHMUAHNv9x zTk$bOJAT9Y!{1I`2C5al--(7unXFbYBhkURy^h6aOsBmRzTEL!BVRdHQKXHwe@jju z(%yoU6!*JKJx;Wt)cds1PR7El=v4thDWpY7WlDeJ&csV^|zFT|^YciMi#B41i)yVui>w!GSd?i5QN(jSDezu;*}>3pC-?0GF<`1i1%nl&s_ z)0L?eU`h8O&Je9cI#rGHJx(Ri0>!etIL)em&)HhL~10(0{AlZkM)vGqJ8XA0DM z#R1WpFR!cjT3Ck&msNM2XWtB)N``|r6z~_Ci1&MiDOO?lCHqOjY-K|NR#mnWh)`eb&AMyQU1bnUUDJ}>G>K<|0;44rm!Y=!@MDDoVE6)0 zcjX6iMTP6R2%Vo4>baV#@a8Mi?|9iu5=wc#85^##T@RQSs&RyQHM+3;?-^w1YCOw% zNvUzR$fjzvoa8>hhyaMmp$S2q6B_JuTqiIY?IVealVZw$(|^BXcy*d0H07(cEtiO) z>41@1Q5M~y3A<_$dwoX$RKVV|{V5?kW=F14uy<*mh)2~b-q%CBOK68)V zZM#lJfiLNIV>|EIwR(X8*P5dDe`j0GqST(y|@hi zg`d=f=y~=hvWB2Sf>9x+FUM>4s~~}nrQfN6uU?BUv#CWtD_GOELUb<90m2*znHyW z%eQ(VY@^}sb^t?#EW!K7@IdTt(6JOa>;5t7to^d~4Tm{)Y zMKNv>*1Kwv#69=taRx8uNBM`Qn$V`6 z-+tS6N>}H}_y>+E^95}wj9aQ+t(fxY=Wf$8|0U|dF$!vp$m{VhyhhuV1x6{7c2S>1 zX%s@uksFSUF6kvrkw-gn&Hd1#&W2DTGE9+Q9DJeXQig)GXh-2BYxAkG)f|ys<6RuSOe9>e z?>bxK)^addOi8w@o6o3uoqYZ9T6AYW;C;UVzFu)X`S89uMW$$3-AOxc+vSNAyq9gu z(_{8|cB7p)O#8GGpLGN7voM-(=H{8$z>_l{(c*6puQSVqy@k4q4}4Hk@Q1T9O$6X| zj`e$O_ILj2ra}PRkBS~Qt(e)>k*)ZFonyRnlO}IIKR8i}er}gz z&J4XNAy_UIPnvp^KD+S5Y``pvcKcZ5r_upo#1FjhWi{{&_5s1EGx-!veP+j@tpmzq z0CozjfF@)zw)J}7?K7$^yK@?+rxH8w9kJf0SARjVr+B@G4zlGT8!HiaIi}0_q&JTDQ$Uv z{-|=_3)=hIVRx~3mr@YrJNd~>$2CT!Q!|Kdqi!+t(@t0J2^$(E@Th1#+m_L@l=@=E z_|;`5=T_*hIM%GU%8aE&J>8&ShLYG?OlTFxVyBq;Znow{i!^YGz<+DFe)d?nsUyLux|6xaB;X4VBhXZO?Pbiex!x?fCvNM^cYBiwKu zYuu8oM$NOw7}CDZp;fc zMfztUqUoQ-I*lJ!oH9ze8sgvguS=k=cGvtWe_xQ+m+L_Uhpho?LPhJ2!Zz`ac zr~e1V$A{hz{>bL*n*o9ZOaX-DKRHQf4OHbu$&1a-&8oby5-*rZ))QQL6#^z|DkJFj z%tyYmi-zMelBj8?AXXOq^W!`&lF(PZ4$i_LIJsoVVq_r9x5s)vL(PWXM8O87*r1SB z$c7_xxIEl#8{AHF#s17PM9_>M#HLBppJ!!6a4wT7Mds{GDMETne=|(=6j->Cq6cDY zc^Y&h9V^nax?hcxlp*yxW_;NeSb2y7ugbn6kBX%1B%XmMcU;!2wqU^Cn~sEG_|5nY z)CQuo^+3|AmVcInX-J-+oeKM>UOF1^@M_4)&ev-!_+&8J`vF)IJsE}f26bRMmhG14 zpbZ8U0@?9u#P6WMs5LP#IKJ8qfwIjlG;N!&qCFOOCSCaRoD6IUkh$A}RTg$5+wP57 zn*2#20M}7bvcR8r9lX5V&)+68=R?s=oAb$!F;{eemI0_gH?+L0I(rTgRH2s9L&>|Wd0{a0_ltgrOEG9{SJ6u6yteg1S2 z(KF&?bK9_^PJNWw|sWn5cpk?G1$ZV7OFYa;kbwh z(S-+yo`ZV&Iy_?%?H6hc`e&PyWwILfCI&N|NH3Gz5-HDT?KWBVllGsp`8HOQI*##x z);VI^in*fpnewq-Z4S(c!@+ulzMct-m?^cwEDLrn!f)Zfc>LXteD}-E$$iLL=0g>_ z0PwXNb^4ljBfWn5#-h;U%+r~AF>`tK!He4rb}qj8z$Lfz+BSb4*x*x7Hmv+l`*uN$ z86l_S_|c7*rQAut+38%~xb5UMKo3RqKAq?u?&sd@Uy#Rg{|TSNaOmiCVq1_E_xY+X z*y2h-8jljy;@5T1gg`xPIz?%p{xZw3xbuW#&$>}XU%X5_(pz|)4k&|+9RhsF7(K}C zM4$PBCTCNMhi#@HcsP(D$Uxns$1kCfGF*r|H@u`rq>2!fw?7}%B14&yu@qG@@FB}1 z^e5|QTJg%*l+Z8in*p+U6Uil)3FE z1gH)F_AFe_c!9^s!gsQ*maJIphJv>~$nHdoSKr$bcKHi(QOe$n^U)(%AaqPQK4CNx z$t?V@z}$f{udzCf#Q@F`zf6w6Eeo0eH-IDAYIJ8yJ{VFOOy@>GF<0Vvuf0+8)oqJZ z!IvfBjsiM-emwqyme;+>HpbDZ(xt{ElMJ2!e{;1O%??>+A+~EduPvvqV82b6Ljl4R zJ_k#xZf8-02QsV)?P^B{5{JH*x$%DLoiq*DPRZ^p5LPrhwJURz98fgSjQ26BXTL;a zf{5#^G5J5mT8SlS1%d`a|Gwd`LV73%`_49=tD<~+U?)JG>wgP@JRDP~hObV$gSLS_ zibEx0-U|bM<7KZuAVS$Tb^r_{|GNUn-lYt9#rslEti!ygCFcT}x1C!??6TuE62hqz8g#cf-i-=WCzQCSe@5>@)ct-O# z)?Tn!L8B5GoPSdHlK#8ToLrWG0&v*;F4A|A46lBqUgi_Zn4)#w)C>!-I>GKmgkRf6 zukOV_7pj5lKti~>ww^c|RZcWL+D@2+09puOr>qn_O;<_VVb$!Dr! zEwGz9KE_&G=?g$)#@mxL?O?pcn-SB;a`d?1kjsvqbNJXW)i9n*BW)kI6wZ8VJ9wDz zM_EW(>_?B%#8oXCf4U2xcxe~O8;vj^h`q)O86YyTHH_qG`rL(+c7INNTAD!I;i$SXm~ zF*&N6`ZLLi7JtKKP|P(R9HFw{s%ny+ePn!x5veineL$w*%nuF%dyXEQdOc%%OL~UF z-tpG3-XF6GZ`byayrro^m{H&nZ%(ZPdTzUgm>6uq$O$)Q{N>p0X%d_b!D)e9Uj6`X zfdXLAH(uV z%K*^G>A;*&fqjX$2n8JCOBHyhrTrUYhsHUb$9cp_Ic$}RQz}=Bda@Z9Q-6~pAGp}) zrZv`xoOCL}{F_PkXO!3{MSnpflBe#+1g3m=QN;2jb1oE6*}$I&2x`DMKtswDwbSxW zp<%*M&W+=_jWbpZaMcuPnlnR){IrvS+zDogntF_$aUQT%=O};A$~<+!kMF%l=eQjK z5lctqqn3UvQ0lwwV7%n^bW-@V*m{q&A4#2!ap5p3Ed}Be#BX$%VBic%+6df0=H#N^ zszhL0l8b=Wy65Y=68)cHZ6yBwj-1@vVm;?9-pf=j)tj+ayHAHrs4`V*Vt(_F-BbOn zLWwethjotqgwV2$jq@C!^9l}RZ}79j_7;(5-5`P|nf9hpb$0~Vu<0(+t}d$$Fts+g z^Ky-{exKFwkmFDxfCpJ{Z2z(Fyfg3b>gckl(s3rESF+=<(;bd;w5EN$6c2T83t01* z)zIfz?V}P*LCw83$ntp@ZmWxV+F$AEV}SVa16SwFBuB}*A5ou{#(O|Bj}4kJ^eJb8 zH-$7S+Dhp-A9v~u#|t)A4!}7uWzrUla0hS4R)^1LPrD1=!-_lpiB16*V)uKo;H@{T zKp<3lzbO_N^m;K1M5G07>pHgW{i`8*-Er%5eKvbIm2n!tcq^07K(01G$4A4$T6bZ? zW^Z|s&Zsk6UMX`gCbmK+k!T+urtdSW^m0hn&HL3ZNHB&zHefkU!r!d$*4fYTdveFyvoNlkeJC0eC0zn zNXX=-=bsv1k)NQb7ZK{7H*iwRdFey-J|0*ob^)}n*@ul=vNKko|DU>JYxz`$`I=0x zdaqNfllB>#$u^M))5rzy7C30ari);YZX z2EBJ|{9YB3TZs_=zG*GgH~}l?K%mVH*F)UrALpg84n=zyRYjDQr9(R=HRRAT&M|(* zMJ|%9p4kW4mhwXZXn6sFBQziG zTe??SaYkPUz~-(jibJmGA3HX`{@FKMjs=Kel(rb{e!vZ`1(`_pp#0tFxf9h{390Fb zneVyC+wr}#GP)psG2Z9peO;fAU~zLg2L0+C)35vuk2qzx)B5to^zx)7XIQbvl@;#l zL(gFGM%QK9cop0i1*ELQQP~HqbPik}L5^}g>TXVT(O6s4 z#-ThZOGjCTIStL4{*+N-Ywi#VPY8Ot&{)xUScAPDpG3Ss^VDq`nfe=@uE;j-eb{cZ zKOT)fYv4&r$ZY~a)%gARdo2QK0#o|0;G&gs$^^RnH?mCS%CTW?Ium~_n$ zi{O6lt0}Lc>(;hc2j-=6#UakcR~o|w7jXvS(d`Rm>0qz>O=QvjgP{6)_4JZUf;Hi_ z0~K?*FJOv6r#OFOMu_tiEoK-nC5MsL!o*~kAck>2f(EsKagDs%l}lQqw2I_t){lZe z9w3o@pVCRfAuX*Th0MUr`0-`*lrfbs494t|r!rNzxU3QYjB<-x70}P-KdUSnvG>if z!lL3pjcpkq_|YMu8 z)X89wXJgmPbKQOJj%>oO1f9vP?mZ@g_i)WC`Mg6%tJ$vis(|aJ#I6C^(=tNbTd4sB znR>5(fug?`;Gg}B! z!|E0{AprA@dX9+P zb1H}&+;n`ZL{;C;2vYE-bTBO9D98U;bn0jl%_Wrd!-a6tB>uQiNa!19G$^#P>nAFl zuK4q4?yiS)Eq_S1b+==%g0-p4vpe1Qe&~0s(A`{km;Ls4fYFsTfzh`VV2P)csU30g z%LYBoZZ9&O@A=&Z3@nqw`Jc#DTd_*WG!Oju-i=rIR&L@gCewsMVhSf{phd9OP36kWH$&&CakkRCG^~*Fg{Mf~_?LH0X=Bv+1Cf^<;{DZcJ zN{c)wxbh_2jKuYqX7%exq*%EBU?$NzRBOdA%)+xnU&}XcUnrPmNn=E5zu}jdG#;xr z=VoN6h_1gzJEp!f*?ZGF?1_Z@;bN4h|4{iG<$nwwHy>dW+Q~@s5gJim2-NahjZ#Jw zyUnlYp-EXF=L=(__?>NScjF>4SwZAUGC%BPo7;0muD96##6ZVtVC;L)bhiR4@iH9$ zFEW{<`lY1mzpS!|eH4mou+j^>TuIoy%SORdt{WKrP!%9njDCaNxL$%)Nf=%*^YM7= zk258+?aYZ#?yH^gpla*?NFvY4;i|9zA;dDDf)z>8jEYdz%r0SS5vXiMvXOmtePX0p z)7^KLMS&W3ABB{>{#WX%xfzIhgZ;0#+t#DfA%<;L?*gInx2m()R4@O`4H@{w_&)(J zlaRyzenI-nI0YFej*;5#zqW3Jp9x$F?R;A{R&i{I>_dgKF~>rbm%>0VL3IrhFQ>9{ zMjGn*5&{dI6s0&oY%Mh5K%jr?XIU8o>{(PNC1d<5tJnCqH8&;XN-$m^!Gb^|St*y@ z(my)5Q745)N`;*mW3(RaOe9;0j-)_W17MbMo9%YPAR9MhJ=f?ElUL3tU4T71I~yZS zHCAtLDpmyPG6eS;PsbQ^OOPP#i9mJh1zqG}r(L;##A~r}Ra18j4YY_O?}hyCkOJm$ zcgzTmNq~itSwh@^wt7RH1P8XtT=+$_8=kyt6Ufe^eCubijB*x07<= zA((ABRn-)VMIRkp&wSxQnY|gF^Z7u(Ay7kuhoADqPy^2=hwtP)x0|(spo-*rV!Jr9 z;uLEOgb3orXEi3?^TvEFwXw$m$|8#&7zf5sw?4@f)^7)rA=y7m>>-`}U*m)%Ja zCX1DxY~S-yDw)mOIOF5TH?xNt?KiHR@_O%fmC#2Q11Ul+dptV~-d0~qh5Me|@7rzG zoY6dA-to<6;dq~)qH=nK8?z1_&FLlOc;#`{5Fxm!~pWfFXwWzvO0F(4{eU) zMNfQKD_#&{8=<^A2!yNC4uobI&%AFd(z4>P|Mf6p$V-Q5sbSskZJZ1a)U^hv()8Cd zwcBrQEk8LXUy=ft{Q~)kdGDuBZfFmSrjG$T-jf{Yj;H}g?t*9${e)h#Qi<70AAaR6 z1%o*)j|w&m8%yqNV_bqE$rekMC6xs+K<97P!P9g;wAa$hbM9YfO=B0tt%@+==1a3S zO)hh;6>sx4Q_g`)UZP2pS$I-2m0^_@JsVCi*5}kSaV~^A^ELBW1aqL=V{nK=Ae9~c z4*tv9sVAMY6tts@&jJXCUKQ`+E|~3}8W?*7-`kG&9K$VJEu zWjmvA$-)WG)0cC@eKjcEI44rbySEMGXY^MxC(fG{3^IV0tehx}h>53KynYOnBMZYv zaU2yK6;qUGLZiH=Y*fA?+Cndwu@1%<4aCYJchlC;kdQ%W;lZ%+&A*F`k+|=uOioI^ zgo*UNTY@^6Vo4q#?B&ayk66Q&;L zgzCd832j7hbhhfha#P?^G7-t*$PlE_!ii%QR?^O8Jd78P!dV2ZC@7|W-ulAZG{U)Xp2lxnK~tcrYGgxZ&6KW5p6Ee- zgD2uwIapo1iFRNK-qy}pO9Wq0k6eVWdwe1vGQNC!<-W9)QrQRc))`8yeO@Z%Bnu|u z{9GTGagK5^_nfN8dOGfuk!Pcy+GDX+NJ7EguK%c>sGWTMM0_xHO=Mz?Evh(r*TpUr zWp&wA&e%sked;+{aus#+A(G1l`-M#dI57k|2_SLPsLt$;;KWiiaMXS^jEgc&G(S42 z_s|RJ{cfgzay8C;x(9 z^p(l!Pcgod6#GxF6|Ak%i&^jUE@VKd54g)!3mQz!geiRliWMO@IQ$i1Oj{-OnhwNHSGH#X zROeB~^eE+XHL;?cNLALh>*iHO8%Fkvf9&Y*DW8)$Gce@+9Q+e`myv*7P*v$rW`fkD z#5$XtUn?_a3(raQs#boGlV|$-8AH81ia(fLY|G-6fijS{WM^HZ!c;}6cI1)RX4K{D z(V(@2X`abNPbw@qBdV{#RcGhxWoSr`~t*e9I^$#gA`?Vk{}8q0z;jp-quVti5wzo)FaZ z?kj9#h#=M|qi1CG@Hx*yZB3>NY*4jmy`-~W*L8~%9l`F!z#KA-TpX2cY5G1ReZ;u& zCw(M2YC%;*J#v1!jpDMvurt3sQW2sdwrmmUQI94+H;%nta_H|db6fdD;aN%TB=R*w z72NB`=Z|^1Y(EXP%d{Xr$bVXT6Eakk2lET0>VCair|WHO-W^$OJdk4wZO5=tO9NYa z|EVcLu9h}_ZqeN4PKd^n+BbmFYJO#?ISHx5%KMnVPE0}F>(5X8$t>vXC9T9cYJeJ0zMTQy zTVp4_c!p4Gn%~i5J3k2_*bhm#?TA<#$SM`%p!2Fm*CPjW&(@5*$NOUDZBS zF%nSS1-zZY^Gcu4bt2UgO=fdp`KqWPE|JtrgK-jHvEvKmsgrIyVr#$4<;x$pb0aW2 zDgD>#1KH!*=!i}x@7g2^z46xi7hP%S>4?}FU977qH#D4dZNqZI55gXoOeEsTpqD}; z(!`6yL=($g^wDNIv(CX|CQ}WGTs$PuhzWVwl@=0KD_7D4WeSx>rG)Ah3lUs}B&0hI zm7-MUC{=S?p@~XHug;LA#5@%iSFD7%GkLc=^kKafQyw?1Z}q z#T%~HZIY4~Q~kRRAIY+d8%7+_{U>gQ_NMYuLTwVbLAo4^{zHq*A+7(cz$Hik@>3ft zu>^>xHU26mxK@pkLXFcsX)0%P2&**6ebGY=T4t<5)YZ{b#BKb%!F9!}GEdYK z`>LuGQ9fup;B$+?+IdLH>$u9_%S_z}1Xor49rr~?g0F7fMqzD#>#<+_-o!9xWn%Kq zIQToB)Yq1kHx#8%nMf{l4S9y|Z}5;oaw{|U;CG6AhTC-!BsjP4 zS`s_i8KcU&F{)#8O0_S4$VTyr2i+D`m~f*h(`Xq-9;O;F-CAOkjV!dCH-A-zXT_qf zlUaG(KG}R}sIfQjRse)PYJA~xGB8B7&v-bxun966ssJSl7KwaBwK>B)3bAbUtr>S? z$vS+lM#Mj=z+Vj|XmQbpMBu|Aa0mQsnj=+MN+g7Itg#NIMY$Xb%!HG(5RaYd`pk&y zXLgSeA8+h-#PLp_peP_m!i)+k&CLoVJ1|7>yI3z$Wba_~VUG{oM^03*Vb-h3SlQ>Ne|qi<-A9cr10%In0jH3Y_gL zt(KT&C{z0-0&2(9jhgG^3;k`(ZteZ%vEmid{VHbHKc@{J3=p&@A6LA2vn`*L+_ePCbhh{cZPLH)#Azzu=pKq7a!Z?# zh*m5pMtB}UniXg;-V=aR*m$~|EXwQ91d9iKF}Mw_pP;RU7tJheHaoxKbg`()!aoj5 zIQ8+qv!-fN$bxoOHsL(r`Et_S8Y1NrWJ3*`*r_BXX=MT0pO)_W>{Qf-sQ`u9!s)wd}81Akv@V?z6&*uq&1+IY`FNo z&x&?De*I?+Z!&f9^5!!`NDdc!dEJ6-MhNic@rTsHfhDZCHV`$Wshgidn9E_?0 zZ4<5+xZ#(oozs*g0IrnJ{VS+cS%EJML-K*LV7e?5l+<~iE)StFzCT)BB@XHR^|9m7 zvqKROaSIE@T5NNpE6Gkd#FL|U=Jbl|uQfmLLkYu8XeFkv4e_QYswO*cmTlWvexdW6z8r6O zwPj7(Q%ExhrIIK&(U8{b>*Wx|2^&{09>o z`A5FBtF+`&yx?+*DBvL7+N6o)+Ll5ZR{BtuG!vXK#CRc`P8Cx3ug9@u%_!FDkC%uMOl?;GyJL$`ycmr|A?by(HB*=7B6W+*tY2AAIisK_ zXRhi_Zrtg@lhQ1Cp35K0l}8GPF~9l+LZB%if|(_(z;t@%k*d_IahQB)#0#|0|Ike* z5`Lw$k+aM7$0Ev=!n8s2#(;&;RAH~OPEp%M{j8ayTu7DHykH+-+0fd_?UXZx1?8~m2ap5}niRdE zlU9eu8$j9@>R9B627X)l3|jRB{-QOVOCYT@0!~-k%7mx*8zK-I3Z7RZ*@saT3krmo zM2!>UAMVC6NrK@)akj8Iq>F#-p4LjU9&(L_V3*?k%&60riT^lL3&>0g zT4XMFJy~dpA({&&s0C1B+gTq+$iA&--v`R(^p$!9`}UEM;lf3LaeJoPE=FaqP9|<~ zwF!UuZ@?`{`8kF0s;R|SXw#&1lZFCvLo@R6+hM>ltqIHFePc|@2vV>J6rEQ>^6K15d?AjEYTKX=C zb_;KRKjK)dH-0H62w#VA?t2nE=U5Io3`+E{x$%jJDBcy@5o9+O`7A8EOo0QTadPuL zCodlZo5DO|M?0RQDxaw0|)3Dz6gDHUK^ar*%;kc-O`V89ks-Kg;9IhK27KSc*Egz9;ILWPIm(Y zYV%o)qoAW9JA|! zRl<=fGPCMsdb1iV#&(_M<1aBQ3wzLtmb_LmwMdY~Spa zSMC7ZohV*oMo5s33YP*i8uTpi_KI6D>}I9-*}XaX10^8qcK0lYXwp;2#QKh zi9NdK+=u}8&t7`KSIaG3sbQDM(Gj=mjeH$gmfFknbodv|OpAb7a{o8xPB1$>$mT?C zg|H}IC+Synle$xM<5t4v+9+4MPdMhg1GHx8)^w#$2wHb2g6S5@zP@c1%F@EMcgm^= zfpIFwS2Jh@ia%Gxqc*ef ztqTn%?Ncdl3AOeD1lHtQs!slFeQNJU%GEm^^&%)hp37D$F=4jQ`Ez!+)Z#?uweImb zBuvyNzQJ)#kk{$|1LHs(zuwG+7hcHamtUR|@G3b4YHT&t;U17IyMy77kAC!{y!53n zW%K6E0KDlG5j@D;7VYg+k{p+I+g2FlUpt;!&ud$N(oA0FPn1nyLcrF-gwAtlE9+YDY~!EI z?Nq1BU@#!dvL>vt?fuey)rq27kk@mm_rLHFZIBWCm3%0Yy=Lx@*ceJwCR%oP8 z(vIY(NuTAtMj6f~Lv?gL?S5;4S**c%?k4cIy;s}(Rj6zFcZ;+MXg!bM*My zLcOLra>|lkA1S8|zHP&vyf3&U_@obj5TnRoVK8Ah5jrDysrRGwP48FjEz|t8E!XM( zYu=J&V_KteM;k)4(%LrS2)z9HKf+TkeH4`sct2uPRg~4z2NtZreG7&<%E%-S)}@cV zn4dqepFe)r2PVobI@PMZh}K%({?cbOfB(|E|80VBYo$c2TH+2AWYdhIZPnBWs6(o> z5DF|3z@)F5Q029!qR1>EMvBZKp;no-SZ6R1x;0T>J>8+IJX_XJ@sN$Ptm+k9yW<{q z+;fQ1SAe10$!L!It`&o^3oBwU7*mI0Q7a|2YePP*0aN$GHVjb0E5KmE#9BE?<$6S* zXk3E1a}3Lh*_kf(WW(%CmrsB77S^trq6&ek-lQ|#4x_4~n`ewlPwpJnM9$f|k-@0s zj(ZL=-%o2H!ks42@?40GW`No%r-d0IW>ZsBz?cwgfm90xLI8Elt3KXQ_e(1~y^{Q7j!id1X{ae~ozN!}O!KDQLax`M!zI}Y*3t!;5&wVbtckkx*+iz#<)~#s{ zmryM9d`Z1AOezZLb8-2l1d(?C4)QXuh7Ny_kcYO_UP8rsz1{>p)J$75 za;2iN#;K*&Nedg=A}nEry03x*ZJcT2?qD!z zPS?@O!dMXs6aCit6nZ|i@To1Sf+ylN)nz0MNw=%p((|k5Tm__q3-`{sX0J(GQ&UsTzG=$=Vqg=+vyC@x zzR;$@<1vgA+I5(iC_mHF(~Ic7#v_1D8`tq&7oN-fU`Q1L3xgs3;V2o9R}_yE4#td) z|HeRnIO5_*Uc{;EKT2Pfo4^3gJSEZOEiZa1FM7*A(d+dXjYhovrO)7H@BCL_Y^l*3 zPwte^Lf&vVWM*at?|riwLI};B9Gd9237{s@Ql}7U+2mqb%ABRh9L|EXmQm6ja2SjQ zV$#NZh~$|gbB?l1VN{>Leg_-ZOtbr7pQ*x8M>n_ea@4Q7y$6a^b#eo9{lF}B%;}D2(>?I05 z^N)AZ~ffxebi@??nn@Ya{_~4WB5h5Yh@5Mk25Mokbe25SN5vW2% z^jZNC;YOHIBogsnB~FKNQuW;2Tyr0Wj;C(Z0`c5*yIq`1=Yor+TZgh}WmXuqw)_hY zXaPa)dJ!BF1wn8`n69?Q$~#hrrk7%kEQ+EDB@&QlT1e7DnDoEF`CumIpsnl~>zkY* zefLn?b1ZYIlNq&O3N+q7y$ALF)$<~Pw?1-J1R3pF(q0S+1JsFyq6~=lN6w>{vhug= zQB8Xwg{F!?sp-@B7~k=^_}1Fy{t+$6$l(r+u`18$_xp=zpwJ4TS=t-Y>2w;av8Er= zwkSqAWKL+UR>q`GEl}z;)a$B^C`Gr^tZPve*vyf43Of0C?KJL)FgZUr--LZhJI>aM za#=Nkz2K>YCW_LiJx5yM*6XeJjo^niP|Eyh+|X;ffB*hPin>-vg(ocKwGz-C$F_o@ zjZ)f!rM*p}c*-3~@~+UGwn8KdyvhP0#azi6KtIX5@g&_D5r%KFIq3w~)llz9~# zgz+67Z5c_*Ei5cdC{(gWns(}YoOG1RU@&OzJ(K+`j04@V`HpY*D&z=?;!sKlBin-BVZ zPCsokUsJy?I@Jos7N~c=?3ujiE&nt@WPaftpWx44{B&OW_Ky=|SJfi%;NG2(3UAYj!|z>pHN zu~Z=t{(tt~JnoXJJQsadRqg2;dv2iVZa_i7s4;2=!#O4@GCD;Mn%rYN0U1S)NsPHC zauXA8%uS4mQISC$5{<^3#DjtewBQsG41&lgGdBH$7cm zTcGIO2RGP(r13nUwHmJJfTBRLeLL*FeK1dZ8ZbT%Yb?gbItOoOp=@X3c|f`vZrr!y zoTtfQ?yFcWEiGYgZVtyCcO34x;|^SV?X{Sloeg8OV#NwCgiC;7ZQasaluCVgU(^%D zbR4YpMGum+o}AC~1nYV(o-ekeR>Jt&QP)(zOAKM%Rvl zIUeMXqe7|lU8p!~%(wPet=Bp>@%XBDLoL2)NYdN~{f%HKR&Mzou=P2QJrQ|cZLS&f zH$H=kx6(?IB*DwX_hKIjHWVO|Cu%=AP8ycQccEggW6s~zgQDP3`7y_z;7q;zfcFse z`+cOQDvch~5rAMo(GWMzRi5WCNmV6?&r~{7Ud=<-Aw4#lZ=q{Q`=uTc0+VL5iB6}p z%$vpJ$heosVJOyKl7$J3^ zpHW^v8YW`n01XPwW)sac!EiW0w?9B5%YFhn{(}x{EehvQ(uh)%n1a2>gUpfY#0 z7?Q{cFfMuXOL6|?Hv|H>@-3&~+)J+m09)&{k3c#cWMNuYoHZsgEbkRstG_nM}e%1dD0|#JU@)FoxyO3Zr|VciN>TslYHFB27}*!g^1Hb#Sny zGM@lQJiRR^e_#B-S`S)n0b5wu!U9RTmP&WCbMOGC$l)CPZ`M|y&@e0uTljZz4!&RI z?}fE6#-S*pxMf=eZ;1X=i?%9;>UrZ@r@)~#UKMN7Ppo+8vqmZQ8k(e`QZXHAjJ&Qb z-lLX&Dp9;ol|{D8cUGmI??}O%rYTyDcCG)a)Wq+=?>e4bKN|AEKB&T^O0xC=KuzJU zDg9Wk=&16`^L%d&X=EP`wt-%nrXiF==Y?Yzcz>ic>701_Sx$3$ty=n3w(x$+7VVc> za!G%@CI%lYo?_|_6=*$-F|}w0j;Zz|9us{wl~fJCRIsKV6_ri8*4aO`$Xi=&+@^htK{MB9493_` zj8u^Ja4Wah!D>hnyavSET`tnf~K=YnZa?Oh7%?DF%W`lK67{ zV))Z!bM{L?ni>|Auf_8dj4pW<4yxc=!#owpXq)V_hOcsNls0reb&dbz_>Vc9bFhV7 zRsm~da=E(X)q-%v1*e4H&%N|IoOjvvwfqk~&b#dT(2mAfl5jtE(do78H6e_ab6PrB zO+=MohS_jYdg3$~42z(|XG!9>%%D;qay0mxD|0GT8So06vs7+M;9|UjX39@Ez?u?` z1Rn;!`(OOyOqAnm$`CEc?+(V%GsC|<2r*?Jd8sKCN8U_BxL=4KTqWguWN8AI7!3OZ zOpUd$Z+;2wMuxdaAd@j@e0&V@oO2;u38(`HWq{QIhj7GR*hNg;6X5|nIL zt;hU5ncIOEZ`_NGM?VLXD>nj#`Tz}gexnn%FS&cU@4 zxO6RwefxmtJO_^*n8nIX4QxDm<-fX}M{R+k$WaWvOBt|OyLK%$Z{CcHF1iSBdCOZc z7!2@(AN&BH{NyJwcVG@ntCoCtOf!Q^QHEU={$Fm(7}Xp_F$|zYl>{zuj$KgzMQ+P> zEWmIohY}Q7z*-azWw=#Q0uBowT~+vyE7%Y&Rrh+(3**6&{#_Kd?0cnfb6zPR;;=ha zQdBt}<#`~T#Y#D8Nd*wkkzc6dtICrqB1(%&XG)tqN1j9>tYJke@YQpx{Z#=R6aZOQ z`p#-8jlqMqrE5^Hj|DN?AWzkIjX@X3R@b3ELwN$(ept*Hs3Kcy3080>xvd3EDhVNsZ@*zr}Pj>hE~2sIaQitQPv17IYjnbWZ8s70dT8 zVBhGGP*1bYSC(bV%I&hBs(AA^ZCAmo%J2C7)ni0(p~`XW9aPU{{4X98&UFmxV(_cV zZmfj&`~6UkJf^)gO|i7J6g)NR4Nyaw_FKc4G{;bl657vrjl_yQ+gFcm46b=z!;DAq z2R-z6U4wDE%u&)YQE#HI8Me!JDvDy+2B@RJUkqflkJ_e&Epn`_wZSXwoEuSE2#mC=z2Ap%a;(}9xH^W+sk6w7{GV2)sjs^R9 zmt9}m7?APNw21J2U8Dpe6do!7ilV@%AJTaBwhHsa#v-cW`YO!{7KFC4i}iTsiS<@P>dvjDg`883Wh?#+26W&=>?a zIyh$nelgOtuPji3l4h2n*B@eftc9h1RsDkt9jHRt4pz(ET3d@(_wQL3Y-Gr4?IIIY zp&nau!y844i#|io#{$S&i=SCzvEiuaV@3Ksw9HX(!y(d5o8Ynx_LyT(+mG>qDpVJTLPn>LrbN@Hap9LjaX4x)RV8U~pwl zhjj`XDo;gWOK^jdk%#avhJv}023=tIz6b+D!|LHmv}UR@p>rK8aB9Ji6*G=utn?hr ztHx(>FBkb2pT zQzb@2r1);y4_#leLZntj8Vp7$e-!+>#@Uuyay3sz6+i+~@&=uoniamNV#F16kl(ag zjlicg?}9ly%wb_aSm=P`#2EcpDb?q3%wul=>4zXFwkF5?Z4{tT;HqQHaUBIx*+*jx zjEs4_u40(sD7I*1QOJXE2J)p}>)3wkC-0VaKarPbJgIg0P7VOjZx(h)<+xNa5nj54hJ zX~S^$9>}O#F1z=rjb4PU9>iB#CLAH?U1zN*N>l74BDsFqx7d3f zE37(Rd|$OX>vMGd$A`YJ@)o|AhJTR`)M%sthkS_qh4V(mBOZ5N8_#PDlGJm~K5=}= zyR8EA~D<$lBk0F=*Zh+}MoRiLHs zt$a~sy7D`HMm&BTSAJKc0sF3?qS0uCQtwKqI;KiH>Z#>>#hzO3o1~yR=Jz@_N}oJp zK*J-?hc!AHmImw}=O~H-bF=$vzbS|YfMHoIpwKxL&(M#0?0a$#mX;Prp0n0s`{R%P z(7r9}f1ab??=9oy{}y94>KK{hp^N{#%dW>K-u8-c&2N10>acjvz4SU<`Ighdb(f!i zaxD}`54GH#^`UF<@wc84&YgSdbwRM{V%3;`6{uu-{LV$v8*q_&r5-K777nff?#1YydF>`pL5=TYM&NmPO*hh1;1KUc&uu(B2fdzJp*p>YM6B=1=o|wG9(*T zV(|U_XdHD!7*JyxFm@Ac|3+jhehIyP|B$q!LN9A%=!~_oYV|72?3=;eci)45_=kVM zOJ4F4Tzv7xFvj56V~;H>s|+w44$vx^aAGCQ^W0Y}Gd_1CR_gq0YraGQKb#T<<>=ke zByb9LURPdI6yl(b0trpB zNE2Z-M2dMFio|Qwdm9VuQD7k!dTOcHJQWQcA($HFy-A6PEW@Ze{jaZU|zk`JkOMFE2Hw-^vo;qlL@<^-sRl*FKyd`sJjXgKb3mU zjaEa`cN}%Ze1Qrq*N_4`dRR11LDyoEB*9arc{x-jS>;NBxdJP;$G>$hm8LYBKy@v8 z82CJ0_w?fMH}Xmi@r=Q+D&$2`1dm0$=9C_DnIkn40H}GBV)|4)Lg`{u$Ta6!LC7eX z&-qkYp}?0y2D`{b3l|DNyTSI;^ZjjZu6de-0n&Bt)(ZQsGh zqmIUb+5MQCoyFwzG$yB}v3utZyzoW;32*tWcVe;EKWN_o;O_5z7oYw36}acl+wt5N z{47?iT#fJDb}NqDd=!53U4Mi%>(>99j8#b_wXZec(JvLR7`dJGp=*{+CW-u_kKGi6 z?VB&T7S>vv^`UD5VK5Yn?aQ95RR9jwT$gM>yOEDbgXru}H#wTXOSQt}TzZ`sCL@1Rj!mNxc@e87yM^^$C>s;8N z&?L@>XEB-GRQVme-i5z?`(B{aE8yh8rP5LxZ|6g@P?gQ%GlgZHf%=<>U zR{@M4NzcsJty_y;uZt~Pw%`RXcmYm5_0)1aeW+8gvKv&Mi6Tc)Do6cZ zf0@-eNs>?&llc{4oE$@@eAu8ZL#kj=#>1B5Sl;Wbt*&)cWEg@)0pP;39Lf-@kV8^| zL+fmyPyVB2i`G&VG^(&D{lymCSbLqn)Y>(05L#L|MQ z&(XCID;F_%R^=uLOaA^rvg{X7^52p)93a2x~h2qTQz zpEP!$f(8ljvE#1PNx&v(5g-fv znwU1NwLnpojwi3wF;H2qYlL&Bdm!mp(bK5FRB2e{Jl`P(?rcZHzt~4LnvObt?!*_P z(@1ggJN`S~{XT2GV-9FMZ z!NtG(4m{`R!?2V#vDEh-fUn`swH6jMuytcinLt-u$LF;x+%}4Y=v6 zU&ViY{p+yhA3uZt_TPW!azl_h001BWNkl1SCMycz#rIsV}9 zzJc*(1D#d_yAXS?U%rCV?^KXX)u6J}`7eI2YU#l>j7@ujHn4m4(p3 z`AdxVJU9S}scdz=IVy7e%>{2ouvYkn%V(DTa}F>GjNvCj^{e3R;HP0eit(k)hB+F| z20G0KR!vP|)nprw@0|~V7^w1i=rSdPRz}_Jc7wG@>gZ=otls&K34jP${f1y%lIMV{^OVEZiR z>DOspF=DA)O$t>3%|vPu{`W2`5FSk_fyk zpkmkQbdY6P*!zU@8i&J>rzw5(dc6>mqx-#3QH+!3wd6SIhJ2Mulv<3H=ThlXI$~S& znDM(-gd$D1JH9Vz)dB63p;bJ3f1HDc|Z5aAed9bgn})~-l8ZlF)>jKi|%&2o(4>1h-8&H z9VhZgrBgna<*1b&X_#Rd{WQ%2-y04zblw|lo2{1DKP`}^CFrz3Vp3#j<;CLOAfzA8 zS(^H4kfV;peihohZO=?ik>+t=(N`tQ(ggeF7V!A4eYpF3-^Cff`Wu*7wGRCSe|~9k z0lS~r0RZ^aUw;z6`TKv2nK>T{H7pAJ(rK^4o!`3ypZ&~daN=pN#V{{$%gx`y@BRKC z;-T$(ux-!GkNhkE_@nnm=o~gbw>oY`c!@M{i_Q2 z?3y`%?fVV@0LI7KICjH&-2Lc|LpoOBcq)&Fx*-9-zlOi3T1;FtT&T)~G+aWFyfTYX z799X#5vgmOu08f4F7rxD02PlTS5F|z5)2EA!~l8WU=p8LKFl5R%wd@OoR2)WKnmo! z_2p6F6)AcW4C9pG_tLPAz@@VIS(G6sj=35o1aYM=!clZnF{78nRss_gto2Wno(&3c z0}JE4rv}C#F{M(+33auPQn4&8-?@QnWVq+?-I!hI1xqi-Rp*?R3LR%M8V$dSl^^F^ zV4QKXSCS;5mN^*)+osoq@vmy3i=Uy2ulCI?Ey5KAiYJ}`jy(>py$)_Tg}l2Ju5~1e z`TKzJ=L5|Zu<1@<-#%c)3gp|i0Y@DT*PMo3y7$nvBg!{vzTfZHN(PRPk7M)Z%>aO# zZ@#&<8B?C;F!J}((o#@n4;(mv`T6+(#sFa7zJ28;msR*$@CX5+DwSHqbUfqF__`X{ zpl2=mr`KwG)>>rcoD$S(1stwjhB9!xM!C&Nllm;uFuiqbSNpBBt%{xIG0`%}=V&|X z;ZWYA^sVcNiUGe<8C5XOcD0`dNzMAK2bI`hrHmB#WdTT))?a>=8+aiN7c_c^aFEEQ z3PcD<;4^C1)w~?^91DLEp1SOCY}Y|w-MPw;GwP^$S3-BI@#Xx+@23iNY=KtAo^zw1 zfdG}>dxo-Te7Y+CnxCQZ{mS2Bdeh%X6Qee8)ip5I?u;-FxX@=cG^qud@5Ad=rt3HX z%lW>Jrz+E`cyjE>vjR|Z^qhK`Lurw<+hV%VIqY;g0emoIlw;@f zTw2RK2kJG`b*1mlag0N#bgl`&c|GO3u1V5|7OCNy84L!1Szh%@=arEHqmH7GGT{f| zChB1`FzEMt`21)727h_!mDn*e9}H~Y{`yz(+W+HC_|o71Ev~-$YW%_f{U?}_c3$-2 zm*TR&_YzAD611jP zVcVXWAE|FiNq-MK@BrreSK(8i`~;r!yyxQ&{@@QV2y=Jn#)^lFbf$;%K zSp1DIPxMKELE?&CXl$DcPc2DQL={!jTA@kJ+Fzi`O&Jyrdk%Cl-pVk@{WIo!LnJU5 z2g5TIr1gR^f zv~yx$)P*8KC8cn24oTvXzexdK$*Qth)q4?ysE6t;jX%FiG~J@{rtE3fc*uZV&1`+{h>LjGNb zZRwoF!bxdF=U0_+jo*#wl!gffB+7p@v_%1c(y3OZ0hVu)sR#L&3VbS%d@kFg0ztl| zvH3htkBb5>RYFu5QohN)tF_nj1uQ#iGB-Lu3Y65tr7EvQ>^b38HE)UH7zyJ#>4a7I z%6~HgX7Jk(ubo}_KA+`$AX&BRLUJTb!{>J{XEd@6^t})Qj`HkD-*wVwP?-|bx zbr^nUzja*rH^*4#hJRDn2?mJrUae|HPeH7gX}?sa#`FRpc#~qoP5`xeRl!YZf&Y|7 z6fp4M^L5?Cz)bt3x+V2Ty_UZ-%sb8M22zf7y{JuHF%L)C4K0btkud32f?664Fs_J>7K&Xc6vgwOfJ^SrG3RFq*!J{CU-0@BB)SJpZ2^DG$f_4P0>d_%#juaGBY|MQk8jSRQ%3sVlRl&3g?K* z8W|_&v)klXBK3)j5g*P>Y;l205{oK#NnFM)Jz@$^K*%5GiuZ5X< zF`PAUu7DexfE^!4@!jtt`Kjl^!ocPhE?slT+9BQN!yIs4==XX(EG;de+wJ1n&we(} zIp-Yg*s%j2{NM*MHZ~Tj*BBF*#?67AZ)4-8jrgtK{w-X2<(2r#jW=TR=FRB!dbsPZ zyRc%#3S>0}!2?_c0bxB_TUM>I0f_kXK0M5Wi88oGXu}2iw##^f{?l9)U7w^C&F|3= z4b8!b8LZMGdDX#aSwm!ErA_&q#x9dCV_>UB6IEhH0VDQ9Evx`SI72*kY(LPn@XJ`R zP+@qg<7LOM9QpfK?%Q9pau}Fp*KWCYCU`au)=s$gKCz+EsA4?mU_Z9pTU~R~@vD|U zOON0&aX9H&tLxX^v#;#8!%4?S=kA>eVPr;(JF2kOjTdz=4AkTMH7rVjM+~AzfeyW& zv`oNBt9r~wHC!;xZ%~0xKA;{s{!SXV)}om;YVE6KSwojpZpL&wN~z~>>V;NWp?rbw z$aM|W!Hup@_FM1Q@47ZLY)!oyd|!@ryhf?)^I6IRNFQpQ=TQ_zFctt+_+`BQWBI5u zO3&$7>3gd@)lgF&oO8}yYZq$`lbugOM9;`0DhaZqF%GZ?-YKZ#spj|&-tDyauscuA6=H?-&t zyMS|;9%~{^5)_5Su&~Grhb%E@Hd4$k_0S&zjVwiy09j%J%<#G4Y#UWR@)h zi^=gO8d-uynwDV6hs_Lz1!fnQuzBqY?3r6YBT0}ZW!?uw$)e(uB*^CuU}!Q7ZDl!C z5NH<;U~vC;vGmxzu)DVUIa$9E$;RW*I^k!LOsxz|K&@4rOVWcTU#hak>&Tqr_;`5*XJih(yZ)2=8hIXfeR zoRs&(3P0OY@S2n+}? zVjw_Z;XM<;Nyo3mNhhqrmV0O5OdVJdG<@a${RmM_BZSdO$NT%)4nJRZf`8VQduQOB z!^@wwI-CnfIhF)2Ja^Iwt8mh@R+sIqUViR`Rk-2aeU%5qRMveex;jruvfQ|$-X*QJ z1fV8|thE>$8w(zJhL6y@L9aZGXPh^+F2~gY`F!RUs6kGFM+|0P# z@HF9`{ffPRy4GSVILB3uPr9y^_H}(KIA>op{8yEG9{Jq(mH@tDPHj^Rc6j$F%H zYMN~+VAQ=dV&eqc?DzZ2bS7S}3V%FC8f`!;e%v43$vsxyl@F@HLw{!|8xKYo(AdZK zB+yXash%{|>0&)hb92}h@8L6a%oU{QysJ*h>z#A85J&cbxm00X&FE0TBG<70>i2uU ze+SmCKOAe<9~Q#a^E}5BduQ?3uDy8KulySJ&n@CxU;7Gv@t0nPo4 zA}sa#IPK(@<6GbQ7F=(k_Pl33_j%a3`6yg<)m3=Kt6qc6M;sQCzXAM6&l+_wWXaQ5 z{oIezHwB~n=N9qA{spYxuo2(8?N+QE%dmgp(2Z5S<8_&l)X!B+N&y6!qsBw(J&bYX z=1egfrXXNP;{;HG4#xlUrL!zUyWI}8#`(L})gHBB4xA4_%B@4Y)xh+4D+tUiNzg7q z0019vO>yfMCg zlma+O#fy^2=Ywc}`)Uk6 z`5yFcxh^oTnE9$@O$BY1Wi>|6xg1rCpTZ+#a5KW7U|lZ+jcQb@R=BZ;*BiV`CkpS>|c5D3F(H#yW?*4281J;;y^yMz`C=#*G{C zy4Sr9fA(j8h8;V0`p~vkGkC@Fyo@C;+bOsP2Ltp61N3`6^m{$@x?Oa;OITQ3#M0s- z78Vw;xVThSChMZt?V{i7qdyp6I2d3!EKB?5Llo9xn0v1$0a9TL4PS}7|A z4V>FvuNO8iRKTmo81)b<-(=}ozK_aVmA?ug6g(3=DUEX6^!+t~TS2pi(~bf~{GMfg zgGS~6s?m`5Gh~MS7!HT%cDo@zPS+m$pdqOY@jaMaQLB5b*m6Gm{XV+gZU~i&!zk6D zraXrLAvU-Hb+1|sTGT*GeyF*6)Rky(P}){Ls0N9cKkyo@LdpIsFXFw!;c(epjRFKQ zos2RRlIGNStl&jyTX`zqiFzmJRx5p3YeN;zUa#lr=O8%=YE0m#eH|(iS?_daP!x1!s(}-hWj6X0^hmsQM~X!zZ3v)?H8}AwE+Mx zJ?&LkT3W(uUiC^OiSfq0=y@8je0{ z6IzYzkd4(-9W{@KCKlX;38M~Cg0BIS5WxY~|%!f>+X#&8a znKUrWbL54^(0X8z7YhBQeq(Qcq99NJCq-AEE2 zf&~LK(+mUnd6``tpwrB-2$(el9o7ste8gT zr}3}WR%Ma-FIFy9oAe(I-?Z`}$^P60`RqPP2ywd0BcHuw8jn8L@d=0%Qs@4Yu9iWm71&b5%FNr&KRRX;|+~NSXh`1w=_ z!WsvGKrDW$b^FxtYh0+=CC9kH@SNFtH z(8oRnua~R5OX?LUiULbZOQGZ~fv#4kqH#~bjM8}w4q||#Je;4(E7?co$4bxi`o-1T zcx~2510Uo&tLMtU?{bgFmp=dZxcO^0V#CHw*s^5{zV)5k@Yv&9@!Q=mH7PUKaacb`vGR=7O-o_cHH^h@8EYYem5@ti$BAYyPm*jKYj&H z|FyHQ^|42A|J`@uhz%R?=}&(e{leg9UwUeI?k}Hw8m{`gzrh)2oQ}I6dm{XP zp?yoor|^Zp|65EJ9Mu&wb{{9xL{LS6MM@ z)RDzMXszzc&U<<7+yz%`36soET?CqZsmz-PnZ!pgJQZ)b^2Sifh)C9$sx~tj1eqsG zsHrkRVopZEYpSTG}teU{%d*@&skR^UY!Nu+njVwhYOOO)jgL2iN zs$7-8B=GxmB!ElGdy*7|Es&_P=zy_S19?$k-Si4{2SaRHJB>ZF3pi}m6rR{Ohfb@3 zxurguSy~rt&U;$Yv{a~JU>z{XE#Pu=`vsbr52@-6a&%e^?4R$3-_leCF#wZz;NlGO zB*EDF!_c3ZMc$o5V__b>%_pFH+fCTK;~uQtw8=Al_=NPOGR&uwrpTI2SO9BRug3K3 z<9PJjUq<^EPOT~Z8iTpGxEOXc)sO<`T(Bssm4){fMLn^c07L1B3Y{v3;n=(xMZ1jx z4*8xv$Tl2~V&Oqx>}P;v1#*nTbhiQ72ISj!0Gl@hoiP+90ru{NTQ!Y*@xepZ4jDDU zSHIuK{DFDwo!N(l`FXtVb+5xU*IW(fTt)lN1Z7VxbeiaurYYv<<}ow7AKj%T{K~KV z3U0XJ`kFP=mvpY$QR^Iq*j6#c{rR#2{2HY^pe^lBWV&`A(cnr`BO>ZK%4XapS5a zs<2cgKtYLG7Hc$&x^aia7!<_N^5?BxeziZ9GoW@hzL1f0CTb&~$z_ezF$dqnET5C* z3FaOg#GzF2!4NG4F!8kn=;1Y}1Q`O73doUPs60{SNO_BT1(XIDEHnr^YS{|g4*eIj#Oj@TL)yhn4gZU*|nTy3l*f%P}%J+4BXgH(- z0QDsCy>-2EE*SQ!;Y3`kqpab&Hq=|7Kv0dHYS>~Qv_7q zRDP5Ha@>`tDtMzuGM+ExseCsU8=xVLz^G=WD38;B|JCzyF6=yJ8Z% z_s^lQ7Qg=|e~zaeu?{OI$52>{$9L@u6{c3MUW326>UzK=wfv5kp85*B@r`eM%4gmE z$o6IL@#;6c5pRF{Z{qMZE6{3W=nr#j+cSgh04{j@JMpylt^1*UJNe|3@$@6tW4ztM z+)@|wi#<$^wb2_4@z3`^ddS9VRR8r@mRznSKmp*wkA4NcUJtES3zwdI5-$4KO;}i1 z2$N3286O`HHO=XHP=rf@w$|d!ms}eZ8!mDZD;ES0jr*kX%D&P{N$Up}H}6^K_0Y^x z>|f|1F$VoXf$??&{b7zQ0TLMG)*?ZIq|D6rbZtD)a9%pos9_9X(iFxzWThf8(Qd*z zpq2S}xK1lWUO23p9K*fa_Tp)Wt-=qs@5K?Tr?6*s0c)nlv2T6}|hi6flGcd@lPmCWHm0%eR3pBC>!@e!c@0N-{sWZU&1bSlut!W^oAq%ar^1{fb7$Jp3dt>O!#et9+qP|9$8!#yu`#4ciX`>r zpM5Syx%+p?{CmAV_RsFe?p?d^^rt@^XP$W`ZomC@?AWohq??1a_{bxTdLGP#9Cu zp(T)?andXQkw4^l9`*wuZHc4{Zw9gj-B2YdyMh>cQl(#n8X9LMzf8)u*e$Rae0V#OUvw< zF!nM0*EgAa001BWNklr-RE~e!U2ZD?kte6UYg#XK%2^zEy4B>Ll zJ!Kdd$~SrWLGn0i$?S;VV|iQF<|WUz$g(7iU$h^Vazu7Iax{Kd0#WD%C0uj42_A2&?mK! zvv5MQ(XM3luxzKWXt%}ySFJVf*~B%bKpvn%OjsQ!O$gQo6UurDqbUKyZF#>o1z}7u8*t%!5!Gp)n zy|dW4clIf-0f76rR^E%nZXe%&c-z4*PEvgBj{BDx&3@Ep#m8g2G<{W6n_br}?pCb0 zyF+m;u7%((!Cgy>ySqav?(P(~0Kr<^i(87j+sXT#@!upFxk{d~_g-twJ>_nv(bzZh zRh$iNt?xD(dNbvV&BpfpB~e;PXFKZOcj60xav=p8miXhQX|xAt50FSS#=4&%j!-|{EwOWw)5^z(|f(83RAWQ=|}jSQ>MstZJSg5#*bqY(E78! zRUBb_-%3NRL%D{Pt!e(8+&njw6y|!JB0?59ymh%F5KG!{YYaYi)G7ECT{({#(;q|P z{?xT@99=OU)i)UmP6@(KkS}esT6<$hzdKqg0&wGC&2VufTALVr7$hk_&k)P|F75u? z@I(*T&MIy&_aUic;_7OT(k{=7!OOoF{k`^$w5HW-V2sh?0DAB)mrGzS9aB5-U*?pP z$FtwB#WcR(#E{HU84tyLc`>e4I5MX%R+p<^a`l6bm;gfa<3$=~$ z9HATmMv_Nh`~Q<3g<81!Za%x-t=L#R-;ujc0!v%}wTN)(AHuSZ1a0;HH3s$eJpXsf z)R^{BWE#1nLOIhL?{D|JP;Zy((Qhh3oc#B@;$#@`LEez_UOZBM$9`#Su`n^;JG1Az z)AZYAA*dS+At^su^loAx!!O5`kidtLz#B-YD^vQ$tkl_KXlN@F-Z5*n1OZa(Q}s_% z_HQgWw+6Wo(J66L)N z>vCO0&P-Aorv(XHn%)T~5!AoMpYb_Cln+Lx^W26rFZEN$0G;`2*El!pqA zIs@}w`Pz4h6G2f6kWwd_dP30CIH6HRmp33+cD6jfV39drqcLxPrOwPFwvpGsVWB&NXb^M@0=jUXL0 z9`L*iex(D)LeD@-J~s%$dP}*7~15HKW(pqW9F}YK;N!vG z9D<1Nr}e#2a>c<=y^1{yl48n#WPe&eMm*}l>16}Bj~pp|0ngQNfN0`!4=ml@ect~O zCzAtjK$>o6rArG4H!GBHxZ+S5m*9ER@nx^+JN;U-6D6T<-at*BEwWW*6%1lua0Y8| zxZZpmkRB(HTtLRy9;9`l-BFDu7BIp>AS%KhB7ZdX2;el_I?}LUyaRc!A&txWEm%|T-OWjBUeGoC* zufqReY&k{s4PTB{wK?U<-m3@`^a|;6E45=(Ot1+#0S@x&_ziU!jP8Wa3q)6xH_1*0 zsLN!(uHT(+Ce|8Kj-WE;1SxZL z&`k$6uM9DYU!r#JlArf4)*AK{{P{ zkG@P(|Hq<+t^rd!?5mBPcHJ^oEl3GoYkc1P%RqSsBvec@a4tXtMFU8EK@P>c;RIC4 z7Vi|gj!KIWwe0eziUkx10LJid##LzId=j$w#H|=fMDz8Wfm%eko5yZ)ueo_j)DXTP zm?YeqA!?msn2|&bsk}~j7!{b}mOXF`8T+vTAc&$tuW?s0*0*N#*a-F(M+C7!bO$Y< zD zREYA?-OS))zzr)ONLLME3w7r|^~?x|Fge6;Z=(I>CDm=iYH2BpPU6*V%- z#fviVn7EMhCZ=Kn@b|ohj>B-7dR8;39urGJ#F8VV;sE_?xZ3sT!g%-dqEt*YwN^OOHtz~jE)<@W&OKOI8LEBcmpF{&<6bxclV4$!H$f@xRt zqgc3xV=JlEI!*J;gDydnUtO#A;VKO!)fA-^|1XNiJpcafjq$xJvf)YM_661?>~C<^ z9*=R+moC3R`Ry1SY z=M3xquuGU0d>RQ=5&eveM`=P?yKu?_X~E-*Va|XL(7j>fHuOP$3o4{ELrI_8%{}*H zJnMa?X!*;|ONv=yr(uvb&f>IXa2q!~)y29LTFb1hwFZN*T?I;1N@BrPqE!-z%ZEr@ z;k4cikC(w=ma1zb*mj^(AZmu|*uIFT6Wry$L_-uNFqmiFxGj^MDocg#Q0zBzuk-#~ zNG$g7PHd($YHhOVzM@>M>Y{(gQ5e9985B7eM*8mhkxr zEdiI$Ohr5E=nRgmwwL0BlJ1LfkNt@Vw!t85Shwi$l~s%j2HIDn6#fqv!=Z6Rks!8i|bQE+mFOj*Gkr{IK!~;JJ+ZUX0T{ zCo>7jx0bK{=O@79mV1bM(1gVvGtnEDUWt*g)9UR|iz0&GJ4V=(kt{dCZG{|j>2E>_ ze&<9bEJ3l}FHrDdXU81nRI`6*kO=QHBIMJ?;2@DEAS!oKIn#0wT*+a1dAmj7GCXTpu9?v!Gh)W^Ule1q$uh`r#`w>9DORrNDaAs<|eWLL8hP9{v#8nO9|dmp;X>C(Sf`4b662e*{jr2 zAAIrPcU8Q~MkLec`MGTQH0TuJH7$^?m5J}1g-I|i8miVKJh9*%X0r0YCER**hg1%2 z$p*3A3sRjfloSC|zFfxOTK+i6=6s8~-<8{<>QuHnq!7Iy3XWqM)Ob^^CdG@#f+ev^ z(2Lh$Gl`h(R0V+^qCxpzpc(_j66UpFcqYP+LGe?O#+{@JOM}#*q$hIfUxU9$V0QUK zpmhF12`Ig{;a9)DO>THRsQ_3R6Q;p!SFZaFnmC61b_K6zWFWJsFk{Orjk*@-Boc3p zRDkHZmLr>|seey6>D=+S1#Q||ejhjb-aD^(Lvr_p??E)IXeEkh=;s}uz$3c|t^izM z^2dD+$Zh<+78U$0AddHogOE6oqX@u^h%^34YpeiQ4lxX^Z&nw>eI+4cb4beF(GjZb zm682SFtzLp3h=XzEV9O{&#nu5b<4ztj9fV9xTIOM&_a5%rM>B(eiwox;oP z(6x7hE0!F^UYEh?y*Zmu(}NlcPV`VTXhlP+_t$!@oM3L((BV*L5d?l%umz?(yjIQ| zkdbzq4@dWPMfEC1`tbu~KV1B%a!vHf@L@E+SW&2^J8q{e!PMs6wZX~a(ha})xR^kW zgj2HUa^PxPX<#fZIv+w|xUeE{rzshBa5;RAq;)K}X&jF$It4K5VZd6M;nseXc>-|a zL)7ERyAxj*?&i%3V8W)f6Kg`uayd<^{et`fgC>q~(SNsk5E>U(`%Q0!FtcE_TvxmM;7-bF607tKIpNiJSBz-6=sU%R7>SgK8|KO z$P`SG>|atznm}b~Qtx%`TlhJz+nm&2=CTD4D||&ZZh|?t>Nnr2lH|?f1~wi!biTCA z*q4pYx?6~g^m~cOx+E1Zj6(lTDta0W=rO6ZshFB>BKa}H1RL4Pntm{dt$RqfQpun0 z^N)VRj3{N?z8N#A&pY>&my5$qIh;Xkml&b^l#=>F;FZ}OqX#AZ*jMow zo{a#m79y4U5wp`|qA6@DSOD+OthKI>xtTo}wNUyawD&9|V7)k5(KnsE?T2k4jfeT2 zQKo}zU6dijqe;N4#iPA~aAb974zAU~6C1guQNe{W}g2?EZ*tKjweNTEGOIoyZox|jLb;*1%0#mJ3 zt`Rthl=JP$uKf~BI&+QaLDhDrYddq{ur7MU3=PkSKnCztCBQr)q$}VmwagvXz47z;^o6jC6uJ3h74;2 zDAfdMK*5*EFgNy*DhhH3r$6Q+z131xW}tfH2u*Jb$rLY6?F{X6$X~^2Tg%Z%Z6=Z) zERv#@{odDPkN&VK&8IHzvbD+_m_bArgg%cPFY7Tft!$~o@cu$n4NVDvx z5&2|3%9y8DYz8@(F^<|&_G~nCn z$o5=tUE!d;#91F3o8Y>B+)H)&!rDIFLo=$$>mn&e%l)Uq$bur$r|_W%N@n&NW&(f# zJ6?6n$Nz54p2uyp3YVre|5Jj>wEaZ{Xk+i?j37}Me^Lr9wrr}y75>rgU8Aqbp zJ9K+92jD}&c5M47%V)(Dv%h$1=5PJ5rjzT!EyCoyO*L`0kEk$@ZQ_F82M1uTuaUW$ zzjH#d@dp3D{9B_(og^32VTH>qiu09WLx>lol-->f^Zo+_hH?hdU)`9Pw*MPvs3&-G zpt8;Fdk^ueeH`%ZfpT4~aUc|u2n*mc0?q@K`dte|`Xl&KU9n91GfCfwE&M{fOdi%l zoqmiO%OXI%^O}s#94F5i^n8qO`>9g#cBR{RcJca2?a+`Tc@-tC24SejM7gzn87>Ri zpAL7sjzxBnWY>gmkc0@kWKrU6>4+It184_;>ID*uL)ZzTgoEcjWzCN@^<(r!MbOV1|wZ^25Kg3WG^7M5G=G>IYu`VygJbYf1QnOLc{ODM&l(R?^2 zWhL`$45)u~J7QbTG+1OthBnr5vG@mWQXB29B9ux8kx8%WyX1K1QFE3aMBYAyR3aA19W6^zK!XiwoYs1 z>x%HR!l+mEjpMI`Ugg@fq5uuDm3N6&&*ty0XX>T!EuKP3Z3Df!^p=6gc_oEL5dS!c z#Bk0VQ;ZQ!CCCTYn@mpXiS|pwIoemR-XGdCaA+ni=SCKdI|oK$`1}dY_k}vQWK8~8 zBVWYeSau0)Q(cZ3{QS-Z&!w*si0sR#kO}wQYy%bUq zpq0BYP%BJZU1zHhNn;_}md)H$(UjL3Aj#NYpP~>2yD&rnVxA_Tf*C*G@L-7XtG3n4Z^=aT;euAs{MCT% z_Y~0F9j*3T*G<(c-TUt5lj|fYq*FOIYh5Dr_4y7sa_6-C%j2WYgs1*%Stt5ARPJ1_ zPXb&2@SlPpam5Ncph-Vc>nE9h zxJ5$O+C^%I5d-uzow51%@JUCXTBusL*Nt zB6=GphaG^AH0bwW(F(6yzbsQ9i5DJB?}aTlax?>&hMQ+Mye!|0Yv_zL`FYv&!j;bN zSq+a8@HyPLj^qG3;l^`8-3~Cz)OY@_0bGgZ#|4`tZc8dhZ-Nod9*LXY1<%sJ{0@Y-e==NhWJDcLV~1fjKvJ1rPp4T74w+?vCBOAXd?fiRStK8npk4gN^<8G^Y5<&aU+EUKEqe0N zgQ9{cQHeuWw&Pc@%@7{y2@Hb4uA~}DuxU4|@UzNB@rF7Sdz%(QK1qf9O=No{h>O!~ z)SS>0(o7`7lGv~aDjGN5@w87n`YOzq&1lR*K0P<^eVTjgc~0T;D^E~KiHRg&b44hB z5H59V;{7YK$#0He`l|^5EaM%;{qep!Ft3ulw9ma?6Or z635?;93UdJ!J&n8pKP}7zAwrH$$y>Z4zB;bGe1s6c3mDvg^tu9Vd(WBhKDIhCZV_i zEc2FVVKZI*`_DYg!IH1j6F}%S`S)wN>;ipfy=7<1(I))(c`81|4(h^pmALzEx@buQ z7FPWu2~ACX*t3-=U>x@0coK~rQOPNiPbgUr zLG|T2Ucd*5wp*;1ooqh6Q651D?w77U=}ve0I|fv+_${-FFfz1N`^~e~8z|B1zcvN* z`?D?iSWNJP6rf&^+EE3d-7Vl+BvEvooNcy{aapYw}8GeLXO48k!zSRMZp({DZI zAwO?Uk;PzgEMPYKGU$m47XmPYljRPRBxSnq2om{v3NZ5&CilDkxjB@OD*}bi5uv%P zgrXPy@Opg#V!hCd%`Ff*=TNN44GlD!Ydcw^ zl)iRB4!dzcH_n7q9TQzFNFs8q8o&KU2cSsZRqn-xIzx zy$Tky^s@ck1de=;;r*<>2lE2X8>GITGhz}Nzt^Tuy7{ubVaxSCJyu(#H@bw&+G_|3-(Zlk_Go7@W{x_`Mrbo@IxNS;*efoZ4)0Emc#Q; zEx82ywXZo}L%&=_L1oNO@+%*@W$E9y1T`W)e8t%s5yC#A-g@=p@Cci?knRV2mmASx z&HyI97mq}5L=KqY9LNf2%_q@plm}kxb2CASt<-TfOUH=BDo~N8t(Gj`{PGC5nx-jH z$%^elPyz{d`T;?WLnFo)d&Y)Yg><=3@Np1XJDu4Eb^%Wn%qFdg10R!3XYCJ!YeA1t zFGo(lQ-0CiC<47Ucig!-O?vppZP5e9k;LzSk6Ax{4AdESxow6f_FFqy;@$R?+V>aF z9U@pCo5x{f?8Km7Gk*UnmkuJ3*ymV>`G;di3ghdFdKd8 zh>rIl+S{*dUZiQ;mlL!4G2h+b>N@ISnAgUHW?EX+P&9-%98wy92mSThdVS_=k*;Z} zPPxd$b{PffrT@x>uGWh3AzKH|lf-)+l+}|{`5~+j8!quKc*Z*Y(BM`gUXm;o&8-+# zvo7Pf#Q4Hox^_=%BueL6Ifz6b50_8@#Vn4lBW#tPW>20+5y+HX%W~2(ghaP$kzPA4 z_6=*<5;5isYwwh~UWGvD1g~qGfg#BMd26xu@+qDi^zA!{p*r}?xqb12cnK3FMbo!s zPPBqZ$^fF~XPd@=bz#3lWm8a7XX2(uEI=@}7|A__Z?whwry#&aIt>8t96) z7TmFM{X{rYL9o6N!$=lf77g6X&0b(H$3y{ZFzYqZPl07eFxaelc`yMpOV$yNny%3Q zT<5#4?vly(iQiDm+r2SSfoi&JV(%C6b7t}Uv#L&kPdq$3L;8V`u!}U|A?fQl4N27CAbDWrXu(?5$-OEGn#%6n$QHC6UwCYf&tN+pj~YnifhQ zcK7{x#bQHDIKNk^sW0Iu*?F!P2BdhsioXV2b^8#Au9r_TCkJ@anH)%A6bai)1!TI} zW^=_vE>&9pvB2#bS-z^+McTBU3XRHv1#T_ur z^nqk10bfjq*pNJ=`msxnZB*1TowmqR()dxKue#S%!gPWU2G5IpL6*D2!F?{-%DMX* z<=gy7+2DAzIXG8_o;<6-y`Kr*H+)O(!ZmiD~HZtj8)kt(e}RetSWsU)m*R{3aiZ`a(>38GKRTR2`${?Y>+_Y1xYEfUf! zx?jICcHI7Bt`{U$6P1K*M)N;i2MwNLdwT3#au9r!$ghR;#7I1i^zE;T)bAsH(mZi# zi$-^`TXptmS=tTcV7oee{W#aT^O#qV);|MM@0R0%?A+p%YDVUTqoT2oJw;v8+>f0b zm>r7mt?*NBlh~wU9*oU4`z_R`6I^Y>GzUtYjef|=9zuk5{)+oEzQ7aq*LP%uX1$L) z85xwn0`}7WamAp72052c5J)`Wn>;mTatjHKU0v}&Z*6z)1K+W|-c4T#mP~w|^l27^ zl^F`(-s1yuTIs2jh!i+{{}6yA;>L_@`>`6djCK8f~?r zd5V=zP1(NcyF0}>wD4~_G52*(B6QL8(S#g+GshE+sQ4< z&q2i}wUm;u_EArt_3*%n=2nc1?@Q@yx7~K(`d}!g`?UI$mmyYs-&aO$fgpDhC1uLN zhZb&)Q9Pe{grUNXJc%2TocTvGh^QD>ze0pLJaaymqH`f$r3c)3c#E4Wdxr=(;v_Im zIHnhtZ>_7)^{aW(|2{jv`DLzS(qpEAaHjg^dDN?wqx?`UkM+87!QV4-R)%G~cn;^HZrG>*n8oP>VRL!d|&V z{S1%SFKrhyQvty)o9+TKm`w(- zKattXS>xo8Du{FM+~;eZ#ST|jGhA~Cb)I~^sr~z`k#tJ_nCn1C}~-jF;P&#axDKJ2nx=Mt-)38PcI7xP+w&rmAys0&lGbBQL5S|u$; z%(Zf^%3d+2X@W=?IHMosBOhy4xjG1?8=`3xXL?oq@0dpTbYc zgTd4}4qJ|4$NBw8e;d)!u?;2MBM=vHWj_3|PIWQzWjB49%>-AKjz%}8uu}aNobZ_C zeKtLlw`E0vgWE0ifsrJ7LP5$~i5=L+7V575of%aWWm2!Jec0%XQDm8Z6}(z+9uy~u z@3=oUA5%g|=tJJU3X5G zOXvPcOKQ-dgXCRm<~68bF=#CG?fu2}#Kgu}--rqWuBfOex45DgQdN=Gyg_PZb#|Sj z!aA^Mj`>#Z#XYeW4vMY92<1vLLO-8Dk7>3l2ueY+T1Mx-!>AJl%mIkGuEaIcm09J4 zwv_S=({_0gr^Z>^znEP_Y8-^S+4oTLa3TG)kqyvNFEiQ$aP!-VyV#3nYmsCbD6+i$ z;vX@nRk0Zkh$W6^aVa<{M@}^^(n1TI-*;^FOO5I~lkT1Km4(14lt^MIJJH$cbKEd@ zYf2QSmBwICwC@LS|3={C1ZPfm;p~<=^BzW}-qgqAd2>R#)n`=9Qv_)eq*9oQ(q&{| z>41Q=$olR-q3TL5Sv)Ep{7BMB(99*_(Z;OHDc&o{p^Y zA=6O_ixV~=5!v$DN$k^>X&iO^d!ICwr%F;~a2B`!mxp?OG{f4(ELvI86u9KM4suU; z1JaV!xp;n!qnUo(4Vx;ur+#hW98tBw%OS2h`N$GQ6Vv9IttFFpcaxhMR)wO9 zkiq~7{t?>Kz`~B%=zsxli0f^0{s6MXlil0#-wnjpmtJV)^8I?FR(lc+qBS9*MjG(7 z2N77)kt$gfP871p%Aot5GP8-9IIk(we0iGFR^EH`l+}K!mY5P6VLDCEOZMseQ^pm% z14D!H1CKLS_ohV((FC<0e}h+7M$!?Mew?SmDWEoIVM*HX0pMh*ZsM5tao=AImWX?t zxUHKx9hDxR!6ursFT0h>OkGmnyw+g%gB?lSzYKpp%*huJ_VA{O8C%%SiU9x^{1tE zB^1}1)t|PY^R+dNrNdcNfNF}TW}>e?;KfLnp4hOK5DyRUD`O{b3Rn+RHl2DCc(TlW z0D%b9LaZ{4!5|QvgM&kWkQV$BbZ{Zn_f6h zCbS`$yYz1r4{o7@_z|(2&pcpj+R91wu_lm*K!99u^EZ{sKeZC&h>Y4LMs`D;qW9%X z&H9Z=;;D4Z#r8!R|2*;cdrzeCXF|Z&Y}FXWI~`a48D&Gk^^*|0Awnx2vGePE<>26$ zkFnfR1HP{sXiyDPO$eSaq`0xEcOzMIO#IOhU(luH@eR}v!(~T5G8Z1X{ODP$#B*Gc zKAkw(m2`d_A#Z%?Sr-)2@OUBSP@P(N9FuE8c?>RQlq%0QomWH1bW`Anj#DHR1x@jx zNu8_jvYiTAcP>~v`=p5dWmw^xvuau|$d%c^X%+XWw;R`oc&B(QuhF6|K1=KPz^k&= zdidGJFKk2svy%;!vu(_-cW&m~WGmM2oJH-`A@te0b{!1vhNLj=JsX_a^8W&Eee5mN zz!q^pE~2=Z@KNPt*wumZ`wl+_2-bpK`J_CQ8H_t%xeH)=WPVY#jN|V=2HWflR#oYZ z8I5|&^VIp^jX>Q-FpsHF6i7BM@1AtKqbPlLEXLfG^Ad@yeoTe?4_!Slx*!E}`i4!X z=3NK|_;^RR_1uSAEX5O3NVOO7#&AKywV^T7P4?3M(lJf7spfp!Q9K4b&ch7_AfPgn z!JvDAhS#njN6(3S{+kKzsFI|CqsC?3aS5ZCv$rnL_KEzaYxH1N$%823Ees5@NGh|d zB(n!oMIzhF)m@XdTp0&`cM2O@MJb*G8cOf)|7Y7{7b=>Tao+ zc4t#qzuVUZYX5@lIBi?`I2k+dyv%k}cERJ{nyhizcF;;&7Q2gvnBf5*5(~k|p z5t$jLc*e56r*B^)2&s{EW*j~5OE-HDUoI_;EYCNmxMI6kQZobNi@uwfpyI0a1cuR0 zT#Y-tialL=pvTKeRWFqM%{7B!iz=$B8E7iT+k~qNNbfs4I~g$#(7?h^pFZsiE-o$# zwtB_1F7Tb7vT>DKRKkw~GV;yooE#m~cB|&T%TDZA5lVZ%aB*ES*_+U})TU0}s@WOn z<8-Ep)!c_{M6P}+hm>!O_vHS?T?9mW#%mul-`VKpOyMC&y4}SH(3p{3RgGx)5TPTk zPZcM60hnedz4S_CXyNdTjnf+P!VBh)Lf4A~F9j4>;&36z0W9HiSv&)N$G zENV?tU323V(BP9fc@e9U&CY9i4D9uMKTbeH=S+c>TZXPTo6ppk2jXNoP^;;kj#Eoy z(^C9M725)%Fxa|!;A`9Pcx;IAFd$+$Vxwzq;51tqi;%qV8r zN1Wt2rkv(lW$m-oV73PW!yorC7m*`?)^1>&VQO*y5r!3#i z)Lp*!Y2lb}mQjrrn3+Blu?J!x-P^TeK75i7>xvi-KQvDjm@qMkARWbyaVAIJlsjn| zW&7<3NOo`ac19BnPB3HQQ2fW@`SY>n?9$o^ex{LER{TKHZv&tILVQN$^C-DR-^PAs z+QG&Pf{6ZM%Zr|IXI>3pzmvmzu4ipiV<@uirYJJOO{-(F--H9gT=#q`5PCTfI|x!| zb=o($R#TQ#3hVIyO}C1grfE|9J$CbwIHohvk8^4*8w(lLZk{Lhbm_eDhGsb2AXT$U z@yLl$3YSZ|F#Nz4bIDe;wU#P`nNJnmf4W+Wb$prkKGfYXX1qM~oR0{xW?B8jxI3q( z{6!aoM;W^cd4ij~87UPqB*`tjb{iX;AKBUFf@H2o^EBw_=uq>pMjQzM zIhF0m6vlK+iU$FP90nJ5XjM^e_F{Md!Y`_+ClfU3^$zusdIw>=V(GaWA{6x#OplyE ziLpNBbP;4AKvImgmszfjlME)QO#e}zkMbw&0K>(2tqhydV{W23H2u_UYbNsUs$HJk z5^{q7{Oz~%A^Xd_dGn(CIXt8H)*j7Lz`ZI|^6RubPpYn<1rovtjJ1p_JW88mqZZW}6^DWnA5IsRPN{Id8=n~-C$zwv05ArOJ#6f|tcPBBiV74b75 zA|wCS=BSKT)?CK6ST>2$b0nKz1tG>Nj(FFT2g_eB`G%br@V&;iGu`3H-C$A9R8t5R zOrD2*fPueJNzOhi|C-n7p?%O^cfEkQONBy&C1-I{SDbQnei7V*@dU3!6L>J=nwG+> zut$6Pu0k580k`YDR=?4;H;XIVsN`*jgDll7O(i`Ihd2YLhQ-h_7Kwc*NLB>AAAHj zjxW46JNhljUl*q@A-`EqoMhe4^NTBPgs9k>!;qVTIP|6zum3Br0;}y#aJu(gL_~tS zEv@86@r-R6J+hU_tn0Vl;Wc_imTHwnGX)|HM_|Hz%Wah%J1`fkq5r<> zy0WH5gv4w3rHr9`x!FeG)2HTAv*Y`o{H$xMR_%cySN!2r2i<{O zvT?FWeh5f+RiOdyj@~Uyq_z`=+tJ9geaOh*6F#DLr;nj=ci=yjgjDeygkX4a(W;0v z@9A$*e}hdpjOO>XX6$&_qPRQHDf@uf&St^&ulX`C$mL5bjX_u0vdG-Q@(#&8M@{3} z)H2b27L|2Y0!j!@aoT$1j$+aC{5#5|9H^L|%@l6uP%lkhM0Z}? z18_&7d5GUQzlR<7fVmNIKs4lc?!j;P9lvE^HR$N*7{|Qfo=eEX!*M8}m@@mhc`-wU zJ%&cZ#_m^kX%S<(%GGaQ1CrJ%vnvh|)ld|!T!9#|1{PqP6@4%%1a>Fgp8u!C1_h7` zPBPWCC(rj!O$j#8`Hyuoh7h`RowT0i+gvMO2-~1aPB`fr>^9jXG$48g{e4Aj+2VTo z@H5d)My5Y~Y=rmc zA0D@!6~~Ns1SO&oZm7%)dV`>&qa)~jRU+`YDUP>RG##u3E+z!|^f^$MF6llcHGe*O zxI@d(%++G+R%WI1&iF)9wP$@i&XjSH=(9i`oQM+NYT(|lw@n}r5dreUHilF^s`xP?Bc1NbE zmQJTn$v~lh*JJHcUr68M{KvxS~FPr*3F9bOVcwh zinqNxGJlBkRHb)uVYr3QuwaqDb5KJUnypth{ts4By@@ev))n4zPlcMg{(0v%<$VC8 z8b#t#PDkz&mXO6g#z)HKKZWamZRx4`r~cKuyfk0bs^T^UJ|i^ehT%<+u6dh7lPvN_ zA94ysry3?d$X;e)q}?yLs_2JqyXrCI$5K!Uq0`*+AsAY=`EmNB{u4IH9BZnXcWIH< z05gbcQPqNe?(27ga*gUexMg*5{AIw5O*-Ou@LE^AtZ&Y?{6BA=UmI!AgwN?P2Aj;H z6ZHP^93z~cCiF2sF*4g1y4=dt^8G+`fa-?9Zjr#i>7uD&H85212yLpR` zh|*v>iQ%11v?uy=`lTf?&MOt;9;t(JXMpWg5*IOXo7QQvL+gk%z8CP9uxK^Vjl{WZ zuxu3vcTjGVv+wwJHaWf)G{j@W^N|PZ^(?z?XrgGau}Pe}@!xeG=9BY(m!iAY1}E>1owFfNvMZ5G$r9ImSc;cr`T?3mNT#VxICr4jR3 z0&WJMwb#G<5T4m>Bg8HqBnAcsXi>rTy+315k8Ew_)6vz{H!-mW0)wI6)IL2FJtm3q z@t6^Vz(Y2iLV5?3*gB$ybj_R1L(RhBcS;rF^FjgHm&*I7CmcCW@MJ^f=Gg z;vqa&R7$31G46|2yqXs;s+Xll0o&98YAaIIl@A7p`#7uj=T=OjrNwFeL?&N{=|!)R zIo5985_xUuxH)~#u(@aKF5cAqf(yc`V8J+qXqf^}2)H|su@c?tUIqPMWDc~1WW^}L*eXA#WRQvEGJDelGD+chB~#CP&Wc@DEv04ee58^Vr-$>fs$m_@J1BBkQKmCYedG z$Yrk2$iCTP0!a9@Y{X#@a3uY$Erw8Gj)2PZtL)U6j_bD$)w7xW!`swQzgB0yvP}Y2 z!elkiz37YfP7dl_f{l7L4uP12)(LB=3*GU&qQ8Bf>vXO%$ zp*gu^F3QU{Ei^+e@aJ1WONy`a*44KeLJJ^0QVLJ%s=-a~JPWANR;p$R@H3fg`%Z_)E7&m>ab&kiGVlE{&uvrMsPwRG9(r=f&k>!*EYI!!J!1z1{SY|giw5Cd8 z{RdHOhgdvNyc*eNk1uv47P+~-J(>jubp1jP{EUnYLC+)na@AaOH@9TyGKY3euK|5w z_!%CjeyZUEE5H#H&TXI0$*V4ZWtBYLlAc4#3dS1s8fEl^b zNGqp!SmKcmZ^Ly$@Laaa=tJc>=323HH-lhglCAwY`+?OMEa|fQyk!_b z;b^k|UFrSm`(cYamS&C~g&eoKmXK<8`9ND|e3HjERi3|guJ<4kMx98E*$)gT(Wyvo zIcX+uPjB-MRFkBqj`LrOu0QPsIC&*p@`EwiPW|TH0eQ8+->bw-AJCE;R~xH5DRSHE z=tGACYysZA6q#UNB|TMN7sP|&9>vbh zT6}|3f(50HU*r44o(@J+(biN=9WCn3+FA;E77nck^ z36l%KDwP73;_C4)^KmTAeOJQ^uiXTRrc7M{Lgo zHn?(3@juVS|3YQ2l3H3@TQzJs+1MIdT875Q(O6koEuuHnad41(eN<2oCUKFeH58gY z$jhiJKqHg>Acavwv!vyP26?U043P9y`A0|PqDq&_?YMl}M?-WC_8LCQM*b0D(?69rJL%6otK}*4 zEDrR~#^`!Zamv-KGzOo?1K38wbkVNaXxR&h+{j$aXH6vT)RSNyW+;8w5?kgR-gj2i zcB_}x|EWzo@e*4J@vFhg)Z(1uV^q4{Ga zY8lrQ1Q1|z7G_!`RIL0U(%a`ML3GQiQ_GO_Z|#>6V}j%y|k`ZO|BLtP%5+O>y=1Y=Ns z#%mo+6vJ`#l5iDGBY?wKo@j>Y&fQGS?&P6dOz>PF6P?Oq4{E;>y zN;%k#f)neT{Yo%W`xP_TXbfT?F%6D#_C&7Z{c~)o%Im+Nj$tTtesjNLIlE-{$~NUe zqubL=QC~BmBP1(~#<1_YzwRl^?nZTdzc8sD{9|IdMN*W3l|R@A{IfgrU)7Raqp65}{ zlO0XGz;#U>XQ^xSvd8`@3Lm&zVps2lB*7smL& zXa6hngyx;k+{%OKJP_{TqLNiA(7wpDRf3Aw9cS(Nqf?@yD>;C;bCfrS*qS7Y@DMnkP}u5Y z|BNNyo4Ghq@+RS$xTI}Yh4MM(cwfaMe9pg6a?*g=O2qpB`>ExZpy15>T?F=U?9TpDu!h-T4r3vAFd>SO9J+N6XYDOu zmZ!u~#k(RmM_sZJTTWJ^E@50wU5lvb;XUrY1bLYG#yju&0e#Zix@eI0^k%h={IB;~ zIOHPzxEK9_r~i3`7(506)pM-)({EIq$$eT)Ixr2f(UX0=DR7tkU zsZZrK(KV4wV3Jh^c^FH5F!)J$5NL2!J1Kop`^|D$VvY{v_I?!v%u6nh;GM98`u)2b zO*{%`OTSmoCBKzJe=PkZad2?c zmJY)Lp;%H$2a(|FfLJQ_wlRG0vZM zC5qY_2OQr2a9$_ozX{r9U!~~wMo9P`Q(mOm7>#L5Tv_Zd_dYH-crE3>2qbKL$9}$} z+>$rB!V|sze>7cXRFe<52b6B5JEgl@xU04=9%`;}HCxXoDsgBrYV`TkkY=GcyH!U%QMhS{W|h*WBy(aqbK` z2c_P2Z3nr`U9WF#4T~4-XFK{oF4xp~z3M;p)$Z_etl1A-F+_MVNNOE@U}qL3Y%yoh*2dEsqCGY9`t*h~cTy_K)VN0>nh zRIP+4S@;M)2>W=h z?#Ttpdo{NwPh6~{=e8}xRZ^$JG4wmbZV zZRw&|9m7;Va#S(zS|cv1lF}ziwB^e$%Rr%?UX8qlEh3TCHM~Q95fR9y+te!(jS6TZ;=P6cNAqh{+=UgoY;Yr z=^9D9@tiCF7XP(NPTl?>@>WuTh(7K?njkY{e+U=KV1QAdmmUufA$^1N(`xZFv(JPD zhG2>!ZAxr)d$L*u)tToW9uamRv-mcqp_!t)p$USgOjbye5q^5oNx)FG_wD$HSS71w zPguf^QKIP*Q>^WkhT{0uCi-YyfsB+ns?r-SvRI{d`P&v-gJ9p51IpV~i@2Jv_~CEO zG{jiWOsq%0G2Ls1?6wRQt^~a3ciaW$a&eL+d^Rz#3<${I{1V(hv+te_txHv_=yws^ z*?B_sB_&<#Lk0LGX)p7X-(Pw!X3h0~qrgVMWlIj+LPCm8Y+-?gz^kvuSto^bGr9!) zrcYC$dAgMoH^*)&YO2XFAqE&s-`JR1@g0ymqgVlD8^#HzhBn{#p|Y?*s#rPZPOssz zvhplFp*QsfultR|MlM+#fyctG_I6$!fsW5H?ik2#YOZpE858_RzrIg%iPu!r{9996 z3Pv5ej30o4(B+@N z213;r+=C!~_Js(!#*$AWa9v~?(=ryE)xz3!SL2cJ9TgSceLjzZUbn2tKnVo9*xU87 zv5Udu@u34(gHqxrGKz#Fe)^#sFL)5_?ahJW!QbqZp#ff|2*TTh; zB7z=)Q6o2X{ux!jT7OmskC8PSfa^-V_vI6H4in6y_Y_(@3G7L3-1tn|^==}1dk)-s zbaJ!uwlV{sy*}qi3|+5xC$v1BzIfodbF1w6y%U`YAUSUi&#fM}N3)r#KMK z-ej6evS8h(2VEkq-CaryvzT7btpxaolrUrtt!;KWzgmc&6-aCw2VF~62hL+RG|q1~ z8+Jq73hv!?2@)-FpB_6m(>eYXbe#U2n3@13HJ^ke-W~tV~y~i6iolMiFw#_CxJ({!6Z15nQ{|z|y zO`i@C=T+!wh*SQjnnD`kG-iVc z?`a4n)C2*hiH79_vQ}`Z?53`joHB{Fy(Q`CB+Sh z-cAKD9{P}L;MF;LX2|DzQTW{){U++)+M)rjU)v+ew0VM{&_UUnk7G>Dig-Zt!C=9v z$Wl{4Mh0r@m_Lq*%hp zH@E#}brtTfenca_L1A>9jj36IIdfBFU1nns4-Z7~F&`I_eO}f{*YmgoAFp<>U1gLHq$*V8QWVt3Snhu-#5gQ4rWyixcIfxggQ=x z(ksL*LZP27j+vfxg^LqbCMek~jd|+-f^qB~ppfj9PA6TL5SF?~!myabgJ7+$LTN_0U`)cm@ z;}znH&K6-W-_W9$ zTW6HWpTNXU{YAXi0+Hi&Q@?Svr=bG{&@eI)uVV_4-MgaYO=;wj2I?UpxiInh@VApw z@jQ{7vFH&z=4lCk9!AJW9Lg^-$%h0fW4Q5+Mo-t1N;lQTD6x=r{xGB1@sdPrS8ch~ zOB~1wU`>en|T#q~3LKZ}fV6=)G?G zLZ4}RO*6|1t6cEUHhuZ~IzTS}uPrDrIX=Xz`R$O4WngkD#J5LGax}UX?=+6y&ZfQLo*t&Ie?~ICKD{5NnLvmRcCk1%-QiO3g5pN8hD4I zx4W)`E-7TVVlZEE%YtBT^YTZ}Ni&8IwUW_}yb76_bv#;}!V$`zq2FA}LrC*NzaX{L zqct?NQeENv{L8o3=+a*XT*P8LcYIEGL}Wgba9llygf#bx`L+U#CXh?-_Hqwvu$cmy z4OrQH5;v1+i;h zCKp>WM}I1+<%yb5`;8Bt-6X^VUGKO=89$4xu0`DP#NOG>c@de9N}mxB^W#A#uNJSo$mFLh-TLB z{t|vkquA4(sDH%in|eglK5E}L;v-Nu>=KxNeLcs;(!i5hy5ud)M5FrD_Y1)S|* z?FK!uO5(Vz2i(?mcbf$Ez~XoW{qIEAUI~LH8>-uLDvZu~Pr47WOr9??xAL~{Ii569 z=>FvgJ$T*(f+%aA?@f1}&?zUb*V{9&1I}vT6}G~kZOAt^ilqGkkmf8VF(|FF;8FCX z=hVdH@mJ>D*OvQa!e<~9H@@aPK_Y;ZBWSlWd$l><5eicSVsiOKB2?wW-)>ZyV%ya5 zzuCt2#;frrHqMByv`}%n_rZ^tNGaP!GZa=*B#GRdE|i>cYDvE^lKE3lrZNaCd#8>k zZzZF;S5~UC&7^z@0-&f$W*sSc4I+YjX6me8X}@GD%dm@Pn12!aSP8e}-c`Pea9G-S zoG9c7=fsDTYmrlUotR9niG|e>e}h1<`&v3TbLdc4Pwb+0Q#13Xof5qL2Q#t#q#W~Y@-??O8}x9H(4C7|Om`;4 z(&63yv~LN2)s<149Bt@y+`d4upDk=(dRXMwO({#kB=luN;&Sb_J2U9~LJtHHlOMf^ zdt-C5nc(>=L%oWZV2}Jq9uCyt5UgNP9WLm#hAZmg1lu-C8J#Q;H<6c8fk5haoLB^ zcnk1yyQ|s4i?Cn=vPs=sWB(yW}>f1TP;^)XdeY@(L z4XVb_haOk-U*vEeJ>KG85a zzgqfD?PwLscR$|8?)ZdKzEwJHH^q6)&!ei2F+tInDs%0+iL*}(`O*eG_EfCBo+zzU z3SsDuiHWIu+`XrHn}6B#%J)8XV*c0@;OC8kqO^M=n5N#9dhivD?-UIpwfKW7FweojOya~4bNA1R8k&M&Y)T|=t zHd$9!r?KVdA)}a;P87x6J=?EymEV$+6@h4!C%J)dDIaNpCeVsrFJ(*oMnCefv5rAq zYPugc6?$a0Kb*+K@ykWQMBdIO7}tyrr!hF@C+eiwt&!*HnlTCaa3Uov1es!EAm+V= zoP5V$%f};Dk!Ht|Ioo zi!rz0@JW(*xi&8VZDrfjNIQ$>xf|54(;v&kHRYyhOn$!F+10`e*CPVQKiKzs%& z(UMPF1N~woWrAd&WZpvGZUZb2K>}Xu9t;v^0}R}Je1n6TMnllGbBdGpV}ihk z3X@5B@v8;V?IpeQg`h`}!9@7$1+-@n`Z^4~`-bhG)qq7|UJu;_$`QUVIXoz=HXlEY z8onk0Bv0VAKu{D+>@~t&gpIxPjOOK<)O2d0#}{*|^I{2*ivcO@SY$xqyMGK!^z@zx z7~_zF9UGu`JHsyt0O@IR8)>@ptFmzFo9TsS;MM)qW=5{_mJf&bOh!U7vOVB|U5Z87 zj%GwfNyT|T6b=fbiXCO$nV)fJsr^{6&m0;N2GNOnL46(NE>y=O>~!H}RSHL(=G|0OU>&sT>4a<9yr>N>_c#mHwm|yd^uXOtrO2s&J0F*n4D`{&B zr7|iIMbd+#kLDwbt5S)SXlP}?7y@vhm8os^t}Kg+3<2ya@1)4x z{btS}gkYdQ$Cs~Pc77+4)M;ah!yiQa{2y$zn(f#~f|)@;c=sSsQ_|oj(Q9Y?<}7xh zE7=}6ym}tWUwgL;{1MfAwvdH1zYU3MSDvnOVVT?>Kx}fT4hiZ$_!`^6b@nlBPWbXl z`Ek&*GLuRZB@@AMQ)uXVDCn@jgxgg72*0d^(T(}-KXc*JDT{Kr=BtUO-OsquKJSf0 zaB7I{zq|PxsS;3N`F@Pr--Gp+H^so0k|?Oya?D8aZ5hVlavs?TLwl+6ZECI^px;71BX$)~ z^*w;U@T?S6<)Iab>`YE#ryR_Q0Y*9-NBufRNVs^c5YyO^b+)>R*Zk~$qtI#Rle^kGae%hZiyVpmxW4)=$<4K`I1?xginY8ttny`FMQaiY)Y*@9|j3v5R zFYmcQN0qqoa{U)OmZwY=U5{r4~wky+|$t*V4`JQcPU7-XK#Yjz>RKi zLV}sPz?akT&A6Y@Vp^zf`N3^Y0}djVzlgb)FtThT+)3cUbl-6cP1M5H*1gt1q`>cF z{mz&4BnX2^om(k#q4k2l&n>WBMlC2H&5qE|MxukskEa%C59VlnQzt#I z-L4!%8ZsTLAJpCf2}q`|V|)&UP{2r!N7_X(FI4S)ptaQD#UZfnE-i^2IBEKvvryOB zIDoledOECgHW@R@birB%J$9YQ?C$mI4j@--05IoUtF;@>Ooxjg?qEcW!UOPcQz zWuHV?FJ8fOG$#fMf^^D285s`d2NZ-9fQPfiLCfh?sz#RGH^t^q*=h}D*5&5H2(V;l z!HT%_;|7c;%bs2i6xVkoN7%XfbW!;1X)_&6tUUjd>808u0`R?MQzAg?LDH@0Be zE`CuzWcQeA`xqWkfzo5!0+o%~jNAOuD+kILAzJ57lA(564JT)NV^^r*944w-7ZEpS z;ty8e`w<^$^xIoUow#P6xUpY7kF~fnGucurJth5#a|Zrx0o_cJbwiugjzR(Wn)(Nt zADyD6F5FylE%=M+HDC;s?w@q0Gm!TlNM^<{H{RD=)1LxQ!q0N@yODfiv72grkcYd2 z+`b421YRB*-5*s7wsn>>*v^S@M{xw6fAbj@S^NFC{t934RLU^>Ra?}~)i-ijY#;d^ zNFtRCivvy%yy4Er)l+M`7kDi4K)ho)DQC{*KRw}de4%nJOO6yy9j&dc``R~yTIg_O z!tT*Ao(;Sy>^)DeCXelfyT_;Z=yLtIH9@OW)5)m=>M)XxxPV8WEv_89 zEz2%bA)R|YT_CFNyw+Y+B4CWV^z(N?sj1`Nh5jOvz;ZEkVO~`6b}E9Ok*T9{u9N9A zZ}riM1k48x=`E&P709d8`R1le8Dpu)ZP=H?jzFXR^+Eeuq-?@8bQaUMhxZF^v_1BK zqsd@iLw>nYqY_hiU-{%5hag=G_utz$Q4zZwE8ladXXBt%roEI2Z2jjHZ$a`G!jt)IC=52tA&)QJkbEjLfAm z#_V0|5oad_=dqHT&sD+Yh+O>PCc!{OagmhEDO=Qt1x-i4kCx(0$aczA8?O69%BkS? zpG29)&zy0fs3kDvLUd?SZ!m`&Q&ivdNAG=VVFN3@UPAbsof(eHt+7`Q+-yUv#UGozHWHx}9tnv#+weUx0AD9s$pJnRml0F8wV)!!EG(H*|MT z4h;!6@XV=9jn@NFD%D*#2D9!mH7&nKQyF}gc?t%h>o>aHPlG`_6(*NFRbamm2@=e< zEjxAC4b*8Dz3`E>|95aigpcR(vUJJP7bhp=E>`$d`$l3 zH*O`#AFmFT+&D2{k+HB;8eV{TRqdJ#+&heykDEEHHDp8@8e=;c^7;&oB*PH5gDW&Ju&p@+l(5vT3^m+?bD<=*ODr@aaLP(-(0 z4u2`HrmUJ|=KW9k!7Kj}{{p9ujVY zVBKEqG8y1czHS9OG&z;VdELbgI= zbl?hau7M|RHqSX=+aN_ZjJ(6M!RFMdk7{EQdb+VQv#sLUA&6cQVwJWlqw(=m+SvF7 zg2)j z5<)GIETKj1^d+AjgSOtGvsD%^Vy8`Sa=@jwP46NPJVx(pXDyt1QpaSs_w8<1 zd!sF0gvfFR4zHiHw^fdmOAMRQwC$vH7m4P`DPhGEL|BX*( zn|N>xYGkk&b^jCr%~T}Q*JZu?WxeYE(z|xYDhnYIiQQSA71FoV2lETitr@@Ql9FotD{dL%=5~MCVnJKI9op6u}s=tw8234+xRv`V# zYXGvvcycN z_&Ju3J$-@;Kl(La^e#Zdh{-P8Si&qSPa8YuR9`YK_2htAOXF`R*?#f_ooKDXz0Vb^Vd zjAXC;>rKByRSjsgWb&V`)y7^qF4wGY7v|ba#`^Zc+Ea%^ZvL)S4PY>Rd<|qNd>pUH z5^@oTyIl=nSTMPiec4qu8T!v-tmo9;Fis3X_gR2`hW#hg*!V3?CU~fFV%)ylSWR4R zj^W7fsowFodL2lNqVGBRTf_2d0FMXJW_fAHcJ#I_)95e!&Mgzp6(hwy->sWB$U|lE zuHW`4pt1)M_7l~l&)fXR-eCl)!ky+2%k9Ac+d0kOO}%$T$*@Dvp{+)S6<{e)Ex^gP z7jq(SN)k#vv@$^y_$k1(?>*Zc4#a412+m4^(2jIx2=vv7E38*$YpmIGtldowjTxTp zwzdCB$!j+X{6c#azL+8T5X}X)TbbpyUW{VwyYXG5qEaZYEqPD$fhxMTM2D5Dd}*+{ zTFl`&&81;{p;tiiQ~v2EM!FC#z)R`^=%fI__ulOzD=TYSpI=~L0U(auVl1C?;)3Q| z*3X$ij~w*Z@L{xEr1lWHeEtc!u+pXCxq~lJ#J8(K9H2j6oS95BG|ZO~Su~kAl<`ms^u@f_ z&q7icWHxTIGr{h>RHT@rQ5TQV?u)lbgSHC%BxwcU)4z?uFM&DzbpcKysi~<%7+vw0 zz}zb+pJP>3RpoNCd5O{=?9>9OUiI^S;3*s|5c6w$V$z$=&$?2YxILp9TLcCWRUqaR zL5?qo(n)tjOVXgr7_dxt>FOTO1lCc1q4p` zevN}lyHWT+ATB9{Blr_>7eesjv(y!l%A-bX^1S4Kp}LWB&#B-?SX9dVQdLIg29B4);qS+r2D& z5Y(FE@!ylC+#1StwgP$NZ_6jTJPY(p*1S#zS(QUK2ez2a@Xq5ZO?#7}&i`bmx?-JI z`~_^tMRPN|j;sbW6moWRhc5n8OxXjdd0ySAia5~PaJE}x3K6?q8$NFF&n(kI7k$}6 z7i?eImX&x>nq2lm>bV4vN}yu?NBvrcZxe6$$0ZjmdMh=(ECn{DMb-VJf12ZBDQ&xJgrX#sw5$yG z_aFE@OqT-Naoo0VfuMniqaM`~>*4w3bDBlvUT_|$h&b-){b1WA?yY*A63e37)pNAKX z;ZS8ov>hE6HHQ%**PuvtPxrBkTAe2^j7F}gs>egC7tz&?4NCLs9IM;o9NDr#f4R&E#9-N&Zd3B+R%_)e_WLZneZdo&B-d{xMi*HeSy1B1%cXs-y$sAdA;{4C?F(Q?0IFazQ1T% z@mMexeTh-O82SoQSnC_Te~s$8#cJ7MJbjYTA)gWwm|V$D<|UyIm%*zY86rwb;w?%3 zOn}3$5NcJ2J8F^=56^L9zTHbApYoh#nL9{Dox8{Pj&@1rm6g+^;Y+S%+T8d`cCOli z_Vr^)dwb<$frz96Rg6y|Y|Ty0%LDkkH9WnI7v0*#hV4&ze<-|LlqbfBKa?aJe%dq) z%5{Eu>^ZOOc`+2disSK~;OI_ZdA$f*@r6tv4NxMYkj6XA6N*VPXYV1~7@b^*@8h;| z`bjF_c%E$&NyMF(L{lAX$df-Y@~?-%h0ny2nizi_Ewib@E}+8*;OYG7gz1_Nf307T zx`Vh)C%K(m%A{Yd{GDx7XFunl3V!~%R`YFS5$GpN!~(CX%HY3F6M3MkDL(+Wr8OTO zs`-)Z0r2pIY{Ps@w`XL!)64A;g{!{l0KVFH{JbZ;KshuCXyj)I%)HgD^Qs<^#V8HrU z=ywlUYWB%cBdQf9<5AMw;{4LQK#vySwEW5jM-g-qA$nQCVFfixd=VVH^e-32UGECu zXgLuZ*$&!LJX-6@ol{?t<4E{Sp75D8F8gFCZZ0n9VO~}K&(Q1~yAKwJ-x5U7>5_Go z(XvfqC!+(KxBe@6EEe(hCXE|mh~e*MOsr+9K}5_G$d-6s^}pqpW?0r+nKVrd2!bYi zRvpJtX{h^61ZbuaU3)p`rmTK>so5{}@uJa`e_>#M@;$G;H%c8SSxUsK`s_561@h78 zyJZcSoMk%{ZGm;)r{L$z#>#uh4(rD{E{Y6zedTETbJs`DVd%Vh+rr*tk%&#T-4dTf z$L-kj@k7!wOxr$wY+)rrsAUlK4zNI+W)0aX$P4G0L?+(KYyy`j5Tg{7Zpg`k`cRDm{zkRk%)LfsFTIZGIo%=Gvu=+^iRC#ClgpD) z`WS2uK0gY;k=--Wb$l;wMhx4zV8hPB z?zg=CyZzw2s`@n8jusjBO)Yt0M-BcXen zl8SP*X1(gM$NK8Yhj38(WOe5(MNj(n(fx8=>B+BV2?)z0es{BQ5;HB`T=wLp}L@0NpB(0cpk9iMtgodr-u_`e(8lJCsZR(%6KzR)|Z z_gK#V(`o}jTKpNC6vaOL2p(Ya>!FhB#grNsch}r!EjDM#i8>a z*>%|YV12V5!&jL@2vu6-Yh~EiKgZ5Ys>>}(wK^UlJ!n(YdR;7m;5D{j%bH;@9;GZU^G_C@7URi2P1=7>4H$Kf z$u6rcM|thT9lDfW;#E2W2-;pV_O|+V)}I^7oghR<9?NOrN@_(Z^xE$AGm{X_ug9KD z85v*9(1jt&G1}kvi&CCPY?0f4s21EBNMN1SPIZ%a;DiE;!F~lGj-E3UrDy<<(U<2( zeFFn?AD`@VZGsiOS#aUDdL6=sN@Jq){y{Aj|C+{CL3`thU#Om*m;5B{SwmJJFyxt3KWIPqHpBrKggBg@kHW<_nv=gjOlsUU|De=l$w z<$mRT0w-INyW?zazW1hx;PsgmQb|%SP^uGnF0hL(p#O8`}{h1xAz59y^lL&9IBRCJ4YMcp)kb)kE*aQN!);oD6T=oxf)Mq`cXByBn_QfKWmeTHYa&EYM$kp{(|#?bIVt&Jb3=V>Ks6YmlTSY`ru$xDBCdvrA++iYr7ya8I)2+YUVQq8 z(98!W)iZa)#bzmA%rpBICci|7$7Z9gF$9A{IlHjt-97{kmJxLtAXJW^y2@tZfq$u4 z1COsh*PzziBK62`)pKE?U8Sw^fW)R+)CNq6bVnADhfI_?{TPZ?=8~#Tzn|Q;NP)Sk zXVIhg^V%png7CSTKO$`-Ppg^FL|kv=N;DA&BN(GX9($DKaKk9#^k`?nT6Nvu-RW5vV8&`uXA!!* z+iRd=q#B8+2qM$lcH!|~5^Cf_KYCguoPDrZexRtu{Z`-EH{8Nfz~6r9x98U{^E;Si*?jfQ+26O@Vzj*9fOqOT|KOf8Yic41XO~eFK@~^a zh<9b4IyRf=ud-`Jna|=5-_EGez$IILR)T^X&bUtEf80nSM6ZsB#fx}tXV=n(}~v7BGQq}(N# zv%2%fAMJg%06)XxV5QAubI!O$a~Z_d3+F{m;p%<#W$;Bb~>M$ zoU|XR6*F27k5+I~Bt~yzDFO9;nPx~3!KBtuQp6)6{0mB{udkP?S)h&WW1bGm zE7cKMza_dt_6JHv0XBu=C0d@8u&|eb*=IpeQQy$e%GWni4R6aQg%v_f9~qVHNFlIx zC#I{LvD9c6qKa;Kz}x*EAyuGw-S_f!r(xk#QfcZ>N%ESRh3JVoU8xP6LWT5VHcgyg z_$q~&s4R}x#~dneH?|MB;qil<3zjRRvBhs`^wo0y(dnzD{9|;iiuE!1&P6=#&}o{I z^@t=l;W~7KI4N?o(lW~n{5v_oHKklx+;Y`yV=aDLFlBp1UEt<-K*dUTm8iws zpEvZ;ImJ-Nb1CgUGZK8C~nTSiz3&WxjPC zYl9XRQsiB<2Pb17P9$B)8mJgZ51pOB*hRa(G4iacpH8W4cb{-kCp1f^||%#`uZGQcrR3)B(c6zMII zjM&9GU~W!jbKwtm&8ur<6-R0^HS*Vo;AX{J?Nej>C?MAUUjT)pBbd1thW`8M2xHiB zb?oX1!+=FN8Fdm*Dk(w!s@xCnKC3NTAB&X&h{;0)R1uL^VxMAPzVxnot;q&?41N~m zy!}{$TY%jyzaiT5uPyb{9ye4ol6j5;y~e#kJ7(0J-oL!UZ={!jBTk}iQkApvXL9nU zPpFf?2M7-uSa6#1yZbZy=P&zy6h(T|O!L!i@^N%S!?!f=e;Bq)Yh+ca7}%$J1=Y%N z8(E0%@laGJV?31hHz9}bZWYYF-VSn{b-q4GB}kY(nnAxh#ujJy#ig{V=4R)whcg#W zaK^!@rdU%|(K~gKW-^&)O5UrftE#3;ecS!rgv2|cvU^Vz>%G`dsb8Mn?2kKJmGJw} z(~q%?h`}zq=;sN;D0gS^m&b%uL9@pgykEr9UZqr<)gq@mmxa%MtOod5S@@5_Xw-Kc zue-#eyQ46dv7Z&`*)aYrbt+XJr}q61-DeR~=Sx#2UNRPUfr^!(mbFUUpAm2PZ+rkl z+bv4FSn&y-WQF`nE_HOr5T9DwQtMdLd}=7;)y!&pA}eI%B2>eCZH`qBLyn{fA3;l6 z25xROYc8>n;YMs|d(*dDhX}rI;ZP?i3V~c6o$P}?Q^L(%e7B)1e!=`9jpDm4H7~LC z3$d+U3-Pg~+|p9i57aT=>yV(=(*#oPc5zAOR z)~-Rf$_rmaPC~F=2}3LDhuy9{9QH!QDCQGl*bm352$rg4};o=o&R@>uh?OGNv!oD?Nu*tiitUf`0gsp#83 z^CWg16*X8*qoQE*k*)bXFaG(dR759NiE`Kd1;Fziq0N9X2{fi*6*d8((v@h7?p{^d@Lx+Q?v&v@7;Z6 zfV+@$28D}S%_ z#yT|NLv<&^{OFR{R?*x4EUrk+A7kWo!mFy73ffOu-Z8XQb^2Z(u*h2699j+DoIxV4u0@;3T)oz7#J14DOaHLi#N8S^7!VECu}HCA zKWTuuQtCofz65Yd3pDo@K-oMkh~*7?TVNq1yB$=6P40`CU^ed-;t=H%$ZjYewEI~P z@i?|Ts1*OkKL_SIG39CEAmPLsEjIL3x@xHSSko%INvsBuw7IAG)Rx{qZMYE~V@chw z2TU;LR8yZ8!>)vyMo81ZhsLH+f?K>GY3mr=_dW4sXwM=1%w}3C^k!y^cxCSYwE#;b z(Gt0bldQGAwo9WL<$Cl$^$qbiY7Ki*T4x3#Q4~T*IK#s{&zq8m!{n2(B zr5lhNpHK5p4ADzm=an3o(^4OHfA`xqD*xuUHuYC8Fna1`)#JloaIOB741VEtV})24 z&y+vWH)LI62Kh|Ek4mG&HhH5Ia8%1K z=6^U)eIKr2MP8&W>?x{97KQRwgG$H)!kWzR;esu9a4ZzdkPHV4TNn#j@IzDXmCp}H zQuxgH-Z*@BM~&4PTkSTH`)-z6`%j2PWBL$2ni~yS>vsmx<)noy46O*AVl4rYRdCJ= zhI(xop0M|H$NHg_s(=p4go3hb139kIkkvHQ{3cm%HZn zF>uQ&62tuQJYr^tox|}PgzGRz$(ZXpH_A^7LA8#)Zjw?^HV9f7!Ac_!U0d|3>|gdb zAl#0}J{1!6N3X}-{DHTt&(+YZ!#P{6_9iyIo2+y1m5*m9ZAL8N-*#3(t=W7lDmb2n zklGddO4EPKDZAX{A{l_W;CeGBk}cKN5VWk)S5Oo1d_&P(hnAsg=@SVv??p0Iq_yFG9z29)hyK>M9Sz-)|qKmL3c+Kw@eifA9CN3TVp@QlF z<88)&t=kFGOsPK|fpm)*+W}i%?gcPkhz)ay`jpDtz6Wuk2{fmS35(bmxwQ}EQ4EWnIZs;*l0B_oWDZ<61*cPQB3+$?b=st~f{ zs$M|_V}sJg*eL7ky*TXV-36XBK2U!=)KsqFGeCKH3J4loK0urD`;C77ym6NQgaYv3 z{u@(kkSK|}5Tw_dR}53(L#{*{PO`erde1vGh9uosdGQO@?x)>X=T%>ipfXYA(M)Vk zBk@npc%eT;RR84DR` z)qT)abJlzA-nOpgxEXZ3<>0lh{MSd9<{TxbuB9Un6Jk}l4QedkKF0Tq?+Jc+1_h+J zlM9X(tNeN~LqFUu-Xec9OWH~H06(k(i_imOrcrZFA$3R2G6Vwzk+{s;TH?{xA_tJG z`c2Kurr?k8GB$DQ`1ao|aJdpJ#&$&X$P)aN-j1*ia?0mGQ!W_fRQMjjQ>P@K{m0E^ zO*j4yE$>iDLT>YvLqx=p$=x4DB6LDyq?l9R22M`uv)T?NwCeYO(%8}@KI48VwRrqd z8vSp!7Q97E`7lrUs`PQOBh+uM0~Jb8X836M5x+wEyNbTK^j{>AEw6Q83=DNKOv{SC zWd_^=3-&XEo)dDP|H!_5ll2KM`roK$1>ncc4+y|+Ve0%-kchAj5)sV%3;|*o9y@<_ z8J;!dYxn?t3~N20e^SFq>8vwEms;pZq+AQOY5sDegsG~`&C5OdR$0E+6f10hS9|Ld z&(`#N*j0&S^xV!WOJkF}wdyN$l6=8A`{j+30kNYyuE7d3^%Wjm*XE zD8#Bm?Du^Ic3Im6Z9N|v{(hJ#O4Hg>iGU=zmU!81(46)sgGcMfr60=e%crB4_QkTp zo{X@o?9FI%83Q;|zaLdwkM-KYLe0%`_7OAvs1qUo?%?z#)ST<(82`>-aM5_zssLog zV$;mfb~?`M_Rp&8tw$5Fnfb-4zd#(h;zupwSY+)W#xK~3fW+%a26*YB)BCcU8U%(( zxl~Sd6pM#3?14F1lyTFx5tk);q_2PR(9#-^@2))l@rWf&xP4}!E0LF>5E1@4;)Ri&xz}9 zKM%p~Fnzl8?tL|CaoU=&)zraRTDKHsxq~_6$4(F?U?8naNqFJVF9u4Q*Xp7w^9OBs4`oX>{{pfyt~8J!M4I=i;CEB6yo;1cHWQx0LI&hrq#@q ztS^~zqTp)~EL5Om11Tcp_3HkzWhPtal`B+MYaUXSG(VKM;9&lG`C{75d3LCmewuoT zpi}0~O7z%OO8wilcDd~62kDO$qO`FF;TaM`^kNT-zrtO{uakj?0vFD?myg2IdT`jx zX+O2Gv~cJoV8SGE10Fx#c&|SW@Dpp1(OvY9?G9m3_{%##K_OavCPqU@?@TAY@rb_y z9K6J4B*Xq`e28J~}S-nzFMA$byYcLySw zy_G}{nmyPA7@$hd@~@Hqm9ZNS9frlxcfFIRPxBVq^gDOFFY}8q6DNR=Ps2QQF@lFrz+f(wf>_K1IetK}1}GzmchL)6!R*{QGkI48fwQIvP&_c$n0 z$@Glz!#o1xp~E;B@KqvLMt>5Su0Aac|9*2unaMi7C+~TBRzJ}mY1OkYFI7# zLLPH~a;Qeq^v9Q5@=v1kn#UhA0zPxmEz!i0+OusKlC*V_3BN{63^${QMi%CMq!mO7 zSUDv+juM+{WL_L6JPe(4rk@JDYPUl83`mr7E4A`SC`NdhEC74&Hpw<*U>L`A>mn z`{vV>3XIst#m(cOLe9cU!_f%fUOUAQorBBj#Y^iJi&8*gvMu%>>!a=f81D!(-|c)^oQ5>7BSVL zl+uk}OiBn<7DBU5JVu`!J5R<_fO|PuHJSH+4hY>nePZc8(`!|a$+Ne?jI)_-^e60BZ1qG6j zlm@ruAjCkZIEFeZMOJ`!aOr3|Ub3LMn=yQ#&80Mpiag<4Ru*bK0}@pIUjq1`^4$*u zvBEMr{as5Dp(7_PSV!Q69;HEHiMTm$R$*lIY2?VBNor7tYVoh9;zU5j2g?bDmX?zm zip-!|?;dZ)_FEahCIE1wSYOfevF_ZJULOoTH{_}bjb7CUPYS@+17`A@FKhg&nM|n7k(>Na;m?Z^~xhTpavL@ z9HLl;9|}0G!th03-8Si0?{u{4Vr8-z+OPJlJ~9ngZWLVhoznTPx`GIu^%$95Svfmk zbklK5a)nB@Jg1L-=`+$ss3oAFhCvm%?cI$1h#@O}CFj)?b-GEaCY%N4&X3u8Lwt_@ zVU`%aWZ4KOl;^hQlpDKHiuv}Jb{)HWlg^7~{w2DrTkTG6DhPm9^;<~unPGq945atm zhrknZ1YR%7@36(ZK0CkNcu)OW6l%W_11lBhDKZ0|;bsLvL)+Yug8VP5EZ5gW}RiW6Uhm%;{ zPn({Ns#@QCon`%VeJ-MjvpF+@uoV_|G9JB}sKC!xVtp=_ak4Naz9WUb+NGu1RpOOl z{Vr1zg{nok`r^C-(0C*I6je4``f`PpZS(2NC0=lgkDbQXSdKX16JL7r>4jP*E~^7RSZu#l z!O;uEK`S5+=u^T!m|y?xS23{yMtb}!9~)WL{Y?9{@&d{^@DS)Tg+0691@yk#Lw`d| zJJB(D2?%wuASar`co?pUdQ6upZp!i zpiHUNo-06|9;~3Ztn5LZY zff3+|?;|#Nx1`B<$RHm?`+i!>zE{%d+i+}4hleh?Ri5K-p~Aq zC4Rr55285k;Xi^iy|uqSXid|B>Ye)+#A8vVyem+rTBl)M>^Y8PU-Yqn-`jDYSGTcL zz&1G2K(us4eifu=XTh@X2uF+3I=h~2Ub%8agk8t0sPmdqsF+E%D#rqc6KQ7gX`@N1 zy6dc*PnW!-z)#py?8;dPV)UG?92vIxi>lNLeLt`h|;d(ou4h7hX4=l*NUz3+Yqj z!7$y=@Fj@ugP7oDEy1mrWnA&&gVPw6*}0AbcEM55G}R?S2ESrDv)|t% zNd-&fB~Mpc=ddzgXiqNxZ2^K7cu4^d_ z+=AH+`aQ*p7<9x9l*_&h1^Bfo`8ur);?=3}x&HbkpGpBmmKD80&)iRQPmynnVqHJw z2KIU)!%*?s8OZ!1cQKFc_lVc8biH?_Hf9&>WMbf>)!QigUfAs$V<JMOBEExK2I+PRe#lJs(e>5m@<9%b&4CbbdeJ0R(Ds}?z`MIDwgZbk& z?QN($yKmoiM6+BLAv07nzLAD32nw3#Zn2Rrf6bm=p@%$V07L$G)z8L=(T~Y41ms zedkfO{ZxxlXWLPS^~0`_kaw86p*#2?;S%4Zi`WBtfc=&Awau14Z46lA9vJ12fL;CN zFRHH^j%_JsK94aEgJAb(GaNUU@3An`3kz6-MVLS1c&PJpY^?3F19BU2q|u_`A?wTc zW#y+T$~l@z?E}<(=ONe8a1v9J1oPuK&wE^XmhrkOw|zCtGu6yuvdCqV<6K+qhr4LQGM)V_-7QBsX z`ALCo4S}moY|LA^9)q4JQu`oJ^4_YqAm717=&6PkW&7i%sRnV+_%-d>NMcp1AykV8 z21Nbo=(*B?IYG3VPS}?3q$StwD;RtkMRE6DLA#5sx6B`)XDO=>RD>==V1i@b zaIsO=IIdLjroY3e=NOz5VLN z7I<4t#8t>!Z43^AW%}=lDFoVzQ*wt1F~$)5?yddm*fGfr0P^KS{tk+$;X7xb-tcO` z$7jfM^_;#AxHYMD)eL0P=1%XgA5zpKuv%#7STzREQo#7ycJliNhNR0h?<(Xj*!{hQ zPZ45Y2XT7}zHBb|K4s$R2y?eWWo=Qqz(XsaTBWN#sHK)E%@IRY-l&(&|nZW&K%Q$ zi=|^zmonK`R;CUB$Td8TwZS&Bz+_lQ7>6#vbIlSa%EW_1l$$gl-XS6jL96~~} zt=JkkWiTDu6Pgz6zVa8yFCF5g$UP1#9t|s>LM^~ycCVc0(eiV1P~_>$Dmt%*b82y4 zJn_--D5$c_N_p}fsL6r4k=9KW!Sq=~P%67l$^^~IvQ?Jd-lN8mi7rF~CD;}va- zZde#155n#Q1%#`4a5)F8lDp2!N3+r1Bsu$k*_i!!I0K2KKPUcNKYo=+u%~VZCe;Pf zcjmvr1qGjLU#;T(<5* zrl2~bfU>{lwzciHAeB1s)Pte>Wi#zJj?iQL%XO5&adq)R=f>A zR55%k^_seuiPm+|@pm@h4Uziq?j%+aHj+bCZK@P8Ja587b}0^b`eA9t35sa-GEyxW z1(ypUQ7@Unc>y9smvuOM__fuOSJ^g*S9$t?IE9n#ONjA()^^`5^oAyXHM9bDIOeyQ zxUCfoBQ>meqZzpv5=Kw-BTwucL|pW_n@nHfPP3_{Lk35>V!%>x(tSS{&j(9Y?ytI~ zUMM}!zGZxfOo{^kPGd$nIZNtYx3tqA>&rM>BP#eqDgYE&2APBV8cx84*m<2=cv44g zUT0}3?2v0~e=#J%0lXirKE15=8iPg8Yntd_lBj0&jP*KH>(1WAC6NaDhJHE@wbJ3L z2$T2->C#=msmWnM5L&9+oYQVJV~o^wD6#dl;5%Z<6A!2 zt@f|j#pu$969@eP>Sa=fh(J#5?#{#P!}zp^73|uaslkr#m(TyApF359B8)N(XY!K> z?d1p8B^HNg^u02B5aSO&YAH>CAhJzSwu|Q0KJ0D`skLTvbka)9{=Ih*h{c(s;kWy9cMZzx$v& zuX&>IUp@0es8@Lb$4&KsKLzq0Z4I^6`({Axjp)%1LtJUIGX#OqWFb-qyu})5wpj zbMKy*+xGmGfAR&c4+oq$vmuxU?4$*}4C1R^T=xioKtlB1hOezOty?n|U#1bSZVM$muc1v?aBtqU5($(b* zrlMbWBXKYaVfdY#N+bWRoeJrzcBVGLy)2D<+yrXCX_{56{1-;r&}&W=H(|!=FpCx{ zze_*b3#09wpf_8MyqF@|2qZJ6TpdpIZ{4Km_a54gSm!-UqI=7uFV}_buUDP_Iv(0& ztg8#2#(_`*!8Y8wjgD(`>$*@76yD6r8o4k{DNP(z&BlXZ&oT^|{&^J_h=wv~q0^sc z$Fle`8U`cnMc^l{<$@2tAm5}?zuaVNq1QRA(j)Cg!M;9Ce1TT+Z=bK?qnbN-NNLE` z4f%Z78M^qa-XhIpsFED>fmH~5k2Z^YMH57N4?(fjZ_Fr>t8!x9?_{`<8mqOgo#zCz zEzJ2w#mbND9E7iSwZFma(jthGeDzy|o?oL-Z1SP{*o^`Np~|lUV8c7fVU-Zb0N?{> z*IVa+%%q@O*j&E;Cb+BEsa8(X@#m{o*V;@W05}h~$G?9(B&(^kz?a0uqi^5E!T@ni zWVV_h4)>?c0)qUU12>DU0?p35LG5E?1)O^}3_R6t+cU~~i zmlKu*ez|r%LN#u$+cJW1gH1M{`SN+N8-IP*uvC7kaBwx+xZQf4#?J%lIKKo2?KO*i zzbLq<-=E2!*Tr%u3hKc?u{u~%SuVQFEZ)PT3>9fUdjzC&yZI{1 zU`-)QE8T{Sp+_nQd>_>(IWu-dEIYpvH#pUDh)^sf;a zDO#LXiEnFV6qXyOWe986Ap=GJb*~Dso#YK8yMm8z+F65oN;M11cZ{#yLFlBeTYG_4 z>LdF78jLmyeF94{7+|lMleNLsOTRnfkN+NL{`Q&_{E3EpKeHE? zR^|iHd4zV^G1>m=J-L`A1$aGhZhF;uC)WwOn)TgO;%JzjAIpg@rv3s5N7!HQ&iK z<9Z)hM4wuF-Twtb9q0Gv%zkv=W{I6Q>)gJrcIVOyKW|C-jk(&0C8_r8){MZP{+_ir z0e*VFivqyyg!j6B5%Yg%6Lx#waPA4-g4?{~?;QY&xlog)xWB-Q&1#AHzn~v%f>v@# z5)(a?vYJm7DqJEs;wKM1b-Iro6HHwwrNcLnD2ef)Y!PCKN3^e5f9}=i5+pnZ6*sK? z;#8m{;-rbv<2^P_+JCgHUB)-|p@e{&ce(wQE=D3dz0$+N2FDc7BmG4b>?7=KYVGuO z9|Npa-Fun(e~9>sq<&@ds$v+^I_3XCLx^8E!ceQLXK2~gGBQxc$};6lg(lhI8eM6O zUMm!z${F6wD`RgzH9N~K)ENsI41YN-Lbr0-(8k6V6H(^jpAF&>p+(XPoxzbfuweOG z=pL#5Mcob#zT?}GYL*f+QwUYumqPe%4OR0aGaK<%RHUeIXheMcTanlmc;6IEX-q-I zaW`c?6}2jHzpyU3`3~{I>c|RG>Xp9^q{V}2)lv+A>J0mVBrM)qV&Z1Zi|*IYzbn3$ zZ%_*=89^~4(d~1B`OeaXoPf&?mNT$b4FDK zA6+&QhtypeP)Ad8EfyXiZN;|l=6RQgsiv&ZRTbyfS}1t&;zkt%H`1!v7vK00ep?x> z);eB3XvA1w)T$A6#7VBTsTi~lZUH)6gelu^QYY7dx6IYzK%?1?nq55#h3d$AsZxgf z5@=~6<)XN%vGVqwb$9Vhi#bW1RVRr&*V?LD>L638)xq&^z3-J`M{)SZGa7fvn;RqK z!F#Gzfpr0%Gn0UX;JwqXtMYuc%a4uoy4uFCq_thhH;A1D69w12m2Kn?|Hv)Q+VI^6 zo7sgf{;yF2n&1Zx=m`HvdciCXNCdQ>iC`aJd|UNyYl!`C8()UN9oSx;9pgSJet}xn zJZ@(O(W^0;6OI)abFPt|5^VLJlwN~j>P2l3XCc+w3VfjgWwogbKUr}isG@%ulIwnI z;Yi5=SBV~JrDOKPAn-$NSywD{e8%uT{h~v_oEE}VxBp+?Pq247o7O7u-Kf*c)d*SC zE(F^DaS6(7way54uw}#hQka}lXLZnZURiM{J);H-0{LsS;o&Y|}NhO#R=CF4gw zL_Ip|{PF1AvC^f+PgG#moszvYOAb_pMuaILRo)opn#3&X9J?`>$Jq>!xK7`T5~| zNThtmkFV`I%}I{r1;=0HQz=RhIFo$Qa`>I;Cb|A&aOHz9Y%(hs+_^p}JR!|~l|$D6 zRIXgXTK(~;Y-5IZQTv@3y+WTsNuoDHHVwFK)nlZeB{>Y`q7hj zRmUiOcC08&UFPT&nYmVLO(T@wd=@P zu50};kfxKZJ=d;QGG~9%Q~w)?eo|kdGasTn3%^^!7d>wrA<#FOC^)3CHNc*%G^3gj z;GwL#9CNGbct+6Pu@Cq6h$s8?*G%fiB!%I~8A6^xZ!aSCe&8QS3KpBMh^@fiv-+2Gbk;zV%u&8hBaE`@EF2cJ=}TC0ZM{c%&Q(%uSQ$EB{bCz&cnE^PRms) zF_|=-;E$nESm<gEe(IWq__;fGDzsykKyLSm=G3HM#{4Bv@e}7P+a@5RB`sp({ntW z$o9D!m{q~%7Y4gThQB_hd`9#T!i3D4m(#6MZwb^G#4+JSBNNA(a7<88O2<*9ua*$q z;L}?QK#=1CgiE+{O@vi|x}h z-}A`l|DIsZx56mMXM23*y;Z{ku)E-&k$Au|OBal>E`X6jX2}+0a_`cUop$A$sTJIY z=8AF_JYGRDvPQ3|Yw&;=dnvNE_ut}O=rSv9jb$;fZd7j@L8IE2exEV%1rG$6c$ept z+Zoo2bZJX2uRfRtUI%GbKnvHXmOqUDwdB@oAMP!MIU!q}lkf0+qXLHp{65Tsvrg>aJIZO_q+oc+pF^tTIP_xVXJT9|Of z7G?UORpGO!Jh;Ql-Pk7mdTU)8m{G>yUsWa3T*VJq$O%%DP=AYK*qR0WD3yYv=;Ry}w%R$2FQ>95eJqypY@IH?*uQ(aB8Z|Tf#?jUN%DT{H?0u37dAoVUm$vnw`mtUnQ zQP?%8&0`%lZqC@NnLNtXUj^E(n)-{Z6L98!VeC(FsjHUwt*>(aeJLGl_P(51F_kne z{Zq}oZ5K)l6$U#VyR;WshOW>cwjmD%i!%btX=3CQk88dGLTUS~FfsbiyM%w2*r zcM2T^Lo??(M>WX!fw4UAAPkkQg4bA`?@i>Az+>D9j?TqFg!?;DMZ$5Ky`=X@iNrbZ zgne5=(6D;_W5!@x-CdY*t=-+mYPzz8zXosSL66P`^aZ?hi9SDzpp9R_J-Q}#nza;( zq9Mg}4POzz_m?`uH1R2|Aoe^dXI@Tn`vv zd9KtkrcK|zABE$qz(vGG_L*{4O-~^OAL&#B4%2m$76}#t z(ooF6FVPE)gEOAg8n#w%z)P}l(jGk_0K1Io3txEK-qq^MIdoWc#wqG>U!uHS7M}QX zwLF3b13NCSktrI^^$4Q0)}Z}K@eN&|^{mD96QvZZ-(jl&VY^&wj^P zXDyaqO?7B#HRyzVl{9%3CuMvAV7vk8tt z>wdoNC&=3lXMuG@{ASOvXt+%0`N`@}j_<4E3R>xz4_edMlK8Sy>xNxxrS+8~rQL{n zr+$0!cYT42f2%edh2|B~Df56}L}@`u<(pndoVklkTwWA%5}8ik1sP(BzvGo%06=c#hW4g9n7o+ ztGrr=X%DxBT?7TgUWnSxZb47@>3+*@K@03hUFZ1gSM^dgrqOFDC3Y4L$Z)EEg_VOJ zwwvtUVFCP_{I!|E4(8xotfo!HjAr*2jvRC>2 zWiSv2W`Lni-L2D!SXwRl_=~ie#7(zLPU1Y|OqP{~D7FxC#sel-UK{TQWHO>!wD!I= ze%%Ojp}Eo~&T}u`N4GNif~BZbzDSw4A2q!Ha$e7#hI2rB!w^2HxDyI1U>KQ&Iv1}} zZN(Zkakr}v$5c*Z#Y?M#2vJZ(3tsIEyv_=t%mpF`tF=lWeE<@noYPaxBXnhx|9G$(uC3JHrYQ zpfit-T|o%9CSG0VeD3H223WV`X&x5fOY#`ODLSIe{aJ}U)33Epf38cH@mp9bNdCDd z%krdNh6hhnq&%S~TONU+nUY4ke@5hh(F|DwH-nJ9rOf1xUM=Or<}h@VO%CK{i@CR` zFNW^@j5-!IBJyH_dS?X)7fkf1<+1#_A%+<0G{`;2*m4pi@C;gWCdNf$f1cqjl+ zim^COqzB&7Nq!JlklFb!IV2Gc4i`V7-$)TsPgo1N&BM~~mnJc-UkJd2_iXORK?7v&9rdPbuiG`|H=c%uGJf~lTVliA%i|6y0lLRo z-GHdpa{ixp{M84|-2(gs*Y``54O1TcvRd^C4$cIP8ad4=IXUW|{`oAmn3wBByoFC= zuS(h5kg4Az6KaOKpAnN$PSmQ^0AJpfyYYS8gqymSs1C~MxTh;!rtvM)+|ioD_3LQ? z__~yN?uErs`&@I_@885+BCiZg>TfiR8lwRg>e2Q zW4sy{6*1^`{})H%O@|Ru019Es6B!(I=za39iPH};e`m7#HLZ+TQTxl&oSAQ0rutAEzaGQO7)yi}4alUR|)%U+_1{?=&Q`$y6M(#yF zYMJ6pnZ`Vn(gy5LnhZcJGQPxjoFk#^WWy90oxC}k^E;7Fob2}}i5=tTJo z-Z6WqQi*uZ_YineD3^2p?L_7RcZ~*BKejElOy>G-WGKURBdna-iFXw?edB)1fRc>F z%tT!Ey<%+j2V-N)9;^~<+gG-oo!B@A{T*tQ*#q-J4zJDO+>O1sMGUFSJQ zpe@pCf71Nhi1IxM!5T&EZHRpIy7yxyHjm3Mg1_qwzU_WIb5~x~53BBVnBTl`VtSW2 z7)WI3cbc>(8}wB9v<%ViKz%!9G<^PrubsG|&~i4Z4Y(9?K~wb~(YgZ62Jc)s);BNb zo;bjNJRHsr4y*bI=D){J-tM&q`42wJgVNINMNebJB4BOA4u!JO8{adXR7xx}8?Z}% zmehiCJH`6-XdQ%x3Bx{`Mtk>J>%C3)qwsrSoP&YngTJsm41s)bDLoO2xRGpaaIv5~@+k=)Idx6?|8$1=@Py28L+Vsz6fx8DI=MPjGIyJMVe|#i zW&SzxkbFGN0uer<;5I121gIP_H)*u?d2qjh&Fc7xN$qW=sXAoQeLB2%zUv0r+Y3 zwg}WsT=2X!+TeG@(?}!1MP?>&QY=P9m1t4&J&I=E);8wW6mr3p zhCbf&SU+AwUcY`*FykSDTx)(Pk+V&vPEehMN|5lr3Rcs%5FpY&vKZ(Y*4y{Pz3VvF z0t2rP03|?qKKJ(ZIMOT+C9ptYymPv`xwZAk3JudE>&y0)9#%dMQXsn74&}Y0pMf5C z;m<483w;D>v+ldj4mn0hcz=*(ngqf^?M&D^53ydq3kf0M+4!$5ukJTi*S9u*QPMl# zpE%x(sLlrpLoMr*j1W}pk*OOcP1d*1hY<;`V1%0=#@Ra$q|dfj3PdqNl?8YUMHLhL zP5zs?ao;!6?T}?p*xWB#?!Ngp&cqP-@FPE3k=;+=kyN0?Y^I~N8-G>!a@^<6YIZ1{ z|D9YQMa64={jBX(5^&I%C)&E~)3QM8&BPw?*gL3^AA}op-^hGBH0jirBkHvt*qV3M zz3A1F(}c1QX`ez-C@;o-cVj%qZI`@}45Ps}0WozQ&5|X2gZfbb#x``jWZ!H5c)^DX zIqrFTcw#SjCknc7As+O18vdGpBiwbi_TfJm((}@hbEC7LbCadIjf9R83~VGHZVtZD z+Y$H8YrTpa%*YTGn$Ri9eb0+LP$Cy?S!`73dYu;JKGV2qlqsQZOrjxD17 zrrdd@9AJ zxt#MiN5~A9k0e#BMIQr=ZxoHjV|OvsY-{@gn4`~@BSO72oqdv5W*lQeb$+`QJ;94u zfe|9OL5u#%&o6UMRTrc2^belAvymB;HuqkonP^-eopt&>!mN$+8CC&b{VGwaV09z+ z{@EG;Y&k0c7Nq}!IERI|4%av6y*_w7HQ)gHL9E_goO~%U3{54^`G`1E&KVn^c&?e| zKE~6-W>ka_gWJCC?yGAz2OVR7Y|{_}RJ?<#PGx7ZYiNE77chuOZM__~m-$9HThxJlz##dxw=abuX14F(y8j)UM;y zWKkV&6YeGn^j(K(J`ekk7Ps8NF*Y9)y*rB^m$FyOuAs=CaVw!fr|MgWQpGFHpby7u zh(C@N_|+SX&E~V7;4^;vfo$r(#2 z38CihLxO4wmCz;mQtm|)G6N6fpEiR<+xEu+RqR5S$0}A$U(`qPfS9-% z0{rBTYZ+>=ZxC>C-=r9j0JPSfl{8VN6qFg?ywg)Q#P%)H%|Pl8(W?d#wGWeFPvu4^ zCbPuKkA*lj8$AVU*gr~<79wy^8s5j|$II#@PArC6!RwRDX%p%9(aDK(Ph1wahlf(HSNIkL|gKsk>@ zZJReJ-j5z2XEtZcTe!z5YzCG0l`*sr|C?S^^D?enJ>qN=h+-ANl?0MYLsp>;WuRnY zs9ZQ?`DT)BkRkZyJK@L~cX9iJ*t|*M$z*H7zvG-F2}P*cn-E!NsC6ea;gjvOk{J&P zm7<%Rs-!q%Vd7yvyYUJI%CHuy6sM7;3Ci;8=5*b~MBOGJ^%$i)KY@B5nPRhN*;dnB z*k@&O`sasy+SmNKw0RX&KoPM^+Q(4V~M&da}N!=%`xXf0d{& zhD$Ejw3hVuCb<*O7m}LzfBJf|HM77R63p!eF^pE2(-?jgAS$ zI@*ICL(>UbjRX;){(TIJMRx|DmG;5yH2g0<+q0Awj$4RkwO<}oBKu&V;ycfgK%xWS z?*BW4Kk3zu9fo&m-zuR#T(_ovfMwZ-Q*7R%USB=QU$6~t4@URVkB(ja8bW)Y4iM?` zL9<(MAAv2JeZ^~&$zH*0VDM`28`s;E=j?!UES~XmRBvBr@VnpJrh)TXBfIBX75l~m zaH2Xt_Eun2FR2{{!rBZ%#&6-T9c+_$${nh1rC32oXzfPO*}Bt;FK)W6cJ?6+x~Q2c zJ7#~Mw0H445`0?<&3L`oo{C0=D*34OLZpnFPRp(SH9riK$hAy{$Y3RIHtzG!;;((? z$+cX$2vUhq6;Cx)TlWIcb$G3l{->7G!O(@)a0;1MpC z=%fHNHLr}NayIfv^i6^r1_Vh`7N);!pLe z-{;@x#2r?dO*y7?O08WTbS+k)oHR)UwB%_&GglJ4@iz`E#zGp8qJQ3o>2U!n@Dkj^^kXx_6Nd8T1(~# z4g@_lTzy5@Qh007G^HIM>Ey1t@ZLYG2}*tAklHmPj7H{8w_c=ffNU{lDvc}K>O&y& zB-e4=aCN!{u^}Df%f_(VMa0^#A#InwN};FU|F;XkdAk14!f&5+UqFH6axjLeq^zv< z$SOF3lM#?^HK!nPO!&qTMlSQml^7%xuGpaRP=QVrTlG>#FFT zM1v31g?F3lhyM=U{{^)dlPCCPd(1v~&#nt}fV|nvm(=?<3WM2TXWe~IoP!6#Ug)xlD!@Oc!ut}$0Q%Lzg6^E5xE`#7JQh$9RQ6ZS_AA{H~9XS zj|ZM90)|*2xHZw+h7WwmaFFlqmHfk5=n53|g>xn826)^J2O%%BK2>Luj?5Z^I}KWH zuS~!UWUuyOIuN(OspzJIzA)IYaeU=^ks|~gf10f(ysh@%ks51ClOk5DIv{tGzN#{0PyP=h18O5(Q>v-@s@STSjw3&IkqHUz_8b*9)SfqUL+aD|8%Hz=rN z$@UbJxT!S#9Awlw2uL&(v|MRlzZQP6+7}Az>xlRi{WV@DV|jdGDO8bYmTkX*x$_Q; z39Z{Y9TdD6I0*vBg*IMA@iLNyD}sMb&#ZkH@{F>8NSbTK5y8~rlo8^cTO*sBn^A#3 z3MT8I(rx5qzYc-xl;6~72r>V}dF=SxMnGp5CG|DAQe<#)UJ)=(m?tzsA?p|hLfjNv zlV2BkA?t#Wlp0i&aU0t=3W;HV+LrK_uOp;A%W-ERP-fv%^=*$MaiJ-sMvKJojD$`` z-$;;$y$Oss#ANUT>v}IIlMKCBbZf=q@@RQpAo^Sg{C3yo_9t_GLK2_Pp}?&MpjW?TPdb+^hAGEAAR9*jgEGWfR))y13J^xQxt%`WI7{FN(dF@c_4a z)uiW~`VcH506d6={Q%>?F>c?4euN&IxKRdyfqtwe%e1whdG}V;*KER? ze=xZQ4#j|iHQ*Hsy2K*3v?H3_AYrH-F%e*ZckS-LFce*QlE)4F+oK~&L5H8k9Ok$e za5n_8dR9WUmD9x)lVYkkV3VAHirfqEMX3+YntcbQxH^RLWTF!ZEM(GC~|9 z>RI(ZBwK(u61s0LwbmD+OS}Q=kWB(hCg}{OvA)rM>d`q{@8mK@nPSWru;>f8$I2U} z1y-`q>Fr;-(OUYeUc|IRdrME^>FH_gE1p$5)oSr{{@V1fggjw@(`*40#o46`lv>jH zucb_R2jq{Ou!&jGR|gM44dO@E!m|YfZ^s1%iBal;8l3THi3jCl>DTI{yK9@DBmYxq{%WCa*^ zsPok0lgL?6&Pr^#)T1u>Q2=kr+7C~(utd(Jn z3hD_PPiagmt$3+fa@(14qcWLTNnO9oI3(_{U9qy^q7iwaT=iB*@cX9~`BS6g@l)Ns zkkyQ9$J_fW@4eu8qk}D{NA;Pppm!(#0z0^lr>uGSP+XFPPeGi+(3rfRCa_|k5C5=0 z7QcUeCaED(tFEwuuo6bIkq#yw&>%vKD+fy!1h|&KAR6$KLbhhF7al^SzkXRr9Z{J#*l!*xb7r6&42YzN4dTxQ;Y|0G$zm+tU!wsjK`zL#I?gAiOqYcmsvg*+_zs9GemyhK2A z#)>O83nT8>zWagIKakM%aL#*7?2GN~m^8pja7#B#|8b+F7x=+9KU0Iz_)m~GOa{-U zq~*c&EY6zCEE$VNdi!-dh}>Qx_^?EkJ(8DHE2UYOBcY_@c4CuEfb5UwdF`6nyZcGl zXX&&xM6sQ8;-z3> zrTMRp!gA^vk{|e@vcX zPp6O({L6Makx{48{iEDRdFCGnPvsA4o!>ABp`)Pk@KHg@AEiEw*n^9>c-(ykRT8BA zLD&3Ze>TKQW+X9TZB@|&L1U&L=qIoSsQ^~)nm_CU$((8O!X^GA+x^)}*ppZog#bnY z^}y*Y4)2Vrxi}W|gxKMW)C{UcikN3g^JsMFfmd8l7$!0+w#xG*%*h}fp?qNhILH?N zFONh*R6I4(Hc#kZ+-sfh+)y@{IQ`}1aIaNJHsMd<6(%z7QYd95X4bsp{3CgslWA$) z-C>!r0x$dI!GP2VsB{6c3jp5P#5&At$v(A1_YwdRj($iRUeTCC4NHwu_~cxv_ZFl# z)rsyH9Tztpv#}fW=aAN&r;S7~`tx7a!)%}XrJorYZFbGR)9>?&b>27Diw&rC!|^e= zHPo)C;Rgtnu_Bw@57c#RLyl^F0gKR{WF8xQvRzxxRze7j;H?36NR-o`meZvLwn@hV zAE9)1&9?|E+w!}lAGB5hIu2ve$t?B7FY4Bu{V6p!O^#tHRA!_|NBL5~S zDNQH4HfzRvK&E$B@HoQ3)w@VV_SSny?9C?lmX`wuB7X1sv-M=x4kI7G^E%>7YWIG0 zv*w}GxqQsC*}A~ZJ-q+Cs=l)%le+q2cW1|a$w|+@;|eOPC#Mt9?=80TpkqVfq4>*6 zC5gewmk&-Xo)rOA+_y&uB%y4@3H_p0(UJvQcsLV96-kc1*?yikuFj*C{Fu9D?-#LK zxD!0Bh}+DXP`z~!W}?^3Q@?Lr7`bCDEqN%0Os{oCU~pS4_M^?rk!8izc&EGA9(IF& zuE}Rw0VWc$4Fbrj8O{vwb8kpd)c1#o$6iqGNfjlt;`_oAgB28xg)1_Mclamu3mt#^n#R+Xm_G?ToO5-1a zDqp6l2kd#e>{1p7pG2%HDGy4)XFSP?3a3SJJTEiFyzxf2^ zh$f}K%&678MpVPkGJTxFV((76?Dep-P3D1pf4UJnWgeVJqxfG{2am@!bxLX~5U-n5 z^K>aN5p?kKVP_0ri(U7;SYiZdEx*Yrx-_gddbVdf3FHeqp9i)l9yFq(9#6cfM@f)W zS-V)j{e1R-<-O(~TyOWfN-E)p11i$8o9y1sHvTa&x1O>e!`5EHsKM6UH3lZ9Ts zR`r0*A(4lBGE^hDbx^h@w(9>)Xg+=@$|J67CgP8706Y+LKvMSp>OjejN4`(eMgSa2 zv~$7L>uk%Yq2z`2Z>AJ=Yxd_gr39Ey6A|l5?j+&T4n&^=_o*)Y5VD_oGE7t$y25j3}oA`j(CW4TjI_m ztBn}(+rFl~cdb?xju_PkY+-(cH~_1JKUMgmh&u22sY@Fu8r3IuzG*1GKd@fr>AnsC zXQP@=!zzR^CjJwolz(js<+uZjuVc$Txc<(?jE!2@E~+AO#=Ie}ju-5<5MTHuFcjg) z%l|+OSF5I+7*{G2!DRdlR9jG2${% zMIXU~NJ;aE+BU>r9{A-h)=(-wd$yAT`K>_5_Y}*v9^+wE3R^aw2fjT(H+lO7gE0#3 z8eY_Ehg7%;qjk!|*x>ITCn}7w`BY+;7Ed2rTGih&E5KO3j7NMP*;J@v{HKN(y#X)? zTx*T=H~p(WT!VQQ>u(rEvP&-cM3{}yP z+nZoL%3tX5k*<7z#+Y+h`O^9d@X@Og&{au)4UsK-NMvolFPpnhGgPV(vE9(x-%t(h zMJ_WPM4Og>6Nq@}-_~*ezGCuiEg7M~8bpdCu0VtWmysBWUNlw^vA3Gzq(S-3jOT9dd4TVvu9~$4* zX&WxAlttwyC4-ulS|+-(M<@XM2dEBTrn4;h^2X|`g7;*Aq8r=_(+|5W@+|Tj4^!rx zKsk9l4U$|6IR{~{Lm=nH_U>9qoXM9Y^+h3WgbfVVh!0U=T&-&9#o$;-c{yV*m^^|o zPC150-k>Z&%DiCG+Kn~2xar_L@$jK_h&`lsLI58DgShYYl^>myH(b?iJ!C64)|s=8 z9L`_99b5Pjns;GndXx?y&H@=NdeX{(m<9l31i?S~yio^99Z3Vpg&SA;kB{krWJRE~ zFa`)8d-!QYBE4@^k0SWr&QnNbt;DUtE?$-^7Nw}6p#@lVj^bKPH)kA?0i=vmZ@xo9 zwivVe*I2+#iLX<&*8!L0V1dI?-JCJUWn|68n0yc?jVgq?6&BB6%@87vgABX8sWs7# zm0kB)M6SAZhG-&p@&yg|q^uZkf1n3D6W2~1feVq(yn&me*I4{CIqeS=U$HrXXw$d$-QV>LLQORkze8}4v%t?Y{9<^qHn~mz1OtslZcKtkz1ju&CxY~waFkQPHJ?l^0?&upYjj{fiKH0w_#ERn zXMf*ktClN3_)%13vK#XKTcsl{Mb)zY*IBDPD!66Q{Li1Gy?J`Bx3TXh&IVue3NBwk z8?VpY)y@xVN(DGiv;(hsR6O>Vw858Eb?)#d43R@&RK^zQ6h7cBC{bamuv;G~L}8** zPO+3CR#8!lpsJ<5Meyip&R__>e$@CaC%{VsA6P*29Cdf+f(j2)XrVoTgKI&xSLrne zu*5K;!|l2k<{QH|#A~(-bQgDSy`~0+94SE^D*$-FCQ=dd+j;r*+dVN7*TWkSfRo|t zd$R#jiiWjD*cT1o>$ULeejP{%zx-wh)+>4Nt#=$0EAmHCNE}@VqpH$C%~Rldqrw%- zi*cQ+^|hqyk6G`RbqTC@r~~*tM)Upy=!T!H*4jG1pmZmw)$fe#HR%0AGv2r9Z+(F| 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 z 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

    From 7bd16311b19dd125972b654c91a1e4d16c688041 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 04:37:55 +0100 Subject: [PATCH 108/262] p101 - patch by ryan hayward to handle dead hand before BB --- docs/known-bugs-and-planned-features.txt | 1 + pyfpdb/fpdb_simple.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 17ef57f4..a9bebe40 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -97,6 +97,7 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== +improve handling of dead hand before BB, see git101/ make DB version error offer reimport, recreation and continue. In many places there are unnecessary database accesses or it regenerates information it already had before or just generally does things in obscenely inefficient ways. Optimise this multi-select in bulk importer diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index f8fa6524..d94f1c81 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -53,7 +53,8 @@ def checkPositions(positions): except TypeError:#->not string->is int->fine pass - if (pos!="B" and pos!="S" and pos!=0 and pos!=1 and pos!=2 and pos!=3 and pos!=4 and pos!=5 and pos!=6 and pos!=7): + ### RHH modified to allow for "position 9" here (pos==9 is when you're a dead hand before the BB + if (pos!="B" and pos!="S" and pos!=0 and pos!=1 and pos!=2 and pos!=3 and pos!=4 and pos!=5 and pos!=6 and pos!=7 and pos!=9): raise FpdbError("invalid position found in checkPositions. i: "+str(i)+" position: "+str(pos)) #end def fpdb_simple.checkPositions @@ -857,6 +858,12 @@ def parsePositions (hand, names): positions[arraypos]=distFromBtn arraypos-=1 distFromBtn+=1 + + ### RHH - Changed to set the null seats before BB to "9" + i=bb-1 + while positions[i] < 0: + positions[i]=9 + i-=1 arraypos=len(names)-1 if (bb!=0 or (bb==0 and sbExists==False)): @@ -1563,6 +1570,9 @@ def generateHudCacheData(player_ids, base, category, action_types, actionTypeByN hudDataPositions.append('M') elif pos>=5 and pos<=7: hudDataPositions.append('L') + ### RHH Added this elif to handle being a dead hand before the BB (pos==9) + elif pos==9: + hudDataPositions.append('X') else: raise FpdbError("invalid position") elif base=="stud": From c19c933942a7c5f1bed103ba89f059452f33d7d5 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 05:22:17 +0100 Subject: [PATCH 109/262] p102 - moved initial data INSERTs for mysql to separate method for future unification with pgsql table creation code --- docs/known-bugs-and-planned-features.txt | 2 +- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_db.py | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index a9bebe40..506ded2e 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -97,7 +97,7 @@ verify at least 2 or 3 sng hands no rush but before 1.0RC ======================== -improve handling of dead hand before BB, see git101/ +improve handling of dead hand before BB, see git101/7bd1631 make DB version error offer reimport, recreation and continue. In many places there are unnecessary database accesses or it regenerates information it already had before or just generally does things in obscenely inefficient ways. Optimise this multi-select in bulk importer diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 85a294d3..98f0aa27 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -420,7 +420,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha4+, p93 or higher") + self.window.set_title("Free Poker DB - version: alpha4+, p102 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 1468d8d2..1b672bab 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -151,6 +151,13 @@ class fpdb_db: return (self.host, self.database, self.user, self.password) #end def get_db_info + def fillDefaultData(self): + self.cursor.execute("INSERT INTO Settings VALUES (76);") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") + self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") + #end def fillDefaultData + def recreate_tables(self): """(Re-)creates the tables of the current DB""" @@ -167,6 +174,7 @@ class fpdb_db: if sql == '': continue curse.execute(sql) + #self.fillDefaultData() self.db.commit() curse.close() return @@ -379,10 +387,7 @@ class fpdb_db: street4CheckCallRaiseChance INT NOT NULL, street4CheckCallRaiseDone INT NOT NULL)""") - self.cursor.execute("INSERT INTO Settings VALUES (76);") - self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") - self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") - self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") + self.fillDefaultData() self.db.commit() print "finished recreating tables" #end def recreate_tables From 005627b9d7395c7b6f31e3f2ef051bec0fc24f3e Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 24 Sep 2008 05:47:17 +0100 Subject: [PATCH 110/262] p103 - reading small blinds now rather than assuming them --- docs/known-bugs-and-planned-features.txt | 5 ++--- pyfpdb/fpdb_parse_logic.py | 9 ++++++++- pyfpdb/fpdb_simple.py | 10 +++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 506ded2e..702e40bf 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -4,7 +4,6 @@ Please also see db-todo.txt alpha4 (release 25Sep-2Oct) ====== pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. -reading small blind wrong for PS 25/50ct check we're reading mucked cards from PS ebuild: support pgsql fix HUD config location and update release script accordingly @@ -32,9 +31,9 @@ finish todos in git instructions debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html howto remote DB move all user docs to webpage -contributor list on webpage +(steffen) contributor list on webpage finish bringing back tourney -No river stats for stud games +No river stats for stud games? hole/board cards are not correctly stored in the db for stud games HORSE (and presumably other mixed games) hand history files not handled correctly Some MTTs won't import (rebuys??) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index e7ec42e9..60cf16de 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -39,7 +39,14 @@ def mainParser(db, cursor, site, category, hand): #print "parse logic, siteID:",siteID,"site:",site isTourney=fpdb_simple.isTourney(hand[0]) - gametypeID=fpdb_simple.recogniseGametypeID(cursor, hand[0], siteID, category, isTourney) + smallBlindLine=0 + for i in range(len(hand)): + if hand[i].find("posts small blind")!=-1 or hand[i].find("posts the small blind")!=-1: + smallBlindLine=i + #print "found small blind line:",smallBlindLine + break + #print "small blind line:",smallBlindLine + gametypeID=fpdb_simple.recogniseGametypeID(cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney) if isTourney: if site!="ps": raise fpdb_simple.FpdbError("tourneys are only supported on PS right now") diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index d94f1c81..0d5eb749 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -972,7 +972,7 @@ def recogniseCategory(line): #end def recogniseCategory #returns the int for the gametype_id for the given line -def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: this method is messy +def recogniseGametypeID(cursor, topline, smallBlindLine, site_id, category, isTourney):#todo: this method is messy #if (topline.find("HORSE")!=-1): # raise FpdbError("recogniseGametypeID: HORSE is not yet supported.") @@ -1043,8 +1043,12 @@ def recogniseGametypeID(cursor, topline, site_id, category, isTourney):#todo: th hiLo='s' if (limit_type=="fl"): - big_blind=small_bet #todo: read this - small_blind=big_blind/2 #todo: read this + big_blind=small_bet + if smallBlindLine==topline: + raise fpdb_simple.FpdbError("invalid small blind line") + else: + pos=smallBlindLine.rfind("$")+1 + small_blind=float2int(smallBlindLine[pos:]) cursor.execute("""INSERT INTO Gametypes (siteId, type, base, category, limitType, hiLo, smallBlind, bigBlind, smallBet, bigBet) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, base, category, limit_type, hiLo, small_blind, big_blind, small_bet, big_bet)) From 66bfe6a68a97c9d0a1627b1bffadfa4458ae7c8f Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 25 Sep 2008 13:12:42 -0400 Subject: [PATCH 111/262] log errors --- HUD_main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/HUD_main.py b/HUD_main.py index 15bbd034..a839c2a1 100755 --- a/HUD_main.py +++ b/HUD_main.py @@ -38,6 +38,9 @@ import os import thread import Queue +errorfile = open('HUD-error.txt', 'w') +sys.stderr = errorfile + # pyGTK modules import pygtk import gtk @@ -105,13 +108,13 @@ def producer(): # This is the thread function dataQueue.put(hand_no) # and puts result on the queue if __name__== "__main__": - print "HUD_main starting" + sys.stderr.write("HUD_main starting\n") try: db_name = sys.argv[1] except: db_name = 'fpdb-p' - print "Using db name = ", db_name + sys.stderr.write("Using db name = %s\n" % (db_name)) config = Configuration.Config() # db_connection = Database.Database(config, 'fpdb', 'holdem') From 96e9a0112fb9c1212b1f84624c3310eeba419106 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 26 Sep 2008 01:05:00 +0100 Subject: [PATCH 112/262] p104 - grapher improvements by carl --- docs/known-bugs-and-planned-features.txt | 6 +++--- pyfpdb/GuiGraphViewer.py | 21 +++++++++++++++++---- pyfpdb/fpdb_import.py | 0 3 files changed, 20 insertions(+), 7 deletions(-) mode change 100644 => 100755 pyfpdb/fpdb_import.py diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 702e40bf..e21ec833 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,7 +1,7 @@ Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha4 (release 25Sep-2Oct) +alpha5 (release 25Sep-2Oct) ====== pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. check we're reading mucked cards from PS @@ -22,8 +22,6 @@ printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring change to savannah? implement steal and positions in stud anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no -Import draw (maybe without HudCache for a start) -table with data for graphs for SD/F, W$wSF, W$@SD separate db table design version and last bugfix in importer change tabledesign VALIGN and add link to webpage finish updating filelist @@ -41,6 +39,8 @@ Many STTs won't import before beta =========== +Import draw (maybe without HudCache for a start) +graphs for SD/F, W$wSF, W$@SD validate webpage make linux use /etc/fpdb for config first, then ~/.fpdb. FTP file with only one partial hand causes error diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index e2f62fae..d0d328a9 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -20,12 +20,14 @@ import pygtk pygtk.require('2.0') import gtk import os +import pokereval try: from matplotlib.figure import Figure from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar - from numpy import arange, sin, pi + from numpy import arange, cumsum + from pylab import * except: print "Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib." @@ -55,8 +57,18 @@ class GuiGraphViewer (threading.Thread): site=1 self.fig = Figure(figsize=(5,4), dpi=100) + + #Set graph properties self.ax = self.fig.add_subplot(111) + # + self.ax.set_title("Profit graph for ring games") + + #Set axis labels and grid overlay properites + self.ax.set_xlabel("Hands", fontsize = 12) + self.ax.set_ylabel("$", fontsize = 12) + self.ax.grid(color='g', linestyle=':', linewidth=0.2) + self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers INNER JOIN Players ON HandsPlayers.playerId = Players.id INNER JOIN Hands ON Hands.id = HandsPlayers.handId @@ -74,11 +86,12 @@ class GuiGraphViewer (threading.Thread): profit[i]=(i, winnings[i][1]-spent[0]) y=map(lambda x:float(x[1]), profit) - line = range(len(y)) + line = cumsum(y) + line = line/100 - for i in range(len(y)): - line[i] = y[i] + line[i-1] + self.ax.annotate ("All Hands, Site %s", (61,25), xytext =(0.1, 0.9) , textcoords ="axes fraction" ,) + #Now draw plot self.ax.plot(line,) self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py old mode 100644 new mode 100755 From 6926306147b7372c8b1b96e65160afa79e7332a6 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 26 Sep 2008 02:54:08 +0100 Subject: [PATCH 113/262] p105 - commented import in Grapher as its not used yet, reverted bug i introduced in p90 --- pyfpdb/GuiGraphViewer.py | 8 ++++---- pyfpdb/fpdb_simple.py | 10 ++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index d0d328a9..612ac2ff 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -20,7 +20,7 @@ import pygtk pygtk.require('2.0') import gtk import os -import pokereval +#import pokereval try: from matplotlib.figure import Figure @@ -49,12 +49,12 @@ class GuiGraphViewer (threading.Thread): site=self.siteTBuffer.get_text(self.siteTBuffer.get_start_iter(), self.siteTBuffer.get_end_iter()) if site=="PS": - site=1 - elif site=="FTP": site=2 + elif site=="FTP": + site=1 else: print "invalid text in site selection in graph, defaulting to PS" - site=1 + site=2 self.fig = Figure(figsize=(5,4), dpi=100) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 0d5eb749..dadeba99 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -993,16 +993,14 @@ def recogniseGametypeID(cursor, topline, smallBlindLine, site_id, category, isTo pos1=pos2+2 if isTourney: pos1-=1 - if (site_id==2): #ftp + if (site_id==1): #ftp pos2=topline.find(" ", pos1) - elif (site_id==1): #ps + elif (site_id==2): #ps pos2=topline.find(")") if pos2<=pos1: pos2=topline.find(")", pos1) - #pos2-=1 - if isTourney: big_bet=int(topline[pos1:pos2]) else: @@ -1136,10 +1134,10 @@ def recogniseSite(line): #returns the ID of the given site def recogniseSiteID(cursor, site): if (site=="ftp"): - return 2 + return 1 #cursor.execute("SELECT id FROM Sites WHERE name = ('Full Tilt Poker')") elif (site=="ps"): - return 1 + return 2 #cursor.execute("SELECT id FROM Sites WHERE name = ('PokerStars')") else: raise FpdbError("invalid site in recogniseSiteID: "+site) From c887d14a7aa22f1b4cf9d0886c21fefcd31b16d4 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 25 Sep 2008 23:47:42 -0400 Subject: [PATCH 114/262] Full Tilt support for HUD --- Tables.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Tables.py b/Tables.py index f2b229d0..cb6209c8 100644 --- a/Tables.py +++ b/Tables.py @@ -73,13 +73,12 @@ def discover_posix(c): tables = {} for listing in os.popen('xwininfo -root -tree').readlines(): # xwininfo -root -tree -id 0xnnnnn gets the info on a single window - if re.search('Lobby', listing): continue - if re.search('Instant Hand History', listing): continue + if re.search('Lobby', listing): continue + if re.search('Instant Hand History', listing): continue if not re.search('Logged In as ', listing, re.IGNORECASE): continue + if re.search('\"Full Tilt Poker\"', listing): continue # FTP Lobby for s in c.supported_sites.keys(): if re.search(c.supported_sites[s].table_finder, listing): - print "listing = ", listing - print "found ", c.supported_sites[s].site_name mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) if mo.group(2) == '(has no name)': continue if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup @@ -96,8 +95,6 @@ def discover_posix(c): # use this eval thingie to call the title bar decoder specified in the config file eval("%s(tw)" % c.supported_sites[s].decoder) tables[tw.name] = tw - print "found table named ", tw.name - print tw return tables # # The discover_xx functions query the system and report on the poker clients @@ -141,6 +138,8 @@ def discover_nt(c): win32gui.EnumWindows(win_enum_handler, titles) for hwnd in titles.keys(): if re.search('Logged In as', titles[hwnd], re.IGNORECASE) and not re.search('Lobby', titles[hwnd]): + if re.search('Full Tilt Poker', titles[hwnd]): + continue tw = Table_Window() tw.number = hwnd (x, y, width, height) = win32gui.GetWindowRect(hwnd) @@ -155,9 +154,9 @@ def discover_nt(c): tw.site = "Full Tilt" else: tw.site = "Unknown" - + sys.stderr.write("Found unknown table = %s" % tw.title) if not tw.site == "Unknown": - eval("%s(tw)" % c.supported_sites[s].decoder) + eval("%s(tw)" % c.supported_sites[tw.site].decoder) else: tw.name = "Unknown" tables[tw.name] = tw @@ -213,11 +212,12 @@ def fulltilt_decode_table(tw): title_bits = re.split(' - ', tw.title) name = title_bits[0] tw.tournament = None -# for pattern in [r' (6 max)', r' (heads up)', r' (deep)', r' (deep hu)', r' (deep 6)', -# r' (2)', r' (edu)', r' (edu, 6 max)', r' (6)' ]: -# name = re.sub(pattern, '', name) - (tw.name, trash) = name.split(r' (', 1) - tw.name = tw.name.rstrip() + for pattern in [r' \(6 max\)', r' \(heads up\)', r' \(deep\)', + r' \(deep hu\)', r' \(deep 6\)', r' \(2\)', + r' \(edu\)', r' \(edu, 6 max\)', r' \(6\)' ]: + name = re.sub(pattern, '', name) +# (tw.name, trash) = name.split(r' (', 1) + tw.name = name.rstrip() if __name__=="__main__": c = Configuration.Config() From 67ffba65a6066d0a7132feb02ae98ed70dfd10d0 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 25 Sep 2008 23:50:06 -0400 Subject: [PATCH 115/262] removed files added by mistake --- .project | 17 ++++ .pydevproject | 9 ++ testout.xml | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 .project create mode 100644 .pydevproject create mode 100644 testout.xml diff --git a/.project b/.project new file mode 100644 index 00000000..9baee632 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + HUD + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 00000000..9c88558e --- /dev/null +++ b/.pydevproject @@ -0,0 +1,9 @@ + + + + + +/HUD/src + +python 2.5 + diff --git a/testout.xml b/testout.xml new file mode 100644 index 00000000..589603bc --- /dev/null +++ b/testout.xml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 801d6ddb176e24cab2038d7b7c23318a8d746e32 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 25 Sep 2008 23:51:32 -0400 Subject: [PATCH 116/262] removed file added by mistake --- testout.xml | 224 ---------------------------------------------------- 1 file changed, 224 deletions(-) delete mode 100644 testout.xml diff --git a/testout.xml b/testout.xml deleted file mode 100644 index 589603bc..00000000 --- a/testout.xml +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From ce3187667c9664a476c3fa643708737e2766dd04 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 26 Sep 2008 13:01:57 +0100 Subject: [PATCH 117/262] p106 - fixed small blind reading --- pyfpdb/fpdb.py | 4 ++-- pyfpdb/fpdb_simple.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 98f0aa27..dbbc746f 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -18,8 +18,8 @@ import os import sys -errorfile = open('fpdb-error.log', 'w') -sys.stderr = errorfile +#errorfile = open('fpdb-error.log', 'w') +#sys.stderr = errorfile import pygtk pygtk.require('2.0') diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index dadeba99..73b71669 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1042,11 +1042,14 @@ def recogniseGametypeID(cursor, topline, smallBlindLine, site_id, category, isTo if (limit_type=="fl"): big_blind=small_bet - if smallBlindLine==topline: - raise fpdb_simple.FpdbError("invalid small blind line") + if base=="hold": + if smallBlindLine==topline: + raise FpdbError("invalid small blind line") + else: + pos=smallBlindLine.rfind("$")+1 + small_blind=float2int(smallBlindLine[pos:]) else: - pos=smallBlindLine.rfind("$")+1 - small_blind=float2int(smallBlindLine[pos:]) + small_blind=0 cursor.execute("""INSERT INTO Gametypes (siteId, type, base, category, limitType, hiLo, smallBlind, bigBlind, smallBet, bigBet) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", (site_id, type, base, category, limit_type, hiLo, small_blind, big_blind, small_bet, big_bet)) From b549aa2605a938ec9b914d609617cc1f73ba03ef Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 26 Sep 2008 14:18:47 +0100 Subject: [PATCH 118/262] p107 - HUD updates from ray, FTP now supported --- pyfpdb/Configuration.py | 0 pyfpdb/Database.py | 12 +-- pyfpdb/HUD_config.xml.example | 168 +++++++++++++++++++--------------- pyfpdb/HUD_main.py | 7 +- pyfpdb/Tables.py | 44 ++++++--- pyfpdb/fpdb.py | 4 +- 6 files changed, 135 insertions(+), 100 deletions(-) mode change 100755 => 100644 pyfpdb/Configuration.py diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py old mode 100755 new mode 100644 diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 6006a246..d634e134 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -32,16 +32,12 @@ import sys import Configuration import SQL -try: # pgdb database module for posgres via DB-API - import psycopg2 -except: - pass +import psycopg2 +# pgdb uses pyformat. is that fixed or an option? + # mysql bindings -try: - import MySQLdb -except: - pass +import MySQLdb class Database: def __init__(self, c, db_name, game): diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index 649493fa..423787c5 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -2,7 +2,7 @@ - + @@ -22,16 +22,16 @@ - - - + + + - - + + @@ -44,87 +44,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 15bbd034..a839c2a1 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -38,6 +38,9 @@ import os import thread import Queue +errorfile = open('HUD-error.txt', 'w') +sys.stderr = errorfile + # pyGTK modules import pygtk import gtk @@ -105,13 +108,13 @@ def producer(): # This is the thread function dataQueue.put(hand_no) # and puts result on the queue if __name__== "__main__": - print "HUD_main starting" + sys.stderr.write("HUD_main starting\n") try: db_name = sys.argv[1] except: db_name = 'fpdb-p' - print "Using db name = ", db_name + sys.stderr.write("Using db name = %s\n" % (db_name)) config = Configuration.Config() # db_connection = Database.Database(config, 'fpdb', 'holdem') diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index 41db0645..cb6209c8 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -42,7 +42,7 @@ class Table_Window: # __str__ method for testing temp = 'TableWindow object\n' temp = temp + " name = %s\n site = %s\n number = %s\n title = %s\n" % (self.name, self.site, self.number, self.title) - temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) +# temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) temp = temp + " width = %d\n height = %d\n x = %d\n y = %d\n" % (self.width, self.height, self.x, self.y) if getattr(self, 'tournament', 0): temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table) @@ -73,9 +73,10 @@ def discover_posix(c): tables = {} for listing in os.popen('xwininfo -root -tree').readlines(): # xwininfo -root -tree -id 0xnnnnn gets the info on a single window - if re.search('Lobby', listing): continue - if re.search('Instant Hand History', listing): continue - if not re.search('Logged In as ', listing): continue + if re.search('Lobby', listing): continue + if re.search('Instant Hand History', listing): continue + if not re.search('Logged In as ', listing, re.IGNORECASE): continue + if re.search('\"Full Tilt Poker\"', listing): continue # FTP Lobby for s in c.supported_sites.keys(): if re.search(c.supported_sites[s].table_finder, listing): mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) @@ -90,8 +91,6 @@ def discover_posix(c): tw.x = int (mo.group(5) ) tw.y = int (mo.group(6) ) tw.title = re.sub('\"', '', tw.title) -# this rather ugly hack makes my fake table used for debugging work - if tw.title == "PokerStars.py": continue # use this eval thingie to call the title bar decoder specified in the config file eval("%s(tw)" % c.supported_sites[s].decoder) @@ -138,9 +137,10 @@ def discover_nt(c): tables = {} win32gui.EnumWindows(win_enum_handler, titles) for hwnd in titles.keys(): - if re.search('Logged In as', titles[hwnd]) and not re.search('Lobby', titles[hwnd]): + if re.search('Logged In as', titles[hwnd], re.IGNORECASE) and not re.search('Lobby', titles[hwnd]): + if re.search('Full Tilt Poker', titles[hwnd]): + continue tw = Table_Window() -# tw.site = c.supported_sites[s].site_name tw.number = hwnd (x, y, width, height) = win32gui.GetWindowRect(hwnd) tw.title = titles[hwnd] @@ -148,10 +148,17 @@ def discover_nt(c): tw.height = int( height ) - b_width - tb_height tw.x = int( x ) + b_width tw.y = int( y ) + tb_height - eval("%s(tw)" % "pokerstars_decode_table") - tw.site = "PokerStars" - - + if re.search('Logged In as', titles[hwnd]): + tw.site = "PokerStars" + elif re.search('Logged In As', titles[hwnd]): + tw.site = "Full Tilt" + else: + tw.site = "Unknown" + sys.stderr.write("Found unknown table = %s" % tw.title) + if not tw.site == "Unknown": + eval("%s(tw)" % c.supported_sites[tw.site].decoder) + else: + tw.name = "Unknown" tables[tw.name] = tw return tables @@ -199,6 +206,19 @@ def pokerstars_decode_table(tw): elif tw.game in ('omaha', 'omaha hi/lo'): pass +def fulltilt_decode_table(tw): +# extract the table name OR the tournament number and table name from the title +# other info in title is redundant with data in the database + title_bits = re.split(' - ', tw.title) + name = title_bits[0] + tw.tournament = None + for pattern in [r' \(6 max\)', r' \(heads up\)', r' \(deep\)', + r' \(deep hu\)', r' \(deep 6\)', r' \(2\)', + r' \(edu\)', r' \(edu, 6 max\)', r' \(6\)' ]: + name = re.sub(pattern, '', name) +# (tw.name, trash) = name.split(r' (', 1) + tw.name = name.rstrip() + if __name__=="__main__": c = Configuration.Config() tables = discover(c) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index dbbc746f..98f0aa27 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -18,8 +18,8 @@ import os import sys -#errorfile = open('fpdb-error.log', 'w') -#sys.stderr = errorfile +errorfile = open('fpdb-error.log', 'w') +sys.stderr = errorfile import pygtk pygtk.require('2.0') From 622a00be96203d6ff017bb0e6017e3826a7619c4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 26 Sep 2008 14:39:24 +0100 Subject: [PATCH 119/262] p108 - These damn small blinds... all previously supported tourneys should be working again now --- pyfpdb/fpdb_simple.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 73b71669..2d809763 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1045,6 +1045,9 @@ def recogniseGametypeID(cursor, topline, smallBlindLine, site_id, category, isTo if base=="hold": if smallBlindLine==topline: raise FpdbError("invalid small blind line") + elif isTourney: + pos=smallBlindLine.rfind(" ")+1 + small_blind=int(smallBlindLine[pos:]) else: pos=smallBlindLine.rfind("$")+1 small_blind=float2int(smallBlindLine[pos:]) From 3e61bb1729558209bb9a8cdee3cb3085c4dad03d Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 26 Sep 2008 20:06:27 +0100 Subject: [PATCH 120/262] p109 - change of window title. this is alpha5 --- pyfpdb/fpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 98f0aa27..f767794c 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -420,7 +420,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha4+, p102 or higher") + self.window.set_title("Free Poker DB - version: alpha5, p109") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 4b432d7ccdef71bc1ace50394ef0a15aa597c748 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 26 Sep 2008 20:13:27 +0100 Subject: [PATCH 121/262] p110 - updated ebuild with new dependencies, this is now really alpha5 --- ...1.0_alpha4_p86.ebuild => fpdb-1.0_alpha5_p110.ebuild} | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) rename packaging/gentoo/{fpdb-1.0_alpha4_p86.ebuild => fpdb-1.0_alpha5_p110.ebuild} (87%) diff --git a/packaging/gentoo/fpdb-1.0_alpha4_p86.ebuild b/packaging/gentoo/fpdb-1.0_alpha5_p110.ebuild similarity index 87% rename from packaging/gentoo/fpdb-1.0_alpha4_p86.ebuild rename to packaging/gentoo/fpdb-1.0_alpha5_p110.ebuild index 839f578e..4e55238e 100644 --- a/packaging/gentoo/fpdb-1.0_alpha4_p86.ebuild +++ b/packaging/gentoo/fpdb-1.0_alpha5_p110.ebuild @@ -1,6 +1,7 @@ # Copyright 1999-2008 Gentoo Foundation +# Gentoo had nothing to do with the production of this ebuild, but I'm pre-emptively transferring all copyrights (as far as legally possible under my local jurisdiction) to them. # Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha2_p68.ebuild,v 1.0 2008/08/31 23:00:00 steffen@sycamoretest.info Exp $ +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha5_p110.ebuild,v 1.0 2008/09/26 steffen@sycamoretest.info Exp $ NEED_PYTHON=2.3 @@ -10,7 +11,6 @@ MY_P="fpdb-${PV}" DESCRIPTION="A database program to track your online poker games" HOMEPAGE="https://sourceforge.net/projects/fpdb/" SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2" -#SRC_URI="mirror://sourceforge/fpdb/fpdb-1.0_alpha3-p80.tar.bz2" LICENSE="AGPL-3" SLOT="0" @@ -21,7 +21,10 @@ IUSE="" RDEPEND="virtual/mysql dev-python/mysql-python >=x11-libs/gtk+-2.10 - dev-python/pygtk" + dev-python/pygtk + dev-python/numpy + dev-python/matplotlib" + DEPEND="${RDEPEND}" src_install() { From 2ccf3c6370e13bec92586935d44a08e99298ac8f Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Fri, 26 Sep 2008 18:21:38 -0500 Subject: [PATCH 122/262] support HUD_config in default location, in cwd, or passed by caller --- pyfpdb/Configuration.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 55b8ed2b..84439da8 100644 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -23,6 +23,8 @@ Handles HUD configuration files. ######################################################################## # Standard Library modules +import os +import sys import shutil import xml.dom.minidom from xml.dom.minidom import Node @@ -166,7 +168,35 @@ class Popup: return temp + "\n" class Config: - def __init__(self, file = 'HUD_config.xml'): + def __init__(self, file = None): + +# "file" is a path to an xml file with the fpdb/HUD configuration +# we check the existence of "file" and try to recover if it doesn't exist + + if not file == None: # configuration file path has been passed + if not os.path.exists(file): + print "Configuration file %s not found. Using defaults." % (file) + sys.stderr.write("Configuration file %s not found. Using defaults." % (file)) + file = None + +# if "file" is invalid or None, we look for a HUD_config in the cwd + if file == None: # configuration file path not passed or invalid + if os.path.exists('HUD_config.xml'): # there is a HUD_config in the cwd + file = 'HUD_config.xml' # so we use it + else: # no HUD_config in the cwd, look where it should be in the first place +# find the path to the default HUD_config for the current os + if os.name == 'posix': + config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'HUD_config.xml') + elif os.name == 'nt': + config_path = os.path.join(os.environ["APPDATA"], 'fpdb', 'HUD_config.xml') + else: config_path = False + + if config_path and os.path.exists(config_path): + file = config_path + else: + print "No HUD_config_xml found. Exiting" + sys.stderr.write("No HUD_config_xml found. Exiting") + sys.exit() doc = xml.dom.minidom.parse(file) From 4500f48aa78b3e3a34e1c23d98fdeb3cc6449809 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 29 Sep 2008 09:12:04 -0500 Subject: [PATCH 123/262] Handle xml parsing exceptions --- pyfpdb/Configuration.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 84439da8..4195ed52 100644 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -25,6 +25,7 @@ Handles HUD configuration files. # Standard Library modules import os import sys +import traceback import shutil import xml.dom.minidom from xml.dom.minidom import Node @@ -197,8 +198,14 @@ class Config: print "No HUD_config_xml found. Exiting" sys.stderr.write("No HUD_config_xml found. Exiting") sys.exit() - - doc = xml.dom.minidom.parse(file) + try: + doc = xml.dom.minidom.parse(file) + except: + print "Error parsing %s. See error log file." % (file) + traceback.print_exc(file=sys.stderr) + print "press enter to continue" + sys.stdin.readline() + sys.exit() self.doc = doc self.file = file From 968e9e3c5ac7be18f7466107a435fd39885f5a8b Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 29 Sep 2008 19:40:42 -0500 Subject: [PATCH 124/262] Close when stdin get eof --- pyfpdb/HUD_main.py | 4 ++++ 1 file changed, 4 insertions(+) mode change 100755 => 100644 pyfpdb/HUD_main.py diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py old mode 100755 new mode 100644 index a839c2a1..36fb1b9e --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -99,12 +99,16 @@ def check_stdin(db_name): def read_stdin(source, condition, db_name): new_hand_id = sys.stdin.readline() + if new_hand_id == "": + destroy() process_new_hand(new_hand_id, db_name) return True def producer(): # This is the thread function while True: hand_no = sys.stdin.readline() # reads stdin + if new_hand_id == "": + destroy() dataQueue.put(hand_no) # and puts result on the queue if __name__== "__main__": From 053b3a9b107a73c681e5a3587b99164c9a1a244b Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 29 Sep 2008 19:42:27 -0500 Subject: [PATCH 125/262] default db name should be fpdb --- pyfpdb/HUD_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 36fb1b9e..59dc53db 100644 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -117,7 +117,7 @@ if __name__== "__main__": try: db_name = sys.argv[1] except: - db_name = 'fpdb-p' + db_name = 'fpdb' sys.stderr.write("Using db name = %s\n" % (db_name)) config = Configuration.Config() From 31d40a627497bbbd28d40ac9f14f441f204af33e Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 29 Sep 2008 20:14:55 -0500 Subject: [PATCH 126/262] debugging output in read_stdin function, remove later!! --- pyfpdb/HUD_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 59dc53db..c4a84b55 100644 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -101,6 +101,7 @@ def read_stdin(source, condition, db_name): new_hand_id = sys.stdin.readline() if new_hand_id == "": destroy() + print "new_hand_id = ", new_hand_id process_new_hand(new_hand_id, db_name) return True From f1947bb6cb765114f71ff5bfd3c709cf7ba61d47 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sat, 4 Oct 2008 19:03:02 +0100 Subject: [PATCH 127/262] p111 - Database.py now works without unnecessary db libs. Configuration.py made executable. stderr now unbuffered. --- docs/known-bugs-and-planned-features.txt | 4 +++- pyfpdb/Configuration.py | 0 pyfpdb/Database.py | 10 ++++++++-- pyfpdb/HUD_main.py | 2 +- pyfpdb/fpdb.py | 7 ++++--- 5 files changed, 16 insertions(+), 7 deletions(-) mode change 100644 => 100755 pyfpdb/Configuration.py diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index e21ec833..6c1a0018 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,7 +1,7 @@ Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha5 (release 25Sep-2Oct) +alpha6 (release 3Oct-10Oct) ====== pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. check we're reading mucked cards from PS @@ -39,6 +39,8 @@ Many STTs won't import before beta =========== +autoimport doesnt seem to work with just one hand in the file +make Database.py display error if wrong or missing db lib Import draw (maybe without HudCache for a start) graphs for SD/F, W$wSF, W$@SD validate webpage diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py old mode 100644 new mode 100755 diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index d634e134..cbbfeec4 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -33,11 +33,17 @@ import Configuration import SQL # pgdb database module for posgres via DB-API -import psycopg2 +try: + import psycopg2 # pgdb uses pyformat. is that fixed or an option? +except: + pass +try: # mysql bindings -import MySQLdb + import MySQLdb +except: + pass class Database: def __init__(self, c, db_name, game): diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index a839c2a1..7aa2c8f3 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -38,7 +38,7 @@ import os import thread import Queue -errorfile = open('HUD-error.txt', 'w') +errorfile = open('HUD-error.txt', 'w', 0) sys.stderr = errorfile # pyGTK modules diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index f767794c..146211b4 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -18,8 +18,9 @@ import os import sys -errorfile = open('fpdb-error.log', 'w') -sys.stderr = errorfile +errorFile = open('fpdb-error.log', 'w', 0) +#errorFileObject = os.fdopen(errorFile)#, 'w', int(1)) +sys.stderr = errorFile import pygtk pygtk.require('2.0') @@ -420,7 +421,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha5, p109") + self.window.set_title("Free Poker DB - version: alpha6, p111") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 3b618723d23c4959d75d94a3263ccc778c6f29ff Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 4 Oct 2008 15:43:50 -0500 Subject: [PATCH 128/262] fix bug in saving layouts --- pyfpdb/Configuration.py | 4 ++++ pyfpdb/Database.py | 41 ++++++++++++++++++++++++++++------------- pyfpdb/Hud.py | 4 ++-- 3 files changed, 34 insertions(+), 15 deletions(-) mode change 100755 => 100644 pyfpdb/Hud.py diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 4195ed52..5a384b12 100644 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -247,6 +247,8 @@ class Config: def get_layout_node(self, site_node, layout): for layout_node in site_node.getElementsByTagName("layout"): + if layout_node.getAttribute("max") == None: + return None if int( layout_node.getAttribute("max") ) == int( layout ): return layout_node @@ -268,8 +270,10 @@ class Config: def edit_layout(self, site_name, max, width = None, height = None, fav_seat = None, locations = None): + print "max = ", max site_node = self.get_site_node(site_name) layout_node = self.get_layout_node(site_node, max) + if layout_node == None: return for i in range(1, max + 1): location_node = self.get_location_node(layout_node, i) location_node.setAttribute("x", str( locations[i-1][0] )) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index d634e134..8c7bee56 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -25,6 +25,7 @@ Create and manage the database objects. # Standard Library modules import sys +import traceback # pyGTK modules @@ -32,30 +33,44 @@ import sys import Configuration import SQL -# pgdb database module for posgres via DB-API -import psycopg2 -# pgdb uses pyformat. is that fixed or an option? - -# mysql bindings -import MySQLdb - class Database: def __init__(self, c, db_name, game): if c.supported_databases[db_name].db_server == 'postgresql': - self.connection = psycopg2.connect(host = c.supported_databases[db_name].db_ip, + # psycopg2 database module for posgres via DB-API + import psycopg2 + + try: + self.connection = psycopg2.connect(host = c.supported_databases[db_name].db_ip, user = c.supported_databases[db_name].db_user, password = c.supported_databases[db_name].db_pass, database = c.supported_databases[db_name].db_name) + except: + print "Error opening database connection %s. See error log file." % (file) + traceback.print_exc(file=sys.stderr) + print "press enter to continue" + sys.stdin.readline() + sys.exit() elif c.supported_databases[db_name].db_server == 'mysql': - self.connection = MySQLdb.connect(host = c.supported_databases[db_name].db_ip, + # mysql bindings + import MySQLdb + try: + self.connection = MySQLdb.connect(host = c.supported_databases[db_name].db_ip, user = c.supported_databases[db_name].db_user, passwd = c.supported_databases[db_name].db_pass, db = c.supported_databases[db_name].db_name) + except: + print "Error opening database connection %s. See error log file." % (file) + traceback.print_exc(file=sys.stderr) + print "press enter to continue" + sys.stdin.readline() + sys.exit() else: - print "Database not recognized." - return(0) + print "Database = %s not recognized." % (c.supported_databases[db_name].db_server) + sys.stderr.write("Database not recognized, exiting.\n") + print "press enter to continue" + sys.exit() self.type = c.supported_databases[db_name].db_type self.sql = SQL.Sql(game = game, type = self.type) @@ -155,8 +170,8 @@ class Database: if __name__=="__main__": c = Configuration.Config() -# db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem - db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem + db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem +# db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem # db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz # db_connection = Database(c, 'ptracks', 'razz') # postgres print "database connection object = ", db_connection.connection diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py old mode 100755 new mode 100644 index debfaa67..9c52dc26 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -105,8 +105,8 @@ class Hud: loc = self.stat_windows[sw].window.get_position() new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) new_layout.append(new_loc) - print new_layout - self.config.edit_layout(self.table.site, self.table.max, locations = new_layout) +# print new_layout + self.config.edit_layout(self.table.site, self.max, locations = new_layout) self.config.save() def create(self, hand, config): From 643f76ebb7949df2a8e8794e2809971259df34e3 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 4 Oct 2008 19:22:53 -0500 Subject: [PATCH 129/262] created accessor for database parameters --- pyfpdb/Configuration.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 5a384b12..d2504365 100644 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -25,6 +25,7 @@ Handles HUD configuration files. # Standard Library modules import os import sys +import string import traceback import shutil import xml.dom.minidom @@ -280,6 +281,24 @@ class Config: location_node.setAttribute("y", str( locations[i-1][1] )) self.supported_sites[site_name].layout[max].location[i] = ( locations[i-1][0], locations[i-1][1] ) + def get_db_parameters(self, name = None): + if name == None: name = 'fpdb' + db = {} + try: + db['databaseName'] = name + db['host'] = self.supported_databases[name].db_ip + db['user'] = self.supported_databases[name].db_user + db['password'] = self.supported_databases[name].db_pass + db['server'] = self.supported_databases[name].db_server + if string.lower(self.supported_databases[name].db_server) == 'mysql': + db['backend'] = 2 + elif string.lower(self.supported_databases[name].db_server) == 'postgresql': + db['backend'] = 3 + else: db['backend'] = 0 # this is big trouble + except: + pass + return db + if __name__== "__main__": c = Config() @@ -316,4 +335,6 @@ if __name__== "__main__": print "----------- END MUCKED WINDOW FORMATS -----------" c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) - c.save(file="testout.xml") \ No newline at end of file + c.save(file="testout.xml") + + print c.get_db_parameters() \ No newline at end of file From f9f58f88e6e30190c528f988cf6b5f40e052cded Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 5 Oct 2008 02:30:16 +0100 Subject: [PATCH 130/262] p112 - fix from ray that Hud sometimes didn't save positions --- pyfpdb/Hud.py | 4 ++-- pyfpdb/fpdb.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 pyfpdb/Hud.py diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py old mode 100755 new mode 100644 index debfaa67..9c52dc26 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -105,8 +105,8 @@ class Hud: loc = self.stat_windows[sw].window.get_position() new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) new_layout.append(new_loc) - print new_layout - self.config.edit_layout(self.table.site, self.table.max, locations = new_layout) +# print new_layout + self.config.edit_layout(self.table.site, self.max, locations = new_layout) self.config.save() def create(self, hand, config): diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 146211b4..2f2637ef 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -421,7 +421,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha6, p111") + self.window.set_title("Free Poker DB - version: alpha6+, p112 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 463432afbcfcd4daeaf75baab0e7a3533ee00d45 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 5 Oct 2008 04:45:53 +0100 Subject: [PATCH 131/262] p113 - fixed bug in parseWinLine that it would cut off the first digit for tourneys. --- pyfpdb/fpdb_simple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2d809763..2012f04f 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -938,7 +938,7 @@ def parseWinLine(line, site, names, winnings, isTourney): for i in range(len(names)): if (line.startswith(names[i].encode("latin-1"))): #found a winner if isTourney: - pos1=line.rfind("collected ")+11 + pos1=line.rfind("collected ")+10 if (site=="ftp"): pos2=line.find(")", pos1) elif (site=="ps"): From d29b4f19eaf62ccd6cabff5a6a9fbf600674a527 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 5 Oct 2008 04:55:22 +0100 Subject: [PATCH 132/262] p114 - fixed bug in parseAnteLine causing tourney hands where paying ante meant all in to fail --- pyfpdb/fpdb_simple.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2012f04f..2f5845e2 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -658,14 +658,20 @@ def parseActionType(line): #parses the ante out of the given line and checks which player paid it, updates antes accordingly. def parseAnteLine(line, site, names, antes): + #print "parseAnteLine line: ",line for i in range(len(names)): if (line.startswith(names[i].encode("latin-1"))): #found the ante'er pos=line.rfind("$")+1 if pos!=0: #found $, so must be ring antes[i]+=float2int(line[pos:]) else: - pos=line.rfind(" ")+1 - antes[i]+=int(line[pos:]) + if line.find("all-in")==-1: + pos=line.rfind(" ")+1 + antes[i]+=int(line[pos:]) + else: + pos1=line.rfind("ante")+5 + pos2=line.find(" ",pos1) + antes[i]+=int(line[pos1:pos2]) #end def parseAntes #returns the buyin of a tourney in cents From 1cd9eb898f76d200ba194c8bd4ae8db284a43c6f Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 5 Oct 2008 05:04:42 +0100 Subject: [PATCH 133/262] p115 - fixed bugs in tourney handling of playername with $ in it in these methods: parseActionAmount, parseAnteLines --- pyfpdb/fpdb_parse_logic.py | 4 ++-- pyfpdb/fpdb_simple.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 60cf16de..d9761d6b 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -87,7 +87,7 @@ def mainParser(db, cursor, site, category, hand): if (lineTypes[i]=="cards"): fpdb_simple.parseCardLine (site, category, lineStreets[i], hand[i], names, cardValues, cardSuits, boardValues, boardSuits) elif (lineTypes[i]=="action"): - fpdb_simple.parseActionLine (site, base, hand[i], lineStreets[i], playerIDs, names, actionTypes, actionAmounts, actionNos, actionTypeByNo) + fpdb_simple.parseActionLine (site, base, isTourney, hand[i], lineStreets[i], playerIDs, names, actionTypes, actionAmounts, actionNos, actionTypeByNo) elif (lineTypes[i]=="win"): fpdb_simple.parseWinLine (hand[i], site, names, winnings, isTourney) elif (lineTypes[i]=="rake"): @@ -99,7 +99,7 @@ def mainParser(db, cursor, site, category, hand): elif (lineTypes[i]=="header" or lineTypes[i]=="rake" or lineTypes[i]=="name" or lineTypes[i]=="ignore"): pass elif (lineTypes[i]=="ante"): - fpdb_simple.parseAnteLine(hand[i], site, names, antes) + fpdb_simple.parseAnteLine(hand[i], site, isTourney, names, antes) elif (lineTypes[i]=="table"): tableResult=fpdb_simple.parseTableLine(site, base, hand[i]) else: diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2f5845e2..a6fa7142 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -542,7 +542,7 @@ def isWinLine(line): #end def isWinLine #returns the amount of cash/chips put into the put in the given action line -def parseActionAmount(line, atype, site): +def parseActionAmount(line, atype, site, isTourney): if (line.endswith(" and is all-in")): line=line[:-14] elif (line.endswith(", and is all in")): @@ -573,8 +573,8 @@ def parseActionAmount(line, atype, site): pos=line.find("to $")+4 amount=float2int(line[pos:]) else: - pos=line.rfind("$")+1 - if pos!=0: + if not isTourney: + pos=line.rfind("$")+1 amount=float2int(line[pos:]) else: #print "line:"+line+"EOL" @@ -591,7 +591,7 @@ def parseActionAmount(line, atype, site): #doesnt return anything, simply changes the passed arrays action_types and # action_amounts. For stud this expects numeric streets (3-7), for # holdem/omaha it expects predeal, preflop, flop, turn or river -def parseActionLine(site, base, line, street, playerIDs, names, action_types, action_amounts, actionNos, actionTypeByNo): +def parseActionLine(site, base, isTourney, line, street, playerIDs, names, action_types, action_amounts, actionNos, actionTypeByNo): if (street=="predeal" or street=="preflop"): street=0 elif (street=="flop"): @@ -609,7 +609,7 @@ def parseActionLine(site, base, line, street, playerIDs, names, action_types, ac atype=parseActionType(line) playerno=recognisePlayerNo(line, names, atype) - amount=parseActionAmount(line, atype, site) + amount=parseActionAmount(line, atype, site, isTourney) action_types[street][playerno].append(atype) action_amounts[street][playerno].append(amount) @@ -657,12 +657,12 @@ def parseActionType(line): #end def parseActionType #parses the ante out of the given line and checks which player paid it, updates antes accordingly. -def parseAnteLine(line, site, names, antes): +def parseAnteLine(line, site, isTourney, names, antes): #print "parseAnteLine line: ",line for i in range(len(names)): if (line.startswith(names[i].encode("latin-1"))): #found the ante'er pos=line.rfind("$")+1 - if pos!=0: #found $, so must be ring + if not isTourney: antes[i]+=float2int(line[pos:]) else: if line.find("all-in")==-1: From 2ab942128b8144ce75317a13f91786af13da34f4 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 5 Oct 2008 05:33:22 +0100 Subject: [PATCH 134/262] p116 - fixed bug in fpdb_import.py that prevented it from running directly --- docs/known-bugs-and-planned-features.txt | 11 +++++------ pyfpdb/fpdb_import.py | 8 ++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 6c1a0018..718138a2 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,11 +1,10 @@ Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha6 (release 3Oct-10Oct) +alpha7 (release 11Oct-17Oct) ====== -pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. +(carl i think) pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. check we're reading mucked cards from PS -ebuild: support pgsql fix HUD config location and update release script accordingly (michael) update website for windows installer @@ -30,15 +29,15 @@ debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html howto remote DB move all user docs to webpage (steffen) contributor list on webpage -finish bringing back tourney +stud/razz tourneys, No river stats for stud games? hole/board cards are not correctly stored in the db for stud games HORSE (and presumably other mixed games) hand history files not handled correctly -Some MTTs won't import (rebuys??) -Many STTs won't import +rebuy/addon tourney support before beta =========== +ebuild: support pgsql autoimport doesnt seem to work with just one hand in the file make Database.py display error if wrong or missing db lib Import draw (maybe without HudCache for a start) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index f4fe56ab..9c52d6bc 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -56,17 +56,17 @@ def import_file_dict(options, settings, callHud=False): inputFile=open(options.inputFile, "rU") #connect to DB - if options.settings['db-backend'] == 2: + if settings['db-backend'] == 2: if not mysqlLibFound: raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") db = MySQLdb.connect(host = options.server, user = options.user, passwd = options.password, db = options.database) - elif options.settings['db-backend'] == 3: + elif settings['db-backend'] == 3: if not pgsqlLibFound: raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") db = psycopg2.connect(host = options.server, user = options.user, password = options.password, database = options.database) - elif options.settings['db-backend'] == 4: + elif settings['db-backend'] == 4: pass else: pass @@ -224,5 +224,5 @@ if __name__ == "__main__": (options, sys.argv) = parser.parse_args() - settings={'imp-callFpdbHud':False} + settings={'imp-callFpdbHud':False, 'db-backend':2} import_file_dict(options, settings, False) From ce5f6f1d7182a76213d2bc621f281c505959e34b Mon Sep 17 00:00:00 2001 From: steffen123 Date: Sun, 5 Oct 2008 06:22:31 +0100 Subject: [PATCH 135/262] p117 - fixed bug in HudCache generation that it stored L rather than E for position new blackbox regression testing data --- docs/known-bugs-and-planned-features.txt | 2 +- pyfpdb/fpdb_simple.py | 2 +- regression-test/PrintPlayerHudData.py | 3 +- .../ps-flags-B-1hands.expected.txt | 68 +++++++++++++++++++ regression-test/ps-flags-CBflop.expected.txt | 68 +++++++++++++++++++ .../ps-flags-M-2hands.expected.txt | 68 +++++++++++++++++++ regression-test/regression-test.sh | 8 ++- 7 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 regression-test/ps-flags-B-1hands.expected.txt create mode 100644 regression-test/ps-flags-CBflop.expected.txt create mode 100644 regression-test/ps-flags-M-2hands.expected.txt diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 718138a2..bd092291 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -17,7 +17,7 @@ update abbreviations.txt export settings[hud-defaultInterval] to conf fill check-/call-raise cache fields -printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt +printplayerflags on ps-lhe-ring-successful-steal-by-cutoff.txt, finish existing regression tests change to savannah? implement steal and positions in stud anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index a6fa7142..d80151b3 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1583,7 +1583,7 @@ def generateHudCacheData(player_ids, base, category, action_types, actionTypeByN elif pos>=2 and pos<=4: hudDataPositions.append('M') elif pos>=5 and pos<=7: - hudDataPositions.append('L') + hudDataPositions.append('E') ### RHH Added this elif to handle being a dead hand before the BB (pos==9) elif pos==9: hudDataPositions.append('X') diff --git a/regression-test/PrintPlayerHudData.py b/regression-test/PrintPlayerHudData.py index 351472b1..5b4b0599 100755 --- a/regression-test/PrintPlayerHudData.py +++ b/regression-test/PrintPlayerHudData.py @@ -29,7 +29,7 @@ parser.add_option("-e", "--seats", default="7", type="int", help="number of acti parser.add_option("-g", "--gameType", default="ring", help="Whether its a ringgame (ring) or a tournament (tour)") parser.add_option("-l", "--limit", "--limitType", default="fl", help="Limit Type, one of: nl, pl, fl, cn, cp") parser.add_option("-n", "--name", "--playername", default="Player_1", help="Name of the player to print") -parser.add_option("-o", "--position", default="B", help="Position, can be B, S, or a number between 0 and 7") +parser.add_option("-o", "--position", default="B", help="Position, can be B, S, D, C, M or E (see tabledesign.html)") parser.add_option("-p", "--password", help="The password for the MySQL user") parser.add_option("-s", "--site", default="PokerStars", help="Name of the site (as written in the history files)") @@ -54,6 +54,7 @@ cursor.execute("SELECT id FROM Players WHERE name=%s", (options.name,)) playerId=cursor.fetchone()[0] cursor.execute("SELECT id FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s",(gametypeId, playerId, options.seats, options.position)) +#print "debug: gametypeId:", gametypeId, "playerId:", playerId, "options.seats:", options.seats, "options.position:", options.position hudDataId=cursor.fetchone()[0] print "siteId:", siteId, "gametypeId:", gametypeId, "playerId:", playerId, "hudDataId:", hudDataId diff --git a/regression-test/ps-flags-B-1hands.expected.txt b/regression-test/ps-flags-B-1hands.expected.txt new file mode 100644 index 00000000..9b134e91 --- /dev/null +++ b/regression-test/ps-flags-B-1hands.expected.txt @@ -0,0 +1,68 @@ +Connected to MySQL on localhost. Print Player Flags Utility + +Basic Data +========== +bigblind: 2 category: holdem limitType: fl name: Player_5 gameType: ring site: PokerStars +siteId: 2 gametypeId: 1 playerId: 5 hudDataId: 12 + +HUD Raw Hand Counts +=================== +HDs: 1 +street0VPI: 0 +street0Aggr: 0 +street0_3B4BChance: 0 +street0_3B4BDone: 0 + +street1Seen: 1 +street2Seen: 1 +street3Seen: 1 +street4Seen: 0 +sawShowdown: 1 + +street1Aggr: 1 +street2Aggr: 1 +street3Aggr: 0 +street4Aggr: 0 + +otherRaisedStreet1: 0 +otherRaisedStreet2: 1 +otherRaisedStreet3: 1 +otherRaisedStreet4: 0 +foldToOtherRaisedStreet1: 0 +foldToOtherRaisedStreet2: 0 +foldToOtherRaisedStreet3: 0 +foldToOtherRaisedStreet4: 0 + +wonWhenSeenStreet1: 0 +wonAtSD: 0 +stealAttemptChance: 0 +stealAttempted: 0 +foldBbToStealChance: 0 +foldedBbToSteal: 0 +foldSbToStealChance: 0 +foldedSbToSteal: 0 +street1CBChance: 0 +street1CBDone: 0 +street2CBChance: 0 +street2CBDone: 0 +street3CBChance: 0 +street3CBDone: 0 +street4CBChance: 0 +street4CBDone: 0 +foldToStreet1CBChance: 0 +foldToStreet1CBDone: 0 +foldToStreet2CBChance: 0 +foldToStreet2CBDone: 0 +foldToStreet3CBChance: 0 +foldToStreet3CBDone: 0 +foldToStreet4CBChance: 0 +foldToStreet4CBDone: 0 +totalProfit: +street1CheckCallRaiseChance: +street1CheckCallRaiseDone: +street2CheckCallRaiseChance: +street2CheckCallRaiseDone: +street3CheckCallRaiseChance: +street3CheckCallRaiseDone: +street4CheckCallRaiseChance: 0 +street4CheckCallRaiseDone: 0 diff --git a/regression-test/ps-flags-CBflop.expected.txt b/regression-test/ps-flags-CBflop.expected.txt new file mode 100644 index 00000000..7fea235d --- /dev/null +++ b/regression-test/ps-flags-CBflop.expected.txt @@ -0,0 +1,68 @@ +Connected to MySQL on localhost. Print Player Flags Utility + +Basic Data +========== +bigblind: 25 category: holdem limitType: fl name: player3 gameType: ring site: PokerStars +siteId: 2 gametypeId: 2 playerId: 11 hudDataId: 22 + +HUD Raw Hand Counts +=================== +HDs: 1 +street0VPI: 1 +street0Aggr: 1 +street0_3B4BChance: 1 +street0_3B4BDone: 1 + +street1Seen: 1 +street2Seen: 1 +street3Seen: 1 +street4Seen: 0 +sawShowdown: 1 + +street1Aggr: 1 +street2Aggr: 0 +street3Aggr: 0 +street4Aggr: 0 + +otherRaisedStreet1: 0 +otherRaisedStreet2: +otherRaisedStreet3: +otherRaisedStreet4: 0 +foldToOtherRaisedStreet1: 0 +foldToOtherRaisedStreet2: 0 +foldToOtherRaisedStreet3: 0 +foldToOtherRaisedStreet4: 0 + +wonWhenSeenStreet1: 0.0 +wonAtSD: 0.0 +stealAttemptChance: 0 +stealAttempted: 0 +foldBbToStealChance: 0 +foldedBbToSteal: 0 +foldSbToStealChance: 0 +foldedSbToSteal: 0 +street1CBChance: 1 +street1CBDone: 1 +street2CBChance: 0 +street2CBDone: 0 +street3CBChance: 0 +street3CBDone: 0 +street4CBChance: 0 +street4CBDone: 0 +foldToStreet1CBChance: 0 +foldToStreet1CBDone: 0 +foldToStreet2CBChance: 0 +foldToStreet2CBDone: 0 +foldToStreet3CBChance: 0 +foldToStreet3CBDone: 0 +foldToStreet4CBChance: 0 +foldToStreet4CBDone: 0 +totalProfit: +street1CheckCallRaiseChance: 0 +street1CheckCallRaiseDone: 0 +street2CheckCallRaiseChance: 0 +street2CheckCallRaiseDone: 0 +street3CheckCallRaiseChance: 0 +street3CheckCallRaiseDone: 0 +street4CheckCallRaiseChance: 0 +street4CheckCallRaiseDone: 0 diff --git a/regression-test/ps-flags-M-2hands.expected.txt b/regression-test/ps-flags-M-2hands.expected.txt new file mode 100644 index 00000000..aa55e1c5 --- /dev/null +++ b/regression-test/ps-flags-M-2hands.expected.txt @@ -0,0 +1,68 @@ +Connected to MySQL on localhost. Print Player Flags Utility + +Basic Data +========== +bigblind: 2 category: holdem limitType: fl name: Player_1 gameType: ring site: PokerStars +siteId: 2 gametypeId: 1 playerId: 1 hudDataId: 8 + +HUD Raw Hand Counts +=================== +HDs: 2 +street0VPI: 0 +street0Aggr: 0 +street0_3B4BChance: 0 +street0_3B4BDone: 0 + +street1Seen: 0 +street2Seen: 0 +street3Seen: 0 +street4Seen: 0 +sawShowdown: 0 + +street1Aggr: 0 +street2Aggr: 0 +street3Aggr: 0 +street4Aggr: 0 + +otherRaisedStreet1: 0 +otherRaisedStreet2: 0 +otherRaisedStreet3: 0 +otherRaisedStreet4: 0 +foldToOtherRaisedStreet1: 0 +foldToOtherRaisedStreet2: 0 +foldToOtherRaisedStreet3: 0 +foldToOtherRaisedStreet4: 0 + +wonWhenSeenStreet1: 0.0 +wonAtSD: 0.0 +stealAttemptChance: 0 +stealAttempted: 0 +foldBbToStealChance: 0 +foldedBbToSteal: 0 +foldSbToStealChance: 0 +foldedSbToSteal: 0 +street1CBChance: 0 +street1CBDone: 0 +street2CBChance: 0 +street2CBDone: 0 +street3CBChance: 0 +street3CBDone: 0 +street4CBChance: 0 +street4CBDone: 0 +foldToStreet1CBChance: 0 +foldToStreet1CBDone: 0 +foldToStreet2CBChance: 0 +foldToStreet2CBDone: 0 +foldToStreet3CBChance: 0 +foldToStreet3CBDone: 0 +foldToStreet4CBChance: 0 +foldToStreet4CBDone: 0 +totalProfit: 0 +street1CheckCallRaiseChance: 0 +street1CheckCallRaiseDone: 0 +street2CheckCallRaiseChance: 0 +street2CheckCallRaiseDone: 0 +street3CheckCallRaiseChance: 0 +street3CheckCallRaiseDone: 0 +street4CheckCallRaiseChance: 0 +street4CheckCallRaiseDone: 0 diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index 5e0cfb9d..5bf93149 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -30,7 +30,13 @@ echo "it should've reported first that it stored 3, then that it had 3 duplicate ./PrintHand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt ./PrintHand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt -./PrintPlayerHudData.py -p$1 > ps-flags-3hands.found.txt && colordiff ps-flags-3hands.found.txt ps-flags-3hands.expected.txt +./PrintPlayerHudData.py -p$1 -oM > ps-flags-M-2hands.found.txt && colordiff ps-flags-M-2hands.found.txt ps-flags-M-2hands.expected.txt +./PrintPlayerHudData.py -p$1 -nPlayer_5 -oB > ps-flags-B-1hands.found.txt && colordiff ps-flags-B-1hands.found.txt ps-flags-B-1hands.expected.txt + + +../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-call-3B-preflop-cb-no2b.txt -x +echo "it should've now reported another successful store of 1 hand" +./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b25 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt #./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ftp.6367428246.found.txt && colordiff ftp.6367428246.found.txt ftp.6367428246.expected.txt From 2a90030982fa614b04504ab51eac0e925257d66b Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 6 Oct 2008 04:26:59 +0100 Subject: [PATCH 136/262] p118 - added all in field to HandsActions and parsing to importer. still need to update hudCache generation to use this tho expanded some todo prints and the graph missing lib error to clarify for users --- docs/known-bugs-and-planned-features.txt | 14 +++++--- docs/tabledesign.html | 5 +++ pyfpdb/GuiGraphViewer.py | 3 +- pyfpdb/fpdb.py | 7 ++-- pyfpdb/fpdb_db.py | 5 +-- pyfpdb/fpdb_parse_logic.py | 14 ++++---- pyfpdb/fpdb_save_to_db.py | 4 +-- pyfpdb/fpdb_simple.py | 42 +++++++++++++++++------- pyfpdb/schema.postgres.sql | 3 +- 9 files changed, 64 insertions(+), 33 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index bd092291..afbe3a5d 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -3,6 +3,8 @@ Please also see db-todo.txt alpha7 (release 11Oct-17Oct) ====== +fix bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin using the new all-in parsing + (carl i think) pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. check we're reading mucked cards from PS fix HUD config location and update release script accordingly @@ -18,9 +20,9 @@ export settings[hud-defaultInterval] to conf fill check-/call-raise cache fields printplayerflags on ps-lhe-ring-successful-steal-by-cutoff.txt, finish existing regression tests +update blackbox testing to include all-in change to savannah? implement steal and positions in stud -anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no separate db table design version and last bugfix in importer change tabledesign VALIGN and add link to webpage finish updating filelist @@ -29,14 +31,19 @@ debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html howto remote DB move all user docs to webpage (steffen) contributor list on webpage -stud/razz tourneys, +stud/razz tourneys No river stats for stud games? hole/board cards are not correctly stored in the db for stud games HORSE (and presumably other mixed games) hand history files not handled correctly -rebuy/addon tourney support +copy stderr rather than redirecting it http://aspn.activestate.com/ASPN/Mail/Message/python-list/3401682 before beta =========== +move any remaining all-in text filters into goesAllInOnThisLine +find solution for capped in parseActionAmount +anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no +rebuy/addon tourney support +convert ante to an action so that it can be all-in ebuild: support pgsql autoimport doesnt seem to work with just one hand in the file make Database.py display error if wrong or missing db lib @@ -62,7 +69,6 @@ move version into seperate file for fpdb gui and db SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug create little test script for people to run to verify successful installation of pydeps split hud data generation into separate for loops and make it more efficient -fix bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin - might as well add all-in recognition for this make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) make it work with postgres expand instructions for profile file diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 8d283f64..8fcf4884 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -747,6 +747,11 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    + + + + + diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 612ac2ff..e441f8ba 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -29,7 +29,8 @@ try: from numpy import arange, cumsum from pylab import * except: - print "Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib." + print "Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib if you want to use graphs." + print "This is of no consequence for other parts of the program, e.g. import and HUD are NOT affected by this problem." import fpdb_import import fpdb_db diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 2f2637ef..3943165f 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -19,7 +19,6 @@ import os import sys errorFile = open('fpdb-error.log', 'w', 0) -#errorFileObject = os.fdopen(errorFile)#, 'w', int(1)) sys.stderr = errorFile import pygtk @@ -347,11 +346,11 @@ class fpdb: #end def load_profile def not_implemented(self): - print "todo: called unimplemented menu entry"#remove this once more entries are implemented + print "todo: called unimplemented menu entry (users: pls ignore this)"#remove this once more entries are implemented #end def not_implemented def obtain_global_lock(self): - print "todo: implement obtain_global_lock" + print "todo: implement obtain_global_lock (users: pls ignore this)" #end def obtain_global_lock def quit(self, widget, data): @@ -421,7 +420,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha6+, p112 or higher") + self.window.set_title("Free Poker DB - version: alpha6+, p118 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 1b672bab..36e39ba9 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -48,7 +48,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=76: + if settings[0]!=118: print "outdated or too new database version - please recreate tables" self.wrongDbVersion=True except:# _mysql_exceptions.ProgrammingError: @@ -152,7 +152,7 @@ class fpdb_db: #end def get_db_info def fillDefaultData(self): - self.cursor.execute("INSERT INTO Settings VALUES (76);") + self.cursor.execute("INSERT INTO Settings VALUES (118);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") @@ -311,6 +311,7 @@ class fpdb_db: street SMALLINT NOT NULL, actionNo SMALLINT NOT NULL, action CHAR(5) NOT NULL, + allIn BOOLEAN NOT NULL, amount INT NOT NULL, comment TEXT, commentTs DATETIME)""") diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index d9761d6b..832c5a2b 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -30,7 +30,7 @@ def mainParser(db, cursor, site, category, hand): lineTypes=[] #char, valid values: header, name, cards, action, win, rake, ignore lineStreets=[] #char, valid values: (predeal, preflop, flop, turn, river) - cardValues, cardSuits, boardValues, boardSuits, antes, actionTypes, actionAmounts, actionNos, actionTypeByNo, seatLines, winnings, rakes=[], [],[],[],[],[],[],[],[],[],[],[] + cardValues, cardSuits, boardValues, boardSuits, antes, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo, seatLines, winnings, rakes=[],[],[],[],[],[],[],[],[],[],[],[],[] #part 1: read hand no and check for duplicate siteHandNo=fpdb_simple.parseSiteHandNo(hand[0]) @@ -76,7 +76,7 @@ def mainParser(db, cursor, site, category, hand): startCashes=tmp['startCashes'] seatNos=tmp['seatNos'] - fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, actionAmounts, actionNos, actionTypeByNo) + fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo) #3b read positions if base=="hold": @@ -87,7 +87,7 @@ def mainParser(db, cursor, site, category, hand): if (lineTypes[i]=="cards"): fpdb_simple.parseCardLine (site, category, lineStreets[i], hand[i], names, cardValues, cardSuits, boardValues, boardSuits) elif (lineTypes[i]=="action"): - fpdb_simple.parseActionLine (site, base, isTourney, hand[i], lineStreets[i], playerIDs, names, actionTypes, actionAmounts, actionNos, actionTypeByNo) + fpdb_simple.parseActionLine (site, base, isTourney, hand[i], lineStreets[i], playerIDs, names, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo) elif (lineTypes[i]=="win"): fpdb_simple.parseWinLine (hand[i], site, names, winnings, isTourney) elif (lineTypes[i]=="rake"): @@ -138,22 +138,22 @@ def mainParser(db, cursor, site, category, hand): if base=="hold": result = fpdb_save_to_db.tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteID, - siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) + siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) elif base=="stud": result = fpdb_save_to_db.tourney_stud(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, cardSuits, winnings, rakes, - actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) + actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) else: raise fpdb_simple.FpdbError ("unrecognised category") else: if base=="hold": - result = fpdb_save_to_db.ring_holdem_omaha(cursor, base, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) + result = fpdb_save_to_db.ring_holdem_omaha(cursor, base, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) elif base=="stud": result = fpdb_save_to_db.ring_stud(cursor, base, category, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, - cardSuits, winnings, rakes, actionTypes, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) + cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) else: raise fpdb_simple.FpdbError ("unrecognised category") db.commit() diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index c07fd225..dfde35dd 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -35,7 +35,7 @@ def ring_stud(cursor, base, category, site_hand_no, gametype_id, hand_start_time return hands_id #end def ring_stud -def ring_holdem_omaha(cursor, base, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): +def ring_holdem_omaha(cursor, base, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a holdem/omaha hand into the database""" fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) fpdb_simple.fill_board_cards(board_values, board_suits) @@ -48,7 +48,7 @@ def ring_holdem_omaha(cursor, base, category, site_hand_no, gametype_id, hand_st fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos) return hands_id #end def ring_holdem_omaha diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index d80151b3..3d2097c2 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -204,7 +204,7 @@ def convertCardValuesBoard(arr): #end def convertCardValuesBoard #this creates the 2D/3D arrays. manipulates the passed arrays instead of returning. -def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, action_amounts, actionNos, actionTypeByNo): +def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, allIns, action_amounts, actionNos, actionTypeByNo): for i in range(seats):#create second dimension arrays tmp=[] card_values.append(tmp) @@ -223,6 +223,8 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake tmp=[] action_types.append(tmp) tmp=[] + allIns.append(tmp) + tmp=[] action_amounts.append(tmp) tmp=[] actionNos.append(tmp) @@ -232,6 +234,8 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake tmp=[] action_types[i].append(tmp) tmp=[] + allIns[i].append(tmp) + tmp=[] action_amounts[i].append(tmp) tmp=[] actionNos[i].append(tmp) @@ -543,10 +547,10 @@ def isWinLine(line): #returns the amount of cash/chips put into the put in the given action line def parseActionAmount(line, atype, site, isTourney): - if (line.endswith(" and is all-in")): - line=line[:-14] - elif (line.endswith(", and is all in")): - line=line[:-15] + #if (line.endswith(" and is all-in")): + # line=line[:-14] + #elif (line.endswith(", and is all in")): + # line=line[:-15] if line.endswith(", and is capped"):#ideally we should recognise this as an all-in if category is capXl line=line[:-15] @@ -591,7 +595,7 @@ def parseActionAmount(line, atype, site, isTourney): #doesnt return anything, simply changes the passed arrays action_types and # action_amounts. For stud this expects numeric streets (3-7), for # holdem/omaha it expects predeal, preflop, flop, turn or river -def parseActionLine(site, base, isTourney, line, street, playerIDs, names, action_types, action_amounts, actionNos, actionTypeByNo): +def parseActionLine(site, base, isTourney, line, street, playerIDs, names, action_types, allIns, action_amounts, actionNos, actionTypeByNo): if (street=="predeal" or street=="preflop"): street=0 elif (street=="flop"): @@ -606,18 +610,32 @@ def parseActionLine(site, base, isTourney, line, street, playerIDs, names, actio for count in range(len(actionNos[street][player])): if actionNos[street][player][count]>=nextActionNo: nextActionNo=actionNos[street][player][count]+1 - + + line, allIn=goesAllInOnThisLine(line) atype=parseActionType(line) playerno=recognisePlayerNo(line, names, atype) amount=parseActionAmount(line, atype, site, isTourney) action_types[street][playerno].append(atype) + allIns[street][playerno].append(allIn) action_amounts[street][playerno].append(amount) actionNos[street][playerno].append(nextActionNo) tmp=(playerIDs[playerno], atype) actionTypeByNo[street].append(tmp) #end def parseActionLine +def goesAllInOnThisLine(line): + """returns whether the player went all-in on this line and removes the all-in text from the line.""" + isAllIn=False + if (line.endswith(" and is all-in")): + line=line[:-14] + isAllIn=True + elif (line.endswith(", and is all in")): + line=line[:-15] + isAllIn=True + return (line, isAllIn) +#end def goesAllInOnThisLine + #returns the action type code (see table design) of the given action line def parseActionType(line): if (line.startswith("Uncalled bet")): @@ -1185,14 +1203,14 @@ def splitRake(winnings, rakes, totalRake): rakes[i]=totalRake*winPortion #end def splitRake -def storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos): +def storeActions(cursor, handsPlayersIds, actionTypes, allIns, actionAmounts, actionNos): #stores into table hands_actions #print "start of storeActions, actionNos:",actionNos #print " action_amounts:",action_amounts - for i in range (len(action_types)): #iterate through streets - for j in range (len(action_types[i])): #iterate through names - for k in range (len(action_types[i][j])): #iterate through individual actions of that player on that street - cursor.execute ("INSERT INTO HandsActions (handPlayerId, street, actionNo, action, amount) VALUES (%s, %s, %s, %s, %s)", (hands_players_ids[j], i, actionNos[i][j][k], action_types[i][j][k], action_amounts[i][j][k])) + for i in range (len(actionTypes)): #iterate through streets + for j in range (len(actionTypes[i])): #iterate through names + for k in range (len(actionTypes[i][j])): #iterate through individual actions of that player on that street + cursor.execute ("INSERT INTO HandsActions (handPlayerId, street, actionNo, action, allIn, amount) VALUES (%s, %s, %s, %s, %s, %s)", (handsPlayersIds[j], i, actionNos[i][j][k], actionTypes[i][j][k], allIns[i][j][k], actionAmounts[i][j][k])) #end def storeActions def store_board_cards(cursor, hands_id, board_values, board_suits): diff --git a/pyfpdb/schema.postgres.sql b/pyfpdb/schema.postgres.sql index 74653ba6..2affb1c6 100644 --- a/pyfpdb/schema.postgres.sql +++ b/pyfpdb/schema.postgres.sql @@ -137,6 +137,7 @@ CREATE TABLE HandsActions ( street SMALLINT, actionNo SMALLINT, action CHAR(5), + allIn BOOLEAN, amount INT, comment TEXT, commentTs timestamp without time zone); @@ -211,7 +212,7 @@ CREATE TABLE HudCache ( street4CheckCallRaiseChance INT, street4CheckCallRaiseDone INT); -INSERT INTO Settings VALUES (76); +INSERT INTO Settings VALUES (118); INSERT INTO Sites ("name", currency) VALUES ('Full Tilt Poker', 'USD'); INSERT INTO Sites ("name", currency) VALUES ('PokerStars', 'USD'); INSERT INTO TourneyTypes (buyin, fee, knockout, rebuyOrAddon) VALUES (0, 0, 0, FALSE); From f1c94dac3ea7fba4f05d70a4cace97e4ba801133 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 6 Oct 2008 05:19:55 +0100 Subject: [PATCH 137/262] p119 - fixed bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin using the new all-in parsing moved most of known bugs to wiki roadmap (not actually online yet since sf seems to malfunctioning..) --- docs/known-bugs-and-planned-features.txt | 92 +----------------------- pyfpdb/fpdb_parse_logic.py | 4 +- pyfpdb/fpdb_simple.py | 24 +++++-- 3 files changed, 23 insertions(+), 97 deletions(-) diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index afbe3a5d..a8a3f988 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -1,104 +1,17 @@ Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha7 (release 11Oct-17Oct) -====== -fix bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin using the new all-in parsing - -(carl i think) pgsql recreate doesnt work, and it may not store version field on creation if using sql file with pgadmin. -check we're reading mucked cards from PS -fix HUD config location and update release script accordingly - -(michael) update website for windows installer -(steffen) update install-in-gentoo on website -(steffen) update ebuild and ubuntu guide for HUD_config.xml - -(steffen) store raw hand in db and write reimport function using the raw hand field -make sure totalProfit shows actual profit rather than winnings. -update abbreviations.txt -export settings[hud-defaultInterval] to conf -fill check-/call-raise cache fields - -printplayerflags on ps-lhe-ring-successful-steal-by-cutoff.txt, finish existing regression tests -update blackbox testing to include all-in -change to savannah? -implement steal and positions in stud -separate db table design version and last bugfix in importer -change tabledesign VALIGN and add link to webpage -finish updating filelist -finish todos in git instructions -debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html -howto remote DB -move all user docs to webpage -(steffen) contributor list on webpage -stud/razz tourneys -No river stats for stud games? -hole/board cards are not correctly stored in the db for stud games -HORSE (and presumably other mixed games) hand history files not handled correctly -copy stderr rather than redirecting it http://aspn.activestate.com/ASPN/Mail/Message/python-list/3401682 - before beta =========== -move any remaining all-in text filters into goesAllInOnThisLine -find solution for capped in parseActionAmount -anonymiser script to generate testdata without making a dozen find&replace all... remember to replace hand no with running no -rebuy/addon tourney support -convert ante to an action so that it can be all-in -ebuild: support pgsql -autoimport doesnt seem to work with just one hand in the file -make Database.py display error if wrong or missing db lib -Import draw (maybe without HudCache for a start) -graphs for SD/F, W$wSF, W$@SD -validate webpage -make linux use /etc/fpdb for config first, then ~/.fpdb. -FTP file with only one partial hand causes error -No Full Tilt support in HUD -HUD stat windows are too big on Windows -HUD task bar entries on Windows won't go away -MTT/STT not tested in HUD -HUD stats not aggregated -Player names with non-Latin chars throw warnings in HUD -HUD doesn't start when fpdb is started from the Windows "Start Menu" -Exiting HUD on Windows doesn't properly clean up stat windows +change to savannah? + -ebuild: USE gtk, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, git-ebuild, get it into sunrise -make hud display W$SD etc as fraction. -add dedicated update page -update status or make a support matrix table for website -move version into seperate file for fpdb gui and db -SD/F, W$wsF, W$@SD too low as reported by daedal in 2+2 forum on 12/13aug -create little test script for people to run to verify successful installation of pydeps -split hud data generation into separate for loops and make it more efficient -make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) -make it work with postgres -expand instructions for profile file -maybe remove siteId from gametypes -?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? -rakeback/frequent player points -skins -separate all gui and all processing into files that are named accordingly -ensure that there is only one db handle flying around and that its state is handled properly, ie. by the GUI. i think that might be why we have to reconnect the DB in tableviewer. -why do we have to reconnect in tv.read_names_clicked? -implement error file in importer -catch index error, type error, file not found error -HUD: use different colours according to classification. -move prepare-git.sh and create-release.sh to utils -offer not storing db password -change definition of bet to exclude bring in -fix GUI's load profile -config wizard -file permission script, use games group -make bulk importer display a grand total in the GUI -change save_to_db into one method and probably move into parse_logic Any comment or print with "todo" in it in the sourcecode except what is marked todo in the menu make a quick benchmark of mysql and postgresql: import of my whole db, some tableviewer refreshes with and without updated file Make tab and enter work as sensible in GUIs and implement Ctrl+Q, Ctrl+X and Alt+F4 for close. use profile file for bulk import and table viewer settings and pathes -handle errors properly, in particular wrt to SQL rollback. check that we read sitout correctly in: Full Tilt Poker Game #6150325318: Table Bogside -setup database, database-user and permission from GUI. -update prepare-git to check for license header and copyright. verify at least 2 or 3 sng hands no rush but before 1.0RC @@ -122,7 +35,6 @@ can wait till 1.x ================= in all importer: stop doing if site=="ftp", make class constants for site_id instead It treats fold due to disconnect as voluntary fold which is not ideal -check for unnecessary db.commit() aliases repair hands where the seat lines are missing, happens when observing at FTP flags for storing the reason for winning (best hi, tie for best low, etc.) to DB. not sure actually if this is such a good idea remember that there can be multiple reasons for the same player in the same hand diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 832c5a2b..8ee8f5b2 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -126,9 +126,9 @@ def mainParser(db, cursor, site, category, hand): totalWinnings+=winnings[i] if base=="hold": - hudImportData=fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes, actionTypeByNo, winnings, totalWinnings, positions) + hudImportData=fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes, allIns, actionTypeByNo, winnings, totalWinnings, positions) else: - hudImportData=fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes, actionTypeByNo, winnings, totalWinnings, None) + hudImportData=fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes, allIns, actionTypeByNo, winnings, totalWinnings, None) if isTourney: ranks=[] diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 3d2097c2..5d26df28 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1341,7 +1341,7 @@ def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, return result #end def store_hands_players_stud_tourney -def generateHudCacheData(player_ids, base, category, action_types, actionTypeByNo, winnings, totalWinnings, positions): +def generateHudCacheData(player_ids, base, category, action_types, allIns, actionTypeByNo, winnings, totalWinnings, positions): """calculates data for the HUD during import. IMPORTANT: if you change this method make sure to also change the following storage method and table_viewer.prepare_data if necessary""" #setup subarrays of the result dictionary. street0VPI=[] @@ -1489,14 +1489,28 @@ def generateHudCacheData(player_ids, base, category, action_types, actionTypeByN if myStealAttempted: someoneStole=True - + + #calculate saw* values - if (len(action_types[1][player])>0): + isAllIn=False + for i in range(len(allIns[0][player])): + if allIns[0][player][i]: + isAllIn=True + if (len(action_types[1][player])>0 or isAllIn): myStreet1Seen=True - if (len(action_types[2][player])>0): + + for i in range(len(allIns[1][player])): + if allIns[1][player][i]: + isAllIn=True + if (len(action_types[2][player])>0 or isAllIn): myStreet2Seen=True - if (len(action_types[3][player])>0): + + for i in range(len(allIns[2][player])): + if allIns[2][player][i]: + isAllIn=True + if (len(action_types[3][player])>0 or isAllIn): myStreet3Seen=True + mySawShowdown=True for count in range (len(action_types[3][player])): if action_types[3][player][count]=="fold": From 31ab863b47996df125f7a034608a6e52ff0f7906 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 7 Oct 2008 02:21:43 +0100 Subject: [PATCH 138/262] p120 - minor tv fix, renamed fpdb error, added notice about it to output. fixed tourney and stud to run again. --- pyfpdb/GuiTableViewer.py | 1 + pyfpdb/fpdb.py | 5 +++-- pyfpdb/fpdb_save_to_db.py | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 3714138e..15a21149 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -20,6 +20,7 @@ import pygtk pygtk.require('2.0') import gtk import os +import fpdb_simple try: import MySQLdb diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 3943165f..841d71c0 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -18,7 +18,8 @@ import os import sys -errorFile = open('fpdb-error.log', 'w', 0) +print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." +errorFile = open('fpdb-error-log.txt', 'w', 0) sys.stderr = errorFile import pygtk @@ -444,7 +445,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") ("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), ("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), - ("/Viewers/Poker_table Viewer", "T", self.tab_table_viewer, 0, None ), + ("/Viewers/Poker_table Viewer (obselete)", "T", self.tab_table_viewer, 0, None ), #( "/Viewers/Tourney Replayer ( "/_Database", None, None, 0, "" ), ( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ), diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index dfde35dd..5a06b1aa 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -21,7 +21,7 @@ import fpdb_simple #stores a stud/razz hand into the database -def ring_stud(cursor, base, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): +def ring_stud(cursor, base, category, site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) @@ -31,7 +31,7 @@ def ring_stud(cursor, base, category, site_hand_no, gametype_id, hand_start_time fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos) return hands_id #end def ring_stud @@ -53,7 +53,7 @@ def ring_holdem_omaha(cursor, base, category, site_hand_no, gametype_id, hand_st #end def ring_holdem_omaha def tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId, siteId, #end of tourney specific params - site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): + site_hand_no, gametype_id, hand_start_time, names, player_ids, start_cashes, positions, card_values, card_suits, board_values, board_suits, winnings, rakes, action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a tourney holdem/omaha hand into the database""" fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) fpdb_simple.fill_board_cards(board_values, board_suits) @@ -69,7 +69,7 @@ def tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knoc fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts, actionNos) + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos) return hands_id #end def tourney_holdem_omaha @@ -77,7 +77,7 @@ def tourney_stud(cursor, base, category, site_tourney_no, buyin, fee, knockout, tourney_start, payin_amounts, ranks, #end of tourney specific params site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, - action_types, action_amounts, hudImportData): + action_types, allIns, action_amounts, hudImportData): #stores a tourney stud/razz hand into the database fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) @@ -92,6 +92,6 @@ def tourney_stud(cursor, base, category, site_tourney_no, buyin, fee, knockout, fpdb_simple.storeHudData(cursor, base, category, player_ids, hudImportData) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, action_amounts) + fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts) return hands_id #end def tourney_stud From ea022a6497b8fd8b5b35adc86d72d0155162cd08 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Tue, 7 Oct 2008 05:18:26 +0100 Subject: [PATCH 139/262] p121 - fixed typo in stats.py causing wrong display of fold to SB %. note that this did not affect the DB data, just display --- pyfpdb/SQL.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index 072d478e..91ebd05b 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -200,10 +200,10 @@ class Sql: sum(wonAtSD) AS wmsd, sum(stealAttemptChance) AS steal_opp, sum(stealAttempted) AS steal, - sum(foldBbToStealChance) AS SBstolen, - sum(foldedBbToSteal) AS BBnotDef, - sum(foldBbToStealChance) AS BBstolen, + sum(foldSbToStealChance) AS SBstolen, sum(foldedSbToSteal) AS SBnotDef, + sum(foldBbToStealChance) AS BBstolen, + sum(foldedBbToSteal) AS BBnotDef, sum(street1CBChance) AS CB_opp_1, sum(street1CBDone) AS CB_1, sum(street2CBChance) AS CB_opp_2, From d67f014bc937f73781d4ab4c5da0503e80b421f8 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 7 Oct 2008 00:04:52 -0400 Subject: [PATCH 140/262] removed mistaken files --- .project | 17 ----------------- .pydevproject | 9 --------- 2 files changed, 26 deletions(-) delete mode 100644 .project delete mode 100644 .pydevproject diff --git a/.project b/.project deleted file mode 100644 index 9baee632..00000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - HUD - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index 9c88558e..00000000 --- a/.pydevproject +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -/HUD/src - -python 2.5 - From 9a4a17c32f3f4d75396f89f9b1be5bd22bd3063c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 7 Oct 2008 00:08:02 -0400 Subject: [PATCH 141/262] Removed files from root that should be only in pyfpdb --- Configuration.py | 278 ------------------ Database.py | 183 ------------ HUD_config.xml.example | 146 ---------- HUD_main.py | 140 ---------- HandHistory.py | 194 ------------- Hud.py | 449 ------------------------------ Mucked.py | 243 ---------------- SQL.py | 290 ------------------- Stats.py | 618 ----------------------------------------- Tables.py | 231 --------------- 10 files changed, 2772 deletions(-) delete mode 100644 Configuration.py delete mode 100644 Database.py delete mode 100644 HUD_config.xml.example delete mode 100755 HUD_main.py delete mode 100644 HandHistory.py delete mode 100755 Hud.py delete mode 100644 Mucked.py delete mode 100644 SQL.py delete mode 100644 Stats.py delete mode 100644 Tables.py diff --git a/Configuration.py b/Configuration.py deleted file mode 100644 index 55b8ed2b..00000000 --- a/Configuration.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python -"""Configuration.py - -Handles HUD configuration files. -""" -# Copyright 2008, Ray E. Barker - -# -# 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 - -######################################################################## - -# Standard Library modules -import shutil -import xml.dom.minidom -from xml.dom.minidom import Node - -class Layout: - def __init__(self, max): - self.max = int(max) - self.location = [] - for i in range(self.max + 1): self.location.append(None) - - def __str__(self): - temp = " Layout = %d max, width= %d, height = %d, fav_seat = %d\n" % (self.max, self.width, self.height, self.fav_seat) - temp = temp + " Locations = " - for i in range(1, len(self.location)): - temp = temp + "(%d,%d)" % self.location[i] - - return temp + "\n" - -class Site: - def __init__(self, node): - self.site_name = node.getAttribute("site_name") - self.table_finder = node.getAttribute("table_finder") - self.screen_name = node.getAttribute("screen_name") - self.site_path = node.getAttribute("site_path") - self.HH_path = node.getAttribute("HH_path") - self.decoder = node.getAttribute("decoder") - self.layout = {} - - for layout_node in node.getElementsByTagName('layout'): - max = int( layout_node.getAttribute('max') ) - lo = Layout(max) - lo.fav_seat = int( layout_node.getAttribute('fav_seat') ) - lo.width = int( layout_node.getAttribute('width') ) - lo.height = int( layout_node.getAttribute('height') ) - - for location_node in layout_node.getElementsByTagName('location'): - lo.location[int( location_node.getAttribute('seat') )] = (int( location_node.getAttribute('x') ), int( location_node.getAttribute('y'))) - - self.layout[lo.max] = lo - - def __str__(self): - temp = "Site = " + self.site_name + "\n" - for key in dir(self): - if key.startswith('__'): continue - if key == 'layout': continue - value = getattr(self, key) - if callable(value): continue - temp = temp + ' ' + key + " = " + value + "\n" - - for layout in self.layout: - temp = temp + "%s" % self.layout[layout] - - return temp - -class Stat: - def __init__(self): - pass - - def __str__(self): - temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s, popup = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click, self.popup) - return temp - -class Game: - def __init__(self, node): - self.game_name = node.getAttribute("game_name") - self.db = node.getAttribute("db") - self.rows = int( node.getAttribute("rows") ) - self.cols = int( node.getAttribute("cols") ) - - self.stats = {} - for stat_node in node.getElementsByTagName('stat'): - stat = Stat() - stat.stat_name = stat_node.getAttribute("stat_name") - stat.row = int( stat_node.getAttribute("row") ) - stat.col = int( stat_node.getAttribute("col") ) - stat.tip = stat_node.getAttribute("tip") - stat.click = stat_node.getAttribute("click") - stat.popup = stat_node.getAttribute("popup") - - self.stats[stat.stat_name] = stat - - def __str__(self): - temp = "Game = " + self.game_name + "\n" - temp = temp + " db = %s\n" % self.db - temp = temp + " rows = %d\n" % self.rows - temp = temp + " cols = %d\n" % self.cols - - for stat in self.stats.keys(): - temp = temp + "%s" % self.stats[stat] - - return temp - -class Database: - def __init__(self, node): - self.db_name = node.getAttribute("db_name") - self.db_server = node.getAttribute("db_server") - self.db_ip = node.getAttribute("db_ip") - self.db_user = node.getAttribute("db_user") - self.db_type = node.getAttribute("db_type") - self.db_pass = node.getAttribute("db_pass") - - def __str__(self): - temp = 'Database = ' + self.db_name + '\n' - for key in dir(self): - if key.startswith('__'): continue - value = getattr(self, key) - if callable(value): continue - temp = temp + ' ' + key + " = " + value + "\n" - return temp - -class Mucked: - def __init__(self, node): - self.name = node.getAttribute("mw_name") - self.cards = node.getAttribute("deck") - self.card_wd = node.getAttribute("card_wd") - self.card_ht = node.getAttribute("card_ht") - self.rows = node.getAttribute("rows") - self.cols = node.getAttribute("cols") - self.format = node.getAttribute("stud") - - def __str__(self): - temp = 'Mucked = ' + self.name + "\n" - for key in dir(self): - if key.startswith('__'): continue - value = getattr(self, key) - if callable(value): continue - temp = temp + ' ' + key + " = " + value + "\n" - return temp - -class Popup: - def __init__(self, node): - self.name = node.getAttribute("pu_name") - self.pu_stats = [] - for stat_node in node.getElementsByTagName('pu_stat'): - self.pu_stats.append(stat_node.getAttribute("pu_stat_name")) - - def __str__(self): - temp = "Popup = " + self.name + "\n" - for stat in self.pu_stats: - temp = temp + " " + stat - return temp + "\n" - -class Config: - def __init__(self, file = 'HUD_config.xml'): - - doc = xml.dom.minidom.parse(file) - - self.doc = doc - self.file = file - self.supported_sites = {} - self.supported_games = {} - self.supported_databases = {} - self.mucked_windows = {} - self.popup_windows = {} - -# s_sites = doc.getElementsByTagName("supported_sites") - for site_node in doc.getElementsByTagName("site"): - site = Site(node = site_node) - self.supported_sites[site.site_name] = site - - s_games = doc.getElementsByTagName("supported_games") - for game_node in doc.getElementsByTagName("game"): - game = Game(node = game_node) - self.supported_games[game.game_name] = game - - s_dbs = doc.getElementsByTagName("supported_databases") - for db_node in doc.getElementsByTagName("database"): - db = Database(node = db_node) - self.supported_databases[db.db_name] = db - - s_dbs = doc.getElementsByTagName("mucked_windows") - for mw_node in doc.getElementsByTagName("mw"): - mw = Mucked(node = mw_node) - self.mucked_windows[mw.name] = mw - - s_dbs = doc.getElementsByTagName("popup_windows") - for pu_node in doc.getElementsByTagName("pu"): - pu = Popup(node = pu_node) - self.popup_windows[pu.name] = pu - - def get_site_node(self, site): - for site_node in self.doc.getElementsByTagName("site"): - if site_node.getAttribute("site_name") == site: - return site_node - - def get_layout_node(self, site_node, layout): - for layout_node in site_node.getElementsByTagName("layout"): - if int( layout_node.getAttribute("max") ) == int( layout ): - return layout_node - - def get_location_node(self, layout_node, seat): - for location_node in layout_node.getElementsByTagName("location"): - if int( location_node.getAttribute("seat") ) == int( seat ): - return location_node - - def save(self, file = None): - if not file == None: - f = open(file, 'w') - self.doc.writexml(f) - f.close() - else: - shutil.move(self.file, self.file+".backup") - f = open(self.file, 'w') - self.doc.writexml(f) - f.close - - def edit_layout(self, site_name, max, width = None, height = None, - fav_seat = None, locations = None): - site_node = self.get_site_node(site_name) - layout_node = self.get_layout_node(site_node, max) - for i in range(1, max + 1): - location_node = self.get_location_node(layout_node, i) - location_node.setAttribute("x", str( locations[i-1][0] )) - location_node.setAttribute("y", str( locations[i-1][1] )) - self.supported_sites[site_name].layout[max].location[i] = ( locations[i-1][0], locations[i-1][1] ) - -if __name__== "__main__": - c = Config() - - print "\n----------- SUPPORTED SITES -----------" - for s in c.supported_sites.keys(): - print c.supported_sites[s] - - print "----------- END SUPPORTED SITES -----------" - - - print "\n----------- SUPPORTED GAMES -----------" - for game in c.supported_games.keys(): - print c.supported_games[game] - - print "----------- END SUPPORTED GAMES -----------" - - - print "\n----------- SUPPORTED DATABASES -----------" - for db in c.supported_databases.keys(): - print c.supported_databases[db] - - print "----------- END SUPPORTED DATABASES -----------" - - print "\n----------- MUCKED WINDOW FORMATS -----------" - for w in c.mucked_windows.keys(): - print c.mucked_windows[w] - - print "----------- END MUCKED WINDOW FORMATS -----------" - - print "\n----------- POPUP WINDOW FORMATS -----------" - for w in c.popup_windows.keys(): - print c.popup_windows[w] - - print "----------- END MUCKED WINDOW FORMATS -----------" - - c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) - c.save(file="testout.xml") \ No newline at end of file diff --git a/Database.py b/Database.py deleted file mode 100644 index d634e134..00000000 --- a/Database.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python -"""Database.py - -Create and manage the database objects. -""" -# Copyright 2008, Ray E. Barker -# -# 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 - -######################################################################## - -# postmaster -D /var/lib/pgsql/data - -# Standard Library modules -import sys - -# pyGTK modules - -# FreePokerTools modules -import Configuration -import SQL - -# pgdb database module for posgres via DB-API -import psycopg2 -# pgdb uses pyformat. is that fixed or an option? - -# mysql bindings -import MySQLdb - -class Database: - def __init__(self, c, db_name, game): - if c.supported_databases[db_name].db_server == 'postgresql': - self.connection = psycopg2.connect(host = c.supported_databases[db_name].db_ip, - user = c.supported_databases[db_name].db_user, - password = c.supported_databases[db_name].db_pass, - database = c.supported_databases[db_name].db_name) - - elif c.supported_databases[db_name].db_server == 'mysql': - self.connection = MySQLdb.connect(host = c.supported_databases[db_name].db_ip, - user = c.supported_databases[db_name].db_user, - passwd = c.supported_databases[db_name].db_pass, - db = c.supported_databases[db_name].db_name) - - else: - print "Database not recognized." - return(0) - - self.type = c.supported_databases[db_name].db_type - self.sql = SQL.Sql(game = game, type = self.type) - - def close_connection(self): - self.connection.close() - - def get_table_name(self, hand_id): - c = self.connection.cursor() - c.execute(self.sql.query['get_table_name'], (hand_id, )) - row = c.fetchone() - return row - - def get_last_hand(self): - c = self.connection.cursor() - c.execute(self.sql.query['get_last_hand']) - row = c.fetchone() - return row[0] - - def get_xml(self, hand_id): - c = self.connection.cursor() - c.execute(self.sql.query['get_xml'], (hand_id)) - row = c.fetchone() - return row[0] - - def get_recent_hands(self, last_hand): - c = self.connection.cursor() - c.execute(self.sql.query['get_recent_hands'], {'last_hand': last_hand}) - return c.fetchall() - - def get_hand_info(self, new_hand_id): - c = self.connection.cursor() - c.execute(self.sql.query['get_hand_info'], new_hand_id) - return c.fetchall() - -# def get_cards(self, hand): -# this version is for the PTrackSv2 db -# c = self.connection.cursor() -# c.execute(self.sql.query['get_cards'], hand) -# colnames = [desc[0] for desc in c.description] -# cards = {} -# for row in c.fetchall(): -# s_dict = {} -# for name, val in zip(colnames, row): -# s_dict[name] = val -# cards[s_dict['seat_number']] = s_dict -# return (cards) - - def get_cards(self, hand): -# this version is for the fpdb db - c = self.connection.cursor() - c.execute(self.sql.query['get_cards'], hand) - colnames = [desc[0] for desc in c.description] - cards = {} - for row in c.fetchall(): - s_dict = {} - for name, val in zip(colnames, row): - s_dict[name] = val - cards[s_dict['seat_number']] = s_dict - return (cards) - - def get_stats_from_hand(self, hand, player_id = False): - c = self.connection.cursor() - - if not player_id: player_id = "%" -# get the players in the hand and their seats -# c.execute(self.sql.query['get_players_from_hand'], (hand, player_id)) - c.execute(self.sql.query['get_players_from_hand'], (hand, )) - names = {} - seats = {} - for row in c.fetchall(): - names[row[0]] = row[2] - seats[row[0]] = row[1] - -# now get the stats -# c.execute(self.sql.query['get_stats_from_hand'], (hand, hand, player_id)) - c.execute(self.sql.query['get_stats_from_hand'], (hand, hand)) - colnames = [desc[0] for desc in c.description] - stat_dict = {} - for row in c.fetchall(): - t_dict = {} - for name, val in zip(colnames, row): - t_dict[name] = val -# print t_dict - t_dict['screen_name'] = names[t_dict['player_id']] - t_dict['seat'] = seats[t_dict['player_id']] - stat_dict[t_dict['player_id']] = t_dict - return stat_dict - - def get_player_id(self, config, site, player_name): - print "site = %s, player name = %s" % (site, player_name) - c = self.connection.cursor() - c.execute(self.sql.query['get_player_id'], {'player': player_name, 'site': site}) - row = c.fetchone() - return row[0] - -if __name__=="__main__": - c = Configuration.Config() - -# db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem - db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem -# db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz -# db_connection = Database(c, 'ptracks', 'razz') # postgres - print "database connection object = ", db_connection.connection - print "database type = ", db_connection.type - - h = db_connection.get_last_hand() - print "last hand = ", h - - hero = db_connection.get_player_id(c, 'PokerStars', 'nutOmatic') - print "nutOmatic is id_player = %d" % hero - - stat_dict = db_connection.get_stats_from_hand(h) - for p in stat_dict.keys(): - print p, " ", stat_dict[p] - - print "nutOmatics stats:" - stat_dict = db_connection.get_stats_from_hand(h, hero) - for p in stat_dict.keys(): - print p, " ", stat_dict[p] - - db_connection.close_connection - - print "press enter to continue" - sys.stdin.readline() diff --git a/HUD_config.xml.example b/HUD_config.xml.example deleted file mode 100644 index 423787c5..00000000 --- a/HUD_config.xml.example +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HUD_main.py b/HUD_main.py deleted file mode 100755 index a839c2a1..00000000 --- a/HUD_main.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python - -"""Hud_main.py - -Main for FreePokerTools HUD. -""" -# Copyright 2008, Ray E. Barker -# -# 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 - -######################################################################## - -# to do kill window on my seat -# to do adjust for preferred seat -# to do allow window resizing -# to do hud to echo, but ignore non numbers -# to do no hud window for hero -# to do things to add to config.xml -# to do font and size -# to do bg and fg color -# to do opacity - -# Standard Library modules -import sys -import os -import thread -import Queue - -errorfile = open('HUD-error.txt', 'w') -sys.stderr = errorfile - -# pyGTK modules -import pygtk -import gtk -import gobject - -# FreePokerTools modules -import Configuration -import Database -import Tables -import Hud - -# global dict for keeping the huds -hud_dict = {} - -db_connection = 0; -config = 0; - -def destroy(*args): # call back for terminating the main eventloop - gtk.main_quit() - -def process_new_hand(new_hand_id, db_name): -# there is a new hand_id to be processed -# read the hand_id from stdin and strip whitespace - global hud_dict - - for h in hud_dict.keys(): - if hud_dict[h].deleted: - del(hud_dict[h]) - - db_connection = Database.Database(config, db_name, 'temp') - (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) -# if a hud for this table exists, just update it - if hud_dict.has_key(table_name): - hud_dict[table_name].update(new_hand_id, db_connection, config) -# otherwise create a new hud - else: - table_windows = Tables.discover(config) - for t in table_windows.keys(): - if table_windows[t].name == table_name: - hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_name) - hud_dict[table_name].create(new_hand_id, config) - hud_dict[table_name].update(new_hand_id, db_connection, config) - break -# print "table name \"%s\" not identified, no hud created" % (table_name) - db_connection.close_connection() - return(1) - -def check_stdin(db_name): - try: - hand_no = dataQueue.get(block=False) - process_new_hand(hand_no, db_name) - except: - pass - - return True - -def read_stdin(source, condition, db_name): - new_hand_id = sys.stdin.readline() - process_new_hand(new_hand_id, db_name) - return True - -def producer(): # This is the thread function - while True: - hand_no = sys.stdin.readline() # reads stdin - dataQueue.put(hand_no) # and puts result on the queue - -if __name__== "__main__": - sys.stderr.write("HUD_main starting\n") - - try: - db_name = sys.argv[1] - except: - db_name = 'fpdb-p' - sys.stderr.write("Using db name = %s\n" % (db_name)) - - config = Configuration.Config() -# db_connection = Database.Database(config, 'fpdb', 'holdem') - - if os.name == 'posix': - s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, read_stdin, db_name) - elif os.name == 'nt': - dataQueue = Queue.Queue() # shared global. infinite size - gobject.threads_init() # this is required - thread.start_new_thread(producer, ()) # starts the thread - gobject.timeout_add(1000, check_stdin, db_name) - else: - print "Sorry your operating system is not supported." - sys.exit() - - main_window = gtk.Window() - main_window.connect("destroy", destroy) - label = gtk.Label('Closing this window will exit from the HUD.') - main_window.add(label) - main_window.set_title("HUD Main Window") - main_window.show_all() - - gtk.main() diff --git a/HandHistory.py b/HandHistory.py deleted file mode 100644 index dd2fa427..00000000 --- a/HandHistory.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env python -"""HandHistory.py - -Parses HandHistory xml files and returns requested objects. -""" -# Copyright 2008, Ray E. Barker -# -# 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 - -######################################################################## -# Standard Library modules -import xml.dom.minidom -from xml.dom.minidom import Node - -class HandHistory: - def __init__(self, xml_string, elements = ('ALL')): - - doc = xml.dom.minidom.parseString(xml_string) - if elements == ('ALL'): - elements = ('BETTING', 'AWARDS', 'POSTS', 'PLAYERS', 'GAME') - - if 'BETTING' in elements: - self.BETTING = Betting(doc.getElementsByTagName('BETTING')[0]) - if 'AWARDS' in elements: - self.AWARDS = Awards (doc.getElementsByTagName('AWARDS')[0]) - if 'POSTS' in elements: - self.POSTS = Posts (doc.getElementsByTagName('POSTS')[0]) - if 'GAME' in elements: - self.GAME = Game (doc.getElementsByTagName('GAME')[0]) - if 'PLAYERS' in elements: - self.PLAYERS = {} - p_n = doc.getElementsByTagName('PLAYERS')[0] - for p in p_n.getElementsByTagName('PLAYER'): - a_player = Player(p) - self.PLAYERS[a_player.name] = a_player - -class Player: - def __init__(self, node): - self.name = node.getAttribute('NAME') - self.seat = node.getAttribute('SEAT') - self.stack = node.getAttribute('STACK') - self.showed_hand = node.getAttribute('SHOWED_HAND') - self.cards = node.getAttribute('CARDS') - self.allin = node.getAttribute('ALLIN') - self.sitting_out = node.getAttribute('SITTING_OUT') - self.hand = node.getAttribute('HAND') - self.start_cards = node.getAttribute('START_CARDS') - - if self.allin == '' or \ - self.allin == '0' or \ - self.allin.upper() == 'FALSE': self.allin = False - else: self.allin = True - - if self.sitting_out == '' or \ - self.sitting_out == '0' or \ - self.sitting_out.upper() == 'FALSE': self.sitting_out = False - else: self.sitting_out = True - - def __str__(self): - temp = "%s\n seat = %s\n stack = %s\n cards = %s\n" % \ - (self.name, self.seat, self.stack, self.cards) - temp = temp + " showed_hand = %s\n allin = %s\n" % \ - (self.showed_hand, self.allin) - temp = temp + " hand = %s\n start_cards = %s\n" % \ - (self.hand, self.start_cards) - return temp - -class Awards: - def __init__(self, node): - self.awards = [] # just an array of award objects - for a in node.getElementsByTagName('AWARD'): - self.awards.append(Award(a)) - - def __str__(self): - temp = "" - for a in self.awards: - temp = temp + "%s\n" % (a) - return temp - -class Award: - def __init__(self, node): - self.player = node.getAttribute('PLAYER') - self.amount = node.getAttribute('AMOUNT') - self.pot = node.getAttribute('POT') - - def __str__(self): - return self.player + " won " + self.amount + " from " + self.pot - -class Game: - def __init__(self, node): - print node - self.tags = {} - for tag in ( ('GAME_NAME', 'game_name'), ('MAX', 'max'), ('HIGHLOW', 'high_low'), - ('STRUCTURE', 'structure'), ('MIXED', 'mixed') ): - L = node.getElementsByTagName(tag[0]) - if (not L): continue - print L - for node2 in L: - title = "" - for node3 in node2.childNodes: - if (node3.nodeType == Node.TEXT_NODE): - title +=node3.data - self.tags[tag[1]] = title - - def __str__(self): - return "%s %s %s, (%s max), %s" % (self.tags['structure'], - self.tags['game_name'], - self.tags['game_name'], - self.tags['max'], - self.tags['game_name']) - -class Posts: - def __init__(self, node): - self.posts = [] # just an array of post objects - for p in node.getElementsByTagName('POST'): - self.posts.append(Post(p)) - - def __str__(self): - temp = "" - for p in self.posts: - temp = temp + "%s\n" % (p) - return temp - -class Post: - def __init__(self, node): - self.player = node.getAttribute('PLAYER') - self.amount = node.getAttribute('AMOUNT') - self.posted = node.getAttribute('POSTED') - self.live = node.getAttribute('LIVE') - - def __str__(self): - return ("%s posted %s %s %s") % (self.player, self.amount, self.posted, self.live) - -class Betting: - def __init__(self, node): - self.rounds = [] # a Betting object is just an array of rounds - for r in node.getElementsByTagName('ROUND'): - self.rounds.append(Round(r)) - - def __str__(self): - temp = "" - for r in self.rounds: - temp = temp + "%s\n" % (r) - return temp - -class Round: - def __init__(self, node): - self.name = node.getAttribute('ROUND_NAME') - self.action = [] - for a in node.getElementsByTagName('ACTION'): - self.action.append(Action(a)) - - def __str__(self): - temp = self.name + "\n" - for a in self.action: - temp = temp + " %s\n" % (a) - return temp - -class Action: - def __init__(self, node): - self.player = node.getAttribute('PLAYER') - self.action = node.getAttribute('ACT') - self.amount = node.getAttribute('AMOUNT') - self.allin = node.getAttribute('ALLIN') - - def __str__(self): - return self.player + " " + self.action + " " + self.amount + " " + self.allin - -if __name__== "__main__": - file = open('test.xml', 'r') - xml_string = file.read() - file.close() - - print xml_string + "\n\n\n" - h = HandHistory(xml_string, ('ALL')) - print h.GAME - print h.POSTS - print h.BETTING - print h.AWARDS - - for p in h.PLAYERS.keys(): - print h.PLAYERS[p] \ No newline at end of file diff --git a/Hud.py b/Hud.py deleted file mode 100755 index debfaa67..00000000 --- a/Hud.py +++ /dev/null @@ -1,449 +0,0 @@ -#!/usr/bin/env python -"""Hud.py - -Create and manage the hud overlays. -""" -# Copyright 2008, Ray E. Barker - -# -# 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 - -######################################################################## -# Standard Library modules -import os - -# pyGTK modules -import pygtk -import gtk -import pango -import gobject - -# win32 modules -- only imported on windows systems -if os.name == 'nt': - import win32gui - import win32con - -# FreePokerTools modules -import Tables # needed for testing only -import Configuration -import Stats -import Mucked -import Database -import HUD_main - -class Hud: - - def __init__(self, table, max, poker_game, config, db_name): - self.table = table - self.config = config - self.poker_game = poker_game - self.max = max - self.db_name = db_name - self.deleted = False - - self.stat_windows = {} - self.popup_windows = {} - self.font = pango.FontDescription("Sans 8") - -# Set up a main window for this this instance of the HUD - self.main_window = gtk.Window() -# self.window.set_decorated(0) - self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.main_window.set_keep_above(1) - self.main_window.set_title(table.name) - self.main_window.connect("destroy", self.kill_hud) - - self.ebox = gtk.EventBox() - self.label = gtk.Label("Close this window to\nkill the HUD for\n %s" % (table.name)) - self.main_window.add(self.ebox) - self.ebox.add(self.label) - self.main_window.move(self.table.x, self.table.y) - -# A popup window for the main window - self.menu = gtk.Menu() - self.item1 = gtk.MenuItem('Kill this HUD') - self.menu.append(self.item1) - self.item1.connect("activate", self.kill_hud) - self.item1.show() - self.item2 = gtk.MenuItem('Save Layout') - self.menu.append(self.item2) - self.item2.connect("activate", self.save_layout) - self.item2.show() - self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) - - self.main_window.show_all() -# set_keep_above(1) for windows - if os.name == 'nt': self.topify_window(self.main_window) - - def on_button_press(self, widget, event): - if event.button == 3: - widget.popup(None, None, None, event.button, event.time) - return True - return False - - def kill_hud(self, args): - for k in self.stat_windows.keys(): - self.stat_windows[k].window.destroy() - self.main_window.destroy() - self.deleted = True - - def save_layout(self, *args): - new_layout = [] - for sw in self.stat_windows: - loc = self.stat_windows[sw].window.get_position() - new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) - new_layout.append(new_loc) - print new_layout - self.config.edit_layout(self.table.site, self.table.max, locations = new_layout) - self.config.save() - - def create(self, hand, config): -# update this hud, to the stats and players as of "hand" -# hand is the hand id of the most recent hand played at this table -# -# this method also manages the creating and destruction of stat -# windows via calls to the Stat_Window class - for i in range(1, self.max + 1): - (x, y) = config.supported_sites[self.table.site].layout[self.max].location[i] - self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], - parent = self, - table = self.table, - x = x, - y = y, - seat = i, - player_id = 'fake', - font = self.font) - - self.stats = [] - for i in range(0, config.supported_games[self.poker_game].rows + 1): - row_list = [''] * config.supported_games[self.poker_game].cols - self.stats.append(row_list) - for stat in config.supported_games[self.poker_game].stats.keys(): - self.stats[config.supported_games[self.poker_game].stats[stat].row] \ - [config.supported_games[self.poker_game].stats[stat].col] = \ - config.supported_games[self.poker_game].stats[stat].stat_name - -# self.mucked_window = gtk.Window() -# self.m = Mucked.Mucked(self.mucked_window, self.db_connection) -# self.mucked_window.show_all() - - def update(self, hand, db, config): - self.hand = hand # this is the last hand, so it is available later - stat_dict = db.get_stats_from_hand(hand) - for s in stat_dict.keys(): - self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] - for r in range(0, config.supported_games[self.poker_game].rows): - for c in range(0, config.supported_games[self.poker_game].cols): - number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) - self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(number[1]) - tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \ - number[3] + ", " + number[4] - Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip) -# self.m.update(hand) - - def topify_window(self, window): - """Set the specified gtk window to stayontop in MS Windows.""" - - def windowEnumerationHandler(hwnd, resultList): - '''Callback for win32gui.EnumWindows() to generate list of window handles.''' - resultList.append((hwnd, win32gui.GetWindowText(hwnd))) - - unique_name = 'unique name for finding this window' - real_name = window.get_title() - window.set_title(unique_name) - tl_windows = [] - win32gui.EnumWindows(windowEnumerationHandler, tl_windows) - - for w in tl_windows: - if w[1] == unique_name: - win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) -# notify_id = (w[0], -# 0, -# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, -# win32con.WM_USER+20, -# 0, -# '') -# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) -# - window.set_title(real_name) - -class Stat_Window: - - def button_press_cb(self, widget, event, *args): -# 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. - if event.button == 1: # left button event - if event.type == gtk.gdk.BUTTON_PRESS: # left button single click - if self.sb_click > 0: return - self.sb_click = gobject.timeout_add(250, self.single_click, widget) - elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click - if self.sb_click > 0: - gobject.source_remove(self.sb_click) - self.sb_click = 0 - self.double_click(widget, event, *args) - - if event.button == 2: # middle button event - pass -# print "middle button clicked" - - if event.button == 3: # right button event - pass -# print "right button clicked" - - def single_click(self, widget): -# Callback from the timeout in the single-click finding part of the -# button press call back. This needs to be modified to get all the -# arguments from the call. -# print "left button clicked" - self.sb_click = 0 - Popup_window(widget, self) - return False - - def double_click(self, widget, event, *args): - self.toggle_decorated(widget) - - def toggle_decorated(self, widget): - top = widget.get_toplevel() - (x, y) = top.get_position() - - if top.get_decorated(): - top.set_decorated(0) - top.move(x, y) - else: - top.set_decorated(1) - top.move(x, y) - - def __init__(self, parent, game, table, seat, x, y, player_id, font): - self.parent = parent # Hud object that this stat window belongs to - self.game = game # Configuration object for the curren - self.table = table # Table object where this is going - self.x = x + table.x # table.x and y are the location of the table - self.y = y + table.y # x and y are the location relative to table.x & y - self.player_id = player_id # looks like this isn't used ;) - self.sb_click = 0 # used to figure out button clicks - - self.window = gtk.Window() - self.window.set_decorated(0) - self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.window.set_keep_above(1) - self.window.set_title("%s" % seat) - self.window.set_property("skip-taskbar-hint", True) - - self.grid = gtk.Table(rows = self.game.rows, columns = self.game.cols, homogeneous = False) - self.window.add(self.grid) - - self.e_box = [] - self.frame = [] - self.label = [] - for r in range(self.game.rows): - self.e_box.append([]) - self.label.append([]) - for c in range(self.game.cols): - self.e_box[r].append( gtk.EventBox() ) - Stats.do_tip(self.e_box[r][c], 'farts') - self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) - self.label[r].append( gtk.Label('xxx') ) - self.e_box[r][c].add(self.label[r][c]) - self.e_box[r][c].connect("button_press_event", self.button_press_cb) -# font = pango.FontDescription("Sans 8") - self.label[r][c].modify_font(font) - self.window.realize - self.window.move(self.x, self.y) - self.window.show_all() -# set_keep_above(1) for windows - if os.name == 'nt': self.topify_window(self.window) - - def topify_window(self, window): - """Set the specified gtk window to stayontop in MS Windows.""" - - def windowEnumerationHandler(hwnd, resultList): - '''Callback for win32gui.EnumWindows() to generate list of window handles.''' - resultList.append((hwnd, win32gui.GetWindowText(hwnd))) - - unique_name = 'unique name for finding this window' - real_name = window.get_title() - window.set_title(unique_name) - tl_windows = [] - win32gui.EnumWindows(windowEnumerationHandler, tl_windows) - - for w in tl_windows: - if w[1] == unique_name: - win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) -# notify_id = (w[0], -# 0, -# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, -# win32con.WM_USER+20, -# 0, -# '') -# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) -# - window.set_title(real_name) - -def destroy(*args): # call back for terminating the main eventloop - gtk.main_quit() - -class Popup_window: - def __init__(self, parent, stat_window): - self.sb_click = 0 - -# create the popup window - self.window = gtk.Window() - self.window.set_decorated(0) - self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.window.set_keep_above(1) - self.window.set_title("popup") - self.window.set_property("skip-taskbar-hint", True) - self.window.set_transient_for(parent.get_toplevel()) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - - self.ebox = gtk.EventBox() - self.ebox.connect("button_press_event", self.button_press_cb) - self.lab = gtk.Label("stuff\nstuff\nstuff") - -# need an event box so we can respond to clicks - self.window.add(self.ebox) - self.ebox.add(self.lab) - self.window.realize - -# figure out the row, col address of the click that activated the popup - row = 0 - col = 0 - for r in range(0, stat_window.game.rows): - for c in range(0, stat_window.game.cols): - if stat_window.e_box[r][c] == parent: - row = r - col = c - break - -# figure out what popup format we're using - popup_format = "default" - for stat in stat_window.game.stats.keys(): - if stat_window.game.stats[stat].row == row and stat_window.game.stats[stat].col == col: - popup_format = stat_window.game.stats[stat].popup - break - -# get the list of stats to be presented from the config - stat_list = [] - for w in stat_window.parent.config.popup_windows.keys(): - if w == popup_format: - stat_list = stat_window.parent.config.popup_windows[w].pu_stats - break - -# get a database connection - db_connection = Database.Database(stat_window.parent.config, stat_window.parent.db_name, 'temp') - -# calculate the stat_dict and then create the text for the pu -# stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand, stat_window.player_id) - stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand) - db_connection.close_connection() - - pu_text = "" - for s in stat_list: - number = Stats.do_stat(stat_dict, player = int(stat_window.player_id), stat = s) - pu_text += number[3] + "\n" - - self.lab.set_text(pu_text) - self.window.show_all() -# set_keep_above(1) for windows - if os.name == 'nt': self.topify_window(self.window) - - def button_press_cb(self, widget, event, *args): -# This handles all callbacks from button presses on the event boxes in -# the popup windows. There is a bit of an ugly kludge to separate single- -# and double-clicks. This is the same code as in the Stat_window class - if event.button == 1: # left button event - if event.type == gtk.gdk.BUTTON_PRESS: # left button single click - if self.sb_click > 0: return - self.sb_click = gobject.timeout_add(250, self.single_click, widget) - elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click - if self.sb_click > 0: - gobject.source_remove(self.sb_click) - self.sb_click = 0 - self.double_click(widget, event, *args) - - if event.button == 2: # middle button event - pass -# print "middle button clicked" - - if event.button == 3: # right button event - pass -# print "right button clicked" - - def single_click(self, widget): -# Callback from the timeout in the single-click finding part of the -# button press call back. This needs to be modified to get all the -# arguments from the call. - self.sb_click = 0 - self.window.destroy() - return False - - def double_click(self, widget, event, *args): - self.toggle_decorated(widget) - - def toggle_decorated(self, widget): - top = widget.get_toplevel() - (x, y) = top.get_position() - - if top.get_decorated(): - top.set_decorated(0) - top.move(x, y) - else: - top.set_decorated(1) - top.move(x, y) - - def topify_window(self, window): - """Set the specified gtk window to stayontop in MS Windows.""" - - def windowEnumerationHandler(hwnd, resultList): - '''Callback for win32gui.EnumWindows() to generate list of window handles.''' - resultList.append((hwnd, win32gui.GetWindowText(hwnd))) - - unique_name = 'unique name for finding this window' - real_name = window.get_title() - window.set_title(unique_name) - tl_windows = [] - win32gui.EnumWindows(windowEnumerationHandler, tl_windows) - - for w in tl_windows: - if w[1] == unique_name: - win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) -# notify_id = (w[0], -# 0, -# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, -# win32con.WM_USER+20, -# 0, -# '') -# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) -# - window.set_title(real_name) - -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) - db = Database.Database(c, 'fpdb', 'holdem') - - for t in tables: - win = Hud(t, 8, c, db) -# t.get_details() - win.update(8300, db, c) - - gtk.main() diff --git a/Mucked.py b/Mucked.py deleted file mode 100644 index 0af06c65..00000000 --- a/Mucked.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env python -"""Mucked.py - -Mucked cards display for FreePokerTools HUD. -""" -# Copyright 2008, Ray E. Barker -# -# 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 - -######################################################################## - -# to do -# problem with hand 30586 - -# Standard Library modules -import sys -import os -import string -import xml.dom.minidom -from xml.dom.minidom import Node - -# pyGTK modules -import pygtk -import gtk -import gobject - -# FreePokerTools modules -import Configuration -import Database -import Tables -import Hud -import Mucked -import HandHistory - -class Mucked: - def __init__(self, parent, db_connection): - - self.parent = parent #this is the parent of the mucked cards widget - self.db_connection = db_connection - - self.vbox = gtk.VBox() - self.parent.add(self.vbox) - - self.mucked_list = MuckedList (self.vbox, db_connection) - self.mucked_cards = MuckedCards(self.vbox, db_connection) - self.mucked_list.mucked_cards = self.mucked_cards - - def update(self, new_hand_id): - self.mucked_list.update(new_hand_id) - -class MuckedList: - def __init__(self, parent, db_connection): - - self.parent = parent - self.db_connection = db_connection - -# set up a scrolled window to hold the listbox - self.scrolled_window = gtk.ScrolledWindow() - self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) - self.parent.add(self.scrolled_window) - -# create a ListStore to use as the model - self.liststore = gtk.ListStore(str, str, str) - self.treeview = gtk.TreeView(self.liststore) - self.tvcolumn0 = gtk.TreeViewColumn('HandID') - self.tvcolumn1 = gtk.TreeViewColumn('Cards') - self.tvcolumn2 = gtk.TreeViewColumn('Net') - -# add tvcolumn to treeview - self.treeview.append_column(self.tvcolumn0) - self.treeview.append_column(self.tvcolumn1) - self.treeview.append_column(self.tvcolumn2) - -# create a CellRendererText to render the data - self.cell = gtk.CellRendererText() - - # add the cell to the tvcolumn and allow it to expand - self.tvcolumn0.pack_start(self.cell, True) - self.tvcolumn1.pack_start(self.cell, True) - self.tvcolumn2.pack_start(self.cell, True) - self.tvcolumn0.add_attribute(self.cell, 'text', 0) - self.tvcolumn1.add_attribute(self.cell, 'text', 1) - self.tvcolumn2.add_attribute(self.cell, 'text', 2) -# resize the cols if nec - self.tvcolumn0.set_resizable(True) - self.treeview.connect("row-activated", self.activated_event) - - self.scrolled_window.add_with_viewport(self.treeview) - - def activated_event(self, path, column, data=None): - sel = self.treeview.get_selection() - (model, iter) = sel.get_selected() - self.mucked_cards.update(model.get_value(iter, 0)) - - def update(self, new_hand_id): -# info_row = self.db_connection.get_hand_info(new_hand_id) - info_row = ((new_hand_id, "xxxx", 0), ) - iter = self.liststore.append(info_row[0]) - sel = self.treeview.get_selection() - sel.select_iter(iter) - - vadj = self.scrolled_window.get_vadjustment() - vadj.set_value(vadj.upper) - self.mucked_cards.update(new_hand_id) - -class MuckedCards: - def __init__(self, parent, db_connection): - - self.parent = parent #this is the parent of the mucked cards widget - self.db_connection = db_connection - - self.card_images = self.get_card_images() - self.seen_cards = {} - self.grid_contents = {} - self.eb = {} - - self.rows = 8 - self.cols = 7 - self.grid = gtk.Table(self.rows, self.cols + 4, homogeneous = False) - - for r in range(0, self.rows): - for c in range(0, self.cols): - self.seen_cards[(c, r)] = gtk.image_new_from_pixbuf(self.card_images[('B', 'S')]) - self.eb[(c, r)]= gtk.EventBox() - -# set up the contents for the cells - for r in range(0, self.rows): - self.grid_contents[( 0, r)] = gtk.Label("%d" % (r + 1)) - self.grid_contents[( 1, r)] = gtk.Label("player %d" % (r + 1)) - self.grid_contents[( 4, r)] = gtk.Label("-") - self.grid_contents[( 9, r)] = gtk.Label("-") - self.grid_contents[( 2, r)] = self.eb[( 0, r)] - self.grid_contents[( 3, r)] = self.eb[( 1, r)] - self.grid_contents[( 5, r)] = self.eb[( 2, r)] - self.grid_contents[( 6, r)] = self.eb[( 3, r)] - self.grid_contents[( 7, r)] = self.eb[( 4, r)] - self.grid_contents[( 8, r)] = self.eb[( 5, r)] - self.grid_contents[(10, r)] = self.eb[( 6, r)] - for c in range(0, self.cols): - self.eb[(c, r)].add(self.seen_cards[(c, r)]) - -# add the cell contents to the table - for c in range(0, self.cols + 4): - for r in range(0, self.rows): - self.grid.attach(self.grid_contents[(c, r)], c, c+1, r, r+1, xpadding = 1, ypadding = 1) - - self.parent.add(self.grid) - - def translate_cards(self, old_cards): - pass - - def update(self, new_hand_id): - cards = self.db_connection.get_cards(new_hand_id) - self.clear() - - cards = self.translate_cards(cards) - for c in cards.keys(): - self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name']) - - for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'), - (4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')): - if not cards[c][i[1]] == "": - self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \ - set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])]) - - xml_text = self.db_connection.get_xml(new_hand_id) - hh = HandHistory.HandHistory(xml_text, ('BETTING')) - -# action in tool tips for 3rd street cards - tip = "%s" % hh.BETTING.rounds[0] - for c in (0, 1, 2): - for r in range(0, self.rows): - self.eb[(c, r)].set_tooltip_text(tip) - -# action in tools tips for later streets - round_to_col = (0, 3, 4, 5, 6) - for round in range(1, len(hh.BETTING.rounds)): - tip = "%s" % hh.BETTING.rounds[round] - for r in range(0, self.rows): - self.eb[(round_to_col[round], r)].set_tooltip_text(tip) - - def split_cards(self, card): - return (card[0], card[1].upper()) - - def clear(self): - for r in range(0, self.rows): - self.grid_contents[(1, r)].set_text(" ") - for c in range(0, 7): - self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')]) - self.eb[(c, r)].set_tooltip_text('') - def get_card_images(self): - card_images = {} - suits = ('S', 'H', 'D', 'C') - ranks = ('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'B') - pb = gtk.gdk.pixbuf_new_from_file("Cards01.png") - - for j in range(0, 14): - for i in range(0, 4): - temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42) - pb.copy_area(30*j, 42*i, 30, 42, temp_pb, 0, 0) - card_images[(ranks[j], suits[i])] = temp_pb - return(card_images) - -# cards are 30 wide x 42 high - -if __name__== "__main__": - - def destroy(*args): # call back for terminating the main eventloop - gtk.main_quit() # used only for testing - - def process_new_hand(source, condition): #callback from stdin watch -- testing only -# there is a new hand_id to be processed -# just read it and pass it to update - new_hand_id = sys.stdin.readline() - new_hand_id = new_hand_id.rstrip() # remove trailing whitespace - m.update(new_hand_id) - return(True) - - config = Configuration.Config() - db_connection = Database.Database(config, 'fpdb', '') - - main_window = gtk.Window() - main_window.set_keep_above(True) - main_window.connect("destroy", destroy) - - m = Mucked(main_window, db_connection) - main_window.show_all() - - s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) - - gtk.main() diff --git a/SQL.py b/SQL.py deleted file mode 100644 index 072d478e..00000000 --- a/SQL.py +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env python -"""SQL.py - -Set up all of the SQL statements for a given game and database type. -""" -# Copyright 2008, Ray E. Barker -# -# 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 - -######################################################################## - -# Standard Library modules - -# pyGTK modules - -# FreePokerTools modules - -class Sql: - - def __init__(self, game = 'holdem', type = 'PT3'): - self.query = {} - -############################################################################ -# -# Support for the ptracks database, a cut down PT2 stud database. -# You can safely ignore this unless you are me. -# - if game == 'razz' and type == 'ptracks': - - self.query['get_table_name'] = "select table_name from game where game_id = %s" - - self.query['get_last_hand'] = "select max(game_id) from game" - - self.query['get_recent_hands'] = "select game_id from game where game_id > %(last_hand)d" - - self.query['get_xml'] = "select xml from hand_history where game_id = %s" - - self.query['get_player_id'] = """ - select player_id from players - where screen_name = %(player)s - """ - - self.query['get_hand_info'] = """ - SELECT - game_id, - CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, - total_won-total_bet AS net - FROM game_players - WHERE game_id = %s AND player_id = 3 - """ - - self.query['get_cards'] = """ - select - seat_number, - screen_name, - hole_card_1, - hole_card_2, - hole_card_3, - hole_card_4, - hole_card_5, - hole_card_6, - hole_card_7 - from game_players, players - where game_id = %s and game_players.player_id = players.player_id - order by seat_number - """ - - self.query['get_stats_from_hand'] = """ - SELECT player_id, - count(*) AS n, - sum(pre_fourth_raise_n) AS pfr, - sum(fourth_raise_n) AS raise_n_2, - sum(fourth_ck_raise_n) AS cr_n_2, - sum(fifth_bet_raise_n) AS br_n_3, - sum(fifth_bet_ck_raise_n) AS cr_n_3, - sum(sixth_bet_raise_n) AS br_n_4, - sum(sixth_bet_ck_raise_n) AS cr_n_4, - sum(river_bet_raise_n) AS br_n_5, - sum(river_bet_ck_raise_n) AS cr_n_5, - sum(went_to_showdown_n) AS sd, - sum(saw_fourth_n) AS saw_f, - sum(raised_first_pf) AS first_pfr, - sum(vol_put_money_in_pot) AS vpip, - sum(limp_with_prev_callers) AS limp_w_callers, - - sum(ppossible_actions) AS poss_a_pf, - sum(pfold) AS fold_pf, - sum(pcheck) AS check_pf, - sum(praise) AS raise_pf, - sum(pcall) AS raise_pf, - sum(limp_call_reraise_pf) AS limp_call_pf, - - sum(pfr_check) AS check_after_raise, - sum(pfr_call) AS call_after_raise, - sum(pfr_fold) AS fold_after_raise, - sum(pfr_bet) AS bet_after_raise, - sum(pfr_raise) AS raise_after_raise, - sum(folded_to_river_bet) AS fold_to_r_bet, - - sum(fpossible_actions) AS poss_a_2, - sum(ffold) AS fold_2, - sum(fcheck) AS check_2, - sum(fbet) AS bet_2, - sum(fraise) AS raise_2, - sum(fcall) AS raise_2, - - sum(fifpossible_actions) AS poss_a_3, - sum(fiffold) AS fold_3, - sum(fifcheck) AS check_3, - sum(fifbet) AS bet_3, - sum(fifraise) AS raise_3, - sum(fifcall) AS call_3, - - sum(spossible_actions) AS poss_a_4, - sum(sfold) AS fold_4, - sum(scheck) AS check_4, - sum(sbet) AS bet_4, - sum(sraise) AS raise_4, - sum(scall) AS call_4, - - sum(rpossible_actions) AS poss_a_5, - sum(rfold) AS fold_5, - sum(rcheck) AS check_5, - sum(rbet) AS bet_5, - sum(rraise) AS raise_5, - sum(rcall) AS call_5, - - sum(cold_call_pf) AS cc_pf, - sum(saw_fifth_n) AS saw_3, - sum(saw_sixth_n) AS saw_4, - sum(saw_river_n) AS saw_5 - FROM game_players - WHERE player_id in - (SELECT player_id FROM game_players - WHERE game_id = %s AND NOT player_id = %s) - GROUP BY player_id - """ -# alternate form of WHERE for above -# WHERE game_id = %(hand)d AND NOT player_id = %(hero)d) -# WHERE game_id = %s AND NOT player_id = %s) - - self.query['get_players_from_hand'] = """ - SELECT game_players.player_id, seat_number, screen_name - FROM game_players INNER JOIN players ON (game_players.player_id = players.player_id) - WHERE game_id = %s - """ - -###############################################################################3 -# Support for the Free Poker DataBase = fpdb http://fpdb.sourceforge.net/ -# - if type == 'fpdb': - - self.query['get_last_hand'] = "select max(id) from Hands" - - self.query['get_player_id'] = """ - select Players.id AS player_id from Players, Sites - where Players.name = %(player)s - and Sites.name = %(site)s - and Players.SiteId = Sites.id - """ - - self.query['get_stats_from_hand'] = """ - SELECT HudCache.playerId AS player_id, - sum(HDs) AS n, - sum(street0VPI) AS vpip, - sum(street0Aggr) AS pfr, - sum(street0_3B4BChance) AS TB_opp_0, - sum(street0_3B4BDone) AS TB_0, - sum(street1Seen) AS saw_f, - sum(street1Seen) AS saw_1, - sum(street2Seen) AS saw_2, - sum(street3Seen) AS saw_3, - sum(street4Seen) AS saw_4, - sum(sawShowdown) AS sd, - sum(street1Aggr) AS aggr_1, - sum(street2Aggr) AS aggr_2, - sum(street3Aggr) AS aggr_3, - sum(street4Aggr) AS aggr_4, - sum(otherRaisedStreet1) AS was_raised_1, - sum(otherRaisedStreet2) AS was_raised_2, - sum(otherRaisedStreet3) AS was_raised_3, - sum(otherRaisedStreet4) AS was_raised_4, - sum(foldToOtherRaisedStreet1) AS f_freq_1, - sum(foldToOtherRaisedStreet2) AS f_freq_2, - sum(foldToOtherRaisedStreet3) AS f_freq_3, - sum(foldToOtherRaisedStreet4) AS f_freq_4, - sum(wonWhenSeenStreet1) AS w_w_s_1, - sum(wonAtSD) AS wmsd, - sum(stealAttemptChance) AS steal_opp, - sum(stealAttempted) AS steal, - sum(foldBbToStealChance) AS SBstolen, - sum(foldedBbToSteal) AS BBnotDef, - sum(foldBbToStealChance) AS BBstolen, - sum(foldedSbToSteal) AS SBnotDef, - sum(street1CBChance) AS CB_opp_1, - sum(street1CBDone) AS CB_1, - sum(street2CBChance) AS CB_opp_2, - sum(street2CBDone) AS CB_2, - sum(street3CBChance) AS CB_opp_3, - sum(street3CBDone) AS CB_3, - sum(street4CBChance) AS CB_opp_4, - sum(street4CBDone) AS CB_4, - sum(foldToStreet1CBChance) AS f_cb_opp_1, - sum(foldToStreet1CBDone) AS f_cb_1, - sum(foldToStreet2CBChance) AS f_cb_opp_2, - sum(foldToStreet2CBDone) AS f_cb_2, - sum(foldToStreet3CBChance) AS f_cb_opp_3, - sum(foldToStreet3CBDone) AS f_cb_3, - sum(foldToStreet4CBChance) AS f_cb_opp_4, - sum(foldToStreet4CBDone) AS f_cb_4, - sum(totalProfit) AS net, - sum(street1CheckCallRaiseChance) AS ccr_opp_1, - sum(street1CheckCallRaiseDone) AS ccr_1, - sum(street2CheckCallRaiseChance) AS ccr_opp_2, - sum(street2CheckCallRaiseDone) AS ccr_2, - sum(street3CheckCallRaiseChance) AS ccr_opp_3, - sum(street3CheckCallRaiseDone) AS ccr_3, - sum(street4CheckCallRaiseChance) AS ccr_opp_4, - sum(street4CheckCallRaiseDone) AS ccr_4 - FROM HudCache, Hands - WHERE HudCache.PlayerId in - (SELECT PlayerId FROM HandsPlayers - WHERE handId = %s) - AND Hands.id = %s - AND Hands.gametypeId = HudCache.gametypeId - GROUP BY HudCache.PlayerId - """ -# AND PlayerId LIKE %s -# HudCache.gametypeId AS gametypeId, -# activeSeats AS n_active, -# position AS position, -# HudCache.tourneyTypeId AS tourneyTypeId, - - self.query['get_players_from_hand'] = """ - SELECT HandsPlayers.playerId, seatNo, name - FROM HandsPlayers INNER JOIN Players ON (HandsPlayers.playerId = Players.id) - WHERE handId = %s - """ -# WHERE handId = %s AND Players.id LIKE %s - - self.query['get_table_name'] = """ - select tableName, maxSeats, category - from Hands,Gametypes - where Hands.id = %s - and Gametypes.id = Hands.gametypeId - """ - - self.query['get_cards'] = """ - select - seatNo AS seat_number, - name AS screen_name, - card1Value, card1Suit, - card2Value, card2Suit, - card3Value, card3Suit, - card4Value, card4Suit, - card5Value, card5Suit, - card6Value, card6Suit, - card7Value, card7Suit - from HandsPlayers, Players - where handID = %s and HandsPlayers.playerId = Players.id - order by seatNo - """ - -# self.query['get_hand_info'] = """ -# SELECT -# game_id, -# CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, -# total_won-total_bet AS net -# FROM game_players -# WHERE game_id = %s AND player_id = 3 -# """ - -if __name__== "__main__": -# just print the default queries and exit - s = Sql(game = 'razz', type = 'ptracks') - for key in s.query: - print "For query " + key + ", sql =" - print s.query[key] diff --git a/Stats.py b/Stats.py deleted file mode 100644 index 3531c017..00000000 --- a/Stats.py +++ /dev/null @@ -1,618 +0,0 @@ -#!/usr/bin/env python - -"""Manage collecting and formatting of stats and tooltips. -""" -# Copyright 2008, Ray E. Barker - -# -# 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 - -######################################################################## - -# How to write a new stat: -# 1 You can see a listing of all the raw stats (e.g., from the HudCache table) -# by running Database.py as a stand along program. You need to combine -# those raw stats to get stats to present to the HUD. If you need more -# information than is in the HudCache table, then you have to write SQL. -# 2 The raw stats seen when you run Database.py are available in the Stats.py -# in the stat_dict dict. For example the number of vpips would be -# stat_dict[player]['vpip']. So the % vpip is -# float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']). You can see how the -# keys of stat_dict relate to the column names in HudCache by inspecting -# the proper section of the SQL.py module. -# 3 You have to write a small function for each stat you want to add. See -# the vpip() function for example. This function has to be protected from -# exceptions, using something like the try:/except: paragraphs in vpip. -# 4 The name of the function has to be the same as the of the stat used -# in the config file. -# 5 The stat functions have a peculiar return value, which is outlined in -# the do_stat function. This format is useful for tool tips and maybe -# other stuff. -# 6 For each stat you make add a line to the __main__ function to test it. - -# Standard Library modules -#import sys - -# pyGTK modules -import pygtk -import gtk - -# FreePokerTools modules -import Configuration -import Database - -def do_tip(widget, tip): - widget.set_tooltip_text(tip) - -def do_stat(stat_dict, player = 24, stat = 'vpip'): - return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) -# OK, for reference the tuple returned by the stat is: -# 0 - The stat, raw, no formating, eg 0.33333333 -# 1 - formatted stat with appropriate precision and punctuation, eg 33% -# 2 - formatted stat with appropriate precision, punctuation and a hint, eg v=33% -# 3 - same as #2 except name of stat instead of hint, eg vpip=33% -# 4 - the calculation that got the stat, eg 9/27 -# 5 - the name of the stat, useful for a tooltip, eg vpip - -########################################### -# functions that return individual stats -def vpip(stat_dict, player): - """ Voluntarily put $ in the pot.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'v=%3.1f' % (100*stat) + '%', - 'vpip=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']), - 'vpip' - ) - except: return (stat, - '%3.1f' % (0) + '%', - 'w=%3.1f' % (0) + '%', - 'wtsd=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - 'wtsd' - ) - -def pfr(stat_dict, player): - """ Preflop (3rd street) raise.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['pfr'])/float(stat_dict[player]['n']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'p=%3.1f' % (100*stat) + '%', - 'pfr=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']), - 'pfr' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'p=%3.1f' % (0) + '%', - 'pfr=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - 'pfr' - ) - -def wtsd(stat_dict, player): - """ Went to SD when saw flop/4th.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'w=%3.1f' % (100*stat) + '%', - 'wtsd=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['sd'], stat_dict[player]['saw_f']), - '% went to showdown' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'w=%3.1f' % (0) + '%', - 'wtsd=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% went to showdown' - ) - -def wmsd(stat_dict, player): - """ Won $ at showdown.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'w=%3.1f' % (100*stat) + '%', - 'wmsd=%3.1f' % (100*stat) + '%', - '(%f5.0/%d)' % (stat_dict[player]['wmsd'], stat_dict[player]['sd']), - '% won money at showdown' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'w=%3.1f' % (0) + '%', - 'wmsd=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% won money at showdown' - ) - -def saw_f(stat_dict, player): - """ Saw flop/4th.""" - try: - num = float(stat_dict[player]['saw_f']) - den = float(stat_dict[player]['n']) - stat = num/den - return (stat, - '%3.1f' % (100*stat) + '%', - 'sf=%3.1f' % (100*stat) + '%', - 'saw_f=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['saw_f'], stat_dict[player]['n']), - 'saw_f' - ) - except: - stat = 0.0 - num = 0 - den = 0 - return (stat, - '%3.1f' % (stat) + '%', - 'sf=%3.1f' % (stat) + '%', - 'saw_f=%3.1f' % (stat) + '%', - '(%d/%d)' % (num, den), - 'saw_f' - ) - -def n(stat_dict, player): - """ Number of hands played.""" - try: - return (stat_dict[player]['n'], - '%d' % (stat_dict[player]['n']), - 'n=%d' % (stat_dict[player]['n']), - 'n=%d' % (stat_dict[player]['n']), - '(%d)' % (stat_dict[player]['n']), - 'number hands seen' - ) - except: - return (0, - '%d' % (0), - 'n=%d' % (0), - 'n=%d' % (0), - '(%d)' % (0), - 'number hands seen' - ) - -def fold_f(stat_dict, player): - """ Folded flop/4th.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['fold_2'])/fold(stat_dict[player]['saw_f']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'ff=%3.1f' % (100*stat) + '%', - 'fold_f=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['fold_2'], stat_dict[player]['saw_f']), - 'folded flop/4th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'ff=%3.1f' % (0) + '%', - 'fold_f=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - 'folded flop/4th' - ) - -def steal(stat_dict, player): - """ Steal %.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'st=%3.1f' % (100*stat) + '%', - 'steal=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['steal'], stat_dict[player]['steal_opp']), - '% steal attempted' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'st=%3.1f' % (0) + '%', - 'steal=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% steal attempted' - ) - -def f_SB_steal(stat_dict, player): - """ Folded SB to steal.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['SBnotDef'])/float(stat_dict[player]['SBstolen']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'fSB=%3.1f' % (100*stat) + '%', - 'fSB_s=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['SBnotDef'], stat_dict[player]['SBstolen']), - '% folded SB to steal' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'fSB=%3.1f' % (0) + '%', - 'fSB_s=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% folded SB to steal' - ) - -def f_BB_steal(stat_dict, player): - """ Folded BB to steal.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['BBnotDef'])/float(stat_dict[player]['BBstolen']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'fBB=%3.1f' % (100*stat) + '%', - 'fBB_s=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['BBnotDef'], stat_dict[player]['BBstolen']), - '% folded BB to steal' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'fBB=%3.1f' % (0) + '%', - 'fBB_s=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% folded BB to steal' - ) - -def three_B_0(stat_dict, player): - """ Three bet preflop/3rd.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['TB_0'])/float(stat_dict[player]['TB_opp_0']) - return (stat, - '%3.1f' % (100*stat) + '%', - '3B=%3.1f' % (100*stat) + '%', - '3B_pf=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['TB_0'], stat_dict[player]['TB_opp_0']), - '% 3/4 Bet preflop/3rd' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - '3B=%3.1f' % (0) + '%', - '3B_pf=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% 3/4 Bet preflop/3rd' - ) - -def WMsF(stat_dict, player): - """ Won $ when saw flop/4th.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['w_w_s_1'])/float(stat_dict[player]['saw_1']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'wf=%3.1f' % (100*stat) + '%', - 'w_w_f=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['w_w_s_1'], stat_dict[player]['saw_f']), - '% won$/saw flop/4th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'wf=%3.1f' % (0) + '%', - 'w_w_f=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% won$/saw flop/4th' - ) - -def a_freq_1(stat_dict, player): - """ Flop/4th aggression frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['aggr_1'])/float(stat_dict[player]['saw_f']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'a1=%3.1f' % (100*stat) + '%', - 'a_fq_1=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_f']), - 'Aggression Freq flop/4th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'a1=%3.1f' % (0) + '%', - 'a_fq_1=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - 'Aggression Freq flop/4th' - ) - -def a_freq_2(stat_dict, player): - """ Turn/5th aggression frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['aggr_2'])/float(stat_dict[player]['saw_2']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'a2=%3.1f' % (100*stat) + '%', - 'a_fq_2=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['aggr_2'], stat_dict[player]['saw_2']), - 'Aggression Freq turn/5th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'a2=%3.1f' % (0) + '%', - 'a_fq_2=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - 'Aggression Freq turn/5th' - ) - -def a_freq_3(stat_dict, player): - """ River/6th aggression frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['aggr_3'])/float(stat_dict[player]['saw_3']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'a3=%3.1f' % (100*stat) + '%', - 'a_fq_3=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_1']), - 'Aggression Freq river/6th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'a3=%3.1f' % (0) + '%', - 'a_fq_3=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - 'Aggression Freq river/6th' - ) - -def a_freq_4(stat_dict, player): - """ 7th street aggression frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['aggr_4'])/float(stat_dict[player]['saw_4']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'a4=%3.1f' % (100*stat) + '%', - 'a_fq_4=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['aggr_4'], stat_dict[player]['saw_4']), - 'Aggression Freq 7th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'a4=%3.1f' % (0) + '%', - 'a_fq_4=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - 'Aggression Freq flop/4th' - ) - -def cb_1(stat_dict, player): - """ Flop continuation bet.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['CB_1'])/float(stat_dict[player]['CB_opp_1']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'cb1=%3.1f' % (100*stat) + '%', - 'cb_1=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['CB_1'], stat_dict[player]['CB_opp_1']), - '% continuation bet flop/4th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'cb1=%3.1f' % (0) + '%', - 'cb_1=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% continuation bet flop/4th' - ) - -def cb_2(stat_dict, player): - """ Turn continuation bet.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['CB_2'])/float(stat_dict[player]['CB_opp_2']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'cb2=%3.1f' % (100*stat) + '%', - 'cb_2=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['CB_2'], stat_dict[player]['CB_opp_2']), - '% continuation bet turn/5th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'cb2=%3.1f' % (0) + '%', - 'cb_2=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% continuation bet turn/5th' - ) - -def cb_3(stat_dict, player): - """ River continuation bet.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['CB_3'])/float(stat_dict[player]['CB_opp_3']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'cb3=%3.1f' % (100*stat) + '%', - 'cb_3=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['CB_3'], stat_dict[player]['CB_opp_3']), - '% continuation bet river/6th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'cb3=%3.1f' % (0) + '%', - 'cb_3=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% continuation bet river/6th' - ) - -def cb_4(stat_dict, player): - """ 7th street continuation bet.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['CB_4'])/float(stat_dict[player]['CB_opp_4']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'cb4=%3.1f' % (100*stat) + '%', - 'cb_4=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['CB_4'], stat_dict[player]['CB_opp_4']), - '% continuation bet 7th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'cb4=%3.1f' % (0) + '%', - 'cb_4=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% continuation bet 7th' - ) - -def ffreq_1(stat_dict, player): - """ Flop/4th fold frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['f_freq_1'])/float(stat_dict[player]['was_raised_1']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'ff1=%3.1f' % (100*stat) + '%', - 'ff_1=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['f_freq_1'], stat_dict[player]['was_raised_1']), - '% fold frequency flop/4th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'ff1=%3.1f' % (0) + '%', - 'ff_1=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% fold frequency flop/4th' - ) - -def ffreq_2(stat_dict, player): - """ Turn/5th fold frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['f_freq_2'])/float(stat_dict[player]['was_raised_2']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'ff2=%3.1f' % (100*stat) + '%', - 'ff_2=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['f_freq_2'], stat_dict[player]['was_raised_2']), - '% fold frequency turn/5th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'ff2=%3.1f' % (0) + '%', - 'ff_2=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% fold frequency turn/5th' - ) - -def ffreq_3(stat_dict, player): - """ River/6th fold frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['f_freq_3'])/float(stat_dict[player]['was_raised_3']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'ff3=%3.1f' % (100*stat) + '%', - 'ff_3=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['f_freq_3'], stat_dict[player]['was_raised_3']), - '% fold frequency river/6th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'ff3=%3.1f' % (0) + '%', - 'ff_3=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% fold frequency river/6th' - ) - -def ffreq_4(stat_dict, player): - """ 7th fold frequency.""" - stat = 0.0 - try: - stat = float(stat_dict[player]['f_freq_4'])/float(stat_dict[player]['was_raised_4']) - return (stat, - '%3.1f' % (100*stat) + '%', - 'ff4=%3.1f' % (100*stat) + '%', - 'ff_4=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['f_freq_4'], stat_dict[player]['was_raised_4']), - '% fold frequency 7th' - ) - except: - return (stat, - '%3.1f' % (0) + '%', - 'ff4=%3.1f' % (0) + '%', - 'ff_4=%3.1f' % (0) + '%', - '(%d/%d)' % (0, 0), - '% fold frequency 7th' - ) - -if __name__== "__main__": - c = Configuration.Config() - db_connection = Database.Database(c, 'fpdb', 'holdem') - h = db_connection.get_last_hand() - stat_dict = db_connection.get_stats_from_hand(h) - - for player in stat_dict.keys(): - print "player = ", player, do_stat(stat_dict, player = player, stat = 'vpip') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'pfr') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'wtsd') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'saw_f') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'n') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B_0') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_1') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_2') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_3') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_4') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_1') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_2') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_3') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_4') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_1') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_2') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3') - print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4') - - print "\n\nLegal stats:" - for attr in dir(): - if attr.startswith('__'): continue - if attr in ("Configuration", "Database", "GInitiallyUnowned", "gtk", "pygtk", - "player", "c", "db_connection", "do_stat", "do_tip", "stat_dict", - "h"): continue - print attr, eval("%s.__doc__" % (attr)) -# print " " % (attr) - - db_connection.close - diff --git a/Tables.py b/Tables.py deleted file mode 100644 index cb6209c8..00000000 --- a/Tables.py +++ /dev/null @@ -1,231 +0,0 @@ -#!/usr/bin/env python -"""Discover_Tables.py - -Inspects the currently open windows and finds those of interest to us--that is -poker table windows from supported sites. Returns a list -of Table_Window objects representing the windows found. -""" -# Copyright 2008, Ray E. Barker - -# -# 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 - -######################################################################## - -# Standard Library modules -import os -import sys -import re - -# Win32 modules - -if os.name == 'nt': - import win32gui - -# FreePokerTools modules -import Configuration - -class Table_Window: - def __str__(self): -# __str__ method for testing - temp = 'TableWindow object\n' - temp = temp + " name = %s\n site = %s\n number = %s\n title = %s\n" % (self.name, self.site, self.number, self.title) -# temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) - temp = temp + " width = %d\n height = %d\n x = %d\n y = %d\n" % (self.width, self.height, self.x, self.y) - if getattr(self, 'tournament', 0): - temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table) - return temp - - def get_details(table): - table.game = 'razz' - table.max = 8 - table.struture = 'limit' - table.tournament = 0 - -def discover(c): - if os.name == 'posix': - tables = discover_posix(c) - return tables - elif os.name == 'nt': - tables = discover_nt(c) - return tables - elif ox.name == 'mac': - tables = discover_mac(c) - return tables - else: tables = {} - - return(tables) - -def discover_posix(c): - """ Poker client table window finder for posix/Linux = XWindows.""" - tables = {} - for listing in os.popen('xwininfo -root -tree').readlines(): -# xwininfo -root -tree -id 0xnnnnn gets the info on a single window - if re.search('Lobby', listing): continue - if re.search('Instant Hand History', listing): continue - if not re.search('Logged In as ', listing, re.IGNORECASE): continue - if re.search('\"Full Tilt Poker\"', listing): continue # FTP Lobby - for s in c.supported_sites.keys(): - if re.search(c.supported_sites[s].table_finder, listing): - mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) - if mo.group(2) == '(has no name)': continue - if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup - tw = Table_Window() - tw.site = c.supported_sites[s].site_name - tw.number = mo.group(1) - tw.title = mo.group(2) - tw.width = int( mo.group(3) ) - tw.height = int( mo.group(4) ) - tw.x = int (mo.group(5) ) - tw.y = int (mo.group(6) ) - tw.title = re.sub('\"', '', tw.title) - -# use this eval thingie to call the title bar decoder specified in the config file - eval("%s(tw)" % c.supported_sites[s].decoder) - tables[tw.name] = tw - return tables -# -# The discover_xx functions query the system and report on the poker clients -# currently displayed on the screen. The discover_posix should give you -# some idea how to support other systems. -# -# discover_xx() returns a dict of TableWindow objects--one TableWindow -# object for each poker client table on the screen. -# -# Each TableWindow object must have the following attributes correctly populated: -# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site -# name specified in the config file. -# tw.number = This is the system id number for the client table window in the -# format that the system presents it. -# tw.title = The full title from the window title bar. -# tw.width, tw.height = The width and height of the window in pixels. This is -# the internal width and height, not including the title bar and -# window borders. -# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative -# to the top left of the display screen. This also does not include the -# title bar and window borders. To put it another way, this is the -# screen location of (0, 0) in the working window. - -def win_enum_handler(hwnd, titles): - titles[hwnd] = win32gui.GetWindowText(hwnd) - -def child_enum_handler(hwnd, children): - print hwnd, win32.GetWindowRect(hwnd) - -def discover_nt(c): - """ Poker client table window finder for Windows.""" -# -# I cannot figure out how to get the inside dimensions of the poker table -# windows. So I just assume all borders are 3 thick and all title bars -# are 29 high. No doubt this will be off when used with certain themes. -# - b_width = 3 - tb_height = 29 - titles = {} - tables = {} - win32gui.EnumWindows(win_enum_handler, titles) - for hwnd in titles.keys(): - if re.search('Logged In as', titles[hwnd], re.IGNORECASE) and not re.search('Lobby', titles[hwnd]): - if re.search('Full Tilt Poker', titles[hwnd]): - continue - tw = Table_Window() - tw.number = hwnd - (x, y, width, height) = win32gui.GetWindowRect(hwnd) - tw.title = titles[hwnd] - tw.width = int( width ) - 2*b_width - tw.height = int( height ) - b_width - tb_height - tw.x = int( x ) + b_width - tw.y = int( y ) + tb_height - if re.search('Logged In as', titles[hwnd]): - tw.site = "PokerStars" - elif re.search('Logged In As', titles[hwnd]): - tw.site = "Full Tilt" - else: - tw.site = "Unknown" - sys.stderr.write("Found unknown table = %s" % tw.title) - if not tw.site == "Unknown": - eval("%s(tw)" % c.supported_sites[tw.site].decoder) - else: - tw.name = "Unknown" - tables[tw.name] = tw - return tables - -def discover_mac(c): - """ Poker client table window finder for Macintosh.""" - tables = {} - return tables - -def pokerstars_decode_table(tw): -# extract the table name OR the tournament number and table name from the title -# other info in title is redundant with data in the database - title_bits = re.split(' - ', tw.title) - name = title_bits[0] - mo = re.search('Tournament (\d+) Table (\d+)', name) - if mo: - tw.tournament = int( mo.group(1) ) - tw.table = int( mo.group(2) ) - tw.name = name - else: - tw.tournament = None - for pattern in [' no all-in', ' fast', ',', ' 50BB min']: - name = re.sub(pattern, '', name) - name = re.sub('\s+$', '', name) - tw.name = name - - mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball)', tw.title) - - tw.game = mo.group(1).lower() - tw.game = re.sub('\'', '', tw.game) - tw.game = re.sub('h/l', 'hi/lo', tw.game) - - mo = re.search('(No Limit|Pot Limit)', tw.title) - if mo: - tw.structure = mo.group(1).lower() - else: - tw.structure = 'limit' - - tw.max = None - if tw.game in ('razz', 'stud', 'stud hi/lo'): - tw.max = 8 - elif tw.game in ('5-card draw', 'triple draw 2-7 lowball'): - tw.max = 6 - elif tw.game == 'holdem': - pass - elif tw.game in ('omaha', 'omaha hi/lo'): - pass - -def fulltilt_decode_table(tw): -# extract the table name OR the tournament number and table name from the title -# other info in title is redundant with data in the database - title_bits = re.split(' - ', tw.title) - name = title_bits[0] - tw.tournament = None - for pattern in [r' \(6 max\)', r' \(heads up\)', r' \(deep\)', - r' \(deep hu\)', r' \(deep 6\)', r' \(2\)', - r' \(edu\)', r' \(edu, 6 max\)', r' \(6\)' ]: - name = re.sub(pattern, '', name) -# (tw.name, trash) = name.split(r' (', 1) - tw.name = name.rstrip() - -if __name__=="__main__": - c = Configuration.Config() - tables = discover(c) - - for t in tables.keys(): - print "t = ", t - print tables[t] - - print "press enter to continue" - sys.stdin.readline() From 83fa3901fe5f40491dd93dce82542ecffcec6446 Mon Sep 17 00:00:00 2001 From: Carl Gherardi Date: Tue, 7 Oct 2008 14:11:48 +0800 Subject: [PATCH 142/262] New file for DB queries used in fpdb. Currently just a framework and simple CLI --- pyfpdb/FpdbSQLQueries.py | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 pyfpdb/FpdbSQLQueries.py diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py new file mode 100644 index 00000000..9a17600e --- /dev/null +++ b/pyfpdb/FpdbSQLQueries.py @@ -0,0 +1,64 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + + +############################################################################ +# +# File for DB queries used in fpdb +# + +import sys +import os + +class FpdbSQLQueries: + + def __init__(self, db): + self.query = {} + self.dbname = db + +#Boilerplate code. +# if(self.dbname == 'MySQL InnoDB'): +# self.query[''] = """ """ +# elif(self.dbname == 'PostgreSQL'): +# elif(self.dbname == 'SQLite'): + +if __name__== "__main__": + from optparse import OptionParser + + print "FpdbSQLQueries starting from CLI" + + #process CLI parameters + usage = "usage: %prog [options]" + parser = OptionParser() + parser.add_option("-t", "--type", dest="dbtype", help="Available 'MySQL InnoDB', 'PostgreSQL', 'SQLite'(default: MySQL InnoDB)", default="MySQL InnoDB") + parser.add_option("-s", "--show", action="store_true", dest="showsql", help="Show full SQL output") + parser.add_option("-v", "--verbose", action="store_true", dest="verbose") + + + (options, args) = parser.parse_args() + + if options.verbose: + print """No additional output available in this file""" + + obj = FpdbSQLQueries(options.dbtype) + + print "Available Queries for '" + options.dbtype + "':" + + for key in obj.query: + print " " + key + if options.showsql: + print obj.query[key] From e6fa946835a3b69fe0eeeb1c0498d015f05e338f Mon Sep 17 00:00:00 2001 From: Carl Gherardi Date: Tue, 7 Oct 2008 14:47:24 +0800 Subject: [PATCH 143/262] Add drop_table query. Syntax verified against MySQL, PostgreSQL and SQLite docs --- pyfpdb/FpdbSQLQueries.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 9a17600e..6adb1367 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -36,6 +36,13 @@ class FpdbSQLQueries: # elif(self.dbname == 'PostgreSQL'): # elif(self.dbname == 'SQLite'): + ################################################################## + # Drop Tables - MySQL, PostgreSQL and SQLite all share same syntax + ################################################################## + + if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): + self.query['drop_table'] = """DROP TABLE IF EXISTS """ + if __name__== "__main__": from optparse import OptionParser From d07d1c5bccd42089c6e5c561851ccd3576880b87 Mon Sep 17 00:00:00 2001 From: Carl Gherardi Date: Tue, 7 Oct 2008 16:15:44 +0800 Subject: [PATCH 144/262] Add unit testing file. Currently tests connection to fpdbtest and verifies that the database has the correct number of tables --- pyfpdb/RegressionTest.py | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 pyfpdb/RegressionTest.py diff --git a/pyfpdb/RegressionTest.py b/pyfpdb/RegressionTest.py new file mode 100644 index 00000000..10bf071d --- /dev/null +++ b/pyfpdb/RegressionTest.py @@ -0,0 +1,51 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + + +############################################################################ +# +# File for Regression Testing fpdb +# + +import os +import sys + +import fpdb_db +import FpdbSQLQueries + +import unittest + +class TestSequenceFunctions(unittest.TestCase): + + def setUp(self): + """Configure MySQL settings/database and establish connection""" + self.mysql_settings={ 'db-host':"localhost", 'db-backend':2, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"} + self.mysql_db = fpdb_db.fpdb_db() + self.mysql_db.connect(self.mysql_settings['db-backend'], self.mysql_settings['db-host'], + self.mysql_settings['db-databaseName'], self.mysql_settings['db-user'], + self.mysql_settings['db-password']) + self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB') + + + def testDatabaseConnection(self): + """Test all supported DBs""" + result = self.mysql_db.cursor.execute("SHOW TABLES") + self.failUnless(result==13, "Number of tables in database incorrect. Expected 13 got " + str(result)) + +if __name__ == '__main__': + unittest.main() + From 66033036cc8a7b7bc446e3e14dcdd03807e4128f Mon Sep 17 00:00:00 2001 From: Carl Gherardi Date: Tue, 7 Oct 2008 17:25:24 +0800 Subject: [PATCH 145/262] Adds table create SQL to file. Included empty stubs for SQLite. --- pyfpdb/FpdbSQLQueries.py | 530 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 530 insertions(+) diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 6adb1367..11982168 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -43,6 +43,536 @@ class FpdbSQLQueries: if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): self.query['drop_table'] = """DROP TABLE IF EXISTS """ + + ################################ + # Create Tables + ################################ + + + + + ################################ + # Create Settings + ################################ + if(self.dbname == 'MySQL InnoDB'): + self.query['createSettingsTable'] = """CREATE TABLE Settings ( + version SMALLINT NOT NULL) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createSettingsTable'] = """CREATE TABLE Settings (version SMALLINT)""" + + elif(self.dbname == 'SQLite'): + #Probably doesn't work. + self.query['createSettingsTable'] = """ """ + + + ################################ + # Create Sites + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createSitesTable'] = """CREATE TABLE Sites ( + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + name varchar(32) NOT NULL, + currency char(3) NOT NULL) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createSitesTable'] = """CREATE TABLE Sites ( + id SERIAL UNIQUE, PRIMARY KEY (id), + name varchar(32), + currency char(3))""" + elif(self.dbname == 'SQLite'): + self.query['createSitesTable'] = """ """ + + + ################################ + # Create Gametypes + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createGametypesTable'] = """CREATE TABLE Gametypes ( + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), + type char(4) NOT NULL, + base char(4) NOT NULL, + category varchar(9) NOT NULL, + limitType char(2) NOT NULL, + hiLo char(1) NOT NULL, + smallBlind int, + bigBlind int, + smallBet int NOT NULL, + bigBet int NOT NULL) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createGametypesTable'] = """CREATE TABLE Gametypes ( + id SERIAL UNIQUE, PRIMARY KEY (id), + siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), + type char(4), + base char(4), + category varchar(9), + limitType char(2), + hiLo char(1), + smallBlind int, + bigBlind int, + smallBet int, + bigBet int)""" + elif(self.dbname == 'SQLite'): + self.query['createGametypesTable'] = """ """ + + + ################################ + # Create Players + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createPlayersTable'] = """CREATE TABLE Players ( + id INT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + name VARCHAR(32) CHARACTER SET utf8 NOT NULL, + siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), + comment text, + commentTs DATETIME) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createPlayersTable'] = """CREATE TABLE Players ( + id SERIAL UNIQUE, PRIMARY KEY (id), + name VARCHAR(32), + siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), + comment text, + commentTs timestamp without time zone)""" + elif(self.dbname == 'SQLite'): + self.query['createPlayersTable'] = """ """ + + + ################################ + # Create Autorates + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createAutoratesTable'] = """CREATE TABLE Autorates ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + description varchar(50) NOT NULL, + shortDesc char(8) NOT NULL, + ratingTime DATETIME NOT NULL, + handCount int NOT NULL) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createAutoratesTable'] = """CREATE TABLE Autorates ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + description varchar(50), + shortDesc char(8), + ratingTime timestamp without time zone, + handCount int)""" + elif(self.dbname == 'SQLite'): + self.query['createAutoratesTable'] = """ """ + + + ################################ + # Create Hands + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createHandsTable'] = """CREATE TABLE Hands ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + tableName VARCHAR(20) NOT NULL, + siteHandNo BIGINT NOT NULL, + gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + handStart DATETIME NOT NULL, + importTime DATETIME NOT NULL, + seats SMALLINT NOT NULL, + maxSeats SMALLINT NOT NULL, + comment TEXT, + commentTs DATETIME) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createHandsTable'] = """CREATE TABLE Hands ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + tableName VARCHAR(20), + siteHandNo BIGINT, + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + handStart timestamp without time zone, + importTime timestamp without time zone, + seats SMALLINT, + maxSeats SMALLINT, + comment TEXT, + commentTs timestamp without time zone)""" + elif(self.dbname == 'SQLite'): + self.query['createHandsTable'] = """ """ + + + ################################ + # Create Gametypes + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createBoardCardsTable'] = """CREATE TABLE BoardCards ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + handId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handId) REFERENCES Hands(id), + card1Value smallint NOT NULL, + card1Suit char(1) NOT NULL, + card2Value smallint NOT NULL, + card2Suit char(1) NOT NULL, + card3Value smallint NOT NULL, + card3Suit char(1) NOT NULL, + card4Value smallint NOT NULL, + card4Suit char(1) NOT NULL, + card5Value smallint NOT NULL, + card5Suit char(1) NOT NULL) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createBoardCardsTable'] = """CREATE TABLE BoardCards ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1))""" + elif(self.dbname == 'SQLite'): + self.query['createBoardCardsTable'] = """ """ + + + ################################ + # Create TourneyTypes + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createTourneyTypesTable'] = """CREATE TABLE TourneyTypes ( + id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), + buyin INT NOT NULL, + fee INT NOT NULL, + knockout INT NOT NULL, + rebuyOrAddon BOOLEAN NOT NULL) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createTourneyTypesTable'] = """CREATE TABLE TourneyTypes ( + id SERIAL, PRIMARY KEY (id), + siteId INT, FOREIGN KEY (siteId) REFERENCES Sites(id), + buyin INT, + fee INT, + knockout INT, + rebuyOrAddon BOOLEAN)""" + elif(self.dbname == 'SQLite'): + self.query['createTourneyTypesTable'] = """ """ + + + ################################ + # Create Tourneys + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createTourneysTable'] = """CREATE TABLE Tourneys ( + id INT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + siteTourneyNo BIGINT NOT NULL, + entries INT NOT NULL, + prizepool INT NOT NULL, + startTime DATETIME NOT NULL, + comment TEXT, + commentTs DATETIME) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createTourneysTable'] = """CREATE TABLE Tourneys ( + id SERIAL UNIQUE, PRIMARY KEY (id), + tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + siteTourneyNo BIGINT, + entries INT, + prizepool INT, + startTime timestamp without time zone, + comment TEXT, + commentTs timestamp without time zone)""" + elif(self.dbname == 'SQLite'): + self.query['createTourneysTable'] = """ """ + + ################################ + # Create HandsPlayers + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createHandsPlayersTable'] = """CREATE TABLE HandsPlayers ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + handId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handId) REFERENCES Hands(id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + startCash INT NOT NULL, + position CHAR(1), + seatNo SMALLINT NOT NULL, + ante INT, + + card1Value smallint NOT NULL, + card1Suit char(1) NOT NULL, + card2Value smallint NOT NULL, + card2Suit char(1) NOT NULL, + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1), + card6Value smallint, + card6Suit char(1), + card7Value smallint, + card7Suit char(1), + + winnings int NOT NULL, + rake int NOT NULL, + comment text, + commentTs DATETIME, + + tourneysPlayersId BIGINT UNSIGNED, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id)) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createHandsPlayersTable'] = """CREATE TABLE HandsPlayers ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + startCash INT, + position CHAR(1), + seatNo SMALLINT, + ante INT, + + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1), + card6Value smallint, + card6Suit char(1), + card7Value smallint, + card7Suit char(1), + + winnings int, + rake int, + comment text, + commentTs timestamp without time zone, + tourneysPlayersId BIGINT, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id))""" + elif(self.dbname == 'SQLite'): + self.query['createHandsPlayersTable'] = """ """ + + + ################################ + # Create TourneysPlayers + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createTourneysPlayersTable'] = """CREATE TABLE TourneysPlayers ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + tourneyId INT UNSIGNED NOT NULL, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + payinAmount INT NOT NULL, + rank INT NOT NULL, + winnings INT NOT NULL, + comment TEXT, + commentTs DATETIME) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createTourneysPlayersTable'] = """CREATE TABLE TourneysPlayers ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + tourneyId INT, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + payinAmount INT, + rank INT, + winnings INT, + comment TEXT, + commentTs timestamp without time zone)""" + elif(self.dbname == 'SQLite'): + self.query['createTourneysPlayersTable'] = """ """ + + + ################################ + # Create HandsActions + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createHandsActionsTable'] = """CREATE TABLE HandsActions ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + handPlayerId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), + street SMALLINT NOT NULL, + actionNo SMALLINT NOT NULL, + action CHAR(5) NOT NULL, + allIn BOOLEAN NOT NULL, + amount INT NOT NULL, + comment TEXT, + commentTs DATETIME) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createHandsActionsTable'] = """CREATE TABLE HandsActions ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handPlayerId BIGINT, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), + street SMALLINT, + actionNo SMALLINT, + action CHAR(5), + allIn BOOLEAN, + amount INT, + comment TEXT, + commentTs timestamp without time zone)""" + elif(self.dbname == 'SQLite'): + self.query['createHandsActionsTable'] = """ """ + + + ################################ + # Create HudCache + ################################ + + if(self.dbname == 'MySQL InnoDB'): + self.query['createHudCacheTable'] = """CREATE TABLE HudCache ( + id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), + activeSeats SMALLINT NOT NULL, + position CHAR(1), + tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + + HDs INT NOT NULL, + street0VPI INT NOT NULL, + street0Aggr INT NOT NULL, + street0_3B4BChance INT NOT NULL, + street0_3B4BDone INT NOT NULL, + + street1Seen INT NOT NULL, + street2Seen INT NOT NULL, + street3Seen INT NOT NULL, + street4Seen INT NOT NULL, + sawShowdown INT NOT NULL, + + street1Aggr INT NOT NULL, + street2Aggr INT NOT NULL, + street3Aggr INT NOT NULL, + street4Aggr INT NOT NULL, + + otherRaisedStreet1 INT NOT NULL, + otherRaisedStreet2 INT NOT NULL, + otherRaisedStreet3 INT NOT NULL, + otherRaisedStreet4 INT NOT NULL, + foldToOtherRaisedStreet1 INT NOT NULL, + foldToOtherRaisedStreet2 INT NOT NULL, + foldToOtherRaisedStreet3 INT NOT NULL, + foldToOtherRaisedStreet4 INT NOT NULL, + wonWhenSeenStreet1 FLOAT NOT NULL, + wonAtSD FLOAT NOT NULL, + + stealAttemptChance INT NOT NULL, + stealAttempted INT NOT NULL, + foldBbToStealChance INT NOT NULL, + foldedBbToSteal INT NOT NULL, + foldSbToStealChance INT NOT NULL, + foldedSbToSteal INT NOT NULL, + + street1CBChance INT NOT NULL, + street1CBDone INT NOT NULL, + street2CBChance INT NOT NULL, + street2CBDone INT NOT NULL, + street3CBChance INT NOT NULL, + street3CBDone INT NOT NULL, + street4CBChance INT NOT NULL, + street4CBDone INT NOT NULL, + + foldToStreet1CBChance INT NOT NULL, + foldToStreet1CBDone INT NOT NULL, + foldToStreet2CBChance INT NOT NULL, + foldToStreet2CBDone INT NOT NULL, + foldToStreet3CBChance INT NOT NULL, + foldToStreet3CBDone INT NOT NULL, + foldToStreet4CBChance INT NOT NULL, + foldToStreet4CBDone INT NOT NULL, + + totalProfit INT NOT NULL, + + street1CheckCallRaiseChance INT NOT NULL, + street1CheckCallRaiseDone INT NOT NULL, + street2CheckCallRaiseChance INT NOT NULL, + street2CheckCallRaiseDone INT NOT NULL, + street3CheckCallRaiseChance INT NOT NULL, + street3CheckCallRaiseDone INT NOT NULL, + street4CheckCallRaiseChance INT NOT NULL, + street4CheckCallRaiseDone INT NOT NULL) + ENGINE=INNODB""" + elif(self.dbname == 'PostgreSQL'): + self.query['createHudCacheTable'] = """CREATE TABLE HudCache ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + activeSeats SMALLINT, + position CHAR(1), + tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + + HDs INT, + street0VPI INT, + street0Aggr INT, + street0_3B4BChance INT, + street0_3B4BDone INT, + street1Seen INT, + street2Seen INT, + street3Seen INT, + street4Seen INT, + sawShowdown INT, + street1Aggr INT, + street2Aggr INT, + street3Aggr INT, + street4Aggr INT, + otherRaisedStreet1 INT, + otherRaisedStreet2 INT, + otherRaisedStreet3 INT, + otherRaisedStreet4 INT, + foldToOtherRaisedStreet1 INT, + foldToOtherRaisedStreet2 INT, + foldToOtherRaisedStreet3 INT, + foldToOtherRaisedStreet4 INT, + wonWhenSeenStreet1 FLOAT, + wonAtSD FLOAT, + + stealAttemptChance INT, + stealAttempted INT, + foldBbToStealChance INT, + foldedBbToSteal INT, + foldSbToStealChance INT, + foldedSbToSteal INT, + + street1CBChance INT, + street1CBDone INT, + street2CBChance INT, + street2CBDone INT, + street3CBChance INT, + street3CBDone INT, + street4CBChance INT, + street4CBDone INT, + + foldToStreet1CBChance INT, + foldToStreet1CBDone INT, + foldToStreet2CBChance INT, + foldToStreet2CBDone INT, + foldToStreet3CBChance INT, + foldToStreet3CBDone INT, + foldToStreet4CBChance INT, + foldToStreet4CBDone INT, + + totalProfit INT, + + street1CheckCallRaiseChance INT, + street1CheckCallRaiseDone INT, + street2CheckCallRaiseChance INT, + street2CheckCallRaiseDone INT, + street3CheckCallRaiseChance INT, + street3CheckCallRaiseDone INT, + street4CheckCallRaiseChance INT, + street4CheckCallRaiseDone INT)""" + elif(self.dbname == 'SQLite'): + self.query['createHudCacheTable'] = """ """ + + if __name__== "__main__": from optparse import OptionParser From 791ac7418da485cd0e86d64acb4ba6a6a93c2b7b Mon Sep 17 00:00:00 2001 From: Carl Gherardi Date: Tue, 7 Oct 2008 17:33:37 +0800 Subject: [PATCH 146/262] Converts fpdb_db to use the query dictionary for table creation --- pyfpdb/fpdb_db.py | 224 ++++------------------------------------------ 1 file changed, 17 insertions(+), 207 deletions(-) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 36e39ba9..bf77d1a8 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -17,12 +17,14 @@ import os import fpdb_simple +import FpdbSQLQueries class fpdb_db: def __init__(self): """Simple constructor, doesnt really do anything""" self.db=None self.cursor=None + self.sql = {} self.MYSQL_INNODB=2 self.PGSQL=3 self.SQLITE=4 @@ -44,6 +46,8 @@ class fpdb_db: else: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) self.cursor=self.db.cursor() + # Set up query dictionary as early in the connection process as we can. + self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) self.wrongDbVersion=False try: self.cursor.execute("SELECT * FROM Settings") @@ -181,213 +185,19 @@ class fpdb_db: self.drop_tables() - self.create_table("""Settings ( - version SMALLINT NOT NULL)""") - - self.create_table("""Sites ( - id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - name varchar(32) NOT NULL, - currency char(3) NOT NULL)""") - - self.create_table("""Gametypes ( - id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), - type char(4) NOT NULL, - base char(4) NOT NULL, - category varchar(9) NOT NULL, - limitType char(2) NOT NULL, - hiLo char(1) NOT NULL, - smallBlind int, - bigBlind int, - smallBet int NOT NULL, - bigBet int NOT NULL)""") - #NOT NULL not set for small/bigBlind as they are not existent in all games - - self.create_table("""Players ( - id INT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - name VARCHAR(32) CHARACTER SET utf8 NOT NULL, - siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), - comment text, - commentTs DATETIME)""") - - self.create_table("""Autorates ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), - gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - description varchar(50) NOT NULL, - shortDesc char(8) NOT NULL, - ratingTime DATETIME NOT NULL, - handCount int NOT NULL)""") - - self.create_table("""Hands ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - tableName VARCHAR(20) NOT NULL, - siteHandNo BIGINT NOT NULL, - gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - handStart DATETIME NOT NULL, - importTime DATETIME NOT NULL, - seats SMALLINT NOT NULL, - maxSeats SMALLINT NOT NULL, - comment TEXT, - commentTs DATETIME)""") - - self.create_table("""BoardCards ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - handId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handId) REFERENCES Hands(id), - card1Value smallint NOT NULL, - card1Suit char(1) NOT NULL, - card2Value smallint NOT NULL, - card2Suit char(1) NOT NULL, - card3Value smallint NOT NULL, - card3Suit char(1) NOT NULL, - card4Value smallint NOT NULL, - card4Suit char(1) NOT NULL, - card5Value smallint NOT NULL, - card5Suit char(1) NOT NULL)""") - - self.create_table("""TourneyTypes ( - id SMALLINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - siteId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (siteId) REFERENCES Sites(id), - buyin INT NOT NULL, - fee INT NOT NULL, - knockout INT NOT NULL, - rebuyOrAddon BOOLEAN NOT NULL)""") - - self.create_table("""Tourneys ( - id INT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), - siteTourneyNo BIGINT NOT NULL, - entries INT NOT NULL, - prizepool INT NOT NULL, - startTime DATETIME NOT NULL, - comment TEXT, - commentTs DATETIME)""") - - self.create_table("""TourneysPlayers ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - tourneyId INT UNSIGNED NOT NULL, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), - playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), - payinAmount INT NOT NULL, - rank INT NOT NULL, - winnings INT NOT NULL, - comment TEXT, - commentTs DATETIME)""") - - self.create_table("""HandsPlayers ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - handId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handId) REFERENCES Hands(id), - playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), - startCash INT NOT NULL, - position CHAR(1), - seatNo SMALLINT NOT NULL, - ante INT, - - card1Value smallint NOT NULL, - card1Suit char(1) NOT NULL, - card2Value smallint NOT NULL, - card2Suit char(1) NOT NULL, - card3Value smallint, - card3Suit char(1), - card4Value smallint, - card4Suit char(1), - card5Value smallint, - card5Suit char(1), - card6Value smallint, - card6Suit char(1), - card7Value smallint, - card7Suit char(1), - - winnings int NOT NULL, - rake int NOT NULL, - comment text, - commentTs DATETIME, - - tourneysPlayersId BIGINT UNSIGNED, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id))""") - #NOT NULL not set on cards 3-7 as they dont exist in all games - - self.create_table("""HandsActions ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - handPlayerId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), - street SMALLINT NOT NULL, - actionNo SMALLINT NOT NULL, - action CHAR(5) NOT NULL, - allIn BOOLEAN NOT NULL, - amount INT NOT NULL, - comment TEXT, - commentTs DATETIME)""") - - self.create_table("""HudCache ( - id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - playerId INT UNSIGNED NOT NULL, FOREIGN KEY (playerId) REFERENCES Players(id), - activeSeats SMALLINT NOT NULL, - position CHAR(1), - tourneyTypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), - - HDs INT NOT NULL, - street0VPI INT NOT NULL, - street0Aggr INT NOT NULL, - street0_3B4BChance INT NOT NULL, - street0_3B4BDone INT NOT NULL, - - street1Seen INT NOT NULL, - street2Seen INT NOT NULL, - street3Seen INT NOT NULL, - street4Seen INT NOT NULL, - sawShowdown INT NOT NULL, - - street1Aggr INT NOT NULL, - street2Aggr INT NOT NULL, - street3Aggr INT NOT NULL, - street4Aggr INT NOT NULL, - - otherRaisedStreet1 INT NOT NULL, - otherRaisedStreet2 INT NOT NULL, - otherRaisedStreet3 INT NOT NULL, - otherRaisedStreet4 INT NOT NULL, - foldToOtherRaisedStreet1 INT NOT NULL, - foldToOtherRaisedStreet2 INT NOT NULL, - foldToOtherRaisedStreet3 INT NOT NULL, - foldToOtherRaisedStreet4 INT NOT NULL, - wonWhenSeenStreet1 FLOAT NOT NULL, - wonAtSD FLOAT NOT NULL, - - stealAttemptChance INT NOT NULL, - stealAttempted INT NOT NULL, - foldBbToStealChance INT NOT NULL, - foldedBbToSteal INT NOT NULL, - foldSbToStealChance INT NOT NULL, - foldedSbToSteal INT NOT NULL, - - street1CBChance INT NOT NULL, - street1CBDone INT NOT NULL, - street2CBChance INT NOT NULL, - street2CBDone INT NOT NULL, - street3CBChance INT NOT NULL, - street3CBDone INT NOT NULL, - street4CBChance INT NOT NULL, - street4CBDone INT NOT NULL, - - foldToStreet1CBChance INT NOT NULL, - foldToStreet1CBDone INT NOT NULL, - foldToStreet2CBChance INT NOT NULL, - foldToStreet2CBDone INT NOT NULL, - foldToStreet3CBChance INT NOT NULL, - foldToStreet3CBDone INT NOT NULL, - foldToStreet4CBChance INT NOT NULL, - foldToStreet4CBDone INT NOT NULL, - - totalProfit INT NOT NULL, - - street1CheckCallRaiseChance INT NOT NULL, - street1CheckCallRaiseDone INT NOT NULL, - street2CheckCallRaiseChance INT NOT NULL, - street2CheckCallRaiseDone INT NOT NULL, - street3CheckCallRaiseChance INT NOT NULL, - street3CheckCallRaiseDone INT NOT NULL, - street4CheckCallRaiseChance INT NOT NULL, - street4CheckCallRaiseDone INT NOT NULL)""") - + self.cursor.execute(self.sql.query['createSettingsTable']) + self.cursor.execute(self.sql.query['createSitesTable']) + self.cursor.execute(self.sql.query['createGametypesTable']) + self.cursor.execute(self.sql.query['createPlayersTable']) + self.cursor.execute(self.sql.query['createAutoratesTable']) + self.cursor.execute(self.sql.query['createHandsTable']) + self.cursor.execute(self.sql.query['createBoardCardsTable']) + self.cursor.execute(self.sql.query['createTourneyTypesTable']) + self.cursor.execute(self.sql.query['createTourneysTable']) + self.cursor.execute(self.sql.query['createTourneysPlayersTable']) + self.cursor.execute(self.sql.query['createHandsPlayersTable']) + self.cursor.execute(self.sql.query['createHandsActionsTable']) + self.cursor.execute(self.sql.query['createHudCacheTable']) self.fillDefaultData() self.db.commit() print "finished recreating tables" From 3018cc660be29bf3b38e1ba055483cb2483bd855 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 8 Oct 2008 01:16:26 +0800 Subject: [PATCH 147/262] Move create tables code into its own function. Remove Postgres specific code --- pyfpdb/fpdb_db.py | 55 +++++++++++++++++------------------------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index bf77d1a8..a8a4c90e 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -89,7 +89,24 @@ class fpdb_db: #print "started fpdb_db.reconnect" self.disconnect(due_to_error) self.connect(self.backend, self.host, self.database, self.user, self.password) - #end def disconnect + + def create_tables(self): + #todo: should detect and fail gracefully if tables already exist. + self.cursor.execute(self.sql.query['createSettingsTable']) + self.cursor.execute(self.sql.query['createSitesTable']) + self.cursor.execute(self.sql.query['createGametypesTable']) + self.cursor.execute(self.sql.query['createPlayersTable']) + self.cursor.execute(self.sql.query['createAutoratesTable']) + self.cursor.execute(self.sql.query['createHandsTable']) + self.cursor.execute(self.sql.query['createBoardCardsTable']) + self.cursor.execute(self.sql.query['createTourneyTypesTable']) + self.cursor.execute(self.sql.query['createTourneysTable']) + self.cursor.execute(self.sql.query['createTourneysPlayersTable']) + self.cursor.execute(self.sql.query['createHandsPlayersTable']) + self.cursor.execute(self.sql.query['createHandsActionsTable']) + self.cursor.execute(self.sql.query['createHudCacheTable']) + self.fillDefaultData() +#end def disconnect def drop_tables(self): """Drops the fpdb tables from the current db""" @@ -165,41 +182,9 @@ class fpdb_db: def recreate_tables(self): """(Re-)creates the tables of the current DB""" - if self.backend == 3: -# postgresql - print "recreating tables in postgres db" - schema_file = open('schema.postgres.sql', 'r') - schema = schema_file.read() - schema_file.close() - curse = self.db.cursor() -# curse.executemany(schema, [1, 2]) - for sql in schema.split(';'): - sql = sql.rstrip() - if sql == '': - continue - curse.execute(sql) - #self.fillDefaultData() - self.db.commit() - curse.close() - return - self.drop_tables() - - self.cursor.execute(self.sql.query['createSettingsTable']) - self.cursor.execute(self.sql.query['createSitesTable']) - self.cursor.execute(self.sql.query['createGametypesTable']) - self.cursor.execute(self.sql.query['createPlayersTable']) - self.cursor.execute(self.sql.query['createAutoratesTable']) - self.cursor.execute(self.sql.query['createHandsTable']) - self.cursor.execute(self.sql.query['createBoardCardsTable']) - self.cursor.execute(self.sql.query['createTourneyTypesTable']) - self.cursor.execute(self.sql.query['createTourneysTable']) - self.cursor.execute(self.sql.query['createTourneysPlayersTable']) - self.cursor.execute(self.sql.query['createHandsPlayersTable']) - self.cursor.execute(self.sql.query['createHandsActionsTable']) - self.cursor.execute(self.sql.query['createHudCacheTable']) - self.fillDefaultData() + self.create_tables() self.db.commit() - print "finished recreating tables" + print "Finished recreating tables" #end def recreate_tables #end class fpdb_db From 3d43e8167c53158302adcf3970b20f1e0ede0eb0 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 8 Oct 2008 01:41:06 +0800 Subject: [PATCH 148/262] Make drop_tables generic for mysql so we dont end up with a function that has 20 if(dbversion ==) statements within --- pyfpdb/fpdb_db.py | 80 ++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index a8a4c90e..b5b16d41 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -16,6 +16,7 @@ #agpl-3.0.txt in the docs folder of the package. import os +import re import fpdb_simple import FpdbSQLQueries @@ -110,53 +111,40 @@ class fpdb_db: def drop_tables(self): """Drops the fpdb tables from the current db""" - oldDbVersion=0 - try: - self.cursor.execute("SELECT * FROM settings") #for alpha1 - oldDbVersion=self.cursor.fetchone()[0] - except:# _mysql_exceptions.ProgrammingError: - pass - try: - self.cursor.execute("SELECT * FROM Settings") - oldDbVersion=self.cursor.fetchone()[0] - except:# _mysql_exceptions.ProgrammingError: - pass - - if oldDbVersion<=34: - self.cursor.execute("DROP TABLE IF EXISTS settings;") - self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - self.cursor.execute("DROP TABLE IF EXISTS autorates;") - self.cursor.execute("DROP TABLE IF EXISTS board_cards;") - self.cursor.execute("DROP TABLE IF EXISTS hands_actions;") - self.cursor.execute("DROP TABLE IF EXISTS hands_players;") - self.cursor.execute("DROP TABLE IF EXISTS hands;") - self.cursor.execute("DROP TABLE IF EXISTS tourneys_players;") - self.cursor.execute("DROP TABLE IF EXISTS tourneys;") - self.cursor.execute("DROP TABLE IF EXISTS players;") - self.cursor.execute("DROP TABLE IF EXISTS gametypes;") - self.cursor.execute("DROP TABLE IF EXISTS sites;") - - if oldDbVersion>34 and oldDbVersion<=45: - self.cursor.execute("DROP TABLE IF EXISTS HudDataHoldemOmaha;") - - self.cursor.execute("DROP TABLE IF EXISTS Settings;") - self.cursor.execute("DROP TABLE IF EXISTS HudCache;") - self.cursor.execute("DROP TABLE IF EXISTS Autorates;") - self.cursor.execute("DROP TABLE IF EXISTS BoardCards;") - self.cursor.execute("DROP TABLE IF EXISTS HandsActions;") - self.cursor.execute("DROP TABLE IF EXISTS HandsPlayers;") - self.cursor.execute("DROP TABLE IF EXISTS Hands;") - self.cursor.execute("DROP TABLE IF EXISTS TourneysPlayers;") - self.cursor.execute("DROP TABLE IF EXISTS Tourneys;") - self.cursor.execute("DROP TABLE IF EXISTS Players;") - self.cursor.execute("DROP TABLE IF EXISTS Gametypes;") - if oldDbVersion>45 and oldDbVersion<=51: - self.cursor.execute("DROP TABLE IF EXISTS TourneysGametypes;") - self.cursor.execute("DROP TABLE IF EXISTS TourneyTypes;") - self.cursor.execute("DROP TABLE IF EXISTS Sites;") - - self.db.commit() + + if(self.get_backend_name() == 'MySQL InnoDB'): + # Query the DB to see what tables exist + self.cursor.execute('SHOW TABLES') + #Databases with FOREIGN KEY support need this switched of before you can drop tables + self.drop_referencial_integrity() + + for table in self.cursor: + self.cursor.execute(self.sql.query['drop_table'] + table[0]) + elif(self.get_backend_name() == 'PostgreSQL'): + #todo: postgres version here + elif(self.get_backend_name() == 'SQLite'): + #todo: sqlite version here #end def drop_tables + + def drop_referencial_integrity(self): + """Update all tables to remove foreign keys""" + + self.cursor.execute('SHOW TABLES') # todo: move to FpdbSQLQueries + result = self.cursor.fetchall() + + for i in range(len(result)): + self.cursor.execute("SHOW CREATE TABLE " + result[i][0]) + inner = self.cursor.fetchall() + + for j in range(len(inner)): + # result[i][0] - Table name + # result[i][1] - CREATE TABLE parameters + #Searching for CONSTRAINT `tablename_ibfk_1` + for m in re.finditer('(ibfk_[0-9]+)', inner[j][1]): + key = "`" + inner[j][0] + "_" + m.group() + "`" + self.cursor.execute("ALTER TABLE " + inner[j][0] + " DROP FOREIGN KEY " + key) + self.db.commit() + #end drop_referencial_inegrity def get_backend_name(self): """Returns the name of the currently used backend""" From dc81ca854af46c1288e9b0c99afbd39be49bb93a Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 8 Oct 2008 01:50:43 +0800 Subject: [PATCH 149/262] Indentation fixes --- pyfpdb/fpdb_db.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index b5b16d41..c5507bf6 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -122,27 +122,29 @@ class fpdb_db: self.cursor.execute(self.sql.query['drop_table'] + table[0]) elif(self.get_backend_name() == 'PostgreSQL'): #todo: postgres version here + print "Empty function here" elif(self.get_backend_name() == 'SQLite'): #todo: sqlite version here + print "Empty function here" #end def drop_tables - def drop_referencial_integrity(self): - """Update all tables to remove foreign keys""" + def drop_referencial_integrity(self): + """Update all tables to remove foreign keys""" - self.cursor.execute('SHOW TABLES') # todo: move to FpdbSQLQueries - result = self.cursor.fetchall() + self.cursor.execute('SHOW TABLES') # todo: move to FpdbSQLQueries + result = self.cursor.fetchall() - for i in range(len(result)): - self.cursor.execute("SHOW CREATE TABLE " + result[i][0]) - inner = self.cursor.fetchall() + for i in range(len(result)): + self.cursor.execute("SHOW CREATE TABLE " + result[i][0]) + inner = self.cursor.fetchall() - for j in range(len(inner)): - # result[i][0] - Table name - # result[i][1] - CREATE TABLE parameters - #Searching for CONSTRAINT `tablename_ibfk_1` - for m in re.finditer('(ibfk_[0-9]+)', inner[j][1]): - key = "`" + inner[j][0] + "_" + m.group() + "`" - self.cursor.execute("ALTER TABLE " + inner[j][0] + " DROP FOREIGN KEY " + key) + for j in range(len(inner)): + # result[i][0] - Table name + # result[i][1] - CREATE TABLE parameters + #Searching for CONSTRAINT `tablename_ibfk_1` + for m in re.finditer('(ibfk_[0-9]+)', inner[j][1]): + key = "`" + inner[j][0] + "_" + m.group() + "`" + self.cursor.execute("ALTER TABLE " + inner[j][0] + " DROP FOREIGN KEY " + key) self.db.commit() #end drop_referencial_inegrity From 5dd9dbc86fb88f2520b315e3ec978382ac5c22e6 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 8 Oct 2008 02:28:18 +0800 Subject: [PATCH 150/262] Fix residual bugs in table droping and creation Add Regression test for recreating_tables in mysql --- pyfpdb/RegressionTest.py | 10 ++++++++-- pyfpdb/fpdb_db.py | 21 +++++---------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/pyfpdb/RegressionTest.py b/pyfpdb/RegressionTest.py index 10bf071d..da03c350 100644 --- a/pyfpdb/RegressionTest.py +++ b/pyfpdb/RegressionTest.py @@ -43,8 +43,14 @@ class TestSequenceFunctions(unittest.TestCase): def testDatabaseConnection(self): """Test all supported DBs""" - result = self.mysql_db.cursor.execute("SHOW TABLES") - self.failUnless(result==13, "Number of tables in database incorrect. Expected 13 got " + str(result)) + self.result = self.mysql_db.cursor.execute("SHOW TABLES") + self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + + def testMySQLRecreateTables(self): + """Test droping then recreating fpdb table schema""" + self.mysql_db.recreate_tables() + self.result = self.mysql_db.cursor.execute("SHOW TABLES") + self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) if __name__ == '__main__': unittest.main() diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index c5507bf6..6605aef6 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -61,20 +61,6 @@ class fpdb_db: self.wrongDbVersion=True #end def connect - def create_table(self, string): - """creates a table for the given string - The string should the name of the table followed by the column list - in brackets as if it were an SQL command. Do NOT include the "CREATE TABLES" - bit at the beginning nor the ";" or ENGINE= at the end""" - string="CREATE TABLE "+string - if (self.backend==self.MYSQL_INNODB): - string+=" ENGINE=INNODB" - string+=";" - #print "create_table, string:", string - self.cursor.execute(string) - self.db.commit() - #end def create_table - def disconnect(self, due_to_error=False): """Disconnects the DB""" if due_to_error: @@ -107,17 +93,18 @@ class fpdb_db: self.cursor.execute(self.sql.query['createHandsActionsTable']) self.cursor.execute(self.sql.query['createHudCacheTable']) self.fillDefaultData() + self.db.commit() #end def disconnect def drop_tables(self): """Drops the fpdb tables from the current db""" if(self.get_backend_name() == 'MySQL InnoDB'): - # Query the DB to see what tables exist - self.cursor.execute('SHOW TABLES') #Databases with FOREIGN KEY support need this switched of before you can drop tables self.drop_referencial_integrity() + # Query the DB to see what tables exist + self.cursor.execute('SHOW TABLES') for table in self.cursor: self.cursor.execute(self.sql.query['drop_table'] + table[0]) elif(self.get_backend_name() == 'PostgreSQL'): @@ -126,6 +113,8 @@ class fpdb_db: elif(self.get_backend_name() == 'SQLite'): #todo: sqlite version here print "Empty function here" + + self.db.commit() #end def drop_tables def drop_referencial_integrity(self): From 6aca36b564cded989983fc14ae8462ef7c353e6a Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 8 Oct 2008 03:12:38 +0800 Subject: [PATCH 151/262] Moved SHOW TABLES query to query dict as list_tables Added failing tests for Postgres until table deletion and table listing is fixed. --- pyfpdb/FpdbSQLQueries.py | 14 +++++++++++--- pyfpdb/RegressionTest.py | 21 ++++++++++++++++++++- pyfpdb/fpdb_db.py | 9 +++++---- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 11982168..7c1e2ec5 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -36,6 +36,17 @@ class FpdbSQLQueries: # elif(self.dbname == 'PostgreSQL'): # elif(self.dbname == 'SQLite'): + + ################################ + # List tables + ################################ + if(self.dbname == 'MySQL InnoDB'): + self.query['list_tables'] = """SHOW TABLES""" + elif(self.dbname == 'PostgreSQL'): + self.query['list_tables'] = """SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'""" + elif(self.dbname == 'SQLite'): + self.query['list_tables'] = """ """ + ################################################################## # Drop Tables - MySQL, PostgreSQL and SQLite all share same syntax ################################################################## @@ -48,9 +59,6 @@ class FpdbSQLQueries: # Create Tables ################################ - - - ################################ # Create Settings ################################ diff --git a/pyfpdb/RegressionTest.py b/pyfpdb/RegressionTest.py index da03c350..db1a88b9 100644 --- a/pyfpdb/RegressionTest.py +++ b/pyfpdb/RegressionTest.py @@ -40,10 +40,23 @@ class TestSequenceFunctions(unittest.TestCase): self.mysql_settings['db-password']) self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB') + """Configure Postgres settings/database and establish connection""" + self.pg_settings={ 'db-host':"localhost", 'db-backend':3, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"} + self.pg_db = fpdb_db.fpdb_db() + self.pg_db.connect(self.pg_settings['db-backend'], self.pg_settings['db-host'], + self.pg_settings['db-databaseName'], self.pg_settings['db-user'], + self.pg_settings['db-password']) + self.pgdict = FpdbSQLQueries.FpdbSQLQueries('PostgreSQL') + def testDatabaseConnection(self): """Test all supported DBs""" - self.result = self.mysql_db.cursor.execute("SHOW TABLES") + self.result = self.mysql_db.cursor.execute(self.mysqldict.query['list_tables']) + self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + + print self.pgdict.query['list_tables'] + + self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) def testMySQLRecreateTables(self): @@ -52,6 +65,12 @@ class TestSequenceFunctions(unittest.TestCase): self.result = self.mysql_db.cursor.execute("SHOW TABLES") self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + def testPostgresSQLRecreateTables(self): + """Test droping then recreating fpdb table schema""" + self.pg_db.recreate_tables() + self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) + self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + if __name__ == '__main__': unittest.main() diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 6605aef6..35aa3282 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -104,12 +104,13 @@ class fpdb_db: self.drop_referencial_integrity() # Query the DB to see what tables exist - self.cursor.execute('SHOW TABLES') + self.cursor.execute(self.sql.query['list_tables']) for table in self.cursor: self.cursor.execute(self.sql.query['drop_table'] + table[0]) elif(self.get_backend_name() == 'PostgreSQL'): - #todo: postgres version here - print "Empty function here" + self.cursor.execute(self.sql.query['list_tables']) + for table in self.cursor: + print table elif(self.get_backend_name() == 'SQLite'): #todo: sqlite version here print "Empty function here" @@ -120,7 +121,7 @@ class fpdb_db: def drop_referencial_integrity(self): """Update all tables to remove foreign keys""" - self.cursor.execute('SHOW TABLES') # todo: move to FpdbSQLQueries + self.cursor.execute(self.sql.query['list_tables']) result = self.cursor.fetchall() for i in range(len(result)): From 1e7b584dbab35a4ab82078313503aa7789057326 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 7 Oct 2008 19:56:01 -0400 Subject: [PATCH 152/262] changes to allow dropping of tables on postgres --- pyfpdb/fpdb_db.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 35aa3282..92a5ed86 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -108,9 +108,11 @@ class fpdb_db: for table in self.cursor: self.cursor.execute(self.sql.query['drop_table'] + table[0]) elif(self.get_backend_name() == 'PostgreSQL'): + self.db.commit()# I have no idea why this makes the query work--REB 07OCT2008 self.cursor.execute(self.sql.query['list_tables']) - for table in self.cursor: - print table + tables = self.cursor.fetchall() + for table in tables: + self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade') elif(self.get_backend_name() == 'SQLite'): #todo: sqlite version here print "Empty function here" @@ -154,8 +156,8 @@ class fpdb_db: def fillDefaultData(self): self.cursor.execute("INSERT INTO Settings VALUES (118);") - self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"Full Tilt Poker\", 'USD');") - self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, \"PokerStars\", 'USD');") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');") self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") #end def fillDefaultData From 9e819f3ad24941510bf7af9593c9ccbc16d936a1 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 8 Oct 2008 05:47:47 +0100 Subject: [PATCH 153/262] p122 - started on table design for draw games --- docs/tabledesign.html | 9 +++++++-- pyfpdb/HUD_main.py | 0 pyfpdb/fpdb.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) mode change 100644 => 100755 pyfpdb/HUD_main.py diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 8fcf4884..e326ce65 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -141,7 +141,8 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    + stud - Stud and Razz
    + draw - (incl Badugi)

    @@ -152,7 +153,11 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    omahahilo=Omaha 8 or better
    razz=Razz
    studhi=7 Card Stud High only
    - studhl=7 Card Stud 8 orbetter

    + studhilo=7 Card Stud 8 or better
    + fivedraw=Five Card Draw
    + 27sgldraw=2-7 Single Draw
    + 27tridraw=2-7 Tripple Draw
    + badugi=Badugi

    diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py old mode 100644 new mode 100755 diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 841d71c0..56a24c5b 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -445,7 +445,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") ("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), ("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), - ("/Viewers/Poker_table Viewer (obselete)", "T", self.tab_table_viewer, 0, None ), + ("/Viewers/Poker_table Viewer", "T", self.tab_table_viewer, 0, None ), #( "/Viewers/Tourney Replayer ( "/_Database", None, None, 0, "" ), ( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ), From b20c44d147db10c59d5d873c3bf7184860e0a242 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 8 Oct 2008 06:00:49 +0100 Subject: [PATCH 154/262] p123 - finished adapting table design for initial draw support. --- docs/tabledesign.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tabledesign.html b/docs/tabledesign.html index e326ce65..47b7a72e 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -302,7 +302,7 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    - + @@ -312,7 +312,8 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    Field name

    street0VPI

    int

    number of hands where player paid to see flop

    number of hands where player paid to see flop
    + Please note that this already includes street0Aggr!
    + To calculate limp just deduct street0Aggr from this number

    street0Aggr

    action

    char(5)

    Bet stands for bring in, complete, bet, double bet, raise and double raise, since they all - technically - do the same thing. Unbet is used for when an uncalled bet is returned.

    +

    Bet stands for bring in, complete, bet, double bet, raise and double raise, since they all - technically - do the same thing. Unbet is used for when an uncalled bet is returned, this will have a negative value for amount.

    Other valid values: blind call check fold

    Bet stands for bring in, complete, bet, double bet, raise and double raise, since they all - technically - do the same thing. Unbet is used for when an uncalled bet is returned, this will have a negative value for amount.

    Other valid values: blind call check fold

    allIn

    boolean

    Whether the player went all-in on this action

    amount

    int

    char(4)

    The underlying structure. valid entries:
    hold - Holdem and Omaha
    - stud - Stud and Razz

    category

    limitType

    cardXValue

    smallint

    2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x

    2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x, kept=k (draw only)

    cardXSuit


    Table HandsPlayers

    -

    cardX: can be 1 through 7, one for each card. In holdem/omaha this stores the hole cards so 3-7 or 5-7 are empty

    +

    cardX: can be 1 through 20, one for each card. In holdem only 1-2 of these are used, in omaha 1-4, in stud/razz 1-7, in single draw games 1-10 is used and in badugi 1-16 (4*4) is used.

    +

    For the draw games: the first 5 (badugi: 4) cards are the initial cards, the next 5 (badugi: 4) are after the first draw. If a player keeps some cards then those cards' spaces are filled with "k", short for "kept". E.g. if a player gets 2-6 spades for his first five cards and decides to throw away the 4 and then gets a 7 of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 4, 5, 6, 7, k, k, k, k

    I did not separate this into an extra table because I felt the lost space is not sufficiently large. Also the benefit for searching is far less relevant.

    From e23ae25d0032f7a12127ec1a4fb1e24cd1877e74 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 8 Oct 2008 06:28:09 +0100 Subject: [PATCH 155/262] p124 - renamed categories for 27 single/tripple draw. updated abbreviations. --- docs/abbreviations.txt | 4 ++++ docs/tabledesign.html | 4 ++-- pyfpdb/fpdb.py | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/abbreviations.txt b/docs/abbreviations.txt index fd02a6b1..a1ca4f50 100644 --- a/docs/abbreviations.txt +++ b/docs/abbreviations.txt @@ -5,8 +5,11 @@ AF=Flop Bet/Raise percentage AT=River Bet/Raise percentage AR=Turn Bet/Raise percentage F3-7=3rd-7th street Fold percentage +FB=like FSB but for big blinds only FF=Flop Fold percentage FR=River Fold percentage +FSB=Fold to steal - combined of small and big blind (FSB means Fold Small Big). E.g. if a player faced a steal attempt in the SB 7 times and +FS=like FSB but for small blinds only. FT=Turn Fold percentage HD=Hands PF3B4B=Pre Flop 3Bet or 4Bet @@ -14,6 +17,7 @@ PFR=Pre Flop Raise Postf A=Postflop (ie. flop+turn+river) Aggression% Postf F=Postflop Fold % SD/F=Showdown/Flop=WtSD=How often player went to showdown when he saw the flop +ST=Steal chance (nobody had entered the pot before the player in question, and the player is in cutoff or button position) W$wsF=Won $ when he saw flop W$@SD=Won $ at showdown VPI3=Voluntary Put In on 3rd Street (ie. call+complete+raise) diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 47b7a72e..45d09d5a 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -155,8 +155,8 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt

    studhi=7 Card Stud High only
    studhilo=7 Card Stud 8 or better
    fivedraw=Five Card Draw
    - 27sgldraw=2-7 Single Draw
    - 27tridraw=2-7 Tripple Draw
    + 27_1draw=2-7 Single Draw
    + 27_3draw=2-7 Tripple Draw
    badugi=Badugi

    diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 56a24c5b..da58e6ef 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -421,7 +421,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha6+, p118 or higher") + self.window.set_title("Free Poker DB - version: alpha6+, p124 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) @@ -445,7 +445,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") ("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), ("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), - ("/Viewers/Poker_table Viewer", "T", self.tab_table_viewer, 0, None ), + ("/Viewers/Poker_table Viewer (mostly obselete)", "T", self.tab_table_viewer, 0, None ), #( "/Viewers/Tourney Replayer ( "/_Database", None, None, 0, "" ), ( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ), From 75ff2f7d429cedb038a2b971b35c797a38dfb6a1 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 8 Oct 2008 06:37:16 +0100 Subject: [PATCH 156/262] p125 - expanded explanation of cardXValue for draw --- docs/tabledesign.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/tabledesign.html b/docs/tabledesign.html index 45d09d5a..ce39d4bc 100644 --- a/docs/tabledesign.html +++ b/docs/tabledesign.html @@ -313,7 +313,10 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt


    Table HandsPlayers

    cardX: can be 1 through 20, one for each card. In holdem only 1-2 of these are used, in omaha 1-4, in stud/razz 1-7, in single draw games 1-10 is used and in badugi 1-16 (4*4) is used.

    -

    For the draw games: the first 5 (badugi: 4) cards are the initial cards, the next 5 (badugi: 4) are after the first draw. If a player keeps some cards then those cards' spaces are filled with "k", short for "kept". E.g. if a player gets 2-6 spades for his first five cards and decides to throw away the 4 and then gets a 7 of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 4, 5, 6, 7, k, k, k, k

    +

    For the draw games: the first 5 (badugi: 4) cards are the initial cards, the next 5 (badugi: 4) are after the first draw. If a player keeps some cards then those cards' spaces are filled with "k", short for "kept".
    +Example 1: If a player gets 2-6 spades for his first five cards and decides to throw away the 4 and then gets a 7 of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 4, 5, 6, k, k, 7, k, k
    +Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and decides to throw away the 2 and the 3 and then gets a Q and K of spades then the first 10 fields of cardXValue would be as follows: 2, 3, 5, 8, J, Q, K, k, k, k
    +Note that it will k in the space of which card was there previously, so in example 2 where the player kept the last 3 cards, the last 3 fields of the first draw (ie. card8-10Value) are replaced with k.

    I did not separate this into an extra table because I felt the lost space is not sufficiently large. Also the benefit for searching is far less relevant.

    From 79651706f67651602b3d612f62ea53958ee04f06 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 8 Oct 2008 19:53:25 +0800 Subject: [PATCH 157/262] Make GuiGraphViewer use the query file. Make minor adjustment to Graph --- pyfpdb/FpdbSQLQueries.py | 36 ++++++++++++++++++++++++++++++++ pyfpdb/GuiGraphViewer.py | 44 +++++++++++++++++++--------------------- pyfpdb/fpdb.py | 5 ++++- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 7c1e2ec5..e582c681 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -580,6 +580,42 @@ class FpdbSQLQueries: elif(self.dbname == 'SQLite'): self.query['createHudCacheTable'] = """ """ + ################################ + # Queries used in GuiGraphViewer + ################################ + + + # Returns all cash game handIds and the money won(winnings is the final pot) + # by the playerId for a single site. + if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'): + self.query['getRingWinningsAllGamesPlayerIdSite'] = """SELECT handId, winnings FROM HandsPlayers + INNER JOIN Players ON HandsPlayers.playerId = Players.id + INNER JOIN Hands ON Hands.id = HandsPlayers.handId + WHERE Players.name = %s AND Players.siteId = %s AND (tourneysPlayersId IS NULL) + ORDER BY handStart""" + elif(self.dbname == 'SQLite'): + #Probably doesn't work. + self.query['getRingWinningsAllGamesPlayerIdSite'] = """SELECT handId, winnings FROM HandsPlayers + INNER JOIN Players ON HandsPlayers.playerId = Players.id + INNER JOIN Hands ON Hands.id = HandsPlayers.handId + WHERE Players.name = %s AND Players.siteId = %s AND (tourneysPlayersId IS NULL) + ORDER BY handStart""" + + # Returns the profit for a given ring game handId, Total pot - money invested by playerId + if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'): + self.query['getRingProfitFromHandId'] = """SELECT SUM(amount) FROM HandsActions + INNER JOIN HandsPlayers ON HandsActions.handPlayerId = HandsPlayers.id + INNER JOIN Players ON HandsPlayers.playerId = Players.id + WHERE Players.name = %s AND HandsPlayers.handId = %s + AND Players.siteId = %s AND (tourneysPlayersId IS NULL)""" + elif(self.dbname == 'SQLite'): + #Probably doesn't work. + self.query['getRingProfitFromHandId'] = """SELECT SUM(amount) FROM HandsActions + INNER JOIN HandsPlayers ON HandsActions.handPlayerId = HandsPlayers.id + INNER JOIN Players ON HandsPlayers.playerId = Players.id + WHERE Players.name = %s AND HandsPlayers.handId = %s + AND Players.siteId = %s AND (tourneysPlayersId IS NULL)""" + if __name__== "__main__": from optparse import OptionParser diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index e441f8ba..5b3fa081 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -69,30 +69,12 @@ class GuiGraphViewer (threading.Thread): self.ax.set_xlabel("Hands", fontsize = 12) self.ax.set_ylabel("$", fontsize = 12) self.ax.grid(color='g', linestyle=':', linewidth=0.2) - - self.cursor.execute("""SELECT handId, winnings FROM HandsPlayers - INNER JOIN Players ON HandsPlayers.playerId = Players.id - INNER JOIN Hands ON Hands.id = HandsPlayers.handId - WHERE Players.name = %s AND Players.siteId = %s AND (tourneysPlayersId IS NULL) - ORDER BY siteHandNo""", (name, site)) - winnings = self.db.cursor.fetchall() - - profit=range(len(winnings)) - for i in profit: - self.cursor.execute("""SELECT SUM(amount) FROM HandsActions - INNER JOIN HandsPlayers ON HandsActions.handPlayerId = HandsPlayers.id - INNER JOIN Players ON HandsPlayers.playerId = Players.id - WHERE Players.name = %s AND HandsPlayers.handId = %s AND Players.siteId = %s AND (tourneysPlayersId IS NULL)""", (name, winnings[i][0], site)) - spent = self.db.cursor.fetchone() - profit[i]=(i, winnings[i][1]-spent[0]) - - y=map(lambda x:float(x[1]), profit) - line = cumsum(y) - line = line/100 - self.ax.annotate ("All Hands, Site %s", (61,25), xytext =(0.1, 0.9) , textcoords ="axes fraction" ,) - #Now draw plot + #Get graph data from DB + line = self.getRingProfitGraph(name, site) + + #Draw plot self.ax.plot(line,) self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea @@ -100,13 +82,29 @@ class GuiGraphViewer (threading.Thread): self.canvas.show() #end of def showClicked - def __init__(self, db, settings, debug=True): + def getRingProfitGraph(self, name, site): + self.cursor.execute(self.sql.query['getRingWinningsAllGamesPlayerIdSite'], (name, site)) + winnings = self.db.cursor.fetchall() + + profit=range(len(winnings)) + for i in profit: + self.cursor.execute(self.sql.query['getRingProfitFromHandId'], (name, winnings[i][0], site)) + spent = self.db.cursor.fetchone() + profit[i]=(i, winnings[i][1]-spent[0]) + + y=map(lambda x:float(x[1]), profit) + line = cumsum(y) + return line/100 + #end of def getRingProfitGraph + + def __init__(self, db, settings, querylist, debug=True): """Constructor for GraphViewer""" self.debug=debug #print "start of GraphViewer constructor" self.db=db self.cursor=db.cursor self.settings=settings + self.sql=querylist self.mainVBox = gtk.VBox(False, 0) self.mainVBox.show() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 841d71c0..8c8ac5e9 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -344,6 +344,9 @@ class fpdb: response = diaDbVersionWarning.run() diaDbVersionWarning.destroy() + + # Database connected to successfully, load queries to pass on to other classes + self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name()) #end def load_profile def not_implemented(self): @@ -407,7 +410,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") def tabGraphViewer(self, widget, data): """opens a graph viewer tab""" #print "start of tabGraphViewer" - new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings) + new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings,self.querydict) self.threads.append(new_gv_thread) gv_tab=new_gv_thread.get_vbox() self.add_and_display_tab(gv_tab, "Graphs") From 98b556f42c67bf4571dc71ea60d2605c6dd442b9 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 9 Oct 2008 01:36:08 +0800 Subject: [PATCH 158/262] Turn fpdb_import functions into class Importer Fix all callers of fpdb_import --- pyfpdb/GuiAutoImport.py | 3 ++- pyfpdb/GuiBulkImport.py | 5 +++-- pyfpdb/GuiTableViewer.py | 3 ++- pyfpdb/HUD_main.py | 0 pyfpdb/RegressionTest.py | 6 +++--- pyfpdb/fpdb.py | 1 + pyfpdb/fpdb_import.py | 25 +++++++++++++++---------- 7 files changed, 26 insertions(+), 17 deletions(-) mode change 100644 => 100755 pyfpdb/HUD_main.py diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 5081022f..4b594d52 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -57,7 +57,7 @@ class GuiAutoImport (threading.Thread): self.inputFile = os.path.join(self.path, file) stat_info = os.stat(self.inputFile) if not self.import_files.has_key(self.inputFile) or stat_info.st_mtime > self.import_files[self.inputFile]: - fpdb_import.import_file_dict(self, self.settings, callHud = True) + self.importer.import_file_dict(self, self.settings, callHud = True) self.import_files[self.inputFile] = stat_info.st_mtime print "GuiAutoImport.import_dir done" @@ -121,6 +121,7 @@ class GuiAutoImport (threading.Thread): def __init__(self, settings, debug=True): """Constructor for GuiAutoImport""" self.settings=settings + self.importer = fpdb_import.Importer() self.server=settings['db-host'] self.user=settings['db-user'] diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 0a6bd10d..cd1277b6 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -32,7 +32,7 @@ class GuiBulkImport (threading.Thread): print "BulkImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file - fpdb_import.import_file_dict(self, self.settings, False) + self.importer.import_file_dict(self, self.settings, False) print "GuiBulkImport.import_dir done" def load_clicked(self, widget, data=None): @@ -69,7 +69,7 @@ class GuiBulkImport (threading.Thread): if os.path.isdir(self.inputFile): self.import_dir() else: - fpdb_import.import_file_dict(self, self.settings, False) + self.importer.import_file_dict(self, self.settings, False) def get_vbox(self): """returns the vbox of this thread""" @@ -83,6 +83,7 @@ class GuiBulkImport (threading.Thread): def __init__(self, db, settings): self.db=db self.settings=settings + self.importer = fpdb_import.Importer() self.vbox=gtk.VBox(False,1) self.vbox.show() diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 15a21149..3582971f 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -255,8 +255,9 @@ class GuiTableViewer (threading.Thread): self.failOnError=False self.minPrint=0 self.handCount=0 + self.importer = fpdb_import.Importer() - self.last_read_hand_id=fpdb_import.import_file_dict(self, self.settings, False) + self.last_read_hand_id=importer.import_file_dict(self, self.settings, False) #end def table_viewer.import_clicked def all_clicked(self, widget, data): diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py old mode 100644 new mode 100755 diff --git a/pyfpdb/RegressionTest.py b/pyfpdb/RegressionTest.py index db1a88b9..8a8facc5 100644 --- a/pyfpdb/RegressionTest.py +++ b/pyfpdb/RegressionTest.py @@ -57,19 +57,19 @@ class TestSequenceFunctions(unittest.TestCase): print self.pgdict.query['list_tables'] self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) - self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) def testMySQLRecreateTables(self): """Test droping then recreating fpdb table schema""" self.mysql_db.recreate_tables() self.result = self.mysql_db.cursor.execute("SHOW TABLES") - self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) def testPostgresSQLRecreateTables(self): """Test droping then recreating fpdb table schema""" self.pg_db.recreate_tables() self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) - self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) if __name__ == '__main__': unittest.main() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 8c8ac5e9..b4df905c 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -32,6 +32,7 @@ import GuiBulkImport import GuiTableViewer import GuiAutoImport import GuiGraphViewer +import FpdbSQLQueries class fpdb: def tab_clicked(self, widget, tab_name): diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 9c52d6bc..6fe8e28a 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -38,17 +38,22 @@ import fpdb_simple import fpdb_parse_logic from optparse import OptionParser +class Importer: -def import_file(server, database, user, password, inputFile): - self.server=server - self.database=database - self.user=user - self.password=password - self.inputFile=inputFile - self.settings={'imp-callFpdbHud':False} - import_file_dict(self, settings) + def __init__(self): + """Constructor""" -def import_file_dict(options, settings, callHud=False): + + def import_file(self, server, database, user, password, inputFile): + self.server=server + self.database=database + self.user=user + self.password=password + self.inputFile=inputFile + self.settings={'imp-callFpdbHud':False} + self.import_file_dict(self, settings) + + def import_file_dict(self, options, settings, callHud=False): last_read_hand=0 if (options.inputFile=="stdin"): inputFile=sys.stdin @@ -225,4 +230,4 @@ if __name__ == "__main__": (options, sys.argv) = parser.parse_args() settings={'imp-callFpdbHud':False, 'db-backend':2} - import_file_dict(options, settings, False) +# import_file_dict(options, settings, False) From 14ab9f68145e8bead9238d408759b5a00f3ec388 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 9 Oct 2008 01:48:16 +0800 Subject: [PATCH 159/262] Kill command line interface to fpdb_import until restructured --- pyfpdb/fpdb_import.py | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 6fe8e28a..e65a7ece 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -42,16 +42,7 @@ class Importer: def __init__(self): """Constructor""" - - - def import_file(self, server, database, user, password, inputFile): - self.server=server - self.database=database - self.user=user - self.password=password - self.inputFile=inputFile self.settings={'imp-callFpdbHud':False} - self.import_file_dict(self, settings) def import_file_dict(self, options, settings, callHud=False): last_read_hand=0 @@ -204,30 +195,4 @@ class Importer: if __name__ == "__main__": - failOnError=False - quiet=False - - #process CLI parameters - parser = OptionParser() - parser.add_option("-c", "--handCount", default="0", type="int", - help="Number of hands to import (default 0 means unlimited)") - parser.add_option("-d", "--database", default="fpdb", help="The MySQL database to use (default fpdb)") - parser.add_option("-e", "--errorFile", default="failed.txt", - help="File to store failed hands into. (default: failed.txt) Not implemented.") - parser.add_option("-f", "--inputFile", "--file", "--inputfile", default="stdin", - help="The file you want to import (remember to use quotes if necessary)") - parser.add_option("-m", "--minPrint", "--status", default="50", type="int", - help="How often to print a one-line status report (0 means never, default is 50)") - parser.add_option("-p", "--password", help="The password for the MySQL user") - parser.add_option("-q", "--quiet", action="store_true", - help="If this is passed it doesn't print a total at the end nor the opening line. Note that this purposely does NOT change --minPrint") - parser.add_option("-s", "--server", default="localhost", - help="Hostname/IP of the MySQL server (default localhost)") - parser.add_option("-u", "--user", default="fpdb", help="The MySQL username (default fpdb)") - parser.add_option("-x", "--failOnError", action="store_true", - help="If this option is passed it quits when it encounters any error") - - (options, sys.argv) = parser.parse_args() - - settings={'imp-callFpdbHud':False, 'db-backend':2} -# import_file_dict(options, settings, False) + print "CLI for fpdb_import is currently on vacation please check in later" From ac525f4f17779882231a06a2b606d283f5c37d9d Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 9 Oct 2008 00:54:50 +0100 Subject: [PATCH 160/262] p126 - added windows installer sources from Michael --- docs/abbreviations.txt | 2 +- packaging/windows/fpdbEnvInstaller2.au3 | 182 ++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 packaging/windows/fpdbEnvInstaller2.au3 diff --git a/docs/abbreviations.txt b/docs/abbreviations.txt index a1ca4f50..94fd5b16 100644 --- a/docs/abbreviations.txt +++ b/docs/abbreviations.txt @@ -17,7 +17,7 @@ PFR=Pre Flop Raise Postf A=Postflop (ie. flop+turn+river) Aggression% Postf F=Postflop Fold % SD/F=Showdown/Flop=WtSD=How often player went to showdown when he saw the flop -ST=Steal chance (nobody had entered the pot before the player in question, and the player is in cutoff or button position) +ST=Steal chance (nobody had entered the pot before the player in question, and the player is in cutoff, button or SB position) W$wsF=Won $ when he saw flop W$@SD=Won $ at showdown VPI3=Voluntary Put In on 3rd Street (ie. call+complete+raise) diff --git a/packaging/windows/fpdbEnvInstaller2.au3 b/packaging/windows/fpdbEnvInstaller2.au3 new file mode 100644 index 00000000..73b7cb92 --- /dev/null +++ b/packaging/windows/fpdbEnvInstaller2.au3 @@ -0,0 +1,182 @@ +;"%programfiles%\MySQL\MySQL Server 5.0\bin\mysqld" --remove +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Includes +#include + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Variables +Dim $rootPwd = "" +Dim $fpdbUserPwd = "" + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Welcome message and option to abort. Change of working dir to \fpdbEnv +Dim $welcomeBox = MsgBox(4100, "fpdb environment installation", "This installer will automatically create the environment which is needed to run fpdb." & @CRLF & @CRLF & _ +"This means installing and configuring MySQL and Python including some special modules," & @CRLF & "creating a directory for your fpdb user profile and adding gtk to your path." & @CRLF & @CRLF & _ +"You are advised to close all aplications before you proceed." & @CRLF & @CRLF & _ +"DON'T use the keyboard or the mouse during installation unless you are asked to! Just WAIT until the message box 'End of Installation' pops up!" & @CRLF & @CRLF & _ +"If you want to continue the installation click 'Yes'." & @CRLF & "If you want to abort the installation click 'No'.") +If $welcomeBox == 7 Then + Exit ;Exit Installation if 'No' button is clicked in message box +EndIf + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Ask user for mysql root password +GUICreate("FPDB Environment Installation", 600, 400) +GUICtrlCreateLabel("For the installation of the FPDB Environment the MySQL root password and your poker database password are needed.", 20, 25) +GUICtrlCreateLabel("In case MySQL and/or your fpdb poker database aren't installed on your computer, just pick a password.", 20, 50) +GUICtrlCreateLabel("MySQL Root Password:", 20, 100) +$rootPw = GUICtrlCreateInput("your password here", 150, 100, 100, 20) +GUICtrlCreateLabel("Retype password:", 290, 100) +$rootPwR = GUICtrlCreateInput("", 420, 100, 100, 20) +GUICtrlCreateLabel("Poker DB User Password:", 20, 150) +$userPw = GUICtrlCreateInput("your password here", 150, 150, 100, 20) +GUICtrlCreateLabel("Retype password:", 290, 150) +$userPwR = GUICtrlCreateInput("", 420, 150, 100, 20) +$okbutton = GUICtrlCreateButton("OK", 270, 200, 60, 20) +$status = GUICtrlCreateLabel("This is the status line. It describes what the installer is doing at the moment.", 20, 250, 560) +GUISetState(@SW_SHOW) +While 1 + $msg = GUIGetMsg() + Select + Case $msg = $okbutton + If Not(GUICtrlRead($rootPw) == GUICtrlRead($rootPwR)) OR GUICtrlRead($rootPw) == "" Then + MsgBox(16, "", "The passwords don't macht! Try again!", 20, 250) + ElseIf Not(GUICtrlRead($userPw) == GUICtrlRead($userPwR)) OR GUICtrlRead($userPw) == "" Then + MsgBox(16, "", "The passwords don't macht! Try again!", 20, 250) + Else + $rootPwd = GUICtrlRead($rootPw) + $fpdbUserPwd = GUICtrlRead($userPw) + GUICtrlSetState($okbutton, $GUI_DISABLE) + ExitLoop + EndIf + EndSelect +WEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Files Needed +FileInstall("fpdb\7za.exe", "7za.exe") +FileInstall("fpdb\MySQL Server 5.0.7z", "MySQL Server 5.0.7z") +FileInstall("fpdb\gtk.7z", "gtk.7z") +FileInstall("fpdb\python-2.5.2.msi", "python-2.5.2.msi") +FileInstall("fpdb\pymysql.7z", "pymysql.7z") +FileInstall("fpdb\pycairo.7z", "pycairo.7z") +FileInstall("fpdb\pygobject.7z", "pygobject.7z") +FileInstall("fpdb\pygtk.7z", "pygtk.7z") +FileInstall("fpdb\psykopg2.7z", "psykopg2.7z") +FileInstall("fpdb\pywin32.7z", "pywin32.7z") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; MySQL Install and configuration +If NOT FileExists(EnvGet("programfiles") & "\MySQL\MySQL Server 5.0\bin\mysql.exe") Then + GUICtrlSetData($status, "Installing MySQL Database Management System and creating MySQL windows service.") + RunWait('7za.exe x "MySQL Server 5.0.7z" -o"' & EnvGet("programfiles") & '\MySQL\" -aoa', "", @SW_HIDE) + RunWait('"' & EnvGet("programfiles") & '\MySQL\MySQL Server 5.0\bin\mysqld" --install', "", @SW_HIDE) + RunWait("net start mysql", "", @SW_HIDE) + ProcessWait("mysqld.exe") + GUICtrlSetData($status, "Securing important MySQL user accounts.") + Sleep(5000) + FileWrite(EnvGet("programfiles") & "\MySQL\MySQL Server 5.0\bin\mysql1.txt", "DELETE FROM mysql.user WHERE User = '';" & @CRLF) + FileWrite(EnvGet("programfiles") & "\MySQL\MySQL Server 5.0\bin\mysql1.txt", "UPDATE mysql.user SET Password = PASSWORD('" & $rootPwd & "') WHERE User = 'root';" & @CRLF) + FileWrite(EnvGet("programfiles") & "\MySQL\MySQL Server 5.0\bin\mysql1.txt", "FLUSH PRIVILEGES;" & @CRLF) + RunWait(@ComSpec & ' /c mysql --user=root -e "source mysql1.txt"', EnvGet("programfiles") & '\MySQL\MySQL Server 5.0\bin\', @SW_HIDE) +EndIf +If NOT FileExists(EnvGet("programfiles") & "\MySQL\MySQL Server 5.0\data\fpdb") Then + GUICtrlSetData($status, "Creating fpdb database.") + FileWrite(EnvGet("programfiles") & "\MySQL\MySQL Server 5.0\bin\mysql2.txt", "create database fpdb;" & @CRLF) + RunWait(@ComSpec & ' /c mysql --user=root --password=' & $rootPwd & ' -e "source mysql2.txt"', EnvGet("programfiles") & '\MySQL\MySQL Server 5.0\bin\', @SW_HIDE) +EndIf +GUICtrlSetData($status, "Creating MySQL user 'fpdb' and granting privileges.") +FileWrite(EnvGet("programfiles") & "\MySQL\MySQL Server 5.0\bin\mysql3.txt", "GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY '" & $fpdbUserPwd & "' WITH GRANT OPTION;" & @CRLF) +RunWait(@ComSpec & ' /c mysql --user=root --password=' & $rootPwd & ' -e "source mysql3.txt"', EnvGet("programfiles") & '\MySQL\MySQL Server 5.0\bin\', @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; GTK +GUICtrlSetData($status, "Installing GTK.") +RunWait('7za.exe x "gtk.7z" -oc:\ -aoa', "", @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Python 2.5 Installation +GUICtrlSetData($status, "Installing Python 2.5.2") +RunWait("msiexec /quiet /i python-2.5.2.msi", "", @SW_HIDE) ;Install Python without user interaction + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; pymysql +GUICtrlSetData($status, "Installing pymysql") +RunWait('7za.exe x "pymysql.7z" -oC:\Python25\Lib\site-packages -aoa', "", @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; pycairo +GUICtrlSetData($status, "Installing pycairo") +RunWait('7za.exe x "pycairo.7z" -oC:\ -aoa', "", @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; pygobject +GUICtrlSetData($status, "Installing pygobject") +RunWait('7za.exe x "pygobject.7z" -oC:\ -aoa', "", @SW_HIDE) +RunWait(@ComSpec & ' /c C:\Python25\python.exe pygobject_postinstall.py -install', "C:\Python25\SCRIPTS", @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; pygtk +GUICtrlSetData($status, "Installing pygtk") +RunWait('7za.exe x "pygtk.7z" -oC:\ -aoa', "", @SW_HIDE) +RunWait(@ComSpec & ' /c C:\Python25\python.exe pygtk_postinstall.py -install', "C:\Python25\SCRIPTS", @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; psykopg2 +GUICtrlSetData($status, "Installing psykopg2") +RunWait('7za.exe x "psykopg2.7z" -oC:\ -aoa', "", @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; pywin32 +GUICtrlSetData($status, "Installing pywin32 for Python 2.5") +RunWait('7za.exe x "pywin32.7z" -oC:\ -aoa', "", @SW_HIDE) +RunWait(@ComSpec & ' /c C:\Python25\python.exe pywin32_postinstall.py -install', "C:\Python25\SCRIPTS", @SW_HIDE) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Creating directory for default.conf and coping the file into it +GUICtrlSetData($status, "Creating and installing default.conf file.") +$file = FileOpen("default.conf", 1) +FileWriteLine($file, "db-backend=2" & @CRLF) +FileWriteLine($file, "db-host=localhost" & @CRLF) +FileWriteLine($file, "db-databaseName=fpdb" & @CRLF) +FileWriteLine($file, "db-user=fpdb" & @CRLF) +FileWriteLine($file, "db-password=" & $fpdbUserPwd & @CRLF) +FileWriteLine($file, "tv-combinedStealFold=True" & @CRLF) +FileWriteLine($file, "tv-combined2B3B=True" & @CRLF) +FileWriteLine($file, "tv-combinedPostflop=True" & @CRLF) +FileWriteLine($file, "bulkImport-defaultPath=default" & @CRLF) +FileWriteLine($file, "hud-defaultPath=default" & @CRLF) +FileWriteLine($file, "imp-callFpdbHud=True" & @CRLF) +FileClose($file) +FileCopy("default.conf", EnvGet("appdata") & "\fpdb\", 9) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Registry +GUICtrlSetData($status, "Creating backup of path variable and adding GTK and Python to path variable.") +RegWrite("HKEY_CURRENT_USER\Environment", "PathBackup", "REG_SZ", RegRead("HKEY_CURRENT_USER\Environment", "path")) +RegWrite("HKEY_CURRENT_USER\Environment", "path", "REG_SZ", RegRead("HKEY_CURRENT_USER\Environment", "path") & ';C:\gtk\bin;C:\Python25') + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Delete installation files +GUICtrlSetData($status, "Deleting temporary installation files.") +FileDelete("7za.exe") +FileDelete("MySQL Server 5.0.7z") +FileDelete("gtk.7z") +FileDelete("python-2.5.2.msi") +FileDelete("pymysql.7z") +FileDelete("pycairo.7z") +FileDelete("pygobject.7z") +FileDelete("pygtk.7z") +FileDelete("psykopg2.7z") +FileDelete("pywin32.7z") +FileDelete("default.conf") + +$goodbyeBox = MsgBox(4100, "End of Installation", "The Computer needs to be restarted for the installation to be complete." & @CRLF & _ + "After that you can start fpdb by double clicking the file fpdb.py which is located in the folder pyfpdb of the fpdb build you downloaded." & @CRLF & _ + "If you haven't downloaded fpdb yet you can do it here: http://ovh.dl.sourceforge.net/sourceforge/fpdb/fpdb-alpha2-p68.zip" & @CRLF & @CRLF & _ + "If you want to restart the computer now click 'Yes'." & @CRLF & _ + "If you want to restart the computer later click 'No'.") +If $goodbyeBox == 7 Then + Exit ;Exit Installation if 'No' button is clicked in message box +EndIf +Run("shutdown.exe -r -t 0") \ No newline at end of file From bc76d4baf099ce405f3be075ae4dd379cf579049 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 9 Oct 2008 05:04:31 +0100 Subject: [PATCH 161/262] p127 - fixed stud importer bugs missing hole cards and added one blackbox hand verification for studhilo. --- pyfpdb/fpdb_parse_logic.py | 4 ++ pyfpdb/fpdb_simple.py | 21 ++++-- regression-test/ps-studhilo-ring-showdown.txt | 67 +++++++++++++++++++ regression-test/ps.15043388146.expected.txt | 43 ++++++++++++ regression-test/regression-test.sh | 26 +++---- 5 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 regression-test/ps-studhilo-ring-showdown.txt create mode 100644 regression-test/ps.15043388146.expected.txt diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 8ee8f5b2..c6e9ad9f 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -86,6 +86,10 @@ def mainParser(db, cursor, site, category, hand): for i in range(len(hand)): if (lineTypes[i]=="cards"): fpdb_simple.parseCardLine (site, category, lineStreets[i], hand[i], names, cardValues, cardSuits, boardValues, boardSuits) + #if category=="studhilo": + # print "hand[i]:", hand[i] + # print "cardValues:", cardValues + # print "cardSuits:", cardSuits elif (lineTypes[i]=="action"): fpdb_simple.parseActionLine (site, base, isTourney, hand[i], lineStreets[i], playerIDs, names, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo) elif (lineTypes[i]=="win"): diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 5d26df28..6187d536 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -730,18 +730,31 @@ def parseCardLine(site, category, street, line, names, cardValues, cardSuits, bo raise FpdbError("read too many/too few holecards in parseCardLine") elif (category=="razz" or category=="studhi" or category=="studhilo"): if (line.find("shows")==-1): - cardValues[playerNo][street-1]=line[pos:pos+1] - cardSuits[playerNo][street-1]=line[pos+1:pos+2] + #print "parseCardLine(in stud if), street:", street + if line[pos+2]=="]": #-> not (hero and 3rd street) + cardValues[playerNo][street+2]=line[pos:pos+1] + cardSuits[playerNo][street+2]=line[pos+1:pos+2] + else: + #print "hero card1:", line[pos:pos+2], "hero card2:", line[pos+3:pos+5], "hero card3:", line[pos+6:pos+8], + cardValues[playerNo][street]=line[pos:pos+1] + cardSuits[playerNo][street]=line[pos+1:pos+2] + cardValues[playerNo][street+1]=line[pos+3:pos+4] + cardSuits[playerNo][street+1]=line[pos+4:pos+5] + cardValues[playerNo][street+2]=line[pos+6:pos+7] + cardSuits[playerNo][street+2]=line[pos+7:pos+8] else: + print "parseCardLine(in stud else), street:", street cardValues[playerNo][0]=line[pos:pos+1] cardSuits[playerNo][0]=line[pos+1:pos+2] pos+=3 cardValues[playerNo][1]=line[pos:pos+1] cardSuits[playerNo][1]=line[pos+1:pos+2] - if street==7: - pos+=15 + if street==4: + pos=pos=line.rfind("]")-2 cardValues[playerNo][6]=line[pos:pos+1] cardSuits[playerNo][6]=line[pos+1:pos+2] + print "cardValues:", cardValues + print "cardSuits:", cardSuits else: print "line:",line,"street:",street raise FpdbError("invalid category") diff --git a/regression-test/ps-studhilo-ring-showdown.txt b/regression-test/ps-studhilo-ring-showdown.txt new file mode 100644 index 00000000..7c69615b --- /dev/null +++ b/regression-test/ps-studhilo-ring-showdown.txt @@ -0,0 +1,67 @@ +PokerStars Game #15043388146: 7 Card Stud Hi/Lo Limit ($0.10/$0.20) - 2008/02/03 - 22:04:15 (ET) +Table 'Lydia' 8-max +Seat 2: olimpicon99 ($5.31 in chips) +Seat 4: PokerPig55 ($1.58 in chips) +Seat 5: VISTA GIRL ($0.83 in chips) +Seat 6: br1an ($5.10 in chips) +Seat 7: steffen780 ($4 in chips) +Seat 8: willowdale ($3.92 in chips) +olimpicon99: posts the ante $0.02 +PokerPig55: posts the ante $0.02 +VISTA GIRL: posts the ante $0.02 +br1an: posts the ante $0.02 +steffen780: posts the ante $0.02 +willowdale: posts the ante $0.02 +*** 3rd STREET *** +Dealt to olimpicon99 [8c] +Dealt to PokerPig55 [Kh] +Dealt to VISTA GIRL [8h] +Dealt to br1an [2d] +Dealt to steffen780 [Kc 9h 9c] +Dealt to willowdale [5s] +br1an: brings in for $0.05 +steffen780: calls $0.05 +willowdale: calls $0.05 +olimpicon99: folds +olimpicon99 leaves the table +PokerPig55: folds +VISTA GIRL: folds +*** 4th STREET *** +Dealt to br1an [2d] [Qh] +Dealt to steffen780 [Kc 9h 9c] [5c] +Dealt to willowdale [5s] [4s] +br1an: checks +steffen780: checks +willowdale: checks +*** 5th STREET *** +Dealt to br1an [2d Qh] [6d] +Dealt to steffen780 [Kc 9h 9c 5c] [8s] +Dealt to willowdale [5s 4s] [Ad] +willowdale: bets $0.20 +br1an: calls $0.20 +steffen780: folds +*** 6th STREET *** +Dealt to br1an [2d Qh 6d] [6h] +Dealt to willowdale [5s 4s Ad] [5h] +br1an: checks +willowdale: checks +*** RIVER *** +br1an: checks +willowdale: bets $0.20 +br1an: calls $0.20 +*** SHOW DOWN *** +willowdale: shows [3s 4d 5s 4s Ad 5h 2c] (HI: a straight, Ace to Five; LO: 5,4,3,2,A) +br1an: shows [4c 7d 2d Qh 6d 6h 2h] (HI: two pair, Sixes and Deuces) +willowdale collected $0.51 from pot +willowdale collected $0.51 from pot +*** SUMMARY *** +Total pot $1.07 | Rake $0.05 +Seat 2: olimpicon99 folded on the 3rd Street (didn't bet) +Seat 4: PokerPig55 folded on the 3rd Street (didn't bet) +Seat 5: VISTA GIRL folded on the 3rd Street (didn't bet) +Seat 6: br1an showed [4c 7d 2d Qh 6d 6h 2h] and lost with HI: two pair, Sixes and Deuces +Seat 7: steffen780 folded on the 5th Street +Seat 8: willowdale showed [3s 4d 5s 4s Ad 5h 2c] and won ($1.02) with HI: a straight, Ace to Five; LO: 5,4,3,2,A + + + diff --git a/regression-test/ps.15043388146.expected.txt b/regression-test/ps.15043388146.expected.txt new file mode 100644 index 00000000..639baa1f --- /dev/null +++ b/regression-test/ps.15043388146.expected.txt @@ -0,0 +1,43 @@ +Connected to MySQL on localhost. Print Hand Utility +options.site: PokerStars siteId: 2 + +From Table Hands +================ +handId: 4 tableName: Lydia siteHandNo: 15043388146 gametypeId: 2 handStart: 2008-02-04 03:04:15 seats: 6 maxSeats: 8 + +From Table Gametypes +==================== +type: ring base: stud category: studhilo limitType: fl hiLo: s + sbet: 10 bbet: 20 + +From Table BoardCards +===================== + +From Table HandsPlayers +======================= +playerName:olimpicon99 playerStartcash:531 ante:2 cards:?? ?? 8c ?? ?? ?? ?? winnings:0 rake:0 +playerName:PokerPig55 playerStartcash:158 ante:2 cards:?? ?? Kh ?? ?? ?? ?? winnings:0 rake:0 +playerName:VISTA GIRL playerStartcash:83 ante:2 cards:?? ?? 8 ?? ?? ?? ??h winnings:0 rake:0 +playerName:br1an playerStartcash:510 ante:2 cards:4c 7d 2d Qh 6d 6h 2h winnings:0 rake:0 +playerName:steffen780 playerStartcash:400 ante:2 cards:Kc 9h 9c 5c 8s ?? ?? winnings:0 rake:0 +playerName:willowdale playerStartcash:392 ante:2 cards:3s 4d 5s 4s Ad 5h 2c winnings:102 rake:5 + +From Table HandsActions +======================= +#playerName:olimpicon99 street:0 streetActionNo:3 action:fold amount:0 +#playerName:PokerPig55 street:0 streetActionNo:4 action:fold amount:0 +#playerName:VISTA GIRL street:0 streetActionNo:5 action:fold amount:0 +#playerName:br1an street:0 streetActionNo:0 action:blind amount:0 +#playerName:br1an street:1 streetActionNo:0 action:check amount:0 +#playerName:br1an street:2 streetActionNo:1 action:call amount:0 +#playerName:br1an street:3 streetActionNo:0 action:check amount:0 +#playerName:br1an street:4 streetActionNo:0 action:check amount:0 +#playerName:br1an street:4 streetActionNo:2 action:call amount:0 +#playerName:steffen780 street:0 streetActionNo:1 action:call amount:0 +#playerName:steffen780 street:1 streetActionNo:1 action:check amount:0 +#playerName:steffen780 street:2 streetActionNo:2 action:fold amount:0 +#playerName:willowdale street:0 streetActionNo:2 action:call amount:0 +#playerName:willowdale street:1 streetActionNo:2 action:check amount:0 +#playerName:willowdale street:2 streetActionNo:0 action:bet amount:0 +#playerName:willowdale street:3 streetActionNo:1 action:check amount:0 +#playerName:willowdale street:4 streetActionNo:1 action:bet amount:0 diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index 5bf93149..1609b8b8 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -20,30 +20,22 @@ echo "Please note for this to work you need to work on an empty database, otherw rm *.found.txt ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x -#../pyfpdb/fpdb_import.py -p$1 --file=ftp-stud-hilo-ring-001.txt -x -#../pyfpdb/fpdb_import.py -p$1 --file=ftp-omaha-hi-pl-ring-001-005.txt -x +../pyfpdb/fpdb_import.py -p$1 --file=ps-studhilo-ring-showdown.txt -x echo "it should've reported first that it stored 3, then that it had 3 duplicates" -#echo " then 1 stored, then 5 stored" +echo " then 1 stored" -./PrintHand.py -p$1 --hand=14519394979 > ps.14519394979.found.txt && colordiff ps.14519394979.found.txt ps.14519394979.expected.txt -./PrintHand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt -./PrintHand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt +#./PrintHand.py -p$1 --hand=14519394979 > ps.14519394979.found.txt && colordiff ps.14519394979.found.txt ps.14519394979.expected.txt +#./PrintHand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt +#./PrintHand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt +./PrintHand.py -p$1 --hand=15043388146 > ps.15043388146.found.txt && colordiff ps.15043388146.found.txt ps.15043388146.expected.txt -./PrintPlayerHudData.py -p$1 -oM > ps-flags-M-2hands.found.txt && colordiff ps-flags-M-2hands.found.txt ps-flags-M-2hands.expected.txt -./PrintPlayerHudData.py -p$1 -nPlayer_5 -oB > ps-flags-B-1hands.found.txt && colordiff ps-flags-B-1hands.found.txt ps-flags-B-1hands.expected.txt +#./PrintPlayerHudData.py -p$1 -oM > ps-flags-M-2hands.found.txt && colordiff ps-flags-M-2hands.found.txt ps-flags-M-2hands.expected.txt +#./PrintPlayerHudData.py -p$1 -nPlayer_5 -oB > ps-flags-B-1hands.found.txt && colordiff ps-flags-B-1hands.found.txt ps-flags-B-1hands.expected.txt ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-call-3B-preflop-cb-no2b.txt -x echo "it should've now reported another successful store of 1 hand" -./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b25 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt - -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6367428246 > ftp.6367428246.found.txt && colordiff ftp.6367428246.found.txt ftp.6367428246.expected.txt - -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929537410 > ftp.6929537410.found.txt && colordiff ftp.6929537410.found.txt ftp.6929537410.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929553738 > ftp.6929553738.found.txt && colordiff ftp.6929553738.found.txt ftp.6929553738.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929572212 > ftp.6929572212.found.txt && colordiff ftp.6929572212.found.txt ftp.6929572212.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929576743 > ftp.6929576743.found.txt && colordiff ftp.6929576743.found.txt ftp.6929576743.expected.txt -#./print_hand.py -p$1 --site="Full Tilt Poker" --hand=6929587483 > ftp.6929587483.found.txt && colordiff ftp.6929587483.found.txt ftp.6929587483.expected.txt +#./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b25 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt echo "if everything was printed as expected this worked" From 5dfa5061cf02e9a5756d4ca1bc7304b1ec73f2db Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 9 Oct 2008 05:30:09 +0100 Subject: [PATCH 162/262] p128 - corrected 2 bugs in PrintHand.py. finished stud blackbox testdata. --- pyfpdb/fpdb_parse_logic.py | 1 + pyfpdb/fpdb_save_to_db.py | 1 + pyfpdb/fpdb_simple.py | 10 +++--- regression-test/PrintHand.py | 4 +-- regression-test/ps.15043388146.expected.txt | 38 ++++++++++----------- regression-test/regression-test.sh | 21 +++++++----- 6 files changed, 41 insertions(+), 34 deletions(-) diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index c6e9ad9f..2408516e 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -112,6 +112,7 @@ def mainParser(db, cursor, site, category, hand): tableResult=fpdb_simple.parseTableLine(site, base, hand[0]) maxSeats=tableResult['maxSeats'] tableName=tableResult['tableName'] + #print "before part5, antes:", antes #part 5: final preparations, then call fpdb_save_to_db.saveHoldem with # the arrays as they are - that file will fill them. diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index 5a06b1aa..9033c2bb 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -26,6 +26,7 @@ def ring_stud(cursor, base, category, site_hand_no, gametype_id, hand_start_time hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names, tableName, maxSeats) + #print "before calling store_hands_players_stud, antes:", antes hands_players_ids=fpdb_simple.store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, seatNos) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 6187d536..ea9a4ee4 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -579,6 +579,7 @@ def parseActionAmount(line, atype, site, isTourney): else: if not isTourney: pos=line.rfind("$")+1 + #print "parseActionAmount, line:", line, "line[pos:]:", line[pos:] amount=float2int(line[pos:]) else: #print "line:"+line+"EOL" @@ -676,7 +677,6 @@ def parseActionType(line): #parses the ante out of the given line and checks which player paid it, updates antes accordingly. def parseAnteLine(line, site, isTourney, names, antes): - #print "parseAnteLine line: ",line for i in range(len(names)): if (line.startswith(names[i].encode("latin-1"))): #found the ante'er pos=line.rfind("$")+1 @@ -690,6 +690,7 @@ def parseAnteLine(line, site, isTourney, names, antes): pos1=line.rfind("ante")+5 pos2=line.find(" ",pos1) antes[i]+=int(line[pos1:pos2]) + #print "parseAnteLine line: ", line, "antes[i]", antes[i], "antes", antes #end def parseAntes #returns the buyin of a tourney in cents @@ -743,7 +744,7 @@ def parseCardLine(site, category, street, line, names, cardValues, cardSuits, bo cardValues[playerNo][street+2]=line[pos+6:pos+7] cardSuits[playerNo][street+2]=line[pos+7:pos+8] else: - print "parseCardLine(in stud else), street:", street + #print "parseCardLine(in stud else), street:", street cardValues[playerNo][0]=line[pos:pos+1] cardSuits[playerNo][0]=line[pos+1:pos+2] pos+=3 @@ -753,8 +754,8 @@ def parseCardLine(site, category, street, line, names, cardValues, cardSuits, bo pos=pos=line.rfind("]")-2 cardValues[playerNo][6]=line[pos:pos+1] cardSuits[playerNo][6]=line[pos+1:pos+2] - print "cardValues:", cardValues - print "cardSuits:", cardSuits + #print "cardValues:", cardValues + #print "cardSuits:", cardSuits else: print "line:",line,"street:",street raise FpdbError("invalid category") @@ -1280,6 +1281,7 @@ def store_hands_players_stud(cursor, hands_id, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes, seatNos): #stores hands_players rows for stud/razz games. returns an array of the resulting IDs result=[] + #print "before inserts in store_hands_players_stud, antes:", antes for i in range (len(player_ids)): cursor.execute ("""INSERT INTO HandsPlayers (handId, playerId, startCash, ante, diff --git a/regression-test/PrintHand.py b/regression-test/PrintHand.py index e29414ba..c8ecf67f 100755 --- a/regression-test/PrintHand.py +++ b/regression-test/PrintHand.py @@ -148,7 +148,7 @@ for i in range (len(handsPlayers)): else: printstr+=ful.cards2String(line[5:13]) elif (category=="razz" or category=="studhi" or category=="studhilo"): - printstr+=" ante:"+str(line[3])+" cards:" + printstr+=" ante:"+str(line[4])+" cards:" printstr+=ful.cards2String(line[5:19]) else: print "TODO: raise error, print_hand.py" @@ -167,7 +167,7 @@ for i in range (len(handsPlayers)): line=handsActions[j][2:] printstr="playerName:"+playerNames[i] printstr+=" street:"+ful.street_int2String(category, line[0])+" streetActionNo:"+str(line[1])+" action:"+line[2] - printstr+=" amount:"+str(line[3]) + printstr+=" amount:"+str(line[4]) print printstr cursor.close() diff --git a/regression-test/ps.15043388146.expected.txt b/regression-test/ps.15043388146.expected.txt index 639baa1f..300af5ed 100644 --- a/regression-test/ps.15043388146.expected.txt +++ b/regression-test/ps.15043388146.expected.txt @@ -3,7 +3,7 @@ options.site: PokerStars siteId: 2 From Table Hands ================ -handId: 4 tableName: Lydia siteHandNo: 15043388146 gametypeId: 2 handStart: 2008-02-04 03:04:15 seats: 6 maxSeats: 8 +handId: 5 tableName: Lydia siteHandNo: 15043388146 gametypeId: 3 handStart: 2008-02-04 03:04:15 seats: 6 maxSeats: 8 From Table Gametypes ==================== @@ -17,27 +17,27 @@ From Table HandsPlayers ======================= playerName:olimpicon99 playerStartcash:531 ante:2 cards:?? ?? 8c ?? ?? ?? ?? winnings:0 rake:0 playerName:PokerPig55 playerStartcash:158 ante:2 cards:?? ?? Kh ?? ?? ?? ?? winnings:0 rake:0 -playerName:VISTA GIRL playerStartcash:83 ante:2 cards:?? ?? 8 ?? ?? ?? ??h winnings:0 rake:0 +playerName:VISTA GIRL playerStartcash:83 ante:2 cards:?? ?? 8h ?? ?? ?? ?? winnings:0 rake:0 playerName:br1an playerStartcash:510 ante:2 cards:4c 7d 2d Qh 6d 6h 2h winnings:0 rake:0 playerName:steffen780 playerStartcash:400 ante:2 cards:Kc 9h 9c 5c 8s ?? ?? winnings:0 rake:0 playerName:willowdale playerStartcash:392 ante:2 cards:3s 4d 5s 4s Ad 5h 2c winnings:102 rake:5 From Table HandsActions ======================= -#playerName:olimpicon99 street:0 streetActionNo:3 action:fold amount:0 -#playerName:PokerPig55 street:0 streetActionNo:4 action:fold amount:0 -#playerName:VISTA GIRL street:0 streetActionNo:5 action:fold amount:0 -#playerName:br1an street:0 streetActionNo:0 action:blind amount:0 -#playerName:br1an street:1 streetActionNo:0 action:check amount:0 -#playerName:br1an street:2 streetActionNo:1 action:call amount:0 -#playerName:br1an street:3 streetActionNo:0 action:check amount:0 -#playerName:br1an street:4 streetActionNo:0 action:check amount:0 -#playerName:br1an street:4 streetActionNo:2 action:call amount:0 -#playerName:steffen780 street:0 streetActionNo:1 action:call amount:0 -#playerName:steffen780 street:1 streetActionNo:1 action:check amount:0 -#playerName:steffen780 street:2 streetActionNo:2 action:fold amount:0 -#playerName:willowdale street:0 streetActionNo:2 action:call amount:0 -#playerName:willowdale street:1 streetActionNo:2 action:check amount:0 -#playerName:willowdale street:2 streetActionNo:0 action:bet amount:0 -#playerName:willowdale street:3 streetActionNo:1 action:check amount:0 -#playerName:willowdale street:4 streetActionNo:1 action:bet amount:0 +playerName:olimpicon99 street:0 streetActionNo:3 action:fold amount:0 +playerName:PokerPig55 street:0 streetActionNo:4 action:fold amount:0 +playerName:VISTA GIRL street:0 streetActionNo:5 action:fold amount:0 +playerName:br1an street:0 streetActionNo:0 action:blind amount:5 +playerName:br1an street:1 streetActionNo:0 action:check amount:0 +playerName:br1an street:2 streetActionNo:1 action:call amount:20 +playerName:br1an street:3 streetActionNo:0 action:check amount:0 +playerName:br1an street:4 streetActionNo:0 action:check amount:0 +playerName:br1an street:4 streetActionNo:2 action:call amount:20 +playerName:steffen780 street:0 streetActionNo:1 action:call amount:5 +playerName:steffen780 street:1 streetActionNo:1 action:check amount:0 +playerName:steffen780 street:2 streetActionNo:2 action:fold amount:0 +playerName:willowdale street:0 streetActionNo:2 action:call amount:5 +playerName:willowdale street:1 streetActionNo:2 action:check amount:0 +playerName:willowdale street:2 streetActionNo:0 action:bet amount:20 +playerName:willowdale street:3 streetActionNo:1 action:check amount:0 +playerName:willowdale street:4 streetActionNo:1 action:bet amount:20 diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index 1609b8b8..6ef45066 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -20,22 +20,25 @@ echo "Please note for this to work you need to work on an empty database, otherw rm *.found.txt ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x -../pyfpdb/fpdb_import.py -p$1 --file=ps-studhilo-ring-showdown.txt -x echo "it should've reported first that it stored 3, then that it had 3 duplicates" -echo " then 1 stored" -#./PrintHand.py -p$1 --hand=14519394979 > ps.14519394979.found.txt && colordiff ps.14519394979.found.txt ps.14519394979.expected.txt -#./PrintHand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt -#./PrintHand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt -./PrintHand.py -p$1 --hand=15043388146 > ps.15043388146.found.txt && colordiff ps.15043388146.found.txt ps.15043388146.expected.txt +./PrintHand.py -p$1 --hand=14519394979 > ps.14519394979.found.txt && colordiff ps.14519394979.found.txt ps.14519394979.expected.txt +./PrintHand.py -p$1 --hand=14519420999 > ps.14519420999.found.txt && colordiff ps.14519420999.found.txt ps.14519420999.expected.txt +./PrintHand.py -p$1 --hand=14519433154 > ps.14519433154.found.txt && colordiff ps.14519433154.found.txt ps.14519433154.expected.txt -#./PrintPlayerHudData.py -p$1 -oM > ps-flags-M-2hands.found.txt && colordiff ps-flags-M-2hands.found.txt ps-flags-M-2hands.expected.txt -#./PrintPlayerHudData.py -p$1 -nPlayer_5 -oB > ps-flags-B-1hands.found.txt && colordiff ps-flags-B-1hands.found.txt ps-flags-B-1hands.expected.txt +./PrintPlayerHudData.py -p$1 -oM > ps-flags-M-2hands.found.txt && colordiff ps-flags-M-2hands.found.txt ps-flags-M-2hands.expected.txt +./PrintPlayerHudData.py -p$1 -nPlayer_5 -oB > ps-flags-B-1hands.found.txt && colordiff ps-flags-B-1hands.found.txt ps-flags-B-1hands.expected.txt ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-call-3B-preflop-cb-no2b.txt -x echo "it should've now reported another successful store of 1 hand" -#./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b25 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt +./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b25 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt + + +../pyfpdb/fpdb_import.py -p$1 --file=ps-studhilo-ring-showdown.txt -x +echo "it should've now reported another successful store of 1 hand" +./PrintHand.py -p$1 --hand=15043388146 > ps.15043388146.found.txt && colordiff ps.15043388146.found.txt ps.15043388146.expected.txt + echo "if everything was printed as expected this worked" From 14eb29f70283f799a5eed66f74ab2224835b2557 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 9 Oct 2008 06:15:50 +0100 Subject: [PATCH 163/262] p129 - fixed stud river in HudCache generation PrintPlayerHud... now uses bigbet rather than bigblind to determine gametype. --- pyfpdb/fpdb_simple.py | 42 ++++++++++-- regression-test/PrintPlayerHudData.py | 8 +-- .../ps-flags-B-1hands.expected.txt | 6 +- regression-test/ps-flags-CBflop.expected.txt | 2 +- .../ps-flags-M-2hands.expected.txt | 2 +- .../ps-flags-studhilo.expected.txt | 68 +++++++++++++++++++ regression-test/regression-test.sh | 3 +- 7 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 regression-test/ps-flags-studhilo.expected.txt diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index ea9a4ee4..b63405fe 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1526,10 +1526,26 @@ def generateHudCacheData(player_ids, base, category, action_types, allIns, actio if (len(action_types[3][player])>0 or isAllIn): myStreet3Seen=True - mySawShowdown=True - for count in range (len(action_types[3][player])): - if action_types[3][player][count]=="fold": - mySawShowdown=False + print "base:", base + if base=="hold": + mySawShowdown=True + for count in range (len(action_types[3][player])): + if action_types[3][player][count]=="fold": + mySawShowdown=False + else: + print "in else" + for i in range(len(allIns[3][player])): + if allIns[3][player][i]: + isAllIn=True + if (len(action_types[4][player])>0 or isAllIn): + print "in if" + myStreet4Seen=True + + mySawShowdown=True + for count in range (len(action_types[4][player])): + if action_types[4][player][count]=="fold": + mySawShowdown=False + #flop stuff street=1 @@ -1585,6 +1601,24 @@ def generateHudCacheData(player_ids, base, category, action_types, allIns, actio if action_types[street][player][countOtherFold]=="fold": myFoldToOtherRaisedStreet3=True + #stud river stuff - copy of flop with different vars + street=4 + if myStreet4Seen: + for count in range(len(action_types[street][player])): + if action_types[street][player][count]=="bet": + myStreet4Aggr=True + + for otherPlayer in range (len(player_ids)): + if player==otherPlayer: + pass + else: + for countOther in range (len(action_types[street][otherPlayer])): + if action_types[street][otherPlayer][countOther]=="bet": + myOtherRaisedStreet4=True + for countOtherFold in range (len(action_types[street][player])): + if action_types[street][player][countOtherFold]=="fold": + myFoldToOtherRaisedStreet4=True + if winnings[player]!=0: if myStreet1Seen: myWonWhenSeenStreet1=winnings[player]/float(totalWinnings) diff --git a/regression-test/PrintPlayerHudData.py b/regression-test/PrintPlayerHudData.py index 5b4b0599..af4e60c9 100755 --- a/regression-test/PrintPlayerHudData.py +++ b/regression-test/PrintPlayerHudData.py @@ -23,7 +23,7 @@ from optparse import OptionParser import fpdb_util_lib as ful parser = OptionParser() -parser.add_option("-b", "--bigblind", default="2", type="int", help="big blinds in cent") +parser.add_option("-b", "--bigBet", default="4", type="int", help="big bet in cent") parser.add_option("-c", "--cat", "--category", default="holdem", help="Category, e.g. holdem or studhilo") parser.add_option("-e", "--seats", default="7", type="int", help="number of active seats") parser.add_option("-g", "--gameType", default="ring", help="Whether its a ringgame (ring) or a tournament (tour)") @@ -42,19 +42,19 @@ print "Connected to MySQL on localhost. Print Player Flags Utility" print "" print "Basic Data" print "==========" -print "bigblind:",options.bigblind, "category:",options.cat, "limitType:", options.limit, "name:", options.name, "gameType:", options.gameType, "site:", options.site +print "bigBet:",options.bigBet, "category:",options.cat, "limitType:", options.limit, "name:", options.name, "gameType:", options.gameType, "site:", options.site cursor.execute("SELECT id FROM Sites WHERE name=%s", (options.site,)) siteId=cursor.fetchone()[0] -cursor.execute("SELECT id FROM Gametypes WHERE bigBlind=%s AND category=%s AND siteId=%s AND limitType=%s AND type=%s", (options.bigblind, options.cat, siteId, options.limit, options.gameType)) +cursor.execute("SELECT id FROM Gametypes WHERE bigBet=%s AND category=%s AND siteId=%s AND limitType=%s AND type=%s", (options.bigBet, options.cat, siteId, options.limit, options.gameType)) gametypeId=cursor.fetchone()[0] cursor.execute("SELECT id FROM Players WHERE name=%s", (options.name,)) playerId=cursor.fetchone()[0] -cursor.execute("SELECT id FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s",(gametypeId, playerId, options.seats, options.position)) #print "debug: gametypeId:", gametypeId, "playerId:", playerId, "options.seats:", options.seats, "options.position:", options.position +cursor.execute("SELECT id FROM HudCache WHERE gametypeId=%s AND playerId=%s AND activeSeats=%s AND position=%s",(gametypeId, playerId, options.seats, options.position)) hudDataId=cursor.fetchone()[0] print "siteId:", siteId, "gametypeId:", gametypeId, "playerId:", playerId, "hudDataId:", hudDataId diff --git a/regression-test/ps-flags-B-1hands.expected.txt b/regression-test/ps-flags-B-1hands.expected.txt index 9b134e91..fc3d1642 100644 --- a/regression-test/ps-flags-B-1hands.expected.txt +++ b/regression-test/ps-flags-B-1hands.expected.txt @@ -2,7 +2,7 @@ Connected to MySQL on localhost. Print Player Flags Utility Basic Data ========== -bigblind: 2 category: holdem limitType: fl name: Player_5 gameType: ring site: PokerStars +bigBet: 4 category: holdem limitType: fl name: Player_5 gameType: ring site: PokerStars siteId: 2 gametypeId: 1 playerId: 5 hudDataId: 12 HUD Raw Hand Counts @@ -33,8 +33,8 @@ foldToOtherRaisedStreet2: 0 foldToOtherRaisedStreet3: 0 foldToOtherRaisedStreet4: 0 -wonWhenSeenStreet1: 0 -wonAtSD: 0 +wonWhenSeenStreet1: 0.0 +wonAtSD: 0.0 stealAttemptChance: 0 stealAttempted: 0 foldBbToStealChance: 0 diff --git a/regression-test/ps-flags-CBflop.expected.txt b/regression-test/ps-flags-CBflop.expected.txt index 7fea235d..a7f9469f 100644 --- a/regression-test/ps-flags-CBflop.expected.txt +++ b/regression-test/ps-flags-CBflop.expected.txt @@ -2,7 +2,7 @@ Connected to MySQL on localhost. Print Player Flags Utility Basic Data ========== -bigblind: 25 category: holdem limitType: fl name: player3 gameType: ring site: PokerStars +bigBet: 50 category: holdem limitType: fl name: player3 gameType: ring site: PokerStars siteId: 2 gametypeId: 2 playerId: 11 hudDataId: 22 HUD Raw Hand Counts diff --git a/regression-test/ps-flags-M-2hands.expected.txt b/regression-test/ps-flags-M-2hands.expected.txt index aa55e1c5..1169e488 100644 --- a/regression-test/ps-flags-M-2hands.expected.txt +++ b/regression-test/ps-flags-M-2hands.expected.txt @@ -2,7 +2,7 @@ Connected to MySQL on localhost. Print Player Flags Utility Basic Data ========== -bigblind: 2 category: holdem limitType: fl name: Player_1 gameType: ring site: PokerStars +bigBet: 4 category: holdem limitType: fl name: Player_1 gameType: ring site: PokerStars siteId: 2 gametypeId: 1 playerId: 1 hudDataId: 8 HUD Raw Hand Counts diff --git a/regression-test/ps-flags-studhilo.expected.txt b/regression-test/ps-flags-studhilo.expected.txt new file mode 100644 index 00000000..11839d76 --- /dev/null +++ b/regression-test/ps-flags-studhilo.expected.txt @@ -0,0 +1,68 @@ +Connected to MySQL on localhost. Print Player Flags Utility + +Basic Data +========== +bigBet: 20 category: studhilo limitType: fl name: br1an gameType: ring site: PokerStars +siteId: 2 gametypeId: 3 playerId: 21 hudDataId: 32 + +HUD Raw Hand Counts +=================== +HDs: 1 +street0VPI: 0 +street0Aggr: 0 +street0_3B4BChance: 0 +street0_3B4BDone: 0 + +street1Seen: 1 +street2Seen: 1 +street3Seen: 1 +street4Seen: 1 +sawShowdown: 1 + +street1Aggr: 0 +street2Aggr: 0 +street3Aggr: 0 +street4Aggr: 0 + +otherRaisedStreet1: 0 +otherRaisedStreet2: 1 +otherRaisedStreet3: 0 +otherRaisedStreet4: 1 +foldToOtherRaisedStreet1: 0 +foldToOtherRaisedStreet2: 0 +foldToOtherRaisedStreet3: 0 +foldToOtherRaisedStreet4: 0 + +wonWhenSeenStreet1: 0.0 +wonAtSD: 0.0 +stealAttemptChance: 0 +stealAttempted: 0 +foldBbToStealChance: 0 +foldedBbToSteal: 0 +foldSbToStealChance: 0 +foldedSbToSteal: 0 +street1CBChance: 0 +street1CBDone: 0 +street2CBChance: 0 +street2CBDone: 0 +street3CBChance: 0 +street3CBDone: 0 +street4CBChance: 0 +street4CBDone: 0 +foldToStreet1CBChance: 0 +foldToStreet1CBDone: 0 +foldToStreet2CBChance: 0 +foldToStreet2CBDone: 0 +foldToStreet3CBChance: 0 +foldToStreet3CBDone: 0 +foldToStreet4CBChance: 0 +foldToStreet4CBDone: 0 +totalProfit: -0.47 +street1CheckCallRaiseChance: 0 +street1CheckCallRaiseDone: 0 +street2CheckCallRaiseChance: 0 +street2CheckCallRaiseDone: 0 +street3CheckCallRaiseChance: 0 +street3CheckCallRaiseDone: 0 +street4CheckCallRaiseChance: 1 +street4CheckCallRaiseDone: 0 diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index 6ef45066..3cc6078e 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -33,12 +33,13 @@ echo "it should've reported first that it stored 3, then that it had 3 duplicate ../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-call-3B-preflop-cb-no2b.txt -x echo "it should've now reported another successful store of 1 hand" -./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b25 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt +./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b50 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt ../pyfpdb/fpdb_import.py -p$1 --file=ps-studhilo-ring-showdown.txt -x echo "it should've now reported another successful store of 1 hand" ./PrintHand.py -p$1 --hand=15043388146 > ps.15043388146.found.txt && colordiff ps.15043388146.found.txt ps.15043388146.expected.txt +./PrintPlayerHudData.py -p$1 -nbr1an -o0 -e6 -b20 -cstudhilo> ps-flags-studhilo.found.txt && colordiff ps-flags-studhilo.found.txt ps-flags-studhilo.expected.txt echo "if everything was printed as expected this worked" From 96664d009cf9b772cc8f798383057a770f376058 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 9 Oct 2008 07:17:18 +0100 Subject: [PATCH 164/262] p130 - stud/razz tourneys run again, but completely unverified (like holdem/omaha tourneys changed fpdb_import slightly to not die if a file had 0 stored hands --- pyfpdb/fpdb_import.py | 17 +++++++++++------ pyfpdb/fpdb_parse_logic.py | 9 +++------ pyfpdb/fpdb_save_to_db.py | 22 +++++++++------------- pyfpdb/fpdb_simple.py | 24 ++++++++++++------------ 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 9c52d6bc..3758c0b0 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -185,17 +185,22 @@ def import_file_dict(options, settings, callHud=False): startpos=endpos print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors - if stored==0 and duplicates>0: - for line_no in range(len(lines)): - if lines[line_no].find("Game #")!=-1: - final_game_line=lines[line_no] - handsId=fpdb_simple.parseSiteHandNo(final_game_line) - #todo: this will cause return of an unstored hand number if the last hadn was error or partial + if stored==0: + if duplicates>0: + for line_no in range(len(lines)): + if lines[line_no].find("Game #")!=-1: + final_game_line=lines[line_no] + handsId=fpdb_simple.parseSiteHandNo(final_game_line) + else: + print "failed to read a single hand from file:", inputFile + handsId=0 + #todo: this will cause return of an unstored hand number if the last hand was error or partial db.commit() inputFile.close() cursor.close() db.close() return handsId +#end def import_file_dict if __name__ == "__main__": diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 2408516e..ec9a53f6 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -114,7 +114,7 @@ def mainParser(db, cursor, site, category, hand): tableName=tableResult['tableName'] #print "before part5, antes:", antes - #part 5: final preparations, then call fpdb_save_to_db.saveHoldem with + #part 5: final preparations, then call fpdb_save_to_db.* with # the arrays as they are - that file will fill them. fpdb_simple.convertCardValues(cardValues) if base=="hold": @@ -145,11 +145,8 @@ def mainParser(db, cursor, site, category, hand): result = fpdb_save_to_db.tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, positions, cardValues, cardSuits, boardValues, boardSuits, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) elif base=="stud": - result = fpdb_save_to_db.tourney_stud(cursor, base, category, siteTourneyNo, buyin, fee, - knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, - siteHandNo, siteID, gametypeID, handStartTime, names, playerIDs, - startCashes, antes, cardValues, cardSuits, winnings, rakes, - actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) + result = fpdb_save_to_db.tourney_stud(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteID, + siteHandNo, gametypeID, handStartTime, names, playerIDs, startCashes, antes, cardValues, cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos) else: raise fpdb_simple.FpdbError ("unrecognised category") else: diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py index 9033c2bb..af6ab09d 100644 --- a/pyfpdb/fpdb_save_to_db.py +++ b/pyfpdb/fpdb_save_to_db.py @@ -74,25 +74,21 @@ def tourney_holdem_omaha(cursor, base, category, siteTourneyNo, buyin, fee, knoc return hands_id #end def tourney_holdem_omaha -def tourney_stud(cursor, base, category, site_tourney_no, buyin, fee, knockout, entries, prizepool, - tourney_start, payin_amounts, ranks, #end of tourney specific params - site_hand_no, site_id, gametype_id, hand_start_time, names, player_ids, - start_cashes, antes, card_values, card_suits, winnings, rakes, - action_types, allIns, action_amounts, hudImportData): +def tourney_stud(cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries, prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteId, + siteHandNo, gametypeId, handStartTime, names, playerIds, startCashes, antes, cardValues, cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, hudImportData, maxSeats, tableName, seatNos): #stores a tourney stud/razz hand into the database - fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) + fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits) - tourney_id=fpdb_simple.store_tourneys(cursor, site_id, site_tourney_no, buyin, fee, knockout, entries, prizepool, tourney_start) + tourney_id=fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime) - tourneys_players_ids=fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings) + tourneys_players_ids=fpdb_simple.store_tourneys_players(cursor, tourney_id, playerIds, payin_amounts, ranks, winnings) - hands_id=fpdb_simple.storeHands(cursor, site_hand_no, gametype_id, hand_start_time, names) + hands_id=fpdb_simple.storeHands(cursor, siteHandNo, gametypeId, handStartTime, names, tableName, maxSeats) - hands_players_ids=fpdb_simple.store_hands_players_stud_tourney(cursor, hands_id, player_ids, - start_cashes, antes, card_values, card_suits, winnings, rakes, tourneys_players_ids) + hands_players_ids=fpdb_simple.store_hands_players_stud_tourney(cursor, hands_id, playerIds, startCashes, antes, cardValues, cardSuits, winnings, rakes, seatNos, tourneys_players_ids) - fpdb_simple.storeHudData(cursor, base, category, player_ids, hudImportData) + fpdb_simple.storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData) - fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts) + fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos) return hands_id #end def tourney_stud diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index b63405fe..39de0c9e 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1334,24 +1334,24 @@ def store_hands_players_holdem_omaha_tourney(cursor, category, hands_id, player_ #end def store_hands_players_holdem_omaha_tourney def store_hands_players_stud_tourney(cursor, hands_id, player_ids, start_cashes, - antes, card_values, card_suits, winnings, rakes, tourneys_players_ids): + antes, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids): #stores hands_players for tourney stud/razz hands result=[] for i in range (len(player_ids)): cursor.execute ("""INSERT INTO HandsPlayers - (hand_id, player_id, player_startcash, ante, - card1_value, card1_suit, card2_value, card2_suit, - card3_value, card3_suit, card4_value, card4_suit, - card5_value, card5_suit, card6_value, card6_suit, - card7_value, card7_suit, winnings, rake, tourneys_players_id) + (handId, playerId, startCash, ante, + card1Value, card1Suit, card2Value, card2Suit, + card3Value, card3Suit, card4Value, card4Suit, + card5Value, card5Suit, card6Value, card6Suit, + card7Value, card7Suit, winnings, rake, tourneysPlayersId, seatNo) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, %s)""", + %s, %s, %s, %s, %s, %s)""", (hands_id, player_ids[i], start_cashes[i], antes[i], card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1], card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3], card_values[i][4], card_suits[i][4], card_values[i][5], card_suits[i][5], - card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i])) - cursor.execute("SELECT id FROM hands_players WHERE hand_id=%s AND player_id=%s", (hands_id, player_ids[i])) + card_values[i][6], card_suits[i][6], winnings[i], rakes[i], tourneys_players_ids[i], seatNos[i])) + cursor.execute("SELECT id FROM HandsPlayers WHERE handId=%s AND playerId=%s", (hands_id, player_ids[i])) result.append(cursor.fetchall()[0][0]) return result #end def store_hands_players_stud_tourney @@ -1526,19 +1526,19 @@ def generateHudCacheData(player_ids, base, category, action_types, allIns, actio if (len(action_types[3][player])>0 or isAllIn): myStreet3Seen=True - print "base:", base + #print "base:", base if base=="hold": mySawShowdown=True for count in range (len(action_types[3][player])): if action_types[3][player][count]=="fold": mySawShowdown=False else: - print "in else" + #print "in else" for i in range(len(allIns[3][player])): if allIns[3][player][i]: isAllIn=True if (len(action_types[4][player])>0 or isAllIn): - print "in if" + #print "in if" myStreet4Seen=True mySawShowdown=True From 6832234cfb1b1ec1677b0f90663b94ed5ace4644 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 9 Oct 2008 10:03:04 -0400 Subject: [PATCH 165/262] get rid of some useless intermediate output --- pyfpdb/HUD_main.py | 2 -- 1 file changed, 2 deletions(-) mode change 100644 => 100755 pyfpdb/HUD_main.py diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py old mode 100644 new mode 100755 index 33bdc890..1d3fc299 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -94,14 +94,12 @@ def check_stdin(db_name): process_new_hand(hand_no, db_name) except: pass - return True def read_stdin(source, condition, db_name): new_hand_id = sys.stdin.readline() if new_hand_id == "": destroy() - print "new_hand_id = ", new_hand_id process_new_hand(new_hand_id, db_name) return True From 037178ead3698a3175a13a17bd26ef339dbcad6f Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 10 Oct 2008 00:13:56 +0800 Subject: [PATCH 166/262] Shift db connection code to its own function Add class vars for db and cursor --- pyfpdb/fpdb_import.py | 71 ++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 22715ec1..53b5362b 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -43,6 +43,27 @@ class Importer: def __init__(self): """Constructor""" self.settings={'imp-callFpdbHud':False} + self.db = None + self.cursor = None + self.options = None + + def dbConnect(self, options, settings): + #connect to DB + if settings['db-backend'] == 2: + if not mysqlLibFound: + raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") + self.db = MySQLdb.connect(host = options.server, user = options.user, + passwd = options.password, db = options.database) + elif settings['db-backend'] == 3: + if not pgsqlLibFound: + raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") + self.db = psycopg2.connect(host = options.server, user = options.user, + password = options.password, database = options.database) + elif settings['db-backend'] == 4: + pass + else: + pass + self.cursor = self.db.cursor() def import_file_dict(self, options, settings, callHud=False): last_read_hand=0 @@ -51,33 +72,15 @@ class Importer: else: inputFile=open(options.inputFile, "rU") - #connect to DB - if settings['db-backend'] == 2: - if not mysqlLibFound: - raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") - db = MySQLdb.connect(host = options.server, user = options.user, - passwd = options.password, db = options.database) - elif settings['db-backend'] == 3: - if not pgsqlLibFound: - raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") - db = psycopg2.connect(host = options.server, user = options.user, - password = options.password, database = options.database) - elif settings['db-backend'] == 4: - pass - else: - pass - cursor = db.cursor() - - if (not options.quiet): - print "Opened file", options.inputFile, "and connected to MySQL on", options.server + self.dbConnect(options,settings) line=inputFile.readline() if line.find("Tournament Summary")!=-1: print "TODO: implement importing tournament summaries" inputFile.close() - cursor.close() - db.close() + self.cursor.close() + self.db.close() return 0 site=fpdb_simple.recogniseSite(line) @@ -127,11 +130,11 @@ class Importer: hand=fpdb_simple.filterCrap(site, hand, isTourney) try: - handsId=fpdb_parse_logic.mainParser(db, cursor, site, category, hand) - db.commit() + handsId=fpdb_parse_logic.mainParser(self.db, self.cursor, site, category, hand) + self.db.commit() stored+=1 - db.commit() + self.db.commit() # if settings['imp-callFpdbHud'] and callHud and os.sep=='/': if settings['imp-callFpdbHud'] and callHud: #print "call to HUD here. handsId:",handsId @@ -148,10 +151,10 @@ class Importer: print hand[0] if (options.failOnError): - db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. inputFile.close() - cursor.close() - db.close() + self.cursor.close() + self.db.close() raise except (fpdb_simple.FpdbError), fe: errors+=1 @@ -160,13 +163,13 @@ class Importer: print "Here is the first line so you can identify it." print hand[0] #fe.printStackTrace() #todo: get stacktrace - db.rollback() + self.db.rollback() if (options.failOnError): - db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. inputFile.close() - cursor.close() - db.close() + self.cursor.close() + self.db.close() raise if (options.minPrint!=0): if ((stored+duplicates+partial+errors)%options.minPrint==0): @@ -191,10 +194,10 @@ class Importer: print "failed to read a single hand from file:", inputFile handsId=0 #todo: this will cause return of an unstored hand number if the last hand was error or partial - db.commit() + self.db.commit() inputFile.close() - cursor.close() - db.close() + self.cursor.close() + self.db.close() return handsId #end def import_file_dict From ed7122ca31a5b12742e1da576f49090f77e37fef Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 10 Oct 2008 01:21:01 +0800 Subject: [PATCH 167/262] Move callHud to class attribute and remove from function parameters Fix all callers --- pyfpdb/GuiAutoImport.py | 3 ++- pyfpdb/GuiBulkImport.py | 4 ++-- pyfpdb/GuiTableViewer.py | 2 +- pyfpdb/fpdb_import.py | 10 +++++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 4b594d52..b155b9f4 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -57,7 +57,7 @@ class GuiAutoImport (threading.Thread): self.inputFile = os.path.join(self.path, file) stat_info = os.stat(self.inputFile) if not self.import_files.has_key(self.inputFile) or stat_info.st_mtime > self.import_files[self.inputFile]: - self.importer.import_file_dict(self, self.settings, callHud = True) + self.importer.import_file_dict(self, self.settings) self.import_files[self.inputFile] = stat_info.st_mtime print "GuiAutoImport.import_dir done" @@ -122,6 +122,7 @@ class GuiAutoImport (threading.Thread): """Constructor for GuiAutoImport""" self.settings=settings self.importer = fpdb_import.Importer() + self.importer.setCallHud(True) self.server=settings['db-host'] self.user=settings['db-user'] diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index cd1277b6..4bd221cb 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -32,7 +32,7 @@ class GuiBulkImport (threading.Thread): print "BulkImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file - self.importer.import_file_dict(self, self.settings, False) + self.importer.import_file_dict(self, self.settings) print "GuiBulkImport.import_dir done" def load_clicked(self, widget, data=None): @@ -69,7 +69,7 @@ class GuiBulkImport (threading.Thread): if os.path.isdir(self.inputFile): self.import_dir() else: - self.importer.import_file_dict(self, self.settings, False) + self.importer.import_file_dict(self, self.settings) def get_vbox(self): """returns the vbox of this thread""" diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 3582971f..6b5cb398 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -257,7 +257,7 @@ class GuiTableViewer (threading.Thread): self.handCount=0 self.importer = fpdb_import.Importer() - self.last_read_hand_id=importer.import_file_dict(self, self.settings, False) + self.last_read_hand_id=importer.import_file_dict(self, self.settings) #end def table_viewer.import_clicked def all_clicked(self, widget, data): diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 53b5362b..f5a11975 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -46,6 +46,7 @@ class Importer: self.db = None self.cursor = None self.options = None + self.callHud = False def dbConnect(self, options, settings): #connect to DB @@ -65,7 +66,10 @@ class Importer: pass self.cursor = self.db.cursor() - def import_file_dict(self, options, settings, callHud=False): + def setCallHud(self, value): + self.callHud = value + + def import_file_dict(self, options, settings): last_read_hand=0 if (options.inputFile=="stdin"): inputFile=sys.stdin @@ -135,8 +139,8 @@ class Importer: stored+=1 self.db.commit() -# if settings['imp-callFpdbHud'] and callHud and os.sep=='/': - if settings['imp-callFpdbHud'] and callHud: +# if settings['imp-callFpdbHud'] and self.callHud and os.sep=='/': + if settings['imp-callFpdbHud'] and self.callHud: #print "call to HUD here. handsId:",handsId #pipe the Hands.id out to the HUD # options.pipe_to_hud.write("%s" % (handsId) + os.linesep) From 5e1362abf90018c20d629ffb09c3ce6e4f3fc74d Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 10 Oct 2008 02:18:54 +0800 Subject: [PATCH 168/262] Read file in one hit then close. Make lines of for a class member Add timing code --- pyfpdb/fpdb_import.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index f5a11975..bb2b5ec9 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -37,6 +37,7 @@ import datetime import fpdb_simple import fpdb_parse_logic from optparse import OptionParser +from time import time class Importer: @@ -47,6 +48,7 @@ class Importer: self.cursor = None self.options = None self.callHud = False + self.lines = None def dbConnect(self, options, settings): #connect to DB @@ -70,6 +72,7 @@ class Importer: self.callHud = value def import_file_dict(self, options, settings): + starttime = time() last_read_hand=0 if (options.inputFile=="stdin"): inputFile=sys.stdin @@ -78,19 +81,20 @@ class Importer: self.dbConnect(options,settings) - line=inputFile.readline() - - if line.find("Tournament Summary")!=-1: + # Read input file into class and close file + self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) + inputFile.close() + + firstline = self.lines[0] + + if firstline.find("Tournament Summary")!=-1: print "TODO: implement importing tournament summaries" - inputFile.close() self.cursor.close() self.db.close() return 0 - site=fpdb_simple.recogniseSite(line) - category=fpdb_simple.recogniseCategory(line) - inputFile.seek(0) - lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) + site=fpdb_simple.recogniseSite(firstline) + category=fpdb_simple.recogniseCategory(firstline) startpos=0 stored=0 #counter @@ -98,10 +102,10 @@ class Importer: partial=0 #counter errors=0 #counter - for i in range (len(lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method - if (len(lines[i])<2): + for i in range (len(self.lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method + if (len(self.lines[i])<2): endpos=i - hand=lines[startpos:endpos] + hand=self.lines[startpos:endpos] if (len(hand[0])<2): hand=hand[1:] @@ -120,7 +124,7 @@ class Importer: if (len(hand)<3): pass - #todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work. + #todo: the above 2 self.lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work. elif (hand[0].endswith(" (partial)")): #partial hand - do nothing partial+=1 elif (hand[1].find("Seat")==-1 and hand[2].find("Seat")==-1 and hand[3].find("Seat")==-1):#todo: should this be or instead of and? @@ -156,7 +160,6 @@ class Importer: if (options.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. - inputFile.close() self.cursor.close() self.db.close() raise @@ -171,7 +174,6 @@ class Importer: if (options.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. - inputFile.close() self.cursor.close() self.db.close() raise @@ -183,23 +185,22 @@ class Importer: if ((stored+duplicates+partial+errors)>=options.handCount): if (not options.quiet): print "quitting due to reaching the amount of hands to be imported" - print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors + print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime) sys.exit(0) startpos=endpos - print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors + print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors, " time:", (time() - starttime) if stored==0: if duplicates>0: - for line_no in range(len(lines)): - if lines[line_no].find("Game #")!=-1: - final_game_line=lines[line_no] + for line_no in range(len(self.lines)): + if self.lines[line_no].find("Game #")!=-1: + final_game_line=self.lines[line_no] handsId=fpdb_simple.parseSiteHandNo(final_game_line) else: print "failed to read a single hand from file:", inputFile handsId=0 #todo: this will cause return of an unstored hand number if the last hand was error or partial self.db.commit() - inputFile.close() self.cursor.close() self.db.close() return handsId From 0743114c756700849ba8dc03d4fccc0eb0d4e2ec Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 10 Oct 2008 02:36:12 +0800 Subject: [PATCH 169/262] Factor out multiple instances of "email steffen" error message into a function --- pyfpdb/fpdb_import.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index bb2b5ec9..295836d0 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -153,11 +153,8 @@ class Importer: duplicates+=1 except (ValueError), fe: errors+=1 - print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." - print "Filename:",options.inputFile - print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" - print hand[0] - + self.printEmailErrorMessage(errors, options.inputFile, hand[0] + if (options.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. self.cursor.close() @@ -165,10 +162,8 @@ class Importer: raise except (fpdb_simple.FpdbError), fe: errors+=1 - print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." - print "Filename:",options.inputFile - print "Here is the first line so you can identify it." - print hand[0] + self.printEmailErrorMessage(errors, options.inputFile, hand[0] + #fe.printStackTrace() #todo: get stacktrace self.db.rollback() @@ -206,6 +201,12 @@ class Importer: return handsId #end def import_file_dict + def printEmailErrorMessage(self, errors, filename, line): + print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." + print "Filename:",options.inputFile + print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" + print hand[0] + if __name__ == "__main__": print "CLI for fpdb_import is currently on vacation please check in later" From 54f0eee98485b25ccec9a3016b0ae194efdf28c5 Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 10 Oct 2008 02:53:57 +0800 Subject: [PATCH 170/262] Syntax - Fix last commit --- pyfpdb/fpdb_import.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 295836d0..b510f9dc 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -124,7 +124,7 @@ class Importer: if (len(hand)<3): pass - #todo: the above 2 self.lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work. + #todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work. elif (hand[0].endswith(" (partial)")): #partial hand - do nothing partial+=1 elif (hand[1].find("Seat")==-1 and hand[2].find("Seat")==-1 and hand[3].find("Seat")==-1):#todo: should this be or instead of and? @@ -153,7 +153,7 @@ class Importer: duplicates+=1 except (ValueError), fe: errors+=1 - self.printEmailErrorMessage(errors, options.inputFile, hand[0] + self.printEmailErrorMessage(errors, options.inputFile, hand[0]) if (options.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. @@ -162,7 +162,7 @@ class Importer: raise except (fpdb_simple.FpdbError), fe: errors+=1 - self.printEmailErrorMessage(errors, options.inputFile, hand[0] + self.printEmailErrorMessage(errors, options.inputFile, hand[0]) #fe.printStackTrace() #todo: get stacktrace self.db.rollback() From 35625f6d29234229217c00d4246f71aaa8df5fdb Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 9 Oct 2008 21:21:40 +0100 Subject: [PATCH 171/262] p131 - insignificant changes to fpdb.py --- pyfpdb/fpdb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index da58e6ef..07246ca8 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -351,7 +351,8 @@ class fpdb: #end def not_implemented def obtain_global_lock(self): - print "todo: implement obtain_global_lock (users: pls ignore this)" + #print "todo: implement obtain_global_lock (users: pls ignore this)" + pass #end def obtain_global_lock def quit(self, widget, data): @@ -421,7 +422,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha6+, p124 or higher") + self.window.set_title("Free Poker DB - version: alpha6+, p131 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 79900948358b483a9557dbe14bead8687bc973f7 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 9 Oct 2008 20:50:12 -0400 Subject: [PATCH 172/262] added support for favorite seat on Stars --- pyfpdb/Database.py | 6 ++++++ pyfpdb/Hud.py | 23 ++++++++++++++++++++++- pyfpdb/SQL.py | 8 ++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 8c7bee56..63de54b2 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -106,6 +106,12 @@ class Database: c.execute(self.sql.query['get_hand_info'], new_hand_id) return c.fetchall() + def get_actual_seat(self, hand_id, name): + c = self.connection.cursor() + c.execute(self.sql.query['get_actual_seat'], (hand_id, name)) + row = c.fetchone() + return row[0] + # def get_cards(self, hand): # this version is for the PTrackSv2 db # c = self.connection.cursor() diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 9c52dc26..003e0be1 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -109,14 +109,35 @@ class Hud: self.config.edit_layout(self.table.site, self.max, locations = new_layout) self.config.save() + def adj_seats(self, hand, config): + adj = range(0, self.max + 1) # default seat adjustments = no adjustment +# does the user have a fav_seat? + try: + if int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0: + fav_seat = config.supported_sites[self.table.site].layout[self.max].fav_seat + db_connection = Database.Database(config, self.db_name, 'temp') + actual_seat = db_connection.get_actual_seat(hand, config.supported_sites[self.table.site].screen_name) + db_connection.close_connection() + for i in range(0, self.max + 1): + j = actual_seat + i + if j > self.max: j = j - self.max + adj[j] = fav_seat + i + if adj[j] > self.max: adj[j] = adj[j] - self.max + except: + pass + return adj + def create(self, hand, config): # update this hud, to the stats and players as of "hand" # hand is the hand id of the most recent hand played at this table # # this method also manages the creating and destruction of stat # windows via calls to the Stat_Window class + + adj = self.adj_seats(hand, config) +# create the stat windows for i in range(1, self.max + 1): - (x, y) = config.supported_sites[self.table.site].layout[self.max].location[i] + (x, y) = config.supported_sites[self.table.site].layout[self.max].location[adj[i]] self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], parent = self, table = self.table, diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index 91ebd05b..9bc125f6 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -257,6 +257,14 @@ class Sql: and Gametypes.id = Hands.gametypeId """ + self.query['get_actual_seat'] = """ + select seatNo + from HandsPlayers + where HandsPlayers.handId = %s + and HandsPlayers.playerId = (select Players.id from Players + where Players.name = %s) + """ + self.query['get_cards'] = """ select seatNo AS seat_number, From 4cc0eb539ee27e4abe2eea99ac7dde33e72f0278 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 10 Oct 2008 04:14:26 +0100 Subject: [PATCH 173/262] p132 - fixed tv to new importer. added CliFpdb as initial CLI importer interface. fixed fpdb.printEmailError --- pyfpdb/CliFpdb.py | 83 +++++++++++++++++++ pyfpdb/GuiTableViewer.py | 2 +- pyfpdb/fpdb_import.py | 9 +- regression-test/ps-studhilo-ring-showdown.txt | 63 ++++++++++++++ regression-test/regression-test.sh | 8 +- 5 files changed, 156 insertions(+), 9 deletions(-) create mode 100755 pyfpdb/CliFpdb.py diff --git a/pyfpdb/CliFpdb.py b/pyfpdb/CliFpdb.py new file mode 100755 index 00000000..9b0fcc99 --- /dev/null +++ b/pyfpdb/CliFpdb.py @@ -0,0 +1,83 @@ +#!/usr/bin/python + +#Copyright 2008 Steffen Jobbagy-Felso +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + +import os +import sys +import fpdb_simple +from optparse import OptionParser + +try: + import MySQLdb +except: + diaSQLLibMissing = gtk.Dialog(title="Fatal Error - SQL interface library missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) + + print "Please note that the CLI importer only works with MySQL, if you use PostgreSQL this error is expected." + +import fpdb_import +import fpdb_db + + +# def __init__(self, db, settings, debug=True): +# """Constructor for table_viewer""" +# self.debug=debug +# #print "start of table_viewer constructor" +# self.db=db +# self.cursor=db.cursor +# self.settings=settings + +if __name__ == "__main__": + failOnError=False + quiet=False + + #process CLI parameters + parser = OptionParser() + parser.add_option("-c", "--handCount", default="0", type="int", + help="Number of hands to import (default 0 means unlimited)") + parser.add_option("-d", "--database", default="fpdb", help="The MySQL database to use (default fpdb)") + parser.add_option("-e", "--errorFile", default="failed.txt", + help="File to store failed hands into. (default: failed.txt) Not implemented.") + parser.add_option("-f", "--inputFile", "--file", "--inputfile", default="stdin", + help="The file you want to import (remember to use quotes if necessary)") + parser.add_option("-m", "--minPrint", "--status", default="50", type="int", + help="How often to print a one-line status report (0 means never, default is 50)") + parser.add_option("-p", "--password", help="The password for the MySQL user") + parser.add_option("-q", "--quiet", action="store_true", + help="If this is passed it doesn't print a total at the end nor the opening line. Note that this purposely does NOT change --minPrint") + parser.add_option("-s", "--server", default="localhost", + help="Hostname/IP of the MySQL server (default localhost)") + parser.add_option("-u", "--user", default="fpdb", help="The MySQL username (default fpdb)") + parser.add_option("-x", "--failOnError", action="store_true", + help="If this option is passed it quits when it encounters any error") + + (options, sys.argv) = parser.parse_args() + + settings={'imp-callFpdbHud':False, 'db-backend':2} + + #self.inputFile=options.inputFile + + #self.server=options.server + #self.database=options.database + #self.user=options.user + #self.password=options.password + + #self.quiet=False + #self.failOnError=False + #self.minPrint=0 + #self.handCount=0 + importer = fpdb_import.Importer() + + importer.import_file_dict(options, settings) diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 6b5cb398..577be619 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -257,7 +257,7 @@ class GuiTableViewer (threading.Thread): self.handCount=0 self.importer = fpdb_import.Importer() - self.last_read_hand_id=importer.import_file_dict(self, self.settings) + self.last_read_hand_id=self.importer.import_file_dict(self, self.settings) #end def table_viewer.import_clicked def all_clicked(self, widget, data): diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index b510f9dc..db15ed54 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -36,7 +36,6 @@ import os import datetime import fpdb_simple import fpdb_parse_logic -from optparse import OptionParser from time import time class Importer: @@ -72,6 +71,7 @@ class Importer: self.callHud = value def import_file_dict(self, options, settings): + self.options=options starttime = time() last_read_hand=0 if (options.inputFile=="stdin"): @@ -136,7 +136,8 @@ class Importer: if not isTourney: fpdb_simple.filterAnteBlindFold(site,hand) hand=fpdb_simple.filterCrap(site, hand, isTourney) - + self.hand=hand + try: handsId=fpdb_parse_logic.mainParser(self.db, self.cursor, site, category, hand) self.db.commit() @@ -203,9 +204,9 @@ class Importer: def printEmailErrorMessage(self, errors, filename, line): print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." - print "Filename:",options.inputFile + print "Filename:", self.options.inputFile print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" - print hand[0] + print self.hand[0] if __name__ == "__main__": diff --git a/regression-test/ps-studhilo-ring-showdown.txt b/regression-test/ps-studhilo-ring-showdown.txt index 7c69615b..ff1276e8 100644 --- a/regression-test/ps-studhilo-ring-showdown.txt +++ b/regression-test/ps-studhilo-ring-showdown.txt @@ -65,3 +65,66 @@ Seat 8: willowdale showed [3s 4d 5s 4s Ad 5h 2c] and won ($1.02) with HI: a stra +PokerStars Game #20711747191: Razz Limit ($1/$2) - 2008/09/26 14:36:50 ET +Table 'Siwa III' 8-max +Seat 2: dainmat ($63.15 in chips) +Seat 3: fnJ's ($30.75 in chips) +Seat 4: ambythegreat ($24.90 in chips) +Seat 5: jt studd ($39.90 in chips) +Seat 6: KyleHruby ($11.55 in chips) +Seat 7: nutOmatic ($71.30 in chips) +dainmat: posts the ante $0.10 +fnJ's: posts the ante $0.10 +ambythegreat: posts the ante $0.10 +jt studd: posts the ante $0.10 +KyleHruby: posts the ante $0.10 +nutOmatic: posts the ante $0.10 +*** 3rd STREET *** +Dealt to dainmat [Ts] +Dealt to fnJ's [3s] +Dealt to ambythegreat [5h] +Dealt to jt studd [Ad] +Dealt to KyleHruby [As] +Dealt to nutOmatic [6d Kd Th] +dainmat: brings in for $0.50 +fnJ's: calls $0.50 +ambythegreat: raises $0.50 to $1 +garnishgut joins the table at seat #1 +jt studd: folds +KyleHruby: folds +nutOmatic: folds +dainmat: folds +fnJ's: calls $0.50 +*** 4th STREET *** +Dealt to fnJ's [3s] [6h] +Dealt to ambythegreat [5h] [4c] +ambythegreat: bets $1 +fnJ's: calls $1 +*** 5th STREET *** +Dealt to fnJ's [3s 6h] [2h] +Dealt to ambythegreat [5h 4c] [Kh] +fnJ's: bets $2 +ambythegreat: calls $2 +*** 6th STREET *** +Dealt to fnJ's [3s 6h 2h] [2c] +Dealt to ambythegreat [5h 4c Kh] [5s] +fnJ's: bets $2 +ambythegreat: calls $2 +*** RIVER *** +fnJ's: bets $2 +ambythegreat: calls $2 +*** SHOW DOWN *** +fnJ's: shows [9d 5c 3s 6h 2h 2c 4h] (Lo: 6,5,4,3,2) +ambythegreat: mucks hand +fnJ's collected $16.35 from pot +*** SUMMARY *** +Total pot $17.10 | Rake $0.75 +Seat 2: dainmat folded on the 3rd Street +Seat 3: fnJ's showed [9d 5c 3s 6h 2h 2c 4h] and won ($16.35) with Lo: 6,5,4,3,2 +Seat 4: ambythegreat mucked [6s 7h 5h 4c Kh 5s Jc] +Seat 5: jt studd folded on the 3rd Street (didn't bet) +Seat 6: KyleHruby folded on the 3rd Street (didn't bet) +Seat 7: nutOmatic folded on the 3rd Street (didn't bet) + + + diff --git a/regression-test/regression-test.sh b/regression-test/regression-test.sh index 3cc6078e..44a1a1f7 100755 --- a/regression-test/regression-test.sh +++ b/regression-test/regression-test.sh @@ -18,8 +18,8 @@ echo "Please note for this to work you need to work on an empty database, otherwise some info (the id fields) will be off" rm *.found.txt -../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x -../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-3hands.txt -x +../pyfpdb/CliFpdb.py -p$1 --file=ps-lhe-ring-3hands.txt -x +../pyfpdb/CliFpdb.py -p$1 --file=ps-lhe-ring-3hands.txt -x echo "it should've reported first that it stored 3, then that it had 3 duplicates" @@ -31,12 +31,12 @@ echo "it should've reported first that it stored 3, then that it had 3 duplicate ./PrintPlayerHudData.py -p$1 -nPlayer_5 -oB > ps-flags-B-1hands.found.txt && colordiff ps-flags-B-1hands.found.txt ps-flags-B-1hands.expected.txt -../pyfpdb/fpdb_import.py -p$1 --file=ps-lhe-ring-call-3B-preflop-cb-no2b.txt -x +../pyfpdb/CliFpdb.py -p$1 --file=ps-lhe-ring-call-3B-preflop-cb-no2b.txt -x echo "it should've now reported another successful store of 1 hand" ./PrintPlayerHudData.py -p$1 -nplayer3 -oE -e10 -b50 > ps-flags-CBflop.found.txt && colordiff ps-flags-CBflop.found.txt ps-flags-CBflop.expected.txt -../pyfpdb/fpdb_import.py -p$1 --file=ps-studhilo-ring-showdown.txt -x +../pyfpdb/CliFpdb.py -p$1 --file=ps-studhilo-ring-showdown.txt -x echo "it should've now reported another successful store of 1 hand" ./PrintHand.py -p$1 --hand=15043388146 > ps.15043388146.found.txt && colordiff ps.15043388146.found.txt ps.15043388146.expected.txt ./PrintPlayerHudData.py -p$1 -nbr1an -o0 -e6 -b20 -cstudhilo> ps-flags-studhilo.found.txt && colordiff ps-flags-studhilo.found.txt ps-flags-studhilo.expected.txt From c871ce067cfafed3ada6051c312ff1966f1ba628 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 10 Oct 2008 04:29:12 +0100 Subject: [PATCH 174/262] p133 - cleaned CliFpdb.py --- pyfpdb/CliFpdb.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/pyfpdb/CliFpdb.py b/pyfpdb/CliFpdb.py index 9b0fcc99..87c50025 100755 --- a/pyfpdb/CliFpdb.py +++ b/pyfpdb/CliFpdb.py @@ -30,19 +30,7 @@ except: import fpdb_import import fpdb_db - -# def __init__(self, db, settings, debug=True): -# """Constructor for table_viewer""" -# self.debug=debug -# #print "start of table_viewer constructor" -# self.db=db -# self.cursor=db.cursor -# self.settings=settings - if __name__ == "__main__": - failOnError=False - quiet=False - #process CLI parameters parser = OptionParser() parser.add_option("-c", "--handCount", default="0", type="int", @@ -66,18 +54,5 @@ if __name__ == "__main__": (options, sys.argv) = parser.parse_args() settings={'imp-callFpdbHud':False, 'db-backend':2} - - #self.inputFile=options.inputFile - - #self.server=options.server - #self.database=options.database - #self.user=options.user - #self.password=options.password - - #self.quiet=False - #self.failOnError=False - #self.minPrint=0 - #self.handCount=0 importer = fpdb_import.Importer() - importer.import_file_dict(options, settings) From 576f80da25b5347f49cecb08ae33e950d0b0037b Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 10 Oct 2008 13:42:09 -0400 Subject: [PATCH 175/262] use fh.tell() and fh.seek() to skip hh already seen in file --- pyfpdb/fpdb_import.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index b510f9dc..27d5d241 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -49,6 +49,7 @@ class Importer: self.options = None self.callHud = False self.lines = None + self.pos_in_file = {} # dict to remember how far we have read in the file def dbConnect(self, options, settings): #connect to DB @@ -74,15 +75,20 @@ class Importer: def import_file_dict(self, options, settings): starttime = time() last_read_hand=0 + loc = 0 if (options.inputFile=="stdin"): inputFile=sys.stdin else: inputFile=open(options.inputFile, "rU") + try: loc = self.pos_in_file[options.inputFile] + except: pass self.dbConnect(options,settings) # Read input file into class and close file + inputFile.seek(loc) self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) + self.pos_in_file[options.inputFile] = inputFile.tell() inputFile.close() firstline = self.lines[0] From 52c651555ddf6f2028aaab8726c465afa4fa2514 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 10 Oct 2008 15:13:47 -0400 Subject: [PATCH 176/262] updated to include default popup definition that got deleted sometime --- pyfpdb/HUD_config.xml.example | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index 423787c5..f5312c99 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -135,6 +135,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + From 27ca82ca48b6ecce8eae80ea1174e475fcd07f05 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 12 Oct 2008 01:12:30 +0800 Subject: [PATCH 177/262] Change api so object and settings are passed in at creation time. --- pyfpdb/CliFpdb.py | 4 +-- pyfpdb/GuiAutoImport.py | 4 +-- pyfpdb/GuiBulkImport.py | 8 ++--- pyfpdb/GuiTableViewer.py | 4 +-- pyfpdb/fpdb_import.py | 72 ++++++++++++++++++---------------------- 5 files changed, 42 insertions(+), 50 deletions(-) diff --git a/pyfpdb/CliFpdb.py b/pyfpdb/CliFpdb.py index 87c50025..e79a9d49 100755 --- a/pyfpdb/CliFpdb.py +++ b/pyfpdb/CliFpdb.py @@ -54,5 +54,5 @@ if __name__ == "__main__": (options, sys.argv) = parser.parse_args() settings={'imp-callFpdbHud':False, 'db-backend':2} - importer = fpdb_import.Importer() - importer.import_file_dict(options, settings) + importer = fpdb_import.Importer(options,settings) + importer.import_file_dict() diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index b155b9f4..79b25fee 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -57,7 +57,7 @@ class GuiAutoImport (threading.Thread): self.inputFile = os.path.join(self.path, file) stat_info = os.stat(self.inputFile) if not self.import_files.has_key(self.inputFile) or stat_info.st_mtime > self.import_files[self.inputFile]: - self.importer.import_file_dict(self, self.settings) + self.importer.import_file_dict() self.import_files[self.inputFile] = stat_info.st_mtime print "GuiAutoImport.import_dir done" @@ -121,7 +121,7 @@ class GuiAutoImport (threading.Thread): def __init__(self, settings, debug=True): """Constructor for GuiAutoImport""" self.settings=settings - self.importer = fpdb_import.Importer() + self.importer = fpdb_import.Importer(self,self.settings) self.importer.setCallHud(True) self.server=settings['db-host'] diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 4bd221cb..ed0a19b0 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -32,7 +32,7 @@ class GuiBulkImport (threading.Thread): print "BulkImport is not recursive - please select the final directory in which the history files are" else: self.inputFile=self.path+os.sep+file - self.importer.import_file_dict(self, self.settings) + self.importer.import_file_dict() print "GuiBulkImport.import_dir done" def load_clicked(self, widget, data=None): @@ -64,12 +64,10 @@ class GuiBulkImport (threading.Thread): else: self.failOnError=True - self.server, self.database, self.user, self.password=self.db.get_db_info() - if os.path.isdir(self.inputFile): self.import_dir() else: - self.importer.import_file_dict(self, self.settings) + self.importer.import_file_dict() def get_vbox(self): """returns the vbox of this thread""" @@ -83,7 +81,7 @@ class GuiBulkImport (threading.Thread): def __init__(self, db, settings): self.db=db self.settings=settings - self.importer = fpdb_import.Importer() + self.importer = fpdb_import.Importer(self,self.settings) self.vbox=gtk.VBox(False,1) self.vbox.show() diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 577be619..d7f3f8f2 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -255,9 +255,9 @@ class GuiTableViewer (threading.Thread): self.failOnError=False self.minPrint=0 self.handCount=0 - self.importer = fpdb_import.Importer() + self.importer = fpdb_import.Importer(self, self.settings) - self.last_read_hand_id=self.importer.import_file_dict(self, self.settings) + self.last_read_hand_id=self.importer.import_file_dict() #end def table_viewer.import_clicked def all_clicked(self, widget, data): diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 786aea59..46b97e7d 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -40,29 +40,32 @@ from time import time class Importer: - def __init__(self): + def __init__(self, options, settings): """Constructor""" - self.settings={'imp-callFpdbHud':False} + self.settings=settings + self.options=options self.db = None self.cursor = None - self.options = None self.callHud = False self.lines = None self.pos_in_file = {} # dict to remember how far we have read in the file + if not self.settings.has_key('imp-callFpdbHud'): + self.settings['imp-callFpdbHud'] = False + self.dbConnect() - def dbConnect(self, options, settings): + def dbConnect(self): #connect to DB - if settings['db-backend'] == 2: + if self.settings['db-backend'] == 2: if not mysqlLibFound: raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") - self.db = MySQLdb.connect(host = options.server, user = options.user, - passwd = options.password, db = options.database) - elif settings['db-backend'] == 3: + self.db = MySQLdb.connect(self.settings['db-host'], self.settings['db-user'], + self.settings['db-password'], self.settings['db-databaseName']) + elif self.settings['db-backend'] == 3: if not pgsqlLibFound: raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") - self.db = psycopg2.connect(host = options.server, user = options.user, - password = options.password, database = options.database) - elif settings['db-backend'] == 4: + self.db = psycopg2.connect(self.settings['db-host'], self.settings['db-user'], + self.settings['db-password'], self.settings['db-databaseName']) + elif self.settings['db-backend'] == 4: pass else: pass @@ -71,32 +74,30 @@ class Importer: def setCallHud(self, value): self.callHud = value - def import_file_dict(self, options, settings): - self.options=options + def addImportFile(self, filename): + self.options.inputFile = filename + + def import_file_dict(self): starttime = time() last_read_hand=0 loc = 0 - if (options.inputFile=="stdin"): + if (self.options.inputFile=="stdin"): inputFile=sys.stdin else: - inputFile=open(options.inputFile, "rU") - try: loc = self.pos_in_file[options.inputFile] + inputFile=open(self.options.inputFile, "rU") + try: loc = self.pos_in_file[self.options.inputFile] except: pass - self.dbConnect(options,settings) - # Read input file into class and close file inputFile.seek(loc) self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) - self.pos_in_file[options.inputFile] = inputFile.tell() + self.pos_in_file[self.options.inputFile] = inputFile.tell() inputFile.close() firstline = self.lines[0] if firstline.find("Tournament Summary")!=-1: print "TODO: implement importing tournament summaries" - self.cursor.close() - self.db.close() return 0 site=fpdb_simple.recogniseSite(firstline) @@ -151,41 +152,36 @@ class Importer: stored+=1 self.db.commit() # if settings['imp-callFpdbHud'] and self.callHud and os.sep=='/': - if settings['imp-callFpdbHud'] and self.callHud: + if self.settings['imp-callFpdbHud'] and self.callHud: #print "call to HUD here. handsId:",handsId #pipe the Hands.id out to the HUD -# options.pipe_to_hud.write("%s" % (handsId) + os.linesep) - options.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) + self.options.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) except fpdb_simple.DuplicateError: duplicates+=1 except (ValueError), fe: errors+=1 - self.printEmailErrorMessage(errors, options.inputFile, hand[0]) + self.printEmailErrorMessage(errors, self.options.inputFile, hand[0]) - if (options.failOnError): + if (self.options.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. - self.cursor.close() - self.db.close() raise except (fpdb_simple.FpdbError), fe: errors+=1 - self.printEmailErrorMessage(errors, options.inputFile, hand[0]) + self.printEmailErrorMessage(errors, self.options.inputFile, hand[0]) #fe.printStackTrace() #todo: get stacktrace self.db.rollback() - if (options.failOnError): + if (self.options.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. - self.cursor.close() - self.db.close() raise - if (options.minPrint!=0): - if ((stored+duplicates+partial+errors)%options.minPrint==0): + if (self.options.minPrint!=0): + if ((stored+duplicates+partial+errors)%sielf.options.minPrint==0): print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors - if (options.handCount!=0): - if ((stored+duplicates+partial+errors)>=options.handCount): - if (not options.quiet): + if (self.options.handCount!=0): + if ((stored+duplicates+partial+errors)>=self.options.handCount): + if (not self.options.quiet): print "quitting due to reaching the amount of hands to be imported" print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime) sys.exit(0) @@ -203,8 +199,6 @@ class Importer: handsId=0 #todo: this will cause return of an unstored hand number if the last hand was error or partial self.db.commit() - self.cursor.close() - self.db.close() return handsId #end def import_file_dict From ae9b70ea5d4caa476cf77f02a2c7319a435d89c2 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 12 Oct 2008 01:19:57 +0800 Subject: [PATCH 178/262] Change variable name to indicate what it actually is. --- pyfpdb/fpdb_import.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 46b97e7d..bb4d21d3 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -40,10 +40,10 @@ from time import time class Importer: - def __init__(self, options, settings): + def __init__(self, caller, settings): """Constructor""" self.settings=settings - self.options=options + self.caller=caller self.db = None self.cursor = None self.callHud = False @@ -75,23 +75,23 @@ class Importer: self.callHud = value def addImportFile(self, filename): - self.options.inputFile = filename + self.caller.inputFile = filename def import_file_dict(self): starttime = time() last_read_hand=0 loc = 0 - if (self.options.inputFile=="stdin"): + if (self.caller.inputFile=="stdin"): inputFile=sys.stdin else: - inputFile=open(self.options.inputFile, "rU") - try: loc = self.pos_in_file[self.options.inputFile] + inputFile=open(self.caller.inputFile, "rU") + try: loc = self.pos_in_file[self.caller.inputFile] except: pass # Read input file into class and close file inputFile.seek(loc) self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) - self.pos_in_file[self.options.inputFile] = inputFile.tell() + self.pos_in_file[self.caller.inputFile] = inputFile.tell() inputFile.close() firstline = self.lines[0] @@ -155,33 +155,33 @@ class Importer: if self.settings['imp-callFpdbHud'] and self.callHud: #print "call to HUD here. handsId:",handsId #pipe the Hands.id out to the HUD - self.options.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) + self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) except fpdb_simple.DuplicateError: duplicates+=1 except (ValueError), fe: errors+=1 - self.printEmailErrorMessage(errors, self.options.inputFile, hand[0]) + self.printEmailErrorMessage(errors, self.caller.inputFile, hand[0]) - if (self.options.failOnError): + if (self.caller.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. raise except (fpdb_simple.FpdbError), fe: errors+=1 - self.printEmailErrorMessage(errors, self.options.inputFile, hand[0]) + self.printEmailErrorMessage(errors, self.caller.inputFile, hand[0]) #fe.printStackTrace() #todo: get stacktrace self.db.rollback() - if (self.options.failOnError): + if (self.caller.failOnError): self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. raise - if (self.options.minPrint!=0): - if ((stored+duplicates+partial+errors)%sielf.options.minPrint==0): + if (self.caller.minPrint!=0): + if ((stored+duplicates+partial+errors)%self.caller.minPrint==0): print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors - if (self.options.handCount!=0): - if ((stored+duplicates+partial+errors)>=self.options.handCount): - if (not self.options.quiet): + if (self.caller.handCount!=0): + if ((stored+duplicates+partial+errors)>=self.caller.handCount): + if (not self.caller.quiet): print "quitting due to reaching the amount of hands to be imported" print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime) sys.exit(0) @@ -204,7 +204,7 @@ class Importer: def printEmailErrorMessage(self, errors, filename, line): print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." - print "Filename:", self.options.inputFile + print "Filename:", self.caller.inputFile print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" print self.hand[0] From 1e8333ec5d65e0fd41064a77fd19cf9602a67f2e Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 12 Oct 2008 01:42:08 +0800 Subject: [PATCH 179/262] Move minPrint variable from callling class to setting in Importer class --- pyfpdb/GuiAutoImport.py | 2 +- pyfpdb/GuiBulkImport.py | 4 ++-- pyfpdb/GuiTableViewer.py | 2 +- pyfpdb/fpdb_import.py | 14 ++++++++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 79b25fee..97653c77 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -123,6 +123,7 @@ class GuiAutoImport (threading.Thread): self.settings=settings self.importer = fpdb_import.Importer(self,self.settings) self.importer.setCallHud(True) + self.importer.setMinPrint(30) self.server=settings['db-host'] self.user=settings['db-user'] @@ -130,7 +131,6 @@ class GuiAutoImport (threading.Thread): self.database=settings['db-databaseName'] self.quiet=False self.failOnError=False - self.minPrint=30 self.handCount=0 self.mainVBox=gtk.VBox(False,1) diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index ed0a19b0..061e3848 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -48,9 +48,9 @@ class GuiBulkImport (threading.Thread): self.minPrint=self.min_print_tbuffer.get_text(self.min_print_tbuffer.get_start_iter(), self.min_print_tbuffer.get_end_iter()) if (self.minPrint=="never" or self.minPrint=="Never"): - self.minPrint=0 + self.importer.setMinPrint(0) else: - self.minPrint=int(self.minPrint) + self.importer.setMinPrint=int(self.minPrint) self.quiet=self.info_tbuffer.get_text(self.info_tbuffer.get_start_iter(), self.info_tbuffer.get_end_iter()) if (self.quiet=="yes"): diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index d7f3f8f2..2cccdbe5 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -253,9 +253,9 @@ class GuiTableViewer (threading.Thread): self.quiet=False self.failOnError=False - self.minPrint=0 self.handCount=0 self.importer = fpdb_import.Importer(self, self.settings) + self.importer.setMinPrint(0) self.last_read_hand_id=self.importer.import_file_dict() #end def table_viewer.import_clicked diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index bb4d21d3..5a2aaab5 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -49,8 +49,11 @@ class Importer: self.callHud = False self.lines = None self.pos_in_file = {} # dict to remember how far we have read in the file + #Set defaults if not self.settings.has_key('imp-callFpdbHud'): self.settings['imp-callFpdbHud'] = False + if not self.settings.has_key('minPrint'): + self.settings['minPrint'] = 30 self.dbConnect() def dbConnect(self): @@ -77,6 +80,9 @@ class Importer: def addImportFile(self, filename): self.caller.inputFile = filename + def setMinPrint(self, value): + self.settings['minPrint'] = int(value) + def import_file_dict(self): starttime = time() last_read_hand=0 @@ -163,7 +169,7 @@ class Importer: self.printEmailErrorMessage(errors, self.caller.inputFile, hand[0]) if (self.caller.failOnError): - self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + self.db.commit() #dont remove this, in case hand processing was cancelled. raise except (fpdb_simple.FpdbError), fe: errors+=1 @@ -173,10 +179,10 @@ class Importer: self.db.rollback() if (self.caller.failOnError): - self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + self.db.commit() #dont remove this, in case hand processing was cancelled. raise - if (self.caller.minPrint!=0): - if ((stored+duplicates+partial+errors)%self.caller.minPrint==0): + if (self.settings['minPrint']!=0): + if ((stored+duplicates+partial+errors)%self.settings['minPrint']==0): print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors if (self.caller.handCount!=0): From 638a6d6dabf2f122eb3c27c0c04f4be37ac2aeb6 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 12 Oct 2008 02:14:06 +0800 Subject: [PATCH 180/262] Move more "options" from calling class into settings has of fpdb_import and fix all callers --- pyfpdb/GuiAutoImport.py | 6 +++--- pyfpdb/GuiBulkImport.py | 12 ++++++------ pyfpdb/GuiTableViewer.py | 6 +++--- pyfpdb/fpdb_import.py | 19 ++++++++++++++----- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 97653c77..2c4246dc 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -124,14 +124,14 @@ class GuiAutoImport (threading.Thread): self.importer = fpdb_import.Importer(self,self.settings) self.importer.setCallHud(True) self.importer.setMinPrint(30) + self.importer.setQuiet(False) + self.importer.setFailOnError(False) + self.importer.setHandCount(0) self.server=settings['db-host'] self.user=settings['db-user'] self.password=settings['db-password'] self.database=settings['db-databaseName'] - self.quiet=False - self.failOnError=False - self.handCount=0 self.mainVBox=gtk.VBox(False,1) self.mainVBox.show() diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 061e3848..4f3d7ef6 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -40,9 +40,9 @@ class GuiBulkImport (threading.Thread): self.handCount=self.hand_count_tbuffer.get_text(self.hand_count_tbuffer.get_start_iter(), self.hand_count_tbuffer.get_end_iter()) if (self.handCount=="unlimited" or self.handCount=="Unlimited"): - self.handCount=0 + self.importer.setHandCount(0) else: - self.handCount=int(self.handCount) + self.importer.setHandCount(int(self.handCount)) self.errorFile="failed.txt" @@ -54,15 +54,15 @@ class GuiBulkImport (threading.Thread): self.quiet=self.info_tbuffer.get_text(self.info_tbuffer.get_start_iter(), self.info_tbuffer.get_end_iter()) if (self.quiet=="yes"): - self.quiet=False + self.importer.setQuiet(False) else: - self.quiet=True + self.importer.setQuiet(True) self.failOnError=self.fail_error_tbuffer.get_text(self.fail_error_tbuffer.get_start_iter(), self.fail_error_tbuffer.get_end_iter()) if (self.failOnError=="no"): - self.failOnError=False + self.importer.setFailOnError(False) else: - self.failOnError=True + self.importer.setFailOnError(True) if os.path.isdir(self.inputFile): self.import_dir() diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 2cccdbe5..f2bfecd9 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -251,11 +251,11 @@ class GuiTableViewer (threading.Thread): self.user=self.db.user self.password=self.db.password - self.quiet=False - self.failOnError=False - self.handCount=0 self.importer = fpdb_import.Importer(self, self.settings) self.importer.setMinPrint(0) + self.importer.setQuiet(False) + self.importer.setFailOnError(False) + self.importer.setHandCount(0) self.last_read_hand_id=self.importer.import_file_dict() #end def table_viewer.import_clicked diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 5a2aaab5..9c2955eb 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -83,6 +83,15 @@ class Importer: def setMinPrint(self, value): self.settings['minPrint'] = int(value) + def setHandCount(self, value): + self.settings['handCount'] = int(value) + + def setQuiet(self, value): + self.settings['quiet'] = value + + def setFailOnError(self, value): + self.settings['failOnError'] = value + def import_file_dict(self): starttime = time() last_read_hand=0 @@ -168,7 +177,7 @@ class Importer: errors+=1 self.printEmailErrorMessage(errors, self.caller.inputFile, hand[0]) - if (self.caller.failOnError): + if (self.settings['failOnError']): self.db.commit() #dont remove this, in case hand processing was cancelled. raise except (fpdb_simple.FpdbError), fe: @@ -178,16 +187,16 @@ class Importer: #fe.printStackTrace() #todo: get stacktrace self.db.rollback() - if (self.caller.failOnError): + if (self.settings['failOnError']): self.db.commit() #dont remove this, in case hand processing was cancelled. raise if (self.settings['minPrint']!=0): if ((stored+duplicates+partial+errors)%self.settings['minPrint']==0): print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors - if (self.caller.handCount!=0): - if ((stored+duplicates+partial+errors)>=self.caller.handCount): - if (not self.caller.quiet): + if (self.settings['handCount']!=0): + if ((stored+duplicates+partial+errors)>=self.settings['handCount']): + if (not self.settings['quiet']): print "quitting due to reaching the amount of hands to be imported" print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime) sys.exit(0) From 6c4e2f3eb9287d5ec2606d293805d5cc6dcd5375 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 12 Oct 2008 15:49:09 +0800 Subject: [PATCH 181/262] Changes import_file_dict interface to Importer internal. Users of Importer should now addImportFile and addImportDirectory, set paramaters, then run either runImport - all files or runUpdated - modified files. Comments out postgres regression test until fixed Adds initial code for importing regression test files. --- pyfpdb/GuiAutoImport.py | 25 +++------------- pyfpdb/GuiBulkImport.py | 12 ++++---- pyfpdb/RegressionTest.py | 61 ++++++++++++++++++++++---------------- pyfpdb/fpdb_import.py | 64 +++++++++++++++++++++++++++++++++------- 4 files changed, 98 insertions(+), 64 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 2c4246dc..9cc10906 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -50,16 +50,7 @@ class GuiAutoImport (threading.Thread): def do_import(self): """Callback for timer to do an import iteration.""" - for file in os.listdir(self.path): - if os.path.isdir(file): - print "AutoImport is not recursive - please select the final directory in which the history files are" - else: - self.inputFile = os.path.join(self.path, file) - stat_info = os.stat(self.inputFile) - if not self.import_files.has_key(self.inputFile) or stat_info.st_mtime > self.import_files[self.inputFile]: - self.importer.import_file_dict() - self.import_files[self.inputFile] = stat_info.st_mtime - + self.importer.runUpdated() print "GuiAutoImport.import_dir done" return True @@ -96,17 +87,9 @@ class GuiAutoImport (threading.Thread): # self.pipe_to_hud = os.popen(command, 'w') self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) -# Iniitally populate the self.import_files dict, which keeps mtimes for the files watched - - self.import_files = {} - for file in os.listdir(self.path): - if os.path.isdir(file): - pass # skip subdirs for now - else: - inputFile = os.path.join(self.path, file) - stat_info = os.stat(inputFile) - self.import_files[inputFile] = stat_info.st_mtime - +# Add directory to importer object and set the initial mtime reference. + self.importer.addImportDirectory(self.path) + self.importer.setWatchTime() self.do_import() interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 4f3d7ef6..c2347528 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -27,12 +27,8 @@ class GuiBulkImport (threading.Thread): def import_dir(self): """imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it""" self.path=self.inputFile - for file in os.listdir(self.path): - if os.path.isdir(file): - print "BulkImport is not recursive - please select the final directory in which the history files are" - else: - self.inputFile=self.path+os.sep+file - self.importer.import_file_dict() + self.importer.addImportDirectory(self.path) + self.importer.runImport() print "GuiBulkImport.import_dir done" def load_clicked(self, widget, data=None): @@ -67,7 +63,9 @@ class GuiBulkImport (threading.Thread): if os.path.isdir(self.inputFile): self.import_dir() else: - self.importer.import_file_dict() + self.importer.addImportFile() + self.importer.runImport() + self.importer.clearFileList() def get_vbox(self): """returns the vbox of this thread""" diff --git a/pyfpdb/RegressionTest.py b/pyfpdb/RegressionTest.py index 8a8facc5..b4dd1b7d 100644 --- a/pyfpdb/RegressionTest.py +++ b/pyfpdb/RegressionTest.py @@ -25,51 +25,62 @@ import os import sys import fpdb_db +import fpdb_import import FpdbSQLQueries import unittest class TestSequenceFunctions(unittest.TestCase): - def setUp(self): - """Configure MySQL settings/database and establish connection""" - self.mysql_settings={ 'db-host':"localhost", 'db-backend':2, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"} - self.mysql_db = fpdb_db.fpdb_db() - self.mysql_db.connect(self.mysql_settings['db-backend'], self.mysql_settings['db-host'], - self.mysql_settings['db-databaseName'], self.mysql_settings['db-user'], - self.mysql_settings['db-password']) - self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB') + def setUp(self): + """Configure MySQL settings/database and establish connection""" + self.mysql_settings={ 'db-host':"localhost", + 'db-backend':2, + 'db-databaseName':"fpdbtest", + 'db-user':"fpdb", + 'db-password':"fpdb"} + self.mysql_db = fpdb_db.fpdb_db() + self.mysql_db.connect(self.mysql_settings['db-backend'], self.mysql_settings['db-host'], + self.mysql_settings['db-databaseName'], self.mysql_settings['db-user'], + self.mysql_settings['db-password']) + self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB') + self.mysqlimporter = fpdb_import.Importer(self, self.mysql_settings) - """Configure Postgres settings/database and establish connection""" - self.pg_settings={ 'db-host':"localhost", 'db-backend':3, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"} - self.pg_db = fpdb_db.fpdb_db() - self.pg_db.connect(self.pg_settings['db-backend'], self.pg_settings['db-host'], - self.pg_settings['db-databaseName'], self.pg_settings['db-user'], - self.pg_settings['db-password']) - self.pgdict = FpdbSQLQueries.FpdbSQLQueries('PostgreSQL') +# """Configure Postgres settings/database and establish connection""" +# self.pg_settings={ 'db-host':"localhost", 'db-backend':3, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"} +# self.pg_db = fpdb_db.fpdb_db() +# self.pg_db.connect(self.pg_settings['db-backend'], self.pg_settings['db-host'], +# self.pg_settings['db-databaseName'], self.pg_settings['db-user'], +# self.pg_settings['db-password']) +# self.pgdict = FpdbSQLQueries.FpdbSQLQueries('PostgreSQL') def testDatabaseConnection(self): """Test all supported DBs""" self.result = self.mysql_db.cursor.execute(self.mysqldict.query['list_tables']) - self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) - - print self.pgdict.query['list_tables'] - - self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) +# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) +# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + def testMySQLRecreateTables(self): """Test droping then recreating fpdb table schema""" self.mysql_db.recreate_tables() self.result = self.mysql_db.cursor.execute("SHOW TABLES") self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) - def testPostgresSQLRecreateTables(self): - """Test droping then recreating fpdb table schema""" - self.pg_db.recreate_tables() - self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) - self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) + def testImportHandHistoryFiles(self): + """Test import of single HH file""" + self.mysqlimporter.addImportFile("regression-test-files/hand-histories/ps-lhe-ring-3hands.txt") + self.mysqlimporter.runImport() + self.mysqlimporter.addImportDirectory("regression-test-files/hand-histories") + self.mysqlimporter.runImport() + +# def testPostgresSQLRecreateTables(self): +# """Test droping then recreating fpdb table schema""" +# self.pg_db.recreate_tables() +# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) +# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) if __name__ == '__main__': unittest.main() diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 9c2955eb..c844c00d 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -46,6 +46,9 @@ class Importer: self.caller=caller self.db = None self.cursor = None + self.filelist = [] + self.queued = [] + self.updated = 0 #Time last import was run, used as mtime reference self.callHud = False self.lines = None self.pos_in_file = {} # dict to remember how far we have read in the file @@ -74,12 +77,10 @@ class Importer: pass self.cursor = self.db.cursor() + #Set functions def setCallHud(self, value): self.callHud = value - def addImportFile(self, filename): - self.caller.inputFile = filename - def setMinPrint(self, value): self.settings['minPrint'] = int(value) @@ -92,21 +93,62 @@ class Importer: def setFailOnError(self, value): self.settings['failOnError'] = value - def import_file_dict(self): + def setWatchTime(self): + self.updated = time() + + def clearFileList(self): + self.filelist = [] + + #Add an individual file to filelist + def addImportFile(self, filename): + #todo: test it is a valid file + self.filelist = self.filelist + [filename] + print "Filelist in addImportFile: ", self.filelist + #Remove duplicates + set(filelist) + + #Add a directory of files to filelist + def addImportDirectory(self,dir): + #todo: test it is a valid directory + for file in os.listdir(dir): + if os.path.isdir(file): + print "BulkImport is not recursive - please select the final directory in which the history files are" + else: + blah = [dir+os.sep+file] + self.filelist = self.filelist + [dir+os.sep+file] + #Remove duplicates + set(self.filelist) + + #Run full import on filelist + def runImport(self): + for file in self.filelist: + print "Importing file: ", file + self.import_file_dict(file) + + #Run import on updated files, then store latest update time. + def runUpdated(self): + for file in self.filelist: + stat_info = os.stat(file) + if stat_info.st_mtime > self.updated: + self.import_file_dict(file) + self.updated = time() + + # This is now an internal function that should not be called directly. + def import_file_dict(self, file): starttime = time() last_read_hand=0 loc = 0 - if (self.caller.inputFile=="stdin"): + if (file=="stdin"): inputFile=sys.stdin else: - inputFile=open(self.caller.inputFile, "rU") - try: loc = self.pos_in_file[self.caller.inputFile] + inputFile=open(file, "rU") + try: loc = self.pos_in_file[file] except: pass # Read input file into class and close file inputFile.seek(loc) self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) - self.pos_in_file[self.caller.inputFile] = inputFile.tell() + self.pos_in_file[file] = inputFile.tell() inputFile.close() firstline = self.lines[0] @@ -175,14 +217,14 @@ class Importer: duplicates+=1 except (ValueError), fe: errors+=1 - self.printEmailErrorMessage(errors, self.caller.inputFile, hand[0]) + self.printEmailErrorMessage(errors, file, hand[0]) if (self.settings['failOnError']): self.db.commit() #dont remove this, in case hand processing was cancelled. raise except (fpdb_simple.FpdbError), fe: errors+=1 - self.printEmailErrorMessage(errors, self.caller.inputFile, hand[0]) + self.printEmailErrorMessage(errors, file, hand[0]) #fe.printStackTrace() #todo: get stacktrace self.db.rollback() @@ -219,7 +261,7 @@ class Importer: def printEmailErrorMessage(self, errors, filename, line): print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." - print "Filename:", self.caller.inputFile + print "Filename:", filename print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" print self.hand[0] From c2c37dc5ee095f8bf7bc4b7143b657b671d78ba4 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 12 Oct 2008 18:21:42 +0800 Subject: [PATCH 182/262] Fix mtime time detection --- pyfpdb/GuiAutoImport.py | 2 +- pyfpdb/fpdb_import.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 9cc10906..23493664 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -89,7 +89,6 @@ class GuiAutoImport (threading.Thread): # Add directory to importer object and set the initial mtime reference. self.importer.addImportDirectory(self.path) - self.importer.setWatchTime() self.do_import() interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) @@ -110,6 +109,7 @@ class GuiAutoImport (threading.Thread): self.importer.setQuiet(False) self.importer.setFailOnError(False) self.importer.setHandCount(0) + self.importer.setWatchTime() self.server=settings['db-host'] self.user=settings['db-user'] diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index c844c00d..458b34d5 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -130,6 +130,7 @@ class Importer: for file in self.filelist: stat_info = os.stat(file) if stat_info.st_mtime > self.updated: + # print "File: " + str(file) + " mtime: " + str(stat_info.st_mtime) + " update: " + str(self.updated) self.import_file_dict(file) self.updated = time() From 8ca5a5c9657c5f0ba01fb7ae0d5775679a2a54a3 Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 13 Oct 2008 01:26:04 +0800 Subject: [PATCH 183/262] Should fix detection of new hh files. --- pyfpdb/fpdb_import.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 458b34d5..73e141e7 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -47,7 +47,8 @@ class Importer: self.db = None self.cursor = None self.filelist = [] - self.queued = [] + self.dirlist = [] + self.monitor = False self.updated = 0 #Time last import was run, used as mtime reference self.callHud = False self.lines = None @@ -108,13 +109,16 @@ class Importer: set(filelist) #Add a directory of files to filelist - def addImportDirectory(self,dir): + def addImportDirectory(self,dir,monitor = False): #todo: test it is a valid directory + if monitor == True: + self.monitor = True + self.dirlist = self.dirlist + [dir] + for file in os.listdir(dir): if os.path.isdir(file): print "BulkImport is not recursive - please select the final directory in which the history files are" else: - blah = [dir+os.sep+file] self.filelist = self.filelist + [dir+os.sep+file] #Remove duplicates set(self.filelist) @@ -127,6 +131,13 @@ class Importer: #Run import on updated files, then store latest update time. def runUpdated(self): + #Check for new files in directory + #todo: make efficient - always checks for new file, should be able to use mtime of directory + # ^^ May not work on windows + for dir in self.dirlist: + for file in os.listdir(dir): + self.filelist = self.filelist + [dir+os.sep+file] + for file in self.filelist: stat_info = os.stat(file) if stat_info.st_mtime > self.updated: From 9a60cf84c8cd90894dd0af14a09d88a57f6f3e6e Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 13 Oct 2008 02:06:28 +0800 Subject: [PATCH 184/262] Real fix this time, modify GuiAutoImport to actually tell importer to monitor the directory --- pyfpdb/GuiAutoImport.py | 2 +- pyfpdb/fpdb_import.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 23493664..6953d1f9 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -88,7 +88,7 @@ class GuiAutoImport (threading.Thread): self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) # Add directory to importer object and set the initial mtime reference. - self.importer.addImportDirectory(self.path) + self.importer.addImportDirectory(self.path, True) self.do_import() interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 73e141e7..fdf3ab3c 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -119,7 +119,7 @@ class Importer: if os.path.isdir(file): print "BulkImport is not recursive - please select the final directory in which the history files are" else: - self.filelist = self.filelist + [dir+os.sep+file] + self.filelist = self.filelist + [os.path.join(dir, file)] #Remove duplicates set(self.filelist) @@ -137,6 +137,7 @@ class Importer: for dir in self.dirlist: for file in os.listdir(dir): self.filelist = self.filelist + [dir+os.sep+file] + set(filelist) for file in self.filelist: stat_info = os.stat(file) From 3e9ef61fc893a01fb3441d0f173fff75bf089c31 Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 13 Oct 2008 23:34:18 +0800 Subject: [PATCH 185/262] Fix bug: incorrect use of set function to remove duplicates Now detects new tables without issues --- pyfpdb/fpdb_import.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index fdf3ab3c..b652f204 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -104,9 +104,8 @@ class Importer: def addImportFile(self, filename): #todo: test it is a valid file self.filelist = self.filelist + [filename] - print "Filelist in addImportFile: ", self.filelist #Remove duplicates - set(filelist) + self.filelist = list(set(self.filelist)) #Add a directory of files to filelist def addImportDirectory(self,dir,monitor = False): @@ -121,12 +120,11 @@ class Importer: else: self.filelist = self.filelist + [os.path.join(dir, file)] #Remove duplicates - set(self.filelist) + self.filelist = list(set(self.filelist)) #Run full import on filelist def runImport(self): for file in self.filelist: - print "Importing file: ", file self.import_file_dict(file) #Run import on updated files, then store latest update time. @@ -137,12 +135,12 @@ class Importer: for dir in self.dirlist: for file in os.listdir(dir): self.filelist = self.filelist + [dir+os.sep+file] - set(filelist) + + self.filelist = list(set(self.filelist)) for file in self.filelist: stat_info = os.stat(file) if stat_info.st_mtime > self.updated: - # print "File: " + str(file) + " mtime: " + str(stat_info.st_mtime) + " update: " + str(self.updated) self.import_file_dict(file) self.updated = time() From 155b2e93b0f1a457b6db7c4726d41273f0250720 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 13 Oct 2008 23:09:27 +0100 Subject: [PATCH 186/262] p134 - updated CliFpdb for new importer --- pyfpdb/CliFpdb.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyfpdb/CliFpdb.py b/pyfpdb/CliFpdb.py index e79a9d49..75dd1251 100755 --- a/pyfpdb/CliFpdb.py +++ b/pyfpdb/CliFpdb.py @@ -54,5 +54,12 @@ if __name__ == "__main__": (options, sys.argv) = parser.parse_args() settings={'imp-callFpdbHud':False, 'db-backend':2} - importer = fpdb_import.Importer(options,settings) - importer.import_file_dict() + settings['db-host']=options.server + settings['db-user']=options.user + settings['db-password']=options.password + settings['db-databaseName']=options.database + settings['handCount']=options.handCount + + importer = fpdb_import.Importer(options, settings) + importer.addImportFile(options.inputFile) + importer.runImport() From 43cd55246669025651819307b69c50a59a7837fb Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 14 Oct 2008 10:33:32 -0400 Subject: [PATCH 187/262] threaded the hudcache query, update to install in windows web page --- pyfpdb/HUD_main.py | 104 +++++++++++++++---------------- pyfpdb/Hud.py | 17 ++++- website/docs-install-windows.php | 9 ++- 3 files changed, 71 insertions(+), 59 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 1d3fc299..5e3286f1 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -36,7 +36,6 @@ Main for FreePokerTools HUD. import sys import os import thread -import Queue errorfile = open('HUD-error.txt', 'w', 0) sys.stderr = errorfile @@ -61,54 +60,58 @@ config = 0; def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() -def process_new_hand(new_hand_id, db_name): -# there is a new hand_id to be processed -# read the hand_id from stdin and strip whitespace +def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_connection, config, stat_dict): global hud_dict + def idle_func(): + global hud_dict + gtk.threads_enter() + try: + hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_name) + hud_dict[table_name].create(new_hand_id, config) + hud_dict[table_name].update(new_hand_id, db_connection, config, stat_dict) + return False + finally: + gtk.threads_leave + gobject.idle_add(idle_func) - for h in hud_dict.keys(): - if hud_dict[h].deleted: - del(hud_dict[h]) - - db_connection = Database.Database(config, db_name, 'temp') - (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) -# if a hud for this table exists, just update it - if hud_dict.has_key(table_name): - hud_dict[table_name].update(new_hand_id, db_connection, config) -# otherwise create a new hud - else: - table_windows = Tables.discover(config) - for t in table_windows.keys(): - if table_windows[t].name == table_name: - hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_name) - hud_dict[table_name].create(new_hand_id, config) - hud_dict[table_name].update(new_hand_id, db_connection, config) - break -# print "table name \"%s\" not identified, no hud created" % (table_name) - db_connection.close_connection() - return(1) - -def check_stdin(db_name): - try: - hand_no = dataQueue.get(block=False) - process_new_hand(hand_no, db_name) - except: - pass - return True - -def read_stdin(source, condition, db_name): - new_hand_id = sys.stdin.readline() - if new_hand_id == "": - destroy() - process_new_hand(new_hand_id, db_name) - return True +def update_HUD(new_hand_id, table_name, db_connection, config, stat_dict): + global hud_dict + def idle_func(): + gtk.threads_enter() + try: + hud_dict[table_name].update(new_hand_id, db_connection, config, stat_dict) + return False + finally: + gtk.threads_leave + gobject.idle_add(idle_func) def producer(): # This is the thread function - while True: - hand_no = sys.stdin.readline() # reads stdin - if new_hand_id == "": + global hud_dict + db_connection = Database.Database(config, db_name, 'temp') + + while True: # wait for a new hand number on stdin + new_hand_id = sys.stdin.readline() + if new_hand_id == "": # blank line means quit destroy() - dataQueue.put(hand_no) # and puts result on the queue + +# delete hud_dict entries for any HUD destroyed since last iteration + for h in hud_dict.keys(): + if hud_dict[h].deleted: + del(hud_dict[h]) + + (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) + stat_dict = db_connection.get_stats_from_hand(new_hand_id) + +# if a hud for this table exists, just update it + if hud_dict.has_key(table_name): + update_HUD(new_hand_id, table_name, db_connection, config, stat_dict) +# otherwise create a new hud + else: + table_windows = Tables.discover(config) + for t in table_windows.keys(): + if table_windows[t].name == table_name: + create_HUD(new_hand_id, table_windows[t], db_name, table_name, max, poker_game, db_connection, config, stat_dict) + break if __name__== "__main__": sys.stderr.write("HUD_main starting\n") @@ -120,18 +123,9 @@ if __name__== "__main__": sys.stderr.write("Using db name = %s\n" % (db_name)) config = Configuration.Config() -# db_connection = Database.Database(config, 'fpdb', 'holdem') - if os.name == 'posix': - s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, read_stdin, db_name) - elif os.name == 'nt': - dataQueue = Queue.Queue() # shared global. infinite size - gobject.threads_init() # this is required - thread.start_new_thread(producer, ()) # starts the thread - gobject.timeout_add(1000, check_stdin, db_name) - else: - print "Sorry your operating system is not supported." - sys.exit() + gobject.threads_init() # this is required + thread.start_new_thread(producer, ()) # starts the thread main_window = gtk.Window() main_window.connect("destroy", destroy) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 003e0be1..611fd338 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -52,6 +52,7 @@ class Hud: self.max = max self.db_name = db_name self.deleted = False + self.stacked = True self.stat_windows = {} self.popup_windows = {} @@ -63,7 +64,10 @@ class Hud: self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) self.main_window.set_keep_above(1) self.main_window.set_title(table.name) + self.main_window.connect("destroy", self.kill_hud) + if self.stacked: + self.main_window.connect("window-state-event", self.on_window_event) self.ebox = gtk.EventBox() self.label = gtk.Label("Close this window to\nkill the HUD for\n %s" % (table.name)) @@ -93,6 +97,15 @@ class Hud: return True return False + def on_window_event(self, widget, event): + + if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: + for sw in self.stat_windows.keys(): + self.stat_windows[sw].window.iconify() + else: + for sw in self.stat_windows: + self.stat_windows[sw].window.deiconify() + def kill_hud(self, args): for k in self.stat_windows.keys(): self.stat_windows[k].window.destroy() @@ -160,9 +173,9 @@ class Hud: # self.m = Mucked.Mucked(self.mucked_window, self.db_connection) # self.mucked_window.show_all() - def update(self, hand, db, config): + def update(self, hand, db, config, stat_dict): self.hand = hand # this is the last hand, so it is available later - stat_dict = db.get_stats_from_hand(hand) +# stat_dict = db.get_stats_from_hand(hand) for s in stat_dict.keys(): self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] for r in range(0, config.supported_games[self.poker_game].rows): diff --git a/website/docs-install-windows.php b/website/docs-install-windows.php index 8141c294..341e2f15 100644 --- a/website/docs-install-windows.php +++ b/website/docs-install-windows.php @@ -9,12 +9,17 @@ require 'sidebar.php';
    +

    Before You Begin

    +

    Most people should install using the Installer. These instructions have not been updated recently, but can serve as a guide to someone who knows what he is doing. Unless you have a special reason for installing manually, you really should be using the installer.

    + +

    Vista Users

    +

    The installer does not install mysql properly on Microsoft Vista installations, due to the UAC. You should first install mysql using this how to (pdf). Then run the installer. The installer will detect the mysql installation and not reintstall.

    +

    Installing in Windows

    -

    These instructions were made with Windows XP. They should also work with Windows NT / 2000 / 2003 / Vista and 2008. Please report any differences to gmic at users.sourceforge.net. +

    These instructions were made with Windows XP. They should also work with Windows NT / 2000 / 2003 and 2008. Please report any differences to gmic at users.sourceforge.net.

    If you're still using Win3/95/98/ME then you should switch to GNU/Linux, *BSD or WinXP.

    -

    An Installer will be made at some point.

    windows install guide screenshot From c7ba32c755e23733ae479333d33125da58afb556 Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 14 Oct 2008 22:35:05 +0800 Subject: [PATCH 188/262] Fix graph message to include site and player name --- pyfpdb/GuiGraphViewer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 5b3fa081..58c0cc31 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -51,8 +51,10 @@ class GuiGraphViewer (threading.Thread): if site=="PS": site=2 + sitename="PokerStars: " elif site=="FTP": site=1 + sitename="Full Tilt: " else: print "invalid text in site selection in graph, defaulting to PS" site=2 @@ -69,7 +71,9 @@ class GuiGraphViewer (threading.Thread): self.ax.set_xlabel("Hands", fontsize = 12) self.ax.set_ylabel("$", fontsize = 12) self.ax.grid(color='g', linestyle=':', linewidth=0.2) - self.ax.annotate ("All Hands, Site %s", (61,25), xytext =(0.1, 0.9) , textcoords ="axes fraction" ,) + text = "All Hands, " + sitename + str(name) + + self.ax.annotate (text, (61,25), xytext =(0.1, 0.9) , textcoords ="axes fraction" ,) #Get graph data from DB line = self.getRingProfitGraph(name, site) From 725f0589f807d1d174eeaec2b430e1d19e797f40 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 15 Oct 2008 00:15:01 +0800 Subject: [PATCH 189/262] Fix importing single files again - missing arguement --- pyfpdb/GuiBulkImport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index c2347528..978f328d 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -63,7 +63,7 @@ class GuiBulkImport (threading.Thread): if os.path.isdir(self.inputFile): self.import_dir() else: - self.importer.addImportFile() + self.importer.addImportFile(self.inputFile) self.importer.runImport() self.importer.clearFileList() From bc15025be559ef1567a59306a8a5ac903d3f93bf Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 15 Oct 2008 18:20:33 +0100 Subject: [PATCH 190/262] p135 - updated tv --- ...b-1.0_alpha5_p110.ebuild => fpdb-1.0_alpha7_p135.ebuild} | 2 +- pyfpdb/CliFpdb.py | 1 + pyfpdb/GuiTableViewer.py | 6 ++++-- pyfpdb/fpdb.py | 2 +- pyfpdb/fpdb_import.py | 1 + pyfpdb/fpdb_simple.py | 4 +++- 6 files changed, 11 insertions(+), 5 deletions(-) rename packaging/gentoo/{fpdb-1.0_alpha5_p110.ebuild => fpdb-1.0_alpha7_p135.ebuild} (97%) diff --git a/packaging/gentoo/fpdb-1.0_alpha5_p110.ebuild b/packaging/gentoo/fpdb-1.0_alpha7_p135.ebuild similarity index 97% rename from packaging/gentoo/fpdb-1.0_alpha5_p110.ebuild rename to packaging/gentoo/fpdb-1.0_alpha7_p135.ebuild index 4e55238e..07fbb188 100644 --- a/packaging/gentoo/fpdb-1.0_alpha5_p110.ebuild +++ b/packaging/gentoo/fpdb-1.0_alpha7_p135.ebuild @@ -1,7 +1,7 @@ # Copyright 1999-2008 Gentoo Foundation # Gentoo had nothing to do with the production of this ebuild, but I'm pre-emptively transferring all copyrights (as far as legally possible under my local jurisdiction) to them. # Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha5_p110.ebuild,v 1.0 2008/09/26 steffen@sycamoretest.info Exp $ +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha7_p135.ebuild,v 1.0 2008/10/15 steffen@sycamoretest.info Exp $ NEED_PYTHON=2.3 diff --git a/pyfpdb/CliFpdb.py b/pyfpdb/CliFpdb.py index 75dd1251..47649728 100755 --- a/pyfpdb/CliFpdb.py +++ b/pyfpdb/CliFpdb.py @@ -59,6 +59,7 @@ if __name__ == "__main__": settings['db-password']=options.password settings['db-databaseName']=options.database settings['handCount']=options.handCount + settings['failOnError']=options.failOnError importer = fpdb_import.Importer(options, settings) importer.addImportFile(options.inputFile) diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index f2bfecd9..1082bff1 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -220,7 +220,7 @@ class GuiTableViewer (threading.Thread): #print "start of table_viewer.read_names_clicked" self.db.reconnect() self.cursor=self.db.cursor - self.hands_id=self.last_read_hand_id + #self.hands_id=self.last_read_hand_id self.db.cursor.execute("SELECT gametypeId FROM Hands WHERE id=%s", (self.hands_id, )) self.gametype_id=self.db.cursor.fetchone()[0] @@ -257,7 +257,9 @@ class GuiTableViewer (threading.Thread): self.importer.setFailOnError(False) self.importer.setHandCount(0) - self.last_read_hand_id=self.importer.import_file_dict() + self.importer.addImportFile(self.inputFile) + self.importer.runImport() + self.hands_id=self.importer.handsId #end def table_viewer.import_clicked def all_clicked(self, widget, data): diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index e1440601..d3642bb5 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -426,7 +426,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha6+, p131 or higher") + self.window.set_title("Free Poker DB - version: alpha6+, p135 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index b652f204..c622a526 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -267,6 +267,7 @@ class Importer: handsId=0 #todo: this will cause return of an unstored hand number if the last hand was error or partial self.db.commit() + self.handsId=handsId return handsId #end def import_file_dict diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 39de0c9e..217b9163 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -315,10 +315,12 @@ def filterAnteBlindFold(site,hand): #removes useless lines as well as trailing spaces def filterCrap(site, hand, isTourney): - #remove one trailing space at end of line + #remove two trailing spaces at end of line for i in range (len(hand)): if (hand[i][-1]==' '): hand[i]=hand[i][:-1] + if (hand[i][-1]==' '): + hand[i]=hand[i][:-1] #print "hand after trailing space removal in filterCrap:",hand #general variable position word filter/string filter From 5c73bd7f3bc312a2ceb3792a078aa85047436c79 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Wed, 15 Oct 2008 18:26:24 +0100 Subject: [PATCH 191/262] p136 - title update - alpha7 --- ...{fpdb-1.0_alpha7_p135.ebuild => fpdb-1.0_alpha7_p136.ebuild} | 2 +- pyfpdb/fpdb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packaging/gentoo/{fpdb-1.0_alpha7_p135.ebuild => fpdb-1.0_alpha7_p136.ebuild} (97%) diff --git a/packaging/gentoo/fpdb-1.0_alpha7_p135.ebuild b/packaging/gentoo/fpdb-1.0_alpha7_p136.ebuild similarity index 97% rename from packaging/gentoo/fpdb-1.0_alpha7_p135.ebuild rename to packaging/gentoo/fpdb-1.0_alpha7_p136.ebuild index 07fbb188..58527c79 100644 --- a/packaging/gentoo/fpdb-1.0_alpha7_p135.ebuild +++ b/packaging/gentoo/fpdb-1.0_alpha7_p136.ebuild @@ -1,7 +1,7 @@ # Copyright 1999-2008 Gentoo Foundation # Gentoo had nothing to do with the production of this ebuild, but I'm pre-emptively transferring all copyrights (as far as legally possible under my local jurisdiction) to them. # Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha7_p135.ebuild,v 1.0 2008/10/15 steffen@sycamoretest.info Exp $ +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha7_p136.ebuild,v 1.0 2008/10/15 steffen@sycamoretest.info Exp $ NEED_PYTHON=2.3 diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index d3642bb5..789fcc30 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -426,7 +426,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha6+, p135 or higher") + self.window.set_title("Free Poker DB - version: alpha7, p136") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 112f041a981d13fe2183d31563ff229659512a17 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 16 Oct 2008 21:21:11 +0800 Subject: [PATCH 192/262] Start of changes to parse tournament hostory files --- pyfpdb/fpdb_import.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index b652f204..14c9936d 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -34,6 +34,7 @@ except: import math import os import datetime +import re import fpdb_simple import fpdb_parse_logic from time import time @@ -52,6 +53,7 @@ class Importer: self.updated = 0 #Time last import was run, used as mtime reference self.callHud = False self.lines = None + self.faobs = None #File as one big string self.pos_in_file = {} # dict to remember how far we have read in the file #Set defaults if not self.settings.has_key('imp-callFpdbHud'): @@ -166,6 +168,8 @@ class Importer: if firstline.find("Tournament Summary")!=-1: print "TODO: implement importing tournament summaries" + self.faobs = readfile(inputFile) + self.parseTourneyHistory() return 0 site=fpdb_simple.recogniseSite(firstline) @@ -270,6 +274,12 @@ class Importer: return handsId #end def import_file_dict + def parseTourneyHistory(self): + print "Tourney history parser stub" + #Find tournament boundaries. + #print self.foabs + + def printEmailErrorMessage(self, errors, filename, line): print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." print "Filename:", filename From 5a085a6d236197ff9f066423b1cd95f8d9a71d85 Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 17 Oct 2008 01:36:02 +0800 Subject: [PATCH 193/262] Draft fix to mtime detection for files --- pyfpdb/GuiAutoImport.py | 2 +- pyfpdb/fpdb_import.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 6953d1f9..e4d145dd 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -109,7 +109,7 @@ class GuiAutoImport (threading.Thread): self.importer.setQuiet(False) self.importer.setFailOnError(False) self.importer.setHandCount(0) - self.importer.setWatchTime() +# self.importer.setWatchTime() self.server=settings['db-host'] self.user=settings['db-user'] diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 12e854c0..860cb778 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -50,7 +50,7 @@ class Importer: self.filelist = [] self.dirlist = [] self.monitor = False - self.updated = 0 #Time last import was run, used as mtime reference + self.updated = {} #Time last import was run {file:mtime} self.callHud = False self.lines = None self.faobs = None #File as one big string @@ -96,8 +96,8 @@ class Importer: def setFailOnError(self, value): self.settings['failOnError'] = value - def setWatchTime(self): - self.updated = time() +# def setWatchTime(self): +# self.updated = time() def clearFileList(self): self.filelist = [] @@ -142,9 +142,15 @@ class Importer: for file in self.filelist: stat_info = os.stat(file) - if stat_info.st_mtime > self.updated: - self.import_file_dict(file) - self.updated = time() + try: + lastupdate = self.updated[file] +# print "Is " + str(stat_info.st_mtime) + " > " + str(lastupdate) + if stat_info.st_mtime > lastupdate: + self.import_file_dict(file) + self.updated[file] = time() + except: +# print "Adding " + str(file) + " at approx " + str(time()) + self.updated[file] = time() # This is now an internal function that should not be called directly. def import_file_dict(self, file): From b35e0e48802a358009cdf2b8456cb2922f2f9b46 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 16 Oct 2008 15:15:28 -0400 Subject: [PATCH 194/262] various changes with threaded HUD_main.py --- pyfpdb/Database.py | 2 + pyfpdb/HUD_main.py | 7 +- pyfpdb/fpdb_import.py | 152 +++++++++++++----------------------------- 3 files changed, 53 insertions(+), 108 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 63de54b2..09461a87 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -79,9 +79,11 @@ class Database: self.connection.close() def get_table_name(self, hand_id): + print "searching for ", hand_id c = self.connection.cursor() c.execute(self.sql.query['get_table_name'], (hand_id, )) row = c.fetchone() + print "found = ", row return row def get_last_hand(self): diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 5e3286f1..faacc0bf 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -36,6 +36,7 @@ Main for FreePokerTools HUD. import sys import os import thread +import time errorfile = open('HUD-error.txt', 'w', 0) sys.stderr = errorfile @@ -91,6 +92,7 @@ def producer(): # This is the thread function while True: # wait for a new hand number on stdin new_hand_id = sys.stdin.readline() + print "hand = ", new_hand_id if new_hand_id == "": # blank line means quit destroy() @@ -98,9 +100,10 @@ def producer(): # This is the thread function for h in hud_dict.keys(): if hud_dict[h].deleted: del(hud_dict[h]) - + print "getting table name" (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) stat_dict = db_connection.get_stats_from_hand(new_hand_id) + print "table = %s, max = %s, game = %s" % (table_name, max, poker_game) # if a hud for this table exists, just update it if hud_dict.has_key(table_name): @@ -108,8 +111,10 @@ def producer(): # This is the thread function # otherwise create a new hud else: table_windows = Tables.discover(config) + print "searching for %s" % (table_name) for t in table_windows.keys(): if table_windows[t].name == table_name: + print "found" create_HUD(new_hand_id, table_windows[t], db_name, table_name, max, poker_game, db_connection, config, stat_dict) break diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index b652f204..6960de03 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -36,136 +36,67 @@ import os import datetime import fpdb_simple import fpdb_parse_logic +from optparse import OptionParser from time import time class Importer: - def __init__(self, caller, settings): + def __init__(self): """Constructor""" - self.settings=settings - self.caller=caller + self.settings={'imp-callFpdbHud':False} self.db = None self.cursor = None - self.filelist = [] - self.dirlist = [] - self.monitor = False - self.updated = 0 #Time last import was run, used as mtime reference + self.options = None self.callHud = False self.lines = None self.pos_in_file = {} # dict to remember how far we have read in the file - #Set defaults - if not self.settings.has_key('imp-callFpdbHud'): - self.settings['imp-callFpdbHud'] = False - if not self.settings.has_key('minPrint'): - self.settings['minPrint'] = 30 - self.dbConnect() - def dbConnect(self): + def dbConnect(self, options, settings): #connect to DB - if self.settings['db-backend'] == 2: + if settings['db-backend'] == 2: if not mysqlLibFound: raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") - self.db = MySQLdb.connect(self.settings['db-host'], self.settings['db-user'], - self.settings['db-password'], self.settings['db-databaseName']) - elif self.settings['db-backend'] == 3: + self.db = MySQLdb.connect(host = options.server, user = options.user, + passwd = options.password, db = options.database) + elif settings['db-backend'] == 3: if not pgsqlLibFound: raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") - self.db = psycopg2.connect(self.settings['db-host'], self.settings['db-user'], - self.settings['db-password'], self.settings['db-databaseName']) - elif self.settings['db-backend'] == 4: + self.db = psycopg2.connect(host = options.server, user = options.user, + password = options.password, database = options.database) + elif settings['db-backend'] == 4: pass else: pass self.cursor = self.db.cursor() - #Set functions def setCallHud(self, value): self.callHud = value - def setMinPrint(self, value): - self.settings['minPrint'] = int(value) - - def setHandCount(self, value): - self.settings['handCount'] = int(value) - - def setQuiet(self, value): - self.settings['quiet'] = value - - def setFailOnError(self, value): - self.settings['failOnError'] = value - - def setWatchTime(self): - self.updated = time() - - def clearFileList(self): - self.filelist = [] - - #Add an individual file to filelist - def addImportFile(self, filename): - #todo: test it is a valid file - self.filelist = self.filelist + [filename] - #Remove duplicates - self.filelist = list(set(self.filelist)) - - #Add a directory of files to filelist - def addImportDirectory(self,dir,monitor = False): - #todo: test it is a valid directory - if monitor == True: - self.monitor = True - self.dirlist = self.dirlist + [dir] - - for file in os.listdir(dir): - if os.path.isdir(file): - print "BulkImport is not recursive - please select the final directory in which the history files are" - else: - self.filelist = self.filelist + [os.path.join(dir, file)] - #Remove duplicates - self.filelist = list(set(self.filelist)) - - #Run full import on filelist - def runImport(self): - for file in self.filelist: - self.import_file_dict(file) - - #Run import on updated files, then store latest update time. - def runUpdated(self): - #Check for new files in directory - #todo: make efficient - always checks for new file, should be able to use mtime of directory - # ^^ May not work on windows - for dir in self.dirlist: - for file in os.listdir(dir): - self.filelist = self.filelist + [dir+os.sep+file] - - self.filelist = list(set(self.filelist)) - - for file in self.filelist: - stat_info = os.stat(file) - if stat_info.st_mtime > self.updated: - self.import_file_dict(file) - self.updated = time() - - # This is now an internal function that should not be called directly. - def import_file_dict(self, file): + def import_file_dict(self, options, settings): starttime = time() last_read_hand=0 loc = 0 - if (file=="stdin"): + if (options.inputFile=="stdin"): inputFile=sys.stdin else: - inputFile=open(file, "rU") - try: loc = self.pos_in_file[file] + inputFile=open(options.inputFile, "rU") + try: loc = self.pos_in_file[options.inputFile] except: pass + self.dbConnect(options,settings) + # Read input file into class and close file inputFile.seek(loc) self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) - self.pos_in_file[file] = inputFile.tell() + self.pos_in_file[options.inputFile] = inputFile.tell() inputFile.close() firstline = self.lines[0] if firstline.find("Tournament Summary")!=-1: print "TODO: implement importing tournament summaries" + self.cursor.close() + self.db.close() return 0 site=fpdb_simple.recogniseSite(firstline) @@ -211,8 +142,7 @@ class Importer: if not isTourney: fpdb_simple.filterAnteBlindFold(site,hand) hand=fpdb_simple.filterCrap(site, hand, isTourney) - self.hand=hand - + try: handsId=fpdb_parse_logic.mainParser(self.db, self.cursor, site, category, hand) self.db.commit() @@ -220,36 +150,42 @@ class Importer: stored+=1 self.db.commit() # if settings['imp-callFpdbHud'] and self.callHud and os.sep=='/': - if self.settings['imp-callFpdbHud'] and self.callHud: + if settings['imp-callFpdbHud'] and self.callHud: #print "call to HUD here. handsId:",handsId #pipe the Hands.id out to the HUD - self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) +# options.pipe_to_hud.write("%s" % (handsId) + os.linesep) + print "handsID = ", handsID + options.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) except fpdb_simple.DuplicateError: duplicates+=1 except (ValueError), fe: errors+=1 - self.printEmailErrorMessage(errors, file, hand[0]) + self.printEmailErrorMessage(errors, options.inputFile, hand[0]) - if (self.settings['failOnError']): - self.db.commit() #dont remove this, in case hand processing was cancelled. + if (options.failOnError): + self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + self.cursor.close() + self.db.close() raise except (fpdb_simple.FpdbError), fe: errors+=1 - self.printEmailErrorMessage(errors, file, hand[0]) + self.printEmailErrorMessage(errors, options.inputFile, hand[0]) #fe.printStackTrace() #todo: get stacktrace self.db.rollback() - if (self.settings['failOnError']): - self.db.commit() #dont remove this, in case hand processing was cancelled. + if (options.failOnError): + self.db.commit() #dont remove this, in case hand processing was cancelled this ties up any open ends. + self.cursor.close() + self.db.close() raise - if (self.settings['minPrint']!=0): - if ((stored+duplicates+partial+errors)%self.settings['minPrint']==0): + if (options.minPrint!=0): + if ((stored+duplicates+partial+errors)%options.minPrint==0): print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors - if (self.settings['handCount']!=0): - if ((stored+duplicates+partial+errors)>=self.settings['handCount']): - if (not self.settings['quiet']): + if (options.handCount!=0): + if ((stored+duplicates+partial+errors)>=options.handCount): + if (not options.quiet): print "quitting due to reaching the amount of hands to be imported" print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime) sys.exit(0) @@ -267,14 +203,16 @@ class Importer: handsId=0 #todo: this will cause return of an unstored hand number if the last hand was error or partial self.db.commit() + self.cursor.close() + self.db.close() return handsId #end def import_file_dict def printEmailErrorMessage(self, errors, filename, line): print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." - print "Filename:", filename + print "Filename:",options.inputFile print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" - print self.hand[0] + print hand[0] if __name__ == "__main__": From eeef0a079b183072798a51da0fa4a8e5f9a53d8c Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 17 Oct 2008 10:59:56 +0800 Subject: [PATCH 195/262] Fix from Ray - fix typo in producer function --- pyfpdb/HUD_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 1d3fc299..0bfe7f40 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -106,7 +106,7 @@ def read_stdin(source, condition, db_name): def producer(): # This is the thread function while True: hand_no = sys.stdin.readline() # reads stdin - if new_hand_id == "": + if hand_no == "": destroy() dataQueue.put(hand_no) # and puts result on the queue From 7833c0c5cd01fd1ba0d34ec3149010fe225b5d55 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 17 Oct 2008 05:59:50 +0100 Subject: [PATCH 196/262] p136 - title update - alpha7 --- ...{fpdb-1.0_alpha7_p136.ebuild => fpdb-1.0_alpha8_p137.ebuild} | 2 +- pyfpdb/fpdb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packaging/gentoo/{fpdb-1.0_alpha7_p136.ebuild => fpdb-1.0_alpha8_p137.ebuild} (97%) diff --git a/packaging/gentoo/fpdb-1.0_alpha7_p136.ebuild b/packaging/gentoo/fpdb-1.0_alpha8_p137.ebuild similarity index 97% rename from packaging/gentoo/fpdb-1.0_alpha7_p136.ebuild rename to packaging/gentoo/fpdb-1.0_alpha8_p137.ebuild index 58527c79..c55903f3 100644 --- a/packaging/gentoo/fpdb-1.0_alpha7_p136.ebuild +++ b/packaging/gentoo/fpdb-1.0_alpha8_p137.ebuild @@ -1,7 +1,7 @@ # Copyright 1999-2008 Gentoo Foundation # Gentoo had nothing to do with the production of this ebuild, but I'm pre-emptively transferring all copyrights (as far as legally possible under my local jurisdiction) to them. # Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha7_p136.ebuild,v 1.0 2008/10/15 steffen@sycamoretest.info Exp $ +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha8_p137.ebuild,v 1.0 2008/10/17 steffen@sycamoretest.info Exp $ NEED_PYTHON=2.3 diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 789fcc30..2a27f72b 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -426,7 +426,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha7, p136") + self.window.set_title("Free Poker DB - version: alpha8, p137") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From ee2017bb055c7baae36edd5e1cf19a955649feab Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 17 Oct 2008 20:23:28 +0800 Subject: [PATCH 197/262] Fix comment - no mtime set at that point --- pyfpdb/GuiAutoImport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index e4d145dd..25e38fec 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -87,7 +87,7 @@ class GuiAutoImport (threading.Thread): # self.pipe_to_hud = os.popen(command, 'w') self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) -# Add directory to importer object and set the initial mtime reference. +# Add directory to importer object. self.importer.addImportDirectory(self.path, True) self.do_import() From 7fd517d76ed0d591e8c59bd96b496b4f16f997fb Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 17 Oct 2008 20:31:36 +0800 Subject: [PATCH 198/262] Replace multiline TBuffer with an Entry widget and rename --- pyfpdb/GuiAutoImport.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 25e38fec..4c00b36a 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -91,7 +91,7 @@ class GuiAutoImport (threading.Thread): self.importer.addImportDirectory(self.path, True) self.do_import() - interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) + interval=int(self.intervalEntry.get_text()) gobject.timeout_add(interval*1000, self.do_import) #end def GuiAutoImport.browseClicked @@ -127,11 +127,10 @@ class GuiAutoImport (threading.Thread): self.settingsHBox.pack_start(self.intervalLabel) self.intervalLabel.show() - self.intervalTBuffer=gtk.TextBuffer() - self.intervalTBuffer.set_text(str(self.settings['hud-defaultInterval'])) - self.intervalTView=gtk.TextView(self.intervalTBuffer) - self.settingsHBox.pack_start(self.intervalTView) - self.intervalTView.show() + self.intervalEntry=gtk.Entry() + self.intervalEntry.set_text(str(self.settings['hud-defaultInterval'])) + self.settingsHBox.pack_start(self.intervalEntry) + self.intervalEntry.show() self.pathHBox = gtk.HBox(False, 0) From d338a7c2f58efff0354f62ea8c895423a5f5107a Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 18 Oct 2008 00:28:33 +0800 Subject: [PATCH 199/262] First draft of multi-site AutoImport. GUI looks ugly, but appears to be working Changed TextView widget to Entry because it look neater on my system --- pyfpdb/GuiAutoImport.py | 76 +++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 4c00b36a..320079e4 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -27,10 +27,10 @@ import time import fpdb_import class GuiAutoImport (threading.Thread): - def browseClicked(self, widget, data): + def starsBrowseClicked(self, widget, data): """runs when user clicks browse on auto import tab""" - #print "start of GuiAutoImport.browseClicked" - current_path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) + #print "start of GuiAutoImport.starsBrowseClicked" + current_path=self.starsDirPath.get_text() dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, @@ -42,11 +42,32 @@ class GuiAutoImport (threading.Thread): response = dia_chooser.run() if response == gtk.RESPONSE_OK: #print dia_chooser.get_filename(), 'selected' - self.pathTBuffer.set_text(dia_chooser.get_filename()) + self.starsDirPath.set_text(dia_chooser.get_filename()) elif response == gtk.RESPONSE_CANCEL: print 'Closed, no files selected' dia_chooser.destroy() - #end def GuiAutoImport.browseClicked + #end def GuiAutoImport.starsBrowseClicked + + def tiltBrowseClicked(self, widget, data): + """runs when user clicks browse on auto import tab""" + #print "start of GuiAutoImport.tiltBrowseClicked" + current_path=self.tiltDirPath.get_text() + + dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", + action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) + #dia_chooser.set_current_folder(pathname) + dia_chooser.set_filename(current_path) + #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import + + response = dia_chooser.run() + if response == gtk.RESPONSE_OK: + #print dia_chooser.get_filename(), 'selected' + self.tiltDirPath.set_text(dia_chooser.get_filename()) + elif response == gtk.RESPONSE_CANCEL: + print 'Closed, no files selected' + dia_chooser.destroy() + #end def GuiAutoImport.tiltBrowseClicked def do_import(self): """Callback for timer to do an import iteration.""" @@ -85,15 +106,17 @@ class GuiAutoImport (threading.Thread): # command = command + " %s" % (self.database) # print "command = ", command # self.pipe_to_hud = os.popen(command, 'w') - self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) + self.starspath=self.starsDirPath.get_text() + self.tiltpath=self.tiltDirPath.get_text() # Add directory to importer object. - self.importer.addImportDirectory(self.path, True) + self.importer.addImportDirectory(self.starspath, True) + self.importer.addImportDirectory(self.tiltpath, True) self.do_import() interval=int(self.intervalEntry.get_text()) gobject.timeout_add(interval*1000, self.do_import) - #end def GuiAutoImport.browseClicked + #end def GuiAutoImport.startClicked def get_vbox(self): """returns the vbox of this thread""" @@ -132,27 +155,38 @@ class GuiAutoImport (threading.Thread): self.settingsHBox.pack_start(self.intervalEntry) self.intervalEntry.show() - self.pathHBox = gtk.HBox(False, 0) self.mainVBox.pack_start(self.pathHBox, False, True, 0) self.pathHBox.show() - self.pathLabel = gtk.Label("Path to auto-import:") - self.pathHBox.pack_start(self.pathLabel, False, False, 0) - self.pathLabel.show() - - self.pathTBuffer=gtk.TextBuffer() - self.pathTBuffer.set_text(self.settings['hud-defaultPath']) - self.pathTView=gtk.TextView(self.pathTBuffer) - self.pathHBox.pack_start(self.pathTView, False, True, 0) - self.pathTView.show() + self.pathStarsLabel = gtk.Label("Path to PokerStars auto-import:") + self.pathHBox.pack_start(self.pathStarsLabel, False, False, 0) + self.pathStarsLabel.show() + self.starsDirPath=gtk.Entry() + self.starsDirPath.set_text(self.settings['hud-defaultPath']) + self.pathHBox.pack_start(self.starsDirPath, False, True, 0) + self.starsDirPath.show() + self.browseButton=gtk.Button("Browse...") - self.browseButton.connect("clicked", self.browseClicked, "Browse clicked") - self.pathHBox.pack_end(self.browseButton, False, False, 0) + self.browseButton.connect("clicked", self.starsBrowseClicked, "Browse clicked") + self.pathHBox.pack_start(self.browseButton, False, False, 0) self.browseButton.show() - + self.pathTiltLabel = gtk.Label("Path to Full Tilt auto-import:") + self.pathHBox.pack_start(self.pathTiltLabel, False, False, 0) + self.pathTiltLabel.show() + + self.tiltDirPath=gtk.Entry() + self.tiltDirPath.set_text(self.settings['hud-defaultPath']) + self.pathHBox.pack_start(self.tiltDirPath, False, True, 0) + self.tiltDirPath.show() + + self.browseButton=gtk.Button("Browse...") + self.browseButton.connect("clicked", self.tiltBrowseClicked, "Browse clicked") + self.pathHBox.pack_start(self.browseButton, False, False, 0) + self.browseButton.show() + self.startButton=gtk.Button("Start Autoimport") self.startButton.connect("clicked", self.startClicked, "start clicked") self.mainVBox.add(self.startButton) From eb67d071a551eb97d4759007504dd9597c880509 Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 18 Oct 2008 00:35:59 +0800 Subject: [PATCH 200/262] Changed TextBuffer widget over to Entry as it looks nicer, and we dont need multiline text. --- pyfpdb/GuiGraphViewer.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 58c0cc31..99bade5e 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -45,9 +45,9 @@ class GuiGraphViewer (threading.Thread): try: self.canvas.destroy() except AttributeError: pass - name=self.nameTBuffer.get_text(self.nameTBuffer.get_start_iter(), self.nameTBuffer.get_end_iter()) + name=self.nameEntry.get_text() - site=self.siteTBuffer.get_text(self.siteTBuffer.get_start_iter(), self.siteTBuffer.get_end_iter()) + site=self.siteEntry.get_text() if site=="PS": site=2 @@ -121,21 +121,19 @@ class GuiGraphViewer (threading.Thread): self.settingsHBox.pack_start(self.nameLabel) self.nameLabel.show() - self.nameTBuffer=gtk.TextBuffer() - self.nameTBuffer.set_text("name") - self.nameTView=gtk.TextView(self.nameTBuffer) - self.settingsHBox.pack_start(self.nameTView) - self.nameTView.show() + self.nameEntry=gtk.Entry() + self.nameEntry.set_text("name") + self.settingsHBox.pack_start(self.nameEntry) + self.nameEntry.show() self.siteLabel = gtk.Label("Site (PS or FTP):") self.settingsHBox.pack_start(self.siteLabel) self.siteLabel.show() - self.siteTBuffer=gtk.TextBuffer() - self.siteTBuffer.set_text("PS") - self.siteTView=gtk.TextView(self.siteTBuffer) - self.settingsHBox.pack_start(self.siteTView) - self.siteTView.show() + self.siteEntry=gtk.Entry() + self.siteEntry.set_text("PS") + self.settingsHBox.pack_start(self.siteEntry) + self.siteEntry.show() self.showButton=gtk.Button("Show/Refresh") self.showButton.connect("clicked", self.showClicked, "show clicked") From 9bb8e229e48fc8757cff9e99d9def667c7fabb93 Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 18 Oct 2008 02:09:34 +0800 Subject: [PATCH 201/262] Fix TODO item - detect if directory passed in is a valid directory --- pyfpdb/fpdb_import.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 860cb778..fb170240 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -104,25 +104,27 @@ class Importer: #Add an individual file to filelist def addImportFile(self, filename): - #todo: test it is a valid file + #TODO: test it is a valid file self.filelist = self.filelist + [filename] #Remove duplicates self.filelist = list(set(self.filelist)) #Add a directory of files to filelist def addImportDirectory(self,dir,monitor = False): - #todo: test it is a valid directory - if monitor == True: - self.monitor = True - self.dirlist = self.dirlist + [dir] + if os.path.isdir(dir): + if monitor == True: + self.monitor = True + self.dirlist = self.dirlist + [dir] - for file in os.listdir(dir): - if os.path.isdir(file): - print "BulkImport is not recursive - please select the final directory in which the history files are" - else: - self.filelist = self.filelist + [os.path.join(dir, file)] - #Remove duplicates - self.filelist = list(set(self.filelist)) + for file in os.listdir(dir): + if os.path.isdir(file): + print "BulkImport is not recursive - please select the final directory in which the history files are" + else: + self.filelist = self.filelist + [os.path.join(dir, file)] + #Remove duplicates + self.filelist = list(set(self.filelist)) + else: + print "Warning: Attempted to add: '" + str(dir) + "' as an import directory" #Run full import on filelist def runImport(self): From fe2554ce358eed6a7f79d7d646dcb268c927b21a Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 17 Oct 2008 21:28:33 -0400 Subject: [PATCH 202/262] handle new hands in a thread, minimize stat windows on demand --- pyfpdb/Database.py | 2 -- pyfpdb/HUD_main.py | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 09461a87..63de54b2 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -79,11 +79,9 @@ class Database: self.connection.close() def get_table_name(self, hand_id): - print "searching for ", hand_id c = self.connection.cursor() c.execute(self.sql.query['get_table_name'], (hand_id, )) row = c.fetchone() - print "found = ", row return row def get_last_hand(self): diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index faacc0bf..996a92ac 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -37,6 +37,7 @@ import sys import os import thread import time +import string errorfile = open('HUD-error.txt', 'w', 0) sys.stderr = errorfile @@ -65,14 +66,14 @@ def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_conn global hud_dict def idle_func(): global hud_dict - gtk.threads_enter() + gtk.gdk.threads_enter() try: hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_name) hud_dict[table_name].create(new_hand_id, config) hud_dict[table_name].update(new_hand_id, db_connection, config, stat_dict) return False finally: - gtk.threads_leave + gtk.gdk.threads_leave gobject.idle_add(idle_func) def update_HUD(new_hand_id, table_name, db_connection, config, stat_dict): @@ -88,11 +89,10 @@ def update_HUD(new_hand_id, table_name, db_connection, config, stat_dict): def producer(): # This is the thread function global hud_dict - db_connection = Database.Database(config, db_name, 'temp') while True: # wait for a new hand number on stdin new_hand_id = sys.stdin.readline() - print "hand = ", new_hand_id + new_hand_id = string.rstrip(new_hand_id) if new_hand_id == "": # blank line means quit destroy() @@ -100,10 +100,12 @@ def producer(): # This is the thread function for h in hud_dict.keys(): if hud_dict[h].deleted: del(hud_dict[h]) - print "getting table name" + +# connect to the db and get basic info about the new hand + db_connection = Database.Database(config, db_name, 'temp') (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) stat_dict = db_connection.get_stats_from_hand(new_hand_id) - print "table = %s, max = %s, game = %s" % (table_name, max, poker_game) + db_connection.close_connection() # if a hud for this table exists, just update it if hud_dict.has_key(table_name): @@ -111,10 +113,8 @@ def producer(): # This is the thread function # otherwise create a new hud else: table_windows = Tables.discover(config) - print "searching for %s" % (table_name) for t in table_windows.keys(): if table_windows[t].name == table_name: - print "found" create_HUD(new_hand_id, table_windows[t], db_name, table_name, max, poker_game, db_connection, config, stat_dict) break From 941631c1e571162728e8d2d8562c6183011382bb Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 18 Oct 2008 14:16:24 +0800 Subject: [PATCH 203/262] Fix table detection for Full Tilt so it detects 6 max tables (others aren't verified) --- pyfpdb/Tables.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index cb6209c8..6c131d91 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -212,9 +212,9 @@ def fulltilt_decode_table(tw): title_bits = re.split(' - ', tw.title) name = title_bits[0] tw.tournament = None - for pattern in [r' \(6 max\)', r' \(heads up\)', r' \(deep\)', - r' \(deep hu\)', r' \(deep 6\)', r' \(2\)', - r' \(edu\)', r' \(edu, 6 max\)', r' \(6\)' ]: + for pattern in [' (6 max)', ' (heads up)', ' (deep)', + ' (deep hu)', ' (deep 6)', ' (2)', + ' (edu)', ' (edu, 6 max)', ' (6)' ]: name = re.sub(pattern, '', name) # (tw.name, trash) = name.split(r' (', 1) tw.name = name.rstrip() From 93a167b64daeb01f5302dc63d430340eaf5cb7fd Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2008 11:48:24 -0400 Subject: [PATCH 204/262] cleanup of threads in HUD_main.py --- pyfpdb/HUD_main.py | 12 ++++++------ pyfpdb/Hud.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 996a92ac..8b7c8700 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -70,24 +70,24 @@ def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_conn try: hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_name) hud_dict[table_name].create(new_hand_id, config) - hud_dict[table_name].update(new_hand_id, db_connection, config, stat_dict) + hud_dict[table_name].update(new_hand_id, config, stat_dict) return False finally: gtk.gdk.threads_leave gobject.idle_add(idle_func) -def update_HUD(new_hand_id, table_name, db_connection, config, stat_dict): +def update_HUD(new_hand_id, config, stat_dict): global hud_dict def idle_func(): gtk.threads_enter() try: - hud_dict[table_name].update(new_hand_id, db_connection, config, stat_dict) + hud_dict[table_name].update(new_hand_id, config, stat_dict) return False finally: gtk.threads_leave gobject.idle_add(idle_func) -def producer(): # This is the thread function +def read_stdin(): # This is the thread function global hud_dict while True: # wait for a new hand number on stdin @@ -109,7 +109,7 @@ def producer(): # This is the thread function # if a hud for this table exists, just update it if hud_dict.has_key(table_name): - update_HUD(new_hand_id, table_name, db_connection, config, stat_dict) + update_HUD(new_hand_id, config, stat_dict) # otherwise create a new hud else: table_windows = Tables.discover(config) @@ -130,7 +130,7 @@ if __name__== "__main__": config = Configuration.Config() gobject.threads_init() # this is required - thread.start_new_thread(producer, ()) # starts the thread + thread.start_new_thread(read_stdin, ()) # starts the thread main_window = gtk.Window() main_window.connect("destroy", destroy) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 611fd338..ddbffed4 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -99,12 +99,13 @@ class Hud: def on_window_event(self, widget, event): - if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: - for sw in self.stat_windows.keys(): - self.stat_windows[sw].window.iconify() - else: - for sw in self.stat_windows: - self.stat_windows[sw].window.deiconify() + if self.stacked: + if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: + for sw in self.stat_windows.keys(): + self.stat_windows[sw].window.iconify() + else: + for sw in self.stat_windows: + self.stat_windows[sw].window.deiconify() def kill_hud(self, args): for k in self.stat_windows.keys(): @@ -173,9 +174,8 @@ class Hud: # self.m = Mucked.Mucked(self.mucked_window, self.db_connection) # self.mucked_window.show_all() - def update(self, hand, db, config, stat_dict): + def update(self, hand, config, stat_dict): self.hand = hand # this is the last hand, so it is available later -# stat_dict = db.get_stats_from_hand(hand) for s in stat_dict.keys(): self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] for r in range(0, config.supported_games[self.poker_game].rows): From 3c4d05148b9d419bb95ab3fb61a2ef673672f154 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2008 12:00:29 -0400 Subject: [PATCH 205/262] error in HUD_main threads --- pyfpdb/HUD_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 8b7c8700..ed474f8c 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -76,7 +76,7 @@ def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_conn gtk.gdk.threads_leave gobject.idle_add(idle_func) -def update_HUD(new_hand_id, config, stat_dict): +def update_HUD(new_hand_id, table_name, config, stat_dict): global hud_dict def idle_func(): gtk.threads_enter() @@ -109,7 +109,7 @@ def read_stdin(): # This is the thread function # if a hud for this table exists, just update it if hud_dict.has_key(table_name): - update_HUD(new_hand_id, config, stat_dict) + update_HUD(new_hand_id, table_name, config, stat_dict) # otherwise create a new hud else: table_windows = Tables.discover(config) From 5c8cf5760dceaa6f8b52401b3cb55ba4e2a4c328 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2008 14:05:38 -0400 Subject: [PATCH 206/262] minor cleanups to HUD_main.py thread stuff --- pyfpdb/HUD_main.py | 4 ++-- pyfpdb/Hud.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index ed474f8c..e369e7f3 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -79,12 +79,12 @@ def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_conn def update_HUD(new_hand_id, table_name, config, stat_dict): global hud_dict def idle_func(): - gtk.threads_enter() + gtk.gdk.threads_enter() try: hud_dict[table_name].update(new_hand_id, config, stat_dict) return False finally: - gtk.threads_leave + gtk.gkd.threads_leave gobject.idle_add(idle_func) def read_stdin(): # This is the thread function diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index ddbffed4..3abca197 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -52,7 +52,7 @@ class Hud: self.max = max self.db_name = db_name self.deleted = False - self.stacked = True + self.stacked = False self.stat_windows = {} self.popup_windows = {} From a449e1948b5548339611eeb744debb155cbbd088 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2008 15:41:26 -0400 Subject: [PATCH 207/262] one last tipo in HUD_main.py thread stuff --- pyfpdb/HUD_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index e369e7f3..4c0eccdd 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -84,7 +84,7 @@ def update_HUD(new_hand_id, table_name, config, stat_dict): hud_dict[table_name].update(new_hand_id, config, stat_dict) return False finally: - gtk.gkd.threads_leave + gtk.gdk.threads_leave gobject.idle_add(idle_func) def read_stdin(): # This is the thread function From d2c3c93a9970bf4f881dbbe0c42e496f0f74d7c6 Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 20 Oct 2008 18:22:06 +0800 Subject: [PATCH 208/262] Fix permissions for php --- website/contact.php | 0 website/docs-abreviations.php | 0 website/docs-benchmarks.php | 0 website/docs-git-instructions.php | 0 website/docs-hudHowTo.php | 0 website/docs-install-gentoo.php | 0 website/docs-install-windows.php | 0 website/docs-overview.php | 0 website/docs-requirements.php | 0 website/docs-usage.php | 0 website/docs.php | 0 website/features.php | 0 website/footer.php | 0 website/header.php | 0 website/index.php | 0 website/license.php | 0 website/screenshots.php | 0 website/sidebar.php | 0 18 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 website/contact.php mode change 100644 => 100755 website/docs-abreviations.php mode change 100644 => 100755 website/docs-benchmarks.php mode change 100644 => 100755 website/docs-git-instructions.php mode change 100644 => 100755 website/docs-hudHowTo.php mode change 100644 => 100755 website/docs-install-gentoo.php mode change 100644 => 100755 website/docs-install-windows.php mode change 100644 => 100755 website/docs-overview.php mode change 100644 => 100755 website/docs-requirements.php mode change 100644 => 100755 website/docs-usage.php mode change 100644 => 100755 website/docs.php mode change 100644 => 100755 website/features.php mode change 100644 => 100755 website/footer.php mode change 100644 => 100755 website/header.php mode change 100644 => 100755 website/index.php mode change 100644 => 100755 website/license.php mode change 100644 => 100755 website/screenshots.php mode change 100644 => 100755 website/sidebar.php diff --git a/website/contact.php b/website/contact.php old mode 100644 new mode 100755 diff --git a/website/docs-abreviations.php b/website/docs-abreviations.php old mode 100644 new mode 100755 diff --git a/website/docs-benchmarks.php b/website/docs-benchmarks.php old mode 100644 new mode 100755 diff --git a/website/docs-git-instructions.php b/website/docs-git-instructions.php old mode 100644 new mode 100755 diff --git a/website/docs-hudHowTo.php b/website/docs-hudHowTo.php old mode 100644 new mode 100755 diff --git a/website/docs-install-gentoo.php b/website/docs-install-gentoo.php old mode 100644 new mode 100755 diff --git a/website/docs-install-windows.php b/website/docs-install-windows.php old mode 100644 new mode 100755 diff --git a/website/docs-overview.php b/website/docs-overview.php old mode 100644 new mode 100755 diff --git a/website/docs-requirements.php b/website/docs-requirements.php old mode 100644 new mode 100755 diff --git a/website/docs-usage.php b/website/docs-usage.php old mode 100644 new mode 100755 diff --git a/website/docs.php b/website/docs.php old mode 100644 new mode 100755 diff --git a/website/features.php b/website/features.php old mode 100644 new mode 100755 diff --git a/website/footer.php b/website/footer.php old mode 100644 new mode 100755 diff --git a/website/header.php b/website/header.php old mode 100644 new mode 100755 diff --git a/website/index.php b/website/index.php old mode 100644 new mode 100755 diff --git a/website/license.php b/website/license.php old mode 100644 new mode 100755 diff --git a/website/screenshots.php b/website/screenshots.php old mode 100644 new mode 100755 diff --git a/website/sidebar.php b/website/sidebar.php old mode 100644 new mode 100755 From a719efc6dc368a56f87045b85457b4eacfcf82ee Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2008 10:22:01 -0400 Subject: [PATCH 209/262] consolidate config files--default.conf no longer needed --- pyfpdb/Configuration.py | 103 ++++++++++++++++++++++++++++++++-------- pyfpdb/fpdb.py | 74 ++++------------------------- 2 files changed, 94 insertions(+), 83 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index d2504365..4e29dba1 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -169,6 +169,24 @@ class Popup: temp = temp + " " + stat return temp + "\n" +class Import: + def __init__(self, node): + self.interval = node.getAttribute("interval") + self.callFpdbHud = node.getAttribute("callFpdbHud") + + def __str__(self): + return " interval = %s\n callFpdbHud = %s\n" % (self.interval, self.callFpdbHud) + +class Tv: + def __init__(self, node): + self.combinedStealFold = node.getAttribute("combinedStealFold") + self.combined2B3B = node.getAttribute("combined2B3B") + self.combinedPostflop = node.getAttribute("combinedPostflop") + + def __str__(self): + return (" combinedStealFold = %s\n combined2B3B = %s\n combinedPostflop = %s\n" % + (self.combinedStealFold, self.combined2B3B, self.combinedPostflop) ) + class Config: def __init__(self, file = None): @@ -200,6 +218,7 @@ class Config: sys.stderr.write("No HUD_config_xml found. Exiting") sys.exit() try: + print "Reading configuration file %s\n" % (file) doc = xml.dom.minidom.parse(file) except: print "Error parsing %s. See error log file." % (file) @@ -221,26 +240,34 @@ class Config: site = Site(node = site_node) self.supported_sites[site.site_name] = site - s_games = doc.getElementsByTagName("supported_games") +# s_games = doc.getElementsByTagName("supported_games") for game_node in doc.getElementsByTagName("game"): game = Game(node = game_node) self.supported_games[game.game_name] = game - s_dbs = doc.getElementsByTagName("supported_databases") +# s_dbs = doc.getElementsByTagName("supported_databases") for db_node in doc.getElementsByTagName("database"): db = Database(node = db_node) self.supported_databases[db.db_name] = db - s_dbs = doc.getElementsByTagName("mucked_windows") +# s_dbs = doc.getElementsByTagName("mucked_windows") for mw_node in doc.getElementsByTagName("mw"): mw = Mucked(node = mw_node) self.mucked_windows[mw.name] = mw - s_dbs = doc.getElementsByTagName("popup_windows") +# s_dbs = doc.getElementsByTagName("popup_windows") for pu_node in doc.getElementsByTagName("pu"): pu = Popup(node = pu_node) self.popup_windows[pu.name] = pu + for imp_node in doc.getElementsByTagName("import"): + imp = Import(node = imp_node) + self.imp = imp + + for tv_node in doc.getElementsByTagName("tv"): + tv = Tv(node = tv_node) + self.tv = tv + def get_site_node(self, site): for site_node in self.doc.getElementsByTagName("site"): if site_node.getAttribute("site_name") == site: @@ -285,56 +312,94 @@ class Config: if name == None: name = 'fpdb' db = {} try: - db['databaseName'] = name - db['host'] = self.supported_databases[name].db_ip - db['user'] = self.supported_databases[name].db_user - db['password'] = self.supported_databases[name].db_pass - db['server'] = self.supported_databases[name].db_server + db['db-databaseName'] = name + db['db-host'] = self.supported_databases[name].db_ip + db['db-user'] = self.supported_databases[name].db_user + db['db-password'] = self.supported_databases[name].db_pass + db['db-server'] = self.supported_databases[name].db_server if string.lower(self.supported_databases[name].db_server) == 'mysql': - db['backend'] = 2 + db['db-backend'] = 2 elif string.lower(self.supported_databases[name].db_server) == 'postgresql': - db['backend'] = 3 - else: db['backend'] = 0 # this is big trouble + db['db-backend'] = 3 + else: db['db-backend'] = None # this is big trouble except: pass return db + def get_tv_parameters(self): + tv = {} + try: + tv['combinedStealFold'] = self.tv.combinedStealFold + tv['combined2B3B'] = self.tv.combined2B3B + tv['combinedPostflop'] = self.tv.combinedPostflop + except: # Default tv parameters + tv['combinedStealFold'] = True + tv['combined2B3B'] = True + tv['combinedPostflop'] = True + return tv + + def get_import_parameters(self): + imp = {} + try: + imp['imp-callFpdbHud'] = self.imp.callFpdbHud + imp['hud-defaultInterval'] = int(self.imp.interval) + except: # Default import parameters + imp['imp-callFpdbHud'] = True + imp['hud-defaultInterval'] = 10 + return imp + + def get_default_paths(self, site = "PokerStars"): + paths = {} + try: + paths['hud-defaultPath'] = os.path.expanduser(self.supported_sites[site].HH_path) + paths['bulkImport-defaultPath'] = os.path.expanduser(self.supported_sites[site].HH_path) + except: + paths['hud-defaultPath'] = "default" + paths['bulkImport-defaultPath'] = "default" + return paths + if __name__== "__main__": c = Config() print "\n----------- SUPPORTED SITES -----------" for s in c.supported_sites.keys(): print c.supported_sites[s] - print "----------- END SUPPORTED SITES -----------" print "\n----------- SUPPORTED GAMES -----------" for game in c.supported_games.keys(): print c.supported_games[game] - print "----------- END SUPPORTED GAMES -----------" print "\n----------- SUPPORTED DATABASES -----------" for db in c.supported_databases.keys(): print c.supported_databases[db] - print "----------- END SUPPORTED DATABASES -----------" print "\n----------- MUCKED WINDOW FORMATS -----------" for w in c.mucked_windows.keys(): print c.mucked_windows[w] - print "----------- END MUCKED WINDOW FORMATS -----------" - + print "\n----------- POPUP WINDOW FORMATS -----------" for w in c.popup_windows.keys(): print c.popup_windows[w] - print "----------- END MUCKED WINDOW FORMATS -----------" + print "\n----------- IMPORT -----------" + print c.imp + print "----------- END IMPORT -----------" + + print "\n----------- TABLE VIEW -----------" + print c.tv + print "----------- END TABLE VIEW -----------" + c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) c.save(file="testout.xml") - print c.get_db_parameters() \ No newline at end of file + print "db = ", c.get_db_parameters() + print "tv = ", c.get_tv_parameters() + print "imp = ", c.get_import_parameters() + print "paths = ", c.get_default_paths("PokerStars") diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 2a27f72b..4e919f93 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -33,6 +33,7 @@ import GuiTableViewer import GuiAutoImport import GuiGraphViewer import FpdbSQLQueries +import Configuration class fpdb: def tab_clicked(self, widget, tab_name): @@ -256,71 +257,16 @@ class fpdb: def load_profile(self, filename): """Loads profile from the provided path name. also see load_default_profile""" - self.obtain_global_lock() - file=open(filename, "rU") - lines=file.readlines() - print "Opened and read profile file", filename - self.profile=filename - - self.settings={'db-host':"localhost", 'db-backend':2, 'db-databaseName':"fpdb", 'db-user':"fpdb"} + self.settings = {} if (os.sep=="/"): self.settings['os']="linuxmac" else: self.settings['os']="windows" - self.settings['tv-combinedStealFold']=True - self.settings['tv-combined2B3B']=True - self.settings['imp-callFpdbHud']=True - - if self.settings['os']=="windows": - self.settings['bulkImport-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\filename.txt" - self.settings['hud-defaultPath']="C:\\Program Files\\PokerStars\\HandHistory\\" - else: - self.settings['bulkImport-defaultPath'] = os.path.expanduser("~") + "/.wine/drive_c/Program Files/PokerStars/HandHistory/filename.txt" - self.settings['hud-defaultPath'] = os.path.expanduser("~")+"/.wine/drive_c/Program Files/PokerStars/HandHistory/" - - self.settings['hud-defaultInterval']=10 - - for i in range(len(lines)): - if lines[i].startswith("db-backend="): - self.settings['db-backend']=int(lines[i][11:-1]) - elif lines[i].startswith("db-host="): - self.settings['db-host']=lines[i][8:-1] - elif lines[i].startswith("db-databaseName="): - self.settings['db-databaseName']=lines[i][16:-1] - elif lines[i].startswith("db-user="): - self.settings['db-user']=lines[i][8:-1] - elif lines[i].startswith("db-password="): - self.settings['db-password']=lines[i][12:-1] - elif lines[i].startswith("imp-callFpdbHud="): - if lines[i].find("True")!=-1: - self.settings['imp-callFpdbHud']=True - else: - self.settings['imp-callFpdbHud']=False - elif lines[i].startswith("tv-combinedPostflop="): - if lines[i].find("True")!=-1: - self.settings['tv-combinedPostflop']=True - else: - self.settings['tv-combinedPostflop']=False - elif lines[i].startswith("tv-combinedStealFold="): - if lines[i].find("True")!=-1: - self.settings['tv-combinedStealFold']=True - else: - self.settings['tv-combinedStealFold']=False - elif lines[i].startswith("tv-combined2B3B="): - if lines[i].find("True")!=-1: - self.settings['tv-combined2B3B']=True - else: - self.settings['tv-combined2B3B']=False - elif lines[i].startswith("bulkImport-defaultPath="): - if lines[i][23:-1]!="default": - self.settings['bulkImport-defaultPath']=lines[i][23:-1] - elif lines[i].startswith("hud-defaultPath="): - if lines[i][15:-1]!="default": - self.settings['hud-defaultPath']=lines[i][16:-1] - elif lines[i].startswith("#"): - pass #comment - dont parse - else: - raise fpdb_simple.FpdbError("invalid line in profile file: "+lines[i]+" if you don't know what to do just remove it from "+filename) + + self.settings.update(self.config.get_db_parameters()) + self.settings.update(self.config.get_tv_parameters()) + self.settings.update(self.config.get_import_parameters()) + self.settings.update(self.config.get_default_paths()) if self.db!=None: self.db.disconnect() @@ -355,8 +301,7 @@ class fpdb: #end def not_implemented def obtain_global_lock(self): - #print "todo: implement obtain_global_lock (users: pls ignore this)" - pass + print "todo: implement obtain_global_lock (users: pls ignore this)" #end def obtain_global_lock def quit(self, widget, data): @@ -421,12 +366,13 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") def __init__(self): self.threads=[] self.db=None + self.config = Configuration.Config() self.load_default_profile() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha8, p137") + self.window.set_title("Free Poker DB - version: alpha6+, p124 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From 2f783041851a15bdce62447c83955731c6ed622b Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2008 21:46:30 -0400 Subject: [PATCH 210/262] cleanup of config file consolidation --- pyfpdb/GuiAutoImport.py | 9 ++++++--- pyfpdb/GuiBulkImport.py | 3 ++- pyfpdb/HUD_main.py | 4 +++- pyfpdb/Hud.py | 5 +++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 320079e4..a6a994c7 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -123,9 +123,10 @@ class GuiAutoImport (threading.Thread): return self.mainVBox #end def get_vbox - def __init__(self, settings, debug=True): + def __init__(self, settings, config, debug=True): """Constructor for GuiAutoImport""" self.settings=settings + self.config=config self.importer = fpdb_import.Importer(self,self.settings) self.importer.setCallHud(True) self.importer.setMinPrint(30) @@ -164,7 +165,8 @@ class GuiAutoImport (threading.Thread): self.pathStarsLabel.show() self.starsDirPath=gtk.Entry() - self.starsDirPath.set_text(self.settings['hud-defaultPath']) + paths = self.config.get_default_paths("PokerStars") + self.starsDirPath.set_text(paths['hud-defaultPath']) self.pathHBox.pack_start(self.starsDirPath, False, True, 0) self.starsDirPath.show() @@ -178,7 +180,8 @@ class GuiAutoImport (threading.Thread): self.pathTiltLabel.show() self.tiltDirPath=gtk.Entry() - self.tiltDirPath.set_text(self.settings['hud-defaultPath']) + paths = self.config.get_default_paths("FullTilt") + self.tiltDirPath.set_text(paths['hud-defaultPath']) self.pathHBox.pack_start(self.tiltDirPath, False, True, 0) self.tiltDirPath.show() diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index 978f328d..ee5af2c6 100644 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -76,9 +76,10 @@ class GuiBulkImport (threading.Thread): print "todo: implement bulk import thread" #end def run - def __init__(self, db, settings): + def __init__(self, db, settings, config): self.db=db self.settings=settings + self.config=config self.importer = fpdb_import.Importer(self,self.settings) self.vbox=gtk.VBox(False,1) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 4c0eccdd..b1d5df14 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -134,8 +134,10 @@ if __name__== "__main__": main_window = gtk.Window() main_window.connect("destroy", destroy) + eb = gtk.EventBox() label = gtk.Label('Closing this window will exit from the HUD.') - main_window.add(label) + eb.add(label) + main_window.add(eb) main_window.set_title("HUD Main Window") main_window.show_all() diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 3abca197..b6233ec6 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -52,7 +52,7 @@ class Hud: self.max = max self.db_name = db_name self.deleted = False - self.stacked = False + self.stacked = True self.stat_windows = {} self.popup_windows = {} @@ -70,7 +70,8 @@ class Hud: self.main_window.connect("window-state-event", self.on_window_event) self.ebox = gtk.EventBox() - self.label = gtk.Label("Close this window to\nkill the HUD for\n %s" % (table.name)) + self.label = gtk.Label("Close this window to\nkill the HUD for\n %s\nMinimizing it hides stats." % + (table.name)) self.main_window.add(self.ebox) self.ebox.add(self.label) self.main_window.move(self.table.x, self.table.y) From 6d449b9a2e629a60931147e9ea19fb6ecbe5ab23 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2008 21:49:17 -0400 Subject: [PATCH 211/262] more cleanup of config file consolidation --- pyfpdb/fpdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 4e919f93..f56895f0 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -321,7 +321,7 @@ class fpdb: def tab_auto_import(self, widget, data): """opens the auto import tab""" - new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings) + new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings, self.config) self.threads.append(new_aimp_thread) aimp_tab=new_aimp_thread.get_vbox() self.add_and_display_tab(aimp_tab, "Auto Import") @@ -330,7 +330,7 @@ class fpdb: def tab_bulk_import(self, widget, data): """opens a tab for bulk importing""" #print "start of tab_bulk_import" - new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings) + new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings, self.config) self.threads.append(new_import_thread) bulk_tab=new_import_thread.get_vbox() self.add_and_display_tab(bulk_tab, "Bulk Import") From bc834a9d79aaa22e0d09b74b406dde92b6d36b57 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 22 Oct 2008 10:14:25 -0400 Subject: [PATCH 212/262] more cleanup of config file consolidation --- pyfpdb/fpdb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index f56895f0..ab038113 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -255,7 +255,7 @@ class fpdb: self.diaSetupWizard(path=defaultpath) #end def load_default_profile - def load_profile(self, filename): + def load_profile(self): """Loads profile from the provided path name. also see load_default_profile""" self.settings = {} if (os.sep=="/"): @@ -367,7 +367,8 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.threads=[] self.db=None self.config = Configuration.Config() - self.load_default_profile() + self.load_profile() +# self.load_default_profile() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) From 3f082346f7585edab00314b9a4114d5cfeb96710 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 22 Oct 2008 10:51:43 -0400 Subject: [PATCH 213/262] updated for config integration --- pyfpdb/HUD_config.xml.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index f5312c99..abdf7618 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -161,6 +161,9 @@ + + + From 1c5024d35c187bd87952a79cb2e0f411d2677998 Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 24 Oct 2008 21:49:20 +0800 Subject: [PATCH 214/262] Fix typo for mac HT - Eric Blade (blade.eric at gmail.com) --- pyfpdb/Tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index 6c131d91..a0f367da 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -61,7 +61,7 @@ def discover(c): elif os.name == 'nt': tables = discover_nt(c) return tables - elif ox.name == 'mac': + elif os.name == 'mac': tables = discover_mac(c) return tables else: tables = {} From 2bd4932a77181c3d41d08faad214c3b6d7c3983b Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 24 Oct 2008 15:44:53 -0400 Subject: [PATCH 215/262] Tables discovered via process name, then window name, only going through two nested loops, instead of two nested loops followed by another loop. (can someone test to make sure this actually doesn't break stuff on *nix?) Windows HUD Stats Windows no longer appear in Windows taskbar (now gtk transients for table hud window) --- pyfpdb/HUD_main.py | 16 ++++---- pyfpdb/Hud.py | 92 +++++++++++++++++++++++++++++++++++++++++----- pyfpdb/Tables.py | 92 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 176 insertions(+), 24 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 0bfe7f40..88e87104 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -77,14 +77,14 @@ def process_new_hand(new_hand_id, db_name): hud_dict[table_name].update(new_hand_id, db_connection, config) # otherwise create a new hud else: - table_windows = Tables.discover(config) - for t in table_windows.keys(): - if table_windows[t].name == table_name: - hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_name) - hud_dict[table_name].create(new_hand_id, config) - hud_dict[table_name].update(new_hand_id, db_connection, config) - break -# print "table name \"%s\" not identified, no hud created" % (table_name) + tablewindow = Tables.discover_table_by_name(config, table_name) + if tablewindow == None: + sys.stderr.write("table name "+table_name+" not found\n") + else: + hud_dict[table_name] = Hud.Hud(tablewindow, max, poker_game, config, db_name) + hud_dict[table_name].create(new_hand_id, config) + hud_dict[table_name].update(new_hand_id, db_connection, config) + db_connection.close_connection() return(1) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 003e0be1..6d3b1fef 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -23,6 +23,7 @@ Create and manage the hud overlays. ######################################################################## # Standard Library modules import os +import sys # pyGTK modules import pygtk @@ -61,9 +62,10 @@ class Hud: self.main_window = gtk.Window() # self.window.set_decorated(0) self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.main_window.set_keep_above(1) - self.main_window.set_title(table.name) + self.main_window.set_keep_above(True) + self.main_window.set_title(table.name + " FPDBHUD") self.main_window.connect("destroy", self.kill_hud) + #self.main_window.set_transient_for(parent.get_toplevel()) self.ebox = gtk.EventBox() self.label = gtk.Label("Close this window to\nkill the HUD for\n %s" % (table.name)) @@ -136,8 +138,57 @@ class Hud: adj = self.adj_seats(hand, config) # create the stat windows - for i in range(1, self.max + 1): - (x, y) = config.supported_sites[self.table.site].layout[self.max].location[adj[i]] + for i in range(1, self.max + 1): + # the below IF always appears to be passed as TRUE, I don't know why. If you have an 8-max game, but + # your config file doesn't understand 8 max, it's a crash. It was even a crash when I tried using the + # full-fledged exception handling blocks. + # - Eric + + if self.max in config.supported_sites[self.table.site].layout: + (x, y) = config.supported_sites[self.table.site].layout[self.max].location[adj[i]] + else: + if i == 1: + x = 684 + y = 61 + elif i == 2: + x = 689 + y = 239 + elif i == 3: + x = 692 + y = 346 + elif i == 4: + x = 586 + y = 393 + elif i == 5: + x = 421 + y = 440 + elif i == 6: + x = 267 + y = 440 + elif i == 7: + x = 0 + y = 361 + elif i == 8: + x = 0 + y = 280 + elif i == 9: + x = 121 + y = 280 + elif i == 10: + x = 46 + y = 30 + + sys.stderr.write("at location "+str(x)+" "+str(y)+"\n") + sys.stderr.write("config:"+str(config)+"\n") + gameslist = config.supported_games + sys.stderr.write("supported games:"+str(gameslist)+"\n") + sys.stderr.write("desired game:"+str(self.poker_game)+"\n") + thisgame = gameslist['holdem'] + sys.stderr.write("this game:"+str(thisgame)+"\n") + # the above code looks absolutely completely useless. The below line was freezing the interpreter, so I added the above lines to try and debug + # which piece of the below line was causing it to lock up. Adding the "thisgame = gameslist['holdem']" line fixed it, for some unknown reason. + # removing any one of the lines above causes the interpreter to freeze for me on the next statement. + # -eric self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], parent = self, table = self.table, @@ -189,7 +240,15 @@ class Hud: for w in tl_windows: if w[1] == unique_name: - win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# win32gui.ShowWindow(w[0], win32con.SW_HIDE) +# style = win32gui.GetWindowLong(w[0], win32con.GWL_EXSTYLE) +# style |= win32con.WS_EX_TOOLWINDOW +# style &= ~win32con.WS_EX_APPWINDOW +# win32gui.SetWindowLong(w[0], win32con.GWL_EXSTYLE, style) +# win32gui.ShowWindow(w[0], win32con.SW_SHOW) + + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) + # notify_id = (w[0], # 0, # win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, @@ -262,6 +321,7 @@ class Stat_Window: self.window.set_keep_above(1) self.window.set_title("%s" % seat) self.window.set_property("skip-taskbar-hint", True) + self.window.set_transient_for(parent.main_window) self.grid = gtk.Table(rows = self.game.rows, columns = self.game.cols, homogeneous = False) self.window.add(self.grid) @@ -328,6 +388,7 @@ class Popup_window: self.window.set_title("popup") self.window.set_property("skip-taskbar-hint", True) self.window.set_transient_for(parent.get_toplevel()) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) self.ebox = gtk.EventBox() @@ -440,7 +501,14 @@ class Popup_window: for w in tl_windows: if w[1] == unique_name: - win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# win32gui.ShowWindow(w[0], win32con.SW_HIDE) +# style = win32gui.GetWindowLong(w[0], win32con.GWL_EXSTYLE) +# style |= win32con.WS_EX_TOOLWINDOW +# style &= ~win32con.WS_EX_APPWINDOW +# win32gui.SetWindowLong(w[0], win32con.GWL_EXSTYLE, style) +# win32gui.ShowWindow(w[0], win32con.SW_SHOW) + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) + # notify_id = (w[0], # 0, # win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, @@ -459,12 +527,16 @@ if __name__== "__main__": main_window.show_all() c = Configuration.Config() - tables = Tables.discover(c) + #tables = Tables.discover(c) + t = Tables.discover_table_by_name(c, "Southend") + if t is None: + print "Table not found." db = Database.Database(c, 'fpdb', 'holdem') - for t in tables: - win = Hud(t, 8, c, db) +# for t in tables: + win = Hud(t, 10, 'holdem', c, db) + win.create(1, c) # t.get_details() - win.update(8300, db, c) + win.update(8300, db, c) gtk.main() diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index cb6209c8..9cbcc384 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -33,6 +33,9 @@ import re if os.name == 'nt': import win32gui + import win32process + import win32api + import win32con # FreePokerTools modules import Configuration @@ -57,17 +60,24 @@ class Table_Window: def discover(c): if os.name == 'posix': tables = discover_posix(c) - return tables elif os.name == 'nt': tables = discover_nt(c) - return tables - elif ox.name == 'mac': + elif os.name == 'mac': tables = discover_mac(c) - return tables else: tables = {} return(tables) +def discover_table_by_name(c, tablename): + if os.name == 'posix': + table = discover_posix_by_name(c, tablename) + elif os.name == 'nt': + table = discover_nt_by_name(c, tablename) + elif os.name == 'mac': + table = discover_mac_by_name(c, tablename) + else: table = None + return(table) + def discover_posix(c): """ Poker client table window finder for posix/Linux = XWindows.""" tables = {} @@ -94,8 +104,17 @@ def discover_posix(c): # use this eval thingie to call the title bar decoder specified in the config file eval("%s(tw)" % c.supported_sites[s].decoder) + tables[tw.name] = tw return tables + +def discover_posix_by_name(c, tablename): + tables = discover_posix(c) + for t in tables: + if t.name.find(tablename) > -1: + return t + return None + # # The discover_xx functions query the system and report on the poker clients # currently displayed on the screen. The discover_posix should give you @@ -120,6 +139,7 @@ def discover_posix(c): def win_enum_handler(hwnd, titles): titles[hwnd] = win32gui.GetWindowText(hwnd) + def child_enum_handler(hwnd, children): print hwnd, win32.GetWindowRect(hwnd) @@ -150,7 +170,7 @@ def discover_nt(c): tw.y = int( y ) + tb_height if re.search('Logged In as', titles[hwnd]): tw.site = "PokerStars" - elif re.search('Logged In As', titles[hwnd]): + elif re.search('Logged In As', titles[hwnd]): #wait, what??! tw.site = "Full Tilt" else: tw.site = "Unknown" @@ -159,14 +179,73 @@ def discover_nt(c): eval("%s(tw)" % c.supported_sites[tw.site].decoder) else: tw.name = "Unknown" - tables[tw.name] = tw + tables[len(tables)] = tw return tables +def discover_nt_by_name(c, tablename): + # this is pretty much identical to the 'search all windows for all poker sites' code, but made to dig just for a specific table name + # it could be implemented a bunch better - and we need to not assume the width/height thing that (steffen?) assumed above, we should + # be able to dig up the window's titlebar handle and get it's information, and such .. but.. for now, i guess this will work. + # - eric + b_width = 3 + tb_height = 29 + titles = {} +# tables = discover_nt(c) + win32gui.EnumWindows(win_enum_handler, titles) + for s in c.supported_sites.keys(): + for hwnd in titles.keys(): + processid = win32process.GetWindowThreadProcessId(hwnd) + pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1]) + exe = win32process.GetModuleFileNameEx(pshandle, 0) + if exe.find(c.supported_sites[s].table_finder) == -1: + continue + if titles[hwnd].find(tablename) > -1: + if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1: + continue + tw = Table_Window() + tw.number = hwnd + (x, y, width, height) = win32gui.GetWindowRect(hwnd) + tw.title = titles[hwnd] + tw.width = int(width) - 2 * b_width + tw.height = int(height) - b_width - tb_height + tw.x = int(x) + b_width + tw.y = int(y) + tb_height + tw.site = c.supported_sites[s].site_name + if not tw.site == "Unknown" and not tw.decoder == "Unknown": + eval("%s(tw)" % c.supported_sites[tw.site].decoder) + else: + tw.name = tablename + return tw + + # if we don't find anything by process name, let's search one more time, and call it Unknown ? + for hwnd in titles.keys(): + if titles[hwnd].find(tablename) > -1: + if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1: + continue + tw = Table_Window() + tw.number = hwnd + (x, y, width, height) = win32gui.GetWindowRect(hwnd) + tw.title = titles[hwnd] + tw.width = int(width) - 2 * b_width + tw.height = int(height) - b_width - tb_height + tw.x = int(x) + b_width + tw.y = int(y) + tb_height + tw.site = "Unknown" + tw.name = tablename + return tw + + return None + def discover_mac(c): """ Poker client table window finder for Macintosh.""" tables = {} return tables +def discover_mac_by_name(c, tablename): + # again, i have no mac to test this on, sorry -eric + return discover_mac(c) + + def pokerstars_decode_table(tw): # extract the table name OR the tournament number and table name from the title # other info in title is redundant with data in the database @@ -221,6 +300,7 @@ def fulltilt_decode_table(tw): if __name__=="__main__": c = Configuration.Config() + print discover_table_by_name(c, "Catacaos") tables = discover(c) for t in tables.keys(): From 654b2e0ba920923f78ddc4a027a074d4e257d808 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 24 Oct 2008 16:30:08 -0400 Subject: [PATCH 216/262] trying to fix my repository --- pyfpdb/Hud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 6d3b1fef..36fb8e8d 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -42,7 +42,7 @@ import Configuration import Stats import Mucked import Database -import HUD_main +import HUD_main class Hud: From 0c8acc5ad46b55a25546cfcffa31cac040ad3523 Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 25 Oct 2008 14:05:01 +0800 Subject: [PATCH 217/262] Fix typo so Full Tilt default HH_path is read --- pyfpdb/GuiAutoImport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index a6a994c7..7a6313cc 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -180,7 +180,7 @@ class GuiAutoImport (threading.Thread): self.pathTiltLabel.show() self.tiltDirPath=gtk.Entry() - paths = self.config.get_default_paths("FullTilt") + paths = self.config.get_default_paths("Full Tilt") self.tiltDirPath.set_text(paths['hud-defaultPath']) self.pathHBox.pack_start(self.tiltDirPath, False, True, 0) self.tiltDirPath.show() From 81a6d5f9720a1a9e879a1213478cd789bc1a5e97 Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 25 Oct 2008 14:24:10 +0800 Subject: [PATCH 218/262] Revert label from Ray - Alpha6+ back to Alpha8+ --- pyfpdb/fpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index ab038113..50f38632 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -373,7 +373,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha6+, p124 or higher") + self.window.set_title("Free Poker DB - version: alpha8+, p137 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) From c48b2f3057170fe2886eff4316985e6a07d88e76 Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 25 Oct 2008 14:45:39 +0800 Subject: [PATCH 219/262] Remove load_default_profile function. All references to load default profile were removed when Ray merged the config files. Deletes function and all references to it. --- pyfpdb/fpdb.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 50f38632..6fb8e629 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -237,26 +237,8 @@ class fpdb: return self.item_factory.get_widget("
    ") #end def get_menu - def load_default_profile(self): - """Loads the defaut profile""" - defaultpath=os.path.expanduser("~") - if not defaultpath.endswith(os.sep):#todo: check if this is needed in *nix, if not delete it - defaultpath+=(os.sep) - - if (os.sep=="\\"):#ie. if Windows use application data folder - defaultpath=os.environ["APPDATA"]+os.sep - else:#ie. if POSIX OS prefix fpdb with a . - defaultpath+="." - defaultpath+=("fpdb"+os.sep+"default.conf") - - if os.path.exists(defaultpath): - self.load_profile(defaultpath) - else: - self.diaSetupWizard(path=defaultpath) - #end def load_default_profile - def load_profile(self): - """Loads profile from the provided path name. also see load_default_profile""" + """Loads profile from the provided path name.""" self.settings = {} if (os.sep=="/"): self.settings['os']="linuxmac" @@ -368,7 +350,6 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.db=None self.config = Configuration.Config() self.load_profile() -# self.load_default_profile() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) From 389d9361b96727ceaa3ec9baf8ab130ce33121ad Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 25 Oct 2008 14:57:29 +0800 Subject: [PATCH 220/262] Update Graph Viewer to use config file, fill Entry with player_name field from the config. --- pyfpdb/GuiGraphViewer.py | 5 ++++- pyfpdb/fpdb.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 99bade5e..7385e9fe 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -101,7 +101,7 @@ class GuiGraphViewer (threading.Thread): return line/100 #end of def getRingProfitGraph - def __init__(self, db, settings, querylist, debug=True): + def __init__(self, db, settings, querylist, config, debug=True): """Constructor for GraphViewer""" self.debug=debug #print "start of GraphViewer constructor" @@ -134,6 +134,9 @@ class GuiGraphViewer (threading.Thread): self.siteEntry.set_text("PS") self.settingsHBox.pack_start(self.siteEntry) self.siteEntry.show() + + #Note: Assumes PokerStars is in the config + self.nameEntry.set_text(config.supported_sites["PokerStars"].screen_name) self.showButton=gtk.Button("Show/Refresh") self.showButton.connect("clicked", self.showClicked, "show clicked") diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 6fb8e629..134e6ea1 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -339,7 +339,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") def tabGraphViewer(self, widget, data): """opens a graph viewer tab""" #print "start of tabGraphViewer" - new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings,self.querydict) + new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config) self.threads.append(new_gv_thread) gv_tab=new_gv_thread.get_vbox() self.add_and_display_tab(gv_tab, "Graphs") From 5abaa2f5b024706f06a3bb90f0bb467f519baeb7 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 26 Oct 2008 18:23:32 +0900 Subject: [PATCH 221/262] Added Screenshots for website. Screenshot-2-Site-HUD - Shows PS and Full tilt running at same time Screenshow-Graph-v137 - Shows the Graph viewer functioning Screenshot-Stacked-Everleaf-137 - Shows fpdb working in stacked mode on everleaf --- website/img/Screenshot-2-Site-HUD.png | Bin 0 -> 994922 bytes website/img/Screenshot-Graph-v137.png | Bin 0 -> 42998 bytes .../img/Screenshot-Stacked-Everleaf-137.jpg | Bin 0 -> 153065 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 website/img/Screenshot-2-Site-HUD.png create mode 100644 website/img/Screenshot-Graph-v137.png create mode 100644 website/img/Screenshot-Stacked-Everleaf-137.jpg diff --git a/website/img/Screenshot-2-Site-HUD.png b/website/img/Screenshot-2-Site-HUD.png new file mode 100644 index 0000000000000000000000000000000000000000..5c1283a50d21ee392f0640144303df7f5d34ef0e GIT binary patch literal 994922 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�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%0DX;kyx$bW-auqne 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ÐJL=hfygD2=In1`Zk?2{8= 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

    GD(Uicg*0+ay_io$QRt3=(P# zrLxTkVb5(h?jId|bEcop=BaMKgyfaOo!jn5s8q0m6AQUrDXa>NW#tG<`CZjJmYo&3 z+=rAMHtSmo4_@`Pm0+zwKM~R#+RQk+M(~n*-}{kQUv)pM&mJE4Z#z3THy!EOm8Xuz zYR-jcYPs_vcz&_(f4_Uh^U@xN*Q&qZpZmFg*Js`Ge8JAi1$9)3x;|LDpglXgmP2>nI^=`wt9wd-eAKUm{2Il2`Sy?y}YEgI2z2E9&jcce_t#1z3Q3>bg!uW+0b- z?lO(I87uDsb)9KjPq)(oY`edl#s!|c90XZ>icQ3N)6eTab>$^rbFV8`+MM{*jkkQ_ zBOkl|hCle!nHx`SM7!$v(M!fv?_i2##0D|Xl1YkWw%p{PymW7M=IFecy$Q-b)G}Ic zo-NWtEPrA~3XRALz=~pmaHOYIK&@u^mbP>wl`sRv0o3(5oxLf8WQXZ009DM!Aj6O(MJx{EF)nDl|Ay58&Zq; zr;I`&x>wnI!3P0HCbB#aEOe9AZ2Jthond|gToY=W+k*H*f~9e z@`V+Wn4t!ZHMJB}aml24HnDy!FdI^`>TRs(V&OEiDP>7?7H&Y8G0{O;1>Oji6r_%+ z=x7RKmXhg_QRllS)EP6gGQ>tpEjHKyjL{V+fe^xIP(?2zzZe~M%=f?jzuxq|H$Uhp zPr2!)n?5rC)`zdZ;VbWV|EnMTnBIrBamfLY(XC>R^^Na*^ZP#h!9Te1gGX+=<+95z z`>o&pPp3uQr@iZq?|8?*{+*Y;_@BJ!#V=Z~*Drth%U}HB7r*LNuX@T;p7Q8NKl<8h zuLba-4}It-fAS}v`qZbs>s{}9-t(UKq8Ghr7>1Ys=*xfT*+0}haKOD`C1-`zXF*v7 zzilf&mx~QBGX>z){!cAMg0UQW66k;|TQhe_iF6T$gl&-l%JNz!1qEqdm1xy42PgzA zErozmmSDHwCKi*aLv>2aER#JEOEqOU>W$UQfRZ{fDakE2>8_}7q$5c%Or~HIvVc^@ zUZY+n*k{l!Bh9+m3O4~~2o9(vyShRVj)?7YOjS(wT2iGYIc41dW_d(uDY)q3jC3QD zVpq;HF-+ys?Zlo8Fqd6XBa}zsfGRu*MA-z>d#?jI7uYA$w$-VmD-yDBQc8IzCMtLw zMuBF?H9eIb-IS6=(J~g`u0V9ELPE7#bz$j&a=4fRT3O+kCiVcsoVHHTvO{q>RO)^Y zk&<;8QEM#g8l#Ag6wu1~RSPL;P8EBkRbyhB)!IZsQjkU~KUykfl9@}bQqt+U^btbo zAd>2<4^?QTEK^Evh*;vr%95c2X$FOQ^VRhcPe{^)8hyAlU<&&Uedt71hhH}@aKy0N$z`?qXZe4a+y@Og3 zuR{G7lql3rR$*_Uu(j}zO1CR(ZKPUg({OgJIWM}$y&itQ`#xkqoHy>T1&zH8ox59S z?gpa~W>uIAfZi?Znod9OrtynzfBWxo=lMlc>nH?wn|!s_$C1uaR}EaR5ETV5pKjN&!fan=11IqWcwL7=CKdm@4>IX;lxva`KMrh{oOx&)t+v;boGYY@3?87dHs0L zPrUzIapjp~U-N^nDB?T_ePfNYs z@_dgG`=jp*)9BcRVHb+UA;3j7*)3|_IAKSC@tp{`^Tc62Y6LZ{2cIyH2g2gk9(V{K zQ|gwmsUv9bLaNzR=G^rpfLLKY4nskzcXr?DyrR15@EaEhPr+dq`*102Q*HTaS2E5G z#tu9<-G_6xIe3NX5RzgXhHdnAuJ>?P9cTYA!|VUZW9|jUS=%mMo4U{5v|_z|2EnfU zclC9v*+Il1!448KQ!Q^6u3yKhvo2^&y@hCfzq%Cp6}l}>*$uSWydOu9P+D^ASF2-P z?rCLq`s^8ytu>UNE3>?2-uEQ4g`|2XP0_jU4u;KU<6MfOGqYOx4mNI05%Xp<@9%f> z=Bw^@(VxEdUKe}r-+Jqb8~)&iPkiDJZaw|+6YH~cTzdT2JubWC=+V97V}Hys=IrXS zHRkN30ohuc=h59KuFrD4&bH~*A%(R%w(P8H0rp5H!#hQbv@jHnu}UE$O_FtGAOoO| zS>0?=(HmGxnL&_4o;0_n6zFBgNmaE}R#CiYNSdVx!4htYU^p@v0JlI$zuOF%fg4c) z@0yz>D9VYhYMUzC#oQ1H8elNDgq#K{j0{<`Y9LU^;n@jZ;W=f1xZ0jLQi$LY^!x^wbYl>=XCeE?K#){-go53 zJJ;HKpYFC=k|j%(UO#s2z4qF3t-aQq-<;q0#u$$oR7F*i47oVDS8XB*M^cuSlxP52 zR_@6VOz(|3$OWaZdFniAj`Hdqqy#sLR(mNGioOmWR-VNKT-DB&3kVB_H-g4YbyF9j z^$b%w5!I*VRcja&*(_RAlbwUu1q3$0Jdu)(FgNB zxMiqFtBHC)fW;scSGAkWlYIq`W*KSPe(V>t2o3BM)9Dg2=_VxAz%D4zEFIR7j$80-NA5W$}EvhsCvYJ+kRbZhvJ1|@H!|89U~CE?1u!wPFcdz z$TZSS0+>xqG$|ypF=QrKXuBQc!C}zu&putO_sWDxZ4TeiStzf2vky#23 z!?I$@Qql%63<+)uC4s_)I?G0^Xhc^uk}3eGMcKq@o(fval)({zD`~cbEFB?;^0daP zxhwNlM#6oqU+XZ4gLT3qqtjd=w(5l|5|I-|EkcG@Q^Q#6Ok&b4V`TO8bEiz1)2Xm< zxx$bsGc8r?KzG7?Pb%?HFqoF+-~=oH<&d1}@XoM6AYmx{x(g`jSWSWV{=UfI@02p%js^I7}YI=w&D zFg8KsG#B=9+>u*O(7wj*gabP@JJix0`n=Y6XFE<|TR%P+?kXxcZ*>w_^}O9ji1|c} z_$mdlz18llHg~Ymo!o!ci+A$%6&-eKaPl{NXBsaa)VhPc4gl}F@3r^7ZLo$0zcNMa z{qKEry8M|(j^6m*XFm1#@1OqYJMRJT$ne4wUlg$Wfq(g-dme1|@Jk>6$Ndlg=(B(R zpZN!#yY+ki_#gj)ANYYE{Ll~9i@fVn`-z|Yz90QVfAKs2^dJ2LAH3%kUg5OeZSf5* z7@T(1VUK)0UD0?(OggKn9kjVJ!JRh5mHBkS8jL;N^SGUJZ`nJ+vz~syiT2H`ro8=u zwjE2FU-Rsz{&20NoTrU|qj5SQr)$L6a=XUfrwQBbc8$O%PA?~Hx7&kLZO!9j8nImm zrIl!LFu0w_<*3AdY5l8Swz)ft z+f`_HEJW^RziRpVdG?$6JZn}0n;C5wzqCQp2g57HV5`H<-CAn^qN0(5iAeQv97!G@ zAA94uql-jF$P>>g?}+pz1#TUKV>1ly!$y3EuKV=t7mbUwIXW_L-k8gd#=+fvE$?cr zNwQXP*A7Y9L7KUlMTD8HPK&Znrku8ZFBSVVGea3Ykm}14yIr3q@5?(Lxchx?d-SeB z$5)@b^3gUbv_v6(!b$S`zxQ3wc@wAQ1qIknM{!k za!AUm0bLT##CDah1Qh&I(Q@Zz*l0EP=Pqu%aWJj#>{Y8>3(Ri zNbS4JtUQV}=Zw`RV(wyAfpL*ZA$LnbRw$J;Ae4lQ9nSUciWH*=qa_JR>{J1nyGjMT z_`S5hjjy=F?#|z5LAA$j>ElNcu|3-U;0NCKkALCQl@C5BbZ3?a%zDq?`@Zk~(1$+s zlRx>BzwiscIQ-BK%sapBB7cz)@sW>wsYRp?o*`ZTDqycd08#@d58df&A>`4zE%7B zI;o^nt(gKLrQS6!I-aq^#Rnhx&bPngcbwmDkFPv)+j+k?=Y_2w4Q*`0wza{1aB}5b zhyvivWR=x0a|h(0j8>IxLPB4?M)oyYE&j4L`x+m7yI?01jm?^+#jYz78R)yJAqIB1 za4+tEGoJpnZ%m)v|KD$S-iLeNi23G8Vokl4{$FvDIi8Jrji;<=^I6kQO)qapYTl;w zor1mAq<21Of*4Du+n!bxHpe5lpBitmYE%Ts?LOAGEiT?Vjh{Ed#O-OdXd?DuKw-j! zEysQ6^(5uG(lckE?IV71VzS$UuM!I$>zz2rg1;d)HoQ!mJM|TZ`-?I$;dB)C>xxGh zzx|zW{9}LcHX7_3|JWbYLyx`ojg;iD+kSUh!fGga_HKNeTP3BD-MtCY&tc(oG!Ez4ng3Tts=}tpO{V%5A)u@ zS>w&PKJPT21XQOJ{OdH1cg+AX&1<{d3aL058^<$i24|KWMgY4t(tF@boO-}?W)z)P z)T_1Hchr!;SupeV%>&^!g>wv+4A~ux)gA`_gY$33jjW_HqT~u2;@g zLgqB1V7~ET7<}X~4Cju{MdbBs*MfXe_}n7jMGwSa7dG59HFs;nL%^8F<9zuGUz=_0icN3H{t+001BWNklTrxD7uRMJ!65NM>-s|Tk; zl&tb(8R6Wq%v(mt_jyK;t)f6+KyFx=8@Wpab`oV#={~biY`C&4DyUVcPYJNKTgmt4+hYDhO*t zb*!fhrcxi39$95&0a*%`DU)`jrJU?iP-Va?K%pC&tQwKJ%vAM8Gy|xmTRH?VG8_ZN zu*zbl5opMW1jzu&Ag!zD92i*s9U6h2<9`DdQIa^>mWa&33(CV|Q3)Oj$C) z)gxDJ1hs5oyin z<_6ly0o(XWt%iv*M>g4FSTe^IUFxrCb<}0GZ)e{auW}d3`iemJLwNhZ9m}@Vl zFjQ9?ElTDt_AY4xlrZX`S<32tt&Cr;IfVt zk;+sL%?obrn7s4hH~sc^zWx2@x96rSPoF*S_ni?JwtjxoHf76 zoQLdf*5weHEM9EBtgz*q@v`>IdJQk5#P(i7>sn$$U_Qp~80KrejyWFx9z6SLWPkm- z{TuG~`1b(7ZdwmSC8k>dmNQ(!&HBx>nYXuTRNK`U!tKdqbsDjqM*tJ%?KB?x-4C9W zZOyS?Ch%>i@sy}HpHkoUTFwf7)pnX!VQSy5wXbxW_PW4!OJJOAZRjdT)E2}RQ&<`C zh_%BGaWpQ+r}c&P`*FrztvgJ^%X0@{3aetf$^a7EH8uwz&cVMtNX>*VX~}!J+wQcA zhgba7D=TZ$*J|9k^DqC{ZGzncfBDD0?5*UN9Vk8s!uMXdFkSx4J&%9n-G2h_SpK_P znI&I-|94H9`6GYj`OkdrfB*Y`|F8c~KlOjM@Axc$fBTPp_h0`Tf9;q5&fmRVB7K$H z9y;-jYh}{3(i_$nywAg)&W#?_wC(mpN0VmM{in=y>l@<7Yk}i-{r)tPqC&Ckt^wd^-W-qMh%F7% zp&0Je2Rm=I#dex1AzLr*uzs*l!fxkUIT*olI}yCRitb5hHk$O$nk!EM=jZa^|No%`w zRSJ`E=l0zBUF3=6c;?+a&%1FPGnKtx3AH63PNov1S(x{Yw_(`aXB@S=Bd&b< zxKFsFD}A$Rqmi-7ANyC(TC28kR1rBzz}-O$W%O>WxK|1ohoOj$)m2v&QrUa#c6~S5 zvfSJJJ05xD-EVsE4v*uj&t3lPuRis}uRQV8bDwgl^eHaW@M_D zX92BoK`L$FXqE~Hw&?zAI)157Bh}iijm$-C>%%snc8TJN^e5C_y~ z^9LB;m`YhF!?AY5md9g&2U>L2#sEYNKC&g{gN`$8i zfuy`zxd5ixdE5Q2z`yXzKX>`d>actB2QYt28jK2!dHws2^%S0c_Sw%YKlA3NpZzUw z{g!vX_fEoOQFxl$Jajj&;G@){y)F^)RRxW>Dr@jd&A?$kMA%#*w-G$YUdYAWth zONp5Tas%7J66(N8F)`9q^&7-!P-wV0LX@uHsfdcdqIX4(I*Nu(@+H4*lFq{oJ+bO2o7sbGv~%Hoh{5 z*HIr>P0h9RA_h;g1hog%&QrJ-a(^_{^e!c72`ydog1K*eeDJMr{qDye`)%i&-@5Y5 z+4KG~6F5Km#f@#uH|7J|VBTmnx*`N+8K(EOdWM&=xxNlc*;n&Kukmlc+^4u*uv?Zj zxTX}>0*hnZdI{T$Ya80l9WLIBw|@Y?{C~WD-TsYrd-J>T;M;Kh^4{8FSY}Mu8qw{< zKenHFZm?=_XZ&$z6t~seGSAx)6Hx+OZKrG_>yI-I)8p+}MmKE7`GixkEsn(691rX# zx>Z)Sogr){n5LXYocMyKwN7h`)6B~Kb_xqSV}#$@`ubuj4_lbmf<+wkxPw%&PP+XV z^9Z1vLj{jv9Y(K4()@MRxb2rV+U?zTulV)V?)+NZUX2F3J&T)o&r2`;`j3D4 zmN#F87`6gC?zT6A`#=2Q5B0XIDIqvH<%v&L>#KCP(j5vFgjlxU7c51er#}m~XP#H(J1)1O) z{KhRz-1^~1)d4tR+-@=DF?L4>8=Y23?%;6SVq3x`Pd?7+WSGKk8tbvCamSM?#R=L@ z`zFnG9b$rzwOGKi$W>?d(>%-RN#zyc%s zt=G@9-_++>wM=$Jr*pxVUfm&yRYOXY^Jt&pM9p%u*Z1yDab)<^a`WcR z`|i7sV0Q1eTt>&dWFqnqTfu17TD!N|MeBFZ*FSk2m-`ai^8s)eMA|U)-b;pa_o||c zh@rk0LIJ>NSj%O~P>1YmL#|aZwlaE;rN=b2*uC-Mx$k=G8{c~G#Zk+x%TGW5xlcaz zsbBrn7oL0K`CFgAwwrs7jPsQGE@L+|Ts(K~!qMOaW3d=>nN8BNi`YoncT;A|zHCw# zZsh}4bKXeGxPapgU66bB7|?cREqhQjH)wUfNJ@#eTGKkL6v_FW-Q8BQCdi5}=lDnKu3A&ku}cR{V4 z{N?XZWD4dqR&5gCfRTH5l1eNN%7Gc-)}U@^o(Wr#8?17hn*I>B5;Emjo5LeZN$Ve_<}dd7_3Mir09lGqRL5W<*25_2#y!qLVxy7~pwTT-L}Vyr z%~~>mZm|kavW8^&o06h07AD%tqBJF7O3~IjI}<>d6r_S{%VVyJaJfR(vM4hkab%x) z{L}4wH+WL7vBR!?@8-$Jp47#CU6dn_@)zImOZM9wpUKzQVaIRhCm#Og1iKkcQsw3l zV$$4FVP<99aZ5LrvsEb~OkRA6G-}^|#Z+!!QkLR|w5G}q3bwM78>2HVs>jXMkezh6 zi0Iv#(7D1L(uWhFtysBs75H0#>^P9&9flNL5_mfYp{Y;{)n)LWDwdk&Nk;RWTo~C+)HFVp%30#k3f&z)PV?PsvjROJu+}~+j)N&w|8yEQRmL>E?haj zd_7;9m%iPgZ!|dESF0VWL~&K^(1GfL%=K`uWuG%hvHB`06Peltc8li4g6A&Y^R~Br z>-RqJ;A5NYH!pwT^m+fOYr9F(`AxfXw9(tfhQZoEcbdZhYh)>jFdu*t&{a!`PWo+Y zoUgbtdK=pH>uLtZj+rsUFT}13%k5mYe=pENCV>nv-l_9+N4Bq^1DDW%)wAw{q zzy0Rl9(_07`8)B_Gl*rKyPD(Xij&=H%pJLwfHPY1Pbhs{uKk-bA|noqnW z(}?Y=*44~&su^$D@#b+0Y%x#VM`EY(3!YjRO^0y8tk|I{Lg>< zJHF%Fe*WiQ7ySz z{(<)%0r)3B^;4&R`+xuJ$G-cwzbYBy>w}E_^UZOI zdl0**32O*_$NliKJu87ttMIm81eni=VFhT}j;JNX309H#lQoIGDR|m%Z97hN(YXbt z`Lw>4IvEUs+e$wxiaVbSYiIu7@nvEA8NhZ2V6+V^(KgL@JzhW0elwqEk8k98s#|58 zti6T~J1<<{`c*hUCA8LnuYyVf3FOk}3P6}yYpql~Gh3F$&CSs^Lx9gqn3f*VGna@S zd8a^fio zvnm%+sxM0}y+@DT%--+X_&eYDhIhT;{s%6Mm~UKr=BcZn|Ku~Dd*Uv%#2tyJIa#1 zg9e#eFiFi-(uM9uMG)#R86vfb1SmQ^t+1_BRDV(e&SdOzonwY2$st+k&V?x;1eXt% zh-x4!=3q$G`6q`2cc_EG0SW~McSbLm^6ICe(4poPT8%JS?8K!S{nsLMV*t!zI2yBu z43_APp5YZnHN`+ICMF;T%3LGKWOR0qGNmAy4XAm3f|beKDI4ph0fQm6fI!synet#S zo1H{hJq^2PtOR8OKw4*%h0LK64JuYvpDsuQlMstGU?RYZoY*Vzdi8Vd1dp0L20Y1OxSZr&+!A`o3G?k_vtyA8eXZxF0I=GzQBrpygNvmUZ8!AKRMl`t?^Dx$|K+esjN2ej(X106;a)eW`_4lGoS;HHEl zQd~%qkt#_a%pI)ufQ?p8pR$p%NVpR)xu`A+Vqp*rm;^7!Um;@31ZaZ6;6VaC(S#&G zm`P-&;9#ts{|xxH+@_F=^716No>p4vh6te17KBMOkaQ{Ok)nd6i(Z^-Fv%nZhElSP zp{=E$Xy9BKbt8ct>o{542tbCV6B{Am4q0Y8p=_|AnQ}-iX5B!_nM#GVWbBd-ks6Ga zu%w_yN$jf5HHb_?9epj3lR!&^)%ZkOfL?f8mIPc)bf{Rp*O8dQ+Q14kMPIF4-K|NX z0{vLwsjVku-a;l%E`T|z>JBLy%|%M0hCl}vu-0i>rV~ZyNYQ{aq1tZgl!1eE_bt5T z`F{VkWvnJeu15wNqDPNzKDeUyyg5&E;Km#`r)h}}C9>a~GKA&)xt=<6-al%0jN`bu zG;Z#AalUeGdbY>$&|}1r zi%-8ko%l_ZYjDRUe9L?Bz+-sfX)HS&c9*un3s1qbAmT92X5*F&$^ZQOfB&DWd%o}Y z|G&TUedj;&r`}dS{(%qP^CN#Hf9=!HzxTfLfBG%oa_hIe6}NzMCtLW}Klpo|fByO3 z+|TGMehsX|_t}qFnmLd2U^rWBcUrC6_FU;JzIWC2T$?1%Dtf2??toFQr=EwSEpn}K zT#thF&rdY*aRyKb+EsX40AF>+gCM9AOawGd*pAc0?e=7)U>Y@ICbp&4)w%+=I5E?0 zj#uErcAOzhs4mOtfTo?cdtL-)l*%^m#j+7`ZpM5n;<^^wl}LGF0^IMY<6#+N0M z=go0xh~sIL#CG35xjE7G=Cc~*dN2VcVaSNXA zG~@<-kdHumgrZ9bHFyP2dt#u%Fi=cOVqs=rE!+kOuSysfyxO#?o86*?FHj_{TFHc@ zTq?I+1^SA=mr#e1xf49ClBrNAoTfqMs4A=MG*`l4$uuG9W~5w{NidfTFqKg)6A*i* zwL*~|y3#7>l_+u~VJ^)w6SVKwgIMKUDN75A;Y37tODe;9;a}GPAiESmsG@M);pW<{ z9-oS^21JLF`%b2nWb55}W*CdyTJ8Qxu{S8gt4WNpOP8lonYICqbco(6S;(wz!ic1@ zb*}k6l~U0r*Y+!tqH-x^jU@_s4(X9u)BlW+h2(~=N(N^nL+R#Ot$@s`P6|*o zQ+S#zVc5YSIIJ_v6I*7f02Y>C%pF}+<+GiFOfWEs6qsvNRVhTO*@f@a`M1BY`>Y@T z+R^TbOivNA|7~YdhqGIXToMDz~r!w=A|rJ1vZC%?(KKrQH{?H{b^sG-Np4Oju!xa zH2B5s=An&0obA4G%`Atsn7P!%3?LYtvu5Qp(yqeTLcr9LA+Nw|g8|u6X{iWR zkW!5+6T%G4t^j=K43pLH+g&6)Vb<1=eJUNuq}2eT0GntDH*_^2l4|tEL?UPgbUaE7*GCbC*TYYZ5`6q95mO|l|NcrZ7F zQ&9pR>7rqt%fl!2$(BFYSq~ZbRz}!%!IKi6XS|@ zfPo8@ur$>0=G*`XbK%0xeCYD={a?5-+}It*`sqp8hQZZ(^kuh*y=_J_o91O&Fei5s zh`}NRMBJFBYiO6UEt$8S_m7S)Y`1q_%sa15mtHu&eC_zz-7*bBZajx(&FNL=w}xbO zBHQgLXgG9oiY3)8p&nYgcZ~Eq9(?HA-uURZ-*^8Tx77UN?dSb3Uf)fEpWoQs+jedo zwrxBbhK;wut=@S%BwkB*D$G$`^8~L!cRz&Pgv*~rRvpVazd#qReu4Kij5dhooVS`g$5TB@?i*-uVf zE!%l>jNO81gUvB^N8{@A+5!`H9I>637l7@!a8Fk}#*SD4i!=7+?(<%NIAma7vF&Wl z#%{-&(gT>9VVbcWw_5XS@hU(^=*FnTI6AOzxH4M_>cT00Jw$U_q#9ryZ_eX zryup6@45(F{E5HwBY)vp{@E}5>S?v^cYNRjSFc|E9Uu6>ZMM7Hm8!4h$n_du!z&-> zXAA63Yj)$Zif+|vN1SMFTR+x9Bun4h5P< z9H$w~Y_r*jtShu-Sq=z@aU9Le-2fV>!8>?`d3=3Vx8=q%cipk;7kB+)UoK=GUA*h! zcyxaBhb|7!9mnTx-T1;S?tH7(y1BKDj2&#lzK2Mk001BWNklL?Fk+FYk_Q=(Caqqyj9yV?M{ekW3=hIhl4}ZURLG1G@HA%#NYXY+gUgs{kb)Ui{he?@ zj-E;>%$YIJxwdGIo<3|mQ=NQKPSaAUcUD$E-MESZrEnoJ6Ihz1vX$c+dB2n!`)YzZMl zkc(CxDvMMl#RN!nGKCHqELhCby)Q-r)rBA&NpY2fsV=3_lpzES@Fv~a&;8aodiSm< zAhgIN;3YfJ5h)7Y@|O|_?MU2bTG>aAmEA_O`a2^$PcRLD38 zB0J5bm5#917#5_L20$j;7p$Sk&Qi%6EbFER!cE9EC0L~bVJTR>ds22<2ONaMW97Q` zC<-hIvG^FQqt~jNtrEMvjxR9bZkY;E0X0z!(imk<>kKzQ^@T~9DbnaDXXl#qh1le@ zEMkL@q9tVMa9M(65`gUxq-~Lw)@fexZKTPPDG_2b5A^`MLGTetP*vVY>#KAHgCYTI z?%}ZVoh6tm^EMnw^T?H2nkBT!86+arxR$oj%$gKH(t5ph5MXJtP|%{P5)$<(OWcqh z(Tqz#E6#w8BolxuL=f7Uu_#4r86XInn1~MZPLw3uQ;=+J2<))7XO$Wwi4G@ZD8uui zf}cwg6zP`AvRWexJP~lKKS`vEg$bsBwe=1Ot0$qa4!Rocp#;0+@C+5;1f(HBcAz7> znM=LPX(*zpd!m>dkv0sCf`gC^9p~nJ@WpuK>Ek=D%}#c~NSZ~@?CBf0S+rs4kx~Tb zz_D;+UUu0dI;nw>;acdK<9;*sQ1_+l<>&o7#-sB`58QLt{V(2pjY+KX(3g`XHw|Xb+ z;@0op4(Eq94x6zJqqmK_mkAQE)h^c~%vlHiiT zaL2VPxc)rfagG;=^Ozmj5w@%Lx*5Rsz;!)LL0?`ezujwgr)?vp%57%su-oh%GKW9h za*J^)FWx$-Jxp{Dioq#E-uK&)@pa z-};_&z$cDh`YbMe3(nz>eE9L(9`zmHt{?gP_>=$Dk3Dg8_uqWvssDEWi%XX-UA=l0 zz(4)zpT2bI(yMvg=C9?a^BP~ns}K6))aPt6T1|56kv>mboc8~2jseVh;`6OXu5Eh2sle?1E!wQ2UOD4{@fAn8@eE30jrU`> z%-Ea)L{tUd8IAA2m#9}WziewS8%0m-c({%8)36LSY|Yg?KbOV`G zGkXmyv9aIOI*}=hk{QW{eHBZDlPVm7yB|8{+8DcT}GU{c;|5L!X1t0&JXvDdYd*c1fHJe=NBIDc666{Mx+oG zzA_a652BPzWCl}+eqqxdzU#u9?z!`UJ1^XKZn$u?;cl8%g>jcp)H>T+|>?qUa+V((Js z&_JYmZePedl(V=5F~AonDF;%GAy^f{1t=lX2v0iDGufnWGKd)}R-G0R zi7+`NMw1zgwQwgI(oV#vR7nY>2f`%Fp0qH!l8#gt=$R=coHkVLU*ry;6Mf~hLTy2i zbSkP`oL>G0IU%}M>_sQ4``gz%6J%>qxeOEsfm8rX(#>3{lo3Hf(ZTGIu;ylIAV+!? z8K$~uhUN%MHkQ{ZmI`d7+}HvM!Lz`Yq`SgaE18QL5&>8F`D9oiA|yj|Gi}8UB$)yXmPS*M*+pOJqp6i9>r@kVMrA^*cE6I1iq`>f{Xe0kt91H|Bvrcx zJyT?)nsHTTlS~?v$d;&QQ@0KV?BM6-YWQq?D`yYxiT->YG*Icts9mKrk~`(k!*26p$#Su8>A&L>e+dqJxbh zWK=|hi4{PEkzG=nqgbbqlU2Ge(q#!HzyMs8G0e&rR|OmosbtGc!BP^rG>P@ ze-a83hyx^5fl$RIva50-fWyreB`kquN!>@?eY($OpS|Z?^LyWsAMady?{m5(wc2f` z9c%qKwf5O-&vl#goAVpr7~=s*MaASRgNd-MiH{cG#v)BY3{*KI!h|XSwd4vJoXTz{ z1Enk~PHhsKZq&m)&c~~0M5>9t?!V-n?u>=yi5>2+xNLYPu1H!=|pxm3Y=jNCu;p#~O zW2DT}62e{?1)~+3lWd_hV9a!yQXO!@!_gFyZy=EBmb6p`tUgF8M7dc(Jyecn1c1FU zGg96OhZ!^^g^cD6i9nb+qJ!)j%88tiBS|F8nROg7DVi)0NdgK}69Gg@W)WS4U~)IG z!w2_-tvRlE;_mvc&pf#H!h=5dPDIMx%93rZ2qoP2=w16|-kU#I`n@^MGJ7PLe)y_h z#Be5kcJ52)xyZTa+1_7!-oMs%yPNMgI(ll2_b>gy-P0Ezp1quXaqC2w$tYvNB>+_Z zzYNQka>)~e57(c3`hCy5=2c|Y|pDMy~~E*t>^IqmhrRZ_BzmsDn{9w|BU9eX8Gbj#f@iy zM>5R-kR7KBPG9-TRsH{m{;JnET-^Q?PMA-&JT_b>wi(3um{NvEHLBOtEzM&N|5%-vs_JtP0Qnx+XKn*K!z1&p#ZAFWe@#@h(lmE7mQ*CFwdKt z=5eFhMZf+dQ|R>^zSZW&6~S@xir`>_wjGm6f}SS zlm8Rm`*q*_+kX4+_+5YG*x1LYa`5Z@R*sYl3Xxin4{&4X~;4#nUc0ezKy^(nMg?xMgYd*Xx z+k&){RG?VwPzU(T-Na=S_Qks0a`&{OjXNTzrzSj z%Y5OyyBK+{NN;;y&`p-{9Q*OHfpNjK`y#u?s4J@xQ|WQ9xAKAA^oXK&Xd5svaYSA> zKHc<{^X$*S^DIq1y6qa8(M;5Q{-wtgAvbyh4^b3yTQsGrs187;ySoqLR#g%)j$<3H zpPilEzI{6)Zrr#&j-wYB)X=PT#JWa|(mPHbW-PCspPl!k1!rq6Yc4%*o}V3Gzd0UX zv)%EvVfW;*zjqkEPP_X(PrF{7`>XqSuwTzYNp=Y!9TIlt23#A5YrE;`Ye&!CymsSw zdh+<_WPoQP_WL`poW1h-doMo!^7B9O+1oFD_SJ`x?k4Z8_$(Fwsl>{ad_u5 z&z_&3-P8FKnn7f)56{o_tuuGr**7auu#q#m<;$5lB}i9nBS6#T_Ee4MG>V%EeUBI4V<0l0c`G;N>ETHJp}V zEX1L8C9GRjtsqTGnV@?4QhK&5i(&-4RH1YeauCQKux3IqNOPzMjl~2JV4#Ajo=ztl z%2)`fy2Z?$khx1LA%+8)&>r4UCFovpzH~*=mVpkl;)ftv=b^+3B6=8`f=mwzTZb0H zL5R6eYZTlOLIVo2<%3&x_=TQCr8NurNtS(Rf#n#`={whNMUBIwQ(WXYaX zv6)3!Dl>$%9O2fHhDKSKIVBn*g=Q@iLRfiy)REj93XxnK4blthDRN(o2v$Rsi7^Q4 zU<|SJS)^dC{-E~YNDPLc5f)7IG%?eRy~^iHY&2Cmx!yw+DGH^aq9+@1c-gf8UiEVh zW>=aVmZ^GkTZR@MP-A8|86YFf9W0q9GR>%z!yGBw5XLS^8*RkJPEZ^uK(rJLsZ6*) zzc7kAEG^M1HJN2COoxdG7{x>e-I%rUVh_tn!OSetsdPkA2}3r|QRTi>@o)_YssRE! zF>XkQVws9%KIA5I7WlL}GKE6}rg0UEZ&N|V1R)I6*58&rV3h^kQwH0MWUfgV4cSbT zb)?`#rYN#OX%-4p0~~D6l3>Ydhcif)#;v|V8lY5z3CK>CergE{f$Xrhy|wJZHb~^K z?GIT7_Kt87WPrv3;*{#UiQx4=1c5XHrd-`KvMZgkw5m_^I>wcP=(LQK!HpRM8%ww^ z@=mq#5l9`3(N#q$GTjWhg0`Bnv`9i`G?`3IQTE-2OeoO}O_oxrb%abCJRR`rUzB-( zZPTJG=?Po0TXFM#y!SH?-*NllwCF$vV(;k(?WHxRHz|Zziv#Yi1`D!|%}fLCvYnbk z+5<24*hkJIAND+MeBJZ@^ExN4*KSSI&NI;W`UAnblvheYGB*Z>^d<$npNaOm*AeVg^y^;ZM=kfj$ChXJ@bXXK zsrMq+FO~Fgz+T%0_?6pRG-pU;e5tRX;ofcBdjbF4F|ONi;;T8#V7Mef&X+yuSH-p6 zm4CP27LT=TFZ-iq(r(_SxYINVGiK~CE%SUKd@S>RhY=GN>`;?!%!e|&xtgbTGnPp^ zEST%BHX%ANb=h|Nig!y1W1RAN=<9rCxa_LhGm|JVQhWxR$ruHRkNj^<{&6 z?`~(y0?ad}X{vy{W&j6M;aZ(^6JFE5iJ2R2B1>yyElwQjGOtQ?({92tW0`h`M;>km z((9(*RkU_LUu8$^faU7Lu7qT->Uxh2s>{_?kPH3Lusa6KvRl8t!_OuDkFT6(f99QM z{is2TW%ZRoQLyI4@A^`Qvn26Cx2vt1T@8a&V;wVl?{$B_-$$;!_i-GDVTkCNX=bzw z-H*UGM7M6YqKV^5FJ}FkxBeS5NO>Ke=&o ze0=1iJDmoFK^!xCcbj9Sx1O;Q2B+`)@rtL;GACsN45zeErb2^f$~`nTt*}r=&%0R?B^E7Q1v~%am~?7pVau8LR0Xp~x_pHL1a} znwyA#2Qw2+Z%9j1%M2BhAW+6E8J!HP?nYG}8LHoiOu?mevP*7EMzzh!V6;@4DF``S z2*CFa3j+hFL}xcycWbJ8JBc(zu+nu7>Jd0Wbpo2X3xRH7fpnqRu<&Zor=oJzsxT}G zWXc@jw6p{)GK&-$N~EnN8i*i5QLq^#g6v=zNO)E;U`c>w6RjpdgNk61Es;ip_Yxyy zKpMM9Qx5R7?3pGS4VgP*kNP+hL_|%Dx5hbUS^*hM3brIX0@W$VBqGva#OA%RFt@&k zoI|QN$C~Df6lEpMWibM{2_?40x@oz!bE=BMl2v_nKu|Cvk-o8EnG|vlwJiP4qQ*os z54n4&6AhL~gltHpM~Y1qYh)S_Fbj4kgb+86Ssk)QDP#kK9cf;&=4x5)IWaO_3?<=) zqHDkq*-Io82Y`uA2$qRtSzTQ!(J@$NwqUmEyik%#CE+BH8-rGr&WD>35%OR*=%TGM zipNp1g{i^T@<5q7FbJcx&_sD|aaoywEIHDv%`H8cS$5Autk_V&U2dj5q6s}qQ>HN~ z?uE>GOfloeOkXEoC(YV1Y~)Va1G~KK`GmD+RxncgweJ*q;U|?F((BF6Vyw9Cj7B zkO7fZ@mIVO8!TV^Bu;K(nDE6#&8wHW_g}{6e)KDs|8K$V-CvIzPrt!k34OusALFg2 zcQ)K~J23>X$F5x|U7D87^f6zV^j^6={o^O^`Q)>YZTsHWe13V~Lre!J-8>aB0y7U) zcl+HmR_d0Rfbza=dfYUX&vnK;EhzXbcHr)c&kmDz7v>iLa|PvyNi$TM-h^cWQIbR) zqllI>G2t>}TH<;M#%on3L&9qrI%iOUF#nv-~a)5Yj1DxHN1f@ z_jRza6*#>R-F63c?1Cn78J=}u*_G?9_LGwHE?Va<^5YkK{#75}e_4 zy-SI3zI<)XJ8Tl-Tu|vt ztKI&pwmx4_VXr8i^K#|xGVONrqfnG>kenaU<_@gbE!3H$ET+cDiv|KekBPy%r#EFKQ`TH{l@X~>e>9t{P1jlf1j_;{np&C zkMr?x|75yzL*RPM$wdvZ8-SM@f>FBuG6GtvOz0usB0H!yQeSQn{VHrA1tv4_Nn_l7U8;cJf2$fc|HfS2gcEcljF~y zA6;8dK}a}#c=qC}FOR!jKe>4{jDrDC4{7baFWGya_v$^eLrF5CXJkCo@qWD7r*$3n zH-@}!N>gM`Kr~lI>u$(}iG+EEAV!w9qgYrq?SP!vn$|mLfR{xFGI5V`70GCs8-p~% zid;I7ghG}Z<^diCs;9Zh5|9XH(x^GptUBO8OY2Rp6vE9To23y`*dAcXGf7Bc48fr_ zk#0g59Ri_XLRFGQD~c(7sUom|GvQE2Hznz<6V|FI-ypCHA&_!u6%tAs5v6j#=&5wJ zRI9I~BOA{%8!QnP9twBw21yN-Q|&2Bqa*fWqnbkI%+=IZ3Mxwtfea!;Y-T{OlDKL( z15Gd{nxQ9*se$MjfYmI#n7^B+Esaz(c@m0*Su1-Fkyf3q5u#O#o3f{+!;?TlCPu&- zGsR6=5enJOZf zGPE*#601>3k;5kIBWVdZW1nD?i3DtG){0VaLuevl4U*WYo**%za+NK$0^D!*K9#&^ zndwNe5t&BEYDt-~_J0@>Y0i|RXAll5WkYiH%%p7}kq5JUK327AD!~pysy-=Cq$v*R z@~!3GJ7o!4qT;dTMXm3P&0`R(n=ABIw=G;kh>aPnZ+AUl_uPfohFwITwMVo zJLE+&VnWTj27w*Jf|GN+=cVe&)%)eLD=fM#aa`?^QxvbgWo><{;G+I5z8 zw)T15uf6!N0Ps1Cr{*SAFtUm^&xO5k+T&)|Y0o!u-sdpdG$!5LY3<^?e^|z3=8_M; z^m)INIhfrT{Mycs+;=_<=7SBR`(PD%HS??9x_Sb0et`?8IyT>?#PVkbL;?;k9emNS>aaUcY{w zBP%FTCqr?;`p9^UuHg-R}l)ap!ZN z``mlp^Bw@7{p@Ei?p*xpUGI9=#T`k{KmYtY-}%n^^XHy>PSWk$x1W9XSpc_h-+sqC z-XZD57hinlnP=+G(@#Gw>E)MSe)7pDCEdAm=ZPntkaYL%-Fm0@?%lh7{ko+4_wQf7 zejUL5`}eP1yC&)M^z`K91iGAP#MBKf14@$g&6?+h{hTA`e^?|l8b~B4pxzn-?N9AEzFwgT;_Bu@1%~)&#uuQ>9%plb>JI+i}_fXNQtI>}FzkL|j|yZ{v9K1a|eSISe?uknM^O ztIdXUeubX7yw+0mc9iP4`^gm8Ju-)Ulm`2jJf25DzS+-kn*PBHU$;Iu{WZt<#lQUb zeAn;&_|glsH&*hr=CNj$U!Vqlhut#&l(g8r@z?N0ZUvMmis@C`8c-mFyejHdm)v8K)nz!E zmLkSJYM`6WOU_${qoToH3e%;dm;4>uV=m09SB_({6grPBtI$`!nV|dw$=J8jaOdopn7An5nsasj+Q(-Q{9dx!Iw^PLdsN05Y@ubQeQP zYi$^Y)+{q4A~TedayO0B@h&3fzMrP!{mHl<&&!>MXQ%6fHD8_M+F+C8WIVrhx?DfH zb3C+bH*X$azcrrR7^mYlIIP))U}pv?A|iUM>%1)c^Yd69u4fM(KDdAP;r#N0vsX@+ zmml^!XZzEA>{Cg0w^QYV9_D_&pFu?m$gle9uex#b#=U#@@4RxS+3p=S-zuHPboar7 zfiK`0=C$L=c7%`_kpQEs3j)`m63x;`KrOU;xW8q-lF^WCajn5jJ=3z>Fj1km=rUP{ zDo@Cu*CZ?vOwfbMh_Y1lYR-|8$OH`DNw04Skb;!43a+YQ-k}6K%V&{By3#w`NxF~< zEkIqXyV24aBdrzuh3)O|ML_%hCOMzP%ST|H1pHO9C zpra+zwNEqVK!V70Aq}uhr#GU*QibAZlxR81(vv}0DaUA*8LZMWk}|l=fMLzvkjO5s zWYSo>Z#BA`Wt4$OgeA500MZ*mUgC-(2~(yOD%5e<5E)1SDbq#PPDyevk?N3Cb|o2% zFoQA_#x51m)l>mdYswBn+2pn1C=;P+4}&Q(Av@AW4qlT3*;8g)ny$fm1=tEMiKG~C zNAGMvCz+LjuEzgr)dReoI7Yw?p^T)13Yj3^@EcJ&u}77YX{{(OsbH{VHthhD8j!}Q zRUDJh2{)610?n8J;2~9gUMV?E1`o)Zlq{DhEt5_t(ugENYGGlSghix~a&H#FW+{rQ zsyq?p7h_sCwGxtq1cu@}Y&wZb$xvfRMHzN#pIM$%Y!*=^SjB%?ZlkiaLM5#?L=+U% z!sN^zW?AX(qP!?+7KThqlahwCl-Q(>!i-oF==G_$ES?8wUVWkohYf@>Objc*HpFI` z2{(^oXUovjWZ7$2E<8k$MogXD%Lc}pY{sx`5@8}6n$V-rtFWlZtjm%Cu7E{0FYc>? z@XWy0!?=Zb2|*MGWzZ;VRPey|q#Bu3vL_p$5R%zpw*4KlCiiooYNGq5Fl}RJ4vs>A9X;Q0?#Z~+gCQK$zNCC<%L%k@i#!S(%0t1r; z0>1FjCM(2kW<!q#)w#4wmqS-o(SfsN*!zH&H=XI|dF`zIb;yYq0J z_W*Jn0QBAov|$85VP5wVW&884ecCDP;hOvGJwnJ-5}1Y0U?M<+kya*Hqks@jWpnaj z%Nd+7ZF#@ zcR$tA*xU2gUMmpF$h-^|8%!w&xI>)Y!R=4s*$<$KIA3E2eC=FtA;NN@p zxBlU8{{!Fmx&Qj3fBj?M`_112;C%nmZU5Wj^DhC|zw4QIKJ^Vp<8OU8@W=k!@BGNO z|Hkk8UBC0+|4YCBk9_7&W7;jL1Bn8dxnRax#Zl8{z?o}`JZr|~)cSycEXj#g$6iHL zTcsU=8L7#RW(d=6!ucFW@Usb=XJEQA$Btu8=hKoi>~T2k73>nfR4I%;oL8uPkqkuy~aQ`&K=EH1bVRVuAUWz4*#gC%<^Y{Hwo-U-R=n@Xul;xai+}LKkMSG6>&fr=xBt!dzV}@J#K-=%?`;3czkKx0Tl;VN{%xoFV;}vq zfAP=%sf!K$if{hrU-uoq{d+(1TVB^=_fvFx!>{2BOQP%fYps+QHoDrGckB7ocUm8R zBU|Ddn6cSNAJy`9nwL$91JF^&;N+Nbgewlydak)`yjRTt7GA+CbHZ-kB)JmlG%uw{ zZ3^QwYo5v{2ms^x&PYsiZEZLL&aRAN)BLE`HbY2liTnA`d$;vyPCJ{IX*U5X6>r0~ zF6C(3VZt;oGqC3}T`AAp40_mY9^v`Qkhl$2hsMu?xo?A2Uy&&dakk=@?C`f=`O10rXTW*()#rWZ){MSQ5z9D0qc{JO7nD_q zwlUB)=LRLY+aW58OyvfHqz?AE*4i+7C1=-li%wqzX-UFhG^pMH6`LjbEH8*c=#a2)&_9(cLnUq!gk=9PP zx_Cy0$>=J6qi78bq>_Y{`z?_`6T()|4IUxb-XZo(pAoI9iF|3ssRk*0jq1fm!CRgctm!32Ua<8p`aXE&*7P zmh5JMD5|MZd#EbLig%LaDjS(y?8HDdrQivyU=l3=D8N=oS(XM=LWLx;CiybB&`E*rW}xEOBV~;T1S8Mq`Y{d1}dYpFFMtQWHtf@PiDd>F%c}ZDhMJ*5mfXF2nuOb zSlBAjq4aFgfan;2Ol{%?%kJrP8zc9i2Yo09WuY-)P*za6`o3S>8^sMS%+Sn1n$l7VdWlmsR# z_XbT#3IiaABn#vUZx&1? znAk8V46L34oasO_jETL;C775Vg@PssU$X~%Tc!!?M8Ri}RPl5iEf5A~MvZXrPJ$lr zh0~nR-i!C&K6}Tj=VLNjBRP(v0C$%*=d}p8Az^zdjp<{LNSAWmlJo+AAJ=Za*i$b4 zEijQ7(hA&5oJCHdqqFr@!w5`o^?5%GGPCPLyD_!n<~#Sn+hD_JK6)F>oMvS45ok(2 zD!^60-}dhp%9UJ^>V7u1!8|V5wPu-{!8ky6NqUJgKIZ)!E|A)BRBywc3Uac%t|j#eh#qa`4e)+ zjGQMss$lHgUZ)iQ0`_{DUGp&u_{5+5FHb)Cw|{MWCxAco8=m{t z@45YtKK0=L{3F9R{t^KA-jDp2HzJKK(?&bl{}jCUH~1Rf(6Dfbh+YU}7uL2C$m&NP zQsEc0huWF3>>km12Fzho$5MO8SQoxmW?ZmW!hmb)qZy{?v;0V+8y+@ zLr(vqziv4=>*fPiV!||UJdF#I?0nT6cR5a8>G!-^qPNF1;vzOXVhpOQ>p!~|3cy)e&sy-c0bSB=suK|fa<=Y8=Jd(bNiBl-FAI-crh3E z_xpWBR0yv8bnY(O>e1>pj2kX?zu)io>o5$Nb+OAW9!t#Uk&0Bvau{v!fo5Hy;d?UJ+h}d zB=?*wcJ2C;H&5^0r92UIxHHt(zIwCsd4B1o7tL+A+wIq-HNR`a3pTz-dsAj$-Y<9W z-@SHpaQfZ$@fOiYK$6ug?(OuwPpn&>0~W=NLWWe~vaFm1_=RVyZGCa}Iw7@$&A zNTZa1HnwJh2{fdN%}MJmuvX%`8P|{*TuKgeYcx0@mLyzF-sx;Xnz*G;(N!3PD(I_-unlCHrHX@@0npG;Z=xr_OwzC+ zCq)Ff8o?kdFlxJU0`bs#1#?JY=Dj!{DRfs>V_cN@0~hJGsvD|RPHTnuXd+T@l)otz zbZ#wHUJ^oQSZ4Wlr0DD>Bw^{?Cg`oG3?>Tb5V<+KSVB)xEP(Px5e`#EnysJ#A?7xR zS0_nzSqQRl7|o)bfhAzEWkw=EjxY%Zu_=>dabU%+KquX**RE~c&g`(YQjUcPgbI}D zG_UHztng=qS)?psZWXdODj`z|D;mO}s9&T2z&)6Zus+BNe3}*#7%>37NCl~6rE^W_ z&X$=liA+V(sv{s%kSZXlYFmOq1XeN0ptgd%dca1og`=|R39x}7D{ghB!l7<54Pv0a zwCt2%1ax{fM(bXUztW)Wo98T zJ(w;UTm;lX%O=2>L<5LSv(2lEs)Q`rl0|c_yO|=9wQi8W068e-nh2voE3KtE&5{+t z-&hAAQ%Qu}hyqrmxSCSv(2fMkAF5{XG(eqZ2$@kV%FfDqITnz}UbT8;f}up9(_BIN zfHcuyjUB*Bv_zr;urMH0os^w21B}_j!nZ{SLqZHg+u*LTf~lg}$dF7lm;$nNvy7C} z0VfGnzg{T1nM2Lk1&CfE;RU@Has@NoM?bmqeh5Kf=XNr*Yoi@CAFZUiHnP8&V%K9AVr*{{LTjV+HO z6?gtMo1fqh@yv+mdHT{>Uop>E_sdH!wbmrH)(VC8vwV4fzu)io`@47VuIrl8`I;Dy zaP0)wo`E;)PB0$5?v7BqKVVoM;`xu`;T`+All`1sf5!2Y?@nwPZHEhc-Bl&z()97c zKlY(-_KfA~W`_=7+A*u%f&mmThW%eQ>^!yo$SM?dy!%6+`reNNSjKYz%f9pvUd~qMX__$S`Gx*f^Va2HTES4l-6oyvuq@bB<@^p@ z4)yc9*D(-b0_V;8v%{m|y9&KN#){n>b6fM>W{cTmJ8TX8+m_$%BRtjEKWZ>QTj zZkEb7GiaS%I`}^RKVSO~{``;p%CCR#KlnQj|E1scRqy#bf9;ch<}ZEh=Y8Xk%|HJ4 z{!?P;*S5;C1|X>i-V`DHMa(`~?n^No_+4z{?! z)N|UMU-(!r39vZB%u9wW7T~BnboGoA&av#^C=1@)u68?~Uy-)AAE%zWa+DpAwaZmw z;O4mnHG`FO^|O-dHvH=a+hM9VJY9-iCDP4T_M@D4HJ%(ixtpLlUzT)Uju^ueSkDjs z;`z!*GA}9YmxHu-F+9!N!b4f@N)QEr{ep2z$Zn4uuLysKVdxONeH4dXe!4v{?hc)a zm%-a+Jb|)t1)@X8_>mXBa-Mygo@XE2@zFULh!QX8Kx;O%)*9WQ9$$)Cu`KIrHN^Ti zBD3b6s_SxNmk)6FQt|ft7{}4fhG95AKYQ$QvqZbHnY4l9=+p5|=0YwVHthDJse{)V zpPcsh&gXl3oUfR3*r(gtV1sS5>3Zwi2Uz-FYM;48W@XmM07YuF7%fJ#T}y@^4R}aP zf>|@V)|`gvXxi<@cfa@D*RNfd^dmp|BN36(XE8pd^{C!)^!ul$pKmW#LHMqXfCzOl zfpzV%-$!4W%1k1Wx%Qqtx$dXgPtN_EyFI5pT%&39IfG(C;W@w;OW!!KmJGrX=E^kW z=4V0myK~55Ef!D^t}J4yhFnGF*1+YKWrzu-iOmveE7gNz*`U}C-Bz@eV5Sm)+?ZR4 zj8d4K;WClk8<8@LL#B-g1YseJ4oj5_Batw3Bxq4SC3H)M$!UQk5y};09LyUGPl4bQ_$vb+Om5|u&TyV z;wIravph`-8!Ztg@5Ofrf+i^(9+`rMibc{YRmaVp1DGUsmt`h>O?axy0%?S6Yq2Ui za5!ugtRjIiWJ@U$ETm}|sKK}b0hOI`0jbh9v;|rvK~b(XMUo*>N!!T0y-=x2CbI<5 zO*fTO2c6QoQltglv$D&pkzxY^3?yAcIIEvl^{Y!3kE!7QbWvk+xN3 z)*-RhVON%5St$KrW$YWG(|To*p(b@h60WSsZX8w}HldY4Dd}Po?f?@Gq?cK4vzJ>r zJH_y<0>p&-CR8%0OeL(L&T$-&a6{iH8CGJrkQ;{p4h_K-H2RthdB;DgMta$81#%r{6*Cg}j#_5~nqDos1s8?A5a8QzfH!6;l1MuS0% znG&dTN@1A`C?vrc!yiePwtzml&=h51} z(ckEK-vF}&F&HQF<7P+A$H51)=HBSdhta&b4>UtD$1f^&Gqz%bQm(hXQ@mKd`~nM{ z{jt@}w_Ln?%k{Old~1(i#P{C0^#S^Y^cWttr)u5I=~K&xcl61Zar2#c>b*F+j<$pO zw(|ejhqya|60ygvcOW`qMTTO(o}J?1tM`@_r{{?MJkKSdF3U0u!~ef$4)Z);OdIz5 z{pr~`h8_F}CpR(O!0re%ctacE1AN45J)-qdM@KAJ9^(E>c0J!=~yO`oQ0K|2O}|s}lCl|-Y z5B<;&0r=q`{^8&7n||ZR{?h)tf72g&<@0}K`5SjX^{;#ixZ<6`gh@Lr7d3gi{n4T0 zsjB5J6}`zXm55?G?&d3d`OK=8-ySeurq4DBpfYIKI`ywK;@gEpc&RL0Ny~1N>({1b zFIB~(i(KKG%TE5l5B$JSi>~t~U2H!MFK;xyY4h3-{N+bp-nYK*6S(ttF;K=c{1r*AiwInzN|O3Nh0&n<{tE`$`Yo&?^qa_EBAJud5Qh8AqEYIA0!R zyY*b_RXg5p4y%4iU^!U-j^JmN`CS}Wj{13-r`dNabj?DoWj&5k#oU#v77oO<&kuEKHH?QxYmv8#oOJuqV# z1@JS(^ zs7iTJM)TIJH5gN}D`C|yxEOJE29(<(uVzDbyWMLqNe@ZbQPTPRu{@M4b{Jf%Y%nxxGTz!@jaXJKnjdy7y{s9^szIt{a2_(d zicFYmpj=y1w;7FU#%8{25_U(UyR~MbK$vkDyqrJ}PESAaiBAxG*Sp@O;tK%hw11lI z>wP7VnSEXEojw?bp?SN0d~NV%18FW2-b#GT><}Vlgwtf1J=i^B0kwPTAHHB5$Y$HMc>JNn480-SBA@FnFpB!<|d)O=W?SkTfz(}1=QgxhZZ-#VM9t*aD&VkO5!S6plbZ6 zF6-R3*rc9vY!y)EfM98zaEQo|H=`%Zq7?uudFk0p*$5gfE!jP+@~|i>tvo67l7Zx1 z*@B=M5>`SPD9a!bL%@td3Pe{w1T$NG_Ed%P+$e)p=U(E_VFfopNpT=Ho1ZbWfDC;t z`Y--p_TD|#wlur$8e`6HJAsc`8+qT1&IK!WZ-VzVl71Ym9o04awIl*vja95TC`Rr7p`5Zo*f3HcS_ozt3@V{g4}l5_Gb~aTN=tY(vLRjUfFTFCkSbFwN_(r?g6ezMLHiaE z1i(=SOD2;78D+Q=L$)Dc+>i}khz{)V2XD&Kc*7GyXH6o|6+AKkPQK2dW$BP`MVKQ= z$wUQ;wOo;gf8F|e7k8w2!XCuUEn(73EpPD{^JN`=owb0c_A_Od1uZO2JVkungr zN#=DjRk2jKYiI?=K(V>2PI3=XGfB9$lswaEU0Ff#T8C9H30M*a+fas54UxM>p3=^{ zN$gv>1cRB7nQqMm4<9xThkWkI_QkJWf6*^J7#?p2ATj~;ZKFFvFbv+d6npO@H}Md< zN-VMkTDL>rw(W30^$vo+tnmTbju$=}?2CLQ(st8jt$COXN$pu?X!OX;lmdMVc6xRM zn2-t-Bkb0}J$##z%rMeM`7X2x(lL27@%qCj=m%sh%hFowz2Ds2{JI`mEX%TO+naB`S$7^je2DP^G>q@UyPm`I@2%7C zK4xeG+5meSH^h2_!xgp#%M)BZz$pZero^us;~O0kAC!{IF@1m;Xm|;{=gsn zCS2i5-uG=U{^b9O!_ObhaVKNlUeYvSS>i?D=B@^J9QBSjT24x09U^wK%FGE9re%N4 z!qEc2taVvu%or}<*GpW^p5+*v!IM1q6fyQ(1X<$Z$bntYe1GRK*^Ak4;Md1;`(KS( z_rVW-@bj~cN$VOHe5WqnukQAFTfFI3IC;k{o|fBvclI0jF2Gg1^yH5_`qwmrC$(<& zgs(d#8Hp9;ush0X(;YTf@QAw>>h$WenDD}ZFDIdh{FYDm2h95eL`hrj{;$>vgOuiw=NUrb((Qb zi>(y#ifO94%)<_^IAv(d5O=%tpNyFE$uT$Mz^8x}c#FV4-SOF--OT}qZ9P$x=f0^8 zK=+|xvw!}cKIO27`F9^8He7t+Jo}IDJiFb{$Y}%&-02du3eD|VRg#g}BL@4-g562@ zR+ZKq&$HL-wIsq%k=TS;i_CoV_`x`i7Z-Dtok#RXj~>tST-jbQNtSxo+R%J@a&>dF zF2SCbm-jC(pSzsy-FxzQX=@yAI;1vdw?{yX_nT?7;PutbvaEwojV;!#A3`amlxd_l zPlX}dG|24c=4QLu05+0zYi7;e?Ed{1QgL~C5r-sc&8)Rlj?*x-;oiObuf6tgns9M> zY38Iy^yclgt-TWcJzm+&T-WOdk8EfoVb5J&j@CL7Z2(~2vN1E&)w@~2Jl%mFTaKw0 zux`c!dGO&$LnA@&O3T(g@{~~3a!wP2lHMnV>P0-q{@B*16@kwAfrVK zOQ)%*3n6{%;W}`v_YXg-q^_|Gc!UF^c@GvY}{&en3m{f z9w^Q)1vpL2PjC!=hwDKjH`A{hf(AyP6)7%~M>$eFJ>a5p-uN z9YB^P&9aGQP&0;V{N8{JrFXVk8J09pnQs89+b#r8ORQ2kuZmcZJIIBZkXyCO?csUs z_@0tovVxt}MYk7Enjya%Qx*aYZtjw$hlSu<8Qf|XL~M-2j`mKN34wx+SWAzp zlrsg)sM;D0a6t^gCFLI4CABMmB$>IBiI3pqEzxQU0#>DcG*o;W^xdHc05r>kJVATKS>Nm0OvAT8qNAGGGFfvC*&#$;xD@ z8nR@%ASg`|70dtvRzfwE@(Gn@7G^_ruSJ5C2P}zTOE*UoTjy}l zb2s|ZPxUYT#MMjJWuLFhwA|fsh-w;eX_F4?w!v4lYvHPFb=%EhTa^h4!d3E@G$hk_ zbWyr#?^7`IJ$vqryZI_hRIe6Op)7nq1Ww~nZtv-0 zqEkpC+u&^+Qyq;7MC8SE(R(C(XzUS@eH|%E!_1mvD8H;*Q}fieZ8szh=5Cn+fqpH@ zu)>Aq4FGtI{M5$~fs5xbT;TFKJog?<&tblYHo=EmucME^y)$3mhUZ^FKVVy-j_3f^ zho4*zSGa!k4?g_>eM9aeif!8-KYm;`fp^-i5;x1TeCku5y1u?X91d^3`6k8-DADfY z-CvAh#N`VB&?b0;4`?HXx5ot>7A#M&-QeaiZXV+1G1`DfuVNtXkN66M8^(KnVZ3Mg zF2_s0T{he|Oa{Yhx8AS`&rt3F+|Kb1qi}26f9A3Zo?ysHj)5lt(&p#fqc71(uh4RXd0G}=z6L5!H_zY#olA8`pWVfYHF?Llbn1xr+)L}A8EZ9W9S780 z;@FNC7xTSBFhf81zIWi_?I6+_ z;}5*!7EeF)wd{FnC#;IP^TxU*F3_+8A_m)$EXU2e@W&_Zs3KS=6}yb3_3zvh{aDY| zbFXx(`**Y9C-lVGu6?r(7bhSr)(O*k((KN(@~ITM#jg>UbI%#MXoX{e=5=25@MMm| zvAXWu+IoAG95o$osS!t09xzWRZuO~+?(6}(#J1cy46e7v)O!AM49wmFGM*CPZtZ(- zIh;<$6<9}22R@Sti<4#V$kmx}v4Rs8U|wq-Uzo9tZU+dDyxm`Q)BL$j+l2lEhsW6) ze*PD+bvy?l{L4W}sH4fmWuWcNrvH9p`NNr}E-1?(Fd2_0i+&VVv3Qa9F+Bz2`3u6Ps``j6~yXyt3v~YeV01h+Nl05gM9v7<^mT z*ft*>-VQie29pLSXjY61A4YG3@4Q0pZu;s~ljLU4KR=G+)VCmQ9EQv!xYGXa#(&N< zmvJKE`uh5Te|j({ba8Px`j8+^Xrp1HrocLsxdCp~M>Mcy`aycIZX}#&P zTGii01>=kp3F`{mD5EHA)j(zKiO!?lM^-C{*-VW~@jom1bVx0(q0t zqMKFay{VeZcC7>z>x?uvRpUAodoEQeSiofIXh1Ldt|~DL(qQFpVfNCdvL_6V$jwNE z(*z4sHTMlf3QWmNt;*(7s>*yw2?JzE80k*-6yQxtkIbxIKt#eq-V(H)W}T3l6=@U)(T02y}~Gg+)|gVo`a>9E}7E$AqYvA`WQNwHUMpkUq^GB3%1t`vJq%d*{xVl-H{ zPN{Vz90rmp6T`zc^y-W=Oakc)G=qRO)m+NXB*;M0njx5KhJXtlR{FiAXpBmi25f-R zlBgXNlNqy2QV8T`5I90(WjtX4(TG$>X{y@WA_FpbaxskZgi;BjA*K(P|Y38o-(sQ zA~hJCIApTQLyt3pC*__TO2xHKoCL7Xx|K|3APh7ur z9c8u5jI4Y!Si4)QzxNydy_diK_uvb+v)fPp^#A&k|Id%h{3uA2D<1!7FHV15U}xLg zsWT%)#!u4zUiH|~r4S4R|xO^V>U&6)nnD1kl zF-)+whfv;nA-yel>3xU|+k)7K7R#HjE{7}V!K-gPfjZXfPk;K;mzS4yflKmmI6Qgs zJR$XZGa{gQwraKr~>q*4QV;J;Qw;p7-sNm~83> zI}6z?nC9Ct=i4Q=)~nZk^H+ZLQycD6ZoBi_Z~n@!UawxmiPkVL({k1WH3~NGrnloS zceQT3+5dFlK9C(=T#q!&GRu@xrtaSfdrs@LfSiDJ87}6dYi}>)TR6{qA`g9U>?&W< zx!(tGfk^B>#-+9!&ht4X_N0C6AK@LN*_qk+gCAI$Rj9BelIc%=;GMI0`tFwjj3?96 z0{p-~{f=6^12AGf995L*9i!QmT35Tw-M#cy!a6zWrtM}y*+r+>6DzaZd-~nF)hG?^$32Qfc2!-EoXMR`I&AztjmJ=Q1RPIxtdJ?W}{;o z+Irb|<-JFH+!)z6V0GUNFIsE^`T{K0ALd7Y78`-~h4bvM={#E(+(vVPz}TvIWykul zY%`#NRXq-j7=36}Emw2Dr>cj~Sg>Myj>T@#6G;GWtm`iT-};UJ_V4~(?`R+N=l}Nb zZ~f$>zxflL?262Oye* zk$`2&6d7Px^~zy1Q5J-xLvpOlA~A@~b1P+pG%CsySRu-i2`eW|GE#&JWSK4(6a(BK z-!hsZE!6`CgcB0CmQa<)8<}7W$q-53%54TgX!OWkBy%G=;mBkrlTlTHk%HC1KrC8_ z=+s!LWF@O|O<57)%t72Mw;(ytVPeLvimN)AQs|_o8Ul$g_MLLLQKF*KuUTESY{!(%EV^9a>702 z9Lykfq~t)hOu@iJ84gQ*I6AYaG=oTHF>Rx^(yF^em?Gh%$-4zAf(;nFz{49hAZ4N*UN25QZY(nq0-X0E0pfj-Ika7`9|Hd9s@} zW7VZq>Xl^Xw%Onoo7k*Z*JCFG0y8U~&K-7AXb=$%H09pxCIqtlWm(u<$CO01T5Si4Dc3rZSxjQ^by`Wku|B|0LZArLl;sC`z@; zmJ2J)NJU2}Byt0HI!Un5GS#tQon}gyvrrYWBXB7TO9q2R3DIDRI%EsRM3?ABNRg?E z*qRKgPXQLh8HR)qy|T&GE4tH^!%TIUQW~W+8kIoOeBS~C3MDedhU_pefig23R!w=w zBRFa`ST+q!ve3Ol|4onkSA1f5?^SPYNb}x1HzqW;u_jAeB9$+H{qOnE2fsmJfW7{W z?9n57F@bg)e%hnGI&>eNlY4^)?eybuq728Yl3h{+-cl*9eRAPA!=U{YPTEej<5~H& z_cyXr(V)9YG&2Grai?GCcAjA0rphZ`Eg5ZJu+zsV#zpKFLhOaj`XB$yU;aP-=0}7& zuk1?I=h;~I4w3)b7q0()gWta9`RDdw62<pSU#T&l+<->ktT`=!*$%+*}@V@iKd+DW@ zPWRt<{H{`w~QFv-^HAv)7Zh=FpbkRjzb%q_5*+B zM<4&KztK30z_ulr>IAx^t{prV!q%UAGtvN@Kx4m{KqTUM1KLWYCk<-m4pP}ev{cY& z#*~#+A_Xs408s-($cW6Xu>(VD773Z8umGZFtSy-|31&SB<_LOq3@HI*_1O^&u!%6s z+z47K%;<0`xEn7NKm&VMQ^1@3pZs5-Nd8X(al(P&aPxR>7~A;qCu zjgm!rl(5kvl?XGhmk7uKC9|T+DJMa6Wrxel4zoAeb)mjTON2WWf^POUGq5w1!N? z2$y95m8^CWF4La&9L;K!YN;fWMn%G;tn#d#s;F}FSRqD92$j!Nro36jU((D|L3F{3 z5?k$)&Y`lspvhK?1_NoN!IIcIMQg51x~Zo(L2$;NQgy(QfUlMTGr}cHf;<4mj%>x; zAUJ?gqGu+OBpOO*EtUb&(`6D``Q!{)E-#|XQXI%Z8mLgF(c~&QpmZp->fU#VxqF=w z%^)dSTAErilwnpyU&WUIsIURtm|o41?m$O(6?A$}>Q%ll0fxjqAFF^X5r7#CvVwg8 zgrx=3(ve+OB#q2SGzjLV;$s+*hjcL{>!6#M5M}8b_Z&W@4RWaM^?gWLf=M~Tk~7$m zAUCDat-N@tt@4i`nO$k3(5ytfB zPeO(P=oQ_s9Ig-|g*3oxgJd*%4;|nGK*~z@--E6YvxAaCXuu{bJCK9n-7uU09hA~} zkVdO!fYRS9!px9XY6cI-dPEZ(X+)%VlS2_evkIZ6C5;*AP#p~#ty!oWJW11jFjCUx zWTZ1&R)pO*MgUYc6#(2w1UyWXr#s*)E)V%7pWeRWAKbj~+Ew3rknV2g!|1)IxmU6o zU_@<=7GMA=c4cH_#JO6xCLX8lyY;TZo!aa620Cc}-Oi}xtjp5Q7Z zJ$eR@4>4mo86}t_qAElILXnZ>&z7L(Ed{6T{3{gANhE4IDU?+OD&(}xVwaqzR{!;jW=iZI^dCd1QUSPUJ8!Dv?0qreulEV#N z{9?q0et;5(-5zrN<}c&s$?NswlLrrO4%ilCzW(~_7Z(@Xw%y#^TwY#2e*F03AOASX znyUw}uIr;mkDj{o`0-;&^_WMG9zA~i_|;cmts#0n>+0(2wbx#Q4 zXcL}$8325MH?#>P#*00V^Yph=2aCXVz;=TtZ(@0b!ws(9#G}{o6&)jSk$4ei4!f5i zW};;d4UHIyk(dZrzsF%BCfhD(lVR9r{&4_~rjPZmxnmu5R;-S{^NGLx4e$H5o5SmE z`m!JS(LeoN-}!wwGg_W+Kp*+YN6xpfANkQg-KH1tSc%u8m2AZVoUhlogkNLaL9}H)nvOBO zr_h#1579B`x;u~Vc=3*pDccd|=?*b=`ja0xx7|IHC;JwdWaP^q~)FeX`MAyfNw#r1GODe)8%bf*op2HX_{AHUKj1w)p~FRCk$2bGI-Lx3^~yw*+)cQ{x15%+>1bu$*%wE=?D6UA5*Ow4oPEbl zoHpQ-0~G57t(Z08&9%I>uyfAM}Cgb1vUfQR^dKZ}D9hWC8oJo`51 z*@v%NUY}i~N~0YCYj33)^e&|ht?USN`q7^+ z?K9`QhG=^Wb%7vNL;b?h9sf|Ktux5n=|7IsXh!!Yt(hV+BBp6pMm>Q?E5jAMp||6c zBl>#SM<#3yz?Ke#4|9;VDPvIIGNO1GEVZYDJ)V1G4del1VC zsiCnW9IoO~@F-kU06JhPGnqK-7#GqQnUv(ojRpl_pj$Gy8F!R{#8k+eNf|H>3ege~ zw5Vph%-kux?5+rGxsO3TEiEMs)RsBYAybK-d!9%E1}v$d0Y`cvI$6dyVaFiQh)`sj zdm@npG61-&?30yShF}Ra2)kWKMs~?+H?CfLbD-}=UrN~}(aXM;yDVldSsJZVMpF<& zB!MIbRA*mo0^lQ9umXdUC_bzs`ex&xLt;XxqK?L(5Ek@io02BUkD_Ttf=($dP2J?d z0=Al2DSJ-9GJ4yyh=NR>6tZzI2P|x{u|nFgVt|qZc0Xg5b8Cn#Xx7|Hs-#Q~qQI<3 zkWs7O{E#pjvREZPsMENoHBAggsclk-PE_2oWp)^P9eV|+8BJjE2m}G$(SpTksXj*` z6%GS|4J-wCpqZt2Be>B74BVZrpl?b8D~>8zT8J?1cwLZ%)KP(kRi%Cf73R1%(}Bc- zG%=|nUsxg;28RlqTB&QKlEyBWLaldw@hI@s&*CG(;Ng9k!DflX4yISIIRz`&t6 z!$22-XmD8%d4L1*=#nDAE{M$?)j*j^FslwSfrBhSD;pl@4ud+G35RuLrW_uq9K9Nl zWp@wTPmhR_m^yR=v|>6l4Mt@O#-3?Wr`M=Y&a5goGKB=%&M@`X%3qu^ge)mnM3PCT zxud1F5~{H)ydBo-;7ib2=MeNB_pbAm*Vp&`;?)ap-sHM1Au}`2HVk_~t+F)>!rgir zP~Zo4VT(*C$Z%QyJx^n5xI zpQ&{`+S-2XFZ>k%-}-yM33snIcDHP)0YqKcM5ql-nL?O(k8C~(sYLVEd%qR>1SHzn zA~GY*s{1hAj9!s^FMdiUck;wemH^I^n|1Mg$Tom`_~3H_hkb{8>z*yxJ+(Sc>dZ61 z9Ob~Yr!LL3mrJsy1@5eNw*uRX4?YG!pZr-#xO@pOelfNM_g=u|yD+~C^F5507$&%% z4K1G4hkdxgyWfYt?)L#~>pqcrSg)RZ65HVbKD>SdHb1F~pvPBlT>TONWdHP&W_uHM9xH}UwB z_)74da|8eYAOJ~3K~&fT+-vzV(1@{lBOAfqZ9w)m4E-W8+t9F$BSvBjv;j#tw|m<( z_+++MAlaSa>ND!(9`O|50`TbvuU!v+?GpdW&-}mb~(M5H@@!cz7D_- z|L_n0ji30xpFI2*-}hTyz8?PCrysnw3vdUX2Bp|h5;Z2h!Qlc6mwhxluehlJ^WGVM z;5utsxSp!qfhw_DXH}sUb^>e`H1kM=-~!ElvjDSQoXcM8F*AD-%Vtk4>pEOa+pf*+ zvK_R<qI;f>fLoz>B9|{HNy|HD(VldWlU7*2d5iEjm4o{4 z);rSPQJ6cA;oYirN6Dg878s-jV~t1`0a)j?RJj#17VYXraWLmndg^YLVP5`|Uj)GKc?GEN73)n&8d!mG z0p{f?IcD79!t-Ii{nVxUjmHHzzf;na{`hORZ*);dsil&Y(^*^DRH4ON!Dn6Psveux zonx>Zmz~qCQZqlT8O_`^yISG%vybWL-&cuuC0Ct(&wU-Py4Gm5PK&5&gbii8Css+&yT9@ZPfWt%Rs_Uoz5H7|*Sunql*u&p%o!+-;Db-=+9f*M}DhfP?8_@rgm-~W4B zgz-hZ@EiC%`{*wR21iSUu@sw-1y(Fs#)g^x-BCwBcVebF^5B=!r!T-*` z|1EW?w_mL1K7DU)c6*6WlICX3BaTz^I!*KMk{xwEN;m~^SOt7Vjssz4!_bzcA0s!r zD%Z&ywCB`!lbsaV*R6t80PyJMdTwoUv|;Q?^Gu0}>>_(A05wDJIjq~e9{^OQGR8-s z3~PYR%psEowD3^omNs}YSzyL0<*(QS#e02D?nx|2Sg%>G%|@u_e`KSx1Qxjt0r^~iVl;f zA^|rgT}lHI68xa?uC-1 zp=TBBry0A{Qw?;nd8RA{ZG?-0L4>EJz;usD(JMjhNvK-VOGbj2Q8_;{kpXK7SQ^+% zc2qSK2IMkqAv9wTl*3918uqYYiN?raqde(`NEN~|(@P|^Rz5~qNKWlT>VjgX!44HC zbxeo=!HfWC2db$8p&8~OL;@BfnC!G>X&aM4XVst`(j^`o?A^R&FYZD&7=e)~hZtKl zcnI#GiD~MIvCANHDuGatE(K9gQSNpA5Se15O0SFB_m!eIq`Xwb;*E%)CBo8a3a2tU zqByx$<`5u+Ms=3n8fJ<}khE;pgrs9MX>GLxbmd=GLqS);ZLz+t)Nj_`1~BRg_~lR1J0&&;5W6;Z4WiR|SY zCTdNR4s|Cf_)cNLRze%I6~Pz<2~E)eO9_ZH_T5y|ssNy4Kld5D6$JuDM%E69lqh1X ze$|u_K_VrxRpeGy0BDCxOxRZXDybArAdz&iGYZlQ1Ni)ka5@+A+{z=&6-X+2-bEkZ;zuyua#|*Vixm`A6@4aATW_`wSoz(Fv;5 z{ob9-%;8Y86{#e9W-2m5d8^g|c-(Ol`*!iPc7xlqfApw;s*cw%l}J5a&)nMP_A|Bd zjs@@J^aplXjdCO1p#0H4_rHAb_kFXOLFt1N7Co1%^~vLF(xfzVGd~=*X_{K|ZR_r4 zX1#CbHni3whH;|X?yo1@-ONa2#>xM6`rPZy3XJB4W0%M&?e{rS)=q!@Y~SK3pVP1I z^^?zj$}Tvy8#ttvfhI+^Q zl8Y_I|2 z2%p#hacss*OaoWn;D$C#2dpzD^QSx=rvc`p%YO-~R2dz4qEiKJt<8`(r=!U;h`s z|NUP+zWaq&cGuoK?e4k5GR;8V;J|6dic`JaQOl^Y_o&I1eZagB$7U#;`NEE-8(>*6 zTvlD(G_NyeU|NnuZ z7iSHD7pEs4b(2#y*v&cA;`m)@29Jwz_kGt1(>nW2Ni6`TTl>;F_X&NH_UilvnwI$~ zW8JN3@w}veCU+TV+U2BOJ1Yf+Co|Z(EYn1*EaoC*+kv2O;j=#M|VBLIE)m2;_%GcYe&X3o%uJ_djv ze*f+6ekRqG0*Dy{C*CcFtKDSz(Cr7A|Eu?7!L!|b`|sDO&=aW9=el%>by~H zKB{#yu)LjG_e}Bbv?c5dbL39c)-o-anM?yTq0 zN}E1ziZ=TVrd=}MH~Z<1QxDUfqYq11$IVInJc*AdpDd~ZZ(8SdC*B@+q#4kJmGiti z=EfT>7k9hl>f7Z!@4DV}+ITjA%Y+MTH|w^ZAz#m)J1;Ayv)$p&ws*&jyqkqDp4RLx z?zD5RPq)Jz?kMNWZgwuG^EwKLi_@!3C(K4obsYVA``3RtW5J3A_uGm-1;7g=)}D9~ zh-Q5qW7Lz}w!}e$4cN3@2}`z3y^EXp{D`j54Ypsvo8ZI0kJnNMK_l+{lY5@MegyPg z#WI7Fc9gY_Q^-8osc;OE;OO-_n#Ssvd#|NV_P2c^v-dEFbd#Bv=*cX6DLz9L=6Uq~ zNS~;yJ@7oD+s6an#`x<-w!PSfRXTKPkkkDy0OJ6-((KDhXBE>i4Id{(up)$=|~Ai znVBM5Z4^5)R$J190+9B|4wld~kULH_ORT8?cq3)<=9xsokTJmEyN?Kj0^Q0vq)xL; zhYeIz*^&0ZCdkSaZe~I@c}SbFGn8;MOYJll!Mswq)v1q`r$BlIQl07ny4ZJbDGg!7 zrX-Q1RGCO96=-C3gQK_ss=+?MfCTLgV4+GV(kXcPAmwFxg;AC}!PFA6jIfHD5!Ef1 z18Ha`C6pTISjhT|0peCb|sctCsodwM>Fs;mq4KUdumCgX# z-n5nw3?9e;OoptSXQ zu~36&CrQtMkx4b~@>pdSJItAYIjkd89ypX6(uEXAx)N!zzZn&ENN1p|xB$qO?g;Gf zQ*$Iomob&_j$lt3tOE4h1J}hkq7((1F1`w~!-}Ys`mU=#`q@rM{LjDbFP3eMDTEAE zVDADj3xcX0Hc4WmT9&Fxt3uEqEeTWYyGj$hDiVo(E-KaOh6LeB2Y_&Ep-7fcXG=Iu z5_@I?2gv0?740NTeKdrqHozI(T&5i4E%Nd@|EW(OzWkT2Uw*XNrc*~7tG2KAI(=?I zGLqJNKgdh43uI)ZA~Md^x|_h`emiS;e!7>Q`cb#DY^TeMlPJeK+FYG11f3i5j8>U} zqe+Wx80LBU@BW)V51=(~%_R-)X6%UJ{(N(NfJ7hXaR%A90C-pqhpi7oGxzPVnK4_g z+vaPhIgpX*#&H_k(0IHqdTO?_A2d!l*Xe%kg59sLTYb`JKqu^YJE?WO_uRH;DuT~g zUtW5x9VZB?x4F#jr%uf}fafWf&FFA`(cH|?z|_O#%{P|UbBXM!;F$Mc#Qpc+{)@PH z9v9DJx`*jvhvK>Kt&+#62pky3ecJZdemgZu^X~Spy}e@}Hf$>nH&`Cy`Z1P=c>E@= zUdPo#SjT9%0PZ)u2bc%=tVSQm(QG0w0`p{-8w{B5Z4=SC-5Y9BbRt#*4&Z%*0b1bm zJ}mSs#R6yD>c?^5*)Vd!Iv>ZDqX1&jK86GUeC+2BpImM|K9KYPk;Mw|7`%i@B6>w`@a7> z-aCKmSO1pXH+Rg4%@`Lf7q`fWJM@XOrL}ooh*=X(DcyN0r_Fq%xT1V{3s68kpNvF`Vr*Ti}+sR>f()CK|JNFhHopF0Z|EX?&-YswEdf&y_BTw4j z#Zw{N<04hA@Kz;6TrRj1Z#*RpfvG};oF~Mt$gH#Gbv}Bj61cTu?dHL=J7)>OAtZE)%e>fEH|8hr?tSn7-$g!E<(m2G3BOcdczciGaMo@*lspeCW0LOp+@(|4-L*NaC1mfa+=6Lds~KRLv0|EcfpfvxVjT7B{fYI;oVB88TQO}BpAdcX9ta|W`~GcTbIYh)63V4 z1F|r#>w7aNeUHMD@h(TDp#%jXVm#FiSVVy%C|%p@8X~YnW`|2KI+|nAzH$Icn0GRm zNm&skp;%_uh-~0zF}Q#V^Z}ueitKJug)^+BSc;PE36-6w_oy-7ff&a zsqvoiz9-#!^WFEn@#d3HWq$iy+4b{WCxOVAmm-f`B0*{!(famlRTmm ztE*5BvGjX-Y3Jo#hloNkEKog10UW7Wm;gnfn0bVm8N(uGnM4+Lk+tg}u&^^M)vS{u zd%9xD0yoR9ear4H)ldnVsTn!~l2vBDmH~JwEg___2HMlCFc2qSP`7u$-qDqS5z}oRFT$nw5&oLA&+D%z(fG%G7AyaQ5+dO4;E|C50ZgU zEu{|_mm(9ngsMcdf|8%?h3wTN&B&;oefyBZfQg=F1Xk4=goQ%;R@BKB4+lYo zD1&GL^(X;$D%Fha!aCQ(r+Caq_qYE3Cm#8+)3PM|1SGmrXz-d{sR*z$)}X8iCCh*+ zkWtw<>#bV?A7ALvZ0=WB_|tc`UJj=beBZl%dHeJ2&wo0AW2Ng%xg8JQyZo=1z4hnb zyFL82=Rd9QN~(1L-u83vfAPznC+Uqp`Oa;bXWjl(5AUse*E@b;yYo5EeZ~iV;n$CM z^tL;nVKuUeGKBCfddQ@}wnyjvgE-4X-J+j=e$=|F2zDR+?SGH|^7EPdx#5|A5g1Td zI`^*cI-;-WS-YKI!+zvq){nMy`=Re}VVo_&NPq!00VBq7z%*ZIbH$-eO83BSCV+jL zc^=7j=UsJUn>fxp9b3YhEk?TWaq(@h{JL-djvu`Fk&pQBhd=zRzwu9=`LrLpVfTB# z{JMMhH^1*7eBAfs8=mltxqSY!{>JzIyZ`qm9{bqG-uJOT{h=THjvF5rklM_46foq3 zJR`0ErU_`bx1AiD))o_Rf@uaujvLQ*oOY+Vl4d{~c{E)gFwO@D9;gY*x~?^{x@mSz zN9(ciWC3WJ&nXKT30#-S^xtrf4|}i$9f>L1R#NFWgbHh%)t~0Ev>5DaHHyjYjHpqE zP1kFTPCv;H=-i)Vt=I|%y^LYjgkd>yujXJ`8^nf(C_y{(TyiL_#cEfyZf)^pVw&bD zZ>-+87_e{ca=RmGY`bSZa^CT-TT(}zHJ|d@4N{|KD+(*|A#X>c64JnCwXTE77YeEY)y4Y9dl|mk*%xo zNC8#M#F-52mIwx}lLN4bZeruF!6WNyK0;IN)mvTR1<+JODxN-_t0KgmTm#6sS+rt)n{NY z%=hEcpW;s)r@%hoM1L)=eFPr%s79V0uEB5tyAj>);KieDveKuk)~O{<FlI*V^3Vy)hH2Bfj^iV&9-^#xEkdQG#pA? z><|yxl);Crp5xWouhvRf64b!N$O1@i>?e&*9IY@hqa}r@h#*U@+)5Jy-90m|+4HjN zyT0$-W53@cTBK#WGAgJpRSJaw?1sULz5X?C-?sk3Z+QMuka(RE>??`4|McNY{{=6- z?VWEpoP2-POKo1JSuviUlaNN(7=uI$NBuN-!X@C=3wkG?Sa;49FZtOs*6m-x(kY5VM3@CxBB7a{x<>`7WU`*>WKJNQzyfrl zQ>#RnGfXoWEGp2;0?yDxxQbC`?kZu~?PVeMh#ep^5A6%AQC(s$xacyYLgtLVAj{e! z!W4bjl3ijn0wGXQA*e7Bl5Blg8DOFmrYaOupGs0Vj4ma+fDMvJSnNd;C51&=978fi zu^eM4jmsK@6mIGS4^rx!u_Oi<>mAoZ$ZZ-s9HemSd{+P6M_N|HZ#q+ zDv~JUr+q=<&Z7$w@BaLE8xf$NM5#mpK`T%N7O=}St)VcM9N^G;)D99I3tNd=h$OA1 zY?p_!5_>6{-x_b zc4{?P%+lE2u@stF*Y!(vSUKss3PRe4xr)Yi(In&%p5NWub+wN#m;(p0#1(}g$N#U0 z&BFiO=RaLI%ZBASgB>KLPb;LlEVVB0f5)$X%}ef>=Q*Tzz4Zg{d&jSS^-J#fyWjl6 zpZ@VT19-{HpI?mcecP`Cc=_M^s(Gq!eBC>K@x8zH%x6C}(HQ`q@$CPx?|KEE{*0$y zf5Y`R-+c3rzUIdPEc4P?XI-}}i$aB^m=!Bs*CC(t!MES`uO$E+gu?tt{&x^*Wgu}NY=Wp>0=N4@nd#&^UaU{p6~efN8R}7H~rY}e&Jufk&pf@ zUiy;9ea@EwUwe2ipM%p&f5J!qt1tY^H~!a8_@)|lCYSjaX4eD%09bxl0LL9 zse>oze1X?l%g?}-gt7iNJrmci-n2+}4NEWPtvF+Yi zQVq;s1H(KXO3#$#tdul&gTs(bnB7L_fpzAnywLiSffPaJvJ z4q?Yl1mtUP!F066X){b=!v)M3h6(e?0n;!|GX{*~j0sb^MAzXzo?}f+S0WLHE0VCQ z1~;a>J3;6#49jW0<8Acjl&cQs<-g5bS%&q~F;9Ln9*y2(VQ#Z$X8y9@=w1K-AOJ~3 zK~x%k(gx&OFegkL#}TV3)iHH8mUGWxMi%5{A>DMEgK87F*X-&vkHa((<7G_Tw5#=n z(U59g0#znXyVGG$j^i}85bMx1zHx@oS}&2EcjL%ub;(T-mit>Ic4Cfa9NYB9X`Zm| zz;rlX9bNyxh&hD)bnKr@^>%Kbj;-$SsOWH9Wyswr_RE0Xf%DVG&XI}lcq~jUDZY6* zhjD}U?18|T68rARj+di*8kFX{5x8y`=Q#koFa&U6vRxUXpgIbM%e$yz2#dgexvYNK zW9gO(EI<_&p$a~r0wGjjaa_8O^M~;#(iB(%4uoO67Q@4E;Rc*M47(HbC+G+CJCt23 zo->1W$av`%{|s-nY_SP*6?Km~H|yMTipvk;^8K71AAU%46UN|OH(ZHS(A+bd z03(3i&_%>27wj0Dw@wn!bh^g*YaX49Cm{oZS(OKPhDn!Y>HDr(2+y9r(pLJT6F{nU zFZp|4vFzi`Kl#o#|I|AUPi=i%cEse**S!3WQcBmg&-vH9><)Ja@YdJA7r+a?{tkj~ z{h4?D(z|}`nYTafFslIG`R4Zkc=uc1fBOrbeaP~hp3XC9GAkrg&@81Gtu?MwiVa-R z>W}y0CtTN+)`Kl6X`P?AeChtayLMM_%{69SPmrd@aR?c$>+0fE;?d*~<>THSb#ZTG zu)_|QWIQqTtVD<+)qZBRRr$NfP9@-h1mdcIMPx7t2Rx!PMnn<}iopT%AgvfeO>692Fd+;b z5V62WrM<91dw`0Pqcp%StXftB5;8AlvIrF;qNi0T0?7(pR1pPawd@3wg@F-3A0?WD z$|V_#%-vb@t)eI~tP}+hlsk$=Q6m!yC;>8DqyvS?fCxiSmT_E~syaXhhm7U{DiZyx zpp%vkD*%uQofsBkGI(JO36(Sm1tuB+H$Z`ig5sIiQ?(S=(km7zr7&zsjZn2%pYcY7Bx?@E zs#bqktR|`zrR#;+oA=6$R=uKdFPc;!2zwSO0?{)SCW$I61+Fkz37ADAEmQ377~c z10Wo9Qq&F^bCokG1R}vmb9tZ;RWwPKgN6aa>bx>HmNZ_vt4S*bU4$ocJH(P53@)%( zNJd&0nU;-e#q%JC2(m*)KuLy~xmf6qt;`{2h%^R@WqAyr1^$1TjD-i*Qwhe}}qqC^&YZk-UHfeR9L z7b4-dU_o^0?^pDIr~xv<1q+3Vv{2*V2AP0shxMW{GC-dF4k1wpi&M9qVV5S|2(;!s zy97AUBMJp|M(mXyz08p&@|FTB`(VHD+Ie~WA5BmAgVRS`oH{}sjg0UHf%f*L>mCTq zphHt&@-i2@To(Wy9^r^H8P^9FoO&h!pey9LquUSdIOE}p>KXU`(FsX}XX8g#x)=VP zJFS#=zU7yW@&wDl|3=&Bu66Nwwb1mtqsf*KA+sVa7w^CL_P4wfz_V|EM%VAIx#pVc zVJz-75N5Ef**l8s9zQ)JN@rb{IIKAP@1LWes|a?te*TlT+xPt5-Ff5U#RtCe72o== z|MkE5tAF*s{iz@SvFomT__;@2xxl;M^S*!l{XanRAAI+B-G1A%|INSs&2RqJZ~M@% z|H`oYv!>6VwGDliw)ps=c6(ePY7E!*=!j)!*I+1^_Xfj|0kO1HBqeYJG|ltXQIbc| zLTr@F^YYwuh{T@hT;C>*&=AzRK zK(DwL0AK$^>`$8KWtxrym}^cKqn%`9eMZKbaml#4jm9}!-V!D17jT7LP7{D;8Up6A z3}@VPXF{?oIAYcNu7pLLd&v)e8b3psXl^)+)0qroq)2xZi9HB^6HefV{mE60C7Q`* zIPd#A{&jOjZSU#UWyTT0bcNuErdFI+rzT(|2c1r9S`)+Cx;A4PM$8F0O}$|4;=c-; zHD!yF!*JwD_Jcm;i-@iG|Exu>A(7N_H;(ymvL(4b5jaLa(@2uef)->z0E`*aY+ez_31*AwOZ$6}i?HUv17r z9KyvqXxSTg;%WejG?8s@oF)L=ni-`TDY30Za&rq!UomV}J00eBq(Af5VQwFXGE8pc zczTW_kspT)aUVxq(_V*~;Egj0+3hyl$9VuYy?AZUoPb7)-Ymf9PckOWW61>Qi7-tN zV#3II8qby4r8=3Sn~wMo!!*z1uwo^q&5^qv7Y-{q1I}JO$eS!Xv=H8r#CEpqZgpZF z#Mnc?bf?3Hz+KoL3(x zU>N!l-89cOq+_A)767$JbVFcxSj?rzV}??Sw#@Td z&BOz&7XVIWC3I~OkIaCl^XcWOTsqyE*-1axuJ5drZa^_w4at(9=zGY;Vg`^3oh*37 zM<*?PCw}ZvQ0;iqO&#|N(#*PmSyx;cbD&Jf-Y2nGDm4*i8Cld4s_Z(&5I}%t5mCvK zQ8Gla@FVc&3E5}i_^t>?mjoa#l?H3`)?gD-krAr9RJ#GwP$FbZ!77WvWRg-VV4(uV=!kuxDRzPwU6>h`f+Z9276R!k84T;(7gI47H)l{m z2%4dQp{OXJQbJ%M8@D2o)fh=ZrJS;mP=PMsEFR%jMCJ6o01}eOSuAl3*w>I*miMt@ zF(!S4G%|Xk6Zc7~6#5K!p(y2X84;qA0tync2rVExXLR8K1hdtx9jrvNs;u^pARMM5 z1xui$&Jr^72z2ekmk;Z zr*-DGbk=*2o;>8b2X{iHN|7(5N?D}7*Wk8P4-mx>rO@}mu8=O!R2oPkizc0E)>6Vx zGwWQHvN;_T%>`@RgXoY(GYS@L7UBrOyn3=zOu+)DTM&>cZR>*zbw;mLK>Og3Y~)(1b^feAX@Tr7Pf3GXbfe7o9SnyBvuyI2$QH95YvVk zi(4tNK(vxVj7y*j=w=Ai5>^XMf-OL`fK*^blBuGE)Dw#(lU2?FcxPQn<7deaOCx$n z#Sk4l-~E8XLb8xLt~6ScfQ3l?nWoN>^c4q4Tc8E(!H(!NEDRJHxLRv15QT@Y(*O7i z694oqcXc7JVo5j-79f!%L9iM-p~8bDL@`1NXXz{g26K~Ct4KDFX-QNr=?nst_5x`U zQ4~gYESC{zC4@zx?8QqFHed{>FH%=b3r6Q|!F8ARpYtcv=l=f3ufMp5OPxi8D}{Il z6mYr2!W~t$H_lNmFS_h8S=}dvH(X~M=`K}#qB5?c#p#S@clLta6@TFs%N@#0%&dh? z0W-5wcKz-RuX`JSuX^$AZ++8y+v4jvz_MRL?H&G^pZNK0Ls_A={Td1dO6flEi@yxu zSB@9ExyLY`ghxcIy=W;zH=%buZ);DGWGO7gT2|uBzkgorbr0#V+jP3^=SP41 zCqD1?N9u}@B5xly1+mBr{DkPH@#8P*S_TM+#Hk0~TxUiCAqKTigi%ZAdT5crXT?Ekxf9nKB&@=9F_v z%9XOs5vg$41cGg`?6A9h`Shn=f7d3-ZU4sQdjUN1`kRhdn2WvY`@Zk4A2`VlyLopy zfa|pO(8)ARbM};JY7!m*{i{dnyy97nzMcMl`@^-pYoS1=7_ z!$fizhxtlPZL5t0^q_H745mQ`bm=+{)s&_~ z7g**Hr)*$bLw^v5MRVHg=A+>1{18dpqcORXSYLIfHtyW={dmS?H+<(!I2k!kIEq8# z0An~4%RT4L!%36#QWV*rV0iT{x%Ex6YoyMza?F6U#<&BorG*m*aG0hQ6fhsTS%>x^ z-JbV&0P?QCPDk&?cpQ2iruj^zS*~R{JxH; z6RA%$flR{5%9^NwGK?kK0ckvK%QOSvF)sN`^mY5SIXw@=3A9%w5wUrdVt0CY`Z3LD zw!5QT@Q4ZHc%lv0+gdJ(QDZsPG96)gr%gFaKu2B%I0W{aU344{;`MqZBfY&_!RA@Y z#V`$xq>KZD<*2vrAncye6LAK}v1;E(tcf)nJG2kw77)(fgLc4g9SGxjDDB}OvgejM zUk7K~tGxD;`dvG{$qSVMz=oLS;R3D$0*DE92k%iQM(OKr@g9qKDjMLOJ8-&pcAcwE ziHK?qSsuV%DgaOdzO)lUYxl{-*AUimzjv?Dz?+8M zBd*GVLecQX<^1e+yG-KCy!_b}1r)C!6mOBe2o!$6m~_^EYiU|QU3hP+s$&~DN77x%qrF1bG8z)7cFS0Pr_q6jk& zFxXp&6w1PCumS<16(z;3S$H6`fM9YZ1AxIsRErj&YK%pPc3>yaAdr4$LVN3z&dqNx z-gEc$w?65<-+a$_{gdvVMgWf)r~9Tqy7@_8mVdhV_kVc2bsDoUP(XozG+2q*)=wdg zI$;$)!xm{+-9ZHkAt-_^!>uSVBxBZOih!b6WYjh_ZCWu>G^myoFg>hLl;pHN8NCv~ z8g2enL%ms*2Tf8nQ0Rd^skNXp5NXJQ%n7St1j2+!0%wgVouaZhC82}@9ZQ(tCMQGT zmK11&T&aZQAkVyb0X;w~^nD?H6}>EA>;s_)qDm?jO_XOf6e^u>U@4HTp?eR{*1gp8 zlEBPxFUG29l*L49gppJsJUTMWA$07*^Bq9z{|drHis1p7(W5h##8}ILQV9ZH9MwuO zK%Nb1xq^&l(*!tWvWkF=YPEYN13hG<1*!xy5K&2b2<%AjfNJbrFv3eTcT>}^6+~fp zPm-l96$Q2v6Iq(8hUlTLqdOrPDnLU7L^DDTvoKjz5>N#}CQ1Xihdj)wj^-4?)W9GR zSg0b@RK!TqI`b4owC1q|K{TkdsxX==u%i^LKu|OUXw8D!l=uuK%VLH~m?2b5o?@ae zncJ0hKy4*@4(nWQL=l|nWP=q_1W^KedNKBrlc^3?QjxL`3si|t3?Qi(R1?-iV1|1o z&?FWH6s_-5-boe&QNb`P`IHrCJ%7=q^$;dAPob90+#AX-;;sv4$10ZO#>!miRB1+8 zHKkT#q$R_Xs6Yo*NGOgVP-GS^D@CyoPV3?`c0mH>1?G)f>-*@CpU$+7N;0~NNJC7a zFzkvvGO8c5$?tH=1k7EaQy|&l){j#*3{Kjv4(SBNX z{mCEv{(Z}`l(NZ6ELN7K9(U-Y$q`|Gb%wH|7L+~oiMfBc^x{pd&UeD3Z4 z`E2O3w8ckVz?7jS%$VRg-P$+j#U=xc`(fJU)O601CJ{fE!_TmSbre#7?wr$AW0O!wdLXN3rFxc?1r z`zKHQg1>m*$Nuymy!xANd|W*G){mc^s2;SrWP|dxUoZoF{S#Aq8}^(rpUVbrBE|UH zFW7`XU_5>4@1#yQf~PP29li8-`Py67%C%|4nC7yzj6Wu~W_~))cs-*jZ1L429K?CG zY18tq>U!I3cgg5n3+7JN3cNTM(M*+ zbB^x1%5&3v3LMK45B-x5-%uF)Rk<6+Gd*R8FU40jg6(mHd^nPHa3;B#y2%Jksf=Vs za>bxvUUS65%9=Q<)1`Pc3};X=?S5%fg|7HAID$)PYNpFfKx-QnLmEyG8%<%ZS@8VG zcG^lC2VfX$T)DxivvH2xp9X#%qv^P3Ni2sr?L%aHoBMCV*o348jTuKw7~vNZCNmgu zmGZbL6R&OLahRsL{dG;;9!AkPAf{o@1oci(MqHa?ZaO}qPPlRqp5fIF!z#iKn6}qv zTf?Af<|-dgNBPzX!+1oISU>jj6(H|o9c)7&?RybqJsUr@Pq$G4!X?QhW5`WxxgR``9lI z%s|HfW^^48=!CjQ(RvXU!36~o=o_=%*CM$}E`k|}M@+cchU<(ZPWw86X1w*`yGNW1 zCuH&Bu#WRATRK!~o;e#X#NsjqI>V_)VkMd(&RRJ1+&K`}+gl{IQSS@S!`iac>S-`BSx2_;|7Jxx&`q5!Rd*9t3Iyb)^|K*2p z-!m{i`TF~Qa~huv;QEWddEfYC{L8N!@AfdvS23rl@uF`@znQXSOGqRWnc zb&yD_SqL7FS2cQ}x)({ZUs1up# z16YbY80p92lF>p_AqwGOpB5;Kv>!EXf}zGe5Ji~)m&)=)tLf_z7Q~n6Q*FK?30uFr~u~%cZR5<1{(^9qJ^QORV@WV>5ybubTAQ> zIkK<{GvQt;yiioQ5X=GTlI^LEPB28#;YCm_rElEqK$k+Jk*E>_X4$I-XBTplk0rTUe4pGUrP{U3wIga-mY-jugO6XjlVp#7|Iy z(ZbnO(=f6g`bjMTsWg;GTcRp0hed@@RShHu zAt~ktM<}zgsfq#?Nrz_1uwfJbTttvX&b-7Slwwph(OD`CWCsZ>NnaI$N`}Y|)}ejZ zEkGd%4_btRBD+F&C0o`Pex@!+7?mM(D~t+;frugtg{)L&mfi$-sJ6_snUd$-1$}`c zVo#c4v}};1Ib?-$gF0bSaf8cyQ&1jOXl7DS77I#Qaa9O|6@s;N9=k=4__*Kl2bUlJ z;RjA0Tnf!Px`&yu6eP963ax}l&~`(BUbRP=sxAd)ugfwmb;bek3IJ7juv(G@4>mdS ztPFRg&hf0mb?n9C`5Y~;P@3|;fV4_%$6Gj7qtuS*l?#0LcYenUU;GjP-}7Cs z{GlKI(QkRhH0W-;HuPEA;_-Cu5UsLxdO04J8K!|Frd5&134g<^jz`XsO%yov zwGYFDY1|5xaF#o9u1Y^GE}K2%y6aB9=rKCiVfXp}&6nQnUvjd$@w{fT|C9U5zxus@ z`y2oLfAz|(!|sb7bLpDvPKNynCSjbh)AX|6ISjkke!)5g#q667I1cBU;m(Lh(|~M` zIwP^^kZT)fX!GT9yl!&Cl82UQEYx!Nupcv9R|sr2aLp^Q_2d{1dlr$|U=c^&y-ijd z&g+1VvpL*2Cs1wdi0z%3+=gYtWt^8waKy6Mta)>P(#Wu>>@2_rBAA4cV-4_Jui2Ho zY3n^Lmv__=IC7q}4YbuPc2F+nY1~cVIAfFNj&#m?G7T8VSKlLdDrLyi?_zBa8q zR^D}sUU~0yg|`k@RqJv+Q*+*BtaV$zealV#*zVjw6X$ik69w?V* zT!2Gs9#+%LBYEafm{8|P_m%!<_eQpnr*5LbBW&dRBncN!W#VoLrjj+-@)B-=3=V^K5l(^ z1EzTxcdKH!KR7z4@yuJ1*1}<051ab|yJ3AnnhH5hhs!Xzso~6reprpT^O50tWi+pQ zYrA1};mu$kS>^!xI>a?IVAoy1>4d;?0SLf5OM_S^On}e_5YUuh#XW>hyNVeou^)uF zAb>&=`!xx>rwp8>{r;x@!sqUXaYrur#NHNS79M}i!>%1M5@q2Wg!O$)lQ7HNv;f8v zXr5vQ@IbBUgKKJ_9Vb4H&V*K-Zqw|VFxT;_7fSDv3!FaMit+sS69x-3gw zf`)Mzy1ozhRW=11DJ~1Xw=HWC9&p@1^xQTY^XX*-!d(%yF4vx1+jpgy0CRR$k(PvSYhZjZ8 zt{qFxgcI{1c?U*hNUnz*LL>&KkXHkViOAdSs2V+70+WNZMjGC%O z6c14a@Q|&p8YxJTE_5M+JrtH|4jk;0O`|}8WiujCSOgEHF{=iQqJaf)szZ{EDT>aL zj!Ur8M0bFdrO`bdZtfZK^O6u%g7j)apfmJxC#y58mdL7pWE-Cc)f(%}l_L=nml9o| zv%o?oLLr#Zwxw|l0H_co-6W~POhKxtre1?qJqUF67=BWpos-tMdDciOUy_Z;ZY2v z$wjGjh^V#X_@g2`57cQ=g?a_d7P*Th^Fbb`g8Bqh#NS_fb0YJq} zS^c;O4jUK+tdvUy4ADyLBw9o!++|gjDk8~F$YQ_LkYO@6r3bAO3u&#|PCKwd4k9_l zCRqSxsu8xQ2$aLI`ZI7rB8WA|O-d#UT1`|2h|pr45?Eq1MM0+YQK1f=UP!43fucRi z7%WH=T@<2+!C{@KurLoOYYsh3Tr6oba;T_;bu0#z%5Krgo{xMmp77z*&;7`JVJ@*7 zN-6G6Rx2I1(Ls1a7|SE}VW)x19;Xr0eqZan_&(}#Hn55Z_O_2RD$Yvx(iVw*%2S{6 zFMjP`{Q9pRNUUG}^#6f8C*I*$=u_)lJgoSgZ~n#Y_VZr&Oi3^PyU%;mPrvhjdD{oK z`-_!fH)KNgXJ7ZWm%QvNzv6|@{`oim!fSu%=W>DD?tBJ~`_|0@NwwB(Gw*xP`}4-L zZ-3UZ)L}Q|2?wbp;aIygs;f2eMJatL|FMt>e#%s|;}FKDz6RN`!ue-CHkUD7eVOtQ zYv~>;5Bt%Nee4ZyeA735>$knr0q&*sda>O(Z16E8UFs5Gx zS9?^hf3;prXaBU*qi%fkSnvKzPyFj2{mA@jblCl+C;s(+{}=w!nXcRW!SW{W}}1P z7mh0APB|U`vfE;-9=EtoCaWCbqDN2z%ZQ5Ga7Va zSY2%0X%mvOKW>-9VIAA!UAG*EJsWf)_a%ni+&C4-%H8mtH!V{uvm!DLWsps@Bc@N#iygy<1~S3lxwo| zVbHFE+CGE6&6PF}Et@>eID%rYFq}h_6?so$0KjP3ZZ-qyziuyvHjJzdnr7GxWFy{; zaY(D(A&NY%$Fwy>JR-+xOQ}!ie1;=1HT}+7sULP1w(k*y&kgOm#(^UM=yIB5Pxb(W z9WLzKu3v~b2Y{_Loa=X1CtPEH;el%(j{b&|akA-DZWA8;AKb@i}NNBdTV?Ay&(_MMib?%-`^~?JMe!QO5*M?S$!|b6$gpyxq`sC+@ zyIpsjvE2l_h;To6){Lvbn3)lhWCSD_NGlP+BQY(FAEMlA?q{0WUi6AP{^!@e`)#jl z1lX^9`Tx}S-J4(g_P77^d-I;J`o=qpl}&3iv+BOZTFK0s=_|kfwzvPx`+oin4HWy- zXMWka3-))u;5H+l`+{e`=WV~RXD0YOkO5I#K_d|X7SUwYV=;h6wR*N_b|jPlKy#1i zL{)1jO3PL`SqaY!Ws)nRPxT033Haja^Fe?HnMol*Ze1r?a|Xc*YebqCtHGSe94l>h zS)tOnXP^T_Kp|MrN>)u;%SjToSmy{UEq@7Bbs0qU1WjbI7a9`6lP*m;P^3agHY+GI zmQp+<_hKa_SV|=a42S};C<2zgDwBK0R3VGbEsz}FiqaK%N`wUnws@GaIzH0ex77tgyzS4qaHXh>h}+I|o6A8t|Z*Vj*+fK%V{_p{~h41Dx#P5EIqyVq~QO zVUe_6t3-ikTH6_+WZ<^LM6z3ZB0`3!WUL1h8r`u#>QEF)LV|@#TAoZ&C5u4NsUR)V zAQyCNiF(NpqIE=o6r+#Gpl+xT%@9Zt1qw5RqXa|}ao^B(B$xHWr3tB(3&$5QZa^91GK0x zMjk7KVx+4W7O+|gN`bC~BA-U=Aro0Zae)~EP%nrjO`(B=I}?TuMni}yoMd2udRW~_n-F%mmd4!OS`?6i2NpZukPBhG^Mc=ngLu~)ylH>%kF!0dts`z)_Gs&s5Qcm z1)YyCoMSze(Ge=*jOGJSPkGu?R_C48(*OXt{ds?@y(!SyEG&=Yxn~`v4%AM5NHf0V zWzYM>Cob*!-a~b%R?2=qFZ<;yzUJ9xR%^X<>C$t*;%UpWgsUsNd0vW@zVDy^f;-aT zAMQ)_FMrxoy1s|tUU$O);A9-#Lm@FSQ3yr)l(Akx)Ug>bwR;mjTR)$=wD;NkdB{lY zqaOLlZ+*o#ebJM@xSd1XbkiSx_`{!f%PoKKhktnUO*db;{6j6^?mzltKmI-6^~!Jk z``_{AH@y+Si~jc4{TVviXKh2Dr7ezowCbYWa84E(b3V5F|JZx`s9Vyqyz{#6=c!%w zp8lY_HQ_WLjlGugrHLY%l`O}PG0M__AOh+HmY_~_Br4HKV$?C?7!#vLp-}`P0^QOW zp<7UCF`Dr+<78Y>mKnzkt}Mx*K)0ZMy8E27pQ?K9`gm z_FH>b?fTmFTy5;1fHC*2CObY;c~?eA|iyF=|$r~|_=6yG1EK9_7A+DLX^>}05p zG%iS1oTbMeMTZYBj#qLv=>Ze5SmeU2w0}sB`%mY+NPqBMjy-x5Q>4p%5g8uTwHH$I>h2u7gU|F- zU_6YU2N8KwoJW*?zT*BThn2Q``N;DM27rAaS+U0?eQB+ieY=$*Sokv-%IJg+@ne3( zd_XCCVvv3)DDC8S8kSu)TVyiU3)`+;hpR7jh3SmZw>K`Ob>nfU zww)>S4Z{u)lu;L*?r_IN$EA~STFwjrx>Dw$ZAm~oU|!ep4uTOo$bNTS zN^(`w<-Jpn8`ADbLuR>5jDSqMyuEny2{UH$QgouxR_9vFZ^&7K>cL)jY&VmTtG0nz z(@lOepqcSGIc?-N$13E_^@E;~%`BEsRe>lncap;}m|2X`TdFs#b*fI1aJRYj$K=Cu zZlDtpw_NU1hbov<(c% zFMRQH++B5{ni9`>(bN0gK$A-sFKw@Gzu+04pK>~P^*I%o+q1s(>C-ePMO1Y=Ef!_y zdk9j>^E8RdE_E?}Fcq^ZB5v*rfsU(3fY`gN1XvyCNi@2ECcoM&Q#!Xi?b5Z*Yjm#(3DuEZMd=y|Hn2itWDZb)3;?j3GiytnK4_I!B(AeOF94iefAdrBjXzzu?#2t(4dqEUpN~YeCRfSy+EJ@2#Rv^~Bus2oBm*5NA~TVJ z88QAAQKvfqVKj6_7!;k;R3^ZbOQ1Mv|FH|0qH7Wnno%iE5h1HUz!Z4N=(TR@h8 zBz2f1a28UW6r&rFBrqKw*?gs|s30?ZV}OT9v+Renh`6Y_0yEix2B;_!5!N$tp(05# zLLrV0oYUINBveCe8zvF#7#(?uYF;G?C=NoQP|v1n%qkOTA-1}75`e>fn<(i> zF7BbtBF!*B=~QuD%FfUVOf3S@1PlsDf`O4er9w=+0`BUhB#4DLlQO6(GZ%Mr@NVJF z2;>rFeyUIv57%FV0Go&=V@(vO0Ro0wH(X~%57?@}U7<#pnj)z(-z{!_h|m#R847XZsG2D% zB#9LF1?Vmb3Z)_pjIKgLMoJf@j+9wMl`0@BHAMh8SrV9wgvoOjnV3Du;hM>7F4wg-#dboV4o*P7KMH^hem1P?&UFfSvaoUBx9`_vEtVC2*LG5x6*vV_)-czwQmcbMsaCo=flfg^&H*{s-T8 zXOCg@{W!GNc6sGZD?x%U`G4efZX6?P$5Iwvy>& z90)r>y1YttWdTzXWk^6;hQ{eZj`n718)J;R4bK8cdB9>P8}#3QXFLWpw{=EyPpO=p|+;`JW;ty&lGjR$wnoiWx$emOeb%90;kOhsc9>j#4( z#`zf6-Dz@0rQV3Ys1Ld2aeeuartG%@ZaI=~nknfJPH!SvKb#GiVk_G%e;PI})FX*y z90)si*tp$E+dazBKbw+^)x*2A#aYW!FNl?PDDl|k*JEGMxXBF#n{}vusSU8moyCa!z<1^ig`pZn$#yu9C3|QzoYr=Gk z7)CjB$~ZcF%Hp0wAFSBri}#^ijK}Um;O?*f9enih;d|WpoS08no~}V?z+Th z9Ih!@yhRl4f|zcW6hprQS@xH+rmV@`>oh3=QdQiz-(Oz5ahPirm82>ji_m)AiN$`m z)jgws&fRIb%VQ}HC|xCdL2k8PebrSdsl!DxohhYiCJ^%qfzw4`?n%|E6I5CH#YHaJ zI;>c}snt4`eUxsg0J!#=>$vMAsdf~rmK0i?lFaCpnkn-*xlh%q38}MRzn}GT)sM-n zgI?oP%~O_n#{>yy#0ngMiKw`M${5H3Qr%L(K$9zlYDtwEQfwuvyQr(HQ{c`_XHIJ# zW_Z^yT)1%eh5vYZd^_A;4HK!6ynan9wD8hsoenV(ZFxvEz2$4w`Rb$*18Z-%{M1w-z zAvBN7kZLF*tV(DiP)(8s10@MOlfhnqdy1|}!VO3es8FfqE;|^YQ{$6TXiANIU8QGm z7oB?%KW{CBNC@|oIw;~2wQ5e+2<3(wikd`CT>`Tk-mLJuwIfKB>gukMC3lDjt##|Q zSZD;9lShdga~LKQbKp+YjT&h|ht4F$a-vz%upWvpa+3s%&e(PhQLJyrf_ep-3r;kpgDP+g$7j!6LySLERn?6 z04ogNrvhSCvN~J;8q{E!(U&M1c1hZ-61Nb@RAC7)u?Vj-g$APn2FfYg_f%X#qmFJ1 z!oi8bJSpR1WCg`TsI^r zh|ISb8C?+q0UeT5nSlz4u(5+llr(cxNOoukfQe3GP_I=arRtIbJFS=qf+xkPJzrR2keKajD*TPkGcIK5*^(FQ!V@=)KgOa#G2edAIAjoyApwCP^94 zywh2;PjnU=_jTS+^JH~mc=MMZwFfFL&Y`_?4Y6)aaI%%@*c@rAbFQg%%fG{0D|c?f zt?T1?U-69QPZdq7bXs-uT8U|I(>&3cl8)27+hx%-^u2ev+I+(e*H2Tqca$ zf9cxq{JLLy*MGYHAAHxdp8AxhKI&1A0`Shqe>{LE{QiFe@ZR^n_qX2hTfg#aKmGTg z{axSr%Fq3ztL_J`ebpbm?e3fI?}2_gIhaTE^iE%VA0zD+h)esDh%yGALSA_jRU!;4 zw>+U=zSv5JJQAb^7CVP|-@J!u9Gf$35eVWfcKwj{#dJs^W(p%*Hyj)NavKM)0@)TU zdq$PRqU-69w$jGYKM$+f&XwhjM`B-dV2ljMR>F|>oEMrz+83N`0&~Eo#_p6*5|oMl z2;wn3h*WoW=UkfXmK7atvL3R4Cb{*;wI+TYV}Fi3?AF@0+aFZw-KoQ%U~*y{H_}(z zQq!{3$M3pAlilj7Yp23;H05;0MYua@u+oJy5CwdPLt`rh_YprF7ZH3oXkp_4Zg6lK z9_cuTlCm+dq?11jCV3-D8jeWj%@PI8G%#bkPX+j3oVp@y^X)G=00dDJ%ITRHfn`z6)elCW4D+2i4zNPczI{@h(v=eaKNTapwfQUp~r zY48n%#3A(RQ6-0F1BAxNU!*ys7Ox>{oud{H@apt7J`Sl7vbN*^i+kZPqb6CZ5U!Ht z;6v0lNQkb$7QtE8mNZ#NRY}Zsnr4!oa!xr}wZPHJVkTrX^#zu6A38@h-BRkxY*>}m!&t5phAX$&=_Bf8P@`}cY3cQZav zx=Ytcx+)1U?^LXcN@AwEs|LU?brE07Of{3R?CL^?1%Ojcxx|&lA;}Zz2{5`+=}K_5 zZN-2V!*!>|x7S^G2d=yB{LR40^|w*hU7jLo4aA#7plB6Lwf4pK7% zLSYbfMle@MDwGUq%PS!wv&T5UiX;lmX<6JDI!Q!PJse#sMoo+GlME?Ekd_XZ&=cI? z$__+wp;%Z@pv+7&s|x9Wc5229g6@_)C3RjW?3z>;y>3u51F|GY zkP0Z!8i>nQ@xK^Pm((aU02m|!HR=Gg3ME6UGC4h|WWpMsPz0=%D z7q?dSPk2&siYwKfL02$nqElVGQaX^56eiFbs=Eu|4jROr;cioixJUww8pUWrIDw?% z1xZ{adG@F$OfC+Gs8xz4Go)^YtBFb_lhPeon5dHknp!p5;9#Cq8dH@}5lawCN-%eZ zmrvNij7(I6iBwO9dR2l@s>%e4O9xj~t5nsA5TPaqksSfLCAJMU!bM5scD_;%bWbqH6J3M|1}x}UR7B6=aTCV$wTdc0R`o0y0W$#s z8YSG5BM{AL3h@Lraik=s5E9}Hc62~XcTk1aXpAJ7IJbI80hr*9grqdgVZKul6}1#p zAxDbp02i1zn8giIOet_;lyDxJp#3f(Iwwhk5gp+bP#B`mft)-rI3a08iD5u6#E>A2 z(Dkg$-t=2Vi>U)NiGZsCDZyRc86MtfYotQGtszv1idMnWIQD11A(2ch06I=)5rRe} zK-_e>&yS79hMR`Ok6q@>Ky4%^I#GgEGE@zn zq)hM4Kla}GnD<<7x#m*}ZS;AsP+ol;KXqPyKL=UoP7!Wz9w7h^EL0`9pbP~5YgjZdC5>KMcnI`mE1f%=zKWwRx^?P9mjFAZE4idw0)W$~m8$3=ceTpSfLs{k79HUcB_cBOm#f zNdt48(tF=?Ph8QsH56>_x_O!aF{?ReGu!PtcNbCjXx?=QHR0r{emLnoS^>^pp3(nH zF6aK7TvdKV9d@fwwS8QF{dKqB_PU3d@`@AmyWRixtF38r?R|Ub$HO+Ghi{Ihg`-+b zwXB1{H4<2QV#y>97-C2^0%e5ubjUESQpSjW2+e6~njNO*!iabj4i!je5S&Bv*$r6y zCIIjUH@*Lcr@r+M{_sn2Y1&`>D_O<>>$cbc03ZNKL_t)a`<7>eNHlBIC=FG_Fwx>E^j}1^%I7nv1J125u6?G*y@oouUj;jtbR7dT%hbBXk7~6~F7^%aMQJN2HY4<-O6%?L|UWQmC(sQ_P z3=iFAg1#)-&(d!kK;vpF!!Ys!dVqWE09jJJ+~Zh2I_Pe@J-Ek&vI9El<+PT!WN9}d z-R_h(^~_^U&Yp_6p8BZW$#BXmx2m80N^3nDLLUp?tN1e;XBFZ$lCJ35#)-)iM?PPhBjsIqb#ov7)uzd*Ys=K_IJJg z=U?{nAAa|{-~EYCe)2#0U;gJ$d~&0(-TLEi_?NGF?fc$$H_5ZV=5a$V9@kuR&42jq z-@Nmu-um6&^Up=(JHG8(pZ&$p{H@=9$2We{|M*M4`1Ad)E$YKIqxCJ@PrGp8zOR4D zH~ss6|68B-X-|E{%U|}0M_l`0FK!=aCwS<&QwgBDVMBghwYhG2dg=nqgn&} z!L!p3LNqI7THm#G>eX6PN)aIe@EM=+83c0f@-BxUNHU_Msv0_4Xm}y;sj8?#Sjy#V zA8`!;(sP%a0OvkUQ%Y&6tqy*Lbi2;oH6?d5GeNK)AgXnqNd{I+Ma_MlrkuNk-Ngqk z?JpN`5ovAwDQkbyhZjUdE?&G;CsW|uxpVFgp}^q;4L<;vd&^&E${oT(_ECRTt1{l~(=SIXl^%q?EFDNJv>kN4QhiB^8>` z>r_D|g+Q`1U$ow*TzN0?fOqQM-e`SBM;Jp7@)RR0p(xB?%1kF)sap0(ktLzbkr0(E zs5U1`Rf=XOot|CSR`wXsu92~Ie0%rsl=CMS?*6@d&)*1Kc-Lk?d+%XD+e+EV5DP}p z1nZDG@#b{_1)_*jDvlwihSZ_(=-ZQ|5JM$|pdzWzVuQ!9UFjmDnIe03v%tV&0c$`b zmg^B07P>J>U|uVd&|P21w4 zLJ9LKq2)@eInyMG1iePpoPrFGR7WM<6m%6h44!31CM^M^cMzzkcoh*N0865TMZKCp z!5Y~F3au?z_U+PfZ7EXLqx$IVjxw`9l=+nR~JxH0$fakvkak%vjZ(oo}%jp zYMvvJ8X#LpQrbdLNC=mriztZ%hOSDQNrE6S3nrETnCuh=uTW9Mu0l~0dUzqBN(hx= z9smnS?Ej&%hji(2)5eg*lej=#36oJYiuM%D6jEwyqhk^Qq$-I3f~Y5?0D(5gr&ME~ zqHWQfNn$HY6=o54Koukn)})vgrH%Bg0O;UOF%gl@09tZI0u|x{f}$ZzH_T?#3`TpO zG*>wyx~mc8S9h1JPDk9Ph`vrV$vQQH^bVjzsPOby4av^e1e`J)QVVNMZI(S)#tQV^z8RHjDXN-*o7GGeEx zo9$fj8{R)Y<~uOQKrNvW=)oV3%QOZ`-)6FfpYVe%ix<1DGcz;GIiH*x=4o>J)mL9t%KrZQ@4MlK8=MBFg#PZk|Cr8* zDyNjHc}^*)_keU=x8Gmxx-PD*NDU?|;VEM{*AFKh;XITdw#SQK^bd%?cwyL&glL#Q zdTae7{3t7H0*6ZPVI8Ee)pdumxw4LGhow$yY7?wRyo0-EY8Z;}Xl#aa{n_#UF!rl& zXc8PuL-^BAdiSMI`tGZ5{f9B_wb$PO;A4SvH$CAef8uq+7ku86AOFO9;hs|;`G;Tl z-Ti;$4;qf_=RIaA-J!Z`-E@&F z43ARV8bOP+b1_XQ8YL^DRxfX%w9Nw;Vq7E#hb3SeYaH*Y3;P+hO@aZV;+S1I z!6M3l!hS%YW2037tI2NIs@DJ}>^dA*aAK{2D*#I`Lu1PxoOHX*hAn6ip$S+@4}z5| z127%bymJyydMwbK4u<__`l` z^(TD7$6xV^Vb#KZI_|Wfq=(*>d5ec-7e0E4) z(6)}?(}&)6%D7_o4}L><8hyD@evs{2#5H-X&0+T|zw&DUa+mv|kGQBi-Ay&+oWfnF zs?qWm0n)f+-5~* z<23DZC+IHTe`$Ytj4bFX*qvN;LeOafTzueCt)75$=gx%~S8X0grUX%3bM=X8(v%_? zSnKR=s!DpfTvXKNsQE)qIi)mDb2#`2`J>uhHPuJ&Y~euA>I6Y))+$OtO1LVY3 zM@+qniXo}EaE24eRFyDwAX@8BrX&;!UAOq2qg)_=@z?^C`2UD2vJd|xCi@EXSXD9mjpKo#a$%I%0xs3 zCSk`5R;xIq3RAEGRA6*Sg3m4q;$+nZ9SH}Fwf-anl#&1%7(24FIX08HQ6QKZW#oZ^ z(8eN(7BYsL&`3!laHx`iQ9PM=5+;f{Tj5ut0|IepVuCtq^qc{iCQ)M6sGbfNf+m{8 zGPMfkphpO`wnIe04&e)fg6@Ql7?Z-Be)3{B^+$o zSPdpdFhre-WN=qFDm{U2DHBZeovVt8iz_Q3h;A`+QY0zeGIM3){K_qujYEn@uU}WT6 zoq!7AiEtN#DYxZ#r7z_GVY`0tY@2yU^gtq#~+pf~g67m|>)}(cu3E)p&_=i4rA$Y8C~}nS|8SK z-w!et^hL&@ZzK8DthL)?l7hZ37={x9zDQ(q^#$*_l+AIKLU$PGUmN?NL zo~MI4wk_}qj3#kktbZ9|iCP>5ztnA9|$btdZgVScKZ5MUpdTc3 zy2-gKG7ak&EI96uTZQs*JxGU^uO7!?#CR;zVD9ITW80y$SP}bjbbR-xgRZN)bHR2F zS4hGh+v|qy&V2f(WkVwg_DGnswu$dkX1$?05@Z;Poxm_uANsZv9!*pGp8NRS++roL z3Nu7B z1sHa{9-jr4oxR^mv0Exdn`~G&efwnkDBA;YBkdjUI&tb;r~KiUzcija|G)dPAAH%% z+{!JqJrZ@l8%U|{br!H20?brY92R`tD=YGkvw-Y~XGrHpA zmw)+JZ+rb~hGF>XuYBQ)zUpg2o4ewRAM{-hG501&LC=yX(8|hzV_^=!T<0si&M5!> zAoqml*{^t|I88NYm^FTdTPt*GEL91=MJv-|R52fFY+BELNNgc~vB5ALV6@iI8zb_8VHDYht*Tm}(xl>Aju#=UY6K0&xcB8w{;kT>0~gvi5&lOs~YE~-gWPE}J# zhc?f3zn@#!5KzG|^dfLFEyI(Pq{~S~nKVAWODW~ukR4Ey0`qv;AqMe@(WjHdRka`OhP~+CcEx4xAE+F!>LzuHdZs2-YOTg7 zItO)*6`mZ_$&mq}lgKJYr+CO$D#;gVqiQG-+l<)V<$b5ew=tl7*S+U)JuX}Tfa|W0 z1lirgQ|>Lplh#u9*2_k8qS+$=+9J&=G}47(j%k(?hyghm&xq{_r6#zEIknOpQi)(e zxP+Xh0CKX0nF$ChnE)!>9N~LY2;DU~t#$DSZo-L#%*C#@7@dTiL2o=*RuO?{qLNfK znlVwGf-K?`WhgWe6?5|-#%dP0AtyJAdhD>Rbue~Vqj)svQAON@YD`dr3w4=FgseaY zjmUyPHAIX~9U^LODQQx#0SV4D37Dr$?ujBw7n-!ZWG7N7Dh{D^&G6;Wku+B-h-jmW zcfuQQ6qOQ?M-f%0BM~#8m9Dvknin>y3nY^+HB=)?0$r-OdJdift%7O_CQpmW3mn`n zsZ>Gs=<}+oaL)m*psBM0ChEze(Gg8?N`NuC)53q}PEj#8Wuh9Zc#1G{$|f^Z6UD>@ zqQT8*D@j2kQQ7+ARA6B85R* z8Yu4?DN+~BPVaCb5RW>X@bEz z0c%2rkx7IzBmr6AE&^Bzcn&L?pOH-w7HMY*1yIo`fh0;@DFrF=(PEB-XmF$(B#9_Q z!R1Hu&wfKf9CR#AsDf%xvN{Qn$O09qG)*XrV^En$lW-Qc7$})MRYW7H2y(L;GXYt2 z6jb6wM3H8wr{qkQ^4_}{A9L^c$oE}()F0k=?*37YRF2iVT?PSjBr!vPk|7$_#hJ8( znOY2c_gQAEbFHJ-yIh{hjA}m0G9ZQIihhHLtvVo#6Q|d@fDK{q%;{ z-?3ffFM8G&MrlAws;cgEr!UttSTI;35yO>@#qaRXGg6xV#Zv1&LfYR)1-p;zqi9icLo5_HA>*u)cW z{Pj=y-CxJ=E~5(I`yUPTz$?cSw9dF2H%k@%lt}=LURLNt4-B_Ft_?oRwpFU7*bXVe z07pQ^wfAc53&*yuCSVr@ZB4f57yph(U)U@yC>UfYeK0FVw0tmoE;Uobkwxfu1s_Qg zNX-aV1c@{B$Dv6v3< zlC>`fZ&aJ_rbq0c(=AeIyb@Ny17kE06WH|D6^_kkHz4G>VLVVKhO!#+WLWp8ejLW# z1?*H<`WfSNa%6P6qPK2U=tl2>aaGoKhyM>pK*=EhuWg^7zzBZJ3C8_tBc2pwl!^fZ zc0j8p8(J<>hO!(Hr<0Of%?71xD(aTym7(v;I1XFEWOccYtE5(r(4U9nsPwBvvB!!< z-4J6--*o1!dUz%byGqZ?p<+~>0tf#Z~d0< z|ACkOy_bALs8?qn58I5kkGt===h2UP6oAJ(`q6jabN55M_|Wfq=()2=Vsh$|tL02{ z+Fpbm{CDXv4L1>5ne2l`V&{ox?}`I36BTu%M>l)(zQDaf4_mn&g2h%F>Eh6Bb6Ebk z{8iPQGs9!yZf0+I#ajT}{v&UB{?|V@h653V{+8|6{F`6+IkE!kM7%9|wCOpPa=k#YQlUu&hy+&@ z;bu)L5Hg9FJAjj}J2`h!=JB1EMuFVebJgtJW~nk~T)cFt)@o)(Kk3hPqFKZsWLB+` zx{6)(CA(DxNM`Qtsu+O(F4gHSNaC$>EV{)~UJ$=-$O;EkBhd&j60Al^u~w2&j3&uom0E=&YP9Oiq5{c@ zq`1heMgr3KlsSP;P+cgB&M>QJIE)~saCm8?NJ)e;gg=3}1r3&T5rrmkVZ_TqkxX_Jg0~^M=CXFKq^*M zv#C2(;GQ9_N^g`BW}#=KDoz0jjmW1^M5>DjJ4jMY9_5Wp?nyx)!J%$U6~Szcdn%kn zGR#Hdd}Rz#k`5)&i8?L1@ywf+V!;lf1Ei!H$3+r#RUw! zM5U7|)io41g(N58SvgU~;R1kFqMDWBnuS%YLdBgb6xA@is*^M)lg8_CTvj9|*HoZP zo$=g7yWvl!$G-34`S)MyN0*e+E|ExTIj5ABfzVNoo~GBInV!Fci#SU&wKvU-+cRB z&watuA$ap`cRlYbo-U9#-S)OO-TJd%{=zT%SFitR0MC2=GlY248{hW&AHCx%zvj8& zlXE8^Z@ld-0G{=nFUYC8^G!c{`>l68>p6eR+x;?p$@9KwnxRLl;%WQ^78jgoHbzm$dppZC&L|hBwF%VvcVOxqnHe1c= zQrWQuXt9p`fldSb+Lt2oh-OR+@~oX3(tg0{AwM=PW-Y$DxRXNiSn&>=5!BZq`34rT zxT%^Q@5nmG`4EyVSz*8;tc3Ovk{`|>Xp8C42hL(_mL*`Q9?`{vm1*(ZZ9|TW+iqRB z(uc)~W7G1&LpN+m53NA08Hft|L7Jj82nrYbSlhQ2#zNl%%I?Y^K{oLw_+o-9dl%X3AtG7+FZ}QwcggP=#Acrtw;gF3y+JOh0kzM*D zFY3^5^^~^p)}wFO6;OQcH^k2^RiI!6P>ic8Sm0x){kR-c{XxVjhcQ|+VYLJIEY-KP z#AF#UfMwYF?ABVwEB|B+2VYRg->qP-wD+zwi4XerVv6ph+3qkFa2TX7d!%OT+vt5m zu@m4VaGc`xbs`S{X72kT|aZht^AX3 zzT?ZjwDqp89jte~{pa1il=75M|LXw$@wb25v%mP65C4olLXU@@dn7|CTd8Yvy`_h! z>^8pI_x&k*U9-h${SdUsE4%hYl1MttzX9KZfZ>5%U?WVpyXD*_4j;i%RTEdr%>l9#0NmRX31tJo1Bpso0L{oeWrIQNW%{-+{b3_P7kX7%e{N5f8K6=;2GU2lWAX&;Z zk@p*Yp6ki(s-f=$i2~6i9M7E_=B%J47~JVbGdLv++}(NgoUXm!uhIEj#h+Zm2d+yc zW9m^MSwI(0)D)vTdCd&yaaNz6P$VPXVHP4LOxkanO@ zR7pzG6f|GZNv%qV&h82o4j$WJ001BWNklHQY%>8Q!Cw*h&`G!3eBN(lPuB! z6sK!28x&IERaD$v#MA|vz=9m| zV3q3H!nQkj4Fj^IC@KWhSv%9LE+$Y^r6DCZR{|QrSOWx!0jY2ylByY49VSe`4q6%Z z;7HI$AR3Mef?+e@jG1nZ@hG?R@#3qo1qwr_HA%P@bp;<(omXe7HRHKGZ zsGcUR6V(xI1Drvybq_M3#qc3j)QBCNE`U##EIW~R8aK40Y8I7|GHW;cxyow_AN7Ih zvG3de<@@R`pF;+nxyuUFYLdhpk~F7egla6?Y5!=;@*fQDJ6HA=c+o# ztw%Ep%uBTMwacGI_*(8yp88^Zm^$qKoIgGcKEt>E^v^!=6F>RC;Egwb!js~^?TZ&K z-1mYPe$^8{@snTh!mqmTzWX2I#fN^^oj>)~r+oTf|J1+sH{SBrp8@b&zx|HS`0URe zr|In6w+rxLpV1f!Q`uqAu?6Ky3=7AQONOamn(Te-S|wINzllC=Rf`_Lpcn=WL+jNV zre*jyjo2~=(Jz1tY;cF;v7c@{Dt-bs!rykXeq%WShK*BcnZ68tk;P2686KB$jQkp| zq$6S&Aec@f&^qq+W0O}7V_F#o=m3ujD`K~NRG?7{WEe`;7KvTdx>cTIY5i7PwQd<8 zuWDT*!(xd6u9L0W+{RKjZvHf`0-Ox}gL(Ht^$Xk4P(3ojL)x$U;Ii(^Ve$HL)oA*8 zAG#1ZmaOAI86d+I3|ZSo{1(z|deAA;rvMlWQTo0;;gPOZdKCKDv5Y8%1uUaqC>Y0S zU*_QufF1Y1IN}62L4R^uq?^v@cWpxqkg`$t_5~;FyLt|o%OT<_rTqt>qN#OQkt;wq zKu&;@2Scw8CmR*j%BM0ZKc3Pz%4*2u@6TL-!@a(hEwGr z=N;*K1?1TNI+WOL4tnv}F9%5bPo!UCu>HJ`ZoAd0xe{H_$R~&J_`2(@M|!c7ShkpK zSlS_Fz_{CEx5usli2I@R%{*AbMJU5C?)D>q?)rmePfvzcDmN}`U@fs@0mp$@P_rxt zwVrFi+<<;SziBXB&VgAG z^A&%2@4x?nU;dR}{o-dHX=G;h(wDvb?|<{(`={UYy|=#hHMhR@HQ)75|JnN2hiyh@ zALk$W$oIVWy#W5`J?}aH$ny{J;s<@#L(JWlM|jY-4|lKAY;(2$m%Vonx@En~0-xvi zcCYSl?|n}8P7Lm#0;fbJTFM-eC^MxXr6UNGBmsf})NwAQg`kKtqf_cQk-sa>hs^?1{QT=c@v~Bw=&t7beCEtr3iZ%JpRQRA- z$BGy7+$MreDHe%RO4GEZG^G^d={cKQRBV#YB0R!9YOV7$V|$7xUitf8p2?3OU-Qjx zJW=ybor04@WlM?89_m;BmtXOvU-?CscIusP`P%@#>}$UC;z;#^CxR$VdHCUnpZ|gv z+`9GrLwkq__f>eCQIBRcYl@L9W=5DX+`ZPa;;7+uJRZH)VjN`eKIgv}uovU{JXN1P z>R9Ld=l9O1<515XvWCr}X%20F2u zg-SajBFtx5q;V6{mOUDlKx#>uf;i#3~5VGWx!j(z7 zniT2a6$m;eCz$?z#SO4X1OZrrVKOwcAPSb0NQo+nbc9mL3qn;M#)>GMAqRx0N>85(@BmX3%}gvPR%vUM^S6C5hE0-69}1ZU7l8XTBa zJYy6h3^agX!%{$tD1agqXbA!a78#(Dg%(W0;PA}FASak3mSE(CHAWRw0Fe?r7^5Me zaQV?N6^tq)N|h42Z6mB8$RMl7Y!b~uQC!+8f^vh3oR+3;l?=%P=`&bJHNcXr?SwKN z!Hw?L1Qd!A5-Q{o>7rPjE?^-{dsgcbmIZLeh%=?D)QN)o+;At=xU7XiD2p3Q+cv$!Jpy%K{f z^$PuC{mr`rb^{VO?+(N8_!Ir30G@cy9ozw)xQi$5;trm83^?9-qJMN49=~~axc%P8 z0B9B~Gt<@(j*!D6qJ|_)C7h^WFvuw4L55PISW^^;c>x%S0Ej9C=>}0DvywR>C9J?u zRc7la-8jb!@77O${P@d$`uvmbj8@%i_39oDg;VbCvrqFp&pymPg^tzl4a5C${P0uf zAG&}1;Jx9_z4Lp=`^WoF4&(V;hlogT3S9yazJsDhCI0qnYrSG3^{t5!ctEO?T^J1Fev-@hGak}rn?R($(_V4}fcYJRt z#jKEPnu(Ac|9lPs;2y&;jKh%58w*OezAt~P4p`8}=>PW!R=cE!eGcWZKU=`=hkp1+ zUj5qFec>1WmXBfG$A03ySEJ?9EBXij=|B3kPy5ur@J)aD8D>87T7U9S{m-xZ*1!I3 zfBpZ+$MLKF*4qZ`x8b=jsJ9i}IRO2LzFSE^-K8F059{XB%(Z_urg(ceY~As0aY`DX zq0@jN#xY4ohpT~n4lgKr&u8j_P7*4 zvEgOFc!lh?sQ>n`MRMOx=-9ZK-^Ji(Tfpn)f_K3kZ#ytGFm@O*G`7Mv{V)$W0OEyU*?9mks2_mgfHB?f8VsqHRS#g$ zD&0*KZ#vt<8JWDX5Io1Vp6|D99&i}P=>+#|)p)gy!*CeKE$JdtZnx{@^iPi|k)z{) z^8|X9R1GDEamh6ux44hP&;ywJaU3uXLqdKr#-)=r0{Md{H0|yVw%c7nC)M)~w%g-( zySsCLsh;P$OCC!`9dzR!yk4qVH;(NY`={mN_2rls$aalnOs%?)Y=GK+I~Q&pFdjA* z-G1MD4d|v57q!cPnz6%pfr&f2!;rD97}1THG7;Jzx(@w#VR)k7 zILNzjA3V)S!>%SAbP34oa?m9ybpys>X@^hh>4_ODkwY&_J2>pT>iU&Zm#}Yt$`snB zZ~iNP^);`1{p(-zsu#ZSh1<;E_<5iE9pCxJVHm#SJKy+AKK&OzurYb-+ur`Uzy8cdtsAY|d=rw$y?5zgRUTdv$^_tc>-~N~02%wlXO=F8yw{{zror(o}h+Tk& z6{~e_iY;j1rEbAGz46=M^5)mQ{VlJ5XK5ODkQR};IJ*1!`T3JiKKazWr?L-V*?U}5 zZZnT?A$o+9=I&Ah3?(_@;`FPL3T%uZh6=hOLdJf06v zWqDDWDKH1!;Ve^)DO4jyuk$q5T5I)bs>3)>$B|E-KlE?zfAY^f^`f7;_rfQRH$FI@ zeWZ4yTF7SFNJJ1#8cs35YCh5(Ml8@~p`5HnSD>(=gF)G>OFh_2{o5z{N0%P#13R>L z-+S}!&_C8M(2_zPpaRv32*8Y}VjlRus8<|hEntX( z#EK)G?HveWq6N{&3?dAHfQ_aRRt1w<_>#V=2*Bk6Bf?Wy4OVD|GLkfX(VQAh=di&z zLrzH6(z%2jiq7=vt0Wzzh_Z0ah@&_%WTi+Y4fs(UnwUt9IZbrZEkHzLa#Sf?Q8Q-( zO0APaNfQZ)W+8**-~@9nXlo_sRH!m6SkjQGSOYL|8CV38KkB4VMAZzLrNIJJ$)BdG zae|x*OAj?R@)kHi#U*2*jHM+C)OwMzrlz<=NuUix$ms%WWHKx&1u2X!L@~f3SZN3= z2n#3WK$YgOxlkUUHFQ=6B!p-oRk;)hE5!-@DPJ8K*m5})H&c5AvH+`4I?du$tD!Cq}ihJK+Bv8Cyvk{%s^F)Dvmtv zA)pEpqfih+5fOnI41>c2Auz%RVm1P5WM?dgV37i*qKvr^;ZUVbEVG!QC?>NIgi3|N zgG-u)R0()QLkB#PhKGPf1Q-3h!%0FY%`Bq zC>AmxG$Ll93PtXqhei}%8kK?^z7{+-V$6=b!hv})~bZ_tv-=FUs>yv}uoBez|o*(ax z(|wOg5zyicKp=!C2L9mP<|AIrN-3ojyCkJt{p)ygsaqUomo|~BN_K(UmM{3g=!Wln z%{y?C)#44WddC}H^^P~Z>g}tHfL84FuYT+6U;S19FMq`sg_}F(*~w;_>e_uiJuGZL z^@>-%>=mzkIe_nZ$M>eTplus7D^2lQB^(ZI)3gHGw&~CM98U~wg3wGOLTQ@a9$}Y> zn+gN%)u|7(8~y~t?rEnFe(-~T^pF3EZ~msgaOck5=W2t#@B6<0zxdt1E4`w>@9%#9 zGt7L}weT_dTR+DI4TpUnuUlea`_+2B?}oLnzDYgzd8>(9;E+c6-P%j;E-|M13H^j& zN2wdu&g706G-5!1io$%Fbb^(Nas}-fu|w$kp<5MUUB5Boh7RMJR6BG)XWS6C3@}AM z?Ckc3RVTQK77ysVWQQ#=3%!OoUO*qRN8O(F{c2h^ogrB~D+g!1s_)w%31IYFxMHZk zfopv^u7Kd!mpJj}w$1J`6ZQi30dy;VcLi(P%}m^Fw^ifiunWJY0m87c0(1G5#CMrR zm1rG%bQ!++^x(q6g-o;#Lx-Wu)ZjtD*bSRdZya~1(P2G<r5dE}>1zV--_Fz7*0(y7j~BfCrZ8v6^?CadMxo{jC<;1|HS#M(CH?6`xix0?LG zdcHObTVi5o9QzLFm-pSiEY5c;gKp3`Xe9bEG$tn7cO$wTxHf12 zdaX^?Qy4Z%Cf_`T043Kllf}{_lL>_kZSRz2tST zf5V^tU;flaC%+_9{qiq+*@catrf>So-~3;H<2PXSdix#!*?;~=|HvQCZ-34T+6eD^ z=KD8%{ntJA*iU}uXT9XH$A0n;{{F9jhMD`)AGp9Xl%5d{Ic=uM+nhsbS}P5$S;-># z%8Z5_U$@c*)*sy=fNKVBIkfHJa4=&{uWsVdHldj3iGUTHyWVqNe)7nw6Yf~& zsm{~v^EgfS?%(^!lOMTr=Yz*_9EWkNF;t8)(O60ij?S9>J4cQ1Y7sUK8V1EZ?>;o$ z{8z&Zf9n3t$B)-OFkkzS_a8xjZq2A>Fiye*t3++&0Kx)M(+P{oRtX43D~efgp)%In z#@2(q)W5wwybJxE2XttG+rzv1n=ih5vwz~nj{r+{M3Nt2=@o=RX%a@*ngz&AdB{)& zH#LZZcrazwVjA!il@ctXF$l_vP$LXvMHvMbX0n2noK$C+#F?5YM-;J84bYa-1zQw` zQoL6oSR+u(USG(hh+a|UMPAPfu_0xqxu z?km6nR*>)%X^mkcGu+Y$iU18LMHv`tk-lN7^CA$AnK365F{!CFA(+e)@j0j>0VicNq8DwaZKku{jrJIQE?>7i&P)gpsSQPK{LH3(&ZZ!xJc znv(yTxCQ}M!$i!fwv&X)Ad47uYvgt?S&)UHs8+}bD_jCoK}SOr7LsOwE3vGID4A9h zgt3N>u$GlnBZ?MiLN-cpC8|_I6`1H$C=@aw0H^^Xs0fy75{nAwRK=H?)M97i%4$r- ziI6KiCI5sQk0>>bH|Z6LHkO83A_t%VXOyfP)e#wF99B)r%O_}2C?HUl;8Ys+qghz+4IhnN(z87$$*e5K5!JsuZ;gG?PjKuo0Ce zMX_oS1I7#!317lwkYv;uB_u~NsWKcG$bj6GyX7ILl&*`GBS2`785bdjgO!P`3kZ|V zz@lgrO3JZO7VeYDAOkDFlT9Zq2U%btIum7mt_CDBn{PPcvAAIuu zllShQAJ5N+`}dCb@15VjKi_-u{Qmj*G}pPhh$@V;4;7=zz-F|=5oMxsV?P0hE;WJ3FGq}>utseT7ZJMj^}o^$>;`5i zCmgsOtg)xs4IMU@w;o9T(Uu#0;!375Vv>3$02mKPwCuQxf*se?h&_RB+0^3$W^U}c zwm-4$CIA@bp+EF{P;WS(2RK370n04pJw%wR^9(|t6uEA_#CGsge*mUofMubp;S{@B z_jv*iBMq1`9Cpus?M}HFi*Mc71F!|ZJ9(ilm(EpwI)H~Ce&|)-`q!?K z+4mpc@z(FU+=d(u|KR)oPX51ny*=IPKgR{#x^?rlul}}=X6AnN2QKgorL$O?A}_!P zPMj}E)^?eW>h7@&kb{c_&4tj3T&rY+=Ce$(de&Th#}Wl`;b~pCFvhJ2ZazT|oV27v z>^yGBSN`7L_PW3F*8KR||Lu*h_&r}*%=WZ~f^7GEuhT3gYtQ*;%^=)^k_wV^M zvnj=jM+_|QzRxN>a)#DUm>mGL8l)J#);hY*4rg6CG%RK)?DJfC)ChVVt+l9htC;|x z2qWm|CV%*TwD-J!LA`UY+`D;pdGfc z9_%xD*gb~ZFT&zscN5%i69%GLYd9rFe%y;w?Y;f z+KWI2f5w7y2@VvBGBP3rlPE6zD+ZL{mzib}4631Op%pTTo&_LFn9Nxb4uNQ^+{i*Y zg0MiKB!{9Pn!=QQM$jcos$HP60uA8=GISu1UkH)JIt!>&M1U%8$V1FUf@YXy6Qcr+ zGDwsZKs4se+zh&;86X<4&T6VW5C*F-Qmxof4yE$Z+*E|n1W7PUoF`OdVI>wzu&lzS zFjyi*Ojcqz&A0G<>7^@a-gJTn+2RyLW+(sOD<{V!w__9DOadL2m)u6HWz}T zxWp(nhuZY_R|dSo5e0z4nBIdT7mGHE9^PcqHPNV4ctuHke@(jyF!3v83=T!gLk458 z7!VA=lV_LB4a}sdFd>P7Wa7C?V*@iXe2G4ebPY|Qmk0t;l1@ja+7^hRr~#LSj0`xUFc2ji?rfTX3h7b_g|#I-7^Is_@L*VJD3Mc=>mZ0OZ$Y6{ zMhiJ;CI=ldpap6!mfCKZxtvDUA`2Cxo52DVm<6MN%%w@`ac$HXqkxqpR6v0U3j^g2 zn}whq=#3Uw+NdVk+Ej&H_KY5O{mmEOeBxcWdGq!oz}ZC)JK*;4uDiGYr@J>FOAovB zmo&n`l*uP1Yf+-e8b$z}G&>BHDAAy)0aIkcY&e}Qu>`KrXhlFmvw^0dgxZtnhe$`i;pWkoV=8bQ7>#|W&X4v~hV&HlwR}$#jwp|T>qgKy$(MlmC$$+M5 zN-5Rjb0BQ?=Ghqe8?$gulkfBb*=@|VBt(MSIy0FOWZ#J_7t?JK_Q z&M{%NaeD;^_U3jQO?n8^O71b>~KH!9r|ve&kRg3 zE_GymztHIVa?JT_5M=H$PZnBGx@eKBp`Y85^I48Nykqf(+n~D5Hg0Dym*=hny1ty! zT37GE5#6{+Rn@2L7ufL%sIw63Ru0rvOS?-7*;Vv9 zj2OmKT^qYy3TlTF-)OV$5<6SPgG{rzD@9FD|8aGr8{3mUbcHy2>cqGl+hM@2mZ#$N zu%4#F?*HiwhqGQA9697EUx3cbnXS8u%rj7n+*QXVa(SN~o4DkZv9^8Rrb*lpI4+m- z_ER0u*|?~KZR`@K8At0!E->^sE-qv|8P#oS+#Vx3>t^&9uehEaFoj^F*s||BfEWP8 z7#Osp4(CgxV&fQ zl?ExPBd!IoOXN-)sVzsZQ~Da=R?KKi76ebvxCtUI=-x>n3p4wg-*=)t7PIg8mbd(l zum1{a|HMfWU-f%m_BCJo^56bn{We@3+2;iUz!!Y+Z{D%`KqohiMw7$vq<^VNE%7UDGx!#jG()Q+!Oqd+zg}o$s!Xh&oR`&k=R@ zdT$<{y8q<8`}gk+<4~snRM>Qf?s|9a#-Z<+Q9N z+J|l&ZXN8CJU;FI{HY(BUi6{ktvl1h$J!5S=V<1z3MF_HRFDBxD9mPHmLLyW3N16) z^h|q7-R(zTc8PGeNXA}t3%7uqkKp#rrBduxOKl1_6k&m{@1WYLNk@%4cF0Ck2?-pA0xaJ=97LxGY2jL7F%# z9B`DQSQ!KyaHiKA!%z_s>2O7f0g9g1U!HLyG>UE3V4~8n7`G9HMdoJ8{7>1UWOo!q zkVPt3D7Xh^&5j5rH>o9x^3a7sOSB9uD5soJnS8$`I&~ChvN9@|0hfv(gD72)5k@jt zGY5oLNEr!vp+mFBXsl9=DpxI>MGsBj455-ONGqmF4QOgcYDyMnB?1LRKyD^msK2G5 zY6BP~6o$+!qKLJrfFi>^HM?plMsMJ4WY@E_gp$ZbBbr8RsYd1kVMRtdWk7W)6ed+L zRBF(PnkIm`2uMRKH^l)FLIr0;lS+Iu$_a)vCCDf)LeE7;$WV3C1qCLHS~Qt?reHLv zphQF%Wd=b`*kUIqK@qITXbCdFqK46k9W1Y*s-*Q<6iW@DTgjYp6Jg#6GEU-%m=P8h zGMOBhL#pmjB`1>*Mw=)BZCs%aA_rbZzZAp$6L5CyfMSfFG;G6S%JCaoVYQtB=d?iLTbJ;UyJ=ZVJv z;F5v_++_3-m+{dVhAKJA23u*U{ZjcO$xMb9-&Fb{6JoI6vMSrxmT+S<%(NPapY1ckN!iJhpa~ zMSmeybeX?7+3^ZKcWIDp<=d?zc3<%|U-GoiKUnViX5~`AeWG}ga`N2LIIXq%)a||W zmZpKpyHgIurD!1iH-bcI8Y$dsWSGf1UGlKoKl40r`VZF35_dYgXU3tsRIzyIsr{q7(AxE8dtz8Y|PT}D*K5n~z~t&rKGuIjh$ z?%@(!Ztce|{GA=iZsqFr7x1@nzvOiBa!*C=Z~>beG41^9mfsrte*ZhW4eGm8-CiN+ zt->w>{o+HnKw86iIN~7m8V?#*j?D7yFlPJ#<|BHoIk78D&}*f%0KJxDJB&#>Tac`7 zT+(9~ySg#g-GX8_2Rbb25XaP??x}6Upkdr1CKA!uk|GjlTkx_}=8pZՋZj~Rd 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

    &O$#t05g8)3#F$~TF6a)qlzyyYR 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

    4|?DQ$L!|=ANXUh zdEFbH{*))b_VsVP;DQGne}m&c#da5ROPv}Kh=3e`f*?2#!``j@)b70h-}0~9!3W;~ zrtpJzR6Rffb^rwI%xz~oQjB46lWX^8rTme%ENm)E(~&&xrkMo5^f;zY_tv++pC>=%c`tb3Fa6?UEl$5hiLT$-mv4C$-=7@b?e6{#L*RJO zx*gga&&c}XdH1;D>PsIL=3jaKBd7jUeD8+Nf$I(sBC|t6Cd%^y0hx&0Sa=Q*Irjjd zNs=Z2Kq6d(ud5vb~NBSQXS#YxW27&Z2yS>RM?zA`t{>EZCSF zf=l93lw&Uq+dY$;Dp(1Rwxowv56kw~A`KytaOY?HXbrpL32E9a{qW*vb?*GE;lx~0)T4VrY3dQR1%8bVa#WZD0OUnZz(kZ%Fr=jc zrbGa+t^)vh?}#wQU|T}(eWS2SiHMMF&W8|6KxPntl!Q{$I*KM)J#7hA%Us`Ka=I>p0V$cq_rEb@Avw?$hwY*x#)I`ifzR6Nmw() zXv}d1Fe_Ubh11^oz;&nFT{(PXRadd9`~sUwxM(tX7ZHt}UX_*6&s#4kWY zoH3beyxB0e+U{jpwrqO4S(D&wp67&aa&d3Nk10g2Ws;imLseBOmUU|iLQxg1KiJqw zMM*_bkaJ06mSz2Z-~41vKi4@|m8A*TdGBM4d7h^cW@IPw-fI#oK`n~HMC;0`HVl!O zV~pe-066DrNzVI3!bRr2C-0ESK%nv5;W#5A$7a3NxPGbH&+ff9aXljgRyqu$>dO{1 zoHS(~%~4|sjy9ZNW*n93lSB4gYp909S}Rxh#S<3yoUlCKo#P>2cg=xR*YisYEU

      Cu;K9R#en~{^aJ_%%p7*+Iac1|18*aGdl1mRC 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

      m33Dh#i;sCvb#>_$lAf;TLTdk@Iicob{HmDic zoh-HuiAbR6LCuOOJJgNb+~~@?mO>g*2qE>us_UTM#^(IxndcMTiB<{!~0N3vpl)$Rv*RYv2EjAdSwbkLR24JtX3O`0Y>a0aA-J4WRjdSKrA4gO1TwEK3uKZ zO#pN}gDPT&&@#bN?gm(3xv>Z82mAST`3j$#;J3!WzJbusR`KodMvTwz~ZkU z7u=c_-jp}mzwRHA5H6i=rhvP=s+NA(*E6*yRc-GZT|X0AwNT%z%MDU8_|i_B_WS?< zt~Pa^&ugOHqP2IGco42Bj}qaw(1TNL&=l-1QFKv-?RwhRVVc-%fF94;A=ju+MEIo&C`wF?>a;WY3$W$ zR;`ko*vf$^^Q$`IugJN4@K_$+pabU4?E9W`p2L^9xOdB)a$YV7^VB;!I+_iwmOfej zs{6ZlaJ!++ceq~7NW{%2ZNl}huVDKV+)LxvX8Ap=`PSpdU+YumRWH8&8{g#d-aGGL zujhg)i9j%OGbJU=ThGD>b-5+2B;M3w15ivWb-U~bo+hmnEV)kt;s#?$Xrs=1O2;`h>;KJ3l$9 zdZnnnhzE#>oZX#-#bw?W=OX;g0-i5q9F~8nzvLr;=8uMGi9Tb{~qL8Q<5I@er`= z8@m~kT)7xsvo^dfSFPdhf!+dBRqv}UUP2s$hoo>nYMS?+e1bx}fB)$hKY04|{H!@S zg;z*S$lXYoIRqAB2MUV1R+5C7XVV~}YHC&$Z$k(*N85uVS6uJ?)_YZ&s$BlMtpf*~)!Aid0W&-H@xyf|s3}A@ zE10~goXFfb#^Odq#Y|#!f`lM3q>_sp1jUSN=q^mcISnFWxx}U&RFBu2v>O`|Hb31Q zwXrePVS733`cg`CuqyA959~{isu~v9z8m}_leVIyQjbNyT5UEc^SizyTF$+~eUUyxyZ<@4m9Q(enoy82nm69(5cz$tVR-iWX z9hnI1F^0BnLkOzX^_!P{EsKfjB`2to2*;=n?R!`bZG#Yt743Hjf}=o0NF(Rs!gLay zC=UZPbJIYgxoS=@<<7* zQl_=qwSK-#K;Go}ZB~laTu8u7Jj;U5tLpVFVF?5PiD?*nM^RVm7?yV0Dv%^FNs5C$A(lY>FMKleWYX`tyYvUwv1;lVp78MP~o2x_#) z>JA*7ubvjBk3enqMAZEw;68Km4rV28Yr(Dn)^c9V^R)U#%?6rFfNV*1oZo_lrrr`J zt1El$cbNG0HO_biV8k+^2YXkW>C`4Is;g70NH;iKTfVO5vf?&Jj;mQ+HM{D|rIg2y zAD_PUsk0BhboZFPnd38``Haq`Z&fQ=%hzT;qrX!R^z8mWz)M6Byp&S0ZFVXP zRp}{=n<9He4)c3!iO%;JA{Iio^({*TYM!JsU^nB5B0~Er?6T!{IbVpbNnBqGIc`JZ znD`a(1O5>TFiR0#~@W6?m;pi~nt<%$Q`G)u2efR(= ztxk@b0FJX(1ZIVkS$cI6b70x`Mm?u>!Y1dmnb|G{zHpD|3hVzSlyAzSnHmlfF6~C&u zmA%*bJ4xO~xfv|6{T%l^{0dhz`7+4JY; z{Ni+Tn#*p;mnIoZLDmKd#2iB#V(i*3HWZ|hup5Umrs8(I-n{+pJ13hX>;3rjXkRRz z-DSC^Ecfo6nBnEi=kBx{w(Y9@=5PC!h%RRx`*FM54MVCRpTct<6v3^1>z#-9o*WmX zNBZEwCm#$uDg`C0TG>a1MO9zyp65KqHXc9PoIF0tIe+EFmw)9qzjE4c+*nOPCLCF& z(D%cO4xNC>I1UI|F!da!*xVjo6vNT(iEs>o17htfZ9F zsQYL1&{Tl7&l~}5C$hmfiuO)I&5?7URDz}=fkGgsQWRv`<|!x?OdO>m5xG*zwaVWe3Jg^s{5pO|b6p4vd4gqk-kc~d z@0EnGM-07`qF&q`wF7)=ElfCih_^nC_kZP$L#pn&jR2fJ#>00pZf7|yc-dc{Lqg{j zz|FVg>C@cd{VKZu%%v;zHJvFsOt5bf$d?~Jj^ly3&4D@fO+M+Z3&L)NBWLkJQ>Xc`_zo?>-O&Sm@B^~?ahqPZDru{y@cF>vBZq+%hB zdiWNi8c)l3ZVuj!yKy(( zl=0xvBU9d8ZoRmbY(=vw3vmz;b|)Bxq6}(C8nCEa)uNKh#&(o;xB#q zE0^Y)5`-lPIh;X`l#&~1F$08zgbnV>rDUc_&|FoEDVRjWmIds6f^ANb*hNxGB2r3; zF;@RpRl8|O2p}{u_gc;1DJVsX{nwae}(7x>a8WX<{fbG?7#(2ufBrHy?8v$FZc$=Hkfx z7{OAkA<>c>4*7D|k3$|tq|~u98B#(c!O14AC=v%To7C3JTfzlLnHzep>%H(Y4q4_z z29V<7`6Uk|t&Gpp^Y>q*i*eYFn^kwbT8FsFx{(#drs+ph!%XxWw!7Z90Q$aH)rzk@ zeE9J3nUybm;n$r>KolepLpTsS)2eBopFP)Pfm`iRqONT&%-Kki}gvD zM;k{~^@8N4W>77gllA7_W_7fB@#5^|_S_vuM@J7%9$sGT00XhRGlbwmV4|0o7u6`X z3DR|4j1e%0ymj2NAf-gq$A~6&%%R-S9&xM?8;+Zz7qA^*pnn zcuS=ej2Yh57TTf(r6=krw$4HQ5+#8+cAat2G@>Yl;H&7SlnSh%5pRQ+f__~4CBku5 z=t$(wZEJxGDU!Omgup?R;3dPHm_rbCHC0%M60$IgS6LfwyN?0vs%M86SGY(OuckR) z_8Wr$H+6P1_gN*H$gV%_SCKb9KBT@*?YaH+>(%(Bo2p>>b+rS%P9*FJV*8_kD_Cvm zbm8vI*YJXQbqidbQVC4VOv0E%^mRt-qzdMkJNy8kq!0%&}wHJ zZaB;=AKDrUECB*s?;M|}-c8!tL8tk0aj0R+3|nB|)vm8vbJ%CxjhJSe0Wvz|4DM^J zw|A5~Zdzc|f;sz@51vj2KGXWcfBcXAvD5pH&t5#edsN@F@n`<@p9ay)mU7_351(K4 zjD%2Q&05MfcT+8Sa$}fUNK!cf?DiccTq`}OuLzr|*A5~gFoLt0k&>#ZTc~_(poVTl z6q>j>J{r!36+oW7G5G!hCw@9nJHeh!*?#A7?8*k3orNJ^h3K*bj^Ig;cQX)_jP^K!D za10z8u#oT4psr^A|FQRGv6f`nb=cZ_pA&KKyKl&sLuF=FceA^igUD{0-8NwpoIV8T z!zKxR_`?(oTmJDtL7-{>$R9~iq*A5<12hfUFeJ*fXbJ`?a}aEiAS{t=QnWcVCCYF& zRqUbILk(Fuzvhy3FQyxD&faUUy%q~KAu_YU z3wd%cR!ChHFk%q`iy?pnLZk4n5eNiE72mf(Sq+u&MeeOIIaQe_cULDdG8iG0)Hm+$ zzIOc^zWV+9J6kCI&hFNelE=wxurApmGA$>QAk!IVpiu?c-C+*%Dux1#=7hBgsqUOS z2n7~~SDEq<0&Q;rczVoo$fB-N*hpAW|q^0vfB$UOfDxa1e@Haf4fN)8bv1;H9K&jLxm= zx|EWYgkr+7Uap3@tC^MRT;WVcqi|l)lJb0K=I(`ARY4S*$R-M?S#c+*k;hq-6lPm< z^#VZQJAa$LKIK0!~ygFXa+gSS4Fc(8!txD=jw{o?}G9Z$ctv8S}SIE%^(+CIi z@V~XBIzB2xIy^4fYoQJ*$&07thzKkYl2VwRjvt)lV{@j1J4g3FcQ0Do%-TzfJ%Qv^ zS2XA1$A02I$iXv_uuSR3yfRsV3gX;*^Nojh-+J`u5hOTJs<85yYdP>3P_tRvzVGFi zZ(P6j$xnUi_doj4gU2UL+#!UjblkRW+qSWdAuu=C+1D+Ud^89M{aU~+h$-Mj|av60vW0B&T~IR;GXE`0vRxS89bC`l+3%)ls-p@k9HiOeh* z7%-*+Z!{>u;0y{N07*+<@<03h!N2_EtH1g?zxTVZd?pvtuwd`$d`%O%( zZmp$EJ&fyLL9K&{NL?3e#);($%LB}I1~s&kgq|Zmu|~=Csb92utehV2%pp!GIp-R3>o&D~x6zk( zKDnK`@IYtGRi^FRwyhrqV(f^OJgHU9&T@s|UMYlp5n6}Qu52m*3=;SlAnWsmjplTZ}Z+zsReCV6L>G{+8r)vDlul&mO>(@_~%W0l0s-O7urzaM< zjE14^j;yYxd8j~6-+MIztuU^eD^#HXv@0=2X(9*VAS4oq%V6+^D#k2C3RFvDawo6& znw3&WIL0{JS#(FMsq|eBF}By7Nlq$J50g7N5yv3vt^)#PV{^&|qNHU2(AP0LB8n`N zKRZsXa(P|N_6#MzDqh#%(ov&f0T=9NDCV%gr*gWTP=Cx zP{54PTS>0&D)5Hfqf{xjhEY=QB@;_Kn>9_N@MP+_FWHkNbAx*Tft85h>_h@_j0#2| z8!97IJ?{v_kcwFm8M8B^dQ*nMDM(x4rRHFMYjNCAwW(g7iz9n zXZxYtVrFK-)d3VnPh2c8Y8Fqqo0A*Vy|^oUK)nKSHaE?r-l(>*@et7Uk4w6`*h;2I zN#SryaMR+I88 zW--JNLN=W$i*=vtXmK;M001BWNkliuU7NeEKiQ8SR_QIivE(BfS7}p#MP-CRGYagyC;ZfWl0if zy+`Kknm(x<);wj(Xod)2RP`X|veKAiYi&ruJ5ERa?$*xMY{3v1&6Bo zc{7g^%&aJ}8O;vO2gso##PN8$N#3ODc>aG|o=s*d9n zj-RE*U?X8T!)P7zkW;pu7ANr7FYNBcO^eo4i>kKKh+@=4Xnp6WbJN%&H%Qk3^yJGU zB8ee1u};ug3r>N~i?!-D?%}CgcA*QgQG(POncGlRstsH2R_ilWKNJQcKr;^SAT^5YQI$vkxxjijoo_v`%^5G4I`*m*bM#EZtKG((X>!B;SF#Y-T z7Chxl&ZB-q7&Kkj+Kj|cn4>0dmUHkT0}jHcKK3u(x_kGf7hlqn>-=+A%FFM2-|v0& zqu>4=-;uiR`D6R1Vf@lB{qnbd>$e^q9hF>$g-gQG;la=T%+K*QvoH(Q){YwG$HZpl zs+dSv<9fHz+Bq!qstzhBp7nVf63g*iCl!7neyO3$D<9aGd05V`8=gm zN;$vUPGzGrt2?v02ZD&0SWz=L5fd(GDOJ_F;!Lh=sk z;WNtZJs;1<^YMIq1;^3JN%rt-3%Q$ZGJWpdOY$g^`Jr*d3&Y7qAQvgLZ=R6ddNKg6cwF8ow zM@y)&oO4>8I$gp50U6V(Um6r@?8d^i2-a(c8kmJSnrq3Vwk34q(xqjP(1tr!9v&X0 ztim!QDWDLfD>`c$b?>@F)7Ex$m}icUe8Mq^@h4Mu=uj+b{c*)pBod|9G{W&*v4VYeUTFhy2uyV_59& zEEbF7Iyhvt{^Vq9 zHusz;rQPivOMOY1+!(|{Fn23$h&6dtxAnDtaG$5)R^?&~3-D`V&zgj+AdgcN>@PpK}L<%bIM(i2O`n8B~)dUo2DrD_k zyN0bLadYo;18Bt>P6MYQglkE1)=T@_m$!G1A0Iw=^DXO2Y?_jE1Vo64U8?MzA3b{X zuJ^ojb@yuH(VVu!0&1mLUZqQWdwnn4+uN(v$!s<|Ssv}~UwZK1!R7tSrIc=!X0v(D zdU)?}6}!*Bb~p8zh-ULPMggo+`OHvC0r}R}j5!dA8#&Zj9cJoe&O}ThiUj0e1B*tKoM=!}`8o z3CCsMH2C-a{C8fOUHe-<`^$Iw9($J=R8~FAFpDBWofa*|K$J$Jmy}Lc&_Y`+q3Ei2 z@4)l70qiE?n~jb75Uz}{bqs6W4sxS0ZO~jh7l?0&%|9tX-*k)fr|Jl-@yphEf9Eyb zRQ)_GNM}tR&JqSs5z`oBZQN5;g>!x2g{$SZX%!bARj7SbDJ3R9r8I8vfQAI=%x|B@ z29OX~#K(e()LB)H)##~;2TNx^iW?&Zk^(WDH*QN@YD>u&f#BwRwuv6$tP>${40ALYqnVf)$R$c zY7VV27!l(X>u^{#Fgjfh$8(5J%DOKE9ZDHFjpV=cdmsJo@48lH?CVP3_5Ig;_1Apj zl~;c1r+@l~e&k2+3>K5m$Cqh*|M!3Y_kG{@-Mo48(W6Hx z9_GHYSgga@r))bOq!kJ04ba^&nI=z9Ds5E7>aK*sWGGR2h4n*9ox6t+DvCD#~yP~q#nz0=V{jP>d!ftv~7JJrol z13(~Vrf1f$yWraY;$MIM8a^M-$Mf+O89($xKeQpE*=&U_na2PkfQjAU=Gtd1**tKa zpskn`1m#3UvQTWz3?-u!hDV4M(yU5Gf;fv5#6t08T1r=RKHs9+V@l@k*)_p>LuOJ} zb22j+g25@k>?n6VI|qRPji>8$z$9a8)UH5n5wKcbtQkT~?MUEOg+f?&rrg2p@GsWoJyMBRViSAU%J?DZ{tm|t3 z@}xIS4kGWmE&xRnlfX=Ksq1D9eg;905b6m}b-oaS!LycS>Xp#W+h!IG>_l1BHCMR_ zcNfCoWnmBwHQ;H89^Jh+<3Oqn0jfwzU`-Iu$%>L2%!wUFWDc)#iB&L{s;#LLYGYO_bzp5HIrv<IM$A>3b)A8}~-rk;?cAh+26b=zl zLZEPbd|dPN!k)QXas6hsX__%ZsXHH<{AOF*7+aYJrohk}n_Cj3^60I5rIc%X`|-UL zWPWnurXg#|Ij7X++?P@c^8WTE&bar+TV>T3O@M_qm|4>_WmRC7m&vW192};u?~j*E z^!VN*&E{ZAVn8sV!k!fFs=7MSj@+R{E+RP({8=-YIh=_iGa#k5d{jT>z|0hwXx`32 z=I9R&?>%^{d+V(?-u2Q;dwYATN+hL})R&xaeAFEsE+0I2^c9>cYnnL}7)B3e4M10P zl@?p{T_67HdvATy-~K1Ry;30ca7PpbK{3w-1T=x!a>|~2neS-Z5|NdRA_#)^?EpJ9 zJ7-e>FUou|nv_yL#mlOM6^KA#tf&tO!$@QsI!6HG`X@skJ8@@5*vr=e!Rhrl<=3cf zrpL9i9ogM0r4*BQI7k?|dee+po59o!=2LV&VFyGMV;D08RD_+Gm_?l>ggS-Q_G|!= z#ghv~5G&L{BB5Rg^(G?K^oN+Gi6pFUYtt@>xt2v3uu>~LW`KHeuEMUgXy`uy11}yAGIqnGQ*iGySnT89{C9D14-Y?w@Mi7{T*v2*Ahh7EL8q~R^YXr8&^~+DK z&^P9tKw7kT0fB{?+2Pr85C&{ec!U$zfXNYtZvARH1;DeZmsXnf5<*@3MZ~9=qvCEp zvEOVR5ImLL^)O)OKIt6;@HHTNpcz(PtQpn-jv6*uE&Z*l7q4kzl2<{{AV2#vKl>fu z_HDA({O-W<$;tlSrN8=D{>s1qm;TbLufF=3KmN?)!z1G7PxU+U<`}lOX773TyT9gZ zzwYX_t9_qdd+l{|Ut9F3gz}I6(JyfnjufLfT#8C2EzndtjOw4YCk3c|tW2V=$%>FL zoZN$(5O?bA05cK0Q%=gFp%ocdmTXor>>A_7MpR)%p(eyF3n^%ojxI^uZDxXWbYjIr z8;z+i%4St}hnd07fgt3J61X8@Fp6o;l(U)!l|7jg+d@{x}`e=DDl=i~W!hsXc$4*C)TVR$2axsAkItzMpyzx7BS z9WR&JdheUXcHbkrF$;^3h?y~YY#O4maYU7>Qaq9C*sn=MUgiJTnXKrPX4X8qc8Q#| zeLJmZt11t4j4`@5hdFs&-Ui8atJ-p^;%K8#6M_bcO$@?LkRZeY)I=EtZU!}S14%Kh z$X#S18+5JyQFRt-Q1Aa;Vgc_E?-4Flj)V%=b|h_M^M;pRr*K&0596KTy-MrNJ^PUOsR z2Q!Ce*0?#7cgRv4++emk=(FTL{{8!({p=ry z5MF-ISMBZXUA?k5n{AoOn{U4L@WD|C&BkQ)qd)qi@61$L`6UytYq40o_~MJ#uU~)P z`(J+d<*PQEJvceO+AhBJgYWyzS3ddAKlunHW6_d`cfIC#nu0@0C9O`DVX-K&C(prVaYHNF_ z1&Py>pGrSI*dYzP+)84YGL230;8-21H*l()P#H`usMuuCQ=8)_NwVPt&N{o&>*7kNxSsQ^3RpbjT+q~H9=uYL4)f9Jy={@44|uS<0v9UO?r z?#|BFe%;r7;DaB0j&r}$ua4@;@yVw@^{G^foUKqv)4cJi*Z-Hl_xGC1tp`e)&5Gti z&M-@+9Gdlt_vy?DBTxv{Wz|qr;RKHoMu-aTFxA{AR||_!|4>9ml{9La#&g-wps1Br zE6TA6v(|Izm%XYwc%Y}%^bnSN+kyp5eJ#Kvs3(9cydIsWZ4{A;B?Aw zb&}akbttITz17vnb*KX|5!7I=ZdS|!O)ne!Y36gvq_e@wX}E8u>+jBl`7@1T&*o

      Table viewer:

      HUD:

      +

      Graph viewer:

      +

      Working with 2 sites:

      +

      Working stacked:

      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°5Qev^8Y(OlMiHOIpw=s^6m$lY1 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 TFK^%ftirOAiV`(qMuGnaf{80y literal 0 HcmV?d00001 diff --git a/website/img/Screenshot-Graph-v137.png b/website/img/Screenshot-Graph-v137.png new file mode 100644 index 0000000000000000000000000000000000000000..ae281d711e51293226a8705446bba46d6e6bcb80 GIT binary patch literal 42998 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 z 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 z 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 Date: Sun, 26 Oct 2008 18:31:55 +0900 Subject: [PATCH 222/262] Updated screenshots page to reference new screenshots --- website/screenshots.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/screenshots.php b/website/screenshots.php index dbc7ff1c..fb13138e 100755 --- a/website/screenshots.php +++ b/website/screenshots.php @@ -12,6 +12,9 @@ require 'sidebar.php';

    From 7b604e3bec6bae2cfdddc43536b4cee1d2fcb534 Mon Sep 17 00:00:00 2001 From: eblade Date: Sun, 26 Oct 2008 05:34:36 -0400 Subject: [PATCH 223/262] Table HUD window now child of poker table window --- pyfpdb/Hud.py | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index bf23a619..a2c7ab63 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -35,6 +35,7 @@ import gobject if os.name == 'nt': import win32gui import win32con + import win32api # FreePokerTools modules import Tables # needed for testing only @@ -89,7 +90,13 @@ class Hud: self.main_window.show_all() # set_keep_above(1) for windows - if os.name == 'nt': self.topify_window(self.main_window) + if os.name == 'nt': + self.topify_window(self.main_window) + +# set as child of poker table + window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) + self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0]) + self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) def on_button_press(self, widget, event): if event.button == 3: @@ -241,14 +248,20 @@ class Hud: for w in tl_windows: if w[1] == unique_name: -# win32gui.ShowWindow(w[0], win32con.SW_HIDE) -# style = win32gui.GetWindowLong(w[0], win32con.GWL_EXSTYLE) -# style |= win32con.WS_EX_TOOLWINDOW -# style &= ~win32con.WS_EX_APPWINDOW -# win32gui.SetWindowLong(w[0], win32con.GWL_EXSTYLE, style) -# win32gui.ShowWindow(w[0], win32con.SW_SHOW) + #win32gui.ShowWindow(w[0], win32con.SW_HIDE) + #window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) + #self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0]) + #self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) + #win32gui.ShowWindow(w[0], win32con.SW_SHOW) + + style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE) + #style |= win32con.WS_EX_TOOLWINDOW + #style &= ~win32con.WS_EX_APPWINDOW + style |= win32con.WS_CLIPCHILDREN + win32gui.SetWindowLong(self.table.number, win32con.GWL_EXSTYLE, style) - win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) + + #win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) # notify_id = (w[0], # 0, @@ -363,16 +376,15 @@ class Stat_Window: for w in tl_windows: if w[1] == unique_name: - win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) -# notify_id = (w[0], -# 0, -# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, -# win32con.WM_USER+20, -# 0, -# '') -# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) -# - window.set_title(real_name) + + #win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) + +# style = win32gui.GetWindowLong(w[0], win32con.GWL_EXSTYLE) +# style |= win32con.WS_EX_TOOLWINDOW +# style &= ~win32con.WS_EX_APPWINDOW +# win32gui.SetWindowLong(w[0], win32con.GWL_EXSTYLE, style) + win32gui.ShowWindow(w[0], win32con.SW_SHOW) + window.set_title(real_name) def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() @@ -529,7 +541,7 @@ if __name__== "__main__": c = Configuration.Config() #tables = Tables.discover(c) - t = Tables.discover_table_by_name(c, "Southend") + t = Tables.discover_table_by_name(c, "Corona") if t is None: print "Table not found." db = Database.Database(c, 'fpdb', 'holdem') From d07fe6c5c698f31810a866dc347854d393819ea6 Mon Sep 17 00:00:00 2001 From: eblade Date: Sun, 26 Oct 2008 05:39:43 -0400 Subject: [PATCH 224/262] move re-parenting code back into windows-only topify_window() function, as it is not 100% system independent (more like 99%) --- pyfpdb/Hud.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index a2c7ab63..42579eac 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -93,11 +93,6 @@ class Hud: if os.name == 'nt': self.topify_window(self.main_window) -# set as child of poker table - window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) - self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0]) - self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) - def on_button_press(self, widget, event): if event.button == 3: widget.popup(None, None, None, event.button, event.time) @@ -249,9 +244,9 @@ class Hud: for w in tl_windows: if w[1] == unique_name: #win32gui.ShowWindow(w[0], win32con.SW_HIDE) - #window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) - #self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0]) - #self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) + window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) + self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0]) + self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) #win32gui.ShowWindow(w[0], win32con.SW_SHOW) style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE) From a1c6fa1dc6b626b9f3831e4cdca9131b403d0c40 Mon Sep 17 00:00:00 2001 From: eblade Date: Sun, 26 Oct 2008 06:09:29 -0400 Subject: [PATCH 225/262] Fix posix discover by name typo/i'm a dummy error --- pyfpdb/Tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index e480d4c6..6afbddea 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -113,7 +113,7 @@ def discover_posix(c): def discover_posix_by_name(c, tablename): tables = discover_posix(c) for t in tables: - if t.name.find(tablename) > -1: + if tables[t].name.find(tablename) > -1: return t return None From 628f71cf3d34f0947feb70c5a64cf8c43b8fe32f Mon Sep 17 00:00:00 2001 From: eblade Date: Sun, 26 Oct 2008 06:49:47 -0400 Subject: [PATCH 226/262] fix return value for same function i just thought i fixed but didn't all the way --- pyfpdb/Tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index 6afbddea..a5090fd7 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -114,7 +114,7 @@ def discover_posix_by_name(c, tablename): tables = discover_posix(c) for t in tables: if tables[t].name.find(tablename) > -1: - return t + return tables[t] return None # From a1499db849de4a084bd00c69fb7d6d88b742dc58 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 26 Oct 2008 20:13:30 +0900 Subject: [PATCH 227/262] Fix return type of tables_by_name discovery for posix --- pyfpdb/Tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index 6afbddea..a5090fd7 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -114,7 +114,7 @@ def discover_posix_by_name(c, tablename): tables = discover_posix(c) for t in tables: if tables[t].name.find(tablename) > -1: - return t + return tables[t] return None # From 051f04b69f54572efddad165032b9f11aeafb4c1 Mon Sep 17 00:00:00 2001 From: eblade Date: Sun, 26 Oct 2008 15:51:12 -0400 Subject: [PATCH 228/262] trying to get parenting of stats windows to work in nix. --- pyfpdb/Hud.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 42579eac..28cae1d8 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -92,6 +92,10 @@ class Hud: # set_keep_above(1) for windows if os.name == 'nt': self.topify_window(self.main_window) + else: + window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) + self.main_window.gdkhandle = gtk.gdk.window_foreign_new(window.window.xid) + self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) def on_button_press(self, widget, event): if event.button == 3: @@ -107,6 +111,7 @@ class Hud: def save_layout(self, *args): new_layout = [] +# todo: have the hud track the poker table's window position regularly, don't forget to update table.x and table.y. for sw in self.stat_windows: loc = self.stat_windows[sw].window.get_position() new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) From 70bdc0bcb46683a192a18b09e82c9c1d46310017 Mon Sep 17 00:00:00 2001 From: eblade Date: Mon, 27 Oct 2008 03:12:12 -0400 Subject: [PATCH 229/262] fix for re-parenting to work in nix, remove decorations from table hud main window --- pyfpdb/Hud.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 28cae1d8..baafaddf 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -67,11 +67,11 @@ class Hud: self.main_window.set_keep_above(True) self.main_window.set_title(table.name + " FPDBHUD") self.main_window.connect("destroy", self.kill_hud) + self.main_window.set_decorated(False) #self.main_window.set_transient_for(parent.get_toplevel()) self.ebox = gtk.EventBox() - self.label = gtk.Label("Close this window to\nkill the HUD for\n %s\nMinimizing it hides stats." % - (table.name)) + self.label = gtk.Label("Right click to close HUD for %s\nor Save Stat Positions." % (table.name)) self.main_window.add(self.ebox) self.ebox.add(self.label) self.main_window.move(self.table.x, self.table.y) @@ -93,9 +93,12 @@ class Hud: if os.name == 'nt': self.topify_window(self.main_window) else: - window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) - self.main_window.gdkhandle = gtk.gdk.window_foreign_new(window.window.xid) - self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) + self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) + self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) + self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) + #window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) + #self.main_window.gdkhandle = gtk.gdk.window_foreign_new(window.window.xid) + #self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) def on_button_press(self, widget, event): if event.button == 3: From 2bde12a6cf54d11da3dec7735e47ed99c893c84f Mon Sep 17 00:00:00 2001 From: eblade Date: Mon, 27 Oct 2008 03:13:04 -0400 Subject: [PATCH 230/262] remove set keep above on stat windows --- pyfpdb/Hud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index baafaddf..c4099b5b 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -64,7 +64,7 @@ class Hud: self.main_window = gtk.Window() # self.window.set_decorated(0) self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.main_window.set_keep_above(True) + #self.main_window.set_keep_above(True) self.main_window.set_title(table.name + " FPDBHUD") self.main_window.connect("destroy", self.kill_hud) self.main_window.set_decorated(False) @@ -335,7 +335,7 @@ class Stat_Window: self.window = gtk.Window() self.window.set_decorated(0) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.window.set_keep_above(1) + #self.window.set_keep_above(1) self.window.set_title("%s" % seat) self.window.set_property("skip-taskbar-hint", True) self.window.set_transient_for(parent.main_window) From 5f15a4f9286befff3bc0fa745efa318e25bae13d Mon Sep 17 00:00:00 2001 From: eblade Date: Mon, 27 Oct 2008 06:29:39 -0400 Subject: [PATCH 231/262] Configuration.py: add "bgcolor" and "fgcolor" to node HUD: table hud and stat windows respect "bgcolor" and "fgcolor" on a per site basis Tables: force tw.number to be an int in Unix --- pyfpdb/Configuration.py | 8 ++++++++ pyfpdb/HUD_main.py | 1 - pyfpdb/Hud.py | 21 ++++++++++++++++++--- pyfpdb/Tables.py | 2 +- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 4e29dba1..c6262c54 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -53,6 +53,14 @@ class Site: self.site_path = node.getAttribute("site_path") self.HH_path = node.getAttribute("HH_path") self.decoder = node.getAttribute("decoder") + + self.hudbgcolor = node.getAttribute("bgcolor") + if self.hudbgcolor == "": + self.hudbgcolor = "#FFFFFF" + self.hudfgcolor = node.getAttribute("fgcolor") + if self.hudfgcolor == "": + self.hudfgcolor = "#000000" + self.layout = {} for layout_node in node.getElementsByTagName('layout'): diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 8217deb5..a157cf46 100644 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -29,7 +29,6 @@ Main for FreePokerTools HUD. # to do no hud window for hero # to do things to add to config.xml # to do font and size -# to do bg and fg color # to do opacity # Standard Library modules diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index c4099b5b..940050ac 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -59,9 +59,9 @@ class Hud: self.stat_windows = {} self.popup_windows = {} self.font = pango.FontDescription("Sans 8") - + # Set up a main window for this this instance of the HUD - self.main_window = gtk.Window() + self.main_window = gtk.Window() # self.window.set_decorated(0) self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) #self.main_window.set_keep_above(True) @@ -72,8 +72,15 @@ class Hud: self.ebox = gtk.EventBox() self.label = gtk.Label("Right click to close HUD for %s\nor Save Stat Positions." % (table.name)) + + self.label.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudbgcolor)) + self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudfgcolor)) + self.main_window.add(self.ebox) self.ebox.add(self.label) + self.ebox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudbgcolor)) + self.ebox.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudfgcolor)) + self.main_window.move(self.table.x, self.table.y) # A popup window for the main window @@ -351,9 +358,17 @@ class Stat_Window: self.label.append([]) for c in range(self.game.cols): self.e_box[r].append( gtk.EventBox() ) + + self.e_box[r][c].modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudbgcolor)) + self.e_box[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudfgcolor)) + Stats.do_tip(self.e_box[r][c], 'farts') self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) self.label[r].append( gtk.Label('xxx') ) + + self.label[r][c].modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudbgcolor)) + self.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudfgcolor)) + self.e_box[r][c].add(self.label[r][c]) self.e_box[r][c].connect("button_press_event", self.button_press_cb) # font = pango.FontDescription("Sans 8") @@ -544,7 +559,7 @@ if __name__== "__main__": c = Configuration.Config() #tables = Tables.discover(c) - t = Tables.discover_table_by_name(c, "Corona") + t = Tables.discover_table_by_name(c, "Chelsea") if t is None: print "Table not found." db = Database.Database(c, 'fpdb', 'holdem') diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index a5090fd7..752c5929 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -96,7 +96,7 @@ def discover_posix(c): if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup tw = Table_Window() tw.site = c.supported_sites[s].site_name - tw.number = mo.group(1) + tw.number = int(mo.group(1), 0) tw.title = mo.group(2) tw.width = int( mo.group(3) ) tw.height = int( mo.group(4) ) From 64dbe3237dff1740eb0936412eab3c1b9f2c4ad7 Mon Sep 17 00:00:00 2001 From: eblade Date: Mon, 27 Oct 2008 07:12:04 -0400 Subject: [PATCH 232/262] add "hudopacity" to site config, valid settings are potentially from 0.00 to 1.00 --- pyfpdb/Configuration.py | 6 ++++++ pyfpdb/Hud.py | 1 + 2 files changed, 7 insertions(+) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index c6262c54..4690a84c 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -53,6 +53,12 @@ class Site: self.site_path = node.getAttribute("site_path") self.HH_path = node.getAttribute("HH_path") self.decoder = node.getAttribute("decoder") + + self.hudopacity = node.getAttribute("hudopacity") + if self.hudopacity == "": + self.hudopacity = 0.90 + else: + self.hudopacity = float(self.hudopacity) self.hudbgcolor = node.getAttribute("bgcolor") if self.hudbgcolor == "": diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 940050ac..e9ac814c 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -341,6 +341,7 @@ class Stat_Window: self.window = gtk.Window() self.window.set_decorated(0) + self.window.set_opacity(parent.config.supported_sites[self.table.site].hudopacity) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) #self.window.set_keep_above(1) self.window.set_title("%s" % seat) From 56d80d785c0fbddbbfe2e420d7446590fab32524 Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 27 Oct 2008 20:14:08 +0900 Subject: [PATCH 233/262] HUD chmod --- pyfpdb/HUD_main.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 pyfpdb/HUD_main.py diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py old mode 100644 new mode 100755 From 9f84cc93a7bf0a5d441dfb88527eacb9040f282e Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 27 Oct 2008 12:56:09 -0400 Subject: [PATCH 234/262] edits to get the stacked window mods working with X --- pyfpdb/Hud.py | 10 +++++----- pyfpdb/Tables.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 28cae1d8..f286674d 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -64,7 +64,7 @@ class Hud: self.main_window = gtk.Window() # self.window.set_decorated(0) self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.main_window.set_keep_above(True) +# self.main_window.set_keep_above(True) self.main_window.set_title(table.name + " FPDBHUD") self.main_window.connect("destroy", self.kill_hud) #self.main_window.set_transient_for(parent.get_toplevel()) @@ -93,9 +93,9 @@ class Hud: if os.name == 'nt': self.topify_window(self.main_window) else: - window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number)) - self.main_window.gdkhandle = gtk.gdk.window_foreign_new(window.window.xid) - self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) + self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) # gets a gdk handle for poker client + self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) # gets a gdk handle for the hud table window + self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) # def on_button_press(self, widget, event): if event.button == 3: @@ -332,7 +332,7 @@ class Stat_Window: self.window = gtk.Window() self.window.set_decorated(0) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.window.set_keep_above(1) +# self.window.set_keep_above(1) self.window.set_title("%s" % seat) self.window.set_property("skip-taskbar-hint", True) self.window.set_transient_for(parent.main_window) diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index a5090fd7..752c5929 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -96,7 +96,7 @@ def discover_posix(c): if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup tw = Table_Window() tw.site = c.supported_sites[s].site_name - tw.number = mo.group(1) + tw.number = int(mo.group(1), 0) tw.title = mo.group(2) tw.width = int( mo.group(3) ) tw.height = int( mo.group(4) ) From 174b7ecfa08580fe62812e9d02f6bd19eb5573ed Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 28 Oct 2008 09:37:11 -0400 Subject: [PATCH 235/262] permission changes only Please enter the commit message for your changes. --- pyfpdb/HUD_main.py | 0 pyfpdb/fpdb_db.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 pyfpdb/HUD_main.py mode change 100644 => 100755 pyfpdb/fpdb_db.py diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py old mode 100644 new mode 100755 diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py old mode 100644 new mode 100755 From 87c82df4b2b81a18889159740b6beb615e61c2b8 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 29 Oct 2008 23:35:47 +0900 Subject: [PATCH 236/262] Merge branch 'master' of git://git.assembla.com/free_poker_tools Conflicts: pyfpdb/Hud.py --- pyfpdb/Hud.py | 19 ++++++++----------- pyfpdb/fpdb_import.py | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index e9ac814c..2d67c7ca 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -59,12 +59,12 @@ class Hud: self.stat_windows = {} self.popup_windows = {} self.font = pango.FontDescription("Sans 8") - -# Set up a main window for this this instance of the HUD - self.main_window = gtk.Window() + +# Set up a main window for this this instance of the HUD + self.main_window = gtk.Window() # self.window.set_decorated(0) self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) - #self.main_window.set_keep_above(True) +# self.main_window.set_keep_above(True) self.main_window.set_title(table.name + " FPDBHUD") self.main_window.connect("destroy", self.kill_hud) self.main_window.set_decorated(False) @@ -100,12 +100,9 @@ class Hud: if os.name == 'nt': self.topify_window(self.main_window) else: - self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) - self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) - self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) - #window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) - #self.main_window.gdkhandle = gtk.gdk.window_foreign_new(window.window.xid) - #self.main_window.gdkhandle.set_transient_for(window.parentgdkhandle) + self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) # gets a gdk handle for poker client + self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) # gets a gdk handle for the hud table window + self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) # def on_button_press(self, widget, event): if event.button == 3: @@ -343,7 +340,7 @@ class Stat_Window: self.window.set_decorated(0) self.window.set_opacity(parent.config.supported_sites[self.table.site].hudopacity) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) - #self.window.set_keep_above(1) +# self.window.set_keep_above(1) self.window.set_title("%s" % seat) self.window.set_property("skip-taskbar-hint", True) self.window.set_transient_for(parent.main_window) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index fb170240..4a855d1d 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -138,7 +138,7 @@ class Importer: # ^^ May not work on windows for dir in self.dirlist: for file in os.listdir(dir): - self.filelist = self.filelist + [dir+os.sep+file] + self.filelist = self.filelist + [os.path.join(dir, file)] self.filelist = list(set(self.filelist)) From f26baac94cc2bc00b844e91a7784c8e0206551d3 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 29 Oct 2008 22:37:05 -0400 Subject: [PATCH 237/262] added some configuration accessors for Hud.py and some cleanup --- pyfpdb/Configuration.py | 53 ++++++++++++++++++++---------- pyfpdb/Hud.py | 72 ++++++++--------------------------------- 2 files changed, 49 insertions(+), 76 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 4690a84c..6e38d50f 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -53,19 +53,9 @@ class Site: self.site_path = node.getAttribute("site_path") self.HH_path = node.getAttribute("HH_path") self.decoder = node.getAttribute("decoder") - self.hudopacity = node.getAttribute("hudopacity") - if self.hudopacity == "": - self.hudopacity = 0.90 - else: - self.hudopacity = float(self.hudopacity) - self.hudbgcolor = node.getAttribute("bgcolor") - if self.hudbgcolor == "": - self.hudbgcolor = "#FFFFFF" self.hudfgcolor = node.getAttribute("fgcolor") - if self.hudfgcolor == "": - self.hudfgcolor = "#000000" self.layout = {} @@ -88,7 +78,7 @@ class Site: if key == 'layout': continue value = getattr(self, key) if callable(value): continue - temp = temp + ' ' + key + " = " + value + "\n" + temp = temp + ' ' + key + " = " + str(value) + "\n" for layout in self.layout: temp = temp + "%s" % self.layout[layout] @@ -372,6 +362,33 @@ class Config: paths['bulkImport-defaultPath'] = "default" return paths + def get_default_colors(self, site = "PokerStars"): + colors = {} + try: + colors['hudopacity'] = float(self.supported_sites[site].hudopacity) + except: + colors['hudopacity'] = 0.90 + try: + colors['hudbgcolor'] = float(self.supported_sites[site].hudbgcolor) + except: + colors['hudbgcolor'] = "#FFFFFF" + try: + colors['hudfgcolor'] = float(self.supported_sites[site].hudbgcolor) + except: + colors['hudfgcolor'] = "#000000" + return colors + + def get_locations(self, site = "PokerStars", max = "8"): + + try: + locations = self.supported_sites[site].layout[max].location + except: + locations = ( ( 0, 0), (684, 61), (689, 239), (692, 346), + (586, 393), (421, 440), (267, 440), ( 0, 361), + ( 0, 280), (121, 280), ( 46, 30) ) + return locations + + if __name__== "__main__": c = Config() @@ -403,17 +420,19 @@ if __name__== "__main__": print "----------- END MUCKED WINDOW FORMATS -----------" print "\n----------- IMPORT -----------" - print c.imp +# print c.imp print "----------- END IMPORT -----------" print "\n----------- TABLE VIEW -----------" - print c.tv +# print c.tv print "----------- END TABLE VIEW -----------" c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) c.save(file="testout.xml") - print "db = ", c.get_db_parameters() - print "tv = ", c.get_tv_parameters() - print "imp = ", c.get_import_parameters() - print "paths = ", c.get_default_paths("PokerStars") + print "db = ", c.get_db_parameters() +# print "tv = ", c.get_tv_parameters() +# print "imp = ", c.get_import_parameters() + print "paths = ", c.get_default_paths("PokerStars") + print "colors = ", c.get_default_colors("PokerStars") + print "locs = ", c.get_locations("PokerStars", 8) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index dc4ab242..0cb4062b 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -55,6 +55,7 @@ class Hud: self.db_name = db_name self.deleted = False self.stacked = True + self.colors = config.get_default_colors(self.table.site) self.stat_windows = {} self.popup_windows = {} @@ -73,13 +74,13 @@ class Hud: self.ebox = gtk.EventBox() self.label = gtk.Label("Right click to close HUD for %s\nor Save Stat Positions." % (table.name)) - self.label.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudbgcolor)) - self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudfgcolor)) + self.label.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudbgcolor'])) + self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor'])) self.main_window.add(self.ebox) self.ebox.add(self.label) - self.ebox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudbgcolor)) - self.ebox.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(config.supported_sites[self.table.site].hudfgcolor)) + self.ebox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudbgcolor'])) + self.ebox.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor'])) self.main_window.move(self.table.x, self.table.y) @@ -153,58 +154,11 @@ class Hud: # windows via calls to the Stat_Window class adj = self.adj_seats(hand, config) + loc = self.config.get_locations(self.table.site, self.max) + # create the stat windows for i in range(1, self.max + 1): - # the below IF always appears to be passed as TRUE, I don't know why. If you have an 8-max game, but - # your config file doesn't understand 8 max, it's a crash. It was even a crash when I tried using the - # full-fledged exception handling blocks. - # - Eric - - if self.max in config.supported_sites[self.table.site].layout: - (x, y) = config.supported_sites[self.table.site].layout[self.max].location[adj[i]] - else: - if i == 1: - x = 684 - y = 61 - elif i == 2: - x = 689 - y = 239 - elif i == 3: - x = 692 - y = 346 - elif i == 4: - x = 586 - y = 393 - elif i == 5: - x = 421 - y = 440 - elif i == 6: - x = 267 - y = 440 - elif i == 7: - x = 0 - y = 361 - elif i == 8: - x = 0 - y = 280 - elif i == 9: - x = 121 - y = 280 - elif i == 10: - x = 46 - y = 30 - - sys.stderr.write("at location "+str(x)+" "+str(y)+"\n") - sys.stderr.write("config:"+str(config)+"\n") - gameslist = config.supported_games - sys.stderr.write("supported games:"+str(gameslist)+"\n") - sys.stderr.write("desired game:"+str(self.poker_game)+"\n") - thisgame = gameslist['holdem'] - sys.stderr.write("this game:"+str(thisgame)+"\n") - # the above code looks absolutely completely useless. The below line was freezing the interpreter, so I added the above lines to try and debug - # which piece of the below line was causing it to lock up. Adding the "thisgame = gameslist['holdem']" line fixed it, for some unknown reason. - # removing any one of the lines above causes the interpreter to freeze for me on the next statement. - # -eric + (x, y) = loc[adj[i]] self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], parent = self, table = self.table, @@ -338,7 +292,7 @@ class Stat_Window: self.window = gtk.Window() self.window.set_decorated(0) - self.window.set_opacity(parent.config.supported_sites[self.table.site].hudopacity) + self.window.set_opacity(parent.colors['hudopacity']) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) # self.window.set_keep_above(1) @@ -358,15 +312,15 @@ class Stat_Window: for c in range(self.game.cols): self.e_box[r].append( gtk.EventBox() ) - self.e_box[r][c].modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudbgcolor)) - self.e_box[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudfgcolor)) + self.e_box[r][c].modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.colors['hudbgcolor'])) + self.e_box[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.colors['hudfgcolor'])) Stats.do_tip(self.e_box[r][c], 'farts') self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) self.label[r].append( gtk.Label('xxx') ) - self.label[r][c].modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudbgcolor)) - self.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.config.supported_sites[self.table.site].hudfgcolor)) + self.label[r][c].modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.colors['hudbgcolor'])) + self.label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(parent.colors['hudfgcolor'])) self.e_box[r][c].add(self.label[r][c]) self.e_box[r][c].connect("button_press_event", self.button_press_cb) From cf2c7e42670193f9da9ff6c086221156f3a63960 Mon Sep 17 00:00:00 2001 From: eblade Date: Thu, 30 Oct 2008 00:03:28 -0400 Subject: [PATCH 238/262] add lines with "($0 in chips)" to list of things to ignore, as a player with no chips cannot possibly be in the hand, and the lines often contain blank usernames on p4e, which are breaking the parser. --- pyfpdb/fpdb_simple.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 217b9163..ab442d85 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -330,6 +330,8 @@ def filterCrap(site, hand, isTourney): toRemove.append(hand[i]) elif (hand[i].find(" out of hand ")!=-1): hand[i]=hand[i][:-56] + elif (hand[i].find("($0 in chips)") != -1): + toRemove.append(hand[i]) elif (hand[i]=="*** HOLE CARDS ***"): toRemove.append(hand[i]) elif (hand[i].endswith("has been disconnected")): From 6eca3fdce7b00d9efdec13102b22e882ffd24d5c Mon Sep 17 00:00:00 2001 From: eblade Date: Thu, 30 Oct 2008 05:30:29 -0400 Subject: [PATCH 239/262] merge in latest ray changes, replace excessive exception handling with a couple of if blocks in get_default_colors - there were definite errors in there not getting cauhgt due to the exception handling --- pyfpdb/Configuration.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 6e38d50f..ed90bf05 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -364,18 +364,18 @@ class Config: def get_default_colors(self, site = "PokerStars"): colors = {} - try: - colors['hudopacity'] = float(self.supported_sites[site].hudopacity) - except: + if self.supported_sites[site].hudopacity == "": colors['hudopacity'] = 0.90 - try: - colors['hudbgcolor'] = float(self.supported_sites[site].hudbgcolor) - except: + else: + colors['hudopacity'] = float(self.supported_sites[site].hudopacity) + if self.supported_sites[site].hudbgcolor == "": colors['hudbgcolor'] = "#FFFFFF" - try: - colors['hudfgcolor'] = float(self.supported_sites[site].hudbgcolor) - except: + else: + colors['hudbgcolor'] = self.supported_sites[site].hudbgcolor + if self.supported_sites[site].hudfgcolor == "": colors['hudfgcolor'] = "#000000" + else: + colors['hudfgcolor'] = self.supported_sites[site].hudfgcolor return colors def get_locations(self, site = "PokerStars", max = "8"): From e154bdd8f47efdc2486d411578160cf2bb3f90f3 Mon Sep 17 00:00:00 2001 From: eblade Date: Fri, 31 Oct 2008 14:34:41 -0400 Subject: [PATCH 240/262] Add "hudprefix" and "hudsuffix" properties to each of the "STAT" nodes in the config, will display them before/after stats on the HUD, as appropriate. (note that due to the use of a table in the hud display, it can get a little.. ugly.. looking) Add "playername" to list of available stats MIGHT close huds when poker table is closed in Nix. It doesn't in Windows, but it should. --- pyfpdb/Hud.py | 13 +++++++++---- pyfpdb/Stats.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 1d50022e..f1d0cfae 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -102,7 +102,9 @@ class Hud: else: self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) # gets a gdk handle for poker client self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) # gets a gdk handle for the hud table window - self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) # + self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) # + + self.main_window.set_destroy_with_parent(True) def on_button_press(self, widget, event): if event.button == 3: @@ -186,8 +188,10 @@ class Hud: self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] for r in range(0, config.supported_games[self.poker_game].rows): for c in range(0, config.supported_games[self.poker_game].cols): + this_stat = config.supported_games[self.poker_game].stats[self.stats[r][c]] number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) - self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(number[1]) + statstring = this_stat.hudprefix + str(number[1]) + this_stat.hudsuffix + self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(statstring) tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \ number[3] + ", " + number[4] Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip) @@ -266,7 +270,7 @@ class Stat_Window: Popup_window(widget, self) return False - def double_click(self, widget, event, *args): + def double_click(self, widget, event, *args): self.toggle_decorated(widget) def toggle_decorated(self, widget): @@ -291,7 +295,6 @@ class Stat_Window: self.window = gtk.Window() self.window.set_decorated(0) - self.window.set_opacity(parent.colors['hudopacity']) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) self.window.set_title("%s" % seat) @@ -324,6 +327,8 @@ class Stat_Window: self.e_box[r][c].connect("button_press_event", self.button_press_cb) # font = pango.FontDescription("Sans 8") self.label[r][c].modify_font(font) + + self.window.set_opacity(parent.colors['hudopacity']) self.window.realize self.window.move(self.x, self.y) self.window.show_all() diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py index 3531c017..07c69662 100644 --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -58,6 +58,7 @@ def do_tip(widget, tip): def do_stat(stat_dict, player = 24, stat = 'vpip'): return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) + # OK, for reference the tuple returned by the stat is: # 0 - The stat, raw, no formating, eg 0.33333333 # 1 - formatted stat with appropriate precision and punctuation, eg 33% @@ -68,6 +69,15 @@ def do_stat(stat_dict, player = 24, stat = 'vpip'): ########################################### # functions that return individual stats + +def playername(stat_dict, player): + return (stat_dict[player]['screen_name'], + stat_dict[player]['screen_name'], + stat_dict[player]['screen_name'], + stat_dict[player]['screen_name'], + stat_dict[player]['screen_name'], + stat_dict[player]['screen_name']) + def vpip(stat_dict, player): """ Voluntarily put $ in the pot.""" stat = 0.0 From b5ec60f5fac97949671cd3491fcf163affebae2b Mon Sep 17 00:00:00 2001 From: eblade Date: Sun, 2 Nov 2008 05:20:25 -0500 Subject: [PATCH 241/262] forgot to commit configuration.py last time --- pyfpdb/Configuration.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index ed90bf05..4ee691eb 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -109,6 +109,8 @@ class Game: stat.tip = stat_node.getAttribute("tip") stat.click = stat_node.getAttribute("click") stat.popup = stat_node.getAttribute("popup") + stat.hudprefix = stat_node.getAttribute("hudprefix") + stat.hudsuffix = stat_node.getAttribute("hudsuffix") self.stats[stat.stat_name] = stat From 0f10f87373c658304186162e9017f241fa794e0c Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 2 Nov 2008 22:58:55 -0500 Subject: [PATCH 242/262] changes to make consolidated config files happier --- pyfpdb/Configuration.py | 91 ++++++++++++++++++++++++++++------- pyfpdb/HUD_config.xml.example | 2 +- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 4ee691eb..50b5a287 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -205,24 +205,19 @@ class Config: sys.stderr.write("Configuration file %s not found. Using defaults." % (file)) file = None -# if "file" is invalid or None, we look for a HUD_config in the cwd if file == None: # configuration file path not passed or invalid - if os.path.exists('HUD_config.xml'): # there is a HUD_config in the cwd - file = 'HUD_config.xml' # so we use it - else: # no HUD_config in the cwd, look where it should be in the first place -# find the path to the default HUD_config for the current os - if os.name == 'posix': - config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'HUD_config.xml') - elif os.name == 'nt': - config_path = os.path.join(os.environ["APPDATA"], 'fpdb', 'HUD_config.xml') - else: config_path = False - - if config_path and os.path.exists(config_path): - file = config_path - else: - print "No HUD_config_xml found. Exiting" - sys.stderr.write("No HUD_config_xml found. Exiting") - sys.exit() + file = self.find_config() #Look for a config file in the normal places + + if file == None: # no config file in the normal places + file = self.find_example_config() #Look for an example file to edit + + if file == None: # that didn't work either, just die + print "No HUD_config_xml found. Exiting" + sys.stderr.write("No HUD_config_xml found. Exiting") + sys.exit() + +# Parse even if there was no real config file found and we are using the example +# If using the example, we'll edit it later try: print "Reading configuration file %s\n" % (file) doc = xml.dom.minidom.parse(file) @@ -274,6 +269,68 @@ class Config: tv = Tv(node = tv_node) self.tv = tv + db = self.get_db_parameters('fpdb') + if db['db-password'] == 'YOUR MYSQL PASSWORD': + df_file = self.find_default_conf() + if df_file == None: # this is bad + pass + else: + df_parms = self.read_default_conf(df_file) + self.supported_databases['fpdb'].db_pass = df_parms['db-password'] + self.supported_databases['fpdb'].db_ip = df_parms['db-host'] + self.supported_databases['fpdb'].db_user = df_parms['db-user'] + + def find_config(self): + if os.path.exists('HUD_config.xml'): # there is a HUD_config in the cwd + file = 'HUD_config.xml' # so we use it + else: # no HUD_config in the cwd, look where it should be in the first place +# find the path to the default HUD_config for the current os + if os.name == 'posix': + config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'HUD_config.xml') + elif os.name == 'nt': + config_path = os.path.join(os.environ["APPDATA"], 'fpdb', 'HUD_config.xml') + else: config_path = False + + if config_path and os.path.exists(config_path): + file = config_path + else: + file = None + return file + + def find_default_conf(self): + if os.name == 'posix': + config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'default.conf') + elif os.name == 'nt': + config_path = os.path.join(os.environ["APPDATA"], 'fpdb', 'default.conf') + else: config_path = False + + if config_path and os.path.exists(config_path): + file = config_path + else: + file = None + return file + + def read_default_conf(self, file): + parms = {} + fh = open(file, "r") + for line in fh: + line = string.strip(line) + (key, value) = line.split('=') + parms[key] = value + fh.close + return parms + + def find_example_config(self): + if os.path.exists('HUD_config.xml.example'): # there is a HUD_config in the cwd + file = 'HUD_config.xml.example' # so we use it + print "No HUD_config.xml found, using HUD_config.xml.example.\n", \ + "A HUD_config.xml will be written. You will probably have to edit it." + sys.stderr.write("No HUD_config.xml found, using HUD_config.xml.example.\n" + \ + "A HUD_config.xml will be written. You will probably have to edit it.") + else: + file = None + return file + def get_site_node(self, site): for site_node in self.doc.getElementsByTagName("site"): if site_node.getAttribute("site_name") == site: diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index abdf7618..be43765e 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -2,7 +2,7 @@ - + From 79ddee8971c38c2499f264b4136a3042c2caaa9e Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 4 Nov 2008 22:40:03 +1300 Subject: [PATCH 243/262] Add converter attribute to support additional sites --- pyfpdb/Configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 50b5a287..3505b5c1 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -56,7 +56,7 @@ class Site: self.hudopacity = node.getAttribute("hudopacity") self.hudbgcolor = node.getAttribute("bgcolor") self.hudfgcolor = node.getAttribute("fgcolor") - + self.converter = node.getAttribute("converter") self.layout = {} for layout_node in node.getElementsByTagName('layout'): From 2ecfbf6c2d3261abe9ef02bd47a16bdc88e1a00d Mon Sep 17 00:00:00 2001 From: eblade Date: Tue, 4 Nov 2008 05:02:41 -0500 Subject: [PATCH 244/262] fpdb_import: auto-import bases it's decisions to check files on file-size changes rather than mtime - could you guys check it out in nix and other poker sites, and tell me if that breaks anything? HUD: if update() errors due to not enough stat windows being available (ie, your broken site converter tells us we have a 6 max table, but there are people in seats 7-10), it will re-assess the table as a 10-max add StatWindow.relocate(x,y) --- pyfpdb/Hud.py | 18 ++++++++++++++++-- pyfpdb/fpdb_import.py | 7 ++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index f1d0cfae..9dca15d8 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -160,7 +160,10 @@ class Hud: # create the stat windows for i in range(1, self.max + 1): (x, y) = loc[adj[i]] - self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], + if self.stat_windows.has_key(i): + self.stat_windows[i].relocate(x, y) + else: + self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], parent = self, table = self.table, x = x, @@ -185,7 +188,13 @@ class Hud: def update(self, hand, config, stat_dict): self.hand = hand # this is the last hand, so it is available later for s in stat_dict.keys(): - self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] + try: + self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] + except: # omg, we have more seats than stat windows .. damn poker sites with incorrect max seating info .. let's force 10 here + self.max = 10 + self.create(hand, config) + self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] + for r in range(0, config.supported_games[self.poker_game].rows): for c in range(0, config.supported_games[self.poker_game].cols): this_stat = config.supported_games[self.poker_game].stats[self.stats[r][c]] @@ -283,6 +292,11 @@ class Stat_Window: else: top.set_decorated(1) top.move(x, y) + + def relocate(self, x, y): + self.x = x + self.table.x + self.y = y + self.table.y + self.window.move(self.x, self.y) def __init__(self, parent, game, table, seat, x, y, player_id, font): self.parent = parent # Hud object that this stat window belongs to diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 4a855d1d..b5c1dd3e 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -147,12 +147,13 @@ class Importer: try: lastupdate = self.updated[file] # print "Is " + str(stat_info.st_mtime) + " > " + str(lastupdate) - if stat_info.st_mtime > lastupdate: + #if stat_info.st_mtime > lastupdate: + if stat_info.st_size > lastupdate: self.import_file_dict(file) - self.updated[file] = time() + self.updated[file] = stat_info.st_size except: # print "Adding " + str(file) + " at approx " + str(time()) - self.updated[file] = time() + self.updated[file] = 0 # This is now an internal function that should not be called directly. def import_file_dict(self, file): From 093302e76d9284e745d005df8b081a1d5e40f1d8 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 5 Nov 2008 10:39:27 +1300 Subject: [PATCH 245/262] Fix(?) problem with HUD only starting after 2 hands have been entered. Import code will now import_file_dict() on every file it initially finds that was modified in the last minute. Not perfect, as a lot of hands can take longer than that. --- pyfpdb/fpdb_import.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 4a855d1d..d7fa5026 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -110,7 +110,7 @@ class Importer: self.filelist = list(set(self.filelist)) #Add a directory of files to filelist - def addImportDirectory(self,dir,monitor = False): + def addImportDirectory(self,dir,monitor = False, filter = "passthrough"): if os.path.isdir(dir): if monitor == True: self.monitor = True @@ -146,13 +146,15 @@ class Importer: stat_info = os.stat(file) try: lastupdate = self.updated[file] -# print "Is " + str(stat_info.st_mtime) + " > " + str(lastupdate) if stat_info.st_mtime > lastupdate: self.import_file_dict(file) self.updated[file] = time() except: -# print "Adding " + str(file) + " at approx " + str(time()) self.updated[file] = time() + # This codepath only runs first time the file is found, if modified in the last + # minute run an immediate import. + if (time() - stat_info.st_mtime) < 60: + self.import_file_dict(file) # This is now an internal function that should not be called directly. def import_file_dict(self, file): From b0be7c5b7198dde95b923e22af4076f7f2fdcc81 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2008 05:49:05 -0500 Subject: [PATCH 246/262] cleanup and new get/set for db parameters --- pyfpdb/Configuration.py | 55 ++++++++++++++++++++++++++++------- pyfpdb/HUD_config.xml.example | 2 +- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 50b5a287..7f04d971 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -199,6 +199,7 @@ class Config: # "file" is a path to an xml file with the fpdb/HUD configuration # we check the existence of "file" and try to recover if it doesn't exist + self.default_config_path = self.get_default_config_path() if not file == None: # configuration file path has been passed if not os.path.exists(file): print "Configuration file %s not found. Using defaults." % (file) @@ -210,6 +211,8 @@ class Config: if file == None: # no config file in the normal places file = self.find_example_config() #Look for an example file to edit + if not file == None: + pass if file == None: # that didn't work either, just die print "No HUD_config_xml found. Exiting" @@ -276,27 +279,34 @@ class Config: pass else: df_parms = self.read_default_conf(df_file) - self.supported_databases['fpdb'].db_pass = df_parms['db-password'] - self.supported_databases['fpdb'].db_ip = df_parms['db-host'] - self.supported_databases['fpdb'].db_user = df_parms['db-user'] + self.set_db_parameters(db_name = 'fpdb', db_ip = df_parms['db-host'], + db_user = df_parms['db-user'], + db_pass = df_parms['db-password']) + self.save(file=os.path.join(self.default_config_path, "HUD_config.xml")) + def find_config(self): + """Looks in cwd and in self.default_config_path for a config file.""" if os.path.exists('HUD_config.xml'): # there is a HUD_config in the cwd file = 'HUD_config.xml' # so we use it else: # no HUD_config in the cwd, look where it should be in the first place -# find the path to the default HUD_config for the current os - if os.name == 'posix': - config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'HUD_config.xml') - elif os.name == 'nt': - config_path = os.path.join(os.environ["APPDATA"], 'fpdb', 'HUD_config.xml') - else: config_path = False - - if config_path and os.path.exists(config_path): + config_path = os.path.join(self.default_config_path, 'HUD_config.xml') + if os.path.exists(config_path): file = config_path else: file = None return file + def get_default_config_path(self): + """Returns the path where the fpdb config file _should_ be stored.""" + if os.name == 'posix': + config_path = os.path.join(os.path.expanduser("~"), '.fpdb') + elif os.name == 'nt': + config_path = os.path.join(os.environ["APPDATA"], 'fpdb') + else: config_path = None + return config_path + + def find_default_conf(self): if os.name == 'posix': config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'default.conf') @@ -336,6 +346,12 @@ class Config: if site_node.getAttribute("site_name") == site: return site_node + def get_db_node(self, db_name): + for db_node in self.doc.getElementsByTagName("database"): + if db_node.getAttribute("db_name") == db_name: + return db_node + return None + def get_layout_node(self, site_node, layout): for layout_node in site_node.getElementsByTagName("layout"): if layout_node.getAttribute("max") == None: @@ -389,6 +405,23 @@ class Config: pass return db + def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None, + db_pass = None, db_server = None, db_type = None): + db_node = self.get_db_node(db_name) + if not db_node == None: + if not db_ip == None: db_node.setAttribute("db_ip", db_ip) + if not db_user == None: db_node.setAttribute("db_user", db_user) + if not db_pass == None: db_node.setAttribute("db_pass", db_pass) + if not db_server == None: db_node.setAttribute("db_server", db_server) + if not db_type == None: db_node.setAttribute("db_type", db_type) + if self.supported_databases.has_key(db_name): + if not db_ip == None: self.supported_databases[db_name].dp_ip = db_ip + if not db_user == None: self.supported_databases[db_name].dp_user = db_user + if not db_pass == None: self.supported_databases[db_name].dp_pass = db_pass + if not db_server == None: self.supported_databases[db_name].dp_server = db_server + if not db_type == None: self.supported_databases[db_name].dp_type = db_type + return + def get_tv_parameters(self): tv = {} try: diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index be43765e..abdf7618 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -2,7 +2,7 @@ - + From 92381254e84526ed3a7d4d9e222486ca2eae7857 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 6 Nov 2008 00:36:24 +1300 Subject: [PATCH 247/262] Change the filelist and dirlist type to be a hash of lists, delete postgres sql schema dirlist is in the form: {'Site': [ "/path/to/dir", "filter" ] } Where filter will be the plugin to convert to stars/fpdb hand history file. filelist is in the form: {'file': [ "site", "filter" ] } --- pyfpdb/GuiAutoImport.py | 4 +- pyfpdb/fpdb_import.py | 33 +++--- pyfpdb/schema.postgres.sql | 218 ------------------------------------- 3 files changed, 15 insertions(+), 240 deletions(-) delete mode 100644 pyfpdb/schema.postgres.sql diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 7a6313cc..916170f0 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -110,8 +110,8 @@ class GuiAutoImport (threading.Thread): self.tiltpath=self.tiltDirPath.get_text() # Add directory to importer object. - self.importer.addImportDirectory(self.starspath, True) - self.importer.addImportDirectory(self.tiltpath, True) + self.importer.addImportDirectory(self.starspath, True, "PokerStars", "passthrough") + self.importer.addImportDirectory(self.tiltpath, True, "FullTilt", "passthrough") self.do_import() interval=int(self.intervalEntry.get_text()) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index d7fa5026..ffa06cff 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -47,8 +47,8 @@ class Importer: self.caller=caller self.db = None self.cursor = None - self.filelist = [] - self.dirlist = [] + self.filelist = {} + self.dirlist = {} self.monitor = False self.updated = {} #Time last import was run {file:mtime} self.callHud = False @@ -100,29 +100,25 @@ class Importer: # self.updated = time() def clearFileList(self): - self.filelist = [] + self.filelist = {} #Add an individual file to filelist - def addImportFile(self, filename): + def addImportFile(self, filename, site = "default", filter = "passthrough"): #TODO: test it is a valid file - self.filelist = self.filelist + [filename] - #Remove duplicates - self.filelist = list(set(self.filelist)) + self.filelist[filename] = [site] + [filter] #Add a directory of files to filelist - def addImportDirectory(self,dir,monitor = False, filter = "passthrough"): + #Only one import directory per site supported. + #dirlist is a hash of lists: + #dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] } + def addImportDirectory(self,dir,monitor = False, site = "default", filter = "passthrough"): if os.path.isdir(dir): if monitor == True: self.monitor = True - self.dirlist = self.dirlist + [dir] + self.dirlist[site] = [dir] + [filter] for file in os.listdir(dir): - if os.path.isdir(file): - print "BulkImport is not recursive - please select the final directory in which the history files are" - else: - self.filelist = self.filelist + [os.path.join(dir, file)] - #Remove duplicates - self.filelist = list(set(self.filelist)) + self.addImportFile(os.path.join(dir, file), site, filter) else: print "Warning: Attempted to add: '" + str(dir) + "' as an import directory" @@ -136,11 +132,8 @@ class Importer: #Check for new files in directory #todo: make efficient - always checks for new file, should be able to use mtime of directory # ^^ May not work on windows - for dir in self.dirlist: - for file in os.listdir(dir): - self.filelist = self.filelist + [os.path.join(dir, file)] - - self.filelist = list(set(self.filelist)) + for site in self.dirlist: + self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1]) for file in self.filelist: stat_info = os.stat(file) diff --git a/pyfpdb/schema.postgres.sql b/pyfpdb/schema.postgres.sql deleted file mode 100644 index 2affb1c6..00000000 --- a/pyfpdb/schema.postgres.sql +++ /dev/null @@ -1,218 +0,0 @@ - -DROP TABLE IF EXISTS Settings CASCADE; -CREATE TABLE Settings (version SMALLINT); - -DROP TABLE IF EXISTS Sites CASCADE; -CREATE TABLE Sites ( - id SERIAL UNIQUE, PRIMARY KEY (id), - name varchar(32), - currency char(3)); - -DROP TABLE IF EXISTS Gametypes CASCADE; -CREATE TABLE Gametypes ( - id SERIAL UNIQUE, PRIMARY KEY (id), - siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), - type char(4), - base char(4), - category varchar(9), - limitType char(2), - hiLo char(1), - smallBlind int, - bigBlind int, - smallBet int, - bigBet int); - -DROP TABLE IF EXISTS Players CASCADE; -CREATE TABLE Players ( - id SERIAL UNIQUE, PRIMARY KEY (id), - name VARCHAR(32), - siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), - comment text, - commentTs timestamp without time zone); - -DROP TABLE IF EXISTS Autorates CASCADE; -CREATE TABLE Autorates ( - id BIGSERIAL UNIQUE, PRIMARY KEY (id), - playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), - gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - description varchar(50), - shortDesc char(8), - ratingTime timestamp without time zone, - handCount int); - -DROP TABLE IF EXISTS Hands CASCADE; -CREATE TABLE Hands ( - id BIGSERIAL UNIQUE, PRIMARY KEY (id), - tableName VARCHAR(20), - siteHandNo BIGINT, - gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - handStart timestamp without time zone, - importTime timestamp without time zone, - seats SMALLINT, - maxSeats SMALLINT, - comment TEXT, - commentTs timestamp without time zone); - -DROP TABLE IF EXISTS BoardCards CASCADE; -CREATE TABLE BoardCards ( - id BIGSERIAL UNIQUE, PRIMARY KEY (id), - handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), - card1Value smallint, - card1Suit char(1), - card2Value smallint, - card2Suit char(1), - card3Value smallint, - card3Suit char(1), - card4Value smallint, - card4Suit char(1), - card5Value smallint, - card5Suit char(1)); - -DROP TABLE IF EXISTS TourneyTypes CASCADE; -CREATE TABLE TourneyTypes ( - id SERIAL, PRIMARY KEY (id), - siteId INT, FOREIGN KEY (siteId) REFERENCES Sites(id), - buyin INT, - fee INT, - knockout INT, - rebuyOrAddon BOOLEAN); - -DROP TABLE IF EXISTS Tourneys CASCADE; -CREATE TABLE Tourneys ( - id SERIAL UNIQUE, PRIMARY KEY (id), - tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), - siteTourneyNo BIGINT, - entries INT, - prizepool INT, - startTime timestamp without time zone, - comment TEXT, - commentTs timestamp without time zone); - -DROP TABLE IF EXISTS TourneysPlayers CASCADE; -CREATE TABLE TourneysPlayers ( - id BIGSERIAL UNIQUE, PRIMARY KEY (id), - tourneyId INT, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), - playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), - payinAmount INT, - rank INT, - winnings INT, - comment TEXT, - commentTs timestamp without time zone); - -DROP TABLE IF EXISTS HandsPlayers CASCADE; -CREATE TABLE HandsPlayers ( - id BIGSERIAL UNIQUE, PRIMARY KEY (id), - handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), - playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), - startCash INT, - position CHAR(1), - seatNo SMALLINT, - ante INT, - - card1Value smallint, - card1Suit char(1), - card2Value smallint, - card2Suit char(1), - card3Value smallint, - card3Suit char(1), - card4Value smallint, - card4Suit char(1), - card5Value smallint, - card5Suit char(1), - card6Value smallint, - card6Suit char(1), - card7Value smallint, - card7Suit char(1), - - winnings int, - rake int, - comment text, - commentTs timestamp without time zone, - tourneysPlayersId BIGINT, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id)); - -DROP TABLE IF EXISTS HandsActions CASCADE; -CREATE TABLE HandsActions ( - id BIGSERIAL UNIQUE, PRIMARY KEY (id), - handPlayerId BIGINT, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), - street SMALLINT, - actionNo SMALLINT, - action CHAR(5), - allIn BOOLEAN, - amount INT, - comment TEXT, - commentTs timestamp without time zone); - -DROP TABLE IF EXISTS HudCache CASCADE; -CREATE TABLE HudCache ( - id BIGSERIAL UNIQUE, PRIMARY KEY (id), - gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), - playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), - activeSeats SMALLINT, - position CHAR(1), - tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), - - HDs INT, - street0VPI INT, - street0Aggr INT, - street0_3B4BChance INT, - street0_3B4BDone INT, - street1Seen INT, - street2Seen INT, - street3Seen INT, - street4Seen INT, - sawShowdown INT, - street1Aggr INT, - street2Aggr INT, - street3Aggr INT, - street4Aggr INT, - otherRaisedStreet1 INT, - otherRaisedStreet2 INT, - otherRaisedStreet3 INT, - otherRaisedStreet4 INT, - foldToOtherRaisedStreet1 INT, - foldToOtherRaisedStreet2 INT, - foldToOtherRaisedStreet3 INT, - foldToOtherRaisedStreet4 INT, - wonWhenSeenStreet1 FLOAT, - wonAtSD FLOAT, - - stealAttemptChance INT, - stealAttempted INT, - foldBbToStealChance INT, - foldedBbToSteal INT, - foldSbToStealChance INT, - foldedSbToSteal INT, - - street1CBChance INT, - street1CBDone INT, - street2CBChance INT, - street2CBDone INT, - street3CBChance INT, - street3CBDone INT, - street4CBChance INT, - street4CBDone INT, - - foldToStreet1CBChance INT, - foldToStreet1CBDone INT, - foldToStreet2CBChance INT, - foldToStreet2CBDone INT, - foldToStreet3CBChance INT, - foldToStreet3CBDone INT, - foldToStreet4CBChance INT, - foldToStreet4CBDone INT, - - totalProfit INT, - - street1CheckCallRaiseChance INT, - street1CheckCallRaiseDone INT, - street2CheckCallRaiseChance INT, - street2CheckCallRaiseDone INT, - street3CheckCallRaiseChance INT, - street3CheckCallRaiseDone INT, - street4CheckCallRaiseChance INT, - street4CheckCallRaiseDone INT); - -INSERT INTO Settings VALUES (118); -INSERT INTO Sites ("name", currency) VALUES ('Full Tilt Poker', 'USD'); -INSERT INTO Sites ("name", currency) VALUES ('PokerStars', 'USD'); -INSERT INTO TourneyTypes (buyin, fee, knockout, rebuyOrAddon) VALUES (0, 0, 0, FALSE); From 8191db648740e8766def07a80a4fb2105f71da56 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 6 Nov 2008 06:57:15 +1300 Subject: [PATCH 248/262] Add new parameters to HUD_config.xml.example in prep for HH filter plugins --- pyfpdb/HUD_config.xml.example | 39 +++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index abdf7618..aeeaa219 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -2,7 +2,7 @@ - + @@ -49,7 +49,42 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 22831cc8c6173dffdf22bcc42d8a5cb3b61d97a8 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2008 15:04:04 -0500 Subject: [PATCH 249/262] added get_converter to support Carl's importer change --- pyfpdb/Configuration.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 01521fb0..bdc5f356 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -480,7 +480,10 @@ class Config: ( 0, 280), (121, 280), ( 46, 30) ) return locations - + def get_converter(self, site): + if not self.supported_sites.has_key(site): + return None + return self.supported_sites[site].converter if __name__== "__main__": c = Config() @@ -528,3 +531,4 @@ if __name__== "__main__": print "paths = ", c.get_default_paths("PokerStars") print "colors = ", c.get_default_colors("PokerStars") print "locs = ", c.get_locations("PokerStars", 8) + print "converter = ", c.get_converter("Everleaf") From a41aec44dde9660b0dc47ed7a3a7949eacd99439 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2008 16:52:47 -0500 Subject: [PATCH 250/262] added set/get _sit_parameters to Config + removed keep_above in Hud --- pyfpdb/Configuration.py | 50 ++++++++++++++++++++++++++++++++++++++--- pyfpdb/Hud.py | 2 +- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index bdc5f356..5e599d4b 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -480,10 +480,52 @@ class Config: ( 0, 280), (121, 280), ( 46, 30) ) return locations - def get_converter(self, site): + def get_site_parameters(self, site): + """Returns a dict of the site parameters for the specified site""" if not self.supported_sites.has_key(site): return None - return self.supported_sites[site].converter + parms = {} + parms["converter"] = self.supported_sites[site].converter + parms["decoder"] = self.supported_sites[site].decoder + parms["hudbgcolor"] = self.supported_sites[site].hudbgcolor + parms["hudfgcolor"] = self.supported_sites[site].hudfgcolor + parms["hudopacity"] = self.supported_sites[site].hudopacity + parms["screen_name"] = self.supported_sites[site].screen_name + parms["site_path"] = self.supported_sites[site].site_path + parms["table_finder"] = self.supported_sites[site].table_finder + parms["HH_path"] = self.supported_sites[site].HH_path + return parms + + def set_site_parameters(self, site_name, converter = None, decoder = None, + hudbgcolor = None, hudfgcolor = None, + hudopacity = None, screen_name = None, + site_path = None, table_finder = None, + HH_path = None): + """Sets the specified site parameters for the specified site.""" + site_node = self.get_site_node(site_name) + if not db_node == None: + if not converter == None: site_node.setAttribute("converter", converter) + if not decoder == None: site_node.setAttribute("decoder", decoder) + if not hudbgcolor == None: site_node.setAttribute("hudbgcolor", hudbgcolor) + if not hudfgcolor == None: site_node.setAttribute("hudfgcolor", hudfgcolor) + if not hudopacity == None: site_node.setAttribute("hudopacity", hudopacity) + if not screen_name == None: site_node.setAttribute("screen_name", screen_name) + if not site_path == None: site_node.setAttribute("site_path", site_path) + if not table_finder == None: site_node.setAttribute("table_finder", table_finder) + if not HH_path == None: site_node.setAttribute("HH_path", HH_path) + + if self.supported_databases.has_key(db_name): + if not converter == None: self.supported_sites[site].converter = converter + if not decoder == None: self.supported_sites[site].decoder = decoder + if not hudbgcolor == None: self.supported_sites[site].hudbgcolor = hudbgcolor + if not hudfgcolor == None: self.supported_sites[site].hudfgcolor = hudfgcolor + if not hudopacity == None: self.supported_sites[site].hudopacity = hudopacity + if not screen_name == None: self.supported_sites[site].screen_name = screen_name + if not site_path == None: self.supported_sites[site].site_path = site_path + if not table_finder == None: self.supported_sites[site].table_finder = table_finder + if not HH_path == None: self.supported_sites[site].HH_path = HH_path + return + if __name__== "__main__": c = Config() @@ -531,4 +573,6 @@ if __name__== "__main__": print "paths = ", c.get_default_paths("PokerStars") print "colors = ", c.get_default_colors("PokerStars") print "locs = ", c.get_locations("PokerStars", 8) - print "converter = ", c.get_converter("Everleaf") + for site in c.supported_sites.keys(): + print "site = ", site, + print c.get_site_parameters(site) \ No newline at end of file diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 9dca15d8..ebea595a 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -385,7 +385,7 @@ class Popup_window: self.window = gtk.Window() self.window.set_decorated(0) self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) - self.window.set_keep_above(1) +# self.window.set_keep_above(1) self.window.set_title("popup") self.window.set_property("skip-taskbar-hint", True) self.window.set_transient_for(parent.get_toplevel()) From a29372a756cdd8c843d912b1358f33b331f6435a Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 6 Nov 2008 12:14:46 +1300 Subject: [PATCH 251/262] Fix copy-paste error in Stat.py for a_freq_3 function. Hat tip to sqlcoder on sourceforge. --- pyfpdb/Stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py index 07c69662..7bb9a8c5 100644 --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -382,7 +382,7 @@ def a_freq_3(stat_dict, player): '%3.1f' % (100*stat) + '%', 'a3=%3.1f' % (100*stat) + '%', 'a_fq_3=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['aggr_1'], stat_dict[player]['saw_1']), + '(%d/%d)' % (stat_dict[player]['aggr_3'], stat_dict[player]['saw_3']), 'Aggression Freq river/6th' ) except: From cf1efb8d27cf3f1e2a03d25a71fe1af0c2a72e84 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2008 22:44:29 -0500 Subject: [PATCH 252/262] transaction isolation code suggested by sql_coder on the forums --- pyfpdb/Database.py | 4 ++++ pyfpdb/HUD_main.py | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 63de54b2..4039b256 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -59,6 +59,10 @@ class Database: user = c.supported_databases[db_name].db_user, passwd = c.supported_databases[db_name].db_pass, db = c.supported_databases[db_name].db_name) + cur_iso = self.connection.cursor() + cur_iso.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED') + cur_iso.close() + except: print "Error opening database connection %s. See error log file." % (file) traceback.print_exc(file=sys.stderr) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index a157cf46..b1955a6b 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -89,6 +89,7 @@ def update_HUD(new_hand_id, table_name, config, stat_dict): def read_stdin(): # This is the thread function global hud_dict + db_connection = Database.Database(config, db_name, 'temp') while True: # wait for a new hand number on stdin new_hand_id = sys.stdin.readline() new_hand_id = string.rstrip(new_hand_id) @@ -101,10 +102,8 @@ def read_stdin(): # This is the thread function del(hud_dict[h]) # connect to the db and get basic info about the new hand - db_connection = Database.Database(config, db_name, 'temp') (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) stat_dict = db_connection.get_stats_from_hand(new_hand_id) - db_connection.close_connection() # if a hud for this table exists, just update it if hud_dict.has_key(table_name): From c330bd772b6de8a5304bd8629cd116ed009bf517 Mon Sep 17 00:00:00 2001 From: eblade Date: Thu, 6 Nov 2008 06:26:25 -0500 Subject: [PATCH 253/262] add .exe to PokerStars tablefinder in default config, change some things for the "everleaf" entry --- pyfpdb/HUD_config.xml.example | 53 +++++++++++++++-------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index aeeaa219..3a595fe2 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -2,7 +2,7 @@ - + @@ -84,39 +84,30 @@ - - - - - - - - - - + + + + + + + + - - - - - - - + + + + + + + + + + + - + - - - - - - - - - - - - + From 27c6c4884edb55bee25cd2deb4b7c0034698b53b Mon Sep 17 00:00:00 2001 From: eblade Date: Thu, 6 Nov 2008 06:58:28 -0500 Subject: [PATCH 254/262] stat popups attach themselves to the stat window as children, have proper colors --- pyfpdb/Hud.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index ebea595a..2712c55b 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -399,6 +399,14 @@ class Popup_window: # need an event box so we can respond to clicks self.window.add(self.ebox) self.ebox.add(self.lab) + + self.ebox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudbgcolor'])) + self.ebox.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudfgcolor'])) + self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudbgcolor'])) + self.window.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudfgcolor'])) + self.lab.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudbgcolor'])) + self.lab.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudfgcolor'])) + self.window.realize # figure out the row, col address of the click that activated the popup @@ -440,6 +448,9 @@ class Popup_window: self.lab.set_text(pu_text) self.window.show_all() + + self.window.set_transient_for(stat_window.main_window) + # set_keep_above(1) for windows if os.name == 'nt': self.topify_window(self.window) From dd74c29551d865b501e00d7704cd2ae4c520dba1 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Thu, 6 Nov 2008 23:58:41 +0000 Subject: [PATCH 255/262] p139 - fixed TV and removed old config --- docs/default.conf | 11 --------- pyfpdb/GuiTableViewer.py | 49 +++++++++++----------------------------- 2 files changed, 13 insertions(+), 47 deletions(-) delete mode 100644 docs/default.conf diff --git a/docs/default.conf b/docs/default.conf deleted file mode 100644 index 031b8fb7..00000000 --- a/docs/default.conf +++ /dev/null @@ -1,11 +0,0 @@ -db-backend=2 -db-host=localhost -db-databaseName=fpdb -db-user=fpdb -db-password=enterYourPwHere -imp-callFpdbHud=True -tv-combinedStealFold=True -tv-combined2B3B=True -tv-combinedPostflop=True -bulkImport-defaultPath=default -hud-defaultPath=default diff --git a/pyfpdb/GuiTableViewer.py b/pyfpdb/GuiTableViewer.py index 1082bff1..f024a193 100644 --- a/pyfpdb/GuiTableViewer.py +++ b/pyfpdb/GuiTableViewer.py @@ -78,22 +78,13 @@ class GuiTableViewer (threading.Thread): if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"): tmp=("Name", "HDs", "VPIP", "PFR", "PF3B4B", "ST") - if self.settings['tv-combinedStealFold']: - tmp+=("FSB", ) - else: - tmp+=("FS", "FB") + tmp+=("FS", "FB") tmp+=("CB", ) - if self.settings['tv-combined2B3B']: - tmp+=("23B", ) - else: - tmp+=("2B", "3B") + tmp+=("2B", "3B") - if self.settings['tv-combinedPostflop']: - tmp+=("Postf A", "Postf F") - else: - tmp+=("AF", "FF", "AT", "FT", "AR", "FR") + tmp+=("AF", "FF", "AT", "FT", "AR", "FR") tmp+=("WtSD", "W$wsF", "W$SD") else: @@ -144,34 +135,20 @@ class GuiTableViewer (threading.Thread): tmp.append(self.hudDivide(row[31],row[30])+" ("+str(row[30])+")") #ST - if self.settings['tv-combinedStealFold']: - tmp.append(self.hudDivide(row[35]+row[33],row[34]+row[32])+" ("+str(row[34]+row[32])+")") #FSB - else: - tmp.append(self.hudDivide(row[35],row[34])+" ("+str(row[34])+")") #FS - tmp.append(self.hudDivide(row[33],row[32])+" ("+str(row[32])+")") #FB + tmp.append(self.hudDivide(row[35],row[34])+" ("+str(row[34])+")") #FS + tmp.append(self.hudDivide(row[33],row[32])+" ("+str(row[32])+")") #FB tmp.append(self.hudDivide(row[37],row[36])+" ("+str(row[36])+")") #CB - if self.settings['tv-combined2B3B']: - tmp.append(self.hudDivide(row[39]+row[41],row[38]+row[40])+" ("+str(row[38]+row[40])+")") #23B - else: - tmp.append(self.hudDivide(row[39],row[38])+" ("+str(row[38])+")") #2B - tmp.append(self.hudDivide(row[41],row[40])+" ("+str(row[40])+")") #3B + tmp.append(self.hudDivide(row[39],row[38])+" ("+str(row[38])+")") #2B + tmp.append(self.hudDivide(row[41],row[40])+" ("+str(row[40])+")") #3B - if self.settings['tv-combinedPostflop']: - aggCount=row[16]+row[17]+row[18] - handCount=row[11]+row[12]+row[13] - foldCount=row[24]+row[25]+row[26] - otherRaiseCount=row[20]+row[21]+row[22] - tmp.append(self.hudDivide(aggCount,handCount)+" ("+str(handCount)+")") #Agg - tmp.append(self.hudDivide(foldCount,otherRaiseCount)+" ("+str(otherRaiseCount)+")") #FF - else: - tmp.append(self.hudDivide(row[16],row[11])+" ("+str(row[11])+")") #AF - tmp.append(self.hudDivide(row[24],row[20])+" ("+str(row[20])+")") #FF - tmp.append(self.hudDivide(row[17],row[12])+" ("+str(row[12])+")") #AT - tmp.append(self.hudDivide(row[25],row[21])+" ("+str(row[21])+")") #FT - tmp.append(self.hudDivide(row[18],row[13])+" ("+str(row[13])+")") #AR - tmp.append(self.hudDivide(row[26],row[22])+" ("+str(row[22])+")") #FR + tmp.append(self.hudDivide(row[16],row[11])+" ("+str(row[11])+")") #AF + tmp.append(self.hudDivide(row[24],row[20])+" ("+str(row[20])+")") #FF + tmp.append(self.hudDivide(row[17],row[12])+" ("+str(row[12])+")") #AT + tmp.append(self.hudDivide(row[25],row[21])+" ("+str(row[21])+")") #FT + tmp.append(self.hudDivide(row[18],row[13])+" ("+str(row[13])+")") #AR + tmp.append(self.hudDivide(row[26],row[22])+" ("+str(row[22])+")") #FR tmp.append(self.hudDivide(row[15],row[11])) #WtSD tmp.append(self.hudDivide(row[28],row[11])) #W$wSF From c7393951893011df98986bd89b82af4df08d33cc Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 7 Nov 2008 00:14:25 +0000 Subject: [PATCH 256/262] p140 - added CLI option to fpdb.py to control redirection of stderr --- pyfpdb/fpdb.py | 14 +++++++++++--- pyfpdb/fpdb_import.py | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 134e6ea1..ad4d3f3d 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -17,10 +17,18 @@ import os import sys +from optparse import OptionParser -print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." -errorFile = open('fpdb-error-log.txt', 'w', 0) -sys.stderr = errorFile + +parser = OptionParser() +parser.add_option("-x", "--errorsToConsole", action="store_true", + help="If passed error output will go to the console rather than .") +(options, sys.argv) = parser.parse_args() + +if not options.errorsToConsole: + print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." + errorFile = open('fpdb-error-log.txt', 'w', 0) + sys.stderr = errorFile import pygtk pygtk.require('2.0') diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index ffa06cff..5398c83b 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -292,4 +292,4 @@ class Importer: if __name__ == "__main__": - print "CLI for fpdb_import is currently on vacation please check in later" + print "CLI for fpdb_import is now available as CliFpdb.py" From 98d0305aa8102250d666fc52f6116400fd4b7ced Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 7 Nov 2008 11:07:42 -0500 Subject: [PATCH 257/262] fixed psycopg2 connection parameters --- pyfpdb/fpdb_import.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index ffa06cff..b43b5740 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -72,8 +72,11 @@ class Importer: elif self.settings['db-backend'] == 3: if not pgsqlLibFound: raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") - self.db = psycopg2.connect(self.settings['db-host'], self.settings['db-user'], - self.settings['db-password'], self.settings['db-databaseName']) + print self.settings + self.db = psycopg2.connect(host = self.settings['db-host'], + user = self.settings['db-user'], + password = self.settings['db-password'], + database = self.settings['db-databaseName']) elif self.settings['db-backend'] == 4: pass else: From e26fdd79a4d484c44e8bcdd14d4e07a3f6ba8362 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 7 Nov 2008 12:22:37 -0500 Subject: [PATCH 258/262] possible fix to wrong window positioning seen by Elaetic --- pyfpdb/HUD_main.py | 39 +++++++++++++++++++++++++++++++-------- pyfpdb/Hud.py | 9 ++++++++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index b1955a6b..f54fa9af 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -37,6 +37,7 @@ import os import thread import time import string +import re errorfile = open('HUD-error.txt', 'w', 0) sys.stderr = errorfile @@ -70,9 +71,10 @@ def create_HUD(new_hand_id, table, db_name, table_name, max, poker_game, db_conn hud_dict[table_name] = Hud.Hud(table, max, poker_game, config, db_name) hud_dict[table_name].create(new_hand_id, config) hud_dict[table_name].update(new_hand_id, config, stat_dict) + hud_dict[table_name].reposition_windows() return False finally: - gtk.gdk.threads_leave + gtk.gdk.threads_leave() gobject.idle_add(idle_func) def update_HUD(new_hand_id, table_name, config, stat_dict): @@ -83,13 +85,15 @@ def update_HUD(new_hand_id, table_name, config, stat_dict): hud_dict[table_name].update(new_hand_id, config, stat_dict) return False finally: - gtk.gdk.threads_leave + gtk.gdk.threads_leave() gobject.idle_add(idle_func) def read_stdin(): # This is the thread function global hud_dict db_connection = Database.Database(config, db_name, 'temp') +# tourny_finder = re.compile('(\d+) (\d+)') + while True: # wait for a new hand number on stdin new_hand_id = sys.stdin.readline() new_hand_id = string.rstrip(new_hand_id) @@ -101,20 +105,39 @@ def read_stdin(): # This is the thread function if hud_dict[h].deleted: del(hud_dict[h]) -# connect to the db and get basic info about the new hand +# get basic info about the new hand from the db (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) + +# find out if this hand is from a tournament + is_tournament = False +# (t_number, s_number) = (0, 0) +# mat_obj = tourny_finder(table_name) +# if len(mat_obj.groups) == 2: +# is_tournament = True +# (t_number, s_number) = mat_obj.group(1, 2) + stat_dict = db_connection.get_stats_from_hand(new_hand_id) -# if a hud for this table exists, just update it +# if a hud for this CASH table exists, just update it if hud_dict.has_key(table_name): update_HUD(new_hand_id, table_name, config, stat_dict) +# if a hud for this TOURNAMENT table exists, just update it +# elif hud_dict.has_key(t_number): +# update_HUD(new_hand_id, t_number, config, stat_dict) # otherwise create a new hud else: - tablewindow = Tables.discover_table_by_name(config, table_name) - if tablewindow == None: - sys.stderr.write("table name "+table_name+" not found\n") + if is_tournament: + tablewindow = Tables.discover_tournament_table(config, t_number, s_number) + if tablewindow == None: + sys.stderr.write("table name "+table_name+" not found\n") + else: + create_HUD(new_hand_id, tablewindow, db_name, t_number, max, poker_game, db_connection, config, stat_dict) else: - create_HUD(new_hand_id, tablewindow, db_name, table_name, max, poker_game, db_connection, config, stat_dict) + tablewindow = Tables.discover_table_by_name(config, table_name) + if tablewindow == None: + sys.stderr.write("table name "+table_name+" not found\n") + else: + create_HUD(new_hand_id, tablewindow, db_name, table_name, max, poker_game, db_connection, config, stat_dict) if __name__== "__main__": sys.stderr.write("HUD_main starting\n") diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 2712c55b..11cf61cc 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -93,6 +93,10 @@ class Hud: self.menu.append(self.item2) self.item2.connect("activate", self.save_layout) self.item2.show() + self.item3 = gtk.MenuItem('Reposition Stats') + self.menu.append(self.item3) + self.item3.connect("activate", self.reposition_windows) + self.item3.show() self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) self.main_window.show_all() @@ -118,6 +122,10 @@ class Hud: self.main_window.destroy() self.deleted = True + def reposition_windows(self, args): + for w in self.stat_windows: + self.stat_windows[w].window.move(self.stat_windows[w].x, + self.stat_windows[w].y) def save_layout(self, *args): new_layout = [] # todo: have the hud track the poker table's window position regularly, don't forget to update table.x and table.y. @@ -180,7 +188,6 @@ class Hud: self.stats[config.supported_games[self.poker_game].stats[stat].row] \ [config.supported_games[self.poker_game].stats[stat].col] = \ config.supported_games[self.poker_game].stats[stat].stat_name - # self.mucked_window = gtk.Window() # self.m = Mucked.Mucked(self.mucked_window, self.db_connection) # self.mucked_window.show_all() From 56c928ccdc1888d2af956428d98eeea62ecd9e43 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Fri, 7 Nov 2008 20:26:03 +0000 Subject: [PATCH 259/262] p141 - support new PS UTC timestamps --- pyfpdb/fpdb_simple.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index ab442d85..6b532e97 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -817,17 +817,26 @@ def parseHandStartTime(topline, site): topline=topline[0:pos+1]+"0"+topline[pos+1:] counter+=1 if counter==10: break - + + isUTC=False if site=="ftp": pos = topline.find(" ", len(topline)-26)+1 tmp = topline[pos:] #print "year:", tmp[14:18], "month", tmp[19:21], "day", tmp[22:24], "hour", tmp[0:2], "minute", tmp[3:5], "second", tmp[6:8] result = datetime.datetime(int(tmp[14:18]), int(tmp[19:21]), int(tmp[22:24]), int(tmp[0:2]), int(tmp[3:5]), int(tmp[6:8])) elif site=="ps": - tmp=topline[-30:] - #print "parsehandStartTime, tmp:", tmp - pos = tmp.find("-")+2 - tmp = tmp[pos:] + if topline.find("UTC")!=-1: + pos1 = topline.find("-")+2 + pos2 = topline.find("UTC") + tmp=topline[pos1:pos2] + isUTC=True + print "UTC tmp:",tmp + else: + tmp=topline[-30:] + #print "parsehandStartTime, tmp:", tmp + pos = tmp.find("-")+2 + tmp = tmp[pos:] + print "ET tmp:",tmp #Need to match either # 2008/09/07 06:23:14 ET or # 2008/08/17 - 01:14:43 (ET) @@ -837,9 +846,10 @@ def parseHandStartTime(topline, site): else: raise FpdbError("invalid site in parseHandStartTime") - if site=="ftp" or site=="ps": #these use US ET + if (site=="ftp" or site=="ps") and not isUTC: #these use US ET result+=datetime.timedelta(hours=5) + print "parseHandStartTime result:",result return result #end def parseHandStartTime From 28037de7a6c011ab59ba3b8c3c2f15fa335c1218 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 8 Nov 2008 16:58:24 -0500 Subject: [PATCH 260/262] skip the set_opacity statement in windows until figured out --- pyfpdb/Hud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py index 11cf61cc..2490ad91 100644 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -349,7 +349,8 @@ class Stat_Window: # font = pango.FontDescription("Sans 8") self.label[r][c].modify_font(font) - self.window.set_opacity(parent.colors['hudopacity']) + if not os.name == 'nt': # seems to be a bug in opacity on windows + self.window.set_opacity(parent.colors['hudopacity']) self.window.realize self.window.move(self.x, self.y) self.window.show_all() From 49151a92bfbb110b3ba1af104a255cfb7177915c Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 10 Nov 2008 02:02:12 +0000 Subject: [PATCH 261/262] p142 - commented tourney summary parsing as it fails on me. this is alpha9 --- packaging/gentoo/fpdb-1.0_alpha9_p142.ebuild | 62 ++++++++++++++++++++ pyfpdb/fpdb.py | 3 +- pyfpdb/fpdb_import.py | 4 +- pyfpdb/fpdb_simple.py | 3 - 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 packaging/gentoo/fpdb-1.0_alpha9_p142.ebuild diff --git a/packaging/gentoo/fpdb-1.0_alpha9_p142.ebuild b/packaging/gentoo/fpdb-1.0_alpha9_p142.ebuild new file mode 100644 index 00000000..c55903f3 --- /dev/null +++ b/packaging/gentoo/fpdb-1.0_alpha9_p142.ebuild @@ -0,0 +1,62 @@ +# Copyright 1999-2008 Gentoo Foundation +# Gentoo had nothing to do with the production of this ebuild, but I'm pre-emptively transferring all copyrights (as far as legally possible under my local jurisdiction) to them. +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/games-util/fpdb/fpdb-1.0_alpha8_p137.ebuild,v 1.0 2008/10/17 steffen@sycamoretest.info Exp $ + +NEED_PYTHON=2.3 + +#inherit distutils + +MY_P="fpdb-${PV}" +DESCRIPTION="A database program to track your online poker games" +HOMEPAGE="https://sourceforge.net/projects/fpdb/" +SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2" + +LICENSE="AGPL-3" +SLOT="0" +KEYWORDS="~amd64 ~x86" +#note: this should work on other architectures too, please send me your experiences +IUSE="" + +RDEPEND="virtual/mysql + dev-python/mysql-python + >=x11-libs/gtk+-2.10 + dev-python/pygtk + dev-python/numpy + dev-python/matplotlib" + +DEPEND="${RDEPEND}" + +src_install() { + DIRINST="${D}usr/share/games/fpdb/" + mkdir -p "${DIRINST}" + cp -R * "${DIRINST}" || die + + DIRBIN="${D}usr/games/bin/" + mkdir -p "${DIRBIN}" + #echo "pathes" + #echo "${DIRINST}pyfpdb/fpdb.py" + #echo "${DIRBIN}fpdb.py" + #echo + echo "cd /usr/share/games/fpdb/pyfpdb/ && python fpdb.py" > "${DIRBIN}fpdb" || die + chmod 755 "${DIRBIN}fpdb" || die +} + +#src_test() { +#} + +pkg_postinst() { + elog "Fpdb has been installed and can be called by executing /usr/games/bin/fpdb" + elog "You need to perform a couple more steps manually." + elog "Please also make sure you followed instructions from previous emerges, in particular make sure you configured mysql and set a root pw for it" + elog "Now run this command to connect to MySQL: mysql --user=root --password=yourPassword" + elog "In the mysql command line interface you need to type these two lines (make sure you get the ; at the end)" + elog "In the second line replace \"newPassword\" with a password of your choice" + elog "CREATE DATABASE fpdb;" + elog "GRANT ALL PRIVILEGES ON fpdb.* TO 'fpdb'@'localhost' IDENTIFIED BY 'newPassword' WITH GRANT OPTION;" + elog "Finally copy the default config file from ${DIRINST}docs/default.conf to ~/.fpdb/ for every user that is to use fpdb." + elog "You will need to edit the default.conf, in particular you need to replace the password with what you entered in the \"GRANT ALL...\"" + elog "Finally run the GUI and click the menu database -> recreate tables" + elog "That's it! See our webpage at http://fpdb.sourceforge.net for more documentation" + elog " " +} diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index ad4d3f3d..660bf089 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -331,6 +331,7 @@ class fpdb: #print "start of tab_main_help" mh_tab=gtk.Label("""Welcome to Fpdb! For documentation please visit our website at http://fpdb.sourceforge.net/ or check the docs directory in the fpdb folder. +Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.add_and_display_tab(mh_tab, "Help") #end def tab_main_help @@ -362,7 +363,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha8+, p137 or higher") + self.window.set_title("Free Poker DB - version: alpha9, p142") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index ee70a9d3..3b0fb983 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -174,8 +174,8 @@ class Importer: if firstline.find("Tournament Summary")!=-1: print "TODO: implement importing tournament summaries" - self.faobs = readfile(inputFile) - self.parseTourneyHistory() + #self.faobs = readfile(inputFile) + #self.parseTourneyHistory() return 0 site=fpdb_simple.recogniseSite(firstline) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 6b532e97..d1b191de 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -830,13 +830,11 @@ def parseHandStartTime(topline, site): pos2 = topline.find("UTC") tmp=topline[pos1:pos2] isUTC=True - print "UTC tmp:",tmp else: tmp=topline[-30:] #print "parsehandStartTime, tmp:", tmp pos = tmp.find("-")+2 tmp = tmp[pos:] - print "ET tmp:",tmp #Need to match either # 2008/09/07 06:23:14 ET or # 2008/08/17 - 01:14:43 (ET) @@ -849,7 +847,6 @@ def parseHandStartTime(topline, site): if (site=="ftp" or site=="ps") and not isUTC: #these use US ET result+=datetime.timedelta(hours=5) - print "parseHandStartTime result:",result return result #end def parseHandStartTime From f38cdab8e076104cc562998ff810d13ccc430eb1 Mon Sep 17 00:00:00 2001 From: steffen123 Date: Mon, 10 Nov 2008 02:21:47 +0000 Subject: [PATCH 262/262] p143 - update title --- pyfpdb/fpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 660bf089..bec9be78 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -363,7 +363,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha9, p142") + self.window.set_title("Free Poker DB - version: alpha9+, p143 or higher") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True)