// Warp
// By Karl Hornell, June 10, 1996
// Last modified June 27

import java.awt.*;
import java.awt.image.*;
import java.applet.AudioClip;
import java.net.*;
import java.awt.Font;

public final class warp extends java.applet.Applet implements Runnable
{
	int i,j,k,p,q,r,counter=0,mileage,windowHeight,windowStep,loadedLev=-1;
	int runMode,objCount,level,lives,currentKey=0,bulletIx,currentSnd=-1;
	int objX[],objY[],objType[],objMode[],objLook[],objPar[];
	int brickPos[],brickType[],brickArea[];
	int brickMap[];
	int sortBuf1[],sortBuf2[],touching[]={0,0,0,0};
	int fixQueueX[],fixQueueY[],fixQueueBrick[];
	Font fo;
	boolean objDraw[],bulletOn,showLights,drawScore;
	final int maxObj=10,maxLev=9,maxFix=10,objectImages=26,maxOtherBricks=6;
	final int cyclic[]={0,1,2,1},shark[]={31,32,33,34,35,35,35,35,35,35,35,34,33,32,31};
	final int groundMouth[]={46,47,48,49,50,51,52,52,52,51,50,49,48,47,46};
	final int cyclic2[]={0,1,2,3,2,1};
	final int maxEnemies=4,levObjStart[]={13,19,29,36,43,53,61,74,82,90};
	final int blastable=14,totalBricks=10;
	final int objImX[]={240,280,240,280,296,280,96,128,160,192,224,256,288,
		248,288,328,368,408,448, 288,328,368,408,448,488,528,568,608,648,
		312,352,392,424,456,488,528, 288,328,368,408,448,488,528,
		280,320,360,400,432,464,504,544,584,624, 288,328,368,408,448,488,528,568,
		328,368,408,448,488,528,568,600,632,664,704,744,784,
		320,360,400,440,480,520,560,600, 320,360,400,440,480,520,560,576};
	final int objImY[]={128,128,160,160,160,176,96,96,96,96,96,96,96};
	final int objImW[]={40,40,40,16,16,16,32,32,32,32,32,32,32,
		40,40,40,40,40,40, 40,40,40,40,40,40,40,40,40,40,
		40,40,32,32,32,40,40, 40,40,40,40,40,40,40, 40,40,40,32,32,40,40,40,40,40,
		40,40,40,40,40,40,40,40, 40,40,40,40,40,40,32,32,32,40,40,40,40,
		40,40,40,40,40,40,40,40, 40,40,40,40,40,40,16,16};
	final int objImPractW[]={32,32,32,16,16,16,32,32,32,32,32,32,32,
		32,32,32,32,32,32, 32,32,32,32,32,32,32,32,32,32,
		32,32,32,32,32,32,32, 32,32,32,32,32,32,32, 32,32,32,32,32,32,32,32,32,32,
		32,32,32,32,32,32,32,32, 32,32,32,32,32,32,32,32,32,32,32,32,32,
		32,32,32,32,32,32,32,32, 32,32,32,32,32,32,16,16};
	final int objImH[]={32,32,32,16,16,16,32,32,32,32,32,32,32,
		32,32,32,32,32,32, 32,32,32,32,32,32,32,32,32,32,
		32,32,32,32,32,32,32, 32,32,32,32,32,32,32, 32,32,32,32,32,32,32,32,32,32,
		32,32,32,32,32,32,32,32, 32,32,32,32,32,32,32,32,32,32,32,32,32,
		32,32,32,32,32,32,32,32, 32,32,32,32,32,32,16,16};
	final int objNum[]={0,1,2,3,4,5,6,7,8,9,10,11,12, 13,14,15,16,17,18,
		13,14,15,16,17,18,19,20,21,22, 13,14,15,16,17,18,19, 13,14,15,16,17,18,19,
		13,14,15,16,17,18,19,20,21,22, 13,14,15,16,17,18,19,20,
		13,14,15,16,17,18,19,20,21,22,23,24,25, 13,14,15,16,17,18,19,20,
		13,14,15,16,17,18,19,20};
	
