 /**
  *  GameCanvas contains the Canvas for the Epsilon game.
  *  Epsilon is a simple space-shooter-vertical-scroller game.
  *
  *  Epsilon is available for non-commercial use only!
  *  You can learn from this sources and you can modify them
  *  for learning purposes.
  * 
  *  @author      Ziga Hajdukovic
  *  @version     1.0
  *
  *  CHANGES (01 --> 02)
  *  + new player sprite, p.png
  *  + added boss sprite boss1.png
  *  + added tileset t04.png
  *  + added enemy sprites e1.png, e2.png, e3.png
  *  + added fire.png, removed pf.png
  *  + added explosion sprite, exp.png
  *  + moved Image objects for sprites to spriteImage array
  *  + added tileImage
  *  + moved image loading to loadImages()
  *  + setClip in paintSprite
  *  + player sprite frames for left and right movement
  *  + multiple fire sprites + fire_cooldown_wait_frames
  *  + 20 FPS fixed frame rate
  *  + Vector explosionSprites
  *  + updateExplosionSprites()
  *  + doCollision() -> add explosion on collision
  *  + paint explosionSprites
  *  + Level class
  *  + loadLevel(), tile map, and sprite positions
  *  + space and tiles y and vy
  *  + load sprites positions
  *  + level paint
  *  +   (scrolling background space layer)
  *  +   (scrolling tile layer)
  *  + new (but still random) enemies
  *  + player game over explosion
  * 
  *
  *  CHANGES (02 -> 03)
  *  + run() if(gameRunning)
  *  + run(), + midlet.display.callSerially(this);
  *  + midlet.startApp(), gameCanvas.run()
  *  
  *  + pause game screen
  *     + new menu scr paused game, with 2 softkey labels
  *     	+ resume on softkey1
  *     	+ main menu on softkey2
  *     + hidenotify()
  *  + player.vy += Level.tiles_scroll_vy
  *  + removed automatic enemy sprite generation
  *  + updated player screen edge collision detection
  *  + spriteData array  
  *  + damage and score added to GameSprite, see doCollision
  *  + EXPLOSION_FRAME_SMOKE_START
  *  + move player with level scroll
  *  + enemy activation zone 
  *  + remove out-of-screen enemies, updated!
  *  + doEnemyAI()
  *  + fire_cooldown_frames_counter moved to GameSprite
  *  + collision updated, vector loop inverted...
  *  + enemyFireSprites Vector, updateEnemyFireSprites() paint ...
  *  + doEnemyAI(), shooting enemies!
  *  + enemy fire sprites <-> player collision
  *  + boss big bada boom explosion, like players...
  *  + god_mode
  *  + boss, moving, shooting
  *  + view and enter hiscore screens
  *  + flow for score view and edit
  *  + import javax.microedition.rms.*;
  *  + import java.io.*;
  *  + loadHighscoreFromRMS(), add call to intro mode, when loading
  *  + isQualified(int score)
  *  + addHighscore(int score, String name)
  *  + getHighscoreData(), highscore helper function
  *  + saveHighscores(), highscore helper function
  *  
  *  
*/

import java.util.*;

import javax.microedition.lcdui.*;

import javax.microedition.rms.*;
import java.io.*;

//import com.nokia.mid.ui.FullCanvas;
//
//class GameCanvas extends FullCanvas implements Runnable
class GameCanvas extends Canvas implements Runnable
{
	private final Game midlet;
	
	public static int canvasWidth;
	public static int canvasHeight;

	public static boolean gameRunning;

	public static final int SOFTKEY_1 = -6;
	public static final int SOFTKEY_2 = -7;
	
	public static boolean menu_command_1;
	public static boolean menu_command_2;
	
	public static boolean command_up;
	public static boolean command_down;
	public static boolean command_left;
	public static boolean command_right;
	public static boolean command_fire;
	
	public static byte mode;
	public static final byte MODE_INTRO = 0;
	public static final byte MODE_GAME = 1;
	public static final byte MODE_MENU = 2;
	
	public static int loadingCounter;

	public static Image imgIntro;
	public static Image imgBackground;

	public static Image[] spriteImages;
	public static final byte SPRITE_IMAGE_PLAYER = 0;
	public static final byte SPRITE_IMAGE_FIRE = 1;
	public static final byte SPRITE_IMAGE_BOSS1 = 2;
	public static final byte SPRITE_IMAGE_ENEMY1 = 3;
	public static final byte SPRITE_IMAGE_ENEMY2 = 4;
	public static final byte SPRITE_IMAGE_ENEMY3 = 5;
	public static final byte SPRITE_IMAGE_EXPLOSION = 6;
	public static final byte SPRITE_IMAGE_MAX = 7;
	
	public static final byte SPRITE_FRAME_PLAYER_LEFT = 0;
	public static final byte SPRITE_FRAME_PLAYER_CENTER = 1;
	public static final byte SPRITE_FRAME_PLAYER_RIGHT = 2;
	
	public static final byte PLAYER_FIRE_COOLDOWN_WAIT_FRAMES = 5;
	public static final byte ENEMY_FIRE_COOLDOWN_WAIT_FRAMES = 40;
	
