Source/SprocketInvaders.c

/*
    File:       SprocketInvaders.c
 
    Contains:   xxx put contents here xxx
 
    Version:    xxx put version here xxx
 
    Copyright:  © 1998-1999 by Apple Computer, Inc., all rights reserved.
 
    File Ownership:
 
        DRI:                xxx put dri here xxx
 
        Other Contact:      xxx put other contact here xxx
 
        Technology:         xxx put technology here xxx
 
    Writers:
 
        (cjd)   Chris De Salvo
        (cjd)   Chris DeSalvo
        (BWS)   Brent Schorsch
 
    Change History (most recent first):
 
      <SP20>      3/3/99    cjd     Change player shot/CD controls code so that shots go through CD
                                    controls.
      <SP19>     1/29/99    cjd     Added code for new CD controller sprites.
      <SP18>     1/21/99    cjd     Removing 68K build code
        <17>      7/2/98    BWS     fix 68k build (no SSp toggle)
        <16>      7/1/98    cjd     Made it so that you can shoot enemy shots, added optimization
                                    for collission of player shots and enemies, added new FPS
                                    display for SSp usage.
        <15>     6/12/98    BWS     InputSprocket 68k now used
*/
 
//¥ ------------------------------------------------------------------------------------------  ¥
//¥
//¥ Copyright © 1996 Apple Computer, Inc., All Rights Reserved
//¥
//¥
//¥     You may incorporate this sample code into your applications without
//¥     restriction, though the sample code has been provided "AS IS" and the
//¥     responsibility for its operation is 100% yours.  However, what you are
//¥     not permitted to do is to redistribute the source as "DSC Sample Code"
//¥     after having made changes. If you're going to re-distribute the source,
//¥     we require that you make it clear in the source that the code was
//¥     descended from Apple Sample Code, but that you've made changes.
//¥
//¥     Authors:
//¥         Chris De Salvo
//¥         Jamie Osborne
//¥         Michael Evans
//¥         Tim Carroll
//¥
//¥ ------------------------------------------------------------------------------------------  ¥
 
//¥ ------------------------------  Includes
 
#include <Fonts.h>
#include <stdio.h>
#include <string.h>
 
#include "ErrorHandler.h"
#include "EventHandler.h"
#include "GameObject.h"
#include "Graphics.h"
#include "MoviePlayback.h"
#include "ObjectActions.h"
#include "CD_Utils.h"
#include "SIResources.h"
#include "SoundHandler.h"
#include "SprocketInvaders.h"
#include "Sprite.h"
#include "NetSprocketSupport.h"
#include "CommonStuff.h"
 
//¥ ------------------------------  Private Definitions
 
#define RAND(x)                     ((Random() & 0x7FFF) % (x))
 
#define kPlayerVelocity             2L  //¥ Speed at which player moves
#define kPlayerShotVelocity         10L //¥ Speed at which player shots travel
#define kPointsVelocity             -2L //¥ Vertical speed of the "points" object (negative is up)
#define kEnemyVelocity              5L  //¥ Speed at which enemies move
#define kEnemyShotVelocity          4L  //¥ Speed at which enemy shots drop
 
#define kEnemyOffset                8   //¥ Space between columns of enemies
 
#define kEnemyParticleColor         200
#define kPlayerParticleColor        192
#define kPlasmaParticleColor        208
#define kMissileParticleColor       216
 
//¥ ------------------------------  Private Types
 
enum
{
    scPlayer,
    scPlayer2,
    scPlayerShot,
    scPoints,
    scEnemy,
    scEnemyShot,
    scPlayButton,
    scStopButton,
    scPrevButton,
    scSkipButton,
    scNumSprites
};
 
//¥ ------------------------------  Private Variables
 
static SpritePtr    gSpriteCache[scNumSprites]; //¥ Sprite artwork is cached here so that multiple items using
                                                //¥ the same artwork only need one copy.
 
static UInt32   gWave;                  //¥ Current level number
static UInt32   gEnemyGas;              //¥ Number of enemies that get updated per game loop
static SInt32   gEnemyNewDirection;     //¥ If enemies changed directions, this is the new direction
static UInt32   gFrameRateBaseTime;     //¥ Base time for this game to count frame rate
 
static float    gGreenAccum;
static float    gRedAccum;
 
//¥ ------------------------------  Private Functions
 
static void AddPlayers(void);
static void AddEnemies(void);
static void CollideShotsToEnemies(void);
static void CollideShotsToPlayers(void);
static void CollideShotsToShots(void);
static void AddCDControls(void);
static void AddPoints(short x, short y);
static void DisplayGameOver(CGrafPtr backBuff);
static void DropEnemies(void);
static void AdvanceEnemies(void);
static void DisplayFrameRate(void);
static void EndGame(CGrafPtr backBuff);
static void GetGreenInput(void);
static void GetRedInput(void);
static Boolean WasButtonHit (ISpElementReference inElement);
static void BuildEnemyRect(void);
static void DoCDAction(UInt32 inAction);
static void CollideShotsToCDControls(void);
 
//¥ ------------------------------  Public Variables
 
