user12282981
user12282981

Reputation: 9

Classifying Poker Hands in Java

I am currently working on a CS project that classifies player's hands. I solved the first half of the project to print out the deck, shuffled deck and the hands of player1, player2 and remaining deck. The problem comes up when I have to evaluate the hands. My code has to somehow evaluate which classification the hands are, and print out whether player1 or player2 wins. I have three classes so far:

public class Card {

    static String[] card_suit = {"hearts", "diamonds", "clubs", "spades"};

    static int[] card_rank = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};// 11 is Jack, 12 is Queen, 13 is King and 14 is Ace 

    public int[] getRank() {
        return card_rank;
    }
    public String[] getSuit() {
        return card_suit;
    }
}
public class Driver {

    public static void main(String[] args) {
        Card card = new Card();
        Deck deck = new Deck();

        deck.getDeck();
        System.out.print("ORIGINAL DECK: ");
        deck.printDeck();
        deck.shuffleDeck();
        System.out.print("SHUFFLED DECK: ");
        deck.printDeck();

        System.out.println();


        System.out.print("PLAYER ONE: ");
        System.out.println(java.util.Arrays.toString(deck.playerOneHands()));
        System.out.print("PLAYER TWO: ");
        System.out.println(java.util.Arrays.toString(deck.playerTwoHands()));
        System.out.print("REMAINING DECK: ");
        System.out.println(java.util.Arrays.toString(deck.remainingDeckCards()));               
        }

}
import java.util.Arrays;
import java.util.Collections;

public class Deck extends Card {

    Card card = new Card();

    private String[] deck_card = new String[52];

    public String[] getDeck() {
        int i = 0;
        for(int s = 0; s < 4; s++) {
            for(int r = 0; r < 13; r++) {
                deck_card[i]=(card_suit[s] + " of " + card_rank[r]);
                i++;
                }

            }
        return deck_card;
    }

    public void printDeck() {
        System.out.println (java.util.Arrays.toString (deck_card));
    }

    public void shuffleDeck() {
        Collections.shuffle(Arrays.asList(deck_card));
    }

    public String[] playerOneHands() {
        String [] firsthand = new String[5];
        for(int a = 0; a < 5; a++) {
            firsthand[a] = deck_card[a];
        }
        return firsthand;
    }

    public String[] playerTwoHands() {
        String[] secondhand = new String[5];
        for(int a = 0; a < 5; a++) {
            secondhand[a] = deck_card[a+5];
        }
        return secondhand;
    }

    public String[] remainingDeckCards() {
        String[] remainDeck = new String[42];
        for(int a = 0; a < 42; a++){
            remainDeck[a] = deck_card[a+10];
        }
        return remainDeck;
    }

}

What I thought it would work is because the Deck class extends from the Card class, I can use the getRank method to compare each hand, but I am not sure how to construct the conditionals.

Any help is greatly appreciated. Thanks.

Upvotes: 0

Views: 1696

Answers (3)

Maurice Perry
Maurice Perry

Reputation: 9650

It seems that you class Card only has static fields; I would change it so that an instance of Card would represent a single card from a Deck. I would also make the suites an enum type. You can also add integer constants for the figures and aces. The class can implement Comparable<Card>:

public class Card implements Comparable<Card> {
    public enum Suite {CLUBS, DIAMONDS, HEARTS, SPADES};

    public static final int JACK = 11;
    public static final int QUEEN = 12;
    public static final int KING = 13;
    public static final int ACE = 14;

    public final Suite suite;
    public final int rank;

    public Card(Suite suite, int rank) {
        if (suite == null) {
            throw new IllegalArgumentException("Suite cannot be null");
        }
        if (rank < 2 || rank > 14) {
            throw new IllegalArgumentException(
                    "Value must be between 2 and 14");
        }
        this.suite = suite;
        this.rank = rank;
    }

    public Suite getSuite() {
        return suite;
    }

    public int getRank() {
        return rank;
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        if (rank >= 2 && rank <= 10) {
            buf.append(rank);
        } else {
            switch (rank) {
                case JACK:
                    buf.append("jack");
                    break;
                case QUEEN:
                    buf.append("queen");
                    break;
                case KING:
                    buf.append("king");
                    break;
                case ACE:
                    buf.append("ace");
                    break;
            }
        }
        buf.append(" of ");
        buf.append(suite.toString().toLowerCase());
        return buf.toString();
    }

    @Override
    public int compareTo(Card other) {
        if (rank > other.rank) {
            return 1;
        } else if (rank < other.rank) {
            return -1;
        } else {
            return suite.compareTo(other.suite);
        }
    }
}

