File Coverage

blib/lib/Tk/Taxis.pm
Criterion Covered Total %
statement 43 49 87.7
branch n/a
condition n/a
subroutine 15 15 100.0
pod n/a
total 58 64 90.6


line stmt bran cond sub pod time code
1             package Tk::Taxis;
2              
3 1     1   21649 use 5.008006;
  1         4  
  1         33  
4 1     1   6 use strict;
  1         1  
  1         34  
5 1     1   4 use warnings::register( 'Tk::Taxis' );
  1         6  
  1         291  
6              
7             our $VERSION = '2.03';
8              
9             ################################## defaults ####################################
10              
11 1     1   5 use constant WIDTH => 400;
  1         2  
  1         81  
12 1     1   4 use constant HEIGHT => 400;
  1         1  
  1         34  
13 1     1   5 use constant POPULATION => 20;
  1         1  
  1         40  
14 1     1   6 use constant PREFERENCE => [ 100, 100 ];
  1         1  
  1         42  
15 1     1   5 use constant TUMBLE => 0.03;
  1         2  
  1         55  
16 1     1   4 use constant SPEED => 0.006;
  1         1  
  1         42  
17 1     1   4 use constant IMAGES => "woodlice";
  1         2  
  1         52  
18 1         35 use constant FILL => [ [ 'white', 'gray' ],
19 1     1   4 [ 'white', 'gray' ] ];
  1         1  
20 1     1   4 use constant LEFT_FILL => "white"; # deprecated
  1         1  
  1         39  
21 1     1   10 use constant RIGHT_FILL => "gray"; # deprecated
  1         2  
  1         102  
22             use constant CALCULATION =>
23             sub
24             {
25 0         0 my ( $critter ) = @_;
26 0         0 my %boundries = $critter->get_boundries();
27 0         0 my ( $x, $y ) = $critter->get_pos();
28             return
29             $x / $boundries{ width },
30 0         0 $y / $boundries{ height };
31 1     1   10 };
  1         1  
  1         50  
32            
33             ################################### widget #####################################
34              
35 1     1   417 use Tk qw( DoOneEvent DONT_WAIT );
  0            
  0            
