// David Mazzocco
// CS 113 Fall 2007

   import ucigame.*;
   import java.util.*;
   import java.lang.Math;

    public class RSS extends Ucigame{
      final static int PLAYER_MAX_HEALTH = 1000;
      final static int ENEMY1_MAX_HEALTH = 5;
      final static int ENEMY2_MAX_HEALTH = 10;
      final static int ENEMY3_MAX_HEALTH = 20;
      final static int BOSS_MAX_HEALTH = 1000;
      final static int PLAYER_SPEED = 3;
      final static int PLAYER_BULLET_Y_SPEED = 6;
      final static int PLAYER_PARABOLIC_X_SPEED = 15;
      final static int PLAYER_CUBIC_X_SPEED = 8;
      final static int ENEMY_BULLET_Y_SPEED = 10;
   
      Sprite player; 										
      Sprite playerBullet; 								
      Sprite startSprite;  								
      Sprite rSimpleSprite;
      Sprite simpleSprite;
      Sprite nsSimpleSprite;
      Sprite rssSprite;
      Sprite arrowSprite;
      Sprite winSprite;
      Sprite endShipSprite;
      Sprite pilotSprite;
      Sprite gameOverSprite;
      Sprite angryPilotSprite;
      Sprite flameSprite;
      Sprite loseShipSprite;
      Sprite bossSprite;
      Sprite cannon1Sprite;
      Sprite cannon2Sprite;
   
      int playerX, playerY; 								// player's starting position
      int waitTime; 										// milliseconds of wait time between player shots
      int enemyWaitTime;									// milliseconds of wait time between enemy shots
      int playerHealth; 									// player's health
      int playerBulletPower;								// damage player bullets do to enemies
      int enemyBulletPower;									// damage enemy bullets do to player
      int wave;												// current wave of enemies
      int level; 											// level of player's gun
      int difficulty;
      int bossHealth;
   
      boolean canShoot = true; 								// flag for when a shot can be fired
      boolean enemyShoot = true;							// flag for when enemies can shoot
      boolean waveIn = false;								// flag for when a wave moves into screen
      boolean mainMenu = true;
      boolean atStart = true;
      boolean gameWon = false;
      boolean gameLost = false;
      boolean bossFight = false;
      boolean bossIn = false;
      boolean bossShot = true;
   	
      ArrayList<Sprite> playerBullets; 						// array of player's straight moving bullets
      ArrayList<Sprite> playerParabolic; 					// array of player's (approx.) parabolic bullets
      ArrayList<Sprite> playerCubic; 						// array of player's (approx.) cubic bullets
      ArrayList<Sprite> enemy1, enemy2, enemy3;				// array of enemies
      ArrayList<Sprite> enemyBullets;						// array of enemies' straight-moving bullets
      ArrayList<Sprite> aimedBullets;						// array of enemies' bullets aimed towards player
      ArrayList<Sprite> trackingBullets;					// array of enemies' player-tracking bullets
      ArrayList<Sprite> levelUps;
      ArrayList<Sprite> speedUps;
      ArrayList<Sprite> healthUps;
      ArrayList<Sprite> bossBullets;
      ArrayList<Sprite> spiralBullets;
      ArrayList<Integer> enemy1Health;						// type 1 enemy health
      ArrayList<Integer> enemy2Health;						// type 2 enemy health
      ArrayList<Integer> enemy3Health;						// type 3 enemy health
      ArrayList<Integer> spiralBulletAngles;
      ArrayList<ArrayList<Integer>> waves;					// list of enemies in each wave
   
   
       public void setup(){
         window.size(400, 600);
         window.title("Really Simple Shooter.");
         canvas.background(255, 255, 255);
         player = makeSprite(getImage("images/player.png", 0, 0, 255));
         playerBullet = makeSprite(getImage("images/playerBullet.png", 0, 0, 255));
         startSprite = makeSprite(getImage("images/start.png"));
         rSimpleSprite = makeSprite(getImage("images/rSimple.png"));
         simpleSprite = makeSprite(getImage("images/simple.png"));
         nsSimpleSprite = makeSprite(getImage("images/nsSimple.png"));
         rssSprite = makeSprite(getImage("images/rss.png"));
         arrowSprite = makeSprite(getImage("images/arrow.png"));
         winSprite = makeSprite(getImage("images/win.png"));
         endShipSprite = makeSprite(getImage("images/endShip.png"));
         gameOverSprite = makeSprite(getImage("images/gameOver.png"));
         flameSprite = makeSprite(getImage("images/flame.png", 0, 0, 255));
         loseShipSprite = makeSprite(getImage("images/loseShip.png"));
         pilotSprite = makeSprite(62, 64);
         pilotSprite.addFrames(getImage("images/pilot.png", 255), 0, 0, 62, 0);
         pilotSprite.framerate(1);
         angryPilotSprite = makeSprite(62, 64);
         angryPilotSprite.addFrames(getImage("images/angryPilot.png", 255), 0, 0, 62, 0);
         angryPilotSprite.framerate(1);
         bossSprite = makeSprite(getImage("images/boss.png", 0, 0, 255));
         cannon1Sprite = makeSprite(getImage("images/cannon.png", 0, 0, 255));
         cannon2Sprite = makeSprite(getImage("images/cannon.png", 0, 0, 255));
         bossSprite.pin(cannon1Sprite, 100, 120);
         bossSprite.pin(cannon2Sprite, 300, 120);
         bossSprite.position(0, -210);
      	
         player.position(canvas.width() / 2 - player.width() / 2,
            					 	(float)canvas.height() * 7.0/8.0);
         startSprite.position(canvas.width() / 2 - startSprite.width() / 2, 
            						canvas.height() / 2);
         rSimpleSprite.position(canvas.width() / 2 - rSimpleSprite.width() / 2, 
            						  startSprite.y());
         simpleSprite.position(rSimpleSprite.x(), rSimpleSprite.y() + rSimpleSprite.height());
         nsSimpleSprite.position(simpleSprite.x(), simpleSprite.y() + simpleSprite.height());
         rssSprite.position(20, 49);
         arrowSprite.position(startSprite.x() - arrowSprite.width() * 1.5, startSprite.y() + 5);
         winSprite.position(canvas.width() / 2 - winSprite.width() / 2, 50);
         endShipSprite.position(110, 250);
         pilotSprite.position(180, 340);
         gameOverSprite.position(canvas.width() / 2 - gameOverSprite.width() / 2, 50);
         loseShipSprite.position(110, 250);
         angryPilotSprite.position(220, 340);
         flameSprite.position(110, 183);
         rSimpleSprite.hide();
         simpleSprite.hide();
         nsSimpleSprite.hide();
      								
         playerBullets = new ArrayList<Sprite>();
         playerParabolic = new ArrayList<Sprite>();
         playerCubic = new ArrayList<Sprite>();
         enemy1 = new ArrayList<Sprite>();
         enemy2 = new ArrayList<Sprite>();
         enemy3 = new ArrayList<Sprite>();
         levelUps = new ArrayList<Sprite>();
         speedUps = new ArrayList<Sprite>();
         healthUps = new ArrayList<Sprite>();
         enemy1Health = new ArrayList<Integer>();
         enemy2Health = new ArrayList<Integer>();
         enemy3Health = new ArrayList<Integer>();
         enemyBullets = new ArrayList<Sprite>();
         aimedBullets = new ArrayList<Sprite>();
         trackingBullets = new ArrayList<Sprite>();
         bossBullets = new ArrayList<Sprite>();
         spiralBullets = new ArrayList<Sprite>();
         spiralBulletAngles = new ArrayList<Integer>();
         waves = new ArrayList<ArrayList<Integer>>();
         playerHealth = 1000;
         waitTime = 667;
         wave = -1;
         level = 0;
         difficulty = 1;
         bossHealth = BOSS_MAX_HEALTH;
      	
         for(int i = 0; i < 5; i++)
            waves.add(new ArrayList<Integer>());
      		
         for(int i = 0; i < 32; i++){
            waves.get(0).add(1);
            waves.get(2).add(2);
            waves.get(4).add(3);
            if(i < 24){
               waves.get(1).add(1);
               waves.get(3).add(2);
            }
            else{
               waves.get(1).add(2);
               waves.get(3).add(3);
            }
         }
         changeDifficulty();
         framerate(30);
      }
   
       /*
        * Removes bullets from play when they collide with opposing ships,
        * or are outside the window.
        */
       public ArrayList<Sprite> removeBullets(ArrayList<Sprite> bulletList){
         boolean removeBullet;
       	
         for(int i = 0; i < bulletList.size(); i++){
            removeBullet = false;
            if(bulletList.get(i).y() + bulletList.get(i).height() < 0 ||
            bulletList.get(i).y() > canvas.height() ||
            bulletList.get(i).x() + bulletList.get(i).width() < 0 ||
            bulletList.get(i).x() > canvas.width()){
               removeBullet = true;
            }
         	
            for(int j = 0; j < enemy1.size(); j++){
               bulletList.get(i).checkIfCollidesWith(enemy1.get(j));
               if(bulletList.get(i).collided()){
                  enemy1Health.set(j, enemy1Health.get(j)-playerBulletPower);
                  removeBullet = true;
               }
            }
            
            for(int j = 0; j < enemy2.size(); j++){
               bulletList.get(i).checkIfCollidesWith(enemy2.get(j));
               if(bulletList.get(i).collided()){
                  enemy2Health.set(j, enemy2Health.get(j)-playerBulletPower);
                  removeBullet = true;
               }
            }
         	
            for(int j = 0; j < enemy3.size(); j++){
               bulletList.get(i).checkIfCollidesWith(enemy3.get(j));
               if(bulletList.get(i).collided()){
                  enemy3Health.set(j, enemy3Health.get(j)-playerBulletPower);
                  removeBullet = true;
               }
            }
         	
            bulletList.get(i).checkIfCollidesWith(bossSprite, PIXELPERFECT);
            if(bulletList.get(i).collided() && bossFight){
               if(bossIn)
                  bossHealth -= playerBulletPower;
               removeBullet = true;
            }
         	
            if(removeBullet){
               bulletList.remove(i);
               i--;
            }
         }
         return bulletList;
      }
      
       /*
        * Removes enemy-fired bullets when they collide w/ the player,
        * or are outside the window.
        */
       public ArrayList<Sprite> removeEnemyBullets(ArrayList<Sprite> bulletList){
         boolean removeBullet;
       	
         for(int i = 0; i < bulletList.size(); i++){
            removeBullet = false;
            if(bulletList.get(i).y() + bulletList.get(i).height() < 0 ||
            bulletList.get(i).y() > canvas.height() ||
            bulletList.get(i).x() + bulletList.get(i).width() < 0 ||
            bulletList.get(i).x() > canvas.width()){
               removeBullet = true;
            }
         	
            bulletList.get(i).checkIfCollidesWith(player);
            if(bulletList.get(i).collided()){
               playerHealth -= enemyBulletPower;
               removeBullet = true;
            }
                      
            if(removeBullet){
               bulletList.remove(i);
               i--;
            }
         }
         return bulletList;
      }
   	
       /*
        * Checks for collisions between the player and other objects.
        */
       public void checkPlayerCollision(){
         for(int i = 0; i < enemy1.size(); i++){
            player.checkIfCollidesWith(enemy1.get(i));
            if(player.collided())
               playerHealth -= 5;
         }	
      	
         for(int i = 0; i < enemy2.size(); i++){
            player.checkIfCollidesWith(enemy2.get(i));
            if(player.collided())
               playerHealth -= 5;
         }
      		
         for(int i = 0; i < enemy3.size(); i++){
            player.checkIfCollidesWith(enemy3.get(i));
            if(player.collided())
               playerHealth -= 5;
         }
         
         for(int i = 0; i < levelUps.size(); i++){
            player.checkIfCollidesWith(levelUps.get(i));
            if(player.collided()){
               level++;
               levelUps.remove(i);
               i--;
            }
         }
         
         for(int i = 0; i < speedUps.size(); i++){
            player.checkIfCollidesWith(speedUps.get(i));
            if(player.collided()){
               waitTime *= .667;
               speedUps.remove(i);
               i--;
            }
         }
      	
         for(int i = 0; i < healthUps.size(); i++){
            player.checkIfCollidesWith(healthUps.get(i));
            if(player.collided()){
               playerHealth += 100;
               if(playerHealth > 1000)
                  playerHealth = 1000;
               healthUps.remove(i);
               i--;
            }
         }
         
         player.checkIfCollidesWith(bossSprite, PIXELPERFECT);
         if(player.collided())
            playerHealth -= 5;
      }
   	
       /*
        * Determines if a powerup will be dropped after the player
        * defeats an enemy.
        */
       public void powerUpChance(int x, int y){
         int rn = randomInt(80);
         if(rn <= 1){
            levelUps.add(makeSprite(getImage("images/levelUp.png", 0, 0, 255)));
            levelUps.get(levelUps.size() - 1).motion(0, 2, SET);
            levelUps.get(levelUps.size() - 1).position(x, y);
         }
         else if(rn <= 3){
            healthUps.add(makeSprite(getImage("images/health.png", 0, 0, 255)));
            healthUps.get(healthUps.size() - 1).motion(0, 2, SET);
            healthUps.get(healthUps.size() - 1).position(x, y);
         }
         else if(rn <= 5){
            speedUps.add(makeSprite(getImage("images/bSpeed.png", 0, 0, 255)));
            speedUps.get(speedUps.size() - 1).motion(0, 2, SET);
            speedUps.get(speedUps.size() - 1).position(x, y);
         }
      }
   	
       /*
        * Removes enemies whose health <= 0.
        */
       public void removeDeadEnemies(){
         for(int i = 0; i < enemy1Health.size(); i++){
            if(enemy1Health.get(i) <= 0){
               powerUpChance(enemy1.get(i).x(), enemy1.get(i).y());
               enemy1Health.remove(i);
               enemy1.remove(i);
               i--;          
            }
         }
      	
         for(int i = 0; i < enemy2Health.size(); i++){
            if(enemy2Health.get(i) <= 0){
               powerUpChance(enemy2.get(i).x(), enemy2.get(i).y());
               enemy2Health.remove(i);
               enemy2.remove(i);
               i--;
            }
         }
      	
         for(int i = 0; i < enemy3Health.size(); i++){
            if(enemy3Health.get(i) <= 0){
               powerUpChance(enemy3.get(i).x(), enemy3.get(i).y());
               enemy3Health.remove(i);
               enemy3.remove(i);
               i--;
            }
         }
      }
   
       /*
        * These determine the starting positions for 
        * each enemy.
        */
       public int initEnemyX(int space){
         return 40 + ((space%8) * 40);
      }
   
       public int initEnemyY(int space){
         return -16 - (space/8 * 16 * 2);
      }
   
       /*
        * Begins a new wave, spawning new enemies.
        */
       public void initiateWave(ArrayList<Integer> enemies){
         for(int i = 0; i < enemies.size(); i++){
            switch(enemies.get(i)){
               case 1:
                  enemy1.add(makeSprite(getImage("images/enemy1.png", 0, 0, 255)));
                  enemy1Health.add(ENEMY1_MAX_HEALTH);
                  enemy1.get(enemy1.size() - 1).position(initEnemyX(i), initEnemyY(i));
                  enemy1.get(enemy1.size() - 1).motion(0, 5, SET);
                  break;
               case 2:
                  enemy2.add(makeSprite(getImage("images/enemy2.png", 0, 0, 255)));
                  enemy2Health.add(ENEMY2_MAX_HEALTH);
                  enemy2.get(enemy2.size() - 1).position(initEnemyX(i), initEnemyY(i));
                  enemy2.get(enemy2.size() - 1).motion(0, 5, SET);
                  break;
               case 3:
                  enemy3.add(makeSprite(getImage("images/enemy3.png", 0, 0, 255)));
                  enemy3Health.add(ENEMY3_MAX_HEALTH);
                  enemy3.get(enemy3.size() - 1).position(initEnemyX(i), initEnemyY(i));
                  enemy3.get(enemy3.size() - 1).motion(0, 5, SET);
            }
         }
      	
         startTimer("WaveIn", 250 * enemies.size()/8);
      } 	
   
       /*
        * Stops the movement of the incoming wave.
        */
       public void stopWave(){
         for(int i = 0; i < enemy1.size(); i++)
            enemy1.get(i).motion(0, 0, SET);
         for(int i = 0; i < enemy2.size(); i++)
            enemy2.get(i).motion(0, 0, SET);
         for(int i = 0; i < enemy3.size(); i++)
            enemy3.get(i).motion(0, 0, SET);
      }
   	
       /*
        * 
        */
       public void enemyShot(){
         int totalEnemies = enemy1.size() + enemy2.size() + enemy3.size();
         int enemyNumber = randomInt(totalEnemies);
         int enemyX, enemyY;
      	
         if(enemyNumber < enemy1.size()){
            enemyX = enemy1.get(enemyNumber).x();
            enemyY = enemy1.get(enemyNumber).y();
            enemyBullets.add(makeSprite(getImage("images/enemyBullet.png", 0, 0, 255)));
            enemyBullets.get(enemyBullets.size()-1).position(enemyX + enemy1.get(enemyNumber).width()/2,
               															 enemyY + enemy1.get(enemyNumber).height());
            enemyBullets.get(enemyBullets.size()-1).motion(0, ENEMY_BULLET_Y_SPEED, SET);
         }
         	
         else if(enemyNumber < enemy1.size() + enemy2.size()){
            Sprite enemy = enemy2.get(enemyNumber - enemy1.size());
            double totalDisp;
            double xMov, yMov;
            double dx, dy;
            dx = (double) (player.x() + player.width() / 2 - enemy.x() - enemy.width() / 2);
            dy = (double) (player.y() - enemy.y() - enemy.height());
         	
            enemyX = enemy2.get(enemyNumber - enemy1.size()).x();
            enemyY = enemy2.get(enemyNumber - enemy1.size()).y();
            aimedBullets.add(makeSprite(getImage("images/enemyBullet.png", 0, 0, 255)));
            aimedBullets.get(aimedBullets.size()-1).position(enemyX + enemy2.get(enemyNumber-enemy1.size()).width()/2,
               												enemyY + enemy2.get(enemyNumber-enemy1.size()).height());
            totalDisp = Math.abs(dx) + Math.abs(dy);
            xMov = (double) ENEMY_BULLET_Y_SPEED * dx / totalDisp;
            yMov = (double) ENEMY_BULLET_Y_SPEED * dy / totalDisp;
            aimedBullets.get(aimedBullets.size()-1).motion(xMov, yMov, SET);
         }
         	
         else{
            Sprite enemy = enemy3.get(enemyNumber - enemy1.size() - enemy2.size());
         	
            enemyX = enemy.x();
            enemyY = enemy.y();
            trackingBullets.add(makeSprite(getImage("images/enemyBullet.png", 0, 0, 255)));
            trackingBullets.get(trackingBullets.size()-1).position(enemyX + enemy3.get(enemyNumber-enemy1.size()-enemy2.size()).width()/2,
               													 enemyY + enemy3.get(enemyNumber-enemy1.size()-enemy2.size()).height());
         }
      }
   	
       /*
        * Displays the player's health bar.
        */
       public void healthBar(int thickness){
         int yPos = canvas.height() - 5;			
         canvas.line(0, yPos, canvas.width(), yPos);
         canvas.line(0, yPos - thickness - 1, canvas.width(), yPos - thickness - 1);
      	
         for(int i = 1; i <= thickness; i++)
            canvas.line(0, yPos - i, 
               			(double)canvas.width() * (double)playerHealth / (double) PLAYER_MAX_HEALTH, yPos - i);
      }
   	
       /*
        * Displays the boss' health bar.
        */
       public void enemyHealthBar(int thickness){
         int xPos = 5;
         canvas.line(xPos, 580, xPos, 280);
         canvas.line(xPos + thickness + 1, 580, xPos + thickness + 1, 280);
         canvas.line(xPos, 280, xPos + thickness + 1, 280);
         if(bossHealth < 0)
            bossHealth = 0;
      	
         for(int i = 1; i <= thickness; i++)
            canvas.line(xPos + i, 580, xPos + i, 580 - 300 * (double)bossHealth / (double) BOSS_MAX_HEALTH);
      }
      
       /*
        * Updates the positions of the enemy tracking bullets.
        */
       public void updateTrackingBullets(){
         for(int i = 0; i < trackingBullets.size(); i++){
            if(player.x() + player.width()/2 < trackingBullets.get(i).x() + trackingBullets.get(i).width()/2)
               trackingBullets.get(i).motion(-1.2, ENEMY_BULLET_Y_SPEED, SET);
            else if(player.x() + player.width()/2 > trackingBullets.get(i).x() + trackingBullets.get(i).width()/2)
               trackingBullets.get(i).motion(1.2, ENEMY_BULLET_Y_SPEED, SET);
            else
               trackingBullets.get(i).motion(0, ENEMY_BULLET_Y_SPEED, SET);
         }
      }
   	
		/*
		 * Changes environment variables according to difficulty.
		 */
       public void changeDifficulty(){
         switch(difficulty){
            case 0:
               playerBulletPower = 5;
               enemyBulletPower = 10;
               enemyWaitTime = 667;
               break;
            case 1:
               playerBulletPower = 2;
               enemyBulletPower = 50;
               enemyWaitTime = 500;
               break;
            case 2:
               playerBulletPower = 1;
               enemyBulletPower = 100;
               enemyWaitTime = 333;
               break;
         }
      }
   
		/*
		 * Draws the main menu.
		 */
       public void drawMainMenu(){
         canvas.clear();
         gameLost = false;
         keyboard.typematicOff();
      	
         rssSprite.draw();
         startSprite.draw();
         rSimpleSprite.draw();
         simpleSprite.draw();
         nsSimpleSprite.draw();
         arrowSprite.draw(); 
      }
   	
		/*
		 * Handles key presses on main menu.
		 */
       public void onKeyPressMainMenu(){
         if(keyboard.isDown(keyboard.Z)){
            if(atStart){
               atStart = false;
               startSprite.hide();
               rSimpleSprite.show();
               simpleSprite.show();
               nsSimpleSprite.show();
               arrowSprite.position(simpleSprite.x() - arrowSprite.width() * 1.5, simpleSprite.y() + 5);
               arrowSprite.show();
            }
            
            else{
               keyboard.typematicOn();
               mainMenu = false;
            }
         }
         
         if(keyboard.isDown(keyboard.UP) && difficulty > 0 && !atStart){
            difficulty--;
            arrowSprite.position(arrowSprite.x(), arrowSprite.y() - simpleSprite.height());
            changeDifficulty();
         }
      	
         if(keyboard.isDown(keyboard.DOWN) && difficulty < 2 && !atStart){
            difficulty++;
            arrowSprite.position(arrowSprite.x(), arrowSprite.y() + simpleSprite.height());
            changeDifficulty();
         }
      }
   	
		/*
		 * Displays the win screen after the boss is defeated.
		 */
       public void drawWinScreen(){
         canvas.clear();
         winSprite.draw();
         endShipSprite.draw();
         pilotSprite.draw();
      }
   	
		/*
		 * Handles key presses on Win screen. Z = main menu.
		 */
       public void onKeyPressWinScreen(){
         if (keyboard.isDown(keyboard.Z)){
            gameWon = false;
            atStart = true;
            mainMenu = true;
            setup();
         }
      }
   	
		/*
		 * Draw game over screen on player death.
		 */
       public void drawGameOverScreen(){
         canvas.clear();
         gameOverSprite.draw();
         loseShipSprite.draw();
         flameSprite.draw();
         angryPilotSprite.draw();
      }
      
		/*
		 * Handles key presses on game over screen. Z = main menu.
		 */
       public void onKeyPressGameOverScreen(){
         if(keyboard.isDown(keyboard.Z)){
            gameLost = false;
            atStart = true;
            mainMenu = true;
            setup();
         }
      }
   		
		/*
		 * Points the boss' cannons at the player's position.
		 */
       public void aimCannon(Sprite cannon){
         int dy = player.y() - cannon.y();
         int dx = player.x() + player.width() / 2 - cannon.x() - cannon.width() / 2;
         double inverseSlope;
         double rotation = 0;
      	
         if(dy != 0){
            inverseSlope = (double) dx / (double) dy;
            rotation = 180 * Math.atan(-inverseSlope) / Math.PI;
         }
         
         else{
            if(dx > 0)
               rotation = -90;
            else
               rotation = 90;
         }
      	
         if(dy < 0)
            rotation += 180;
      		
         cannon.rotate(rotation, cannon.width() / 2, 0);  
      }
   	
		/*
		 * Shoots the boss' cannons.
		 */
       public void shootCannon(Sprite cannon){
         double totalDisp;
         double xMov, yMov;
         double dx, dy;
         dx = (double) (player.x() + player.width() / 2 - cannon.x() - cannon.width() / 2);
         dy = (double) (player.y() - cannon.y());
         	
         aimedBullets.add(makeSprite(getImage("images/enemyBullet.png", 0, 0, 255)));
         aimedBullets.get(aimedBullets.size()-1).position(cannon.x() + cannon.width()/2, cannon.y());
         totalDisp = Math.abs(dx) + Math.abs(dy);
         xMov = (double) ENEMY_BULLET_Y_SPEED * dx / totalDisp;
         yMov = (double) ENEMY_BULLET_Y_SPEED * dy / totalDisp;
         aimedBullets.get(aimedBullets.size()-1).motion(xMov, yMov, SET);
      }
   		
		/*
		 * Shoots the boss-specific bullet.
		 */ 
       public void shootBossBullet(){
         bossBullets.add(makeSprite(getImage("images/bossBullet.png", 0, 0, 255)));
         bossBullets.get(bossBullets.size() - 1).position(bossSprite.width()/2 - bossBullets.get(0).width()/2, bossSprite.height());
         bossBullets.get(bossBullets.size() - 1).motion(0, 4, SET);
      }
   	
		/*
		 * Updates the boss bullets. The boss-specific bullet will break into smaller ones.
		 */
       public void checkBossBullets(){
         int x, y;
         for(int i = 0; i < bossBullets.size(); i++){
            x = bossBullets.get(i).x();
            y = bossBullets.get(i).y();
         	
            if(y >= 300){
               for(int j = 0; j < 8; j++){
                  spiralBullets.add(makeSprite(getImage("images/enemyBullet.png", 0, 0, 255)));
                  spiralBullets.get(spiralBullets.size() - 1).position(x, y);
               }
               spiralBullets.get(spiralBullets.size() - 1).motion(-4, 0, SET);
               spiralBullets.get(spiralBullets.size() - 2).motion(4, 0, SET);
               spiralBullets.get(spiralBullets.size() - 3).motion(0, 4, SET);
               spiralBullets.get(spiralBullets.size() - 4).motion(0, -4, SET);
               spiralBullets.get(spiralBullets.size() - 5).motion(2, 2, SET);
               spiralBullets.get(spiralBullets.size() - 6).motion(2, -2, SET);
               spiralBullets.get(spiralBullets.size() - 7).motion(-2, 2, SET);
               spiralBullets.get(spiralBullets.size() - 8).motion(-2, -2, SET);
            	
               bossBullets.remove(i);
               i--;
            }	
         }
      }
       
		/*
		 * The main draw method. This determines what portion of the
		 * game is currently being drawn.
		 */
       public void draw(){
         if(playerHealth <= 0){
            keyboard.typematicOff();
            startTimer("GameOverWait", 100);
         }
      	
         if(mainMenu)
            drawMainMenu();
         
         else if(gameLost)
            drawGameOverScreen();
          
         else if(bossFight)
            drawBossFight();
         
         else if(gameWon)
            drawWinScreen();
         
         else{
            canvas.clear();
         
            if(enemy1.size() + enemy2.size() + enemy3.size() == 0){
               wave++;
               if(wave < 5){
                  waveIn = false;
                  initiateWave(waves.get(wave));
               }
               else
                  bossFight = true;              
            }	
         	
            if(enemyShoot && enemy1.size() + enemy2.size() + enemy3.size() > 0){
               enemyShot();
               enemyShoot = false;
               startTimer("EnemyShot", enemyWaitTime);
            }
         
            playerBullets = removeBullets(playerBullets);
            playerCubic = removeBullets(playerCubic);
            playerParabolic = removeBullets(playerParabolic);
            enemyBullets = removeEnemyBullets(enemyBullets);
            aimedBullets = removeEnemyBullets(aimedBullets);
            trackingBullets = removeEnemyBullets(trackingBullets);
            checkPlayerCollision();
            updateTrackingBullets();
            removeDeadEnemies();
         
         // move & draw regular bullets   
            for(int i = 0; i < playerBullets.size(); i++){
               playerBullets.get(i).move();        
               playerBullets.get(i).draw();
            }
         
         // move & draw cubic bullets
            for(int i = 0; i < playerCubic.size(); i++){
               playerCubic.get(i).motion(.9, 1, MULTIPLY);
               playerCubic.get(i).move();
               playerCubic.get(i).draw();
            }
         
         // move & draw parabolic bullets
            for(int i = 0; i < playerParabolic.size(); i++){
               playerParabolic.get(i).motion(.9, 1, MULTIPLY);
               playerParabolic.get(i).move();
               playerParabolic.get(i).draw();
            }
         
         // move & draw type 1 enemies  	   
            for(int i = 0; i < enemy1.size(); i++){
               enemy1.get(i).move();
               enemy1.get(i).draw();
            }
         
         // move & draw type 2 enemies
            for(int i = 0; i < enemy2.size(); i++){
               enemy2.get(i).move();
               enemy2.get(i).draw();
            }
         
         // move & draw type 3 enemies
            for(int i = 0; i < enemy3.size(); i++){
               enemy3.get(i).move();
               enemy3.get(i).draw();
            }
         
            for(int i = 0; i < healthUps.size(); i++){
               healthUps.get(i).move();
               healthUps.get(i).draw();
            }
         	
            for(int i = 0; i < speedUps.size(); i++){
               speedUps.get(i).move();
               speedUps.get(i).draw();
            }
         
            for(int i = 0; i < levelUps.size(); i++){
               levelUps.get(i).move();
               levelUps.get(i).draw();
            }
         	
         // move & draw straight-moving enemy bullets   		
            for(int i = 0; i < enemyBullets.size(); i++){
               enemyBullets.get(i).move();
               enemyBullets.get(i).draw();
            }
         
         // move & draw bullets aimed towards player
            for(int i = 0; i < aimedBullets.size(); i++){
               aimedBullets.get(i).move();
               aimedBullets.get(i).draw();
            }
         
            for(int i = 0; i < trackingBullets.size(); i++){
               trackingBullets.get(i).move();
               trackingBullets.get(i).draw();
            }
         
            healthBar(5);
         	
         // draw player
            player.draw();	
         }
      }
   
       public void drawBossFight(){
         canvas.clear();
      	
         if(bossSprite.y() < 0)
            bossSprite.nextY(bossSprite.y() + 1);
         
         else
            bossIn = true;
      		
         aimCannon(cannon1Sprite);
         aimCannon(cannon2Sprite);
         bossSprite.draw();
      
         playerBullets = removeBullets(playerBullets);
         playerCubic = removeBullets(playerCubic);
         playerParabolic = removeBullets(playerParabolic);
         enemyBullets = removeEnemyBullets(enemyBullets);
         aimedBullets = removeEnemyBullets(aimedBullets);
         trackingBullets = removeEnemyBullets(trackingBullets);
         spiralBullets = removeEnemyBullets(spiralBullets);
         checkPlayerCollision();
         updateTrackingBullets();
         removeDeadEnemies();	
         checkBossBullets();
      
         if(enemyShoot && bossIn){
            enemyShoot = false;
            shootCannon(cannon1Sprite);
            shootCannon(cannon2Sprite);
            startTimer("EnemyShot", 3 * enemyWaitTime);
         }
         
         if(bossShot && bossIn){
            bossShot = false;
            shootBossBullet();
            startTimer("BossShot", 9 * enemyWaitTime);
         }
      
         for(int i = 0; i < playerBullets.size(); i++){
            playerBullets.get(i).move();        
            playerBullets.get(i).draw();
         }
         
         for(int i = 0; i < playerCubic.size(); i++){
            playerCubic.get(i).motion(.9, 1, MULTIPLY);
            playerCubic.get(i).move();
            playerCubic.get(i).draw();
         }
         
         for(int i = 0; i < playerParabolic.size(); i++){
            playerParabolic.get(i).motion(.9, 1, MULTIPLY);
            playerParabolic.get(i).move();
            playerParabolic.get(i).draw();
         }
         
         for(int i = 0; i < bossBullets.size(); i++){
            bossBullets.get(i).move();
            bossBullets.get(i).draw();
         }
      	
         for(int i = 0; i < spiralBullets.size(); i++){
            spiralBullets.get(i).move();
            spiralBullets.get(i).draw();
         }
      	
         for(int i = 0; i < spiralBullets.size(); i++)
            spiralBullets.get(i).draw();
      	
         for(int i = 0; i < aimedBullets.size(); i++){
            aimedBullets.get(i).move();
            aimedBullets.get(i).draw();
         }
         
         for(int i = 0; i < healthUps.size(); i++){
            healthUps.get(i).move();
            healthUps.get(i).draw();
         }
         	
         for(int i = 0; i < speedUps.size(); i++){
            speedUps.get(i).move();
            speedUps.get(i).draw();
         }
         
         for(int i = 0; i < levelUps.size(); i++){
            levelUps.get(i).move();
            levelUps.get(i).draw();
         }
      	
         healthBar(5);
         enemyHealthBar(5);
         player.draw();
      	
         if(bossHealth <= 0){
            keyboard.typematicOff();
            startTimer("WinWait", 100);
         }
      }
   
       public void onKeyPress(){
         if(mainMenu)
            onKeyPressMainMenu();
            
         else if(gameLost){
            keyboard.typematicOff();
            onKeyPressGameOverScreen();
         }
         
         else if(gameWon){
            keyboard.typematicOff();
            onKeyPressWinScreen();
         }
            
         else{
            if(keyboard.isDown(keyboard.UP) && player.y() > 0)
               player.nextY(player.y() - PLAYER_SPEED);
            if(keyboard.isDown(keyboard.DOWN) && player.y() + player.height() < canvas.height() - 12)
               player.nextY(player.y() + PLAYER_SPEED);
            if(keyboard.isDown(keyboard.LEFT) && player.x() > 0)
               player.nextX(player.x() - PLAYER_SPEED);
            if(keyboard.isDown(keyboard.RIGHT) && player.x() + player.width() < canvas.width())
               player.nextX(player.x() + PLAYER_SPEED);
         
            if(keyboard.isDown(keyboard.P))
               level++;
            if(keyboard.isDown(keyboard.O))
               level--;
         
         
            if(keyboard.isDown(keyboard.Z) && canShoot){
               startTimer("Wait", waitTime);
               playerBullets.add(makeSprite(getImage("images/playerBullet.png", 0, 0, 255)));
               playerBullets.get(playerBullets.size() - 1).position(player.x() + player.width()/2,
                  															  player.y() - playerBullet.height());
               playerBullets.get(playerBullets.size() - 1).motion(0, -PLAYER_BULLET_Y_SPEED, SET);
            
            
               if(level >= 1){
                  for(int i = 0; i < 2; i++){
                     playerBullets.add(makeSprite(getImage("images/playerBullet.png", 0, 0, 255)));
                     playerBullets.get(playerBullets.size() - 1).position(player.x() + player.width()*i,
                        															  player.y() - playerBullet.height());
                     playerBullets.get(playerBullets.size() - 1).motion(0, -PLAYER_BULLET_Y_SPEED, SET);
                  }
               }
            
               if(level >= 2){
                  for(int i = 0; i < 2; i++){
                     playerCubic.add(makeSprite(getImage("images/playerBullet.png", 0, 0, 255)));
                     playerCubic.get(playerCubic.size() - 1).position(player.x() + player.width()/2,
                        															player.y() - playerBullet.height());
                     playerCubic.get(playerCubic.size() - 1).motion(PLAYER_CUBIC_X_SPEED * Math.pow(-1, i), -PLAYER_BULLET_Y_SPEED, SET);																						
                  }
               }
            
               if(level >= 3){
                  for(int i = 0; i < 2; i++){
                     playerParabolic.add(makeSprite(getImage("images/playerBullet.png", 0, 0, 255)));
                     playerParabolic.get(playerParabolic.size() - 1).position(player.x() + player.width()/2,
                        																	player.y() - playerBullet.height());
                     playerParabolic.get(playerParabolic.size() - 1).motion(PLAYER_PARABOLIC_X_SPEED * Math.pow(-1, i), -PLAYER_BULLET_Y_SPEED, SET);
                  }
               }
               canShoot = false;
            }
         }
      }
   
       public void WaitTimer(){
         canShoot = true;
         stopTimer("Wait");
      }
   	
       public void WaveInTimer(){
         stopWave();
         stopTimer("WaveIn");
      }
   	
       public void EnemyShotTimer(){
         enemyShoot = true;
         stopTimer("EnemyShot");
      }
      
       public void WinWaitTimer(){
         gameWon = true;
         bossFight = false;
         bossIn = false;
         stopTimer("WinWait");
      }
      
       public void BossShotTimer(){
         bossShot = true;
         stopTimer("BossShot");
      }
   	
       public void GameOverWaitTimer(){
         gameLost = true;
         stopTimer("GameOverWait");
      }
   }