100 Guess the Word Before It s Too Late: Hangman


#100 Guess the Word Before It's Too Late: Hangman

A classic word game with a macabre metaphor, hangman is nonetheless popular and enjoyable. In the game, you guess letters that might be in the hidden word, and each time you guess incorrectly, the man hanging on the gallows has an additional body part drawn in. Make too many wrong guesses, and the man is fully illustrated , so not only do you lose, but, well, you presumably die too. Not very pleasant!

However, the game itself is fun, and writing it as a shell script proves surprisingly easy.

The Code

 #!/bin/sh # hangman - A rudimentary version of the hangman game. Instead of showing a #   gradually embodied hanging man, this simply has a bad guess countdown. #   You can optionally indicate the initial distance from the gallows as the only #   arg. wordlib="/usr/lib/games/long-words.txt" randomquote="$HOME/bin/randomquote.sh"     # Script #76 empty="\."      # we need something for the sed [set] when $guessed="" games=0 if [ ! -r $wordlib ] ; then   echo " 
 #!/bin/sh # hangman - A rudimentary version of the hangman game. Instead of showing a # gradually embodied hanging man, this simply has a bad guess countdown. # You can optionally indicate the initial distance from the gallows as the only # arg. wordlib="/usr/lib/games/long-words.txt" randomquote="$HOME/bin/randomquote.sh" # Script #76 empty="\." # we need something for the sed [set] when $guessed="" games=0 if [ ! -r $wordlib ] ; then echo "$0: Missing word library $wordlib" >&2 echo "(online: http://www.intuitive.com/ wicked /examples/long-words.txt" >&2 echo "save the file as $wordlib and you're ready to play!)" >&2 exit 1 fi while [ "$guess" != "quit" ] ; do match="$($randomquote $wordlib)" # pick a new word from the library if [ $games -gt 0 ] ; then echo "" echo "*** New Game! ***" fi games="$(($ games + 1))" guessed="" ; guess="" ; bad=${1:-6} partial="$(echo $match  sed "s/[^$empty${guessed}]/-/g")" while [ "$guess" != "$match" -a "$guess" != "quit" ] ; do echo "" if [ ! -z "$guessed" ] ; then echo -n "guessed: $guessed, " fi echo "steps from gallows: $bad, word so far: $partial" echo -n "Guess a letter: " read guess echo "" if [ "$guess" = "$match" ] ; then echo "You got it!" elif [ "$guess" = "quit" ] ; then sleep 0 # a 'no op' to avoid an error message on 'quit' elif [ $(echo $guess  wc -c  sed 's/[^[:digit:]]//g') -ne 2 ] ; then echo "Uh oh: You can only guess a single letter at a time" elif [ ! -z "$(echo $guess  sed 's/[[:lower:]]//g')" ] ; then echo "Uh oh: Please only use lowercase letters for your guesses" elif [ -z "$(echo $guess  sed "s/[$empty$guessed]//g")" ] ; then echo "Uh oh: You have already tried $guess" elif [ "$(echo $match  sed "s/$guess/-/g")" != "$match" ] ; then guessed="$guessed$guess" partial="$(echo $match  sed "s/[^$empty${guessed}]/-/g")" if [ "$partial" = "$match" ] ; then echo "** You've been pardoned!! Well done! The word was \"$match\"." guess="$match" else echo "* Great! The letter \"$guess\" appears in the word!" fi elif [ $bad -eq 1 ] ; then echo "** Uh oh: you've run out of steps. You're on the platform... <SNAP!>" echo "** The word you were trying to guess was \"$match\"" guess="$match" else echo "* Nope, \"$guess\" does not appear in the word." guessed="$guessed$guess" bad=$(($bad - 1)) fi done done exit 0 
