Hangman wordgame golf

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.
• Can I let each word in the list be the same number of characters? – Peter Olson Apr 23 '11 at 15:04
• Do the letters in the output have to be separated by spaces? – Lowjacker Apr 23 '11 at 15:44
• @Peter Of The Corn: you should assume that the word list is arbitrary – drspod Apr 23 '11 at 16:28
• @Lowjacker: the spaces improve the legibility of consecutive underscores, otherwise it's hard to count how many letters they represent. – drspod Apr 23 '11 at 16:29

Ruby 1.9, 134132120117108 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}  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))))  • I'm upvoting you because I'm pretty sure you're being intentionally humorous :-) – Dr. Pain Apr 29 '11 at 19:25 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  • 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)) – badp Apr 28 '11 at 10:28 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 • 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. – drspod Apr 23 '11 at 16:54 • @drspod You should edit the question to reflect that, then. – Lowjacker Apr 23 '11 at 19:24 • edited to clarify – drspod Apr 23 '11 at 22:49 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. 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 • 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--} – user1379 Apr 23 '11 at 21:54
• You could just edit your original answer. – Lowjacker Apr 23 '11 at 22:03
• This doesn't display the number of remaining attempts. – Lowjacker Apr 23 '11 at 22:27

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))

(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*)))


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

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

• Is all the indentation really required? – Lowjacker Apr 24 '11 at 0:19
• it makes it easier to read, doesn't it? – Nate Koppenhaver Apr 24 '11 at 0:33
• As in blocks? No, it's not required, but I don't count it as characters. – Ry- Apr 24 '11 at 2:32

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