hv,
You have good points. I just find it odd that with everything under the sun, there isn't already one out there. With regards to your point on clarity and explanation - let me take this opportunity now to do so.
Precalculating Scores
When calculating points for sums of 15 you do not need to consider the suit of the card. You can also consider 10, Jack, Queen, and King as the same since they all have the value of 10. This means that there are only 1993 unique hands necessary to calculate any cribbage hand for 15s. I used a brute-force approach of summing all combinations of cards in group sizes of 2-5.
I then realized that if there was no card present that had a value of 10 (T/J/Q/K), I could also safely determine the points resulting from 2/3/4 of a kind. The reason the code works may not be obvious so I have included comments.
my %card;
++$card{$_} for @hand;
$score += $_ * ($_ - 1) for values %card;
# 1 * 0 = 0 (0 points for 1 of a kind)
# 2 * 1 = 2 (2 points for 2 of a kind)
# 3 * 2 = 6 (6 points for 3 of a kind)
# 4 * 3 = 12 (12 points for 4 of a kind)
If you have more than 1 2 of a kind or 3/4 of a kind then you can't possibly have a flush. I added a flag if I knew it was safe not to check. You also can't possibly have right-jack if no card has a value of 10 - another flag.
I could also determine the value of any straights if no card had a value of 10. Since there are only 10 possible sequences of cards that can score points for a run, I avoided using loops. I am just going to explain the algorithm and hope that the code is clear as a result.
To be a run, each card must have a difference of 1 with its adjacent card
Sort the unique values in your hand
Determine the number of unique values
Check for possible runs of that size in descending order - end if none
Multiply the length of the run by the product of the count of cards at each position in the run
The hand, the flags, and the score are outputed in 8 bytes. 5 for the hand, 1 for the flags, and 2 for the score.
Calculate Total Score
The first step in calculating the total score is to convert the argument into the same format used to precalulate. We then lookup that hand and get back a 3 element array.
0 = calculated score so far
1 = flag indicating if any card has a value of 10
2 = flag indicating if it is necessary to check for flush
We know that we only have to check for 2/3/4 of a kind, straights, and right-jack if a card with the value of 10 is present. The method for determining 2/3/4 of a kind and straights has already been covered. To determine if we have right-jack:
# Find suits of any jack in the first 4 cards
my %jack = map { $_ => 1 } substr($str, 0, 8) =~ /(?<=J)(.)/g;
# Determine if the suit of the 5th card matches
++$score if $jack{ substr($str, -1, 1) };
Finally, we address potential flushes if necessary.
# Get the unique list of suits in the first 4 cards
my %suit = map {$_ => undef} unpack('xAxAxAxA', $str);
# If all the same, count 1 per card
if (keys %suit == 1) {
$score += 4;
# If cut-card matches, add 1 for that too
$score += 1 if substr($str, 1, 1) eq substr($str, -1, 1);
}
I am not sure how fast this is compared to other approaches but I think it is a great start for someone interested in working on a Games::Cards::Cribbage.