Merge branch 'master' of git://git.assembla.com/fpdboz.git

This commit is contained in:
eblade 2009-06-09 10:14:00 -04:00
commit 868c573643
19 changed files with 1484 additions and 477 deletions

View File

@ -312,11 +312,13 @@ The program itself is licensed under AGPLv3, see agpl-3.0.txt</p>
</TABLE> </TABLE>
<p><BR></P> <p><BR></P>
<p><B>Table HandsPlayers</B></P> <p><B>Table HandsPlayers</B></P>
<p>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 1-10, in tripple draw all 20 and in badugi 1-16 (4*4).</P> <p>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.</P>
<p>For the draw games: the first 5 (badugi: 4) cards are the initial cards, the next 5 (badugi: 4) are after the first draw, etc.<br> <p>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".<br>
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, 2, 3, 5, 6, 7<br> 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<br>
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, 5, 8, J, Q, K.</p> 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<br>
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.</p>
<p>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.</P> <p>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.</P>
<p>ToDo: Original plan was to implement the many flags from hudcache as booleans - need to try this out as it will save space and may therefore be quicker.</p>
<TABLE BORDER=1 CELLPADDING=2 CELLSPACING=0> <TABLE BORDER=1 CELLPADDING=2 CELLSPACING=0>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>Field Name</P></TD> <TD><P>Field Name</P></TD>
@ -353,33 +355,24 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>smallint</P></TD> <TD><P>smallint</P></TD>
<TD><p>The seat in which the person was sitting - necessary for HUD</P></TD> <TD><p>The seat in which the person was sitting - necessary for HUD</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>card1(..7)</P></TD>
<TD><P>smallint</P></TD>
<TD><p>0=none/unknown, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>startCards</P></TD>
<TD><P>smallint</P></TD>
<TD><p>int representing Holdem starting cards.<br/>Hand is stored as an int 13 * x + y where x and y
are in range 0..12, and (x+2) and (y+2) represents rank of each card (2=2 .. 14=Ace). <br/>
If x > y then pair is suited, if x < y then unsuited.<br/>
Omaha and other games may need to use this as a key into another table. (to be decided ...)</P></TD>
</TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>ante</P></TD> <TD><P>ante</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>note: for cash this could be boolean, but in tourneys you may enter a hand with less than the full ante</P></TD> <TD><P>note: for cash this could be boolean, but in tourneys you may enter a hand with less than the full ante</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>cardXValue</P></TD>
<TD><P>smallint</P></TD>
<TD><p>2-10=2-10, J=11, Q=12, K=13, A=14 (even in razz), unknown/no card=x<br>
see note above table</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>cardXSuit</P></TD>
<TD><P>char(1)</P></TD>
<TD><P>h=hearts, s=spades, d=diamonds, c=clubs, unknown/no card=x</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>cardXDiscarded</P></TD>
<TD><P>boolean</P></TD>
<TD><P>Whether the card was discarded (this only applies to draw games, X can be 1 through 15 since the final cards can obviously not be discarded).</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>DrawnX</P></TD>
<TD><P>smallint</P></TD>
<TD><p>X can be 1 through 3.<br>
This field denotes how many cards the player has drawn on each draw.</P></TD>
</TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>winnings</P></TD> <TD><P>winnings</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
@ -388,7 +381,12 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>rake</P></TD> <TD><P>rake</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>rake for this player for this hand</P></TD> <TD><P>rake for this player for this hand (i.e. final pot(s) size = winnings + rake)</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>totalProfit</P></TD>
<TD><P>int</P></TD>
<TD><P>profit for this player for this hand ( i.e. winnings - (ante + bets) )</P></TD>
</TR> </TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>comment</P></TD> <TD><P>comment</P></TD>
@ -405,6 +403,384 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>bigint</P></TD> <TD><P>bigint</P></TD>
<TD><P>references TourneysPlayers.id</P></TD> <TD><P>references TourneysPlayers.id</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>tourneyTypeId</P></TD>
<TD><P>bigint</P></TD>
<TD><P>references TourneyTypes.id (maybe this should be on Hands?)</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>wonWhenSeenStreet1(..4)</P></TD>
<TD><P>float</P></TD>
<TD><P>How many hands the player won after seeing the flop/street4 - this can be a "partial win" if the pot is split.<br>
To be completely clear, this stores a hand count, NOT a money amount.<br/>
(2/3/4: Same for turn/street5, river/street6, street7)</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>wonAtSD</P></TD>
<TD><P>float</P></TD>
<TD><P>As wonWhenSeenStreet1, but for showdown.</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0VPI</P></TD>
<TD><P>int</P></TD>
<TD><P>did player pay to see flop, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0Aggr</P></TD>
<TD><P>int</P></TD>
<TD><P>did player raise before flop, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0_3BChance</P></TD>
<TD><P>int</P></TD>
<TD><P>did player have chance to 3B, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0_3BDone</P></TD>
<TD><P>int</P></TD>
<TD><P>did player 3bet before flop, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0_4BChance</P></TD>
<TD><P>int</P></TD>
<TD><P>did player have chance to 4B, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0_4BDone</P></TD>
<TD><P>int</P></TD>
<TD><P>did player 4bet before flop, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>other_3BStreet0</P></TD>
<TD><P>int</P></TD>
<TD><P>did other player 3bet before flop, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>other_4BStreet0</P></TD>
<TD><P>int</P></TD>
<TD><P>did other player 4bet before flop, 1 or 0</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Seen(/2/3/4)</P></TD>
<TD><P>int</P></TD>
<TD><P>did player see flop/street4 (.. etc)</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>sawShowdown</P></TD>
<TD><P>int</P></TD>
<TD><P>did player see showdown</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Aggr</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where player raised flop/street4</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2Aggr</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where player raised turn/street5</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3Aggr</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where player raised river/street6</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4Aggr</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where player raised street7</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>otherRaisedStreet0</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised pre-flop/street3</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>otherRaisedStreet1</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised flop/street4</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>otherRaisedStreet2</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised turn/street5</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>otherRaisedStreet3</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised river/street6</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>otherRaisedStreet4</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised street7</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToOtherRaisedStreet0</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised flop/street4 and the player folded</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToOtherRaisedStreet1</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised flop/street4 and the player folded</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToOtherRaisedStreet2</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised Turn/street5 and the player folded</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToOtherRaisedStreet3</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised River/street6 and the player folded</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToOtherRaisedStreet4</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised street7 and the player folded</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>stealAttemptChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player was in CO, BTN or SB and nobody has called yet</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>stealAttempted</P></TD>
<TD><P>int</P></TD>
<TD><P>Player took a chance per the above condition</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldBbToStealChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Somebody tried to steal BB from player</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldedBbToSteal</P></TD>
<TD><P>int</P></TD>
<TD><P>Player folded BB to steal attempt</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldSbToStealChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Somebody tried to steal SB from player</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldedSbToSteal</P></TD>
<TD><P>int</P></TD>
<TD><P>Player folded SB to steal attempt</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to make continuation bet on flop/street4</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to make continuation bet on flop/street4</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to make continuation bet on turn/street5</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to make continuation bet on turn/street5</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to make continuation bet on river/street6</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to make continuation bet on river/street6</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to make continuation bet on street7</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to make continuation bet on street7</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet1CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet1CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet2CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet2CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet3CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet3CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet4CBChance</P></TD>
<TD><P>int</P></TD>
<TD><P>Player had chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>foldToStreet4CBDone</P></TD>
<TD><P>int</P></TD>
<TD><P>Player used chance to fold to continuation bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1CheckCallRaiseChance</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player had the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1CheckCallRaiseDone</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player used the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2CheckCallRaiseChance</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player had the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2CheckCallRaiseDone</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player used the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3CheckCallRaiseChance</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player had the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3CheckCallRaiseDone</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player used the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4CheckCallRaiseChance</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player had the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4CheckCallRaiseDone</P></TD>
<TD><P>int</P></TD>
<TD><P>How often player used the chance to do a check-raise or a call-raise on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>actionString</P></TD>
<TD><P>int</P></TD>
<TD><P>Experimental - idea is to store the action on this street as a string: e.g. kkBrcfC, with
player's own choices in upper case and other players in lower case. k=check, b=bet, c=call,
r=raise. (Perhaps NL would miss out bet sizes for this?) It would then be possible to do complex
ad-hoc queries using queries like: actionString like '%B%r%C%
</P></TD>
</TR>
</TABLE> </TABLE>
<p><BR></P> <p><BR></P>
<P><B>Table HudCache</B></P> <P><B>Table HudCache</B></P>
@ -444,12 +820,23 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>smallint</P></TD> <TD><P>smallint</P></TD>
<TD><P>References TourneyTypes.id</P></TD> <TD><P>References TourneyTypes.id</P></TD>
</TR> </TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>HDs</P></TD> <TD><P>HDs</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>number of hands this player played in this gametype with this number of seats</P></TD> <TD><P>number of hands this player played in this gametype with this number of seats</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>wonWhenSeenStreet1(/2/3/4)</P></TD>
<TD><P>float</P></TD>
<TD><P>How many hands the player won after seeing the flop/street4 - this can be a "partial win" if the pot is split.<br>
To be completely clear, this stores a hand count, NOT a money amount.<br/>
(/2/3/4: Same for turn/street5, river/street6, street7)</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>wonAtSD</P></TD>
<TD><P>float</P></TD>
<TD><P>As wonWhenSeenStreet1, but for showdown.</P></TD>
</TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>street0VPI</P></TD> <TD><P>street0VPI</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
@ -463,14 +850,24 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>number of hands where player raised before flop</P></TD> <TD><P>number of hands where player raised before flop</P></TD>
</TR> </TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>street0_3B4BChance</P></TD> <TD><P>street0_3BChance</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>number of hands where player had chance to 3B or 4B</P></TD> <TD><P>number of hands where player had chance to 3B before flop</P></TD>
</TR> </TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>street0_3B4BDone</P></TD> <TD><P>street0_3BDone</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>number of hands where player 3bet/4bet before flop</P></TD> <TD><P>number of hands where player 3bet before flop</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0_4BChance</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where player had chance to 4B before flop</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0_4BDone</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where player 4bet before flop</P></TD>
</TR> </TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>street1Seen</P></TD> <TD><P>street1Seen</P></TD>
@ -517,6 +914,11 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>number of hands where player raised street7</P></TD> <TD><P>number of hands where player raised street7</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>otherRaisedStreet0</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised pre-flop/street3</P></TD>
</TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>otherRaisedStreet1</P></TD> <TD><P>otherRaisedStreet1</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
@ -537,6 +939,11 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>number of hands where someone else raised street7</P></TD> <TD><P>number of hands where someone else raised street7</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>foldToOtherRaisedStreet0</P></TD>
<TD><P>int</P></TD>
<TD><P>number of hands where someone else raised pre-flop/street3 and the player folded</P></TD>
</TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>foldToOtherRaisedStreet1</P></TD> <TD><P>foldToOtherRaisedStreet1</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
@ -557,18 +964,6 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>int</P></TD> <TD><P>int</P></TD>
<TD><P>number of hands where someone else raised street7 and the player folded</P></TD> <TD><P>number of hands where someone else raised street7 and the player folded</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>wonWhenSeenStreet1</P></TD>
<TD><P>float</P></TD>
<TD><P>How many hands the player won after seeing the flop/street4 - this can be a "partial win" if the pot is split.<br>
To be completely clear, this stores a hand count, NOT a money amount.</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>wonAtSD</P></TD>
<TD><P>float</P></TD>
<TD><P>As wonWhenSeenStreet1, but for showdown.</P></TD>
</TR>
<TR VALIGN=TOP> <TR VALIGN=TOP>
<TD><P>stealAttemptChance</P></TD> <TD><P>stealAttemptChance</P></TD>
<TD><P>int</P></TD> <TD><P>int</P></TD>
@ -729,6 +1124,84 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P>How often player used the chance to do a check-raise or a call-raise on this street</P></TD> <TD><P>How often player used the chance to do a check-raise or a call-raise on this street</P></TD>
</TR> </TR>
<TR VALIGN=TOP>
<TD><P>street0Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4Calls</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player called on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4Bets</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player bet on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street0Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street1Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street2Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street3Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>street4Raises</P></TD>
<TD><P>int</P></TD>
<TD><P>Number of times player raised on this street</P></TD>
</TR>
</TABLE> </TABLE>
<P></P> <P></P>
<P><B>Table HandsActions</B></P> <P><B>Table HandsActions</B></P>
@ -926,5 +1399,32 @@ Example 2: If a player gets 2, 3, 5, 8, J of spades for his first five cards and
<TD><P><BR></P></TD> <TD><P><BR></P></TD>
</TR> </TR>
</TABLE> </TABLE>
<p><BR></P>
<p><B>Possible Changes</B></P>
<TABLE BORDER=1 CELLPADDING=2 CELLSPACING=0>
<TR VALIGN=TOP>
<TD><P>Table</P></TD>
<TD><P>Comment</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>BoardCards</P></TD>
<TD><P>Remove as these attributes are now stored on Hands</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>HandsActions</P></TD>
<TD><P>Remove if/when these attributes are stored on Hands or elsewhere</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>HandsPlayers</P></TD>
<TD><P>Move tourneyTypeId field to Hands table.</P></TD>
</TR>
<TR VALIGN=TOP>
<TD><P>Comments</P></TD>
<TD><P>Comment fields on various tables should probably be moved to a single comment table. Aim
should be to where possible reduce tables to a list of fixed length not-null columns and have
the larger, sparser comment columns in a dedicated table. (May not be possible or practical but
something to aim at.)</P></TD>
</TR>
</TABLE>
</BODY> </BODY>
</HTML> </HTML>

