/**
 * Created by: Joey Brunner
 *             Jeff Bush
 *  
 * GravityWorm.java
 * base on the GameTemplate class
 * developed by Mr Ruth 03/22/03
 * 
 * It's a really fun game!
 * make sure you tell it the dimensions in the HTML file
 */

import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;

/**
 *  GravityWorm is set up to be 400 x 300 (width x height)
 *  these values come from the <Applet> tag in the .html file
 */
 
public class GravityWorm extends Applet 
		implements Runnable, MouseListener, KeyListener
{
//constants:

// == YOUR CODE GOES HERE ==  create any necessary constants
	private final int NUMLINES = 100; //number of lines on horizontal axis
	private final int LINEWIDTH = 4; //line width horizontally
	private final int HEADLINEINDEX = 30; //the index where the head of the snake is
	private final int MAXVELOCITY = 5; //top falling/rising speed

//fields:
	private Thread thread;//acts as a 'timer'
	private Image imageBuffer;//image gets painted to Applet in update()
	private Graphics panel;//draw primatives to panel
	
	private boolean started,finished;//keep track of the status of the game
	              //three game states: before start, running, game over
	private boolean mousePushed;//handle mouse events
	
	private int count;//master count of how many times the 'timer' fires
	
	private int DELAY = 45;//delay in milliseconds
	
// == YOUR CODE GOES HERE ==  create any necessary fields
	private int score; //the curret score
	private int hiscore; //the best score this session
	private int tunnelTop[]; //array of tunnel top positions
	private int tunnelBottom[]; //array of bottom top positions
	private int wormPos[]; //array of positions
	private int block[]; //if -1, then there is no block at that line
	private int bonusLoc[];
	private int bonusType[];
	private int blockHeight;
	private int velocity;
	private int changeTop; //length of top slope
	private int goingUpTop; //if slope is up
	private int changeBottom; //length of bottom slope
	private int goingUpBottom; //if slope is up
	private int level;
	
//methods:
//-----------------------------------------------------------------------------
	public void init()
	{	
		//initialize imageBuffer and panel to the size of the Applet 
		// note: size of Applet determined in <Applet> tag in .html file
		imageBuffer = createImage(400,300);//must be changed if the <Applet> tag changes
		panel = imageBuffer.getGraphics();
		
		count = 0;//actual number of DELAYs in time
		started = finished = false;
		mousePushed = false;

// == YOUR CODE GOES HERE ==  initialize your fields
	
		//setup variables
		tunnelTop = new int[NUMLINES];
		tunnelBottom = new int[NUMLINES];
		wormPos = new int[HEADLINEINDEX + 1];
		block = new int[NUMLINES];
		bonusLoc = new int[NUMLINES];
		bonusType = new int[NUMLINES];
		hiscore = 0;
		
		addMouseListener(this);//needed to handle mouse events

	}//end init()


/** handles the double buffering rendering of the Applet
 *  ... draws the panel to the Applet
 */
	public void update(Graphics g)
	{
		if (!started)
			gameStart();
		else 
			move();	
		g.drawImage(imageBuffer,0,0,this);//flush the imageBuffer to the Applet
	}

//-----------------------------------------------------------------------------
/** handles the initialization of fields
 *  that might need to be reinitialized after the game
 *  has been played once, but will be played again
 *  without the Applet being reloaded
 *  (i.e. set score to 0 but leave hiScore alone, etc.)
 *  it also handles drawing any instructions for 
 *  game play to the user
 */
	public void gameStart()
	{
		started = finished = mousePushed = false;
		//game has not started, it is not finished,
		//and the mouse has not been pushed
		count = 0;//init master count to 0 to start game...

// == YOUR CODE GOES HERE ==  initialize your fields

		score = 0; //reset score
		for (int i = 0; i < NUMLINES; i++) {
			tunnelTop[i] = 20; //create tunnel for beginning of game
			tunnelBottom[i] = 180;
			block[i] = -blockHeight;
			bonusLoc[i] = -1; //create bonus (none) for beginning of game
			bonusType[i] = 0;
		}
		for (int i = 0; i <= HEADLINEINDEX; i++)
			wormPos[i] = 98; //create worm for beginning of game
		velocity = 0; //going striaght
		blockHeight = 40; //starting height
		DELAY = 50; //default delay
		changeTop = 5; //length of slope of top at beginning of game
		goingUpTop = -1; //slope is down
		changeBottom = 5;  //length of slope of bottom at beginning of game
		goingUpBottom = -1; //slope is down
		level = 1; //starting level
		draw();//draw the initial scene ( values are set in init() )
		panel.setColor(Color.blue);
		panel.drawString("CLICK MOUSE TO START!" , 50, 50);
		repaint(); //updates
	}

//-----------------------------------------------------------------------------
/** handles the mouse events....
 *  at every increment... if the mouse is pressed
 *  then the mousePushed field are set to true..
 *  ( it also starts the game by setting started to true )
 */
	public void mousePressed(MouseEvent e)
	{
		started = true;
		mousePushed=true;
	}

//-----------------------------------------------------------------------------
/** handles the mouse events....
 *  at every increment... if the mouse has been released
 *  then the mousePushed field is set to false..
 */
  public void mouseReleased(MouseEvent e)
	{
		mousePushed=false;
  }

//-----------------------------------------------------------------------------
/**
 * checks the game state..
 * if not finished means game is running...
 *   then update all necessary fields for the move 
 * else the game is over... then it can be started again)
 * move checks to see if the mouse has been pushed
 *  then updates all the appropriate data accordingly
 * move() is only called if the game has started ( see update() )
 */	
	public void move()
	{	
		if ( !finished) //game is running
		{
			// == YOUR CODE GOES HERE ==  update all fields accordingly
			for (int i = 0; i < NUMLINES - 1; i++) {
				tunnelTop[i] = tunnelTop[i+1]; //move all arrays down by one
				tunnelBottom[i] = tunnelBottom[i+1];
				block[i] = block[i+1];
				bonusLoc[i] = bonusLoc[i+1];
				bonusType[i] = bonusType[i+1];
			}
			
			for (int i = 0; i < HEADLINEINDEX; i++)
				wormPos[i] = wormPos[i+1]; //move worm down by one
	
			//Get falling rraising speed
			if (mousePushed) // if mouse is pushed, going up
				velocity += 1;
			else  // otherwise falling
				velocity -= 1;
			if (velocity < -MAXVELOCITY) //if we are falling faster then max, readjust
				velocity = -MAXVELOCITY;
			if (velocity > MAXVELOCITY)//if we are raising faster then max, readjust
				velocity = MAXVELOCITY;
			
			//New tunnel space:
			//New tunnel top
			if (goingUpTop == 1) //if going up
				tunnelTop[NUMLINES - 1] += 1; //tunnel top goes up
			else //if going down
				tunnelTop[NUMLINES - 1] -= 1; //tuneel top goes down
			changeTop--; //length of slope decreases by one
			if (changeTop <= 0 || tunnelTop[NUMLINES - 1] <= 1) { 
				//if slope is done or if tunnel is off-screen
				changeTop = (int)(8.0*Math.random()) + 2; //new slope length
				goingUpTop *= -1; //switch directions
			}
			//New tunnel bottom
			if (goingUpBottom == 1) //if going up
				tunnelBottom[NUMLINES - 1] += 1; //tunnel bottom goes up
			else //if going down
				tunnelBottom[NUMLINES - 1] -= 1; //tunnel bottom goes down
			changeBottom--; //length of slope decreases by one
			if (changeBottom <= 00 || tunnelBottom[NUMLINES - 1] >= 199) {
				//if slope is done or if tunnel is off-screen
				changeBottom = (int)(8.0*Math.random()) + 2; //new slope length
				goingUpBottom *= -1; //switch directions
			}
			
			//New block space
			if (Math.random() <= .1) //probability of a new block is 1/10
				block[NUMLINES - 1] = (int)(200.0 * Math.random()); //make the new block
			else
				block[NUMLINES - 1] = -blockHeight; //make off-screen block
			
			//New bonus space
			if (Math.random() <= .05) { //probability of a new bonus is 5/100
				bonusLoc[NUMLINES - 1] = (int)(200.0 * Math.random()); //new bonus
				bonusType[NUMLINES - 1] = (int)(5.0 * Math.random()) + 1; //bonus type
			} else {
				bonusLoc[NUMLINES - 1] = -1; //turn off bonus
				bonusType[NUMLINES - 1] = 0;
			}
			
			//New worm space
			wormPos[HEADLINEINDEX] -= velocity; //adjust for the velocity
			int head = wormPos[HEADLINEINDEX]; //get the head position
			if (head >= bonusLoc[HEADLINEINDEX] - LINEWIDTH //if head is at bonus
				&& head <= bonusLoc[HEADLINEINDEX] + LINEWIDTH * 3) {
				score += 15; //add to score
				switch (bonusType[HEADLINEINDEX]) { //which type of bonus?
					case 1: DELAY += 5; break; //Slower
					case 2: DELAY -= 10; break; //Faster
					case 3:  //Bomb 
					for (int i = 0; i < NUMLINES; i++)
						block[i] = -blockHeight;
					break;
					case 4: blockHeight -= 5; break; //Shrink
					case 5: blockHeight += 5; break; //Grow
				}
				bonusType[HEADLINEINDEX] = 0; //erase bonus
				bonusLoc[HEADLINEINDEX] = -1;
			}
			if (head <= tunnelTop[HEADLINEINDEX] //hit the roof
				|| head >= tunnelBottom[HEADLINEINDEX] //hit the floor
				|| (head >= block[HEADLINEINDEX] && //hit block
						head <= (block[HEADLINEINDEX] + blockHeight))) {
					finished = true; //GAME OVER
					score += count; //add the count to score
					count = 0; //reset count
			}
			
			//New level
			if (count > (100 * level)) { //every 100 increase level 
				level++; //increase level
				DELAY -= 5; //make it faster
				blockHeight += 5; //make them longer
				score += 50 * level; //add to score
			}
			draw(); //draw
		}
		else //finished .. (i.e. GAME OVER)
		{
			panel.setColor(Color.red);
			panel.drawString("GAME OVER" , 200, 240);
			if (score > hiscore) hiscore = score;
			//wait until user clicks mouse or 100 DELAY's, then reset game
			if ((mousePushed && count > 25) || count > 200)
				gameStart();
		}					
	}//end move()

//-----------------------------------------------------------------------------
/**
 * draws all necessary game elements to the panel
 */	
	public void draw()
	{	//draw black background rect for game grid (full width, 2/3 of height)
		panel.setColor(Color.black);
		panel.fillRect(0,0,400,200);
		
// == YOUR CODE GOES HERE ==  draw all necessary items.. 
//                            remove the count if necessary
		for (int i = 0; i < NUMLINES; i++) { //check for all bonuses
			if (bonusLoc[i] != -1) {
				switch (bonusType[i]) { //select type
					case 1: panel.setColor(Color.blue); break; //Slower
					case 2: panel.setColor(Color.red); break; //Faster
					case 3: panel.setColor(Color.orange); break; //Bomb
					case 4: panel.setColor(Color.yellow); break; //Shrink
					case 5: panel.setColor(Color.lightGray); break; //Grow
				}
				panel.fillRoundRect(i*LINEWIDTH-LINEWIDTH/2, //draw little sausage thingy
					bonusLoc[i]-LINEWIDTH/2,
					2*LINEWIDTH,4*LINEWIDTH,5,5);
			}
		}
		if (level == 1) panel.setColor(Color.green); //level one is green
		else if (level == 2) panel.setColor(new Color(50,200,50)); //2 is faded green
		else if (level == 3) panel.setColor(Color.lightGray); //3 is light gray
		else if (level == 4) panel.setColor(Color.darkGray); //4 is dark gray
		//levels 5-9 are multicolor bases on last bonus
		else if (level >= 10 && finished) panel.setColor(Color.white);//level 10+ is white after dead
		else if (level >= 10) panel.setColor(Color.black);//otherwise black
		
		for (int i = 0; i < NUMLINES; i++) { //draws the scene (blocks, roof, floor)
			panel.fillRect(i*LINEWIDTH,0,LINEWIDTH,tunnelTop[i]); //roof
			panel.fillRect(i*LINEWIDTH,tunnelBottom[i],LINEWIDTH,200); //floor
			if (block[i] >= 0) //if the block exsists...
				panel.fillRect(i*LINEWIDTH,block[i],LINEWIDTH,blockHeight); //draw block
		}
		if (!finished) panel.setColor(Color.green); //head color normally green
		else panel.setColor(Color.red); //when dead, turns read
		panel.fillRoundRect(HEADLINEINDEX*LINEWIDTH, //draws the circle head
												wormPos[HEADLINEINDEX]-(int)(.5*LINEWIDTH),
												2*LINEWIDTH,2*LINEWIDTH,5,5);
		int green = 0; //green starts black
		for (int i = 0; i < HEADLINEINDEX; i++) {
			panel.setColor(new Color(0,green,0)); //new shade of green
			green = (255 / (HEADLINEINDEX)) * i; //green goes toward 255
			panel.drawLine(i*LINEWIDTH,wormPos[i],(i+1)*LINEWIDTH,wormPos[i+1]); //snake is made of four lines
			panel.drawLine(i*LINEWIDTH,wormPos[i]+1,(i+1)*LINEWIDTH,wormPos[i+1]+1); //each one under the other
			panel.drawLine(i*LINEWIDTH,wormPos[i]+2,(i+1)*LINEWIDTH,wormPos[i+1]+2);
			panel.drawLine(i*LINEWIDTH,wormPos[i]+3,(i+1)*LINEWIDTH,wormPos[i+1]+3);
		}
		
		//draw text items on score board
		panel.setColor(Color.cyan);
		panel.drawString("Count: " + count, 0,280);
		panel.drawString("Score: " + (score + count), 0,240);
		panel.drawString("Level: " + level, 0,230);
		panel.drawString("Hi-Score: " + hiscore, 0,250);
	}//end draw()

//-----------------------------------------------------------------------------
// |                         |
// | do not modify this code |
// v                         v

/** these methods handles the so called 'timer' ...
 *  and is needed to implement the Runnable interface
 */
	public void start()
	{	
		thread = new Thread(this);
		thread.start();
	}
	public void stop()
	{
		thread.stop();
		thread=null;
		return;
	}
	public void run()
	{
		while(true)
		{	try
			{	Thread.currentThread().sleep(DELAY);
			}
			catch(InterruptedException e)
			{	thread.stop();
				thread=null;
				return;
			}
			Thread.currentThread().yield();
			count++;//keeps track of how many times the 'timer' fires
			repaint();
		}
	}
	
	//empty methods needed to implement MouseListener
  public void mouseClicked(MouseEvent e){}
  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}
	
	//empty methods needed to implement KeyListener
	public void keyPressed(KeyEvent e){}
	public void keyReleased(KeyEvent e){}
	public void keyTyped(KeyEvent e){}

// ^                         ^
// | do not modify this code |
// |                         |	
//-----------------------------------------------------------------------------

}//end of GravityWorm