	public static Image tileImage;

	public static int menuScreen;
	public static final byte MENU_SCR_MAIN = 0; 
	public static final byte MENU_SCR_TEXT_ABOUT = 1;
	public static final byte MENU_SCR_TEXT_HELP = 2;
	public static final byte MENU_SCR_TEXT_GAME_OVER = 3;
	public static final byte MENU_SCR_PAUSE_GAME = 4; 
	public static final byte MENU_SCR_HISCORE_ENTER = 5; 
	public static final byte MENU_SCR_HISCORE_VIEW = 6; 

	public static int menuSelectedOption;
	public static final byte MENU_OPTION_MAIN_NEW_GAME = 0;
	public static final byte MENU_OPTION_MAIN_HISCORES = 1;
	public static final byte MENU_OPTION_MAIN_ABOUT = 2;
	public static final byte MENU_OPTION_MAIN_HELP = 3;
	public static final byte MENU_OPTION_MAIN_MAX = 3;
	
	public static final String[] menuScreenMainOptions = { "New game", "Highscore", "About", "Help"};
	
	public static final String[][] menuScreensTextLines = {
		{ "" },
		{ "About", "", "Epsilon v0.1", "(c) 2005 zigah." },
		{ "Help", "", "Press LEFT, RIGHT, ", "UP or DOWN", "to move and", "FIRE to shoot." },
		{ "Game Over", "", "Well done!"},
		{ "","Paused!"},
	};


	// Highscore
	public static final byte MAX_SCORES = 5;
	public static final String SCORE_DB_NAME = "EPS";
	public static String hiNames[] = { "JOE", "DOE", "LAA", "BIG", "MAC" };
	public static int hiScores[] = { 300, 200, 150, 100, 50 };
	private static RecordStore db;

	public static final char[] HI_NAME_ENTER_DEFAULT = {'A','A','A'};
	public static final byte MAX_HI_NAME_LENGTH = 3;
    public static char[] hi_name_enter = new char[MAX_HI_NAME_LENGTH];
	public static byte hi_name_enter_char_pos;
	

	// for debug purposes
	public boolean god_mode; 
	
	public static GameSprite player;
	public static Vector playerFireSprites;
	public static int player_score;
	public static int player_gameover_explosion_counter;
	public static final byte PLAYER_GAMEOVER_EXPLOSION_COUNTER_MAX = 40;
	
	public static boolean activate_boss_energy_bar;
	
	public static int boss_dead_explosion_counter;
	public static final byte BOSS_DEAD_EXPLOSION_COUNTER_MAX = 80;

	public static final byte FIRE_VELOCITY = 8;
	public static final byte MAX_ENEMY_FIRE = 6;
		
	public static Vector enemyFireSprites;

	public static final byte MAX_ENERGY_PLAYER = 100;
	public static final byte MAX_ENERGY_PLAYER_FIRE = 10;
	public static final byte MAX_ENERGY_ENEMY = 10;
	
	public static Vector explosionSprites;

	public static byte max_enemy_sprites = 3;
	
	public static final short FIXED_FRAME_TIME = 40;

	public static int global_frame_counter;
	
	public static Random random = new Random();
	
	GameCanvas(Game midlet)
	{
		this.midlet = midlet;

		canvasWidth = this.getWidth();
		canvasHeight = this.getHeight();

		loadingCounter = 0;
		menuScreen = MENU_SCR_MAIN;
		menuSelectedOption = MENU_OPTION_MAIN_NEW_GAME;
		mode = MODE_INTRO;
		gameRunning = true;
	}