: Missing word library $wordlib" >&2 echo "(online: http://www.intuitive.com/wicked/examples/long-words.txt" >&2 echo "save the file as $wordlib and you're ready to play!)" >&2 exit 1 fi while [ "$guess" != "quit" ] ; do match="$($randomquote $wordlib)" # pick a new word from the library if [ $games -gt 0 ] ; then echo "" echo "*** New Game! ***" fi games="$(($games + 1))" guessed="" ; guess="" ; bad=${1:-6} partial="$(echo $match sed "s/[^$empty${guessed}]/-/g")" while [ "$guess" != "$match" -a "$guess" != "quit" ] ; do echo "" if [ ! -z "$guessed" ] ; then echo -n "guessed: $guessed, " fi echo "steps from gallows: $bad, word so far: $partial" echo -n "Guess a letter: " read guess echo "" if [ "$guess" = "$match" ] ; then echo "You got it!" elif [ "$guess" = "quit" ] ; then sleep 0 # a 'no op' to avoid an error message on 'quit' elif [ $(echo $guess wc -c sed 's/[^[:digit:]]//g') -ne 2 ] ; then echo "Uh oh: You can only guess a single letter at a time" elif [ ! -z "$(echo $guess sed 's/[[:lower:]]//g')" ] ; then echo "Uh oh: Please only use lowercase letters for your guesses" elif [ -z "$(echo $guess sed "s/[$empty$guessed]//g")" ] ; then echo "Uh oh: You have already tried $guess" elif [ "$(echo $match sed "s/$guess/-/g")" != "$match" ] ; then guessed="$guessed$guess" partial="$(echo $match sed "s/[^$empty${guessed}]/-/g")" if [ "$partial" = "$match" ] ; then echo "** You've been pardoned!! Well done! The word was \"$match\"." guess="$match" else echo "* Great! The letter \"$guess\" appears in the word!" fi elif [ $bad -eq 1 ] ; then echo "** Uh oh: you've run out of steps. You're on the platform... <SNAP!>" echo "** The word you were trying to guess was \"$match\"" guess="$match" else echo "* Nope, \"$guess\" does not appear in the word." guessed="$guessed$guess" bad=$(($bad - 1)) fi done done exit 0

How It Works

The tests in this script are all interesting and worth examination. Consider this test to see if the player has entered more than a single letter as his or her guess:

 elif [ $(echo $guess  wc -c  sed 's/[^[:digit:]]//g') -ne 2 ] ; then 

Why test for the value 2 rather than 1? Because the entered value has a carriage return appended by the read statement, and so it has two letters if it's correct, not one. The sed in this statement strips out all nondigit values, of course, to avoid any confusion with the leading tab that wc likes to emit.

Testing for lowercase is straightforward: Remove all lowercase letters from guess and see if the result is zero (empty) or not:

 elif [ ! -z "$(echo $guess  sed 's/[[:lower:]]//g')" ] ; then 

And, finally, to see if the user has guessed the letter already, transform the guess such that any letters in guess that also appear in the guessed variable are removed, and see if the result is zero (empty) or not:

 elif [ -z "$(echo $guess  sed "s/[$empty$guessed]//g")" ] ; then 

Apart from all these tests, however, the trick behind getting hangman to work is to translate into dashes all occurrences in the original word of each guessed letter and then to compare the result to the original word. If they're different, the guessed letter is in that word:

 elif [ "$(echo $match  sed "s/$guess/-/g")" != "$match" ] ; then 

One of the key ideas that made it possible to write hangman was that the partially filled-in word shown to the player, the variable partial , is rebuilt each time a correct guess is made. Because the variable guessed accumulates each letter guessed by the player, a sed transformation that translates into a dash each letter in the original word that is not in the guessed string does the trick:

 partial="$(echo $match  sed "s/[^$empty${guessed}]/-/g")" 

Running the Script

The hangman game has one optional argument: If you specify a numeric value as a parameter, it will use that as the number of incorrect guesses allowed, rather than the default of 6.

The Results

 $  hangman  steps from gallows: 6, word so far: ------------- Guess a letter:  e  * Great! The letter "e" appears in the word! guessed: e, steps from gallows: 6, word so far: -e--e-------- Guess a letter:  i  * Great! The letter "i" appears in the word! guessed: ei, steps from gallows: 6, word so far: -e--e--i----- Guess a letter:  o  * Great! The letter "o" appears in the word! guessed: eio, steps from gallows: 6, word so far: -e--e--io---- Guess a letter:  u  * Great! The letter "u" appears in the word! guessed: eiou, steps from gallows: 6, word so far: -e--e--iou--- Guess a letter:  m  * Nope, "m" does not appear in the word. guessed: eioum, steps from gallows: 5, word so far: -e--e--iou--- Guess a letter:  n  * Great! The letter "n" appears in the word! guessed: eioumn, steps from gallows: 5, word so far: -en-en-iou--- Guess a letter:  r  * Nope, "r" does not appear in the word. guessed: eioumnr, steps from gallows: 4, word so far: -en-en-iou--- Guess a letter:  s  * Great! The letter "s" appears in the word! guessed: eioumnrs, steps from gallows: 4, word so far: sen-en-ious-- Guess a letter:  t  * Great! The letter "t" appears in the word! guessed: eioumnrst, steps from gallows: 4, word so far: sententious-- Guess a letter:  l  * Great! The letter "l" appears in the word! guessed: eioumnrstl, steps from gallows: 4, word so far: sententiousl- Guess a letter:  y  ** You've been pardoned!! Well done!  The word was "sententiously". *** New Game! *** steps from gallows: 6, word so far: ---------- Guess a letter:  quit  

Hacking the Script

Obviously it's quite difficult to have the fancy guy-hanging-on-the-gallows graphic if we're working with a shell script, so we use the alternative of counting "steps to the gallows" instead. If you were motivated, however, you could probably have a series of predefined "text" graphics, one for each step, and output them as the game proceeds. Or you could choose a nonviolent alternative of some sort , of course!

Note that it is possible to pick the same word twice, but with the default word list containing 2,882 different words, there's not much chance of that occurring. If this is a concern, however, the line where the word is chosen could also save all previous words in a variable and screen against them to ensure that there aren't any repeats.

Finally, if you were motivated, it'd be nice to have the guessed letters list be sorted alphabetically . There are a couple of approaches to this, but I think I'd try to use sedsort .




Wicked Cool Shell Scripts. 101 Scripts for Linux, Mac OS X, and Unix Systems
Wicked Cool Shell Scripts
ISBN: 1593270127
EAN: 2147483647
Year: 2004
Pages: 150
Authors: Dave Taylor

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net