Work the Shell

Iterating Turns in Zombie Dice

Dave Taylor

Issue #241, May 2014

You know you're dying to learn how to finish up the Zombie Dice game. Dave shows how in his latest shell script programming column.

Back again so soon? Okay, we can make this work. After all, I'm busy trying to avoid gunshots while harvesting as many brains as I can manage—in the game, don't get worried. We might work a lot here at Linux Journal, but we haven't become the undead as of yet. Then again, we do have a predilection for nighttime activities.

More seriously, I've been building a computer version of the dice-rolling game Zombie Dice in my past few articles, and I'm ready to take all the individual components and wrap them up in a turn-based mechanism that lets you actually play the game.

You'll recall that Zombie Dice (www.sjgames.com) has you rolling three dice at a time, getting either brains (good—accumulate 13 and you win), gunshots (bad—get three and you're dead) and runners (neutral—they force you to roll that same die again next round). Dice are unevenly divided into green, yellow and red, and each has a different ratio of good-to-bad outcome possibilities on the die faces.

Here's where I am at this point:

sh ./zdice.sh
    rolled red die: runner
    rolled yellow die: brain
    rolled yellow die: gunshot
1 brains and 1 gunshots.

Now, you can see that if I stop, I'm alive and have one brain out of the 13 I need to win. If I continue, I have one gunshot and can survive one more, but if I get shot twice in the next round, I'm dead. The first die is red (the toughest), and it's a runner, so that's not very good.

What I haven't talked about yet is the gameplay. The way Zombie Dice works in real life is that it's a two-or-more-player game, and you keep rolling until you either get killed from gunshots, which summarily ends the turn (but you retain any brains you've accumulated from previous turns), get spooked from the gunshots you've had inflicted (in which case you add the brains you've accumulated in this round to your cumulative total) or hit 13 brains, in which case, you win.

Once your turn is over, you pass the dice to the next player. The game continues, turn by turn, until someone gets 13 brains and wins. The trick, of course, is to balance risk aversion (you don't want to lose the brains you've already won) with risk taking (you can handle another gunshot, go for it) in the desire to win before the other player does.

To model this turn-by-turn gameplay, you need a number of global variables to keep track of your scores per round and in the overall game.

Let's start with per round. I code that as follows, starting by showing your per-round accumulated score, then testing to see if you've died from gunshot wounds, and if you haven't, asking if you want to risk another round:

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

/bin/echo -n "You now have $brains brains. Stop here? (y/n) "

read answer
if [ $answer = "y" ] ; then
  echo "You survived. Now it's my turn..."
  exit 0
fi
totalrolls=$(( $totalrolls + 1 ))

Most of this is straightforward, but you might be curious about the use of /bin/echo rather than just a plain echo statement. This is a quirk of Bash and one that I've never really understood. The version of echo that's built in to the Bash shell doesn't know what the -n flag means, so if you want to have a prompt that leaves the cursor on the end of the line, here's what ends up happening:

-n You now have 2 brains. Stop here? (y/n)
_

Use /bin/echo instead, however, and it works as you'd hope:

You now have 2 brains. Stop here? (y/n) _

Yeah, it's a bit daft, but easily addressed with the /bin/ prefix. Now the code properly accumulates the score, and here's how it plays:

$ sh ./zdice.sh
    rolled red die: gunshot
    rolled red die: brain
    rolled yellow die: gunshot
1 brains and 2 gunshots.
You now have 1 brains. Stop here? (y/n) n
    rolled green die: runner
    rolled green die: brain
    rolled green die: runner
2 brains and 2 gunshots.
You now have 2 brains. Stop here? (y/n) n
      dice 1 was a runner, rerolling the same color die again:
    rolled green die: gunshot
    rolled green die: brain
      dice 3 was a runner, rerolling the same color die again:
    rolled green die: brain
4 brains and 3 gunshots.
BOOM. You died. But you did get to roll 3 times and eat 4 brains.

How did I get that to work? Here's the entire main routine, so you can see how everything fits together (the functions I've presented in previous columns if you want to check them out):

totalrolls=1
diceroll[1]=0;diceroll[2]=0;diceroll[3]=0
while /usr/bin/true ; do
  for rollcount in 1 2 3
  do
    if [ ${diceroll[$rollcount]} -eq 0 ] ; then
      pick_color
    else
      echo "      dice $rollcount was a runner, \
          rerolling the same color die again:"
      color=${diceroll[$rollcount]}
      diceroll[$rollcount]=0      # reset it for next roll
    fi

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

    add_score

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

  # done, show score, ask if they want to proceed...

  show_score

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

  /bin/echo -n "You now have $brains brains. Stop here? (y/n) "
  read answer

  if [ $answer = "y" ] ; then
    echo "You survived. Now it's my turn..."
    exit 0
  fi
  totalrolls=$(( $totalrolls + 1 ))
done

Eagle-eyed readers will notice immediately that the last conditional says “Now it's my turn...” and then immediately exits. Yeah, that's where the computer's code will go, so you'll be playing against the script rather than just against the chance that you'll die before you accumulate 13 brains. In fact, the way it's written now, the script really isn't much more than a single turn of a multi-turn game. However, at three columns, I think I've beaten this brain to death—or, um, something like that. So I'll leave it as an exercise for you, dear reader, to add the code necessary to make the computer play against you and for each of you to have a turn, alternating until one or the other gets those 13 brains.

For a risk strategy, I'd take into account the difference in score between the players (that is, if you have 11 brains and it has two, it should take more risks trying to win than if the score is reversed) and add a dose of random luck too. Maybe it'll be nervous and stop after getting two brains and one gunshot. Maybe it'll proceed with two gunshots and one brain. Who knows? Next month, I'll jump into a new topic. I don't know what yet, so it's a great time for you to e-mail me suggestions!

And don't be stingy, go check out the real Zombie Dice game from Steve Jackson Games: www.sjgames.com. I know you're dying to try it!

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 his tech site www.AskDaveTaylor.com.