Shell Scripting a Bunco Game

I haven't dug into any game programming for a while, so I thought it was
high time to do something in that realm. At first, I thought "Halo as a
shell script?", but then I came to my senses. Instead, let's look at a
simple dice game called Bunco. You may not have heard of it, but I bet your Mom
has—it's a quite popular game for groups of gals at a local pub or
tavern.

Played in six rounds with three dice, the game is simple. You roll all three
dice and have to match the current round number. If all three dice match the
current round number (for example, three 3s in round three), you score 21. If all
three match but aren't the current round number, it's a Mini Bunco and
worth five points. Failing both of those, each die with the same value as the round
number is worth one point.

Played properly, the game also involves teams, multiple tables including a
winner's table, and usually cash prizes funded by everyone paying $5 or
similar to play and based on specific winning scenarios like "most
Buncos" or "most
points". I'll skip that part here, however, and just focus on the dice part.

Let's Do the Math

Before I go too far into the programming side of things, let me talk briefly
about the math behind the game. Dice are easy to work with because on a properly
weighted die, the chance of a particular value coming up is 1:6.

Random tip: not sure whether your dice are balanced? Toss them in salty water and spin
them. There are some really interesting YouTube videos from the D&D world showing
how to do this test.

So what are the odds of three dice having the same value? The first die has a
100% chance of having a value (no leaners here), so that's easy. The second
die has a 16.66% chance of being any particular value, and then the third die has the
same chance of being that value, but of course, they multiply, so three dice have
about a 2.7% chance of all having the same value.

Then, it's a 16.66% chance that those three dice would be the current
round's number—or, in mathematical terms:
0.166 * 0.166 * 0.166 = 0.00462.

In other words, you have a 0.46% chance of rolling a Bunco, which is a bit less
than once out of every 200 rolls of three dice.

It could be tougher though. If you were playing with five dice, the chance of
rolling a Mini Bunco (or Yahtzee) is 0.077%, and if you were trying to accomplish
a specific value, say just sixes, then it's 0.00012% likely on any given
roll—which is to say, not bloody likely!

And So into the Coding

As with every game, the hardest part is really having a good random number
generator that generates truly random values. That's actually hard to affect
in a shell script though, so I'm going to sidestep this entire issue and assume
that the shell's built-in random number generator will be sufficient.

What's nice is that it's super easy to work with. Just reference
$RANDOM,
and you'll have a random value between 0 and MAXINT (32767):

$ echo $RANDOM $RANDOM $RANDOM
10252 22142 14863

To constrain that to values between 1–6 use the modulus function:

$ echo $(( $RANDOM % 6 ))
3
$ echo $(( $RANDOM % 6 ))
0

Oops! I forgot to shift it one. Here's another try:

$ echo $(( ( $RANDOM % 6 ) + 1 ))
6

That's the dice-rolling feature. Let's make it a function where you can
specify the variable you'd like to have the generated value as part of the
invocation:

That's probably the hardest of the tests, and notice the unusual use of test
in the first conditional: [ cond1 ] && [ cond2 ]. If you're thinking that you
could also write it as cond1 -a cond2, you're right. As with so much in the
shell, there's more than one way to get to the solution.

The remainder of the code is straightforward; you just need to test for whether
the die matches the current round value:

The only thing to consider here is that you don't want to score die value vs.
round if you've also scored a Bunco or Mini Bunco, so the entire second set
of tests needs to be within the else clause of the first conditional (to see if
all three dice have the same value).

Put it together and specify the round number on the command line, and here's
what you have at this point:

A Bunco so quickly? Well, as I said, there might be a slight issue with the
randomness of the random number generator in the shell.

You can test it once you have the script working by running it a few hundred
times and then checking to see what percentage are Bunco or Mini Bunco, but
I'll leave that as an exercise for you, dear reader. Well, maybe I'll
come back to it another time.

Let's finish up this script by having it accumulate score and run for all six
rounds instead of specifying a round on the command line. That's easily done,
because it's just a wrapper around the entire script, or, better, the big
conditional statement becomes a function all its own:

Ugh. Not too impressive, but it's probably a typical round. Again, you can run it a
few hundred—or thousand—times, just save the "Game over" line, then
do some quick statistical analysis to see how often you score more than 3 points
in six rounds. (With three dice to roll a given value, you should hit that 50% of the
time.)

It's not a complicated game by any means, but it makes for an interesting little programming
project. Now, what if they used 20-sided die and let you re-roll one die per round and
had a dozen rounds?

Dave Taylor has been hacking shell scripts on UNIX and Linux systems for a
really long time. He's the author of Learning Unix for Mac OS
X and Wicked Cool Shell Scripts. You can find him on Twitter
as @DaveTaylor, and you can reach him through his tech Q&A site: Ask Dave Taylor.