Hangman
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:
0----+----1----+----2----+----3
|
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)
(cls)
(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)))
(display-gibbet)
(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.
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)) (cond ((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!")) (else (pretty-print str) (let ((c (read-char))) (cond ((eof-object? c) (pretty-print "Bye")) ((char=? c #\newline) (loop h letters)) (else (if (member c letters) (begin (for-each (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))) (begin (pretty-print (car h)) (loop (cdr h) letters)))))))))))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"; } #else 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]; } #endif 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) { break; } word[i++] = blank_char; ++changes; } 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::time(&tm); std::srand(static_cast<unsigned int>(tm)); bool quit = false; do { 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; break; } if (ch >= 'A' && ch <= 'Z') { if (used.find_first_of(ch) != std::string::npos) { std::cout << "You have already used that letter" << std::endl; continue; } used += ch; if (word.find_first_of(ch) != std::string::npos) { blanked = word; if (!blankWord(blanked, '_', used)) { quit = endGame(blanked, "You won! Congratulations!", level); break; } } else { if (++level == LEVELS) { quit = endGame(word, "You lost!", level); break; } } } } } while (!quit); return 0; }[…] 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. […]
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: print(game_display.format(**status)) guess = input('\nEnter a letter: ').strip() status[guess] = guess if guess in letters: letters.remove(guess) else: part,char = bodyparts.pop(0) status[part] = char else: print(game_display.format(**status)) if bodyparts: print("You win!") else: print("Sorry, you lose! The word was", game_word) play = 'y' while play == 'y': game() play = input('\nPlay again? ')[0].strip().lower() print('Thanks for playing!')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): letters.insert(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: printLine(word,letters) c = raw_input("Guess a letter: ") if (re.match("^[a-zA-Z]$",c)== None): continue if ((c in word) and ( not c in letters)): letters = addLetters(word,letters,c) else: n +=1 printGibbet(n) 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 TrueI 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))
try:
while(letterCount < len(word)):
input = raw_input("Guess the %d letter: " %(letterCount+1))
if word[letterCount] == input:
print "Good Work"
letterCount +=1
else:
print hangmanGenerator.next()
except:
print "You lost! better luck next time"
print "Game Completed!"
if __name__ == '__main__':
hm = HangMan()
hm.PlayGame()