December 20, 2011

We’ll assume a plain ascii VT-100 terminal that responds to the following commands:

(define esc (integer->char 27))
(define (cls) (for-each display `(,esc #\[ #\2 #\J)))
(define (goto r c) (for-each display `(,esc #\[ ,r #\; ,c #\H)))
(define (erase-eol) (for-each display `(,esc #\[ #\K)))

Our game looks like this:

 2    +----+
 |    |    |
 4    |    O         YOU xxx!!!
 |    |   \|/
 6    |   / \
 |    |
 8    +-------
10    _ _ _ _ _ _ _ _ _ _ _ _ _
12    abcdefghijklmnopqrstuvwxyz
 |    Play again (y/n)? X

Simple functions display the gibbet, the current state of the word being guessed, the remaining letters of the alphabet, and the answer:

(define (display-gibbet)
  (goto 2 5) (display "+----+")
  (goto 3 5) (display "| |")
  (goto 4 5) (display "|")
  (goto 5 5) (display "|")
  (goto 6 5) (display "|")
  (goto 7 5) (display "|")
  (goto 8 5) (display "+-------"))

(define (display-word word)
  (goto 10 4) (erase-eol)
  (for-each (lambda (c) (display #\space) (display c)) word))

(define (display-alphabet alphabet)
  (goto 12 5) (erase-eol)
  (for-each display alphabet))

(define (display-answer answer)
  (goto 5 19) (erase-eol)
  (for-each (lambda (c) (display (char-upcase c))) answer))

Displaying the win-or-die message at the end of the game is a bit more involved because that function also asks if the player want to play another game:

(define (display-message win? answer)
  (goto 4 19)
  (if win? (display "YOU WIN!!!")
    (begin (display "YOU DIE!!!")
           (display-answer answer)))
  (goto 13 5) (display "Play again (y/n)? ")
  (let loop ((c (get-key)))
    (cond ((char=? c #\y) #t)
          ((char=? c #\n) #f)
          (else (goto 13 24) (erase-eol)
                (loop (get-key))))))

Get-key and get-letter fetch input from the player:

(define (get-key) (char-downcase (read-char)))

(define (get-letter alphabet)
  (goto 4 19) (erase-eol)
  (let loop ((c (get-key)))
    (cond ((member c alphabet) c)
          (else (goto 4 19) (erase-eol) (loop (get-key))))))

Let’s look now at the top-level program:

(define (hangman)
  (rand (time-second (current-time)))
  (let ((words (read-words "/usr/share/dict/words")))
    (let play ((answer (fortune words)))
      (let loop ((man 0) (word (make-list (length answer) #\_))
                 (alphabet (string->list "abcdefghijklmnopqrstuvwxyz")))
        (display-man man) (display-word word) (display-alphabet alphabet)
        (cond ((not (member #\_ word))
                (when (display-message #t answer) (play (fortune words))))
              ((= 6 man)
                (when (display-message #f answer) (play (fortune words))))
              (else (let ((c (get-letter alphabet)))
                      (if (member c answer)
                          (loop man (insert answer c word) (remove c alphabet))
                          (loop (+ man 1) word (remove c alphabet))))))))))

The program begins by seeding the random number generator; the method used here is specific to Chez Scheme. After reading the word list, the program enters the play loop, which chooses a word and plays a single game. For each game the gibbet is displayed, then the program enters a loop that asks the player for a letter and processes it, as appropriate, until the game is over, updating the display before each letter. Hangman calls read-words to get the wordlist and insert to replace the blanks with a correctly-guessed letter:

(define (read-words filename)
  (with-input-from-file filename (lambda ()
    (let loop ((word (read-line)) (words (list)))
      (if (eof-object? word) (reverse words)
        (let ((word (map char-downcase (string->list word))))
          (if (all? char-alphabetic? word)
              (loop (read-line) (cons word words))
              (loop (read-line) words))))))))

(define (insert answer c word)
  (let loop ((word word) (answer answer) (new (list)))
    (if (null? word) (reverse new)
      (loop (cdr word) (cdr answer)
            (cons (if (char=? c (car answer))
                      (char-upcase c) (car word)) new)))))

We used filter, all?, read-line, rand and fortune from the Standard Prelude. You can see the complete program at http://programmingpraxis.codepad.org/sNnrfd58.

Pages: 1 2

6 Responses to “Hangman”

  1. Axio said

    Not much to see here.

    (define hangman-database
      ;; size of db, db.  Real game would read /usr/share/dict...
      '(5 ("hello" "ectoplasm" "mauger" "subterfuge" "lucent")))
    (define (select-word #!optional (db hangman-database))
      (list-ref (cadr db) (random-integer (car db))))
    (define (game)
      (let* ((w (select-word))
             (l (string-length w))
             (str (make-string l #\_))
             (letters (nub (string->list w) test: char=?))
             (hangman '(head torso left-arm right-arm left-leg right-leg)))
        (let loop ((h hangman) (letters letters))
            ((null? h)
             (pretty-print "Hang on your head Tom Dooley!")
             (pretty-print (list "The word was:" w)))
            ((null? letters)
             (pretty-print w)
             (pretty-print "Lucky guy!"))
              (pretty-print str)
              (let ((c (read-char)))
                  ((eof-object? c) (pretty-print "Bye"))
                  ((char=? c #\newline) (loop h letters))
                    (if (member c letters)
                          (lambda (i)
                            (when (char=? c (string-ref w i))
                              (string-set! str i c)))
                          (iota 0 (1- l)))
                        (loop h (filter (lambda (d) (not (char=? c d))) letters)))
                        (pretty-print (car h))
                        (loop (cdr h) letters)))))))))))
  2. C++ solution, slightly less interactive and rather longer. Procedural, nothing more fancy than a string.

    #include <iostream>
    #include <string>
    #include <cstdlib>
    #include <ctime>
    const int LEVELS = 6;
    //#define TEST
    #ifdef TEST
    char getCh()
      static int k = 0;
      char inputs[]= "EAEHMSTROGB_HNGMNA.";
      return inputs[k++]; 
    std::string getNextWord()
      return "HANGMAN";
    char getCh()
      std::string s;
      std::cin >> s;
      if (s.length() > 0)
        return s[0];
      return ' ';
    std::string getNextWord()
      char words[][20] = { "THE", "QUICK", "BROWN", "FOX", "JUMPS", "OVER", "LAZY", "DOG", "RED", "GREEN", "GROSS", "GRAAL" };
      unsigned int sz = sizeof(words) / sizeof(words[0]);
      return words[rand() % sz];
    void display(const std::string& word_with_blanks, int level)
      //                    012345678901
      char pict[6][13] = { "+--------  \n",
                           "|/      |  \n",
                           "|       o  \n",
                           "|      /#\\ \n",
                           "|      / \\ \n",
                           "|          \n" };
      int blank_line_pos[LEVELS * 2] = { 2, 8,  3, 8,  3, 7,  3, 9,  4, 7,  4, 9};
      for (int i = level; i < LEVELS; ++i)
        pict[blank_line_pos[2 * i]][blank_line_pos[2 * i + 1]] = ' ';
      for (unsigned int i = 0; i < sizeof(pict) / sizeof(pict[0]); ++i)
        std::cout << pict[i];
      for (unsigned int i = 0; i < word_with_blanks.length(); ++i)
        std::cout << word_with_blanks[i] << ' ';
      std::cout << std::endl;
    int blankWord(std::string& word, const char blank_char, const std::string& uncover)
      std::string::size_type i = 0;
      int changes = 0;
      while (i != std::string::npos)
        i = word.find_first_not_of(uncover, i);
        if (i == std::string::npos)
        word[i++] = blank_char;
      return changes;
    bool endGame(const std::string& word, const std::string& message, const int level)
      display(word, level);
      std::cout << message << std::endl;
      std::cout << "Enter . to quit, other input to continue" << std::endl;
      return (getCh() == '.');
    int main(int argc, char** argv)
      time_t tm;
      std::srand(static_cast<unsigned int>(tm));
      bool quit = false;
        int level = 0;
        std::string word = getNextWord();
        std::string blanked = word;
        std::string used = "";
        blankWord(blanked, '_', used);
        while (level < LEVELS)
          display(blanked, level);
          std::cout << "Enter a letter, . to quit." << std::endl;
          char ch = getCh();
          std::cout << "Your input was: " << ch << std::endl;
          if (ch == '.')
            quit = true;
          if (ch >= 'A' && ch <= 'Z')
            if (used.find_first_of(ch) != std::string::npos)
              std::cout << "You have already used that letter" << std::endl;
            used += ch;
            if (word.find_first_of(ch) != std::string::npos)
              blanked = word;
              if (!blankWord(blanked, '_', used))
                quit = endGame(blanked, "You won! Congratulations!", level);
              if (++level == LEVELS)
                quit = endGame(word, "You lost!", level);
      } while (!quit); 
      return 0;
  3. […] today a task that was more practical engineering than theoretical science. Write a game of Hangman. The language is C++ although, for the sake of simplicity, I avoided classes and algorithms. […]

  4. Mike said

    Python 3 version with ascii graphics.

    Uses wordlist from 12dicts, which can be found at http://wordlist.sourceforge.net/

    from operator import methodcaller
    from random import randrange
    from string import ascii_lowercase
    def random_word():
        with open("12dicts/2of12inf.txt", "rt") as wordlist:
            for n, word in enumerate(wordlist, 1):
                if not randrange(n):
                    pick = word
        return pick.strip(' %\n')
    def game():
        game_word = random_word()
        letters = set(game_word)
        word_display = ' '.join(map('{{{}}}'.format, game_word))
        game_display = '''
             +--+     used: {a} {b} {c} {d} {e} {f} {g} {h} {i}
             |  |           {j} {k} {l} {m} {n} {o} {p} {q} {r}
             |  {hd}           {s} {t} {u} {v} {w} {x} {y} {z}
             | {la}{bd}{ra}      
             | {ll} {rl}    word: ''' + word_display  + '''
        bodyparts = [('hd', 'O'), ('bd', '|'), ('la', '/'),
                     ('ra', '\\'), ('ll', '/'), ('rl', '\\')]
        status = {c:'_' for c in ascii_lowercase}
        status.update((p,' ') for p,_ in bodyparts)
        while letters and bodyparts:
            guess = input('\nEnter a letter: ').strip()
            status[guess] = guess
            if guess in letters:
                part,char = bodyparts.pop(0)
                status[part] = char
            if bodyparts:
                print("You win!")
                print("Sorry, you lose!  The word was", game_word)
    play = 'y'
    while play == 'y':
        play = input('\nPlay again? ')[0].strip().lower()
    print('Thanks for playing!')
  5. Diego Giuliani said

    My attempt using python. It uses the description of the exercise as a source of words, but this can be easily changed.

    from random import randint
    import re
    cad =  """
    The player is given a series of blanks, one per letter of a word to be guessed. 
    At each turn the player guesses a letter; if it is in the word, 
    all occurrences of the letter are filled in the appropriate blank, 
    but if the guess is wrong, another body part - traditionally, the head, torso, 
    two arms and two legs, for a total of six - is added to the gibbet. 
    The player wins by guessing all the letters of the word before the hangman adds all the pieces of his body.
    def printLine(word,letters):
        cad = ""
        for i in range(len(word)):
            cad += (word[i] + "." ) if word[i] in letters else "_."
        print cad
    def printGibbet(n):
        gibbet = ["head","torso","left Arm","right Arm","left leg","right leg"]
        print ",".join(gibbet[:n])
    def addLetters(word,letters,c):
        for i in range(len(word)):        
            if (word[i] == c): 
        return letters
    def game():
        # We take a list of words from the first line of text to have something to play with,
        # we could change this to load from a file or hardcode a list of words
        pattern = "^(\w{0,20})([;|,|.]*)$"
        word_list = [re.search(pattern,c).group(1) for c in cad.split() if ((len(c) >= 5) & (re.search(pattern,c) != None ))]
        word = word_list[randint(0,len(word_list) - 1 ) ]
        n = 0    
        letters = []   
        while True:
            c = raw_input("Guess a letter: ")
            if (re.match("^[a-zA-Z]$",c)== None):
            if ((c in word) and ( not c in letters)):
                letters = addLetters(word,letters,c)
                n +=1
                if (n == 6) :
                    print "You loose the word was %s" % word
                    return False
            print "".join(letters)    
            if  ("".join(letters)== word ):
                print "You win! the word was %s " % word
                return True
  6. Mahesh said

    I have hardcoded the

    class HangMan(object):
    def __init__(self):
    self.hangman = [‘head’, ‘torso’, ‘leftarm’, ‘rightarm’, ‘leftleg’, ‘rightleg’]
    self.wordsList = [‘head’, ‘torso’, ‘leftarm’, ‘rightarm’, ‘leftleg’, ‘rightleg’]

    def GetNextHangManPart(self):
    for word in self.hangman:
    ostr = “Wrong guess! you got %s Try Again!” %(word)
    yield ostr

    def PlayGame(self):
    for word in self.wordsList:
    letterCount = 0
    GuessCount = 0
    hangmanGenerator = self.GetNextHangManPart()
    print “New word with %d characters” %(len(word))
    while(letterCount < len(word)):
    input = raw_input("Guess the %d letter: " %(letterCount+1))
    if word[letterCount] == input:
    print "Good Work"
    letterCount +=1
    print hangmanGenerator.next()
    print "You lost! better luck next time"
    print "Game Completed!"

    if __name__ == '__main__':
    hm = HangMan()

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Get every new post delivered to your Inbox.

Join 729 other followers

%d bloggers like this: