#!/usr/local/bin/perl
###################################################################
# #
# rocket.pl - Land a rocket ship on a moving platform #
# David Bradford - Tinypig software - www.tinypig.com #
# Copyright (c) 2003 David Bradford. #
# All rights reserved. This program is free software; you can #
# redistribute it and/or modify it under the same terms as Perl #
# itself; however, you must leave this copyright statement #
# intact. #
# #
###################################################################
use strict;
#####################
# Class: Game_Piece #
#####################
# This is the base class for game_pieces
package Game_Piece;
# Class: Game_Piece
# Method: r
# Return a random integer from 1 to the first parameter.
sub r {
my ($this,$num) = @_;
return int(rand($num) + 1);
}
###############
# Class: Base #
###############
package Base;
use vars qw(@ISA);
@ISA = ('Game_Piece');
# Constants
sub HEIGHT {10;}
sub SIDE_WIDTH {10;}
sub BOTH_SIDES {(SIDE_WIDTH) * 2;}
# Class: Base
# Method: new
# Called to instantiate the class.
sub new {
my ($class, $screen, $width) = @_;
my $this = bless {
'inc' => 0,
'move' => 1,
'screen' => $screen,
'width' => $width,
}, $class;
# initialize
return $this;
}
# Class: Base
# Method: calcMove
# This routine calculates where the base should move next
sub calcMove {
my ($this,$hpos,$move) = @_;
if( $$hpos + $$move < 1 ) { $$move = $this->{'inc'} }
elsif( $$hpos + $$move >
(&main::SCR_WIDTH) - $this->{'width'} - BOTH_SIDES ) { $$mo
+ve = -$this->{'inc'} }
$$hpos += $$move;
}
# Class: Base
# Method: getHpos
# Returns the horizontal position of the base in pixels.
sub getHpos {
my $this = shift;
return ( $this->{'hpos'} + SIDE_WIDTH );
}
# Class: Base
# Method: getMove
# Returns the move attribute.
sub getMove {
my $this = shift;
return $this->{'move'};
}
# Class: Base
# Method: getWidth
# Set the width attribute of the base
sub getWidth {
my $this = shift;
return $this->{'width'};
}
# Class: Base
# Method: initPosition
# Prepares the base to be drawn in a random horizontal position.
sub initPosition {
my $this = shift;
$this->{'hpos'} = $this->r((&main::SCR_WIDTH) - $this->{'width'} -
+ BOTH_SIDES );
}
# Class: Base
# Method: make
# Draw the base.
sub make {
my ( $this, $color ) = @_;
$this->{'screen'}->delete('base');
$this->{'screen'}->createPolygon( $this->{'hpos'} + 0, 500,
$this->{'hpos'} + (SIDE_WIDTH), 490,
$this->{'hpos'} + (SIDE_WIDTH) + $this->{'
+width'}, 490,
$this->{'hpos'} + (BOTH_SIDES) + $this->{'
+width'}, 500,
-fill => $color,
-outline => 'white',
-tags => ['base', 'base_and_rocket', 'ev
+erything'] );
$this->{'screen'}->lower('base','everything');
}
# Class: Base
# Method: move
# This routine will reverse the direction of the base if it has hit ei
+ther side of
# the screen, and also moves the base one "tick" in whatever direction
+ it is going.
sub move {
my ( $this, $tag ) = @_;
if(!&main::DEBUG) {
$this->calcMove(\$this->{'hpos'},\$this->{'move'});
$this->{'screen'}->move($tag, $this->{'move'},0);
}
}
# Class: Base
# Method: predictPosition
# Predicts where the base will be n ticks from now
sub predictPosition {
my ($this, $n) = @_;
my $hpos = $this->{'hpos'};
my $move = $this->{'move'};
for(my $i = 1; $i < $n; ++$i) {
$this->calcMove(\$hpos,\$move);
}
return $hpos;
}
# Class: Base
# Method: setIncrement
# Sets the number of pixels the base will move each "tick". This is u
+sed
# to increase the difficulty throughout the game.
sub setIncrement {
my ($this, $inc) = @_;
$this->{'inc'} = $inc;
$this->{'move'} = $this->{'move'}<0?-$inc:$inc;
}
# Class: Base
# Method: setWidth
# Set the width attribute of the base
sub setWidth {
my ($this,$width) = @_;
$this->{'width'} = $width;
}
#################
# Class: Rocket #
#################
package Rocket;
use vars qw(@ISA);
@ISA = ('Game_Piece');
# Constants
sub WIDTH { 40;}
sub HEIGHT { 55;}
sub BASECOLOR { 'blue';}
sub CONECOLOR { 'purple';}
sub FINCOLOR {'dark grey';}
# Class: Rocket
# Method: new
# Called to instantiate the class.
sub new {
my ($class, $screen) = @_;
my $this = bless {
'screen' => $screen,
'everyother' => 0,
'screen_width' => &main::SCR_WIDTH
}, $class;
$this->initPosition();
return $this;
}
# Class: Rocket
# Method: changeDirection
# The rocket is always be in motion. When a player decides to
# change direction or just go straight down, this routine is
# called.
sub changeDirection {
my ($this,$dir) = @_;
if( $dir eq 'left' ) {
$this->{'move'} = -(&main::INCREMENT);
} elsif( $dir eq 'right' ) {
$this->{'move'} = &main::INCREMENT;
} elsif( $dir eq 'down' ) {
$this->{'move'} = 0;
}
}
# Class: Rocket
# Method: crashTick
# When the rocket crashes, each time this routine is called it
# moves each of the pieces one "tick" in their course of being
# blown apart.
sub crashTick {
my ($this) = @_;
$this->_movePiece('p1', -1, -1);
$this->_movePiece('p2', 1, -1);
$this->_movePiece('p3', -1, $this->{'everyother'} % 2 * -1);
$this->_movePiece('p4', 1, $this->{'everyother'} % 2 * -1);
$this->_movePiece('p5', -1, $this->{'everyother'} % 2 * 1);
$this->_movePiece('p6', 1, $this->{'everyother'} % 2 * 1);
$this->_movePiece('p7', -1, 1);
$this->_movePiece('p8', 1, 1);
$this->{'everyother'} = $this->{'everyother'}?0:1;
}
# Class: Rocket
# Method: delete
# Deletes the rocket from the screen after it explodes or
# lands successfully
sub delete {
my $this = shift;
$this->{'screen'}->clearRocket();
}
# Class: Rocket
# Method: getHpos
# Get the horizontal position of the rocket in pixels.
sub getHpos {
my $this = shift;
return $this->{'hpos'};
}
# Class: Rocket
# Method: getVpos
# Get the vertical position of the rocket in pixels.
sub getVpos {
my $this = shift;
return $this->{'vpos'};
}
# Class: Rocket
# Method: initPosition
# Prepares the rocket to be drawn in a random horizontal initial
# position at the top of the screen, preparing it to begin descent.
sub initPosition {
my $this = shift;
$this->{'move'} = 0;
$this->{'vpos'} = 0;
if( &main::DEBUG ) { $this->{'vpos'} = (&main::SCR_HEIGHT) - $this
+->HEIGHT - 10 - 3 }
$this->{'hpos'} = $this->r((&main::SCR_WIDTH) - $this->WIDTH);
}
# Class: Rocket
# Method: makeRocket
# Draw the rocket for the first time. These polygons will be manipula
+ted after
# creation to animate them and only need to be "drawn" initially once,
+ or after
# the rocket is deleted.
sub makeRocket {
my ($this) = @_;
$this->_makePiece( 'p1', $this->CONECOLOR, 10, 15, 20, 0, 20, 15)
+;
$this->_makePiece( 'p2', $this->CONECOLOR, 20, 0, 30, 15, 20, 15)
+;
$this->_makePiece( 'p3', $this->BASECOLOR, 10, 15, 10, 30, 20, 30,
+ 20, 15 );
$this->_makePiece( 'p4', $this->BASECOLOR, 20, 15, 30, 15, 30, 30,
+ 20, 30 );
$this->_makePiece( 'p5', $this->BASECOLOR, 10, 30, 20, 30, 20, 45,
+ 10, 45 );
$this->_makePiece( 'p6', $this->BASECOLOR, 20, 30, 30, 30, 30, 45,
+ 20, 45 );
$this->_makePiece( 'p7', $this->FINCOLOR, 10, 45, 0, 55, 20, 55,
+ 20, 45 );
$this->_makePiece( 'p8', $this->FINCOLOR, 30, 45, 40, 55, 20, 55,
+ 20, 45 );
$this->{'screen'}->raise('rocket','base');
}
# Class: Rocket
# Method: move
# Moves the rocket for one "tick". Also makes sure it doesn't move be
+yond
# the edge of the screen horizontally.
sub move {
my ($this) = @_;
my $inc = &main::INCREMENT;
if( &main::DEBUG ) { $inc = 0; }
if( $this->{'hpos'} + $this->{'move'} > 0 &&
$this->{'hpos'} + $this->{'move'} <= $this->{'screen_width'} -
+ $this->WIDTH + &main::RIGHT_SIDE_FIX) {
$this->{'screen'}->move('rocket',$this->{'move'},$inc);
$this->{'hpos'} += $this->{'move'};
} else {
$this->{'screen'}->move('rocket',0,$inc);
}
$this->{'vpos'} += $inc;
}
# Class: Rocket
# Method: _makePiece
# Used internally by instances of this class and called by makeRocket,
+ this
# does the actual work of drawing each polygon in the rocket.
sub _makePiece {
my ( $this, $tag, $color, @points ) = @_;
for( my $i = 0; $i < $#points; $i += 2 ) {
$points[$i ] += $this->{'hpos'};
$points[$i + 1] += $this->{'vpos'};
}
$this->{'screen'}->createPolygon(@points,
-fill => $color,
-tags => [$tag,'rocket','base_and_rocket','everyt
+hing','game_pieces']);
}
# Class: Rocket
# Method: _movePiece
# Used internally by instances of this class and called by crashTick,
+this
# actually does the work of moving the individual pieces one "tick" af
+ter
# a crash.
sub _movePiece {
my ( $this, $piece_tag, $x_multip, $y_multip ) = @_;
$this->{'screen'}->move( $piece_tag,
$x_multip * $this->r(&main::EXPLODE_MOVE),
$y_multip * $this->r(&main::EXPLODE_MOVE) );
}
#################
# Class: Screen #
#################
package Screen;
# Class: Screen
# Method: new
# Called to instantiate the class.
sub new {
my ($class, $MW) = @_;
my $this;
my $canvas = $MW
->Canvas(
-width => &main::SCR_WIDTH,
-height => &main::SCR_HEIGHT,
-border => 1,
-relief => 'ridge',
-background => 'black'
)
->pack();
my $image = $MW->Photo(-format => 'jpeg', -file => &main::ROCKET_D
+IR.'/stars.jpg');
$canvas->createImage(250,250,-image => $image);
$this = bless {
'canvas' => $canvas
}, $class;
return $this;
}
# Class: Screen
# Method: afterCancel
# Cancels a specific alarm that is set, or all alarms. These alarms a
+re
# set to cause routines like "tick" to be called by the OS every few m
+illiseconds
# in order to animate the game.
sub afterCancel {
my $this = shift;
$this->{'canvas'}->afterCancel(@_);
}
# Class: Screen
# Method: clearGamePieces
# Clears all game pieces from the screen.
sub clearGamePieces {
my $this = shift;
$this->{'canvas'}->delete('game_pieces');
}
# Class: Screen
# Method: clearMessages
# Clears all messages from the screen.
sub clearMessages {
my $this = shift;
$this->{'canvas'}->delete('messages');
}
# Class: Screen
# Method: clearRocket
# Clears the rocket from the screen.
sub clearRocket {
my $this = shift;
$this->{'canvas'}->delete('rocket');
}
# Class: Screen
# Method: createPolygon
# Allows game piece objects to draw a polygon on the canvas.
sub createPolygon {
my $this = shift;
$this->{'canvas'}->createPolygon(@_);
}
# Class: Screen
# Method: delete
# Allows game piece objects to delete themselves from the canvas.
sub delete {
my $this = shift;
$this->{'canvas'}->delete(@_);
}
# Method: lower
# Lower an object in relation to other objects
sub lower {
my $this = shift;
$this->{'canvas'}->lower(@_);
}
# Class: Screen
# Method: move
# Used by the game piece objects to move themselves across the canvas.
sub move {
my $this = shift;
$this->{'canvas'}->move(@_);
}
# Class: Screen
# Method: raise
# Raise an object in relation to other objects
sub raise {
my $this = shift;
$this->{'canvas'}->raise(@_);
}
# Class: Screen
# Method: showDebug
# Displayes the debugging info
sub showDebug {
my ($this,@arg) = @_;
$this->{'canvas'}->delete('debug');
$this->_showText('debug', 100, 80, "Rocket vpos: $arg[0]");
$this->_showText('debug', 100,100, "Rocket hpos: $arg[1]");
$this->_showText('debug', 100,120, "Base hpos: $arg[2]");
$this->_showText('debug', 100,140, "Status: $arg[3]");
$this->_showText('debug', 350, 80, "Base width: $arg[4]");
$this->_showText('debug', 350,100, "Base extra: $arg[5]");
$this->_showText('debug', 240,200, 'if( $rocket->getHpos() >= ( $b
+ase->getHpos() - SUCCESS_PLAY ) &&
$rocket->getHpos() + $rocket->WIDTH <=
( $base->getHpos() + $base->getWidth() + SUCCESS_PLAY ) ) { ok
+ }');
}
# Class: Screen
# Method: showGameOver
# This displays the Game Over message.
sub showGameOver {
my $this = shift;
$this->_showText( 'message', 250, 200, "GAME\nOVER");
}
# Class: Screen
# Method: showGuys
# Draws the icons representing each life (rocket) the player has left.
sub showGuys {
my ($this,$rocket,$guys) = @_;
$this->{'canvas'}->delete('guys');
for(1..$guys-1) {
my $guy_hval = $_ * 20 + 320;
$this->{'canvas'}->createPolygon( $guy_hval + 7, 15,
$guy_hval + 12, 9,
$guy_hval + 17, 15,
-fill => $rocket->CONECOLOR,
-tags => ['guys','everything
+'] );
$this->{'canvas'}->createPolygon( $guy_hval + 7, 15,
$guy_hval + 17, 15,
$guy_hval + 17, 25,
$guy_hval + 7, 25,
-fill => $rocket->BASECOLOR,
-tags => ['guys','everything
+'] );
$this->{'canvas'}->createPolygon( $guy_hval + 7, 25,
$guy_hval + 17, 25,
$guy_hval + 22, 30,
$guy_hval + 2, 30,
$guy_hval + 7, 25,
-fill => $rocket->FINCOLOR,
-tags => ['guys','everything
+'] );
}
}
# Class: Screen
# Method: showPause
# Displays the "PAUSE" message.
sub showPause {
my $this = shift;
$this->_showText('message', 250, 200, 'PAUSE', 'pause');
}
# Class: Screen
# Method: showScores
# Displays the score and the high score.
sub showScores {
my ($this, $score, $high) = @_;
my ($display_high, $display_score);
$this->delete('scores');
$score ||= 0;
$display_score = sprintf("%6d", $score);
$display_high = sprintf("%6d", $high);
$this->_showText('score', 80, 41, $display_score);
$this->_showText('score', 250, 41, $display_high );
}
# Class: Screen
# Method: showTitles
# Displayes the "SCORE" and "HIGH" titles.
sub showTitles {
my $this = shift;
$this->_showText('title', 250, 16, 'HIGH' );
$this->_showText('title', 80, 16, 'SCORE');
}
# Class: Screen
# Method: _showText
# Used internally by instances of this class to display certain
# types of text on the screen.
sub _showText {
my ( $this, $type, $x, $y, $text, $extra_tag ) = @_;
if( $type eq 'message' ) {
$this->{'canvas'}->createText( $x, $y,
-fill => 'white',
-font => 'Arial 20 bold',
-text => $text,
-tags => [ 'messages', 'everything', $extra
+_tag ]);
} elsif( $type eq 'score' ) {
$this->{'canvas'}->createText( $x, $y,
-fill => 'white',
-font => 'Arial 20 bold',
-text => $text,
-tags => [ 'scores', 'everything', $ext
+ra_tag ]);
} elsif( $type eq 'title' ) {
$this->{'canvas'}->createText( $x, $y,
-fill => 'red',
-font => 'Arial 10 bold',
-text => $text,
-tags => [ 'titles', 'everything', $ext
+ra_tag ]);
} elsif( $type eq 'debug' ) {
$this->{'canvas'}->createText( $x, $y,
-fill => 'red',
-font => 'Arial 10 bold',
-text => $text,
-tags => [ 'debug', 'everything', $extr
+a_tag ]);
}
}
########
# MAIN #
########
package main;
use Tk;
use Tk::Dialog;
use Tk::JPEG;
my ( $anim_seq, $autopilot, $base, $cheat, %config );
my ( $game, $guys, $nosound );
my ( $pause_cb, $rocket, $score, $screen, $sound_c, %timer_id, $width
+);
my ( $frame, $hmenu, $menu, $MW); # window variables
# Constants
sub BWIDTH {10;}
sub COPYRIGHT {'(c) 2003 David Bradford, Tinypig Software (www.tinyp
+ig.com)';}
sub DEBUG {0;}
sub FONT {'Arial 8 normal';}
sub PADX {5;}
sub PADY {3;}
sub ROCKET_DIR {'.';}
sub VERSION {'1.02';}
# game constants
sub AUTO_DOWN_RAND { 30;}
sub AUTO_WRONG_DEVIATE { 50;}
sub AUTO_WRONG_DONE_RAND { 100;}
sub AUTO_WRONG_RAND { 30;}
sub BASE_RAND { 3;}
sub CRASH_DELAY { 15;}
sub CRASH_FRAMES { 55;}
sub EXPLODE_MOVE { 6;}
sub EXTRA_GUY {1000;}
sub INCREMENT { 2;}
sub INI_FILE {'rocket.ini';}
sub MAX_GUYS { 5;}
sub PIECE_SPACING { 2;}
sub RIGHT_SIDE_FIX { 4;}
sub SCR_WIDTH { 500;}
sub SCR_HEIGHT { 500;}
sub SUCCESS_FRAME_MIN { 200;}
sub SUCCESS_FRAME_PLAY { 100;}
sub SUCCESS_PLAY { 8;}
sub SUCCESS_POINTS { 100;}
sub TICK_DELAY { 15;}
sub CRASH_SOUND {'arcade11.wav';}
sub EXTRA_GUY_SOUND {'arcade07.wav';}
sub LANDING_SOUND {'arcade02.wav';}
# Initialization
my $auto_down = 0;
my $gameover = 0;
my $level = 0;
my $paused = 0;
my $purpose = 0;
my $success_frames = 0;
my @b_colors = ( 'green', 'magenta', 'blue', 'red', 'turquoi
+se' );
my @b_increments = ( 3, 4, 5, 6,
+ 7 );
my @next_level = ( 400, 800, 1200, 1600, 999
+999 );
my @width = ( 58, 56, 54, 52,
+ 50 );
my $auto_wrong = 0;
readConfig();
eval {
require Win32::Sound;
};
$nosound = $@?1:0;
$config{'sound'} = $@?0:$config{'sound'};
$sound_c = $config{'sound'};
setupWindow();
$screen = Screen->new($MW);
# Debugging key binds
$MW->bind('<s>' => sub { $score += SUCCESS_POINTS; $screen->showScores
+($score, $config{'high'}) });
$MW->bind('<a>' => sub { $score -= SUCCESS_POINTS; $screen->showScores
+($score, $config{'high'}) });
$MW->bind('<f>' => sub { ++$guys; $screen->showGuys($
+rocket, $guys) });
$MW->bind('<d>' => sub { $guys -= $guys>1?1:0; $screen->showGuys($
+rocket, $guys) });
# game key binds
$MW->bind('<x>' => sub { exit } );
$MW->bind('<n>' => \&startGame );
$MW->bind('<p>' => \&pause );
$MW->bind('<Left>' => sub { $rocket->changeDirection('left') });
$MW->bind('<Right>' => sub { $rocket->changeDirection('right') });
$MW->bind('<Down>' => sub { $rocket->changeDirection('down') });
$MW->bind('<Up>' => sub { if($cheat=$cheat?0:1) { calcAutopilot()
+} else { $autopilot = 0 } });
# menu key binds
$MW->bind('<Alt-Key-r>' => sub { $menu->Post; Tk::Menu->Unpost });
$MW->bind('<Alt-Key-h>' => sub { $hmenu->Post; Tk::Menu->Unpost });
$base = Base->new($screen,$width[$level]);
$rocket = Rocket->new($screen);
$screen->showTitles();
$base->initPosition();
# run as if game is over until a new game is started
gameOver();
# MainLoop gives control back to windows.
MainLoop;
# Subroutine: tick
# This is the main subroutine in the game. Each time it is called,
# game pieces move their alloted distance and checks are made
# for success or failure. This is out of alphabetical order because
# it is the main routine.
sub tick() {
if( $paused ) {
$timer_id{'t'} = $MW->after(TICK_DELAY, \&tick);
} else {
# if the game is over, auto-pilot the rocket
if($autopilot) {
# Here I am trying to make the auto-pilot look a little
# more human by giving it the wrong target location
# every so often
if( r(AUTO_WRONG_RAND) == 1 && !$auto_wrong && !$cheat ) {
if( r(2) == 1 ) {
my $scr_max = (SCR_WIDTH) - $rocket->WIDTH + RIGHT
+_SIDE_FIX - $base->SIDE_WIDTH;
my $scr_min = $base->SIDE_WIDTH;
$auto_wrong = $autopilot - (AUTO_WRONG_DEVIATE) +
+r((AUTO_WRONG_DEVIATE) * 2);
if( $auto_wrong < $scr_min ) { $auto_wrong = $scr_
+min }
if( $auto_wrong > $scr_max ) { $auto_wrong = $scr_
+max }
} else {
$auto_down = r(AUTO_DOWN_RAND);
}
}
my $goal = $auto_wrong?$auto_wrong:$autopilot;
if( abs($rocket->getHpos() - $goal) < 2 || $auto_down) {
$rocket->changeDirection('down');
if($auto_wrong){ if(r(AUTO_WRONG_DONE_RAND) == 1) { $
+auto_wrong = 0 } }
if($auto_down) { --$auto_down }
} elsif($rocket->getHpos() < $goal) {
$rocket->changeDirection('right');
} elsif($rocket->getHpos() > $goal) {
$rocket->changeDirection('left');
}
}
$rocket->move();
if( DEBUG ) {
$rocket->changeDirection('down');
my $status = detectCrash()?'land':'CRASH';
$screen->showDebug($rocket->getVpos(), $rocket->getHpos(),
+ $base->getHpos(), $status,
$base->getWidth(), $base->BOTH_SIDES,
+);
}
if( $rocket->getVpos() < ( (SCR_HEIGHT) - $rocket->HEIGHT - $b
+ase->HEIGHT - PIECE_SPACING ) ) {
$base->move('base');
$timer_id{'t'} = $MW->after(TICK_DELAY, \&tick);
} else {
$auto_wrong = 0;
if( detectCrash() ) {
success();
} else {
crash();
}
}
}
}
sub calcAutopilot {
# calculate number of ticks until landing
my $k = int((((SCR_HEIGHT) - $rocket->HEIGHT - $base->HEIGHT - 2 -
+ $rocket->getVpos()) / INCREMENT) + .5);
# calculate horizontal position rocket needs to be at
$autopilot = $base->predictPosition($k) + ($base->SIDE_WIDTH) + r(
+SUCCESS_PLAY);
if( !$cheat ) { $auto_down = r(AUTO_DOWN_RAND) };
}
# Subroutine: crash
# This routine is called when a crash is detected.
sub crash {
--$guys unless $gameover;
$anim_seq = 1;
playSound(CRASH_SOUND);
$success_frames = r(SUCCESS_FRAME_PLAY) + SUCCESS_FRAME_MIN;
crashAnim();
}
# Subroutine: crashAnim
# Called by crash, this routine is like the "tick" routine for the
# crash animation (the explosion and the base moving back and forth).
sub crashAnim {
if( !$paused ) {
++$anim_seq;
if( $anim_seq <= CRASH_FRAMES ) {
$rocket->crashTick();
$base->move('base');
$timer_id{'c'} = $MW->after(CRASH_DELAY, \&crashAnim);
} else {
$rocket->delete();
if( $anim_seq <= $success_frames ) {
$base->move('base');
$timer_id{'c'} = $MW->after(CRASH_DELAY, \&crashAnim);
} else {
if( $guys > 0 ) {
init();
} else {
gameOver();
}
}
}
} else {
$timer_id{'c'} = $MW->after(CRASH_DELAY, \&crashAnim);
}
}
# Subroutine: detectCrash
# Tell us if we are not aligned correctly with the base,
# and ready for a crash.
sub detectCrash {
if( $rocket->getHpos() >= ( $base->getHpos() - SUCCESS_PLAY ) &&
$rocket->getHpos() + $rocket->WIDTH <=
( $base->getHpos() + $base->getWidth() + SUCCESS_PLAY ) )
{ return 1 }
return 0;
}
# Subroutine: gameOver
# This routine is called after a crash, when it is determined you have
# no more "guys" left.
sub gameOver {
$screen->showGameOver();
startGame(1);
}
# Subroutine: gameOverAnim
# Called by gameOver, this routine is like the "tick" routine for the
# game over animation (the base just keeps moving back and forth
# until a new game is started).
sub gameOverAnim {
$base->move('base');
$timer_id{'c'} = $MW->after(CRASH_DELAY, \&gameOverAnim);
}
# Subroutine: init
# This routine resets the board after a successful landing, a crash,
# or the beginning of the game.
sub init {
$rocket->initPosition();
for(keys %timer_id) { $screen->afterCancel($timer_id{$_}) }
%timer_id = ();
$screen->clearGamePieces();
if(!$gameover){ $screen->clearMessages() }
$screen->showGuys($rocket, $guys);
$rocket->makeRocket();
# Autopilot the ship if the game is over
if($gameover || $cheat ) {
calcAutopilot();
} else {
$autopilot = 0;
}
tick();
}
# Subroutine: pause
# Called to pause the game, either when the pause key is pressed
# or when an option or help window pops up.
sub pause {
my $pause = shift;
$pause ||= '';
$paused ||= 0;
$purpose ||= 0;
if( ref $pause ) { $pause = shift };
if( !$gameover ) {
if( ( $paused && $pause eq '' ) || ( $pause eq 'off' && !$purp
+ose ) ) {
$paused = 0;
$pause_cb = 0;
$purpose = 0;
$screen->clearMessages();
} else {
$paused = 1;
$pause_cb = 1;
$purpose = ($pause eq '' || $purpose)?1:0;
$screen->showPause();
}
}
}
# Subroutine: playSound
# Plays the specified sound. (Win32 only)
sub playSound {
my $sound = shift;
if(!$nosound && $config{'sound'} && !$gameover) {
Win32::Sound::Play(ROCKET_DIR."/$sound",&Win32::Sound::SND_ASY
+NC);
}
}
# Subroutine: r
# Return a random integer from 1 to the first parameter.
sub r {
my $num = shift;
return int(rand($num) + 1);
}
# Subroutine: readConfig
# Read the configuration file
sub readConfig {
open IN, ROCKET_DIR.'/'.INI_FILE or die "Can't open ini file: $!";
while(<IN>) {
chomp;
s/\s//g;
my ($key, $value) = split /=/;
$config{$key} = $value;
}
close IN;
}
# Subroutine: setBaseLevel
# This is called to set the difficulty level of the game
# by adjusting the base.
sub setBaseLevel {
my $level = shift;
$base->setWidth($width[$level]);
$base->make($b_colors[$level]); #ZZZ all of this stuff should be c
+ontained in Base
$base->setIncrement($b_increments[$level]);
}
# Subroutine: setHigh
# set the high score
sub setHigh {
my $h = shift;
$config{'high'} = $h;
writeConfig();
}
# Subroutine: setupWindow
# Create the main window
sub setupWindow {
$MW = MainWindow->new;
$MW->title("Rocket");
$frame = $MW->Frame(-relief => 'ridge',
-borderwidth => 2)
->pack(-side => 'top',
-anchor => 'n',
-fill => 'x');
$menu = $frame
->Menubutton(-text => "Rocket",
-underline => 0,
-font => FONT,
-tearoff => 0,
-menuitems => [['command' => " New Game (n)",
-underline => 1,
-font => FONT,
-command => \&startGame],
['checkbutton' => " Pause (p)",
-underline => 1,
-onvalue => 1,
-offvalue => 0,
-variable => \$pause_cb,
-command => \&pause,
-font => FONT,
-command => \&pause],
['command' => " Options",
-underline => 1,
-font => FONT,
-command => \&showOptions],
['command' => " Exit (x)",
-underline => 2,
-font => FONT,
-command => sub { exit }]])
->pack(-side => 'left');
$hmenu = $frame
->Menubutton(-text => "Help",
-underline => 0,
-font => FONT,
-tearoff => 0,
-menuitems => [['command' => "Help",
-underline => 0,
-font => FONT,
-command => \&showHelp],
['command' => "About",
-underline => 0,
-font => FONT,
-command => \&showAbout]])
->pack(-side => 'left');
}
# Subroutine: startGame
# Initializes the game. Called when a new game is started.
sub startGame {
$gameover = shift;
if(ref $gameover){ $gameover = shift };
if(!$gameover) {
$score = 0;
$level = 0;
}
$guys = MAX_GUYS;
$purpose = 0;
pause('off');
$screen->showScores($score, $config{'high'});
$screen->showGuys($rocket, $guys);
setBaseLevel($level);
init();
}
# Subroutine: showAbout
# Called to bring up the About window.
sub showAbout {
my $howtouse_d=$MW->Dialog(
-text => qq|Rocket\nversion |.VERSION."\n".COPYRIGHT.qq|\
+n\n|,
-title => 'About',
-font => FONT,
-default_button => 'Ok',
-buttons => ['Ok']);
pause('on');
$howtouse_d->geometry('480x160');
$howtouse_d->Show;
pause('off');
}
# Subroutine: showHelp
# Called to bring up the Help window.
sub showHelp {
my $help_d;
my $text = qq|Rocket
version |.VERSION."\n".COPYRIGHT.qq|
Land the rocket on the moving platform.
Use the left, right, and down arrows to navigate.
Other keys:
p - pause
x - exit
n - new game
There is no "thrust". It's part of the challenge.
|;
$text =~ s/ //g;
$help_d=$MW->Dialog(
-text => $text,
-title => 'Help',
-font => FONT,
-default_button => 'Ok',
-buttons => ['Ok']);
pause('on');
$help_d->Show;
pause('off');
}
# Subroutine: showOptions
# Called to bring up the Options window.
sub showOptions {
my $option_d=$MW->Toplevel();
my $index;
my $state = 'normal';
my $subok = sub {
$option_d->destroy;
$config{'sound'} = $sound_c;
writeConfig();
pause('off')
};
if( $nosound ) {
$state = 'disabled';
$sound_c = 0;
}
pause('on');
$option_d->geometry('130x80');
$option_d->grab();
$option_d->bind('<Return>' => $subok );
$option_d->bind('<Alt-Key-p>' => sub { $sound_c = $sound_c?0:1 } );
$option_d->title("Options");
$option_d->focus;
my $undoc_cb=$option_d
->Checkbutton(-text => "Play sound",
-underline => 0,
-variable => \$sound_c,
-font => FONT,
-state => $state)
->pack(-side => 'top',
-padx => PADX,
-pady => PADY);
my $ok=$option_d->Button(-text => 'OK',
-font => FONT,
-width => BWIDTH,
-command => $subok )
->pack(-side => 'top');
$option_d->waitWindow();
}
# Subroutine: success
# This is called when a successful landing on the base is detected.
sub success {
if(!$gameover) {
$score += SUCCESS_POINTS;
if( $score > $config{'high'} ) {
setHigh($score);
}
}
$screen->showScores($score, $config{'high'});
if( $gameover && r(BASE_RAND) == 1 ) {
setBaseLevel(r($#b_increments + 1) - 1);
}
$score ||= 0;
if( $score >= $next_level[$level] && $level < $#b_increments ) {
setBaseLevel(++$level);
}
if( !($score % EXTRA_GUY) && !$gameover ) {
++$guys;
$screen->showGuys($rocket, $guys);
playSound(EXTRA_GUY_SOUND);
} else {
playSound(LANDING_SOUND);
}
$anim_seq = 1;
$success_frames = r(SUCCESS_FRAME_PLAY) + SUCCESS_FRAME_MIN;
successAnim();
}
# Subroutine: successAnim
# Called by success, this routine is like the "tick" routine for the
# success animation (that period of time where the rocket is just goin
+g
# back and forth on the platform after a landing).
sub successAnim {
if( !$paused ) {
++$anim_seq;
if( $anim_seq <= $success_frames ) {
# strictly speaking in OO terms, this is cheating. The ba
+se
# shouldn't know anything about the rocket, but it's much
# more convenient and efficient to use the canvas methods
# to move the rocket with the base. I refuse to feel guil
+ty! :)
$base->move('base_and_rocket');
$timer_id{'t'} = $MW->after(TICK_DELAY, \&successAnim);
} else {
init();
}
} else {
$timer_id{'t'} = $MW->after(TICK_DELAY, \&successAnim);
}
}
# Subroutine: writeConfig
# Write the configuration back to the config file
sub writeConfig {
open OUT, '>'.ROCKET_DIR.'/'.INI_FILE or die "Can't open ini file:
+ $!";
for(keys %config) {
print OUT "$_ = $config{$_}\n";
}
close OUT;
}
__END__
=head1 NAME
Rocket
=head1 DESCRIPTION
This is a game, the object of which is to land a rocket on a moving pl
+atform.
=head1 README
Unzip the archive into its own directory. cd to the directory.
Usage:
perl rocket.pl
You fly the rocket with the left, right, and down keys.
Other keys:
<p> - pause
<x> - exit
<n> - new game
<up> - God mode?
=head1 PREREQUISITES
This script requires C<Tk>, C<Tk::Dialog>, C<Tk::JPEG>, and C<Win32::S
+ound> (for sound under Win32 - no sound support
for Unix).
=head1 OSNAMES
Win32, Unix
=head1 SCRIPT CATEGORIES
Win32
Games
=head1 VERSION
1.02
=head1 HISTORY
Version 1.02
- Cleaned up code
Version 1.01
- Added autopilot AI
- Cleaned up code
Version 1.00
- Finally stable (whew)
=head1 AUTHOR
David Bradford dmbradford@altavista.com
=head1 COPYRIGHT
Copyright (c) 2003 David Bradford. All rights reserved. This program
+ is free software;
you can redistribute it and/or modify it under the same terms as Perl
+itself; however, you
must leave this copyright statement intact.
=head1 DATE
May 1, 2003
=head1 SOURCE
This distribution can also be found at the author's web site
http://www.tinypig.com
=cut
In reply to Rocket
by tinypig
-
Are you posting in the right place? Check out Where do I post X? to know for sure.
-
Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
<code> <a> <b> <big>
<blockquote> <br /> <dd>
<dl> <dt> <em> <font>
<h1> <h2> <h3> <h4>
<h5> <h6> <hr /> <i>
<li> <nbsp> <ol> <p>
<small> <strike> <strong>
<sub> <sup> <table>
<td> <th> <tr> <tt>
<u> <ul>
-
Snippets of code should be wrapped in
<code> tags not
<pre> tags. In fact, <pre>
tags should generally be avoided. If they must
be used, extreme care should be
taken to ensure that their contents do not
have long lines (<70 chars), in order to prevent
horizontal scrolling (and possible janitor
intervention).
-
Want more info? How to link
or How to display code and escape characters
are good places to start.