import java.applet.*; 
import java.awt.*; 
import java.awt.image.*; 
import java.awt.event.*; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 

public class PrototypeZed extends BApplet {
String[] HERO_FRAMES_LIST = { "hero_001.gif", "hero_002.gif", "hero_003.gif" };
String[] HERO_DEATH_FRAMES_LIST = {"herodeath_001.gif", "herodeath_002.gif", "herodeath_003.gif", "herodeath_004.gif", "herodeath_005.gif", "herodeath_006.gif", "herodeath_007.gif", "herodeath_008.gif", "herodeath_009.gif", "herodeath_010.gif", "herodeath_011.gif", "herodeath_012.gif",  "herodeath_013.gif",  "herodeath_014.gif",  "herodeath_015.gif",  "herodeath_016.gif" };
String[] LAZER_FRAMES_LIST = { "lazer_001.gif", "lazer_002.gif", "lazer_003.gif" };
String[] ALIEN_FRAMES_LIST = { "alien_001.gif", "alien_002.gif", "alien_003.gif", "alien_004.gif" };
String[] ALIEN_DEATH_FRAMES_LIST = { "aliendeath_001.gif", "aliendeath_002.gif", "aliendeath_003.gif", "aliendeath_004.gif", "aliendeath_005.gif", "aliendeath_006.gif", "aliendeath_007.gif", "aliendeath_008.gif", "aliendeath_009.gif", "aliendeath_010.gif", "aliendeath_011.gif", "aliendeath_012.gif" };

BImage[] HERO_FRAMES, HERO_DEATH_FRAMES, LAZER_FRAMES, ALIEN_FRAMES, ALIEN_DEATH_FRAMES;

BFont theFont;

List gameObjectList;

int alienGeneratorCounter;

boolean gameStarted = false;
boolean gamePaused = false;
boolean gameOver = false;
int pauseFrameDelay = 0;
int[] screenCapture;

Point[] stars = new Point[100];

void setup()
{
  size( 400, 450 );
  background( 0xffEEEEEE );
  
  // Load up all the frames for all the sprites.
  HERO_FRAMES = loadFrames( HERO_FRAMES_LIST );
  LAZER_FRAMES = loadFrames( LAZER_FRAMES_LIST );
  ALIEN_FRAMES = loadFrames( ALIEN_FRAMES_LIST );
  ALIEN_DEATH_FRAMES = loadFrames( ALIEN_DEATH_FRAMES_LIST );
  HERO_DEATH_FRAMES = loadFrames( HERO_DEATH_FRAMES_LIST );

  theFont = loadFont( "OCR-B.vlw.gz" );
  
  int x = 0, y = 0;
  for( int i=0; i < stars.length; i++ )
  {
    x = (int)(random( 0, width ));
    y = (int)(random( 0, height ));
    stars[i] = new Point( x, y );
  }

  gameObjectList = new List();
  gameObjectList.add( new Hero() );
}

void loop()
{
  if( gameOver )
  {
    drawGameOverScreen();
  }
  else if( gameStarted)
  {
    if( gamePaused )
      drawPauseScreen();
    else 
      runGamePlay();
  }
  else
    drawTitleScreen();

  getUserInput();
}

void resetGame()
{
  gameStarted = false;
  gamePaused = false;
  gameOver = false;
  
  gameObjectList = new List();
  gameObjectList.add( new Hero() );
}

void drawGameOverScreen()
{
  redrawCapture();

  setFont( theFont, 40 );
  fill( 0xff000000 );
  specialText( "PROTOTYPE zed-6", 10, 30 );
  specialText( "GAME OVER", 10, height/3 );

  setFont( theFont, 27 );
  specialText( "press Q to start over", 10, height - 30 );
}

void drawPauseScreen()
{
  redrawCapture();

  setFont( theFont, 40 );
  specialText( "PROTOTYPE zed-6", 10, 30 );
  specialText( "PAUSED", 10, height/3 );
}

void drawTitleScreen()
{
  drawStarField();
  setFont( theFont, 40 );
  specialText( "PROTOTYPE zed-6", 10, 30 );
  
  setFont( theFont, 27 );

  int textY = height/3;

  specialText( "B - begin the game", 10, textY );
  textY += 30;
  specialText( "P - toggle the pause mode", 10, textY );
  textY += 30;
  specialText( "Q - return to this screen", 10, textY );
  textY += 30;
  specialText( "J - move ship left", 10, textY );
  textY += 30;
  specialText( "K - move ship down", 10, textY );
  textY += 30;
  specialText( "L - move ship right", 10, textY );
  textY += 30;
  specialText( "I - move ship up", 10, textY );
  textY += 30;
  specialText( "SPC - fire ship's weapon", 10, textY );
}

void specialText( String theText, int x, int y )
{
  fill( 0xff000000 );
  text( theText, x+1, y+1 );
  fill( 0xff995252 );
  text( theText, x, y );
}

void getUserInput()
{
  if( pauseFrameDelay > 0 ) pauseFrameDelay--;
  if( keyPressed && key == 'b' )
  {
    gameStarted = true;
  }
  else if( keyPressed && key == 'p' && pauseFrameDelay == 0 && gameStarted )
  {
    gamePaused = ! gamePaused;
    captureScreen();
    pauseFrameDelay = 10;
  }
  else if( keyPressed && key == 'q' )
    resetGame();
}

void runGamePlay()
{
  drawStarField();
  GameObject o = null;

  generateAlien();

  int index = 0;
  while( index < gameObjectList.length() )
  {
    o = (GameObject)gameObjectList.get( index );
    if( o.destroyed() )
    {
      if( o instanceof Hero )
      {
        captureScreen();
        gameOver = true;
      }
      gameObjectList.remove( o );
    }
    else
      index++;
  }

  int length = gameObjectList.length();
  for( int i=0; i < length; i++ )
  {
     o = (GameObject)gameObjectList.get(i);
     o.beginTurn();
  }

  length = gameObjectList.length();
  for( int i=0; i < length; i++ )
  {
     o = (GameObject)gameObjectList.get(i);
     for( int j=0; j < length; j++ )
     {
       GameObject o2 = (GameObject)gameObjectList.get(j);
       if( o != o2 )
         o.act( o2 );
     }
  }
  
  length = gameObjectList.length();
  for( int i=0; i < length; i++ )
  {
     o = (GameObject)gameObjectList.get(i);
     o.display();
  }
}

void drawStarField()
{
  fill( 0xffDFDFDF );
  noStroke();
  rectMode( CENTER_DIAMETER );
  for( int i=0; i < stars.length; i++ )
  {
    stars[i].y = (stars[i].y + 1) % height;
    rect( stars[i].x, stars[i].y, 3, 3 );
  }
}

/**
 * Just makes a new alien every so often.
 **/
void generateAlien()
{
  if( alienGeneratorCounter == 120 )
  {
    alienGeneratorCounter = 0;
    gameObjectList.add( new Alien() );
  }
  else
    alienGeneratorCounter++;
}

class Point
{
  int x,  y;
  Point( int anX, int aY )
  {
    this.x = anX;
    this.y = aY;
  }
}

/**
* The game object is the thing that gives a sequence of images more behavior than
* simply being animated.  Implementations of this abstract class will be the specific items
* of the game, e.g. the player character icon, enemies, missiles and lazers.  This allows
* us maintain a level of expedient brute force while modularizing rendering.
**/
abstract class GameObject
{

