import java.awt.*;
import java.awt.image.*;
import java.applet.*;


/** 
 *  The BillInvaders class extends the MyRaster class to implement the game
 *  of Bill Invaders (similar to Space Invaders).  Everything specific to the
 *  game is implemented in this class.
 */


class BillInvaders extends MyRaster implements Runnable {
    int x,y;                // the x,y coordinates of the playfield
    
    Sprite sprite[];        // array of sprites on this playfield
    int totalSprites;       // total number of sprites that have been added to the playfield
	MyRaster buffer;          // the raster object into which we draw sprites
	
    Thread thread = new Thread(this);   // the thread in which processing occurs
    Applet root;            // the Applet object
    boolean mouse_down;     // stores the state of the mouse button
    
    // images we're going to use
    String fileSprite[] = {
        "think2.gif",
        "mybill2.gif",
        "shootup.gif",
        "shootdown.gif",
        "ie.gif",
        "bo.jpg",
        "zerolives.gif",
        "onelife.gif",
        "twolives.gif",
        "gameover.gif",
        "click.gif",
        "youwin.gif",
    };
    
    String animFile = "world";
    
    // Image objects to store the images
    Image imgSprite[] = new Image[fileSprite.length];
    Image animImg[] = new Image[12];
    int NUM_IMAGES = fileSprite.length;

    // sprites for the good guys, bad guys, bullets, and shields
    Sprite spriteEnemy[], fireYou[], fireEnemy[], spriteYou, spriteShield[];
    // a couple more sprites
    Sprite spriteLives[], spriteGameOver, spriteRestart, spriteYouWin;
    AnimatedSprite spriteWorld;
    
    int numLives = 2;
    boolean gameInProcess = false, inited;
    
    // the invaders starting position
    int enemyXPosition = 0;
    int enemyYPosition = 40;
 
    // the player's starting position
    int playerXLocation = 110;
    int playerYLocation = 220;

    // the system time of the player's last shot, enemy's next shot
    long lastPlayerFireTime, nextEnemyFireTime, timeTillNextGame, playerSafetyTime;
    
    // index into the array of good guy's and bad guys' bullets
    int current_player_fire, current_enemy_fire;
    
    // the following values set up the number of each kind of object we want to
    // allocate
    public static final int INDEX_FIRST_ENEMY = 0;
    public static final int TOTAL_ENEMIES = 15;
    public static final int INDEX_GOOD_GUY = INDEX_FIRST_ENEMY + TOTAL_ENEMIES;
    public static final int INDEX_FIRST_GOOD_FIRE = INDEX_GOOD_GUY + 1;
    public static final int TOTAL_GOOD_FIRES = 4;
    public static final int INDEX_FIRST_ENEMY_FIRE = INDEX_FIRST_GOOD_FIRE + TOTAL_GOOD_FIRES;
    public static final int TOTAL_ENEMY_FIRES = 10;
    public static final int INDEX_FIRST_SHIELD = INDEX_FIRST_ENEMY_FIRE + TOTAL_ENEMY_FIRES;
    public static final int TOTAL_SHIELDS = 2;
    public static final int INDEX_FIRST_LIFE = INDEX_FIRST_SHIELD + TOTAL_SHIELDS;
    public static final int TOTAL_LIVES = 3;
    public static final int INDEX_GAME_OVER = INDEX_FIRST_LIFE + TOTAL_LIVES;
    public static final int INDEX_RESTART = INDEX_GAME_OVER + 1;
    public static final int INDEX_YOU_WIN = INDEX_RESTART + 1;
    public static final int INDEX_WORLD = INDEX_YOU_WIN + 1;
    
    // total number of sprites we'll use
    public static final int TOTAL_SPRITES = INDEX_WORLD + 1;
    
    Image bgnd;
    
    /**
     *  This is the default constructor for the BillInvaders class.  Typically you
     *  want to use the next constructor, but this might be useful if you derive
     *  from this class.
     */
    
    public BillInvaders() {
    }
    
  
    /**
     *  This is the constructor you typically use for the BillInvaders class.  Pass
     *  the background image for the playfield, the maximum number of sprites you
     *  will ever use, and the Applet object (which we'll use to create images,
     *  get the code base, and get the graphics object onto which we'll draw).
     */
    