36             use Tk::Taxis::Critter;
37              
38             require Tk::Frame;
39             our @ISA = ( 'Tk::Frame' );
40              
41             Tk::Widget->Construct( 'Taxis' );
42              
43             sub Populate
44             {
45             my ( $taxis, $options ) = @_;
46             my $canvas = $taxis->Canvas();
47             $taxis->Advertise( 'canvas' => $canvas );
48             $canvas->pack();
49            
50             $taxis->{ _supress_redraw } = 1; # so no multiple redraws on initialisation
51             $taxis->images( delete $options->{ -images } || IMAGES );
52             $taxis->preference( delete $options->{ -preference } || PREFERENCE );
53             $taxis->tumble( delete $options->{ -tumble } || TUMBLE );
54             $taxis->speed( delete $options->{ -speed } || SPEED );
55             $taxis->width( delete $options->{ -width } || WIDTH );
56             $taxis->height( delete $options->{ -height } || HEIGHT );
57             $taxis->population( delete $options->{ -population } || POPULATION );
58             $taxis->fill( delete $options->{ -fill } || FILL );
59             $taxis->calculation( delete $options->{ -calculation } || CALCULATION );
60            
61             # deprecated options
62             if ( $options->{ -left_fill } )
63             {
64             $taxis->left_fill( delete $options->{ -left_fill } || LEFT_FILL );
65             }
66             if ( $options->{ -right_fill } )
67             {
68             $taxis->right_fill( delete $options->{ -right_fill } || RIGHT_FILL );
69             }
70              
71             $taxis->{ _supress_redraw } = 0;
72             $taxis->refresh();
73            
74             $taxis->ConfigSpecs
75             (
76             -images => [ 'METHOD', 'images', 'Images', undef ],
77             -preference => [ 'METHOD', 'preference', 'Preference', undef ],
78             -tumble => [ 'METHOD', 'tumble', 'Tumble', undef ],
79             -speed => [ 'METHOD', 'speed', 'Speed', undef ],
80             -width => [ 'METHOD', 'width', 'Width', undef ],
81             -height => [ 'METHOD', 'height', 'Height', undef ],
82             -population => [ 'METHOD', 'population', 'Population', undef ],
83             -fill => [ 'METHOD', 'fill', 'Fill', undef ],
84             -calculation => [ 'METHOD', 'calculation', 'Calculation', undef ],
85             DEFAULT => [ $canvas ],
86             );
87             $taxis->SUPER::Populate( $options );
88             $taxis->Delegates( DEFAULT => $canvas );
89             }
90              
91             ################################### images #####################################
92              
93             sub images
94             {
95             my ( $taxis, $images ) = @_;
96             if ( $images )
97             {
98             $taxis->{ images } = $images;
99             unless ( $taxis->{ image_bank }{ $images } )
100             {
101             $taxis->{ image_bank }{ $images } =
102             {
103             n => $taxis->Photo( -file => $taxis->_find_image( "n.gif" ) ),
104             ne => $taxis->Photo( -file => $taxis->_find_image( "ne.gif" ) ),
105             e => $taxis->Photo( -file => $taxis->_find_image( "e.gif" ) ),
106             se => $taxis->Photo( -file => $taxis->_find_image( "se.gif" ) ),
107             s => $taxis->Photo( -file => $taxis->_find_image( "s.gif" ) ),
108             sw => $taxis->Photo( -file => $taxis->_find_image( "sw.gif" ) ),
109             w => $taxis->Photo( -file => $taxis->_find_image( "w.gif" ) ),
110             nw => $taxis->Photo( -file => $taxis->_find_image( "nw.gif" ) ),
111             0 => $taxis->Photo(),
112             };
113             }
114             $taxis->image_height
115             (
116             $taxis->{ image_bank }{ $images }{ n }->height() || 50
117             );
118             $taxis->image_width
119             (
120             $taxis->{ image_bank }{ $images }{ n }->width() || 50
121             );
122             $taxis->refresh();
123             }
124             return $taxis->{ images };
125             }
126              
127             sub _find_image
128             {
129             my ( $taxis, $file ) = @_;
130             my $dir = $taxis->{ images };
131             my $found;
132             if ( my ( $path ) = $dir =~ /^\@(.*)$/ )
133             {
134             $found = ( grep { -e $_ } "$path/$file" )[ 0 ];
135             warnings::warn( "No such file $path/$file" ) unless $found;
136             }
137             else
138             {
139             $found =
140             ( grep { -f $_ } map { "$_/Tk/Taxis/images/$dir/$file" } @INC )[ 0 ];
141             warnings::warn( "No such file \@INC/Tk/Taxis/images/$dir/$file" )
142             unless $found;
143             }
144             return $found;
145             }
146              
147             sub _create_critter_image
148             {
149             my ( $taxis, $critter ) = @_;
150             my $canvas = $taxis->Subwidget( 'canvas' );
151             my @pos = $critter->get_pos();
152             my $id = $critter->get_id();
153             my $image =
154             $taxis->{ image_bank }{ $taxis->{ images } }{ $critter->get_orient() };
155             if ( defined $id )
156             {
157             $canvas->coords( $id, $pos[ 0 ], $pos[ 1 ] );
158             $canvas->itemconfigure( $id, -image => $image );
159             }
160             else
161             {
162             my $id = $canvas->create
163             ( 'image', $pos[ 0 ], $pos[ 1 ],
164             -anchor => 'center', -image => $image );
165             $critter->set_id( $id );
166             }
167             return $taxis;
168             }
169              
170             sub _hide_critter_image
171             {
172             my ( $taxis, $critter ) = @_;
173             my $canvas = $taxis->Subwidget( 'canvas' );
174             my $id = $critter->get_id();
175             my $image = $taxis->{ image_bank }{ $taxis->{ images } }{ 0 };
176             if ( defined $id )
177             {
178             $canvas->itemconfigure( $id, -image => $image );
179             }
180             return $taxis;
181             }
182              
183             sub image_height
184             {
185             my ( $taxis, $image_height ) = @_;
186             if ( defined $image_height )
187             {
188             $taxis->{ image_height } = $image_height;
189             }
190             return $taxis->{ image_height };
191             }
192              
193             sub image_width
194             {
195             my ( $taxis, $image_width ) = @_;
196             if ( defined $image_width )
197             {
198             $taxis->{ image_width } = $image_width;
199             }
200             return $taxis->{ image_width };
201             }
202              
203             ################################## critters ####################################
204              
205             sub preference
206             {
207             my ( $taxis, $preference ) = @_;
208             if ( defined $preference )
209             {
210             $preference = [ $preference ] unless ref $preference;
211             for my $i ( 0 .. 1 )
212             {
213             if ( defined $preference->[ $i ] )
214             {
215             if ( abs $preference->[ $i ] < 1 )
216             {
217             warnings::warn( "Absolute value of preference must be greater than 1" );
218             ${ $preference }[ $i ] = 1;
219             }
220             }
221             else
222             {
223             $preference->[ $i ] = 1;
224             }
225             }
226             $taxis->{ preference } = $preference;
227             }
228             return $taxis->{ preference };
229             }
230              
231             sub tumble
232             {
233             my ( $taxis, $tumble ) = @_;
234             if ( defined $tumble )
235             {
236             if ( $tumble > 1 )
237             {
238             warnings::warn( "Tumble value too high, setting to 1" );
239             $tumble = 1;
240             }
241             elsif ( $tumble < 0 )
242             {
243             warnings::warn( "Tumble value too low, setting to 0" );
244             $tumble = 0;
245             }
246             $taxis->{ tumble } = $tumble;
247             }
248             return $taxis->{ tumble };
249             }
250              
251             sub speed
252             {
253             my ( $taxis, $speed ) = @_;
254             if ( defined $speed )
255             {
256             my $canvas = $taxis->Subwidget( 'canvas' );
257             my $max_x = $canvas->cget( -width );
258             my $max_y = $canvas->cget( -height );
259             my $min_speed = 2 / sqrt ( $max_x**2 + $max_y**2 );
260             if ( $speed < $min_speed )
261             {
262             warnings::warn( "Speed too low, setting to minimum value of $min_speed" );
263             $speed = $min_speed;
264             # or they sit there and spin
265             }
266             $taxis->{ speed } = $speed;
267             }
268             return $taxis->{ speed };
269             }
270              
271             sub calculation
272             {
273             my ( $taxis, $calculation ) = @_;
274             if ( defined $calculation )
275             {
276             $taxis->{ calculation } = $calculation;
277             }
278             return $taxis->{ calculation };
279             }
280              
281             #################################### taxis #####################################
282              
283             sub taxis
284             {
285             my ( $taxis, $options ) = @_;
286             my $canvas = $taxis->Subwidget( 'canvas' );
287             if ( $taxis->{ critters } )
288             {
289             my $critter;
290             for my $i ( 1 .. $taxis->{ population } )
291             {
292             $critter = $taxis->{ critters }[ $i ];
293             $critter->move();
294             $taxis->_create_critter_image( $critter );
295             }
296             DoOneEvent( DONT_WAIT );
297             }
298             return $taxis;
299             }
300              
301             #################################### arena #####################################
302              
303             sub population
304             {
305             my ( $taxis, $population ) = @_;
306             if ( defined $population )
307             {
308             $taxis->{ population } = abs $population;
309             $taxis->refresh();
310             }
311             if ( wantarray )
312             {
313             my $canvas = $taxis->Subwidget( 'canvas' );
314             my ( $top_left, $top_right, $bottom_left, $bottom_right )
315             = ( 0, 0, 0, 0 );
316             my $vert_limit = $canvas->cget( -height ) / 2;
317             my $horiz_limit = $canvas->cget( -width ) / 2;
318             for my $i ( 1 .. $taxis->{ population } )
319             {
320             if ( ${ $taxis->{ critters } }[ $i ]{ pos }[ 1 ]
321             <= $vert_limit )
322             {
323             ${ $taxis->{ critters } }[ $i ]{ pos }[ 0 ]
324             <= $horiz_limit ?
325             $top_left++ :
326             $top_right++;
327             }
328             else
329             {
330             ${ $taxis->{ critters } }[ $i ]{ pos }[ 0 ]
331             <= $canvas->cget( -width ) / 2 ?
332             $bottom_left++ :
333             $bottom_right++;
334             }
335             }
336             return
337             (
338             top => ( $top_left + $top_right ),
339             bottom => ( $bottom_left + $bottom_right ),
340             left => ( $bottom_left + $top_left ),
341             right => ( $bottom_right + $top_right ),
342             top_left => $top_left,
343             bottom_left => $bottom_left,
344             top_right => $top_right,
345             bottom_right => $bottom_right,
346             total => ( $top_left + $top_right + $bottom_left + $bottom_right ),
347             );
348             }
349             else
350             {
351             return $taxis->{ population };
352             }
353             }
354              
355             sub width
356             {
357             my ( $taxis, $width ) = @_;
358             if ( $width )
359             {
360             $taxis->{ width } = $width;
361             $taxis->refresh();
362             }
363             return $taxis->{ width };
364             }
365              
366             sub height
367             {
368             my ( $taxis, $height ) = @_;
369             if ( $height )
370             {
371             $taxis->{ height } = $height;
372             $taxis->refresh();
373             }
374             return $taxis->{ height };
375             }
376              
377             sub fill
378             {
379             my ( $taxis, $fill ) = @_;
380             if ( defined $fill )
381             {
382             if ( not ref $fill )
383             {
384             $taxis->{ fill } = [ [ $fill, $fill ], [ $fill, $fill ] ];
385             }
386             elsif ( ref $fill &&
387             ( not ref $fill->[0] ) &&
388             ( not ref $fill->[1] ) )
389             {
390             $taxis->{ fill } = [ [ $fill->[0], $fill->[1] ],
391             [ $fill->[0], $fill->[1] ] ];
392             }
393             elsif ( ref $fill->[0] && ref $fill->[1] )
394             {
395             $taxis->{ fill } = [ [ $fill->[0][0], $fill->[0][1] ],
396             [ $fill->[1][0], $fill->[1][1] ] ];
397             }
398             else
399             {
400             warnings::warn( "Invalid argument to fill" );
401             return;
402             }
403             $taxis->refresh();
404             }
405             return $taxis->{ fill };
406             }
407              
408             sub left_fill
409             {
410             my ( $taxis, $left_fill ) = @_;
411             if ( $left_fill )
412             {
413             warnings::warn( "left_fill is deprecated, use fill instead" );
414             $taxis->{ fill }[0][0] = $left_fill;
415             $taxis->{ fill }[1][0] = $left_fill;
416             $taxis->refresh();
417             }
418             return $taxis->{ fill }[0][0];
419             }
420            
421             sub right_fill
422             {
423             my ( $taxis, $right_fill ) = @_;
424             if ( $right_fill )
425             {
426             warnings::warn( "right_fill is deprecated, use fill instead" );
427             $taxis->{ fill }[0][1] = $right_fill;
428             $taxis->{ fill }[1][1] = $right_fill;
429             $taxis->refresh();
430             }
431             return $taxis->{ fill }[1][1];
432             }
433              
434             sub refresh
435             {
436             my ( $taxis, $options ) = @_;
437             return if $taxis->{ _supress_redraw };
438             my $canvas = $taxis->Subwidget( 'canvas' );
439             $canvas->configure( -width => $taxis->width() );
440             $canvas->configure( -height => $taxis->height() );
441             my $max_x = $taxis->{ width };
442             my $max_y = $taxis->{ height };
443             if ( $taxis->{ arena } )
444             {
445             my ( $top_left, $top_right, $bottom_left, $bottom_right )
446             = @{ $taxis->{ arena } };
447             $canvas->coords
448             ( $top_left, 0, 0, $max_x/2, $max_y/2 );
449             $canvas->itemconfigure( $top_left, -fill => $taxis->{fill}[0][0] );
450            
451             $canvas->coords
452             ( $top_right, $max_x/2, 0, $max_x, $max_y/2 );
453             $canvas->itemconfigure( $top_right, -fill => $taxis->{fill}[0][1] );
454            
455             $canvas->coords
456             ( $bottom_left, 0, $max_y/2, $max_x/2, $max_y);
457             $canvas->itemconfigure( $bottom_left, -fill => $taxis->{fill}[1][0] );
458            
459             $canvas->coords
460             ( $bottom_right, $max_x/2, $max_y/2, $max_x, $max_y );
461             $canvas->itemconfigure( $bottom_right, -fill => $taxis->{fill}[1][1] );
462            
463             }
464             else
465             {
466             my $top_left = $canvas->create
467             ( 'rectangle', 0, 0, $max_x/2, $max_y/2,
468             -fill => $taxis->{fill}[0][0] );
469            
470             my $top_right = $canvas->create
471             ( 'rectangle', $max_x/2, 0, $max_x, $max_y/2,
472             -fill => $taxis->{fill}[0][1] );
473            
474             my $bottom_left = $canvas->create
475             ( 'rectangle', 0, $max_y/2, $max_x/2, $max_y,
476             -fill => $taxis->{fill}[1][0] );
477            
478             my $bottom_right = $canvas->create
479             ( 'rectangle', $max_x/2, $max_y/2, $max_x, $max_y,
480             -fill => $taxis->{fill}[1][1] );
481              
482             $taxis->{ arena } = [ $top_left, $top_right, $bottom_left, $bottom_right ];
483             }
484             my $i;
485             for ( $i = 1 ; $i <= $taxis->{ population } ; $i++ )
486             {
487             my $critter = $taxis->{ critters }[ $i ];
488             unless ( $critter )
489             {
490             $critter = Tk::Taxis::Critter->new( -taxis => $taxis );
491             $taxis->{ critters }[ $i ] = $critter;
492             }
493             $critter->randomise();
494             $taxis->_create_critter_image( $critter );
495             }
496             for my $j ( $i .. @{ $taxis->{ critters } } - 1 )
497             {
498            
499             # We don't delete the critters from the critters arrayref,
500             # we just keep track of the current population size, and
501             # grow this as appropriate; we only hide their images from view in the
502             # canvas. We do this because we cannot satifactorily
503             # delete images from canvases, as this appears to cause memory leakage
504             # even if we delete all references, and call the delete method on all
505             # widgets. I presume this is a bug in Tk::Canvas, as it works for other
506             # imaged widgets. This way we only get as big as the largest population
507             # called during the life of the script.
508            
509             my $critter = $taxis->{ critters }[ $j ];
510             $taxis->_hide_critter_image( $critter );
511             }
512             DoOneEvent( DONT_WAIT );
513             return $taxis;
514             }
515              
516             1;
517              
518             __END__