// Some code derived from xasteroids Copyright Phil Goetz.
// See the file README.xast, which is the README file from the xasteroids
// distribution.
//
// All other code Copyright 1994 Brad Pitzel   pitzel@cs.sfu.ca
// Feel free to use/distribute/modify as long as acknowledgement to
// sasteroids author(s) is made.
//
//

#include <stdio.h>
#include <vga.h>
#include <vgagl.h>
#include <math.h>
#include <iostream.h>

#include "SBitmap.h"
#include "RawImageLoader.h"
#include "cfclass.h"
#include "Keyboard.h"
#include "Timer.h"
#include "HiScore.h"
#include "Ui.h"
#include "FastMath.h"
#include "Explosion.h"
#include "Joystick.h"

#include "Obj.h"		// game sprites
#include "sasteroids.h"

// Version should be defined and passed in from Makefile
#ifndef VERSION
#define VERSION "version?"
#endif

#define FPS	20		// frames per second for the animation
				// play with this if you like

// I thought of changing from xasteroids method of putting objects in
// an array to using a nice C++ linked list, but I think that would only
// slow things down, so the following bit is straight from xasteroids
// source code. (BJP)

// Indexes for 1st dimension of obj	
// The order they are in is important	
#define	AST	0
#define	ENEMY_I	96
#define ENEMYBUL 97
#define	FBUL	98
#define	LASTBUL	102
#define	SHIP	103
#define LASTOBJ 103


// Shapes	
#define	ASTSHAPE1	0
#define ASTSHAPE2	1
#define ASTSHAPE3	2
#define ENBULSH		3
#define	BULSHAPE	4
#define	SHIPSHAPE	5
#define SHIPTHRSHAPE	6
#define	ENEMYSHAPE	7
#define SPINNERSHAPE	8

// The following define 'masses' for the game objects.  From xasteroids
// source, this allows the cool bouncing/collision routines
// to work the way they do..
 
// Masses	
#define M_BIG	FLOAT2FP(8.0)
#define M_MED	FLOAT2FP(4.0)
#define M_SMALL	FLOAT2FP(1.0)
#define M_SHIP	FLOAT2FP(5.0/4.0 )
#define M_ENEMY	FLOAT2FP(1.0)
#define M_BULLET FLOAT2FP( (1.0/8.0) )

#define START_DIST 70		// when starting a level, place new asteroids
				// at least this far from your ship.



/////////////////////////////////////////////////////////////////////////////
// Global variables:	

Joystick	Joy0(0);

typedef Sprite *ObjPtr;
ObjPtr	obj[SHIP+1];
ObjPtr	objShelf[SHIP+1];

// The player ship is an instance of the ship object, eh?
Ship	playerShip( SHIPSHAPE, M_SHIP );

// A cfc object provides an interface to '.cf' files
cfc	Gcf;

// manages the explosions:
BoomManager<SimpleExpl> GboomList;

// some bitmaps needed on-the-fly through-out the game
SBitmap Gbit[8];
SBitmap Gbackdrop;

// I've got to finish C++'ing this program and hide the rest
// of these global vars.
int	score,
	Glevel,
	nextbul = FBUL,			// Obj# of next bullet fired	
	numasts, oldscore;

// Glevel increments everytime you clear out a screen of asteroids
// LEVEL increments up to 20, then no higher (as many parts of the game
// get tougher until level 20, then stay the same after that)
#define MAXLEVEL 20
#define LEVEL   (Glevel <? MAXLEVEL )

////////////////////////////////////////////////////////////////////////////

// ugly hack hack hack.  for convenience, define white and red colors
// into the color palette.
#define WHITE 255
#define RED   254
#define RED2  253

// set palette by loading one of the game bitmaps.
void setGamePalette()
	{
	RawImageLoader loader(Gcf);

	if ( loader.load("SHIP0.RAW", LOAD_PALETTE) == EXIT_SUCCESS )
		gl_setpalettecolors( 0, loader.numColors(), loader.palette() );
	
	gl_setpalettecolor(RED,63,15,25); 
	gl_setpalettecolor(RED2,63,50,10);
	gl_setpalettecolor(WHITE,63,63,63);
	}


// load bitmap image from 'file' into 'b'
void loadBitmap( Bitmap &b, const char *file )
	{
	RawImageLoader	loader(Gcf);

	if ( loader.load(file, LOAD_IMAGE) == EXIT_SUCCESS )
		b.setMap( loader );	
	}

	
