Work the Shell

Scissors, Paper or Rock?

Dave Taylor

Issue #274, February 2017

I've spent a lot of time in this column looking at the sky—whether it was a Martian lander or a phase of the moon program, lots of math, lots of interesting code. Now let's land back on Earth and tackle a simple, straightforward challenge that has nothing to do with asteroids, gravitational anomalies or wormholes—well, hopefully not.

In this article, I'm going to tackle a children's game that's extraordinarily complicated, with many variations, and the programming task is going to be quite tricky. Just kidding! Rock Paper Scissors (or RPS, as it's known) is pretty darn easy to simulate because there aren't really many variants or possible outcomes.

If you've never played it before, it's a one-vs.-one game where each person secretly chooses one of three possible options (rock, paper or, you guessed it, scissors). The players reveal their choices simultaneously, and then there are rules about what beats what. For example, scissors beats paper because “scissors cut paper”, and rock beats scissors because “rock beats scissors”. If both players pick the same option, it's a tie and the game proceeds.

Although you can play it as a one-off, it's also generally played as a best of three to even things out slightly, although if everything's completely random, you'll win 33.33% of the time. For any given choice, there's a 1/3 chance that you'll have a tie, where both players pick the same thing, a 1/3 chance that you'll win, and a 1/3 chance that you'll lose.

The World Rock Paper Scissors Society

Except, in the real game, it turns out that there's psychology involved too. In fact, according to the World Rock, Paper, Scissors Society (worldrps.com), rock is chosen 35.4%, paper 35% of the time and scissors only 29.6% of the time. Got it?

For the first version of the program, however, let's stick with a completely random choice. The easy way to choose a random number between 1 and 3 in a Linux shell script is to use the variable $RANDOM like this:

compchoice=$(( ($RANDOM % 3) + 1 ))

The % is a modulus function and causes the random integer to be divided by 3, resulting in a 0..2 value. Add one, and you've got the 1...3 value. Easy enough.

With a simple shell array, you can add the name of the choice (remember, arrays start at index 0):

declare -a RPS; RPS=(nothing rock paper scissors)

Then the choice name is specified simply as:

choicename=${RPS[$compchoice]}

Those three lines are good enough for a tiny script where the computer can choose randomly between rock, paper and scissors:

declare -a RPS; RPS=(nothing rock paper scissors)
compchoice=$(( ($RANDOM % 3) + 1 ))
echo "The computer chose ${RPS[$compchoice]}"
exit 0

Easy, but not very glamorous:

$ sh rps.sh
The computer chose rock
$

It's considerably more fun to have the computer prompt users for their selection, then “choose” its own and decide who won.

Making It into a Game

Interactivity is easily added by prompting users to choose whether they want rock, paper or scissors using a numeric value. Even better, you can prompt them using the same numeric values you're using internally:

echo -n "Please choose (1 = rock / 2 = paper / 3 = scissors): "
read choice

It's not a particularly onerous task to add interactivity, eh?

Now you need to compare answers and generate a result message. This is best done in a function, either standalone or by including an output string and tracking win/loss. I'll go for overkill (of course), so here's my function:

results() {
   # output results of the game, increment wins if appropriate
   echo ""
   if [ $choice = $compchoice ] ; then
    echo "You both chose $choicename. TIED!"

   # rock beats scissors. paper beats rock. scissors beat paper.
   #  OR: 1 beats 3, 2 beats 1, and 3 beats 2.

   elif [ $choice -eq 1 -a $compchoice -eq 3 ] ; then
     echo "Your rock beats the computer's scissors! Huzzah!!"
     wins=$(( $wins + 1 ))
   elif [ $choice -eq 2 -a $compchoice -eq 1 ] ; then
     echo "Your paper beats the computer's rock! Hurray!"
     wins=$(( $wins + 1 ))
   elif [ $choice -eq 3 -a $compchoice -eq 2 ] ; then
     echo -n "Your scissors cut - and beat - the computer's "
     echo "paper! YAY!"
     wins=$(( $wins + 1 ))
   elif [ $choice -eq 3 -a $compchoice -eq 1 ] ; then
     echo "The computer's rock beats your scissors! Boo."
   elif [ $choice -eq 1 -a $compchoice -eq 2 ] ; then
     echo "The computer's paper beats your rock! Ptoi!"
   elif [ $choice -eq 2 -a $compchoice -eq 3 ] ; then
     echo -n "The computer's scissors cut - and beat - "
     echo "your paper! Bummer."
   else
     echo "Huh? choice=$choice and compchoice=$computer"
   fi
}

It's straightforward, just a lot of typing. But really, that's 95% of the program. All you need is a looping mechanism so that you're “stuck” in the program until you get sick of the game—I mean ready to wrap things up.

Notice that the above code tracks wins, but not total games played; that'll have to be done in the main code, which, of course, is pretty straightforward because of how much of the code is pushed into the results() function:

echo "Rock, paper, scissors..."
echo "(quit by entering 'q' to see your results)"
while [ true ] ; do
  echo ""
  echo -n "Choose (1 = rock / 2 = paper / 3 = scissors): "
  read choice
  if [ "$choice" = "q" -o "$choice" = "quit" -o -z "$choice" ]
  then
    echo ""
    echo "Done. You played $games games, and won $wins of 'em."
    exit 0
  fi
  compchoice=$(( ($RANDOM % 3) + 1 ))
  choicename=${RPS[$compchoice]}
  games=$(( $games + 1 ))
  results
done

A quick run reveals that scissors isn't a bad strategy when the game is picking completely randomly:

$ sh rps.sh
Choose (1 = rock / 2 = paper / 3 = scissors): 3
Your scissors cut - and beat - the computer's paper! YAY!
$

When I tried it, I had a surprisingly longer-term result: an all-scissors strategy produced a 50% win rate (six games out of 12). Statistically that's unlikely if the computer really is picking randomly, but sometimes random is not so random.

Let's look at choosing paper:

$ sh rps.sh
Choose (1 = rock / 2 = paper / 3 = scissors): 2
The computer's scissors cut - and beat - your paper! Bummer.
$

In fact, playing all paper won only four of 14 games on a trial, and rock, the most popular choice? That produces a win rate of three out of 14—worse than paper!

Matching Probabilities

The biggest change you could make to this program to match the “real” choice statistics is to stop picking randomly and instead reflect the percentages that the Rock Paper Scissors Society publishes: rock is chosen 35.4%, paper 35% and scissors only 29.6% of the time.

The easiest way to model that is to choose a random number between 1–1000 and then say that 1–354 is rock, 355–705 is paper, and 706–1000 is scissors. Instead of a single line where the number is being chosen, a function would be well written, and it's pretty darn easy.

The other area you can expand this is to add a few more possibilities, and I bet most everyone reading this knows how to add “lizard” and “Spock” to the mix. Not sure? Here's how a five-object RPS game works: www.samkass.com/theories/RPSSL.html.

So there you have it. Scientific? Not really. But, uh, rock, paper, scissors—come on!

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 or reach him through his tech Q&A site: www.AskDaveTaylor.com.