Issue #230, June 2013

Debugging his last article's script and calculating straights in a
*Cribbage*
hand keep Dave busy coding this month, with punctuation graffiti included!

The *Cribbage* game programming continues with further expansion
of the subhand evaluation code. You'll recall that in a two-player game
of *Cribbage*, you're dealt six cards but have to put two into the
“crib”, a third hand that alternates between players.

The challenge is this: which four cards of the six leave you with the most points possible?

There's a secondary consideration, because you also want to avoid putting points in the crib when it's not yours, if you can help it, but for now, I'm going to stick with the six-choose-four challenge.

And a challenge it is, because cards are worth points based on whether they have the same rank (for example, 9S and 9C = 2 points for a pair); whether they add up to 15, with all face cards = 10 (for example, 7S and 8C = 15 = 2 points); whether all four cards have the same suit (for example, 3D, 7D, 9D, QD = 4 points); and whether three or four of the cards are in sequential rank order (for example, 9D, 10C and JS = 3 points), even if they aren't the same suit.

I wrapped up my last article with code that could figure out the six-choose-four combinations (it's a straight combinatorics problem—I knew that stuff I learned in college eventually would come in handy), then evaluate each four-card set for possible fifteens and pairs. With some debugging code added, the current output looks like this:

$ sh cribbage.sh Hand: 2C, 4S, 6S, 8C, 8H, 9C. Subhand 0: 2C 4S 6S 8C calc15() given ranks: 2 4 6 8 total 15-point value of that hand: 0 Subhand 1: 2C 4S 6S 8H calc15() given ranks: 2 4 6 8 total 15-point value of that hand: 0 Subhand 2: 2C 4S 6S 9C calc15() given ranks: 2 4 6 9 total 15-point value of that hand: 4 ...

As you can see, the third subhand is worth more than the first two. In fact, 2C + 4S + 9C and 6S + 9C are both fifteens, so it's worth four points. Not too bad.

Further down in the debugging output, subhands start to appear with the pair of eights:

Subhand 6: 2C 6S 8C 8H calc15() given ranks: 2 6 8 8 we've got a pair: 8 and 8 total 15-point value of that hand: 0

So at this point the code recognizes pairs, but the point accumulator isn't actually scoring them. That's not good.

Let's start by fixing that. The scoring code is getting pretty long, so I'll just share the two-card code, which is a bit simpler too:

for subhand in {0..5} do sum=0 for thecard in ${fourtwo[$subhand]} do sum=$(( $sum + ${c15[$thecard]} )) done if [ $sum -eq 15 ] ; then points=$(( $points + 2 )) fi # now let's look at pairs # remember: ${string:position:length} twocards=${fourtwo[$subhand]} card1=${twocards:0:1} card2=${twocards:2} if [ ${cr15[$card1]} = ${cr15[$card2]} ] ; then echo "we've got a pair: ${cr15[$card1]} and ${cr15[$card2]}" points=$(( $points + 2 )) fi done

Here's the line that fixed the scoring problem for pairs:

points=$(( $points + 2 ))

It's easy shell math, and something I hope you're using with some frequency.
In fact, `$( )` for subshells and `$((
))` for math equations that
alternatively could be handled by `eval` are good.

That single line fixes the problem, as demonstrated in the very first test run:

Hand: 3H, 3D, 4C, 8H, 9H, JH. Subhand 0: 3H 3D 4C 8H calc15() given ranks: 3 3 4 8 we've got a pair: 3 and 3 total point value of that hand: 6

How did I get six points? 3H + 3D is a pair (2 points), then 3H + 4C + 8H =
15 (2 points) and 3D + 4C + 8H = 15 (2 points). That's a pretty decent
little four-card *Cribbage* hand, actually.

What about when there are three cards that are the same? It turns out that
*Cribbage* has a very logical scoring system, and three of a kind are scored
as 3 * 2-card pairs, which makes sense. Here's an example to
illustrate:

Hand: 4C, 4D, 4H, 7D, 10H, JS. Subhand 0: 4C 4D 4H 7D calc15() given ranks: 4 4 4 7 we've got a pair: 4 and 4 we've got a pair: 4 and 4 we've got a pair: 4 and 4 total point value of that hand: 12

So 4C + 4D, 4C + 4H and 4D + 4H are the three pair and are worth six points. This subhand is really superb, however, because there also are a number of card combinations that add up to fifteen, totaling 12 points. Very good for four cards!

The piece that's missing with the scoring is straights. This is going to get a bit complicated, so stick with me.

There's already code in place that generates all three-card combinations that catches when three cards sum up to fifteen points, so that's easily tapped within a “for” loop to extract the three-card index values:

combo=${fourthree[$subhand]}

That's going to be set to “0 1 2”, “0 1 3” and so on.

The card's normalized rank (for example, J=10, Q=10) is set in the point
calculation function as the local array `$cardrank[]`, and the original rank
(J=11, Q=12 and so on) is in `$cardrankfull[]`. These
originally were `c15[]` and
`cr15[]`, but I renamed them to make their purpose a bit clearer in the
script.

With “combo” set to the card indices, the full rank of a specific card in the four-card subhand can be referenced like this:

${cardrankfull[${combo:0:1}]}

As Douglas Adams would say, don't panic. Let's unwrap it instead.

The reference `${combo:0:1}` is a string slice and
extracts a one-character-long substring starting at index 0. The second value in the combo array is
:2:1 and the third is :4:1. That's used directly, so it's akin to
`${cardrankfull[1]}`.

Put the three together and output the three ranks:

echo "testing card ranks ${cardrankfull[${combo:0:1}]} and ${cardrankfull[${combo:2:1}]} and ${cardrankfull[${combo:4:1}]}"

Testing the values is easy because the hand's already sorted
by lowest-to-highest rank. There's more notational complexity because I'm going
to use the `$(( ))` mathematical shortcut again, but here's the conditional
test to see if the three-card subset is in sequential rank order:

if [ $(( ${cardrankfull[${combo:0:1}]} + 1 )) -eq ${cardrankfull[${combo:2:1}]} -a $(( ${cardrankfull[${combo:2:1}]} + 1 )) -eq ${cardrankfull[${combo:4:1}]} ] ; then

I warned you, it was notationally complex and once the mathematics are
added,
the `-eq` for algebraic equals and
`-a` for the logical “AND” between two statements, well, it's pretty thick with
punctuation, to say the least.

The result (a subset to show it's working):

Subhand 13: 4H 5H 6D 6H Calc4cardValue() given original ranks: 4 5 6 6 combo set to 0 1 2 testing card ranks 4 and 5 and 6 yup, those three cards are a run for three! combo set to 0 1 3 testing card ranks 4 and 5 and 6 yup, those three cards are a run for three! combo set to 0 2 3 testing card ranks 4 and 6 and 6 combo set to 1 2 3 testing card ranks 5 and 6 and 6 total point value of that hand: 6

This calculation is correct, that both cards 1,2,3 and cards 1,2,4 are
runs, so it's worth twice 3-points. But, there's another bug
looming: the situation where all four cards are a four-card run. That's
worth four points, not six. But we'll have to figure out that bug fix
next month—I've already gone *way* long on this column.

Copyright © 1994 - 2018 Linux Journal. All rights reserved.