// set up the array of game objects.
void initObjArray()
	{	
	loadBitmap( Gbit[SMALLAST], "SMALLAST.RAW" );
	loadBitmap( Gbit[MEDAST], "MEDAST.RAW" );
	loadBitmap( Gbit[BIGAST], "BIGAST.RAW" );
	loadBitmap( Gbit[ENEMY], "ENEMY.RAW" );
	loadBitmap( Gbit[BULLET], "BULLET.RAW" );
	loadBitmap( Gbit[BULLET2], "BULLET2.RAW" );
	loadBitmap( Gbit[SPINNER], "SPINNER.RAW" );
	loadBitmap( Gbit[SHIELD], "SHIELD.RAW" );
#ifdef BACKDROPS
	loadBitmap( Gbackdrop, "BACK1.RAW" );
#endif


#ifdef COMPILE_BITMAPS
	Gbit[BIGAST].compile();
	Gbit[MEDAST].compile();
	Gbit[SMALLAST].compile();
	Gbit[SHIELD].compile();
#endif

	int i;
	for (i = 0; i < ENEMY_I; i++)
		{	
		objShelf[i] = obj[i] = new Sprite( ASTSHAPE1, M_BIG, Gbit[BIGAST] );
		}

	objShelf[SHIP] = obj[SHIP] = &playerShip;
		
	objShelf[ENEMYBUL] = obj[ENEMYBUL] = 
				new Sprite( ENBULSH, M_BULLET, Gbit[BULLET2] );
				
	objShelf[ENEMY_I] = obj[ENEMY_I] = 
				new Enemy( ENEMYSHAPE, M_ENEMY, playerShip, *obj[ENEMYBUL] );
	
	obj[ENEMYBUL]->die();
	
	for (i = FBUL; i < LASTBUL+1; i++)
		{	
		objShelf[i] = obj[i] = new Sprite( BULSHAPE, M_BULLET, Gbit[BULLET] );
		}	
	}

// create asteroids for beginning of each level/game
// makeasts - modified from xasteroids
void makeasts()
	{	
	int i;

	// Erase objs from last level except ship 
	for (i = 0; i < SHIP; i++)
		{
		// check if any new objects were thrown into the game
		// during the last level. If so, delete them are set our
		// obj array to the standard asteroids/enemies/etc.
		if (obj[i]!=objShelf[i])
			{
			delete obj[i];
			obj[i] = objShelf[i];
			}
		
		obj[i]->die(); 
		}
		
	// make new asteroids
	for (i = 0; i < (LEVEL/2+1); i++) // # of Asteroids
		{
		obj[i]->shape = ASTSHAPE1;

		do {
		   obj[i]->setFpos( INT2FP((rand()>>5)%WIDTH), 
		                    INT2FP((rand()>>5)%HEIGHT) );
		   } while (distance( playerShip, *obj[i] ) < START_DIST);
		   
		obj[i]->randomDir( INT2FP(1) );
		obj[i]->setBitmap(Gbit[BIGAST]);
		obj[i]->Fmass = M_BIG;
		obj[i]->revive();
		}
	numasts = i;
	}


// nextast - from xasteroids
int nextast()	// Find next unused asteroid object
	{	
	register int i;
	for (i = 0; obj[i]->isAlive(); i++);	// guaranteed to find one
	return i;
	}


void blastpair(int i, int j)
// Generate random velocity vector v.	
// Add v to object i, -v to j.			
	{
	unsigned int c;	// for rand	
	FixedPoint Fvx, Fvy;
	c = (rand()>>2);
	
	Fvx = FastMath::Fcos( c );
	Fvy = FastMath::Fsin( c );

	obj[i]->addVel( Fvx, Fvy );
	obj[j]->addVel( -Fvx, -Fvy );
	}


typedef struct {FixedPoint x, y, mag;} FVector;

// dot product of 2 vectors	
#define dot(i,j)	(FPMULT( i.x, j.x ) + FPMULT( i.y, j.y ))