Boolean gGameInProgress = false;
Boolean gTwoPlayers = false;
 
GameObjectPtr   gEnemyList = nil;               //¥ List of enemies
GameObjectPtr   gEnemyShotList = nil;           //¥ List of enemy shots
GameObjectPtr   gPlayerList = nil;              //¥ List of players
GameObjectPtr   gGreenPlayerShotList = nil;     //¥ List of green player shots
GameObjectPtr   gRedPlayerShotList = nil;       //¥ List of red player shots
GameObjectPtr   gMiscObjectList = nil;          //¥ List of misc items (score sprites, etc)
 
Rect            gEnemyRect;                     //¥ Rect enclosing all enemies
 
UInt32          gEnemyTask;
SInt32          gEnemiesChangeDirection;
SInt32          gEnemyVelocity;
UInt32          gNumEnemies;
 
UInt32          gEnemyLevel;
UInt32          gNumEnemiesProcessed;
 
UInt32          gNumGreenPlayerLives = 0;
UInt32          gNumRedPlayerLives = 0;
 
Boolean         gNetPlay;
 
//¥ --------------------    InitNewRound
 
static void
InitNewRound(UInt32 wave)
{
SInt16  numCDTracks;
 
    //¥ Flush out our object lists
    GameObjectDisposeList(&gEnemyList);
    GameObjectDisposeList(&gEnemyShotList);
    GameObjectDisposeList(&gPlayerList);
    GameObjectDisposeList(&gGreenPlayerShotList);
    GameObjectDisposeList(&gRedPlayerShotList);
    GameObjectDisposeList(&gMiscObjectList);
 
    //¥ Add our initial objects
    AddPlayers();
    AddEnemies();
 
    if (true == gCDAudio)
        AddCDControls();
    
    gEnemyTask = kEnemyMovingRight;
    gEnemiesChangeDirection = 0;
    gEnemyVelocity = kEnemyVelocity;
    gEnemyLevel = 1;
    gWave = wave;
 
    if (true == gCDAudio)
    {
        //¥ Pick a CD audio track and start playback if CD audio was chosen
        numCDTracks = CD_GetNumTracks();
 
        if (numCDTracks > 0)
        {
        SInt16  trackNum;
        
            if (wave > numCDTracks)
            {
                trackNum = (wave % numCDTracks) + 1;
            }
            else
            {
                trackNum = wave;
            }
            
            //¥ Unnecessary error checking
            if (trackNum < 1)
                trackNum = 1;
                
            if (trackNum > numCDTracks)
                trackNum = numCDTracks;
                
            CD_PlayAudioTrack(trackNum, playmodeStereo, true);
        }
    }
 
    SetRect(&gEnemyRect, 0, 0, 0, 0);
}
 
//¥ --------------------    NextRound
 
static void
NextRound(void)
{
    InitNewRound(gWave + 1);
}
 
 
//¥ --------------------    InitNewGame
 
void
InitNewGame(UInt32 wave)
{
UInt32  i;
    
    //¥ Start up the background movie if one was chosen
    PlaybackMovie();
    
    for (i = 0; i < numInputs; i++)
        ISpElement_Flush(gInputElements[i]);
    
    gGreenAccum = 0;
    gRedAccum = 0;
 
    gGameInProgress = true;
    gFrameRateBaseTime = 0L;
 
    //¥ Flush out the sprite cache and load in the new sprites
    for (i = 0; i < scNumSprites; i++)
        SpriteDispose(&(gSpriteCache[i]));
 
    gSpriteCache[scPlayer] = SpriteLoad(kSPRTPlayer);
    if (! gSpriteCache[scPlayer])
        FatalError("Could not load player tank.");
 
    gSpriteCache[scPlayer2] = SpriteLoad(kSPRTPlayer2);
    if (! gSpriteCache[scPlayer])
        FatalError("Could not load player 2 tank.");
 
    gSpriteCache[scPlayerShot] = SpriteLoad(kSPRTPlayerShot);
    if (! gSpriteCache[scPlayerShot])
        FatalError("Could not load player shot.");
        
    gSpriteCache[scPoints] = SpriteLoad(kSPRTPoints);
    if (! gSpriteCache[scPoints])
        FatalError("Could not load points.");
 
    gSpriteCache[scEnemy] = SpriteLoad(kSPRTEnemy);
    if (! gSpriteCache[scEnemy])
        FatalError("Could not load enemy.");
 
    gSpriteCache[scEnemyShot] = SpriteLoad(kSPRTEnemyShot);
    if (! gSpriteCache[scEnemyShot])
        FatalError("Could not load enemy shot.");
 
    gSpriteCache[scPlayButton] = SpriteLoad(kSPRTPlayButton);
    if (! gSpriteCache[scPlayButton])
        FatalError("Could not load play button.");
 
    gSpriteCache[scStopButton] = SpriteLoad(kSPRTStopButton);
    if (! gSpriteCache[scStopButton])
        FatalError("Could not load stop button.");
 
    gSpriteCache[scPrevButton] = SpriteLoad(kSPRTPrevButton);
    if (! gSpriteCache[scPrevButton])
        FatalError("Could not load prev button.");
 
    gSpriteCache[scSkipButton] = SpriteLoad(kSPRTSkipButton);
    if (! gSpriteCache[scSkipButton])
        FatalError("Could not load skip button.");
 
    gNumGreenPlayerLives = 3;
    
    if (gTwoPlayers)
        gNumRedPlayerLives = 3;
    else
        gNumRedPlayerLives = 0;
    
    //¥ Activate our graphics context and tell InputSprocket to start
    //¥ watching our controllers
    GraphicsActive();
    ISpResume();
    
    InitNewRound(wave);
}
 
