| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | use v5.14; | 
| 2 | 1 |  |  | 1 |  | 579 | use warnings; | 
|  | 1 |  |  |  |  | 3 |  | 
| 3 | 1 |  |  | 1 |  | 4 | use utf8; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 20 |  | 
| 4 | 1 |  |  | 1 |  | 530 |  | 
|  | 1 |  |  |  |  | 12 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 5 |  |  |  |  |  |  | our $VERSION = "0.11"; | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | use Data::Dumper; | 
| 8 | 1 |  |  | 1 |  | 500 | use List::Util qw(shuffle max); | 
|  | 1 |  |  |  |  | 5595 |  | 
|  | 1 |  |  |  |  | 52 |  | 
| 9 | 1 |  |  | 1 |  | 6 | use Try::Tiny; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 75 |  | 
| 10 | 1 |  |  | 1 |  | 422 | use Getopt::EX::Colormap qw(colorize ansi_code); | 
|  | 1 |  |  |  |  | 1589 |  | 
|  | 1 |  |  |  |  | 50 |  | 
| 11 | 1 |  |  | 1 |  | 489 | use Text::VisualWidth::PP 0.05 'vwidth'; | 
|  | 1 |  |  |  |  | 16330 |  | 
|  | 1 |  |  |  |  | 58 |  | 
| 12 | 1 |  |  | 1 |  | 364 | use App::Greple::wordle::word_all    qw(@word_all %word_all); | 
|  | 1 |  |  |  |  | 19313 |  | 
|  | 1 |  |  |  |  | 59 |  | 
| 13 | 1 |  |  | 1 |  | 575 | use App::Greple::wordle::word_hidden qw(@word_hidden); | 
|  | 1 |  |  |  |  | 66 |  | 
|  | 1 |  |  |  |  | 135 |  | 
| 14 | 1 |  |  | 1 |  | 409 | use App::Greple::wordle::game; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 85 |  | 
| 15 | 1 |  |  | 1 |  | 330 | use App::Greple::wordle::util qw(); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 27 |  | 
| 16 | 1 |  |  | 1 |  | 314 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 19 |  | 
| 17 |  |  |  |  |  |  | use Getopt::EX::Hashed; { | 
| 18 | 1 |  |  | 1 |  | 407 | has answer  => '   =s ' , default => $ENV{WORDLE_ANSWER} ; | 
|  | 1 |  |  |  |  | 5867 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 19 |  |  |  |  |  |  | has index   => ' n =i ' , default => $ENV{WORDLE_INDEX} ; | 
| 20 |  |  |  |  |  |  | has try     => ' x =i ' , default => 6 ; | 
| 21 |  |  |  |  |  |  | has total   => '   =i ' , default => 30 ; | 
| 22 |  |  |  |  |  |  | has random  => '   !  ' , default => 0 ; | 
| 23 |  |  |  |  |  |  | has series  => ' s =i ' , default => 1 ; | 
| 24 |  |  |  |  |  |  | has compat  => '      ' , action  => sub { $_->{series} = 0 } ; | 
| 25 |  |  |  |  |  |  | has keymap  => '   !  ' , default => 1 ; | 
| 26 |  |  |  |  |  |  | has result  => '   !  ' , default => 1 ; | 
| 27 |  |  |  |  |  |  | has correct => '   =s ' , default => "\N{U+1F389}" ; # PARTY POPPER | 
| 28 |  |  |  |  |  |  | has wrong   => '   =s ' , default => "\N{U+1F4A5}" ; # COLLISION SYMBOL | 
| 29 |  |  |  |  |  |  | has debug   => '   !  ' ; | 
| 30 |  |  |  |  |  |  | } | 
| 31 |  |  |  |  |  |  | no Getopt::EX::Hashed; | 
| 32 | 1 |  |  | 1 |  | 199 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 3 |  | 
| 33 |  |  |  |  |  |  | my $app = shift; | 
| 34 |  |  |  |  |  |  | my $argv = shift; | 
| 35 | 0 |  |  | 0 | 0 |  | use Getopt::Long qw(GetOptionsFromArray Configure); | 
| 36 | 0 |  |  |  |  |  | Configure qw(bundling no_getopt_compat pass_through); | 
| 37 | 1 |  |  | 1 |  | 661 | $app->getopt($argv) || die "Option parse error.\n"; | 
|  | 1 |  |  |  |  | 8426 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 38 | 0 |  |  |  |  |  | $app; | 
| 39 | 0 | 0 |  |  |  |  | } | 
| 40 | 0 |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | use Date::Calc qw(Delta_Days); | 
| 42 |  |  |  |  |  |  | my($mday, $mon, $year, $yday) = (localtime(time))[3,4,5,7]; | 
| 43 |  |  |  |  |  |  | Delta_Days(2021, 6, 19, $year + 1900, $mon + 1, $mday); | 
| 44 | 1 |  |  | 1 |  | 534 | } | 
|  | 1 |  |  |  |  | 4482 |  | 
|  | 1 |  |  |  |  | 1587 |  | 
| 45 | 0 |  |  | 0 |  |  |  | 
| 46 | 0 |  |  |  |  |  | my $app = shift; | 
| 47 |  |  |  |  |  |  | for ($app->{index}) { | 
| 48 |  |  |  |  |  |  | $_   = int rand @word_hidden if $app->{random}; | 
| 49 |  |  |  |  |  |  | $_ //= _days; | 
| 50 | 0 |  |  | 0 | 0 |  | $_  += _days if /^[-+]/; | 
| 51 | 0 |  |  |  |  |  | } | 
| 52 | 0 | 0 |  |  |  |  | if (my $answer = $app->{answer}) { | 
| 53 | 0 |  | 0 |  |  |  | $app->{index} = undef; | 
| 54 | 0 | 0 |  |  |  |  | $word_all{$answer} or die "$answer: wrong word\n"; | 
| 55 |  |  |  |  |  |  | } else { | 
| 56 | 0 | 0 |  |  |  |  | if ($app->{series} > 0) { | 
| 57 | 0 |  |  |  |  |  | srand($app->{series}); | 
| 58 | 0 | 0 |  |  |  |  | @word_hidden = shuffle @word_hidden; | 
| 59 |  |  |  |  |  |  | } | 
| 60 | 0 | 0 |  |  |  |  | $app->{answer} = $word_hidden[ $app->{index} ]; | 
| 61 | 0 |  |  |  |  |  | } | 
| 62 | 0 |  |  |  |  |  | } | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 0 |  |  |  |  |  | my $app = shift; | 
| 65 |  |  |  |  |  |  | my $answer = $app->{answer}; | 
| 66 |  |  |  |  |  |  | my @re = map | 
| 67 |  |  |  |  |  |  | { sprintf "(?<=^.{%d})%s", $_, substr($answer, $_, 1) } | 
| 68 |  |  |  |  |  |  | 0 .. length($answer) - 1; | 
| 69 | 0 |  |  | 0 | 0 |  | my $green  = join '|', @re; | 
| 70 | 0 |  |  |  |  |  | my $yellow = "[$answer]"; | 
| 71 |  |  |  |  |  |  | my $black  = "(?=[a-z])[^$answer]"; | 
| 72 | 0 |  |  |  |  |  | map { ( '--re' => $_ ) } $green, $yellow, $black; | 
|  | 0 |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | } | 
| 74 | 0 |  |  |  |  |  |  | 
| 75 | 0 |  |  |  |  |  | my $app = shift; | 
| 76 | 0 |  |  |  |  |  | my $label = 'Greple::wordle'; | 
| 77 | 0 |  |  |  |  |  | return $label if not defined $app->{index}; | 
|  | 0 |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | sprintf('%s %s%s', | 
| 79 |  |  |  |  |  |  | $label, | 
| 80 |  |  |  |  |  |  | $app->{series} == 0 ? '' : sprintf("%d-", $app->{series}), | 
| 81 | 0 |  |  | 0 | 0 |  | $app->{index}); | 
| 82 | 0 |  |  |  |  |  | } | 
| 83 | 0 | 0 |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | ###################################################################### | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | my $app = __PACKAGE__->new or die; | 
| 87 | 0 | 0 |  |  |  |  | my $game; | 
| 88 |  |  |  |  |  |  | my $interactive; | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | sprintf '%d: ', $game->attempt + 1; | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | my($mod, $argv) = @_; | 
| 94 |  |  |  |  |  |  | $app->parseopt($argv)->setup; | 
| 95 |  |  |  |  |  |  | $game = App::Greple::wordle::game->new(answer => $app->{answer}); | 
| 96 |  |  |  |  |  |  | push @$argv, $app->patterns; | 
| 97 | 0 |  |  | 0 | 0 |  | if ($interactive = -t STDIN) { | 
| 98 |  |  |  |  |  |  | push @$argv, '--interactive', ('/dev/stdin') x $app->{total}; | 
| 99 |  |  |  |  |  |  | select->autoflush; | 
| 100 |  |  |  |  |  |  | say $app->title; | 
| 101 | 0 |  |  | 0 | 0 |  | print prompt(); | 
| 102 | 0 |  |  |  |  |  | } | 
| 103 | 0 |  |  |  |  |  | } | 
| 104 | 0 |  |  |  |  |  |  | 
| 105 | 0 | 0 |  |  |  |  | local $_ = $_; | 
| 106 | 0 |  |  |  |  |  | my $chomped = chomp; | 
| 107 | 0 |  |  |  |  |  | print ansi_code("{CHA}{CUU}") if $chomped; | 
| 108 | 0 |  |  |  |  |  | print ansi_code(sprintf("{CHA(%d)}", | 
| 109 | 0 |  |  |  |  |  | max(11, vwidth($_) + length(prompt()) + 2))); | 
| 110 |  |  |  |  |  |  | print s/(?<=.)\z/\n/r for @_; | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | say colorize('#6aaa64', uc $game->answer); | 
| 114 | 0 |  |  | 0 | 0 |  | } | 
| 115 | 0 |  |  |  |  |  |  | 
| 116 | 0 | 0 |  |  |  |  | printf "\n%s %d/%d\n\n", $app->title, $game->attempt, $app->{try}; | 
| 117 | 0 |  |  |  |  |  | say $game->result; | 
| 118 |  |  |  |  |  |  | } | 
| 119 | 0 |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | my $word = lc s/\n//r; | 
| 121 |  |  |  |  |  |  | if (not $word_all{$word}) { | 
| 122 |  |  |  |  |  |  | command($word) or respond $app->{wrong}; | 
| 123 | 0 |  |  | 0 | 0 |  | $_ = ''; | 
| 124 |  |  |  |  |  |  | } else { | 
| 125 |  |  |  |  |  |  | $game->try($word); | 
| 126 |  |  |  |  |  |  | } | 
| 127 | 0 |  |  | 0 | 0 |  | } | 
| 128 | 0 |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | my $word = shift; | 
| 130 |  |  |  |  |  |  | my @cmd = split ' ', $word or return; | 
| 131 |  |  |  |  |  |  | my @word = @word_all; | 
| 132 | 0 |  |  | 0 | 0 |  | state @remember; | 
| 133 | 0 | 0 |  |  |  |  | $cmd[0] =~ /^u(niq)?$/ and unshift @cmd, 'hint'; | 
| 134 | 0 | 0 |  |  |  |  |  | 
| 135 | 0 |  |  |  |  |  | while (@cmd) { | 
| 136 |  |  |  |  |  |  | local $_ = shift @cmd; | 
| 137 | 0 |  |  |  |  |  | try { | 
| 138 |  |  |  |  |  |  | if    ($_ eq '|')   {} | 
| 139 |  |  |  |  |  |  | elsif (/^d$/)       { | 
| 140 |  |  |  |  |  |  | $app->{debug} ^= 1; | 
| 141 |  |  |  |  |  |  | printf 'Debug %s', $app->{debug} ? 'on' : 'off'; | 
| 142 | 0 |  |  | 0 | 0 |  | return; | 
| 143 | 0 | 0 |  |  |  |  | } | 
| 144 | 0 |  |  |  |  |  | elsif (/^\?$/)      { help(); return } | 
| 145 | 0 |  |  |  |  |  | elsif (/^!!$/)      { @word = @remember } | 
| 146 | 0 | 0 |  |  |  |  | elsif (/^h(int)?$/) { @word = choose($game->hint, @word) } | 
| 147 |  |  |  |  |  |  | elsif (/^u(niq)?$/) { @word = grep { !/(.).*\1/i } @word } | 
| 148 | 0 |  |  |  |  |  | elsif (/^=(.+)/)    { @word = choose(includes($1), @word) } | 
| 149 | 0 |  |  |  |  |  | elsif (/^!(.+)/)    { @word = choose("^(?!.*[$1])", @word) } | 
| 150 |  |  |  |  |  |  | elsif (/\W/)        { @word = choose($_, @word); } | 
| 151 | 0 | 0 |  | 0 |  |  | else  { return } | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | 1; | 
| 153 | 0 |  |  |  |  |  | } or do { | 
| 154 | 0 | 0 |  |  |  |  | warn "ERROR: $_" if $app->{debug}; | 
| 155 | 0 |  |  |  |  |  | return /^[a-z]+$/i ? 0 : 1; | 
| 156 |  |  |  |  |  |  | }; | 
| 157 | 0 |  |  |  |  |  | } | 
|  | 0 |  |  |  |  |  |  | 
| 158 | 0 |  |  |  |  |  | if (@word == 0) { | 
| 159 | 0 |  |  |  |  |  | warn "No match\n"; | 
| 160 | 0 |  |  |  |  |  | return 1; | 
|  | 0 |  |  |  |  |  |  | 
| 161 | 0 |  |  |  |  |  | } | 
| 162 | 0 |  |  |  |  |  | @remember = @word; | 
| 163 | 0 |  |  |  |  |  | do { | 
| 164 | 0 |  |  |  |  |  | local $, = ' '; | 
| 165 | 0 |  |  |  |  |  | say $game->hint_color(@word); | 
| 166 | 0 | 0 |  |  |  |  | }; | 
| 167 | 0 | 0 |  |  |  |  | 1; | 
| 168 | 0 | 0 |  |  |  |  | } | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | my $message = << "    END"; | 
| 171 | 0 | 0 |  |  |  |  | #   d      debug | 
| 172 | 0 |  |  |  |  |  | ?      help | 
| 173 | 0 |  |  |  |  |  | h      show hint | 
| 174 |  |  |  |  |  |  | u      uniq | 
| 175 | 0 |  |  |  |  |  | !!     repeat last result | 
| 176 | 0 |  |  |  |  |  | =<str> include characters | 
| 177 | 0 |  |  |  |  |  | !<str> exclude characters | 
| 178 | 0 |  |  |  |  |  | END | 
| 179 |  |  |  |  |  |  | print $message =~ s/^\s*(#.*)\n//gr; | 
| 180 | 0 |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | '^' . join '', map { "(?=.*$_)" } $_[0] =~ /./g; | 
| 183 |  |  |  |  |  |  | } | 
| 184 | 0 |  |  | 0 | 0 |  |  | 
| 185 |  |  |  |  |  |  | my $p = shift; | 
| 186 |  |  |  |  |  |  | $p =~ s/([A-Z])/[^$1]/g; | 
| 187 |  |  |  |  |  |  | warn "> $p\n" if $app->{debug}; | 
| 188 |  |  |  |  |  |  | grep /$p/, @_; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | if ($game->solved) { | 
| 192 |  |  |  |  |  |  | respond $app->{correct} x ($app->{try} - $game->attempt + 1); | 
| 193 | 0 |  |  |  |  |  | show_result if $app->{result}; | 
| 194 |  |  |  |  |  |  | exit 0; | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  | if (length) { | 
| 197 | 0 |  |  | 0 | 0 |  | if ($game->attempt >= $app->{try}) { | 
|  | 0 |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | show_answer; | 
| 199 |  |  |  |  |  |  | exit 1; | 
| 200 |  |  |  |  |  |  | } | 
| 201 | 0 |  |  | 0 | 0 |  | $app->{keymap} and respond $game->keymap; | 
| 202 | 0 |  |  |  |  |  | } | 
| 203 | 0 | 0 |  |  |  |  | print prompt(); | 
| 204 | 0 |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | 1; | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 0 | 0 |  | 0 | 0 |  |  | 
| 209 | 0 |  |  |  |  |  | mode function | 
| 210 | 0 | 0 |  |  |  |  |  | 
| 211 | 0 |  |  |  |  |  | define GREEN  #6aaa64 | 
| 212 |  |  |  |  |  |  | define YELLOW #c9b458 | 
| 213 | 0 | 0 |  |  |  |  | define BLACK  #787c7e | 
| 214 | 0 | 0 |  |  |  |  |  | 
| 215 | 0 |  |  |  |  |  | option default \ | 
| 216 | 0 |  |  |  |  |  | -i --need 1 --no-filename \ | 
| 217 |  |  |  |  |  |  | --cm 555/GREEN  \ | 
| 218 | 0 | 0 |  |  |  |  | --cm 555/YELLOW \ | 
| 219 |  |  |  |  |  |  | --cm 555/BLACK | 
| 220 | 0 |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | # --interactive is set in initialize() when stdin is a tty | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | option --interactive \ | 
| 224 |  |  |  |  |  |  | --if 'head -1' \ | 
| 225 |  |  |  |  |  |  | --begin    __PACKAGE__::check   \ | 
| 226 |  |  |  |  |  |  | --end      __PACKAGE__::inspect \ | 
| 227 |  |  |  |  |  |  | --epilogue __PACKAGE__::show_answer |