Perfect Hangman in reverse

5

2

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.

NRGdallas

Posted 2012-07-23T21:52:23.457

Reputation: 707

Will there be no spaces in the input? – beary605 – 2012-07-24T04:46:20.957

4I'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. – ceased to turn counterclockwis – 2012-07-24T07:10:28.397

@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. – Matt – 2012-07-24T10:22:38.123

2ispell 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? – milo5b – 2012-07-24T12:50:23.137

What if (assuming 4 answers) two people solve with method A and two with method B, does everyone get the +1000 penalty? – Gaffi – 2012-07-24T14:51:18.690

Also running time and "game steps" have a definition a bit too loose: running time on what machine? Is the time taken to ask the first input (word's length) considered as a step or could it take as long as needed and still be eligible for the 100 points deduction (as long as the other steps are completed in the 5 secs timeframe)? – milo5b – 2012-07-24T14:58:34.070

Could we please have an example of expected output and input? (i.e. "7 lives remaining" or "7", "1 life remaining" or "1 lives remaining" is acceptable?) – milo5b – 2012-07-24T18:22:28.380

Please can we have some clarification on the format of output strings (if they are mandatory). Obviously people actually printing the word "guess" have a longer program than those who are not. – Leigh – 2012-07-26T06:32:46.267

Since alot of people decided to just use "guess" lets go ahead and make it the standard on this one. "X Lives Remaining" will be the other standard output string. and then either "WIN" or "LOSE" for the final output.

the 5 second rule just simply assumes that once given input, it completes that step of the process within 5 seconds on a reasonable machine (will be tested on an i7 930 2.9mhz 4xcore)

tiebreaks for the program in case of the same counts, simply have your program pick the one with the most possible words matching that guess, if still a tie, pick the one alphabetically first. – NRGdallas – 2012-07-27T17:01:06.780

This contest will end in 1 week. August 3rd, at noon, CST.

at that time, the next step of the contest will be posted. – NRGdallas – 2012-07-27T17:07:51.390

one other issue I am seeing, after an incorrect guess, your program should recount, removing all words with that letter from the calculations. a good example of where this would CHANGE the guesses would be a list of words such as (good, google, open, amp, ant) its first guess would be "o"(3 values), if incorrect, it should remove good, google, and open from the wordlist it is considering, resulting in the next guess of "a" - ant, amp, NOT a guess of g. – NRGdallas – 2012-07-27T17:19:29.440

also, you should NOT prompt for the correct word before beginning. only the number of letters, and then the result after each word. – NRGdallas – 2012-07-27T17:21:03.260

regarding tiebreaks, the above statement "tiebreaks for the program in case of the same counts, simply have your program pick the one with the most possible words matching that guess, if still a tie, pick the one alphabetically first." - can simply be read as "alphabetically first", as the first tiebreak is kinda the point of the program... Don't ask where I got it from >.>

in addition; words from the wordlist can assume to not include any symbols such as apostrophes etc, and it can be assumed that a word will be chosen from the list. – NRGdallas – 2012-07-27T18:17:36.467

Answers

4

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

tmartin

Posted 2012-07-23T21:52:23.457

Reputation: 3 917

How come you didn't manage to get engineer correctly? – Leigh – 2012-07-25T13:23:22.907

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 – tmartin – 2012-07-25T13:34:18.450

I think the requirement is that after you lose a life, you re-analyse the possible words. – Leigh – 2012-07-25T13:49:27.970

That's fixed now – tmartin – 2012-07-25T14:54:09.210

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. – NRGdallas – 2012-07-27T18:16:41.820

3

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

shiona

Posted 2012-07-23T21:52:23.457

Reputation: 2 889

Yes, I fixed that. Thanks for letting me know, although I just noticed :X. – beary605 – 2012-07-26T05:13:38.877

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." – shiona – 2012-07-26T05:40:53.547

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. – ceased to turn counterclockwis – 2012-07-27T22:19:11.977

@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) – shiona – 2012-07-28T10:29:40.017

3

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

beary605

Posted 2012-07-23T21:52:23.457

Reputation: 3 904

Not sure if it is allowed to hardcode the length into the program, and not ask the user. – Leigh – 2012-07-25T12:23:52.693

@Leigh: The line a=input() asks for the number of letters. – beary605 – 2012-07-25T15:16:21.417

My mistake, sorry (not so hot with python). l=8 is lives, not length :) – Leigh – 2012-07-25T15:22:02.833

@Leigh: It's ok, I find it hard to understand golfed code too :P – beary605 – 2012-07-25T15:35:37.267

You can remove f=open('a') and open the file directly in the loop, save yourself 4 chars (5 including newlines). – Casey Kuball – 2012-07-26T04:57:20.923

You don't re-analyse correctly after an incorrect guess. You should remove all words that contain the incorrect letter to get an accurate count of the remaining possible letters. – Leigh – 2012-07-26T21:16:48.197

@Leigh: Does b=''.join(re.findall(c.replace('_','[A-Z]'),b)) not work well enough? A fault with this challenge is that there's no test cases, so I can't test my program :X – beary605 – 2012-07-26T23:46:56.833

There's cases where yours does better (since will continue to use letter weights including words that have an invalid letter in them), and there's cases where it does worse (since it's possible to guess letters that can't possibly exist in the word) – Leigh – 2012-07-27T07:12:48.897

2

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!

Leigh

Posted 2012-07-23T21:52:23.457

Reputation: 261

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 – milo5b – 2012-07-25T14:35:46.550

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);" – milo5b – 2012-07-25T14:46:08.487

@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! – Leigh – 2012-07-25T15:20:57.967

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 – milo5b – 2012-07-26T08:38:14.020

You can save two more bytes by removing <? at the start and invoke the code with the proper parameter. – hakre – 2012-07-27T09:18:16.730