Poker Hands

Now that we have a Card class, we can start building some interesting card Games. There are an entire family of Poker games that use a hierarchy of poker hands to determine the winner. There are many web sites that explain the basic rank, and how to determine the winner if two players both have the same rank. For example, if two players have a Full House (a pair with 3-of-a-kind), the winner is the one whose 3-of-kind is highest.

We need a new class that will determine the strength of a poker hand. It can then be used in a variety of different poker games like Texas Hold-Em, 5 card stud, 7 card stud, etc. Before we write a poker game, we must thoroughly test this class' ability to determine the highest hand.

Your job is to write some methods that can determine the rank. Each method like hasStraight() or hasFullHouse() will return a boolean.

I have written the rest so that if you correctly write your methods, the job will be done. But testing is a big part of that job. I wrote TwoHandViewer so that you can check for errors in determining the winning hand. It will randomly make two poker hands, show the score for each, and announce the better of the two. I made this a GUI application to demonstrate some of the new ideas in your reading including inner classes and interfaces.

Card.java

package poker;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JApplet;
import java.awt.*;

/**
 * class Card here represents a playing card
 * 
 * @author Chris Thiel 
 * @version 28 Sept 2008
 */
public class Card 
{
    //some handy constants
    public static final int SPADES=3;
    public static final int HEARTS=2;
    public static final int DIAMONDS=1;
    public static final int CLUBS=0;
    /**
     * suitName is an array contains the name of the Suit 
     * so that the index matches the suit number
     */
    public static final String[] suitName={"Clubs","Diamonds","Hearts","Spades" };
    /**
     * suitSymbol Have the unicode values to print the correct symbol, 
     * arranges so thet the index matches the suit number
     */
    public static final String[] suitSymbol={"\u2663", "\u2666","\u2665","\u2660"};
    /**
     * pipName is an array that contains the name of the card's pips 
     * so that the index matches the number of the card.
     * For example pipName[1] has a "Ace"
     */
    public static final String[] pipName={"-","Ace","2","3","4","5","6","7","8","9","10",
                                    "Jack","Queen","King"};
    // instance variables - or Card Attributes
    /**
     * suit contains the suit (0 to 3)
     */
    private int suit; 
    /**
     * pips contains the number of pips of the card (1-13)
     * Ace is 1 and 13 is King
     */
    private int pips;
    /**
     * faceUp is true is the card is exposed
     * and false otherwise.  
     */
    private boolean faceUp;

    /**
     * Default Constructor for objects of class Card
     * is a random card of a random suit
     */
    public Card()
    {
        // The default card is randomly chosen
        suit = (int)(Math.random()*4);
        pips = 1+(int)(Math.random()*13);
        faceUp=false;
    }
    /**
     * A Card can be constructed with a determined 
     * number of pips (and integer 1-13) 
     * and suit (an integer from 0-3)
     */
    public Card(int p, int s)
    {
        if (s>=0 && s<4) suit = s;
        if (p>0 && p<14) pips = p;
        faceUp=false;
    }
    /**
     * A Card can be constructed with a determined 
     * number of pips (a String "Ace" to "King") 
     * and suit (a String "Clubs" to "Spades")
     */
    public Card(String p, String s)
    {
        suit = indexOf(suitName, s);
        pips = indexOf(pipName, p);
        faceUp=false;
    }
    /**
     * indexOf returns the index of the String s
     * in the String Array a
     * (it returns -1 if not found). This method
     * ignores case.
     */
    public int indexOf(String[] a, String s)
    {
        int idx=-1;
        for (int i=0; i< a.length; i++)
           if (a[i].toLowerCase().equals(s.toLowerCase())) idx=i;
        return idx;
    }
    /**
     * @return getSuit returns the Card's suit,
     * an int from 0 to 3
     */
    public int getSuit()
    {

        return suit;
    }
    /**
     * setSuit changes the Card's suit,
     * @param  the suit 0 to 3.  You can use the 
     * predefined integer constants 
     * SPADES, HEARTS, DIAMONDS and CLUBS
     * from the Card class.
     */

    public void setSuit(int s)
    {      
        if (s>=0 && s<4) suit=s;
    }
    /**
     * @return getPips returns the Card's pips,
     * an int from 1 to 13 where an Ace is a 1
     * and a King is a 13.
     */
    public int getPips()
    {

        return pips;
    }
    public int getPipsAceHigh()
    {
        if (pips==1) return 14;
        return pips;
    }
    /**
     * setPips changes the Card's pips,
     * @param  the pips are an integer from 1 to 13,  
     * where 1 is an Ace and 13 is a King.
     */
    public void setPips(int p)
    {
        // put your code here
        if (p>0 && p<14) pips=p;
    }

