Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine if an Indian Rummy hand is a winning hand - Java

Tags:

I'm looking for an efficient solution to determine if a hand is a winning hand in Indian rummy. Indian rummy is similar to gin rummy in terms of melding. One could either meld a sequence (straight) of the same suit or meld a set of same value. Both sequences and sets should at least contain 3 cards. Unlike gin rummy, in Indian rummy a hand consists of 13 cards. A winning hand should contain at least two sequences and at least one of those sequences have to be a pure sequence. By pure, I mean the sequence should not be made with the help of a joker (wild card). The rest of the hand can consist of sequences and sets made with or without jokers. Note: Apart from the 2 jokers in a deck (52 + 2), there is also a random card from the deck used as a joker. For example, if 5 of spades was randomly picked as a joker, then the remaining three 5s of other suits in the deck can be used as a joker on top of the 2 regular jokers.

Here are a few examples of valid winning hands without the use of a joker:

  • A,K,Q,J(spades)|2,3,4(hearts)|2,2,2(spades,clubs,diamonds)|3,4,5(diamonds)
  • A,K,Q,J,10(spades)|4,5,6,7,8(clubs)|9,9,9(diamonds,clubs,spades)
  • A,K,Q,J,10,9,8,7,6,5(spades)|4,3,2(spades)

Here are a few examples of winning hands with the use of a joker. Let's assume 6(spades) was the joker randomly picked from the deck; so all the remaining 6s can be used as a joker.

  • A,K,Q,J(spades;pure sequence)|7,7,7(diamonds,clubs,spades)|3,3,6(diamonds,clubs,clubs; set with joker)|A,2,6(clubs,clubs,hearts)
  • A,2,3(hearts)|4,5,6(hearts)|7,7,7,7(spades,clubs,diamonds,hearts)|8,6,10,Joker(spades,diamonds,spades; sequence with jokers, 6 and a regular joker)