View File

@ -43,6 +43,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising Betfair converter class") logging.info("Initialising Betfair converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 7 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()

View File

@ -54,6 +54,7 @@ class CarbonPoker(HandHistoryConverter):
print "Initialising Carbon Poker converter class" print "Initialising Carbon Poker converter class"
HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init
self.setFileType("xml") self.setFileType("xml")
self.siteId = 4 # Needs to match id entry in Sites database
def readSupportedGames(self): def readSupportedGames(self):
pass pass

View File

@ -49,6 +49,7 @@ debugging: if False, pass on partially supported game types. If true, have a go
logging.info("Initialising Everleaf converter class") logging.info("Initialising Everleaf converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 3 # Needs to match id entry in Sites database
self.debugging = debugging self.debugging = debugging
if autostart: if autostart:
self.start() self.start()

View File

@ -44,6 +44,7 @@ class Filters(threading.Thread):
self.games = {} self.games = {}
self.limits = {} self.limits = {}
self.seats = {} self.seats = {}
self.groups = {}
self.siteid = {} self.siteid = {}
self.heroes = {} self.heroes = {}
self.boxes = {} self.boxes = {}
@ -52,6 +53,7 @@ class Filters(threading.Thread):
self.filterText = {'limitsall':'All', 'limitsnone':'None', 'limitsshow':'Show _Limits' self.filterText = {'limitsall':'All', 'limitsnone':'None', 'limitsshow':'Show _Limits'
,'seatsbetween':'Between:', 'seatsand':'And:', 'seatsshow':'Show Number of _Players' ,'seatsbetween':'Between:', 'seatsand':'And:', 'seatsshow':'Show Number of _Players'
,'limitstitle':'Limits:', 'seatstitle':'Number of Players:' ,'limitstitle':'Limits:', 'seatstitle':'Number of Players:'
,'groupstitle':'Grouping:', 'posnshow':'Show Position Stats:'
} }
# For use in date ranges. # For use in date ranges.
@ -109,6 +111,15 @@ class Filters(threading.Thread):
self.fillSeatsFrame(vbox, self.display) self.fillSeatsFrame(vbox, self.display)
seatsFrame.add(vbox) seatsFrame.add(vbox)
# Groups
groupsFrame = gtk.Frame()
groupsFrame.show()
vbox = gtk.VBox(False, 0)
self.sbGroups = {}
self.fillGroupsFrame(vbox, self.display)
groupsFrame.add(vbox)
# Date # Date
dateFrame = gtk.Frame("Date:") dateFrame = gtk.Frame("Date:")
dateFrame.set_label_align(0.0, 0.0) dateFrame.set_label_align(0.0, 0.0)
@ -131,6 +142,7 @@ class Filters(threading.Thread):
self.mainVBox.add(gamesFrame) self.mainVBox.add(gamesFrame)
self.mainVBox.add(limitsFrame) self.mainVBox.add(limitsFrame)
self.mainVBox.add(seatsFrame) self.mainVBox.add(seatsFrame)
self.mainVBox.add(groupsFrame)
self.mainVBox.add(dateFrame) self.mainVBox.add(dateFrame)
self.mainVBox.add(self.Button1) self.mainVBox.add(self.Button1)
self.mainVBox.add(self.Button2) self.mainVBox.add(self.Button2)
@ -148,6 +160,8 @@ class Filters(threading.Thread):
limitsFrame.hide() limitsFrame.hide()
if "Seats" not in self.display or self.display["Seats"] == False: if "Seats" not in self.display or self.display["Seats"] == False:
seatsFrame.hide() seatsFrame.hide()
if "Groups" not in self.display or self.display["Groups"] == False:
groupsFrame.hide()
if "Dates" not in self.display or self.display["Dates"] == False: if "Dates" not in self.display or self.display["Dates"] == False:
dateFrame.hide() dateFrame.hide()
if "Button1" not in self.display or self.display["Button1"] == False: if "Button1" not in self.display or self.display["Button1"] == False:
@ -183,6 +197,9 @@ class Filters(threading.Thread):
self.seats['to'] = self.sbSeats['to'].get_value_as_int() self.seats['to'] = self.sbSeats['to'].get_value_as_int()
return self.seats return self.seats
def getGroups(self):
return self.groups
def getDates(self): def getDates(self):
return self.__get_dates() return self.__get_dates()
@ -274,6 +291,11 @@ class Filters(threading.Thread):
self.seats[seat] = w.get_active() self.seats[seat] = w.get_active()
print "self.seats[%s] set to %s" %(seat, self.seats[seat]) print "self.seats[%s] set to %s" %(seat, self.seats[seat])
def __set_group_select(self, w, group):
#print "__set_seat_select: seat =", seat, "active =", w.get_active()
self.groups[group] = w.get_active()
print "self.groups[%s] set to %s" %(group, self.groups[group])
def fillPlayerFrame(self, vbox): def fillPlayerFrame(self, vbox):
for site in self.conf.get_supported_sites(): for site in self.conf.get_supported_sites():
pathHBox = gtk.HBox(False, 0) pathHBox = gtk.HBox(False, 0)
@ -389,10 +411,33 @@ class Filters(threading.Thread):
self.sbSeats['show'] = cb self.sbSeats['show'] = cb
self.seats['show'] = False self.seats['show'] = False
self.sbSeats['from'] = sb1 self.sbSeats['from'] = sb1
self.sbSeats['to'] = sb2 self.sbSeats['to'] = sb2
def fillGroupsFrame(self, vbox, display):
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, False, 0)
lbl_title = gtk.Label(self.filterText['groupstitle'])
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
hbox.pack_start(lbl_title, expand=True, padding=3)
showb = gtk.Button(label="hide", stock=None, use_underline=True)
showb.set_alignment(xalign=1.0, yalign=0.5)
showb.connect('clicked', self.__toggle_box, 'groups')
hbox.pack_start(showb, expand=False, padding=1)
vbox1 = gtk.VBox(False, 0)
vbox.pack_start(vbox1, False, False, 0)
self.boxes['groups'] = vbox1
hbox = gtk.HBox(False, 0)
vbox1.pack_start(hbox, False, True, 0)
cb = gtk.CheckButton(self.filterText['posnshow'])
cb.connect('clicked', self.__set_group_select, 'posn')
hbox.pack_start(cb, False, False, 0)
self.sbGroups['posn'] = cb
self.groups['posn'] = False
def fillCardsFrame(self, vbox): def fillCardsFrame(self, vbox):
hbox1 = gtk.HBox(True,0) hbox1 = gtk.HBox(True,0)
hbox1.show() hbox1.show()

View File

