Introduction
The title says it all: make a program that can create, evaluate, and compare 5-card poker hands.
Background
This tutorial works fine as a stand-alone, but if you want some more information about creating the deck and card class in Java, see here.
Using the code
This program will be able to generate, evaluate, and compare poker hands. A basic understanding of OO design is required (making classes, and having them interact with one another). No inheritance or interfaces here. ;) Random
, ArrayList
s, and static variables and methods are used on occasions, but they won't be a show stopper if you don't know what they are yet. I include quick descriptions in the For beginners: notes.
So what do we need in OO poker? We have cards, decks, and hands. Card
will be a class that contains rank
and suit
variables, Deck
will be a container for Card
s, 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. This array functions as a dictionary, allowing us to convert an int
to the appropriate string quickly and easily. 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 (we'll use it later).
For beginners: Notes on the static
keyword: static methods and variables apply to a class as a whole, not any particular instance like instance methods and variables. We call getRank()
on a specific card instance, created with new
. But we would call rankAsString
on the class, with "Card.rankAsString( 4 );
". We have a static array to represent the different names of the suits. This array belongs to the class as a whole, not just one Card
, so it can be accessed by the instance methods of each Card
, and by the static methods of the class.
Note: Now, we could've used short
or even byte
for the rank
and suit
variables, since there are only four possible values. But computers work much faster with 4 byte int
s than 2 byte short
s, so what would be lost in extremely tiny bits of memory you'll gain in processing speed. Using int
s instead of short
s 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 = 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 put the cards in the ArrayList
of Card
s, 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 beforehand in the constructor (prior to any actual dealing of the cards), making our drawFromDeck
method much simpler and less processor intensive.
Note for beginners: A brief tutorial of ArrayList
: ArrayList
is an example of a "generic" in Java. Generics are sort of like arrays because they store objects of a specific type (the type between the <>). But ArrayList
is an object, not just an array, so it has neat methods to help us out. We can automatically get its size with size()
, remove or add an object with remove()
or add()
, get and set objects like an array with get()
and set()
, and maybe most helpful of all, if we need more space, it will just automatically grow for us (actually just copies its items into a bigger array, but it handles all of this so we don't have to). This makes features we commonly need with data structures, like adding a new element, really easy to use (with an array, to add a new element to the end, we have to make an index variable that starts at zero; each time we add an object at that index, we increment it to put us at a fresh spot so we can add more objects; of course, we have to hope the array has enough space; you will see this technique used later). To use an ArrayList
, we have to import java.util.ArrayList
at the top of the source file.
Note for beginners: Summary of Random
: Random
is a neat little utility class we can use to generate random numbers. To use it, we import java.util.Random
, then make a random with Random generator = new Random()
. After we do this, we can use our Random
instance "generator
" to get random int
s with nextInt()
. If we pass an integer to nextInt()
, it will give us a random integer less than that int
and greater than zero. So when we call generator.nextInt( 52 );
, it gives us a random number between 0 and 51 that we can use as an index in our ArrayList
to swap two cards, shuffling the deck.
Okay, that wasn't so bad, we've got cards that can describe themselves as a string and a deck that can hold them in an ArrayList
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 int
s 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 two determining factors, like a straight. The first value would be a straight's spot in the rank of the 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();
}
}
void displayAll()
{
for (int x=0; x<5; x++)
System.out.println(cards[x]);
}
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])
return -1;
}
return 0;
}
}
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 is a pair? If we have two cards of the same rank. How will we implement this? We could cycle through the ranks, seeing if any of the ranks has two 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 int
s 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;
}
for (int x=0; x<=4; x++)
{
ranks[ cards[x].getRank() ]++;
}
For simplicity's sake, we've used card ranks starting at 1 for ace instead of 0 for ace. If we use 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 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 two 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;
for (int x=13; x>=1; x--)
{
if ( ranks[x] > sameCards)
{
sameCards=ranks[x];
groupRank=x;
}
}
sameCards
starts at 1, so if we find a rank of which there are two cards, then we record 2 in sameCards
and 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 second, 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 are 3 fives, then we overwrite sameCards
with 3 since the number of cards of that rank is more than the current value of sameCards
. Similar situation: we have two pairs, 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 two 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 two ranks of cards and how many cards are of each rank, so we'll have two variables to represent the ranks (largeGroupRank
, smallGroupRank
) and two to represent the number of cards that have that rank (sameCards
, sameCards2
).
int sameCards=1,sameCards2=1;
for (int x=13; x>=1; x--)
{
if (ranks[x] > sameCards)
{
if (sameCards != 1)
{
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 it is 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 it is 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 earlier 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 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 pairs, three of a kind, four of a kind, and a full house. The determinations left to do is 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 two cards are not the same suit, then there's no flush, so let's try this. We hitch a ride on the loop that iterates through the cards recording their ranks:
boolean flush=true;
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 five cards in a row. So, if there is one card in five5 consecutive ranks, we have a straight.
int topStraightValue=0;boolean straight=false;
for (int x=1; x<=9; x++)
{
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;
break;
}
}
if (ranks[10]==1 && ranks[11]==1 && ranks[12]==1 &&
ranks[13]==1 && ranks[1]==1)
{
straight=true;
topStraightValue=14;
}
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, and 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)
{
orderedRanks[index]=14;
index++;
}
for (int x=13; x>=2; x--)
{
if (ranks[x]==1)
{
orderedRanks[index]=x;
index++;
}
}
Now we have an array that will hold all the miscellaneous cards that don't mean anything else. (w00t!)
In our Hand
class, we have a private array value that holds six int
s. 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, and 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 two hands of the same type. Take a look:
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;
value[2]=orderedRanks[0];
value[3]=orderedRanks[1];
value[4]=orderedRanks[2];
}
if (sameCards==2 && sameCards2==2)
{
value[0]=3;
value[1]= largeGroupRank>smallGroupRank ? largeGroupRank : smallGroupRank;
value[2]= largeGroupRank<smallGroupRank ? largeGroupRank : smallGroupRank;
value[3]=orderedRanks[0];
}
if (sameCards==3 && sameCards2!=2)
{
value[0]=4;
value[1]= largeGroupRank;
value[2]=orderedRanks[0];
value[3]=orderedRanks[1];
}
if (straight)
{
value[0]=5;
value[1]=;
}
if (flush)
{
value[0]=6;
value[1]=orderedRanks[0];
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]=;
}
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])
return -1;
}
return 0;
}
And, we can add this method to display a summary of the hand (this is also where we use the static rankAsString
method in the Card
class to convert an integer to an 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;
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, let's 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();
hand.displayAll():
}
}
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 article on CodeProject.
The code
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();
}
}
Hand.java
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];
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)
{
sameCards2 = sameCards;
smallGroupRank = largeGroupRank;
}
sameCards = ranks[x];
largeGroupRank = x;
} else if (ranks[x] > sameCards2)
{
sameCards2 = ranks[x];
smallGroupRank = x;
}
}
if (ranks[1]==1)
{
orderedRanks[index]=14;
index++;
}
for (int x=13; x>=2; x--)
{
if (ranks[x]==1)
{
orderedRanks[index]=x;
index++;
}
}
for (int x=1; x<=9; x++)
{
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;
break;
}
}
if (ranks[10]==1 && ranks[11]==1 && ranks[12]==1 &&
ranks[13]==1 && ranks[1]==1)
{
straight=true;
topStraightValue=14;
}
for (int x=0; x<=5; x++)
{
value[x]=0;
}
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;
value[2]=orderedRanks[0];
value[3]=orderedRanks[1];
value[4]=orderedRanks[2];
}
if (sameCards==2 && sameCards2==2)
{
value[0]=3;
value[1]= largeGroupRank>smallGroupRank ? largeGroupRank : smallGroupRank;
value[2]= largeGroupRank<smallGroupRank ? largeGroupRank : smallGroupRank;
value[3]=orderedRanks[0];
}
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]=;
}
if (flush && !straight)
{
value[0]=6;
value[1]=orderedRanks[0];
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]=;
}
}
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;
System.out.println(s);
}
void displayAll()
{
for (int x=0; x<5; x++)
System.out.println(cards[x]);
}
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])
return -1;
}
return 0;
}
}
Points of interest
When I was watching this program evaluate and compare 200,000 poker hands, I really felt powerful, like I had accomplished something. I couldn't have done that in 10 years, but the computer did it in a bit more than a minute.
History
- 8-7-09: Submitted article.