  Point myPosition;
  int myWidth, myHeight;

  Point position()
  {
    return new Point( myPosition.x, myPosition.y );
  }

  void position( int anX, int aY )
  {
    this.myPosition.x = anX;
    this.myPosition.y = aY;
  }

  int width()
  {
    return myWidth;
  }
  
  int height()
  {  
    return myHeight;
  }

  /**
   * Returns true of if the given GameObject collides with this one.
   **/
  boolean collidesWith( GameObject o )
  {
    int a1 = this.myPosition.x;
    int a2 = a1 + this.myWidth;
    int b1 = o.myPosition.x;
    int b2 = b1 + o.myWidth;
    
    boolean xOverlaps = intervalOverlaps( a1, a2, b1, b2 );
    
    a1 = this.myPosition.y;
    a2 = a1 + this.myHeight;
    b1 = o.myPosition.y;
    b2 = b1 + o.myHeight;
    
    boolean yOverlaps = intervalOverlaps( a1, a2, b1, b2 );
    
    return xOverlaps && yOverlaps;
  }

  /**
  * The beginTurn method will be called for each object for every frame of the game before
  * anyone's act method is called.
  **/
  abstract void beginTurn();

  /**
  * For every frame/turn the sprite is given access to every other sprite in the game, one at
  * a time, in no particular order.
  **/
  abstract void act( GameObject obj );

