// 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 "Keyboard.h"
#include "bitmaps.h"		// bitmaps for asteroids, ship, etc...
#include "titleScn.h"		// bitmap for title screen
#include "backScn.h"		// bitmap for high scores backdrop
#include "Timer.h"
#include "sasteroids.h"
#include "HiScore.h"
#include "Ui.h"
#include "Obj.h"		// game sprites
#include "FastMath.h"
#include "Explosion.h"
#include "Joystick.h"

// not really needed yet, but harmless (and useful for testing)
#include "Sound.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	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	8.0
#define M_MED	4.0
#define M_SMALL	1.0
#define M_SHIP	1.5
#define M_ENEMY	1.0
#define M_BULLET 0.1

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


typedef struct {double x, y, mag;} Vector;

/////////////////////////////////////////////////////////////////////////////
// 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 );


// 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;

#define MAXLEVEL 20
#define LEVEL   (Glevel <? MAXLEVEL )

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

// ugly hack hack hack.  for convience, define white and red colors
// into the bitmap's color palette.
#define WHITE 255
#define RED   254
#define RED2  253
void setBPcolormap() {
    bitmaps.color[RED].red = 63;
    bitmaps.color[RED].green =15;
    bitmaps.color[RED].blue = 25;
    		
    bitmaps.color[RED2].red = 63;
    bitmaps.color[RED2].green = 50;
    bitmaps.color[RED2].blue = 10;

    gl_setpalette(&bitmaps);
}

// set up the array of game objects.
void initObjArray()
{	int i;
	for (i = 0; i < ENEMY; i++)
		{	
		objShelf[i] = obj[i] = new Sprite( ASTSHAPE1, M_BIG, bigast );
		}

	objShelf[SHIP] = obj[SHIP] = &playerShip;
		
	objShelf[ENEMYBUL] = obj[ENEMYBUL] = 
				new Sprite( ENBULSH, M_BULLET, bullet2 );
				
	objShelf[ENEMY] = obj[ENEMY] = 
				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, 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;

		obj[i]->setFpos( INT2FP((rand()>>5)%WIDTH), 
		                INT2FP((rand()>>5)%HEIGHT) );

		// make sure new ast is not near player's ship
		if (distance( playerShip, *obj[i] ) > START_DIST)
			{
			obj[i]->randomDir( INT2FP(1) );
			obj[i]->setBitmap(bigast);
			obj[i]->mass = M_BIG;
			obj[i]->revive();
			}
		else	i--;	// ast too close to player, try again
	}
	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 );
}


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

// bounce - from xasteroids
// cause two objects to collide elastically	
// Hasn't been converted to fixed point math yet.
void bounce(int i, int j )
{
	double	temp;
	Vector	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]->xvel(); vi.y = obj[i]->yvel();
	vj.x = obj[j]->xvel(); vj.y = obj[j]->yvel();
	ij.x = obj[j]->x() - obj[i]->x(); ij.y = obj[j]->y() - obj[i]->y();
	ij.mag = sqrt(ij.x*ij.x + ij.y*ij.y);

/*
Calculate velocities projected onto ij;
vi_ij = vi*cos(a) = (vi dot ij) / |ij|		
*/

	vi_ij.mag = dot(vi, ij) / ij.mag;
	vi_ij.x = (ij.x * vi_ij.mag) / ij.mag;
	vi_ij.y = (ij.y * vi_ij.mag) / ij.mag;
	vj_ij.mag = dot(vj, ij) / ij.mag;
	vj_ij.x = (ij.x * vj_ij.mag) / ij.mag;
	vj_ij.y = (ij.y * vj_ij.mag) / ij.mag;

	if (vi_ij.mag - vj_ij.mag < 0)	
		return;
/* 
Objs moving away from each other -
Since objs are round (at least when bouncing), this means
they are moving away from each other already.
*/