#pragma mark -
 
//¥ --------------------    GameLoop
 
void
GameLoop(void)
{
CGrafPtr        backBuff;
ISpElementEvent event;
Boolean         wasEvent;
        
    if (WasButtonHit(gInputElements[soundToggle]))
        gSoundEffects = !gSoundEffects;
 
    if (gNetPlay)
    {
        if (gIAmHost)
        {
            GetGreenInput();
            SendInputState(gGameKeys.greenLeft, gGameKeys.greenRight, gGameKeys.greenFire);
            GetRedInput();
        }
        else
        {
            GetRedInput();
            SendInputState(gGameKeys.redLeft, gGameKeys.redRight, gGameKeys.redFire);
            GetGreenInput();
        }   
    }
    else
    {
        GetGreenInput();
        if (gTwoPlayers)
            GetRedInput();
    }
    
 
    //¥ Load in the current SoundSprocket throttle value
    ISpElement_GetSimpleState(gInputElements[soundSprocketThrottle], &gCPUCurrentLoad);
 
    if (0 == gCPULoadModifier)
        gCPUCurrentLoad = 0;
    else
        gCPUCurrentLoad /= gCPULoadModifier;
 
    //¥ Perform player/missile collission detection
    CollideShotsToCDControls();
    CollideShotsToEnemies();
    CollideShotsToShots();
    CollideShotsToPlayers();
 
    //¥ If any enemy hit a boundry on the last run, switch directions
    if (gEnemiesChangeDirection)
    {
        DropEnemies();
        gEnemiesChangeDirection = 0;
        gEnemyTask = kEnemyDropping;
    }
 
    //¥ gEnemyGas determines how many aliens are processed each game loop
    //¥ This gives the staggered updating on the screen.  It also speeds up the
    //¥ alien movement and shooting speed as the game progresses.
    gEnemyGas = gWave + 1;
 
    ServiceMoviePlayback();
 
    //¥ Get a reference to the back buffer so we can draw into it
    DSpContext_GetBackBuffer(gDisplayContext, kDSpBufferKind_Normal, &backBuff);
 
    //¥ Check the abort key in case the user wishes to quit out early
    ISpElement_GetNextEvent(gInputElements[abort], sizeof (event), &event, &wasEvent);
    if (gGotEndGameMessage || (wasEvent && (event.data == kISpButtonDown)))
    {
        if (gNetPlay && !gGotEndGameMessage)
            SendEndGame();
 
        EndGame(backBuff);
        return;
    }
 
    //¥ Have all game objects perform their actions
    AdvanceEnemies();
    GameObjectListAdvance(gPlayerList);
    GameObjectListAdvance(gEnemyShotList);
    GameObjectListAdvance(gGreenPlayerShotList);
    GameObjectListAdvance(gRedPlayerShotList);
    GameObjectListAdvance(gMiscObjectList);
 
    //¥ Draw all game objects
    GameObjectListDraw(gEnemyList, backBuff);
    GameObjectListDraw(gPlayerList, backBuff);
    GameObjectListDraw(gEnemyShotList, backBuff);
    GameObjectListDraw(gGreenPlayerShotList, backBuff);
    GameObjectListDraw(gRedPlayerShotList, backBuff);
    GameObjectListDraw(gMiscObjectList, backBuff);
 
    //¥ Build a master rect of all the enemies to help optimize hit testing
    //¥ the next time through the loop.
    BuildEnemyRect();
 
    //¥ Check for end of wave/game
    if ((gNumGreenPlayerLives < 1) && (gNumRedPlayerLives < 1))
    {
        EndGame(backBuff);
        return;
    }
    
    //¥ If we just killed the last enemy then start the next round
    if (gNumEnemies < 1)
    {
        NextRound();
    }
 
    //¥ Update the frame rate display information
    DisplayFrameRate();
 
    //¥ Redraw the entire screen.  This is done last so that if the game over screen was drawn it gets blit for free
    GraphicsUpdateScreen();
 
}
 
//¥ --------------------    GetAutoFireButton
 
static Boolean
GetAutoFireButton(ISpElementReference inElement)
{
OSStatus    error;
UInt32      input;
Boolean     wasEvent;
Boolean     fire = false;
    
    //¥ poll    
    error = ISpElement_GetSimpleState(inElement, &input);
    if (! error && (input == kISpButtonDown)) 
        fire = true;
 
    //¥ but don't miss fast clicks or macros
    do
    {
    ISpElementEvent event;
 
        error = ISpElement_GetNextEvent(inElement, sizeof(ISpElementEvent), &event, &wasEvent);
 
        if (! error && wasEvent && (event.data == kISpButtonDown))
        {
            fire = true; 
            break;
        }
    } while (wasEvent && !error);
    
    //¥ flush the queue
    ISpElement_Flush(inElement);
    
    return (fire);
}
 
