File Coverage

blib/lib/App/Greple/wordle.pm
Criterion Covered Total %
statement 47 153 30.7
branch 0 68 0.0
condition 0 3 0.0
subroutine 16 33 48.4
pod 0 15 0.0
total 63 272 23.1


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