// Stoneroids version 0.95  (Last Compiled: 2-10-97)
// Written by Bruce Stone (c)1996-1997, Stonerware Enterprises.

// Stoneroids programmed by Bruce Stone, ZStoner@aol.com
// Web page: http://members.aol.com/ZStoner

// Thanks goes to Ben Sigelman for writing the orginal Asternoids applet.
// Original (Asternoids) programmed by: Ben Sigelman, sigelman@crocker.com
// http://www.crocker.com/~sigelman/   |||  FOR HIRE! ALWAYS!

// This game is based on the Java game Asternoids, but is completely redesigned
// from the ground up for improved object oriented design and flexibility.  
// Also implements polygon collision detection for better gameplay.

// Suggested to run on 486-66 and up with at least 800x600 display, but not 
// required.  Running in a lower video mode improves frame rate stability in 
// tough situations.  Over 95% of all code is original material, with about 5% 
// remaining from the original Asternoids game code.

// HTML PARAMETERS:   (Smaller Java window makes a tougher game!!)
//  wid: the width of the applet (integer)
//  hei: the height of the applet (integer)

// Associated Source Files for Stoneroids:
// aster.java		Contains the main Java applet class, "Stoneroids".
// GameActor.java	The "GameActor" base class for Game Objects.
// Players.java		Definitions for the "stShip" game object class.
// Asteroids.java	Definitions for the "stAsteroid" game object classe.
// Shots.java		Definitions for the "stLaser" and "stPowerup" game object classes.


// Import Java Classes...
import java.awt.*;
import java.applet.*;
import java.net.*;

// Importing Custom Classes...
import GameActor;    import stShip;    import stAlienShip;
import stAsteroid;   import stLaser;   import stPowerup;


// Main Applet Class
public class Stoneroids extends java.applet.Applet implements Runnable
{
	GameActor object[];
	stShip  MyShip;
	stAlienShip aAlien;
	int NumObjects, NumActive, ArrBlockSize;
	long FrameRate, LastFrNum, FrameNum;
	double FrameDelay;
	long fdPrevFrame, fdLastTime;		// For variable frame delay...
	long timeSleep, timeCurr, timeNow;	// For debugging diagnostics
	int NumHits, NumCollisions;
	int NumTargets, TargetsHit;			// Targets remaining & hit this level
	int NextAlien;						// When next Alien is released
	int NextRegen, NextLife;	// Status Regen & Extra Life every 5 and 10K
	int level = 0;
	int levelend = 0;			// Pause at the end of every level
	int width, height;			// Width/height specified by the HTML Parameters
	int dNGCounter = 0;			// Counter used when bNewGame is true
	boolean bNewGame = false;	// True when getting ready for new game...
	boolean apprun = true;		// Is this applet running, or not?
	String szAlienList;			// List of times when Aliens show up during level
	Image im;					// Image buffer used for double-buffering lines.
	Graphics offscreen;			// Graphics context for graphics output.
	URL codeb;						// Code base URL.  The applet's URL base directory
	Font bigfont = new Font("TimesRoman", Font.BOLD, 24);	// Big font
	Font lilfont = new Font("Helvetica", Font.PLAIN, 12);	// Small font
	double starX[], starY[];	// Keep track of 25 bkgnd star dots
	Color starClr[];			// Color of each bkgnd star...
	AudioClip SoundFX[];		// Array of game sound clips...
	String szSoundFX[] =		// Array of game SoundFX filenames...
	{	"explode.au", "forcefld.au", "collide.au",  "laser01.au", "switch01.au", 
		"siren.au",   "collide2.au", "powerup1.au", "powerup2.au"   };


	// Returns Author, Version and Copyright info for this Applet...
	public String getAppletInfo()
	{
		return "Stoneroids! v0.95  -  (c)1997 Stonerware Enterprises.";
	}