	final int brickX[]={64, 0,32,72,104,144,176,208, 0,32,72,104,144,184,216,256,
		0,32,64,96,128,168,200,240,280, 0,32,64,96,136,176,216,248,
		0,32,64,104,136,176,208,240, 0,32,64,104,136,176,216,248,
		0,32,72,112,152,192,224,264,296, 0,32,72,112,144,184,216,256,288,
		0,32,72,104,136,176,216,256,288};
	final int brickW[]={32, 32,40,32,40,32,32,40, 32,40,32,40,32,32,40,32,
		32,32,32,32,40,32,40,40,32, 32,32,32,40,40,40,32,40, 32,32,40,32,40,32,32,40,
		32,32,40,32,40,40,32,40, 32,40,40,40,40,32,40,32,32, 32,40,40,32,40,32,40,32,32,
		32,40,32,32,40,40,40,32,32};
	final int levBrickStart[]={1,8,16,25,33,41,49,58,67,76};
	final int brickQual[]={0, 0,3,0,1,0,0,1, 0,3,0,3,0,0,1,0, 0,4,4,4,3,4,1,3,4,
		0,0,0,1,1,3,0,1, 0,4,3,4,1,4,4,1, 0,0,3,0,1,0,0,1, 0,3,0,3,0,4,1,0,0,
		0,1,3,0,3,0,1,0,0, 0,3,0,0,1,1,3,4,0};
	final int brickIm[]={0, 1,2,3,4,5,6,7, 1,2,3,4,5,6,7,8, 1,2,3,4,5,6,7,8,9,
		1,2,3,4,5,6,7,8, 1,2,3,4,5,6,7,8, 1,2,3,4,5,6,7,8, 1,2,3,4,5,6,7,8,9,
		1,2,3,4,5,6,7,8,9, 1,2,3,4,5,6,7,8,9};
	final int levBackgr[]={1,8,16,25,33,41,49,58,67};
	final int blastBrick[]={2,9,11,20,23,30,35,43,50,52,60,62,68,73};
	final int brickPoints[]={15,15,15,20,10,20,25,15,25,20,20,20,25,25};
	final double bricksPerLine[]={3,3.2,3,3.1,3,3.2,3.1,3.2,2.9};
	final int levBrick[]={2,4,5,6,7,0, 9,11,13,14,15,0, 17,18,19,20,22,23,
		26,27,28,29,30,32, 34,35,37,38,39,40, 42,43,45,46,47,48, 50,52,54,55,56,57,
		59,60,62,64,65,66, 68,70,71,72,73,75};
	final double randBrick[]={0.25,0.5,0.75,0.85,1,1, 0.2,0.4,0.55,0.70,1,1,
		0.15,0.30,0.4,0.55,0.80,1, 0.1,0.35,0.5,0.65,0.85,1, 0.15,0.3,0.4,0.55,0.70,1,
		0.14,0.46,0.54,0.70,0.85,1, 0.22,0.4,0.5,0.68,0.82,1, 0.18,0.4,0.6,0.75,0.83,1,
		0.2,0.34,0.52,0.67,0.83,1};
	final int levEnemy[]={7,8,0,0, 8,12,0,0, 13,14,0,0, 7,15,0,0, 8,14,0,0, 15,13,7,0,
		15,8,7,0, 7,15,0,0, 8,16,0,0};
	final int enemyPoints[]={25,30,0,0, 30,40,0,0, 40,50,0,0, 35,30,0,0, 30,50,0,0,
		30,40,25,0, 20,30,25,0, 25,25,0,0, 30,25,0,0};
	final double randEnemy[]={0.5,1,1,1, 0.5,1,1,1, 0.5,1,1,1, 0.5,1,1,1, 0.5,1,1,1,
		0.4,0.6,1,1, 0.4,0.7,1,1, 0.5,1,1,1, 0.7,1,1,1};
	final int levLength[]={700,700,700,700,700,700,700,700,700};
	Image offImage,landscape,defaultStrip,modStrip,logo,bricks[];
	Image panel,outline[],objects[],scoreDisp,clearDisp,clearScreen;
	AudioClip sound[];
	Graphics offGraphics,landscapeG,defaultSG,modSG,scoreDG,clearDG,clearSG;
	Color bgcolor;
	ImageFilter filter;
	Thread updateThread;
	long startTime,score,newScore,tempScore,highScores[]={0,0,0,0,0};
	Math m;

	public void init()
	{
		bgcolor=findBGColor();
		setBackground(bgcolor);
		fo=new Font("Courier",Font.BOLD,14);
		setFont(fo);
		sound=new AudioClip[3];
		sound[0] = getAudioClip(getCodeBase(),"warpsnd0.au");
		sound[1] = getAudioClip(getCodeBase(),"warpsnd1.au");
		sound[2] = getAudioClip(getCodeBase(),"warpsnd2.au");
		
		bricks=new Image[totalBricks];
		brickMap=new int[22*9];
		outline=new Image[5];
		objects=new Image[objectImages];

		defaultStrip=createImage(288,32);
		defaultSG=defaultStrip.getGraphics();
		modStrip=createImage(288,32);
		modSG=modStrip.getGraphics();
		scoreDisp=createImage(74,13);
		scoreDG=scoreDisp.getGraphics();
		scoreDG.setColor(Color.lightGray);
		scoreDG.fillRect(0,0,74,13);
		scoreDG.setColor(Color.black);
		scoreDG.setFont(fo);
		clearDisp=createImage(74,13);
		clearDG=clearDisp.getGraphics();
		clearDG.setColor(Color.lightGray);
		clearDG.fillRect(0,0,74,13);
		clearScreen=createImage(288,8);
		clearSG=clearScreen.getGraphics();
		clearSG.setColor(Color.black);
		clearSG.fillRect(0,0,288,8);
		getMainGraphics();
		System.gc();
		
		objX=new int[maxObj];
		objY=new int[maxObj];
		objType=new int[maxObj];
		objMode=new int[maxObj];
		objLook=new int[maxObj];
		objPar=new int[maxObj];
		objDraw=new boolean[maxObj];
		brickPos=new int[5];
		brickType=new int[5];
		brickArea=new int[9];
		fixQueueX=new int[maxFix];
		fixQueueY=new int[maxFix];
		fixQueueBrick=new int[maxFix];
		sortBuf1=new int[maxObj];
		sortBuf2=new int[maxObj];
					
		offImage=createImage(288,288);
		offGraphics=offImage.getGraphics();
		offGraphics.setFont(fo);
		landscape=createImage(288,608);
		landscapeG=landscape.getGraphics();
		landscapeG.setFont(fo);
		preparePresentation();
		handleSound(2,false);
		resize(320,400);
	}

	public Color findBGColor() // Convert hexadecimal RGB parameter to color
	{
		int hex[];
		String s,h="0123456789abcdef";
		Color c;
		hex=new int[6];
		s=getParameter("bgcolor");
		if ((s!=null)&&(s.length()==6))
		{
			for (i=0;i<6;i++)
				for (j=0;j<16;j++)
					if (s.charAt(i)==h.charAt(j))
						hex[i]=j;
			c=new Color(hex[0]*16+hex[1],hex[2]*16+hex[3],hex[4]*16+hex[5]);
		}
		else
			c=Color.lightGray; // Default
		return c;
	}

