#!/usr/bin/perl -w use strict; use Getopt::Std; my %opts=(); my $usage="usage: [-hprv] [-l <#>] [-w ] $0\n"; die $usage unless(getopts("hl:prvw:",\%opts)); die $usage if(@ARGV); if($opts{h}) { print $usage,<<'EOD'; -h show this help message -l <#> deal with <#>-letter words (default: 5) -p only show perfect (in-place) letters, not out-of-place letters -r require a real word be guessed -v require all guesses to match everything learned so far -w specify the word to guess as (entering "#" as your word will tell you how many possible words are left) (entering "?" as your word will suggest a word to guess) (entering "@" as your word will show what letters are possible) EOD exit 0; } my $wordlen=5; if($opts{l}) { die "invalid word length" unless($opts{l} =~ /^[1-9]\d*$/s); $wordlen=$opts{l}; } my $onlyperfect=$opts{p}; my $requirereal=$opts{r}; my $requirevalid=$opts{v}; my $target=""; if($opts{w}) { die "invalid target word" unless($opts{w} =~ /^[a-z]+$/s); $target=$opts{w}; $wordlen=length($target); } # Pick a dictionary word my @words=(); open(DICT,"<",($ENV{DICT} or "/usr/share/dict/words")) or die "open: $!"; while(defined(my $line=)) { push @words,$1 if($line =~ /^([a-z]{$wordlen})\n$/s); } close(DICT) or die; if($target) { push @words,$target unless(grep {$_ eq $target} @words); } else { $target=$words[int(rand(@words))]; } # Calculate the stats my %letters=(); foreach my $letter (split //,$target) { $letters{$letter}=0 unless($letters{$letter}); $letters{$letter}++; } my @valid=@words; while(defined(my $line=)) { chomp $line; if("#" eq $line) { if(1==@valid) { print "There is only 1 possible word\n"; } else { print "There are ",scalar(@valid)," possible words\n"; } next; } if("?" eq $line) { my @suggestions=grep {$_ ne $target} @valid; if(@suggestions) { print "How about ",$suggestions[rand(@suggestions)],"?\n"; } else { print "There's only one option left!\n"; } next; } if("\@" eq $line) { if(1==@valid) { print "There is only 1 possible word\n"; } else { my %poss=(); map {map {$poss{$_}=1} split //,$_} @valid; print "Possible letters: ".join("",sort keys %poss)."\n"; } next; } $line=lc($line); unless($line =~ /^[a-z]{$wordlen}$/s) { print STDERR "Your guess should be a $wordlen-letter word\n"; next; } if(($requirereal or $requirevalid) and not grep {$_ eq $line} @words) { print STDERR "That's not a real word\n"; next; } if($requirevalid and not grep {$_ eq $line} @valid) { print STDERR "That can't be the answer\n"; next; } my @guess=split //,$line; my %lettersleft=%letters; # Mark the perfect letters (in place) first my $incorrect=0; for(my $i=0;$i<@guess;$i++) { if(substr($target,$i,1) eq $guess[$i]) { $lettersleft{$guess[$i]}--; $guess[$i]=uc($guess[$i]); } else { $incorrect++; } } unless($incorrect) { print "---CORRECT!---\n"; last; } my @out=(); foreach my $letter (@guess) { if($letter eq uc($letter)) { push @out,$letter; } elsif($lettersleft{$letter}) { push @out,$onlyperfect?" ":$letter; $lettersleft{$letter}--; } else { push @out," "; } } print @out,"\n"; # First throw out any non-perfect matches for(my $i=0;$i<$wordlen;$i++) { if($guess[$i] eq uc($guess[$i])) { @valid=grep {lc($guess[$i]) eq substr($_,$i,1)} @valid; } else { @valid=grep {$guess[$i] ne substr($_,$i,1)} @valid; } } unless($onlyperfect) { # Calculate minimum count of each letter guessed my %minmax=map {($_,[0,$wordlen])} "a".."z"; map {$minmax{lc($_)}[0]++} grep /[A-Za-z]/,@out; # Set maximum to number seen if we saw a space for this guessed letter for(my $i=0;$i<$wordlen;$i++) { $minmax{$guess[$i]}[1]=$minmax{$guess[$i]}[0] if(" " eq $out[$i]); } # For each word, count number of letters and throw out word if max @valid=grep { my %count=map {($_,0)} "a".."z"; map {$count{$_}++} split //,$_; not grep {$count{$_}<$minmax{$_}[0] or $minmax{$_}[1]<$count{$_}} "a".."z"; } @valid; } } 0;