	public synchronized void run()
	{
		if(gameRunning)
		{
			long frameStartTime = System.currentTimeMillis();
			global_frame_counter++;
			
			repaint();
			//serviceRepaints();
			midlet.display.callSerially(this);

			switch (mode) {
			case MODE_INTRO:

				try
				{
					if (loadingCounter == 0)
					{
						imgIntro = Image.createImage("/intro.png");
					}
					else if (loadingCounter == 1)
					{
						loadImages();
						loadHighscoreFromRMS();
					}
					else
					{
						// view the intro splash image for a while
						// it would be better to let user skip intro by pressing a key
						// but, in a full game project, loading would take forever, anyway, so...
						try	{
							Thread.sleep(1000);
						} catch (Exception e) {};
						// switch mode
						mode = MODE_MENU;
					}
					loadingCounter++;
				}
				catch (Exception e)	{
					// ignore loading exception	
				};
				break;
				
			case MODE_MENU:

				if (command_up) {
					command_up = false;
					menuSelectedOption--;
					if (menuSelectedOption < 0)
						menuSelectedOption = 0;
					
					if (menuScreen == MENU_SCR_HISCORE_ENTER)
					{
						if ( hi_name_enter[hi_name_enter_char_pos] > 'A')
							hi_name_enter[hi_name_enter_char_pos]--;
					}
				}
				if (command_down) {
					command_down = false;
					menuSelectedOption++;
					if ((menuScreen == MENU_SCR_MAIN) &&
							(menuSelectedOption > MENU_OPTION_MAIN_MAX))
						menuSelectedOption = MENU_OPTION_MAIN_MAX;
					
					if (menuScreen == MENU_SCR_HISCORE_ENTER)
					{
						if ( hi_name_enter[hi_name_enter_char_pos] < 'Z')
							hi_name_enter[hi_name_enter_char_pos]++;
					}
				}
				if (command_left) {
					command_left = false;
					
					if (menuScreen == MENU_SCR_HISCORE_ENTER)
					{
						if (hi_name_enter_char_pos > 0)
							hi_name_enter_char_pos--;
					}
				}
				if (command_right) {
					command_left = false;
					
					if (menuScreen == MENU_SCR_HISCORE_ENTER)
					{
						if (hi_name_enter_char_pos < MAX_HI_NAME_LENGTH-1)
							hi_name_enter_char_pos++;
					}
				}
				
				if (menu_command_1)
				{
					menu_command_1 = false;
					if (menuScreen == MENU_SCR_MAIN)
					{
						switch(menuSelectedOption)
						{
						case MENU_OPTION_MAIN_NEW_GAME:
							startGame();
							break;
							
						case MENU_OPTION_MAIN_HISCORES:
							menuScreen = MENU_SCR_HISCORE_VIEW;
							break;
						
						case MENU_OPTION_MAIN_ABOUT:
							menuScreen = MENU_SCR_TEXT_ABOUT;
							break;
							
						case MENU_OPTION_MAIN_HELP:
							menuScreen = MENU_SCR_TEXT_HELP;
							break;
						}
					} else if ( menuScreen == MENU_SCR_PAUSE_GAME) {
						// resume pressed
						mode = MODE_GAME;
					} else if ( menuScreen == MENU_SCR_TEXT_GAME_OVER) {
						// ok pressed
						if ( isQualified( player_score ) != -1 )
						{
							menuScreen = MENU_SCR_HISCORE_ENTER;
							// init enter name character position
							hi_name_enter_char_pos = 0;
							hi_name_enter = HI_NAME_ENTER_DEFAULT;
						} else {
							menuScreen = MENU_SCR_HISCORE_VIEW;
						}
					} else if ( menuScreen == MENU_SCR_HISCORE_ENTER) {
						// save highscore

						String name = new String(hi_name_enter);
						addHighscore(player_score, name);
						
						menuScreen = MENU_SCR_HISCORE_VIEW;
					} else {
						// all other menu screens go to main menu when OK is selected
						menuScreen = MENU_SCR_MAIN;
					}
				}
				if (menu_command_2)
				{
					menu_command_2 = false;
					
					// back to main menu, when end game selected
				    if ( menuScreen == MENU_SCR_PAUSE_GAME) {
						menuScreen = MENU_SCR_MAIN;
				    } else if (menuScreen == MENU_SCR_MAIN)
				    {
						midlet.exitRequested();
				    }
				}

				// delay, so the keypress events get their turn...
				try	{
					Thread.sleep(40);
				} catch (Exception e) {};
				break;
				
			case MODE_GAME:

				// scroll space and tile background
				Level.space_scroll_y += Level.space_scroll_vy;
				Level.tiles_scroll_y += Level.tiles_scroll_vy;

				// top of the level (boss) reached, stop scrolling
				if (Level.tiles_scroll_y < 0) {
					Level.tiles_scroll_y = 0;
					Level.tiles_scroll_vy = 0;
				}
				
//				int tiles_scroll_x_range = (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth);
//				int player_x_range = canvasWidth - player.w;
//				Level.tiles_scroll_x = player.x * tiles_scroll_x_range / player_x_range;


				// update scroll_x
				Level.tiles_scroll_x = player.x + player.w/2 - canvasWidth/2;
				if (Level.tiles_scroll_x > (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth)) {
					Level.tiles_scroll_x = (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth);
				}
				
				if (Level.tiles_scroll_x < 0) {
					Level.tiles_scroll_x = 0;
				}
				
				updatePlayerSprite();
				updatePlayerFireSprites();

				updateEnemySprites();
				updateEnemyFireSprites();
				
				updateExplosionSprites();
				
				// check player <-> enemy <-> player fire collision
				doCollision();
				
				// check for game end
				if ( (player.energy <= 0) && (player_gameover_explosion_counter == 0) )
				{
					// TODO: lose screen
					endGame();
				}
				// check for game end
				if ( (Level.boss.energy <= 0) && (boss_dead_explosion_counter == 0) )
				{
					// TODO: win screen
					endGame();
				}
				if (menu_command_2)
				{
					menu_command_2 = false;
					// on softkey_2 activate pause game menu
					mode = MODE_MENU;
					menuScreen = MENU_SCR_PAUSE_GAME;
				}

				// calc. delay
				long frameTime = System.currentTimeMillis() - frameStartTime;
				if (frameTime < FIXED_FRAME_TIME)
				{
					try	{
						Thread.sleep( FIXED_FRAME_TIME - frameTime );
					} catch (Exception e) {};
				}
				break;
			}
		}
	}
	
