Cribbage: Sorting Your Hand

We've been working on writing code for the game Cribbage, and last time, I created the code needed to pick a random subset of six cards out of a "deck" and display them in an attractive format—like this:


$ sh cribbage.sh
Card 0: 7C
Card 1: 5H
Card 2: 9H
Card 3: 10S
Card 4: 5D
Card 5: AS

The primary task on the agenda this article is to sort the cards after they've been dealt. This means we're going to have to sort the cards by rank while ignoring the suit, then slot them back into the "hand" array. Is there an easy way to do that? Actually, we'll use the sort function.

We can prototype this by using the command line to see what result we get:


$ sh cribbage.sh  | sort -n
Card 0: 4S
Card 1: 7C
Card 2: 9S
Card 3: JC
Card 4: 7H
Card 5: 8C

What the heck? Oh! You can see the problem, right? By telling sort to order things numerically, it properly ignores "Card" but then sees the ordinal value of the card and sorts based on that, rather than on the actual card value itself.

Even if we fix this, however, we still have the problem that face cards will sort before numeric value cards, which isn't what we want. In fact, we want aces to sort as lower than 2s, while jacks, queens and kings sort as higher than 10s.

If you wanted to have aces "high", the easiest way to do that would be to change the display routine, of course: 1 = a deuce, 2 = a three, 12 = king and 13 = ace. Poof. Everything sorts ace-high. That's just not how Cribbage scores them.

To accomplish Cribbage-rank sorting, we'll need to change the output to push out two values: the rank and the total card value. It's going to look ugly, but it's just an interim result.

Here's how I tweak the code to display these values:


showcard()
{
  # given a card value of 0..51 show the suit and rank
  suit=$(( $1 / 13 ))
  rank=$(( ( $1 % 13 ) + 1 ))
  case $rank in
    1)  orank="A" ;;
    11) orank="J" ;;
    12) orank="Q" ;;
    13) orank="K" ;;
     *) orank=$rank ;;
  esac
  showcardvalue=$orank${suits[$suit]}
}

If you compare it to the version we built last month, the main difference is that instead of calculating the rank of the card and then overwriting it with "A", "J", "Q" or "K" as appropriate, we're using a new variable, orank, to store the corrected value. Why? Because now in the main section of the script we also can access the $rank of the card as desired:


showcard ${hand[$card]}
echo "$rank ${hand[$card]}"

For each card chosen, the script has an interim output of rank followed by the numeric value of the card, with no fancy display (even though we're still tapping the showcard function for simplicity). The result:


$ sh cribbage.sh
13 38
6 31
8 33
10 35
5 30
12 24

Ugly? Definitely. But now we can sort it and get useful results, even if they might not look like it quite yet:


$ sh cribbage.sh | sort -n
1 26
2 14
2 40
3 2
7 45
10 22

It still looks confusing, but you can see that it's in rank order.

So, how do we get that back into the "hand" array now that we know how to sort it? That's actually rather tricky because of variable scoping issues, as you'll see.

Before we go there, however, I've written a new "showhand" function that displays all the cards in the hand on a single line, with the help of /bin/echo for echoes without a trailing line break:


showhand()
{
   # show our hand neatly
   /bin/echo -n "Hand: "
   for card in {0..4}
   do
     showcard ${hand[$card]}
     /bin/echo -n "$showcardvalue, "
   done
   showcard ${hand[5]}
   echo "$showcardvalue."
}

With that available, our main code starts to look nice and clean:


dealhand;
showhand;  # for testing sorthand only
sorthand;
showhand;

For debugging purposes, I'm going to display the hand before and after we've sorted by rank. Eventually, the first "showhand" would just be axed, of course.

Now, let's get back to the code needed to sort the cards in our hand (a feature that a lot of iOS Cribbage games seem to omit, as far as I can tell).

My first stab at writing "sorthand" took advantage of a very slick feature in Bourne shell that lets you tie the output of one loop to the input of another with a pipe. For example:


for card in {0..5}
do
  showcard ${hand[$card]}
  echo "$rank ${hand[$card]}"
done | sort -n | while read rank value
do
  hand[$index]=$value
  index=$(( $index + 1 ))
done

The problem is that the shell's pipe implementation pushes the second loop into a subshell without any easy way to get the changed values back up to the parent shell. The result: by the line immediately after the last done statement, all the new values have been lost.

That's too bad, because it definitely was more elegant. But then again, it's not about elegant, it's about functional, right?

Here's how I actually solved it, by using a temporary file to store the intermediate results instead. It's considerably less elegant, for sure:


sorthand()
{
   # hand is dealt, now sort it by card rank...
   index=0
   tempfile="/tmp/.deleteme"
   for card in {0..5}
   do
     showcard ${hand[$card]}
     echo "$rank ${hand[$card]}"
   done | sort -n > $tempfile
 
   while read rank value
   do
     hand[$index]=$value
     index=$(( $index + 1 ))
   done < $tempfile
   rm -f $tempfile
}

Note that to get the input of the temporary file as the input for the while loop, I simply redirect stdin for the loop at the very end of the loop: done < $tempfile.

Let's test it by dealing a few hands and then showing them immediately post-deal and then after they've been rearranged with the sorthand function:


$ sh cribbage.sh
Hand: 9H, 6D, KC, AH, 9S, JH.
Hand: AH, 6D, 9S, 9H, JH, KC.
$ sh cribbage.sh
Hand: 4D, QS, AC, 9H, 10C, JS.
Hand: AC, 4D, 9H, 10C, JS, QS.
$ sh cribbage.sh
Hand: 9H, 10C, 7C, 7H, 5H, AS.
Hand: AS, 5H, 7C, 7H, 9H, 10C.

It looks like it's working exactly as we'd hope. Yeee-ha!

Yes, there are undoubtedly more efficient ways to write this code and you can quite reasonably ask if a shell script is the optimal development environment for this sort of project, but, seriously, lighten up. Let's enjoy this project, not flagellate ourselves over punctuation!

And on that note, let's wrap up this month's column and start thinking about a considerably harder challenge we'll face starting next month: how to evaluate the value of the hand so that we can recommend which four of the six cards dealt should be kept to optimize the Cribbage hand.

You are learning Cribbage as we go, right? You'll want it for the next installment, for sure.

______________________

Dave Taylor has been hacking shell scripts for over thirty 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.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Very efficiently written

hotel sud marocain                's picture

Very efficiently written post. It will be useful to anyone who usess it, including myself. Keep doing what you are doing - i will definitely read more posts.

software job post

Tripti's picture

This is so good, thanks

awesome

Arvin J's picture

this is awesome! thanks!

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState