Counting Cards: Cribbage

I've spent the past few months reviewing shell scripting basics, so I think it's time to get back into an interesting project. It's always a good challenge to capture game logic in a shell script, particularly because we're often pushing the envelope with the capabilities of the Bash shell.

For this new project, let's model how a deck of cards works in a script, developing specific functions as we proceed. The game that we'll start with is a two- or three-player card game called Cribbage. The basic functions we'll create also will be easily extended to simple Poker variants and other multi-card evaluation problems.

If you aren't familiar with Cribbage, you've got time to learn more about the game, because I won't actually get to any game-specific elements until next month. Need a good place to learn? Try this: http://www.bicyclecards.com/card-games/rule/cribbage.

The first and most obvious challenge with any card game is modeling the deck of cards. It's not just the deck, however, it's the challenge of shuffling too. Do you need to go through the deck multiple times to randomize the results? Fortunately, that isn't necessary, because you can create a deck—as an array of integer values—in sequential order and randomly pick cards from the deck instead of worrying about shuffling the deck and picking them in sequential order.

This is really all about arrays, and in a shell script, arrays are easy to work with: simply specify the needed index in the array, and it'll be allocated so that it's a valid slot. For example, I simply could use deck[52]=1, and the deck array will have slots 0..52 created (though all the other elements will have undefined values).

Creating the ordered deck of cards, therefore, is really easy:


for i in {0..51}
do
  deck[$i]=$i
done

Since we're going to use the value -1 to indicate that the card has been pulled out of the deck, this would work just as well if everything were set to any value other than -1, but I like the symmetry of deck[$i]=$i.

Notice also the advanced for loop we're employing. Early versions of Bash can't work with the {x..y} notation, so if that fails, we'll need to increment the variable by hand. It's not a big hassle, but hopefully this'll work fine.

To pick a card, let's tap into the magic $RANDOM variable, a variable that has a different value each time you reference it—darn handy, really.

So, picking a card randomly from the deck is as easy as:


card=${deck[$RANDOM % 52]}

Note that to avoid incorrect syntactic analysis, it's a good habit always to reference arrays as ${deck[$x]} rather than the more succinct $deck[$x].

How do you know whether you've already picked a particular card out of the deck? I don't care what game you're playing, a hand like 3H, 4D, 5D, 9H, 9H and 9H is going to get you in trouble! To solve this, the algorithm we'll use looks like this:


pick a card
if it's already been picked before
   pick again
until we get a valid card

Programmatically, remembering that a value of -1 denotes a card that's already been picked out of the deck, it looks like this:


until [ $card -ne -1 ]
do
  card=${deck[$RANDOM % 52]}
done
echo "Picked card $card from the deck"

The first card picked isn't a problem, but if you want to deal out 45 of the 52 cards, by the time you get to the last few, the program might well bounce around, repeatedly selecting already dealt cards, for a half-dozen times or more. In a scenario where you're going to deal out the entire deck or a significant subset, a smarter algorithm would be to count how many random attempts you make, and when you've hit a threshold, then sequentially go through the deck from a random point until you find one that's available—just in case that random number generator isn't as random as we'd like.

The piece missing in the fragment above is the additional snippet of code that marks a given card as having been picked so that the algorithm identifies twice-picked cards. I'll add that, add an array of six cards I'm going to deal, and also add a variable to keep track of the array index value of the specific card chosen:


for card in {0..5} ; do
  until [ ${hand[$card]} -ne -1 ]
  do
    pick=$(( $RANDOM % 52 ))
    hand[$card]=${deck[$pick]}
  done
  echo "Card ${card} = ${hand[$card]}"
  deck[$pick]=-1     # no longer available
done

You can see that I've added the use of a "pick" variable, and because the equation appears in a different context, I had to add the $(( )) notation around the actual random selection.

There's a bug in this code, however. Can you spot it? It's a classic mistake that programmers make, actually.

The problem? The until loop is assuming that the value of $hand[n] is -1 and remains so until a valid card randomly picked out of the deck is assigned to it. But the value of an array element is undefined when first allocated—not good.

Instead, a quick initialization is required just above this snippet:


# start with an undealt hand:
for card in {0..5} ; do
  hand[$card]=-1
done

We're almost actually ready to deal out a hand and see what we get. Before we do, however, there's one more task: a routine that can translate numeric values like 21 into readable card values like "Nine of Diamonds" or, more succinctly, "9D".

There are four suits and 13 possible card values in each, which means that the div and mod functions are needed: rank = card % 13 and suit = card / 13.