	public void init()
	{
		int i;

		level = 1;							// Set level and score
		width = 640;    height = 480;		// Default width and height

		// Get height and width from HTML Parameters
		width = Integer.valueOf(getParameter("wid")).intValue();
		height = Integer.valueOf(getParameter("hei")).intValue();
		GameActor.screenRect = new Rectangle(width, height);
		GameActor.screenWrap = true;
		GameActor.thisApp = this;

		// These lines initialize double buffering for graphics.
		try 
		{
			im = createImage(width, height);	// Applet size is width*height
			offscreen = im.getGraphics();
		}
		catch (Exception e)
		{
			offscreen = null;
			apprun = false;
			showStatus("Stoneroids::init() - "+e.toString());
			System.out.print(e.toString());
		}

		// Initialize any AudioClip objects here...
		codeb = getCodeBase();								// Get the URL code base
		SoundFX = new AudioClip[szSoundFX.length];
		for (i=0;  i < szSoundFX.length;  i++)
		{
			SoundFX[i] = getAudioClip(codeb, szSoundFX[i]);	// Gets Sound FX clips
			SoundFX[i].play();    SoundFX[i].stop();		// Load audio clips
		}
		GameActor.SFX = SoundFX;

		// Initialize the GameActor object(s)...
		NumHits = NumCollisions = NumActive = 0;
		NumTargets = TargetsHit = NextAlien = 0;
		NextRegen = 5000;    NextLife = 10000;
		FrameRate = LastFrNum = 0;    FrameNum = 0;
		FrameDelay = 40;    fdPrevFrame = 0;

		timeSleep = timeCurr = timeNow = fdLastTime = System.currentTimeMillis();
		object = new GameActor[20];    ArrBlockSize = 20;    NumObjects = 0;
		GameActor.objList = object;
		MyShip = new stShip();    GameActor.AddObject(MyShip);
		MyShip.active = false;

		// Sets up demo asteroids (from Level 3)
		level = 3;    nextlevel();

		aAlien = new stAlienShip();    GameActor.AddObject(aAlien);
		GameActor.AddObject(new stAlienShip());
//		GameActor.AddObject(new stAlienShip());
//		GameActor.AddObject(new stAlienShip());

		// Initialize background star points...
		starX = new double[25];   starY = new double[25];   starClr = new Color[25];
		for (i=0;  i<starX.length;  i++)
		{
			starX[i] = Math.random()*width;    starY[i] = Math.random()*height;
			starClr[i] = Color.gray;
		}

		(new Thread(this)).start();						// Initializes the main thread
		Thread.currentThread().setPriority(Thread.MAX_PRIORITY);	// A must for animation!
	}


	public void start()   {   apprun = true;   }

	public void stop()
	{
		apprun = false;

		for (int i=0;  i<SoundFX.length;  i++)		// Stop any and all sound loops
			if (SoundFX[i] != null)    SoundFX[i].stop();
	}


	public void run()
	{
		int i, j;

		while (true)			// Always runs until stopped
		{
			if (apprun)			// If app is ready to run
			{
				if (MyShip.active == true)
					MyShip.keyDown(null, 0);
				
				try
				{
					// Before anything else, compact the list every 512 frames...
//					if (FrameNum%128 == 1)
//					{
//						NumObjects = GameActor.CompactList(object);
//						GameActor.objList = object;
//					}

					// Is it time to introduce another AlienShip?
					if (!bNewGame  &&  (FrameNum&63) == 0)
					{
						if (szAlienList != null  &&  TargetsHit >= NextAlien)
						{
							// If so, release Alien and shorten Alien event list...
							GameActor.AddObject(new stAlienShip());
							int nextIdx = szAlienList.indexOf(' ');
//							showStatus("Idx: "+nextIdx);
							if (nextIdx > 0)
							{
								szAlienList = szAlienList.substring(nextIdx+1);
								int idx2 = szAlienList.indexOf(' ');
								String sz = szAlienList;
								if (idx2 > 0)    sz = szAlienList.substring(0, idx2);
								NextAlien = Integer.valueOf(sz).intValue();
							}
							else
							{	szAlienList = null;    NextAlien = 9999;   }
//							showStatus("Ast Hits: " + TargetsHit
//								+"   Next: " + NextAlien
//								+"   List: " + szAlienList
//								+"   ("+nextIdx+")");
						}
					}

					NumActive = NumTargets = 0;
					for (i=0;  i < NumObjects;  i++)
					{
						object[i].Move();					// Move object to current location
						object[i].RotatePoly();				// Translates/Rotates the object...
						if (object[i].active == true)
						{
							NumActive++;
							if (object[i] instanceof stAsteroid)   NumTargets++;
						}
						
						if (object[i].active == false)   continue;

						//NumHits = NumCollisions = 0;
						for (j=0;  j<NumObjects;  j++)	// Check for object collisions...
						{
							if (i == j)    continue;		// Don't check yourself

							// Don't check if either object doesn't exist...
							if (object[i].active == false  ||  object[j].active == false)
								continue;

							// Don't check if either object is "dying"...
							if (object[i].status == -1  ||  object[j].status == -1)
								continue;

							//if (object[i].bbox.intersects(object[j].bbox) == true)
							//	NumHits++;

							if (object[i].Collide(object[j]) == true)
							{
							//	NumCollisions++;
								object[i].OnHit(object[j]);		// Objects collided
							}
						}
					}
				}
				catch (Exception e)
				{
					showStatus("Stoneroids::run(loop) - "+e.toString());
					System.out.print(e.toString());
					apprun = false;
				}
				
				// Check for Score-based events...
				// Restore Shield/Energy + 1 "auto" Powerup every 5000 pts
				if (MyShip.ScoreVal >= NextRegen)
				{
					MyShip.shield = 100;    MyShip.energy = 0;
					SoundFX[GameActor.sfxPwrup2].play();
					int pType = stPowerup.RandType();
					GameActor.AddObject(new stPowerup(MyShip, pType, 125));
					NextRegen += 5000;
				}

				repaint();							// Repaints the screen
				if (NumTargets == 0)    levelend++;
				if (levelend > 50)    nextlevel();	// If enough ticks go by, init next level
				if (bNewGame == true)    NewGame();
			}
			
			// Update FrameDelay every 2.5 secs to stabilize game at most ~23 fps
			if (timeNow-fdLastTime > 2500)
			{
				double FrRate = (FrameNum-fdPrevFrame)/2.5;		// 2.5sec fps rate

				// Raise or lower FrameDelay to improve frame rates.
				if (Math.abs(FrRate-22) > .75)    FrameDelay += (FrRate-22)/2.5;

				// FrameDelay>=10 helps prevent thread/process starvation.
				if (FrameDelay < 10)    FrameDelay = 10;
				else    FrameDelay = Math.round(FrameDelay*100)/100f;

				fdLastTime = timeNow;    fdPrevFrame = FrameNum;
			}
			
			try
			{
				long pvFDelay = (long)(FrameDelay*(FrameNum-fdPrevFrame));
				long FDelay = (long)(FrameDelay*(FrameNum-fdPrevFrame+1));
				Thread.sleep(FDelay-pvFDelay);
			}
			catch (InterruptedException e)
			{
				showStatus("Stoneroids::run(sleep) - "+e.toString());
				System.out.print(e.toString());
				apprun = false;
			}

			//REALLY IMPORTANT! THIS DRAMATICALLY ENHANCES APPLET PERFORMANCE!
			Thread.currentThread().yield();
		}
	}


