 /**
  *  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
  *
  *  
  *  CHANGES (03 -> 04)
  *  
  *  + added visible property to GameSprite, used for enemy paint and update optimization
  *  + BUGFIX> menu option after highscore
  *  + BUGFIX> player cannot be killed by enemy fire
  *  + FPS counter
  *  + 1,3,7 and 9 keys for diagonal movement
  *  + all level_scroll and x,y and vx,vy of sprites are now representations of floats, with factor SHIFT_FACTOR
  *  + level paint, used shifting instead of * and /
  *  + paint and run profiling!
  *  + LEVEL_DONE and GAME_DONE, new menu screens
  *  + new enemy3 types, sinusoidal movement, with shooting in players direction
  *  + new boss2 type
  *  + new enemy4 type, gun turret, shooting in players direction
  *  + new image for boss2 and gun turret enemy4
  *  + player_nukes
  *  + nuke status bar indicators
  *  + nuke background fade fx
  *  + playMusic(), loadMusic(), stopMusic()
  *  + hideNotify() added a call to stop music
  *  + showNotify() added to restart music
  *  + 
  *  
*/

import java.util.*;

import javax.microedition.lcdui.*;

import javax.microedition.rms.*;
import java.io.*;

import com.nokia.mid.ui.FullCanvas;

