Work the Shell

Summing Up Points

Dave Taylor

Issue #229, May 2013

Fifteens. Why are they so important to Cribbage, and how do you calculate them? And what about pairs? Read on as Dave continues to step through the construction of a Cribbage game written as a shell script.

We're still building out the Cribbage game with the six-choose-four challenge that's at the very beginning of the game when the players in a two-player game discard two of their six cards into the “crib”, a third hand that alternates between players. Think of it like playing draw poker, except when the players discard their cards, the dealer could also pick them up and play them as a second hand. That'd be weird, but interesting, wouldn't it? Hmmm....

No, let's stay focused!

In my last article, we left the script at a point where it was able to pull out all two-card, three-card and four-card combinations of cards to ascertain which of them add up to 15 or otherwise offer point opportunities. Now let's jump in and actually calculate values and see what happens.

Specifically, here's where we left off:

$ cribbage.sh
Hand: AD, AS, 2D, 3C, 5C, KC.
Subhand 0:  AD  AS  2D  3C
  total 15-point value of that hand: 0
Subhand 4:  AD  AS  3C  KC
  total 15-point value of that hand: 2
Subhand 14:  2D  3C  5C  KC
  total 15-point value of that hand: 4

If we were looking only for combinations that add up to 15, the script has identified the best possible combination—that of a 2, 3, 5 and king. The problem is, runs are worth lots of points too, and the run of AD, AS, 2D, 3C is worth AD+2+3=3, AS+2+3=3, and the pair of aces adds another 2, so that's eight points, far more than the two fifteens. But, we'll get to that later.

For now, let's look at how the fifteens are calculated by just examining the two-card case:

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
  done

Remember that the $fourtwo array is an enumerated list of all possible two-card combinations out of four (for example, 1+2, 1+3, 1+4, 2+3 and so on). The $points variable accumulates how many fifteens are found as the function tests two-card, three-card and, finally, the one possible four-card combination, ending with the echo statement:

echo "  total 15-point value of that hand: $points"

We'll tweak that as the function expands in capabilities, but for now, that's useful.

Calculating Pairs

The next step is to calculate pairs. It turns out that we don't need any additional code to calculate the value of three of a kind or four of a kind (though in years of playing Cribbage, I have never had four of a kind in my hand!), because they are unto themselves combinations of pairs. That is, if a player has 3D, 3S and 3H, it's worth six points: two points for 3D+3S, two points for 3D+3H and two points for 3S+3H—handy, really!

Because the calc15() function (shown in my last article) already offers a lot of the infrastructure we'll need, we're just going to expand on it, even though it technically won't be calculating only 15-point values any more. That's okay; we'll end up renaming the function, but for now, let's just code.

To extract pairs, the loop is slightly different:

twocards=${fourtwo[$subhand]}
card1=${twocards:0:1}
card2=${twocards:2}

Placing this within the snippet for subhand in {0..5} will let us test all two-card combinations of the four-card hand given to the function.

There's a bit of fancy variable referencing here too. It turns out we can extract substrings at a variable reference by using the following notation:

${string:position:length}

In the first instance, card1 will end up being the first value in the twocardsvariable, which itself is extracted from the fourtwo[] array. Its format is “X Y”, so the second reference needs to start at 2. Being lazy, we just grab the rest of the string, which means we can omit the :length parameter.

Why have the interim variable twocards? Because the shell can figure out only a certain level of complexity, and writing something like:

${{fourtwo[$subhand]}:0:1}

just gives me a headache.

The next step is simply to compare the two cards and see if they're the same rank:

if [ ${c15[$card1]} = ${c15[$card2]} ] ; then
   echo "we've got a pair: ${c15[$card1]} and ${c15[$card2]}"
 fi

This all looks good, but there's a glaring bug in the code, as is immediately obvious with some debugging info:

Subhand 14:  9S  10H  JH  KD
calc15() given ranks: 9 10 10 10
PAIRS: testing two cards 0 and 1 from 0 1
PAIRS: testing two cards 0 and 2 from 0 2
PAIRS: testing two cards 0 and 3 from 0 3
PAIRS: testing two cards 1 and 2 from 1 2
we've got a pair: 10 and 10
PAIRS: testing two cards 1 and 3 from 1 3
we've got a pair: 10 and 10
PAIRS: testing two cards 2 and 3 from 2 3
we've got a pair: 10 and 10

The problem is that calc15() is being given the ranks of the cards after they've been scrubbed to just point values, so a 10H, JH and KD all look like they are point value 10. That works great for calculating fifteens, but a 10H+KD is most assuredly not a valid pair.

The fix is easy. We can just have calc15() get both the four normalized ranks and the four original ranks as parameters. Recall that in the function handvalue4() ranks are normalized through code blocks like this:

# now fix rank to normalize for value=10
case $r1 in
  11|12|13) nr1=10 ;;
  *) nr1=$r1 ;;
esac

So $r1 already is the proper rank of the first card (that is, 1–13), and $nr1 is the normalized rank (where a 10 and a K have value 10). Then, invoking calc15() is just a tiny bit more complex:

calc15 $nr1 $nr2 $nr3 $nr4 $r1 $r2 $r3 $r4

For notational convenience, let's also grab the 5th–8th parameters and reassign them into a local array $cr15[] like this:

cr15[0]=$5; cr15[1]=$6; cr15[2]=$7; cr15[3]=$8

Now the fix to calculate proper pairs is quite easy:

Subhand 14:  10S  JS  QC  QD
calc15() given ranks: 10 10 10 10
PAIRS: testing two cards 0 and 1 from 0 1
PAIRS: testing two cards 0 and 2 from 0 2
PAIRS: testing two cards 0 and 3 from 0 3
PAIRS: testing two cards 1 and 2 from 1 2
PAIRS: testing two cards 1 and 3 from 1 3
PAIRS: testing two cards 2 and 3 from 2 3
we've got a pair: 12 and 12

And, I'm out of space for this article. In my next article, we'll continue expanding on the pair calculations and add the final piece we need before we can actually clean it up and pick the best four out of six cards: testing for a flush, the situation where all four cards are of the same suit.

Dave Taylor has been hacking shell scripts for more than 30 years. Really. He's the author of the popular Wicked Cool Shell Scripts and can be found on Twitter as @DaveTaylor and more generally at www.DaveTaylorOnline.com.