void bounce(int i, int j )
	{
	FixedPoint	temp;
	FVector	vi, vj,		// velocity of objs i, j		
		ij,		// vector from center of i to center of j 
		vi_ij, vj_ij,	// velocity of obj along vector ij	
		vipij, vjpij,	// velocity perpendicular to ij		
		vi_ijf, vj_ijf;	// post-collision velocity along ij	

	vi.x = obj[i]->VFxvel; vi.y = obj[i]->VFyvel;
	vj.x = obj[j]->VFxvel; vj.y = obj[j]->VFyvel;
	ij.x = obj[j]->VFx - obj[i]->VFx; ij.y = obj[j]->VFy - obj[i]->VFy;
	ij.mag = FastMath::intSqrt( FP2INT(ij.x) * FP2INT(ij.x) + 
	                    FP2INT(ij.y) * FP2INT(ij.y) );

	if (ij.mag==0) return;
		
	vi_ij.mag = dot(vi, ij) / ij.mag;
	vi_ij.x = FPMULT(ij.x , vi_ij.mag) / ij.mag ;
	vi_ij.y = FPMULT(ij.y , vi_ij.mag) / ij.mag ;
	
	vj_ij.mag = dot(vj, ij) / ij.mag;
	vj_ij.x = FPMULT(ij.x , vj_ij.mag) / ij.mag ;
	vj_ij.y = FPMULT(ij.y , vj_ij.mag) / ij.mag ;

	if (vi_ij.mag - vj_ij.mag < 0)	
		return;

	temp = (FPMULT( vi.y, ij.x) - FPMULT( vi.x, ij.y)) / (ij.mag*ij.mag);
	vipij.x = -FPMULT(ij.y,temp); vipij.y = FPMULT(ij.x,temp);
	
	temp = (FPMULT( vj.x, ij.y) - FPMULT( vj.y, ij.x)) / (ij.mag*ij.mag);
	vjpij.x = -FPMULT(ij.y,temp); vjpij.y = FPMULT(ij.x,temp);

	temp = FPDIV(obj[i]->Fmass,obj[j]->Fmass);
	vj_ijf.x = FPDIV( FPMULT( temp, (2*vi_ij.x - vj_ij.x)) + vj_ij.x, INT2FP(1) + temp);
	vj_ijf.y = FPDIV( FPMULT( temp, (2*vi_ij.y - vj_ij.y)) + vj_ij.y, INT2FP(1) + temp);
	vi_ijf.x = vj_ijf.x + vj_ij.x - vi_ij.x;
	vi_ijf.y = vj_ijf.y + vj_ij.y - vi_ij.y;

	// clip to -10...10 max velocity in either x or y direction.
	obj[i]->setFvel( ((vi_ijf.x + vipij.x) >? -INT2FP(10) ) <? INT2FP(10), ((vi_ijf.y + vipij.y) >? -INT2FP(10) ) <? INT2FP(10) );
	obj[j]->setFvel( ((vj_ijf.x + vjpij.x) >? -INT2FP(10) ) <? INT2FP(10), ((vj_ijf.y + vjpij.y) >? -INT2FP(10) ) <? INT2FP(10) );

	}



// botline - modified from xasteroids
void botline() 
	{	
	char text[70];
	int	y;
	
	y = HEIGHT-Ui::fontHeight();

	// show shield energy level if shield is on
	if (playerShip.shielded())
		{
		int l;
		l = playerShip.shieldPercent()/3;

		sprintf(text, "%07d Ships:%2d  Lvl:%2d  Shield:", 
	               score, playerShip.ships(), Glevel );
		
		gl_hline( 262, y+3, 292   , 254);
		gl_hline( 262, y+4, 262+l , 255);
		gl_hline( 262, y+5, 262+l , 255);
		gl_hline( 262, y+6, 292   , 254);
		}
	else	{
		sprintf(text, "%07d Ships:%2d  Lvl:%2d  Shield:%2d", 
		               score, playerShip.ships(), 
		               Glevel,playerShip.shieldCnt );
		}

	gl_write(0, y, text);
	
	}


// upscore - from xasteroids
void upscore(int killer, int up)	// Only award score for things the player shot 
	{	
	if (killer != ENEMYBUL && killer != SHIP)
		score = score + up;

	// new ship every 10000 points, weeee...
	if (score/10000 > oldscore/10000)
		{
		playerShip.addShip();
		oldscore = score;
		}
	}