	public void getMainGraphics() // Load and process the most common graphics
	{
		Image collection;
		MediaTracker tracker;
		int i;
		
		tracker=new MediaTracker(this);
		collection = getImage(getCodeBase(),"warp0.gif");
		tracker.addImage(collection,0);
		try
		{
			tracker.waitForID(0);
		}
			catch(InterruptedException e) {}
			
		logo=createImage(new FilteredImageSource(
				collection.getSource(),new CropImageFilter(0,128,240,70)));
		tracker.addImage(logo,1);
		bricks[0]=createImage(new FilteredImageSource(
				collection.getSource(),new CropImageFilter(brickX[0],96,
				32,32)));
		tracker.addImage(bricks[0],1);
		outline[0]=createImage(new FilteredImageSource(
			collection.getSource(),new CropImageFilter(0,96,16,32)));
		tracker.addImage(outline[0],1);
		outline[1]=createImage(new FilteredImageSource(
			collection.getSource(),new CropImageFilter(16,96,16,32)));
		tracker.addImage(outline[1],1);
		outline[2]=createImage(new FilteredImageSource(
			collection.getSource(),new CropImageFilter(32,96,32,16)));
		tracker.addImage(outline[2],1);
		outline[3]=createImage(new FilteredImageSource(
			collection.getSource(),new CropImageFilter(32,112,16,16)));
		tracker.addImage(outline[3],1);
		outline[4]=createImage(new FilteredImageSource(
			collection.getSource(),new CropImageFilter(48,112,16,16)));
		tracker.addImage(outline[4],1);

		panel=createImage(new FilteredImageSource(
				collection.getSource(),new CropImageFilter(0,0,320,96)));
		tracker.addImage(panel,1);
		for (i=0;i<levObjStart[0];i++)
		{
			objects[i]=createImage(new FilteredImageSource(
				collection.getSource(),new CropImageFilter(objImX[i],objImY[i],
				objImW[i],objImH[i])));
			tracker.addImage(objects[i],1);
		}		
		try
		{
			tracker.waitForID(1);
		}
			catch(InterruptedException e) {}
	}

	public void getLevelGraphics(int lev) // Fetch new graphics when entering new level
	{
		Image collection;
		MediaTracker tracker;
		int i,j=13;
		tracker=new MediaTracker(this);
		collection = getImage(getCodeBase(),"warp"+(lev+1)+".gif");
		tracker.addImage(collection,0);
		try
		{
			tracker.waitForID(0);
		}
			catch(InterruptedException e) {}
		if (lev!=loadedLev)
		{
			for (i=levObjStart[lev];i<levObjStart[lev+1];i++)
			{
				objects[objNum[i]]=createImage(new FilteredImageSource(
					collection.getSource(),new CropImageFilter(objImX[i],0,
					objImW[i],objImH[i])));
				tracker.addImage(objects[objNum[i]],1);
			}
			for (i=levBrickStart[lev];i<levBrickStart[lev+1];i++)
			{
				bricks[brickIm[i]]=createImage(new FilteredImageSource(
					collection.getSource(),new CropImageFilter(brickX[i],0,
					brickW[i],32)));
				tracker.addImage(bricks[brickIm[i]],1);
			}
			try
			{
				tracker.waitForID(1);
			}
				catch(InterruptedException e) {}
			loadedLev=lev;
		}
	}

	public void handleSound(int sndNum, boolean loop)
	{
		if (currentSnd>=0)
			sound[currentSnd].stop();
		if (sndNum>=0)
			if (loop)
				sound[sndNum].loop();
			else
				sound[sndNum].play();
		currentSnd=sndNum;
	}

	public void preparePresentation()
	{
		windowHeight=0;
		windowStep=2;
		paintBackground(0);
		runMode=1;
		for (i=0;i<maxObj;i++)
			objType[i]=0;
		objCount=0;
		activateObject(128,240,3,0,0,128,true); // Activate dummy spaceship
		level=9;
		lives=4;
		score=0;
		newScore=0;
		scoreDG.drawImage(clearDisp,0,0,this);
		showLights=true;
		drawScore=true;
	}

	public void paintBackground(int brickNum) // Fill the background with given brick
	{
		for (j=0;j<9;j++)
			defaultSG.drawImage(bricks[brickIm[brickNum]],j*32,0,this);
		modSG.drawImage(defaultStrip,0,0,this);
		for (j=0;j<19;j++)
			landscapeG.drawImage(defaultStrip,0,j*32,this);
		for (j=0;j<maxFix;j++)
			fixQueueBrick[j]=0;
	}

	public void scrollScreen() // Move screen window and project onto offImage
	{
		windowHeight-=windowStep;
		if (windowHeight<0)
			windowHeight+=320;
		offGraphics.drawImage(landscape,0,-windowHeight,this);
	}

	public void drawObjects() // Draw each object onto offImage
	{
		int i,j,k;
		for (i=0;i<objCount;i++)
		{
			sortBuf1[i]=i;
			sortBuf2[i]=objX[i];
		}
		for (i=0;i<(objCount-1);i++)
			for (j=i;j<objCount;j++)
				if (sortBuf2[j]>sortBuf2[j+1])
				{
					k=sortBuf1[j];
					sortBuf1[j]=sortBuf1[j+1];
					sortBuf1[j]=k;
					k=sortBuf2[j];
					sortBuf2[j]=sortBuf2[j+1];
					sortBuf2[j]=k;
				}
		for (i=0;i<objCount;i++)
			if ((objType[sortBuf1[i]]>0) && objDraw[sortBuf1[i]])
				offGraphics.drawImage(objects[objNum[objLook[sortBuf1[i]]]],objX[sortBuf1[i]],
					objY[sortBuf1[i]],this);
	}

	public void activateObject(int x,int y,int type,int mode,int look,
		int par, boolean drawIt) // Start up a new live object
	{
		objX[objCount]=x;
		objY[objCount]=y;
		objLook[objCount]=look;
		objMode[objCount]=mode;
		objDraw[objCount]=drawIt;
		objType[objCount]=type;
		objPar[objCount]=par;
		objCount++;
	}

