/* $Id$
 *
 * Copyright (c) 1999 Xenonsoft Limited. All Rights Reserved.
 *
 * This software is protected by copyright. You are hereby notified from
 * now by reading this message. This software is also the confidential 
 * and proprietary information of Xenonsoft Limited.  ("Confidential
 * Information"). 
 *
 * This software is distributed under the Xenonsoft Public end user 
 * License ("XPeL"), where the machine-readable source code is provided 
 * under the "Open Source" model.
 * For more information, please read the file "LICENSE-XPL.txt" 
 */

// File:`HumanoidGameEngine.java'
//
//
// DESCRIPTION
//	Humanoid Game Engine
// 
// Author
//
//	Peter Pilgrim
//	Tue May 18 01:35:52 BST 1999
// 
// RCS HEADER ``HumanoidGameEngine.java''
// 
//	$Author$
//	$Date$
//	$Revision$    $State$    $Locker$
//

package xenon.humanoid;

import java.beans.*;				// for PropertyChangeListener
import java.io.*;				// for FileInputStream, etc
import java.awt.*;				// for Component, Graphics
import java.awt.event.*;			// for ActionEvent, ActionListener
import java.awt.image.*;			// for ImageProducer
import java.util.*;				// for Vector

import xenon.gamekit.*;				// for GameEngine, WorldObject, etc
import xenon.soundkit.*;			// for SoundEngine, PPMCAudioPlayer, etc

