Poker Academy Forums

    SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
Goto page Previous  1, 2, 3  Next
 
Post new topic   Reply to topic    Poker Academy Forums Forum Index -> Meerkat API and AI Discussion
View previous topic :: View next topic  
Author Message
M_White



Joined: 05 Nov 2005
Posts: 40

PostPosted: Sat Dec 23, 2006 7:35 pm    Post subject: Reply with quote

Gullanian wrote:
Sorry to dig up old threads!

I've written a 7 card hand evaluator and am trying to determine if anyone can beat it.

It can evaluate 14.5 million hands per second, on a 2.4ghz p4.

The thing about my one is that is takes any set of 7 integers (0-51) to represent a hand, and returns the strength of the hand as a comparable value to other hands. The integers passed in are UNORDERED, so 4,3,10,11,12,50,0 might be passed in and it would return the correct hand ranking. Is this what all of yours do as well?


In mine, I pass a 64 bit integer where each card is represented by a bit.

Ordering kind of happens automatically.

On average it takes 66 CPU cycles to produce a 32 bit code that can be directly compared to other hands (strength) and also easily decoded to determine the type of hand and composition of the best 5 cards used.

On a 2.4 ghz CPU that should come out to 36 million hands per second.

I'm working on a completely table driven solution that should be MUCH faster, but it would also consume a lot more memory.
Back to top
View user's profile Send private message
M_White



Joined: 05 Nov 2005
Posts: 40

PostPosted: Wed Dec 27, 2006 7:49 pm    Post subject: Reply with quote

I've completed a state driven 7 card Eval table that can be used when speed is more important than memory consumption.

1 card states 52
2 card states 1326
3 card states 22100
4 card states 84448
5 card states 152607
6 card states 352443

The table consists of 31874856 integers.

Rank = Eval[Eval[Eval[Eval[Eval[Eval[Eval[52+c1]+c2]+c3]+c4]+c5]+c6]+c7]

Each state is a multiple of 52.

State = 0 represents some form of error, and all vectors from state 0 point back to state 0.

State_1 = Eval[52+Card_1]

EVal[State_n+Card_(n+1)] = State_(n+1)

Eval[State_6+Card_7] = Hand Rank for those 7 cards.

Total size of the Table is 121Mb
Back to top
View user's profile Send private message
terence
Developer


Joined: 27 Sep 2004
Posts: 1153
Location: Edmonton

PostPosted: Thu Jan 04, 2007 5:12 pm    Post subject: Reply with quote

M_White wrote:
I've completed a state driven 7 card Eval table that can be used when speed is more important than memory consumption.

1 card states 52
2 card states 1326
3 card states 22100
4 card states 84448
5 card states 152607
6 card states 352443

The table consists of 31874856 integers.

Rank = Eval[Eval[Eval[Eval[Eval[Eval[Eval[52+c1]+c2]+c3]+c4]+c5]+c6]+c7]

Each state is a multiple of 52.

State = 0 represents some form of error, and all vectors from state 0 point back to state 0.

State_1 = Eval[52+Card_1]

EVal[State_n+Card_(n+1)] = State_(n+1)

Eval[State_6+Card_7] = Hand Rank for those 7 cards.

Total size of the Table is 121Mb


To let everyone know there is a discussion on this going on at twoplustwo:
http://forumserver.twoplustwo.com/showflat.php?Cat=0&Number=8513906&page=0&fpart=13&vc=1

M_White did you also post in that thread?

Terence.
Back to top
View user's profile Send private message Visit poster's website
M_White



Joined: 05 Nov 2005
Posts: 40

PostPosted: Fri Jan 05, 2007 12:05 am    Post subject: Reply with quote

terence wrote:
M_White wrote:
I've completed a state driven 7 card Eval table that can be used when speed is more important than memory consumption.

1 card states 52
2 card states 1326
3 card states 22100
4 card states 84448
5 card states 152607
6 card states 352443

The table consists of 31874856 integers.

Rank = Eval[Eval[Eval[Eval[Eval[Eval[Eval[52+c1]+c2]+c3]+c4]+c5]+c6]+c7]

Each state is a multiple of 52.

State = 0 represents some form of error, and all vectors from state 0 point back to state 0.

State_1 = Eval[52+Card_1]

EVal[State_n+Card_(n+1)] = State_(n+1)

Eval[State_6+Card_7] = Hand Rank for those 7 cards.

Total size of the Table is 121Mb