	public boolean mouseDown(Event ev, int x, int y)
	{
		int i;
		boolean b1 = apprun;


		if (b1 == true)		stop();		// Pauses the program if apprun is true
		else				start();	// Unpause the program if apprun is false

		// Don't start a new game if paused in demo mode (unpause instead)...
		if (b1 == false  &&  MyShip.active == false)    return true;

		// Start a new game if mouse clicked in unpaused demo mode...
		if (MyShip.active == false)		// If all players are dead
		{
			level = 1;    MyShip.ScoreVal = 0;			// Reset level and score

			// Set flag to destroy current objects and start a new game...
			bNewGame = true;    start();    repaint();
		}
		return true;
	}


	// Kills any active objects in the game, one at a time.
	// When all are gone, it resets game parameters to start new game.
	// Called while bNewGame is true.
	public void NewGame()
	{
		dNGCounter++;
		if (dNGCounter == 3)	// Kill objects every 3 ticks
		{
			int i, j, k;
			dNGCounter = 0;    level = 20;
			for (i=j=k=0;  i<NumObjects;  i++)
			{
				if (object[i].active == true)
				{
					j++;
					if (object[i].status != -1)		// Kill object if not dying yet
					{
						object[i].Explode();    k++;
						if (k == 3)    break;		// Kill up to 3 new objects at a time
					}
				}
			}
			level = 1;
			
			// No active (or dying) objects remain, ready to setup new game
			if (j == 0)
			{
				MyShip = new stShip();    GameActor.AddObject(MyShip);
				MyShip.LaserPower = 1;
				level = MyShip.ScoreVal = 0;    nextlevel();
				FrameDelay = 40;    bNewGame = false;
			}
		}
	}

	public boolean keyDown(Event ev, int key)
	{
		if (MyShip.active == true)
			MyShip.keyDown(ev, key);

		return true;
	}


	public boolean keyUp(Event ev, int key)
	{
		if (MyShip.active == true)
			MyShip.keyUp(ev, key);

		return true;
	}