    public BillInvaders(Image bgnd, Applet root) {
        super(bgnd);
        this.bgnd = bgnd;
        this.root = root;
        
        init();
        
        thread.setPriority(Thread.MIN_PRIORITY);
        thread.setDaemon(true);
        thread.start();
    }
    
    
    /**
     *  This is the initialization routine.  It loads all the images, 
     *  creates all the sprites and places them on the playfield.
     */
  
	public void init() {	
	    inited = false;
	    totalSprites = 0;
	    
        sprite = new Sprite[TOTAL_SPRITES];
        buffer = new MyRaster(bgnd);

	    // load in all the images that we're going to need into Image objects
	    for (int i=0; i < NUM_IMAGES; ++i) {
	        imgSprite[i] = root.getImage(root.getCodeBase(), fileSprite[i]);
	    }
	    for (int i=0; i < 12; ++i) {
	        animImg[i] = root.getImage(root.getCodeBase(), animFile + i + ".gif");
	    }

        

		// create sprites for each of the enemy invaders and place them on the playfield
        enemyYPosition = 40;
		spriteEnemy = new Sprite[TOTAL_ENEMIES];
		for (int i=0; i < TOTAL_ENEMIES; ++i) {
    		spriteEnemy[i] = new Sprite(imgSprite[0], 
    		    (i / 3) * (30) + 10 + (i %3) * 15, 
    		    enemyYPosition - (i % 3) * 15 - 1
    		);	        
    		addSprite(INDEX_FIRST_ENEMY + i, spriteEnemy[i]);
    	}
    	
    	// create a sprite for the player and place it on the playfield
    	spriteYou = new Sprite(imgSprite[1], playerXLocation, playerYLocation);
		addSprite(INDEX_GOOD_GUY, spriteYou);
		
		// create sprites for the player's shots -- initially they are hidden
		fireYou = new Sprite[TOTAL_GOOD_FIRES];
		for (int i=0; i < TOTAL_GOOD_FIRES; ++i) {
        	fireYou[i] = new Sprite(imgSprite[2], 0, 0);
    		fireYou[i].hide(); // hide the shot
    		addSprite(INDEX_FIRST_GOOD_FIRE + i, fireYou[i]);
    		
    	}
    	
        // create sprites for the invader's shots -- initially they are hidden
		fireEnemy = new Sprite[TOTAL_ENEMY_FIRES];
		for (int i=0; i < TOTAL_ENEMY_FIRES; ++i) {
        	fireEnemy[i] = new Sprite(imgSprite[3], 0, 0);
    		fireEnemy[i].hide(); // hide the shot
    		addSprite(INDEX_FIRST_ENEMY_FIRE + i, fireEnemy[i]);
    		
    	}
    	
    	// create sprites for the shields and place them on the playfield
		spriteShield = new Sprite[TOTAL_SHIELDS];
		spriteShield[0] = new Sprite(imgSprite[4], 0 * 150 + 30, 200);
		spriteShield[1] = new Sprite(imgSprite[5], 1 * 150 + 30, 200);
		for (int i=0; i < TOTAL_SHIELDS; ++i) {
    		spriteShield[i].setAlpha((byte)150);
    		addSprite(INDEX_FIRST_SHIELD + i, spriteShield[i]);
    	}
    	
    	// create sprites for the player's lives
		spriteLives = new Sprite[TOTAL_LIVES];
		spriteLives[0] = new Sprite(imgSprite[6], 10, 250);
		spriteLives[1] = new Sprite(imgSprite[7], 10, 250);
		spriteLives[2] = new Sprite(imgSprite[8], 10, 250);
		for (int i=0; i < TOTAL_LIVES; ++i) {
    		spriteLives[i].setAlpha((byte)200);
    		spriteLives[i].hide();
    		addSprite(INDEX_FIRST_LIFE + i, spriteLives[i]);
    	}
    	spriteLives[2].show();
    	
    	// create a game over sprite
    	spriteGameOver = new Sprite(imgSprite[9], 100, 100);
		spriteGameOver.setAlpha((byte)200);
		addSprite(INDEX_GAME_OVER, spriteGameOver);
		
    	// create a restart sprite
    	spriteRestart = new Sprite(imgSprite[10], -100, -50);
		spriteRestart.setAlpha((byte)150);
        spriteRestart.setDestination(100, 150, 3);
		addSprite(INDEX_RESTART, spriteRestart);
		
    	// create a you win sprite
    	spriteYouWin = new Sprite(imgSprite[11], 100, 100);
    	spriteYouWin.hide();
		spriteYouWin.setAlpha((byte)200);
		addSprite(INDEX_YOU_WIN, spriteYouWin);

    	// create a world animated sprite
    	spriteWorld = new AnimatedSprite(animImg, 12, 40, 50);
    	//spriteWorld.hide();
    	for (int i=0; i < 12; ++i) {
    	    spriteWorld.addState(0, i, 10);
    		spriteWorld.setAlpha(i, (byte)150);
    	}
		addSprite(INDEX_WORLD, spriteWorld);
    	
    	inited = true;
	}
        
    
    /**
     *  This method adds a sprite into the array of sprite on the playfield.
     *  Adding a sprite means that during the processing loop, the sprite will
     *  be draw on the screen (if it's not hidden), it's position will be updated,
     *  and more (see the 'run' routine).
     */
    