    /**
     * @return getSuit returns the Card's suit,
     * an int from 0 to 3
     */
    public int getValue()
    {
        if (pips>9) return 10;
        return pips;
    }
    /**
     * Two cards are equal if they have
     * the same number of pips.  The suit is ignored
     * with this comparison
     * 
     * @return true if they match the number of pips
     */
    public boolean equals(Card c)
    {
        if (c.getPips()==pips) return true;
        return false;
    }
    /**
     * toString will return the contents of the card 
     * in a string format
     * @return The pips and suit of a card
     */
    public String toString()
    {
        return pipName[pips]+" of "+suitName[suit];
    }
    /**
     * info will return the contents of the card 
     * in a short two character string
     * @return The pips and suit of a card
     */
    public String info()
    {
        if (pips==10) return pipName[pips].substring(0,2)+suitSymbol[suit];
        return pipName[pips].substring(0,1)+suitSymbol[suit];
    }
    /**
     * comparePips will return the difference between 
     * two cards' number of pips.  The suit is ignored
     * with this comparison.  Here the ace is low.
     * If the two cards happen to have the same
     * number of pips, it returns the integer 0. 
     * 
     * If, for example,
     * card1 is a 2 of clubs and card2 is a 6 of spades, 
     * then card1.comparePips(card2) returns -4, since
     * card1 has four less pips than card2.
     * 
     * @return the difference between number of pips
     */
    public int comparePips( Card c )
    {
        return pips-c.getPips();
    }
    /**
     * comparePipsAceHigh will return the difference between 
     * two cards' number of pips, Ace High.  The suit is ignored
     * with this comparison, but the Ace is above a King.
     * If the two cards happen to have the same
     * number of pips, it returns the integer 0. 
     * 
     * If, for example,
     * card1 is an ace of clubs and card2 is a queen of spades, 
     * then card1.comparePips(card2) returns 2, since
     * card1 is two larger than card2.
     * 
     * @return the difference between number of pips
     */
    public int comparePipsAceHigh( Card c )
    {
        // This needs to be be implemented
        int myPips=pips;
        int theirPips=c.getPips();
        if (myPips==1) myPips=14;
        if (theirPips==1) theirPips=14;

    	return myPips-theirPips;
    }
    /**
     * isLessThan will compare two cards by pips, and in case of 
     * a match, will compare the suit.  If the number
     * of pips match, then the higher suit is considered.
     * 
     * The the order of the suits
     * (from high to low) is SPADES, HEARTS, DIAMONDS, CLUBS
     * 
     * The Ace is low, and is considered to be below a 2.
     * 
     * If the two cards happen to have the same
     * number of pips, it then compares the suit returns the integer 0. 
     * 
     * If, for example,
     * card1 is an ace of clubs and card2 is a 2 of spades, 
     * then card1.comparePips(card2) returns true, since
     * card1 is less than card2.
     * 
     * If card1 is a 6 of hearts and card2 is a 6 of clubs,
     * than card1.isLessThan(card2) returns false,
     * since hearts is greater than clubs
     * 
     * @return the difference between number of pips
     */
    public boolean isLessThan( Card c )
    {

    	if (pips>c.getPips()) return false;
    	if (pips<c.getPips()) return true;
    	//if we got this far, its the same number of pips
    	if (suit <c.getSuit()) return true;
    	// if we got this far, it must have same suit or more
        return false;
    }
    public boolean isLessThanAceHigh( Card c )
    {
    	int myPips=pips;
        int theirPips=c.getPips();
        if (myPips==1) myPips=14;
        if (theirPips==1) theirPips=14;
    	if (myPips>theirPips) return false;
    	if (myPips<theirPips) return true;
    	//if we got this far, its the same number of pips
    	if (suit <c.getSuit()) return true;
    	// if we got this far, it must have same suit or more
        return false;
    }
    public void turnUp(){
    	faceUp=true;
    }
    public void turnDown(){
    	faceUp=false;
    }
    public void turnOver(){
    	faceUp=!faceUp;
    }
    public boolean isFaceUp(){
    	return faceUp;
    }
    public void draw(Graphics g, int x, int y){
    	g.setColor(Color.WHITE);

		Rectangle loc=new Rectangle(x,y,60,80);
		if (faceUp){
			g.setFont(new Font("Helvetica", Font.BOLD,  20));
			g.setColor(Color.WHITE);
			g.fillRect(loc.x,loc.y,loc.width,loc.height);
			g.setColor(Color.BLACK);
			g.drawRect(loc.x,loc.y,loc.width,loc.height);
			if (suit>0 && suit<3) 
			g.setColor(Color.RED);
			g.drawString(this.info(), loc.x+8,loc.y+23);
		} else {
			g.setColor(Color.BLUE);
			g.fillRect(loc.x,loc.y,loc.width,loc.height);
			g.setColor(Color.BLACK);
			g.drawRect(loc.x,loc.y,loc.width,loc.height);
			g.setColor(Color.WHITE);
			for (int i=2; i<25; i+=5)
				g.drawRoundRect(loc.x+i, loc.y+i, loc.width-i*2, loc.height-i*2,
						i, i);
		}

    }
    public void draw(Graphics2D g2, Rectangle loc){
    	g2.setColor(Color.WHITE);
		int letterSize=5*loc.width/12;
		if (faceUp){
			g2.setFont(new Font("Helvetica", Font.BOLD,  letterSize));
			g2.setColor(Color.WHITE);
			g2.fill(loc);
			g2.setColor(Color.BLACK);
			g2.draw(loc);
			if (suit>0 && suit<3) 
			g2.setColor(Color.RED);
			g2.drawString(this.info(), loc.x+8,loc.y+7*letterSize/6);
		} else {
			g2.setColor(Color.BLUE);
			g2.fill(loc);
			g2.setColor(Color.BLACK);
			g2.draw(loc);
			g2.setColor(Color.WHITE);
			for (int i=2; i<25; i+=5)
				g2.drawRoundRect(loc.x+i, loc.y+i, loc.width-i*2, loc.height-i*2,
						i, i);
		}

    }
    public void draw(Graphics2D g2, Rectangle loc, Image cardBack, JApplet a){
    	g2.setColor(Color.WHITE);
		int letterSize=5*loc.width/12;
		if (faceUp){
			g2.setFont(new Font("Helvetica", Font.BOLD,  letterSize));
			g2.setColor(Color.WHITE);
			g2.fill(loc);
			g2.setColor(Color.BLACK);
			g2.draw(loc);
			if (suit>0 && suit<3) 
			g2.setColor(Color.RED);
			g2.drawString(this.info(), loc.x+8,loc.y+7*letterSize/6);
		} else {
			g2.drawImage(cardBack, loc.x, loc.y, loc.width, loc.height,a);

		}

    }
}