	public void smallUpdates() // Make requested small modifications of landscape
	{
		int i;
		if (fixQueueBrick[0]>0)
		{
			landscapeG.drawImage(bricks[brickIm[fixQueueBrick[0]]],fixQueueX[0],
				fixQueueY[0],this);
			fixQueueBrick[0]=0;
			i=1;
			while (fixQueueBrick[i]>0)
			{
				fixQueueX[i-1]=fixQueueX[i];
				fixQueueY[i-1]=fixQueueY[i];
				fixQueueBrick[i-1]=fixQueueBrick[i];
				fixQueueBrick[i]=0;
				i++;
			}
		}
		else if (newScore>score)
		{
			score=newScore;
			scoreDG.drawImage(clearDisp,0,0,this);
			scoreDG.drawString(String.valueOf(score),2,11);
			drawScore=true;
		}
	}

	public void blastBrick(int pos) // Draw destroyed brick (or rather place in queue)
	{
		int i,j;
		for (i=0;i<blastable;i++)
			if (blastBrick[i]==brickMap[pos])
			{
				newScore+=brickPoints[i];
				j=0;
				while (fixQueueBrick[j]>0)
					j++;
				fixQueueBrick[j]=blastBrick[i]+1;
				brickMap[pos]=blastBrick[i]+1;
				fixQueueX[j]=32*(pos % 9);
				fixQueueY[j]=32*(pos/9-1);
				if (pos<90) // We need one update for bottom half too
				{
					brickMap[pos+90]=blastBrick[i]+1;
					fixQueueBrick[j+1]=blastBrick[i]+1;
					fixQueueX[j+1]=fixQueueX[j];
					fixQueueY[j+1]=fixQueueY[j]+320;
				}
				i=blastable;
			}
	}

	public void placeBricks() // Choose bricks to distribute randomly onto modStrip, then enemies
	{
		int i,j,k,l;
		double d;
		i=(int)(m.random()*(1+bricksPerLine[level]));
		for (j=0;j<9;j++)
			brickArea[j]=0;
		for (j=0;j<i;j++)
		{
			k=(int)(m.random()*9);
			while (brickArea[k]>0)
				k=(int)(m.random()*9);
			d=m.random();
			l=0;
			while (d>randBrick[maxOtherBricks*level+l])
				l++;
			brickArea[k]=levBrick[level*maxOtherBricks+l];
		}
		j=0;
		for (i=0;i<9;i++)
			if (brickArea[i]>0)
			{
				brickPos[j]=i;
				brickType[j]=brickArea[i];
				j++;
			}
		i=(int)(m.random()*9); // And now for the enemies
		if ((brickQual[brickMap[9*((32+windowHeight-24)/32)+i]]==0)&&(objCount<5)&&(m.random()<0.3))
		{
			d=m.random();
			l=0;
			while (d>randEnemy[maxEnemies*level+l])
				l++;
			activateObject(i*32,-24,levEnemy[level*maxEnemies+l],0,0,0,true);
		}
	}

	public void initiatePlaying() // Prepares for start of new game or level
	{
		windowHeight=0;
		windowStep=4;
		paintBackground(levBackgr[level]);
		for (i=0;i<9;i++)
			brickArea[i]=0;
		for (i=0;i<22*9;i++)
			brickMap[i]=0;
		for (i=0;i<5;i++)
			brickPos[i]=-1;
		for (i=0;i<maxObj;i++)
			objType[i]=0;
		objCount=0;
		activateObject(128,240,1,0,0,0,true);
		scoreDG.drawImage(clearDisp,0,0,this);
		scoreDG.drawString(String.valueOf(score),2,11);
		bulletOn=false;
	}

	public void loadBalance(int cyclic) // Do timed parts of the graphics updating
	{
		switch (cyclic)
		{
			case 0:
				landscapeG.drawImage(modStrip,0,32*((windowHeight/32)-1),this);
				for (i=0;i<9;i++)
					brickMap[9*(windowHeight/32)+i]=brickArea[i];
				break;
			case 1:
				landscapeG.drawImage(modStrip,0,32*((windowHeight/32)+9),this);
				for (i=0;i<9;i++)
					brickMap[90+9*(windowHeight/32)+i]=brickArea[i];
				break;
			case 6:
				for (i=0;i<5;i++)	// Clear places for bricks
					brickPos[i]=-1;
				if (mileage<levLength[level])
					placeBricks();
				else
					for (i=0;i<9;i++)
						brickArea[i]=0;
				break;
			case 7:
				modSG.drawImage(defaultStrip,0,0,this);
				break;
			default:
				break;
		}
		if ((cyclic>1)&&(cyclic<7))
			if (brickPos[6-cyclic]>=0)
					modSG.drawImage(bricks[brickIm[brickType[6-cyclic]]],
						brickPos[6-cyclic]*32,0,this);
				else
					smallUpdates();
	}

	public void sortObjects() // Called after deleting an object
	{
		int lastEmpty,lastExisting,j;
		lastEmpty=-1;
		lastExisting=-1;
		for (j=0;j<maxObj;j++)
		{
			if (objType[j]==0)
			{
				if (lastEmpty<0)
					lastEmpty=j;
			}
			else
			{
				if (lastEmpty<0)
				{
					lastExisting=j;
				}
				else
				{
					objX[lastEmpty]=objX[j];
					objY[lastEmpty]=objY[j];
					objType[lastEmpty]=objType[j];
					objLook[lastEmpty]=objLook[j];
					objMode[lastEmpty]=objMode[j];
					objDraw[lastEmpty]=objDraw[j];
					objPar[lastEmpty]=objPar[j];
					objType[j]=0;
					j=lastEmpty;
					lastExisting=j;
					lastEmpty=-1;
				}
			}
		}
		objCount=lastExisting+1;
	}