//¥ --------------------    WasButtonHit
 
static Boolean
WasButtonHit (ISpElementReference inElement)
{
OSStatus    error;
Boolean     wasEvent;
Boolean     down = false;
    
    do
    {
    ISpElementEvent event;
 
        error = ISpElement_GetNextEvent(inElement, sizeof(ISpElementEvent), &event, &wasEvent);
 
        if (! error && wasEvent && (event.data == kISpButtonDown))
        {
            down = true; 
            break;
        }
    } while (wasEvent && !error);
    
    return (down);
}
 
//¥ --------------------    GetGreenInput
 
void
GetGreenInput(void)
{
UInt32  input;
    
    if (! gNetPlay || (gNetPlay && gIAmHost))   // in netplay, the host is the green player, and the joiner the red
    {
        //¥ Check the movement axis
        ISpElement_GetSimpleState(gInputElements[greenMovement], &input);
 
        gGameKeys.greenLeft = false;
        gGameKeys.greenRight = false;
        
        if (input < 0x2FFFFFFF)
        {
            gGameKeys.greenLeft = true;
        }
        else if (input > 0xBFFFFFFF)
        {
            gGameKeys.greenRight =  true;
        }
        else if (input < 0x5FFFFFFF)
        { 
            gGreenAccum += ((float) input - (float) 0x5FFFFFFF) / (float) 0x3FFFFFFF;
            if (gGreenAccum < -1)
            {
                gGameKeys.greenLeft = true;
                gGreenAccum += 1;
            }
        }
        else if (input > 0x9FFFFFFF)
        {
            gGreenAccum += ((float) input - (float) 0x9FFFFFFF) / (float) 0x3FFFFFFF;
            if (gGreenAccum > 1)
            {
                gGameKeys.greenRight = true;
                gGreenAccum -= 1;
            }
        }
        
        //¥ Check the fire button
        gGameKeys.greenFire = GetAutoFireButton(gInputElements[greenFire]);
    }
    else if (gNetPlay && !gIAmHost) //¥ wait for player 1's input
    {
        gReceivedInput = false;
        while (! gReceivedInput && !gGotEndGameMessage)
            HandleNetworking();
    }
}
 
//¥ --------------------    GetRedInput
 
void GetRedInput(void)
{
UInt32  input;
 
    if (! gNetPlay || (gNetPlay && !gIAmHost))  // in netplay, the host is the green player, and the joiner the red
    {
        //¥ Check the movement axis
        ISpElement_GetSimpleState(gInputElements[redMovement], &input);
 
        gGameKeys.redLeft = false;
        gGameKeys.redRight = false;
        
        if (input < 0x3FFFFFFF)
        {
            gGameKeys.redLeft = true;
        }
        else if (input > 0xAFFFFFFF)
        {
            gGameKeys.redRight =  true;
        }
        else if (input < 0x5FFFFFFF)
        { 
            gRedAccum += ((float) input - (float) 0x5FFFFFFF) / (float) 0x2FFFFFFF;
            if (gRedAccum < -1)
            {
                gGameKeys.redLeft = true;
                gRedAccum += 1;
            }
        }
        else if (input > 0x9FFFFFFF)
        {
            gRedAccum += ((float) input - (float) 0x9FFFFFFF) / (float) 0x2FFFFFFF;
            if (gRedAccum > 1)
            {   
                gGameKeys.redRight = true;
                gRedAccum -= 1;
            }
        }
 
        //¥ Check the fire button
        gGameKeys.redFire = GetAutoFireButton(gInputElements[redFire]);
    }
    else if (gNetPlay && gIAmHost)  //¥ wait for player 2's input
    {
        gReceivedInput = false;
        while (! gReceivedInput && !gGotEndGameMessage)
            HandleNetworking();
    }
}
 
//¥ --------------------    PlayerShoot
 
void
PlayerShoot(GameObjectPtr whichPlayer)
{
Rect            r;
GameObjectPtr   go;
GameObjectPtr   shotList;
 
 
    if (whichPlayer->kind == objectGreenPlayer)
    {
        if (gGreenPlayerShotList)
            return;
        else
            shotList = gGreenPlayerShotList;
    }
    else
    {
        if (gRedPlayerShotList)
            return;
        else
            shotList = gRedPlayerShotList;
    }
 
    SoundHandlerPlay(soundPlayerFire, whichPlayer->screenX, whichPlayer->screenY);
 
    go = GameObjectAllocate();
    if (! go)
        FatalError("Could not allocate player shot.");
        
    GameObjectAddToList(&shotList, go);
    
    SetRect(&r, 20, 0, 620, 0);
    GameObjectSetBounds(go, &r);
    GameObjectSetSprite(go, gSpriteCache[scPlayerShot]);
    
    go->screenX = whichPlayer->screenX;
    go->screenY = whichPlayer->screenY - SpriteHeight(whichPlayer->objectData.sprite, whichPlayer->frame) - 1;
    go->velocityH = 0;
    go->velocityV = kPlayerShotVelocity;
    go->action = PlayerShotAction;
    
    if (whichPlayer->kind == objectRedPlayer)
    {
        go->kind = objectRedPlayerShot;
        if (! gRedPlayerShotList)
            gRedPlayerShotList = shotList;
    }
    else
    {
        go->kind = objectGreenPlayerShot;
        if (! gGreenPlayerShotList)
            gGreenPlayerShotList = shotList;
    }
}
 
