<start of tutorial>

Salutations, this is CrazyJugglerDrummer with a tutorial on how to make a poker hand evaluator in java. This program will be able to generate, evaluate, and compare poker hands.

So what do we need in OO poker? We have cards, decks, and hands. Card will be a class that contains a rank and suit variable, deck will be a container for cards, and hand will be where we evaluate and compare the poker hands.

package javapoker; public class Card { private short rank, suit; private static String[] suits = { "hearts", "spades", "diamonds", "clubs" }; private static String[] ranks = { "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King" }; public static String rankAsString( int __rank ) { return ranks[__rank]; } Card(short suit, short rank) { this.rank=rank; this.suit=suit; } public @Override String toString() { return ranks[rank] + " of " + suits[suit]; } public short getRank() { return rank; } public short getSuit() { return suit; } }

So we have read-only suit and rank variables, a simple constructor, a toString method, and a rankAsString method. The class will be ultra fast as it knows which strings to output just by accessing indexes of static arrays. We don't even have to use String.ParseInt(). We could've made a switch statement that would find the appropriate string to output based on the suit and rank variables, but it would have to evaluate them every time. Accessing an array at a specific index is much faster. The rankAsString method is a utility method for taking a number and turning it into the appropriate string for the rank.

NOTE: Now we could've used short or even byte for the rank and suit variables, since there's only 4 possible values. But computers work much faster with 4 byte ints than 2 byte shorts, so what would be lost in extremely tiny bits of memory you'll gain in processing speed. Using ints instead of shorts is best practice in java.

Now we need a deck to hold our cards. I've made another tutorial showing many different ways of making a deck, but in this tutorial I'll just show the most efficient way.