	public void run()
	{
		int i;
		while (updateThread !=null)
		{
			try
			{
				updateThread.sleep(m.max(startTime-System.currentTimeMillis(),20));
			} catch (InterruptedException e) {}
			startTime=System.currentTimeMillis()+70;
			counter++;
			switch (runMode)
			{
				case 1: // Presentation loop
					scrollScreen();
					offGraphics.drawImage(logo,24,50,this);
					handleObjects();
					drawObjects();
					if ((currentKey==32)||((currentKey>64)&&(currentKey<70)))
					{
						if ((currentKey>64)&&(currentKey<70))
							level=currentKey-65;
						else
							level=0;
						runMode=3; // Prepare for playing
						offGraphics.setColor(Color.black);
						counter=-1;
					}
					break;
				case 2: // Main game loop
					loadBalance((windowHeight & 28)/4);
					scrollScreen();
					handleObjects();
					drawObjects();
					mileage++;
					if (mileage>levLength[level]+80) // Prepare to finish and exit level
					{
						handleSound(2,true);
						runMode=4;
						tempScore=newScore+500;
						objType[0]=6;
						windowStep=0;
					}
					break;
				case 3: // Prepare for playing, clear screen slowly
					offGraphics.drawImage(clearScreen,0,counter*8,this);
					offGraphics.drawImage(clearScreen,0,280-counter*8,this);
					if (counter>17)
					{
						lives=3; // Initial set-up
						score=0;
						newScore=0;
						counter=0;
						getLevelGraphics(level);
						System.gc();
						initiatePlaying();
						runMode=2;
						showLights=true;
						drawScore=true;
					}
					break;
				case 4: // Finish level
					scrollScreen();
					handleObjects();						
					if (newScore<tempScore) // Ticking bonus
						newScore+=50;
					smallUpdates();
					drawObjects();
					if (objY[0]<=-32)
					{
						runMode=5;
						counter=-1;
						level=(level+1)% maxLev;
						mileage=0;
						handleSound(-1,false);
					}
					break;
				case 5: // Clear up and start again, possibly on new level
					offGraphics.drawImage(clearScreen,0,counter*8,this);
					offGraphics.drawImage(clearScreen,0,280-counter*8,this);
					if (counter>17)
					{
						if (lives>=0)
						{
							getLevelGraphics(level);
							System.gc();
							initiatePlaying();
							runMode=2;
							showLights=true;
							drawScore=true;
						}
						else // Game over?
						{
							counter=0;
							p=getFontMetrics(fo).stringWidth("GAME OVER");
							offGraphics.setColor(Color.white);
							offGraphics.drawString("GAME OVER",144-p/2,140);
							offGraphics.setColor(Color.black);
							runMode=6;
						}
					}
					break;
				case 6: // Game over message
					if (counter>50)
						if (score<=highScores[4])
						{
							mileage=0;
							preparePresentation();
						}
						else // New highscore
						{
							landscapeG.setColor(Color.black);
							landscapeG.fillRect(0,0,288,510);
							landscapeG.setColor(Color.white);
							p=getFontMetrics(fo).stringWidth("GAME OVER");
							landscapeG.drawString("GAME OVER",144-p/2,140);
							highScores[4]=score;
							q=4;
							for (i=3;i>=0;i--)
								if (score>highScores[i])
								{
									highScores[i+1]=highScores[i];
									highScores[i]=score;
									q=i;
								}
							p=getFontMetrics(fo).stringWidth("Current Highscores");
							landscapeG.drawString("Current Highscores",144-p/2,308);
							for (i=0;i<5;i++)
							{
								if (i==q)
									landscapeG.setColor(Color.yellow);
								else
									landscapeG.setColor(Color.white);
								landscapeG.drawString(""+(i+1)+".",100,340+i*27);
								p=getFontMetrics(fo).stringWidth(String.valueOf(
									highScores[i]));
								landscapeG.drawString(""+highScores[i],188-p,340+i*27);
							}
							windowHeight=0;
							windowStep=-4;
							counter=0;
							runMode=7;
							score=0;
						}
					break;
				case 7: // Scroll in highscore table
					scrollScreen();
					if (counter>54)
					{
						counter=0;
						runMode=6; // Go back to waiting
					}
					break;
				default:
					break;
			}

			repaint();
		}
	}

	public void start()
	{
		if (updateThread==null)
		{
			updateThread=new Thread(this,"Game");
			updateThread.start();
			startTime=System.currentTimeMillis();
		}
	}

	public void stop()
	{
		if ((updateThread!=null)&&(updateThread.isAlive()))
		{
			updateThread.stop();
		}
		updateThread=null;
	}

	public boolean keyDown(java.awt.Event e,int key)
	{
		currentKey=key;
		return false;
	}
	
	public boolean keyUp(java.awt.Event e,int key)
	{
		currentKey=0;
		return false;
	}

