passage/gamma256/gameSource/game.cpp
2025-10-03 02:19:59 -04:00

1300 lines
34 KiB
C++

/*
* Modification History
*
* 2007-September-25 Jason Rohrer
* Created.
*/
#include <math.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
// for memcpy
#include <string.h>
// let SDL override our main function with SDLMain
#include <SDL/SDL_main.h>
// must do this before SDL include to prevent WinMain linker errors on win32
int mainFunction( int inArgCount, char **inArgs );
int main( int inArgCount, char **inArgs ) {
return mainFunction( inArgCount, inArgs );
}
#include <SDL/SDL.h>
#include "blowUp.h"
#include "World.h"
#include "map.h"
#include "score.h"
#include "common.h"
#include "musicPlayer.h"
#include "minorGems/system/Time.h"
#include "minorGems/system/Thread.h"
#include "minorGems/util/stringUtils.h"
#include "minorGems/util/SettingsManager.h"
// size of game image
int width = 100;
int height = 12;
// area above game image for score
int scoreHeight = getScoreHeight();
// size of game image plus scoreboard
int totalImageHeight = height + scoreHeight;
char paused = false;
char canPause = false;
// size of screen for fullscreen mode
int screenWidth = 640;
int screenHeight = 480;
//int screenWidth = 100;
//int screenHeight = 16;
// blow-up factor when projecting game onto screen
// Max defined by image size vs screen size.
// This is the cap for in-game user-directed blowup adjustment.
int maxBlowUpFactor;
// current blow-up setting
int blowUpFactor;
// step to take when user hits blowUp key
int blowUpStep = -1;
// flag to force update of entire screen
int blowUpChanged = true;
char fullScreen = true;
// lock down to 15 fps
int lockedFrameRate = 15;
// target length of game
int gameTime = 5 * 60;
//int gameTime = 30;
// life time that passes per frame
double timeDelta = - ( (double)width / ( gameTime * lockedFrameRate ) );
//double timeDelta = -0.0;
// the joystick to read from, or NULL if there's no available joystick
SDL_Joystick *joystick;
// catch an interrupt signal
void catch_int(int sig_num) {
printf( "Quiting...\n" );
SDL_Quit();
exit( 0 );
signal( SIGINT, catch_int );
}
char getKeyDown( int inKeyCode ) {
SDL_PumpEvents();
Uint8 *keys = SDL_GetKeyState( NULL );
return keys[ inKeyCode ] == SDL_PRESSED;
}
char getHatDown( Uint8 inHatPosition ) {
if( joystick == NULL ) {
return false;
}
SDL_JoystickUpdate();
Uint8 hatPosition = SDL_JoystickGetHat( joystick, 0 );
if( hatPosition & inHatPosition ) {
return true;
}
else {
return false;
}
}
int joyThreshold = 25000;
char getJoyPushed( Uint8 inHatPosition ) {
Sint16 x = SDL_JoystickGetAxis(joystick, 0);
Sint16 y = SDL_JoystickGetAxis(joystick, 1);
switch( inHatPosition ) {
case SDL_HAT_DOWN:
return y > joyThreshold;
break;
case SDL_HAT_UP:
return y < -joyThreshold;
break;
case SDL_HAT_LEFT:
return x < - joyThreshold;
break;
case SDL_HAT_RIGHT:
return x > joyThreshold;
break;
default:
return false;
}
}
// returns true if hit, returns false if user quits before hitting
char waitForKeyOrButton() {
SDL_Event event;
while( true ) {
while( SDL_WaitEvent( &event ) ) {
switch( event.type ) {
case SDL_JOYHATMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
case SDL_KEYDOWN:
case SDL_KEYUP:
return true;
break;
// watch for quit event
case SDL_QUIT:
return false;
break;
default:
break;
}
}
}
return false;
}
Uint32 *gameImage;
// flips back buffer onto screen (or updates rect)
void flipScreen( SDL_Surface *inScreen ) {
// unlock the screen if necessary
if( SDL_MUSTLOCK( inScreen ) ) {
SDL_UnlockSurface( inScreen );
}
if( ( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) {
// need to flip buffer
SDL_Flip( inScreen );
}
else if( !blowUpChanged ) {
// just update center
// small area in center that we actually draw in, black around it
int yOffset = ( inScreen->h - totalImageHeight * blowUpFactor ) / 2;
int xOffset = ( inScreen->w - width * blowUpFactor ) / 2;
SDL_UpdateRect( inScreen, xOffset, yOffset,
width * blowUpFactor,
totalImageHeight * blowUpFactor );
}
else {
// update the whole thing
SDL_UpdateRect( inScreen, 0, 0, inScreen->w, inScreen->h );
// reset flag
blowUpChanged = false;
}
}
void lockScreen( SDL_Surface * inScreen ) {
// check if we need to lock the screen
if( SDL_MUSTLOCK( inScreen ) ) {
if( SDL_LockSurface( inScreen ) < 0 ) {
printf( "Couldn't lock screen: %s\n", SDL_GetError() );
}
}
}
void flipGameImageOntoScreen( SDL_Surface *inScreen ) {
if( blowUpChanged
&&
( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) {
// blow up size has changed, and we are double-buffered
// flip onto screen an additional time.
// This will cause us to black-out the background in both buffers.
lockScreen( inScreen );
// when blow-up factor changes:
// clear screen to prepare for next draw,
// which will be bigger or smaller
SDL_FillRect( inScreen, NULL, 0x00000000 );
blowupOntoScreen( gameImage,
width, totalImageHeight, blowUpFactor, inScreen );
flipScreen( inScreen );
}
lockScreen( inScreen );
if( blowUpChanged ) {
SDL_FillRect( inScreen, NULL, 0x00000000 );
}
blowupOntoScreen( gameImage,
width, totalImageHeight, blowUpFactor, inScreen );
flipScreen( inScreen );
// we've handled any blow up change
blowUpChanged = false;
}
SDL_Surface *screen = NULL;
// play a complete game, from title screen to end, on screen
// returns false if player quits
char playGame();
void createScreen() {
if( screen != NULL ) {
// destroy old one first
SDL_FreeSurface( screen );
}
int displayW = width * blowUpFactor;
int displayH = totalImageHeight * blowUpFactor;
Uint32 flags = SDL_HWSURFACE | SDL_DOUBLEBUF;
if( fullScreen ) {
flags = flags | SDL_FULLSCREEN;
// use letterbox mode in full screen
displayW = screenWidth;
displayH = screenHeight;
}
screen = SDL_SetVideoMode( displayW, displayH,
32,
flags );
if ( screen == NULL ) {
printf( "Couldn't set %dx%dx32 video mode: %s\n", displayW,
displayH,
SDL_GetError() );
}
}
int mainFunction( int inArgCount, char **inArgs ) {
// let catch_int handle interrupt (^c)
signal( SIGINT, catch_int );
Uint32 initFlags =
SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE;
#ifdef __mac__
// SDL_Init is dreadfully slow if we try to init JOYSTICK on MacOSX
// not sure why---maybe it's searching all devices or something like
// that.
// Couldn't find anything online about this.
initFlags =
SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE;
#endif
if( SDL_Init( initFlags ) < 0 ) {
printf( "Couldn't initialize SDL: %s\n", SDL_GetError() );
return -1;
}
SDL_ShowCursor( SDL_DISABLE );
// read screen size from settings
char widthFound = false;
int readWidth = SettingsManager::getIntSetting( "screenWidth",
&widthFound );
char heightFound = false;
int readHeight = SettingsManager::getIntSetting( "screenHeight",
&heightFound );
if( widthFound && heightFound ) {
// override hard-coded defaults
screenWidth = readWidth;
screenHeight = readHeight;
}
printf( "Screen dimensions for fullscreen mode: %dx%d\n",
screenWidth, screenHeight );
// set here, since screenWidth may have changed from default
maxBlowUpFactor = screenWidth / width;
// default to max
blowUpFactor = maxBlowUpFactor;
char fullscreenFound = false;
int readFullscreen = SettingsManager::getIntSetting( "fullscreen",
&fullscreenFound );
if( fullscreenFound ) {
fullScreen = readFullscreen;
}
printf( "Starting game in " );
if( fullScreen ) {
printf( "fullscreen" );
}
else {
printf( "windowed" );
}
printf( " mode.\n" );
createScreen();
// try to open joystick
int numJoysticks = SDL_NumJoysticks();
printf( "Found %d joysticks\n", numJoysticks );
if( numJoysticks > 0 ) {
// open first one by default
joystick = SDL_JoystickOpen( 0 );
if( joystick == NULL ) {
printf( "Couldn't open joystick 0: %s\n", SDL_GetError() );
}
int numHats = SDL_JoystickNumHats( joystick );
if( numHats <= 0 ) {
printf( "No d-pad found on joystick\n" );
SDL_JoystickClose( joystick );
joystick = NULL;
}
}
else {
joystick = NULL;
}
#ifdef __mac__
// make sure working directory is the same as the directory
// that the app resides in
// this is especially important on the mac platform, which
// doesn't set a proper working directory for double-clicked
// app bundles
// arg 0 is the path to the app executable
char *appDirectoryPath = stringDuplicate( inArgs[ 0 ] );
printf( "Mac: app path %s\n", appDirectoryPath );
char *appNamePointer = strstr( appDirectoryPath,
"Passage.app" );
if( appNamePointer != NULL ) {
// terminate full app path to get parent directory
appNamePointer[0] = '\0';
printf( "Mac: changing working dir to %s\n", appDirectoryPath );
chdir( appDirectoryPath );
}
delete [] appDirectoryPath;
#endif
// load graphics only once
loadWorldGraphics();
setMusicLoudness( 0 );
startMusic( "music.tga" );
// keep playing until player quits
while( playGame() ) {
}
stopMusic();
destroyWorldGraphics();
if( joystick != NULL ) {
SDL_JoystickClose( joystick );
}
SDL_Quit();
return 0;
}
/*
// TEMP
#include "minorGems/graphics/converters/TGAImageConverter.h"
#include "minorGems/io/file/File.h"
#include "minorGems/io/file/FileInputStream.h"
// END TEMP
*/
char playGame() {
int currentSpriteIndex = 2;
double playerX, playerY;
double maxPlayerX = 0;
int score = 0;
// the gem that marks chests containing points
int specialGem = time( NULL ) % 4;
int exploreScore = 0;
int exploreSubPoints = 0;
// make sure explore score never goes down
int maxExploreScore = 0;
int chestScore = 0;
// track whether we ever met the spouse
// separate from World's haveMetSpouse()
char knowSpouse = false;
initWorld();
initScore();
// room at top for score
int totalGameImagePixels = width * totalImageHeight;
gameImage = new Uint32[ totalGameImagePixels ];
int i;
// fill with black
for( i=0; i<totalGameImagePixels; i++ ) {
gameImage[i] = 0;
}
// first, fill the whole thing with black
// SDL_FillRect( screen, NULL, 0x00000000 );
double dX = 0;
double dY = 0;
char done = false;
double maxWorldX = 0;
double minWorldX = 0;
// start player position
playerX = getTileWidth();
maxPlayerX = playerX;
dX = playerX;
playerY = 2 + height/2;
dY = 0;
setPlayerPosition( (int)playerX, (int)playerY );
setPlayerSpriteFrame( currentSpriteIndex );
double lastFrameTimeStamp = Time::getCurrentTime();
// use to slow player motion after spouse has died
char movingThisFrame = true;
// first, flip title onto screen
Image *titleImage = readTGA( "title.tga" );
int numTitlePixels = titleImage->getWidth() * titleImage->getHeight();
double *titleRed = titleImage->getChannel( 0 );
double *titleGreen = titleImage->getChannel( 1 );
double *titleBlue = titleImage->getChannel( 2 );
Uint32 *titlePixels = new Uint32[ numTitlePixels ];
for( int i=0; i<numTitlePixels; i++ ) {
titlePixels[ i ] =
(unsigned char )( titleRed[i] * 255 ) << 16
|
(unsigned char )( titleGreen[i] * 255 ) << 8
|
(unsigned char )( titleBlue[i] * 255 );
}
// fill screen with title
memcpy( gameImage, titlePixels, 4 * numTitlePixels );
flipGameImageOntoScreen( screen );
char hit = waitForKeyOrButton();
if( !hit ) {
// no key pressed...
// maybe they closed the window
// count as a "quit"
done = true;
}
else {
// they pressed a key to start the game
// return to start
restartMusic();
// turn up music
setMusicLoudness( 1.0 );
}
// fill with black to cover up title
for( i=0; i<totalGameImagePixels; i++ ) {
gameImage[i] = 0;
}
int frameCount = 0;
unsigned long startTime = time( NULL );
char quit = false;
int titleFadeFrame = 0;
int numTitleFadeFrames = 200;
char stepDX = true;
/*
// TEMP
// generate mural image
printf( "Writing mural...\n" );
int muralWidth = 4437;
int muralHeight = 12;
int numStrips = 16;
int stripWidth = muralWidth / numStrips;
int betweenStripSkip = 1;
int yOffset = 160;
Image mural( stripWidth,
( muralHeight + betweenStripSkip) * numStrips,
3, true );
double *muralR = mural.getChannel( 0 );
double *muralG = mural.getChannel( 1 );
double *muralB = mural.getChannel( 2 );
for( int s=0; s<numStrips; s ++ ) {
for( int x=0; x<stripWidth; x++ ) {
for( int y=0; y<muralHeight; y++ ) {
int worldX = x + s * stripWidth;
Uint32 sample =
sampleFromWorld( worldX, y + yOffset, 1 );
int muralY = s * (muralHeight + betweenStripSkip) + y;
int index = muralY * stripWidth + x;
muralR[ index ] = ( (sample >> 16) & 0xFF ) / 255.0;
muralG[ index ] = ( (sample >> 8) & 0xFF ) / 255.0;
muralB[ index ] = ( sample & 0xFF ) / 255.0;
}
}
}
File tgaFile( NULL, "mural.tga" );
FileOutputStream tgaStream( &tgaFile );
TGAImageConverter converter;
converter.formatImage( &mural, &tgaStream );
// END TEMP
*/
while( !done ) {
if( getKeyDown( SDLK_s ) ) {
stepDX = false;
}
else {
stepDX = true;
}
// trick:
// dX advances linearly, which results in smooth motion
// instead of single-pixel tick-tick-tick
// However, this results in smudgy sampling of sprites, which
// makes them hard to see
// If we map our sloping dX, which looks like this: /
// to a constant between integers with a steep slope up to the next
// integer, like this: _/
// Then we get to look at unsmudged sprites between steps, but
// still see a smoothed-out tick
double drawDX = floor( dX );
double floorWidth = 0.5;
if( dX - drawDX > floorWidth ) {
// slope
double slopeValue =
( dX - drawDX - floorWidth ) / ( 1 - floorWidth );
// us trig function to avoid jagged transition from floor to
// slope to next floor
drawDX += -0.5 * cos( slopeValue * M_PI ) + 0.5;
}
if( !stepDX ) {
// disable smoothed stepping
drawDX = dX;
}
for( int y=0; y<height; y++ ) {
for( int x=0; x<width; x++ ) {
//int worldX = (int)( pow( x / 2.0, 1.4 ) );
// compression based on distance of pixel column from player
double trueDistanceFromPlayer = drawDX + x - playerX;
double maxDistance = width - 1;
// cap it so that we don't force tan into infinity below
double cappedDistanceFromPlayer = trueDistanceFromPlayer;
if( cappedDistanceFromPlayer > maxDistance ) {
cappedDistanceFromPlayer = maxDistance;
}
if( cappedDistanceFromPlayer < -maxDistance ) {
cappedDistanceFromPlayer = -maxDistance;
}
// the world position we will sample from
double worldX = x;
// zone around player where no x compression happens
int noCompressZone = 10;
if( trueDistanceFromPlayer > noCompressZone ) {
worldX =
x +
//(width/8) *
// use true distance as a factor so that compression
// continues at constant rate after we pass capped
// distance
// otherwise, compression stops after capped distance
// Still... constant rate looks weird, so avoid
// it by not letting it pass capped distance
trueDistanceFromPlayer / 2 *
( pow( tan( ( ( cappedDistanceFromPlayer -
noCompressZone ) /
(double)( width - noCompressZone ) ) *
M_PI / 2 ), 2) );
/*
trueDistanceFromPlayer / 2 *
( tan( ( cappedDistanceFromPlayer /
(double)( width - 0.5 ) ) * M_PI / 2 ) );
*/
// simpler formula
// actually, this does not approach 0 as
// cappedDistanceFromPlayer approaches 0, so use tan
// instead
// ( trueDistanceFromPlayer / 2 ) * 100
// / pow( ( width - cappedDistanceFromPlayer ), 1.6 );
}
else if( trueDistanceFromPlayer < - noCompressZone ) {
worldX =
x +
//(width/8) *
trueDistanceFromPlayer / 2 *
( pow( tan( ( ( - cappedDistanceFromPlayer -
noCompressZone ) /
(double)( width - noCompressZone ) ) *
M_PI / 2 ), 2) );
/*
trueDistanceFromPlayer / 2 *
( tan( ( - cappedDistanceFromPlayer /
(double)( width - 0.5 ) ) * M_PI / 2 ) );
*/
//( trueDistanceFromPlayer / 2 ) * 50
/// ( width + cappedDistanceFromPlayer );
}
else {
// inside no-compresison zone
worldX = x;
}
//int worldX = x;
worldX += drawDX;
if( worldX > maxWorldX ) {
maxWorldX = worldX;
}
if( worldX < minWorldX ) {
minWorldX = worldX;
}
int worldY = (int)floor( y + dY );
// linear interpolation of two samples for worldX
int intWorldX = (int)floor( worldX );
double bWeight = worldX - intWorldX;
double aWeight = 1 - bWeight;
Uint32 sampleA =
sampleFromWorld( intWorldX, worldY, aWeight );
Uint32 sampleB =
sampleFromWorld( intWorldX + 1, worldY, bWeight );
Uint32 combined = sampleB + sampleA;
gameImage[ ( y + scoreHeight ) * width + x ] = combined;
}
}
drawScore( gameImage, width, height, score );
if( isPlayerDead() ) {
// fade to title screen
double titleWeight =
titleFadeFrame / (double)( numTitleFadeFrames - 1 );
double gameWeight = 1 - titleWeight;
// wipe from left to right during fade
int wipePosition = (int)( titleWeight * width );
// fade out music while we do it
setMusicLoudness( 1.0 - titleWeight );
for( i=0; i<totalGameImagePixels; i++ ) {
Uint32 gamePixel = gameImage[i];
unsigned char gameRed = gamePixel >> 16 & 0xFF;
unsigned char gameGreen = gamePixel >> 8 & 0xFF;
unsigned char gameBlue = gamePixel & 0xFF;
Uint32 titlePixel = titlePixels[i];
unsigned char titleRed = titlePixel >> 16 & 0xFF;
unsigned char titleGreen = titlePixel >> 8 & 0xFF;
unsigned char titleBlue = titlePixel & 0xFF;
unsigned char red =
(unsigned char)(
gameWeight * gameRed + titleWeight * titleRed );
unsigned char green =
(unsigned char)(
gameWeight * gameGreen + titleWeight * titleGreen );
unsigned char blue =
(unsigned char)(
gameWeight * gameBlue + titleWeight * titleBlue );
int x = i % width;
if( x <= wipePosition ) {
gameImage[i] = red << 16 | green << 8 | blue;
}
}
if( !paused ) {
titleFadeFrame ++;
}
if( titleFadeFrame == numTitleFadeFrames ) {
done = true;
}
}
flipGameImageOntoScreen( screen );
// done with frame
double newTimestamp = Time::getCurrentTime();
double frameTime = newTimestamp - lastFrameTimeStamp;
double extraTime = 1.0 / lockedFrameRate - frameTime;
if( extraTime > 0 ) {
Thread::staticSleep( (int)( extraTime * 1000 ) );
}
// start timing next frame
lastFrameTimeStamp = Time::getCurrentTime();
int spouseX, spouseY;
getSpousePosition( &spouseX, &spouseY );
int moveDelta = 1;
if( isPlayerDead() ) {
// stop moving
moveDelta = 0;
}
if( knowSpouse && isSpouseDead() ) {
// player moves slower
// toggle motion on this frame
movingThisFrame = ( frameCount % 2 == 0 );
}
if( getKeyDown( SDLK_p ) && canPause ) {
paused = true;
}
if( getKeyDown( SDLK_o ) ) {
paused = false;
}
if( paused && getKeyDown( SDLK_i ) ) {
stepAnimations();
}
if( getKeyDown( SDLK_b ) ) {
// adjust blowup factor
blowUpFactor += blowUpStep;
if( blowUpFactor > maxBlowUpFactor ) {
blowUpStep *= -1;
blowUpFactor = maxBlowUpFactor - 1;
}
if( blowUpFactor < 1 ) {
blowUpStep *= -1;
blowUpFactor = 2;
}
if( fullScreen ) {
// force redraw of whole screen
blowUpChanged = true;
}
else {
// create a new screen using the new size
createScreen();
}
}
if( getKeyDown( SDLK_f ) ) {
// toggle fullscreen mode
fullScreen = ! fullScreen;
// create a new screen surface (and destroy old one)
createScreen();
}
if( getKeyDown( SDLK_LEFT ) || getJoyPushed( SDL_HAT_LEFT ) ) {
char notBlocked =
!isBlocked( (int)( playerX - moveDelta ), (int)playerY );
// spouse and character move, and are blocked, together
if( haveMetSpouse() &&
isBlocked( spouseX - moveDelta, spouseY ) ) {
notBlocked = false;
}
if( movingThisFrame && notBlocked ) {
playerX -= moveDelta;
if( playerX < 0 ) {
// undo
playerX += moveDelta;
}
else {
// update screen position
dX -= moveDelta;
// pick sprite frame based on position in world
if( ( (int)playerX / 2 ) % 2 == 0 ) {
currentSpriteIndex = 6;
}
else {
currentSpriteIndex = 7;
}
}
}
}
else if( getKeyDown( SDLK_RIGHT ) || getJoyPushed( SDL_HAT_RIGHT )) {
char notBlocked =
!isBlocked( (int)( playerX + moveDelta ), (int)playerY );
// spouse and character move, and are blocked, together
if( haveMetSpouse() &&
isBlocked( spouseX + moveDelta, spouseY ) ) {
notBlocked = false;
}
if( movingThisFrame && notBlocked ) {
dX += moveDelta;
playerX += moveDelta;
// pick sprite frame based on position in world
if( ( (int)playerX / 2 ) % 2 == 0 ) {
currentSpriteIndex = 3;
}
else {
currentSpriteIndex = 2;
}
}
}
else if( getKeyDown( SDLK_UP ) || getJoyPushed( SDL_HAT_UP ) ) {
char notBlocked =
!isBlocked( (int)playerX, (int)( playerY - moveDelta ) );
// spouse and character move, and are blocked, together
if( haveMetSpouse() &&
isBlocked( spouseX, spouseY - moveDelta ) ) {
notBlocked = false;
}
if( movingThisFrame && notBlocked ) {
playerY -= moveDelta;
if( playerY < 0 ) {
// undo
playerY += moveDelta;
}
else {
// update screen position
dY -= moveDelta;
// pick sprite frame based on position in world
if( ( (int)playerY / 2 ) % 2 == 0 ) {
currentSpriteIndex = 0;
}
else {
currentSpriteIndex = 1;
}
}
}
}
else if( getKeyDown( SDLK_DOWN ) || getJoyPushed( SDL_HAT_DOWN )) {
char notBlocked =
!isBlocked( (int)playerX, (int)( playerY + moveDelta ) );
// spouse and character move, and are blocked, together
if( haveMetSpouse() &&
isBlocked( spouseX, spouseY + moveDelta ) ) {
notBlocked = false;
}
if( movingThisFrame && notBlocked ) {
dY += moveDelta;
playerY += moveDelta;
// pick sprite frame based on position in world
if( ( (int)playerY / 2 ) % 2 == 0 ) {
currentSpriteIndex = 5;
}
else {
currentSpriteIndex = 4;
}
}
}
setPlayerPosition( (int)playerX, (int)playerY );
setPlayerSpriteFrame( currentSpriteIndex );
// may change after we set player position
getSpousePosition( &spouseX, &spouseY );
// check for events to quit
SDL_Event event;
while( SDL_PollEvent(&event) ) {
switch( event.type ) {
case SDL_KEYDOWN:
switch( event.key.keysym.sym ) {
case SDLK_q:
case SDLK_ESCAPE:
done = true;
quit = true;
break;
default:
break;
}
break;
case SDL_QUIT:
done = true;
quit = true;
break;
default:
break;
}
}
//t +=0.25;
frameCount ++;
// other animations run independent of whether player is moving
if( !paused && frameCount % 6 == 0 ) {
stepAnimations();
}
if( ! isPlayerDead() && ! paused ) {
// player position on screen inches forward
dX += timeDelta;
}
double age = ( playerX - dX ) / width;
setCharacterAges( age );
if( age >= 0.85 ) {
dieSpouse();
}
if( age >= 0.95 ) {
diePlayer();
}
if( isChest( (int)playerX, (int)playerY ) == CHEST_CLOSED ) {
openChest( (int)playerX, (int)playerY );
int chestX, chestY;
getChestCenter( (int)playerX, (int)playerY, &chestX, &chestY );
if( getChestCode( (int)playerX, (int)playerY ) &
0x01 << specialGem ) {
// reward player
chestScore += 100;
startPrizeAnimation( chestX, chestY );
}
else {
startDustAnimation( chestX, chestY );
}
}
int distanceFromSpouse = (int) sqrt( pow( spouseX - playerX, 2 ) +
pow( spouseY - playerY, 2 ) );
if( ! haveMetSpouse() &&
! isSpouseDead() &&
distanceFromSpouse < 10 ) {
meetSpouse();
knowSpouse = true;
startHeartAnimation(
(int)( ( spouseX - playerX ) / 2 + playerX ),
(int)( ( spouseY - playerY ) / 2 + playerY ) - 2 );
}
// stop after player has gone off right end of screen
if( playerX - dX > width ) {
dX = playerX - width;
}
int exploreDelta = 0;
if( playerX > maxPlayerX ) {
exploreDelta = (int)( playerX - maxPlayerX );
maxPlayerX = playerX;
}
int spouseExploreFactor = 2;
if( haveMetSpouse() ) {
// exploring worth more
exploreDelta *= spouseExploreFactor;
}
exploreSubPoints += exploreDelta;
exploreScore = (int)( exploreSubPoints / 10 );
if( haveMetSpouse() ) {
// show explore score contribution in jumps
exploreScore =
( exploreScore / spouseExploreFactor )
* spouseExploreFactor;
// note:
// this can cause our score to go down (to the previous
// jump) as we transition from not having a spouse to
// having one.
// we fix this below with maxExploreScore
}
if( exploreScore < maxExploreScore ) {
// don't let it go down from max
exploreScore = maxExploreScore;
}
score = chestScore + exploreScore;
if( exploreScore > maxExploreScore ) {
maxExploreScore = exploreScore;
}
}
unsigned long netTime = time( NULL ) - startTime;
double frameRate = frameCount / (double)netTime;
printf( "Max world x = %f\n", maxWorldX );
printf( "Min world x = %f\n", minWorldX );
printf( "Frame rate = %f fps (%d frames)\n",
frameRate, frameCount );
printf( "Game time = %d:%d\n",
(int)netTime / 60, (int)netTime % 60 );
fflush( stdout );
delete titleImage;
delete [] gameImage;
delete [] titlePixels;
destroyWorld();
destroyScore();
if( quit ) {
return false;
}
else {
return true;
}
}