File Coverage

blib/lib/Games/WordFind.pm
Criterion Covered Total %
statement 6 184 3.2
branch 0 40 0.0
condition 0 13 0.0
subroutine 2 11 18.1
pod 5 9 55.5
total 13 257 5.0


line stmt bran cond sub pod time code
1             package Games::WordFind;
2 1     1   823 use strict;
  1         2  
  1         42  
3 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         2  
  1         3167  
4              
5             require Exporter;
6             require AutoLoader;
7              
8             @ISA = qw(Exporter AutoLoader);
9             # Items to export into callers namespace by default. Note: do not export
10             # names by default without a very good reason. Use EXPORT_OK instead.
11             # Do not simply export all your public functions/methods/constants.
12             @EXPORT = qw(
13            
14             );
15             $VERSION = '0.02';
16              
17             # declare some package-wide globals
18             my $lim;
19             my @letters=("A".."Z");
20             my @direction=qw(n ne nw e w se s sw);
21             my $trials=50;
22              
23             # given a direction, where can a word start and still fit
24             # in the desired direction:
25             my %range_hash=(
26             'n' => sub {return ([0..$lim],[(length($_[0])-1)..$lim])},
27             's' => sub {return ([0..$lim],[0..($lim-length($_[0]))])},
28             'e' => sub {return ([0..($lim-length($_[0]))],[0..$lim])},
29             'w' => sub {return ([(length($_[0])-1)..$lim],[0..$lim])},
30             'ne' => sub {return ([0..($lim-length($_[0]))],[(length($_[0])-1)..$lim])},
31             'nw' => sub {return ([(length($_[0])-1)..$lim],[(length($_[0])-1)..$lim])},
32             'se' => sub {return ([0..($lim-length($_[0]))],[0..($lim-length($_[0]))])},
33             'sw' => sub {return ([(length($_[0])-1)..$lim],[0..($lim-length($_[0]))])},
34             );
35              
36             # given a direction, how do we adjust the indices for
37             # subsequent letters when inserting a word:
38             my %dir_hash=(
39             'n' => [-1,0],
40             's' => [1,0],
41             'e' => [0,1],
42             'w' => [0,-1],
43             'ne' => [-1,1],
44             'nw' => [-1,-1],
45             'se' => [1,1],
46             'sw' => [1,-1]
47             );
48              
49              
50             sub new {
51 0     0 1   my $that=shift;
52 0   0       my $class=ref($that)||$that;
53 0   0       my $self=shift||{};
54 0   0       $self->{cols}||=10; # default size
55 0           bless $self,$class;
56 0           return $self;
57             }
58              
59             # build the intitial lattice with elements of '*'
60             sub init {
61 0     0 0   my $self=shift;
62 0           $self->{lattice}=undef;
63 0           for (0..$self->{cols}-1) {
64 0           push @{$self->{lattice}[$_]},("*")x$self->{cols};
  0            
65             }
66             }
67              
68             sub create_puzzle {
69 0     0 1   my $self=shift;
70 0           $self->init();
71 0           @{$self->{words}}=@_;
  0            
72 0           my %dropped; #for storing words that don't fit
73 0           $self->{'dropped'}=\%dropped;
74 0           foreach my $word (@{$self->{words}}) {
  0            
75 0 0         if ($self->check_length($word)) {
76 0           warn "Warning: dropping $word, too long!\n";
77 0           $dropped{$word}++;
78 0           next;
79             }
80 0           $self->{success}=0;
81 0           my $tries=0;
82 0           until ($self->{success}) {
83 0           $tries++;
84 0           $self->get_direction();
85 0           $self->insert_word($word);
86 0 0         if ($tries>$trials) {
87 0           warn "too many tries on $word: Dropping $word!\n";
88 0           $dropped{$word}++;
89 0           $self->{success}=1; #its dropped, get out of until loop!
90             }
91             }
92             }
93             # lattice has words inserted now, and will function as
94             # a solution matrix --- now copy lattice words into puzzle
95             # and fill remainder with random letters:
96 0           foreach my $i (0..$#{$self->{lattice}}) {
  0            
97 0           foreach my $j (0..$#{$self->{lattice}[$i]}) {
  0            
98 0 0         if ($self->{lattice}[$i][$j] ne '*') {
99 0           $self->{puzzle}[$i][$j]=$self->{lattice}[$i][$j];
100             } else {
101 0           $self->{puzzle}[$i][$j]=$letters[rand(@letters)];
102             }
103             }
104             }
105             # for output, remove 'dropped' words from wordlist and
106             # sort and up-case the words actually used in the puzzle:
107 0           @{$self->{words}}=map{" \U$_"}
  0            
  0            
108 0           sort grep !$dropped{$_},@{$self->{words}};
109             # hmm, let's actually return ref's to the lattice and such
110             # so user's can format them however they want if desired:
111 0           my @solution=@{$self->{lattice}};
  0            
112 0           my @puzzle=@{$self->{puzzle}};
  0            
113 0           my @words=@{$self->{words}};
  0            
114 0           return \(@puzzle,@words,@solution);
115             }
116              
117             sub check_length {
118 0     0 0   my $self=shift;
119 0 0         return 0 if length($_[0]) <= $self->{cols};
120 0           return 1;
121             }
122             sub get_direction {
123 0     0 0   my $self=shift;
124 0           $self->{dir}=$direction[rand(@direction)];
125             }
126              
127             sub insert_word {
128 0     0 0   my $self=shift;
129 0           my $word = shift;
130 0           $word=uc($word);
131 0           $lim=$self->{cols}-1;
132 0           my @ranges = $range_hash{$self->{dir}}->($word);
133 0 0 0       unless (@{$ranges[0]} && @{$ranges[1]}) {
  0            
  0            
134 0           $self->{success}= 0;
135 0           return 0;
136             }
137 0           my @x=@{$ranges[0]};
  0            
138 0           my @y=@{$ranges[1]};
  0            
139 0           my ($j,$i)=($x[rand(@x)],$y[rand(@y)]);
140 0           my @word=split //,$word;
141 0           my @lat_refs=();
142 0           foreach my $letter (@word) {
143 0 0         if ($self->{intersect}) { #share letters in puzzle
144 0 0 0       if ($self->{lattice}[$i][$j] eq '*' ||
145             $self->{lattice}[$i][$j] eq $letter) {
146 0           push @lat_refs,\$self->{lattice}[$i][$j];
147 0           $i+=$dir_hash{$self->{dir}}->[0];
148 0           $j+=$dir_hash{$self->{dir}}->[1];
149             } else {
150 0           $self->{success}= 0;
151 0           return 0;
152             }
153             } else { # don't share letters
154 0 0         if ($self->{lattice}[$i][$j] eq '*') {
155 0           push @lat_refs,\$self->{lattice}[$i][$j];
156 0           $i+=$dir_hash{$self->{dir}}->[0];
157 0           $j+=$dir_hash{$self->{dir}}->[1];
158             } else {
159 0           $self->{success}= 0;
160 0           return 0;
161             }
162             } #end if intersect
163             }# end foreach
164              
165             # ok, we have a word and it fits...just assign the letters
166             # to the references of the lattice:
167 0           foreach my $l_ref (@lat_refs) {
168 0           $$l_ref=shift @word;
169             }
170 0           $self->{success}= 1; # we made it
171             }
172              
173             # return a puzzle formatted in plain text, with solution
174             # matrix appended if $opts_ref->{solution} non-zero
175             sub get_plain {
176 0     0 1   my $self=shift;
177 0           my $opts_ref=shift;
178 0           my @words=@{$self->{words}};
  0            
179 0           my $puzzle='';
180 0           $puzzle.= "\t\tWords to Find:\n";
181 0           $puzzle.= "\t\t--------------\n\n";
182 0           while (@words) {
183 0 0         my @line=@words>2?splice(@words,0,3):splice(@words,0);
184 0           $puzzle.= join("\t",@line)."\n";
185             }
186 0           $puzzle.= "\n";
187 0           foreach my $ref (@{$self->{puzzle}}) {
  0            
188 0           $puzzle.= "\t\t@$ref\n";
189             }
190 0 0         if ($opts_ref->{solution}) {
191 0           $puzzle.= "\nSolution:\n";
192 0           foreach my $ref (@{$self->{lattice}}) {
  0            
193 0           $puzzle.= "\t\t@$ref\n";
194             }
195             }
196 0           return $puzzle;
197             }
198              
199             # return a puzzle formatted in latex tabular form
200             # -solution on second page if $opts_ref->{solution} non-zero
201             # -complete latex wrapper if $opts_ref->{wrapper} non-zero
202             sub get_latex {
203 0     0 1   my $self=shift;
204 0           my $opts_ref=shift;
205 0           my @words=@{$self->{words}};
  0            
206 0           my $title="{\\Large \\textbf{Find The Following Words:}}";
207 0           my $puzzle='';
208              
209 0           $puzzle.=<
210             %% the arraystretch and tabcolsep values used here were found
211             %% by visual experimentation, not appropriate for other text sizes
212             \\renewcommand{\\arraystretch}{2.25}
213             \\renewcommand{\\tabcolsep}{5pt}
214             \\begin{center}
215             \\begin{tabular}{lllll}
216             \\multicolumn{5}{c}{$title}\\\\ \\hline
217             EOF
218              
219 0           @words=map{"{\\Large $_}"}@words;
  0            
220 0           while (@words) {
221 0 0         my @line=@words>2?splice(@words,0,3):splice(@words,0);
222 0 0         if (@line<3) {
223 0 0         @line=@line==2?(@line,''):(@line,'','');
224             }
225 0           $puzzle.= join('& &',@line)."\\\\ \n";
226             }
227              
228 0           $puzzle.=<
229             \\end{tabular}
230             \\end{center}
231             EOF
232              
233 0           my $c=$self->{cols};
234 0           $c='c'x$c;
235              
236 0           $puzzle.=<
237             \\begin{center}
238             \\begin{tabular}{$c}
239             EOF
240              
241 0           foreach my $ref (@{$self->{puzzle}}) {
  0            
242 0           @$ref=map{"{\\huge $_}"}@$ref;
  0            
243 0           $puzzle.= join('&',@$ref)."\\\\ \n";
244             }
245              
246 0           $puzzle.=<
247             \\end{tabular}
248             \\end{center}
249             EOF
250            
251 0 0         if ($opts_ref->{solution}) {
252              
253 0           $puzzle.=<
254             \\newpage
255             \\begin{center}
256             \\begin{tabular}{$c}
257             EOF
258              
259 0           foreach my $ref (@{$self->{lattice}}) {
  0            
260 0           @$ref=map{"{\\huge $_}"}@$ref;
  0            
261 0           $puzzle.= join('&',@$ref)."\\\\ \n";
262             }
263              
264 0           $puzzle.=<
265             \\end{tabular}
266             \\end{center}
267             EOF
268             } # end if solution
269              
270 0 0         if ($opts_ref->{wrapper}) {
271              
272 0           $puzzle=<
273             \\documentclass[10pt,letterpaper,]{article}
274             \\oddsidemargin -1in
275             \\textwidth 8.5in
276             \\begin{document}
277             \\pagestyle{empty}
278             $puzzle
279             \\end{document}
280             EOF
281             } # end if wrapper
282 0           return $puzzle;
283             }
284              
285             #############################
286             #############################
287             sub get_html {
288 0     0 1   my $self=shift;
289 0           my $opts_ref=shift;
290 0           my @words=@{$self->{words}};
  0            
291 0           my $title="

Find The Following Words:

";
292 0           my $puzzle='';
293              
294 0           $puzzle.=<
295             $title
296            
297            
298             \n";
299             EOF
300              
301 0           while (@words) {
302 0 0         my @line=@words>2?splice(@words,0,3):splice(@words,0);
303 0 0         if (@line<3) {
304 0 0         @line=@line==2?(@line,''):(@line,'','');
305             }
306 0           $puzzle.= '
'.
307             join('',@line)."
308             }
309              
310 0           $puzzle.="
\n";
311              
312 0           my $c=$self->{cols};
313 0           $c='c'x$c;
314              
315 0           $puzzle.="\n"; \n";
316              
317 0           foreach my $ref (@{$self->{puzzle}}) {
  0            
318 0           @$ref=map{" $_"}@$ref;
  0            
319 0           $puzzle.='
'.
320             join('',@$ref)."
321             }
322              
323 0           $puzzle.="
\n";
324            
325 0 0         if ($opts_ref->{solution}) {
326              
327 0           $puzzle.="

Solution:
\n"; \n";
328              
329 0           foreach my $ref (@{$self->{lattice}}) {
  0            
330 0           @$ref=map{" $_"}@$ref;
  0            
331 0           $puzzle.='
'.
332             join('',@$ref)."
333             }
334              
335 0           $puzzle.="
\n";
336             } # end if solution
337              
338 0 0         if ($opts_ref->{wrapper}) {
339              
340 0           $puzzle=<
341            
342             WordFind
343            
344             $puzzle
345            
346            
347             EOF
348             } # end if wrapper
349 0           return $puzzle;
350             }
351              
352             #############################
353              
354             1;
355             __END__