package javapoker; import java.util.Random; import java.util.ArrayList; public class Deck { private ArrayList<Card> cards; Deck() { cards = new ArrayList<Card>(); int index_1, index_2; Random generator = new Random(); Card temp; for (int a=1; a<=4; a++) { for (int b=1; b<=13; b++) { cards.add( new Card(a,b) ); } } int size for (int i=0; i<100; i++) { index_1 = generator.nextInt( cards.size() - 1 ); index_2 = generator.nextInt( cards.size() - 1 ); temp = (Card) cards.get( index_2 ); cards.set( index_2 , cards.get( index_1 ) ); cards.set( index_1, temp ); } } public Card drawFromDeck() { return cards.remove( 0 ); } public int getTotalCards() { return cards.size(); //we could use this method when making a complete poker game to see if we needed a new deck } }

We put the cards in the ArrayList of Cards, then randomly take 100 pairs of cards and switch them, shuffling our deck. To draw from the deck, we just return the first element/card, and then remove that card from the deck. All the randomization is done before hand in the constructor (prior to any actually dealing of the cards), making our drawFromDeck method much simpler and less processor intensive.

Okay, that wasn't so bad, we've got cards that can describe them selves as a string and a deck that can hold them and deal them out. But now we actually need to process them in a poker hand. The card holding mechanism will be similar to the deck, the bulk of the code will be in evaluating the hand's level. This is going to be pretty interesting, so bear with me.

We're going to have an array of 5 cards, and an array of 6 ints to represent the value of the hand. The first int will correlate to the type of the hand, 0 will be a high card, 1 a pair, with greater values for higher ranked hands. If we have a tie, the second value will have to determine the winner. How we find the second value will be unique to each type of hand: in a pair the higher pair wins, in a straight the higher top card wins, etc. If those values are equal, we move onto the next determining factor, like the highest card besides the pair, or the low pair of a two pair hand. Some hands will only have 2 determining factors, like a straight. The first value would be a straight's spot in the rank of poker hands (greater than 3 of a kind, less than a flush) and the second value would be the top card in the straight (7, jack, king, etc). For a high card, we'd have 0 as high card is the lowest ranked hand, and the next 5 values would be the ranks of the cards in the hand in descending order.

Here's what we have so far:

package javapoker; public class Hand { private Card[] cards; private int[] value; Hand(Deck d) { value = new int[6]; cards = new Card[5]; for (int x=0; x<5; x++) { cards[x] = d.drawFromDeck(); //fill up cards[] array. } //rest of our code to assign values to the value array goes here } void displayAll() { for (int x=0; x<5; x++) System.out.println(cards[x]); //calls cards[x].toString() } int compareTo(Hand that) { for (int x=0; x<6; x++) //cycle through values { if (this.value[x]>that.value[x]) return 1; else if (this.value[x]<that.value[x]) return -1; } return 0; //if hands are equal } }

All righty, now with the dirty work, figuring out the actual value of our poker hand. Let's start with a pair situation. How would we figure out if there was a pair? If we had two cards of the same rank. So how will we implement this? We could cycle through the ranks, seeing if any of the ranks had 2 cards with its value. But then for 3 of a kind we'd have to do the same thing again. How about we make an array of ints starting at 0 with 13 slots (one for each rank), then go through the cards, with each card we increment the appropriate index of the array. Let's see a code representation:

(all the code from this point on is put in the Hand constructor where our comment was)

int[] ranks = new int[14]; for (int x=0; x<=13; x++) { ranks[x]=0; //zero the contents of the array } for (int x=0; x<=4; x++) { ranks[ cards[x].getRank() ]++; //increment rank array at the index of each card's rank }

For simplicity's sake, we've used card ranks starting at 1 for ace instead of 0 for ace. If we used 0 for ace then we would be using 9 for 10, which is just confusing. Since our card ranks run 1-13, the first index of our array ( 0 ) will be empty.

Okay, so now we have our array of the card ranks, now we need to find if there are actually any pairs. We need to know if there is a pair, and if there is, what rank the pair is. So we make an int sameCards to record how many cards are of the same rank, and an int groupRank to hold the rank of the pair. We make an int sameCards because we may have more than 2 cards of the same value, maybe even 3 or 4 (hopefully not 5, unless our processor is a crooked dealer). We could've just made a bool isPair, but we want to know if there is a 3 or 4 of a kind as well.

int sameCards=1; //we know there will be at least one card of any rank int groupRank=0; for (int x=13; x>=1; x--) //loop going from 13 to 1 { if ( ranks[x] > sameCards) //If more cards of rank x than sameCards { sameCards=ranks[x]; //set sameCards to that number of cards groupRank=x; //and record the rank of the cards } }

sameCards starts at 1, so if we find a rank of which there are 2 cards, then we record 2 in sameCards and the rank (x) as groupRank. This will work fine if there's a pair, three of a kind, or four of a kind.

But wait a sec, let's say we have a full house. There is a pair of kings, so we record 2 as sameCards and 13 as groupRank. But we keep going through the other ranks, and if there was 3 fives, then we overwrite sameCards with 3 since the number of cards of that rank was more than the current value of sameCards. Similar situation: we have two pair, it records the first pair, but not the other one. We can do hands with one group of cards, but not hands with 2.We need a way to keep track of at least 2 different groups of cards, tracking the number of cards and the rank of each. Think about it a bit before moving on.

NOTE: this is definitely the most intense logic of the program, so if you don't get this at first, don't worry. The rest of the code is all a little easier, and you can come back to this part later.

my solution: all right, we have to keep track of 2 ranks of cards and how many cards are of each rank, so we'll have 2 variables to represent the ranks (largeGroupRank, smallGroupRank) and 2 to represent the number of cards that haave that rank (sameCards, sameCards2).

int sameCards=1,sameCards2=1; //initialze to 1 int largeGroupRank=0,smallGroupRank=0; for (int x=13; x>=1; x--) { if (ranks[x] > sameCards) { if (sameCards != 1) //if sameCards was not the default value { sameCards2 = sameCards; smallGroupRank = largeGroupRank; } sameCards = ranks[x]; largeGroupRank = x; } else if (ranks[x] > sameCards2) { sameCards2 = ranks[x]; smallGroupRank = x; } }

If ranks[x] is greater than sameCards, we assign the data there, otherwise if its greater than sameCards2, we assign the data there. Now I'm sure you all saw the nested if, so I guess I might as well tell you what its for. Say the if wasn't there: we find a pair of 8's, then we find three 5's. sameCards contains the pair of 8's, and since the three 5's is more than the two 8's, we overwrite sameCards. But the pair we found ealier is just overwritten and not recorded anywhere, when it should have been stuck into sameCards2. So the if statement checks if sameCards was previously assigned to something before overwriting it, and if it was, we take care of that.

Sample run: We find 2 queens, so we record that value in sameCards, since 2 is more than the 1 we initialized sameCards with. Then we find 2 7's, so we record that in the in sameCards2.

We find 3 Jacks, record those in sameCards, then find 2 threes, so we record them in sameCards2.

two 8's, then three 4's. We write the data from the two 8's into sameCards2, then put the data from the three 4's into sameCards1. All is well.

There's a little more to go, but you've made it over the hill; the rest of the code is all downhill from here.

WOO HOO! we've written the code to determine a pair, 2 pair, three of a kind, four of a kind, and a full house. The determinations left to do are whether we have a flush or a straight.

let's do a flush first. How do we find out if all the cards are the same suit? well, if 2 cards are not the same suit, then there's no flush, so let's try this. We hitch a ride on the loop that iterated through the cards recording their ranks:

boolean flush=true; //assume there is a flush for (int x=0; x<4; x++) { if ( cards[x].getSuit() != cards[x+1].getSuit() ) flush=false; }

okey dokey, travel through the cards, and if one of their suits doesn't match the suit of the next card, then there' no flush.

To figure out if there's a straight, we need to know if there are 5 cards in a row. So if there is one card in 5 consecutive ranks, we have a straight.

int topStraightValue=0; boolean straight=false; //assume no straight for (int x=1; x<=9; x++) //can't have straight with lowest value of more than 10 { if (ranks[x]==1 && ranks[x+1]==1 && ranks[x+2]==1 && ranks[x+3]==1 && ranks[x+4]==1) { straight=true; topStraightValue=x+4; //4 above bottom value break; } } if (ranks[10]==1 && ranks[11]==1 && ranks[12]==1 && ranks[13]==1 && ranks[1]==1) //ace high { straight=true; topStraightValue=14; //higher than king }

We check to see if there is one card of 5 consecutive ranks. There's a loop to do straights up to king high, and we add a special separate if for an ace high straight, since the number of aces is contained in ranks[1].

YAY we've covered all the different types of hands! Now we need to start comparing them. We have what we need to determine the type of the hand, but we still need a little more data to fix ties between hands. Say we have a pair, we know that a pair is the second lowest ranked hand. If the hand we're comparing it to is also a pair, then we need to compare the rank of the pair. If the rank of the pair is equal, we need to go to the next highest card, then the next highest card, then the next highest card. The only thing we need now is the next highest cards in order.

int[] orderedRanks = new int[5]; int index=0; if (ranks[1]==1) //if ace, run this before because ace is highest card { orderedRanks[index]=14; //record an ace as 14 instead of one, as its the highest card index++; //increment position } for (int x=13; x>=2; x--) { if (ranks[x]==1) //we have already written code to handle the case of their being two cards of the same rank { orderedRanks[index]=x; index++; } }

Now we have an array that will holds all the miscellaneous cards that don't mean anything else. (w00t!)

In our hand class, we a private array value, than held 6 ints. We are going to use this to contain the values of the hands. This array will hold all the data necessary to compare two poker hands. I mentioned our process of comparing before: "Say we have a pair, we know that a pair is the second lowest ranked hand. If the hand we're comparing it to is also a pair, then we need to compare the rank of the pair. If the rank of the pair is equal, we need to go to the next highest card, then the next highest card, then the next highest card."

This sets up a list of the things we need to compare. The most important thing is what kind of hand, so that will go in the first position. The rest of the positions will hold the data needed to break a tie between to hands of the same type. Take a look:

//start hand evaluation if ( sameCards==1 ) { //if we have no pair... value[0]=1; //this is the lowest type of hand, so it gets the lowest value value[1]=orderedRanks[0]; //the first determining factor is the highest card, value[2]=orderedRanks[1]; //then the next highest card, value[3]=orderedRanks[2]; //and so on value[4]=orderedRanks[3]; value[5]=orderedRanks[4]; } if (sameCards==2 && sameCards2==1) //if 1 pair { value[0]=2; //pair ranked higher than high card value[1]=largeGroupRank; //rank of pair value[2]=orderedRanks[0]; //next highest cards. value[3]=orderedRanks[1]; value[4]=orderedRanks[2]; } if (sameCards==2 && sameCards2==2) //two pair { value[0]=3; value[1]= largeGroupRank>smallGroupRank ? largeGroupRank : smallGroupRank; //rank of greater pair value[2]= largeGroupRank<smallGroupRank ? largeGroupRank : smallGroupRank; //rank of smaller pair value[3]=orderedRanks[0]; //extra card } if (sameCards==3 && sameCards2!=2) //three of a kind (not full house) { value[0]=4; value[1]= largeGroupRank; value[2]=orderedRanks[0]; value[3]=orderedRanks[1]; } if (straight) { value[0]=5; value[1]=topStraightValue; //if we have two straights, the one with the highest top cards wins } if (flush) { value[0]=6; value[1]=orderedRanks[0]; //tie determined by ranks of cards value[2]=orderedRanks[1]; value[3]=orderedRanks[2]; value[4]=orderedRanks[3]; value[5]=orderedRanks[4]; } if (sameCards==3 && sameCards2==2) //full house { value[0]=7; value[1]=largeGroupRank; value[2]=smallGroupRank; } if (sameCards==4) //four of a kind { value[0]=8; value[1]=largeGroupRank; value[2]=orderedRanks[0]; } if (straight && flush) //straight flush { value[0]=9; value[1]=topStraightValue; }

So now that we have all of our value array set up (that was the end of the Hand constructor) we can use this method to compare our hand to any other hand:

int compareTo(Hand that) { for (int x=0; x<6; x++) { if (this.value[x]>that.value[x]) return 1; else if (this.value[x] != that.value[x]) if not greater and not equal must be less return -1; } return 0; //if hands are equal }

And we can add this method to display a summary of the hand: (This is also where we use that static rankAsString method in the Card class to convert an integer in to associate card rank e.g. 11 = "Jack")

void display() { String s; switch( value[0] ) { case 1: s="high card"; break; case 2: s="pair of " + Card.rankAsString(value[1]) + "\'s"; break; case 3: s="two pair " + Card.rankAsString(value[1]) + " " + Card.rankAsString(value[2]); break; case 4: s="three of a kind " + Card.rankAsString(value[1]) + "\'s"; break; case 5: s=Card.rankAsString(value[1]) + " high straight"; break; case 6: s="flush"; break; case 7: s="full house " + Card.rankAsString(value[1]) + " over " + Card.rankAsString(value[2]); break; case 8: s="four of a kind " + Card.rankAsString(value[1]); break; case 9: s="straight flush " + Card.rankAsString(value[1]) + " high"; break; default: s="error in Hand.display: value[0] contains invalid value"; } s = " " + s; //this just moves the output over a little in the console so its easier to see when viewing the output System.out.println(s); }

Now that that exhaustive class is done, we can write some test code and see this stuff at work. Our first main method will test the randomness of the deck we made (discussed more in the past tutorial).

package javapoker public class Main { public static void main(String[] args) { Deck deck= new Deck(); Card C; System.out.println( deck.getTotalCards() ); while (deck.getTotalCards()!=0 ) { C = deck.drawFromDeck(); System.out.println( C.toString() ); } } }

OK, that looks random enough, so now lets try making some hands and seeing what the program thinks they are ranked, and compare that to what we know they should be ranked:

public static void main(String[] args) { for (int i=0; i<100; i++) { Deck deck= new Deck(); Hand hand= new Hand(deck); hand.display(); //show the summary of the hand, e.g. "full house" hand.displayAll(): //look at all the individual cards in the hand } }

All righty, last mechanism to test is the hand comparing, with a similar method:

public static void main(String[] args) { for (int i=0; i<20000; i++) { Deck deck= new Deck(); Hand hand= new Hand(deck); Hand hand2= new Hand(deck); hand.display(); hand.displayAll(); hand2.display(); hand2.displayAll(); System.out.println(hand.compareTo(hand2)); } }

And there you have it, how to make a poker hand evaluator in java! I hope you've enjoyed this tutorial! Please post any and all comments, questions, and suggestions!

NOTE: This tutorial only covers how to make, evaluate, and compare poker hands (which I think was enough for one tutorial,). It does not actually tell how to make a full playable poker game. Making betting wouldn't be that difficult, but the AI involved for making realistic opponents is beyond this scope. This is a good basis for a poker game however, and if anyone completes the poker game with good, realistic AI and gameplay, they can go ahead and post that as a tutorial on DIC.

Here is the full code for each of the classes in the tutorial (for pasting into a compiler):

Card.java

package javapoker; public class Card { private short rank, suit; private static String[] suits = { "hearts", "spades", "diamonds", "clubs" }; private static String[] ranks = { "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King" }; public static String rankAsString( int __rank ) { return ranks[__rank]; } Card(short suit, short rank) { this.rank=rank; this.suit=suit; } public @Override String toString() { return ranks[rank] + " of " + suits[suit]; } public short getRank() { return rank; } public short getSuit() { return suit; } }

Deck.java

package javapoker; import java.util.Random; import java.util.ArrayList; public class Deck { private ArrayList<Card> cards; Deck() { cards = new ArrayList<Card>(); int index_1, index_2; Random generator = new Random(); Card temp; for (short a=0; a<=3; a++) { for (short b=0; b<=12; b++) { cards.add( new Card(a,b) ); } } int size = cards.size() -1; for (short i=0; i<100; i++) { index_1 = generator.nextInt( size ); index_2 = generator.nextInt( size ); temp = (Card) cards.get( index_2 ); cards.set( index_2 , cards.get( index_1 ) ); cards.set( index_1, temp ); } } public Card drawFromDeck() { return cards.remove( cards.size()-1 ); } public int getTotalCards() { return cards.size(); //we could use this method when making a complete poker game to see if we needed a new deck } }

Hand.java

[code]

package javapoker;

public class Hand {

private Card[] cards;

private int[] value;

Hand(Deck d)

{

value = new int[6];

cards = new Card[5];

for (int x=0; x<5; x++)

{

cards[x] = d.drawFromDeck();

}

int[] ranks = new int[14];

int[] orderedRanks = new int[5]; //miscellaneous cards that are not otherwise significant

boolean flush=true, straight=false;

int sameCards=1,sameCards2=1;

int largeGroupRank=0,smallGroupRank=0;

int index=0;

int topStraightValue=0;

for (int x=0; x<=13; x++)

{

ranks[x]=0;

}

for (int x=0; x<=4; x++)

{

ranks[ cards[x].getRank() ]++;

}

for (int x=0; x<4; x++) {

if ( cards[x].getSuit() != cards[x+1].getSuit() )

flush=false;

}

for (int x=13; x>=1; x--)

{

if (ranks[x] > sameCards)

{

if (sameCards != 1) //if sameCards was not the default value

{

sameCards2 = sameCards;

smallGroupRank = largeGroupRank;

}

sameCards = ranks[x];

largeGroupRank = x;

} else if (ranks[x] > sameCards2)

{

sameCards2 = ranks[x];

smallGroupRank = x;

}

}

if (ranks[1]==1) //if ace, run this before because ace is highest card

{

orderedRanks[index]=14;

index++;

}

for (int x=13; x>=2; x--)

{

if (ranks[x]==1)

{

orderedRanks[index]=x; //if ace

index++;

}

}

for (int x=1; x<=9; x++) //can't have straight with lowest value of more than 10

{

if (ranks[x]==1 && ranks[x+1]==1 && ranks[x+2]==1 && ranks[x+3]==1 && ranks[x+4]==1)

{

straight=true;

topStraightValue=x+4; //4 above bottom value

break;

}

}

if (ranks[10]==1 && ranks[11]==1 && ranks[12]==1 && ranks[13]==1 && ranks[1]==1) //ace high

{

straight=true;

topStraightValue=14; //higher than king

}

for (int x=0; x<=5; x++)

{

value[x]=0;

}

//start hand evaluation

if ( sameCards==1 ) {

value[0]=1;

value[1]=orderedRanks[0];

value[2]=orderedRanks[1];

value[3]=orderedRanks[2];

value[4]=orderedRanks[3];

value[5]=orderedRanks[4];

}

if (sameCards==2 && sameCards2==1)

{

value[0]=2;

value[1]=largeGroupRank; //rank of pair

value[2]=orderedRanks[0];

value[3]=orderedRanks[1];

value[4]=orderedRanks[2];

}

if (sameCards==2 && sameCards2==2) //two pair

{

value[0]=3;

value[1]= largeGroupRank>smallGroupRank ? largeGroupRank : smallGroupRank; //rank of greater pair

value[2]= largeGroupRank<smallGroupRank ? largeGroupRank : smallGroupRank;

value[3]=orderedRanks[0]; //extra card

}

if (sameCards==3 && sameCards2!=2)

{

value[0]=4;

value[1]= largeGroupRank;

value[2]=orderedRanks[0];

value[3]=orderedRanks[1];

}

if (straight && !flush)

{

value[0]=5;

value[1]=topStraightValue;

}

if (flush && !straight)

{

value[0]=6;

value[1]=orderedRanks[0]; //tie determined by ranks of cards

value[2]=orderedRanks[1];

value[3]=orderedRanks[2];

value[4]=orderedRanks[3];

value[5]=orderedRanks[4];

}

if (sameCards==3 && sameCards2==2)

{

value[0]=7;

value[1]=largeGroupRank;

value[2]=smallGroupRank;

}

if (sameCards==4)

{

value[0]=8;

value[1]=largeGroupRank;

value[2]=orderedRanks[0];

}

if (straight && flush)

{

value[0]=9;

value[1]=topStraightValue;

}

}

void display()

{

String s;

switch( value[0] )

{

case 1:

s="high card";

break;

case 2:

s="pair of " + Card.rankAsString(value[1]) + "\'s";

break;

case 3:

s="two pair " + Card.rankAsString(value[1]) + " " + Card.rankAsString(value[2]);

break;

case 4:

s="three of a kind " + Card.rankAsString(value[1]) + "\'s";

break;

case 5:

s=Card.rankAsString(value[1]) + " high straight";

break;

case 6:

&n

This post has been edited by **crazyjugglerdrummer**: 26 July 2009 - 04:23 AM