Note that you could also have two subclasses of Card: one for the numbers and one for the figures.

The Deck is a collection of 52 cards. It is initialised by adding each card to a list. One can shuffle a deck, or take a card from the deck:

public class Deck {
    private final List<Card> cards = new ArrayList<>();

    public Deck() {
        for (Card.Suite suite: Card.Suite.values()) {
            for (int i = 2; i <= 14; ++i) {
                cards.add(new Card(suite,i));
            }
        }
    }

    public void shuffle() {
        Collections.shuffle(cards);
    }

    public boolean isEmpty() {
        return cards.isEmpty();
    }

    public Card take() {
        if (cards.isEmpty()) {
            throw new IllegalStateException("Deck is empty");
        }
        return cards.remove(0);
    }
}

You can take shuffle and take 5 cards from the deck like this:

    Deck deck = new Deck();
    deck.shuffle();
    for (int i = 0; i < 5; ++i) {
        Card card = deck.take();
        System.out.println(card);
    }

Now a Hand is a set of five cards taken from a Deck. We can declare Hand as implementing Comparable<Hand> so that we can know which of two hands have the highest value:

public class Hand implements Comparable<Hand> {
    private final Card[] cards = new Card[5];

    public Hand(Deck deck) {
        for (int i = 0; i < 5; ++i) {
            cards[i] = deck.take();
        }
        Arrays.sort(cards);
    }

    @Override
    public int compareTo(Hand other) {
        ...
    }
}

Now here comes the fun part: you must identify the hand type as one of the following (enum type):

public enum HandType {
    SINGLE, PAIR, TWO_PAIRS, THREE, STRAIGHT, FLUSH, FULL_HOUSE, FOUR,
    STRAIGHT_FLUSH, ROYAL_FLUSH;
}

Note that the constants are arranged from the lowest to the highest. In addition, the cards must be arranges so that in case of a tie, you can compare the cards to identify the winner.

I would suggest you make groups of cards of same rank; in the case where you have five different groups, it still can be a flush or a straight.

Another approach would consist in declaring a subclass of Hand for each HandType, but I don't think you would gain much by doing this.

public class Hand implements Comparable<Hand> {
    public enum HandType {
        SINGLE, PAIR, TWO_PAIRS, THREE, STRAIGHT, FLUSH, FULL_HOUSE, FOUR,
        STRAIGHT_FLUSH, ROYAL_FLUSH;
    }

    private final Card[] cards = new Card[5];
    private final int[] groupSize;
    private final HandType type;

    public Hand(Deck deck) {
        for (int i = 0; i < 5; ++i) {
            cards[i] = deck.take();
        }
        groupSize = group(cards);
        type = identifyType(groupSize, cards);
    }

    @Override
    public int compareTo(Hand other) {
        int r = type.compareTo(other.type);
        if (r != 0) {
            return r;
        }
        for (int i = cards.length; --i >= 0; ) {
            int r1 = cards[i].getRank();
            int r2 = other.cards[i].getRank();
            if (r1 < r2) {
                return -1;
            } else if (r1 > r2) {
                return 1;
            }
        }
        return 0;
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(type);
        buf.append(": ");
        buf.append(cards[0]);
        for (int i = 1; i < 5; ++i) {
            buf.append(", ");
            buf.append(cards[i]);
        }
        return buf.toString();
    }

    private static int[] group(Card[] cards) {
        Arrays.sort(cards);
        List<List<Card>> groups = new ArrayList<>();
        int val = -1; // invalid rank
        List<Card> currentGroup = null;
        for (Card card: cards) {
            if (val == card.getRank()) {
                currentGroup.add(card);
            } else {
                if (currentGroup != null) {
                    groups.add(currentGroup);
                }
                currentGroup = new ArrayList<>();
                currentGroup.add(card);
                val = card.getRank();
            }
        }
        if (currentGroup != null) {
            groups.add(currentGroup);
        }
        // identify groups of cards of same value
        // sort groups by size and highest card
        Collections.sort(groups, (List<Card> group1, List<Card> group2) -> {
            int s1 = group1.size();
            int s2 = group2.size();
            if (s1 < s2) {
                return -1;
            } else if (s1 > s2) {
                return 1;
            } else {
                return group1.get(s1-1).compareTo(group2.get(s2-1));
            }
        });
        int[] groupSize = new int[groups.size()];
        int g = 0;
        int i = 0;
        for (List<Card> group: groups) {
            groupSize[g++] = group.size();
            for (Card card: group) {
                cards[i++] = card;
            }
        }
        assert sum(groupSize) == 5;
        return groupSize;
    }

