File Coverage

blib/lib/Games/Literati.pm
Criterion Covered Total %
statement 446 446 100.0
branch 125 136 91.9
condition 68 85 80.0
subroutine 37 37 100.0
pod 13 18 72.2
total 689 722 95.4


line stmt bran cond sub pod time code
1             package Games::Literati;
2 6     6   401701 use warnings;
  6         54  
  6         194  
3 6     6   27 use strict;
  6         10  
  6         114  
4 6     6   26 use Carp;
  6         9  
  6         322  
5            
6 6     6   122 use 5.008;
  6         17  
7             require Exporter;
8            
9             our @ISA = qw(Exporter);
10             our @EXPORT_GAMES = qw(scrabble superscrabble literati wordswithfriends);
11             our @EXPORT_CONFIG = qw($WordFile $MinimumWordLength);
12             our @EXPORT_OTHER = qw(find %valid);
13             our @EXPORT_INFO = qw(n_rows n_cols numTilesPerHand get_solutions);
14             our @EXPORT_MISC = qw(reduce_hand);
15             our @EXPORT_CUSTOMIZER = (@EXPORT_INFO, 'var_init');
16             our @EXPORT_OK = (@EXPORT_GAMES, @EXPORT_CONFIG, @EXPORT_OTHER, @EXPORT_INFO, @EXPORT_MISC, @EXPORT_CUSTOMIZER);
17             our %EXPORT_TAGS = (
18             'allGames' => [@EXPORT_GAMES],
19             'configGame' => [@EXPORT_CONFIG],
20             'infoFunctions' => [@EXPORT_INFO],
21             'miscFunctions' => [@EXPORT_MISC],
22             'customizer' => [@EXPORT_CUSTOMIZER],
23             'all' => [@EXPORT_OK],
24             ); # v0.032007: add the tags
25            
26             our $VERSION = '0.042';
27             our %valid = ();
28             our @bonus;
29             our @onboard;
30             our %values;
31             our %solutions;
32             our %solution_data;
33             our $words;
34             our $bingo_bonus;
35             our @wilds;
36             our $WordFile = './wordlist';
37             our $GameName = '';
38             our $BoardCols = 15; # v0.032001
39             our $BoardRows = 15; # v0.032001
40             our $MinimumWordLength = 2; # v0.032003
41             our $BingoHandLength = 7; # v0.032005
42            
43             sub scrabble {
44 3     3 1 36339 var_init(15,15,7);
45 3         15 _scrabble_init();
46 3         12 display();
47 3         71 search(shift, shift);
48             }
49            
50             sub superscrabble { # v0.032002
51 3     3 1 50167 var_init(21,21,7);
52 3         17 _superscrabble_init();
53 3         12 display();
54 3         58 search(shift, shift);
55             }
56            
57             sub literati {
58 6     6 1 59100 var_init(15,15,7);
59 6         31 _literati_init();
60 6         23 display();
61 6         112 search(shift, shift);
62             }
63            
64             sub wordswithfriends {
65 4     4 1 76348 var_init(15,15,7);
66 4         18 _wordswithfriends_init();
67 4         16 display();
68 4         73 search(shift, shift);
69             }
70            
71 21 50   21 0 74 sub set_rows($) { $BoardRows = shift if defined $_[0]; }
72 21 50   21 0 76 sub set_cols($) { $BoardCols = shift if defined $_[0]; }
73 28960     28960 1 43141 sub n_cols() { return $BoardCols; }
74 2571     2571 1 9122 sub n_rows() { return $BoardRows; }
75 197449     197449   562449 sub _max_col() { return $BoardCols-1; }
76 155919     155919   405603 sub _max_row() { return $BoardRows-1; }
77 2959     2959   3655 sub _center_col() { return _max_col()/2; } # v0.032003
78 74673     74673   88759 sub _center_row() { return _max_row()/2; } # v0.032003
79 5     5 1 18 sub numTilesPerHand() { return $BingoHandLength; } # v0.032005
80 15     15 1 1863 sub get_solutions() { return %solution_data; } # v0.042
81            
82             sub var_init {
83 24 100   24 1 2069 set_rows($_[0]) if (defined $_[0]);
84 24 100       115 set_cols($_[1]) if (defined $_[1]);
85 24 50 33     180 croak "INVALID rows=$BoardRows, cols=$BoardCols:\n\tFor now, must be an odd square board, such as 15x15 or 17x17, not 16x16 or 15x17.\n" unless ($BoardRows==$BoardCols) && ($BoardRows % 2 == 1); # v0.032003 this restriction prevents difficult calcs for the center square
86 24 100       76 $BingoHandLength = ($_[2]) if (defined $_[2]);
87            
88 24         194 %values = ();
89 24         198 undef $words;
90 24         414 undef @bonus;
91 24         183 undef %solutions; # v0.032002 = prevents accidentally combining solution sets from multiple games
92 24         710 undef %solution_data; # v0.042 = needs same prevention
93            
94 24         73 foreach my $r (0.._max_row) {
95 384         529 foreach my $c (0.._max_col) {
96 6264         7196 undef $bonus[$r][$c];
97             }
98             }
99            
100 24         406 print "Hashing words...\n";
101 24         501 my $fh;
102 24 50       953 open( $fh, $WordFile ) || croak "Cannot open words file \"$WordFile\"\n\t$!";
103 24         717 while (<$fh>) {
104 508         617 chomp;
105 508 100       728 next if length($_) < $MinimumWordLength; # ignore short words (v0.032003)
106 464         628 $valid{$_} = 1;
107 464         464 push @{$words->[length $_]}, $_;
  464         1145  
108             }
109 24         284 close $fh;
110             }
111            
112             sub check {
113 2     2 0 1567 my @wordlist = @{ pop @_ };
  2         6  
114 2         5 for my $w (@wordlist) {
115 4 100 66     61 if (exists($valid{$w}) && ($valid{$w} == 1)) {
116 2         10 print qq|"$w" is valid.\n|;
117             }
118             else {
119 2         6 print qq|"$w" is invalid.\n|;
120             }
121             }
122             }
123            
124             sub find { # deprecated
125 6     6   45 no warnings;
  6         11  
  6         7136  
126 3     3 1 2572 my $args = shift;
127 3         5 my $letters = $args->{letters};
128 3   50     8 my $re = $args->{re} || "//";
129 3   100     10 my $internal = $args->{internal} || 0;
130 3         9 my $len;
131             my $hint;
132 3         0 my $check_letters;
133 3         0 my @results;
134 3         9 my ($min_len, $max_len) = (split ",", $args->{len});
135 3   50     10 $min_len ||= 2;
136 3   50     11 $max_len ||= 7;
137            
138 3 50       6 croak "Not enough letters.\n" unless (length($letters) > 1);
139            
140 3         11 LINE: for (keys %valid) {
141 60         144 $len = length $_;
142 60 100 66     147 next LINE if ($len > $max_len || $len < $min_len);
143 57         72 $check_letters = $letters;
144            
145 57 100       2086 next LINE unless (eval $re);
146 14         30 $hint = "";
147            
148 14         39 for my $l (split //, $_) {
149 44 100 66     296 next LINE unless ( $check_letters =~ s/$l// or
      100        
150             ($check_letters =~ s/\?// and $hint .= "($l)") );
151             }
152 5 100       12 unless ($internal) {
153 3         13 print "$_ $hint\n";
154             }
155             else {
156 2         4 push @results, $_;
157             }
158            
159             }
160 3 100       13 return \@results if $internal;
161             }
162            
163             sub _find {
164 4623     4623   5848 my $letters = shift;
165 4623         4680 my $len = shift;
166 4623         5150 my $re = shift;
167 4623         4951 my @results;
168            
169 4623         4913 LINE: for (@{$words->[$len]}) { # for all the words of the right length
  4623         7802  
170 10421         13129 my $check_letters = $letters; # move the tiles being used into
171            
172 10421 100       52441 next LINE unless /^$re$/; # stop looking at this word if it doesn't match the $re
173            
174 889         1316 my (@v, @ltrs); # by having narrower lexical scope, they get automatically reset each word
175 889         2150 for my $l (split //, $_) {
176             # this is a fun one
177             # first line:
178             # 1) if you can take $l out of check-letters (once), then
179             # 2) push its value into @v,
180             # 3) [DEBUG:] then add it to the ltrs array
181             # OR
182             # second line:
183             # 1) if you can take '?' out of check-letters (once), then
184             # 2) push its value (0) into @v,
185             # 3) [DEBUG:] then add '?' to the ltrs array
186 2287 50 66     22678 next LINE unless ( ( $check_letters =~ s/$l// and push @v, $values{$l} and push @ltrs, $l) or
      66        
      66        
      66        
      100        
187             ( $check_letters =~ s/\?// and push @v, 0 and push @ltrs, '?') );
188             }
189             # append anonymous hash to the results array
190 253         2043 push @results, { "trying" => $_, "values" => [ @v ] , "tiles_this_word" => [@ltrs] };
191             }
192 4623         14144 return \@results;
193             }
194            
195             sub display {
196 49     49 1 88 my $f = shift;
197 49         108 my ($t, $r, $c) = @_;
198            
199 49         114 print "\nBoard:\n";
200 49         911 for my $row (0.._max_row) {
201 789 50       12178 print sprintf "%02d ", $row if $f;
202 789         1018 for my $col (0.._max_col) {
203 12969   50     198934 $onboard[$row][$col] ||= '.';
204 12969         20082 print $onboard[$row][$col];
205             }
206 789         12940 print "\n";
207             }
208 49         818 print "\n";
209            
210             }
211            
212             # 0.02: separate input() from search(), to make it easier to override the input() function (for example, with possible future Games::Literati::WebInterface)
213             sub input {
214 16     16 1 29 my $input = "";
215            
216             INPUT:
217 16         27 while(1) {
218 18         40 for my $row (0.._max_row) {
219 274         669 print "row $row:\n";
220 274         4937 $input = ;
221 274         356 chomp $input;
222 274 100       352 if (length($input) > n_cols) {
223 1         8 printf "over board: %d columns is more than %d\n", length($input), n_cols;
224 1         37 next INPUT;
225             }
226 273         2074 $onboard[$row]=[split //, $input];
227             }
228 17         63 print "---------$GameName----------\n";
229 17         308 display();
230            
231 17         278 $input = "";
232 17         53 while( $input !~ /^(yes|no)$/ ) {
233 18         92 print "Is the above correct?\n";
234 18         344 $input = ;
235 18         239 chomp $input;
236             }
237            
238 17 100       118 last INPUT if $input =~ /^yes$/;
239             }
240            
241             WILD:
242 16         29 while(1) {
243 18         42 print "wild tiles are at:[Row1,Col1 Row2,Col2 ...]\n";
244 18         328 $input = ;
245 18         35 chomp $input;
246            
247 18         76 @wilds = ();
248 18 100       49 last WILD unless $input;
249 7         45 my @w = (split /\s/, $input);
250 7         21 for (@w) {
251 17         53 my ($r, $c) = split (/,/, $_);
252 17 100 100     83 unless (defined $onboard[$r][$c] && $onboard[$r][$c] ne '.') {
253 2         5 print "Invalid wild tile positions, please re-enter.\n";
254 2         37 next WILD;
255             }
256 15         34 $wilds[$r][$c] = 1;
257             }
258 5         15 last WILD;
259             }
260            
261             TILES:
262 16         30 while(1) {
263 17         46 print "Enter tiles:\n";
264 17         295 $input = ;
265 17         36 chomp $input;
266 17 100       67 last TILES unless length($input) > $BingoHandLength;
267             }
268 16         57 return lc $input; # v0.032006 = convert to lower case
269             }
270            
271             sub search {
272 16     16 0 37 my $use_min = shift;
273 16         31 my $use = shift;
274 16         24 my $input;
275 16         23 my $best = 0;
276            
277 16         47 $input = input();
278            
279 16         50 print "\nLooking for solutions for $input(in X axis)...\n";
280 16         336 display();
281 16         309 _mathwork($input, "x", $use_min, $use);
282 16         84 _rotate_board();
283            
284 16         142 print "\nLooking for solutions for $input(in Y axis)...\n";
285 16         512 _mathwork($input, "y", $use_min, $use);
286 16         63 _rotate_board();
287            
288 16         28 my @args;
289 16 50       337 for my $key (sort { ($solutions{$b} <=> $solutions{$a}) || ($a cmp $b) } keys %solutions) { # sort by score, then alphabetically by solution
  1690         2471  
290 141 100       2524 last if ++$best > 10;
291            
292 130         462 print "Possible Top Ten Solution $best: $key, score $solutions{$key}\n";
293            
294             }
295            
296             }
297            
298             sub _mathwork {
299 6     6   43 no warnings;
  6         12  
  6         18531  
300 32     32   128 $|=1;
301 32         101 my %found;
302 32         53 my $letters = shift;
303 32         147 my @letters = split //, $letters;
304 32         89 my $rotate = ($_[0] eq "y");
305 32   50     188 my $use_min = $_[1] || 1;
306 32   50     120 my $use = $_[2] || scalar @letters;
307 32         52 my $go_on = 0;
308 32         62 my $actual_letters;
309             my $solution;
310            
311 32         110 while ($use >= $use_min) {
312 158         1544 print "using $use tiles:\n";
313            
314 158         5114 for my $row (0.._max_row) {
315 2550         3649 for my $col (0..n_rows-$use) {
316 35496 100       55672 next if $onboard[$row][$col] ne '.'; # skip populated tiles
317 28680         32164 $go_on = 0;
318 28680         31823 $actual_letters = $letters;
319 28680         29194 my @thisrow = @{$onboard[$row]};
  28680         92388  
320            
321 28680         36795 my $count = $use;
322 28680         30577 my $column = $col;
323            
324             # make sure that number of letters (count=use) will fit on the board
325 28680         40535 while ($count) {
326 110500 100       127925 if ($column > _max_col) {$go_on = 0; last};
  915         1557  
  915         1134  
327            
328 109585 100       151324 unless ($go_on) {
329 83159 100 100     267557 if (
      66        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
330             ($onboard[$row][$col] ne '.') ||
331             ($column > 0 && $onboard[$row][$column-1] ne '.') ||
332             ($column < _max_col && $onboard[$row][$column+1] ne '.') ||
333             ($row > 0 && $onboard[$row-1][$column] ne '.') ||
334             ($row < _max_row && $onboard[$row+1][$column] ne '.') ||
335             ($row == _center_row && $column == _center_col)
336             ) {
337 8710         10828 $go_on = 1;
338             }
339             }
340 109585 100       172162 if ( $thisrow[$column] eq '.' ) {
341 98629         112311 $thisrow[$column] = '/'; # use slash to indicate a letter we want to use
342 98629         99689 $count --;
343             }
344 109585         149155 $column ++;
345             } # $count down to 0
346 28680 50       34271 next if $column > n_cols; # next starting-col if this column has extended beyond the board
347 28680 100       65519 next unless $go_on == 1; # next starting-col if we determined that we should stop this attempt
348            
349             # if we made it here, there's enough room for a word of length==$use;
350             # we have a string that's comprised of
351             # . dots indicating empty spots on the board
352             # / slashes indicating empty spots that we will fill with our new tiles
353             # t letters indicating the letter that's already in that space
354 7795         8857 my $str = "";
355 7795         10757 map { $str .= $_ } @thisrow; # aka $str = join('',@thisrow);
  127665         165260  
356            
357             # split into pieces of the row: each piece is surrounded by empties
358             # look for the piece that includes the contiguous slashes and letters
359 7795         22807 for (split (/\./, $str)) {
360 59392 100       92114 next unless /\//; # if this piece of the row isn't part of our new word, skip it
361 7795         10024 my $record = $str = $_;
362 7795         18613 ~s/\//./g;
363 7795         16017 $str =~ s/\///g;
364 7795         9669 $actual_letters .= $str;
365            
366 7795         8922 my $length = length $_;
367            
368             # look for real words based on the list of 'actual letters', which combines
369             # the tiles in your hand with those letters already in this row.
370             # also grab the point values of each of the tiles in the word
371 7795 100       16821 unless (defined $found{"$actual_letters,$_"}) {
372 4623         6762 $found{"$actual_letters,$_"} = _find($actual_letters, $length, $_);
373             }
374            
375             # now score each of the found words
376 7795         9482 for my $tryin (@{$found{"$actual_letters,$_"}}) {
  7795         19617  
377            
378 1696         2156 my @values = @{ $tryin->{values} };
  1696         3288  
379 1696         2809 my $index = index ($record, "/"); # where the first tile I'm trying is located
380 1696         1881 my $fail = 0;
381 1696         2019 my $replace;
382 1696         2148 my $score = 0;
383 1696         1846 my $v = 0;
384 1696         2200 my $trying = $tryin->{trying};
385 1696 50       1795 my $tiles_this_word = join '', @{ $tryin->{tiles_this_word} || [''] };
  1696         3967  
386             # cycle thru each of the the crossing-words (vertical words that intersect the horizontal word I'm laying down)
387 1696         3331 for my $c ($col..$col + $length - 1 - $index) {
388 4301         4979 $str = '';
389            
390             # build up the full column-string one character at a time (vertical slice of the board)
391             # this will allow us to check for words that cross with our attempted word
392 4301         5808 for my $r (0.._max_row) {
393 70107 100       84218 if ($r == $row) { # if it's the current row, use the replacement character rather than the '.' that's in the real board
394 4301         5733 $str .= substr ($record, $index, 1);
395 4301         5295 $replace = substr ($trying, $index, 1); # this is the character from $trying that is taking the place of the slash for this column
396 4301         5751 $v = $values[$index++];
397             }
398             else { # otherwise use the character from the real board
399 65806         82659 $str .= $onboard[$r][$c];
400             }
401             } # r row loop
402            
403             # find the sub-word of the column-string that is bounded by the array ends or a . on one side or another, and look for the
404             # subword that contains the / (ie, the row where I'm laying down the new tiles
405 4301         14542 for (split /\./, $str) {
406 37979 100       60507 next unless /\//; # if this sub-word doesn't contain the new-tile row, continue
407 4149 100       6657 next if (length($_) == 1); # if this sub-word contains the new-tile row, but is only one character long, don't score the crossing-word for this column
408             # if it makes it here, I actually found that I'm making a vertical word when I lay down my horizontal tiles, so start scoring
409 1407         1754 my $t_score = 0; # "t" means temporary; in this block, t_score holds the score for the tiles already laid down in the vertical word
410 1407         2169 my $vstart = $row - index($_, "/"); # the current vertical word ($_) starts at the board's row=$vstart
411            
412             # loop thru the already existing tiles in the crossing-word; add in their non-bonus score if they are not wild
413             # (non-bonus, because they were laid down in a previous turn, so their bonus has been used up)
414 1407         5008 while (/(\w)/g) {
415             # BUGFIX (pcj): use vrow as the row of the current letter of the vertical word
416             # if it's a wild, 0 points, else add its non-bonus value
417 3274         4187 my $vrow = $vstart + pos() - 1; # vstart is the start of the vertical word; pos is the 1-based position in the vertical word; -1 adjusts for the 1-based to get the row of the current \w character $1
418 3274 100       9642 my ($wr,$wc) = ($rotate) ? ($vrow, $c) : ($c, $vrow); # swap row and column for wilds[][] array, since wilds[][] wasn't transposed.
419            
420 3274 100       5182 unless ( $wilds[$vrow][$c] ) {
421 3261         9029 $t_score += $values{$1};
422             }
423            
424            
425             }; # end of vertical-word's real-letter score
426 1407         3272 s/\//$replace/;
427            
428             # if my vertical cross-word for this column is a valid word, continue scoring by adding the score for the new tile in this column,
429             # including bonuses activated by the new tile
430 1407 100       2669 if ($valid{$_}) {
431 122 100       886 if ($bonus[$row][$c] eq "TL") {
    100          
    100          
    100          
    100          
    100          
432 6         15 $score += $t_score + $v * 3;
433             }
434             elsif ($bonus[$row][$c] eq "DL") {
435 12         28 $score += $t_score + $v * 2;
436             }
437             elsif ($bonus[$row][$c] eq "DW") {
438 6         17 $score += ($t_score + $v) * 2;
439             }
440             elsif ($bonus[$row][$c] eq "TW") {
441 2         5 $score += ($t_score + $v) * 3;
442             }
443             elsif ($bonus[$row][$c] =~ /^(\d+)L$/) { # v0.032002
444 26         96 $score += $t_score + $v * $1;
445             }
446             elsif ($bonus[$row][$c] =~ /^(\d+)W$/) { # v0.032002
447 24         93 $score += ($t_score + $v) * $1;
448             }
449             else {
450 46         111 $score += $t_score + $v;
451             }
452             } # end if valid
453             else { # else invalid
454 1285         1780 $fail = 1; # fail indicates it's not a valid word
455             } # end else invalid
456             } # for split
457 4301 100       9542 last if $fail; # since (at least) one of the verticals isn't a valid word, the whole horizontal placement is bad, so we can stop trying more columns
458             # future: might replace the $fail flag with named loops, so the else { $fail=1 } above would become else { last FOR_MY_C; }
459            
460             } # $c
461 1696 100       5130 next if $fail; # next tryin
462            
463 411         815 my $col_index = 0 - index ($record, "/");
464 411         523 my $t_score = 0; # different lexical scope; this temp score is the score for just the new horizontal word; it will be added to the existing $score above after all bonuses are applied
465 411         593 my $t_flag = '';
466 411         496 my $cc = 0;
467            
468             # this is the scoring for the word I just laid down
469 411         957 for (split //, $trying) {
470 1648 100       2852 if ($onboard[$row][$col+$col_index] eq '.') { # if new tile
471 1415 100       6027 if ($bonus[$row][$col+$col_index] eq "TL") {
    100          
    100          
    100          
    100          
    100          
472 11         22 $t_score += $values[$cc] * 3;
473             }
474             elsif ($bonus[$row][$col+$col_index] eq "DL") {
475 45         72 $t_score += $values[$cc] * 2;
476             }
477             elsif ($bonus[$row][$col+$col_index] =~ /^(\d+)L$/) { # v0.032002
478 111         365 $t_score += $values[$cc] * $1; # multiply tile by the number that prefixes the L
479             }
480             elsif ($bonus[$row][$col+$col_index] eq "DW") {
481 53         87 $t_score += $values[$cc];
482 53         75 $t_flag .= "*2";
483             }
484             elsif ($bonus[$row][$col+$col_index] eq "TW") {
485 7         15 $t_score += $values[$cc];
486 7         14 $t_flag .= "*3";
487             }
488             elsif ($bonus[$row][$col+$col_index] =~ /^(\d+)W$/) { # v0.032002
489 133         199 $t_score += $values[$cc];
490 133         404 $t_flag .= "*$1"; # multiply word by the number that prefixes the W
491             }
492             else {
493 1055         1242 $t_score += $values[$cc];
494             }
495             } # end if new tile
496             else { # else tile already there
497 233         472 my ($wr, $wc) = ($row, $col + $col_index);
498 233 100       454 ($wc, $wr) = ($wr, $wc) if $rotate; # swap row and column for wilds[][] array, since wilds[][] wasn't transposed.
499 233 50       452 unless ($wilds[$wr][$wc]) {
500 233         365 $t_score += $values{$_};
501             }
502             } # end else already a tile there
503 1648         1720 $cc ++;
504 1648         1885 $col_index ++;
505             } # foreach split trying
506            
507 411         25389 $score += eval "$t_score$t_flag"; # add in the bonus-enabled horizontal score to the pre-calculated veritcal scores
508             # POSSIBLY CLEARER: if $t_flag is just changed to $word_multiplier with an integer value starting at 1,
509             # then this could be $t_score * $word_multiplier;
510 411 100       1657 $score += $bingo_bonus if $use == $BingoHandLength; # add in bingo-bonus if all tiles used (v0.032005: configurable)
511            
512 411 100       2220 $solution = ($rotate?"column" : "row") .
    100          
    100          
513             " $row become: '$trying' starting at " .
514             ($rotate?"row" : "column") .
515             " $col " .
516             ($use == $BingoHandLength ? "(BINGO!!!!)" : ""); # v0.032005 = configurable
517            
518 411         2161 print "($score)\t$solution\n";
519            
520             # store the solution in the original %solutions hash
521 411         10274 my $key="$solution using $use tile(s)";
522 411         1254 $solutions{$key} = $score;
523            
524             # v0.042: determine which tiles would be consumed by this solution:
525 411         645 my $consumed = "";
526 411         964 for my $offs ( 0 .. length($record)-1 ) {
527 1648 100       3188 $consumed .= substr($tiles_this_word, $offs,1) if '/' eq substr($record,$offs,1);
528             }
529            
530             # in v0.042, store the structured solution in the new %solution_data hash,
531             # which makes it easier to grab data from the solution engine
532 411 100       4716 $solution_data{$key} = {
    100          
    100          
533             score => $score,
534             direction => $rotate ? "column" : "row",
535             row => ($rotate ? $col : $row), # if it's in the rotate try, swap row and column
536             col => ($rotate ? $row : $col), # if it's in the rotate try, swap row and column
537             tiles_used => $use,
538             word => $trying,
539             bingo => ($use==$BingoHandLength)+0,
540             tiles_this_word => $tiles_this_word,
541             tiles_consumed => $consumed,
542             };
543            
544             } # end for my tryin
545             } # end for split
546            
547             } # end col
548             } # end row
549 158         3059 $use --; # try next shorter word
550             } # end use
551            
552             }
553            
554            
555             sub _rotate_board {
556            
557 32     32   92 for my $row (0..(_max_row-1)) {
558 484         687 for my $col ($row+1.._max_col) {
559 3990         6693 ($onboard[$col][$row], $onboard[$row][$col]) = ($onboard[$row][$col], $onboard[$col][$row]);
560             }
561             }
562 32         97 ($BoardRows, $BoardCols) = ($BoardCols, $BoardRows);
563             }
564            
565             sub _init {
566 1     1   7 _scrabble_init();
567             }
568            
569             sub set_bonus_4quad { # v0.032009
570             # _set_bonus_4quad(r,c,b) will set the bonus array based on the row, column, and bonus text supplied
571             # it puts them in the four quadrants (r,c), (#-r,c), (r,#-c), (#-r,#-c) to keep a perfectly-balanced
572             # board
573 422   100 422 0 684 my $ra = shift || 0;
574 422   100     630 my $ca = shift || 0;
575 422   50     584 my $b = shift || '';
576            
577 422         518 my $rb = _max_row - $ra;
578 422         536 my $cb = _max_col - $ca;
579            
580 422         534 $bonus[$ra][$ca] = $b;
581 422         480 $bonus[$rb][$ca] = $b;
582 422         457 $bonus[$ra][$cb] = $b;
583 422         446 $bonus[$rb][$cb] = $b;
584            
585 422         475 return $b;
586             }
587            
588             sub _scrabble_init {
589            
590 5     5   19 $GameName = "Scrabble";
591             ##########################################################################
592             # Scrabble #
593             ##########################################################################
594             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #
595             # 0 [TW][__][__][DL][__][__][__][TW][__][__][__][DL][__][__][TW] # 0 #
596             # 1 [__][DW][__][__][__][TL][__][__][__][TL][__][__][__][DW][__] # 1 #
597             # 2 [__][__][DW][__][__][__][DL][__][DL][__][__][__][DW][__][__] # 2 #
598             # 3 [DL][__][__][DW][__][__][__][DL][__][__][__][DW][__][__][DL] # 3 #
599             # 4 [__][__][__][__][DW][__][__][__][__][__][DW][__][__][__][__] # 4 #
600             # 5 [__][TL][__][__][__][TL][__][__][__][TL][__][__][__][TL][__] # 5 #
601             # 6 [__][__][DL][__][__][__][DL][__][DL][__][__][__][DL][__][__] # 6 #
602             # 7 [TW][__][__][DL][__][__][__][DW][__][__][__][DL][__][__][TW] # 7 #
603             # 8 [__][__][DL][__][__][__][DL][__][DL][__][__][__][DL][__][__] # 8 #
604             # 9 [__][TL][__][__][__][TL][__][__][__][TL][__][__][__][TL][__] # 9 #
605             # 10 [__][__][__][__][DW][__][__][__][__][__][DW][__][__][__][__] # 10 #
606             # 11 [DL][__][__][DW][__][__][__][DL][__][__][__][DW][__][__][DL] # 11 #
607             # 12 [__][__][DW][__][__][__][DL][__][DL][__][__][__][DW][__][__] # 12 #
608             # 13 [__][DW][__][__][__][TL][__][__][__][TL][__][__][__][DW][__] # 13 #
609             # 14 [TW][__][__][DL][__][__][__][TW][__][__][__][DL][__][__][TW] # 14 #
610             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #
611             ##########################################################################
612            
613 5         21 set_bonus_4quad(0,0,'TW');
614 5         14 set_bonus_4quad(0,7,'TW'); # middle column
615 5         15 set_bonus_4quad(7,0,'TW'); # middle row
616            
617 5         13 set_bonus_4quad(1,1,'DW');
618 5         14 set_bonus_4quad(2,2,'DW');
619 5         13 set_bonus_4quad(3,3,'DW');
620 5         14 set_bonus_4quad(4,4,'DW');
621 5         12 set_bonus_4quad(7,7,'DW'); #center
622            
623 5         11 set_bonus_4quad(0,3,'DL');
624 5         19 set_bonus_4quad(2,6,'DL');
625 5         11 set_bonus_4quad(3,0,'DL');
626 5         11 set_bonus_4quad(3,7,'DL');
627 5         10 set_bonus_4quad(6,2,'DL');
628 5         11 set_bonus_4quad(6,6,'DL');
629 5         12 set_bonus_4quad(7,3,'DL'); #middle row
630            
631 5         13 set_bonus_4quad(1,5,'TL');
632 5         19 set_bonus_4quad(5,1,'TL');
633 5         12 set_bonus_4quad(5,5,'TL');
634            
635 5         11 for my $row (0.._max_row) {
636 75         94 for my $col (0.._max_col) {
637 1125         1417 $onboard[$row][$col] = '.';
638             }
639             }
640            
641             %values = (
642 5         85 a=>1,
643             b=>3,
644             c=>3,
645             d=>2,
646             e=>1,
647             f=>4,
648             g=>2,
649             h=>4,
650             i=>1,
651             j=>8,
652             k=>5,
653             l=>1,
654             m=>3,
655             n=>1,
656             o=>1,
657             p=>3,
658             q=>10,
659             r=>1,
660             s=>1,
661             t=>1,
662             u=>1,
663             v=>4,
664             w=>4,
665             x=>8,
666             y=>4,
667             z=>10
668             );
669 5         13 $bingo_bonus = 50;
670             }
671            
672             sub _superscrabble_init { # v0.032002
673            
674 4     4   8 $GameName = "SuperScrabble";
675            
676             ##################################################################################################
677             # SuperScrabble #
678             ##################################################################################################
679             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #
680             # 0 [4W][__][__][2L][__][__][__][3W][__][__][2L][__][__][3W][__][__][__][2L][__][__][4W] # 0 #
681             # 1 [__][2W][__][__][3L][__][__][__][2W][__][__][__][2W][__][__][__][3L][__][__][2W][__] # 1 #
682             # 2 [__][__][2W][__][__][4L][__][__][__][2W][__][2W][__][__][__][4L][__][__][2W][__][__] # 2 #
683             # 3 [2L][__][__][3W][__][__][2L][__][__][__][3W][__][__][__][2L][__][__][3W][__][__][2L] # 3 #
684             # 4 [__][3L][__][__][2W][__][__][__][3L][__][__][__][3L][__][__][__][2W][__][__][3L][__] # 4 #
685             # 5 [__][__][4L][__][__][2W][__][__][__][2L][__][2L][__][__][__][2W][__][__][4L][__][__] # 5 #
686             # 6 [__][__][__][2L][__][__][2W][__][__][__][2L][__][__][__][2W][__][__][2L][__][__][__] # 6 #
687             # 7 [3W][__][__][__][__][__][__][2W][__][__][__][__][__][2W][__][__][__][__][__][__][3W] # 7 #
688             # 8 [__][2W][__][__][3L][__][__][__][3L][__][__][__][3L][__][__][__][3L][__][__][2W][__] # 8 #
689             # 9 [__][__][2W][__][__][2L][__][__][__][2L][__][2L][__][__][__][2L][__][__][2W][__][__] # 9 #
690             # 10 [2L][__][__][3W][__][__][2L][__][__][__][2W][__][__][__][2L][__][__][3W][__][__][2L] # 10 #
691             # 11 [__][__][2W][__][__][2L][__][__][__][2L][__][2L][__][__][__][2L][__][__][2W][__][__] # 11 #
692             # 12 [__][2W][__][__][3L][__][__][__][3L][__][__][__][3L][__][__][__][3L][__][__][2W][__] # 12 #
693             # 13 [3W][__][__][__][__][__][__][2W][__][__][__][__][__][2W][__][__][__][__][__][__][3W] # 13 #
694             # 14 [__][__][__][2L][__][__][2W][__][__][__][2L][__][__][__][2W][__][__][2L][__][__][__] # 14 #
695             # 15 [__][__][4L][__][__][2W][__][__][__][2L][__][2L][__][__][__][2W][__][__][4L][__][__] # 15 #
696             # 16 [__][3L][__][__][2W][__][__][__][3L][__][__][__][3L][__][__][__][2W][__][__][3L][__] # 16 #
697             # 17 [2L][__][__][3W][__][__][2L][__][__][__][3W][__][__][__][2L][__][__][3W][__][__][2L] # 17 #
698             # 18 [__][__][2W][__][__][4L][__][__][__][2W][__][2W][__][__][__][4L][__][__][2W][__][__] # 18 #
699             # 19 [__][2W][__][__][3L][__][__][__][2W][__][__][__][2W][__][__][__][3L][__][__][2W][__] # 19 #
700             # 20 [4W][__][__][2L][__][__][__][3W][__][__][2L][__][__][3W][__][__][__][2L][__][__][4W] # 20 #
701             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #
702             ##################################################################################################
703            
704            
705 4         18 set_bonus_4quad( 0, 0,'4W');
706            
707 4         12 set_bonus_4quad( 0, 7,'3W');
708 4         11 set_bonus_4quad( 3, 3,'3W');
709 4         10 set_bonus_4quad( 3,10,'3W'); # middle column
710 4         11 set_bonus_4quad( 7, 0,'3W');
711 4         12 set_bonus_4quad(10, 3,'3W'); # middle row
712            
713 4         11 set_bonus_4quad( 1, 1,'2W');
714 4         9 set_bonus_4quad( 1, 8,'2W');
715 4         9 set_bonus_4quad( 2, 2,'2W');
716 4         9 set_bonus_4quad( 2, 9,'2W');
717 4         11 set_bonus_4quad( 4, 4,'2W');
718 4         11 set_bonus_4quad( 5, 5,'2W');
719 4         9 set_bonus_4quad( 6, 6,'2W');
720 4         9 set_bonus_4quad( 7, 7,'2W');
721 4         9 set_bonus_4quad( 8, 1,'2W');
722 4         10 set_bonus_4quad( 9, 2,'2W');
723 4         12 set_bonus_4quad(10,10,'2W'); # center
724            
725 4         11 set_bonus_4quad( 2, 5,'4L');
726 4         12 set_bonus_4quad( 5, 2,'4L');
727            
728 4         10 set_bonus_4quad( 1, 4,'3L');
729 4         10 set_bonus_4quad( 4, 1,'3L');
730 4         10 set_bonus_4quad( 4, 8,'3L');
731 4         10 set_bonus_4quad( 8, 4,'3L');
732 4         11 set_bonus_4quad( 8, 8,'3L');
733            
734 4         59 set_bonus_4quad( 0, 3,'2L');
735 4         39 set_bonus_4quad( 0,10,'2L'); # middle column
736 4         13 set_bonus_4quad( 3, 0,'2L');
737 4         9 set_bonus_4quad( 3, 6,'2L');
738 4         21 set_bonus_4quad( 5, 9,'2L');
739 4         16 set_bonus_4quad( 6, 3,'2L');
740 4         50 set_bonus_4quad( 6,10,'2L'); # middle column
741 4         11 set_bonus_4quad( 9, 5,'2L');
742 4         11 set_bonus_4quad( 9, 9,'2L');
743 4         10 set_bonus_4quad(10, 0,'2L'); # middle row
744 4         8 set_bonus_4quad(10, 6,'2L'); # middle row
745            
746 4         9 for my $row (0.._max_row) {
747 84         104 for my $col (0.._max_col) {
748 1764         2156 $onboard[$row][$col] = '.';
749             }
750             }
751            
752             %values = (
753 4         64 a=>1,
754             b=>3,
755             c=>3,
756             d=>2,
757             e=>1,
758             f=>4,
759             g=>2,
760             h=>4,
761             i=>1,
762             j=>8,
763             k=>5,
764             l=>1,
765             m=>3,
766             n=>1,
767             o=>1,
768             p=>3,
769             q=>10,
770             r=>1,
771             s=>1,
772             t=>1,
773             u=>1,
774             v=>4,
775             w=>4,
776             x=>8,
777             y=>4,
778             z=>10
779             );
780 4         10 $bingo_bonus = 50;
781             }
782            
783             sub _literati_init {
784            
785 7     7   16 $GameName = "Literati";
786            
787             ##########################################################################
788             # Literati #
789             ##########################################################################
790             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #
791             # 0 [__][__][__][3W][__][__][3L][__][3L][__][__][3W][__][__][__] # 0 #
792             # 1 [__][__][2L][__][__][2W][__][__][__][2W][__][__][2L][__][__] # 1 #
793             # 2 [__][2L][__][__][2L][__][__][__][__][__][2L][__][__][2L][__] # 2 #
794             # 3 [3W][__][__][3L][__][__][__][2W][__][__][__][3L][__][__][3W] # 3 #
795             # 4 [__][__][2L][__][__][__][2L][__][2L][__][__][__][2L][__][__] # 4 #
796             # 5 [__][2W][__][__][__][3L][__][__][__][3L][__][__][__][2W][__] # 5 #
797             # 6 [3L][__][__][__][2L][__][__][__][__][__][2L][__][__][__][3L] # 6 #
798             # 7 [__][__][__][2W][__][__][__][__][__][__][__][2W][__][__][__] # 7 #
799             # 8 [3L][__][__][__][2L][__][__][__][__][__][2L][__][__][__][3L] # 8 #
800             # 9 [__][2W][__][__][__][3L][__][__][__][3L][__][__][__][2W][__] # 9 #
801             # 10 [__][__][2L][__][__][__][2L][__][2L][__][__][__][2L][__][__] # 10 #
802             # 11 [3W][__][__][3L][__][__][__][2W][__][__][__][3L][__][__][3W] # 11 #
803             # 12 [__][2L][__][__][2L][__][__][__][__][__][2L][__][__][2L][__] # 12 #
804             # 13 [__][__][2L][__][__][2W][__][__][__][2W][__][__][2L][__][__] # 13 #
805             # 14 [__][__][__][3W][__][__][3L][__][3L][__][__][3W][__][__][__] # 14 #
806             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #
807             ##########################################################################
808            
809 7         23 set_bonus_4quad(0,3,'3W');
810 7         19 set_bonus_4quad(3,0,'3W');
811            
812 7         16 set_bonus_4quad(1,5,'2W');
813 7         17 set_bonus_4quad(3,7,'2W'); # middle column
814 7         15 set_bonus_4quad(5,1,'2W');
815 7         16 set_bonus_4quad(7,3,'2W'); # middle row
816            
817 7         21 set_bonus_4quad(0,6,'3L');
818 7         13 set_bonus_4quad(3,3,'3L');
819 7         15 set_bonus_4quad(5,5,'3L');
820 7         14 set_bonus_4quad(6,0,'3L');
821            
822 7         15 set_bonus_4quad(1,2,'2L');
823 7         16 set_bonus_4quad(2,1,'2L');
824 7         21 set_bonus_4quad(2,4,'2L');
825 7         16 set_bonus_4quad(4,2,'2L');
826 7         15 set_bonus_4quad(4,6,'2L');
827 7         16 set_bonus_4quad(6,4,'2L');
828            
829 7         9 $bingo_bonus = 35;
830            
831 7         14 for my $row (0.._max_row) {
832 105         123 for my $col (0.._max_col) {
833 1575         1835 $onboard[$row][$col] = '.';
834             }
835             }
836            
837             %values = (
838 7         99 a=>1,
839             b=>2,
840             c=>1,
841             d=>1,
842             e=>1,
843             f=>3,
844             g=>1,
845             h=>2,
846             i=>1,
847             j=>5,
848             k=>3,
849             l=>1,
850             m=>1,
851             n=>1,
852             o=>1,
853             p=>2,
854             q=>5,
855             r=>1,
856             s=>1,
857             t=>1,
858             u=>1,
859             v=>4,
860             w=>4,
861             x=>5,
862             y=>3,
863             z=>5
864             );
865            
866             }
867            
868             sub _wordswithfriends_init {
869            
870 5     5   17 $GameName = "Words With Friends";
871            
872             ##########################################################################
873             # Words With Friends #
874             ##########################################################################
875             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #
876             # 0 [__][__][__][3W][__][__][3L][__][3L][__][__][3W][__][__][__] # 0 #
877             # 1 [__][__][2L][__][__][2W][__][__][__][2W][__][__][2L][__][__] # 1 #
878             # 2 [__][2L][__][__][2L][__][__][__][__][__][2L][__][__][2L][__] # 2 #
879             # 3 [3W][__][__][3L][__][__][__][2W][__][__][__][3L][__][__][3W] # 3 #
880             # 4 [__][__][2L][__][__][__][2L][__][2L][__][__][__][2L][__][__] # 4 #
881             # 5 [__][2W][__][__][__][3L][__][__][__][3L][__][__][__][2W][__] # 5 #
882             # 6 [3L][__][__][__][2L][__][__][__][__][__][2L][__][__][__][3L] # 6 #
883             # 7 [__][__][__][2W][__][__][__][__][__][__][__][2W][__][__][__] # 7 #
884             # 8 [3L][__][__][__][2L][__][__][__][__][__][2L][__][__][__][3L] # 8 #
885             # 9 [__][2W][__][__][__][3L][__][__][__][3L][__][__][__][2W][__] # 9 #
886             # 10 [__][__][2L][__][__][__][2L][__][2L][__][__][__][2L][__][__] # 10 #
887             # 11 [3W][__][__][3L][__][__][__][2W][__][__][__][3L][__][__][3W] # 11 #
888             # 12 [__][2L][__][__][2L][__][__][__][__][__][2L][__][__][2L][__] # 12 #
889             # 13 [__][__][2L][__][__][2W][__][__][__][2W][__][__][2L][__][__] # 13 #
890             # 14 [__][__][__][3W][__][__][3L][__][3L][__][__][3W][__][__][__] # 14 #
891             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #
892             ##########################################################################
893            
894 5         15 set_bonus_4quad(0,3,'3W');
895 5         17 set_bonus_4quad(3,0,'3W');
896            
897 5         14 set_bonus_4quad(1,5,'2W');
898 5         11 set_bonus_4quad(3,7,'2W'); # middle column
899 5         12 set_bonus_4quad(5,1,'2W');
900 5         11 set_bonus_4quad(7,3,'2W'); # middle row
901            
902 5         13 set_bonus_4quad(0,6,'3L');
903 5         13 set_bonus_4quad(3,3,'3L');
904 5         13 set_bonus_4quad(5,5,'3L');
905 5         10 set_bonus_4quad(6,0,'3L');
906            
907 5         10 set_bonus_4quad(1,2,'2L');
908 5         14 set_bonus_4quad(2,1,'2L');
909 5         12 set_bonus_4quad(2,4,'2L');
910 5         13 set_bonus_4quad(4,2,'2L');
911 5         11 set_bonus_4quad(4,6,'2L');
912 5         12 set_bonus_4quad(6,4,'2L');
913            
914 5         9 $bingo_bonus = 35;
915            
916 5         9 for my $row (0.._max_row) {
917 75         91 for my $col (0.._max_col) {
918 1125         1383 $onboard[$row][$col] = '.';
919             }
920             }
921            
922             %values = (
923 5         79 a=>1,
924             b=>4,
925             c=>4,
926             d=>2,
927             e=>1,
928             f=>4,
929             g=>3,
930             h=>3,
931             i=>1,
932             j=>10,
933             k=>5,
934             l=>2,
935             m=>4,
936             n=>2,
937             o=>1,
938             p=>4,
939             q=>10,
940             r=>1,
941             s=>1,
942             t=>1,
943             u=>2,
944             v=>5,
945             w=>4,
946             x=>8,
947             y=>3,
948             z=>10
949             );
950            
951             }
952            
953             sub _text_bonus_board { # v0.032010
954 4     4   10 my $str = "";
955 4         11 my ($row, $col);
956 4         10 $str .= " ##" . "#"x4 . "####"x(n_rows) . "#"x8 . "\n";
957 4         8 $str .= sprintf " # %-*s #\n", (4*n_rows+10), $GameName;
958 4         8 $str .= " ##" . "#"x4 . "####"x(n_rows) . "#"x8 . "\n";
959 4         5 $str .= sprintf " # %-4s", '';
960 4         5 $str .= join '', map { sprintf " %-3d", $_ } @{[0 .. _max_col]};
  66         115  
  4         7  
961 4         9 $str .= " "x7 . "#\n";
962 4         6 for $row (0.._max_row) {
963 66         99 $str .= sprintf " # %-4d", $row;
964 66         79 for $col (0.._max_col) {
965 1116   100     2370 $str .= sprintf "[%2s]", $bonus[$row][$col]||'__';
966             }
967 66         108 $str .= sprintf " # %-4d#\n", $row;
968             }
969 4         6 $str .= sprintf " # %-4s", '';
970 4         15 $str .= join '', map { sprintf " %-3d", $_ } @{[0 .. _max_col]};
  66         104  
  4         7  
971 4         19 $str .= " "x7 . "#\n";
972 4         7 $str .= " ##" . "#"x4 . "####"x(n_rows) . "#"x8 . "\n";
973 4         22 return $str;
974             }
975            
976             sub reduce_hand { # v0.042
977 3     3 1 250 my ($hand_tiles, $played_tiles) = @_;
978 3         4 my $stuck = '';
979 3         11 for my $tile ( split //, $played_tiles ) {
980 11 100       86 $hand_tiles =~ s/\Q$tile\E//
981             or $stuck .= $tile;
982             }
983 3 100       17 die "reduce_hand(): could not remove '$stuck' from hand tiles '$hand_tiles'" if length $stuck;
984 2         13 return $hand_tiles;
985             }
986            
987             1;
988            
989            
990             =pod
991            
992             =head1 NAME
993            
994             Games::Literati - For word games like Literati (or Scrabble, or Words With Friends), find the best-scoring solution(s) for a board and hand of tiles.
995            
996             =head1 SYNOPSIS
997            
998             use Games::Literati qw/:allGames/;
999             literati();
1000             wordswithfriends();
1001             scrabble();
1002             superscrabble();
1003            
1004             Example Windows-based one-liner:
1005            
1006             perl -MGames::Literati=literati -e "$Games::Literati::WordList = './mydict.txt'; literati();"
1007            
1008             Example linux-based one-liner:
1009            
1010             perl -MGames::Literati=literati -e "$Games::Literati::WordList = '/usr/dict/words'; literati();"
1011            
1012             =head2 Export Tags
1013            
1014             =over
1015            
1016             =item :allGames => C, C, C, C
1017            
1018             =item :configGame => C<$WordFile>, C<$MinimumWordLength>
1019            
1020             =item :infoFunctions => C, C, C, C
1021            
1022             =begin comments
1023            
1024             =item :customizer => C<:infoFunctions>, C
1025            
1026             =end comments
1027            
1028             =item :miscFunctions => C
1029            
1030             =back
1031            
1032             =head1 DESCRIPTION
1033            
1034             B helps you find out I solutions for a given
1035             board and tiles. It can be used to play
1036             L (the original 15x15 grid),
1037             L (the official 21x21 extended grid),
1038             L (an old Yahoo! Games 15x15 grid, from which B derives its name), and
1039             L (a newer 15x15 grid).
1040             By overriding or extending the package, one could implement other similar letter-tile grids,
1041             with customizable bonus placements.
1042            
1043             To use this module to play the games, a one-liner such as the
1044             following can be used:
1045            
1046             perl -MGames::Literati=literati -e "literati();"
1047            
1048             (This will only work if `F<./wordlist>' is in the current directory. Otherwise,
1049             see L, below.)
1050            
1051             Enter the data prompted then the best 10 solutions will be displayed.
1052            
1053             =head2 Board Input
1054            
1055             The game will prompt you for each row of the board, one row at a time
1056            
1057             row 0:
1058             row 1:
1059             ...
1060             row 14:
1061            
1062             And will expect you to enter the requested row's data. It expects one
1063             character for each column on the board. Thus, on a standard 15x15 board,
1064             it will expect each row to contain 15 characters. The `C<.>' character
1065             represents an empty square. Individual letters (in lower case) represent
1066             tiles that have already been laid on the board. (Don't worry about
1067             indicating wild tiles just yet; that will come momentarily.) An example
1068             input row could be:
1069            
1070             .......s.header
1071            
1072             After requesting the last row, the B will display the
1073             board as it received it, and ask you
1074            
1075             Is the above correct?
1076            
1077             At this point, it is expecting you to type either `C' or `C'.
1078             If you answer `C', the game will progress. If you answer `C',
1079             it will start over asking for C. If you answer with anything
1080             else, it will ask you again if everything is correct.
1081            
1082             Once you have entered `C', B will ask you for
1083             the coordinates of the any wild tiles already on the board
1084            
1085             wild tiles are at:[Row1,Col1 Row2,Col2 ...]
1086            
1087             C and C are 0-referenced, so the upper left of the board
1088             is C<0,0>, and the lowe right of the standard board is C<14,14>.
1089             Multiple wild tiles are space-separated. If there have not been any
1090             wild tiled played yet, just hit C, giving it an empty input.
1091             If you have wilds, with one at one-tile diagonally from the upper right
1092             and the second two tiles diagonally from the lower-left, you would
1093             enter
1094            
1095             1,13 12,2
1096            
1097             If your coordinates resolve to an empty tile (C<.>) or a tile that's
1098             not on the board, you will be notified:
1099            
1100             Invalid wild tile positions, please re-enter.
1101             wild tiles are at:[Row1,Col1 Row2,Col2 ...]
1102            
1103             Finally, after receiving a valid input for the wilds, B
1104             will ask you for what tiles are in your hand.
1105            
1106             Enter tiles:
1107            
1108             You should enter anywhere from 1 to 7 tiles (for a standard game).
1109             Letter tiles should be in lower case; wild tiles are indicated by a
1110             question mark `C'.
1111            
1112             ?omment
1113            
1114             It is recommended to pre-write everything into a file. and run the
1115             program via command-line. See the L, below.
1116            
1117             =head1 SAMPLE TURNS
1118            
1119             These samples will use input file F, to help ensure the correct
1120             input format.
1121            
1122             As described above, the first 15 lines represent board situation, followed
1123             with "yes", followed by wild tile positions, if none, place a empty
1124             line here, then followed by tiles (can be less than 7), use ? to
1125             represent wild tiles. Please make sure the last line in your file
1126             ends with a full NEWLINE character on your system (it's safest to add
1127             a blank line after the list of tiles).
1128            
1129             I' in the working directory when running
1130             the program, or to set C<$WordFile> as described in L, below.>
1131            
1132             =head2 First Turn
1133            
1134             Create game file named F, like this:
1135            
1136             ...............
1137             ...............
1138             ...............
1139             ...............
1140             ...............
1141             ...............
1142             ...............
1143             ...............
1144             ...............
1145             ...............
1146             ...............
1147             ...............
1148             ...............
1149             ...............
1150             ...............
1151             yes
1152            
1153             ?omment
1154            
1155            
1156             Run the game from the command line:
1157            
1158             perl -MGames::Literati=literati -e'literati()' < t
1159            
1160             The output will be (depending on word list)
1161            
1162             [...]
1163             using 7 tiles:
1164             (47) row 7 become: 'comment' starting at column 1 (BINGO!!!!)
1165             (47) row 7 become: 'memento' starting at column 1 (BINGO!!!!)
1166             (47) row 7 become: 'metonym' starting at column 1 (BINGO!!!!)
1167             (47) row 7 become: 'momenta' starting at column 1 (BINGO!!!!)
1168             (47) row 7 become: 'momento' starting at column 1 (BINGO!!!!)
1169             [...]
1170             Possible Ten Best Solution 1: row 7 become: 'metonym' starting at column 5 (BINGO!!!!) using 7 tile(s), score 47
1171             Possible Ten Best Solution 2: row 7 become: 'moments' starting at column 6 (BINGO!!!!) using 7 tile(s), score 47
1172             Possible Ten Best Solution 3: row 7 become: 'momenta' starting at column 6 (BINGO!!!!) using 7 tile(s), score 47
1173             Possible Ten Best Solution 4: column 7 become: 'omentum' starting at row 7 (BINGO!!!!) using 7 tile(s), score 47
1174             Possible Ten Best Solution 5: column 7 become: 'memento' starting at row 7 (BINGO!!!!) using 7 tile(s), score 47
1175             Possible Ten Best Solution 6: column 7 become: 'memento' starting at row 1 (BINGO!!!!) using 7 tile(s), score 47
1176             Possible Ten Best Solution 7: row 7 become: 'comment' starting at column 3 (BINGO!!!!) using 7 tile(s), score 47
1177             Possible Ten Best Solution 8: row 7 become: 'omentum' starting at column 7 (BINGO!!!!) using 7 tile(s), score 47
1178             Possible Ten Best Solution 9: row 7 become: 'omentum' starting at column 1 (BINGO!!!!) using 7 tile(s), score 47
1179             Possible Ten Best Solution 10: column 7 become: 'memento' starting at row 5 (BINGO!!!!) using 7 tile(s), score 47
1180            
1181             If you run the same board with the Scrabble engine:
1182            
1183             $ perl -MGames::Literati=scrabble -e'scrabble()' < t
1184            
1185             You will get
1186            
1187             [...]
1188             (76) row 7 become: 'comment' starting at column 1 (BINGO!!!!)
1189             (76) row 7 become: 'memento' starting at column 1 (BINGO!!!!)
1190             (72) row 7 become: 'metonym' starting at column 1 (BINGO!!!!)
1191             [...]
1192             Possible Ten Best Solution 1: column 7 become: 'memento' starting at row 1 (BINGO!!!!) using 7 tile(s), score 76
1193             Possible Ten Best Solution 2: column 7 become: 'momento' starting at row 1 (BINGO!!!!) using 7 tile(s), score 76
1194             Possible Ten Best Solution 3: row 7 become: 'metonym' starting at column 5 (BINGO!!!!) using 7 tile(s), score 76
1195             Possible Ten Best Solution 4: row 7 become: 'momenta' starting at column 1 (BINGO!!!!) using 7 tile(s), score 76
1196             [...]
1197            
1198             =head2 Intermediate Turn
1199            
1200             For most turns, you input file the F containing a partially
1201             populated game, such as:
1202            
1203             ...............
1204             ...............
1205             ...............
1206             .......c.......
1207             ......ai.......
1208             .......s.header
1209             .......t....r..
1210             ...jurors..soup
1211             .......o....p.h
1212             .upsilon.f..pea
1213             .......speering
1214             .........s..n.e
1215             .........t..g..
1216             .........e.....
1217             ........broils.
1218             yes
1219             7,8 10,14 7,14
1220             eurmsss
1221            
1222            
1223             Run the game from the command line:
1224            
1225             perl -MGames::Literati=literati -e'literati()' < t
1226            
1227             The output will be (depending on word list)
1228            
1229             [....]
1230             using 7 tiles:
1231             using 6 tiles:
1232             (9) row 3 become: 'cussers' starting at column 8
1233             (9) row 12 become: 'russets' starting at column 4
1234             using 5 tiles:
1235             (8) row 3 become: 'cruses' starting at column 8
1236             (8) row 3 become: 'curses' starting at column 8
1237            
1238             [...]
1239             Possible Ten Best Solution 1: column 3 become: 'susses' starting at row 10 using 5 tile(s), score 24
1240             Possible Ten Best Solution 2: column 3 become: 'serums' starting at row 10 using 5 tile(s), score 24
1241             [...]
1242            
1243             If you run the same board with the Scrabble engine:
1244            
1245             perl -MGames::Literati=scrabble -e'scrabble()' < t
1246            
1247             You will get
1248            
1249             [...]
1250             Possible Ten Best Solution 1: row 14 become: 'embroils' starting at column 6 using 2 tile(s), score 36
1251             Possible Ten Best Solution 2: row 6 become: 'stems' starting at column 6 using 4 tile(s), score 23
1252             Possible Ten Best Solution 3: column 2 become: 'spumes' starting at row 8 using 5 tile(s), score 22
1253             [...]
1254            
1255             Good luck!:)
1256            
1257             =head1 PUBLIC FUNCTIONS
1258            
1259             =over 4
1260            
1261             =item literati([I[, I]])
1262            
1263             =item wordswithfriends([I[, I]])
1264            
1265             =item scrabble([I[, I]])
1266            
1267             =item superscrabble([I[, I]])
1268            
1269             These functions execute each of the games. As shown in the L
1270             and L, each turn generally requires just one call to
1271             the specific game function. Each function implements the appropriate
1272             15x15 (or 20x20 for superscrabble) grid of bonus scores.
1273            
1274             There are two optional arguments to the game functions:
1275            
1276             =over 4
1277            
1278             =item I
1279            
1280             The minimum number of tiles to play, which defaults to C<1>. If you
1281             want to only allow your computer player (I, the B
1282             module) to play 3 or more tiles, you would set I=C<3>.
1283            
1284             If you specify C<0> or negative, the magic of perl will occur, and it
1285             will internally use the default of I=C<1>.
1286            
1287             =item I
1288            
1289             The maximum number of tiles to play, which defaults to all the tiles
1290             in the given hand. If you want to restrict your computer player to play 5
1291             or fewer tiles, you would set I=C<5>. It will check to ensure that
1292             I is bounded by the C..
1293            
1294             If you want to specify I, you B also specify a I.
1295            
1296             If you specify I less than I, B will not play
1297             any tiles.
1298            
1299             =back
1300            
1301             Thus, specifying C will restrict the computer Literati
1302             player to using 3, 4, or 5 tiles on this turn.
1303            
1304             =item n_rows()
1305            
1306             =item n_cols()
1307            
1308             Returns number of rows or columns for the most recent game type
1309            
1310             =item numTilesPerHand()
1311            
1312             Returns number of tiles in a full hand for the most recent game type
1313            
1314             =item get_solutions()
1315            
1316             Returns a hash, whose elements are described in the example below
1317            
1318             %solutions = get_solutions();
1319            
1320             # equivalent to
1321            
1322             %solutions = (
1323             $key => { # [string]: the string that is printed;
1324             # it's a really bad idea for the key, but it keeps things
1325             # consistent with the old %Games::Literati::solutions keys
1326             word => $word, # [string]: the word being played
1327             tiles_used => $ntiles, # [number]: the _number_ of tiles used
1328             score => $score, # [number]: the score (equivalent to $Games::Literati::solutions{$key})
1329             direction => $dir, # [string]: either 'column' or 'row'
1330             row => $row, # [number]: the row number for the start of the word (0-based)
1331             col => $col, # [number]: the column number for start of the word (0-based)
1332             bingo => $flag, # [boolean]: whether this word was a BINGO or not
1333             tiles_this_word => $tiles_this_word,
1334             # [string]: Shows the tiles for this word, both those from the board and
1335             # those from your hand. Useful for determining placement of wild
1336             # tiles
1337             tiles_consumed => $consumed,
1338             # [string]: Shows the tiles from your hand that were used for this play
1339             # (a subset of the tiles from tiles_this_word), which can be used
1340             # to remove the tiles from your hand that were played this turn
1341             },
1342             ... # repeat for other solutions
1343             );
1344            
1345             =item reduce_hand( $hand_tiles, $played_tiles )
1346            
1347             Returns the new hand tiles, with the played tiles removed.
1348            
1349             print reduce_hand( "rstlnec", "lest"); # prints "rnc"
1350            
1351             =item DEPRECATED: find(I<\%args>) or find(I<$args>)
1352            
1353             Finds possible valid words, based on the hashref provided. When playing
1354             the automated game using the above functions, this is not needed, but it
1355             is provided to give you access to a function similar to the internal function,
1356             but it outputs extra information to the user.
1357            
1358             =over 4
1359            
1360             =item \%args or $args
1361            
1362             A reference to a hash containing the keys C, C, and
1363             C.
1364            
1365             =over 4
1366            
1367             =item $args->{letters}
1368            
1369             This is the list of letters available to play.
1370            
1371             =item $args->{re}
1372            
1373             This is a string which will be evaluated into a perl regular
1374             expression that is evaluated to determine. Note: this requres the
1375             full regex syntax, so use C<'/c.t/'> to indicate you are looking
1376             for valid letters to put between a `c' and a `t'.
1377            
1378             =item $args->{internal}
1379            
1380             (Boolean) If set to a true value, find() will be quiet (not print
1381             to standard output) and will return an array-reference of possible
1382             solutions. If false, find() will print suggested words to STDOUT.
1383            
1384             =back
1385            
1386             =back
1387            
1388             B: The I function is not under active development, and changes to the
1389             internal function might not be replicated to this public function. (It is
1390             documented and left exportable to be backward compatible with the original
1391             B release.)
1392            
1393            
1394             =back
1395            
1396             =head1 PUBLIC VARIABLES
1397            
1398             These variables are exportable, so can be fully qualified as
1399             C<%Games::Literati::WordFile>, or if included in the export list
1400             when you C the module, you can reference them directly,
1401             as
1402            
1403             use Games::Literati qw/literati $WordFile/;
1404             $WordFile = '/usr/share/dict/words';
1405            
1406             =over 4
1407            
1408             =item $WordFile
1409            
1410             The C<$WordFile> points to a text document, which lists one valid word per line.
1411            
1412             The variable defaults to './wordfile'. (in version 0.01, that was the
1413             only value, and there was no variable.)
1414            
1415             You may change the default wordfile by setting this variable to the path
1416             to find the list.
1417            
1418             $Games::Literati::WordFile = '/usr/dict/words';
1419            
1420            
1421             Sources for C<$WordFile>
1422            
1423             =over
1424            
1425             =item * Your OS may include a builtin dictionary (such as F or
1426             F). Beware: these often have numbers or
1427             punctuation (periods, hyphens), which may interfere with proper functioning
1428            
1429             =item * ENABLE (Enhanced North American Benchmark Lexicon): a
1430             public-domain list with more than 173,000 words, available at a variety of locations,
1431             including in an old L
1432             repository|https://code.google.com/archive/p/dotnetperls-controls/downloads>
1433             as
1434             "L"
1435             The ENABLE dictionary is used by a variety of online tools, and is
1436             the primary source for the official L dictionary.
1437            
1438             =item * Anthony Tan has delved into the Words With Friends app, and
1439             has compared their internal list to the original ENABLE list at
1440             L
1441            
1442             =back
1443            
1444             If you want to use one of the lists from a website, you will need
1445             to download the list to a file, and set C<$WordFile> to the path
1446             to your downloaded list.
1447            
1448             =item %valid
1449            
1450             For each I that B parses from the C<$WordList>
1451             file, it will set C<$valid{I}> to C<1>.
1452            
1453             =item $MinimumWordLength
1454            
1455             Default = 2. This is used when parsing the dictionary file (during C)
1456             to ignore words that are too short. Most of these games don't allow
1457             single-letter words ("I", "a").
1458            
1459             =back
1460            
1461             =begin comment
1462            
1463             =head1 CUSTOMIZATION
1464            
1465             You can override the private internal functions to get your own
1466             functionality. This might be useful if you would like to make
1467             a sub-package (maybe that use a GUI interface), or if you'd like to
1468             build a script on your webserver that will host a game where you
1469             can play against B.
1470            
1471             These brief notes are intended as hints for how to get started.
1472            
1473             For a sub-package, B, you could
1474             inherit from B, and define your own
1475             package-specific I and I functions, which
1476             you could then ask if you could add to the B
1477             distribution.
1478            
1479             For a standalone application, F, you could just C
1480             Games::Literati> and override the default functions, such as
1481             defining your own C function.
1482            
1483             =over 4
1484            
1485             =item sub display()
1486            
1487             This subroutine displays the current state of the board.
1488            
1489             By default, it outputs the board to STDOUT as a 15x15 grid:
1490            
1491             ...............
1492             ...............
1493             ...............
1494             ...............
1495             ...............
1496             ...............
1497             ...............
1498             ...............
1499             ...............
1500             ...............
1501             ...............
1502             ...............
1503             ...............
1504             ...............
1505             ...............
1506            
1507             Override the subroutine to change the style of output
1508            
1509             sub Games::Literati::display { # overrides default behavior
1510             my $f = shift;
1511            
1512             print "\nBoard:\n";
1513             for my $row (0..14) {
1514             print sprintf "%02d ", $row if $f;
1515             for my $col (0..14) {
1516             # use _ instead of .
1517             my $c = $Games::Literati::onboard[$row][$col] || '_';
1518             $c =~ s/\./_/g;
1519             print $c;
1520             }
1521             print "\n";
1522             }
1523             print "\n";
1524             }
1525            
1526             =item input()
1527            
1528             Ask for the current board data: existing tile positions,
1529             wild-tile positions, and the tiles in your hand, and initiate the search
1530             for valid words using the existing board and your hand.
1531            
1532             Overriding sub Games::Literati::input (similarly to display, above)
1533             will allow a change in input method, such as via CGI. Look at the source
1534             code for the default input(), so you know what globals need to be set,
1535             and what to return.
1536            
1537             =item var_init(I, I, I)
1538            
1539             Initialize the board setup, including number of rows and number of columns,
1540             and the number of tiles in a (full) hand. Bingos occur when the number of
1541             tiles played equals I).
1542            
1543             This is used when manually defining a new game. For example, if you wanted
1544             to define a game called C with a 7x7 board, and only 5 tiles
1545             dealt to each player, the "game engine routine" would be defined as
1546            
1547             # run the game:
1548             sub tinyscrabble {
1549             var_init(7,7,5);
1550             _tinyscrabble_init();
1551             display();
1552             search(shift, shift);
1553             }
1554            
1555             =item define your own board
1556            
1557             When creating your own custom game, you need a subroutine to
1558             define the game. Pattern it similar to the following:
1559            
1560             # define the board:
1561             use Games::Literati; # COMING SOON: the use Games::Literati qw/:customizer/
1562             # which will make it easy to get rid of all the $Games::Literati:: prefixes
1563             sub _tinyscrabble_init {
1564             # name the game
1565             $Games::Literati::GameName = "TinyScrabble";
1566            
1567             # define the bonuses
1568             $Games::Literati::bonus[0][0] = 'DW';
1569             # ...
1570             $Games::Literati::bonus[6][6] = 'DW';
1571            
1572             for my $row (0.._max_row) {
1573             for my $col (0.._max_col) {
1574             $onboard[$row][$col] = '.';
1575             }
1576             }
1577            
1578             %Games::Literati::values = (
1579             a => 1,
1580             z => 1000,
1581             )
1582            
1583             $Games::Literati::bingo_bonus = 5000;
1584             }
1585            
1586             =back
1587            
1588             =end comment
1589            
1590             =head1 BUGS AND FEATURE REQUESTS
1591            
1592             Please report any bugs or feature requests thru the web interface at L.
1593            
1594             A simple interface (with examples) for play your own custom grid is in the works. Studying
1595             the source code may point you in the right direction if you want a custom grid before the
1596             customization features are made public.
1597            
1598             =head1 AUTHOR
1599            
1600             Chicheng Zhang Cchichengzhang AT hotmail.comE> wrote the original code.
1601            
1602             Peter C. Jones Cpetercj AT cpan.orgE> is the current maintainer, and
1603             has added various features and made bug fixes.
1604            
1605             =begin html
1606            
1607            
1608            
1609            
1610            
1611            
1612             travis build status
1613             Coverage Status
1614            
1615             =end html
1616            
1617            
1618             =head1 LICENSE AND COPYRIGHT
1619            
1620             Copyright (c) 2003, Chicheng Zhang. Copyright (C) 2016,2019,2020 by Peter C. Jones
1621            
1622             This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
1623            
1624             =cut