File Coverage

blib/lib/Games/Jumble.pm
Criterion Covered Total %
statement 15 149 10.0
branch 0 52 0.0
condition 0 9 0.0
subroutine 5 19 26.3
pod 7 7 100.0
total 27 236 11.4


line stmt bran cond sub pod time code
1             package Games::Jumble;
2              
3 1     1   25577 use warnings;
  1         2  
  1         32  
4 1     1   7 use strict;
  1         2  
  1         35  
5 1     1   6 use Carp;
  1         6  
  1         88  
6 1     1   6 use vars qw($VERSION $AUTOLOAD);
  1         2  
  1         1965  
7              
8             =head1 NAME
9              
10             Games::Jumble - Create and solve Jumble word puzzles.
11              
12             =head1 VERSION
13              
14             Version 0.09
15              
16             =cut
17              
18             our $VERSION = '0.09';
19              
20             =head1 SYNOPSIS
21              
22             use Games::Jumble;
23              
24             my $jumble = Games::Jumble->new();
25             $jumble->set_num_words(6);
26             $jumble->set_word_lengths_allowed(5,6);
27             $jumble->set_word_lengths_not_allowed(7,8);
28             $jumble->set_dict('/home/doug/crossword_dict/unixdict.txt');
29              
30             my @jumble = $jumble->create_jumble;
31              
32             foreach my $word (@jumble) {
33             print "$word\n";
34             }
35              
36             # Solve jumbled word
37             my @good_words = $jumble->solve_word('rta');
38              
39             if (@good_words) {
40             foreach my $good_word (@good_words) {
41             print "$good_word\n";
42             }
43             } else {
44             print "No words found\n";
45             }
46              
47             # Create jumbled word
48             my $word = 'camel';
49             my $jumbled_word = $jumble->jumble_word($word);
50              
51             print "$jumbled_word ($word)\n";
52              
53             =head1 DESCRIPTION
54              
55             C is used to create and solve Jumble word puzzles.
56              
57             Currently C will create random five- and six-letter
58             jumbled words from dictionary. Future versions of C will
59             allow user to create custom jumbles by using a user defined word file
60             with words of any length.
61             Individual words of any length may be jumbled by using the
62             C method.
63              
64             Default number of words is 5.
65             Default dictionary is '/usr/dict/words'.
66             Dictionary file must contain one word per line.
67              
68             =cut
69              
70             {
71             # Encapsulated data
72             my %_attr_data = # DEFAULT ACCESSIBILITY
73             (
74             _num_words => [ 5, 'read/write' ],
75             _dict => [ '/usr/dict/words', 'read/write' ],
76             _word_lengths_allowed => [ '', 'read' ],
77             _word_lengths_not_allowed => [ '', 'read' ],
78             );
79              
80             # Class methods, to operate on encapsulated class data
81             sub _accessible {
82 0     0     my ( $self, $attr, $mode ) = @_;
83 0           $_attr_data{$attr}[1] =~ /$mode/;
84             }
85              
86             # Classwide efault value for a specified object attribute
87             sub _default_for {
88 0     0     my ( $self, $attr ) = @_;
89 0           $_attr_data{$attr}[0];
90             }
91              
92             # List of names of all specified object attributes
93             sub _standard_keys {
94 0     0     keys %_attr_data;
95             }
96             }
97              
98             =head2 new
99              
100             This is the constructor for a new Games::Jumble object.
101              
102             my $jumble = Games::Jumble->new();
103              
104             If C is passed, this method will set the number of words for the puzzle, otherwise number of words is set to default value of 5.
105              
106             my $jumble = Games::Jumble->new(num_words=>$num_words);
107              
108             =cut
109              
110             sub new {
111 0     0 1   my ( $caller, %arg ) = @_;
112 0           my $caller_is_obj = ref($caller);
113 0   0       my $class = $caller_is_obj || $caller;
114 0           my $self = bless {}, $class;
115 0           foreach my $attrname ( $self->_standard_keys() ) {
116 0           my ($argname) = ( $attrname =~ /^_(.*)/ );
117 0 0         if ( exists $arg{$argname} ) {
    0          
118 0           $self->{$attrname} = $arg{$argname};
119             }
120             elsif ($caller_is_obj) {
121 0           $self->{$attrname} = $caller->{$attrname};
122             }
123             else {
124 0           $self->{$attrname} = $self->_default_for($attrname);
125             }
126             }
127 0           return $self;
128             }
129              
130 0     0     sub DESTROY {
131              
132             # This space deliberately left blank
133             }
134              
135             # Non autoloaded methods here
136              
137             =head2 set_word_lengths_allowed ( length1 [, length2, length3,...] )
138              
139             If C is(are) passed, this method will set word lengths
140             that will be used when creating jumble.
141             The default setting will use all word lengths.
142             Note: Allow all is designated by empty hash.
143              
144             =cut
145              
146             sub set_word_lengths_allowed {
147 0     0 1   my($self) = shift;
148 0 0         if(@_) {
149 0           my %allowed;
150 0           foreach my $allow( @_ ) {
151 0           $allowed{$allow}++;
152             }
153 0           $self->{_word_lengths_allowed} = \%allowed;
154             }
155             }
156              
157             =head2 set_word_lengths_not_allowed ( length1 [, length2, length3,...] )
158              
159             If C is(are) passed, this method will set word lengths
160             that will be skipped when creating jumble.
161             The default setting will not skip any word lengths.
162             Note: Skip none is designated by empty hash.
163              
164             =cut
165              
166             sub set_word_lengths_not_allowed {
167 0     0 1   my($self) = shift;
168 0 0         if(@_) {
169 0           my %not_allowed;
170 0           foreach my $length( @_ ) {
171 0           $not_allowed{$length}++;
172             }
173 0           $self->{_word_lengths_not_allowed} = \%not_allowed;
174             }
175             }
176              
177             =head2 create_jumble
178              
179             This method creates the jumble.
180              
181             =cut
182              
183             sub create_jumble {
184              
185 0     0 1   my($self) = shift;
186 0           my @jumble;
187             my @jumble_out;
188 0           my %words;
189              
190             # Read dictionary and get words
191 0 0         open FH, $self->get_dict or croak "Cannot open $self->get_dict: $!";
192 0           while() {
193 0           chomp;
194 0           my $word = lc $_; # Lower case all words
195 0 0         next if $word !~ /^[a-z]+$/; # Letters only
196              
197             # Sort letters so we can check for unique "unjumble"
198 0           my @temp_array = split(//, $word);
199 0           @temp_array = sort(@temp_array);
200 0           my $key = join('', @temp_array);
201              
202             # Check for word lengths allowed
203 0 0         if( $self->get_word_lengths_allowed ) {
204 0           my $allowed_ref = $self->get_word_lengths_allowed;
205 0 0         next unless exists $allowed_ref->{length $_};
206             }
207              
208             # Check for word lengths not allowed
209 0 0         if( $self->get_word_lengths_not_allowed ) {
210 0           my $not_allowed_ref = $self->get_word_lengths_not_allowed;
211 0 0         next if exists $not_allowed_ref->{length $_};
212             }
213              
214             # perlreftut is your friend
215 0           push @{$words{$key}}, $_;
  0            
216            
217             }
218 0           close FH;
219              
220             # Get words that only "unjumble" one way
221 0           my @unique_words;
222              
223 0           foreach my $word (keys %words) {
224 0           my $length = @{$words{$word}};
  0            
225 0 0         if ($length == 1) {
226 0           push @unique_words, @{$words{$word}};
  0            
227             }
228             }
229 0           @unique_words = sort @unique_words;
230              
231              
232             # Get random words for jumble
233 0           for (1..$self->get_num_words) {
234 0           my $el = $unique_words[rand @unique_words];
235 0 0         redo if $el =~ /(\w)\1+/; # No words like ii, ooo or aaa
236 0           push(@jumble, $el);
237             }
238              
239             # Scramble the words
240 0           foreach my $word (@jumble) {
241 0           my $jumbled_word = $self->jumble_word($word);
242 0           push @jumble_out, "$jumbled_word ($word)";
243             }
244              
245 0           return @jumble_out;
246              
247             }
248              
249             =head2 jumble_word ( WORD )
250              
251             This method will create a jumbled word.
252             Returns scalar containing jumbled word.
253              
254             =cut
255              
256             sub jumble_word {
257              
258 0     0 1   my($self) = shift;
259 0           my $word;
260              
261 0 0         if(@_) {
262 0           $word = shift;
263             } else {
264 0           $word = undef;
265 0           return $word;
266             }
267              
268 0           my @temp_array = split(//, $word);
269              
270             # From the camel
271 0           my $array = \@temp_array;
272 0           my $jumbled_word = $word;
273              
274             # Make sure we actually scramble the word
275 0           while( $jumbled_word eq $word ) {
276 0           for (my $i = @$array; --$i; ) {
277 0           my $j = int rand ($i+1);
278 0 0         next if $i == $j;
279 0           @$array[$i,$j] = @$array[$j,$i];
280             }
281 0           $jumbled_word = join('', @temp_array);
282             }
283              
284 0           return $jumbled_word;
285             }
286              
287             =head2 solve_word ( WORD )
288              
289             This method will solve a jumbled word.
290             Returns list of solved words.
291              
292             =cut
293              
294             sub solve_word {
295 0     0 1   my($self) = shift;
296 0           my @good_words;
297            
298 0 0         if(@_) {
299 0           $self->{word} = lc(shift);
300             } else {
301 0           croak "No word to solve\n";
302             }
303              
304 0           my @temp_array = split(//, $self->{word});
305 0           @temp_array = sort(@temp_array);
306 0           $self->{key} = join('', @temp_array);
307              
308             # Read dictionary and get words same length as $self->{word}
309 0 0         open FH, $self->get_dict or croak "Cannot open $self->get_dict: $!";
310 0           while() {
311 0           chomp;
312 0           my $word = lc $_; # Lower case all words
313 0 0         next if $word !~ /^[a-z]+$/; # Letters only
314 0 0         next if length($word) ne length($self->{word});
315              
316             # Sort letters so we can check for unique "unjumble"
317 0           my @temp_array = split(//, $word);
318 0           @temp_array = sort(@temp_array);
319 0           my $key = join('', @temp_array);
320              
321 0 0         if ($self->{key} eq $key) {
322 0           push @good_words, $word;
323             }
324             }
325 0           close FH;
326              
327 0           return @good_words;
328             }
329              
330             =head2 solve_crossword ( WORD )
331              
332             This method will solve an incomplete word as needed for a crossword.
333             WORD format: 'c?m?l' where question marks are used a placeholders
334             for unknown letter.
335             Returns list of solved words.
336              
337             =cut
338              
339             sub solve_crossword {
340 0     0 1   my($self) = shift;
341 0           my @good_words;
342            
343 0 0         if(@_) {
344 0           $self->{word} = lc(shift);
345             } else {
346 0           croak "No word to solve\n";
347             }
348            
349             # Set regex
350 0           ($self->{word_regex} = $self->{word}) =~ s/\?/\\w{1}/g;
351              
352             # Read dictionary and get all words same length as $self->{word}
353 0 0         open FH, $self->get_dict or croak "Cannot open $self->get_dict: $!";
354 0           while() {
355 0           chomp;
356 0           my $word = lc $_; # Lower case all words
357 0 0         next if $word !~ /^[a-z]+$/; # Letters only
358 0 0         next if length($word) ne length($self->{word});
359              
360 0 0         if ($word =~ $self->{word_regex}) {
361 0           push @good_words, $word;
362             }
363             }
364 0           close FH;
365              
366 0           return @good_words;
367             }
368              
369             ### autoloaded methods ###
370             # get_num_words, get_dict
371             # set_num_words, set_dict
372             # set_word_lengths_allowed, set_word_lengths_not_allowed
373              
374             sub AUTOLOAD {
375 1     1   10 no strict "refs";
  1         2  
  1         315  
376 0     0     my ( $self, $newval ) = @_;
377              
378             # Was it a get_... method?
379 0 0 0       if ( $AUTOLOAD =~ /.*::get(_\w+)/ && $self->_accessible( $1, 'read' ) ) {
380 0           my $attr_name = $1;
381 0     0     *{$AUTOLOAD} = sub { return $_[0]->{$attr_name} };
  0            
  0            
382 0           return $self->{$attr_name};
383             }
384              
385             # Was it a set_... method?
386 0 0 0       if ( $AUTOLOAD =~ /.*::set(_\w+)/ && $self->_accessible( $1, 'write' ) ) {
387 0           my $attr_name = $1;
388 0     0     *{$AUTOLOAD} = sub { $_[0]->{$attr_name} = $_[1]; return };
  0            
  0            
  0            
389 0           $self->{$1} = $newval;
390 0           return;
391             }
392              
393             # Must have been a mistake then
394 0           croak "No such method: $AUTOLOAD";
395             }
396              
397             1;
398              
399             =head1 AUTHOR
400              
401             Doug Sparling, C<< >>
402              
403             =head1 BUGS
404              
405             Please report any bugs or feature requests to
406             C, or through the web interface at
407             L.
408             I will be notified, and then you'll automatically be notified of progress on
409             your bug as I make changes.
410              
411             =head1 SUPPORT
412              
413             You can find documentation for this module with the perldoc command.
414              
415             perldoc Games::Jumble
416              
417             You can also look for information at:
418              
419             =over 4
420              
421             =item * AnnoCPAN: Annotated CPAN documentation
422              
423             L
424              
425             =item * CPAN Ratings
426              
427             L
428              
429             =item * RT: CPAN's request tracker
430              
431             L
432              
433             =item * Search CPAN
434              
435             L
436              
437             =back
438              
439             =head1 ACKNOWLEDGEMENTS
440              
441             Tim Maher for pointing out some outdated documentation in the Synopsis.
442              
443             =head1 COPYRIGHT & LICENSE
444              
445             Copyright 2001-2007 Doug Sparling, all rights reserved.
446              
447             This program is free software; you can redistribute it and/or modify it
448             under the same terms as Perl itself.
449              
450             =cut
451              
452             1; # End of Games::Jumble