LJ Archive CD

Work the Shell

Cribbage: Calculating Hand Value Redux

Dave Taylor

Issue #228, April 2013

Six choose four? Four choose two? Dave continues building the Cribbage game, starting to dig in to the code needed to calculate point values of subhands as a way to ascertain which cards are best to discard to maximize hand value.

Last month, I erroneously titled my column “Calculating Hand Value” and then proceeded to spend 1,200 words actually talking about how to figure out all the subhands for a given hand, pointing out that there are 15 four-card subhands out of a six-card hand.

In two-player Cribbage, I explained, each player is dealt six cards but can keep only four. The four cards discarded between the players is known as the “crib”, and it is an extra hand that the dealer counts as his or her own after play is complete. Players alternate dealing, so it balances out, but when you're down to the wire, that extra few points from a randomly assembled hand can be important!

Still, the first substantial challenge in Cribbage is to calculate which four-card combination has the best potential for scoring points, with a point system composed of pairs of same-rank cards, cards that numerically add up to 15 (for example, a 7H and 8D), runs of three or more cards (3D, 4H, 5C) and a flush, wherein all four of your cards—plus the fifth card known as the cut card—are the same suit (for example, 3D, 7D, 8D, JD, KD).

If you're paying attention, you realize that although we're talking about calculating the best four cards out of six, there's a fifth card that gets thrown into the mix too before we actually count up. You can do the Vegas routine of carefully calculating your odds of a specific card showing up (1:40), but because you never know what the other player is holding, it's not of any obvious value.

What you can bet on, however, is the odds that it's a card with a numeric value of ten is quite high, because it can be a 10, J, Q or K of any of the four suits. This means there's a 16:52 or 30% chance that any random card in a deck of cards is a “ten” card. And, if you have none in your hand, the odds are even better, 16:46 or roughly a 35% chance that a given card of those not in your hand is a face card.

How does this apply? Let's say you have a 5D in your hand, along with a 10S and QC. That's two 15-point combinations, for four points. But it's better than that, because there's a good chance that the cut card also will be a ten card, offering another two points or more (if it was, say, a QD).

Enough chatter though, let's jump in to the code!

Last month, we ended with a script that dealt a random hand, sorted it by rank and then produced a list of all 15 possible combinations of four-card subhands. When run, it looked like this:

$ sh cribbage.sh
Hand: 4S, 5C, 5D, 8H, 9H, JC.
Subhand 0:  4S  5C  5D  8H
Subhand 1:  4S  5C  5D  9H
Subhand 2:  4S  5C  5D  JC
Subhand 3:  4S  5C  8H  9H
Subhand 4:  4S  5C  8H  JC
Subhand 5:  4S  5C  9H  JC
Subhand 6:  4S  5D  8H  9H
Subhand 7:  4S  5D  8H  JC
Subhand 8:  4S  5D  9H  JC
Subhand 9:  4S  8H  9H  JC
Subhand 10:  5C  5D  8H  9H
Subhand 11:  5C  5D  8H  JC
Subhand 12:  5C  5D  9H  JC
Subhand 13:  5C  8H  9H  JC
Subhand 14:  5D  8H  9H  JC

The first subhand isn't worth much, by way of example: two points for the pair of fives. Subhand two, on the other hand, is pretty good: two points for the pair of fives, and another four points for the 5C+JC and 5D+JC 15-point sequences.

But how do you calculate these programmatically?

The first step is to extract the four cards out of the hand for analysis, and this is done by adding a few lines to our main post-deal loop:

for subhand in {0..14}
do
  /bin/echo -n "Subhand ${subhand}:"
  cardnum=0  # start over
  for thecard in ${sixfour[$subhand]}
  do
    showcard ${hand[$thecard]}
    /bin/echo -n "  $showcardvalue"
    oursubhand[$cardnum]=${hand[$thecard]}
    cardnum=$(( $cardnum + 1 ))
  done
  echo ""
done

The evaluation must be done while in the outer loop, because we need to do it a bunch of times. By using oursubhand as a four-element array that we keep filling with cards, we can send the four-card subset to our function like this:

handvalue4 ${oursubhand[0]} ${oursubhand[1]}
    ${oursubhand[2]} ${oursubhand[3]}

The function will get the cards in ascending rank value, but we'll still need to have the card value, the suit and the rank—both raw rank and normalized for any face card being worth ten points. Here's how to do that:

handvalue4()
{
   # given four cards, how much are they worth?

   c1=$1; c2=$2; c3=$3; c4=$4

   s1=$(( $c1 / 13 )); s2=$(( $c2 / 13 ))
   s3=$(( $c3 / 13 )); s4=$(( $c4 / 13 ))

   r1=$(( ( $c1 % 13 ) + 1 )); r2=$(( ( $c2 % 13 ) + 1 ))
   r3=$(( ( $c3 % 13 ) + 1 )); r4=$(( ( $c4 % 13 ) + 1 ))

   # now fix rank to normalize for face cards=10
   case $r1 in
     11|12|13) nr1=10 ;;
     *) nr1=$r1 ;;
   esac
   case $r2 in
     11|12|13) nr2=10 ;;
     *) nr2=$r2 ;;
   esac
   case $r3 in
     11|12|13) nr3=10 ;;
     *) nr3=$r3 ;;
   esac
   case $r4 in
     11|12|13) nr4=10 ;;
     *) nr4=$r4 ;;
   esac
}

At the end of this function, each card has three values associated with it: suit ($s1), rank ($r1) and normalized rank where face cards are assigned to a counting value of 10 ($nr1).

It's still more complex, however, because if we have 6D, 7C, 9H, JC, then 6+9 = 15, but they're not neatly adjacent. So in fact, we'll need another function for four-choose-two and four-choose-three combinations too. Those, fortunately, are few:

4 Choose 2: {a,b} {a,c} {a,d} {b,c} {b,d} {c,d}
4 Choose 3: {a,b,c} {a,b,d} {a,c,d} {b,c,d}

Complicated, eh? In the same way we enumerated six choose four, we can do the same thing for these too:

fourtwo[0]="0,1"; fourtwo[1]="0,2";
fourtwo[2]="0,3"; fourtwo[3]="1,2"
fourtwo[4]="1,3"; fourtwo[5]="2,3";
fourthree[0]="0,1,2"; fourthree[1]="0,1,3";
fourthree[2]="0,2,3"; fourthree[3]="1,2,3"

Now let's take a stab at calculating two-card combinations that add up to 15:

calc15()
{
  # given four ranks, see if there are any combinations 
  # that add up to 15. return total point value.
  points=0
  c15[0]=$1; c15[1]=$2; c15[2]=$3; c15[3]=$4

  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

I'm running out of space, but you can see where I'm going with this. Once we can go through all four-card combinations, we then can examine all two-card pairs by rank to see what adds up to 15 points.

As a teaser, with a bit of debugging code and the additional tests for three-card and four-card combinations, here's where we are with the script:

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

We'll pick this up and continue building out the calc15() function and stepping to the code that'll test for runs of three or four cards and all-card flushes too. Stay tuned!

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.

LJ Archive CD