	public void handleObjects() // Control non-stationary stuff
	{
		int i,j,k,mapPos;
		boolean upFree,downFree,leftFree,rightFree;
		for (i=0;i<objCount;i++)
		{			
			switch (objType[i])
			{
				case 1: // Spaceship
					mapPos=checkTouching(i);
					objLook[i]=0;
					if ((currentKey==106)&&(objX[i]>0)) // Go left
					{
						objX[i]-=8;
						objLook[i]=1;
					}
					else if ((currentKey==108)&&(objX[i]<256)) // Go right
					{
						objX[i]+=8;
						objLook[i]=2;
					}
					if ((currentKey==107)&&(!bulletOn)) // Fire
					{
						activateObject(objX[i]+8,objY[i],2,0,3,0,true);
						bulletOn=true;
						handleSound(0,false);
					}
					if (touching[0]+touching[1]+touching[2]+touching[3]>0)
						blowUpShip();
					break;
				case 2: // Friendly bullet
					mapPos=checkTouching(i);
					bulletIx=i;
					if (touching[0]+touching[1]>0) // Smashed into something?
					{
						if ((touching[0] & 2)>0) // Destructible?
						{
							objType[i]=5;
							objX[i]-=8;
							objY[i]-=16;
							objPar[i]=8;
							objLook[i]=6;
							blastBrick(mapPos);
							handleSound(1,false);
						}
						else if ((touching[1] & 2)>0)
						{
							objType[i]=5;
							objX[i]-=8;
							objY[i]-=16;
							objPar[i]=8;
							objLook[i]=6;
							blastBrick(mapPos+1);
							handleSound(1,false);
						}
						else // No? Just remove bullet
						{
							objType[i]=0;
							bulletOn=false;
						}						
					}
					else // Keep going up
					{
						objY[i]-=16;
						if (objY[i]<0)
						{
							objType[i]=0;
							bulletOn=false;
						}
					}
					break;
				case 3: // Dummy ship (used in intro)
					if (objX[i]<objPar[i])
					{
						objX[i]+=8;
						objLook[i]=1;
					}
					else if (objX[i]>objPar[i])
					{
						objX[i]-=8;
						objLook[i]=2;
					}
					else
						objLook[i]=0;
					if (m.random()<0.1)
						objPar[i]=8*(1+(int)(m.random()*30.49));
					if ((objCount<3)&&(m.random()<0.1)) // Dummy fire
						activateObject(objX[i]+8,objY[i],4,0,3,0,true);
					break;
				case 4: // Dummy bullet
					objY[i]-=18;
					if (objY[i]<0)
					{
						objType[i]=0;
					}
					break;
				case 5: // Exploding friendly bullet
					objLook[i]++;
					objY[i]+=windowStep;
					if (objLook[i]>objPar[i])
					{
						objType[i]=0;
						bulletOn=false;
					}
					break;
				case 6: // Exiting ship (also rather dummy)
					if (objX[i]<128)
					{
						objX[i]+=8;
						objLook[i]=1;
					}
					else if (objX[i]>128)
					{
						objX[i]-=8;
						objLook[i]=2;
					}
					else
						objLook[i]=0;
					objY[i]-=4;
					break;
				case 7: // Spiked ball type
					objY[i]+=windowStep;
					mapPos=9*((32+windowHeight+objY[i])/32)+objX[i]/32;
					if ((objX[i]&31)+((objY[i]+windowHeight)&31)==0)
					{
						downFree=(brickQual[brickMap[mapPos+9]]==0);
						leftFree=((brickQual[brickMap[mapPos-1]]==0)&&(objX[i]>0));
						rightFree=((brickQual[brickMap[mapPos+1]]==0)&&(objX[i]<256));
						if (objMode[i]==0)
						{
							if (downFree)
								objMode[i]=2;
							else
							{
								if (leftFree && rightFree)
									objMode[i]=(int)(3+2*m.random());
								else if (leftFree)
									objMode[i]=3;
								else if (rightFree)
									objMode[i]=4;
							}
						}
						else
						{
							if (((objMode[i]==2)&&(!downFree))||
								((objMode[i]==3)&&(!leftFree))||
								((objMode[i]==4)&&(!rightFree)))
								objMode[i]=0;
						}
					}
					switch(objMode[i])
					{
						case 2:
							objY[i]+=4;
							break;
						case 3:
							objX[i]-=4;
							break;
						case 4:
							objX[i]+=4;
							break;
						default:
							break;
					}
					animateObject(i,0);
					if (objY[i]>287)	// Vanished off screen
					{
						objType[i]=0;
					}
					else
						checkForHit(i);
					break;
				case 8: // Tank type
					objY[i]+=windowStep;
					mapPos=9*((32+windowHeight+objY[i])/32)+objX[i]/32;
					if ((objX[i]&31)==0)
					{
						leftFree=((brickQual[brickMap[mapPos-1]]==0)&&(objX[i]>0));
						rightFree=((brickQual[brickMap[mapPos+1]]==0)&&(objX[i]<256));
						if (objMode[i]==0)
						{
							if (leftFree && rightFree)
								objMode[i]=(int)(3+2*m.random());
							else if (leftFree)
								objMode[i]=3;
							else if (rightFree)
								objMode[i]=4;
						}
						else
						{
							if (((objMode[i]==3)&&(!leftFree))||
								((objMode[i]==4)&&(!rightFree)))
								objMode[i]=0;
						}
					}
					if ((objCount<5)&&(objX[i]-objX[0]<40)&&(objX[0]-objX[i]<40)&&
						(m.random()<0.12)) // Fire!
					{
						activateObject(objX[i]+8,objY[i]+12,9,0,4,0,true);
						animateObject(i,5);
						handleSound(0,false);
						objPar[i]=3; // Busy shooting for 3 cycles
					}
					if (objPar[i]==0) // Not busy
					{
						switch(objMode[i])
						{
							case 3:
								objX[i]-=4;
								break;
							case 4:
								objX[i]+=4;
								break;
							default:
								break;
						}
						animateObject(i,objMode[i]);
					}
					else
						objPar[i]--;
					if (objY[i]>287)	// Vanished off screen
					{
						objType[i]=0;
					}
					else
						checkForHit(i);
					break;
				case 9: // Enemy bullet
					objY[i]+=20;
					if (objPar[i]==0) // Start as nozzle explosion
					{
						objLook[i]=5;
						objPar[i]=1;
					}
					else
						objLook[i]=4;
					mapPos=checkTouching(i);
					if ((objType[0]==1)&&((objX[i]-objX[0])*(objX[i]-objX[0])+
						(objY[i]-objY[0])*(objY[i]-objY[0])<500)) // Hit ship?
					{
						blowUpShip();
						objType[i]=0;
					}
					if ((touching[0]+touching[1]+touching[2]+touching[3]>0)||
						(objY[i]>287)) // Crashed or off screen?
					{
						objType[i]=0; // Remove bullet		
					}
					break;
				case 10: // Exploding enemy
					objLook[i]++;
					objY[i]+=windowStep; // Done exploding?
					if (objLook[i]>objPar[i])
						objType[i]=0;
					break;
				case 11: // Exploding self
					objX[i]+=(int)(10*m.cos(objMode[i]));
					objY[i]-=(int)(10*m.sin(objMode[i]));
					if (objPar[i]<6)
						objLook[i]=6+objPar[i]/2;
					else
						objDraw[i]=false;
					objPar[i]++;
					if ((objPar[i]>12)&&(runMode==2))
					{
						objType[i]=0;
						counter=-1;
						lives--;
						mileage=m.max(mileage,0);
						runMode=5;
						showLights=true;
					}
					break;
				case 12: // Homing
					objY[i]+=(windowStep+2);
					if ((objX[i]<objX[0])&&(objPar[i]<10))
						objPar[i]+=2;
					else if ((objX[i]>objX[0])&&(objPar[i]>-10))
						objPar[i]-=2;
					objX[i]+=objPar[i];
					animateObject(i,0);
					if (objY[i]>287)	// Vanished off screen
						objType[i]=0;
					else
						checkForHit(i);
					break;
				case 13: // Dragonfly type
					objY[i]+=(windowStep+8);
					animateObject(i,0);
					if (objY[i]>287)	// Vanished off screen
						objType[i]=0;
					else
						checkForHit(i);
					break;
				case 14: // Shark type
					objY[i]+=windowStep;
					mapPos=9*((32+windowHeight+objY[i])/32);
					if (objPar[i]==0)
					{
						objDraw[i]=false;
						j=(int)(m.random()*8);
						if ((j!=(objX[i]/32))&&(brickQual[brickMap[mapPos+j]]==0))
						{
							objPar[i]=16; // Emerge from the depth
							objX[i]=32*j;
							objDraw[i]=true;
						}
					}
					if (objPar[i]>0)
					{
						objPar[i]--;
						if (objPar[i]>0)
						{
							animateObject(i,objPar[i]-1);
							checkForHit(i);
						}
						else
							objDraw[i]=false;
					}
					if (objY[i]>287)	// Vanished off screen
						objType[i]=0;
					break;
				case 15: // Moving right-left type
					objY[i]+=windowStep;
					mapPos=9*((32+windowHeight+objY[i])/32)+objX[i]/32;
					if ((objX[i]&31)==0)
					{
						leftFree=((brickQual[brickMap[mapPos-1]]==0)&&(objX[i]>0));
						rightFree=((brickQual[brickMap[mapPos+1]]==0)&&(objX[i]<256));
						if (objMode[i]==0)
						{
							if (leftFree && rightFree)
								objMode[i]=(int)(3+2*m.random());
							else if (leftFree)
								objMode[i]=3;
							else if (rightFree)
								objMode[i]=4;
						}
						else
						{
							if (((objMode[i]==3)&&(!leftFree))||
								((objMode[i]==4)&&(!rightFree)))
								objMode[i]=0;
						}
					}
					switch(objMode[i])
					{
						case 3:
							objX[i]-=4;
							break;
						case 4:
							objX[i]+=4;
							break;
						default:
							break;
					}
					animateObject(i,objMode[i]);

					if (objY[i]>287)	// Vanished off screen
					{
						objType[i]=0;
					}
					else
						checkForHit(i);
					break;
				case 16: // Saucer type
					objY[i]+=(windowStep+6);
					animateObject(i,0);
					if ((objCount<5)&&(m.random()<0.08)) // Fire!
					{
						activateObject(objX[i]+8,objY[i]+8,17,0,88,8*(1-(counter&2)),
							true);
						handleSound(0,false);
					}
					if (objY[i]>287)	// Vanished off screen
						objType[i]=0;
					else
						checkForHit(i);
					break;
				case 17: // Enemy fireball
					objY[i]+=(windowStep+5);
					animateObject(i,0);
					if ((objX[i]<(objX[0]+12))&&(objPar[i]<10))
						objPar[i]+=2;
					else if ((objX[i]>objX[0])&&(objPar[i]>-10))
						objPar[i]-=2;
					objX[i]+=objPar[i];
					if (objY[i]>287)	// Vanished off screen
						objType[i]=0;
					else if ((objType[0]==1)&&((objX[i]-objX[0])*(objX[i]-objX[0])+
						(objY[i]-objY[0])*(objY[i]-objY[0])<500))
					{
						blowUpShip(); // Fireball collided with spaceship
						objType[i]=0;
					}
					break;						
				default:
					break;
			}
		}
		sortObjects();
	}