    public void addSprite(int i, Sprite s) {
        sprite[i] = s;
        totalSprites++;
    }
    
    /**
     *  This method converts mouse x-coordinates to playfield x-coordinates.
     */
     
    public int MtoPX(int xx) {
        return xx - x;
    }
    
    
    /**
     *  This method converts mouse y-coordinates to playfield y-coordinates.
     */
     
    public int MtoPY(int yy) {
        return yy - y;
    }
    
    
    /**
     *  Handler for receiving mouse move events.  We use mouse move events to 
     *  update the position of the player on the screen.
     */
     
    public boolean handleMouseMove(int xx, int yy) {
        yy = Math.max(yy, enemyYPosition + 60);
        spriteYou.setDestination(MtoPX(xx) - spriteYou.width / 2, MtoPY(yy) - spriteYou.height / 2, 3);
        return true;
    }
    
    
    /**
     *  Handler for receiving mouse down events.  We'll just store the fact that
     *  mouse button is down for use in the 'fireGoodGuysShots()' method below.
     */

    public boolean handleMouseDown() {
        mouse_down = true;
        return true;
    }
    
    /**
     *  Handler for receiving mouse up events.  We'll just store the fact that
     *  mouse button not longer down for use in the 'fireGoodGuysShots()' method below.
     */

    public boolean handleMouseUp() {
	mouse_down = false;
        return true;
    }
    
    
    /**
     *  This method determines if a given point on the playfield hits a non-transparent
     *  sprite's pixel in the playfield.  Returns the integer id of the sprite if a hit
     *  occurs, or -1 if no hit occurs.
     */
    
    public int coordinateHitsWhichSprite(int xx, int yy) {
        for (int i=0; i < totalSprites; ++i) {
            if (sprite[i].hidden) continue;
            if (sprite[i].testIfHitsNonTransparentPixel(xx - sprite[i].x, yy - sprite[i].y))
                return i;
        }
        return -1;
    }

    /**
     *  This method determines if a given point on the playfield hits a non-transparent
     *  sprite's pixel in the playfield, provided that the sprite's id is not equal to the
     *  first argument passed in.  Returns the integer id of the sprite if a hit occurs, 
     *  or -1 if no hit occurs.
     */
 
    public int coordinateHitsWhichSpriteExceptThis(int except, int xx, int yy) {
        for (int i=0; i < totalSprites; ++i) {
            if (i == except) continue;
            if (sprite[i].hidden) continue;
            if (sprite[i].testIfHitsNonTransparentPixel(xx - sprite[i].x, yy - sprite[i].y))
                return i;
        }
        return -1;
    }

    
    /**
     *  This method determines if a given point on the playfield hits a non-transparent
     *  sprite's pixel in the playfield, provided that the sprite's id is between a specified
     *  range of sprite id's.  Returns the integer id of the sprite if a hit occurs, 
     *  or -1 if no hit occurs.
     */
 
    public int coordinateHitsWhichSpriteInThisRange(int begin, int end, int xx, int yy) {
        for (int i=0; i < totalSprites; ++i) {
            if (i < begin || i >= end) continue;
            if (sprite[i].hidden) continue;
            if (sprite[i].testIfHitsNonTransparentPixel(xx - sprite[i].x, yy - sprite[i].y))
                return i;
        }
        return -1;
    }
    
    
    
