| 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; |