LJ Archive

Work the Shell

Cribbage: Closing Things Up

Dave Taylor

Issue #231, July 2013

Dave wraps up his Cribbage script by explaining how to code a hand-optimizing routine.

After months of work, we're almost done with the Cribbage shell script. As you'll recall, we're focused on writing just the portion of the game that evaluates the best four cards out of the initial six dealt in a two-player game.

This is, of course, a complex task, because there are a lot of ways you can pick four from six, and a lot of ways that any two, three or four cards can produce points. And we haven't even considered “His Nobs”, the two points you can get simply by having a jack of the same suit as the shared extra card turned up after both players discard.

At this point in the evolution of the script, it can extract all six-choose-four subhand combinations, calculate the point value of pairs, three of a kinds, combinations that add up to 15 and three-card runs. There's not much left to do—just making sure it'll properly calculate the rare instance when you have four of a kind and when you might have a four-card run.

The code has lots of debugging output, as befits a script in process, and it's very close. Here's an example:

Subhand 14:  4H  4S  5H  6H
a pair 4 and 4 for two
three card fifteen for two
4 + 5 + 6 run for three
three card fifteen for two
4 + 5 + 6 run for three
total point value of that hand: 12

That's correct. Those four cards combine to be worth a total of 12 points.

But, what about those edge cases? Let's find out what happens if we have four cards in the same suit: does the script recognize it's worth four additional points as a flush? Let's see:

Subhand 6:  2H  3H  4H  5H
2 + 3 + 4 run for three
3 + 4 + 5 run for three
  total point value of that hand: 6

Nope. This is easily done, but it's more code—another edge case. It works by having the calling function give the function calc4cardvalue, not just the point rank of each card and the numeric rank of each card, but also the suit. That's 12 parameters (which is a bit clumsy, admittedly). Each of the suit parameters is assigned a local variable for convenience, then the test is straightforward:

if [ $cs0 -eq $cs1 -a $cs1 -eq $cs2 -a $cs2 -eq $cs3 ] ; then
  echo "four cards flush for four."
  points=$(( $points + 4 ))
fi

Now we get this sort of result:

Subhand 10:  3H  4H  5H  6H
3 + 4 + 5 run for three
three card fifteen for two
4 + 5 + 6 run for three
four card flush for four.
Total point value of that hand: 12

It's close, but there's a problem with the calculations, because runs aren't counted as 3+4+5 and 4+5+6 (twice three points) but instead as a single four-card run (3+4+5+6), which is worth only four points.

The only time that this is an issue, however, is when there are two runs of three, so the easy way to identify this situation is simply to keep a count of how many three-runs are encountered.

Ready? The conditional is pretty gnarly:

if [ $(( ${cardrankfull[${combo:0:1}]} + 1 )) -eq
 ↪${cardrankfull[${combo:2:1}]} -a
     $(( ${cardrankfull[${combo:2:1}]} + 1 )) -eq
      ↪${cardrankfull[${combo:4:1}]} ] ; then
  echo "run for three for two"
  points=$(( $points + 3 ))
  runof3=$(( $runof3 + 1 ))
fi

Now at the end of the routine we check if $runof3 is 2, then we need to subtract two points to correct the counting (remember, a run of four is worth four points, not six).

Here's the same hand, with the new code in place:

Subhand 10:  3H  4H  5H  6H
3 + 4 + 5 run for three
three card fifteen for two
4 + 5 + 6 run for three
four card flush for four.
two runs of three = a run of four. minus two
Total point value of that hand: 10

By George, I think we have it!

Calculating the Optimal Subhand

The challenge we're faced with now is perhaps the easiest one: how to calculate the optimal subhand. This output snippet demonstrates the dramatic variance in point value of different hand subsets:

Subhand 9:  3C  5S  6H  JS
fifteen for two
  total point value of that hand: 2

Subhand 10:  4H  5H  5S  6H
a pair 5 and 5 for two
three card fifteen for two
4 + 5 + 6 run for three
three card fifteen for two
4 + 5 + 6 run for three
  total point value of that hand: 12

Subhand 11:  4H  5H  5S  JS
a pair 5 and 5 for two
fifteen for two
fifteen for two
  total point value of that hand: 6

Subhand 12:  4H  5H  6H  JS
fifteen for two
three card fifteen for two
4 + 5 + 6 run for three
  total point value of that hand: 7

Choose one subset of four cards, and you have 12 points in your hand. Pick a different combination, however, and you have two points. That's why it's important to be able to do this correctly!

Calculating the best hand out of the set is easy, fortunately. We step through all possible four-card combinations and compare the value to the best previously seen. The code looks like this:

if [ $points -gt $handvalue ] ; then
  besthand=$subhand
  handvalue=$points
fi

And at the end, the output is done simply:

echo "Done with calculations. Determined that hand 
$besthand is best, with $handvalue points"

That's really all there is other than some fancy output cleanup. Rather than seeing the subhands and point value of each, let's just have it show all six cards and which four it recommends you keep as your own hand:

Hand: 2S, 5H, 6C, 10S, JD, KD.
Your best subhand is worth 6 points:   5H  10S  JD  KD
$ sh cribbage.sh
Hand: 2S, 3C, 5C, 5D, 6S, 9H.
Your best subhand is worth 4 points:   2S  3C  5C  5D
$ sh cribbage.sh
Hand: AH, 7D, 8D, 9C, 9S, JC.
Your best subhand is worth 8 points:   7D  8D  9C  9S
$ sh cribbage.sh
Hand: 2C, 4D, 5C, 5H, 6C, 8H.
Your best subhand is worth 10 points:   4D  5C  5H  6C
$ sh cribbage.sh
Hand: 4H, 5S, 6C, 8S, JD, QC.
Your best subhand is worth 7 points:   4H  5S  6C  JD

The code underlying it ties in to the conditional statement shown earlier:

/bin/echo -n "Your best subhand is worth $handvalue points: "
for thecard in ${sixfour[$besthand]}
do
  showcard ${hand[$thecard]}
  /bin/echo -n "  $showcardvalue"
done

  echo ""

That's it. Mission accomplished, after months of working on this script. You can find the final script, debug, comments and warts and all, on the LJ Web site at www.linuxjournal.com/231/wts.

Next month, we'll switch gears and look at some administrative tasks for Webmasters who have config and admin scripts that need an occasional verification performed. And, of course, if you have ideas for scripts, don't be shy, send them along!

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