Do we have a Pair?
It seems such a simple problem: find the winning poker hand ignoring all other hands except a pair and high-card. The kata is starting to take shape, but making sure to cover all the cases (or at least as many as I think of!) takes some work; even just considering these two hands.
To briefly recap, the basic structure looks like this:
PokerGame contains one or more PokerRound's. PokerRound contains the players and the set of cards each player is dealt (hole cards and all the community cards). The result of the round is delegated to the RoundWinners class, in which each player's hand is ranked by the RankedHand class. RoundWinners then picks the highest ranked hand(s), where hands with the same rank are then separated by the JointHandResolver.
The RankedHand class is the focus of this effort, as you can see, the ranking is, shall we say, not entirely robust:
if (cards.equals(Cards.from("2d", "2c"))) return new RankedHand(player, 1 /* pair */, cards);
if (cards.equals(Cards.from("Ah", "Kc"))) return new RankedHand(player, 0 /* high card */, cards);
return new RankedHand(player, 0, cards);
The current thinking is to treat the ranking as a set of filters. Conceptually, the filters start out really specific - a royal flush - then if the cards don't get caught by the filter, they become less and less specific, until we have the catch-all 'high card' filter at the bottom.
To begin with, we only have the two: Pair and High card. It's a simple interface for now:
public interface HandIdentifier {
public RankedHand accept(String player, List<Card> cards);
}
Taking the easy one first, High card, it's implementation is just the last line from the old method (as above).
The PairIdentifier implementation initially becomes the if condition from the old method. If it's not the pair of twos, null is returned.
A (failing) test is added to check another pair. So now this implementation needs so serious work.
First, we definitely need to group by the card's face value:
Map<Integer, Collection<Card>> groupedByValue = Iterables.groupBy(cards, new CardNumericValue());
Now, do any of the groupings contain a pair - that is, do any of the groupedByValue values have a collection of size 2?
Collection<Collection<Card>> pairs = Iterables.where(groupedByValue.values(), new ContainsAtLeastOnePair());
if (pairs.size() != 1) return null;
ContainsAtLeastOnePair simply performs a size() == 2 check on the Collection<Card>. One point of note is that it is a test "equals 2" and "not equals 1" so this will only accept one pair and not two pairs or three-of-a-kind. I may have to re-visit this logic later on and so a note is made in the issues file.
Now we have found the pair, we want to return the pair as the first two cards, followed by the remaining cards in face-value order:
List<Card> rankedCards = Iterables.sort(
Iterables.where(cards, new IsNotIn(pairs.iterator().next())),
new FaceValueOrder());
rankedCards.addAll(0, pairs.iterator().next());
The tests now all pass. Phew. Now bad, but as I alluded to at the start, this doesn't mean I'm done with this one. The next bit of work is going to be adding some additional tests for pairs - especially the cases where two hands have pairs and we have to consider the value of the pair and then kickers.
However, that's for another time. I do feel a small sense of achievement at this stage, and I can almost see the point at where I'll just be left filling out the other HandIdentifier implementations. Just.