	public void nextlevel()
	{
		int i, j, temp;

		if (SoundFX[1] != null)    SoundFX[1].stop();	// Stop all sound loops
		if (MyShip.active)
		{
			// Level Bonus  (100*level)     Shields Bonus (up to 50*level)
			MyShip.ScoreVal += 100 * level;
			MyShip.ScoreVal += (int)(MyShip.shield*level/10)*5;
		
			// Give a shield bonus to the player...
			if (level*4 < 16)	MyShip.shield += 16;   else   MyShip.shield += level*4;
			if (MyShip.shield > 100)    MyShip.shield = 100;	// Max shield at 100

			// 50 ticks of invunerability to start level
			MyShip.invincible = true;
			MyShip.ticker += 50;    if (MyShip.ticker < 50)   MyShip.ticker = 50;
//			this.showStatus("Level: " + level);
			SoundFX[1].loop();
			level++;								// Next level (finally)
		}
		levelend = 0;								// Resets levelend ticker
		TargetsHit = 0;

		// Create asteroids and add 3 Alien Ships to start new level...
		int numAsts = (int)(Math.random()*4+level*1.4);
//		int numAsts = (int)(level);
		for (i=0;  i < numAsts;  i++)    GameActor.AddObject(new stAsteroid());

		// At most "level" Aliens will appear this level...
		// Create list of when each one will appear...
		int AList[] = new int[level-1];
		for (i=0;  i < level-1;  AList[i++]=(int)(Math.random()*numAsts*7));
		for (i=0;  i < level-1;  i++)			// Sort the list...
			for (j=i;  j < level-1;  j++)
				if (AList[i] > AList[j])
				{	temp = AList[i];    AList[i] = AList[j];    AList[j] = temp;   }

		// Build the AlienList string used in the main game loop...
		if (level > 1)
		{
			szAlienList = String.valueOf(AList[0]);    NextAlien = AList[0];
			for (i=1;  i < level-1;  i++)
				szAlienList += " " + AList[i];
		}
		else
		{	szAlienList = null;    NextAlien = 9999;   }
	}


	// Overrides Applet.update, which sucks for threaded applets
	public void update(Graphics g)   {   paint(g);   }


	public void paint(Graphics g)
	{
		if (offscreen != null)			// Double-buffering stuff
		{
			paintApplet(offscreen);		// The REAL paint method
			g.drawImage(im, 0, 0, this);
		}
		else
			paintApplet(g);				// The REAL paint method
	}


	// Moves background star dots at -60% of current dx,dy movement...
	public void moveStars(double dx, double dy)
	{
		// Move points... Create new points if it falls out of bounds...
		for (int i=0;  i<starX.length;  i++)
		{
			starX[i] -= dx*.6;    starY[i] -= dy*.6;
			if (starX[i] < 0)        {   starX[i]=width;   starY[i]=Math.random()*height;   }
			if (starX[i] > width)    {   starX[i]=0;       starY[i]=Math.random()*height;   }
			if (starY[i] < 0)        {   starX[i]=Math.random()*width;   starY[i]=height;   }
			if (starY[i] > height)   {   starX[i]=Math.random()*width;   starY[i]=0;        }
		}
	}