public class HumanoidGameEngine extends GameEngine
implements Runnable
{
    /** The initial number of humans */
    public final static int	DEFAULT_NUMBER_OF_HUMANS=10;
    /** The number of alien landers per wave */
    public final static int	DEFAULT_NUMBER_OF_LANDERS_PER_WAVE=10;
    /** A counter for prologue screen */
    public final static int	PROLOGUE_COUNTER=160;
    
    public final static double  MAX_BOMBLET_PROBABILITY=0.40;
    public final static double  BASE_BOMBLET_PROBABILITY=0.0512;
    public final static double  FACTOR_BOMBLET_PROBABILITY=0.0140;
    public final static double  FACTOR2_BOMBLET_PROBABILITY=0.0061;
    public final static double  BAITER_FACTOR_BOMBLET_PROBABILITY=0.0128;
    public final static double  BAITER_BASE_BOMBLET_PROBABILITY=0.0750;
    public final static double  BOMBER_FACTOR_BOMBLET_PROBABILITY=0.0312;
    public final static double  BOMBER_BASE_BOMBLET_PROBABILITY=0.0;
    
    public final static int	NUMBER_OF_SWARMERS_PER_POD=8;

    public final static BonusLevelTuple bonusLevelMap[] = {
	new BonusLevelTuple( 1,  0 ),		/* EMPTY */
	new BonusLevelTuple( 3,  15000 ),
	new BonusLevelTuple( 5,  45000 ),
	new BonusLevelTuple( 7,  75000 ),
	new BonusLevelTuple( 9,  100000 ),
	new BonusLevelTuple( 11, 125000 ),
    };

    public final static String GAME_PAUSED_MESSAGE2[] = {
	"THIS GAME IS NOW PAUSED",
	"Pressed the PAUSE key to continue."
    };

    public final static String GAMEOVER_TEXT[] = {
	"GAME OVER",
	"Baby Bop!"
    };
    
    
    public static String KEYBOARD_CONTROLS_TEXT[] = {
	"Keyboard Controls",
	"===================", 
	"",
	"A ... Starship Up",
	"Z ... Starship Down",
	"SPACE ... Change Direction",
	"RETURN ... Fire Laser",
	"SHIFT ... Thrust Engines",
	"S ... Smart Bomb,  H ... Hyperspace",
	"P...PAUSE",
	"CTRL-M ... Mute Sound, CTRL-K ... Configure Keys",  
	"CTRL-Q ... Quit Game,  CTRL-E ... Exit Session",
	"",
	"Press [SPACE] to start game."
    };

    public final static String CREDITS_TEXT[] = {
	"Humanoid",
	"a pure Java[tm] clone of the classic original",
	"Defender Arcade Game created by Williams 1981.",
	"Written and produced by Peter A. Pilgrim",
	"[c] June 1999, XeNoNSoFT, South London, England.",
	"\"Don't funk around with I.T., Baby Bop!\"",
	"",
	"Reference: ``The Winners Book of Video Games''",
	"by Craig Kubey, first published 1982."
    };

    public static int KEYCODE_UP	= KeyEvent.VK_A;
    public static int KEYCODE_DOWN	= KeyEvent.VK_Z;
    public static int KEYCODE_COD	= KeyEvent.VK_SPACE;
    public static int KEYCODE_LASER	= KeyEvent.VK_ENTER;
    public static int KEYCODE_THRUST	= KeyEvent.VK_SHIFT;
    public static int KEYCODE_SMARTBMB	= KeyEvent.VK_S;
    public static int KEYCODE_HYPERSPC	= KeyEvent.VK_H;
    public static int KEYCODE_PAUSE	= KeyEvent.VK_P;
    
    protected UserKeyTuple gamekeyList [] = {
	new UserKeyTuple( "Move Up",		KEYCODE_UP ), 
	new UserKeyTuple( "Move Down",		KEYCODE_DOWN ), 
	new UserKeyTuple( "Change Direction",	KEYCODE_COD ), 
	new UserKeyTuple( "Fire Laser",		KEYCODE_LASER ), 
	new UserKeyTuple( "Thrust Starship",	KEYCODE_THRUST ), 
	new UserKeyTuple( "Smart Bomb",		KEYCODE_SMARTBMB ), 
	new UserKeyTuple( "Hyperspace Jump",	KEYCODE_HYPERSPC ), 
	new UserKeyTuple( "Pause Key",		KEYCODE_PAUSE ), 
    };
	

    /** Score entry dialog */
    protected ScoreEntry	scoreEntry=null;
    /** Reconfigure game key dialog */
    protected UserKeyControlDialog keyDialog=null;

    /** bonus level index */
    protected int		bonusLevelIndex = 0;
    

    boolean	up_key_pressed;			// Pressed status of up key
    boolean     down_key_pressed;		// Pressed status of down key;
    boolean	thrust_key_pressed;		// pressed status of thrust key;
    boolean	fire_key_pressed;		// pressed status of fire key;
    boolean	cod_key_pressed;		// pressed status of change
						// of direction key;
    boolean	smartbomb_key_pressed;		// Pressed status of smart bomb key
    boolean	hyperspace_key_pressed;		// pressed status of hyperspace key;
    boolean	exit_key_pressed;		// pressed status of fire key;
    boolean	level_key_pressed;		// Pressed status of level key
    boolean	reconfig_key_pressed;		// Pressed status of reconfigure key

    protected int	upperwin_height = 100;

    protected Font	keysFont = new Font("Dialog", Font.PLAIN, 10 );
    protected Font	scoreFont = new Font( "Dialog", Font.BOLD, 10 );
    protected Font	scoreLargeFont = new Font( "SanSerif", Font.BOLD, 12 );
    protected Font      scoreBigFont = new Font( "SanSerif", Font.BOLD, 14 );

    /** the number of humans left at the end of the last sheet */
    protected int	numberOfHumansLeft;

    /** the number of smart bombs */
    protected int	numberOfSmartBombs;

    /** a counter used to disable the smart counter for N frames */
    protected int	smartCounter;
    
    public final static int SMART_BOMB_DELAY=6;
    

    /** number of wave int this level */
    protected int	numberOfWaves;

    /** number of aliens to bombers (for this sheet) */
    protected int	numberOfBombers;

    /** number of aliens to pods (for this sheet) */
    protected int	numberOfPods;

    /** number of aliens to baiters (for this sheet) */
    protected int	numberOfBaiters;

    /** the responsiveness 0=fastest and 6=slowest */ 
    protected int	landerResponsiveness;

    /** the lander magnitude  */ 
    protected int	landerMagnitude;

    /** the next bonus points limit */
    protected int	nextBonus;

    /** the bomblet probability for aliens */
    protected double	bombletProbability;

    /** the bomblet probability for baiters */
    protected double	bombletProbabilityBaiters;

    /** the bomblet probability for bomber */
    protected double	bombletProbabilityBombers;

    /** true if all the landers have mutated */
    protected boolean	allLandersMutated=false;

    /** true if the planet has exploded */
    protected boolean	planetExploded=false;

    /** showing prologue state or not */
    protected int	statePrologue;

    /** the number of lander waves remaining after player died */
    protected int	survivingWaves;

    /** the number of bombers remaining after player died */
    protected int	survivingBombers;

    /** the number of pods remaining after player died */
    protected int	survivingPods;

    /** the number of swarmers remaining after player died */
    protected int	survivingSwarmers;

    /** the number of surviving mutants after player died */
    protected int	survivingMutants;

    /** true if a new sheet is being entered into so that full set 
     * of swarmers and pod are generated for a new sheet 
     */
    protected boolean	enteringNewLevel;
    
    
    protected final static int	PROLOGUE_SHOW_CONTROLS=0;
    protected final static int	PROLOGUE_SHOW_HIGHSCORES=1;
    protected final static int	PROLOGUE_SHOW_ENTITIES=2;
    protected final static int	PROLOGUE_SHOW_CREDITS=3;
    
    
    /** the entity list */
    protected WorldObject []  entityList;

    /** level completed message */
    protected String	levelCompletedMsg;
    /** variable used to count the rescued human counter */
    protected int	rescuedHumanBonusCounter;
    /** variable used to count the rescued human base */
    protected int	rescuedHumanBonusBase;

    /** variable used to show the jump sheet bonus */
    protected int	jumpSheetBonus;

    /** the alien object instance counter, an multithreaded counter
     * for the number of aliens that must be destroyed to complete
     * level.
     */
    protected ObjectCounter alienCounter;

    /** instance counter for the bombers */
    protected ObjectCounter bomberCounter;

    /** instance counter for the pods */
    protected ObjectCounter podCounter;

    /** instance counter for the swarmers */
    protected ObjectCounter swarmerCounter;

    /** property change listener for alien world objects */
    protected PropertyChangeListener alienPropertyListener;
    
    /** property change listener for bomber world objects */
    protected PropertyChangeListener bomberPropertyListener;
    
    /** property change listener for pod world objects */
    protected PropertyChangeListener podPropertyListener;
    
    /** property change listener for alien world objects */
    protected PropertyChangeListener swarmerPropertyListener;
    

    protected Image	mini_starship_image;
    protected int	mini_starship_width;
    protected Image	mini_smartbomb_image;
    protected int	mini_smartbomb_width;
    
    // Bonus sound
    protected Envelope		envBonus;
    protected SoundEngine	soundBonus;
    // Rescue Bonus sound
    protected Envelope		envRescueBonus;
    protected SoundEngine	soundRescueBonus;
    // Humans Rescued sound
    protected Envelope		envHuman;
    protected SoundEngine	soundHuman;
    // Game bongo sound
    protected Envelope		envBongo;
    protected SoundEngine	soundBongo;
    // Game start sound
    protected Envelope		envStart;
    protected SoundEngine	soundStart;
    // Level completed sound "shoosh" to next sheet 
    protected SoundEngine	soundLvlCmpltd;
    // Smart bomb alien 
    protected Envelope		envSmart;
    protected SoundEngine	soundSmart;
	
    protected HighScore		highScore;
    
    /**
     * HumanoidGameEngine - a container for the Humanoid game
     *
     * @param screen_width the screen size
     * @param screen_height the screen height
     * @param world_xpos the starting world map x coordinate 
     * @param world_ypos the starting world map y coordinate 
     * @param player_xpos the starting world map x coordinate (for our hero)
     * @param player_ypos the starting world map y coordinate (for our hero)
     * @param world_width the world map width
     * @param world_height the world map height
     *
     */
    public HumanoidGameEngine( 
	int screen_width, int screen_height,
	int world_xpos,  int world_ypos,
	int player_xpos, int player_ypos,
	int world_width, int world_height )
    {
	super( screen_width, screen_height, 
	       world_xpos, world_ypos, 
	       player_xpos, player_ypos, 
	       world_width, world_height );

	levelCompletedMsg="****";
	cod_key_pressed = false;

	setFrameRate( GameEngine.OPTIMUM_FRAME_RATE-2 );
	
	// Create object instance counters for the aliens, pod, and swarmers
	// Why do we need these counters?
	// 1) The gamekit is multithreaded
	// 2) The Java language has a garbage counter.
	// Hence we cannot guarantee the exact number of the aliens
	// that live int application. Moreover we cannot destroy 
	// explicitly.
	alienCounter = new ObjectCounter();
	bomberCounter = new ObjectCounter();
	podCounter = new ObjectCounter();
	swarmerCounter = new ObjectCounter();

	// Hence we register property change listeners to listen when
	// world object is destroyed. We can then decrement the object
	// counter when a property change event is fired.
	alienPropertyListener   = new AlienPropertyChangeListener();
	bomberPropertyListener  = new BomberPropertyChangeListener();
	podPropertyListener     = new PodPropertyChangeListener();
	swarmerPropertyListener = new SwarmerPropertyChangeListener();
	
	bonusLevelIndex = 0;
	screen_xoffset = 0;
	screen_yoffset = upperwin_height+2;

	bufferedContainer.setBackground( Color.black );		// required by Williams' Defender!!!
	bufferedContainer.setPreferredSize(
	    new Dimension( screen_width, screen_height + upperwin_height + 4) );
	bufferedContainer.addKeyListener( new GameKeyListener() );
	
	// System.out.println( "HumanoidGameEngine() DEBUG:" );
	// System.out.println( "bufferedContainer="+bufferedContainer.hashCode());
	
	CollisionHandler.registerAllCollisions();

	// setDefaultLevel(1);
	// setDefaultLives(3);
	entityList = new WorldObject[10];

	//
	// Create high scores
	//
	highScore = new HighScore( 10, 10000 );

	//
	// Load mini starship and smart bomb
	//
	// The following does not work in Netscape Browsers due to
	// Security Issues see Java Programmer's FAQ for more details
	// http://www.afu.com/
	// 
	// mini_starship_image = 
	//     Toolkit.getDefaultToolkit().getImage( 
	// 	   getClass().getResource( "images/mini-starship-right.gif") );
	// mini_smartbomb_image = 
	//     Toolkit.getDefaultToolkit().getImage( 
	// 	   getClass().getResource( "images/mini-smartbomb.gif") );
	//
	mini_starship_image = 
	    GraphicsUtils.getImageAsClassResource( 
		this, "images/mini-starship-right.gif" );
	mini_smartbomb_image = 
	    GraphicsUtils.getImageAsClassResource( 
		this, "images/mini-smartbomb.gif" );
	GraphicsUtils.loadImageCompletely( 
	    bufferedContainer, mini_starship_image );
	mini_starship_width = mini_starship_image.getWidth( bufferedContainer );
	GraphicsUtils.loadImageCompletely( 
	    bufferedContainer, mini_smartbomb_image );
	mini_smartbomb_width = mini_smartbomb_image.getWidth( bufferedContainer );


	// Create some sounds now and save them for later use
	envBonus = new Envelope ( 1,  4,5, -6,7,  30,10, 
				  1,  4,30,  0,128,  0,128,  -1,128 );

	soundBonus = new SoundEngine( 8000, 7.0, 127, 833, 40,
				      StaticWave.SINE_WAVE, envBonus );
	soundBonus.makeWave();


	// Generating rescued humans sound ... at the end of the sheet
	envRescueBonus = new Envelope(
	    1,  1,2, -2,2, -10,2,
	    1,128,1, 0,128, 0,128, -20,128 );
	soundRescueBonus = new SoundEngine(
	    8000, 2.0, 108, 212, 15,
	    StaticWave.RAMP_BACKWARD_WAVE, envRescueBonus );
	soundRescueBonus.setFlags(0x0 | SoundEngine.HOLD_PITCH_MODULATION | 
				  SoundEngine.NOISE_GATE_FLAG );
	soundRescueBonus.makeWave();
	    
	// Generating rescued humans sound ... 
	// for starship that collide with a falling human.
	envHuman = new Envelope(
	    1,  -7,5, -23,1, 16,2,
	    1,128,1, 0,128, 0,128, -10,128 );
	
	soundHuman = new SoundEngine(
	    8000, 5.0, 127, 837, 35,
	    StaticWave.RAMP_FORWARD_WAVE, envHuman );
	soundHuman.setFlags( 0x0 | SoundEngine.HOLD_PITCH_MODULATION );
	soundHuman.setFilter( new DelaySoundFilter(40) );
	soundHuman.makeWave();

	// Generating sound ...
	envBongo = new Envelope(
	    1,  -2,1, 36,1, -27,1,
	    1,128,1, 0,128, 0,128, -1,128 );
	soundBongo = new SoundEngine(
	    8000, 7.0, 100, 389, 60,
	    StaticWave.SQUARE_WAVE, envBongo );
	soundBongo.setFlags( 0x0 | SoundEngine.NOISE_GATE_FLAG );
	soundBongo.setFilter( new DelaySoundFilter(80) );
	soundBongo.makeWave();

	// Generating sound ...
	envStart = new Envelope(
	    1,  10,1, 16,1, -5,1,
	    1,128,1, 0,128, 0,128, -2,128 );
	soundStart = new SoundEngine(
	    8000, 4.5, 104, 359, 30,
	    StaticWave.RAMP_FORWARD_WAVE, envStart );
	soundStart.setFilter( new DelaySoundFilter(25) );
	soundStart.setFlags( 0x0 | SoundEngine.NOISE_GATE_FLAG | SoundEngine.FM_DELTA_RANDOM_FLAG );
	soundStart.makeWave();
	// ==========
	
	soundLvlCmpltd = new SoundEngine(
	    8000, 7.0, 100, 389, 60,
	    StaticWave.SQUARE_WAVE, envStart );
	soundLvlCmpltd.setFlags( 0x0 | SoundEngine.NOISE_GATE_FLAG );
	soundLvlCmpltd.setFilter( new ReverseClipFilter( new DelaySoundFilter(40) ));
	soundLvlCmpltd.makeWave();

	/** play start of game data */
	ppmcAudioPlayer.play( 6, soundLvlCmpltd.getSoundData() );
	

	// Generating smart bomb sound ...
	envSmart = new Envelope(
	    1,  3,1, -5,1, 15,1,
	    1,32,4, 0,128, 0,128, -2,128 );
	soundSmart = new SoundEngine(
	    8000, 5.0, 127, 464, 64,
	    StaticWave.RAMP_FORWARD_WAVE, envSmart );
	soundSmart.setFlags( SoundEngine.HOLD_PITCH_MODULATION | 
			     SoundEngine.NOISE_GATE_FLAG | 
			     SoundEngine.NOISE_MIX_GATE_FLAG );
	soundSmart.makeWave();

    }
    
    /** Paint method draw alls the active animation sprites int the World */
    public void paintWorld( Graphics g )
    {
	int state = getGameState();
	if ( state == GameEngine.GAME_STATE_INTERMEDIATE )
	    // Don't paint int the intermediate state
	    return;

	Rectangle oldClip = g.getClipBounds();
	g.setClip( 0, 0, screen_width, upperwin_height );

	// Draw the radar here
	paintRadarAndScores( g );

	g.setClip( 0, upperwin_height, screen_width, screen_height );
	g.translate( 0, upperwin_height );
	
	// Draw the background first
	paintBackground(g);

	// Paint the prologue
	if ( state == GameEngine.GAME_STATE_PROLOGUE )
	    paintPrologue(g);
	// Paint the epilogue
	else if ( state == GameEngine.GAME_STATE_EPILOGUE )
	    paintEpilogue(g);
	
	
	// Then the hero player object
	paintPlayer(g);
	// Draw the rest of the world object sprites
	paintSprites(g);

	// Draw top level messages
	paintMessages(g);

	g.setFont( scoreFont );
	if ( isDebugMode() ) {
	    String tmpstr = "humans:"+Human.getInstanceCount()+
		"  landers:"+Lander.getInstanceCount()+ 
		"/"+alienCounter.getCount()+ 
		" b:"+bomberCounter.getCount()+ 
		" p:"+podCounter.getCount()+ 
		" s:"+swarmerCounter.getCount()+ 
		"  waves:"+numberOfWaves+
		"  resp:"+landerResponsiveness+
		"  mag:"+landerMagnitude+
		"  bp:"+bombletProbability;
	    g.drawString( tmpstr, 16, 16 );
	}
	
	g.translate( 0, -upperwin_height );
	g.setClip( oldClip );
    }

    /** Gets the name of this game engine */
    public String getEngineName()
    {
	return "Humanoid V"+ApplicationVersion.getVersionString()+
	    "  XeNoNSoFT [c] 1999";
    }
    
    /**
     * paint the radar and the score board area
     *
     * @param g the graphics context
     */
    protected void paintRadarAndScores( Graphics g )
    {
	g.setColor( bufferedContainer.getBackground() );
	g.fillRect( 0, 0, screen_width, upperwin_height );
	
	g.setFont( scoreLargeFont );
	g.setColor( Color.white );
	g.drawString( "Score:"+score, 4, 16 );
	g.setFont( scoreFont );
	g.setColor( Color.green );
	g.drawString( "Lives:"+lives, 4, 32 );
	g.setColor( Color.cyan );
	g.drawString( "Lvl:"+level+"  B:"+numberOfSmartBombs, 4, 80 );

	int xpos, ypos, N;

	// Draw mini starships (represent the number lives left over) 
	xpos = 5;
	N = Math.min( lives, 5 );
	for ( int j=0; j<N; ++j) {
	    g.drawImage( mini_starship_image, xpos, 40, bufferedContainer );
	    xpos += mini_starship_width + 2;
	}
	
	// Draw mini smart bomb (represent the bombs left over) 
	xpos = 5;
	N = Math.min( numberOfSmartBombs, 7 );
	for ( int j=0; j<N; ++j) {
	    g.drawImage( mini_smartbomb_image, xpos, 56, bufferedContainer );
	    xpos += mini_smartbomb_width + 2;
	}

	// transform the screen coordinates into (mini) world coordinate
	int blip_xpos, blip_ypos;				
	int radar_width = screen_width / 2;
	int radar_height = upperwin_height - 10;

	int center_xpos = screen_width/2 - radar_width/8;

	// System.out.println("----------------------------------------");

	if ( playerObject != null ) {
	    if ( playerObject.wx >= 0 && playerObject.wx <= world_width &&
		 playerObject.wy >= 0 && playerObject.wy <= screen_height ) {
		
		xpos = playerObject.scrx;
		ypos = playerObject.scry;
		blip_xpos = xpos * radar_width / world_width + center_xpos;
		blip_ypos = ypos * radar_height / world_height;
		g.setColor( playerObject.getRadarColor() );
		g.fillRect( blip_xpos, blip_ypos, 4, 4 );
		
		// System.out.println( "wx:"+playerObject.wx+" wy:"+playerObject.wy+
		//		      "  scrx:"+playerObject.scrx+
		//		      " scry:"+playerObject.scry+
		//		      " xpos:"+xpos+" ypos:"+ypos+
		//		      " blip:"+blip_xpos+","+blip_ypos+" <+>" );
	    }
	}
	
	// Work with a clone copy of the list
	Vector copySpriteList = (Vector)spriteList.clone();
	Enumeration etor = copySpriteList.elements();

	while ( etor.hasMoreElements() ) {
	    int widthOver2 = (world_width + screen_width)/2 ;
	    WorldObject sprite = (WorldObject)etor.nextElement();
	    if ( sprite.isVisible() && sprite.isRadarable() ) {
		if ( sprite.wy >= 0 && sprite.wy <= screen_height ) {
		    
		    if ( sprite.scrx  > widthOver2 )
		       // Force the sprite radar blip x to wraparound
			xpos = sprite.scrx - world_width;
		    else
			xpos = sprite.scrx;
		    ypos = sprite.scry;
		    blip_xpos = xpos * radar_width / world_width + center_xpos;
		    blip_ypos = ypos * radar_height / world_height;
		    g.setColor( sprite.getRadarColor() );
		    g.fillRect( blip_xpos, blip_ypos, 2, 2 );

		    // System.out.println( 
		    //		   "wx:"+sprite.wx+" wy:"+sprite.wy+ "  scrx:"+
		    // 		   sprite.scrx+" scry:"+sprite.scry+" xpos:"+
		    // 		   xpos+" ypos:"+ypos+" blip:"+blip_xpos+","+blip_ypos );
		    
		}
	    }
	}
	
	
	// Draw the white separator between the upper and lower window
	g.setColor( Color.white );
	g.drawLine( 0, upperwin_height-1, screen_width, upperwin_height-1);
	g.drawLine( 0, upperwin_height-2, screen_width, upperwin_height-2);
	
	
	// Draw the two vertical lines to depict the screen size.
	g.drawLine( center_xpos, 0, center_xpos, upperwin_height );
	xpos = screen_width * radar_width / world_width + center_xpos ;
	g.drawLine( xpos, 0, xpos, upperwin_height );

	// Finally the extent of the `radar'
	g.setColor( Color.magenta );

	xpos = -radar_width / 2 + center_xpos ;
	g.drawLine( xpos, upperwin_height-25,
		    xpos, upperwin_height );

	xpos = radar_width * 3 / 4 + center_xpos ;
	g.drawLine( xpos, upperwin_height-25,
		    xpos, upperwin_height );
    }
    
    /**
     * paint the top level messages
     *
     * @param g the graphics context
     */
    protected void paintMessages( Graphics g )
    {
	if (paused) {
	    g.setFont( textFont );
	    g.setColor( Color.white );
	    GraphicsUtils.drawCenteredStringList( 
		g, GAME_PAUSED_MESSAGE2, 3*screen_height/5 );
	}
	if (levelCompleted) {
	    g.setFont( textFont );
	    g.setColor( Color.white );
	    GraphicsUtils.drawCenteredString( 
		g, levelCompletedMsg, screen_height / 3 );

	    g.setColor( Color.cyan );
	    GraphicsUtils.drawCenteredString( 
		g, "RESCUE BONUS "+rescuedHumanBonusCounter+
		" x "+rescuedHumanBonusBase+" pts", 48+screen_height/3 );

	    if ( jumpSheetBonus > 0 ) {
		g.setColor( Color.orange );
		GraphicsUtils.drawCenteredString( 
		    g, "JUMP SHEET BONUS "+jumpSheetBonus+" pts", 
		    48+screen_height/2 );
	    }
	}
	
    }

    /** a convenience method for <b>subclassers</b> called
     * <em>after</em> the <code>update()</code> method for each object
     * is called 
     */
    public void updatePostHook()
    {
	if ( smartCounter > 0 ) --smartCounter;
	
	if ( smartbomb_key_pressed && numberOfSmartBombs > 0 && 
	     smartCounter == 0) {
	    if ( playerObject.isActive() && playerObject.isVisible() ) {
		//
		// Code that handles the smart bomb key
		//
		smartCounter=SMART_BOMB_DELAY;
		--numberOfSmartBombs;
		fireSmartBomb();
	    }
	}

	if ( numberOfWaves > 0 && Lander.getInstanceCount() < 2 ) {
	    --numberOfWaves;
	    generateLanders(DEFAULT_NUMBER_OF_LANDERS_PER_WAVE);
	}
	
	if ( Human.getInstanceCount() < 1 && !allLandersMutated ) {
	    mutateAllLanders();
	    allLandersMutated = true;		/* ! */
	    if (backgrdObject != null )
		backgrdObject.setVisible(false);
	    if ( !planetExploded ) {
		PlanetExplosion explosion = 
		    new PlanetExplosion( this, null, world_xpos, world_ypos );
		addWorldObject( explosion );
	    }
	}
    }
    
    public void mutateAllLanders( )
    {
	Vector copySpriteList = (Vector)spriteList.clone();
	Enumeration etor = copySpriteList.elements();
	    
	while ( etor.hasMoreElements() ) {
	    WorldObject sprite = (WorldObject)etor.nextElement();
	    if ( sprite instanceof Lander ) {
		Lander lander = (Lander)sprite;
		lander.mutate();
	    }
	}
    }

    /**
     * Smart bombs all the viewable enemy world object on the screen
     */
    public void fireSmartBomb( )
    {
	/** play smart bomb sound */
	ppmcAudioPlayer.play( 11, soundSmart.getSoundData() );

	// Work with a cloned copy of the sprite list.
	Vector copySpriteList = (Vector)spriteList.clone();
	Enumeration etor = copySpriteList.elements();
	
	while ( etor.hasMoreElements() ) {
	    WorldObject sprite = (WorldObject)etor.nextElement();
	    if ( sprite.isActive() && sprite.isVisible() && sprite.isViewable() )
	    {
		if ( sprite instanceof EnemyObject ) {
		    if ( sprite instanceof Pod ) {
			// 
			// Create the swarmers for Pod, then let player
			// get away with it.
			//
			generateSwarmers( 
			    HumanoidGameEngine.NUMBER_OF_SWARMERS_PER_POD,
			    sprite.getWorldXpos(), sprite.getWorldYpos() );
		    }

		    if ( sprite instanceof Baiter ) {
			// 
			// Create one more baiter for each baiter that
			// that is destroyed. Close this loophole too.
			//
			generateBaiters( 45, 1 );
		    }

		    // Destroy object provide an explosion for it
		    Explosion explosion = new Explosion( 
			this, null, sprite.getWorldXpos(), sprite.getWorldYpos(), 
			sprite.getRadarColor() );
		    incrementScore( sprite.getPointsScored() );
		    addWorldObject( explosion );
		    sprite.destroy();
		}
	    }
	}
    }
    
    /** return an instance to the alien object counter */
    public ObjectCounter getAlienCounter()
    {
	return alienCounter;
    }

    /** return an instance to the pod object counter */
    public ObjectCounter getBomberCounter()
    {
	return bomberCounter;
    }
    
    /** return an instance to the pod object counter */
    public ObjectCounter getPodCounter()
    {
	return podCounter;
    }
    
    /** return an instance to the swarmer object counter */
    public ObjectCounter getSwarmerCounter()
    {
	return swarmerCounter;
    }
    
    
    /** increments the players score 
     * <b>subclassers</b> should override this method to handle such things 
     * game bonuses. Also any collision handlers should call this method
     * instead of increment the score directly.
     *
     * @param points the number of points to be added to the score.
     */
    public synchronized void incrementScore( int points )
    {
	score += points;
	if ( score > nextBonus ) {
	    // Every 10000 pts there is an extra bonus starship
	    // and smart bomb
	    nextBonus += 10000;
	    ++lives;
	    ++numberOfSmartBombs;
	    int xvel = 10;
	    ppmcAudioPlayer.play( 4, soundBonus.getSoundData() );
	    if ( playerObject != null) {
		xvel = playerObject.dx_vel;
		BonusFlyer flyer = new BonusFlyer( 
		    this, playerObject.getWorldXpos(), world_height/2,
		    xvel, 0, "Extra Starship" );
		addWorldObject( flyer );
	    }
	}
    }

    /** startGame calls the system garbage collector resets the score
     * the number of lives and the level (sheet)
     */
    public void startGame()
    {
	score = 0;
	numberOfSmartBombs = 3;
	numberOfHumansLeft = DEFAULT_NUMBER_OF_HUMANS;
	nextBonus = 10000;
	planetExploded = false;
	
	super.startGame();
	
	if (bonusLevelIndex > 0 ) {
	    level = bonusLevelMap[bonusLevelIndex].award_level;
	    if ( nextBonus < bonusLevelMap[bonusLevelIndex].bonus_points )
		nextBonus = 
		    ( bonusLevelMap[bonusLevelIndex].bonus_points/10000 + 1) * 10000;
	}
	else
	    level = 1;
	
	enteringNewLevel = true;		/* ! */

	/** play start of game data */
	ppmcAudioPlayer.play( 9, soundStart.getSoundData() );

    }
    
    /** Code to execute when the when the game ends */
    public void stopGame()
    {
	super.stopGame();

	// Stop all audio channels
	ppmcAudioPlayer.stopAllChannels();

	if ( scoreEntry == null ) {
	    //
	    // Create the high score entry panel
	    //
	    scoreEntry = new ScoreEntry( 
		GraphicsUtils.getFrameOfComponent( bufferedContainer), 
		null, score );
	    scoreEntry.setLocation( screen_width/4, screen_height/2  );
	    scoreEntry.setVisible(false);
	}
	
	if ( highScore.isHighScore( score ) ) {
	    bufferedContainer.repaint();
	    scoreEntry.setVisible(true);
	    String playerName = scoreEntry.getEntryName().trim();
	    if ( playerName.length() > 31 )
		playerName = playerName.substring( 0, 31 );
	    ScoreTuple tuple = new ScoreTuple( playerName, score, level );
	    highScore.addScoreTuple( tuple );
	}

	saveHighScores();
    }
    
    public void startLevel()
    {
	// Set the animation hero player object
	alienCounter.setCount(0);
	bomberCounter.setCount(0);
	podCounter.setCount(0);
	swarmerCounter.setCount(0);
	Lander.setInstanceCount(0);
	
	if (backgrdObject != null) {
	    Planetscape planet = (Planetscape)backgrdObject;
	    planet.wx = world_xpos = 0;
	    planet.wy = world_ypos = 0;
	}

	if ( playerObject != null ) {
	    Starship ship = (Starship)playerObject;
	    ship.setVisible(true);
	    ship.setActive(true);
	    ship.bringToRest();
	}
	
	// 
	// This is exactly where we generate the difficulty of the
	// sheet.
	//

	// ----------------------------------------------------------------------
	// NOTES FROM THE ORIGINAL WILLIAM'S GAME:
	// ----------------------------------------------------------------------
	// The planetscape is restored to perfect health every 5 sheets.
	// Pods are 1,000 points
	// Bombers are 250 points
	// Swarmers are 150 points
	// Baiters are 300 points
	// Lander and Mutants are 150 points
	// Each pod releases 12 swarmers
	// (Each pod releases 8 swarmers until every JVM has a JIT or Hotspot )
	// Every 10000 pts you get another ship and a smart bomb
	// Landing a human gets 500 and rescuing one gets 150.
	// (Only in this version of the popular game the 3rd human
	// landed gets 750 points)
	//
	//
	//
	// ----------------------------------------------------------------------
	// SET UP THE DIFFICULTY
	// ----------------------------------------------------------------------
	// Two bombers and a pod introduced on the 2nd sheet.
	// Three bombers are introduced on the 3rd sheet.
	// Sixs bombers are introduced on the 6th sheet,
	// whereas the Williams machine introduced six from the 
	// 4th onwards.
	// Four pods are introduced on the 5th sheet and
	// Six pods are introduced on the 8th sheet,
	// whereas the Williams machine introduced six from the 
	// 4th onwards.
	// 
	//

	jumpSheetBonus = 0;			/*!*/
	if ( level % 5 == 0 ) {
	    // Regenerate the planetscape every 5 sheets
	    numberOfHumansLeft = DEFAULT_NUMBER_OF_HUMANS;
	    planetExploded = false;
	}
	
	// Set if all landers mutated
	allLandersMutated=false;
	
	// Number of additional Lander waves for this sheet (level)
	if ( enteringNewLevel ) {
	    numberOfWaves=0;
	    if ( level >= 2 )  numberOfWaves=1;
	    if ( level >= 7 )  numberOfWaves=2;
	    if ( level >= 21 ) numberOfWaves=3;
	    // if ( level >= 30 ) numberOfWaves=4;
	}
	
	landerMagnitude = 4;
	if ( level >= 2 )  landerMagnitude = 5;
	if ( level >= 5 )  landerMagnitude = 6;
	if ( level >= 8 )  landerMagnitude = 7;
	if ( level >= 13 ) landerMagnitude = 8;
	if ( level >= 19 ) landerMagnitude = 9;
	if ( level >= 25 ) landerMagnitude = 10;
	if ( level >= 30 ) landerMagnitude = 12;
	
	
	// the responsiveness 0=fastest and 5=slowest
	landerResponsiveness = 6;
	if ( level >= 2 )  landerResponsiveness = 5;
	if ( level >= 6 )  landerResponsiveness = 4;
	if ( level >= 9 )  landerResponsiveness = 3;
	if ( level >= 15 ) landerResponsiveness = 2;
	if ( level >= 21 ) landerResponsiveness = 1;
	if ( level >= 26 ) landerResponsiveness = 0;

	// let off every 7th wave! 
	// This is definitely NOT in the Williams original.
	if ( level % 7 ==0 ) landerResponsiveness = 2; 

	// the bomblet probability
	bombletProbability = BASE_BOMBLET_PROBABILITY + 
	    FACTOR_BOMBLET_PROBABILITY*(level-1);
	if ( level > 3)
	    bombletProbability += level * FACTOR2_BOMBLET_PROBABILITY;
	
	bombletProbability = Math.min( bombletProbability, MAX_BOMBLET_PROBABILITY );

	//
	// Generate the humans
	//
	for ( int k=0; k<numberOfHumansLeft; ++k ) {
	    int wxpos = (int)( Math.random() * world_width );
	    Human human = new Human( this, Human.STATE_FREE, wxpos );
	    // System.out.println( "human["+k+"] wx:"+human.wx+", wy:"+human.wy );
	    addWorldObject( human );
	}

	// Generate the first wave of landers
	generateLanders( DEFAULT_NUMBER_OF_LANDERS_PER_WAVE );

	//
	// Set the Baiters' difficulties
	//
	int initialDelay;
	numberOfBaiters = 1;
	initialDelay = 575 + 548 * numberOfWaves;
	if ( level >= 5 )
	    initialDelay = 675 + 635 * numberOfWaves;
	if ( level >= 3 ) numberOfBaiters=2;
	if ( level >= 10 ) numberOfBaiters=3;
	if ( level >= 21 ) numberOfBaiters=4;
	
	bombletProbabilityBaiters = 
	    BAITER_BASE_BOMBLET_PROBABILITY +
	    BAITER_FACTOR_BOMBLET_PROBABILITY*level;
	
	bombletProbabilityBaiters = Math.min(
	    bombletProbabilityBaiters, MAX_BOMBLET_PROBABILITY );

	generateBaiters( initialDelay, numberOfBaiters );
	
	//
	// Set the Bombers' difficulties
	//
	numberOfBombers = 0;
	if ( level == 2 ) numberOfBombers=2;
	if ( level >= 3 ) numberOfBombers=3;
	if ( level >= 6 ) numberOfBombers=6;	

	bombletProbabilityBombers = 
	    BOMBER_BASE_BOMBLET_PROBABILITY +
	    (numberOfBombers+1)*BOMBER_FACTOR_BOMBLET_PROBABILITY + level*0.0075;
	bombletProbabilityBombers = Math.min( 
	    bombletProbabilityBombers, MAX_BOMBLET_PROBABILITY );

	if ( enteringNewLevel ) 
	    generateBombers( numberOfBombers );
	else
	    generateBombers( survivingBombers );
	
	//
	// Set the Pods' difficulties
	//
	numberOfPods = 0;
	if ( level == 2 ) numberOfPods=1;
	if ( level >= 3 ) numberOfPods=3;
	if ( level >= 5 ) numberOfPods=4;
	if ( level >= 9 ) numberOfPods=5;

	if ( enteringNewLevel ) {
	    // If we are entering a new level (sheet) then generate a full 
	    // set of pods
	    generatePods( numberOfPods );
	}
	else {
	    // The player died in this sheet so we don't create the full
	    // set of pods. Instead create the remaining pods and swarmers.
	    // I think this is how the original Willaims's game worked.
	    // Otherwise the player could smart bomb the pods and swarmer
	    // to get a new ship and smart bomb every 10000 pts or so.
	    generatePods( survivingPods );
	    generateAllSwarmers( survivingSwarmers );
	}
	
	// Hide or show the planetscapre depending on if there are any
	// humans still alive.
	if (backgrdObject != null) {
	    backgrdObject.setVisible( Human.getInstanceCount() > 0 );
	    backgrdObject.setActive( Human.getInstanceCount() > 0 );
	}
	
	// Don't forget to reset the entering new level since we have just
	// created the difficulty for the new level
	if ( enteringNewLevel )
	    enteringNewLevel = false;

	// The last instruction of this method
	// calls the super class method
	super.startLevel();			/* ! */

    }

    public synchronized void generateLanders( int N )
    {
	for ( int k=0; k<12; ++k ) {
	    int wxpos = (int)( Math.random() * world_width );
	    int wypos = (int)( world_height - Math.random() * 30 ) ;

	    if ( k == 0 ) wxpos = 384;
	    
	    Lander lander = 
		new Lander( this, Lander.STATE_HUNTING, 
			    wxpos, wypos, landerMagnitude, 2 );
	    lander.addPropertyChangeListener( alienPropertyListener );
	    alienCounter.increment();
	    Implosion implosion = 
		new Implosion( this, lander, wxpos, wypos, Color.green, 64 );
	    lander.setResponse( landerResponsiveness );	    
	    lander.setBombletProbability( bombletProbability );
	    lander.setVisible(true);
	    lander.setActive(true);
	    addWorldObject( implosion );

	    // Mutate if there are no humans, or the future,
	    // when the implosion graphic is completed.
	    // Or if there are surviving mutants.
	    if (Human.getInstanceCount() < 1 )
		lander.mutate();
	    else
		implosion.addPropertyChangeListener(
		    new MutateLanderPropertyListener(lander) );
	    // Thread.yield();
	}

    }

    public synchronized void generateBombers( int N )
    {
	int wxpos = (int)( Math.random() * world_width );
	int wypos = (int)( world_height - Math.random() * 30 ) ;
	for ( int k=0; k<N; ++k ) {
	    do {
		if ( k % 2 == 0 ) 
		    wxpos = (int)( Math.random() * world_width );
	    }
	    while ( wxpos >= 0 && wxpos < 215 );
	    wypos = (int)( ( world_height - 100 ) * Math.random() ) ;
	    Bomber bomber = 
		new Bomber( this, wxpos, wypos, 
			    8, landerResponsiveness );
	    bomber.addPropertyChangeListener( alienPropertyListener );
	    bomber.setBombletProbability( bombletProbabilityBombers );
	    alienCounter.increment();
	    bomber.addPropertyChangeListener( bomberPropertyListener );
	    bomberCounter.increment();
	    bomber.setVisible(true);
	    bomber.setActive(true);
	    addWorldObject( bomber );
	    wxpos = (wxpos + 100 ) % world_width;
	    // Thread.yield();
	}

    }

    public synchronized void generatePods( int N )
    {
	int wxpos = (int)( Math.random() * world_width );
	int wypos = (int)( world_height - Math.random() * 30 ) ;
	for ( int k=0; k<N; ++k ) {
	    do {
		if ( k % 3 == 0 ) 
		    wxpos = (int)( Math.random() * world_width );
	    }
	    while ( wxpos >= 0 && wxpos < 200 );
	    wypos = (int)( ( world_height - 100 ) * Math.random() ) ;

	    Pod pod = new Pod( this, wxpos, wypos, 4, landerResponsiveness );

	    pod.addPropertyChangeListener( alienPropertyListener );
	    alienCounter.increment();
	    pod.addPropertyChangeListener( podPropertyListener );
	    podCounter.increment();

	    pod.setVisible(true);
	    pod.setActive(true);
	    addWorldObject( pod );
	    wxpos = (wxpos + 100 ) % world_width;
	}

    }

    public synchronized void generateAllSwarmers( int N )
    {
	int wxpos, wypos;
	while ( N > 0 ) {
	    int k;
	    if ( N > NUMBER_OF_SWARMERS_PER_POD  )
		k = NUMBER_OF_SWARMERS_PER_POD;
	    else
		k = N % NUMBER_OF_SWARMERS_PER_POD;
	    wxpos = (int)( Math.random() * world_width );
	    wypos = (int)( world_height - Math.random() * 30 ) ;
	    generateSwarmers( k, wxpos, wypos );
	    N -= NUMBER_OF_SWARMERS_PER_POD;
	    // Thread.yield();
	}
    }
    
    public synchronized void generateSwarmers( int N, int xpos, int ypos )
    {
	// Set a bit of increasing difficulty for the swarmers
	// Take account swarmers can only make their appearance
	// in the 3rd sheet, because pod make their first appearance
	// Increase their order magnitude and responsiveness
	//
	int magnitude = 8 + (level-3)/3 ;
	if ( magnitude < Starship.MAXIMUM_VELOCITY-3 )
	    magnitude = Starship.MAXIMUM_VELOCITY-3;
	int response = 2 - level/10;
	if ( response < 0 ) response=2;

	for ( int k=0; k<N; ++k ) {
	    Swarmer swarmer = new Swarmer( this, xpos, ypos, magnitude, response );

	    addWorldObject( swarmer );
	    swarmer.addPropertyChangeListener( alienPropertyListener );
	    alienCounter.increment();
	    swarmer.addPropertyChangeListener( swarmerPropertyListener );
	    swarmerCounter.increment();
	    swarmer.setVisible(true);
	    swarmer.setActive(true);
	}
    }

    public synchronized void generateBaiters( int initialDelay, int N )
    {
	for ( int k=0; k<N; ++k ) {
	    int wxpos = (int)( Math.random() * world_width );
	    int wypos = (int)( world_height - Math.random() * 30 ) ;

	    Baiter baiter = new Baiter( 
		this, world_xpos, world_ypos, Starship.MAXIMUM_VELOCITY+6, 1 );

	    // We don't add baiters to the alien counter
	    // because these are additional items. You don't 
	    // have to destroy all baiters to end a sheet.
	    Implosion implosion = new Implosion( 
		this, baiter, wxpos, wypos, Color.green, initialDelay );
	    baiter.setResponse( ( level > 10 ? 0 : 1 ) );
	    baiter.setBombletProbability( bombletProbabilityBaiters );
	    baiter.setVisible(true);
	    baiter.setActive(true);
	    addWorldObject( implosion );
	}
    }

    public void endLevel()
    {
	// System.out.println("endLevel()");
	// System.out.println("\tHuman.getInstanceCount()="+Human.getInstanceCount() );
	// System.out.println("\tPod.getInstanceCount()="+Pod.getInstanceCount() );
	// System.out.println("\tSwarmer.getInstanceCount()="+Swarmer.getInstanceCount() );
	// System.out.println("\tbomberCounter.getCount()="+bomberCounter.getCount() );
	// System.out.println("\tpodCounter.getCount()="+podCounter.getCount() );
	// System.out.println("\tswarmerCounter.getCount()="+swarmerCounter.getCount() );
	
	numberOfHumansLeft = Human.getInstanceCount();
	survivingBombers = bomberCounter.getCount();
	survivingPods = podCounter.getCount();
	survivingSwarmers = swarmerCounter.getCount();

	// This is required to restore surviving humans to the planetscape
	((Starship)playerObject).dropAllRescuedHumans( false );
	
	Human.removeAllInstances();
	
	// The last instruction of this method
	// calls the super class method
	super.endLevel();
	
	try { Thread.sleep( 50 ); }
	catch ( InterruptedException e ){ ; /* die? */  }

    }
    
    /**
     * Load high scores from local disk.
     * The method looks for value system property <i>highscore.dir</i>
     * to find out where to load the high scores. If no value is defined
     * then the system property <i>user.home</i> is used instead.
     */
    public void loadHighScores()
    {
	// If we are running inside an applet return immediately.
	if ( isRunningInApplet() ) return;

	String scoreDir = System.getProperty( "highscore.dir" );
	if ( scoreDir == null )
	    scoreDir = System.getProperty( "user.home" );
	File file = new File( scoreDir, "humanoid.scores" );
	if ( file.exists() ) {
	    try {
		highScore.load( new FileInputStream( file ) );
	    }
	    catch (IOException ioe) {
		System.err.println( "Whilst reading file:`"+file.getPath()+
				    "'  I/O Exception:"+ioe.getMessage() );
	    }
	}
    }
    
    /**
     * Save high scores to local disk.
     * The method looks for value system property <i>highscore.dir</i>
     * to find out where to save the high scores. If no value is defined
     * then the system property <i>user.home</i> is used instead.
     */
    public void saveHighScores()
    {
	// If we are running inside an applet return immediately.
	if ( isRunningInApplet() ) return;

	String scoreDir = System.getProperty( "highscore.dir" );
	if ( scoreDir == null )
	    scoreDir = System.getProperty( "user.home" );

	File file = new File( scoreDir, "humanoid.scores" );
	try {
	    highScore.save( new FileOutputStream( file ), 
			    getEngineName()+" High-Scores File" );
	}
	catch (IOException ioe) {
	    System.err.println( "Whilst reading file:`"+file.getPath()+
				"'  I/O Exception:"+ioe.getMessage() );
	}
    }
    
    
    /** a convenience method for <b>Subclassers</b> to override which
     * is called <em>before</em> the actual game has started.  
     */
    public void updatePrologue()
    {

	loadHighScores();
	
	//
	// Create all entities 
	//
	bonusLevelIndex = 0;
	int n=0;
	entityList[n++] = new Starship( this );
	Lander lander1 = new Lander( this );
	lander1.setState( Lander.STATE_HUNTING );
	entityList[n++] = lander1;
	Lander lander2 = new Lander( this );
	lander2.setState( Lander.STATE_MUTANT );
	entityList[n++] = lander2;
	Human human1 = new Human( this );
	human1.setState( Human.STATE_RESCUED );
	entityList[n++] = human1;
	Human human2 = new Human( this );
	human2.setState( Human.STATE_FREE );
	entityList[n++] = human2;
	entityList[n++] = new Baiter( this );
	entityList[n++] = new Bomber( this );
	entityList[n++] = new Pod( this );
	entityList[n++] = new Swarmer( this );

	if (playerObject != null )
	    playerObject.setVisible(false);
	// System.out.println( "HGE.updatePrologue() cod_key_pressed:"+cod_key_pressed ;)
	if ( backgrdObject != null ) {
	    backgrdObject.setActive(true);
	    backgrdObject.setVisible(true);
	}
	    
	// Wait until [SPACE] is depressed
	bufferedContainer.repaint();
	
	statePrologue = PROLOGUE_SHOW_CONTROLS;
	int counter = PROLOGUE_COUNTER;
	int counter2 = 0;
	while ( cod_key_pressed == false ) {
	    if ( counter > 0 ) 
		--counter;
	    if ( counter2 > 0 ) 
		--counter2;
	    if ( statePrologue == PROLOGUE_SHOW_HIGHSCORES &&
		 backgrdObject != null ) {
		world_xpos = (world_width + world_xpos - 3 ) % world_width;
		if ( world_xpos < 0) world_xpos = world_width;
		backgrdObject.update();
		bufferedContainer.repaint();
	    }
	    else if ( statePrologue == PROLOGUE_SHOW_ENTITIES &&
		 backgrdObject != null ) {
		world_xpos = (world_xpos + 8 ) % world_width;
		backgrdObject.update();
		bufferedContainer.repaint();
	    }
	    
	    if ( counter == 0 ) {
		switch (statePrologue) {
		  case PROLOGUE_SHOW_CONTROLS:
		      statePrologue = PROLOGUE_SHOW_HIGHSCORES; break;
		  case PROLOGUE_SHOW_HIGHSCORES:
		      statePrologue = PROLOGUE_SHOW_ENTITIES; break;
		  case PROLOGUE_SHOW_ENTITIES:
		      statePrologue = PROLOGUE_SHOW_CREDITS; break;
		  case PROLOGUE_SHOW_CREDITS:
		      statePrologue = PROLOGUE_SHOW_CONTROLS; break;
		}
		
		counter = PROLOGUE_COUNTER;
		bufferedContainer.repaint();
		try { Thread.sleep( 250 ); } catch ( InterruptedException ie ){  }
	    }

	    if ( reconfig_key_pressed ) {
		popupReconfigureKeysDialog();
		reconfig_key_pressed = false;
	    }
	    
	    if ( level_key_pressed && counter2 < 1 ) {
		bonusLevelIndex = (bonusLevelIndex+1) % bonusLevelMap.length;
		counter2 = 7;
		/** play rescued sound data */
		ppmcAudioPlayer.play( 6, soundRescueBonus.getSoundData() );
		bufferedContainer.repaint();
	    }
	    
	    if ( exit_key_pressed ) {
		setSessionDone( true );
		break;
	    }
	    
	    try { Thread.sleep( 50 ); } catch ( InterruptedException ie ){  }
	}

	try { Thread.sleep( 500 ); } catch ( InterruptedException ie ){  }

	//
	// Remove all entities 
	//
	for (int j=0; j<entityList.length; ++j) 
	    if ( entityList[j] != null )
		entityList[j].destroy();
	    
	cod_key_pressed = false;
    }

    public void popupReconfigureKeysDialog()
    {
	if ( keyDialog == null ) {
	    Frame frame = 
		GraphicsUtils.getFrameOfComponent( getDoubleBufferedContainer() );
	    
	    keyDialog = 
		new UserKeyControlDialog( 
		    frame, "Reconfigure Game Keys Dialog", gamekeyList );
	}
	
	if ( keyDialog.showDialog() == UserKeyControlDialog.OK_BUTTON ) {
	    KEYCODE_UP		= gamekeyList[0].keyCode;
	    KEYCODE_DOWN	= gamekeyList[1].keyCode;
	    KEYCODE_COD		= gamekeyList[2].keyCode;
	    KEYCODE_LASER	= gamekeyList[3].keyCode;
	    KEYCODE_THRUST	= gamekeyList[4].keyCode;
	    KEYCODE_SMARTBMB	= gamekeyList[5].keyCode;
	    KEYCODE_HYPERSPC	= gamekeyList[6].keyCode;
	    KEYCODE_PAUSE	= gamekeyList[7].keyCode;

	    // This is a bit of hack I think
	    // To update the string of the keyboard control text
	    // It is a bit long winded, but does the trick! *PP*
	    int n=3;
	    KEYBOARD_CONTROLS_TEXT[n] =
		KeyEvent.getKeyText( KEYCODE_UP ) + " ... Starship Up"; ++n;
	    KEYBOARD_CONTROLS_TEXT[n] =
		KeyEvent.getKeyText( KEYCODE_DOWN ) + " ... Starship Down"; ++n;
	    KEYBOARD_CONTROLS_TEXT[n] =
		KeyEvent.getKeyText( KEYCODE_COD ) + " ... Change Direction"; ++n;
	    KEYBOARD_CONTROLS_TEXT[n] =
		KeyEvent.getKeyText( KEYCODE_LASER ) + " ... Fire Laser"; ++n;
	    KEYBOARD_CONTROLS_TEXT[n] =
		KeyEvent.getKeyText( KEYCODE_THRUST ) + " ... Thrust Engines"; ++n;
	    KEYBOARD_CONTROLS_TEXT[n] =
		KeyEvent.getKeyText( KEYCODE_SMARTBMB ) + " ... Smart Bomb,  "+
		KeyEvent.getKeyText( KEYCODE_HYPERSPC ) + " ... Hyperspace"; ++n;
	    KEYBOARD_CONTROLS_TEXT[n] =
		KeyEvent.getKeyText( KEYCODE_PAUSE ) + " ... PAUSE"; ++n;
	    
	};
    }
	
    /** a convenience method for <b>Subclassers</b> to override which
     * is called <em>after</em> the actual game has finished.  
     */
    public void updateEpilogue()
    {
	if (playerObject != null )
	    playerObject.setVisible(false);
	// System.out.println( "HGE.updateEpilogue() cod_key_pressed:"+cod_key_pressed );
	bufferedContainer.repaint();
	try { Thread.sleep( 3000 ); } catch ( InterruptedException ie ){  }
	cod_key_pressed = false;
	level_key_pressed = false;
    }

    /** a convenience method for <b>subclassers</b> to override which is called
     * <em>after</em> the level (sheet) has been completed
     * Use the method <code>setNextLevel</code> to set the next level or
     * sheet number.
     *
     * For example the following code accomplishes this:
     * <code>setNextLevel( getLevel() + 1 ); </code>
     *
     * @param currLevel the current level
     *
     * @see #setNextLevel
     */
    public void updateLevelCompletedHook( int currLevel )
    {
	// Award 100 points for each Humanoid still alive in the first sheet
	// Award 200 points for each Humanoid still alive in the second sheet
	// Award 300 points for each Humanoid still alive in the third sheet
	// ...
	// up to 500 points.

	setNextLevel( currLevel + 1 );		/*!*/
	rescuedHumanBonusCounter=0;
	rescuedHumanBonusBase=0;
	jumpSheetBonus=0;
	levelCompletedMsg = "LEVEL "+currLevel+" COMPLETED!";
	
	if ( bonusLevelIndex > 0 &&
	     currLevel == bonusLevelMap[bonusLevelIndex].award_level ) {
	    jumpSheetBonus = bonusLevelMap[bonusLevelIndex].bonus_points;
	    bonusLevelIndex = -1;
	    incrementScore( jumpSheetBonus );
	}
	
	survivingPods=0;			/* ! */
	survivingSwarmers=0;			/* ! */
	enteringNewLevel=true;			/* !! */
	
	rescuedHumanBonusBase = Math.min( currLevel, 5 ) * 100;
	for (int j=0; j<Human.getInstanceCount(); ++j ) {
	    bufferedContainer.repaint();
	    incrementScore( rescuedHumanBonusBase );
	    ++rescuedHumanBonusCounter;

	    ppmcAudioPlayer.play( 3, soundRescueBonus.getSoundData() );
	    
	    try { Thread.sleep( 350 ); }
	    catch ( InterruptedException e ){ }
	}

	bufferedContainer.repaint();

	/** play level completed sound data */
	ppmcAudioPlayer.play( 12, soundLvlCmpltd.getSoundData() );
    }
    
    /** a convenience method for <b>Subclasser</b> to override which
     * is called <em>before</em> the actual game has started.  
     */
    public void paintPrologue( Graphics g )
    {
	// System.out.println( "HGE.paintPrologue() cod_key_pressed:"+cod_key_pressed );
	
	switch (statePrologue) {
	  case PROLOGUE_SHOW_CREDITS:
	      g.setFont( keysFont );
	      g.setColor( Color.cyan );
	      GraphicsUtils.drawCenteredStringList( g, CREDITS_TEXT );
	      break;

	  case PROLOGUE_SHOW_HIGHSCORES:
	      paintPrologueHighScores( g );
	      break;
	  
	  case PROLOGUE_SHOW_ENTITIES:
	      paintPrologueEntities( g );
	      break;
	      
	  case PROLOGUE_SHOW_CONTROLS:
	  default:
	    g.setFont( keysFont );
	    g.setColor( Color.yellow );
	    GraphicsUtils.drawCenteredStringList( g, KEYBOARD_CONTROLS_TEXT, 40 );
	    break;
	}
	
	g.setFont( scoreLargeFont );
	g.setColor( Color.orange );
	String tmpstr = 
	    "Jump Bonus Level:"+bonusLevelMap[bonusLevelIndex].award_level+
	    " ("+bonusLevelMap[bonusLevelIndex].bonus_points+" pts)";
	GraphicsUtils.drawCenteredString(g, tmpstr ,screen_height-24 );
    }

    /** paints the high scores as text */
    public void paintPrologueHighScores( Graphics g )
    {
	int		xpos = 125;
	int		ypos = 64;
	StringBuffer	buf = new StringBuffer();
	int		N = highScore.getItemCount();
	FontMetrics	fm = g.getFontMetrics();
	int		lineHeight = fm.getAscent() + fm.getDescent();

	g.setFont( scoreFont );

	g.setColor( Color.cyan );
	g.drawString("No.#  Lvl  Score     Name", xpos, ypos );
	ypos += lineHeight+3;

	for (int j=0; j<N; ++j) {
	    ScoreTuple tuple = highScore.getItemAt(j);
	    g.setColor( Color.magenta );
	    g.drawString( Integer.toString(j), xpos, ypos );

	    buf.setLength(0);
	    if ( j == 0 ) 
		g.setColor( Color.yellow );
	    else
		g.setColor( Color.white );

	    buf.append( tuple.getLevel() );
	    while ( buf.length()<5 ) buf.append(' ');
	    buf.append( tuple.getScore() );
	    while ( buf.length()<16 ) buf.append(' ');
	    buf.append( tuple.getPlayer() );
	    g.drawString( buf.toString(), xpos+32, ypos );
	    ypos += lineHeight+3;
	}
    }

    /** paints the world objects entities with their names 
     * the number of points scored if applicable 
     */
    public void paintPrologueEntities( Graphics g )
    {
	int xpos = 175;
	int ypos = 64;
	
	for (int j=0; j<entityList.length; ++j) {
	    WorldObject sprite = entityList[j];
	    if ( sprite != null ) {
		String str;
		if ( sprite instanceof Starship )
		    str = "Your "+sprite.getDescriptionType()+"!";
		else {
		    str = sprite.getDescriptionType()+" "+
			sprite.getPointsScored()+" pts.";
		}
		
		ypos = GraphicsUtils.drawImageAndString( 
		    bufferedContainer, g, sprite.getDefaultImage(), 
		    str, xpos, ypos, 64 );
	    }
	}
	
    }
    
    /** a convenience method for <b>Subclasser</b> to override which
     * is called <em>before</em> the actual game has started.  
     */
    public void paintEpilogue( Graphics g )
    {
	// System.out.println( "HGE.paintEpilogue() cod_key_pressed:"+cod_key_pressed );
	g.setFont( scoreBigFont );
	g.setColor( Color.yellow );
	GraphicsUtils.drawCenteredStringList( g, GAMEOVER_TEXT );
    }


    
    // ======================================================================
    // I N N E R   C L A S S E S
    // ======================================================================

    protected static class MutateLanderPropertyListener
    implements PropertyChangeListener
    {
	private Lander lander;
	
	public MutateLanderPropertyListener( Lander lander ) 
	{
	    this.lander = lander;
	}

	public void propertyChange(PropertyChangeEvent evt) 
	{
	    if ( evt.getPropertyName().equals( WorldObject.DESTROYED_PROPERTY ) )
	    {
		// Automatically mutate if there are no humans and this lander
		// is not yet a mutant
		if ( lander.getState() != Lander.STATE_MUTANT && 
		     Human.getInstanceCount() < 1)
		    lander.mutate();
	    }
	}
    }
	
    protected class AlienPropertyChangeListener 
    implements PropertyChangeListener
    {
	public void propertyChange(PropertyChangeEvent evt) {
	    if ( evt.getPropertyName().equals( WorldObject.DESTROYED_PROPERTY ) )
	    {
		alienCounter.decrement();
		if ( alienCounter.getCount() < 1 && !playerDied ) {
		    // The level has been completed, 
		    // when all aliens have been destroyed
		    setLevelCompleted(true);
		}
	    }
	}
    }
    
    protected class BomberPropertyChangeListener 
    implements PropertyChangeListener
    {
	public void propertyChange(PropertyChangeEvent evt) {
	    if ( evt.getPropertyName().equals( WorldObject.DESTROYED_PROPERTY ) )
	    {
		bomberCounter.decrement();
	    }
	}
    }

    protected class PodPropertyChangeListener 
    implements PropertyChangeListener
    {
	public void propertyChange(PropertyChangeEvent evt) {
	    if ( evt.getPropertyName().equals( WorldObject.DESTROYED_PROPERTY ) )
	    {
		podCounter.decrement();
	    }
	}
    }
    
    protected class SwarmerPropertyChangeListener 
    implements PropertyChangeListener
    {
	public void propertyChange(PropertyChangeEvent evt) {
	    if ( evt.getPropertyName().equals( WorldObject.DESTROYED_PROPERTY ) )
	    {
		swarmerCounter.decrement();
	    }
	}
    }
    
    protected class GameKeyListener implements KeyListener 
    {
	public GameKeyListener() 
	{
	    // System.out.println("GameKeyListener() plastic palace people.");
	}
	    
	// Handle Event Driven Input
	public void keyTyped( KeyEvent ke )
	{
	}
	
	public void keyPressed( KeyEvent ke )
	{
	    int keycode = ke.getKeyCode();
	    int mods = ke.getModifiers();
	    
	    if ( keycode == KEYCODE_UP )
		up_key_pressed = true;
	    else if ( keycode == KEYCODE_DOWN )
		down_key_pressed = true;
	    else if ( keycode == KEYCODE_THRUST )
		thrust_key_pressed = true;
	    else if ( keycode == KEYCODE_LASER )
		fire_key_pressed = true;
	    else if ( keycode == KEYCODE_COD )
		cod_key_pressed = true;
	    else if ( keycode == KEYCODE_HYPERSPC )
		hyperspace_key_pressed = true;
	    else if ( keycode == KEYCODE_SMARTBMB )
		smartbomb_key_pressed = true;
	    else if ( keycode == KeyEvent.VK_L &&
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		level_key_pressed = true;
	    }
	    else if ( keycode == KeyEvent.VK_K &&
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		reconfig_key_pressed = true;
	    }
	    else if ( keycode == KeyEvent.VK_Q && 
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		setPlayerDied(true);
		setGameOver(true);
	    }
	    else if ( keycode == KeyEvent.VK_E && 
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		exit_key_pressed=true;
	    }
	    else if ( keycode == KeyEvent.VK_4 && 
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		setDebugMode( !isDebugMode() );
	    }
	    
	    // if ( keycode == KeyEvent.VK_N ) {
	    // 	   System.out.print("B");
	    // 	   System.out.println( "keyPressed() Code: "+keycode+" Mods:"+mods );
	    // }
	    
	    // Special case of shift key
	    if ( (mods & InputEvent.SHIFT_MASK ) != 0 )
		thrust_key_pressed = true;

	}
	
	public void keyReleased( KeyEvent ke )
	{
	    int keycode = ke.getKeyCode();
	    int mods = ke.getModifiers();

	    if ( keycode == KEYCODE_UP )
		up_key_pressed = false;
	    else if ( keycode == KEYCODE_DOWN )
		down_key_pressed = false;
	    else if ( keycode == KEYCODE_THRUST )
		thrust_key_pressed = false;
	    else if ( keycode == KEYCODE_LASER )
		fire_key_pressed = false;
	    else if ( keycode == KEYCODE_COD )
		cod_key_pressed = false;
	    else if ( keycode == KEYCODE_HYPERSPC )
		hyperspace_key_pressed = false;
	    else if ( keycode == KEYCODE_SMARTBMB )
		smartbomb_key_pressed = false;
	    else if ( keycode == KeyEvent.VK_L &&
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		level_key_pressed = false;
	    }
	    else if ( keycode == KeyEvent.VK_K &&
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		reconfig_key_pressed = false;
	    }
	    else if ( keycode == KeyEvent.VK_M &&
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		// TODO: We display loudspeaker enabled / disable icons
		PPMCAudioPlayer mcplayer = getMCAudioPlayer();
		Toolkit.getDefaultToolkit().beep();
		mcplayer.setMute( !mcplayer.isMuted() );
	    }
	    else if ( keycode == KeyEvent.VK_I && 
		      (mods & InputEvent.CTRL_MASK) != 0 ) {
		Frame frame = 
		    GraphicsUtils.getFrameOfComponent( getDoubleBufferedContainer() );

		// Int JDK 1.2 we can iconify a window, in the earlier
		// JDK 1.1.7 least we can do send the window to the
		// back to hide it from those pesky interfering and
		// perhaps money grabing roving managers
		Toolkit.getDefaultToolkit().beep();
		if (frame != null)
		    frame.toBack();
		setPaused(true);
	    }
	    else if ( keycode == KEYCODE_PAUSE ) {
		// Pause button
		if ( getGameState() == GameEngine.GAME_STATE_EPISODE )
		    setPaused( !isPaused() );
	    }

	    // if ( keycode == KeyEvent.VK_N ) {
	    //	  System.out.print("N");
	    //	  System.out.println( "keyReleased() Code: "+keycode+" Mods:"+mods );
	    // }
	    
	

	    // Special case of shift key
	    // if ( (mods & InputEvent.SHIFT_MASK ) != 0 )
	    //     thrust_key_pressed = false;
	}

    }

}

// fini
