patternjavaMinor
Determining winning hand in poker
Viewed 0 times
winningpokerhanddetermining
Problem
After having reviewed this question, I recalled that I haven't solved Project Euler's problem 54. So I did it, however, I fount out that implementing the exact rules is rather tricky.
So I implemented just the necessary part. It works, problem solved, so let's call my code working. However, there are pairs of cards (not present in the input file), which leads to an exception. I'm not asking for a solution to this, I'd like to get the relevant parts reviewed and then I'll do it myself.
Feel free to ignore slightly deviating coding conventions and lacking javadoc (I hope it's commented enough to be understandable). I'm mostly interested in simplifications as it has got rather long. All classes are nested for simplicity.
Upon request I extracted the classes into separate files. As repeating all the imports would make it considerably longer, I'm presenting the header section just once.
(header)
Suit
```
@RequiredArgsConstructor @Getter enum Suit {
SPADES('S'),
HEARTS('H'),
CLUBS('C'
So I implemented just the necessary part. It works, problem solved, so let's call my code working. However, there are pairs of cards (not present in the input file), which leads to an exception. I'm not asking for a solution to this, I'd like to get the relevant parts reviewed and then I'll do it myself.
Feel free to ignore slightly deviating coding conventions and lacking javadoc (I hope it's commented enough to be understandable). I'm mostly interested in simplifications as it has got rather long. All classes are nested for simplicity.
Upon request I extracted the classes into separate files. As repeating all the imports would make it considerably longer, I'm presenting the header section just once.
(header)
package maaartin.euler.e0.e05;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.EnumSet;
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.EnumMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import maaartin.euler.EulerUtils;EulerUtils is a helper class for reading the input (given E054.class, it reads the file E054.txt, which is my local copy of poker.txt).Suit
```
@RequiredArgsConstructor @Getter enum Suit {
SPADES('S'),
HEARTS('H'),
CLUBS('C'
Solution
A new encoding that eliminates "ReorderedCards"
I recall from a previous question of yours that you packed a 16x16 puzzle into a single long. So I know you'll appreciate this encoding.
Using a single long, you can encode all of the cards so that you can compare the two hands using the long without any other histograms, "reordered cards", etc. The encoding is:
This is the basic form of what I called
Creating the handInfo
For each card, if it is the first card of that rank you add a bit into the "single cards". If it isn't the first card of that rank, you shift its bit left by 13. I eliminated the suit and rank histograms and added an
Comparing hands
Comparing hands becomes much simpler:
Determining the HandValue
Instead of using a rank histogram, the handInfo can do the same thing by using
```
@RequiredArgsConstructor enum HandValue implements Predicate {
HIGH_CARD {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return Long.bitCount(handInfo & Hand.SINGLE_MASK) == 5;
}
},
PAIR {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.PAIR_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.SINGLE_MASK) == 3);
}
},
TWO_PAIRS {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.PAIR_MASK) == 2);
}
},
THREE_OF_A_KIND {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.TRIO_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.SINGLE_MASK) == 2);
}
},
STRAIGHT {
@Override public boolean apply(Hand input) {
return input.isStraight();
}
},
FLUSH {
@Override public boolean apply(Hand input) {
return input.isFlush();
}
},
FULL_HOUSE {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.TRIO_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.PAIR_MASK) == 1);
}
},
FOUR_OF_A_KIND {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return Long.bitCount(handInfo & Hand.QUAD_MASK) == 1;
}
},
STRAIGHT_FLUSH {
@Override public boolean app
I recall from a previous question of yours that you packed a 16x16 puzzle into a single long. So I know you'll appreciate this encoding.
Using a single long, you can encode all of the cards so that you can compare the two hands using the long without any other histograms, "reordered cards", etc. The encoding is:
Bits 0-12 Single cards of rank 2..Ace
Bits 13-25 Pairs of rank 2..Ace
Bits 26-38 Triples of rank 2..Ace
Bits 39-51 Quads of rank 2..Ace
For example, a single 2 would set bit 0.
A pair of 2's would set bit 13 and clear bit 0.
A threesome of 2's would set bit 26 and clear bits 0 and 13.
The hand "AAA88" would have bits 38 (three Aces) and 19 (two eights) set.
This is the basic form of what I called
handInfo. This form can be used to do a comparison where you used to use a list of reordered cards. There is a more advanced encoding that I did not code up, but which can replace the whole HandValue class as well. I'll explain that encoding later on.Creating the handInfo
For each card, if it is the first card of that rank you add a bit into the "single cards". If it isn't the first card of that rank, you shift its bit left by 13. I eliminated the suit and rank histograms and added an
isFlush boolean as well:// These are masks of 13 cards each.
public static final long SINGLE_MASK = (1L {
Hand(Iterable cards) {
assert Iterables.size(cards) == CARDS_IN_HAND;
this.cards = ImmutableSortedSet.copyOf(cards);
checkArgument(this.cards.size() == 5, cards);
int suitBits = 0;
long info = 0;
for (final Card c : this.cards) {
final int rank = c.rank().value() - 2;
long rankBits = info & (RANK_MASK >>= Long.numberOfTrailingZeros(singleCards);
isStraight = (singleCards == STRAIGHT_VAL);
isFlush = (Integer.bitCount(suitBits) == 1);
handInfo = info;
}Comparing hands
Comparing hands becomes much simpler:
/**
* Returns a positive number if {@code this} is stronger than {@code other},
* a negative number if it's weaker,
* and 0 if they're equally strong.
*/
@Override public int compareTo(Hand other) {
final HandValue myHandValue = toHandValue();
final HandValue otherHandValue = other.toHandValue();
int result = myHandValue.compareTo(otherHandValue);
if (result!=0) return result;
assert myHandValue == otherHandValue;
return Long.compare(handInfo, other.handInfo());
}Determining the HandValue
Instead of using a rank histogram, the handInfo can do the same thing by using
Long.bitCount().```
@RequiredArgsConstructor enum HandValue implements Predicate {
HIGH_CARD {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return Long.bitCount(handInfo & Hand.SINGLE_MASK) == 5;
}
},
PAIR {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.PAIR_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.SINGLE_MASK) == 3);
}
},
TWO_PAIRS {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.PAIR_MASK) == 2);
}
},
THREE_OF_A_KIND {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.TRIO_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.SINGLE_MASK) == 2);
}
},
STRAIGHT {
@Override public boolean apply(Hand input) {
return input.isStraight();
}
},
FLUSH {
@Override public boolean apply(Hand input) {
return input.isFlush();
}
},
FULL_HOUSE {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.TRIO_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.PAIR_MASK) == 1);
}
},
FOUR_OF_A_KIND {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return Long.bitCount(handInfo & Hand.QUAD_MASK) == 1;
}
},
STRAIGHT_FLUSH {
@Override public boolean app
Code Snippets
// These are masks of 13 cards each.
public static final long SINGLE_MASK = (1L<<13) - (1L<<0);
public static final long PAIR_MASK = (1L<<26) - (1L<<13);
public static final long TRIO_MASK = (1L<<39) - (1L<<26);
public static final long QUAD_MASK = (1L<<52) - (1L<<39);
// This is a mask for one particular rank (4 bits).
public static final long RANK_MASK = (1L<<0) | (1L<<13) |
(1L<<26) | (1L<<39);
// This is used to detect a straight.
private static final long STRAIGHT_VAL = (1L<<5) - (1L<<0);
// The descriptor of the hand which can be used for comparisons.
private final long handInfo;
@Getter @EqualsAndHashCode(of="cards") class Hand implements Comparable<Hand> {
Hand(Iterable<Card> cards) {
assert Iterables.size(cards) == CARDS_IN_HAND;
this.cards = ImmutableSortedSet.copyOf(cards);
checkArgument(this.cards.size() == 5, cards);
int suitBits = 0;
long info = 0;
for (final Card c : this.cards) {
final int rank = c.rank().value() - 2;
long rankBits = info & (RANK_MASK << rank);
if (rankBits == 0) {
// First card of this rank, add it to singles.
rankBits = 1L << rank;
} else {
// Already have card of this rank, shift it to
// next count.
rankBits |= (rankBits << 13);
}
info ^= rankBits;
suitBits |= (1 << (c.suit().symbol() - 'A'));
}
// Determine straight and flush.
long singleCards = info & SINGLE_MASK;
singleCards >>>= Long.numberOfTrailingZeros(singleCards);
isStraight = (singleCards == STRAIGHT_VAL);
isFlush = (Integer.bitCount(suitBits) == 1);
handInfo = info;
}/**
* Returns a positive number if {@code this} is stronger than {@code other},
* a negative number if it's weaker,
* and 0 if they're equally strong.
*/
@Override public int compareTo(Hand other) {
final HandValue myHandValue = toHandValue();
final HandValue otherHandValue = other.toHandValue();
int result = myHandValue.compareTo(otherHandValue);
if (result!=0) return result;
assert myHandValue == otherHandValue;
return Long.compare(handInfo, other.handInfo());
}@RequiredArgsConstructor enum HandValue implements Predicate<Hand> {
HIGH_CARD {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return Long.bitCount(handInfo & Hand.SINGLE_MASK) == 5;
}
},
PAIR {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.PAIR_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.SINGLE_MASK) == 3);
}
},
TWO_PAIRS {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.PAIR_MASK) == 2);
}
},
THREE_OF_A_KIND {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.TRIO_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.SINGLE_MASK) == 2);
}
},
STRAIGHT {
@Override public boolean apply(Hand input) {
return input.isStraight();
}
},
FLUSH {
@Override public boolean apply(Hand input) {
return input.isFlush();
}
},
FULL_HOUSE {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return (Long.bitCount(handInfo & Hand.TRIO_MASK) == 1) &&
(Long.bitCount(handInfo & Hand.PAIR_MASK) == 1);
}
},
FOUR_OF_A_KIND {
@Override public boolean apply(Hand input) {
long handInfo = input.handInfo();
return Long.bitCount(handInfo & Hand.QUAD_MASK) == 1;
}
},
STRAIGHT_FLUSH {
@Override public boolean apply(Hand input) {
return input.isStraight() && input.isFlush();
}
},
;
}// Ace-2-3-4-5
private static final long WHEEL = 0x100fL;
if ((handInfo & SINGLE_MASK) == WHEEL) {
isStraight = true;
// Clear out all cards, or do handInfo ^= 0x1000 to clear just the ace.
// This keeps the comparison correct with other straights.
handInfo ^= WHEEL;
}Context
StackExchange Code Review Q#87991, answer score: 5
Revisions (0)
No revisions yet.