File Coverage

blib/lib/Games/Worms/Board.pm
Criterion Covered Total %
statement 9 191 4.7
branch 0 58 0.0
condition 0 8 0.0
subroutine 3 14 21.4
pod 0 10 0.0
total 12 281 4.2


line stmt bran cond sub pod time code
1             package Games::Worms::Board;
2              
3             # A (base) class encapsulating a worm universe.
4              
5 3     3   17 use strict;
  3         6  
  3         129  
6 3     3   16 use vars qw($Debug $VERSION %Default $Use_Error %Boards);
  3         5  
  3         3250  
7             $VERSION = "0.60";
8             $Debug = 0;
9              
10             $Use_Error = '';
11              
12             %Boards = ();
13              
14             #--------------------------------------------------------------------------
15              
16             #
17             # We need methods Seg and Node that report the names of
18             # the classes our segments and nodes should belong to.
19             #
20             #--------------------------------------------------------------------------
21             # Constants for this universe
22              
23             my $D60 = 3.14159 / 6; # sixty degrees
24             my $SIN60 = sin($D60); # the sin of 60 degrees, tweaked
25              
26             #--------------------------------------------------------------------------
27             %Default = (
28             'cells_wide' => 50,
29             'cells_high' => 50,
30             'tri_base' => 10,
31             'aspect' => 1.3,
32             'bg_color' => "#000000",
33             'line_color' => "#202020",
34             );
35              
36             # return a hash of the defaults in this class
37 0     0 0   sub Default { return %Default }
38              
39             #--------------------------------------------------------------------------
40              
41             sub new {
42 0     0 0   my $c = shift;
43 0   0       $c = ref($c) || $c;
44 0           my $it = bless { $c->Default, @_ }, $c;
45              
46             # deriveds
47 0 0         unless(defined $it->{'inner_border'}) {
48 0           $it->{'inner_border'} = int($it->{'tri_base'} / 10);
49 0 0         $it->{'inner_border'} = 3 if $it->{'inner_border'} < 3;
50             }
51 0   0       $it->{'worms'} ||= [];
52              
53 0           $it->{'tri_height'} =
54             int($it->{'tri_base'} * $SIN60 * $it->{'aspect'} + .5);
55              
56 0           $it->{'canvas_width'} = 2 * $it->{'inner_border'} +
57             ($it->{'cells_wide'} + .5) * $it->{'tri_base'};
58 0           $it->{'canvas_height'} = 2 * $it->{'inner_border'} +
59             $it->{'cells_high'} * $it->{'tri_height'};
60              
61 0           $it->init;
62 0           return $it;
63             }
64              
65 0     0 0   sub init { return; }
66              
67             #--------------------------------------------------------------------------
68              
69             #sub worms { # return worms on this board (whether live or dead)
70             # my $board = $_[0];
71             # return @{$board->{'worms'}};
72             #}
73              
74             #--------------------------------------------------------------------------
75              
76             sub tick { # do system update tasks -- override in derived classes
77 0     0 0   return;
78             }
79              
80             #--------------------------------------------------------------------------
81              
82             sub run {
83 0     0 0   my($board, @Worm_names) = @_;
84              
85 0           $Games::Worms::Color_counter = 0;
86 0           $board->{'generations'} = 0;
87 0 0         @Worm_names = ('Games::Worms::Random2', 'Games::Worms::Random2',
88             'Games::Worms::Beeler', 'Games::Worms::Beeler',
89             ) unless @Worm_names;
90              
91 0           my $n = 0;
92 0           foreach my $w (@Worm_names) {
93 0           my $rules = '';
94 0 0         if($w =~ s<>) {
95 0           $rules = $1;
96 0 0         $w = 'Games::Worms::Beeler' unless length $w;
97             }
98              
99 0 0         unless(&_try_use($w)) {
100 0           die "Can't use $w : $Use_Error\n";
101             }
102             $w->new(
103 0           'current_node' =>
104 0           $board->{'nodes'}[ rand(scalar( @{$board->{'nodes'}} )) ],
105             'board' => $board,
106             'rules' => $rules,
107             'name' => $w . '(' . $n++ . ')',
108             );
109             }
110              
111 0           $board->worm_status_setup;
112              
113 0           while(1) {
114 0           my @worms = grep {$_->is_alive} @{$board->{'worms'}};
  0            
  0            
115 0 0         unless(@worms) {
116 0 0         print "All dead.\n" if $Debug;
117 0           last;
118             }
119 0           foreach my $worm (@worms) { $worm->try_move }
  0            
120              
121             } continue {
122 0           $board->{'generations'}++;
123 0           $board->tick;
124             }
125              
126 0           $board->end_game;
127 0           return;
128             }
129              
130             #--------------------------------------------------------------------------
131             # Something to do once everything's died -- override in derived class
132 0     0 0   sub end_game { return; }
133              
134             #--------------------------------------------------------------------------
135             # Whatever needs to be done to set up the status for the newly created
136             # worms -- override in derived class
137 0     0 0   sub worm_status_setup { return; }
138              
139             #--------------------------------------------------------------------------
140             # Basically a wrapper around "use Modulename"
141             my %tried = ();
142             sub _try_use {
143             # "Many men have tried..." "They tried and failed?" "They tried and died."
144 0     0     my $module = $_[0]; # ASSUME sane module name!
145              
146 0 0         return $tried{$module} if exists $tried{$module}; # memoization
147              
148 3     3   21 { no strict;
  3         7  
  3         18925  
  0            
149 0           return($tried{$module} = 1)
150 0 0 0       if defined(%{$class . "::VERSION"}) || defined(@{$class . "::ISA"});
  0            
151             # we never use'd it, but there it is!
152             }
153              
154 0 0         die "illegal module name \"$module\"\n"
155             unless $module =~ m/^[-a-zA-Z0-9_:']+$/s;
156 0 0         print " About to use $module ...\n" if $Debug;
157             {
158 0           local $SIG{'__DIE__'} = undef;
  0            
159 0           eval "package Nullius; use $module";
160             }
161 0 0         if($@) {
162 0 0         print "Error using $module \: $@\n" if $Debug > 1;
163 0           $Use_Error = $@;
164 0           return($tried{$module} = 0);
165             } else {
166 0 0         print " OK, $module is used\n" if $Debug;
167 0           $Use_Error = '';
168 0           return($tried{$module} = 1);
169             }
170             }
171              
172             #--------------------------------------------------------------------------
173             # Initialize space -- link up nodes and segments
174              
175             sub init_grid {
176 0     0 0   my $it = shift;
177              
178 0           my $Seg = $it->Seg; # class name we want to make segments in
179 0           my $Node = $it->Node; # class name we want to make nodes in
180             # die "No canvas?" unless $it->{'canvas'};
181              
182 0           my $cell = 0;
183              
184             # We use these two lists for comprehensive destruction.
185 0           $it->{'nodes'} = [];
186 0           $it->{'segments'} = [];
187              
188             # Set up the grid now. -- fill a space with rows of nodes.
189              
190 0           $it->{'node_space'} = []; # this is a List of Lists.
191             # usage: $node = $it->{'node_space'}[rownum][colnum]
192 0           for(my $row = 0; $row < $it->{'cells_high'}; ++$row) {
193 0           my $row_r = [];
194 0           push @{$it->{'node_space'}}, $row_r;
  0            
195 0           for(my $col = 0; $col < $it->{'cells_wide'}; ++$col) {
196 0           my $node = $Node->new;
197 0           push @$row_r, $node;
198 0           push @{$it->{'nodes'}}, $node;
  0            
199             }
200             # Now link up each node in this row to its next, and back
201 0           for(my $col = 0; $col < $it->{'cells_wide'}; ++$col) {
202 0           my $here = $row_r->[$col];
203 0           my $next = $row_r->[ ($col + 1) % scalar(@$row_r) ]; # % for wraparound
204 0           $here->{'nodes_toward'}[3] = $next;
205 0           $next->{'nodes_toward'}[0] = $here;
206             }
207             }
208              
209             # now link each node to its southern neighbor, and back
210 0           for(my $row = 0; $row < $it->{'cells_high'}; ++$row) {
211 0           my $here_row_r = $it->{'node_space'}[$row];
212 0           my $next_row_r = $it->{'node_space'}[ ($row + 1) % scalar(@{$it->{'node_space'}})];
  0            
213 0           for(my $col = 0; $col < $it->{'cells_wide'}; ++$col) {
214 0           my $here = $here_row_r->[$col];
215 0           my $south = $next_row_r->[$col];
216 0           my $row_type_top = ((1 + $row) % 2); # 1, 0, 1, 0, 1, 0, ...
217 0 0         if($row_type_top) { # Rows 0, 2, 4...
218 0           $here->{'nodes_toward'}[4] = $south;
219 0           $south->{'nodes_toward'}[1] = $here;
220             } else { # Rows 1, 3, 5...
221 0           $here->{'nodes_toward'}[5] = $south;
222 0           $south->{'nodes_toward'}[2] = $here;
223             }
224             }
225             }
226              
227             # now link each node to its remaining neighbors
228 0           for(my $row = 0; $row < $it->{'cells_high'}; ++$row) {
229 0           my $here_row_r = $it->{'node_space'}[$row];
230 0           my $next_row_r = $it->{'node_space'}[ ($row + 1) % scalar(@{$it->{'node_space'}})];
  0            
231 0           for(my $col = 0; $col < $it->{'cells_wide'}; ++$col) {
232 0           my $here = $here_row_r->[$col];
233 0           my $row_type_top = ((1 + $row) % 2); # 1, 0, 1, 0, 1, 0, ...
234 0 0         if($row_type_top) { # Rows 0, 2, 4...
235 0           my $sw = $here->{'nodes_toward'}[4]{'nodes_toward'}[0];
236 0           $here->{'nodes_toward'}[5] = $sw;
237 0           $sw->{'nodes_toward'}[2] = $here;
238             } else { # Rows 1, 3, 5...
239 0           my $se = $here->{'nodes_toward'}[5]{'nodes_toward'}[3];
240 0           $here->{'nodes_toward'}[4] = $se;
241 0           $se->{'nodes_toward'}[1] = $here;
242             }
243             }
244             }
245              
246 0           my $Tri_height = $it->{'tri_height'};
247 0           my $Tri_base = $it->{'tri_base'};
248 0           my $Inner_Border = $it->{'inner_border'};
249              
250             # Create segments now, drawing them, and linking them to nodes.
251              
252 0           for(my $row = 0; $row < $it->{'cells_high'}; ++$row) {
253 0           my $row_type_top = ((1 + $row) % 2); # 1, 0, 1, 0, 1, 0, ...
254             # There are two types of rows: top-type, and not.
255             #
256 0 0         print "Row $row; Row type top: $row_type_top\n" if $Debug > 2;
257 0           for(my $col = 0; $col < $it->{'cells_wide'}; ++$col) {
258 0           my $x_base = $Inner_Border + $col * $Tri_base;
259 0           my $y_base = $Inner_Border + $row * $Tri_height;
260 0 0         print " Row $row (t$row_type_top) Col $col | xb $x_base | yb $y_base\n"
261             if $Debug > 2;
262 0           my($s1, $s2, $s3);
263 0           my $n = $it->{'node_space'}[$row][$col];
264 0 0         if($row_type_top) { # rows 0,2,4,...
265             #(top-type)
266             # 1 means draw this: i.e., one item is:
267             # --- --- --- N---n_d3 s1
268             # \ / \ / \ / \ / s2 s3
269             # n_d4
270 0           my $n_d3 = $n->{'nodes_toward'}[3];
271 0           my $n_d4 = $n->{'nodes_toward'}[4];
272              
273 0           $s1 = $Seg->new('coords' =>
274             [ $x_base, $y_base, $x_base + $Tri_base, $y_base ],
275             'board' => $it);
276             # @{$s1->{'nodes'}} = ($n, $n_d3);
277 0           $n->{'segments_toward'}[3] = $n_d3->{'segments_toward'}[0] = $s1;
278              
279 0           $s2 = $Seg->new('coords' =>
280             [ $x_base, $y_base,
281             $x_base + $Tri_base / 2, $y_base + $Tri_height ],
282             'board' => $it);
283             # @{$s2->{'nodes'}} = ($n, $n_d4);
284 0           $n->{'segments_toward'}[4] = $n_d4->{'segments_toward'}[1] = $s2;
285              
286 0           $s3 = $Seg->new( 'coords' =>
287             [ $x_base + $Tri_base / 2, $y_base + $Tri_height,
288             $x_base + $Tri_base, $y_base ],
289             'board' => $it);
290             # @{$s3->{'nodes'}} = ($n_d3, $n_d4);
291 0           $n_d3->{'segments_toward'}[5] = $n_d4->{'segments_toward'}[2] = $s3;
292              
293             } else { # rows 1,3,5,..
294             #(top-type)
295             # 0 means draw this: i.e., one item is:
296             # --- --- --- N---nd_3 s1
297             # / \ / \ / \ / \ s2 s3
298             # n_d5 n_d4
299 0           my $n_d3 = $n->{'nodes_toward'}[3];
300 0           my $n_d4 = $n->{'nodes_toward'}[4];
301 0           my $n_d5 = $n->{'nodes_toward'}[5];
302              
303 0           $s1 = $Seg->new( 'coords' =>
304             [ $x_base + $Tri_base / 2, $y_base,
305             $x_base + $Tri_base * 1.5, $y_base ],
306             'board' => $it);
307             # @{$s1->{'nodes'}} = ($n, $n_d3);
308 0           $n->{'segments_toward'}[3] = $n_d3->{'segments_toward'}[0] = $s1;
309              
310 0           $s2 = $Seg->new('coords' =>
311             [ $x_base + $Tri_base / 2, $y_base,
312             $x_base, $y_base + $Tri_height ],
313             'board' => $it);
314             # @{$s2->{'nodes'}} = ($n, $n_d5);
315 0           $n->{'segments_toward'}[5] = $n_d5->{'segments_toward'}[2] = $s2;
316              
317 0           $s3 = $Seg->new('coords' =>
318             [ $x_base + $Tri_base, $y_base + $Tri_height,
319             $x_base + $Tri_base / 2, $y_base ],
320             'board' => $it);
321             # @{$s3->{'nodes'}} = ($n, $n_d4);
322 0           $n->{'segments_toward'}[4] = $n_d4->{'segments_toward'}[1] = $s3;
323              
324             }
325 0           push @{$it->{'segments'}}, $s1, $s2, $s3;
  0            
326             }
327             }
328 0           return;
329             }
330              
331             #--------------------------------------------------------------------------
332             # Reset the grid, then draw
333             sub refresh_and_draw_grid {
334 0     0 0   my $board = $_[0];
335 0 0         if($board->{'segments'}) {
336 0           foreach my $seg ( @{$board->{'segments'}} ) {
  0            
337 0           $seg->refresh;
338 0           $seg->draw;
339             }
340             } else {
341 0           $board->init_grid;
342 0           foreach my $seg ( @{$board->{'segments'}} ) {
  0            
343 0           $seg->draw;
344             }
345             }
346 0           return;
347             }
348              
349             #--------------------------------------------------------------------------
350             # Null out contents of all segments, nodes, and worms
351              
352             sub destroy {
353 0     0 0   my $it = shift;
354 0 0         print "Destroy called on $it\n" if $Debug;;
355 0 0         if(ref($it->{'segments'})) {
356 0 0         print "Destroying ", scalar(@{$it->{'segments'}}) ," segments...\n" if $Debug;
  0            
357 0           foreach my $s (@{$it->{'segments'}}) { %$s = (); bless $s, 'DEAD'; }
  0            
  0            
  0            
358             }
359 0 0         if(ref($it->{'nodes'})) {
360 0 0         print "Destroying ", scalar(@{$it->{'nodes'}}) ," nodes...\n" if $Debug;
  0            
361 0           foreach my $s (@{$it->{'nodes'}}) { %$s = (); bless $s, 'DEAD'; }
  0            
  0            
  0            
362             }
363 0 0         if(ref($it->{'worms'})) {
364 0 0         print "Destroying ", scalar(@{$it->{'worms'}}) ," worms...\n" if $Debug;
  0            
365 0           foreach my $s (@{$it->{'worms'}}) { %$s = (); bless $s, 'DEAD'; }
  0            
  0            
  0            
366             }
367 0           %$it = ();
368 0           bless $it, 'DEAD';
369 0 0         print "Done destroying $it\n" if $Debug;
370              
371 0           return;
372             }
373              
374             # *DESTROY = \&destroy;
375              
376             #--------------------------------------------------------------------------
377              
378             1;
379              
380             __END__