	public synchronized void paint(Graphics g)
	{
		switch(mode)
		{
		case MODE_INTRO:

			g.setColor(0x000000);
			g.fillRect(0, 0, canvasWidth, canvasHeight);
			if (imgIntro != null) {
				g.drawImage(imgIntro, canvasWidth/2, canvasHeight/2, Graphics.VCENTER | Graphics.HCENTER);
			}

//			g.setColor(0xFFFFFF);
//			g.drawString(""+loadingCounter,0,0,Graphics.LEFT | Graphics.TOP);

			break;
			
		case MODE_MENU:

			Level.paintSpaceBackground(g);

			switch(menuScreen)
			{
			case MENU_SCR_MAIN:
				paintMainMenu(g);
				break;
				
			case MENU_SCR_TEXT_ABOUT:
			case MENU_SCR_TEXT_HELP:
			case MENU_SCR_TEXT_GAME_OVER:
			case MENU_SCR_PAUSE_GAME:
				paintTextMenu(g, menuScreen);
				break;
			case MENU_SCR_HISCORE_VIEW:
				paintHiscoreView(g);
				break;
			case MENU_SCR_HISCORE_ENTER:
				paintHiscoreEnter(g);
				break;
			}
			break;
			
		case MODE_GAME:
			
			Level.paintSpaceBackground(g);
			Level.paintTiles(g);
			
			paintSprite( g, player );
			
			for (int i=0; i < Level.enemySprites.size(); i++)
				paintSprite( g, (GameSprite) Level.enemySprites.elementAt(i) );
			
			for (int i=0; i < explosionSprites.size(); i++)
				paintSprite( g, (GameSprite) explosionSprites.elementAt(i) );

			for (int i=0; i < playerFireSprites.size(); i++)
				paintSprite( g, (GameSprite) playerFireSprites.elementAt(i));
			
			for (int i=0; i < enemyFireSprites.size(); i++)
				paintSprite( g, (GameSprite) enemyFireSprites.elementAt(i));

			paintStatusBars(g);
			
			break;		
		}
	}
	
	public void keyPressed(int key)
	{
		switch(key) {
		case SOFTKEY_1:
			menu_command_1 = true;
			break;
			
		case SOFTKEY_2:
			menu_command_2 = true;
			break;

		// TODO: debug, temp, remove
		case KEY_POUND:
			god_mode = !god_mode;
			break;
		// TODO: debug, temp, remove
		case KEY_STAR:
			Level.tiles_scroll_vy *= 2;
			break;
		}
		
		int game_action = getGameAction(key);
		
		switch(game_action)
		{
		case UP:
			command_up = true;
			break;

		case DOWN:
			command_down = true;
			break;

		case LEFT:
			command_left = true;
			break;

		case RIGHT:
			command_right = true;
			break;

		case FIRE:
			command_fire = true;
			menu_command_1 = true;
			break;
		}
	}
	
	public void keyReleased(int key)
	{
		int game_action = getGameAction(key);

		switch(game_action)
		{
		case UP:
			command_up = false;
			break;

		case DOWN:
			command_down = false;
			break;

		case LEFT:
			command_left = false;
			break;

		case RIGHT:
			command_right = false;
			break;

		case FIRE:
			command_fire = false;
			menu_command_1 = false;
			break;
		}
	}