    /**
     *  This is the main processing loop.  It's job is to draw sprites on the
     *  screen when it needs updating, change the positions of the sprites,
     *  fire shots from the player and the invaders, and handle collision
     *  detection.
     */
     
    public void run() {
        while(true) {
            if (inited) {
                drawSpritesToScreenIfNeeded();
                moveSpritesTowardDestinations();
                updateSprites();
                
                if (gameInProcess) {
                    updateEnemyPositions();

                    fireGoodGuysShots();
                    fireEnemiesShots();
          
                    processGoodGuysShots();
                    processEnemiesShots();
                    
                    checkIfInvadersAtShields();
                    checkIfInvadersAlive();
                    reIncarnatePlayerIfDeaded();
                    
                } else {
                    tryToRestartGame();
                }
            }
            
            try {
                thread.sleep(1);
            } catch (Exception e) {
            }
        }
    }




    long playerReIncarnationTime;   // system time for when the player will reappear after dying
    
    
    /**
     *  This is called when the player dies for some reason.  Decrements the number
     *  of lives left and calls gameOver() if none left.
     */
    
    void playerGotHit() {
        spriteLives[numLives].hide();
        numLives--;
        if (numLives < 0) {
            gameOver(true);
        } else {
            spriteLives[numLives].show();
            playerReIncarnationTime = System.currentTimeMillis() + 2000;
        }
    }
    
    
    /**
     *  This will give the player another life when dead.
     */
    
    void reIncarnatePlayerIfDeaded() {
        if (spriteYou.hidden && System.currentTimeMillis() - playerReIncarnationTime > 0) {
            spriteYou.moveSprite(playerXLocation, playerYLocation);
            spriteYou.setDestination(spriteYou.x, spriteYou.y, 3);
            spriteYou.show();
            playerSafetyTime = System.currentTimeMillis() + 1000;
        }
    }
    
    
    /**
     *  Have the invaders landed?  Bill dies if so.
     */
 
    void checkIfInvadersAtShields() {
        if (enemyYPosition > 190) {
            playerGotHit();
        }
    }
   
    
    /**
     *  Has Bill eaten all the apples?  Congratulate him if so.
     */

    void checkIfInvadersAlive() {
        boolean oneLeft=false;
        for (int i=0; i < TOTAL_ENEMIES; ++i) {
            if (! spriteEnemy[i].hidden) {
                oneLeft = true; 
                break;
            }
        }
        if (! oneLeft) {
            playerWonGame();
        }
    }
    
    
    /**
     *  Go Bill.  Calling this method indicates the player won the game.
     */

    void playerWonGame() {
        gameOver(false);
    }
    
    
    /**
     *  This method will display a message indicating the game is over.
     *  Pass in true to indicated the player lost, or false to indicate
     *  the player won the game.
     */
    
    void gameOver(boolean playerKilled) {
        timeTillNextGame = System.currentTimeMillis() + 3000;
        gameInProcess = false;
        enemyYPosition = 40;
        
        if (playerKilled) {
    		spriteGameOver.moveSprite(100, 100);
            spriteGameOver.setDestination(100, 100, 5);
            spriteGameOver.show();
        } else {
    		spriteYouWin.moveSprite(350, 350);
            spriteYouWin.setDestination(100, 100, 5);
            spriteYouWin.show();
        }
		spriteRestart.moveSprite(-100, -50);
        spriteRestart.setDestination(100, 150, 3);
        
        spriteRestart.show();
        spriteWorld.show();
    }
    
    
    /**
     *  Call this method to restart the game.  It basically re-does the initialization
     *  steps and updates a couple sprites.
     */
    
    void restartGame() {
        init();
        spriteGameOver.hide();
        spriteRestart.hide();
        spriteWorld.hide();
        numLives = 2;
        spriteLives[numLives].show();
		for (int i=0; i < TOTAL_ENEMIES; ++i) {
	        spriteEnemy[i].setDestination(999, spriteEnemy[i].y, 2);
    	}
        
        gameInProcess = true;
    }
    
    
    /**
     *  This method checks if the player wants to start a new game.
     */

    void tryToRestartGame() {
        if (mouse_down) {
            if (System.currentTimeMillis() - timeTillNextGame > 0) {
                restartGame();
            }
        }
    }