To let everyone know there is a discussion on this going on at twoplustwo:
http://forumserver.twoplustwo.com/showflat.php?Cat=0&Number=8513906&page=0&fpart=13&vc=1

M_White did you also post in that thread?

Terence.



Yep, it's a small world.
Back to top
View user's profile Send private message
darse
Lemur


Joined: 04 Oct 2004
Posts: 185
Location: Edmonton, Alberta

PostPosted: Tue Jan 09, 2007 7:28 pm    Post subject: Reply with quote

Here is a high-level explanation of the nested table approach, with an example. This is a description of the system I use, which I think is the same as the one in the 2+2 thread (there might be some minor differences, but they should be pretty close since it is "the natural thing to do", for some value of "natural").

Imagine six separate tables, corresponding to the number of cards we have currently processed. Each table holds an index value (or a pointer to a particular location) into the next table. It's like a network of pointers, expanding at each stage, but having a branching factor much less than 52 because each set of cards is sorted and reduced to only the essential information.

Let's suppose the first card is the 9h. It is represented as the 1-card hand "9h". The table entry for the 9h contains 52 indices (or pointers) corresponding to the 52 cards in the deck. The pointer for 9h[9h] might point to -1, indicating an error, since a legal hand can't have two 9h.

The second card is the Kc. The hand ID is the full hand sorted by rank and suit, so we currently have the hand "Kc9h". This entry in the second table is pointed to from two places in the first table, namely 9h[Kc] and Kc[9h]. There are 52 out-going pointers for this hand, two of which point to an error state.

The third card is the Kh. Kc9h[Kh] => KhKc9h. There are six different paths through the network leading to this state.

The fourth card is the Qc. KhKc9h[Qc] => KhKcQc9h. We retain the suit information because both hearts and clubs have a potential flush after 7 cards. If this card had been the Qd, then neither diamonds nor clubs could make a potential flush. They would be mapped to a "meta-suit" for no possible flush, which I will call "o" for "offsuit" or "other". Thus, the Qd would have produced KhKc9h[Qd] => KhKoQo9h.

The fifth card is the 2h. KhKcQc9h[2h] => KhKoQo9h2h. Clubs are now irrelevant with respect to possible flushes.

The sixth card is the 5h. KhKoQo9h2h[5h] => KhKoQo9h5h2h. Each entry of the sixth (and largest) table also has 52 out-going pointers, but instead of an index, they contain hand values.

The seventh card is the 7s. KhKoQo9h5h2h[7s] = value(KKQ97) which is 3844. If this had been the 7h, the table entry would contain the value of K9752f, which is 6367.

Note that a 6-card hand with five flush cards can ignore the offsuit card, as it cannot have a bearing on the final value. Thus, the large 6-card table can be reduced in size a little by using a "meta-rank" for "don't care", merging many cases into one. We cannot eliminate the smallest rank of a 6-card flush, however, because it might be part of a straight flush after the seventh card is known.

Note also that the sequential composition of hand IDs uses a lot less memory than more simplistic approaches. This can be further reduced if we dispense with the duplicate cards pointing to an error state, but the indexing gets trickier.

There are two main reasons this method is much faster for systematic enumeration tasks. First, we move most of the work out of the inner-most loops, and do it once instead of repeating it many times. Second, we ensure cache coherence to minimize the number of expensive accesses to main memory. For example, each fetch of an 8K block contains exactly the information that will be needed for the upcoming enumeration cases.

This technique might also be useful for hybrid Monte Carlo simulations over sets of cases, such as all possible river cards for a given 4-card board, or all possible 2-card hands in connection with a random board.

- Darse.
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Sparky2007



Joined: 11 Oct 2006
Posts: 9

PostPosted: Tue Jan 09, 2007 10:27 pm    Post subject: Reply with quote

Thanks alot for that explanation Darse. This technique makes alot of sense. I am in the process of converting this to java at the minute, I will let you all know how it performs during an enumeration of 7 card hands.

I assume from the 2+2 thread, that a mix between this (for enumeration) and the older evaluation functions would be a good idea.
Back to top
View user's profile Send private message
M_White



Joined: 05 Nov 2005
Posts: 40

PostPosted: Wed Jan 10, 2007 4:08 am    Post subject: Reply with quote

darse wrote:
Here is a high-level explanation of the nested table approach, with an example. This is a description of the system I use, which I think is the same as the one in the 2+2 thread (there might be some minor differences, but they should be pretty close since it is "the natural thing to do", for some value of "natural").


- Darse.


