Work the Shell

Accumulating Brains in Zombie Dice

Dave Taylor

Issue #240, April 2014

Dave continues to build a Zombie Dice game, and in this installment, he deals with runners and counting up how many brains you've harvested while trying to avoid those pesky gunshots.

For the record, this is my 100th column for Linux Journal. One hundred columns. That's more than eight years of me writing about shell scripts and you, dear reader, reading my articles. It seems like a good partnership, and I hope we can stick together into the next decade, which is only a dozen or two columns down the road. That first column was titled “Getting Started with Redirection”, and it explored the differences between >, >>, < and << in shell scripts. I think we've come a long way since then!

And on this occasion of commemorating my 100th column, I again invite you to send me e-mail with your ideas for interesting column topics. If you can dream it up, and it's not insanely complicated, you can write it as a shell script. Enough of that mushy stuff though—let's get back to work!

In my last article, I started writing a game that simulated some elements of the Jackson Games dice game Zombie Dice. As homework, you might want to pick up the game at Target or Walmart next time you're there, or get it at amazon.com. It's fun, easy and great with kids (or with my kids, at least, who can appreciate a few zombies as a source of entertainment).

The game revolves around rolling dice and seeking to maximize one accumulation of items (brains) while avoiding getting shot at too many times (gunshots). There are three different kinds of dice with varying levels of challenges—green, yellow and red—and various nuances about how many you can roll and how. To learn more, check out last month's column or look at the instructions that came with your canister of Zombie Dice.

The script, when last I hacked it, could simulate a roll while also randomly choosing between the different dice:

$ ./zdice.sh
    rolled green die: brain
    rolled red die: runner
    rolled red die: brain

To start, let's add the smarts in the script to know what a “runner” is. “Dude, what is a runner?” I can hear you ask. Okay, okay, a runner is essentially an ambiguous roll, and if you opt to roll your dice again, you roll that specific die, whether it's green, yellow or red. The brains and gunshots you pull aside and accumulate, by contrast. So on the above roll, you'd put the two brain dice aside, re-roll the red “runner” and randomly pick two of the remaining dice to make the three required for another roll. In code, you can simulate this by assigning specific values to the diceroll[] array. If the specific roll status is set to zero, it's a new roll of a new die. If not, it's the numeric value associated with the die color and produces a re-roll:

if [ ${diceroll[$rollcount]} -eq 0 ] ; then
  pick_color
else
  echo "  dice $rollcount was a runner last time, \
      rerolling the same color die again:"
  color=${diceroll[$rollcount]}
  diceroll[$rollcount]=0      # reset for next roll
fi

The entire purpose of this segment is to set the “color” variable correctly. Who knew that'd require so much code?

So that you have some status output though, it's the kind of information that might well be removed once you've thoroughly tested the production code. You'll then have this snippet:

roll_die $color
echo "    rolled ${colorname[$color]} die: ${nameof[$roll]}"

This is an example of debug output. I've written about debugging shell scripts in previous columns if you want to check the archives to learn more about this essential element of script development.

There's not much work in the add_score subroutine, as you'll see shortly, but it fits in here, after the dice rolls. Then there's additional code below to show how you're doing with this roll, and this will be where the challenge of accumulating brains and gunshot scores will happen, along with the test of whether you've had too many gunshots and have lost.

One more step, the mirror of the earlier code: if the roll (variable $roll, set in subroutine roll_die) is a runner, then the diceroll[] value needs to be set appropriately so it can be differentiated from a non-runner roll:

if [ $roll -eq $RUNNER ] ; then
  diceroll[$rollcount]=$color;
fi

This is all neatly wrapped up in a for loop that gives three rolls with the simple expedient of:

for roll count in 1 2 3
do
   all the code shown above goes here
done

Let's have a closer look at the add_score subroutine, then the loop that lets you decide after each three-dice roll whether you want to stop or continue. Remember, your goal is to get the most brains without dying of three gunshot wounds:

function add_score
{
  # Add the current roll to the score so far
  # Only need to score brains and gunshots

  case $roll in
    $BRAIN ) brains=$(( $brains + 1 ))  ;;
    $SHOT  )  shots=$(( $shots + 1 ))  ;;
  esac
}

It's short and sweet, actually. Notice that as with many case statements, there actually are three possible values ($RUNNER is the third possible value), but only two are addressed. That's fine; the third value just drops through. The trick with good coding, of course, is to know that's going to happen.

If you haven't seen it before, the $(( )) notation is a convenience for invoking an equation-solving part of the Bash shell. It's similar to $( expr $shots + 1 ), but it executes faster because it doesn't require instantiation of a subshell.

From a functional perspective, add_score shouldn't test to see if you have accumulated the 13 brains needed to win or have been hit three or more times with gunshots—a lose scenario. Those show up a bit later in the script instead, just below the runner-savvy dice-rolling code shown earlier.

The code now invokes the routine, shown in my last article, that displays your current score:

show_score

Now for the big question: did you accumulate three or more gunshots? If you did, show_score will have returned a non-zero status, which can then be tested:

if [ $? -ne 0 ] ; then
   echo "BOOM. You died. But you did get to roll \
       $totalrolls times and eat $brains brains."
   exit 0
fi

If you survive that test, you're still alive! Hurray.

And that's where I'm going to stop for this month. Next month, I'll explore the rest of the brains/gunshots testing rules and have a functional one-player game. But that's not much fun, so I'll delve into creating a computer player too, suggesting some algorithmic strategies that should make it a decent player.

Meanwhile, go check out the real Zombie Dice game from Steve Jackson Games: www.sjgames.com.

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.