    private static HandType identifyType(int[] groupSize, Card[] cards) {
        switch (groupSize.length) {
            case 2:
                // can be a full house or four cards
                if (groupSize[0] == 1) {
                    return HandType.FOUR;
                } else if (groupSize[0] == 2) {
                    return HandType.FULL_HOUSE;
                } else {
                    assert false;
                    return null;
                }
            case 3:
                if (groupSize[0] == 1) {
                    // three cards or double pair
                    if (groupSize[1] == 1) {
                        return HandType.THREE;
                    } else {
                        assert groupSize[1] == 2 && groupSize[2] == 2;
                        return HandType.TWO_PAIRS;
                    }
                } else {
                    assert false;
                    return null;
                }
            case 4:
                // one pair
                return HandType.PAIR;
            case 5:
                // all different values: check for flush
                Card prev = cards[0];
                boolean sameSuite = true;
                boolean straight = true;
                for (int i = 1; i < 5; ++i) {
                    Card card = cards[i];
                    straight &= card.getRank() == prev.getRank()+1;
                    sameSuite &= card.getSuite() == prev.getSuite();
                }
                if (sameSuite) {
                    if (straight) {
                        if (cards[4].getRank() == Card.ACE) {
                            return HandType.ROYAL_FLUSH;
                        }
                        return HandType.STRAIGHT_FLUSH;
                    } else {
                        return HandType.FLUSH;
                    }
                } else {
                    if (straight) {
                        return HandType.STRAIGHT;
                    } else {
                        return HandType.SINGLE;
                    }
                }

            default:
                assert false;
                return null;
        }
    }

    private static int sum(int[] groupSize) {
        int sum = 0;
        for (int s: groupSize) {
            sum += s;
        }
        return sum;
    }
}

Upvotes: 0

Bohemian
Bohemian

Reputation: 425003

Without wanting to do your homework for you...

This is a problem:

class Deck extends Card

A deck isn’t a subtype of a card. A deck has cards, so:

class Deck {
    List<Card> cards;
}

is a better choice.

Also, the following code does nothing to the deck:

public void shuffleDeck() {
    Collections.shuffle(Arrays.asList(deck_card));
}

It shuffles a copy of the deck, leaving the deck untouched.

Also, you shouldn’t be building strings in a loop. Instead, implement (override) a toString() method on Card and Deck.

Also, make suit an enum.

Also, delete card_rank entirely - it serves no purpose. Instead, add a int rank; field to Card, or better make rank an enum.

Fix these things first, then re-attack the problem by writing a method that is passed a Hand (a new class) that has a List and a method that returns a HandType (another enum) by evaluating if the hand is a straight flush, else four of a kind, else ... all the way down to high card - highest to lowest.

Upvotes: 1

Kedar Tokekar
Kedar Tokekar

Reputation: 418

  1. For modelling the game, first identify entities like Card, Deck etc. (which mostly you have done). I would add few more like Player, Evaluator(explained below) etc.
  2. Rank and Suit are NOT string/ int but they are predefined (not going to change in life time of game) possible variations in Cards. Always use domain vocabulary for best model. Each Card belongs to one Suit and one Rank. (think of making Rank and Suit as enums, this will avoid unknown values breaking the code at run time.
  3. Not giving set method for suite and rank in Card is essential (they form an identity of Card in combination)
  4. Full Deck (at initialization) is formed by cross product of Suit and Rank. Meaning Deck has (contains) multiple cards. Remember Card can be alive outside of Deck (when in players hand) as well, hence its not composition. Inheriting Deck from Card is absolutely wrong. It translates to statement Deck is a kind of Card, which is not correct. Deck will have collection of Cards. Using inheritance, will lead to violation of Liskov's Substitution Principle (One of the SOLID).
  5. To model Deck, consider the fact that Deck does not contain duplicate cards, Deck does not change its order once formed (unless shuffled). This being tricky selection between Set and List, I would go for List with added programmatic constraint for avoiding duplicates (needs to be done only when initialization).
  6. But instead of Modelling Deck as java collection, it will be best to let class Deck contain java collection of suitable choice and Deck class work as wrapper by defining required API (from domain perspective) like shuffle, getTopCard() etc. This is called as Object Adapter design pattern. This makes our design platform (implementation) independent.
  7. You need to model few more classes like Player holds CardInHand etc.
  8. About evaluating Cards in hand, its better to model it as separate class as its different concern and rules can change independent of other classes.
  9. Poker game is best assignment to learn Object Oriented Programming.

Upvotes: 1

Related Questions