PokerHand.java

package poker;

import java.util.ArrayList;
/**
 * Write a description of class PokerHand here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class PokerHand
{
    private Card[] hand;
    private Card highCard;
    private double score;
    /**
     * Constants
     * 
     */
    final String[] r={"High Card","One Pair", "Two Pair", "Three of a Kind","Straight", "Flush", "Full House", "Four of a Kind", "Straight Flush", "Royal Flush"}; 
    /**
     * Constructors
     */

    public PokerHand(Card c1, Card c2, Card c3, Card c4, Card c5)
    {
         hand = new Card[5];
         hand[0]=c1;
         hand[1]=c2;
         hand[2]=c3;
         hand[3]=c4;
         hand[4]=c5;
         highCard=hand[0];
         for (int i=1; i<5;i++)
            if (highCard.isLessThanAceHigh(hand[i])) 
                highCard=hand[i];
         sortAceHigh();
         score=computeScore();
    }
    public PokerHand(ArrayList<Card> list)
    {
        hand = new Card[5];
        highCard=list.get(0);
        for (int i=0;i<5;i++)
        {
            hand[i]=list.get(i);
            if (highCard.isLessThanAceHigh(hand[i]) )
              highCard=hand[i];
        }
        sortAceHigh();
       score=computeScore();
    }
     public PokerHand(Card[] h)
    {
        hand = new Card[5];
        highCard=h[0];
        for (int i=0;i<5;i++)
        {
            hand[i]=h[i];
            if (highCard.isLessThanAceHigh(hand[i]) )
              highCard=hand[i];
        }
        sortAceHigh();
        score=computeScore();
    } 
    /**
     * Accessor method that returns the high card
     * (Ace is considered the highest card,
     *  suit is ignored)
     * @return the highest card of the hand
     */
    public Card getHighCard()
    {
        return highCard;
    }
     public double getScore()
    {
        return score;
    }
    public String toString()
    {
        String result="";
        for(Card c:hand)
           result+=c.toString()+"\n";
        return result;
    }
    /**
     * mutator method that will arrage the hand in
     * descending order, Ace high
     */
    public void sortAceHigh()
    {

        for (int i=0;i<4;i++)
        {
            for(int j=i;j<5;j++)
               if (hand[i].isLessThanAceHigh(hand[j]) )
               {
                   Card h=hand[j];
                   hand[j]=hand[i];
                   hand[i]=h;
               }
        }
    }
    /**
     * mutator method that will arrange the hand in
     * descending order, Ace low
     * 
     */
     public void sortAceLow()
    {

        for (int i=0;i<4;i++)
        {
            for(int j=i;j<5;j++)
               if (hand[i].isLessThan(hand[j]) )
               {
                   Card h=hand[j];
                   hand[j]=hand[i];
                   hand[i]=h;
               }
        }
    }
    public boolean hasPair()
    {
        // your code here
        return false;
    }
    public boolean hasTwoPair()
    {
       //your code here
        return false;
    }
    public boolean hasThreeOfAKind()
    {
       //your code here
        return false;
    }
    public boolean hasFourOfAKind()
    {
        i//your code here
        return false;
    }
    public boolean hasFlush()
    {
        //your code here
        return false;
    }
    public boolean hasStraight()
    {
       //your code here
        return false;
    }
    public boolean hasStraightFlush()
    {
       /your code here
        return false;
    }
    public boolean hasRoyalFlush()
    {
       // your code here
        return false;
    }
    public boolean hasFullHouse()
    {
       //your code here
        return false;
    }
    public int highCardBonus(ArrayList<Card> c)
    {
        int s=0;
        int n=c.size();
        for (int j=0;j<n;j++)
          s=100*s+c.get(j).getPipsAceHigh();
        return s;
    }
    /**
     * sets score to determine the strength of the hand
     * precondition: hand is sorted in descending order
     */
    public double computeScore()
    {
        double score=0; 


        if (hasPair()){
            int pairValue=0;
            ArrayList<Card> c=new ArrayList<Card>();
            for (int j=0;j<5;j++)
                c.add(hand[j]);
            for (int j=0;j<4;j++)
                if (hand[j].equals(hand[j+1])){
                   c.remove(hand[j]);
                   c.remove(hand[j+1]);
                   pairValue=hand[j].getPipsAceHigh();
                }            
            score=1+pairValue/100.0+highCardBonus(c)/100000000.0;
        }
        if (hasTwoPair()){
            int pair1=0;
            int pair2=0;
            ArrayList<Card> c=new ArrayList<Card>();
            for (int j=0;j<5;j++)
                c.add(hand[j]);
            for (int j=0;j<4;j++)
                if (hand[j].equals(hand[j+1])){
                   c.remove(hand[j]);
                   c.remove(hand[j+1]);
                   if(pair1==0)
                       pair1=hand[j].getPipsAceHigh();
                   else
                       pair2=hand[j].getPipsAceHigh();
                }
            score=2+pair1/100.0+pair2/10000.0+highCardBonus(c)/1000000.0;
        }
        if (hasThreeOfAKind()) {//could be in 3 different positions
            int p3=hand[2].getPipsAceHigh();
            int p1a=hand[3].getPipsAceHigh();//presume 3k high
            int p1b=hand[4].getPipsAceHigh();
            if (p3==hand[4].getPipsAceHigh()){//3 K is low rank
                p1a=hand[0].getPipsAceHigh();
                p1b=hand[1].getPipsAceHigh();
            } else
            if (hand[1].getPipsAceHigh()==hand[3].getPipsAceHigh()){// 3K in middle
                p1a=hand[0].getPipsAceHigh();
                p1b=hand[4].getPipsAceHigh();
            }
            score=3+p3/100.00+p1a/10000.0+p1b/1000000.0;
        }
        if (hasStraight()) score=4+highCard.getPipsAceHigh()/100.0;
        if (hasFlush()) {
            int bonus=0;
            for(int j=0;j<5;j++)
               bonus=100*bonus+hand[j].getPipsAceHigh();
            score=5+bonus/10000000000.0;
        }
        if (hasFullHouse()){
            int p3=hand[2].getPipsAceHigh();
            int p2=hand[4].getPipsAceHigh();
            if (p3==p2) p2=hand[0].getPipsAceHigh();
            score=6+p3/100.0+p2/10000.0;
        }
        if (hasFourOfAKind()){
            int p4=hand[2].getPipsAceHigh();
            int p1=hand[4].getPipsAceHigh();
            if (p4==p1) p1=hand[0].getPipsAceHigh();
            score=7+p4/100.0+p1/10000.0;
        }
        if (hasStraightFlush()){
            score=8+highCard.getPipsAceHigh()/100.0;
        }
        if (hasRoyalFlush()) score=9;
        if (score<1.0){
            int bonus=0;
            for(int j=0;j<5;j++)
               bonus=100*bonus+hand[j].getPipsAceHigh();
            score=bonus/10000000000.0;
        }
        return score;
    }
    /**
     * compares One Poker hand to another,
     * taking into account total pips.
     * If positive, this hand is greater
     * than the other.
     */
    public double compareTo(PokerHand other)
    {
        return score - other.getScore();
    }
    public String display(){
        String result="";
        for (int i=0;i<5;i++)
           result+=hand[i].info()+" ";
        return result;
    }
    public String getInfo()
    {
        int rank=(int)score;
        return r[rank];
    }
}