    /**
     *  This method calls the update method for all the sprites in the playfield.
     */

    void updateSprites() {
        for (int i=0; i < totalSprites; ++i) {
            sprite[i].update();
        }
    }

    
    /**
     *  This method goes through the array of sprites on the playfield and determines
     *  if one of them needs updating.  If so, the playfield is draw on the screen.
     */
  
    protected void drawSpritesToScreenIfNeeded() {
        for (int i=0; i < totalSprites; ++i) {
            if (sprite[i].needUpdate) {
                draw();
                break;
            }
        }
    }
    
    /**
     *  This method handles making the invaders follow a pattern on the screen.
     */

    protected void updateEnemyPositions() {
        // initialize values
        int minValue=9999;
        int maxValue=-9999;
        int yValue=-9999;
        
        // first determine the minimum and maximum coords of the invaders as a group
        for (int i=INDEX_FIRST_ENEMY; i < TOTAL_ENEMIES; ++i) {
            if (sprite[i].hidden) continue;
            if (sprite[i].x < minValue) minValue = sprite[i].x;
            if (sprite[i].x > maxValue) maxValue = sprite[i].x;
            if (sprite[i].y > yValue) yValue = sprite[i].y;
        }
        
        // if we've hit the left hand side, then send the invaders down a bit
        if (minValue < 10) {
            enemyXPosition = 999;
            enemyYPosition += 15;
            for (int i=INDEX_FIRST_ENEMY; i < TOTAL_ENEMIES; ++i) 
                sprite[i].setDestination(sprite[i].x + 1, 999, 2);
        }
        
        // if we've hit the right hand side, then send the invaders down a bit
        if (maxValue > 310) {
            enemyXPosition = -999;
            enemyYPosition += 15;
            for (int i=INDEX_FIRST_ENEMY; i < TOTAL_ENEMIES; ++i) 
                sprite[i].setDestination(sprite[i].x - 1, 999, 2);
        }
 
        // if we've gone down, then go across to the other side of the screen
        if (yValue >= enemyYPosition) {
            for (int i=INDEX_FIRST_ENEMY; i < TOTAL_ENEMIES; ++i) 
                sprite[i].setDestination(enemyXPosition, sprite[i].y -1, 2);
        }
        
    }


    /**
     *  This method moves each of the sprites on the playfield towards its 
     *  destination.  The destination is set by calling the setDestination()
     *  method.  If we change the position of a sprite, then we mark that
     *  sprite for updating on the screen.
     */

    protected void moveSpritesTowardDestinations() {
        for (int i=0; i < totalSprites; ++i) {
            if ((!sprite[i].hidden || sprite[i].erase > 0) && 
                (sprite[i].x != sprite[i].destx || sprite[i].y != sprite[i].desty)) {
                
                for (int k=0; k < sprite[i].changeAmt; ++k) {
                    if (sprite[i].x < sprite[i].destx) sprite[i].x++;
                    if (sprite[i].y < sprite[i].desty) sprite[i].y++;
                    if (sprite[i].x > sprite[i].destx) sprite[i].x--;
                    if (sprite[i].y > sprite[i].desty) sprite[i].y--;
                }
                
                sprite[i].needUpdate = true;
            }
        }
    }
    
    
    /**
     *  This method handles firing the player's shots.  We only allow the player
     *  to fire at most every half-second, and at most 'TOTAL_GOOD_FIRES' shots at
     *  once.
     */
 
    protected void fireGoodGuysShots() {
        // check if it's been a half second since the last shot
        if (!spriteYou.hidden && mouse_down && (System.currentTimeMillis() - lastPlayerFireTime) > 500) {
            // if so, see if there are less than TOTAL_GOOD_FIRES shots on the screen
            for (int i=0; i < TOTAL_GOOD_FIRES; ++i) {
    	        if (fireYou[current_player_fire].hidden) { break; }
                current_player_fire++;
	            if (current_player_fire >= TOTAL_GOOD_FIRES) { current_player_fire = 0; }
	        }
	        // if there's a free shot ...
	        if (fireYou[current_player_fire].hidden) {
	            // place it on the playfield, and make it visible
	            //int xx = ((spriteYou.x + spriteYou.width / 2) / 3) * 3;
	            int xx = spriteYou.x + spriteYou.width / 2;
    	        fireYou[current_player_fire].moveSprite(xx, spriteYou.y);
    	        fireYou[current_player_fire].setDestination(xx, -100, 6);
    	        fireYou[current_player_fire].show();
    	        ++current_player_fire;
	            if (current_player_fire >= TOTAL_GOOD_FIRES) { current_player_fire = 0; }
    	        lastPlayerFireTime = System.currentTimeMillis();
    	    }
    	}
    }