	// Paints the current frame for the applet...
	public void paintApplet(Graphics g)
	{
		int i;

		g.setColor(Color.black);			// Prepares screen wash
		g.fillRect(0,0, width,height);	// Makes big, black rectangle washover
		timeNow = System.currentTimeMillis();
		FrameNum++;

		// Move Stars (movement based on -dx,-dy movement)... Draw the points...
		if (object[0] instanceof stShip  ||  object[0] instanceof stAlienShip)
			moveStars(object[0].dx, object[0].dy);
		int idx = (int)(Math.random()*starX.length);	// Random star point
		int twinkle = (int)(64+Math.random()*192);		// Random shade of gray
		starClr[idx] = new Color(twinkle, twinkle, twinkle);
		for (i=0;  i<starX.length;  i++)
		{
			g.setColor(starClr[i]);
			g.drawLine((int)starX[i],(int)starY[i], (int)starX[i],(int)starY[i]);
		}

		// Display Level string, and AppletInfo (only in Demo mode)
		g.setFont(lilfont);
		g.setColor(Color.lightGray);
		String LvStr = "Level:  "+level;
		int rightX = width - g.getFontMetrics().stringWidth(LvStr);
		g.drawString(LvStr, rightX-1, height-2);

		// Draw Score string...
		g.setColor(Color.white);
		g.drawString("SCORE:  " + MyShip.ScoreVal, 0, 10);	// Current Score

		// Display stuff if game is in "demo" mode...
		if (MyShip.active == false)					// Show "Restart Game" if Game Over...
		{
			g.setColor(Color.lightGray);
			g.drawString(getAppletInfo(), 1, height-2);

			int grayX = 70+((int)(FrameNum*4)%160);
			Color clr = new Color(grayX, grayX, grayX);
			g.setFont(bigfont);						// Use big font
			g.setColor(clr);
			String str = "Click here to play Stoneroids";
			int centerX = (width - g.getFontMetrics().stringWidth(str)) / 2;
			g.drawString(str, centerX, (int)(height/3));

/*			if (timeNow-timeCurr > 1000  &&  aAlien.active)//  &&  FrameNum%5==0)
				this.showStatus("Frame: "+FrameNum +
//					"    Angle: "+(int)(aAlien.angle*180/Math.PI) + 
//					"    TAngle: "+(int)(aAlien.TargetAngle*180/Math.PI) +
//					"    delAngle: "+(int)(aAlien.delAngle*180/Math.PI) +
//					//	FrameNum + " Frames     Hits: " + NumHits+","+NumCollisions +
					"    Active: "+NumActive+","+NumObjects+","+object.length +
//					"    Delay: "+FrameDelay +
					"    Targets: "+NumTargets +
					"    index: "+aAlien.TargIdx +
					"    #fails: "+aAlien.numFails
///					+"    Angle: "+dAng
				);
*/		}
		// Display stuff when game is in "Play" mode...
		else
		{
			// Display the Laser Power string...
			Color clr;
			if (MyShip.invincible)    clr = Color.cyan.darker();
			else                      clr = Color.white;
			if (MyShip.LaserPower < 0)    g.setColor(Color.red.darker());
			else                          g.setColor(clr);
			g.drawString("Laser:  " + Math.abs(MyShip.LaserPower), 0, 27);
			if (MyShip.LaserPower < 0)
			{	g.setColor(clr);    g.drawString("Laser:", 0, 27);   }

			// Draw Shield Text and Guage Box...
			String ShStr = "Shield:  ";
			g.drawString(ShStr, 110, 10);
			int box0 = 110 + g.getFontMetrics().stringWidth(ShStr);
			g.drawRect(box0, 0, width-box0-1, 10);
			if (MyShip.shield < 20)			g.setColor(Color.red.darker());
			else if (MyShip.shield < 50)	g.setColor(Color.yellow.darker());
			else							g.setColor(Color.blue.darker());
			g.fillRect(box0+1, 1, (int)(MyShip.shield*((width-box0-1.5)/100)), 9);

			// Draw Laser Temperature Guage Box...
			if (MyShip.status == 3)    g.setColor(Color.red);
			else if (MyShip.SubZero)   g.setColor(Color.green.darker());
			else                       g.setColor(Color.white);
			g.drawString("Temp:", 110, 27);
			g.drawRect(box0, 17, width-box0-1, 10);
			if (MyShip.energy < 70)			g.setColor(Color.green.darker());
			else if (MyShip.energy < 85)	g.setColor(Color.yellow.darker());
			else							g.setColor(Color.red.darker());
			g.fillRect(box0+1, 18, (int)(MyShip.energy*((width-box0-1.5)/100)), 9);
		}

		// Paint all the game objects...
		for (i=0;  i<NumObjects;  i++)
			object[i].paint(g);

		// (Debug) paint aAlien with a white cockpit...
//		if (aAlien.active)
//		{
//			g.setColor(Color.white);
//			g.fillOval((int)(aAlien.cx-2), (int)(aAlien.cy-2), 4, 4);
//		}

		if (timeNow-timeCurr > 1000)
		{
			timeCurr = timeNow;
			FrameRate = FrameNum - LastFrNum;    LastFrNum = FrameNum;
//			double dAng = Math.atan2(MyShip.poly.xpoints[2]-MyShip.poly.xpoints[0],
//				MyShip.poly.ypoints[2]-MyShip.poly.ypoints[0]);
/*			if (MyShip.active)
				this.showStatus("Level: "+level + "     " + FrameRate+" FPS     " +
					FrameNum + " Frames     Hits: " + NumHits+","+NumCollisions +
				//	"     Active: "+NumActive+","+NumObjects+","+object.length
					"Delay: "+FrameDelay + "     Targets: "+NumTargets
					//+"    Angle: "+dAng
					);
*/		}
	}
}


//  Stoneroids Developed by Bruce Stone (c)1996-1997, Stonerware Enterprises.
//	I welcome any comments or suggestions you have about Stoneroids!
//  E-mail: ZStoner@aol.com        WWW: http://members.aol.com/ZStoner
//
//	Thanks and enjoy the game!
