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
1613
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