    /**
     *  This method handles firing the invader's shots.  We only allow the enemy
     *  to fire at most few milliseconds (set in enemyFireTime), and at most 
     *  'TOTAL_ENEMY_FIRES' shots at once.
     */
 
    protected void fireEnemiesShots() {
        // if it's been long enough since the last shot the enemy fired ...
        if ((System.currentTimeMillis() - nextEnemyFireTime) > 0) {
            // then see if there are less than 'TOTAL_ENEMY_FIRES' shots on the screen
            for (int i=0; i < TOTAL_ENEMY_FIRES; ++i) {
    	        if (fireEnemy[current_enemy_fire].hidden) { break; }
                current_enemy_fire++;
	            if (current_enemy_fire >= TOTAL_ENEMY_FIRES) { current_enemy_fire = 0; }
	        }
	        // if there's a free shot ...
	        if (fireEnemy[current_enemy_fire].hidden) {
	            // then determine which enemy should fire it
	            int whichEnemy=-1;
	            for (int i=0; i < TOTAL_ENEMIES; ++i) {
    	            whichEnemy = (int)(Math.random() * TOTAL_ENEMIES);
    	            if (spriteEnemy[whichEnemy].hidden == false) break;
    	            whichEnemy = -1;
    	        }
    	        // if we found one ...
    	        if (whichEnemy != -1) {
    	            // then place the shot on the playfield and make it visible
    	            int xx = ((spriteEnemy[whichEnemy].x + spriteEnemy[whichEnemy].width / 2) / 3) * 3;
        	        fireEnemy[current_enemy_fire].moveSprite(xx, spriteEnemy[whichEnemy].y);
        	        fireEnemy[current_enemy_fire].setDestination(xx, 500, 6);
        	        fireEnemy[current_enemy_fire].show();
        	        ++current_enemy_fire;
    	            if (current_enemy_fire >= TOTAL_ENEMY_FIRES) { current_enemy_fire = 0; }
    	            
    	            // determine when the next enemy shot should occur
        	        nextEnemyFireTime = System.currentTimeMillis() + (long)(Math.random() * 1000.);
        	    }
        	    // if we didn't find an enemy to fire the shot, then we'll
        	    // find one next time
    	    }
    	}
    }
    
    
    /**
     *  This method handles collision detection of the player's shots.  If we hit another
     *  bullet, then destroy it and keep going, if we hit an enemy then destroy it and the
     *  player's bullet, if we hit the shields, then take a piece out of them.
     */
    