// killast - modified from xasteroids
void killast(int killer, int i)
	// i = Asteroid # to kill	
	{	
	int k=0, na=0, oldna=0;
	ObjPtr o1=NULL,o2=NULL;
	
	o1 = obj[i];
	if (o1->shape == ASTSHAPE1)
		{	
		if (numasts == 1)	// Big asteroid was last remaining 
			upscore(killer, LEVEL * 1000);
		else	upscore(killer, 25);
		
		na=nextast();
		o2=obj[na];
		
		o2->setFpos( o1->VFx, o1->VFy );
		o2->setFvel( o1->VFxvel>>1, o1->VFyvel>>1 );

		o2->revive();
		o2->shape = ASTSHAPE2;
		o2->setBitmap(Gbit[MEDAST]);
		o2->Fmass = M_MED;
		
		// every once in a while, one of the medium asteroids is replaced
		// with a little devil ship that flies around and
		// tries to crash into you..
		if (((rand()>>4)%100)<Glevel)
			{
			o1 = obj[i] = new Spinner( SPINNERSHAPE, M_BIG, playerShip );
			((Spinner *)o1)->newShip();
			((Spinner *)o1)->setFpos( o2->VFx, o2->VFy );
			}
		else
			{
			o1->shape = ASTSHAPE2;
			o1->setBitmap(Gbit[MEDAST]);
			o1->Fmass = M_MED;
			numasts++;
			}
		blastpair(i, na);
		GboomList.add( o1->VFx, o1->VFy, 30, 12, o1->VFxvel, o1->VFyvel);
		}
	else if (o1->shape == ASTSHAPE2)
		{
		if (numasts == 1) upscore(killer, 500*LEVEL);

		for (k = 0; k < 3; k++)
			{	
			oldna = na;
			na = nextast();
			o2 = obj[na];
			o2->setFpos( o1->VFx, o1->VFy );
			o2->setFvel( o1->VFxvel, o1->VFyvel );

			o2->revive();
			o2->shape = ASTSHAPE3;
			o2->setBitmap(Gbit[SMALLAST]);
			o2->Fmass = M_SMALL;
			if (k == 1) blastpair(oldna,na);
			}
		if (((rand()>>4)&511)<(Glevel+10))
			{
			o1 = obj[i] = new Spinner( SPINNERSHAPE, M_BIG, playerShip );
			((Spinner *)o1)->newShip();
			((Spinner *)o1)->setFpos( o2->VFx, o2->VFy );
			}
		else
			{
			o1->shape = ASTSHAPE3;
			o1->setBitmap(Gbit[SMALLAST]);
			o1->Fmass = M_SMALL;
			numasts++;
			}

		blastpair(na, i);
		GboomList.add( o1->VFx, o1->VFy, 20, 10, o1->VFxvel, o1->VFyvel);
		numasts = numasts + 2;
		upscore(killer, 50);
		}
	else if (o1->shape == ASTSHAPE3)
		{
		GboomList.add( o1->VFx, o1->VFy, 10, 8, o1->VFxvel, o1->VFyvel);
		o1->die(); numasts--; upscore(killer, 100);

		}
	else if (o1->shape == SPINNERSHAPE)
		{	
		// it takes 3 hits to kill a spinner
		GboomList.add( o1->VFx, o1->VFy, 10, 8, o1->VFxvel, o1->VFyvel);
		((Spinner *)o1)->VhitCount++;
		if ( ((Spinner *)o1)->VhitCount > 3)
			{
			upscore(killer, 200);
			delete obj[i];
			obj[i] = objShelf[i];
			obj[i]->die();
			}
		}
	else	// enemy {ship or bullet}	
		{

		GboomList.add( o1->VFx, o1->VFy, 9, 7, o1->VFxvel, o1->VFyvel);
		o1->die(); upscore(killer, 700);
		}
	}


//	Draw objects
void drawobjs()
	{
	int i;
	for (i = 0; i <= LASTOBJ; i++)
	if (obj[i]->isAlive())
		{
		obj[i]->draw();
		}
		
	GboomList.draw();
	}	


// move all game objects, check for collisions
int moveobjs()
	{	
	int i, j, crash=0;	// Indexes

	GboomList.tick();	// pass time for explosions

	// tell each object to pass 1 frame of time

	for (i = 0; i < LASTOBJ+1; i++)
		if (obj[i]->isAlive())
			obj[i]->tick();

	// check for collisions			
	
	for (i = 0; i < FBUL; i++)
	    if (obj[i]->isAlive())
	        {
	        if (i!=ENEMY_I && i!=ENEMYBUL && obj[ENEMY_I]->isAlive() 
	            && collide( *obj[i], *obj[ENEMY_I]) )
	            	{
	            	bounce(ENEMY_I, i);
	            	}
	        else if (playerShip.isAlive() && collide( *obj[i], playerShip))
			{	
			if (playerShip.shielded()) 
				{
				playerShip.bounce(20); // shake player ship
				bounce(SHIP, i);
				}
			else
				{	
				crash = 2;
				GboomList.add( playerShip.VFx, 
					   playerShip.VFy, 
					   BMAX-1, 40, 
					   playerShip.VFxvel, 
					   playerShip.VFyvel);
				playerShip.die();
				killast(SHIP, i);
				playerShip.reset();
		    		Keyboard::clearhit(); // clear pending key presses
				continue;
				}	
			}
		for (j = ENEMYBUL; j < LASTBUL+1; j++)
			if (obj[j]->isAlive() && collide( *obj[i], *obj[j]) &&
			   (j != ENEMYBUL || (i != ENEMYBUL && i != ENEMY_I)))
				{	
				obj[j]->die();	// Kill the bullet 
				// Don't have 2 bullets kill same asteroid
				if (obj[i]->isAlive()) killast(j,i);
				}
	        }
	 
	return crash;
	}