  /**
  * After evertyone's acted they get notified to update their display.
  **/
  abstract void display();

  /**
   * Instructs the GameObject to die.  Some number of frames later, after a death animation
   * perhaps, the object will report that it is destroyed.
   **/
  abstract void die();
    
  /**
   * Should return true when the object has become destroyed and should no longer be displayed.
   **/
  abstract boolean destroyed();
}

class Hero extends GameObject
{
  int SHIP_SPEED = 4;
  int SHIP_ACCELERATION = 3;
  int MOMENTUM_MAX = 20;
  int lazerRateCount;
  int verticalMomentum, lateralMomentum;
  int deathCount = 13;

  boolean dying = false;
  boolean destroyed = false;

  Sprite normalSprite;
  Sprite dyingSprite;

  Hero()
  {
    myWidth = 39;
    myHeight = 36;
    myPosition = new Point( width/2 - this.myWidth/2, height/2 - this.myHeight/2 );
    this.normalSprite = new Sprite( HERO_FRAMES );
    this.dyingSprite = new Sprite( HERO_DEATH_FRAMES );
  }

  void beginTurn()
  {
    if( this.dying )
    {
      if( this.deathCount > 0 )
        this.deathCount--;
      else
        this.destroyed = true;
    }
    else
    {
      this.getUserInput();
      this.moveShip();
    }
  }

  void act( GameObject obj )
  {
  }

  void display()
  {
    if( this.dying )
      this.dyingSprite.display( this.myPosition.x, this.myPosition.y );
    else
      this.normalSprite.display( this.myPosition.x, this.myPosition.y );
  }

  boolean destroyed()
  {
    return this.destroyed;
  }

  void die()
  {
    this.dying = true;
  }

  void getUserInput()
  {
    if( this.lazerRateCount > 0 ) this.lazerRateCount--;
    
    if( keyPressed )
    {
      if( key == 'i' && this.myPosition.y > 0 )
        this.verticalMomentum += SHIP_ACCELERATION;
      if( key == 'j' && this.myPosition.x > 0 )
        this.lateralMomentum += SHIP_ACCELERATION;
      if( key == 'k' && (this.myPosition.y + this.myHeight < height))
        this.verticalMomentum -= SHIP_ACCELERATION;
      if( key == 'l' && (this.myPosition.x + this.myWidth < width ))
        this.lateralMomentum -= SHIP_ACCELERATION;
      if( key == ' ' && this.lazerRateCount == 0 )
      {
        this.fireLazer();
      }
    }
  }
  
  void fireLazer()
  {
     Lazer lazer = new Lazer();
     lazer.position( this.myPosition.x + this.myWidth/2 - lazer.width()/2,
                     this.myPosition.y - lazer.height() );
     gameObjectList.add( lazer );
     this.lazerRateCount = 6;
  }