    protected void processGoodGuysShots() { 
        for (int i=0; i < TOTAL_GOOD_FIRES; ++i) {
            if (! fireYou[i].hidden) {
                // if we've reached the top, then hide this bullet
                if (fireYou[i].x == fireYou[i].destx && fireYou[i].y == fireYou[i].desty) {
                    fireYou[i].hide();
                }
                
                int xx = fireYou[i].x;
                int xend = xx + fireYou[i].width;
                int yy = fireYou[i].y;
                
                // collision detection
                while(xx < xend) {
                    int hit = coordinateHitsWhichSpriteExceptThis(INDEX_FIRST_GOOD_FIRE + i, xx, yy);
                    
                    // did we hit an enemy
                    if (hit >= INDEX_FIRST_ENEMY && hit < INDEX_FIRST_ENEMY + TOTAL_ENEMIES) {
                        sprite[hit].hide();
                        fireYou[i].hide();
                    } else 
                    // did we hit a bullet
                    if (hit >= INDEX_FIRST_ENEMY_FIRE && hit < INDEX_FIRST_ENEMY_FIRE + TOTAL_ENEMY_FIRES) {
                        sprite[hit].hide();
                    } else
                    // did we hit a shield
                    if (hit >= INDEX_FIRST_SHIELD && hit < INDEX_FIRST_SHIELD + TOTAL_SHIELDS) {
                        int s = hit - INDEX_FIRST_SHIELD;
                        int yend = spriteShield[s].y + spriteShield[s].height-1;
                        int hit1;
                        // find a piece of the shield to remove ...
                        for (int ycoord=yend; ycoord >= spriteShield[s].y; ycoord--) {
                            hit1 = coordinateHitsWhichSpriteInThisRange(
                                INDEX_FIRST_SHIELD, INDEX_FIRST_SHIELD + TOTAL_SHIELDS, xx, ycoord
                            );
                            
                            if (hit1 != -1) {
                                // ... and remove it
                                Rectangle rect = new Rectangle(xx, ycoord-11, xend - xx, 12);
                                if (Clipper.clipRect(rect, spriteShield[s].toRect())) {
                                    for (int k=rect.x; k < rect.x + rect.width; ++k) {
                                        for(int m=rect.y; m < rect.y + rect.height; ++m) {
                                            spriteShield[s].setPixel(0, k - spriteShield[s].x, m - spriteShield[s].y);
                                        }
                                    }
                                }
                                break;
                            }
                        }
                        lastPlayerFireTime = 0;
                        fireYou[i].hide();
                        
                        break;
                    }
                    xx++;
                }
            }
        }
    }
    
    
    /**
     *  This method handles collision detection of the invaders's shots.  If we hit the
     *  player, then destroy him.  If we hit the shields, then take a piece out of them.
     */
    
    protected void processEnemiesShots() {
        
        for (int i=0; i < TOTAL_ENEMY_FIRES; ++i) {
            if (! fireEnemy[i].hidden) {
                if (fireEnemy[i].x == fireEnemy[i].destx && fireEnemy[i].y == fireEnemy[i].desty) {
                    fireEnemy[i].hide();
                }
                int xx = fireEnemy[i].x;
                int xend = xx + fireEnemy[i].width;
                int yy = fireEnemy[i].y + fireEnemy[i].height;
                
                while(xx < xend) {
                    int hit = coordinateHitsWhichSpriteExceptThis(INDEX_FIRST_ENEMY_FIRE + i, xx, yy);
                   
                    // did we hit the player
                    if (hit == INDEX_GOOD_GUY  &&  System.currentTimeMillis() - playerSafetyTime > 0) {
                        fireEnemy[i].hide();
                        spriteYou.hide();
                        draw();
                        playerGotHit();
                    } else
                    // did we hit a shield
                    if (hit >= INDEX_FIRST_SHIELD && hit < INDEX_FIRST_SHIELD + TOTAL_SHIELDS) {
  
                        int s = hit - INDEX_FIRST_SHIELD;
                        int ystart = spriteShield[s].y;
                        int hit1;
                        // find a piece of the shield to remove ...
                        for (int ycoord=ystart; ycoord < spriteShield[s].y + spriteShield[s].height; ycoord++) {
                            hit1 = coordinateHitsWhichSpriteInThisRange(
                                INDEX_FIRST_SHIELD, INDEX_FIRST_SHIELD + TOTAL_SHIELDS, xx, ycoord
                            );
                            
                            if (hit1 != -1) {
                                // ... and remove it
                                Rectangle rect = new Rectangle(xx, ycoord, xend - xx, 12);
                                if (Clipper.clipRect(rect, spriteShield[s].toRect())) {
                                    for (int k=rect.x; k < rect.x + rect.width; ++k) {
                                        for(int m=rect.y; m < rect.y + rect.height; ++m) {
                                            spriteShield[s].setPixel(0, k - spriteShield[s].x, m - spriteShield[s].y);
                                        }
                                    }
                                }
                                break;
                            }
                        }
                        fireEnemy[i].hide();
                        
                        break;
                    } 
                    xx++;
                }
            }
        }
    }
    
    
    int TIME_TO_GC = 5;
    int cur_gc = 0;
    
    /**
     *  This method handles rendering the playfield at each frame.  Notice that
     *  we only redraw those parts of the playfield that we must at each frame.
     *  Basically the process is:
     *  a) for each sprite, clip the sprite to the playfield, and store if 
     *      the sprite is on the playfield
     *  b) for each sprite, erase the playfield's raster at the sprite's location.
     *  c) for each sprite, draw into the playfield's raster at the sprite's location
     *  d) draw the playfield's raster image to the screen
     */

