line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Game::WordBrain; |
2
|
|
|
|
|
|
|
|
3
|
5
|
|
|
5
|
|
18919
|
use strict; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
107
|
|
4
|
5
|
|
|
5
|
|
14
|
use warnings; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
89
|
|
5
|
|
|
|
|
|
|
|
6
|
5
|
|
|
5
|
|
308
|
use Game::WordBrain::Letter; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
84
|
|
7
|
5
|
|
|
5
|
|
882
|
use Game::WordBrain::Word; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
91
|
|
8
|
5
|
|
|
5
|
|
1517
|
use Game::WordBrain::Solution; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
90
|
|
9
|
5
|
|
|
5
|
|
300
|
use Game::WordBrain::WordToFind; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
69
|
|
10
|
5
|
|
|
5
|
|
1517
|
use Game::WordBrain::Prefix; |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
169
|
|
11
|
5
|
|
|
5
|
|
2441
|
use Game::WordBrain::Speller; |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
137
|
|
12
|
|
|
|
|
|
|
|
13
|
5
|
|
|
5
|
|
3380
|
use Storable qw( dclone ); |
|
5
|
|
|
|
|
18849
|
|
|
5
|
|
|
|
|
393
|
|
14
|
5
|
|
|
5
|
|
35
|
use List::Util qw( reduce first ); |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
437
|
|
15
|
5
|
|
|
5
|
|
2971
|
use List::MoreUtils qw( first_index ); |
|
5
|
|
|
|
|
48467
|
|
|
5
|
|
|
|
|
37
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = '0.2.1'; # VERSION |
18
|
|
|
|
|
|
|
# ABSTRACT: Solver for Mag Interactive's WordBrain Mobile Game |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 NAME |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
Game::WordBrain - Solver for the Mobile App "WordBrain" |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 SYNOPSIS |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# Create a new Game::WordBrain |
27
|
|
|
|
|
|
|
my @letters; |
28
|
|
|
|
|
|
|
push @letters, Game::WordBrain::Letter->new({ letter => 't', row => 0, col => 0 }); |
29
|
|
|
|
|
|
|
push @letters, Game::WordBrain::Letter->new({ letter => 'a', row => 0, col => 1 }); |
30
|
|
|
|
|
|
|
push @letters, Game::WordBrain::Letter->new({ letter => 'l', row => 1, col => 0 }); |
31
|
|
|
|
|
|
|
push @letters, Game::WordBrain::Letter->new({ letter => 'k', row => 1, col => 1 }); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $words_to_find = [ Game::WordBrain::WordToFind->... ]; |
34
|
|
|
|
|
|
|
my $speller = Game::WordBrain::Speller->...; |
35
|
|
|
|
|
|
|
my $prefix = Game::WordBrain::Prefix->...; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my $game = Game::WordBrain->new({ |
38
|
|
|
|
|
|
|
letters => \@letters, |
39
|
|
|
|
|
|
|
words_to_find => $words_to_find, |
40
|
|
|
|
|
|
|
speller => $speller, # optional |
41
|
|
|
|
|
|
|
prefix => $prefix, # optional |
42
|
|
|
|
|
|
|
}); |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# Solve a Game |
45
|
|
|
|
|
|
|
$game->solve(); |
46
|
|
|
|
|
|
|
print "Number of Solutions Found: " . ( scalar @{ $game->{solutions} } ) . "\n"; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# Construct a game without a word |
49
|
|
|
|
|
|
|
my $already_used_word = Game::WordBrain::Word->...; |
50
|
|
|
|
|
|
|
my $sub_game = $game->construct_game_without_word( $already_used_word ); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# Get letter at position |
53
|
|
|
|
|
|
|
my $letter = $game->get_letter_at_position({ |
54
|
|
|
|
|
|
|
row => 2, |
55
|
|
|
|
|
|
|
col => 3, |
56
|
|
|
|
|
|
|
}); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# Find Near letters |
60
|
|
|
|
|
|
|
my $near_letters = $game->find_near_letters({ |
61
|
|
|
|
|
|
|
used => [ Game::WordBrain::Letter->... ], |
62
|
|
|
|
|
|
|
row_number => 1, |
63
|
|
|
|
|
|
|
col_number => 1, |
64
|
|
|
|
|
|
|
}); |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# Find Near Words |
68
|
|
|
|
|
|
|
my $near_words = $game->find_near_words({ |
69
|
|
|
|
|
|
|
letter => WordBrain::Letter->..., |
70
|
|
|
|
|
|
|
used => [ ], # Optional |
71
|
|
|
|
|
|
|
max_word_length => 5, # Optional |
72
|
|
|
|
|
|
|
}); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head1 DESCRIPTION |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Game::WordBrain is a solver created to generation potential solutions for L's WordBrain. WordBrain is available for: |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=over 4 |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item L |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=item L |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=back |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
This module is currently functional for I games ( 4x4 and less ) but it requires B time to process larger ones. Feel free to propose improvements at the L for this repo! |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
If you are new to WordBrain or simply want a jumpstart on how this module works and it's limitations (and evolution) please see: |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=over 4 |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=item L |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=item L |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=back |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=head2 B |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
ArrayRef of Ls that comprise the game field |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=head2 B |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
ArrayRef of Ls that indicate the number of words to find as well as the length of each word. |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head2 speller |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
An instance of L that is used to spell check potential words. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
If this is not provided it will be automagically built. You generally do not need to provide this but if you wish to use something other than the provided wordlist creating your own L and providing it in the call to new would be how to accomplish that. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head2 prefix |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
An instance of L used to speed up game play. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
If not provided, the max word_to_find will be detected and used to construct it. You generally do not need to provide this but if you wish to use something other than the provided wordlist creating your own L and providing it in the call to new would be how to accomplish that. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=head2 solutions |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
Generated after a call to ->solve has been made. This is an ArrayRef of Ls. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head1 METHODS |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=head2 new |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
my $letters = [ Game::WordBrain::Letter->... ]; |
129
|
|
|
|
|
|
|
my $words_to_find = [ Game::WordBrain::WordToFind->... ]; |
130
|
|
|
|
|
|
|
my $speller = Game::WordBrain::Speller->...; |
131
|
|
|
|
|
|
|
my $prefix = Game::WordBrain::Prefix->...; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
my $game = Game::WordBrain->new({ |
134
|
|
|
|
|
|
|
letters => $letters, |
135
|
|
|
|
|
|
|
words_to_find => $words_to_find, |
136
|
|
|
|
|
|
|
speller => $speller, # optional |
137
|
|
|
|
|
|
|
prefix => $prefix, # optional |
138
|
|
|
|
|
|
|
}); |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
Given an ArrayRef of Ls, an ArrayRef of L, and optionally an instance of L and L constructs and returns a new WordBrain game. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
B While it is also possible to pass solutions => [ Game::WordBrain::Solution->...], there is really no reason for a consumer to do so. |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=cut |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub new { |
147
|
3
|
|
|
3
|
1
|
85
|
my $class = shift; |
148
|
3
|
|
|
|
|
5
|
my $args = shift; |
149
|
|
|
|
|
|
|
|
150
|
3
|
50
|
|
|
|
10
|
if( !exists $args->{solutions} ) { |
151
|
3
|
|
|
|
|
7
|
$args->{solutions} = undef; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
3
|
100
|
|
|
|
9
|
if( !exists $args->{speller} ) { |
155
|
2
|
|
|
|
|
15
|
$args->{speller} = Game::WordBrain::Speller->new(); |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
3
|
100
|
|
|
|
15
|
if( !exists $args->{prefix} ) { |
159
|
|
|
|
|
|
|
my $largest_word_to_find = reduce { |
160
|
2
|
100
|
|
2
|
|
30
|
$a->{num_letters} > $b->{num_letters} ? $a : $b |
161
|
2
|
|
|
|
|
16
|
} @{ $args->{words_to_find} }; |
|
2
|
|
|
|
|
42
|
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
$args->{prefix} = Game::WordBrain::Prefix->new({ |
164
|
|
|
|
|
|
|
max_prefix_length => $largest_word_to_find->{num_letters} |
165
|
2
|
|
|
|
|
35
|
}); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
3
|
|
|
|
|
31
|
return bless $args, $class; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head2 solve |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
my @letters; |
174
|
|
|
|
|
|
|
push @letters, WordBrain::Letter->new({ letter => 't', row => 0, col => 0 }); |
175
|
|
|
|
|
|
|
push @letters, WordBrain::Letter->new({ letter => 'a', row => 0, col => 1 }); |
176
|
|
|
|
|
|
|
push @letters, WordBrain::Letter->new({ letter => 'l', row => 1, col => 0 }); |
177
|
|
|
|
|
|
|
push @letters, WordBrain::Letter->new({ letter => 'k', row => 1, col => 1 }); |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
my @words_to_find; |
180
|
|
|
|
|
|
|
push @words_to_find, WordBrain::WordToFind->new({ num_letters => 4 }); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
my $game = Game::WordBrain->new({ |
183
|
|
|
|
|
|
|
letters => \@letters, |
184
|
|
|
|
|
|
|
words_to_find => \@words_to_find, |
185
|
|
|
|
|
|
|
}); |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
$game->solve(); |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
print "Number of Solutions Found: " . ( scalar @{ $game->{solutions} } ) . "\n"; |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
The solve method is the real meat of L. When called on a fully formed game this method will enumerate potential solutions and set the $game->{solutions} attribute. |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
B Depending on the size of the game grid, this method can take a very long time to run. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=cut |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub solve { |
198
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
199
|
|
|
|
|
|
|
|
200
|
0
|
|
|
|
|
0
|
my $max_word_length = 0; |
201
|
0
|
|
|
|
|
0
|
for my $word_to_find (@{ $self->{words_to_find} }) { |
|
0
|
|
|
|
|
0
|
|
202
|
0
|
0
|
|
|
|
0
|
if( $max_word_length < $word_to_find->{num_letters} ) { |
203
|
0
|
|
|
|
|
0
|
$max_word_length = $word_to_find->{num_letters}; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
0
|
my @solutions; |
208
|
0
|
|
|
|
|
0
|
for my $letter (@{ $self->{letters} }) { |
|
0
|
|
|
|
|
0
|
|
209
|
0
|
|
|
|
|
0
|
my $possible_words = $self->find_near_words({ |
210
|
|
|
|
|
|
|
letter => $letter, |
211
|
|
|
|
|
|
|
max_word_length => $max_word_length, |
212
|
|
|
|
|
|
|
}); |
213
|
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
0
|
my @actual_words; |
215
|
0
|
|
|
|
|
0
|
for my $possible_word (@{ $possible_words }) { |
|
0
|
|
|
|
|
0
|
|
216
|
0
|
0
|
|
|
|
0
|
if( grep { $_->{num_letters} == length ( $possible_word->word ) } @{ $self->{words_to_find} } ) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
217
|
0
|
0
|
|
|
|
0
|
if( $self->{speller}->is_valid_word( $possible_word ) ) { |
218
|
0
|
|
|
|
|
0
|
push @actual_words, $possible_word; |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
|
224
|
0
|
|
|
|
|
0
|
for my $word ( @actual_words ) { |
225
|
0
|
0
|
|
|
|
0
|
if( scalar @{ $self->{words_to_find} } > 1 ) { |
|
0
|
|
|
|
|
0
|
|
226
|
0
|
|
|
|
|
0
|
my $updated_game = $self->construct_game_without_word( $word ); |
227
|
0
|
|
|
|
|
0
|
my $updated_game_solutions = $updated_game->solve(); |
228
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
0
|
for my $updated_game_solution (@{ $updated_game_solutions }) { |
|
0
|
|
|
|
|
0
|
|
230
|
|
|
|
|
|
|
push @solutions, Game::WordBrain::Solution->new({ |
231
|
0
|
|
|
|
|
0
|
words => [ $word, @{ $updated_game_solution->{words} } ], |
|
0
|
|
|
|
|
0
|
|
232
|
|
|
|
|
|
|
}); |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
else { |
236
|
0
|
|
|
|
|
0
|
push @solutions, Game::WordBrain::Solution->new({ |
237
|
|
|
|
|
|
|
words => [ $word ], |
238
|
|
|
|
|
|
|
}); |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
0
|
|
|
|
|
0
|
$self->{solutions} = \@solutions; |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=head2 construct_game_without_word |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
my $word = Game::WordBrain::Word->...; |
249
|
|
|
|
|
|
|
my $game = Game::WordBrain->...; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
my $sub_game = $game->construct_game_without_word( $word ); |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
In WordBrain, once a word is matched the letters for it are removed from the playing field, causing all other letters to shift down (think of it like gravity pulling the letters straight down). This method exists to simplify the process of generating a new instance of a L from an existing instance minus the found word. |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
There really isn't a reason for a consumer to call this method directly, rather it is used by the solve method during solution enumeration. |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=cut |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
sub construct_game_without_word { |
260
|
1
|
|
|
1
|
1
|
325
|
my $self = shift; |
261
|
1
|
|
|
|
|
2
|
my $found_word = shift; |
262
|
|
|
|
|
|
|
|
263
|
1
|
|
|
|
|
106
|
my $words_to_find = dclone $self->{words_to_find}; |
264
|
|
|
|
|
|
|
my $index_of_found_word = first_index { |
265
|
2
|
|
|
2
|
|
3
|
$_->{num_letters} == scalar @{ $found_word->{letters} } |
|
2
|
|
|
|
|
22
|
|
266
|
1
|
|
|
|
|
4
|
} @{ $self->{words_to_find} }; |
|
1
|
|
|
|
|
10
|
|
267
|
|
|
|
|
|
|
|
268
|
1
|
|
|
|
|
3
|
splice @{ $words_to_find }, $index_of_found_word, 1; |
|
1
|
|
|
|
|
3
|
|
269
|
|
|
|
|
|
|
|
270
|
1
|
|
|
|
|
6
|
my @new_letters; |
271
|
1
|
|
|
|
|
2
|
for my $letter (@{ $self->{letters} }) { |
|
1
|
|
|
|
|
2
|
|
272
|
9
|
100
|
|
|
|
11
|
if( grep { $_ == $letter } @{ $found_word->{letters} } ) { |
|
36
|
|
|
|
|
60
|
|
|
9
|
|
|
|
|
10
|
|
273
|
4
|
|
|
|
|
4
|
next; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
my $num_letters_used_below = grep { |
277
|
|
|
|
|
|
|
$_->{col} == $letter->{col} |
278
|
|
|
|
|
|
|
&& $_->{row} > $letter->{row} |
279
|
5
|
100
|
|
|
|
5
|
} @{ $found_word->{letters} }; |
|
20
|
|
|
|
|
41
|
|
|
5
|
|
|
|
|
5
|
|
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
push @new_letters, Game::WordBrain::Letter->new({ |
282
|
|
|
|
|
|
|
letter => $letter->{letter}, |
283
|
|
|
|
|
|
|
row => $letter->{row} + $num_letters_used_below, |
284
|
|
|
|
|
|
|
col => $letter->{col}, |
285
|
5
|
|
|
|
|
19
|
}); |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
return Game::WordBrain->new({ |
289
|
|
|
|
|
|
|
letters => \@new_letters, |
290
|
|
|
|
|
|
|
words_to_find => $words_to_find, |
291
|
|
|
|
|
|
|
speller => $self->{speller}, |
292
|
|
|
|
|
|
|
prefix => $self->{prefix}, |
293
|
1
|
|
|
|
|
7
|
}); |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=head2 get_letter_at_position |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
my $game = Game::WordBrain->... |
299
|
|
|
|
|
|
|
my $letter = $game->get_letter_at_position({ |
300
|
|
|
|
|
|
|
row => 2, |
301
|
|
|
|
|
|
|
col => 3, |
302
|
|
|
|
|
|
|
}); |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
Simple convenience method to retrieve the instance of L at a given row and col. |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=cut |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub get_letter_at_position { |
309
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
310
|
0
|
|
|
|
|
|
my $args = shift; |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
return first { |
313
|
|
|
|
|
|
|
$_->{row} == $args->{row} |
314
|
|
|
|
|
|
|
&& $_->{col} == $args->{col} |
315
|
0
|
0
|
|
0
|
|
|
} @{ $self->{letters} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head2 find_near_letters |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
my $game = Game::WordBrain->... |
321
|
|
|
|
|
|
|
my $near_letters = $game->find_near_letters({ |
322
|
|
|
|
|
|
|
used => [ Game::WordBrain::Letter->... ], |
323
|
|
|
|
|
|
|
row_number => 1, |
324
|
|
|
|
|
|
|
col_number => 1, |
325
|
|
|
|
|
|
|
}); |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
Given an ArrayRef of already used (for other words) Ls, and the row and col number of a position, returns an ArrayRef of Ls that are "near" the specified position. By "near" we mean a letter that is touching the specified position in one of the 8 cardinal directions and has not already been used. |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=cut |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub find_near_letters { |
332
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
333
|
0
|
|
|
|
|
|
my $args = shift; |
334
|
|
|
|
|
|
|
|
335
|
0
|
|
|
|
|
|
my @near_letters; |
336
|
0
|
|
|
|
|
|
for my $row_offset ( -1, 0, 1 ) { |
337
|
0
|
|
|
|
|
|
for my $col_offset ( -1, 0, 1 ) { |
338
|
0
|
0
|
0
|
|
|
|
if( $row_offset == 0 && $col_offset == 0 ) { |
339
|
|
|
|
|
|
|
### Skipping Center Letter |
340
|
0
|
|
|
|
|
|
next; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
0
|
|
|
|
|
|
my $near_row_number = $args->{row_number} + $row_offset; |
344
|
0
|
|
|
|
|
|
my $near_col_number = $args->{col_number} + $col_offset; |
345
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
|
my $letter = $self->get_letter_at_position({ |
347
|
|
|
|
|
|
|
row => $near_row_number, |
348
|
|
|
|
|
|
|
col => $near_col_number, |
349
|
|
|
|
|
|
|
}); |
350
|
|
|
|
|
|
|
|
351
|
0
|
0
|
|
|
|
|
if( !$letter ) { |
352
|
0
|
|
|
|
|
|
next; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
0
|
0
|
|
|
|
|
if( grep { $_ == $letter } @{ $args->{used} } ) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
### Skipping Already Used Letter |
357
|
0
|
|
|
|
|
|
next; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
|
360
|
0
|
|
|
|
|
|
push @near_letters, $letter; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
0
|
|
|
|
|
|
return \@near_letters; |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
=head2 find_near_words |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
my $game = Game::WordBrain->...; |
370
|
|
|
|
|
|
|
my $near_words = $game->find_near_words({ |
371
|
|
|
|
|
|
|
letter => WordBrain::Letter->..., |
372
|
|
|
|
|
|
|
used => [ ], # Optional |
373
|
|
|
|
|
|
|
max_word_length => 5, # Optional |
374
|
|
|
|
|
|
|
}); |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
Similiar to find_near_letters, but returns an ArrayRef of Ls that can be constructed from the given L, ArrayRef of used Ls and the max_word_length that should be searched for ( this should be the max L->{num_letters} ). |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=cut |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
sub find_near_words { |
381
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
382
|
0
|
|
|
|
|
|
my $args = shift; |
383
|
|
|
|
|
|
|
|
384
|
0
|
|
0
|
|
|
|
$args->{used} //= [ ]; |
385
|
0
|
|
0
|
|
|
|
$args->{max_word_length} //= scalar @{ $self->{letters} }; |
|
0
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
return $self->_find_near_words({ |
388
|
|
|
|
|
|
|
word_root => Game::WordBrain::Word->new({ letters => [ $args->{letter} ] }), |
389
|
|
|
|
|
|
|
letter => $args->{letter}, |
390
|
|
|
|
|
|
|
used => $args->{used}, |
391
|
|
|
|
|
|
|
max_word_length => $args->{max_word_length}, |
392
|
0
|
|
|
|
|
|
}); |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub _find_near_words { |
396
|
0
|
|
|
0
|
|
|
my $self = shift; |
397
|
0
|
|
|
|
|
|
my $args = shift; |
398
|
|
|
|
|
|
|
|
399
|
0
|
|
|
|
|
|
push @{ $args->{used} }, $args->{letter}; |
|
0
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
|
401
|
0
|
0
|
|
|
|
|
if( scalar @{ $args->{word_root}->{letters} } >= $args->{max_word_length} ) { |
|
0
|
|
|
|
|
|
|
402
|
0
|
|
|
|
|
|
return [ ]; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
0
|
0
|
|
|
|
|
if( !$self->{prefix}->is_start_of_word( $args->{word_root} ) ) { |
406
|
0
|
|
|
|
|
|
return [ ]; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
0
|
|
|
|
|
|
my @words; |
410
|
|
|
|
|
|
|
my $near_letters = $self->find_near_letters({ |
411
|
|
|
|
|
|
|
used => $args->{used}, |
412
|
|
|
|
|
|
|
game => $args->{game}, |
413
|
|
|
|
|
|
|
row_number => $args->{letter}{row}, |
414
|
|
|
|
|
|
|
col_number => $args->{letter}{col}, |
415
|
0
|
|
|
|
|
|
}); |
416
|
|
|
|
|
|
|
|
417
|
0
|
|
|
|
|
|
for my $near_letter (@{ $near_letters }) { |
|
0
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
my $new_word_root = Game::WordBrain::Word->new({ |
419
|
0
|
|
|
|
|
|
letters => [ @{ $args->{word_root}{letters} }, $near_letter ] |
|
0
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
}); |
421
|
|
|
|
|
|
|
|
422
|
0
|
|
|
|
|
|
push @words, $new_word_root; |
423
|
|
|
|
|
|
|
|
424
|
0
|
|
|
|
|
|
my $near_letter_used = dclone $args->{used}; |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
push @words, @{ |
427
|
0
|
|
|
|
|
|
$self->_find_near_words({ |
428
|
|
|
|
|
|
|
word_root => $new_word_root, |
429
|
|
|
|
|
|
|
letter => $near_letter, |
430
|
|
|
|
|
|
|
used => $near_letter_used, |
431
|
|
|
|
|
|
|
max_word_length => $args->{max_word_length}, |
432
|
0
|
|
|
|
|
|
}); |
433
|
|
|
|
|
|
|
}; |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
|
436
|
0
|
|
|
|
|
|
return \@words; |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
=head1 AUTHORS |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
Robert Stone, C<< >> |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
Special thanks to the following individuals who submitted bug reports, performance ideas, and/or pull requests. |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=over 4 |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=item Todd Rinaldo |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
=item Mohammad S Anwar C< mohammad.anwar@yahoo.com > |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=back |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=head1 ACKNOWLEDGMENTS |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
Special thanks to L for funding the development of this module and providing test resources. |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
Further thanks to L for providing input and ideas for improvement. |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Copyright 2016 Robert Stone |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU Lesser General Public License as published by the Free Software Foundation; or any compatible license. |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ for more information. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
=cut |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
1; |