/*
Calculate component of velocities perpendicular to ij:
	|vipij| = |vi|*sin(a) = |vi x ij| / |ij|
Same as
	|vipij| = |vi|*cos(M_PI/2 - a) = (vi dot (perp. to ij)) / |ij|
*/

	temp = vi.y*ij.x - vi.x*ij.y;	// - (X-product when 3rd coord is 0)
	temp /= (ij.mag*ij.mag);
	vipij.x = -ij.y*temp; vipij.y = ij.x*temp;
	temp = (vj.x*ij.y - vj.y*ij.x) / (ij.mag*ij.mag);
	vjpij.x = -ij.y*temp; vjpij.y = ij.x*temp;

/*
Calculate the linear elastic collision along ij:
	mass(i)*vi_ij + mass(j)*vj_ij = mass(i)*vi_ijf + mass(j)*vj_ijf
	vi_ij + vi_ijf = vj_ij + vj_ijf	(derived by dividing equation
	for conservation of kinetic energy
	 by eq. for cons. of momentum)
*/

	temp = obj[i]->mass/obj[j]->mass;
	vj_ijf.x = (temp * (2*vi_ij.x - vj_ij.x) + vj_ij.x) / (1 + temp);
	vj_ijf.y = (temp * (2*vi_ij.y - vj_ij.y) + vj_ij.y) / (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;

/*
Now, given vi_ijf and vj_ijf, add them to the perpendicular
	components to get final velocity vectors
*/

	obj[i]->setFvel( FLOAT2FP(vi_ijf.x + vipij.x), FLOAT2FP(vi_ijf.y + vipij.y) );
	obj[j]->setFvel( FLOAT2FP(vj_ijf.x + vjpij.x), FLOAT2FP(vj_ijf.y + vjpij.y) );

}


// 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_line( 262, y+3, 292   ,y+3, 254);
		gl_line( 262, y+4, 262+l ,y+4, 255);
		gl_line( 262, y+5, 262+l ,y+5, 255);
		gl_line( 262, y+6, 292   ,y+6, 254);
		}
	else	{
		sprintf(text, "%07d Ships:%2d  Lvl:%2d  Shield:%2d", 
		               score, playerShip.ships(), 
		               Glevel,playerShip.shieldCnt );
		}

	gl_write(0, y, text);
	

}

void printss() {};

// 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;

	if (obj[i]->shape == ASTSHAPE1)
		{	
		if (numasts == 1)	// Big asteroid was last remaining 
			upscore(killer, LEVEL * 1000);
		else	upscore(killer, 25);
		
		na = nextast();
		obj[na]->setFpos( obj[i]->VFx, obj[i]->VFy );
		obj[na]->setFvel( obj[i]->VFxvel, obj[i]->VFyvel );

		obj[na]->revive();
		obj[na]->shape = ASTSHAPE2;
		obj[na]->setBitmap(medast);
		obj[na]->mass = 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)%60)<Glevel)
			{
			obj[i] = new Spinner( SPINNERSHAPE, M_BIG, playerShip );
			((Spinner *)obj[i])->newShip();
			((Spinner *)obj[i])->setFpos( obj[na]->VFx, obj[na]->VFy );
			}
		else
			{
			obj[i]->shape = ASTSHAPE2;
			obj[i]->setBitmap(medast);
			obj[i]->mass = M_MED;
			numasts++;
			}
		blastpair(i, na);
		obj[i]->explode(30,12);
		}
	else if (obj[i]->shape == ASTSHAPE2)
		{
		if (numasts == 1) upscore(killer, 500*LEVEL);

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

			obj[na]->revive();
			obj[na]->shape = ASTSHAPE3;
			obj[na]->setBitmap(smallast);
			obj[na]->mass = M_SMALL;
			if (k == 1) blastpair(oldna,na);
			}
		if (((rand()>>4)%120)<Glevel)
			{
			obj[i] = new Spinner( SPINNERSHAPE, M_BIG, playerShip );
			((Spinner *)obj[i])->newShip();
			((Spinner *)obj[i])->setFpos( obj[na]->VFx, obj[na]->VFy );
			}
		else
			{
			obj[i]->shape = ASTSHAPE3;
			obj[i]->setBitmap(smallast);
			obj[i]->mass = M_SMALL;
			numasts++;
			}

		blastpair(na, i);
		obj[i]->explode(20,10);
		numasts = numasts + 2;
		upscore(killer, 50);
		}
	else if (obj[i]->shape == ASTSHAPE3)
		{	
		obj[i]->explode(10, 8);
		obj[i]->die(); numasts--; upscore(killer, 100);
		}
	else if (obj[i]->shape == SPINNERSHAPE)
		{	
		// it takes 3 hits to kill a spinner
		obj[i]->explode(10, 8);
		((Spinner *)obj[i])->VhitCount++;
		if ( ((Spinner *)obj[i])->VhitCount > 3)
			{
			upscore(killer, 200);
			delete obj[i];
			obj[i] = objShelf[i];
			obj[i]->die();
			}
		}
	else	// enemy {ship or bullet}	
		{
		Sound::play_file("boom.voc");
		obj[i]->explode(9, 7);
		obj[i]->die(); upscore(killer, 700);
		}
}



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


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

	Boom::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 (playerShip.isAlive() && collide( *obj[i], playerShip))
			{	
			if (playerShip.shielded()) 
				{
				playerShip.bounce(20); // shake player ship
				bounce(SHIP, i);
				}
			else
				{	
				crash = 2;
				Sound::play_file("crash.voc");
				playerShip.explode(BMAX-1, 50);
				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)))
				{	
				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;

	Sound::play_file("shotgun.voc");
	}