import javax.microedition.media.*;
import javax.microedition.media.control.*;

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_BOSS2 = 3;
	public static final byte SPRITE_IMAGE_ENEMY1 = 4;
	public static final byte SPRITE_IMAGE_ENEMY2 = 5;
	public static final byte SPRITE_IMAGE_ENEMY3 = 6;
	public static final byte SPRITE_IMAGE_ENEMY4 = 7;
	public static final byte SPRITE_IMAGE_EXPLOSION = 8;
	public static final byte SPRITE_IMAGE_MAX = 9;
	
	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 = 3;
	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 final byte MENU_SCR_TEXT_LEVEL_DONE = 7;
	public static final byte MENU_SCR_TEXT_GAME_DONE = 8;

	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_MUSIC_ON_OFF = 2;
	public static final byte MENU_OPTION_MAIN_ABOUT = 3;
	public static final byte MENU_OPTION_MAIN_HELP = 4;
	public static final byte MENU_OPTION_MAIN_MAX = 4;

	public static final String TEXT_ON = " on";
	public static final String TEXT_OFF = " off";
	
	public static final String[] menuScreenMainOptions = { "New game", "Highscore", "Music", "About", "Help"};
	
	public static final String[][] menuScreensTextLines = {
		{ "" },
		{ "About", "", "Epsilon v0.5", "(c) 2005 zigah.", "gfx by geci." },
		{ "Help", "", "Press LEFT, RIGHT, ", "UP or DOWN", "to move and", "FIRE to shoot." },
		{ "Game Over", "", "Try harder!"},
		{ "","Paused!"},
		{ },
		{ },
		{ "Stage clear!", "", "Well done!"},
		{ "Game over!", "", "Superb!"},
	};


	// 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 int player_nukes;
	public static int player_nuke_counter = 0;
	public static final byte PLAYER_NUKES_MAX = 3;
	public static final byte PLAYER_NUKE_COUNTER_MAX = 16;

	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 short FIRE_VELOCITY = (8 << GameSprite.SHIFT_FACTOR);
	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();
	
	public static boolean PROFILE_ON_SCREEN = false; // for all the profiling drawString calls

	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 static int FPS_frame_counter;
	public static long FPS_time;
	public static int FPS;

	public synchronized void run()
	{
		if(gameRunning)
		{
			long frameStartTime = System.currentTimeMillis();
			global_frame_counter++;

			FPS_frame_counter++;
			if (FPS_time + 1000 < frameStartTime)
			{
				FPS_time = frameStartTime;
				FPS = FPS_frame_counter;
				FPS_frame_counter = 0;
				System.out.println("FPS:"+ FPS);
			}
			
			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();
						loadMusic();
						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;
						playMusic();
					}
					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:
							
							// init number of nukes available to player
							player_nukes = PLAYER_NUKES_MAX;
							
							// init score
							player_score = 0;
							
							startGame((byte) 1);
							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_MUSIC_ON_OFF:
							musicOn = !musicOn;
							if (musicOn)
								playMusic();
							else
								stopMusic();
							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_LEVEL_DONE) {
						// load next level
						startGame( (byte) (Level.levelno+1) );
						
					} else if ( menuScreen == MENU_SCR_TEXT_GAME_OVER || menuScreen == MENU_SCR_TEXT_GAME_DONE) {
						// 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;
						menuSelectedOption = MENU_OPTION_MAIN_NEW_GAME;
					}
				}
				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 << GameSprite.SHIFT_FACTOR) - (canvasWidth/2 << GameSprite.SHIFT_FACTOR);
				if ((Level.tiles_scroll_x >> GameSprite.SHIFT_FACTOR) > (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth)) {
					Level.tiles_scroll_x = (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth) << GameSprite.SHIFT_FACTOR;
				}
				
				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
				checkEndGame();
				
				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 byte PAINT_PROFILE_TIMES_MAX = 4;
	public long[] paint_profile_times = new long[PAINT_PROFILE_TIMES_MAX*2];
	public String[] paint_profile_labels = { " ", " ", " ", " ", " " };
	public int paint_profile_times_index_counter;

	public void paint_profileStart(int idx, String label) {
		paint_profile_times_index_counter = idx*2;
		paint_profile_times_index_counter = idx*2;
		paint_profile_labels[paint_profile_times_index_counter / 2] = label;
		paint_profile_times[paint_profile_times_index_counter] = System
				.currentTimeMillis();
		paint_profile_times_index_counter++;
	}

	public void paint_profileEnd() {
		paint_profile_times[paint_profile_times_index_counter] = paint_profile_times[paint_profile_times_index_counter]
				+ (System.currentTimeMillis() - paint_profile_times[paint_profile_times_index_counter - 1]);
	}

	public synchronized void paint(Graphics g)
	{
        paint_profile_times_index_counter = 0;
		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_TEXT_GAME_DONE:
			case MENU_SCR_TEXT_LEVEL_DONE:
			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:

            	paint_profileStart(0,"BG");
            Level.paintSpaceBackground(g);
            	paint_profileEnd();
            	paint_profileStart(1,"TL");
			Level.paintTiles(g);
				paint_profileEnd();
			
            	paint_profileStart(2,"SP");
			paintSprite( g, player );
			
			for (int i=0; i < Level.enemySprites.size(); i++)
			{
				GameSprite enemy = (GameSprite) Level.enemySprites.elementAt(i);
				if (enemy.visible)
					paintSprite( g, enemy );
			}
			
			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));

				paint_profileEnd();

            	paint_profileStart(3,"ST");
			paintStatusBars(g);
				paint_profileEnd();
			
            if (GameCanvas.PROFILE_ON_SCREEN) {
				for (int p = 0; p < PAINT_PROFILE_TIMES_MAX; p++) {
					g.setColor(0x000000);
					g.drawString(paint_profile_labels[p] + " "
							+ paint_profile_times[p * 2 + 1], 2, 20 + 20 * p,
							Graphics.LEFT | Graphics.TOP);
					g.setColor(0xFFFFFF);
					g.drawString(paint_profile_labels[p] + " "
							+ paint_profile_times[p * 2 + 1], 2 - 1,
							20 + 20 * p - 1, Graphics.LEFT | Graphics.TOP);
				}
			}

				
			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;
		case KEY_STAR:
			PROFILE_ON_SCREEN = !PROFILE_ON_SCREEN;
			break;
		case KEY_NUM1:
			command_up = true;
			command_left = true;
			break;
		case KEY_NUM3:
			command_up = true;
			command_right = true;
			break;
		case KEY_NUM7:
			command_down = true;
			command_left = true;
			break;
		case KEY_NUM9:
			command_down = true;
			command_right = true;
			break;
		case KEY_NUM0:
			if (player_nuke_counter == 0 && player_nukes > 0)
			{
				player_nukes--;
				player_nuke_counter = PLAYER_NUKE_COUNTER_MAX;
			}
			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(key)
		{
		case KEY_NUM1:
			command_up = false;
			command_left = false;
			break;
		case KEY_NUM3:
			command_up = false;
			command_right = false;
			break;
		case KEY_NUM7:
			command_down = false;
			command_left = false;
			break;
		case KEY_NUM9:
			command_down = false;
			command_right = false;
			break;
		}

		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);
			}
			
			String option_text = menuScreenMainOptions[i];
			
			if (i == MENU_OPTION_MAIN_MUSIC_ON_OFF)
			{
				option_text = option_text + (musicOn ? TEXT_ON : TEXT_OFF);
			}
			g.drawString(option_text, 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 || screen == MENU_SCR_TEXT_LEVEL_DONE || screen == MENU_SCR_TEXT_GAME_DONE)
		{
			// 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) >> GameSprite.SHIFT_FACTOR;
		int dy = (spr.y - Level.tiles_scroll_y) >> GameSprite.SHIFT_FACTOR;
		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);
		
		// paint number of nukes
		for (int i=0; i < PLAYER_NUKES_MAX; i++)
		{
			g.setColor(0xFFFF00);
			int x = canvasWidth - (3-i) * (5+3);
			int y = canvasHeight - 8;
			if ((3-i) <= player_nukes)
			{
				g.fillRect(x,y,5,5);
			}
			g.setColor(0xFFFFFF);
			g.drawRect(x,y,5,5);
		}

		if (PROFILE_ON_SCREEN)
		{
			// paint the FPS
			g.setColor(0xFFFFFF);
			g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_SMALL));
			g.drawString("FPS: "+FPS, 1, canvasHeight-2, Graphics.BOTTOM | Graphics.LEFT);
		}
	}

	public void startGame(byte load_level_no)
	{
		player_gameover_explosion_counter = -1;
		player_nuke_counter = 0;
		
		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) << GameSprite.SHIFT_FACTOR;
		Level.tiles_scroll_y = (Level.LEVEL_HEIGHT * Level.TILE_SIZE - canvasHeight - 1) << GameSprite.SHIFT_FACTOR;
	
		Level.loadLevel( load_level_no );

		// switch to game mode
		mode = MODE_GAME;
	}
	
	public static final byte GAME_END_NONE = 0; 
	public static final byte GAME_END_LOSE = 1; 
	public static final byte GAME_END_WIN = 2; 
	
	public void checkEndGame()
	{
		byte game_end = GAME_END_NONE;
		// check for game end
		if ( (player.energy <= 0) && (player_gameover_explosion_counter == 0) )
		{
			game_end = GAME_END_LOSE;
		} else if ( (Level.boss.energy <= 0) && (boss_dead_explosion_counter == 0) )
		{
			game_end = GAME_END_WIN;
		}
		if (game_end > GAME_END_NONE)
		{
			// back to menu mode, set the game or lever over text screen
			mode = MODE_MENU;
			if (game_end == GAME_END_LOSE)
			{
				menuScreen = MENU_SCR_TEXT_GAME_OVER;
			} else {
				if (Level.levelno == Level.MAX_LEVELS)
					menuScreen = MENU_SCR_TEXT_GAME_DONE;
				else
					menuScreen = MENU_SCR_TEXT_LEVEL_DONE;
			}
	
			// 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 << GameSprite.SHIFT_FACTOR)) )
			{
				// 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 << GameSprite.SHIFT_FACTOR) )
				{
					enemy.visible = true;
					doEnemyAI( enemy );
					i--;
				} else {
					enemy.visible = false;
					// 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_ENEMY4_SHOOTING:
			
			shootEnemyFireTowardPlayer(sprite, Level.SPRITE_ID_FIRE3, (byte) 2);

			break;
		case Level.SPRITE_ID_ENEMY3:
		case Level.SPRITE_ID_ENEMY3_SINUS:
		case Level.SPRITE_ID_ENEMY3_SINUS_SHOOTING:

			// animate sprite to next sprite frame every 3 frames
			if (global_frame_counter % 3 == 0)
				sprite.frameId = (byte) ( (sprite.frameId + 1) % (sprite.frameIdMax+1));
			
			// sinusoidal movement
			if (sprite.spriteId == Level.SPRITE_ID_ENEMY3_SINUS || sprite.spriteId == Level.SPRITE_ID_ENEMY3_SINUS_SHOOTING)
			{
				int tx = Math.abs( (sprite.y >> 1) % 360);
				sprite.vx = sin[tx] >> 4;

//				int ty = Math.abs(global_frame_counter % 360);
//				sprite.vy = - sin[ty] >> 5;
			}
			if (sprite.spriteId == Level.SPRITE_ID_ENEMY3_SINUS)
			{
				shootEnemyFireTowardPlayer(sprite, Level.SPRITE_ID_FIRE3, (byte) 2);
			}
			
			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) << GameSprite.SHIFT_FACTOR);
				new_fire.y = sprite.y + (sprite.h << GameSprite.SHIFT_FACTOR);
				new_fire.vx = 0;
				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 << GameSprite.SHIFT_FACTOR);
				new_fire.y = sprite.y + (sprite.h << GameSprite.SHIFT_FACTOR);
				new_fire.vx = 0;
				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) << GameSprite.SHIFT_FACTOR);
				new_fire.y = sprite.y + (sprite.h << GameSprite.SHIFT_FACTOR);
				new_fire.vx = 0;
				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 << GameSprite.SHIFT_FACTOR) > (((Level.LEVEL_WIDTH * Level.TILE_SIZE) - (Level.TILE_SIZE/2)) << GameSprite.SHIFT_FACTOR) )
				sprite.vx = -(1 << GameSprite.SHIFT_FACTOR);
			if (sprite.x < (Level.TILE_SIZE/2 << GameSprite.SHIFT_FACTOR) )
				sprite.vx = 1 << GameSprite.SHIFT_FACTOR;
			
			break;

		case Level.SPRITE_ID_BOSS2:
			// boss move
			if (sprite.x + (sprite.w << GameSprite.SHIFT_FACTOR) > (((Level.LEVEL_WIDTH * Level.TILE_SIZE) - (Level.TILE_SIZE/2)) << GameSprite.SHIFT_FACTOR) )
				sprite.vx = -(1 << GameSprite.SHIFT_FACTOR);
			if (sprite.x < (Level.TILE_SIZE/2 << GameSprite.SHIFT_FACTOR) )
				sprite.vx = 1 << GameSprite.SHIFT_FACTOR;

			int ty = Math.abs(sprite.x % 360);
			sprite.vy = - sin[ty] >> 5;
			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 << GameSprite.SHIFT_FACTOR);
			new_fire.y = sprite.y + (sprite.h << GameSprite.SHIFT_FACTOR);
			new_fire.vx = 0;
			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 GameSprite shootEnemyFireTowardPlayer(GameSprite sprite, byte fire_sprite_id, byte fire_sprite_frame_id)
	{
		GameSprite new_fire = shootEnemyFire(sprite, fire_sprite_id, fire_sprite_frame_id);
		if (new_fire != null)
		{
			int pdx = player.x - new_fire.x;
			int pdy = player.y - new_fire.y;
			
			// we dont have a tangens function (yet!), so for now this will do...
			int approx_dist = Math.abs(pdx) + Math.abs(pdy);
			int approx_velocity_step = approx_dist / 40;
			new_fire.vx = pdx / approx_velocity_step;
			new_fire.vy = pdy / approx_velocity_step;
		}
		
		return new_fire;
	}
	
	public void updatePlayerSprite()
	{
		//  nuke
		if (player_nuke_counter == PLAYER_NUKE_COUNTER_MAX)
		{
			// activate nuke, destroy all visible enemy sprites
			int i = Level.enemySprites.size() - 1;
			while (i >= 0 )
			{
				GameSprite enemy = (GameSprite) Level.enemySprites.elementAt(i);
				
				if ( enemy.visible )
				{
					System.out.println("destroying opponent: "+i+" / "+Level.enemySprites.size());
					enemy.energy = 0;
					addExplosion(enemy);
				}
				i--;
			}
		}
		if (player_nuke_counter > 0)
		{
			player_nuke_counter--;
		}
		
		// move player with level scroll
		player.y += Level.tiles_scroll_vy;

		if (player_gameover_explosion_counter > 0)
		{
			player_gameover_explosion_counter--;
		}
		else
		{
			if (player.energy <= 0)
			{
				player_gameover_explosion_counter = PLAYER_GAMEOVER_EXPLOSION_COUNTER_MAX;
			}

			// 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) << GameSprite.SHIFT_FACTOR))
					player.y = Level.tiles_scroll_y + ((canvasHeight - player.h) << GameSprite.SHIFT_FACTOR);
			}
			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) << GameSprite.SHIFT_FACTOR))
					player.x = Level.tiles_scroll_x + ((canvasWidth - player.w) << GameSprite.SHIFT_FACTOR);
				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) << GameSprite.SHIFT_FACTOR);
					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);
			
			if ( enemy.visible )
			{
				// check player <-> enemy collision
				if (player.collidesWith(enemy))
				{
					// add explosion
					addExplosion(enemy);
					addExplosion(player);
	
					// player loses energy
					if ( !god_mode )
						player.energy -= enemy.damage;
					
					// 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 || enemy.spriteId == Level.SPRITE_ID_BOSS2)
					{
						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;
					System.out.println("player.energy "+ player.energy);
				}
				
				// 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 << GameSprite.SHIFT_FACTOR)) < 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.x += fire.vx;
			fire.y += fire.vy;
			
			// destroy enemy fire when out of screen
			if ( fire.y > Level.tiles_scroll_y + (canvasHeight << GameSprite.SHIFT_FACTOR))
			{
				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 << GameSprite.SHIFT_FACTOR);
			new_exp.y += random.nextInt() % (player.h << GameSprite.SHIFT_FACTOR);
		}
		// 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 << GameSprite.SHIFT_FACTOR);
			new_exp.y += random.nextInt() % (Level.boss.h << GameSprite.SHIFT_FACTOR);
		}
	}

	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_BOSS2] = Image.createImage("/boss2.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_ENEMY4] = Image.createImage("/e4.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) << GameSprite.SHIFT_FACTOR);
		exp.y = exp_spr.y + ((exp_spr.h/2 - exp.h/2) << GameSprite.SHIFT_FACTOR);
		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;
			stopMusic();
		}
	}

	public void showNotify()
	{
		if (musicOn)
			playMusic();
	}


    // 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;
	}

	public static final short SIN_FACTOR = 1024;
	public static final short[] sin = {
	0, 18, 36, 54, 71, 89, 107, 125, 143, 160, 
	178, 195, 213, 230, 248, 265, 282, 299, 316, 333, 
	350, 367, 384, 400, 416, 433, 449, 465, 481, 496, 
	512, 527, 543, 558, 573, 587, 602, 616, 630, 644, 
	658, 672, 685, 698, 711, 724, 737, 749, 761, 773, 
	784, 796, 807, 818, 828, 839, 849, 859, 868, 878, 
	887, 896, 904, 912, 920, 928, 935, 943, 949, 956, 
	962, 968, 974, 979, 984, 989, 994, 998, 1002, 1005, 
	1008, 1011, 1014, 1016, 1018, 1020, 1022, 1023, 1023, 1024, 
	1024, 1024, 1023, 1023, 1022, 1020, 1018, 1016, 1014, 1011, 
	1008, 1005, 1002, 998, 994, 989, 984, 979, 974, 968, 
	962, 956, 949, 943, 935, 928, 920, 912, 904, 896, 
	887, 878, 868, 859, 849, 839, 828, 818, 807, 796, 
	784, 773, 761, 749, 737, 724, 711, 698, 685, 672, 
	658, 644, 630, 616, 602, 587, 573, 558, 543, 527, 
	512, 496, 481, 465, 449, 433, 416, 400, 384, 367, 
	350, 333, 316, 299, 282, 265, 248, 230, 213, 195, 
	178, 160, 143, 125, 107, 89, 71, 54, 36, 18, 
	0, -18, -36, -54, -71, -89, -107, -125, -143, -160, 
	-178, -195, -213, -230, -248, -265, -282, -299, -316, -333, 
	-350, -367, -384, -400, -416, -433, -449, -465, -481, -496, 
	-512, -527, -543, -558, -573, -587, -602, -616, -630, -644, 
	-658, -672, -685, -698, -711, -724, -737, -749, -761, -773, 
	-784, -796, -807, -818, -828, -839, -849, -859, -868, -878, 
	-887, -896, -904, -912, -920, -928, -935, -943, -949, -956, 
	-962, -968, -974, -979, -984, -989, -994, -998, -1002, -1005, 
	-1008, -1011, -1014, -1016, -1018, -1020, -1022, -1023, -1023, -1024, 
	-1024, -1024, -1023, -1023, -1022, -1020, -1018, -1016, -1014, -1011, 
	-1008, -1005, -1002, -998, -994, -989, -984, -979, -974, -968, 
	-962, -956, -949, -943, -935, -928, -920, -912, -904, -896, 
	-887, -878, -868, -859, -849, -839, -828, -818, -807, -796, 
	-784, -773, -761, -749, -737, -724, -711, -698, -685, -672, 
	-658, -644, -630, -616, -602, -587, -573, -558, -543, -527, 
	-512, -496, -481, -465, -449, -433, -416, -400, -384, -367, 
	-350, -333, -316, -299, -282, -265, -248, -230, -213, -195,
	-178, -160, -143, -125, -107, -89, -71, -54, -36, -18,
	};


	public static boolean musicOn = true;
	public static javax.microedition.media.Player musicPlayer;

	public static void loadMusic()
	{
		InputStream inputstream;

		try 
		{
			inputstream = new Object().getClass().getResourceAsStream("/music.mid");
			musicPlayer = Manager.createPlayer(inputstream, "audio/midi");
			musicPlayer.realize();
			musicPlayer.setLoopCount(-1);
		} 
		catch (Exception e) {
		}
	}

	public static void playMusic()
	{
		try 
		{
			VolumeControl volume = (VolumeControl) musicPlayer
					.getControl("javax.microedition.media.control.VolumeControl");
			volume.setLevel(20);

			musicPlayer.realize();
			musicPlayer.start();
		} 
		catch (Exception e) {
		}
	}

	public static void stopMusic() {
		try 
		{
			if (musicPlayer != null) {
				musicPlayer.stop();
				musicPlayer.setMediaTime(-1);
			}
		} 
		catch (Exception e) {
		}

	}

}