@ -900,7 +900,7 @@ class FpdbSQLQueries:
GROUP BY h.handStart, hp.handId, hp.totalProfit GROUP BY h.handStart, hp.handId, hp.totalProfit
ORDER BY h.handStart""" ORDER BY h.handStart"""
if self.dbname in ['MySQL InnoDB', 'PostgreSQL']: if self.dbname in ['MySQL InnoDB']:
self.query['playerDetailedStats'] = """ self.query['playerDetailedStats'] = """
select <hgameTypeId> AS hgametypeid select <hgameTypeId> AS hgametypeid
,gt.base ,gt.base
@ -910,6 +910,7 @@ class FpdbSQLQueries:
,min(gt.bigBlind) AS minbigblind ,min(gt.bigBlind) AS minbigblind
,max(gt.bigBlind) AS maxbigblind ,max(gt.bigBlind) AS maxbigblind
/*,<hcgametypeId> AS gtid*/ /*,<hcgametypeId> AS gtid*/
,<position> AS plposition
,count(1) AS n ,count(1) AS n
,100.0*sum(cast(hp.street0VPI as <signed>integer))/count(1) AS vpip ,100.0*sum(cast(hp.street0VPI as <signed>integer))/count(1) AS vpip
,100.0*sum(cast(hp.street0Aggr as <signed>integer))/count(1) AS pfr ,100.0*sum(cast(hp.street0Aggr as <signed>integer))/count(1) AS pfr
@ -949,25 +950,111 @@ class FpdbSQLQueries:
,avg(h.seats+0.0) AS avgseats ,avg(h.seats+0.0) AS avgseats
,variance(hp.totalProfit/100.0) AS variance ,variance(hp.totalProfit/100.0) AS variance
from HandsPlayers hp from HandsPlayers hp
inner join Hands h on (h.id = hp.handId) inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId) inner join Gametypes gt on (gt.Id = h.gameTypeId)
inner join Sites s on (s.Id = gt.siteId) inner join Sites s on (s.Id = gt.siteId)
where hp.playerId in <player_test> where hp.playerId in <player_test>
and hp.tourneysPlayersId IS NULL and hp.tourneysPlayersId IS NULL
and h.seats <seats_test> and h.seats <seats_test>
<flagtest> <flagtest>
<gtbigBlind_test> <gtbigBlind_test>
and date_format(h.handStart, '%Y-%m-%d') <datestest>
group by hgameTypeId group by hgameTypeId
,hp.playerId ,hp.playerId
,gt.base ,gt.base
,gt.category ,gt.category
<groupbyseats> <groupbyseats>
,plposition
,upper(gt.limitType) ,upper(gt.limitType)
,s.name ,s.name
order by hp.playerId order by hp.playerId
,gt.base ,gt.base
,gt.category ,gt.category
<orderbyseats> <orderbyseats>
,case <position> when 'B' then 'B'
when 'S' then 'S'
else concat('Z', <position>)
end
<orderbyhgameTypeId>
,maxbigblind desc
,upper(gt.limitType)
,s.name
"""
elif self.dbname in ['PostgreSQL']:
self.query['playerDetailedStats'] = """
select <hgameTypeId> AS hgametypeid
,gt.base
,gt.category
,upper(gt.limitType) AS limittype
,s.name
,min(gt.bigBlind) AS minbigblind
,max(gt.bigBlind) AS maxbigblind
/*,<hcgametypeId> AS gtid*/
,<position> AS plposition
,count(1) AS n
,100.0*sum(cast(hp.street0VPI as <signed>integer))/count(1) AS vpip
,100.0*sum(cast(hp.street0Aggr as <signed>integer))/count(1) AS pfr
,case when sum(cast(hp.street0_3Bchance as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street0_3Bdone as <signed>integer))/sum(cast(hp.street0_3Bchance as <signed>integer))
end AS pf3
,case when sum(cast(hp.stealattemptchance as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.stealattempted as <signed>integer))/sum(cast(hp.stealattemptchance as <signed>integer))
end AS steals
,100.0*sum(cast(hp.street1Seen as <signed>integer))/count(1) AS saw_f
,100.0*sum(cast(hp.sawShowdown as <signed>integer))/count(1) AS sawsd
,case when sum(cast(hp.street1Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.sawShowdown as <signed>integer))/sum(cast(hp.street1Seen as <signed>integer))
end AS wtsdwsf
,case when sum(cast(hp.sawShowdown as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.wonAtSD as <signed>integer))/sum(cast(hp.sawShowdown as <signed>integer))
end AS wmsd
,case when sum(cast(hp.street1Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street1Aggr as <signed>integer))/sum(cast(hp.street1Seen as <signed>integer))
end AS flafq
,case when sum(cast(hp.street2Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street2Aggr as <signed>integer))/sum(cast(hp.street2Seen as <signed>integer))
end AS tuafq
,case when sum(cast(hp.street3Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street3Aggr as <signed>integer))/sum(cast(hp.street3Seen as <signed>integer))
end AS rvafq
,case when sum(cast(hp.street1Seen as <signed>integer))+sum(cast(hp.street2Seen as <signed>integer))+sum(cast(hp.street3Seen as <signed>integer)) = 0 then -999
else 100.0*(sum(cast(hp.street1Aggr as <signed>integer))+sum(cast(hp.street2Aggr as <signed>integer))+sum(cast(hp.street3Aggr as <signed>integer)))
/(sum(cast(hp.street1Seen as <signed>integer))+sum(cast(hp.street2Seen as <signed>integer))+sum(cast(hp.street3Seen as <signed>integer)))
end AS pofafq
,sum(hp.totalProfit)/100.0 AS net
,sum(hp.rake)/100.0 AS rake
,100.0*avg(hp.totalProfit/(gt.bigBlind+0.0)) AS bbper100
,avg(hp.totalProfit)/100.0 AS profitperhand
,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr
,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr
,avg(h.seats+0.0) AS avgseats
,variance(hp.totalProfit/100.0) AS variance
from HandsPlayers hp
inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId)
inner join Sites s on (s.Id = gt.siteId)
where hp.playerId in <player_test>
and hp.tourneysPlayersId IS NULL
and h.seats <seats_test>
<flagtest>
<gtbigBlind_test>
and to_char(h.handStart, 'YYYY-MM-DD') <datestest>
group by hgameTypeId
,hp.playerId
,gt.base
,gt.category
<groupbyseats>
,plposition
,upper(gt.limitType)
,s.name
order by hp.playerId
,gt.base
,gt.category
<orderbyseats>
,case <position> when 'B' then 'B'
when 'S' then 'S'
else 'Z'||<position>
end
<orderbyhgameTypeId> <orderbyhgameTypeId>
,maxbigblind desc ,maxbigblind desc
,upper(gt.limitType) ,upper(gt.limitType)

View File

@ -45,6 +45,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising Fulltilt converter class") logging.info("Initialising Fulltilt converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 1 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()

View File

@ -60,7 +60,7 @@ class GuiPlayerStats (threading.Thread):
"LimitSep" : True, "LimitSep" : True,
"Seats" : True, "Seats" : True,
"SeatSep" : True, "SeatSep" : True,
"Dates" : False, "Dates" : True,
"Groups" : True, "Groups" : True,
"Button1" : True, "Button1" : True,
"Button2" : True "Button2" : True
@ -76,26 +76,28 @@ class GuiPlayerStats (threading.Thread):
# ToDo: create popup to adjust column config # ToDo: create popup to adjust column config
# columns to display, keys match column name returned by sql, values in tuple are: # columns to display, keys match column name returned by sql, values in tuple are:
# is column displayed, column heading, xalignment, formatting # is column displayed, column heading, xalignment, formatting
self.columns = [ ("game", True, "Game", 0.0, "%s") self.columns = [ ["game", True, "Game", 0.0, "%s"]
, ("hand", False, "Hand", 0.0, "%s") # true not allowed for this line , ["hand", False, "Hand", 0.0, "%s"] # true not allowed for this line
, ("n", True, "Hds", 1.0, "%d") , ["plposition", False, "Posn", 1.0, "%s"] # true not allowed for this line (set in code)
, ("avgseats", True, "Seats", 1.0, "%3.1f") , ["n", True, "Hds", 1.0, "%d"]
, ("vpip", True, "VPIP", 1.0, "%3.1f") , ["avgseats", True, "Seats", 1.0, "%3.1f"]
, ("pfr", True, "PFR", 1.0, "%3.1f") , ["vpip", True, "VPIP", 1.0, "%3.1f"]
, ("pf3", True, "PF3", 1.0, "%3.1f") , ["pfr", True, "PFR", 1.0, "%3.1f"]
, ("steals", True, "Steals", 1.0, "%3.1f") , ["pf3", True, "PF3", 1.0, "%3.1f"]
, ("saw_f", True, "Saw_F", 1.0, "%3.1f") , ["steals", True, "Steals", 1.0, "%3.1f"]
, ("sawsd", True, "SawSD", 1.0, "%3.1f") , ["saw_f", True, "Saw_F", 1.0, "%3.1f"]
, ("wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f") , ["sawsd", True, "SawSD", 1.0, "%3.1f"]
, ("wmsd", True, "W$SD", 1.0, "%3.1f") , ["wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f"]
, ("flafq", True, "FlAFq", 1.0, "%3.1f") , ["wmsd", True, "W$SD", 1.0, "%3.1f"]
, ("tuafq", True, "TuAFq", 1.0, "%3.1f") , ["flafq", True, "FlAFq", 1.0, "%3.1f"]
, ("rvafq", True, "RvAFq", 1.0, "%3.1f") , ["tuafq", True, "TuAFq", 1.0, "%3.1f"]
, ("pofafq", False, "PoFAFq", 1.0, "%3.1f") , ["rvafq", True, "RvAFq", 1.0, "%3.1f"]
, ("net", True, "Net($)", 1.0, "%6.2f") , ["pofafq", False, "PoFAFq", 1.0, "%3.1f"]
, ("bbper100", True, "BB/100", 1.0, "%4.2f") , ["net", True, "Net($)", 1.0, "%6.2f"]
, ("rake", True, "Rake($)", 1.0, "%6.2f") , ["bbper100", True, "bb/100", 1.0, "%4.2f"]
, ("variance", True, "Variance", 1.0, "%5.2f") , ["rake", True, "Rake($)", 1.0, "%6.2f"]
, ["bb100xr", True, "bbxr/100", 1.0, "%4.2f"]
, ["variance", True, "Variance", 1.0, "%5.2f"]
] ]
# Detail filters: This holds the data used in the popup window, extra values are # Detail filters: This holds the data used in the popup window, extra values are
@ -135,7 +137,7 @@ class GuiPlayerStats (threading.Thread):
self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True) self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
# make sure Hand column is not displayed # make sure Hand column is not displayed
[x for x in self.columns if x[0] == 'hand'][0][1] == False [x for x in self.columns if x[0] == 'hand'][0][1] = False
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
@ -155,6 +157,8 @@ class GuiPlayerStats (threading.Thread):
siteids = self.filters.getSiteIds() siteids = self.filters.getSiteIds()
limits = self.filters.getLimits() limits = self.filters.getLimits()
seats = self.filters.getSeats() seats = self.filters.getSeats()
groups = self.filters.getGroups()
dates = self.filters.getDates()
sitenos = [] sitenos = []
playerids = [] playerids = []
@ -178,16 +182,16 @@ class GuiPlayerStats (threading.Thread):
print "No limits found" print "No limits found"
return return
self.createStatsTable(vbox, playerids, sitenos, limits, seats) self.createStatsTable(vbox, playerids, sitenos, limits, seats, groups, dates)
def createStatsTable(self, vbox, playerids, sitenos, limits, seats): def createStatsTable(self, vbox, playerids, sitenos, limits, seats, groups, dates):
starttime = time() starttime = time()
# Display summary table at top of page # Display summary table at top of page
# 3rd parameter passes extra flags, currently includes: # 3rd parameter passes extra flags, currently includes:
# holecards - whether to display card breakdown (True/False) # holecards - whether to display card breakdown (True/False)
flags = [False] flags = [False]
self.addTable(vbox, 'playerDetailedStats', flags, playerids, sitenos, limits, seats) self.addTable(vbox, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates)
# Separator # Separator
sep = gtk.HSeparator() sep = gtk.HSeparator()
@ -210,13 +214,13 @@ class GuiPlayerStats (threading.Thread):
# Detailed table # Detailed table
flags = [True] flags = [True]
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats) self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates)
self.db.db.commit() self.db.db.commit()
print "Stats page displayed in %4.2f seconds" % (time() - starttime) print "Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox): #end def fillStatsFrame(self, vbox):
def addTable(self, vbox, query, flags, playerids, sitenos, limits, seats): def addTable(self, vbox, query, flags, playerids, sitenos, limits, seats, groups, dates):
row = 0 row = 0
sqlrow = 0 sqlrow = 0
colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4 colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4
@ -229,7 +233,7 @@ class GuiPlayerStats (threading.Thread):
self.stats_table.show() self.stats_table.show()
tmp = self.sql.query[query] tmp = self.sql.query[query]
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats) tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats, groups, dates)
self.cursor.execute(tmp) self.cursor.execute(tmp)
result = self.cursor.fetchall() result = self.cursor.fetchall()
colnames = [desc[0].lower() for desc in self.cursor.description] colnames = [desc[0].lower() for desc in self.cursor.description]
@ -243,6 +247,8 @@ class GuiPlayerStats (threading.Thread):
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
vbox.pack_start(view, expand=False, padding=3) vbox.pack_start(view, expand=False, padding=3)
textcell = gtk.CellRendererText() textcell = gtk.CellRendererText()
textcell50 = gtk.CellRendererText()
textcell50.set_property('xalign', 0.5)
numcell = gtk.CellRendererText() numcell = gtk.CellRendererText()
numcell.set_property('xalign', 1.0) numcell.set_property('xalign', 1.0)
listcols = [] listcols = []
@ -256,17 +262,18 @@ class GuiPlayerStats (threading.Thread):
listcols.append(gtk.TreeViewColumn(s)) listcols.append(gtk.TreeViewColumn(s))
view.append_column(listcols[col]) view.append_column(listcols[col])
if column[colformat] == '%s': if column[colformat] == '%s':
if col == 1 and holecards: if column[colxalign] == 0.0:
listcols[col].pack_start(textcell, expand=True) listcols[col].pack_start(textcell, expand=True)
listcols[col].add_attribute(textcell, 'text', col)
else: else:
listcols[col].pack_start(textcell, expand=True) listcols[col].pack_start(textcell50, expand=True)
listcols[col].add_attribute(textcell, 'text', col) listcols[col].add_attribute(textcell50, 'text', col)
listcols[col].set_expand(True) listcols[col].set_expand(True)
else: else:
listcols[col].pack_start(numcell, expand=True) listcols[col].pack_start(numcell, expand=True)
listcols[col].add_attribute(numcell, 'text', col) listcols[col].add_attribute(numcell, 'text', col)
listcols[col].set_alignment(1.0)
listcols[col].set_expand(True) listcols[col].set_expand(True)
#listcols[col].set_alignment(column[colxalign]) # no effect?
rows = len(result) # +1 for title row rows = len(result) # +1 for title row
@ -279,6 +286,11 @@ class GuiPlayerStats (threading.Thread):
for col,column in enumerate(cols_to_show): for col,column in enumerate(cols_to_show):
if column[colalias] in colnames: if column[colalias] in colnames:
value = result[sqlrow][colnames.index(column[colalias])] value = result[sqlrow][colnames.index(column[colalias])]
if column[colalias] == 'plposition':
if value == 'B':
value = 'BB'
if value == 'S':
value = 'SB'
else: else:
if column[colalias] == 'game': if column[colalias] == 'game':
if holecards: if holecards:
@ -311,7 +323,7 @@ class GuiPlayerStats (threading.Thread):
#end def addTable(self, query, vars, playerids, sitenos, limits, seats): #end def addTable(self, query, vars, playerids, sitenos, limits, seats):
def refineQuery(self, query, flags, playerids, sitenos, limits, seats): def refineQuery(self, query, flags, playerids, sitenos, limits, seats, groups, dates):
if not flags: holecards = False if not flags: holecards = False
else: holecards = flags[0] else: holecards = flags[0]
@ -371,6 +383,19 @@ class GuiPlayerStats (threading.Thread):
else: else:
query = query.replace("<signed>", '') query = query.replace("<signed>", '')
# Filter on dates
query = query.replace("<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'")
# Group by position?
if groups['posn']:
query = query.replace("<position>", 'hp.position')
# set flag in self.columns to show posn column
[x for x in self.columns if x[0] == 'plposition'][0][1] = True
else:
query = query.replace("<position>", "'1'")
# unset flag in self.columns to hide posn column
[x for x in self.columns if x[0] == 'plposition'][0][1] = False
#print "query =\n", query #print "query =\n", query
return(query) return(query)
#end def refineQuery(self, query, playerids, sitenos, limits): #end def refineQuery(self, query, playerids, sitenos, limits):
@ -438,3 +463,6 @@ class GuiPlayerStats (threading.Thread):
detailDialog.destroy() detailDialog.destroy()