	public void animateObject(int i, int action) // Give the enemy the right look
	{
		switch(objType[i])
		{
			case 7:
				switch(level)
				{
					case 0: // Spiked ball
						objLook[i]=13+(counter%3);
						break;
					case 3: // Spinning cube
						objLook[i]=40+(counter%3);
						break;
					case 5: // Beach ball
						objLook[i]=57+(counter&3);
						break;
					case 6: // Snowman
						objLook[i]=70+(counter&3);
						break;
					case 7: // Killer tomato
						objLook[i]=74+cyclic2[counter%6];
						break;
					default:
						break;
				}
				break;
			case 8:
				switch(level)
				{
					case 0: // Tank
						objLook[i]=16+((objX[i]/4)%3);
						break;
					case 1: // Bazooka bird
						if (action==3) // Left
							objLook[i]=22+cyclic[(objX[i]/4)%3];
						else if (action==4) // Right
							objLook[i]=25+cyclic[(objX[i]/4)%3];
						else // Fire
							objLook[i]=28;
						break;
					case 4: // Eyeball
						if (action==3) // Left
							objLook[i]=44;
						else if (action==4) // Right
							objLook[i]=45;
						else // Fire
							objLook[i]=43;
						break;
					case 6: // Fire
						objLook[i]=67+(counter%3);
						break;
					case 8: // Rolling cannon
						objLook[i]=82+((objX[i]/4)%3);
						break;
					default:
						break;
				}
				break;
			case 12:
				switch(level)
				{
					case 1: // Homing bird
						objLook[i]=19+(counter%3);
						break;
					default:
						break;
				}
				break;
			case 13:
				switch(level)
				{
					case 2: // Dragonfly
						objLook[i]=29+(counter&1);
						break;
					case 5: // Seagull
						objLook[i]=56;
						break;
					default:
						break;
				}
				break;
			case 14:
				switch(level)
				{
					case 2: // Shark
						objLook[i]=shark[action];
						break;
					case 4: // Groundmouth
						objLook[i]=groundMouth[action];
						break;
					default:
						break;
				}
				break;
			case 15:
				switch(level)
				{
					case 3: // Rolling cube
						objLook[i]=36+((objX[i]/4)&3);
						break;
					case 5: // Crab
						objLook[i]=53+cyclic[(objX[i]/4)&3];
						break;
					case 6: // Penguin
						if (action==3)
							objLook[i]=61+cyclic[counter&3];
						else
							objLook[i]=64+cyclic[counter&3];
						break;
					case 7: // Vegetable
						objLook[i]=78+((objX[i]/4)&3);
						break;
					default:
						break;
				}
				break;
			case 16: // Saucer
				objLook[i]=85+(counter%3);
				break;
			case 17: // Fireball
				objLook[i]=88+(counter&1);
				break;
			default:
				break;
		}
	}