  void moveShip()
  {
    if( this.verticalMomentum < 0 )
    {
      this.verticalMomentum++;
      this.myPosition.y += SHIP_SPEED;
    }
    else if( this.verticalMomentum > 0 )
    {
      this.verticalMomentum--;
      this.myPosition.y -= SHIP_SPEED;
    }
    if( this.lateralMomentum < 0 )
    {
      this.lateralMomentum++;
      this.myPosition.x += SHIP_SPEED;
    }
    else if( this.lateralMomentum > 0 )
    {
      this.lateralMomentum--;
      this.myPosition.x -= SHIP_SPEED;
    }
    this.myPosition.x = range( 0, width - this.myWidth, this.myPosition.x );
    this.myPosition.y = range( 0, height - this.myHeight, this.myPosition.y );
    this.lateralMomentum = range( -MOMENTUM_MAX, MOMENTUM_MAX, lateralMomentum );
    this.verticalMomentum = range( -MOMENTUM_MAX, MOMENTUM_MAX, verticalMomentum );
   
  }
    
}

/**
 * A lazer blast originates from the hero and moves rapidly north, when it collides with another
 * game object that object is destroyed.
 **/
class Lazer extends GameObject
{

  Sprite normalSprite;
  boolean destroyed = false;

  Lazer()
  {
    myWidth = 15;
    myHeight = 21;
    this.myPosition = new Point( 0, 0 );
    this.normalSprite = new Sprite( LAZER_FRAMES );
  }
  
  void beginTurn()
  {
    if( this.myPosition.y + this.myHeight > 0 )
      this.myPosition.y -= 6;
    else
      this.destroyed = true;
  }

  void act( GameObject obj )
  {
    if( obj instanceof Alien && this.collidesWith( obj ) )
    {
      obj.die();
      this.destroyed = true;
    }
  }

  void display()
  {
    this.normalSprite.display( this.myPosition.x, this.myPosition.y );
  }
  
  boolean destroyed()
  {
    return destroyed;
  }
  
  void die()
  {
    // lazer blast's can't be killed, this is a no-op.
  }
  
}


class Alien extends GameObject
{
  int deathCounter = 12;
  boolean destroyed = false;
  boolean dying = false;
  Sprite normalSprite;
  Sprite dyingSprite;
  
  Alien()
  {
    this.myWidth = 60;
    this.myHeight = 60;
    int initialX = (int)(random( 0, width - this.myWidth));
    this.myPosition = new Point ( initialX, -this.myHeight  );
    this.normalSprite = new Sprite( ALIEN_FRAMES );
    this.dyingSprite = new Sprite( ALIEN_DEATH_FRAMES );
  }

  void beginTurn()
  {
    if( this.dying )
    {
      if( this.deathCounter > 0 )
         this.deathCounter--;
      else
         this.destroyed = true;
    }
    if( this.myPosition.y < height )
      this.myPosition.y += 2;
    else
      this.destroyed = true;
  }
  
  void act( GameObject obj )
  {
    if( obj instanceof Hero && this.collidesWith( obj ) )
    {
      obj.die();
      this.die();
    }
  }
  
  void display()
  {
    if( this.dying )
      this.dyingSprite.display( this.myPosition.x, this.myPosition.y );
    else
      this.normalSprite.display( this.myPosition.x, this.myPosition.y );
  }
  
  void die()
  {
    this.dying = true;
  }
  
  boolean destroyed()
  {
    return this.destroyed;
  }
}

/**
 * Sprite class will handle counting frames and displaying the correct image in sequence.
 **/
class Sprite
{
  int frameIndex = 0;
  BImage[] frames;

  Sprite( BImage[] someFrames )
  {
    this.frames = someFrames;
  }

  void display( int x, int y )
  {
    this.frameIndex = (frameIndex + 1) % frames.length;
    this.drawTrans( this.frames[frameIndex], x, y );
  }