We need a way to map suit into its mnemonic: hearts, clubs, diamonds and spades. That's easy with another array:


suits[0]="H"; suits[1]="C"; suits[2]="D"; suits[3]="S";

With that initialized, showing a meaningful value for a given card is surprisingly straightforward:


showcard()
{
  suit=$(( $1 / 13 ))
  rank=$(( ( $1 % 13 ) + 1 ))
  showcardvalue=$rank${suits[$suit]}
}

Actually, that's not quite right, because we don't want results like 11H or 1D; we want to convert 1 into an Ace, 11 into a Jack and so on. It's the perfect use for a case statement:


case $rank in
  1)  rank="A" ;;
  11) rank="J" ;;
  12) rank="Q" ;;
  13) rank="K" ;;
esac

Now we're ready to deal a hand and see what we get:


for card in {0..5} ; do
  until [ ${hand[$card]} -ne -1 ]
  do
   pick=$(( $RANDOM % 52 ))
    hand[$card]=${deck[$pick]}
  done
  showcard ${hand[$card]} # sets 'showcardvalue'
  echo "Card ${card}: $showcardvalue"
  deck[$pick]=-1     # no longer available
done

And the result of running this? Here are a few iterations:


$ sh cribbage.sh
Card 0: 5D
Card 1: 5C
Card 2: JS
Card 3: QD
Card 4: 4D
Card 5: JD
$ sh cribbage.sh
Card 0: 10C
Card 1: 5D
Card 2: KC
Card 3: 7S
Card 4: 4S
Card 5: 8C

Cool. Now that we have the basics of how to model a deck and deal a hand of unique cards, we can start with the interesting elements—next month. In the meantime, your homework is to learn Cribbage.

Cribbage photo via Shutterstock.com.

______________________

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.

Big Idea Mastermind

Big Idea Mastermind's picture

Modelling a deck of cards for cribbage is sic! dont you think? I think its kind of awesome that you can do this.
People will find allsorts of ways of Making money.
Try this for making big money online: Big Idea Mastermind

Thank you so much. I am

eticaret's picture

Thank you so much. I am researcing this for e-ticaret

Better approach to picking a random card

Dan J.'s picture

There's a better, IMHO, variation of this approach that avoid the wasted cycles of repeatedly selecting a random number until you find one which has not been selected. Quick and dirty code, not carefully tested and in need of some error checking/limiting constraints for production:

decksize=51

for card in {0..5} ; do
pick=$(( $RANDOM % $decksize))
hand[$card]=${deck[$pick]}
deck[$pick]=${deck[$decksize]}
(( decksize-- ))

showcard ${hand[$card]} # sets 'showcardvalue'
echo "Card ${card}: $showcardvalue"

done

When you select a card, you remove it and then simply move the last card in the deck into the empty space and select a random card from the smaller deck which now has no holes. Not sure if the above will correctly handle a random selection of the last card in the deck, and need to check for $decksize being zero, etc.

Let's work together!

Big Idea Mastermind's picture

This morning you arrived at work early to check on a special project. As you enter the building you hear excited voices coming down the hall.…

http://bigideamastermindteamreview.com/

DOAH!

Anonymous's picture

That is how I do it as well, Have a routine that shuffles the deck by iterating i and swapping deck[$i] with deck[$RANDOM %52]. I just saw the article later in the day.

Reply to comment | Linux Journal

Pizzeria Amsterdam's picture

I was wondering if you ever thought of changing the layout of your blog?
Its very well written; I love what youve got to say. But maybe you could a little more
in the way of content so people could connect with it better.
Youve got an awful lot of text for only having
one or 2 pictures. Maybe you could space it out better?

Reply to comment | Linux Journal

téléphone fixe's picture

Quality articles or reviews is the key to be a focus for the people to pay a quick visit the
web page, that's what this web page is providing.

Reply to comment | Linux Journal

SM-110 Pulse Oximeter Review's picture

Your ability as a blogger positively shows!

Reply to comment | Linux Journal

agencja towarzyska's picture

I am regular visitor, how are you everybody? This piece of writing posted at this web page is
in fact good.

Shuffle and index

Art's picture

I prefer to initialize the deck of cards, then iterate thru the deck with i, then swap deck[i] with deck[$RANDOM % 52].

Now you have a shuffled deck. Keep an index value for your position in the deck when dealing cards.

Reply to comment | Linux Journal

location voitures Casablanca's picture

Amazing! This blog looks just like my old one! It's on a entirely different subject but it has pretty much the same page layout and design. Excellent choice of colors!

My site - location voitures Casablanca

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