% Some configurable constants
const MAX_ASTEROIDS : int := 200
const MAX_FRAME_RATE : int := 30
const TIME_BETWEEN_ASTEROIDS : int := 3000
const BOUNCE_SOUND : string := "bounce.wav"
const EXPLOSION_SOUND : string := "explosion.wav"
const RUMBLE_SOUND : string := "engine.wav"
const SILENCE_SOUND : string := "silence.wav"
% Some constants that shouldn't be changed without looking at the
% source code.
const NUM_SHIP_IMAGES : int := 36
const UP_ARROW : char := chr (200)
const LEFT_ARROW : char := chr (203)
const RIGHT_ARROW : char := chr (205)
const SHIP_WINDOW_MARGINS : int := 130
const ASTEROID_RADIUS : int := 35
const SHIP_RADIUS : int := 19
const SHIPF_RADIUS : int := 23
const MARGINS : int := SHIP_WINDOW_MARGINS + ASTEROID_RADIUS
const MIN_MILLISECS_BETWEEN_FRAMES : int := 1000 div MAX_FRAME_RATE
% The pictures
var asteroid : array 0 .. 119 of int
var ship : array 0 .. 35 of int
var shipf : array 0 .. 35 of int
var space : int
% More information
var backgroundPictureWidth, backgroundPictureHeight : int
var backgroundPictureX, backgroundPictureY : int
var shipAngle : int
var shipX, shipY, shipDX, shipDY : real
var asteroidX, asteroidY : array 1 .. MAX_ASTEROIDS of real
var asteroidDX, asteroidDY : array 1 .. MAX_ASTEROIDS of real
var asteroidAngle, asteroidDAngle : array 1 .. MAX_ASTEROIDS of real
var numAsteroids : int := 0
var lastAsteroidTime : int := 0
% The font for score display
var displayFont : int := Font.New ("Sanserif:14:bold")
var announceFont : int := Font.New ("Sanserif:72:bold")
% The current time
var startTime : int
var lastFrameTime : int := 0
% The keys currently being pressed
var keys : array char of boolean
% Is the engine rumbling or not
var engineRumble : boolean := false
var finished : boolean := false
var frameTime : int := 0
var lastFrameRate : int := 0
% Used to play a single sound effect
process PlaySound (file : string)
engineRumble := false
Music.PlayFile (file)
end PlaySound
% Used to play the rumble sound
process PlayContinuousRumble
loop
Music.PlayFile (RUMBLE_SOUND)
exit when not engineRumble
end loop
end PlayContinuousRumble
% Used to play background music. The background music should be
% a MIDI file or an MP3 file so as not to interfere with the
% sound effects (WAV files).
process PlayBackgroundMusic (file : string)
loop
Music.PlayFile (file)
exit when finished
end loop
end PlayBackgroundMusic
procedure CentreText (s : string, row : int)
locate (row, (maxcol - length (s)) div 2)
put s
View.Update
end CentreText
procedure CentreFont (s : string, x, y, f, clr : int)
var w : int := Font.Width (s, f)
Font.Draw (s, x - w div 2, y, f, clr)
end CentreFont
% This method is called each time the screen is repainted
procedure DrawScreen
var frameRate : int
% Draw the background. We don't have to clear the screen because
% this will do it for us.
Pic.Draw (space, -backgroundPictureX, -backgroundPictureY, picCopy)
% Draw the number of frames per second
frameRate := 1000 div (Time.Elapsed - frameTime)
if abs (lastFrameRate - frameRate) > 1 then
lastFrameRate := frameRate
end if
locate (1, 1)
put lastFrameRate ..
frameTime := Time.Elapsed
% Draw the asteroids
for i : 1 .. numAsteroids
Pic.Draw (asteroid (round ((asteroidAngle (i) - 1.5) / 3)),
round (asteroidX (i) - backgroundPictureX - ASTEROID_RADIUS),
round (asteroidY (i) - backgroundPictureY - ASTEROID_RADIUS),
picMerge)
end for
% Draw the ship (with flame if engine is rumbling0
if engineRumble then
Pic.Draw (shipf (shipAngle),
round (shipX) - backgroundPictureX - SHIPF_RADIUS,
round (shipY) - backgroundPictureY - SHIPF_RADIUS, picMerge)
else
Pic.Draw (ship (shipAngle),
round (shipX) - backgroundPictureX - SHIP_RADIUS,
round (shipY) - backgroundPictureY - SHIP_RADIUS, picMerge)
end if
% Draw score
var timePassed : int := (Time.Elapsed - startTime) div 1000
CentreFont ("SCORE: " + intstr (timePassed, 3) + " " +
"ASTEROIDS: " + intstr (numAsteroids, 2),
maxx div 2, 5, displayFont, white)
% Draw the edge of the universe (the box the ship travels in)
Draw.Box (MARGINS - backgroundPictureX, MARGINS - backgroundPictureY,
backgroundPictureWidth - backgroundPictureX - MARGINS,
backgroundPictureHeight - backgroundPictureY - MARGINS, grey)
% If finished then
if finished then
CentreFont ("BOOM!", maxx div 2, maxy div 2, announceFont, brightred)
% Stop the background music and any playing sounds
engineRumble := false
Music.PlayFileStop
% Play the explosion
fork PlaySound (EXPLOSION_SOUND)
end if
View.Update
end DrawScreen
% We check for a collision by doing things the easy way. We assume
% that both the asteroid and the space ship is circular and see if they
% come within (asteroid radius + ship radius) of each other. If they
% do, then boom! We're a little generous, which means that ships
% and asteroids may overlap a little without a boom.
function CheckForCollision : boolean
var radiiSquared : real := (ASTEROID_RADIUS + SHIP_RADIUS - 4) ** 2
for i : 1 .. numAsteroids
var dxSquared : real := (asteroidX (i) - shipX) ** 2
var dySquared : real := (asteroidY (i) - shipY) ** 2
if dxSquared + dySquared < radiiSquared then
result true
end if
end for
result false
end CheckForCollision
% Start the program
setscreen ("graphics:max,max,noecho,offscreenonly")
CentreText ("TURING ASTEROIDS", maxrow div 2 - 7)
CentreText ("Use the left, right and up arrow keys to", maxrow div 2 - 5)
CentreText ("avoid the asteroids for as long as possible", maxrow div 2 - 4)
CentreText ("Number in upper-left corner is the frame rate (max: 30)",
maxrow div 2 - 2)
CentreText ("Loading the pictures", maxrow div 2)
% Play the background music while loading the picture
fork PlayBackgroundMusic ("background.mid")
% Load the pictures
asteroid (0) := Pic.FileNew ("asteroid.bmp")
Pic.SetTransparentColour (asteroid (0), black)
ship (0) := Pic.FileNew ("ship.bmp")
Pic.SetTransparentColour (ship (0), black)
shipf (0) := Pic.FileNew ("shipf.bmp")
Pic.SetTransparentColour (shipf (0), black)
space := Pic.FileNew ("space.jpg")
CentreText ("Generating rotated versions of the pictures", maxrow div 2 + 2)
for i : 1 .. 35
ship (i) := Pic.Rotate (ship (0), i * 10, SHIP_RADIUS, SHIP_RADIUS)
shipf (i) := Pic.Rotate (shipf (0), i * 10, SHIPF_RADIUS, SHIPF_RADIUS)
end for
for i : 1 .. 119
asteroid (i) := Pic.Rotate (asteroid (0), i * 3, ASTEROID_RADIUS,
ASTEROID_RADIUS)
end for
% Calculate the background's width and height
backgroundPictureWidth := Pic.Width (space)
backgroundPictureHeight := Pic.Height (space)
% Figure out where the in the background we are
backgroundPictureX := (backgroundPictureWidth - (maxx + 1)) div 2
backgroundPictureY := (backgroundPictureHeight - (maxy + 1)) div 2
% Set the ship's initial angle, location and velocity
shipAngle := 0
shipX := backgroundPictureWidth / 2
shipY := backgroundPictureHeight / 2
shipDX := 0
shipDY := 0
% Set the text colour for frame count
colour (brightred)
colourback (black)
% Get the initial time
startTime := Time.Elapsed
% The main loop
loop
Input.KeyDown (keys)
if keys (UP_ARROW) then
% Up arrow is pressed: The main engine is on.
% Turn on the rumble sound if it is not already on.
if not engineRumble then
engineRumble := true
fork PlayContinuousRumble
end if
% Change the velocity by one in the appropriate direction
shipDX -= sind (shipAngle * 10)
shipDY += cosd (shipAngle * 10)
elsif engineRumble then
% Up arrow is not pressed: The main engine is off.
% Playing another WAV file stops the first one. (We didn't want
% to use Music.PlayFileStop because that would stop the background
% MIDI music as well.) The PlaySound procedure also sets
% engineRumble to false.
fork PlaySound (SILENCE_SOUND)
end if
% Turn the ship left or right if the left or right arrows
% are pressed.
if keys (RIGHT_ARROW) then
shipAngle := (shipAngle - 1) mod NUM_SHIP_IMAGES
end if
if keys (LEFT_ARROW) then
shipAngle := (shipAngle + 1) mod NUM_SHIP_IMAGES
end if
% Establish a speed limit
shipDX := min (max (shipDX, -20), 20)
shipDY := min (max (shipDY, -20), 20)
% Change the ship's position
shipX += shipDX
shipY += shipDY
% Bounce the ship when you hit the edges
if shipX < MARGINS then
shipX := MARGINS + (MARGINS - shipX)
shipDX := -shipDX
fork PlaySound (BOUNCE_SOUND)
end if
if shipX > backgroundPictureWidth - MARGINS then
shipX := backgroundPictureWidth - MARGINS - (shipX - (backgroundPictureWidth - MARGINS))
shipDX := -shipDX
fork PlaySound (BOUNCE_SOUND)
end if
if shipY < MARGINS then
shipY := MARGINS + (MARGINS - shipY)
shipDY := -shipDY
fork PlaySound (BOUNCE_SOUND)
end if
if shipY > backgroundPictureHeight - MARGINS then
shipY := backgroundPictureHeight - MARGINS - (shipY - (backgroundPictureHeight - MARGINS))
shipDY := -shipDY
fork PlaySound (BOUNCE_SOUND)
end if
% Move the background when the ship get close to the edges
if shipX - backgroundPictureX < SHIP_WINDOW_MARGINS then
backgroundPictureX := round (shipX - SHIP_WINDOW_MARGINS)
end if
if backgroundPictureX + maxx + 1 - shipX < SHIP_WINDOW_MARGINS then
backgroundPictureX := round (shipX + SHIP_WINDOW_MARGINS - (maxx + 1))
end if
if shipY - backgroundPictureY < SHIP_WINDOW_MARGINS then
backgroundPictureY := round (shipY - SHIP_WINDOW_MARGINS)
end if
if backgroundPictureY + maxy + 1 - shipY < SHIP_WINDOW_MARGINS then
backgroundPictureY := round (shipY + SHIP_WINDOW_MARGINS - (maxy + 1))
end if
backgroundPictureX := max (backgroundPictureX, 0)
backgroundPictureX := min (backgroundPictureX,
backgroundPictureWidth - (maxx + 1))
backgroundPictureY := max (backgroundPictureY, 0)
backgroundPictureY := min (backgroundPictureY,
backgroundPictureHeight - (maxy + 1))
% Create a new asteroid, if it is time (one is created
% every 3 seconds)
var currentTime := Time.Elapsed
if (currentTime > lastAsteroidTime + TIME_BETWEEN_ASTEROIDS) and
(numAsteroids < MAX_ASTEROIDS) then
% Enough time has passed and theirs room in the array,
% create another asteroid.
% We create the astreoid off the screen so it won't
% appear to teleport into the game.
numAsteroids += 1
asteroidX (numAsteroids) := Rand.Int (-ASTEROID_RADIUS,
backgroundPictureWidth + ASTEROID_RADIUS)
asteroidY (numAsteroids) := Rand.Int (-ASTEROID_RADIUS,
backgroundPictureHeight + ASTEROID_RADIUS)
asteroidDX (numAsteroids) := Rand.Int (-30, 30) / 10
asteroidDY (numAsteroids) := Rand.Int (-30, 30) / 10
asteroidAngle (numAsteroids) := 0
% Asteroids don't look good when rotating very slowly
% because you see the jump from one image to the next.
loop
asteroidDAngle (numAsteroids) := Rand.Int (-30, 30) / 10
exit when abs (asteroidDAngle (numAsteroids)) >= 1
end loop
case Rand.Int (1, 4) of
label 1 :
% Create asteroid above the playing field
asteroidY (numAsteroids) := -ASTEROID_RADIUS
label 2 :
% Create asteroid below the playing field
asteroidY (numAsteroids) := backgroundPictureHeight + ASTEROID_RADIUS
label 3 :
% Create asteroid to the left of the playing field
asteroidX (numAsteroids) := -ASTEROID_RADIUS
label 4 :
% Create asteroid to the right of the playing field
asteroidX (numAsteroids) := backgroundPictureWidth + ASTEROID_RADIUS
end case
% To make the game interesting, 1 in 4 asteroids are (1) faster and
% (2) start aimed more or less at the player!
if numAsteroids mod 4 = 1 then
% Set asteroidDX/DY to aim it at the space ship
var d : real := sqrt ((shipX - asteroidX (numAsteroids)) ** 2 +
(shipY - asteroidY (numAsteroids)) ** 2)
var s : int := Rand.Int (7, 11)
asteroidDX (numAsteroids) := s * (shipX - asteroidX (numAsteroids)) / d
asteroidDY (numAsteroids) := s * (shipY - asteroidY (numAsteroids)) / d
end if
lastAsteroidTime := currentTime
end if
% Move all the existing asteroids
for i : 1 .. numAsteroids
% Change the asteroid's position
asteroidX (i) += asteroidDX (i)
asteroidY (i) += asteroidDY (i)
asteroidAngle (i) += asteroidDAngle (i)
if asteroidX (i) < -ASTEROID_RADIUS then
asteroidX (i) += backgroundPictureWidth + 2 * ASTEROID_RADIUS
end if
if asteroidX (i) > backgroundPictureWidth + ASTEROID_RADIUS then
asteroidX (i) -= backgroundPictureWidth + 2 * ASTEROID_RADIUS
end if
if asteroidY (i) < -ASTEROID_RADIUS then
asteroidY (i) += backgroundPictureWidth + 2 * ASTEROID_RADIUS
end if
if asteroidY (i) > backgroundPictureHeight + ASTEROID_RADIUS then
asteroidY (i) -= backgroundPictureHeight + 2 * ASTEROID_RADIUS
end if
if asteroidAngle (i) < 0 then
asteroidAngle (i) += 360
end if
if asteroidAngle (i) >= 360 then
asteroidAngle (i) -= 360
end if
end for
if CheckForCollision then
finished := true
end if
DrawScreen
% If the frame rate is too high, then pause. Otherwise the game
% runs too quickly!
if Time.Elapsed - lastFrameTime < MIN_MILLISECS_BETWEEN_FRAMES then
delay (MIN_MILLISECS_BETWEEN_FRAMES - (Time.Elapsed - lastFrameTime))
end if
lastFrameTime := Time.Elapsed
exit when finished
end loop
|