//¥ --------------------    AdvanceEnemies
 
static void
AdvanceEnemies(void)
{
GameObjectPtr   current, next;
UInt32          oldProcessed;
 
    //¥ This set of loops makes sure that gEnemyGas worth
    //¥ of aliens get processed this loop.  If gEnemyGas is set to
    //¥ 10, and there are only 5 aliens left then they all update twice
    //¥ for a total of ten updates.  This is how we take care us speeeding
    //¥ the aliens up as fewer and fewer of them are present.
    
    do
    {
        gNumEnemiesProcessed = 0;
        current = gEnemyList;
        
        do
        {
            oldProcessed = gNumEnemiesProcessed;
            next = current->next;
            current->action(current);
            
            //¥ If we moved an alien, use up some alien gas
            if (oldProcessed != gNumEnemiesProcessed)
                gEnemyGas--;
                
            current = next;
        } while (next && gEnemyGas);
        
        //¥ If no enemies were processed then they're all at the same level, so move on
        if (gNumEnemiesProcessed == 0)
        {
            gEnemyLevel++;
            
            if (gEnemyTask == kEnemyDropping)
                gEnemyTask = gEnemyNewDirection;
        }
 
    } while (gEnemyGas && (gNumEnemies > 0));
}
 
//¥ --------------------    DropEnemies
 
static void
DropEnemies(void)
{
GameObjectPtr   current, next;
 
    //¥ One of the enemies hit the edge of their boundary so we need
    //¥ to draw them all one row.
    gEnemyNewDirection = gEnemiesChangeDirection;
 
    do
    {
        current = gEnemyList;
        gNumEnemiesProcessed = 0;
        
        do
        {
            next = current->next;
            current->action(current);
            current = next;
        } while (next);
    } while (gNumEnemiesProcessed);
 
    gEnemyLevel++;
}
 
//¥ --------------------    EnemyShoot
 
void
EnemyShoot(GameObjectPtr whichEnemy)
{
Rect            r;
GameObjectPtr   go;
 
    //¥ Create a game object for the shot
    go = GameObjectAllocate();
    if (! go)
        FatalError("Could not allocate enemy shot.");
        
    //¥ Play the alien shooting sound effect
    SoundHandlerPlay(soundEnemyFire, whichEnemy->screenX, whichEnemy->screenY);
 
    //¥ Add the shot to the enemy shot list
    GameObjectAddToList(&gEnemyShotList, go);
    
    //¥ Set the attributes for this object
    SetRect(&r, 20, 0, 620, 412);
    GameObjectSetBounds(go, &r);
    GameObjectSetSprite(go, gSpriteCache[scEnemyShot]);
    
    go->screenX = whichEnemy->screenX;
    go->screenY = whichEnemy->screenY + 1;
    go->velocityH = 0;
    go->velocityV = kEnemyShotVelocity;
    go->action = EnemyShotAction;
}
 
//¥ --------------------    CollideShotsToCDControls
 
static void
CollideShotsToCDControls(void)
{
GameObjectPtr   shot;
GameObjectPtr   target;
 
    if (false == gCDAudio)
        return;
 
    if (nil == gMiscObjectList)
        return;
        
    //¥ Iterate over the green player shots
    if (gGreenPlayerShotList)
    {
        for (shot = gGreenPlayerShotList; shot; shot = shot->next)
        {
            //¥ Iterate over the controls
            for (target = gMiscObjectList; target; target = target->next)
            {
                if (target->refCon < scPlayButton || target->refCon > scSkipButton)
                    continue;
            
                if (IntersectRects(&shot->screenRect, &target->screenRect))
                    DoCDAction(target->refCon);
            }
        }
    }
    
    //¥ Iterate over the red player shots
    if (gRedPlayerShotList)
    {
        for (shot = gRedPlayerShotList; shot; shot = shot->next)
        {
            //¥ Iterate over the controls
            for (target = gMiscObjectList; target; target = target->next)
            {
                if (target->refCon < scPlayButton || target->refCon > scSkipButton)
                    continue;
            
                if (IntersectRects(&shot->screenRect, &target->screenRect))
                    DoCDAction(target->refCon);
            }
        }
    }
}
 
//¥ --------------------    CollideShotsToEnemies
 