TwoHandFrame.java

package poker;

import javax.swing.*;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class TwoHandFrame extends JFrame
{
    private JButton button;
    private JLabel hand1;
    private JLabel hand2;
    private JLabel score1;
    private JLabel score2;
    private JLabel victor;

    private JPanel panel;
    private JPanel subPanel;
    private PokerHand h1;
    private PokerHand h2;

    public TwoHandFrame()
    {
       h1 = new PokerHand(new Card(1,0), new Card(13,0), new Card(12,0), new Card(11,0), new Card(10,0));
       h2 = new PokerHand(new Card("9","Diamonds"), new Card("8","Spades"), new Card("5","Diamonds"), new Card("3","Spades"), new Card("2","Hearts"));

      // Use instance fields for components
      hand1 = new JLabel(h1.display(),JLabel.CENTER);
      hand2 = new JLabel(h2.display(),JLabel.CENTER);
      score1 = new JLabel(" "+h1.getScore(),JLabel.LEFT);
      score2 = new JLabel(" "+h2.getScore(),JLabel.LEFT);
      victor = new JLabel("", JLabel.CENTER);
      if (h1.getScore()>h2.getScore())
             victor.setText("Hand 1 Wins");
      else
           victor.setText("Hand 2 Wins");
      // Use helper methods
      createButton();
      createPanel();
      final int FRAME_WIDTH = 500;
      final int FRAME_HEIGHT = 200;
      setSize(FRAME_WIDTH, FRAME_HEIGHT);
      setTitle("Two Hand Comparison");
   }
   private void createButton()
   {
     button = new JButton("Deal");
     class AddChangeListener implements ActionListener
     {
          public void actionPerformed(ActionEvent event)
          {          
               h1 = new PokerHand(new Card(),new Card(),new Card(),new Card(),new Card());
               h2 = new PokerHand(new Card(),new Card(),new Card(),new Card(),new Card());

               hand1.setText(h1.display());
               hand2.setText(h2.display());
               score1.setText(" "+h1.getScore());
               score2.setText(" "+h2.getScore());
               if (h1.getScore()>h2.getScore())
                  victor.setText("Hand 1 Wins:\n"+h1.getInfo()+" beats "+h2.getInfo());
               else
                  victor.setText("Hand 2 Wins:\n"+h2.getInfo()+" beats "+h1.getInfo());
          }
      }

      ActionListener listener = new AddChangeListener();
      button.addActionListener(listener);

   }
   private void createPanel()
   {
      panel = new JPanel(new GridLayout(0,1) );
      JPanel displayPanel = new JPanel(new GridLayout(3,3));
      displayPanel.add(new JLabel(" "));
      displayPanel.add(new JLabel("Hand", JLabel.CENTER));
      displayPanel.add(new JLabel("Score", JLabel.CENTER));
      displayPanel.add(new JLabel("1", JLabel.CENTER));
      displayPanel.add(hand1);
      displayPanel.add(score1);
      displayPanel.add(new JLabel("2", JLabel.CENTER));
      displayPanel.add(hand2);
      displayPanel.add(score2);
      panel.add(displayPanel);

      panel.add(victor);
      subPanel = new JPanel();
      subPanel.add(button);
      panel.add(subPanel);
      add(panel);
   }

}

TwoHandViewer.java

package poker;

import javax.swing.JFrame;
public class TwoHandViewer
{
   public static void main(String[] args)
   {
      JFrame frame = new TwoHandFrame();

      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      frame.setVisible(true);
   }
}