14
\$\begingroup\$

Inspired by reddit.

Write a program which plays Hangman.

  • The program chooses a random word from a list of N words, where N > 2.
  • The word list can be provided to the program in any way you choose.
  • At each iteration

    • Print out the state of the game using underscores for letters not yet discovered:

    H _ N _ _ _ N

    • Print the number of remaining attempts

    10

    • Read a letter from stdin, and update the state of the game, subtracting an attempt if they guess an incorrect letter.

    A (input)

    H A N _ _ A N

    10

    • Repeat until all letters are guessed or attempts reaches 0
  • Use any language
  • Fewest number of characters wins.
  • Drawing the gallows is not necessary, but will earn you upvotes and kudos.
\$\endgroup\$
4
  • \$\begingroup\$ Can I let each word in the list be the same number of characters? \$\endgroup\$ Commented Apr 23, 2011 at 15:04
  • \$\begingroup\$ Do the letters in the output have to be separated by spaces? \$\endgroup\$
    – Lowjacker
    Commented Apr 23, 2011 at 15:44
  • \$\begingroup\$ @Peter Of The Corn: you should assume that the word list is arbitrary \$\endgroup\$
    – NNN
    Commented Apr 23, 2011 at 16:28
  • \$\begingroup\$ @Lowjacker: the spaces improve the legibility of consecutive underscores, otherwise it's hard to count how many letters they represent. \$\endgroup\$
    – NNN
    Commented Apr 23, 2011 at 16:29

10 Answers 10

8
\$\begingroup\$

Darn, I thought it said "fewest number of lines wins." I'm not going to win any fewest-character contests here, but this Common Lisp program is only one line.