// fire a bullet for player ship
void fire()
	{	
	FixedPoint Fcosrot, Fsinrot;

	obj[nextbul]->revive();
	Fcosrot = FastMath::Fcos( playerShip.Vangle ); 
	Fsinrot = FastMath::Fsin( playerShip.Vangle );
	
	obj[nextbul]->setFpos( playerShip.VFx + playerShip.size() * Fcosrot, 
	                      playerShip.VFy + playerShip.size() * Fsinrot );
	                      
	obj[nextbul]->setFvel( playerShip.VFxvel + 10 * Fcosrot,
	                      playerShip.VFyvel + 10 * Fsinrot );
	                      
	obj[nextbul]->Vangle = playerShip.Vangle;
	obj[nextbul]->setTime(30);	// loops before bullet expires	
	nextbul++; if (nextbul == LASTBUL+1) nextbul = FBUL;

	}

// return 1=easy, 4=medium, 8=hard
int selectGame()
{
	Ui::drawToPhysical();
	gl_clearscreen(0);

	gl_write(24,25, "Please select skill level:");
	gl_write(80,80,"1 - Easy");
	gl_write(80,90,"2 - Medium");
	gl_write(80,100,"3 - Hard");
	gl_write(80,110,"4 - Nasty");

	while (1)
		{
		switch (getchar()) {
			// ok, kind of dumb, but I was in a hurry :-)
			// I want to leave it open for choosing more
			// than just skill levels (ie, different types
			// of games)
			case '1': return 1;
			case '2': return 2;
			case '3': return 3;
			case '4': return 4;
			case 'q': return 0;
			case 'Q': return 0;
			}
		}
return 0;
}

void displayScreen(const char *file)
	{
	RawImageLoader	loader(Gcf);

	loader.load(file, LOAD_IMAGE|LOAD_PALETTE);
	
	// assign TITLE.RAW to a bitmap
	SBitmap scn;
	scn.setMap( loader );

	// set palette according to TITLE.RAW palette
	gl_setpalettecolors( 0, loader.numColors(), loader.palette() );
	loader.releasePaletteOwnership();
	
	// force 255 to white for text
	gl_setpalettecolor(WHITE,63,63,63);

	Ui::drawToPhysical();
	int x = ( WIDTH - scn.width() )>>1;
	int y = ( HEIGHT - scn.height() )>>1;
	gl_clearscreen(0);
	scn.put(x,y);

	}

void showInfo() 
	{
	displayScreen("BACK.RAW");
	
	int x = (WIDTH - 320)>>1;
	int y = (HEIGHT - 200)>>1;

	gl_setwritemode(WRITEMODE_MASKED);

	gl_write( x+120, y+0,"SASTEROIDS");

	gl_write( x+10, y+20, "TO PLAY THE GAME");
	gl_write( x+10, y+30, "");
	gl_write( x+10, y+40, " KEY         ACTION");
	gl_write( x+10, y+50, " ----------  -----------------------");
	gl_write( x+10, y+60, " LEFT ARROW  TURN SHIP LEFT");
	gl_write( x+10, y+70, " RIGHT ARRW  TURN SHIP RIGHT");
	gl_write( x+10, y+80, " LEFT CTRL   FIRE");
	gl_write( x+10, y+90, " UP ARROW    THRUST");
	gl_write( x+10, y+100," DOWN ARROW  SHIELD, ONE PER SHIP");
	gl_write( x+10, y+110," LEFT ALT    HYPERSPACE (ZAP TO");
	gl_write( x+10, y+120,"             RANDOM AREA ON SCREEN)");
	gl_write( x+10, y+140," P           PAUSE (YOU CAN SWITCH");
	gl_write( x+10, y+150,"             VC's WHILE PAUSED)");
	gl_write( x+10, y+160," Q           QUIT GAME");

	gl_write( x+40, y+190,"(Press any key for scoring info)");
	gl_setwritemode(WRITEMODE_OVERWRITE);

	};

