Work the Shell

Keeping Score in Yahtzee

Dave Taylor

Issue #163, November 2007

Push an array sort out to the sort function instead of writing a sort routine.

Last month, I started talking about how to use some simple shell scripting techniques to create a computer simulation of the popular dice game Yahtzee. I'm not going to write the entire game (the computer player would be darn complicated for a shell script, for one thing), but let's spend one more column looking at some of the basics of scoring before we move on to other topics.

One nice thing about Yahtzee as a computer game is that there really isn't much work for the computer to do, because the game works fine as a solitaire variation: you simply can play to try to maximize your score and see how you do, competing against your previous high score.

The basic idea of Yahtzee is that you roll five dice up to three times to get the best possible set of values. Those are then scored against a fixed score pad that offers specific points for specific combinations, ranging from one point per “one” in your roll up to 50 points for five of a kind (a “Yahtzee”, in game parlance).

A quick visit to Google identifies an on-line Yahtzee score sheet; take a look at it before we proceed: www.gamingcorner.nl/images/sheets/yahtzee-3.pdf.

In many ways, it's five-card draw poker with dice, so the first section of the score sheet is for sets: ones, twos, threes and so on, where your score for each is the sum of that value on the dice. The second section is other poker hands, including three of a kind, four of a kind, small straight (four of the five dice have sequential values), large straight (all five are sequential), a full house and one or more Yahtzee rolls.

You can get bonuses for achieving certain levels, including a very nice 35-point bonus for attaining at least 63 on the top section, but the score sheet itself is straightforward.

The key to the game, then, is to figure out how to score a given roll. If you roll four ones, do you want to score that as your four of a kind or your ones? What if they're both filled in already? Fortunately, we're going to defer to the player for that, but that still leaves us with the data question of how to model the different boxes on the score sheet and the interface question of how to prompt the user to select which box to score. Let's take a look.

Modeling the Score Sheet as an Array

As with most of these types of data structures, we'll use an array to model the score sheet. Count the boxes on the score sheet, and you'll see 13 boxes total, including the special Yahtzee box where you can roll—and get credit for—more than one (so a game typically has 13 rolls, but it could have more).

If we initialize the game by filling in all the array values with a known stop value, say -1, then the test for whether a given box has been filled in is easy:

if [ scoresheet[1] != -1 ] ; then
  echo "1: Your one's" ; fi

The trick is that we also want to pre-qualify these options. There's no point in prompting players to select their roll as a Yahtzee if they didn't get five of a kind. This proves to be a bit tricky, so as a first step, I'm going to tweak the code to order the dice in the array in ascending order after each roll automatically.

It might be a long trip to accomplish the task, but rather than write a sorting routine in the script, I'm just going to push out the task to the sort function, then read the results back in and fit them into the individual slots of the dice array. Sound complicated? It is, rather:

function sortDice()
{
   sorted="$( ( echo ${dice[1]} ; echo ${dice[2]}
     echo ${dice[3]} ; echo ${dice[4]}
     echo ${dice[5]} ) | sort )"

   index=1
   for value in $sorted ; do
     dice[$index]=$value
     index=$(( $index + 1 ))
   done
}

You can see here that I'm using a temp variable called sorted to store the resultant values, and that I'm using a subshell—that's the $( ) notation—to do the actual work. The hardest part of this little function is to figure out how to put the values back into the array once everything's sorted properly, and that's accomplished with the for loop.

Notice that, by a lucky coincidence, for loops automatically step through fields separated by white space (spaces and carriage returns), so it's perfect for breaking the resultant sorted sequence back into individual values.

We're running low on space this month, and I'm afraid I've ended up spending quite a bit of time talking, rather than coding. I'll make it up to you, dear reader, next month!

Dave Taylor is a 26-year veteran of UNIX, creator of The Elm Mail System, and most recently author of both the best-selling Wicked Cool Shell Scripts and Teach Yourself Unix in 24 Hours, among his 16 technical books. His main Web site is at www.intuitive.com, and he also offers up tech support at AskDaveTaylor.com.