    public void draw() {
        
        Rectangle clipRect[] = new Rectangle[totalSprites];
        boolean inThePark[] = new boolean[totalSprites];
        
        // a) for each sprite, clip the sprite to the playfield, and store if 
        //    the sprite is on the playfield
        for (int i=0; i < totalSprites; ++i) {
            clipRect[i] = new Rectangle(0,0, width, height);
            inThePark[i] = Clipper.clipRect(clipRect[i], sprite[i].toRect());
            if (inThePark[i] && clipRect[i].x - sprite[i].x < 0) {
                int g=0;
            }
        }

        // b) for each sprite, erase the playfield's raster at the sprite's old location
        for (int i=0; i < totalSprites; ++i) 
            if (inThePark[i]) 
                erasePlayfieldBufferAtThisSpritesLocation(i, clipRect[i]);
        
        // c) for each sprite, draw into the playfield's raster at the sprite's new location
        for (int i=0; i < totalSprites; ++i) 
            if (inThePark[i]) 
                drawIntoPlayfieldBufferAtThisSpritesLocation(i, clipRect[i]);
                
                
        // d) draw the playfield's raster image to the screen
        if (true) {  
            
            Image img = buffer.toImage();
            root.getGraphics().drawImage(img, x, y, null);
            img = null;
            
            
        } else {   
            
            // Notice that this code will only draw the parts of the playfield
            // the need updating on the screen.  I'm not using it because, while
            // this is faster on nt, it seems to be slower on sgi's.  There are
            // a couple other ways to do this, but they require versions of the
            // JDK beyond 1.0.2, which I'd just as soon not use.
            for (int i=0; i < totalSprites; ++i) {
                if (inThePark[i]) {               
                    copyPlayfieldBufferIntoSpritesBuffer(i, clipRect[i]);
                    Graphics g = root.getGraphics();
                    sprite[i].draw(g, x, y, clipRect[i]);
                    g.dispose();
                }
            }
            
        }
        
        // update fields
        for (int i=0; i < totalSprites; ++i) {
            if (sprite[i].erase == 1) sprite[i].erase = 2;
            sprite[i].needUpdate = false;
        }
        
        // garbage collect every now and then
        if (cur_gc > TIME_TO_GC) {
            cur_gc = 0;
            System.gc();
        }
        cur_gc++;
        
    }
    
    
    /**
     *  Erase the playfield's raster at the sprite's old location.  
     *  Argument 1 is the sprite's integer id. 
     *  Argument 2 is the clipping rectangle in playfield coordinates for this sprite.
     */
    
    protected void erasePlayfieldBufferAtThisSpritesLocation(int i, Rectangle clipRect) {
        if (! sprite[i].hidden || sprite[i].erase > 0) {
            buffer.pixBlt(
                this, 
                clipRect.x, clipRect.y, 
                clipRect.width, clipRect.height, 
                clipRect.x, clipRect.y
            );
        }
    }
    
    
    /**
     *  Draw into the playfield's raster at the sprite's new location.
     *  Argument 1 is the sprite's integer id. 
     *  Argument 2 is the clipping rectangle in playfield coordinates for this sprite.
     */
    
    protected void drawIntoPlayfieldBufferAtThisSpritesLocation(int i, Rectangle clipRect) {
        if (! sprite[i].hidden) {
            // use alpha blending
            buffer.pixBltAlpha(
                sprite[i],
                clipRect.x - sprite[i].x, clipRect.y - sprite[i].y, 
                clipRect.width, clipRect.height, 
                clipRect.x, clipRect.y
            );
        }
    }
    
    
    /**
     *  This copies part of the playfield's raster into the sprite's raster.
     *  Argument 1 is the sprite's integer id. 
     *  Argument 2 is the clipping rectangle in playfield coordinates for this sprite.
     */
    
    protected void copyPlayfieldBufferIntoSpritesBuffer(int i, Rectangle clipRect) {
        if (! sprite[i].hidden || sprite[i].erase > 0) {
            sprite[i].buffer.pixBlt(
                buffer, 
                clipRect.x, clipRect.y,
                clipRect.width, clipRect.height, 
                clipRect.x - sprite[i].x, clipRect.y - sprite[i].y
            );
        }
    }
    
    
    
}