	public int checkTouching(int i) // Used by handleObjects()
	{
		int mapPos;
		touching[1]=0;
		touching[2]=0;
		touching[3]=0;
		mapPos=9*((32+windowHeight+objY[i])/32)+objX[i]/32; // Check what's under it
		touching[0]=brickQual[brickMap[mapPos]]&3;
		if (((objX[i]&31)+objImPractW[objLook[i]])>32)
		{
			touching[1]=brickQual[brickMap[mapPos+1]]&3;
			if ((((objY[i]+windowHeight)&31)+objImH[objLook[i]])>32)
				touching[3]=brickQual[brickMap[mapPos+10]]&3;
		}
		if ((((objY[i]+windowHeight)&31)+objImH[objLook[i]])>32)
			touching[2]=brickQual[brickMap[mapPos+9]]&3;
		return mapPos;

	}

	public void checkForHit(int i) // Used by handleObjects().
	{
		int j;
		if ((objType[bulletIx]==2)&&((objX[i]-objX[bulletIx])*
			(objX[i]-objX[bulletIx])+(objY[i]-objY[bulletIx])*
			(objY[i]-objY[bulletIx])<570))
		{
			for (j=0;j<maxEnemies;j++) // Enemy hit by bullet?
				if (objType[i]==levEnemy[j+maxEnemies*level])
					newScore+=enemyPoints[j+maxEnemies*level];
			objType[bulletIx]=0;
			bulletOn=false;
			objType[i]=10;
			objPar[i]=12;
			objLook[i]=9;
			handleSound(1,false);
		}
		else if ((objType[0]==1)&&((objX[i]-objX[0])*(objX[i]-objX[0])+
			(objY[i]-objY[0])*(objY[i]-objY[0])<1000))
		{
			blowUpShip(); // Enemy collided with spaceship
			objType[i]=10;
			objPar[i]=12;
			objLook[i]=9;
		}
	}

	public void blowUpShip()
	{
		objType[0]=0;
		if (bulletOn)
			objType[bulletIx]=0;
		activateObject(objX[0],objY[0],11,(int)(m.random()*180),6,0,true);
		activateObject(objX[0],objY[0],11,(int)(m.random()*180),6,0,true);
		activateObject(objX[0],objY[0],11,(int)(m.random()*180),6,0,true);
		mileage-=96;
		handleSound(1,false);
	}

	public void paint(Graphics g) // Draw the control panel and stuff
	{
		g.drawImage(panel,0,304,this);
		g.drawImage(outline[3],0,0,this);
		g.drawImage(outline[4],304,0,this);
		for (i=0;i<9;i++)
			g.drawImage(outline[2],16+i*32,0,this);
		for (i=0;i<9;i++)
		{
			g.drawImage(outline[0],0,16+i*32,this);
			if (i==3)
				g.drawImage(outline[1],304,16+i*32,this);
			else
				g.drawImage(outline[0],304,16+i*32,this);
		}
		showLights=true;
		drawScore=true;
		update(g);		
	}

	public void update(Graphics g)
	{
		g.drawImage(offImage,16,16,this);
		if (showLights) // Draw lives & level indicators
		{
			for (q=0;q<9;q++)
			{
				if (q<level)
					g.setColor(Color.white);
				else if (q==level)
					g.setColor(Color.yellow);
				else
					g.setColor(Color.red);
				g.fillRect(20+11*q,326,5,10);
			}
			for (q=0;q<4;q++)
			{
				if (q<lives)
					g.setColor(Color.white);
				else if (q==lives)
					g.setColor(Color.yellow);
				else
					g.setColor(Color.red);
				g.fillOval(143+15*q,342,8,8);
			}
			showLights=false;
		}
		if (drawScore)
		{
			g.drawImage(scoreDisp,214,321,this);
			drawScore=false;
		}
	}
}