Here are a few examples of what is NOT a winning hand:

  • A,2,Joker(hearts)|4,5,Joker(hearts)|7,7,7,7(all suits)|9,9,9(clubs,diamonds,hearts) (This is not a valid hand because it does not contain a pure sequence)
  • A,2,3,4(hearts)|7,7,7(clubs,diamonds,hearts)|8,8,8(clubs,diamonds,hearts)|9,9,9(clubs,diamonds,hearts) (This is not valid because it doesn't contain a second sequence)

I hope that has explained what a winning hand is. The model below represents a card:

public class Card {

public final static int SPADES = 0,
        HEARTS = 1,
        DIAMONDS = 2,
        CLUBS = 3;

public final static int ACE = 1,
        JACK = 11,
        QUEEN = 12,
        KING = 13,
        JOKER = 0;

private final int suit;

private final int value;

public Card(int theValue, int theSuit) {
    value = theValue;
    suit = theSuit;
}

public int getSuit() {
    return suit;
}

public int getValue() {
    return value;
}

public String getSuitAsString() {
    switch ( suit ) {
        case SPADES:   return "Spades";
        case HEARTS:   return "Hearts";
        case DIAMONDS: return "Diamonds";
        case CLUBS:    return "Clubs";
        default:       return "??";
    }
}

public String getValueAsString() {
    switch ( value ) {
        case 1:   return "Ace";
        case 2:   return "2";
        case 3:   return "3";
        case 4:   return "4";
        case 5:   return "5";
        case 6:   return "6";
        case 7:   return "7";
        case 8:   return "8";
        case 9:   return "9";
        case 10:  return "10";
        case 11:  return "Jack";
        case 12:  return "Queen";
        case 13:  return "King";
        default:  return "JOKER";
    }
}

@Override
public String toString() {
    return getValueAsString().equals("JOKER") ? "JOKER" : getValueAsString() + "(" + getSuitAsString() + ")";
}

@Override
public boolean equals(Object card) {
    return suit == ((Card) card).getSuit() && value == ((Card) card).getValue();
}

}

I also wrote some functions to get the possible sequences and sets in my card. The argument (List) in the getSequences function is already sorted by suit and then by value. In case of the argument in the getSets function, cards are sorted by value alone.The value of second argument (min) in both the functions are 3.

private List<List<Card>> getSequences(List<Card> hand, int min) {
    List<List<Card>> sequences = new ArrayList<>();
    List<Card> sequence = new ArrayList<>();
    for(int i=1; i<hand.size(); i++) {
        if(hand.get(i).getSuit() == hand.get(i-1).getSuit() &&
                (hand.get(i).getValue() - hand.get(i-1).getValue()) == 1) {
            sequence.add(hand.get(i-1));
            if(hand.get(i).getValue() == 13) {
                int j = i;
                while(hand.get(j).getSuit() == hand.get(i).getSuit()) {
                    j--;
                    if(hand.get(j).getValue() == 1) {
                        sequence.add(hand.get(j));
                    }
                }
            }
            if(i == hand.size() -1) {
                sequence.add(hand.get(i));
                sequences.add(sequence);
            }
        } else {
            sequence.add(hand.get(i-1));
            if(sequence.size() >= min) {
                sequences.add(sequence);
            }
            sequence = new ArrayList<>();
        }
    }
    return sequences;
}

private List<List<Card>> getSets(List<Card> hand, int min) {
    List<List<Card>> sets = new ArrayList<>();
    List<Card> set = new ArrayList<>();
    for(int i=1; i<hand.size(); i++) {
        if(hand.get(i).getValue() != joker.getValue()) {
            if(hand.get(i).getValue() == hand.get(i-1).getValue()) {
                set.add(hand.get(i-1));
                if(i == hand.size() -1) {
                    set.add(hand.get(i));
                }
            } else {
                set.add(hand.get(i-1));
                if(set.size() >= min) {
                    sets.add(set);
                }
                set = new ArrayList<>();
            }
        }
    }
    return sets;
}

I don't think it's the most elegant way of finding sequences and sets. Therefore, I welcome any suggestions on how I can improve it. But what I really need help with is what do I do next? There could be overlaps between the sets and sequences. For example, in case of the following cards:

  • A,2,3(spades)|4,4,4(spades,clubs,hearts) My getSequences function would return A,2,3,4(spades) as a sequence. I should avoid including 4 of spades in my sequence for it to be used in the set of 4s instead.

Please advise on efficiently determining a winning hand.

P.S: At the time of determining a winning hand, the player will have 14 cards in the hand. After melding 13 cards, the fourteenth card will be discarded as finishing card.

like image 718
SerotoninChase Avatar asked Jul 07 '18 17:07

SerotoninChase


1 Answers

I have implemented a java version of Rummikub (a game with similar constraints).

My approach there was to give each card a hidden integer attribute (a prime number).

Then each valid meld can be uniquely represented as an integer number. The precise integer numbers that make valid melds can be calculated beforehand and put in a Set<Long> of course.

Checking whether a hand contains only valid melds is then reduced to checking whether a given long can be written as a product of a set of given numbers. (for which recursion and dynamic programming can be used)

Concrete example (1):

  • Ace of hearts => 2
  • Two of hearts => 3
  • Three of hearts => 5

Set<Long> validMelds = {30, .., ..}

If hand (value = 60) then we know it contains 2 valid melds.

concrete example (2)

  • 1 of clubs = 2
  • 2 of clubs = 3
  • 3 of clubs = 5
  • 4 of clubs = 7
  • 4 of hearts = 179
  • 4 of diamonds = 181

known valid melds = {30, 210, 226793, ..}

value of hand = 6803790

easy (recursive) algorithm:

  1. 6803790 is divisible by 30
  2. (6803790 / 30 = ) 226793 is divisible by 226793
  3. recursive algorithm concludes this is a valid branch

    alternative

  4. 6803790 is divisible by 210

  5. (6803790 / 210) = 32399 is not divisible by any valid meld-number
  6. recursive algorithm concludes the branch stops here

    If you need to be able to handle the situation in which parts of the hand-cards are not always part of a valid meld, you may wish to look into linear algebra.

like image 122
Joris Schellekens Avatar answered Oct 05 '22 11:10

Joris Schellekens