// 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 showInfo() 
{
Ui::drawToPhysical();
gl_clearscreen(0);
gl_setpalette(&backScn);
back.put(0,0);

gl_setwritemode(WRITEMODE_MASKED);

gl_write(120,0,"SASTEROIDS");

gl_write(10,20, "TO PLAY THE GAME");
gl_write(10,30, "");
gl_write(10,40, "  KEY        ACTION");
gl_write(10,50, " ----------  -----------------------");
gl_write(10,60, " LEFT ARROW  TURN SHIP LEFT");
gl_write(10,70, " RIGHT ARRW  TURN SHIP RIGHT");
gl_write(10,80, " LEFT CTRL   FIRE");
gl_write(10,90, " UP ARROW    THRUST");
gl_write(10,100," DOWN ARROW  SHIELD, ONE PER SHIP");
gl_write(10,110," LEFT ALT    HYPERSPACE (ZAP TO");
gl_write(10,120,"             RANDOM AREA ON SCREEN)");
gl_write(10,130," P           PAUSE");
gl_write(10,140," Q           QUIT GAME");
gl_write(6,160,"Send bug reports, patches, sounds");
gl_write(6,170,"or cool bitmaps to pitzel@cs.sfu.ca");

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

};

void showTitle()
{
	Ui::drawToPhysical();

	gl_clearscreen(0);
	gl_setpalette(&titleScn);
	title.put(0,0);

	gl_setwritemode(WRITEMODE_MASKED);

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


// get name for high scores list
String getHighScoreName()
{
char buf[21];

for(int i=0; i<21; i++) buf[i]=' ';    

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::input( 20, 80, buf, 20 );

return String(buf);
}


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

Ui::drawToPhysical();
gl_clearscreen(0);
gl_setpalette(&backScn);
back.put(0,0);

gl_setwritemode(WRITEMODE_MASKED);
gl_write(120,0,"HIGH SCORES");

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

gl_write(104,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(120,100,"GAME  OVER");
	botline();
	
	Ui::updateScreen();
	gl_clearscreen(0);
	Timer::sync();
	}

gl_setwritemode(WRITEMODE_OVERWRITE);
}

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

	char GameOver = 0;
	
	Boom::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();
	
        setBPcolormap(); // set palette to game bitmaps' colors 

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

	//*************************** 
	// put keyboard into raw mode
	if (Keyboard::init())
		{
		cerr << "Sasteroids:: cannot init keyboard" << endl;
		return;
		}
		
	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);
	Keyboard::setdelay(SCANCODE('p'),FPS);
	Keyboard::setdelay(SCANCODE('s'),FPS);
	Keyboard::setdelay(SCANCODE('q'),FPS);
	Keyboard::setdelay(SCANCODE('b'),FPS);
	Keyboard::setdelay(SCANCODE('0'),FPS);

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

	while (!GameOver)
	    {   
	    if (numasts==0) 
		{
		if (Glevel<20) 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 - pause;

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

		if (Keyboard::keyhit(SCANCODE('q')))  // quit game
			GameOver = 1;

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

/*			if (Ui::yesNo("End This Game"))
			    		{
			    		GameOver = 1;
			    		}
			    	break;
*/
	        if (playerShip.isAlive() & !pause)
	            {
		    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(LEFT_CTRL))
		    	fire();
		    	
		    if (Keyboard::keyhit(LEFT_ALT))
		    	playerShip.hyper();
		    	
		    } //player ship alive && !pause
 
		if (!playerShip.isAlive() & !pause)
		    {
		    if (Joy0.ok())
		    	{
		    	if (Joy0.aPressed()||Joy0.bPressed())
		    		{
		    		Keyboard::clearhit();
				playerShip.revive();
				Boom::die();
				}
		    	}
		    	
		    if (Keyboard::keyhit(SCANCODE('s')))
		    		{
		    		Keyboard::clearhit();
				playerShip.revive();
				Boom::die();
		    		}
		     }

		if (!pause)
			{   
			moveobjs();

		   	//if ( (playerShip.ships()) && (score>0) ) 
		   	//	score--;	// timer effect	

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

		if (!GameOver)
			{
			drawobjs();

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

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

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

		if (pause)
      	    		gl_write( (WIDTH>>1) - 13*8, (HEIGHT>>1) + 40,
          	           "PRESS 'p' TO CONTINUE GAME");
					
		// either show frame timings or game stats on bottom line
		if (!showTimings)
			{
			botline();
			Ui::updateScreen(); 
			gl_clearscreen(0);
			Timer::sync();   
			}
		else
			{
			// time how long to create one frame of animation
			Ui::updateScreen(); 
			gl_clearscreen(0);

			sprintf(text,"usec per frame: %d",  Timer::sync() );
			gl_write(0, HEIGHT-Ui::fontHeight(), text);
			}

			/* for Timing tests, ignore:
			t1 = Timer::sync();
			if (t1<0)  cout << t1 << " ";
			else 
			    	{
			    	t2 += t1;
				cnt++;
				}
				
			if (cnt==100)
				{
				GameOver=1;
				cout << "!" << t2/cnt << endl;
				}
			*/
			
		} //while (numasts)
	} //for(level..
Keyboard::restore();
}


int main(int argc, char **argv)
{	

	HiScore	scores("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) );

	titleScnInit();	 // define title screen bitmaps, color map

	bitmapsInit();	 // define bitmaps, color map 
	
#ifdef COMPILE_BITMAPS
	bigast.compile();
	medast.compile();
	smallast.compile();
#endif
	backScnInit();

	cout << "Generating rotated bitmaps" << endl;
	Ship::init();	// setup up player's ship
	Spinner::init();
	
	initObjArray();	// init game objects

	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.   

	int done=0;

	Sound::init();
	
	showTitle();
	
    while(!done)
        {
	switch(getchar())
		{
		case 'h':
		case 'H': 
			showScores(scores);
			getchar();
			showTitle();
			break;
			
		case 'i':
		case 'I':
			showInfo();
			getchar();
			showTitle();
			break;
			
		case 's':
		case 'S':
			playGame();
			
        		// if score was good enough, add to high score list
        		if (scores.isHigh(score) != -1)
        			{
        			//Sound::play_file("applause.voc");
        			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();
	Sound::restore();

	return 0;
}