void showScoringInfo() 
	{
	displayScreen("BACK.RAW");
	
	int x = (WIDTH - 320)>>1;
	int y = (HEIGHT - 200)>>1;

	gl_setwritemode(WRITEMODE_MASKED);

	gl_write( x+120, y+0,"SASTEROIDS");

	gl_write( x+10, y+20, "SCORING INFORMATION");
	gl_write( x+10, y+30, "");
	gl_write( x+10, y+40, " POINTS      WHAT");
	gl_write( x+10, y+50, " ----------  -----------------------");
	gl_write( x+10, y+60, " 25          Big Asteroid");
	gl_write( x+10, y+70, " 50          Medium Asteroid");
	gl_write( x+10, y+80, " 100         Small Asteroid");
	gl_write( x+10, y+100, " 200         Spinning Alien");
	gl_write( x+10, y+110," 700         Shooting Alien or Bullet");
	gl_write( x+10, y+130," 500*level   Medium Asteroid Alone");
	gl_write( x+10, y+140," 1000*level  Big Asteroid All Alone");
	gl_write( x+6, y+160,"Send bug reports, patches, sounds");
	gl_write( x+6, y+170,"or cool bitmaps to pitzel@cs.sfu.ca");

	gl_write( x+104, y+190,"(Press any key)");
	gl_setwritemode(WRITEMODE_OVERWRITE);

	};

void showTitle()
{
	displayScreen("TITLE.RAW");

	int x = (WIDTH - 320)>>1;
	int y = (HEIGHT - 200)>>1;
	
	gl_setwritemode(WRITEMODE_MASKED);

	gl_write( x+15 , y+ 80, "ver "VERSION );
	gl_write( x+110, y+105, "'I' FOR INFORMATION");
	gl_write( x+110, y+115, "'H' FOR HIGH SCORES");
	gl_write( x+110, y+125, "'S' TO START GAME");
	gl_write( x+110, y+135, "'Q' TO QUIT");
	gl_setwritemode(WRITEMODE_OVERWRITE);
}

// get name for high scores list
String getHighScoreName()
{
char buf[31];
for(int i=0; i<21; i++) buf[i]=' ';    

// default to user's login name
strcpy(buf,getlogin());

Ui::drawToPhysical();

gl_clearscreen(0);

gl_write( 20, 30, "Whooohoo!");
gl_write( 20, 40, "You made the high score list.");

gl_write( 20, 60, "Please enter your name :" );

Ui::setFontColor(RED2);
Ui::input( 20, 80, buf, 20 );
Ui::setFontColor(WHITE);

return String(buf);
}


// show high scores screen
void showScores( HiScore &s )
{
int i;
char buf[10];
String line;

displayScreen("BACK.RAW");

int x = (WIDTH - 320)>>1;
int y = (HEIGHT - 200)>>1;
	
gl_setwritemode(WRITEMODE_MASKED);
gl_write( x+120, y+0,"HIGH SCORES");

for(i=0; i<HiScoreListSize; i++)
	{
	sprintf(buf,"%d.",i+1);
	gl_write( x+40, y+40+15*i,buf);
	
	line = s[i];
	gl_write( x+70, y+40+15*i, (char *)(const char*)line );
	}

gl_write( x+104, y+190,"(Press any key)");
gl_setwritemode(WRITEMODE_OVERWRITE);
}


// game over - keep animation going for either 5 secs or user hits 'q'
void gameOverLoop()
{
const int loops = 10 * Timer::FrameRate();
int j;

gl_setwritemode(WRITEMODE_MASKED);

for(j=0; j<loops; j++)
	{
	if ( (vga_getkey()!=0) && (j>Timer::FrameRate()) ) return;

	moveobjs();

	//	Draw objects
	drawobjs();

	gl_write( (WIDTH>>1) - 40, HEIGHT>>1, "GAME  OVER");
	botline();
	
	Ui::updateScreen();
#ifdef BACKDROPS
	gl_putbox(0,0,320,200,Gbackdrop.map());
#else			
	gl_clearscreen(0);
#endif
	Timer::sync();
	}

gl_setwritemode(WRITEMODE_OVERWRITE);
}


// Prepares keyboard for game play.
// Puts keyboard into raw mode, and sets appropriate delays on
// key repeat.
int setGameKeyboard() return result;
	{
	result = Keyboard::init();
		
	if (!result)
		{
		keyboard_translatekeys(TRANSLATE_CURSORKEYS);
	
		// set delay on fire key to 1/4 second
		Keyboard::setdelay(LEFT_CTRL,FPS/4);
		// delay on hyperspace to 1 second
		Keyboard::setdelay(LEFT_ALT,FPS);

		Keyboard::setdelay(SCANCODE('t'),FPS); // toggle timings
		Keyboard::setdelay(SCANCODE('p'),FPS); // pause game
		Keyboard::setdelay(SCANCODE('s'),FPS); // new ship
		Keyboard::setdelay(SCANCODE('q'),FPS); // quit
	
		// hey, there's an advantage to peeking at code :-)
		Keyboard::setdelay(SCANCODE('b'),FPS); // secret - apply brakes
		// this one makes for a bouncy game.	
		Keyboard::setdelay(SCANCODE('0'),FPS); // secret - 1 hour shield
		Keyboard::setdelay(SCANCODE('n'),FPS); // secret - advance level
	
		// Note: no delays set on the rotate left/right and thrust keys.
		}
		
	return result;
	}


