5
\$\begingroup\$

This is similar to other hang-man style problems already posted, except, in this case, the computer is the one guessing. Computers are logical, smart, and consider self-preservation to be important, so there are a few RULES for how computers will guess.

The user will think of any word, that will be contained in the FULL Ispell word list. some of these may be upwards of 10-15 letters. The wordlist is simply the full ispell wordlist (english, and others, all combined), and may be placed in a folder with the program.

The computer will be given 8 lives. The computer will start by simply asking for an input of however many characters the word is. (example game below). After that, the computer will begin guessing, following the ruleset below!

  1. the computer will analyze all possible word outputs, and determine what letter guess has the most possible correct answers, and guess that letter. It will not duplicate any prior guesses.
  2. the computer will then ask for an updated word input, determine if it was correct at all, or wrong, and if wrong, will deduct 1 life.
  3. Lastly, the computer will state the lives remaining.

    EXAMPLE GAME
    User picks the word "Engineer"
    Computer asks for Letter count, User replies 8.
    Computer searches for most likely letter to be used in 8 letter words, determines it is letter H (not correct). Guesses H.
    User replies _ _ _ _ _ _ _ _
    Computer outputs "7 lives remaining" and then picks the next most likely letter, an E
    User replies e _ _ _ _ e e _
    computer outputs "7 lives remaining" and then recalculates based on its current knowledge what is the most likely next letter. 
    Game continues until computer will state WIN, or LOSE.
    

SCORING: every program should follow the EXACT same method to arrive at the final answer. If yours follows the order of every other program, you receive -100 points. If yours is different (aka wrong), you receive 1000 points.

each byte of code is worth 1 point. a 30 letter code, is 30 points.

if your code manages to play each step of the game in less than 5 seconds, award yourself -100 points.

The winner will be chosen 1 week from the first answer, and a new question related to this one, will be asked. Obviously those with the best code here will have a head start on the new one!

EDIT: forgot to make note that as in golf, the lowest score wins.

\$\endgroup\$
  • \$\begingroup\$ Will there be no spaces in the input? \$\endgroup\$ – beary605 Jul 24 '12 at 4:46
  • 4
    \$\begingroup\$ I'm tempted to post code which does something completely unrelated to the task, while there are no other answers (and my program therefore works exactly the same as all programs!). "Different" is a ridiculous definition of "wrong", in particular if you don't give any proper reference to what's right. \$\endgroup\$ – ceased to turn counterclockwis Jul 24 '12 at 7:10
  • \$\begingroup\$ @leftaroundabout the question states that the computer should guess the letter that has the "most possible correct answers" only in cases where multiple letter choices have exactly the same chance of being right should two programs ever differ. I don't know how often that will be though. \$\endgroup\$ – Matt Jul 24 '12 at 10:22
  • 2
    \$\begingroup\$ ispell list contains a lot of words ending with <'s>. Would these be acceptable words, or can we clean the wordlist, excluding the above mentioned words? \$\endgroup\$ – milo5b Jul 24 '12 at 12:50
  • \$\begingroup\$ What if (assuming 4 answers) two people solve with method A and two with method B, does everyone get the +1000 penalty? \$\endgroup\$ – Gaffi Jul 24 '12 at 14:51
4
\$\begingroup\$

K, 196 215 240 (40 points)

When reduced to a single line it's 240 chars but I've added newlines here for readability. The question doesn't state exactly what the output needs to look like so I've come up with something fairly minimal.