In your example you mention 8k blocks of memory.... is that just a number out of the blue or does the cache system access main memory in 8k chunks now?

Last time I ventured into the details of cache, I think it was only 16 bytes or so.. that was something like 20+ years ago.
Back to top
View user's profile Send private message
ChessMan



Joined: 23 Feb 2006
Posts: 76

PostPosted: Wed Jan 10, 2007 5:16 am    Post subject: Reply with quote

Sparky2007 wrote:
Thanks alot for that explanation Darse. This technique makes alot of sense. I am in the process of converting this to java at the minute, I will let you all know how it performs during an enumeration of 7 card hands.

I assume from the 2+2 thread, that a mix between this (for enumeration) and the older evaluation functions would be a good idea.


Please post your code when finished either here or at twoplustwo. I have Senzee's http://senzee.blogspot.com/2006/06/some-perfect-hash.html ported to Java if you want it.
_________________
Windows XP Pro 32-bit
Prescott Pentium 4 (2.5.4 Build 176)
& AMD 3800+ X2 (2.5.8 Build 210)
1GB of RAM & 2GB of RAM
Back to top
View user's profile Send private message
alan



Joined: 27 Oct 2004
Posts: 972

PostPosted: Wed Jan 10, 2007 7:31 am    Post subject: Reply with quote

An evaluator generates a single integer representing hand strength, right?

How are flushes handled? It seems that you would need 1277 equivalence classes for flushes. Is this correct?
Back to top
View user's profile Send private message AIM Address
Sparky2007



Joined: 11 Oct 2006
Posts: 9

PostPosted: Wed Jan 10, 2007 4:46 pm    Post subject: Reply with quote

I have converted the code to Java but when running it, I am getting an out of bounds exception at one point.

I am not an expert on the differences between c++ and java but I think my conversion of the following line may be incorrect.

Code:
if (suit = holdcards[cardnum] & 0xf) {   // find out what suit (if any) was significant
            mainsuit = suit;               // and remember it
}


Is suit being assigned to holdcards[cardnum] & 0xf, or being compared to it? Also the problem may lie with the SWAP function. I have created a swap function as follows

Code:

public static void SWAP(int I, int J) {
        if (workcards[I] < workcards[J]) {
            workcards[I]^=workcards[J];
            workcards[J]^=workcards[I];
            workcards[I]^=workcards[J];
        }
       
    }


but I am pretty sure this can be converted to

Code:

Arrays.sort(workcards);


in java as this should sort the array fine.... I'm lost as to where else c++ differs as my code is compiling fine.
Back to top
View user's profile Send private message
darse
Lemur


Joined: 04 Oct 2004
Posts: 185
Location: Edmonton, Alberta

PostPosted: Thu Jan 11, 2007 12:27 am    Post subject: Reply with quote

alan wrote:
An evaluator generates a single integer representing hand strength, right?

How are flushes handled? It seems that you would need 1277 equivalence classes for flushes. Is this correct?


Right. There are only 7462 distinct hand values, of which 1277 are flushes (ranks 5863-7140). There are only four hands in each flush class, compared to 1020 copies of each straight or high-card hand.

"Cactus Kev" explains this quite nicely on his 5-card eval webpage:
http://www.suffecool.net/poker/evaluator.html
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Sparky2007



Joined: 11 Oct 2006
Posts: 9

PostPosted: Thu Jan 11, 2007 1:54 am    Post subject: Reply with quote

I have converted the code to Java as best I understand the difference between the two languages but I am still unable to get through the entire process of assigning the 7 card hand values at the end of the process.

Hopefully some of you more familiar could give it a shot?
Back to top
View user's profile Send private message
alan



Joined: 27 Oct 2004
Posts: 972

PostPosted: Thu Jan 11, 2007 4:08 am    Post subject: Reply with quote

Quote:

Right. There are only 7462 distinct hand values, of which 1277 are flushes (ranks 5863-7140). There are only four hands in each flush class, compared to 1020 copies of each straight or high-card hand.


It seems to me that the goal of an evaluator is to compare strengths between two or more hands. In many cases, getting a range of values should be sufficient. Once you know that a hand makes a flush, you do not need to eliminate the possiblity of those other 1276 classes unless you are trying to compare the hand to another flush. Have you tried any optimizations along these lines?

Different games have different distributions of hand ranks. It might also be possible to optimize the ranking elimination to the game by looking at which hands are most likely.

