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(' |
||||||
104 | #$mw->bind(' |
||||||
105 | #$mw->bind(' |
||||||
106 | #$mw->bind(' |
||||||
107 | $mw->bind(' |
||||||
108 | $mw->bind(' |
||||||
109 | $mw->bind(' |
||||||
110 | $mw->bind(' |
||||||
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(' |
||||||
428 | $mw->bind(' |
||||||
429 | |||||||
430 | $mw->MainLoop; |