r:0#t:#[c:#w:_0:0;"?"]
a:_:'0:`a;
l:8;
while[~(l=0)|(c=0);
  q:{x@&~x in y}[>#:'=,/a:a@&a like\:t;r];
  if[1=#a;-1 a;j:-1"WIN";exit 0];
  -1"Guess ",u:*q;
  r,:u;
  $[|/u=w;[c-:+/u=w;@[`t;&u=w;:;u]];-1($l-:1)," lives remaining"];
  -1 t;
  $[0=l;-1"LOSE";0=c;j;]]

The wordlist is a file called a in same directory as the code.

$ q hm.k -q
engineer
Guess e
e????ee?
Guess s
7 lives remaining
e????ee?
Guess i
e??i?ee?
engineer
WIN

EDIT: It now reanalyses the word on each step and determines which is the most likely letter based on previous guesses.

EDIT2: If there is only one possible answer (after reanalysis) the computer wins, rather than continuing to guess each individual letter

\$\endgroup\$
  • \$\begingroup\$ How come you didn't manage to get engineer correctly? \$\endgroup\$ – Leigh Jul 25 '12 at 13:23
  • \$\begingroup\$ My code just cycles through the most popular characters for 8 letter words and guesses each in turn. I'm changing a few things now \$\endgroup\$ – tmartin Jul 25 '12 at 13:34
  • \$\begingroup\$ I think the requirement is that after you lose a life, you re-analyse the possible words. \$\endgroup\$ – Leigh Jul 25 '12 at 13:49
  • \$\begingroup\$ That's fixed now \$\endgroup\$ – tmartin Jul 25 '12 at 14:54
  • \$\begingroup\$ just letting you know; I was a bit vague on tie-breaks for the algorithm in the description, please read the above comment on the question for a better idea. Basically tiebreaks should pick the alphabetically first. \$\endgroup\$ – NRGdallas Jul 27 '12 at 18:16
3
\$\begingroup\$

Python (386 352 328 294 300 chars, 100 points)

This was fun to do. Produces an error to stop the program. When inputting please give your word in uppercase.

Put the Ispells word list in a file called "a", no file extensions.

import re
a=input()
l=8
b=open('a').read().upper()
c=0
L=map(chr,range(65,91))
while 1:
 E=max((b.count(i),i)for i in L)[1];L.remove(E);print E,'\n',l,"lives remaining";d=c;c=raw_input();l-=d==c;b=''.join(re.findall(c.replace('_','[%s]'%sum(L)),b))
 if len(b)==a:print"WIN",b;Z
 if l==0:print"LOSE";Z
\$\endgroup\$
  • \$\begingroup\$ Not sure if it is allowed to hardcode the length into the program, and not ask the user. \$\endgroup\$ – Leigh Jul 25 '12 at 12:23
  • \$\begingroup\$ @Leigh: The line a=input() asks for the number of letters. \$\endgroup\$ – beary605 Jul 25 '12 at 15:16
  • \$\begingroup\$ My mistake, sorry (not so hot with python). l=8 is lives, not length :) \$\endgroup\$ – Leigh Jul 25 '12 at 15:22
  • \$\begingroup\$ @Leigh: It's ok, I find it hard to understand golfed code too :P \$\endgroup\$ – beary605 Jul 25 '12 at 15:35
  • \$\begingroup\$ You can remove f=open('a') and open the file directly in the loop, save yourself 4 chars (5 including newlines). \$\endgroup\$ – Casey Kuball Jul 26 '12 at 4:57
3
\$\begingroup\$

Haskell, 781 - 100n

Takes the dictionary file name as command line argument. Notices when you try to cheat.

words tested and able to beat this on my 90k English word dictionary: 'zappa' and quite a few words of form '.u..y', such as putty

It used to have nice prompts and more descriptive output but I ended up golfing those away. It sanitizes dictionary file, filtering out words including non-alphabets and making it all lower case. Removing that would save me 48 bytes, but it's functionality I don't like to remove.

I believe this is fast enough for the -100, but I haven't tested it with anything bigger than that 90k words.

Also my first haskell program that really does something in IO. All comments welcome.

import System.Environment
import Data.List
import Data.Char
import Data.Ord
import qualified Data.Set as S
import Control.Monad
a=['a'..'z']
r=putStrLn
z=show
k=length
fb False=0
fb True=1
lp l=map fst.filter((==l).snd).zip[1..]
gu m@(g,p,t)l=fmap(\e->
 (l:g,S.filter((==e).lp l)p,t-(fb$null e)))$fmap(lp l)(putStrLn[l]>>getLine)
y m@(g,p,t)=case((S.size)p,t)of{(_,0)->r"I lost :<";(1,_)->r$'=':S.toList p!!0;
(0,_)->r"Cheater >:|";_->r(z t++" turns left.")>>
(gu m$fst.last.sortBy(comparing snd).filter(not.flip elem g.fst).zip a$
S.fold(zipWith(+).(\x->[fb$k`elem`x|k<-a]).nub.sort)(repeat 0)p)>>=y}
cg p=fmap(\h->([],S.filter((==(read h)).k)p,8))(r"Word length?">>getLine)
pd n=fmap(S.fromList.filter(all isAlpha).words.map toLower)(readFile n)
main=getArgs>>=pd.(!!0)>>=cg>>=y

Edit, 868 -> 830

Re-thought the input so I was able to golf away the unsafeIO which required not only a long function call but also an import. Took away the stupid prompt nobody has (for this reason I also updated my example run). Made one or two lambdas point-free, the only one remaining is the (\x->[fb$k`elem`x|k<-a]) which gets longer if made point free.

Edit, 830 -> 781

Huge thanks to leftaroundbout here. Removed some unnecessary dos, changed some returns to fmaps and unpacked tuples at binding. I guess I still didn't understand all his advice, but this already got me a long way forward. Unfortunately I had to include one space for indentation to keep my lines under 80 chars, since now I don't have those do blocks with ;s delimiting expressions.

Run with hangover

% ./reversehangman /usr/share/dict/british
Word length?
8
8 turns left.
e
______e_
8 turns left.
s
______e_
7 turns left.
d
______e_
6 turns left.
r
______er
6 turns left.
i
______er
5 turns left.
a
_a____er
5 turns left.
n
_an___er
5 turns left.
o
_an_o_er
5 turns left.
w
_an_o_er
=hangover
\$\endgroup\$
  • \$\begingroup\$ Yes, I fixed that. Thanks for letting me know, although I just noticed :X. \$\endgroup\$ – beary605 Jul 26 '12 at 5:13
  • \$\begingroup\$ Great. I wasn't sure if putting comments as part of my answer was acceptable, but I felt I should try to get the information across somehow. I'm not a big fan of this "Sure you can answer, but you don't have the reputation to comment others' answers." \$\endgroup\$ – shiona Jul 26 '12 at 5:40
  • \$\begingroup\$ You should be able to get a lot of the program logic out of IO. Remember that Haskell is lazy! If you do use IO, always consider that, like every monad, it's also a functor. foo >>= return.bar is more compactly written fmap bar foo, and you usually don't need do{...} at all. — There are a couple of other things to improve, for instance tuples can be unpacked right in the function binding, e.g. y m@(g,p,t)=case(S.size p,t)of...: no need for let(g,p,t)=m in... there! — This fb looks rather suspicious, I don't think it's a good idea. Use guards instead of matching on booleans. \$\endgroup\$ – ceased to turn counterclockwis Jul 27 '12 at 22:19
  • \$\begingroup\$ @leftaroundabout Wow, thanks! I'll definitely look into those later today. I should've at least understood to use m@(g,p,t). I think I don't need the do{...} in main since everything is bound, but how would you implement cg without it? fb is a newly written fromBool :: Integer. I think I need to be able to add or subtract 1 in some cases depending on whether some expression is true. Like newLives = oldLives - fb (null $ length newLetters) \$\endgroup\$ – shiona Jul 28 '12 at 10:29
2
\$\begingroup\$

PHP - 367 431 402 367 321 312 bytes (-100 for flow, -100 for quick turns, 112 total)

Requires PHP 5.4 (short array syntax) and short open tags (saves 3 bytes!) which is on by default with PHP 5.4.

It takes the number of letters in the word as a command line argument.

Example command line php -d short_open_tag=1 golf.php 8

Submitted solution - 312 bytes on disk - there are some deliberate hard newlines in here for pretty output formatting.

<?foreach(file('l',2)as$w)$L[]=strlen($w)^$argv[1]?'':$w;for($l=8,$i=[];$l;){$s=$i+count_chars(join($L));arsort($s);$i[$g=key($s)]=0;$h=chr($g);echo"$h
";$r=count_chars($u=trim(fgets(STDIN)));$r[46]||die('WIN');$L=preg_replace($r[$g]||$l--<0?"/^((?!$u).)*$/i":"/.*$h.*/i",'',$L);echo"$l lives remaining
";}?>LOSE

Before fat-trimming, with some comments to explain what's going on

foreach(file('l',2)as$w) // Words from file called
    $L[]=strlen($w)^$argv[1]?'':$w; // Only words matching wordlen
for($l=8,$i=[];$l;){ // Loop, init lives + used guesses
    $s=$i+count_chars(join($L)); // Character frequency without guesses
    arsort($s); // Most frequent first
    $i[$g=key($s)]=0; // Guess is first item
    $h=chr($g); // Printable output
    echo"$h
"; // Literal newline saves us a byte
    $r=count_chars($u=trim(fgets(STDIN))); // User response
    $r[46]||die('WIN'); // short circuit win, and magic regex below
    $L=preg_replace($r[$g]||$l--<0 ? "/^((?!$u).)*$/i" : "/.*$h.*/i", '', $L);
    echo"$l lives remaining
";
} // End parsing because it's shorter than echoing
?>LOSE

Output with "engineer" - Spaces are not allowed, I'm using full stops instead of underscores due to uh... magic.

The program actually knows the word after 3 guesses, but continues 1 guess at a time anyway to save space.

e
e....ee.
8 lives remaining
s
e....ee.
7 lives remaining
n
en..nee.
7 lives remaining
i
en.inee.
7 lives remaining
g
enginee.
7 lives remaining
r
engineer
WIN

Variation using the same logic as beary605 - 269 bytes, -200 = 69 points.

<?$L=file('l',2);for($l=8,$i=[];$l;){$s=$i+count_chars(strtolower(join($L)));arsort($s);$i[$g=key($s)]=0;$h=chr($g);echo"$h
";$r=count_chars($u=trim(fgets(STDIN)));$r[46]||die('WIN');$r[$g]?$L=preg_replace("/^((?!$u).)*$/i",'',$L):$l--;echo"$l lives remaining
";}?>LOSE

There's some unaddressed comments about how strictly the output/user prompts needs to be, obviously I have taken liberties here as have other solutions.

Edits

Edit 1
Fatal logic error. I wasn't re-analysing correctly when the computer guessed correctly. This got me solving engineer with 5 lives remaining instead of 2, although at the expense of 64 bytes. I can probably improve this later.

Edit 2
Drastically improved matching by switching to a regex based filter. Also means I have to use full stops instead of underscores because the user input is essentially feeding regex patterns into the program.

Edit 3
Remembered to short-circuit evaluation to remove if statements, and use ternary operator to reduce 2 regex statements to 1.

Edit 4
Removed some verbosity with user prompting. Taking initial input on the command line. Removed an unnecessary staging variable. Removed case sensitivity.

Edit 5
Sacrificed efficiency for size, xoring word length with user supplied letter count to indicate inclusion. join is an alias of implode. Moved variable initialisation from outside the while loop to inside a for loop initiator saves me a byte!

\$\endgroup\$
  • \$\begingroup\$ eheh I was trying to solve this in a similar way, but I never really golfed before so my program was over 1KB. This answer will help me improve my golfing skills, although now probably there is no point posting my solution!! +1 \$\endgroup\$ – milo5b Jul 25 '12 at 14:35
  • \$\begingroup\$ btw, I think you could shave off one more byte removing the space after the php tag "<? $l=file('l',2);" becoming "<?$l=file('l',2);" \$\endgroup\$ – milo5b Jul 25 '12 at 14:46
  • \$\begingroup\$ @milo5b: Thanks for the tip on the short tag, you can't do it on long tags and I never use short tags. (My first time golfing too). I've gone one better for saving space and improving logic at the same time by using regex to filter the words. Submit your answer anyway! It's fun! \$\endgroup\$ – Leigh Jul 25 '12 at 15:20
  • \$\begingroup\$ There is no point in submitting it! It's using the same language and same logic as your script, but much worse implementation! I'll submit another challenge, or another language to this if I can find the time. And well done with all the improvements by the way \$\endgroup\$ – milo5b Jul 26 '12 at 8:38
  • \$\begingroup\$ You can save two more bytes by removing <? at the start and invoke the code with the proper parameter. \$\endgroup\$ – hakre Jul 27 '12 at 9:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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