I worked out the equivalence classes last night. This is what I got:
Straight Flush 10
Four of a Kind 13
Full House 13*12=156
Flush C(13, 5)-10=1277
Straight 10
Three of a Kind 13
Two Pair 11*(13*12)/2=858
Pair 13*C(11, 3)=2145
High C(13,5)-10=1277

Flushes, Pairs, and High hands seem like the most expensive of the bunch.

(BTW Darse, have you defended yet? I haven't been keeping up.)
Back to top
View user's profile Send private message AIM Address
ChessMan



Joined: 23 Feb 2006
Posts: 76

PostPosted: Thu Jan 11, 2007 6:24 am    Post subject: Reply with quote

Sparky2007 wrote:

Code:
if (suit = holdcards[cardnum] & 0xf) {   // find out what suit (if any) was significant
            mainsuit = suit;               // and remember it
}


The above is equivalent to
Code:

suit = holdcards[cardnum] & 0xf;
if(suit){
mainsuit = suit;
}

This code won't work in Java because Java has a boolean primitive type and conditional statements in Java must be of boolean type. "suit & 0xf" is actually a bitwise AND and so the result is some number.

Next is a Java conversion of the original code:
Code:

if ((suit = holdcards[cardnum] & 0xf) != 0) {   // find out what suit (if any) was significant
            mainsuit = suit;               // and remember it

_________________
Windows XP Pro 32-bit
Prescott Pentium 4 (2.5.4 Build 176)
& AMD 3800+ X2 (2.5.8 Build 210)
1GB of RAM & 2GB of RAM
Back to top
View user's profile Send private message
thomas



Joined: 11 Jan 2007
Posts: 24

PostPosted: Thu Jan 11, 2007 10:43 am    Post subject: Reply with quote

I am new to this forum and also fairly new to poker AI, so my question may be a bit uninformed.

Could we not just precalculate all hand strengths?

((52 choose 7) + (52 choose 6) + (52 choose 5) + (52 choose 4) + (52 choose 3) + (52 choose 2)) / 4 = 39 259 047.8

I divide by four because I figure the different combinations of suits should yield the same strength.

If I calculate the result as a probability that the hand will win the game and then bin it into 256 discrete values, i.e. a byte I should be able to store the complete set in a bit lesds than 40 mb of memory.

Is there a flaw in my reasoning?

Thomas
Back to top
View user's profile Send private message
Sparky2007



Joined: 11 Oct 2006
Posts: 9

PostPosted: Thu Jan 11, 2007 4:41 pm    Post subject: Reply with quote

Thanks ChessMan, yes I have made that change, the java code creates the Id's now with no errors, but fails at the stage where the actual rankings are assigned at the end.

There must be a difference in the way the java code creates the ID's as they are not being created equally.

I found that a __int64 in C is a 64bit unsigned integer. In java there are no such things as unsigned integers. A long is a Java 64bit signed integer, so I think this is where the ID's are not being created correctly.
Back to top
View user's profile Send private message
Steve Brecher



Joined: 12 Jan 2007
Posts: 6

PostPosted: Fri Jan 12, 2007 1:03 am    Post subject: Reply with quote

thomas wrote:
Could we not just precalculate all hand strengths?

((52 choose 7) + (52 choose 6) + (52 choose 5) + (52 choose 4) + (52 choose 3) + (52 choose 2)) / 4 = 39 259 047.8

There are two ways to obtain a hand evalution (plus a mixture of the two): calculation, i.e., use only the data in the representation of the hand, and table lookup. The problem to be solved by an implementer of a table lookup evaluator is how to quickly get from a representaton of a hand to the corresponding hand value in the table(s). One such solution has been mentioned above by M_White and outlined by that author (as mykey1961, same person) on the twoplustwo forum thread cited, and also a similar solution has been outlined above by darse.

It turns out (see twoplustwo thread) that the table lookup solution is blazingly fast if and only if it is not competing for the CPU data cache -- or more generally, for RAM access -- with code that is using/calling the evaluator; otherwise, calculation routines with perhaps some small tables are faster.
Quote:
I divide by four because I figure the different combinations of suits should yield the same strength.
Yes, but before you throw away the suits you have to determine whether or not the hand is a flush or straight flush. That is a significant portion of the work of a calculation evaluator.
Quote:
If I calculate the result as a probability that the hand will win the game and then bin it into 256 discrete values, i.e. a byte I should be able to store the complete set in a bit lesds than 40 mb of memory.
I'm not sure what the probability is to which you refer. Evaluators are used in comparing the strengths of hands to determine wins/ties/losses among a specified set of two or more hands. So I don't see how a probability could be associated with a hand out of the context of the other hands with which it is competing.

Last edited by Steve Brecher on Fri Jan 12, 2007 1:37 am; edited 1 time in total
Back to top
View user's profile Send private message
Steve Brecher



Joined: 12 Jan 2007
Posts: 6

PostPosted: Fri Jan 12, 2007 1:31 am    Post subject: Reply with quote

Sparky2007 wrote:
I found that a __int64 in C is a 64bit unsigned integer. In java there are no such things as unsigned integers. A long is a Java 64bit signed integer, so I think this is where the ID's are not being created correctly.
Note that __int64 is a Microsoft-specific extension. Other implementations may also provide 64-bit integers with other type designators, e.g., "long long". Also, __int64 is signed; "unsigned __int64" is the unsigned type.

In general -- i.e., without looking at the code -- the difference between signed and unsigned should not be significant if the most significant bit, the sign bit, is clear. Java does provide an unsigned right-shift operator, ">>>", which will not replicate the sign bit if it is set in the left operand.

[Edited to add note about __int64 vs. unsigned __int64.]
Back to top
View user's profile Send private message
Sparky2007



Joined: 11 Oct 2006
Posts: 9

PostPosted: Fri Jan 12, 2007 2:15 am    Post subject: Reply with quote

Hi Steve, yes I have noticed that using the >>> removes java's signed representation but unfortunately there must be some difference that I am not noticing. I would greatly appreciate if for testing could someone who has gotten the C++ version working, to maybe print out the hand ID's as they are created.

his would allow me to see where my code is working differently (hopefully) and if not, narrow the problem down to some other section.

Thanks alot everyone
Back to top
View user's profile Send private message
thomas



Joined: 11 Jan 2007
Posts: 24

PostPosted: Fri Jan 12, 2007 10:48 am    Post subject: Reply with quote

Steve Brecher wrote:
thomas wrote:
Could we not just precalculate all hand strengths?

((52 choose 7) + (52 choose 6) + (52 choose 5) + (52 choose 4) + (52 choose 3) + (52 choose 2)) / 4 = 39 259 047.8

There are two ways to obtain a hand evalution (plus a mixture of the two): calculation, i.e., use only the data in the representation of the hand, and table lookup. The problem to be solved by an implementer of a table lookup evaluator is how to quickly get from a representaton of a hand to the corresponding hand value in the table(s). One such solution has been mentioned above by M_White and outlined by that author (as mykey1961, same person) on the twoplustwo forum thread cited, and also a similar solution has been outlined above by darse.

It turns out (see twoplustwo thread) that the table lookup solution is blazingly fast if and only if it is not competing for the CPU data cache -- or more generally, for RAM access -- with code that is using/calling the evaluator; otherwise, calculation routines with perhaps some small tables are faster.
Quote:
I divide by four because I figure the different combinations of suits should yield the same strength.
Yes, but before you throw away the suits you have to determine whether or not the hand is a flush or straight flush. That is a significant portion of the work of a calculation evaluator.
Quote:
If I calculate the result as a probability that the hand will win the game and then bin it into 256 discrete values, i.e. a byte I should be able to store the complete set in a bit lesds than 40 mb of memory.
I'm not sure what the probability is to which you refer. Evaluators are used in comparing the strengths of hands to determine wins/ties/losses among a specified set of two or more hands. So I don't see how a probability could be associated with a hand out of the context of the other hands with which it is competing.



dividing by four does not get rid of the suit information, it just gets rid of the different possibilites of assigning suits. I.e. Kh9h becomes Kc9c and Ks9s becomes Kc9c, so we are reducing the number of possible hands without losing information.

the main problem is to generate an index from a card hand. I think one should be able to do better than the previously mentioned approaches. One could view a hand as an array of booleans with length 52. We know exactly how many of these booleans are going to be true, i.e. the number of cards in the hand. So how can we minimally represent an array of length n with exactly k being true. The solution is ceil(log2(n choose k)) bits. This is a lot less than other encodings. (here n is 52 and k is the no. of cards for which we are making the table).
The problem is going from such an array representation of the hand to an actual integer index into the lookup array.

this is not making any simplifications with similar suits yet.

Thomas
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Poker Academy Forums Forum Index -> Meerkat API and AI Discussion All times are GMT
Goto page Previous  1, 2, 3  Next
Page 2 of 3

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group