static void
CollideShotsToEnemies(void)
{
GameObjectPtr   shot;
GameObjectPtr   target;
Rect            dummyRect;
 
    if (! gEnemyList)
        return;
        
    //¥ Iterate over the green player shots
    if (gGreenPlayerShotList)
    {
        for (shot = gGreenPlayerShotList; shot; shot = shot->next)
        {
            //¥ Quick easy check to see if the shot is even within the block of enemies
            if (false == SectRect(&shot->screenRect, &gEnemyRect, &dummyRect))
                continue;
        
            //¥ Iterate over the enemies
            for (target = gEnemyList; target; target = target->next)
            {
                if (IntersectRects(&shot->screenRect, &target->screenRect))
                {
                    SoundHandlerPlay(soundEnemyHit, target->screenX, target->screenY);
                    
                    shot->action = PlayerShotDestroy;
                    target->action = EnemyDestroy;
                    AddPoints(target->screenX, target->screenY);
                }
            }
        }
    }
    
    //¥ Iterate over the red player shots
    if (gRedPlayerShotList)
    {
        for (shot = gRedPlayerShotList; shot; shot = shot->next)
        {
            //¥ Quick easy check to see if the shot is even within the block of enemies
            if (false == SectRect(&shot->screenRect, &gEnemyRect, &dummyRect))
                continue;
        
            //¥ Iterate over the enemies
            for (target = gEnemyList; target; target = target->next)
            {
                if (IntersectRects(&shot->screenRect, &target->screenRect))
                {
                    SoundHandlerPlay(soundEnemyHit, target->screenX, target->screenY);
                    
                    shot->action = PlayerShotDestroy;
                    target->action = EnemyDestroy;
                    AddPoints(target->screenX, target->screenY);
                }
            }
        }
    }
}
 
//¥ --------------------    CollideShotsToShots
 
static void
CollideShotsToShots(void)
{
GameObjectPtr   shot;
GameObjectPtr   target;
 
    if (nil == gEnemyShotList)
        return;
        
    //¥ Iterate over the green player shots
    if (gGreenPlayerShotList)
    {
        for (shot = gGreenPlayerShotList; shot; shot = shot->next)
        {
            //¥ Iterate over the enemies
            for (target = gEnemyShotList; target; target = target->next)
            {
                if (IntersectRects(&shot->screenRect, &target->screenRect))
                {
                    shot->action = PlayerShotDestroy;
                    target->action = EnemyShotDestroy;
                }
            }
        }
    }
    
    //¥ Iterate over the red player shots
    if (gRedPlayerShotList)
    {
        for (shot = gRedPlayerShotList; shot; shot = shot->next)
        {
            //¥ Iterate over the enemies
            for (target = gEnemyShotList; target; target = target->next)
            {
                if (IntersectRects(&shot->screenRect, &target->screenRect))
                {
                    SoundHandlerPlay(soundEnemyHit, target->screenX, target->screenY);
                    
                    shot->action = PlayerShotDestroy;
                    target->action = EnemyShotDestroy;
                }
            }
        }
    }
}
//¥ --------------------    CollideShotsToPlayers
 
static void
CollideShotsToPlayers(void)
{
GameObjectPtr   shot;
GameObjectPtr   target;
GameObjectPtr   next;
 
    if (! gEnemyShotList || ! gPlayerList)
        return;
 
    target = gPlayerList;
    next = target->next;
 
    while (target != nil)
    {
        if (target->refCon > 0)
            return;
 
        //¥ Iterate over the enemy shots
        for (shot = gEnemyShotList; shot; shot = shot->next)
        {
            if (IntersectRects(&shot->screenRect, &target->screenRect))
            {
                SoundHandlerPlay(soundPlayerHit,target->screenX, target->screenY);
 
                shot->action = EnemyShotDestroy;
                target->action = PlayerDestroy;
                
                if (target->kind == objectGreenPlayer)
                {
                    gNumGreenPlayerLives--;
                    if (gNumGreenPlayerLives < 1)
                        GameObjectRemoveFromList(&gPlayerList, target);
                }
                else
                {
                    gNumRedPlayerLives--;
                    if (gNumRedPlayerLives < 1)
                        GameObjectRemoveFromList(&gPlayerList, target);
                }
            }
        }
        
        target = next;
        next = target->next;
    }
}
 
//¥ --------------------    BuildEnemyRect
 
static void
BuildEnemyRect(void)
{
Rect            workingRect = { 0, 0, 0, 0 };
GameObjectPtr   currentEnemy;
 
    if (nil != gEnemyList)
        currentEnemy = gEnemyList;
    else
        return;
        
    workingRect = currentEnemy->screenRect;
    
    do
    {
        UnionRect(&workingRect, &currentEnemy->screenRect, &workingRect);
        currentEnemy = currentEnemy->next;
    } while (nil != currentEnemy);
    
    gEnemyRect = workingRect;
}
 
#pragma mark -
 
//¥ --------------------    AddPlayers
 