View File

@ -87,7 +87,7 @@ class GuiPositionalStats (threading.Thread):
) )
self.posnheads = ( "Game", "Seats", "Posn", "VPIP", "PFR", "PF3", "Steals" self.posnheads = ( "Game", "Seats", "Posn", "VPIP", "PFR", "PF3", "Steals"
, "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq" , "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq"
, "PoFAFq", "Net($)", "BB/100", "$/hand", "Variance", "Hds" , "PoFAFq", "Net($)", "bb/100", "$/hand", "Variance", "Hds"
) )
self.fillStatsFrame(self.stats_frame) self.fillStatsFrame(self.stats_frame)

View File

@ -23,11 +23,11 @@ import os
import os.path import os.path
from decimal import Decimal from decimal import Decimal
import operator import operator
import time import time,datetime
from copy import deepcopy from copy import deepcopy
from Exceptions import * from Exceptions import *
import DerivedStats import DerivedStats
import Card
class Hand: class Hand:
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'} UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'}
@ -36,6 +36,7 @@ class Hand:
self.sitename = sitename self.sitename = sitename
self.stats = DerivedStats.DerivedStats(self) self.stats = DerivedStats.DerivedStats(self)
self.gametype = gametype self.gametype = gametype
self.starttime = 0
self.handText = handText self.handText = handText
self.handid = 0 self.handid = 0
self.tablename = "Slartibartfast" self.tablename = "Slartibartfast"
@ -86,10 +87,59 @@ Should not commit, and do minimal selects. Callers may want to cache commits
db: a connected fpdb_db object""" db: a connected fpdb_db object"""
# TODO: # TODO:
# Players - base playerid and siteid tuple # Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
# HudCache data to come from DerivedStats class # HudCache data to come from DerivedStats class
# HandsActions - all actions for all players for all streets - self.actions # HandsActions - all actions for all players for all streets - self.actions
# BoardCards - ? # BoardCards - Skip - no longer necessary?
# Hands - Summary information of hand indexed by handId - gameinfo # Hands - Summary information of hand indexed by handId - gameinfo
#hh['siteHandNo'] = self.handid
# gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
#
#hh['handStart'] = self.starttime
# seats TINYINT NOT NULL,
#
#hh['tableName'] = self.tablenam
#hh['maxSeats'] = self.maxseats
# boardcard1 smallint, /* 0=none, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As */
# boardcard2 smallint,
# boardcard3 smallint,
# boardcard4 smallint,
# boardcard5 smallint,
# texture smallint,
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
# Needs to be recorded
# playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4 */
# Needs to be recorded
# playersAtStreet2 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtStreet3 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtStreet4 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtShowdown SMALLINT NOT NULL,
# Needs to be recorded
# street0Raises TINYINT NOT NULL, /* num small bets paid to see flop/street4, including blind */
# Needs to be recorded
# street1Raises TINYINT NOT NULL, /* num small bets paid to see turn/street5 */
# Needs to be recorded
# street2Raises TINYINT NOT NULL, /* num big bets paid to see river/street6 */
# Needs to be recorded
# street3Raises TINYINT NOT NULL, /* num big bets paid to see sd/street7 */
# Needs to be recorded
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
# Needs to be recorded
# street1Pot INT, /* pot size at flop/street4 */
# Needs to be recorded
# street2Pot INT, /* pot size at turn/street5 */
# Needs to be recorded
# street3Pot INT, /* pot size at river/street6 */
# Needs to be recorded
# street4Pot INT, /* pot size at sd/street7 */
# Needs to be recorded
# showdownPot INT, /* pot size at sd/street7 */
# comment TEXT,
# commentTs DATETIME
# handid = db.storeHand(hh)
# HandsPlayers - ? ... Do we fix winnings? # HandsPlayers - ? ... Do we fix winnings?
# Tourneys ? # Tourneys ?
# TourneysPlayers # TourneysPlayers
@ -98,7 +148,8 @@ db: a connected fpdb_db object"""
def select(self, handId): def select(self, handId):
""" Function to create Hand object from database """ """ Function to create Hand object from database """
pass
def addPlayer(self, seat, name, chips): def addPlayer(self, seat, name, chips):
@ -358,34 +409,34 @@ Map the tuple self.gametype onto the pokerstars string describing it
def printActionLine(self, act, fh): def printActionLine(self, act, fh):
if act[1] == 'folds': if act[1] == 'folds':
print >>fh, _("%s: folds " %(act[0])) print >>fh, ("%s: folds " %(act[0]))
elif act[1] == 'checks': elif act[1] == 'checks':
print >>fh, _("%s: checks " %(act[0])) print >>fh, ("%s: checks " %(act[0]))
elif act[1] == 'calls': elif act[1] == 'calls':
print >>fh, _("%s: calls $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else '')) print >>fh, ("%s: calls $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'bets': elif act[1] == 'bets':
print >>fh, _("%s: bets $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else '')) print >>fh, ("%s: bets $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'raises': elif act[1] == 'raises':
print >>fh, _("%s: raises $%s to $%s%s" %(act[0], act[2], act[3], ' and is all-in' if act[5] else '')) print >>fh, ("%s: raises $%s to $%s%s" %(act[0], act[2], act[3], ' and is all-in' if act[5] else ''))
elif act[1] == 'completea': elif act[1] == 'completea':
print >>fh, _("%s: completes to $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else '')) print >>fh, ("%s: completes to $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'posts': elif act[1] == 'posts':
if(act[2] == "small blind"): if(act[2] == "small blind"):
print >>fh, _("%s: posts small blind $%s%s" %(act[0], act[3], ' and is all-in' if act[4] else '')) print >>fh, ("%s: posts small blind $%s%s" %(act[0], act[3], ' and is all-in' if act[4] else ''))
elif(act[2] == "big blind"): elif(act[2] == "big blind"):
print >>fh, _("%s: posts big blind $%s%s" %(act[0], act[3], ' and is all-in' if act[4] else '')) print >>fh, ("%s: posts big blind $%s%s" %(act[0], act[3], ' and is all-in' if act[4] else ''))
elif(act[2] == "both"): elif(act[2] == "both"):
print >>fh, _("%s: posts small & big blinds $%s%s" %(act[0], act[3], ' and is all-in' if act[4] else '')) print >>fh, ("%s: posts small & big blinds $%s%s" %(act[0], act[3], ' and is all-in' if act[4] else ''))
elif act[1] == 'bringin': elif act[1] == 'bringin':
print >>fh, _("%s: brings in for $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else '')) print >>fh, ("%s: brings in for $%s%s" %(act[0], act[2], ' and is all-in' if act[3] else ''))
elif act[1] == 'discards': elif act[1] == 'discards':
print >>fh, _("%s: discards %s %s%s" %(act[0], act[2], 'card' if act[2] == 1 else 'cards' , " [" + " ".join(self.discards[act[0]]['DRAWONE']) + "]" if self.hero == act[0] else '')) print >>fh, ("%s: discards %s %s%s" %(act[0], act[2], 'card' if act[2] == 1 else 'cards' , " [" + " ".join(self.discards[act[0]]['DRAWONE']) + "]" if self.hero == act[0] else ''))
elif act[1] == 'stands pat': elif act[1] == 'stands pat':
print >>fh, _("%s: stands pat" %(act[0])) print >>fh, ("%s: stands pat" %(act[0]))
class HoldemOmahaHand(Hand): class HoldemOmahaHand(Hand):
def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC"): def __init__(self, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
if gametype['base'] != 'hold': if gametype['base'] != 'hold':
pass # or indeed don't pass and complain instead pass # or indeed don't pass and complain instead
logging.debug("HoldemOmahaHand") logging.debug("HoldemOmahaHand")
@ -420,7 +471,14 @@ class HoldemOmahaHand(Hand):
self.totalPot() # finalise it (total the pot) self.totalPot() # finalise it (total the pot)
hhc.getRake(self) hhc.getRake(self)
elif builtFrom == "DB": elif builtFrom == "DB":
self.select("dummy") # Will need a handId if handid is not None:
self.select(handid) # Will need a handId
else:
logging.warning("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid")
else:
logging.warning("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided")
pass
def addHoleCards(self, cards, player, shown=False): def addHoleCards(self, cards, player, shown=False):
"""\ """\
@ -458,41 +516,41 @@ Card ranks will be uppercased
def writeHand(self, fh=sys.__stdout__): def writeHand(self, fh=sys.__stdout__):
# PokerStars format. # PokerStars format.
print >>fh, _("%s Game #%s: %s ($%s/$%s) - %s" %("PokerStars", self.handid, self.getGameTypeAsString(), self.sb, self.bb, time.strftime('%Y/%m/%d - %H:%M:%S ET', self.starttime))) print >>fh, ("%s Game #%s: %s ($%s/$%s) - %s" %("PokerStars", self.handid, self.getGameTypeAsString(), self.sb, self.bb, datetime.datetime.strftime(self.starttime,'%Y/%m/%d - %H:%M:%S ET')))
print >>fh, _("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos)) print >>fh, ("Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos))
players_who_act_preflop = set(([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']])) players_who_act_preflop = set(([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
logging.debug(self.actions['PREFLOP']) logging.debug(self.actions['PREFLOP'])
for player in [x for x in self.players if x[1] in players_who_act_preflop]: for player in [x for x in self.players if x[1] in players_who_act_preflop]:
#Only print stacks of players who do something preflop #Only print stacks of players who do something preflop
print >>fh, _("Seat %s: %s ($%s in chips) " %(player[0], player[1], player[2])) print >>fh, ("Seat %s: %s ($%s in chips) " %(player[0], player[1], player[2]))
if self.actions['BLINDSANTES']: if self.actions['BLINDSANTES']:
for act in self.actions['BLINDSANTES']: for act in self.actions['BLINDSANTES']:
self.printActionLine(act, fh) self.printActionLine(act, fh)
print >>fh, _("*** HOLE CARDS ***") print >>fh, ("*** HOLE CARDS ***")
if self.involved: if self.involved:
print >>fh, _("Dealt to %s [%s]" %(self.hero , " ".join(self.holecards[self.hero]['PREFLOP']))) print >>fh, ("Dealt to %s [%s]" %(self.hero , " ".join(self.holecards[self.hero]['PREFLOP'])))
if self.actions['PREFLOP']: if self.actions['PREFLOP']:
for act in self.actions['PREFLOP']: for act in self.actions['PREFLOP']:
self.printActionLine(act, fh) self.printActionLine(act, fh)
if self.board['FLOP']: if self.board['FLOP']:
print >>fh, _("*** FLOP *** [%s]" %( " ".join(self.board['FLOP']))) print >>fh, ("*** FLOP *** [%s]" %( " ".join(self.board['FLOP'])))
if self.actions['FLOP']: if self.actions['FLOP']:
for act in self.actions['FLOP']: for act in self.actions['FLOP']:
self.printActionLine(act, fh) self.printActionLine(act, fh)
if self.board['TURN']: if self.board['TURN']:
print >>fh, _("*** TURN *** [%s] [%s]" %( " ".join(self.board['FLOP']), " ".join(self.board['TURN']))) print >>fh, ("*** TURN *** [%s] [%s]" %( " ".join(self.board['FLOP']), " ".join(self.board['TURN'])))
if self.actions['TURN']: if self.actions['TURN']:
for act in self.actions['TURN']: for act in self.actions['TURN']:
self.printActionLine(act, fh) self.printActionLine(act, fh)
if self.board['RIVER']: if self.board['RIVER']:
print >>fh, _("*** RIVER *** [%s] [%s]" %(" ".join(self.board['FLOP']+self.board['TURN']), " ".join(self.board['RIVER']) )) print >>fh, ("*** RIVER *** [%s] [%s]" %(" ".join(self.board['FLOP']+self.board['TURN']), " ".join(self.board['RIVER']) ))
if self.actions['RIVER']: if self.actions['RIVER']:
for act in self.actions['RIVER']: for act in self.actions['RIVER']:
self.printActionLine(act, fh) self.printActionLine(act, fh)
@ -502,7 +560,7 @@ Card ranks will be uppercased
# The logic for a showdown is: at the end of river action there are at least two players in the hand # The logic for a showdown is: at the end of river action there are at least two players in the hand
# we probably don't need a showdown section in pseudo stars format for our filtering purposes # we probably don't need a showdown section in pseudo stars format for our filtering purposes
if self.shown: if self.shown:
print >>fh, _("*** SHOW DOWN ***") print >>fh, ("*** SHOW DOWN ***")
for name in self.shown: for name in self.shown:
# TODO: legacy importer can't handle only one holecard here, make sure there are 2 for holdem, 4 for omaha # TODO: legacy importer can't handle only one holecard here, make sure there are 2 for holdem, 4 for omaha
# TOOD: If HoldHand subclass supports more than omahahi, omahahilo, holdem, add them here # TOOD: If HoldHand subclass supports more than omahahi, omahahilo, holdem, add them here
@ -512,7 +570,7 @@ Card ranks will be uppercased
elif self.gametype['category'] in ('holdem'): elif self.gametype['category'] in ('holdem'):
numOfHoleCardsNeeded = 2 numOfHoleCardsNeeded = 2
if len(self.holecards[name]['PREFLOP']) == numOfHoleCardsNeeded: if len(self.holecards[name]['PREFLOP']) == numOfHoleCardsNeeded:
print >>fh, _("%s shows [%s] (a hand...)" % (name, " ".join(self.holecards[name]['PREFLOP']))) print >>fh, ("%s shows [%s] (a hand...)" % (name, " ".join(self.holecards[name]['PREFLOP'])))
# Current PS format has the lines: # Current PS format has the lines:
# Uncalled bet ($111.25) returned to s0rrow # Uncalled bet ($111.25) returned to s0rrow
@ -522,35 +580,35 @@ Card ranks will be uppercased
# Immediately before the summary. # Immediately before the summary.
# The current importer uses those lines for importing winning rather than the summary # The current importer uses those lines for importing winning rather than the summary
for name in self.pot.returned: for name in self.pot.returned:
print >>fh, _("Uncalled bet ($%s) returned to %s" %(self.pot.returned[name],name)) print >>fh, ("Uncalled bet ($%s) returned to %s" %(self.pot.returned[name],name))
for entry in self.collected: for entry in self.collected:
print >>fh, _("%s collected $%s from x pot" %(entry[0], entry[1])) print >>fh, ("%s collected $%s from x pot" %(entry[0], entry[1]))
print >>fh, _("*** SUMMARY ***") print >>fh, ("*** SUMMARY ***")
print >>fh, "%s | Rake $%.2f" % (self.pot, self.rake) print >>fh, "%s | Rake $%.2f" % (self.pot, self.rake)
board = [] board = []
for s in self.board.values(): for s in self.board.values():
board += s board += s
if board: # sometimes hand ends preflop without a board if board: # sometimes hand ends preflop without a board
print >>fh, _("Board [%s]" % (" ".join(board))) print >>fh, ("Board [%s]" % (" ".join(board)))
for player in [x for x in self.players if x[1] in players_who_act_preflop]: for player in [x for x in self.players if x[1] in players_who_act_preflop]:
seatnum = player[0] seatnum = player[0]
name = player[1] name = player[1]
if name in self.collectees and name in self.shown: if name in self.collectees and name in self.shown:
print >>fh, _("Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]['PREFLOP']), self.collectees[name])) print >>fh, ("Seat %d: %s showed [%s] and won ($%s)" % (seatnum, name, " ".join(self.holecards[name]['PREFLOP']), self.collectees[name]))
elif name in self.collectees: elif name in self.collectees:
print >>fh, _("Seat %d: %s collected ($%s)" % (seatnum, name, self.collectees[name])) print >>fh, ("Seat %d: %s collected ($%s)" % (seatnum, name, self.collectees[name]))
#~ elif name in self.shown: #~ elif name in self.shown:
#~ print >>fh, _("Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name]['PREFLOP']))) #~ print >>fh, _("Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name]['PREFLOP'])))
elif name in self.folded: elif name in self.folded:
print >>fh, _("Seat %d: %s folded" % (seatnum, name)) print >>fh, ("Seat %d: %s folded" % (seatnum, name))
else: else:
if name in self.shown: if name in self.shown:
print >>fh, _("Seat %d: %s showed [%s] and lost with..." % (seatnum, name, " ".join(self.holecards[name]['PREFLOP']))) print >>fh, ("Seat %d: %s showed [%s] and lost with..." % (seatnum, name, " ".join(self.holecards[name]['PREFLOP'])))
else: else:
print >>fh, _("Seat %d: %s mucked" % (seatnum, name)) print >>fh, ("Seat %d: %s mucked" % (seatnum, name))
print >>fh, "\n\n" print >>fh, "\n\n"
@ -1036,6 +1094,154 @@ class Pot(object):
# no small blind and walk in bb (hopefully) # no small blind and walk in bb (hopefully)
return "Total pot $%.2f" % (self.total,) return "Total pot $%.2f" % (self.total,)
else: else:
return _("too many pots.. no small blind and walk in bb?. self.pots: %s" %(self.pots)) return ("too many pots.. no small blind and walk in bb?. self.pots: %s" %(self.pots))
# I don't know stars format for a walk in the bb when sb doesn't post. # I don't know stars format for a walk in the bb when sb doesn't post.
# The thing to do here is raise a Hand error like fpdb import does and file it into errors.txt # The thing to do here is raise a Hand error like fpdb import does and file it into errors.txt
def assemble(cnxn, handid):
c = cnxn.cursor()
# We need the following for the Hand.__init__
c.execute("""
select
s.name,
g.category,
g.base,
g.type,
g.limitType,
g.hilo,
g.smallBlind / 100.0,
g.bigBlind / 100.0 ,
g.smallBet / 100.0,
g.bigBet / 100.0,
s.currency,
bc.card1value,
bc.card1suit,
bc.card2value,bc.card2suit,
bc.card3value,bc.card3suit,
bc.card4value,bc.card4suit,
bc.card5value,bc.card5suit
from
hands as h,
boardcards as bc,
sites as s,
gametypes as g,
handsplayers as hp,
players as p
where
h.id = %(handid)s
and bc.handid = h.id
and g.id = h.gametypeid
and hp.handid = h.id
and p.id = hp.playerid
and s.id = p.siteid
limit 1""", {'handid':handid})
#TODO: siteid should be in hands table - we took the scenic route through players here.
res = c.fetchone()
gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]}
h = HoldemOmahaHand(hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
cards = map("".join, zip(map(str,res[11:21:2]), res[12:21:2]))
if cards[0] != "0x":
h.setCommunityCards('FLOP', cards[0:3])
if cards[3] != "0x":
h.setCommunityCards('TURN', cards[3])
if cards[4] != "0x":
h.setCommunityCards('RIVER', cards[4])
#[Card.valueSuitFromCard(x) for x in cards]
#TODO : doesn't look like this is in the database; don't like the way Hand requires it
h.hero = 'mcturnbull'
# HandInfo : HID, TABLE
# BUTTON - why is this treated specially in Hand?
# answer: it is written out in hand histories
# still, I think we should record all the active seat positions in a seat_order array
c.execute("""
SELECT
h.sitehandno as hid,
h.tablename as table,
h.handstart as starttime
FROM
hands as h
WHERE h.id = %(handid)s
""", {'handid':handid})
res = c.fetchone()
h.handid = res[0]
h.tablename = res[1]
h.starttime = res[2] # automatically a datetime
# PlayerStacks
c.execute("""
SELECT
hp.seatno,
p.name,
round(hp.startcash / 100.0,2) as chips,
(hp.card1,hp.card2) as hole
FROM
handsplayers as hp,
players as p
WHERE
hp.handid = %(handid)s
and p.id = hp.playerid
""", {'handid':handid})
for (seat, name, chips, cards) in c.fetchall():
h.addPlayer(seat,name,chips)
h.addHoleCards([Card.valueSuitFromCard(x) for x in cards],name)
# actions
c.execute("""
SELECT
(ha.street,ha.actionno) as actnum,
p.name,
ha.street,
ha.action,
ha.allin,
ha.amount / 100.0
FROM
handsplayers as hp,
handsactions as ha,
players as p
WHERE
hp.handid = %(handid)s
and ha.handsplayerid = hp.id
and p.id = hp.playerid
ORDER BY
ha.street,ha.actionno
""", {'handid':handid})
res = c.fetchall()
for (actnum,player, streetnum, act, allin, amount) in res:
act=act.strip()
street = h.streetList[streetnum+2]
if act==u'blind':
h.addBlind(player, 'big blind', amount)
# TODO: The type of blind is not recorded in the DB.
# TODO: preflop street name anomalies in Hand
elif act==u'fold':
h.addFold(street,player)
elif act==u'call':
h.addCall(street,player,amount)
elif act==u'bet':
h.addBet(street,player,amount)
elif act==u'check':
h.addCheck(street,player)
elif act==u'unbet':
pass
else:
print act, player, streetnum, allin, amount
# TODO : other actions
#hhc.readCollectPot(self)
#hhc.readShowdownActions(self)
#hc.readShownCards(self)
h.totalPot()
h.rake = h.totalpot - h.totalcollected
return h

View File

@ -132,7 +132,7 @@ Otherwise, finish at eof...
self.processHand(handText) self.processHand(handText)
numHands= len(handsList) numHands= len(handsList)
endtime = time.time() endtime = time.time()
print "Processed %d hands in %.3f seconds" % (numHands, endtime - starttime) print "read %d hands in %.3f seconds" % (numHands, endtime - starttime)
if self.out_fh != sys.stdout: if self.out_fh != sys.stdout:
self.out_fh.close() self.out_fh.close()

View File

@ -72,6 +72,7 @@ class OnGame(HandHistoryConverter):
HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init. HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init.
self.sitename = "OnGame" self.sitename = "OnGame"
self.setFileType("text", "cp1252") self.setFileType("text", "cp1252")
self.siteId = 5 # Needs to match id entry in Sites database
#self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)') #self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n\n+') self.rexx.setSplitHandRegex('\n\n\n+')

View File

@ -44,6 +44,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising PokerStars converter class") logging.info("Initialising PokerStars converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 2 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()
@ -133,8 +134,8 @@ follow : whether to tail -f the input"""
#2008/08/17 - 01:14:43 (ET) #2008/08/17 - 01:14:43 (ET)
#2008/09/07 06:23:14 ET #2008/09/07 06:23:14 ET
m2 = re.search("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)", info[key]) m2 = re.search("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)", info[key])
datetime = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), m2.group('M'),m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S')) datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), m2.group('M'),m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
hand.starttime = time.strptime(datetime, "%Y/%m/%d %H:%M:%S") hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
if key == 'HID': if key == 'HID':
hand.handid = info[key] hand.handid = info[key]
if key == 'TABLE': if key == 'TABLE':