// play a game..
void playGame()
{
	int backOff=0;
	int	showTimings=0,flash=0;
	char	text[70];		// for showTimings mode
	int pause = 0, i;

	char GameOver = 0;
	
	GboomList.die();

	playerShip.ships(3);	// start with 3 ships
	score = 0; oldscore = -1;

	// get starting skill level
	switch (selectGame())
		{
		case 1: Glevel=0;
			break;
			
		case 2: Glevel=4;
			break;
			
		case 3: Glevel=9;
			break;
			
		case 4: Glevel=14;
			break;
						
		default: return;
		}
			

	numasts = 0;
		
	unsigned char c=0;
	playerShip.reset();
	playerShip.revive();
	
        setGamePalette(); // hmm.. I think sets the Palette for the game


	// all drawing takes place on the virtual screen 
	Ui::drawToVirtual();
	gl_clearscreen(0);

	if (setGameKeyboard())
		{
		cerr << "Sasteroids:: cannot init keyboard" << endl;
		return;
		}

	gl_setwritemode(WRITEMODE_MASKED);
	
	Timer::init();	// get animation timer ready

	while (!GameOver)
	    {   
	    if (numasts==0) 
		{
		Glevel++;
		makeasts();
		}

	    // for timing tests, ignore:
	    //long int t1=0,t2=0,cnt=0;
	    //showTimings=1;
	    
	    while ( (numasts) && (!GameOver) )
	    {	
	    	if (Joy0.ok()) 
	    		Joy0.eval();

		Keyboard::update();

		if (Keyboard::keyhit(SCANCODE('p')))
			pause = 1;

		if (Keyboard::keyhit(SCANCODE('t')))
			showTimings = 1 - showTimings; 

		if (Keyboard::keyhit(SCANCODE('q')))  // quit game
			{
			// yesNo() wasn't written for raw keyboard mode.
			gl_setwritemode(WRITEMODE_OVERWRITE);
			Keyboard::restore();
			if (Ui::yesNo("End This Game"))
			    		{
			    		GameOver = 1;
			    		}
			gl_setwritemode(WRITEMODE_MASKED);

			if (setGameKeyboard())
				{
				cerr << "Sasteroids: couldn't re-init keyboard" << endl;
				GameOver=1;
				}
			}

		// cheat -- set shields to last for an hour
		if (Keyboard::keyhit(SCANCODE('0'))) 
			playerShip.shieldMax(FPS*3600);

	        if (playerShip.isAlive())
	            {
		    if (Joy0.ok())
		    	{
		    	if (Joy0.up())
		    		playerShip.thrust( FLOAT2FP(6.0/FPS) );
		    	else if (Joy0.down())
		    		playerShip.shieldOn();
		    	
		    	if (Joy0.left())
		    		playerShip.rotLeft(1);
		    	else if (Joy0.right())
		    		playerShip.rotRight(1);
		    	
		    	if (Joy0.aPressed()) fire();
		    	if (Joy0.bPressed()) playerShip.hyper();
		    	}

		    if (keyboard_keypressed(SCANCODE_CURSORUP))
		  	playerShip.thrust( FLOAT2FP(6.0/FPS) );
		    
		    if (keyboard_keypressed(SCANCODE_CURSORDOWN))
		    	playerShip.shieldOn();
		    
		    if (keyboard_keypressed(SCANCODE_CURSORLEFT))
		    	playerShip.rotLeft(1);
		    else if (keyboard_keypressed(SCANCODE_CURSORRIGHT))
		    	playerShip.rotRight(1);

		    // slam on the brakes -- parks the ship
		    if (Keyboard::keyhit(SCANCODE('b')))
		    	{
		    	playerShip.VFxvel=0;
		    	playerShip.VFyvel=0;
		    	}
		    if (Keyboard::keyhit(SCANCODE('n')))
		    		Glevel++;
		    	
		    if (Keyboard::keyhit(LEFT_CTRL))
		    	fire();
		    	
		    if (Keyboard::keyhit(LEFT_ALT))
		    	playerShip.hyper();
		    	
		    }
		else
		    {
		    // !playerShip.alive()
		    if (Joy0.ok())
		    	{
		    	if (Joy0.aPressed()||Joy0.bPressed())
		    		{
		    		Keyboard::clearhit();
				playerShip.revive();
				GboomList.die();
				}
		    	}
		    	
		    if (Keyboard::keyhit(SCANCODE('s')))
		    		{
		    		Keyboard::clearhit();
				playerShip.revive();
				GboomList.die();
		    		}
		     }

		moveobjs();

		if (!playerShip.ships()) 
			{
	 		GameOver = 1;
	   		gameOverLoop();
	   		}
		   		
		i = (rand()>>8) & 255;
		if (!obj[ENEMY_I]->isAlive())
			{   
			if (i < Glevel)
		    		{
		    		c = rand();
				if (c < Glevel<<4 ) 
					((Enemy*)(obj[ENEMY_I]))->newShip(FLOAT2FP(WIDTH/(FPS/3*(-LEVEL+31))), (Glevel*2)+10);
				}
			}

		if (!GameOver)
			{
			drawobjs();

			if (!playerShip.isAlive())
			    	{
				gl_write( (WIDTH>>1) - 88,
				           (HEIGHT>>1) + 30,
			   	           "PRESS 's' FOR NEXT SHIP");

				// flash where ship will be placed
				// once per second
				flash++;
				flash %=FPS;

				if (flash > (FPS>>1) )
					playerShip.draw();
				}
			}

		// either show frame timings or game stats on bottom line
		if (!showTimings)
			{
			botline();
			}

		if (pause)
			{
      	    		gl_write( (WIDTH>>1) - 13*8, (HEIGHT>>1) + 40,
          	           "PRESS 'p' TO CONTINUE GAME");
			Ui::updateScreen();
			Keyboard::restore();

			// wait for 'p' to unpause game			
			while (vga_getkey()!='p')
				{
				//  1/10th of a second
				usleep(100000);
				}

			if (setGameKeyboard())
				{
				cerr << "Sasteroids: couldn't re-init keyboard" << endl;
				GameOver=1;
				}
			pause = 0;
			}
		else
			Ui::updateScreen();

			
#ifdef BACKDROPS
		gl_putbox(0,0,320,200,Gbackdrop.map()+backOff);
		//backOff +=320;
		//if (backOff==64000) backOff=0;
#else			
		gl_clearscreen(0);
#endif

		if (!showTimings)
			{
			Timer::sync();   
			}
		else
			{
			// time how long to create one frame of animation
			sprintf(text,"usec per frame: %d   %d",  Timer::sync(), numasts );
			gl_write(0, HEIGHT-Ui::fontHeight(), text);
			}

		} //while (numasts)
	} //for(level..
Keyboard::restore();
}


