File Coverage

snake.pl
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             ########################################################################
3             # #
4             # Snakes, by Simon Parsons #
5             # #
6             # This example program is distributed under the terms of the GNU #
7             # Public Licence and the Perl Artistic Licence. #
8             # #
9             # Copyright Simon Parsons, 2002 #
10             ########################################################################
11              
12 1     1   476 use Tk::ObjectHandler;
  0            
  0            
13             use strict;
14              
15             my $game = {
16             play => 0,
17             pause => 0,
18             delay => 700,
19             last => 'j',
20             next => undef,
21             };
22             my $score = 0;
23             my $message = '';
24             my @keypresses = ();
25              
26             my @stage;
27             my @snake = ();
28              
29             my $snakedata = {
30             up => -1,
31             left => 0,
32             growing => 0,
33             shrinking => 0,
34             };
35             my $length = 0,
36              
37             my $apple = {
38             'ready' => 0,
39             'count' => 5,
40             'eaten' => 0,
41             'type' => 0,
42             };
43             my @apple_colours = ('#FFFFFF','#339933', '#990033', '#999900');
44             my $mw;
45              
46             sub about_window {
47             my $widget = shift;
48              
49             $message = 'Snakes by Simon Parsons. Made with
50             Tk::ObjectHandler.';
51             }
52              
53             sub init_game {
54              
55             # Set default snake starting position
56             my $snake_head = {x=>25, y=>17};
57             my $snake_tail = {x=>25, y=>18};
58             $stage[$snake_head->{'x'}][$snake_head->{'y'}] = 1;
59             $stage[$snake_tail->{'x'}][$snake_tail->{'y'}] = 1;
60              
61             # Init variables
62             $game = {
63             play => 1,
64             message => '',
65             pause => 0,
66             delay => 500,
67             last => 'z',
68             next => undef,
69             };
70             $score = 0;
71            
72             $snakedata = {
73             up => -1,
74             left => 0,
75             growing => 0,
76             shrinking => 0,
77             length => 0,
78             };
79             $length=2;
80            
81             $apple = {
82             'ready' => 0,
83             'count' => 5,
84             'eaten' => 0,
85             'type' => 0,
86             };
87              
88              
89             @snake = ();
90             $snake[0] = $snake_head;
91             $snake[1] = $snake_tail;
92              
93             # Clear area
94             $mw->field->createRectangle(0, 0, $mw->field->cget(-width),
95             $mw->field->cget(-height),
96             -outline => $mw->field->cget(-background),
97             -fill => $mw->field->cget(-background));
98              
99             draw_snake(\@snake);
100              
101             # Set up keyboard commands
102              
103             #$mw->bind('', sub{ push @keypresses, 'z'});
104             #$mw->bind('', sub{ push @keypresses, 'a'});
105             #$mw->bind('', sub{ push @keypresses, 'm'});
106             #$mw->bind('', sub{ push @keypresses, 'n'});
107             $mw->bind('', sub{ turn1(-1, 'z', 0, 1, 'a');});
108             $mw->bind('', sub{ turn1(1, 'a', 0, -1, 'z');});
109             $mw->bind('', sub{ turn2(-1, 'm', 1, 0, 'n');});
110             $mw->bind('', sub{ turn2(1, 'n', -1, 0, 'm');});
111             $mw->bind('

', sub{ pause(); });

112             $mw->bind('

', sub{ pause(); });

113             $mw->after($game->{'delay'}, sub{ move() });
114             }
115              
116             sub pause {
117             if($game->{'pause'}==0) {
118             $game->{'pause'}=1;
119             } else {
120             $game->{'pause'}=0;
121             move();
122             }
123             }
124              
125             sub turn {
126             if("az" =~ /$_[0]/i) {
127             turn1(turnargs($_[0]));
128             } else {
129             turn2(turnargs($_[0]));
130             }
131             }
132              
133             sub turnargs {
134             my $dir = shift;
135             if(lc($dir) eq 'a') {
136             return [1, 'a', 0, -1, 'z'];
137             } elsif(lc($dir) eq 'z') {
138             return [-1, 'z', 0, 1, 'a'];
139             } elsif(lc($dir) eq 'n') {
140             return [1, 'n', -1, 0, 'm'];
141             } else {
142             return [-1, 'm', 1, 0, 'n'];
143             }
144             }
145              
146             sub turn1 {
147             return if($game->{'last'} eq $_[1]);
148             if($snakedata->{'up'} != $_[0] or $game->{'last'} ne $_[1]) {
149             $snakedata->{'left'} = $_[2];
150             $snakedata->{'up'} = $_[3];
151             $game->{'next'} = $_[4];
152             }
153             }
154              
155             sub turn2 {
156             return if($game->{'last'} eq $_[1]);
157             if($snakedata->{'left'} != $_[0] or $game->{'last'} ne $_[1]) {
158             $snakedata->{'left'} = $_[2];
159             $snakedata->{'up'} = $_[3];
160             $game->{'next'} = $_[4];
161             }
162             }
163              
164             sub draw_snake {
165             my $snake = shift;
166              
167             foreach my $coord (@$snake) {
168             draw('#000000', $coord);
169             }
170             }
171              
172             sub draw {
173             my $colour = shift;
174             my $x = $_[0]->{'x'} * 10;
175             my $y = $_[0]->{'y'} * 10;
176             $mw->field->createRectangle($x, $y, $x+9, $
177             y+9, -outline => $colour, -fill => $colour);
178             }
179              
180             sub move {
181             return if($game->{'pause'});
182             my $turn;
183              
184             # Normal movement
185             proc_head($snake[0]->{'y'} + $snakedata->{'up'},
186             $snake[0]->{'x'} + $snakedata->{'left'});
187              
188             # Growth movement
189             if($snakedata->{'growing'}) {
190             $snakedata->{'growing'}--;
191             $message = '' if($snakedata->{'growing'} == 1);
192             } else {
193             proc_tail(pop @snake);
194             }
195              
196             # Shrinking movement
197             if($snakedata->{'shrinking'}) {
198             $snakedata->{'shrinking'}--;
199             proc_tail(pop @snake);
200             }
201              
202             $length = $#snake + 1;
203             $score++;
204              
205             # Draw Apple
206             if(--$apple->{'count'} <= 0) {
207             if($apple->{'ready'} == 0) {
208             $apple->{'x'} = get_rand(49);
209             $apple->{'y'} = get_rand(34);
210             until(check_snake($apple->{'x'},
211             $apple->{'y'})) {
212             $apple->{'x'} = get_rand(49);
213             $apple->{'y'} = get_rand(34);
214             }
215              
216             $apple->{'type'} = (get_rand(100) <= 80 ? 1 :
217             (get_rand(100) <= 50 ? 2 : 3));
218              
219             draw($apple_colours[$apple->{'type'}], $apple);
220             } else {
221             draw('#FFFFFF', $apple);
222             }
223             $apple->{'ready'} = not $apple->{'ready'};
224             $apple->{'count'} = ($apple->{'ready'} == 1 ?
225             get_rand(100)+50 : get_rand(5));
226             }
227              
228             if($game->{'play'} == -1) {
229             $message = 'Ouch!!';
230             $game->{'play'} = 0;
231             }
232              
233             if($game->{'next'}) { $game->{'last'} =
234             $game->{'next'}; $game->{'next'} = undef; }
235              
236             $mw->after($game->{'delay'}, sub{ move() }) if $game->{'play'};
237             }
238              
239             sub proc_tail {
240             my $new_tail = shift;
241             if($new_tail) {
242             draw('#FFFFFF', $new_tail);
243             $stage[$new_tail->{'x'}][$new_tail->{'y'}] = 0;
244             }
245             }
246              
247             sub proc_head {
248             my $new_head = {
249             'y' => shift,
250             'x' => shift,
251             };
252              
253             if(($new_head->{'x'} < 0 or $new_head->{'y'} < 0) or
254             ($new_head->{'x'} > 49 or $new_head->{'y'} > 34)) {
255             $game->{'play'} = -1;
256             }
257              
258             # if a snake is there...
259             if($stage[$new_head->{'x'}][$new_head->{'y'}] == 1) {
260             $game->{'play'} = -1;
261             }
262             $stage[$new_head->{'x'}][$new_head->{'y'}] = 1;
263              
264             if(($apple->{'ready'} == 1) and
265             ($new_head->{'x'} == $apple->{'x'}) and
266             ($new_head->{'y'} == $apple->{'y'})) {
267              
268             $apple->{'ready'} = 0;
269             $apple->{'count'} = get_rand(10);
270             $apple->{'eaten'}++;
271             $message = 'Crunch!!';
272              
273             if($apple->{'type'} == 1) {
274             $score += 100;
275             $game->{'delay'} = sprintf "%d", ( $game->{'delay'} * 0.9);
276             $snakedata->{'growing'} += 3+$apple->{'eaten'};
277             $snakedata->{'shrinking'} = 0;
278             } elsif($apple->{'type'} == 2) {
279             $score += 500;
280             $game->{'delay'} = sprintf "%d", ( $game->{'delay'} * 0.9);
281             $snakedata->{'growing'} = 0;
282             $snakedata->{'shrinking'} +=3+$apple->{'eaten'};
283             if(($length - $snakedata->{'shrinking'}) < 2 ) {
284             $snakedata->{'shrinking'} = $length-2;
285             }
286             } else {
287             $score += 500;
288             $game->{'delay'} += 100;
289             }
290              
291             }
292              
293              
294             unshift @snake, $new_head;
295             draw('#000000', $new_head);
296             }
297              
298             sub get_rand {
299             my $max = shift;
300              
301             my $var = (rand() * ($max * 10) % $max) + 1;
302             my $off = $var % 1;
303             return $var - $off;
304             }
305              
306             sub check_snake {
307             my($x, $y) = @_;
308             return 0 if($stage[$x][$y] == 1);
309             return 1;
310             }
311              
312             sub report {
313             $mw->add_widget('Toplevel', 'reportwin', -title =>
314             'ObjectHandler Report');
315             $mw->reportwin->add_widget('Label', 'title', -text =>
316             'Tk::ObjectHandler Report For This Game')->pack(
317             -expand => 0, -fill =>'both');
318             $mw->reportwin->add_widget('Label', 'text', -background =>
319             '#FFFFFF', -justify => 'left', -text =>
320             $mw->report, -font => 'Courier')->pack(
321             -expand => 0, -fill =>'both');
322             $mw->reportwin->add_widget('Button', 'close', -text => 'Close',
323             -command => sub { $mw->reportwin->destroy(); }
324             )->pack();
325              
326             }
327              
328             sub help {
329             $mw->add_widget('Toplevel', 'helpwin', -title => 'Snake Help');
330             $mw->helpwin->add_widget('Label', 'la', -font => 'Courier',
331             -justify => 'left', => -text => <<"HELPTEXT"
332             The object of the game is to move your little snake the black blobs
333             around the white area collecting 'apples' (the green, red and yellow
334             blobs) without hitting the edge of the arena or your snake's body.
335             Each colour apple has a different affect, described below. The
336             keys are:
337             UP
338             a
339             ^
340             |
341             LEFT n <- -> m RIGHT
342             |
343             v
344             z
345             DOWN
346              
347             Green apples will cause your snake to grow and make it move faster.
348             Red apples will cause your snake to shrink and make it move faster.
349             Yellow apples will cause your snake to move slower.
350             HELPTEXT
351             )->pack(-expand => 0, -fill=> 'both');
352              
353              
354             $mw->helpwin->add_widget('Button', 'close', -text => 'Close',
355             -command => sub { $mw->helpwin->destroy(); })->pack();
356             }
357              
358              
359              
360             # Populate stage with blanks
361             for(my $x=0; $x<51; $x++){
362             for(my $y=0; $y<36; $y++) {
363             $stage[$x][$y] = 0; }}
364              
365             # Build the main window
366             $mw = Tk::ObjectHandler->new();
367             $mw->comment('Controlling widget');
368              
369             $mw->add_widget('Frame', 'menu', -relief => 'groove',
370             -borderwidth => '1');
371             $mw->menu->comment('Menubar Frame.');
372              
373             $mw->add_widget('Frame', 'score');
374             $mw->menu->comment('This frame holds score and snake length, etc.');
375              
376             $mw->add_widget('Canvas', 'field', -width => 500, -height => 350,
377             -background => '#FFFFFF');
378             $mw->field->comment('The main playing area.');
379              
380             $mw->add_widget('Frame', 'message', -relief => 'sunken',
381             -borderwidth => '1');
382             $mw->message->comment('This frame is used to hold messages to the player');
383              
384             # Menu Entries
385             $mw->menu->add_widget('Menubutton', 'game', -text => 'Game',
386             -menuitems => [
387             ['command' => "Play F1", -command =>sub{ init_game(); } ],
388             '-',
389             ['command' => "Quit F10", -command =>sub{ $mw->destroy(); }]
390             ])->pack(-side => 'left');
391             $mw->menu->game->comment('Holds game play commands');
392              
393             $mw->menu->add_widget('Menubutton', 'rep', -text => 'Report',
394             -menuitems => [
395             ['command' => 'Report', -command => sub{ report(); } ]
396             ])->pack(-side => 'left');
397             $mw->menu->rep->comment('Prints a sample Tk::ObjectHandler report in a new window');
398              
399             $mw->menu->add_widget('Menubutton', 'help', -text => 'Help',
400             -menuitems => [
401             [ 'command' => 'About', -command => sub{ about_window($mw) } ],
402             [ 'command' => 'How To Play', -command => sub{ help() } ]
403             ])->pack(-side => 'right');
404             $mw->menu->rep->comment('Displays help and copyright info.');
405              
406             # Score entries
407             $mw->score->add_widget('Label', 'l1', -text => 'Score: ',
408             -justify => 'right')->pack(-fill => 'both', -side => 'left',
409             -expand => 0);
410             $mw->score->add_widget('Label', 'score', -textvariable => \$score
411             )->pack(-fill => 'both', -side => 'left', -expand => 0);
412             $mw->score->add_widget('Label', 'l3', -text => 'Snake Length: ',
413             -justify => 'right')->pack(-fill => 'both', -side => 'left',
414             -expand => 0);
415             $mw->score->add_widget('Label', 'snake_length',
416             -textvariable => \$length)->pack(-fill => 'both',
417             -side => 'left', -expand => 0);
418             $mw->message->add_widget('Label', 'messages',
419             -textvariable => \$message)->pack(-side => 'left',
420             -fill => 'both', -expand => 0);
421              
422             $mw->menu->pack( -side => 'top', -expand => 0, -fill => 'both');
423             $mw->score->pack( -side => 'top', -expand => 0, -fill => 'both');
424             $mw->field->pack( -side => 'top', -expand => 0, -fill => 'none');
425             $mw->message->pack( -side => 'top', -expand => 0, -fill => 'both');
426              
427             $mw->bind('', sub{ init_game() if($game->{'play'} == 0);});
428             $mw->bind('', sub{ $mw->destroy(); });
429              
430             $mw->MainLoop;