	public void paintMainMenu(Graphics g)
	{
		// init font
		g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE));
		int menu_y = canvasHeight/2 - 45;
		for(int i=0; i <= MENU_OPTION_MAIN_MAX; i++)
		{
			// paint the main menu options
			if (i == menuSelectedOption) {
				g.setColor(0xFFCC00);
			} else {
				g.setColor(0xFFFFFF);
			}
			
			g.drawString(menuScreenMainOptions[i], canvasWidth/2, menu_y, Graphics.HCENTER | Graphics.TOP);

			menu_y += 20;
		}
		g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
		g.setColor(0xFFFFFF);
		// paint softkey_1 label
		g.drawString("Select", 2, canvasHeight-2, Graphics.LEFT | Graphics.BOTTOM);
		// paint softkey_2 label
		g.drawString("Exit", canvasWidth-2, canvasHeight-2, Graphics.RIGHT | Graphics.BOTTOM);
	}

	public void paintTextMenu(Graphics g, int screen)
	{
		g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
		g.setColor(0xFFFFFF);
		int text_line_y = 10;
		for(int line=0; line < menuScreensTextLines[screen].length; line++)
		{
			// paint all the lines of text in this menu screen
			g.drawString(menuScreensTextLines[screen][line], canvasWidth/2, text_line_y, Graphics.HCENTER | Graphics.TOP);
			
			text_line_y += 15;
		}
		if (screen == MENU_SCR_TEXT_GAME_OVER)
		{
			// print score in the game over screen
			text_line_y += 15;
			g.drawString("Score: "+ player_score, canvasWidth/2, text_line_y, Graphics.HCENTER | Graphics.TOP);
		}

		g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
		
		if (menuScreen == MENU_SCR_PAUSE_GAME )
		{
			// paint softkey_1 label
			g.drawString("Resume", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
			// paint softkey_2 label
			g.drawString("End game", canvasWidth-1, canvasHeight-1, Graphics.RIGHT | Graphics.BOTTOM);
		}
		else
		{
			// paint softkey_1 label
			g.drawString("Ok", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
		}
	}

	public static void paintHiscoreView(Graphics g)
	{
		g.setColor(0xFFFFFF);

		g.drawString("Highscore!", canvasWidth/2, 2, Graphics.HCENTER | Graphics.TOP);

		for (int i=0; i < MAX_SCORES; i++)
		{
			g.drawString(hiNames[i], canvasWidth*1/4, 20 + 20*i, Graphics.LEFT | Graphics.TOP);
			g.drawString(""+hiScores[i], canvasWidth*3/4, 20 + 20*i, Graphics.RIGHT | Graphics.TOP);
		}
		
		// paint softkey_1 label
		g.drawString("Ok", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
	}
	public static void paintHiscoreEnter(Graphics g)
	{
		g.setColor(0xFFFFFF);

		g.drawString("Enter name:", canvasWidth/2, canvasHeight*1/4, Graphics.HCENTER | Graphics.BOTTOM);

		for (int i=0; i < MAX_HI_NAME_LENGTH; i++)
		{
			if (i == hi_name_enter_char_pos)
				g.setColor(0xFFFF00);
			else
				g.setColor(0xFFFFFF);
			g.drawChar(hi_name_enter[i], canvasWidth/2 - 12 + 12*i, canvasHeight*2/4, Graphics.HCENTER | Graphics.BOTTOM);
		}

		g.setColor(0xFFFFFF);
		g.drawString("Score: "+ player_score, canvasWidth/2, canvasHeight*3/4, Graphics.HCENTER | Graphics.BOTTOM);

		// paint softkey_1 label
		g.drawString("Save", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
	}

	public static void paintSprite(Graphics g, GameSprite spr)
	{
		// set the clip window
		int dx = spr.x - Level.tiles_scroll_x;
		int dy = spr.y - Level.tiles_scroll_y;
		int cx = dx;
		int cy = dy;
		int cw = GameSprite.spriteImageFrameWidths[spr.imageId];
		int ch = spr.h;
		g.setClip(cx, cy, cw, ch);

		//  check clip window coordinates, if out of screen
		if (cx < 0)
		{
			cw += cx;
			cx = 0;
		}
		if (cy < 0)
		{
			ch += cy;
			cy = 0;
		}
		
		// calculate the x coordinate of where to start the image paint, so our frame will be seen through the clip window
		dx = dx - spr.frameId * GameSprite.spriteImageFrameWidths[spr.imageId];
		
		// paint the sprites image at the calculated coordinates
		g.drawImage(spriteImages[spr.imageId], dx, dy, Graphics.TOP | Graphics.LEFT);

		// restore the clip window
		g.setClip(0, 0, canvasWidth, canvasHeight);
	}

	public static void paintStatusBars(Graphics g)
	{
		int max_energy_bar_width = canvasWidth/2;

		// paint the energy bar
		g.setColor(0xFFFFFF);
		g.drawRect(5, 5, max_energy_bar_width + 1, 4);
		g.setColor(0xFFCC00);
		g.fillRect(6, 6, player.energy * max_energy_bar_width / player.energyMax, 3);
		
		if ( activate_boss_energy_bar)
		{
			// paint the energy bar
			g.setColor(0xFFFFFF);
			g.drawRect(5, 12, max_energy_bar_width + 1, 4);
			g.setColor(0xFF9900);
			g.fillRect(6, 13, Level.boss.energy * max_energy_bar_width / Level.boss.energyMax, 3);
		}

		
		// paint the score count
		g.setColor(0xFFFFFF);
		g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_SMALL));
		g.drawString(""+player_score, canvasWidth-4, 8, Graphics.TOP | Graphics.RIGHT);
	}

	public void startGame()
	{
		// switch to game mode
		mode = MODE_GAME;

		player_score = 0;
		player_gameover_explosion_counter = -1;
				
		activate_boss_energy_bar = false;
		boss_dead_explosion_counter = -1;

		// initialize the explosion sprite list
		explosionSprites = new Vector();
		
		// initialize the player fire list
		playerFireSprites = new Vector();

		// initialize the enemy fire list
		enemyFireSprites = new Vector();

		// init level tiles position to horizontal center and vertical bottom
		Level.tiles_scroll_x = Level.LEVEL_WIDTH * Level.TILE_SIZE / 2 - canvasWidth /2;
		Level.tiles_scroll_y = Level.LEVEL_HEIGHT * Level.TILE_SIZE - canvasHeight - 1;
		
		Level.loadLevel((byte) 1);
	}
	
	public void endGame()
	{
		// back to menu mode, set the game over text screen
		mode = MODE_MENU;
		menuScreen = MENU_SCR_TEXT_GAME_OVER;

		// clean up
		player = null;
		Level.enemySprites = null;
	}

	public void updateEnemySprites()
	{
		// move enemy sprites
		int i = Level.enemySprites.size()-1;
		while (i >= 0)
		{
			GameSprite enemy = (GameSprite) Level.enemySprites.elementAt(i);
			
			if ( (enemy.y - Level.tiles_scroll_y > canvasHeight) )
			{
				// remove out-of-screen enemies
				Level.enemySprites.removeElementAt(i);
				i--;
			}
			else
			{
				//System.out.println(enemy.y +"-"+ Level.tiles_scroll_y +">"+ Level.ENEMY_AI_ACTIVATION_ZONE_Y);
				// do the AI move, shoot and other tricks
				if ( enemy.y - Level.tiles_scroll_y > Level.ENEMY_AI_ACTIVATION_ZONE_Y )
				{
					doEnemyAI( enemy );
					i--;
				} else {
					// optimization
					// but enemySprites must be ordered by the sprites' y coordinate !
					// they are in this case, because of loadLevel
					break;
				}
			}
		}
		if (boss_dead_explosion_counter > 0)
		{
			boss_dead_explosion_counter--;
		}
	}
	
	public void doEnemyAI( GameSprite sprite )
	{
		//System.out.println("doing enemy ai for "+sprite.spriteId);
		
		switch (sprite.spriteId)
		{
		case Level.SPRITE_ID_ENEMY1:
			break;
		case Level.SPRITE_ID_ENEMY1_SHOOTING:

			shootEnemyFire(sprite, Level.SPRITE_ID_FIRE2, (byte) 1);
			
			break;
		case Level.SPRITE_ID_ENEMY2:
			break;
		case Level.SPRITE_ID_ENEMY2_SHOOTING:
			
			shootEnemyFire(sprite, Level.SPRITE_ID_FIRE2, (byte) 1);

			break;
		case Level.SPRITE_ID_ENEMY3:

			// animate sprite to next sprite frame every 3 frames
			if (global_frame_counter % 3 == 0)
				sprite.frameId = (byte) ( (sprite.frameId + 1) % (sprite.frameIdMax+1));
			
			break;
		case Level.SPRITE_ID_BOSS1:

			activate_boss_energy_bar = true;

			// boss fire
			if (sprite.fire_cooldown_frames_counter <= 0)
			{
				GameSprite new_fire;

				new_fire = new GameSprite(Level.SPRITE_ID_FIRE2);
				new_fire.frameId = 1;
				new_fire.x = sprite.x + sprite.w - 5 - new_fire.w;
				new_fire.y = sprite.y + sprite.h;
				new_fire.vy = sprite.vy + FIRE_VELOCITY/2;
				enemyFireSprites.addElement(new_fire);

				new_fire = new GameSprite(Level.SPRITE_ID_FIRE2);
				new_fire.frameId = 1;
				new_fire.x = sprite.x + 5;
				new_fire.y = sprite.y + sprite.h;
				new_fire.vy = sprite.vy + FIRE_VELOCITY/2;
				enemyFireSprites.addElement(new_fire);

				new_fire = new GameSprite(Level.SPRITE_ID_FIRE3);
				new_fire.frameId = 2;
				new_fire.x = sprite.x + sprite.w / 2 - new_fire.w / 2;
				new_fire.y = sprite.y + sprite.h;
				new_fire.vy = sprite.vy + FIRE_VELOCITY;
				enemyFireSprites.addElement(new_fire);

				sprite.fire_cooldown_frames_counter = ENEMY_FIRE_COOLDOWN_WAIT_FRAMES;
			} else {
				// extra cooldown, boss only
				sprite.fire_cooldown_frames_counter--;
			}
			
			// boss move
			if (sprite.x + sprite.w > (Level.LEVEL_WIDTH * Level.TILE_SIZE) - (Level.TILE_SIZE/2) )
				sprite.vx = -1;
			if (sprite.x < Level.TILE_SIZE/2 )
				sprite.vx = 1;
			
			break;
		}
		
		// move the sprite
		sprite.x += sprite.vx;
		sprite.y += sprite.vy;

		if (sprite.fire_cooldown_frames_counter > 0)
			sprite.fire_cooldown_frames_counter--;
	}

	public GameSprite shootEnemyFire(GameSprite sprite, byte fire_sprite_id, byte fire_sprite_frame_id)
	{
		GameSprite new_fire = null;
		if (enemyFireSprites.size() < MAX_ENEMY_FIRE && sprite.fire_cooldown_frames_counter <= 0)
		{
//			System.out.println("adding fire sprite ... ");
			new_fire = new GameSprite(fire_sprite_id);
			new_fire.frameId = fire_sprite_frame_id;
			new_fire.x = sprite.x + sprite.w / 2;
			new_fire.y = sprite.y + sprite.h;
			new_fire.vy = sprite.vy + FIRE_VELOCITY/2;
			enemyFireSprites.addElement(new_fire);

			sprite.fire_cooldown_frames_counter = ENEMY_FIRE_COOLDOWN_WAIT_FRAMES;
			
		}
		return new_fire;
	}
	
	public void updatePlayerSprite()
	{
		// move player with level scroll
		player.y += Level.tiles_scroll_vy;

		if (player_gameover_explosion_counter > 0)
		{
			player_gameover_explosion_counter--;
		}
		else
		{
			// move player sprite on command, and check player <-> screen edge collision
			if (command_up)
			{
				player.y -= player.vy;
				if (player.y < Level.tiles_scroll_y)
					player.y = Level.tiles_scroll_y;
			}
			if (command_down)
			{	
				player.y += player.vy;
				// level edge collision
				if (player.y - Level.tiles_scroll_y > canvasHeight - player.h)
					player.y = Level.tiles_scroll_y + canvasHeight - player.h;
			}
			player.frameId = SPRITE_FRAME_PLAYER_CENTER;
			if (command_left)
			{	
				player.x -= player.vx;
				if (player.x < Level.tiles_scroll_x)
					player.x = Level.tiles_scroll_x;
				player.frameId = SPRITE_FRAME_PLAYER_LEFT;
			}
			if (command_right)
			{	
				player.x += player.vx;
				if (player.x - Level.tiles_scroll_x > canvasWidth - player.w)
					player.x = Level.tiles_scroll_x + canvasWidth - player.w;
				player.frameId = SPRITE_FRAME_PLAYER_RIGHT;
			}
			if (command_fire)
			{
				if (player.fire_cooldown_frames_counter <= 0)
				{
					GameSprite fire = new GameSprite(Level.SPRITE_ID_FIRE1);
					fire.x = player.x + player.w/2 - fire.w/2;
					fire.y = player.y;
					fire.vy = -FIRE_VELOCITY + Level.tiles_scroll_vy;
					playerFireSprites.addElement(fire);
	
					player.fire_cooldown_frames_counter = PLAYER_FIRE_COOLDOWN_WAIT_FRAMES;
				}
			}
			if (player.fire_cooldown_frames_counter > 0)
				player.fire_cooldown_frames_counter--;
			if ( god_mode )
				player.fire_cooldown_frames_counter -= 2;
		}
	}

	public void doCollision()
	{
		
		int i = Level.enemySprites.size() - 1;
		while (i >= 0 )
		{
			GameSprite enemy = (GameSprite) Level.enemySprites.elementAt(i);
			
			// check player <-> enemy collision
			if (player.collidesWith(enemy))
			{
				// add explosion
				addExplosion(enemy);
				addExplosion(player);

				// player loses energy
				if ( !god_mode )
					player.energy -= enemy.damage;
				
				if (player.energy <= 0 && player_gameover_explosion_counter < 0)
				{
					player_gameover_explosion_counter = PLAYER_GAMEOVER_EXPLOSION_COUNTER_MAX;
				}

				// level boss does not take damage from colliding with player
				if (enemy != Level.boss)
					enemy.energy -= player.damage;

			}
			
			// check the player fire sprites <-> enemy collision
			int j = playerFireSprites.size() - 1;
			while (j >= 0)
			{
				GameSprite fire = (GameSprite) playerFireSprites.elementAt(j);
				if (fire.collidesWith(enemy))
				{
					enemy.energy -= fire.damage;
					
					// add explosions
					addExplosion(enemy);

					// destroy player fire
					playerFireSprites.removeElementAt(j);
					j--;
				}
				j--;
			}
			// remove dead enemies
			if (enemy.energy <= 0)
			{
				Level.enemySprites.removeElementAt(i);
				i--;
				// increase player score
				player_score += enemy.score;

				// if boss sprite
				if (enemy.spriteId == Level.SPRITE_ID_BOSS1)
				{
					boss_dead_explosion_counter = BOSS_DEAD_EXPLOSION_COUNTER_MAX;
				}
			}
			
			i--;
		}
		// check the enemy fire sprites <-> player collision
		int j = enemyFireSprites.size() - 1;
		while (j >= 0)
		{
			GameSprite fire = (GameSprite) enemyFireSprites.elementAt(j);
			if (fire.collidesWith(player))
			{
				if ( !god_mode )
					player.energy -= fire.damage;
				
				// add explosions
				addExplosion(player);

				// destroy enemy fire
				enemyFireSprites.removeElementAt(j);
				j--;
			}
			j--;
		}
	}

	public void updatePlayerFireSprites()
	{
		for (int i=0; i < playerFireSprites.size(); i++)
		{
			GameSprite fire = (GameSprite) playerFireSprites.elementAt(i);
			fire.y += fire.vy;
			
			// destroy player fire when out of screen
			if ( (fire.y + fire.h) < Level.tiles_scroll_y)
			{
				playerFireSprites.removeElementAt(i);
				// only increase counter, if not removed from list
				i--;
			}
		}
	}

	public void updateEnemyFireSprites()
	{
		for (int i=0; i < enemyFireSprites.size(); i++)
		{
//			System.out.println("updating enemy fire sprite...");
			
			GameSprite fire = (GameSprite) enemyFireSprites.elementAt(i);
			fire.y += fire.vy;
			
			// destroy enemy fire when out of screen
			if ( fire.y > Level.tiles_scroll_y + canvasHeight)
			{
				enemyFireSprites.removeElementAt(i);
				// only increase counter, if not removed from list
				i--;
			}
		}
	}

	public void updateExplosionSprites()
	{
		for (int i=0; i < explosionSprites.size(); i++)
		{
			GameSprite exp = (GameSprite) explosionSprites.elementAt(i);
			
			// animate explosion!
			exp.frameId++;
			
			if (exp.frameId > exp.frameIdMax)
			{
				explosionSprites.removeElementAt(i);
				// only increase counter, if not removed from list
				i--;
			}
		}
		// create a big bada boom player gameover explosion
		//  7 - max explosion frames
		//  2 - add an explosion every 2nd frame
		if (player_gameover_explosion_counter > 7 &&
				player_gameover_explosion_counter % 2 == 0
				)
		{
			GameSprite new_exp = addExplosion(player);
			new_exp.x += random.nextInt() % player.w;
			new_exp.y += random.nextInt() % player.h;
		}
		// create a big bada boom boss gameover explosion
		//  7 - max explosion frames
		//  2 - add an explosion every 2nd frame
		if (boss_dead_explosion_counter > 7 &&
				boss_dead_explosion_counter % 2 == 0
				)
		{
			GameSprite new_exp = addExplosion(Level.boss);
			new_exp.x += random.nextInt() % Level.boss.w;
			new_exp.y += random.nextInt() % Level.boss.h;
		}
	}

	public static void loadImages()
	{
		try {
			
			spriteImages = new Image[SPRITE_IMAGE_MAX]; 
			spriteImages[SPRITE_IMAGE_PLAYER] = Image.createImage("/p.png"); 
			spriteImages[SPRITE_IMAGE_FIRE] = Image.createImage("/fire.png"); 
			spriteImages[SPRITE_IMAGE_BOSS1] = Image.createImage("/boss1.png"); 
			spriteImages[SPRITE_IMAGE_ENEMY1] = Image.createImage("/e1.png"); 
			spriteImages[SPRITE_IMAGE_ENEMY2] = Image.createImage("/e2.png"); 
			spriteImages[SPRITE_IMAGE_ENEMY3] = Image.createImage("/e3.png"); 
			spriteImages[SPRITE_IMAGE_EXPLOSION] = Image.createImage("/exp.png"); 
	
			tileImage = Image.createImage("/t04.png");
			
			imgBackground = Image.createImage("/bg.png");
			
		} catch (Exception e)	{};
	}

	public static GameSprite addExplosion(GameSprite exp_spr)
	{
		// add explosion
		GameSprite exp = new GameSprite(Level.SPRITE_ID_EXPLOSION);
		// center the explosion position on the exploded sprite
		exp.x = exp_spr.x + exp_spr.w/2 - exp.w/2;
		exp.y = exp_spr.y + exp_spr.h/2 - exp.h/2;
		exp.vx = exp_spr.vx;
		exp.vy = exp_spr.vy;
		explosionSprites.addElement(exp);
		
		if (exp_spr.energy > 0)
			exp.frameId = GameSprite.EXPLOSION_FRAME_SMOKE_START;
		
		return exp;
	}
	
	public void hideNotify()
	{
		// hideNotify method of Canvas is called on external events, which hide the Canvas
		// this method can be used to handle such events (battery warnings, incoming phone calls...)
		if (mode == MODE_GAME)
		{
			mode = MODE_MENU;
			menuScreen = MENU_SCR_PAUSE_GAME;
		}
	}

//	public void showNotify()
//	{
//		
//	}


    // loads the highscores from RMS - the internal phone database
	// also saves default scores, if no scores exist.
	public static void loadHighscoreFromRMS()
	{
		try
		{
			db = RecordStore.openRecordStore(SCORE_DB_NAME, true);

			byte[] score_data = null;
			// initialize scores
			if (db.getNumRecords() == 0)
			{
				// add record with initial default scores
				score_data = getHighscoreData();
				db.addRecord(score_data, 0, score_data.length);
			}
			// load highscore
			score_data = db.getRecord(1);
			ByteArrayInputStream in = new ByteArrayInputStream(score_data);
			DataInputStream dis = new DataInputStream(in);
			for (byte i = 0; i < MAX_SCORES; i++)
			{
				hiScores[i] = dis.readInt();
				hiNames[i] = dis.readUTF();
			}
			dis.close();
			in.close();
			db.closeRecordStore();
			dis = null;
			in = null;
			db = null;
//			System.gc();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	// prepares data in a byte array, ready for saving to a RMS record
	public static byte[] getHighscoreData() throws IOException
	{
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		DataOutputStream dos = new DataOutputStream(out);
		for (byte i = 0; i < MAX_SCORES; i++)
		{
			dos.writeInt(hiScores[i]);
			dos.writeUTF(hiNames[i]);
		}
		dos.close();
		out.close();

		return out.toByteArray();
	}

	// saves highscores to RMS
	public static void saveHighscores()
	{
		try
		{
			db = RecordStore.openRecordStore(SCORE_DB_NAME, true);

			byte[] data = getHighscoreData();

			db.setRecord(1, data, 0, data.length);
			db.closeRecordStore();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	// returns -1 if not qualified, highscore position otherwise
	public static int isQualified(int score)
	{
		for (byte i = 0; i < MAX_SCORES; i++)
			if (hiScores[i] < score)
				return i;
		return -1;
	}

	// add a score to the scores array and to the rms
	public static void addHighscore(int score, String name)
	{
		int i = isQualified(score);
		
		// move others down 1 place
		for (byte k = (byte) (MAX_SCORES - 1); k > i; k--) {
			hiScores[k] = hiScores[k - 1];
			hiNames[k] = hiNames[k - 1];
		}
		// insert score
		hiScores[i] = score;
		hiNames[i] = name;

		saveHighscores();
		return;
	}

}