int main(int argc, char **argv)
{	
	// a .cf file is a collection of files. In this case,
	// sast.cf contains the bitmaps, sounds, etc used in the game
	if (Gcf.open(BINDIR"sast.cf")!=0)
		{
		cerr << "Open failed on sast.cf" << endl;
		exit(-1);
		}

	HiScore	scores(SCOREDIR"sast.scores");

	cout << "Initializing game." << endl;

	// joystick calibration, if joystick working
	if (Joy0.ok())
		{
		cout << "Joystick found." << endl;
		Joy0.calibrate();
		}
	else
		{
		cout << "Joystick not enabled." << endl;
		}

	FastMath::init(1);	// sin/cos tables

	// randomize using timer
	srand( (unsigned int) time(NULL) );

	initObjArray();	// init game objects

	cout << "Generating rotated bitmaps" << endl;
	Ship::init();	// setup up player's ship
	Spinner::init();
	
	Timer::setFrameRate( FPS );

	uid_t cur_uid=getuid(); // init vga, ui stuff
	setreuid(0,-1);	        // svga init routine restores euid to uid,   
	Ui::init();		// but we want to keep euid as root for      
	setreuid(cur_uid,-1);	// writing to the hi-score file, so we do this
				// little bit of uid save/restoring.   
	Ui::setFontColor(WHITE);
	
	// determines at what point sprites will wrap-around
	Trajectory::setFwrap( INT2FP(WIDTH), INT2FP(HEIGHT) );

	int done=0;

	showTitle();

    while(!done)
        {
	switch(getchar())
		{
		case 'h':
		case 'H': 
			showScores(scores);
			getchar();
			showTitle();
			break;
			
		case 'i':
		case 'I':
			showInfo();
			getchar();
			showScoringInfo();
			getchar();
			showTitle();
			break;
			
		case 's':
		case 'S':
			playGame();
			
        		// if score was good enough, add to high score list
        		if (scores.isHigh(score) != -1)
        			{
        			scores.add( getHighScoreName() , score );
        			showScores(scores);
				getchar();
        			}
        		showTitle();
			break;
			
		case 'q':
		case 'Q':
			if (Ui::yesNo("Do you wish to quit"))
	   			done=1;
	   		else	showTitle();
	   		break;
	   	}	
	}

	// shutdown & restore text mode
	Ui::restore();
	
	return 0;
}