View File

@ -564,13 +564,13 @@ class Sql:
if db_server == 'mysql': if db_server == 'mysql':
self.query['get_hand_1day_ago'] = """ self.query['get_hand_1day_ago'] = """
select coalesce(max(id),0) select coalesce(max(id),0)
from hands from Hands
where handstart < date_sub(utc_timestamp(), interval '1' day)""" where handStart < date_sub(utc_timestamp(), interval '1' day)"""
else: # assume postgresql else: # assume postgresql
self.query['get_hand_1day_ago'] = """ self.query['get_hand_1day_ago'] = """
select coalesce(max(id),0) select coalesce(max(id),0)
from hands from Hands
where handstart < now() at time zone 'UTC' - interval '1 day'""" where handStart < now() at time zone 'UTC' - interval '1 day'"""
if __name__== "__main__": if __name__== "__main__":
# just print the default queries and exit # just print the default queries and exit

View File

@ -42,6 +42,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising UltimateBetconverter class") logging.info("Initialising UltimateBetconverter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 6 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()

View File

@ -181,35 +181,54 @@ class fpdb:
def dia_load_profile(self, widget, data=None): def dia_load_profile(self, widget, data=None):
"""Dialogue to select a file to load a profile from""" """Dialogue to select a file to load a profile from"""
self.obtain_global_lock() if self.obtain_global_lock() == 0: # returns 0 if successful
chooser = gtk.FileChooserDialog(title="Please select a profile file to load", try:
action=gtk.FILE_CHOOSER_ACTION_OPEN, chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) action=gtk.FILE_CHOOSER_ACTION_OPEN,
chooser.set_filename(self.profile) buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_filename(self.profile)
response = chooser.run() response = chooser.run()
chooser.destroy() chooser.destroy()
if response == gtk.RESPONSE_OK: if response == gtk.RESPONSE_OK:
self.load_profile(chooser.get_filename()) self.load_profile(chooser.get_filename())
elif response == gtk.RESPONSE_CANCEL: elif response == gtk.RESPONSE_CANCEL:
print 'User cancelled loading profile' print 'User cancelled loading profile'
except:
pass
self.release_global_lock()
#end def dia_load_profile #end def dia_load_profile
def dia_recreate_tables(self, widget, data=None): def dia_recreate_tables(self, widget, data=None):
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables""" """Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
self.obtain_global_lock() if self.obtain_global_lock() in (0,2): # returns 0 if successful, 2 if Hands table does not exist
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() lock_released = False
dia_confirm.destroy() try:
if response == gtk.RESPONSE_YES: dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
self.db.recreate_tables() buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
elif response == gtk.RESPONSE_NO: 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."
print 'User cancelled recreating tables' 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:
if self.db.backend == self.fdb_lock.MYSQL_INNODB:
# mysql requires locks on all tables or none - easier to release this lock
# than lock all the other tables
# ToDo: lock all other tables so that lock doesn't have to be released
self.release_global_lock()
lock_released = True
self.db.recreate_tables()
else:
# for other dbs use same connection as holds global lock
self.fdb_lock.recreate_tables()
elif response == gtk.RESPONSE_NO:
print 'User cancelled recreating tables'
except:
pass
if not lock_released:
self.release_global_lock()
#end def dia_recreate_tables #end def dia_recreate_tables
def dia_regression_test(self, widget, data=None): def dia_regression_test(self, widget, data=None):
@ -291,7 +310,7 @@ class fpdb:
# Create actions # Create actions
actiongroup.add_actions([('main', None, '_Main'), actiongroup.add_actions([('main', None, '_Main'),
('Quit', gtk.STOCK_QUIT, '_Quit me!', None, 'Quit the Program', self.quit), ('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program', self.quit),
('LoadProf', None, '_Load Profile (broken)', '<control>L', 'Load your profile', self.dia_load_profile), ('LoadProf', None, '_Load Profile (broken)', '<control>L', 'Load your profile', self.dia_load_profile),
('EditProf', None, '_Edit Profile (todo)', '<control>E', 'Edit your profile', self.dia_edit_profile), ('EditProf', None, '_Edit Profile (todo)', '<control>E', 'Edit your profile', self.dia_edit_profile),
('SaveProf', None, '_Save Profile (todo)', '<control>S', 'Save your profile', self.dia_save_profile), ('SaveProf', None, '_Save Profile (todo)', '<control>S', 'Save your profile', self.dia_save_profile),
@ -380,10 +399,17 @@ class fpdb:
#end def not_implemented #end def not_implemented
def obtain_global_lock(self): def obtain_global_lock(self):
print "todo: implement obtain_global_lock (users: pls ignore this)" print "\nTaking global lock ..."
self.fdb_lock = fpdb_db.fpdb_db()
self.fdb_lock.connect(self.settings['db-backend'],
self.settings['db-host'],
self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
return self.fdb_lock.get_global_lock()
#end def obtain_global_lock #end def obtain_global_lock
def quit(self, widget): def quit(self, widget, data):
print "Quitting normally" print "Quitting normally"
#check if current settings differ from profile, if so offer to save or abort #check if current settings differ from profile, if so offer to save or abort
self.db.disconnect() self.db.disconnect()
@ -391,7 +417,9 @@ class fpdb:
#end def quit_cliecked #end def quit_cliecked
def release_global_lock(self): def release_global_lock(self):
print "todo: implement release_global_lock" self.fdb_lock.db.rollback()
self.fdb_lock.disconnect()
print "Global lock released."
#end def release_global_lock #end def release_global_lock
def tab_abbreviations(self, widget, data=None): def tab_abbreviations(self, widget, data=None):
@ -427,7 +455,6 @@ class fpdb:
ps_tab=new_ps_thread.get_vbox() ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Positional Stats") self.add_and_display_tab(ps_tab, "Positional Stats")
def tab_main_help(self, widget, data=None): def tab_main_help(self, widget, data=None):
"""Displays a tab with the main fpdb help screen""" """Displays a tab with the main fpdb help screen"""
#print "start of tab_main_help" #print "start of tab_main_help"

View File

@ -17,6 +17,9 @@
import os import os
import re import re
import sys
from time import time, strftime
import fpdb_simple import fpdb_simple
import FpdbSQLQueries import FpdbSQLQueries
@ -29,6 +32,110 @@ class fpdb_db:
self.MYSQL_INNODB = 2 self.MYSQL_INNODB = 2
self.PGSQL = 3 self.PGSQL = 3
self.SQLITE = 4 self.SQLITE = 4
# Data Structures for index and foreign key creation
# drop_code is an int with possible values: 0 - don't drop for bulk import
# 1 - drop during bulk import
# db differences:
# - note that mysql automatically creates indexes on constrained columns when
# foreign keys are created, while postgres does not. Hence the much longer list
# of indexes is required for postgres.
# all primary keys are left on all the time
#
# table column drop_code
self.indexes = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # indexes for mysql (list index 2)
{'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
]
, [ # indexes for postgres (list index 3)
{'tab':'Boardcards', 'col':'handId', 'drop':0}
, {'tab':'Gametypes', 'col':'siteId', 'drop':0}
, {'tab':'Hands', 'col':'gametypeId', 'drop':0} # mct 22/3/09
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'HandsActions', 'col':'handsPlayerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
, {'tab':'HudCache', 'col':'gametypeId', 'drop':1}
, {'tab':'HudCache', 'col':'playerId', 'drop':0}
, {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'Players', 'col':'siteId', 'drop':1}
, {'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
]
self.foreignKeys = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # foreign keys for mysql
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
, [ # foreign keys for postgres
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
]
# MySQL Notes:
# "FOREIGN KEY (handId) REFERENCES Hands(id)" - requires index on Hands.id
# - creates index handId on <thistable>.handId
# alter table t drop foreign key fk
# alter table t add foreign key (fkcol) references tab(rcol)
# alter table t add constraint c foreign key (fkcol) references tab(rcol)
# (fkcol is used for foreigh key name)
# mysql to list indexes:
# SELECT table_name, index_name, non_unique, column_name
# FROM INFORMATION_SCHEMA.STATISTICS
# WHERE table_name = 'tbl_name'
# AND table_schema = 'db_name'
# ORDER BY table_name, index_name, seq_in_index
#
# ALTER TABLE Tourneys ADD INDEX siteTourneyNo(siteTourneyNo)
# ALTER TABLE tab DROP INDEX idx
# mysql to list fks:
# SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
# FROM information_schema.KEY_COLUMN_USAGE
# WHERE REFERENCED_TABLE_SCHEMA = (your schema name here)
# AND REFERENCED_TABLE_NAME is not null
# ORDER BY TABLE_NAME, COLUMN_NAME;
# this may indicate missing object
# _mysql_exceptions.OperationalError: (1025, "Error on rename of '.\\fpdb\\hands' to '.\\fpdb\\#sql2-7f0-1b' (errno: 152)")
# PG notes:
# To add a foreign key constraint to a table:
# ALTER TABLE tab ADD CONSTRAINT c FOREIGN KEY (col) REFERENCES t2(col2) MATCH FULL;
# ALTER TABLE tab DROP CONSTRAINT zipchk
#
# Note: index names must be unique across a schema
# CREATE INDEX idx ON tab(col)
# DROP INDEX idx
#end def __init__ #end def __init__
def do_connect(self, config=None): def do_connect(self, config=None):
@ -69,7 +176,7 @@ class fpdb_db:
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
# If DB connection is made over TCP, then the variables # If DB connection is made over TCP, then the variables
# host, user and password are required # host, user and password are required
print "host=%s user=%s pass=%s." % (host, user, password) # print "host=%s user=%s pass=%s." % (host, user, password)
if self.host and self.user and self.password: if self.host and self.user and self.password:
try: try:
self.db = psycopg2.connect(host = host, self.db = psycopg2.connect(host = host,
@ -213,8 +320,297 @@ class fpdb_db:
self.drop_tables() self.drop_tables()
self.create_tables() self.create_tables()
fpdb_simple.createAllIndexes(self) self.createAllIndexes()
self.db.commit() self.db.commit()
print "Finished recreating tables" print "Finished recreating tables"
#end def recreate_tables #end def recreate_tables
def prepareBulkImport(self):
"""Drop some indexes/foreign keys to prepare for bulk import.
Currently keeping the standalone indexes as needed to import quickly"""
stime = time()
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for fk in self.foreignKeys[self.backend]:
if fk['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
self.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = self.cursor.fetchone()
#print "preparebulk: cons=", cons
if cons:
print "dropping mysql fk", cons[0], fk['fktab'], fk['fkcol']
try:
self.cursor.execute("alter table " + fk['fktab'] + " drop foreign key " + cons[0])
except:
pass
elif self.backend == self.PGSQL:
# DON'T FORGET TO RECREATE THEM!!
print "dropping pg fk", fk['fktab'], fk['fkcol']
try:
# try to lock table to see if index drop will work:
# hmmm, tested by commenting out rollback in grapher. lock seems to work but
# then drop still hangs :-( does work in some tests though??
# will leave code here for now pending further tests/enhancement ...
self.cursor.execute( "lock table %s in exclusive mode nowait" % (fk['fktab'],) )
#print "after lock, status:", self.cursor.statusmessage
#print "alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol'])
try:
self.cursor.execute("alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol']))
print "dropped pg fk pg fk %s_%s_fkey, continuing ..." % (fk['fktab'], fk['fkcol'])
except:
if "does not exist" not in str(sys.exc_value):
print "warning: drop pg fk %s_%s_fkey failed: %s, continuing ..." \
% (fk['fktab'], fk['fkcol'], str(sys.exc_value).rstrip('\n') )
except:
print "warning: constraint %s_%s_fkey not dropped: %s, continuing ..." \
% (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n'))
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in self.indexes[self.backend]:
if idx['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
# apparently nowait is not implemented in mysql so this just hands if there are locks
# preventing the index drop :-(
self.cursor.execute( "alter table %s drop index %s", (idx['tab'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
# DON'T FORGET TO RECREATE THEM!!
print "dropping pg index ", idx['tab'], idx['col']
try:
# try to lock table to see if index drop will work:
self.cursor.execute( "lock table %s in exclusive mode nowait" % (idx['tab'],) )
#print "after lock, status:", self.cursor.statusmessage
try:
# table locked ok so index drop should work:
#print "drop index %s_%s_idx" % (idx['tab'],idx['col'])
self.cursor.execute( "drop index if exists %s_%s_idx" % (idx['tab'],idx['col']) )
#print "dropped pg index ", idx['tab'], idx['col']
except:
if "does not exist" not in str(sys.exc_value):
print "warning: drop index %s_%s_idx failed: %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
except:
print "warning: index %s_%s_idx not dropped %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
else:
print "Error: Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
self.db.commit() # seems to clear up errors if there were any in postgres
ptime = time() - stime
print "prepare import took", ptime, "seconds"
#end def prepareBulkImport
def afterBulkImport(self):
"""Re-create any dropped indexes/foreign keys after bulk import"""
stime = time()
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for fk in self.foreignKeys[self.backend]:
if fk['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
self.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = self.cursor.fetchone()
print "afterbulk: cons=", cons
if cons:
pass
else:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
self.cursor.execute("alter table " + fk['fktab'] + " add foreign key ("
+ fk['fkcol'] + ") references " + fk['rtab'] + "("
+ fk['rcol'] + ")")
except:
pass
elif self.backend == self.PGSQL:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
self.cursor.execute("alter table " + fk['fktab'] + " add constraint "
+ fk['fktab'] + '_' + fk['fkcol'] + '_fkey'
+ " foreign key (" + fk['fkcol']
+ ") references " + fk['rtab'] + "(" + fk['rcol'] + ")")
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in self.indexes[self.backend]:
if idx['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
self.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
# pass
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
self.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
self.db.commit() # seems to clear up errors if there were any in postgres
atime = time() - stime
print "after import took", atime, "seconds"
#end def afterBulkImport
def createAllIndexes(self):
"""Create new indexes"""
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for idx in self.indexes[self.backend]:
if self.backend == self.MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
self.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
self.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
#end def createAllIndexes
def dropAllIndexes(self):
"""Drop all standalone indexes (i.e. not including primary keys or foreign keys)
using list of indexes in indexes data structure"""
# maybe upgrade to use data dictionary?? (but take care to exclude PK and FK)
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for idx in self.indexes[self.backend]:
if self.backend == self.MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
self.cursor.execute( "alter table %s drop index %s"
, (idx['tab'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
print "dropping pg index ", idx['tab'], idx['col']
# mod to use tab_col for index name?
try:
self.cursor.execute( "drop index %s_%s_idx"
% (idx['tab'],idx['col']) )
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
#end def dropAllIndexes
def analyzeDB(self):
"""Do whatever the DB can offer to update index/table statistics"""
stime = time()
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow vacuum to work
try:
self.cursor.execute("vacuum analyze")
except:
print "Error during vacuum"
self.db.set_isolation_level(1) # go back to normal isolation level
self.db.commit()
atime = time() - stime
print "analyze took", atime, "seconds"
#end def analyzeDB
# Currently uses an exclusive lock on the Hands table as a global lock
# Return values are Unix style, 0 for success, positive integers for errors
# 1 = generic error
# 2 = hands table does not exist (error message is suppressed)
def get_global_lock(self):
if self.backend == self.MYSQL_INNODB:
try:
self.cursor.execute( "lock tables Hands write" )
except:
# Table 'fpdb.hands' doesn't exist
if str(sys.exc_value).find(".hands' doesn't exist") >= 0:
return(2)
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(1)
elif self.backend == self.PGSQL:
try:
self.cursor.execute( "lock table Hands in exclusive mode nowait" )
#print "... after lock table, status =", self.cursor.statusmessage
except:
# relation "hands" does not exist
if str(sys.exc_value).find('relation "hands" does not exist') >= 0:
return(2)
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(1)
return(0)
def storeHand(self, p):
#stores into table hands:
self.cursor.execute ("""INSERT INTO Hands
(siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats
,playersVpi, playersAtStreet1, playersAtStreet2
,playersAtStreet3, playersAtStreet4, playersAtShowdown
,street0Raises, street1Raises, street2Raises
,street3Raises, street4Raises, street1Pot
,street2Pot, street3Pot, street4Pot
,showdownPot
)
VALUES
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
,(p['siteHandNo'], gametype_id, p['handStart'], len(names), p['tableName'], datetime.datetime.today(), p['maxSeats']
,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2']
,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown']
,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises']
,hudCache['street3Raises'], hudCache['street4Raises'], hudCache['street1Pot']
,hudCache['street2Pot'], hudCache['street3Pot'], hudCache['street4Pot']
,hudCache['showdownPot']
)
)
#return getLastInsertId(backend, conn, cursor)
#end class fpdb_db #end class fpdb_db

View File

@ -150,7 +150,9 @@ class Importer:
self.monitor = True self.monitor = True
self.dirlist[site] = [dir] + [filter] self.dirlist[site] = [dir] + [filter]
#print "addImportDirectory: checking files in", dir
for file in os.listdir(dir): for file in os.listdir(dir):
#print " adding file ", file
self.addImportFile(os.path.join(dir, file), site, filter) self.addImportFile(os.path.join(dir, file), site, filter)
else: else:
print "Warning: Attempted to add non-directory: '" + str(dir) + "' as an import directory" print "Warning: Attempted to add non-directory: '" + str(dir) + "' as an import directory"
@ -162,7 +164,7 @@ class Importer:
if self.settings['dropIndexes'] == 'auto': if self.settings['dropIndexes'] == 'auto':
self.settings['dropIndexes'] = self.calculate_auto() self.settings['dropIndexes'] = self.calculate_auto()
if self.settings['dropIndexes'] == 'drop': if self.settings['dropIndexes'] == 'drop':
fpdb_simple.prepareBulkImport(self.fdb) self.fdb.prepareBulkImport()
totstored = 0 totstored = 0
totdups = 0 totdups = 0
totpartial = 0 totpartial = 0
@ -177,8 +179,8 @@ class Importer:
toterrors += errors toterrors += errors
tottime += ttime tottime += ttime
if self.settings['dropIndexes'] == 'drop': if self.settings['dropIndexes'] == 'drop':
fpdb_simple.afterBulkImport(self.fdb) self.fdb.afterBulkImport()
fpdb_simple.analyzeDB(self.fdb) self.fdb.analyzeDB()
return (totstored, totdups, totpartial, toterrors, tottime) return (totstored, totdups, totpartial, toterrors, tottime)
# else: import threaded # else: import threaded
@ -203,14 +205,18 @@ class Importer:
#todo: make efficient - always checks for new file, should be able to use mtime of directory #todo: make efficient - always checks for new file, should be able to use mtime of directory
# ^^ May not work on windows # ^^ May not work on windows
#rulog = open('runUpdated.txt', 'a')
#rulog.writelines("runUpdated ... ")
for site in self.dirlist: for site in self.dirlist:
self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1]) self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
for file in self.filelist: for file in self.filelist:
if os.path.exists(file): if os.path.exists(file):
stat_info = os.stat(file) stat_info = os.stat(file)
#rulog.writelines("path exists ")
try: try:
lastupdate = self.updated[file] lastupdate = self.updated[file]
#rulog.writelines("lastupdate = %d, mtime = %d" % (lastupdate,stat_info.st_mtime))
if stat_info.st_mtime > lastupdate: if stat_info.st_mtime > lastupdate:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
self.updated[file] = time() self.updated[file] = time()
@ -236,7 +242,8 @@ class Importer:
self.addToDirList = {} self.addToDirList = {}
self.removeFromFileList = {} self.removeFromFileList = {}
self.fdb.db.rollback() self.fdb.db.rollback()
#rulog.writelines(" finished\n")
#rulog.close()
# This is now an internal function that should not be called directly. # This is now an internal function that should not be called directly.
def import_file_dict(self, file, site, filter): def import_file_dict(self, file, site, filter):
@ -249,7 +256,7 @@ class Importer:
conv = None conv = None
# Load filter, process file, pass returned filename to import_fpdb_file # Load filter, process file, pass returned filename to import_fpdb_file
print "converting %s" % file print "\nConverting %s" % file
hhbase = self.config.get_import_parameters().get("hhArchiveBase") hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase) hhbase = os.path.expanduser(hhbase)
hhdir = os.path.join(hhbase,site) hhdir = os.path.join(hhbase,site)
@ -282,6 +289,7 @@ class Importer:
starttime = time() starttime = time()
last_read_hand = 0 last_read_hand = 0
loc = 0 loc = 0
#print "file =", file
if file == "stdin": if file == "stdin":
inputFile = sys.stdin inputFile = sys.stdin
else: else:
@ -292,10 +300,17 @@ class Importer:
return (0, 0, 0, 1, 0) return (0, 0, 0, 1, 0)
try: try:
loc = self.pos_in_file[file] loc = self.pos_in_file[file]
#size = os.path.getsize(file)
#print "loc =", loc, 'size =', size
except: except:
pass pass
# Read input file into class and close file # Read input file into class and close file
inputFile.seek(loc) inputFile.seek(loc)
#tmplines = inputFile.readlines()
#if tmplines == None or tmplines == []:
# print "tmplines = ", tmplines
#else:
# print "tmplines[0] =", tmplines[0]
self.lines = fpdb_simple.removeTrailingEOL(inputFile.readlines()) self.lines = fpdb_simple.removeTrailingEOL(inputFile.readlines())
self.pos_in_file[file] = inputFile.tell() self.pos_in_file[file] = inputFile.tell()
inputFile.close() inputFile.close()
@ -303,7 +318,8 @@ class Importer:
try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return. try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return.
firstline = self.lines[0] firstline = self.lines[0]
except: except:
print "DEBUG: import_fpdb_file: failed on self.lines[0]: '%s' '%s' '%s' '%s' " %( file, site, self.lines, loc) # just skip the debug message and return silently:
#print "DEBUG: import_fpdb_file: failed on self.lines[0]: '%s' '%s' '%s' '%s' " %( file, site, self.lines, loc)
return (0,0,0,1,0) return (0,0,0,1,0)
if firstline.find("Tournament Summary")!=-1: if firstline.find("Tournament Summary")!=-1:
@ -348,6 +364,7 @@ class Importer:
if self.callHud: if self.callHud:
#print "call to HUD here. handsId:",handsId #print "call to HUD here. handsId:",handsId
#pipe the Hands.id out to the HUD #pipe the Hands.id out to the HUD
print "sending hand to hud", handsId, "pipe =", self.caller.pipe_to_hud
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
except fpdb_simple.DuplicateError: except fpdb_simple.DuplicateError:
duplicates += 1 duplicates += 1
@ -364,7 +381,6 @@ class Importer:
except (fpdb_simple.FpdbError), fe: except (fpdb_simple.FpdbError), fe:
errors += 1 errors += 1
self.printEmailErrorMessage(errors, file, hand) self.printEmailErrorMessage(errors, file, hand)
self.fdb.db.rollback() self.fdb.db.rollback()
if self.settings['failOnError']: if self.settings['failOnError']:

View File

@ -16,10 +16,15 @@
#agpl-3.0.txt in the docs folder of the package. #agpl-3.0.txt in the docs folder of the package.
#This file contains simple functions for fpdb #This file contains simple functions for fpdb
#Aiming to eventually remove this module, functions will move to, eg:
#fpdb_db db create/re-create/management/etc
#Hands or related files for saving hands to db, etc
import datetime import datetime
import time import time
import re import re
import sys
import Card import Card
@ -27,6 +32,7 @@ PS = 1
FTP = 2 FTP = 2
# TODO: these constants are also used in fpdb_save_to_db and others, is there a way to do like C #define, and #include ? # TODO: these constants are also used in fpdb_save_to_db and others, is there a way to do like C #define, and #include ?
# answer - yes. These are defined in fpdb_db so are accessible through that class.
MYSQL_INNODB = 2 MYSQL_INNODB = 2
PGSQL = 3 PGSQL = 3
SQLITE = 4 SQLITE = 4
@ -34,316 +40,6 @@ SQLITE = 4
# config while trying out new hudcache mechanism # config while trying out new hudcache mechanism
use_date_in_hudcache = True use_date_in_hudcache = True
# Data Structures for index and foreign key creation
# drop_code is an int with possible values: 0 - don't drop for bulk import
# 1 - drop during bulk import
# db differences:
# - note that mysql automatically creates indexes on constrained columns when
# foreign keys are created, while postgres does not. Hence the much longer list
# of indexes is required for postgres.
# all primary keys are left on all the time
#
# table column drop_code
indexes = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # indexes for mysql (list index 2)
{'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
]
, [ # indexes for postgres (list index 3)
{'tab':'Boardcards', 'col':'handId', 'drop':0}
, {'tab':'Gametypes', 'col':'siteId', 'drop':0}
, {'tab':'Hands', 'col':'gametypeId', 'drop':0} # mct 22/3/09
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'HandsActions', 'col':'handsPlayerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
, {'tab':'HudCache', 'col':'gametypeId', 'drop':1}
, {'tab':'HudCache', 'col':'playerId', 'drop':0}
, {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'Players', 'col':'siteId', 'drop':1}
, {'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
]
foreignKeys = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # foreign keys for mysql
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
, [ # foreign keys for postgres
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
]
# MySQL Notes:
# "FOREIGN KEY (handId) REFERENCES Hands(id)" - requires index on Hands.id
# - creates index handId on <thistable>.handId
# alter table t drop foreign key fk
# alter table t add foreign key (fkcol) references tab(rcol)
# alter table t add constraint c foreign key (fkcol) references tab(rcol)
# (fkcol is used for foreigh key name)
# mysql to list indexes:
# SELECT table_name, index_name, non_unique, column_name
# FROM INFORMATION_SCHEMA.STATISTICS
# WHERE table_name = 'tbl_name'
# AND table_schema = 'db_name'
# ORDER BY table_name, index_name, seq_in_index
#
# ALTER TABLE Tourneys ADD INDEX siteTourneyNo(siteTourneyNo)
# ALTER TABLE tab DROP INDEX idx
# mysql to list fks:
# SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
# FROM information_schema.KEY_COLUMN_USAGE
# WHERE REFERENCED_TABLE_SCHEMA = (your schema name here)
# AND REFERENCED_TABLE_NAME is not null
# ORDER BY TABLE_NAME, COLUMN_NAME;
# this may indicate missing object
# _mysql_exceptions.OperationalError: (1025, "Error on rename of '.\\fpdb\\hands' to '.\\fpdb\\#sql2-7f0-1b' (errno: 152)")
# PG notes:
# To add a foreign key constraint to a table:
# ALTER TABLE tab ADD CONSTRAINT c FOREIGN KEY (col) REFERENCES t2(col2) MATCH FULL;
# ALTER TABLE tab DROP CONSTRAINT zipchk
#
# Note: index names must be unique across a schema
# CREATE INDEX idx ON tab(col)
# DROP INDEX idx
def prepareBulkImport(fdb):
"""Drop some indexes/foreign keys to prepare for bulk import.
Currently keeping the standalone indexes as needed to import quickly"""
# fdb is a fpdb_db object including backend, db, cursor, sql variables
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for fk in foreignKeys[fdb.backend]:
if fk['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
fdb.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = fdb.cursor.fetchone()
print "preparebulk: cons=", cons
if cons:
print "dropping mysql fk", cons[0], fk['fktab'], fk['fkcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " drop foreign key " + cons[0])
except:
pass
elif fdb.backend == PGSQL:
# DON'T FORGET TO RECREATE THEM!!
#print "dropping pg fk", fk['fktab'], fk['fkcol']
try:
#print "alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol'])
fdb.cursor.execute("alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol']))
print "dropped pg fk pg fk %s_%s_fkey" % (fk['fktab'], fk['fkcol'])
except:
print "! failed drop pg fk %s_%s_fkey" % (fk['fktab'], fk['fkcol'])
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in indexes[fdb.backend]:
if idx['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s drop index %s", (idx['tab'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# DON'T FORGET TO RECREATE THEM!!
#print "Index dropping disabled for postgresql."
#print "dropping pg index ", idx['tab'], idx['col']
# mod to use tab_col for index name?
try:
fdb.cursor.execute( "drop index %s_%s_idx" % (idx['tab'],idx['col']) )
print "drop index %s_%s_idx" % (idx['tab'],idx['col'])
#print "dropped pg index ", idx['tab'], idx['col']
except:
print "! failed drop index %s_%s_idx" % (idx['tab'],idx['col'])
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit() # seems to clear up errors if there were any in postgres
#end def prepareBulkImport
def afterBulkImport(fdb):
"""Re-create any dropped indexes/foreign keys after bulk import"""
# fdb is a fpdb_db object including backend, db, cursor, sql variables
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for fk in foreignKeys[fdb.backend]:
if fk['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
fdb.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = fdb.cursor.fetchone()
print "afterbulk: cons=", cons
if cons:
pass
else:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " add foreign key ("
+ fk['fkcol'] + ") references " + fk['rtab'] + "("
+ fk['rcol'] + ")")
except:
pass
elif fdb.backend == PGSQL:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " add constraint "
+ fk['fktab'] + '_' + fk['fkcol'] + '_fkey'
+ " foreign key (" + fk['fkcol']
+ ") references " + fk['rtab'] + "(" + fk['rcol'] + ")")
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in indexes[fdb.backend]:
if idx['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# pass
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
fdb.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit() # seems to clear up errors if there were any in postgres
#end def afterBulkImport
def createAllIndexes(fdb):
"""Create new indexes"""
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for idx in indexes[fdb.backend]:
if fdb.backend == MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
fdb.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
#end def createAllIndexes
def dropAllIndexes(fdb):
"""Drop all standalone indexes (i.e. not including primary keys or foreign keys)
using list of indexes in indexes data structure"""
# maybe upgrade to use data dictionary?? (but take care to exclude PK and FK)
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for idx in indexes[fdb.backend]:
if fdb.backend == MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s drop index %s"
, (idx['tab'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
print "dropping pg index ", idx['tab'], idx['col']
# mod to use tab_col for index name?
try:
fdb.cursor.execute( "drop index %s_%s_idx"
% (idx['tab'],idx['col']) )
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
#end def dropAllIndexes
def analyzeDB(fdb):
"""Do whatever the DB can offer to update index/table statistics"""
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow vacuum to work
try:
fdb.cursor.execute("vacuum analyze")
except:
print "Error during vacuum"
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit()
#end def analyzeDB
class DuplicateError(Exception): class DuplicateError(Exception):
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
@ -1442,7 +1138,6 @@ def storeActions(cursor, handsPlayersIds, actionTypes, allIns, actionAmounts, ac
def store_board_cards(cursor, hands_id, board_values, board_suits): def store_board_cards(cursor, hands_id, board_values, board_suits):
#stores into table board_cards #stores into table board_cards
return
cursor.execute ("""INSERT INTO BoardCards (handId, card1Value, card1Suit, cursor.execute ("""INSERT INTO BoardCards (handId, card1Value, card1Suit,
card2Value, card2Suit, card3Value, card3Suit, card4Value, card4Suit, card2Value, card2Suit, card3Value, card3Suit, card4Value, card4Suit,
card5Value, card5Suit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", card5Value, card5Suit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
@ -2686,7 +2381,7 @@ def storeHudCache2(backend, cursor, base, category, gametypeId, hand_start_time,
# Try to do the update first: # Try to do the update first:
num = cursor.execute("""UPDATE HudCache num = cursor.execute("""UPDATE HudCache
SET HDs=HDs+%s, street0VPI=street0VPI+%s, street0Aggr=street0Aggr+%s, SET HDs=HDs+%s, street0VPI=street0VPI+%s, street0Aggr=street0Aggr+%s,
street0_3BChance=street0_3BChance+%s, street0_3BDone=street0_3BDone+%s, street0_3B4BChance=street0_3B4BChance+%s, street0_3B4BDone=street0_3B4BDone+%s,
street1Seen=street1Seen+%s, street2Seen=street2Seen+%s, street3Seen=street3Seen+%s, street1Seen=street1Seen+%s, street2Seen=street2Seen+%s, street3Seen=street3Seen+%s,
street4Seen=street4Seen+%s, sawShowdown=sawShowdown+%s, street4Seen=street4Seen+%s, sawShowdown=sawShowdown+%s,
street1Aggr=street1Aggr+%s, street2Aggr=street2Aggr+%s, street3Aggr=street3Aggr+%s, street1Aggr=street1Aggr+%s, street2Aggr=street2Aggr+%s, street3Aggr=street3Aggr+%s,