(let ((words (list "that" "help" "rent" "chair" "octopus" "monitor" "manual" "speakers" "onomatopoeia" "regardless" "irresponsible" "cornerstone"))) (let ((word (nth (random (length words)) words))) (format t "~a~%" (funcall (defun play (word current remaining-attempts) (progn (if (not (find #\_ current)) (return-from play "You win!")) (if (equalp remaining-attempts 0) (return-from play "You lose!")) (format t "~a~%~d~%" current remaining-attempts) (let ((guess (char (read-line) 0)) (index 0) (found nil)) (loop for letter across word do (if (equalp guess letter) (progn (setf (char current index) letter) (setf found t))) (setf   index (+ index 1))) (if found (play word current remaining-attempts) (play word current (- remaining-attempts 1)))))) word (map 'string #'(lambda (c) #\_) word) 10))))
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I'm upvoting you because I'm pretty sure you're being intentionally humorous :-) \$\endgroup\$
    – Dr. Pain
    Commented Apr 29, 2011 at 19:25
8
\$\begingroup\$

Python 3.

from random,sys import *
w=choice(*argv)
L=set(w)
a=10
while L and a:
 print(" ".join("_"if x in L else x for x in w),a)
 try:L-=set(input()[0])
 except:a-=1

I prefer this one though: longer but nicer.

import random
w=random.choice(list(open("/usr/dict/words")))[:-1]
L=set(w)
a=10
while L and a:
 print(" ".join("_"if x in L else x for x in w),a)
 try:L.remove(input()[0])
 except:a-=1
print w
\$\endgroup\$
1
  • \$\begingroup\$ If I didn't have to print a as well, I could use the * twice: print(*("_"if x in L else x for x in w)) \$\endgroup\$
    – badp
    Commented Apr 28, 2011 at 10:28
6
\$\begingroup\$

Ruby 1.9, 134 132 120 117 108 107

Word list provided in ARGV. The words and the entered letters must match in case.

r=w=$*.sample
t=10
loop{puts [*w.tr(r,?_).chars]*' ',t
t>0&&r>''?w[l=STDIN.gets[0]]?r=r.tr(l,''):t-=1:exit}
\$\endgroup\$
4
\$\begingroup\$

c++ (-headers)

struct h{h(char a):b(a){}char operator()(char c,char d){return d!='_'?d:c==b?c:'_';}char b;};

int main(int a,char**b){
srand(time(0));string c=*(b+rand()%(a-1)+1),d(c.size(),'_'),e;
char f=10,g;
while(f){
cout<<"> ";cin>>g;e=d;
transform(c.begin(),c.end(),d.begin(),d.begin(),h(g));if(e==d)--f;
cout<<d<<endl<<(int)f<<endl;if(d==c)break;
}return 0;}

cat /usr/dict/words | xargs hangman

\$\endgroup\$
3
  • \$\begingroup\$ The "> " prompt for input is not necessary for the solution, I just added it in the question to indicate that it was input, as many languages provide a prompt such as this. \$\endgroup\$
    – NNN
    Commented Apr 23, 2011 at 16:54
  • \$\begingroup\$ @drspod You should edit the question to reflect that, then. \$\endgroup\$
    – Lowjacker
    Commented Apr 23, 2011 at 19:24
  • \$\begingroup\$ edited to clarify \$\endgroup\$
    – NNN
    Commented Apr 23, 2011 at 22:49
2
\$\begingroup\$

Python

import random

DEFAULT_ATTEMPTS = 10

def print_word(word, uncovered):
    for c in word:
        if c not in uncovered:
            c = '_'
        print c,
    print ''

def get_letter():
    letter = None
    while letter is None:
        letter = raw_input('> ')
        if len(letter) != 1:
            print 'Letters must be 1 character.  Try again.'
            letter = None
    return letter

if __name__ == '__main__':
    import sys

    if len(sys.argv) != 2: sys.exit(1)
    with open(sys.argv[1], 'r') as f:
        words = [word.strip() for word in f.readlines() if word.strip()]

    word = random.choice(words)
    uncovered = set([' '])
    attempts = DEFAULT_ATTEMPTS

    while attempts > 0 and any(letter not in uncovered for letter in word):
        print_word(word, uncovered)
        print attempts

        letter = get_letter()
        if letter in uncovered:
            print 'You have already tried that letter.'
        elif letter in word:
            print 'You got it!'
        else:
            print 'Wrong!'
            attempts -= 1

        uncovered.add(letter)

    if attempts == 0:
        print 'You lose!',
    else:
        print 'You win!'
    print 'The phrase was "%s".' % word

I didn't really try for the fewest characters, just wanted to make it as small as possible without sacrificing anything.

\$\endgroup\$
3
2
\$\begingroup\$

Perl, 112 char. I feel like I can do better - perhaps I'll try again later

$_=$ARGV[rand@ARGV];$a=10;while($a&&/[a-z]/){print map/[A-Z]/?$_:'_',split'';$x=<STDIN>;chop$x;s/$x/$x/ig||$a--}

Words are given on the command line, letters typed upper case

\$\endgroup\$
3
  • \$\begingroup\$ Down to 107 $_=$ARGV[rand@ARGV];$a=10;while($a&&/[a-z]/){$y=$_;$y=~y/a-z/_/;print$y;$x=<STDIN>;chop$x;s/$x/$x/ig||$a--} \$\endgroup\$
    – user1379
    Commented Apr 23, 2011 at 21:54
  • 3
    \$\begingroup\$ You could just edit your original answer. \$\endgroup\$
    – Lowjacker
    Commented Apr 23, 2011 at 22:03
  • 1
    \$\begingroup\$ This doesn't display the number of remaining attempts. \$\endgroup\$
    – Lowjacker
    Commented Apr 23, 2011 at 22:27
2
\$\begingroup\$

Clojure

This is 400 bytes gzipped, which is still quite a lot, probably because of how Clojure handles mutable state.

(def m ["will" "work" "for" "food"])
(def w (nth m (rand-int (count m))))
(def *s* (atom (replicate (count w) "_")))
(def *a* (atom 10))

(defn g [s a]
  (str (apply str (interpose " " s)) "\n" a))

(loop [n (read-line)]
  (if (some (set n) w)
    (swap! *s* (fn [s]
                 (map 
                   (fn [i]
                     (if (= n (str (nth w i)))
                       n
                       (nth s i)))
                   (range 0 (count s)))))
    (swap! *a* dec))

  (println (g (deref *s*) (deref *a*))) 

  (if (and (< 0 (deref *a*)) (some #{"_"} (deref *s*)))
    (recur (read-line))))
\$\endgroup\$
2
\$\begingroup\$

C# 370

using System;namespace h{class P{static void Main(string[]a){int c=10,d,l;char y=' ';string w=a[new Random().Next(a.Length)];l=w.Length;char[]x=new char[l];for(d=-1;++d<l;x[d]='-');while(c>0){for(d=-1;++d<l;x[d]=(y==w[d]||x[d]!='-')?w[d]:x[d]);Console.WriteLine(new string(x)+" "+c);if(w==new string(x))return;y=Console.ReadKey(true).KeyChar;if(!w.Contains(y+""))c--;}}}

wordlist as argument

\$\endgroup\$
1
\$\begingroup\$

VB.NET


I haven't tried shrinking it yet, but:
First shrinking:
Second shrinking (3759 characters):

Module Hangman
    Sub Main()
        Dim m As Int32, w = "banana|apple|pear|dog|cat|orange|monkey|programming|hangman".Split("|")(New Random().Next(9)), g = "", e = "", x, c As Char, f As Boolean, r = Sub(z) Console.Write(z), p = Sub(y, h) Console.SetCursorPosition(y, h), a = Sub() Console.Clear(), q = Function() Console.ReadKey(1), d = Sub()
                                                                                                                                                                                                                                                                                                                          r("       +--------+S       |        |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S                |S   ---------------------".Replace("S", vbCrLf))
                                                                                                                                                                                                                                                                                                                          p(0, 2)
                                                                                                                                                                                                                                                                                                                          r(String.Join(vbCrLf, "    /------\S    | O   O|S    \  ... /S     ------ S        |   S        |   S        |   S        |   S -------+-------S        |   S        |   S        |   S       / \  S      /   \  S     /     \  S    /       \  ".Split("S").Take(m * 4)))
                                                                                                                                                                                                                                                                                                                      End Sub
        Console.CursorVisible = 0
        Do
            a()
            d()
            p(30, 10)
            f = 0
            For Each x In w
                If g.Contains(x) Then
                    r(x)
                Else
                    r(" ")
                    f = 1
                End If
                Console.CursorTop += 1
                Console.CursorLeft -= 1
                r("_")
                Console.CursorTop -= 1
                r(" ")
            Next
            If Not f Then
                a()
                d()
                p(30, 10)
                r("You win! Press any key to close.")
                q()
                End
            End If
            p(30, 13)
            r(e)
            Do
                c = q().KeyChar
            Loop Until Char.IsLetter(c)
            If g.Contains(c) Then
                e = "You have already guessed that letter."
            Else
                g &= c
                If w.Contains(c) Then
                    e = "There is a" & If("aehilmnorsx".Contains(c), "n", "") & " """ & c & """ in the word."
                Else
                    e = "There is no """ & c & """ in the word. Try again."
                    m += 1
                End If
            End If
        Loop Until m = 4
        a()
        d()
        p(30, 10)
        r("You lose! Press any key to close.")
        q()
    End Sub
End Module
\$\endgroup\$
3
  • \$\begingroup\$ Is all the indentation really required? \$\endgroup\$
    – Lowjacker
    Commented Apr 24, 2011 at 0:19
  • \$\begingroup\$ it makes it easier to read, doesn't it? \$\endgroup\$ Commented Apr 24, 2011 at 0:33
  • \$\begingroup\$ As in blocks? No, it's not required, but I don't count it as characters. \$\endgroup\$
    – Ry-
    Commented Apr 24, 2011 at 2:32
0
\$\begingroup\$

Powershell, 125 bytes

$w=$h=$args|random|% t*y
for($n=10){$w-replace"[ $h]",'_'-join' ';$n
if(!$h+!$n){break}$n-=($c=Read-Host)-notin$h
$h=$h-ne$c}

Less golfed test script:

$f = {

$word=$hidden=$args|random|% toCharArray    # let $word and $hidden are a random word chars
#$word                                      # uncomment this to cheating
for($n=10){                                 # forever for with init section
    $word-replace"[ $hidden]",'_'-join' '   # display the word with hidden letters
    $n                                      # display $n
    if(!$hidden+!$n){break}                 # break loop if hidden array is empty or n equal to 0
    $n-=($c=Read-Host)-notin$hidden         # input $c from user, decrease $n if $c does not in $hidden array
    $hidden=$hidden-ne$c                    # recreate $hidden array with removed $c
}

}

$words = gc .\wordlist.txt
&$f $words

Output example when the guessing player has lost:

_ _ _ _ _ _ _ _
10
i
_ _ _ _ _ _ _ _
9
e
_ _ e _ _ _ _ e
9
o
o _ e _ _ o _ e
9
a
o _ e _ _ o _ e
8
q
o _ e _ _ o _ e
7
q
o _ e _ _ o _ e
6
q
o _ e _ _ o _ e
5
q
o _ e _ _ o _ e
4
q
o _ e _ _ o _ e
3
q
o _ e _ _ o _ e
2
q
o _ e _ _ o _ e
1
q
o _ e _ _ o _ e
0

Output example when the guessing player has win:

_ _ _ _ _ _ _ _ _ _
10
e
_ _ _ _ e _ _ _ _ _
10
o
_ o _ _ e _ _ _ _ _
10
i
_ o _ _ e _ _ i _ _
10
a
_ o _ _ e _ _ i a _
10
l
_ o _ _ e _ _ i a l
10
c
c o _ _ e _ c i a l
10
m
c o m m e _ c i a l
10
t
c o m m e _ c i a l
9
r
c o m m e r c i a l
9
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.