  void drawTrans( BImage img, int posX, int posY )
  {
    int pixelCount = img.width * img.height;
    int realX = 0, realY = 0;
    for( int i=0; i < pixelCount; i++ )
    {
      if( img.pixels[i] != 0x00FFFFFF )
      {
        realX = posX + (i % img.width);
        realY = posY + (i - (i % img.width))/img.width;
        if( realX > 0 && realY > 0 && realX < width && realY < height)
          setPixel( realX, realY, img.pixels[i] );
      }
    }
  }

}


/**
 * A very primative sort of collection, handles array expansion automatically.  Not very fancy,
 * but it'll get the job done.
 **/
class List
{
  /**
   * How many objects we actually contain.
   **/
  int myCount;
  
  Object[] myList;
  
  List()
  {
    myList = new Object[16];
  }
  
  /**
   * You cannot add null to this list, it winds up being a no-op.
   **/
  void add( Object obj )
  {
    if( obj == null )
      return;
    if( myCount + 1 > myList.length )
      myExpandCapacity();
    myList[myCount] = obj;
    myCount++;
  }
  
  /**
   * Searches list for specified object having reference equality with given object and removes
   * it.  If object occurs more than once in the list then all references are removed.
   **/
  void remove( Object obj )
  {
    int newCount = myCount;
    for( int i=0; i < myCount; i++ )
    {
      if( myList[i] == obj )
      {
        myList[i] = null;
        newCount--;
      }
    }
    if( newCount < myCount )
       reOrder();
    myCount = newCount;
  }
  
  /**
   * Get the object in the specified index.  If the index does not exist then null is returned.
   **/
  Object get( int index )
  {
    if( index >= 0 && index < myCount )
      return myList[index];
    else
      return null;
  }
  
  /**
   * Returns he number of objects contained in the list.
   **/
  int length()
  {
    return myCount;
  }
  
  /**
   * Expands the contained array and copies the objects into it.  Would be a private method
   * if I were doing this in Java.
   **/
  void myExpandCapacity()
  {
    Object[] newList = new Object[ myList.length * 2 ];
    for( int i = 0; i < myList.length; i++ )
    {
      newList[i] = myList[i];
    }
    myList = newList;
  }
  
  /**
   * Called internally to collapse out all null references.  Would be a private method if 
   * I were doing this in Java.
   **/
  void reOrder()
  {
    Object[] newList = new Object[ myList.length ];
    int i=0;
    int j=0;
    while( i < myList.length )
    {
      if( myList[i] == null )
      {
        i++;
      }
      else
      {
        newList[j] = myList[i];
        i++;
        j++;
      }
    }
    myList = newList;
  }
  
}

/**
 * Tests if the interval given by the exclusive pair (a1, a2) overlaps with (b1, b2).
 **/
boolean intervalOverlaps( int a1, int a2, int b1, int b2 )
{
  boolean overlaps = false;
  if( b1 > a1 && b1 < a2 )
    overlaps = true;
  else if( b2 > a1 && b2 < a2 )
    overlaps = true;
  else if( a1 > b1 && a1 < b2 )
    overlaps = true;
  else if( a2 > b1 && a2 < b2 )
    overlaps = true;
  return overlaps;
}

/**
  * Loads the given file names
  **/
BImage[] loadFrames( String[] frameNames )
{
  BImage[] frames = new BImage[frameNames.length];
  for( int i=0; i < frameNames.length; i++ )
  {
    frames[i] = loadImage( frameNames[i] );
  }
  return frames;
}

/**
 * Returns the a value in the (inclusive) range specified by the aMin and aMax parameters
 **/
int range( int aMin, int aMax, int value  )
{
  return max( aMin, min( aMax, value ));
}

void captureScreen()
{
  screenCapture = new int[pixels.length];
  int c = 0;
  for( int i=0; i < pixels.length; i++ )
  {
    c = pixels[i];
    c = (int)( (red(c) + green(c) + blue(c)) / 3 );
    screenCapture[i] = color( c, c, c );
  }
}

void redrawCapture()
{
  for( int i=0; i < screenCapture.length; i++ )
  {
    pixels[i] = screenCapture[i];
  }
}

}