static void
AddPlayers(void)
{
Rect            r;
GameObjectPtr   go;
 
    go = GameObjectAllocate();
    if (! go)
        FatalError("Could not allocate player object.");
        
    GameObjectAddToList(&gPlayerList, go);
    
    SetRect(&r, 20, 0, 620, 0);
    GameObjectSetBounds(go, &r);
    GameObjectSetSprite(go, gSpriteCache[scPlayer]);
    
    go->kind = objectGreenPlayer;
    go->screenX = 20;
    go->screenY = 412;
    go->velocityH = kPlayerVelocity;
    go->velocityV = 0;
    go->action = GreenPlayerAction;
 
    //¥ Sloppy way to do this, but hey, I'm in a hurry
    if (gTwoPlayers)
    {
        go = GameObjectAllocate();
        if (! go)
            FatalError("Could not allocate player 2 object.");
            
        GameObjectAddToList(&gPlayerList, go);
        
        SetRect(&r, 20, 0, 620, 0);
        GameObjectSetBounds(go, &r);
        GameObjectSetSprite(go, gSpriteCache[scPlayer2]);
        
        go->kind = objectRedPlayer;
        go->screenX = 640 - 20;
        go->screenY = 412;
        go->velocityH = kPlayerVelocity;
        go->velocityV = 0;
        go->action = RedPlayerAction;
    }
}
 
//¥ --------------------    AddEnemies
 
static void
AddEnemies(void)
{
Rect            r;
GameObjectPtr   go;
UInt32          i, j;
UInt32          w, h;
UInt32          x, x2, y;
 
    gNumEnemies = 0;
 
    //¥ Find dimensions of enemy sprite so we can center the rows to begin with
    w = SpriteWidth(gSpriteCache[scEnemy], 0);
    w += kEnemyOffset;
    h = SpriteHeight(gSpriteCache[scEnemy], 0);
    h += kEnemyOffset;
    
    x2 = w * kNumEnemyColumns;
    x2 = 320 - (x2 / 2);
 
    y = 100;
 
    SetRect(&r, 20, 20, 620, 400);
 
    for (i = 0; i < kNumEnemyRows; i++)
    {
        x = x2;
        
        for (j = 0; j < kNumEnemyColumns; j++)
        {
            go = GameObjectAllocate();
            if (! go)
                FatalError("Could not allocate enemy object.");
            
            GameObjectAddToList(&gEnemyList, go);
            
            GameObjectSetBounds(go, &r);
            GameObjectSetSprite(go, gSpriteCache[scEnemy]);
            
            go->kind = objectEnemy;
            go->screenX = x;
            go->screenY = y;
            go->velocityH = kEnemyVelocity;
            go->velocityV = 0;
            go->action = EnemyAction;
            
            x += w;
            gNumEnemies++;
        }
        
        y += h;
    }
}
 
//¥ --------------------    AddCDControls
 
static void
AddCDControls(void)
{
Rect            r;
GameObjectPtr   go;
UInt32          i;
 
    for (i = scPlayButton; i <= scSkipButton; i++)
    {
        go = GameObjectAllocate();
        if (! go)
            FatalError("Could not allocate CD control.");
        
        GameObjectAddToList(&gMiscObjectList, go);
    
        SetRect(&r, 0, 0, 640, 400);
        GameObjectSetBounds(go, &r);
        GameObjectSetSprite(go, gSpriteCache[i]);
    
        go->screenX = RAND(640);
        go->screenY = RAND(240);
        go->velocityH = RAND(2) + 1;
        go->velocityV = RAND(3) + 1;
        go->refCon = i;
        go->action = ObjectBounce;
    }
}
 
//¥ --------------------    AddPoints
 
static void
AddPoints(short x, short y)
{
Rect            r;
GameObjectPtr   go;
 
    go = GameObjectAllocate();
    if (! go)
        FatalError("Could not allocate player shot.");
        
    GameObjectAddToList(&gMiscObjectList, go);
    
    SetRect(&r, 0, 0, 640, 440);
    GameObjectSetBounds(go, &r);
    GameObjectSetSprite(go, gSpriteCache[scPoints]);
    
    go->screenX = x;
    go->screenY = y;
    go->velocityH = 0;
    go->velocityV = kPointsVelocity;
    go->action = PointsAction;
}
 
//¥ --------------------    AddParticles
 
void
AddParticles(short x, short y, ObjectKind kind)
{
Rect            r;
GameObjectPtr   go;
ParticlesPtr    particles;
UInt8           color;
 
    go = GameObjectAllocate();
    if (! go)
        FatalError("Could not allocate player shot.");
        
    GameObjectAddToList(&gMiscObjectList, go);
    
    SetRect(&r, 0, 0, 640, 400);
    GameObjectSetBounds(go, &r);
    go->frame = 0;
 
    switch (kind)
    {
        case objectEnemyParticles:
            color = kEnemyParticleColor;
            break;
            
        case objectPlayerParticles:
            color = kPlayerParticleColor;
            break;
            
        case objectPlasmaParticles:
            color = kPlasmaParticleColor;
            break;
            
        case objectMissileParticles:
            color = kMissileParticleColor;
            break;
        
        default:
            color = 255;
    }
    
    particles = ParticlesAllocate(x, y, color);
    GameObjectSetParticles(go, particles);
    go->kind = kind;
 
    go->action = ParticleAction;
}
 
#pragma mark -
 
//¥ --------------------    DisplayFrameRate
 
static void
DisplayFrameRate(void)
{
static UInt32   lastTime = 0;
static UInt32   frames = 0;
 
UInt32          elapsedTime;
Str255          str;
GrafPtr         oldPort;
CGrafPtr        underlay;
GDHandle        device, oldDevice;
Rect            r;
UInt32          stringWidth;
 
    //¥ Initialize the stats at the beginning of each game
    //¥ gFrameRateBaseTime is reset in InitNewGame()
    if (gFrameRateBaseTime == 0)
    {
        gFrameRateBaseTime = TickCount();
        lastTime = 0L;
        frames = 0L;
    }
 
    //¥ Bump the frame counter
    frames++;
    
    //¥ Find the total elapsed time and convert it to seconds
    elapsedTime = TickCount() - gFrameRateBaseTime;
 
    if ( ( elapsedTime - lastTime ) > 120 )
    {
        lastTime = elapsedTime;
        elapsedTime /= 60;
 
        sprintf((char *) str + 1, "FPS:   %0.4ld,   SSp Load:   %ld/%ld", frames / elapsedTime, (gCPULoadMax - gCPUCurrentLoad), gCPULoadMax);
 
        str[0] = strlen ((char *) str + 1);
        stringWidth = StringWidth(str);
 
        GraphicsGetUnderlayGrafPort(&underlay, &device);
 
        GetPort(&oldPort);
        oldDevice = GetGDevice();
        
        SetPort((GrafPtr) underlay);
        SetGDevice(device);
        
        RGBForeColor(&rgbBlack);
        SetRect(&r, 5, 420, 5 + stringWidth, 435);
        PaintRect(&r);
 
        MoveTo(5, 435);
        TextFont(kFontIDGeneva);
        TextFace(0);
        TextSize(9);
        
        RGBForeColor(&rgbYellow);
        DrawString(str);
        RGBForeColor(&rgbBlack);
        
        SetPort(oldPort);
        SetGDevice(oldDevice);
        
        GraphicsSetUnderlayRectDirty(&r);
    }
}
 
//¥ --------------------    DisplayGameOver
 
static void
DisplayGameOver(CGrafPtr backBuff)
{
GrafPtr     oldPort;
GDHandle    oldDevice;
Str255      str = "\pGame Over!";
UInt16      width;
UInt16      offset;
 
    GetPort(&oldPort);
    oldDevice = GetGDevice();
    
    SetGDevice(gGameGDH);
    SetPort((GrafPtr) backBuff);
    
    TextFont(kFontIDGeneva);
    TextFace(bold);
    TextSize(48);
    
    //¥ Find the width of our string
    width = StringWidth(str);
 
    //¥ Find an offset that will center the string in the buffer
    offset = backBuff->portRect.right - backBuff->portRect.left;
    offset /= 2;
    offset -= width / 2;
    
    MoveTo(offset, 200);
    RGBForeColor(&rgbYellow);
    DrawString(str);
 
    MoveTo(offset - 2, 200);
    RGBForeColor(&rgbBlue);
    DrawString(str);
 
    MoveTo(offset - 4, 200);
    RGBForeColor(&rgbRed);
    DrawString(str);
 
    RGBForeColor(&rgbBlack);
    
    SetGDevice(oldDevice);;
    SetPort(oldPort);
 
    GraphicsSetRectDirty(&backBuff->portRect);
}
 
//¥ --------------------    EndGame
 
static void
EndGame(CGrafPtr backBuff)
{
    //¥ Tell InputSprocket to stop watching our devices so that we can use them
    //¥ for menu command or whatever.
    ISpSuspend();
 
    ShutdownMoviePlayback();
 
    //¥ Display the "Game Over" banner and pause the graphics system
    DisplayGameOver(backBuff);
    GraphicsUpdateScreen();
    GraphicsPaused();
 
    ShowCursor();
    gGameInProgress = false;
 
    //¥ Tell network players to shut down
    if (gNetGame)
    {
        Boolean OKHit;
        
        if (gIAmHost)
            gNetState = kHosting;
        else
            gNetState = kJoining;
 
        OKHit = WaitForAllPlayers();
 
        if (OKHit == false)
            ShutdownNetworking();
        else
        {
            gNetState = kStarting;
            SendStartGame();
            InitNewGame(1);
        }
    }   
}
 
//¥ --------------------    DoCDAction
 
static void
DoCDAction(UInt32 inAction)
{
SInt16      currentTrack;
UInt16      numTracks;
OSStatus    theError;
UInt16      trackToPlay = 0;
 
    if (false == gCDAudio)
        return;
 
    numTracks = CD_GetNumTracks();
    theError = CD_GetCurrentAudioTrack(&currentTrack);
    
    if (noErr != theError)
        return;
 
    switch (inAction)
    {
        case scPlayButton:
            trackToPlay = 1;
            break;
            
        case scStopButton:
            CD_StopAudioTrack(true);
            break;
            
        case scPrevButton:
            if (currentTrack == 1)
                trackToPlay = numTracks;
            else
                trackToPlay = currentTrack - 1;
            break;
        case scSkipButton:
            if (currentTrack == numTracks)
                trackToPlay = 1;
            else
                trackToPlay = currentTrack + 1;
                break;
    }
 
    if (0 != trackToPlay)
        CD_PlayAudioTrack(trackToPlay, playmodeStereo, true);
}