I have written Space Invaders before a long time ago and in a different language.
It is a classic and I have a lot of memories playing this.

It took a bit more time than I wanted to spend on a game but I wanted to do it justice.

A few things missing which I may revisit in the future but overall I’m pleased.

I spent some of that extra time working on improved sound generation. Hopefully now there is no or almost no delay before sound is played.

Note : Up till now all my games have been playable on these pages inside an iframe element. But with invasion I had to make a work around to be able to capture the keyboard. If you find you cant control the game hover the mouse over it.

<html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">

	<title>Invasion</title>
	<style>
Body
{
	background-color: #181818;
	color: #ffffff;
	margin: 0px;
}

Canvas
{
	position:fixed;
}

#counter
{
	position:absolute;
	bottom:0;
	right:0;
	color:#ffffff;
	display:none;
}
	</style>
</head>
<body>
	<input id="focus" type="text" style="position:fixed; width:0px;height=0px;" />
	<canvas id="myCanvas">Sorry... your browser does not support the canvas element.</canvas>
<p id="counter">counter</p>

</body>
<script>

// Global variables
// used for calculating and showing the frames per second
var dwFrames=0;
var dwCurrentTime=0;
var dwLastUpdateTime=0;
var dwElapsedTime=0;
var fpsText;
// The game engine needs to persist
var GE;

var LAYER_ENEMY = 1;
var LAYER_BOMB = 2;
var LAYER_PLAYER = 3;
var LAYER_SHOT = 4;
var LAYER_UFO = 5;
var LAYER_SHIELD = 6;
var LAYER_EXPLOSION = 7;
var LAYER_MENU = 8;

var SOUND_SHOT = 0;
var SOUND_ENEMY_EXPLODE = 1;
var SOUND_UFO = 2
var SOUND_UFO_HIT = 3
var SOUND_PLAYER_HIT = 4
var SOUND_ENEMY_MOVE = 5;
var SOUND_STATIC = 6;

var skipSplash = false;

var currentStage;
var soundButton;

init = function()
{
	var canvas = document.getElementById('myCanvas');
	if (canvas.getContext)
	{
		// got to have the game engine
		GE = GameEngine16Colour(canvas,Update,Draw);
		GE.CustomResolution(224,256);
		if ( skipSplash )
			currentStage = GameInitBackground();
		else
			currentStage = Splash(GameData());

		soundButton = Sprite(GE.CanvasWidth-20, 20,CreateSoundButton());
		soundButton.SetAnimator([0,1]);
		soundButton.SetAnimatorStop();
		GE.SpriteAdd("SoundButton",soundButton,LAYER_MENU);

		// when the window resizes we want to resize the canvas
		window.onresize = function() { doResize(); };
		window.onmouseover = function () { getFocus(); };

		doResize();
	}
}

function getFocus()
{
	var f = document.getElementById("focus");
	f.focus();
}

// This gets called every time the engine redraws the canvas
// For slow machines this may be less that the actual fps
Draw = function()
{
	currentStage.Draw();

	// calculate and show the fps
	dwFrames++;
	dwCurrentTime = (new Date).getTime();
	dwElapsedTime = dwCurrentTime - dwLastUpdateTime;
	if(dwElapsedTime >= 1000)
	{
		fpsText = "FPS " + dwFrames;
		dwLastUpdateTime = dwCurrentTime;
		dwFrames = 0;
	}
	var l = document.getElementById('counter');
	l.textContent = fpsText;
}

// This gets called every game update frame
Update = function()
{
	HandleSoundButton();
	currentStage = currentStage.Update();
}

function HandleSoundButton()
{
	if ( GE.mouseClicked
		&& GE.mousePos.x >= soundButton.pos.x
		&& GE.mousePos.x <= soundButton.pos.x + soundButton.bitmap.width
		&& GE.mousePos.y >= soundButton.pos.y
		&& GE.mousePos.y <= soundButton.pos.y + soundButton.bitmap.height)
	{
		soundOn = !soundOn;
		// reset the clicked flag so that it does not get picked up
		// by the game
		GE.mouseClicked = false;
	}

	if ( soundOn )
		soundButton.SetAnimatorFrame(0);
	else
		soundButton.SetAnimatorFrame(1);
}

// This just makes sure the canvas is as big as possible
// given the window size and centered
function doResize()
{
	var canvas = document.getElementById('myCanvas');
	var screenHeight = window.innerHeight;
	var screenWidth = window.innerWidth;

	var ratioX = GE.CanvasWidth / screenWidth;
	var ratioY = GE.CanvasHeight / screenHeight;

	var ratio = ratioX
	if ( ratioX < ratioY )
		ratio = ratioY;

	var width = GE.CanvasWidth / ratio;
	var height = GE.CanvasHeight / ratio;

	canvas.style.height = height+"px";
	canvas.style.width = width+"px";
	canvas.style.marginLeft = (screenWidth-width)/2;
	canvas.style.marginTop = (screenHeight-height)/2;

}

// Splash screen stages
function GameData()
{
	var obj = {};
	obj.score = 0;
	obj.hiscore = 0;
	obj.soundState = false;

	return obj;
}

function SplashData()
{
	var obj = {};
	obj.finished = false;

	return obj;
}

function PlayData()
{
	var obj = {};
	obj.finished = false;
	obj.score = 0;
	obj.lives = 3;
	obj.shields=null;
	obj.enemyWaveData;
	obj.playerData;
	obj.enemiesAlive=0;
	obj.level = 1;
	return obj;
}

function PlayerData()
{
	var obj = {};
	obj.finished = false;
	obj.lives = 3;
	obj.exploding = false;
	obj.shotFired = false;

	return obj;
}

function EnemyWaveData()
{
	var obj = {};
	obj.finished = false;
	obj.pause = false;
	obj.level = 0;
	obj.enemies;
	obj.enemyDirection = 1;
	obj.drop = false;
	obj.bombs = null;
	obj.UFO = null;
	obj.UFOScore = null;
	obj.activeEnemy = null;
	obj.scoreValue = 0;
	obj.soundMoveIndex=0;
	obj.playerExploding = true;
	obj.playerShotFired=0;
	obj.landed = false;

	return obj;
}

function Splash(data)
{
	var data = GameData();

	var splashData = SplashData();
	var obj = GameSatePattern(splashData);

	obj.AddInnerState( SplashStatic(splashData) );
	obj.SetNextState(GameInitBackground(data));

	obj.UpdateCustom = function()
	{
		if (splashData.finished)
			obj.SetFinished();
	}

	return obj;
}

// the following are inner states for the splash
function SplashStatic(data)
{
	var obj = GameSatePattern(data);
	var countFrames = 0;
	var soundPlaying = false;

	obj.Init = function()
	{
		soundStatic = new customSound(24000);
		soundStatic.add(2.5, 0, 0, 100, 100, 200,1,false );
		soundStatic.createSound(SOUND_STATIC);
	}

	obj.UpdateCustom = function()
	{
		if ( !soundPlaying )
			// wait till sound is ready to play
			if ( channels[SOUND_STATIC].readyState < 1 )
				return;
			else
			{
				soundPlaying = true;
				soundPlay(SOUND_STATIC);
			}
		countFrames++;

		if ( countFrames > 30*2 )
		{
			obj.SetFinished();
			soundStop(SOUND_STATIC);
		}
	}

	obj.DrawCustom=function()
	{
		for ( var y = 0; y < GE.CanvasHeight; y++)
		{
			for ( var x = 0; x < GE.CanvasWidth; x++ )
			{
				GE.DrawPixel(x,y,Math.floor(Math.random()*16));
			}
		}
	}

	obj.SetNextState(SplashLogo(data));

	return obj;
}

function SplashLogo(data)
{
	var obj = GameSatePattern(data);
	var countFrames = 0;

	obj.Init = function ()
	{
		data.finished = false;
		GE.DrawRect(0,0,GE.CanvasWidth, GE.CanvasHeight,0);
		GE.WriteStringCentered("QuietBloke Productions",GE.CanvasHeight/2-32-8,15,false,true);
		GE.WriteStringCentered("Presents",GE.CanvasHeight/2,15);
		GE.WriteStringCentered("Invasion",GE.CanvasHeight/2+32,15,false,true);

		GE.WriteStringCentered("You all know this one",GE.CanvasHeight/2+50,9,false,false);
	}

	obj.UpdateCustom=function()
	{
		countFrames++;

		if ( countFrames > 30*2 )
		{
			data.finished = true;
		}
	}

	return obj;
}

function GameInitBackground(data)
{
	var obj = GameSatePattern(data);

	obj.Init = function ()
	{
		if (!data)
			data = GameData();

		GE.DrawRect(0,0,GE.CanvasWidth,GE.CanvasHeight,0);
		GE.DrawLine(0,GE.CanvasHeight-11,GE.CanvasWidth,GE.CanvasHeight-11,15);

		obj.SetNextState(MainMenu(data));
		obj.SetFinished();
	}

	return obj;
}

function MainMenu(data)
{
	var obj = GameSatePattern(data);

	obj.Init = function ()
	{
		var menuBitmap = Bitmap(210,20);
		var menuBitmap2 = Bitmap(180,120);
		var menuBitmap3 = Bitmap(180,16);
		var menuBitmap3Blank = menuBitmap3.Clone();

		menuBitmap.WriteStringCentered("Invasion",GE.CharSet(),4,15,true,true);

		menuBitmap2.WriteString(" = 10 PTS",GE.CharSet(),70,0,15);
		menuBitmap2.Paste(50,0,CreateEnemyCostume2().GetItem(0));
		menuBitmap2.WriteString(" = 20 PTS",GE.CharSet(),70,15,15);
		menuBitmap2.Paste(50,15,CreateEnemyCostume3().GetItem(0));
		menuBitmap2.WriteString(" = 40 PTS",GE.CharSet(),70,30,15);
		menuBitmap2.Paste(50,30,CreateEnemyCostume1().GetItem(0));
		menuBitmap2.WriteString(" = ??? PTS",GE.CharSet(),70,45,15);
		menuBitmap2.Paste(50-2,45,CreateUFOCostume().GetItem(0));
		menuBitmap2.WriteString("Controls",GE.CharSet(),66,70,15);
		menuBitmap2.WriteString("--------",GE.CharSet(),66,80,15);
		menuBitmap2.WriteString("Cursor keys left/right",GE.CharSet(),8,100,15);
		menuBitmap2.WriteString("Space key to shoot",GE.CharSet(),16,110,15);

		menuBitmap3.WriteStringCentered("Press shoot to begin",GE.CharSet(),0,15,false,true);

		var sp = Sprite((GE.CanvasWidth-menuBitmap.width)/2, 20, menuBitmap );
		GE.SpriteAdd("MainMenu", sp, LAYER_MENU);

		sp = Sprite((GE.CanvasWidth-menuBitmap2.width)/2, 65, menuBitmap2 );
		GE.SpriteAdd("MainMenu2", sp, LAYER_MENU);

		var cs = Costume();
		cs.SetItem(0,menuBitmap3);
		cs.SetItem(1,menuBitmap3Blank);
		sp = Sprite((GE.CanvasWidth-menuBitmap3.width)/2, GE.CanvasHeight-48, cs );
		sp.SetAnimatorLoop();
		sp.SetAnimatorUpdateRate(.1);
		GE.SpriteAdd("MainMenu3", sp, LAYER_MENU);

	}

	obj.UpdateCustom=function()
	{
		if ( FirePressed() )
			obj.SetFinished();
			obj.SetNextState(Play(data));
	}

	obj.TearDown = function()
	{
		GE.SpriteDelete("MainMenu");
		GE.SpriteDelete("MainMenu2");
		GE.SpriteDelete("MainMenu3");
	}

	return obj;
}

function Play(data)
{
	var obj = GameSatePattern(data);
	var playData = PlayData();

	obj.AddInnerState(PlayInit(playData));
	obj.Init=function()
	{
		data.score = 0;
		DisplayScore(data);
		DisplayLives(playData);
	}

	obj.UpdateCustom = function()
	{
		if ( playData.finished )
			obj.SetFinished();
		obj.SetNextState(MainMenu(data));

		if ( playData.score != data.score )
		{
			data.score = playData.score;
			if ( data.score > data.hiscore )
				data.hiscore = data.score;
			DisplayScore(data);
		}
		DisplayLives(playData);
	}

	return obj;
}

// the following are inner states to the play state

function PlayInit(data)
{
	var obj = GameSatePattern(data);

	obj.Init=function()
	{
		data.score = 0;
		data.level = 1;
		data.playerData = PlayerData();

		// set up layer collisions
		GE.CollisionRuleAdd(LAYER_SHOT,LAYER_SHIELD);
		GE.CollisionRuleAdd(LAYER_BOMB,LAYER_SHIELD);

		GE.CollisionRuleAdd(LAYER_PLAYER,LAYER_BOMB);

		GE.CollisionRuleAdd(LAYER_SHOT,LAYER_BOMB);
		GE.CollisionRuleAdd(LAYER_ENEMY,LAYER_SHOT);
		GE.CollisionRuleAdd(LAYER_SHOT,LAYER_ENEMY);
		GE.CollisionRuleAdd(LAYER_SHOT,LAYER_UFO);

		obj.SetFinished();
		obj.SetNextState(PlayInitLevel(data));
	}

	return obj;
}

function PlayInitLevel(data)
{
	var obj = GameSatePattern(data);

	data.enemyWaveData = EnemyWaveData();
	data.enemyWaveData.level = data.level;
	obj.AddInnerState(EnemyWaveInit(data.enemyWaveData));
	obj.AddInnerState(PlayerInit(data.playerData));

	obj.Init=function()
	{
		GE.DeleteSpritesInLayer(LAYER_SHIELD);
		if ( data.level < 8 )
			data.shields = CreateShields();
		obj.SetNextState(PlayLevel(data));
	}

	obj.UpdateCustom=function()
	{
		obj.SetFinished();
	}

	function CreateShields()
	{
		var shields = new Array();
		GE.DeleteSpritesInLayer(LAYER_SHIELD);

		var spacing = Math.floor((GE.CanvasWidth-20)/5);
		for ( var x = 0; x < 4; x++)
		{
			var sp = Sprite(x*spacing+spacing, GE.CanvasHeight-48, CreateShieldCostume());
			GE.SpriteAdd("Shield:"+x,sp,LAYER_SHIELD);
			shields.push(sp);
		}

		return shields;
	}

	return obj;
}

function PlayLevel(data)
{
	var obj = GameSatePattern(data);
	var player;
	var shot;
	var shotCount;
	var UFOPoints = [50,150,250,350,450,550,650,750]

	obj.AddInnerState(EnemyWavePlay(data.enemyWaveData));
	obj.AddInnerState(PlayerWait(data.playerData));

	obj.Init = function()
	{
		shot = GE.SpriteGet("Shot");
		shotCount = 0;

		if ( GE.Tiltable())
			GE.TiltCalibrate();
	}

	obj.UpdateCustom=function()
	{
		data.lives = data.playerData.lives;
		data.score += data.enemyWaveData.scoreValue;
		data.enemyWaveData.playerExploding = data.playerData.exploding;
		data.enemyWaveData.playerShotFired = data.playerData.shotFired;

		if ( data.enemyWaveData.landed == true )
		{
			obj.SetFinished();
			obj.SetNextState(PlayGameOver(data));
		}

		if ( data.enemyWaveData.enemiesAlive == 0 )
		{
			obj.SetNextState(PlayLevelCleared(data));
			obj.SetFinished();
		}

		if ( data.playerData.finished )
		{
			obj.SetFinished();
			obj.SetNextState(PlayGameOver(data));
		}

		var bombs = data.enemyWaveData.bombs;
		for( var bombIndex in bombs)
		{
			if ( bombs[bombIndex].collided )
			{
				var bomb = bombs[bombIndex];
				for ( var shieldindex in data.shields )
				{
					var shield = data.shields[shieldindex];
					if ( shield.collided )
					{
						var hitx = bomb.pos.x - shield.pos.x;
						var hity = bomb.pos.y + bomb.bitmap.height - shield.pos.y;
						var shieldbitmap = shield.bitmap;
						DrawShieldDamage(shieldbitmap, hitx, hity-2);
					}
				}
			}
		}

		for ( var shieldindex in data.shields )
		{
			var shield = data.shields[shieldindex];
			if ( shield.collided )
			{
				var hitx = shot.pos.x - shield.pos.x;
				var hity = shot.pos.y - shield.pos.y;
				var shieldbitmap = shield.bitmap;
				DrawShieldDamage(shieldbitmap, hitx, hity);
			}
		}

		var activeEnemy = data.enemyWaveData.activeEnemy;
		if ( activeEnemy )
		{
			for ( var shieldindex in data.shields )
			{
				var shield = data.shields[shieldindex];
				var hitx = activeEnemy.pos.x - shield.pos.x;
				var hity = activeEnemy.pos.y - shield.pos.y; 

				shield.bitmap.DrawRect(hitx-1,hity,15,10,-1);
			}
		}

		// last thing is reset the collision flag on the shields
		for ( var shieldindex in data.shields )
		{
			var shield = data.shields[shieldindex];
			shield.collided = false;
		}
	}

	obj.DrawCustom=function()
	{
		if ( GE.Tiltable() )
		{
			GE.DrawRect(10,32,GE.CanvasWidth,20,0);
			GE.WriteString("XTilt:"+Math.floor(GE.GetTiltX()*100)/100,10,32,15);
		}
	}

	function DrawShieldDamage(shield,hitx,hity)
	{
		shield.DrawRect(hitx-1,hity,4,4,-1);
		shield.SetPixel(hitx-2,hity+1,-1);
		shield.SetPixel(hitx-2,hity+3,-1);
		shield.SetPixel(hitx+3,hity,-1);
		shield.SetPixel(hitx+3,hity+2,-1);

		shield.SetPixel(hitx,hity-1,-1);
		shield.SetPixel(hitx+2,hity-1,-1);
		shield.SetPixel(hitx+1,hity+4,-1);
		shield.SetPixel(hitx-1,hity+4,-1);
	}

	return obj;
}

function PlayLevelCleared(data)
{
	var obj = GameSatePattern(data);
	var countFrames = 0;

	obj.Init = function ()
	{
		var cs=Costume()
		var menuBitmap = Bitmap(8*18,16);
		menuBitmap.DrawRect(0,0,8*18,16,0);

		var blankBitmap = menuBitmap.Clone();

		menuBitmap.WriteString("Level "+data.level + " Cleared",GE.CharSet(),4,4,15);
		cs.SetItem(0,menuBitmap);
		cs.SetItem(1,blankBitmap);
		var sp = Sprite((GE.CanvasWidth-menuBitmap.width)/2, 32, cs );
		sp.SetAnimatorLoop();
		sp.SetAnimatorUpdateRate(.1);
		GE.SpriteAdd("LevelCleared", sp, LAYER_MENU);

		data.level++;
		obj.SetNextState(PlayInitLevel(data));
	}

	obj.UpdateCustom=function()
	{
		countFrames++;

		if ( countFrames > 30*3 )
		{
			obj.SetFinished();
		}
	}

	obj.TearDown = function()
	{
		GE.SpriteDelete("LevelCleared");
	}

	return obj;
}

function PlayGameOver(data)
{
	var obj = GameSatePattern(data);
	var countFrames = 0;

	obj.Init = function ()
	{
		var cs=Costume()
		var menuBitmap = Bitmap(8*10,16);
		menuBitmap.DrawRect(0,0,8*10,16,0);

		var blankBitmap = menuBitmap.Clone();

		menuBitmap.WriteString("GAME OVER",GE.CharSet(),4,4,15);
		cs.SetItem(0,menuBitmap);
		cs.SetItem(1,blankBitmap);
		var sp = Sprite((GE.CanvasWidth-menuBitmap.width)/2, 32, cs );
		sp.SetAnimatorLoop();
		sp.SetAnimatorUpdateRate(.1);
		GE.SpriteAdd("GameOver", sp, LAYER_MENU);
	}

	obj.UpdateCustom=function()
	{
		countFrames++;

		if ( countFrames > 30*3 )
		{
			obj.SetFinished();
			data.finished = true;
		}
	}

	obj.TearDown = function()
	{
		GE.SpriteDelete("GameOver");
		GE.DeleteSpritesInLayer(LAYER_ENEMY);
		GE.DeleteSpritesInLayer(LAYER_SHIELD);
		GE.DeleteSpritesInLayer(LAYER_PLAYER);
		GE.DeleteSpritesInLayer(LAYER_BOMB);
		GE.DeleteSpritesInLayer(LAYER_SHOT);
		GE.DeleteSpritesInLayer(LAYER_UFO);

		// ufo plays in a loop so we must stop it here
		soundStop(SOUND_UFO);
	}

	return obj;
}

function PlayerInit(data)
{
	var obj = GameSatePattern(data);

	obj.Init=function()
	{
		InitPlayerSprites();
		obj.SetNextState(PlayerWait(data));
		obj.SetFinished();

		sound = new customSound(24000);
		sound.add(.2, 900, 500, 50, 20, 0, 0,true );
		sound.createSound(SOUND_SHOT);

		sound = new customSound(24000);
		sound.add(1.6, 0, 0, 60, 60, 300,6,true );
		sound.createSound(SOUND_PLAYER_HIT);

	}

	return obj;
}

function PlayerWait(data)
{
	var obj = GameSatePattern(data);
	var player;
	var frameCount = 0;

	obj.Init=function()
	{
		frameCount = 0;
		player = GE.SpriteGet("Player");
		obj.SetNextState(PlayerPlay(data));

		player.pos.x = 16;
		var cs=Costume()
		var menuBitmap = Bitmap(8*10,16);
		menuBitmap.DrawRect(0,0,8*10,16,0);

		var blankBitmap = menuBitmap.Clone();

		menuBitmap.WriteString("GET READY",GE.CharSet(),4,4,15);
		cs.SetItem(0,menuBitmap);
		cs.SetItem(1,blankBitmap);
		var sp = Sprite((GE.CanvasWidth-menuBitmap.width)/2, 32, cs );
		sp.SetAnimatorLoop();
		sp.SetAnimatorUpdateRate(.1);
		GE.SpriteAdd("GetReady", sp, LAYER_MENU);
	}

	obj.TearDown = function()
	{
		GE.SpriteDelete("GetReady");
	}

	obj.UpdateCustom=function()
	{
		frameCount++;
		if ( frameCount > 60 )
		{
			obj.SetFinished();
		}
	}

	return obj;
}

function PlayerExplode(data)
{
	var obj = GameSatePattern(data);
	var player;
	var shot;
	var frameCount = 0;

	obj.Init=function()
	{
		frameCount = 0;
		player = GE.SpriteGet("Player");
		shot = GE.SpriteGet("Shot");
		obj.SetNextState(PlayerWait(data));
		player.SetAnimator([1,2]);
		player.SetAnimatorUpdateRate(.1);
		soundPlay(SOUND_PLAYER_HIT);

		data.exploding = true;
		data.lives--;
		if ( data.lives > 0 )
			obj.SetNextState(PlayerWait(data));
	}

	obj.TearDown = function()
	{
		player.SetAnimatorStop();
		player.SetAnimator([0]);
		player.SetAnimatorStop();
		player.costumeAnimator.Set(0);
		data.exploding = false;
	}

	obj.UpdateCustom=function()
	{
		frameCount++;
		if ( frameCount > 60 )
		{
			if ( data.lives < 1 )
			{
				data.finished = true;
				player.died = true;
				shot.died = true;
			}
			obj.SetFinished();
		}
		HandleShot();
	}

	function HandleShot()
	{
		if ( shot.pos.y < 8  || shot.collided)
		{
			shot.pos.x = -16;
			shot.pos.y = -16;
			shot.vel.y = 0;
			shot.collided = false;
		}
	}

	return obj;
}

function PlayerPlay(data)
{
	var obj = GameSatePattern(data);
	var player;
	var shot;

	obj.Init=function()
	{
		player = GE.SpriteGet("Player");
		shot = GE.SpriteGet("Shot");
	}

	obj.UpdateCustom=function()
	{
		HandlePlayer();

		HandleShot();
	}

	function HandlePlayer()
	{
		if ( player.collided )
		{
			obj.SetFinished();
				obj.SetNextState(PlayerExplode(data));
		}

		if ((( GE.Tiltable() && GE.GetTiltX() < -15) || GE.GetKeyState(GE.KEY_LEFT)) && player.pos.x >= 16 )
			player.pos.x-=1;

		if ((( GE.Tiltable() && GE.GetTiltX() > 15) || GE.GetKeyState(GE.KEY_RIGHT)) && player.pos.x <= GE.CanvasWidth-32 )
			player.pos.x+=1;
	}

	function HandleShot()
	{
		data.shotFired = false;
		if ( FirePressed() && shot.vel.y == 0 )
		{
			shot.pos.x = player.pos.x + 6;
			shot.pos.y = player.pos.y + 8;
			shot.vel.y = -4;
			data.shotFired = true;
			soundPlay(SOUND_SHOT);
		}

		if ( shot.vel.y == 0 )
		{
			shot.pos.x = player.pos.x + 6;
			shot.pos.y = GE.CanvasHeight;
		}
		if ( shot.pos.y < 8  || shot.collided)
		{
			shot.pos.x = -16;
			shot.pos.y = -16;
			shot.vel.y = 0;
			shot.collided = false;
		}
	}

	return obj;
}

function EnemyWaveInit(data)
{
	var obj = GameSatePattern(data);

	obj.Init=function()
	{
		data.enemies = InitEnemyWave(data.level);
		data.enemiesAlive = 11*6;

		sound = new customSound(24000);
		sound.add(.2, 3000, 1000, 80, 20, 0,0,true );
		sound.createSound(SOUND_ENEMY_EXPLODE);

		sound = new customSound(24000);
		sound.add(.1, 100, 90, 60, 60, 0,0,true );
		sound.createSound(SOUND_ENEMY_MOVE);

		sound = new customSound(24000);
		sound.add(.1, 110, 100, 60, 60, 0,0,true );
		sound.createSound(SOUND_ENEMY_MOVE+1);

		sound = new customSound(24000);
		sound.add(.1, 90, 80, 60, 60, 0,0,true );
		sound.createSound(SOUND_ENEMY_MOVE+2);

		sound = new customSound(24000);
		sound.add(.1, 105, 95, 60, 60, 0,0,true );
		sound.createSound(SOUND_ENEMY_MOVE+3);

		sound = new customSound(24000);
		sound.add(.1, 400, 1100, 50, 50, 0, 0,true );
		sound.add(.1, 1100, 400, 50, 50, 0, 0,true );
		sound.createSound(SOUND_UFO);

		sound = new customSound(24000);
		for ( var bell = 0; bell < 5; bell++)
			sound.add(.2, 1500, 1500, 50, 0, 0, 0,true );
		sound.createSound(SOUND_UFO_HIT);

		obj.SetNextState(EnemyWavePlay(data));
		obj.SetFinished();
	}

	return obj;
}

function EnemyWavePlay(data)
{
	var obj = GameSatePattern(data);

	var moverow = 5;
	var movecol = 11;
	var drop = false;
	var edgehit = false;
	var bombCounter = 0;
	var UFOWait = 15*30;
	var UFODirection = .75;

	var bombWait = 10;
	var shotCount;

	obj.Init=function()
	{
		// create all the bombs that will be used during play
		data.bombs = new Array();
		var bombCostume = CreateEnemyBombCostume();
		for ( var b = 0; b < 10; b++ )
		{
			var sp = Sprite ( -10,-10,bombCostume );
			data.bombs.push(sp);
			GE.SpriteAdd("bomb:"+b, sp, LAYER_BOMB );
		}
		shotCount = 0;

		var sp = Sprite ( -10,-10,CreateUFOCostume() );
		sp.SetAnimatorLoop();
		sp.SetAnimatorUpdateRate(5);
		data.UFO = sp;
		GE.SpriteAdd("UFO"+b, sp, LAYER_UFO );
		data.UFOScore = Sprite (100,16, Bitmap(3*8,8));
	}

	obj.UpdateCustom=function()
	{
		// find next invader to move
		var found = false;
		var prevrow = moverow;
		var prevcol = movecol;

		data.scoreValue = 0;

		if ( data.enemiesAlive == 0 )
			return;

		if ( data.playerExploding )
		{
			HandleBombs(data.bombs);
			HandleEnemyCollisions();
			return;
		}

		if ( data.playerShotFired)
		{
			shotCount = (shotCount + 1 ) % 8;
		}

		while ( !found )
		{
			moverow++;
			if (moverow > 5 )
			{
				moverow = 0;
				movecol++;
				if ( movecol > 10 )
					movecol = 0;
			}
			if ( !data.enemies[moverow][movecol].died )
				found = true;
		}
		data.activeEnemy = data.enemies[moverow][movecol];
		// only update bomb wait if invader does not have an invader directly below
		if ( moverow == 5 || data.enemies[moverow+1][movecol].died)
			bombWait--;

		if ( data.UFO.vel.x == 0 )
		{
			UFOWait--;
			if ( UFOWait == 0 )
			{
				if ( UFODirection > 0 )
				{
					data.UFO.pos.x = -data.UFO.bitmap.width;
					data.UFO.SetAnimator([2,1,0]);
				}
				else
				{
					data.UFO.pos.x = GE.CanvasWidth;
					data.UFO.SetAnimator([0,1,2]);
				}
				soundPlay(SOUND_UFO,true);
				data.UFO.pos.y = 16;
				data.UFO.vel.x = UFODirection;
				UFOWait = 15*30;
				data.UFO.collided = false;
				UFODirection = -UFODirection;
			}
		}

		if ( data.UFO.collided )
		{
			var s = data.UFOScore;
			s.bitmap.DrawRect(0,0,s.bitmap.width, s.bitmap.height,-1);
			s.bitmap.WriteString(""+ (shotCount*100+50),GE.CharSet(),0,0,15);
			s.pos.x = data.UFO.pos.x;
			s.pos.y = data.UFO.pos.y;
			s.vel.x = 0;
			s.vel.y = 0;
			s.collided = false;
			s.died = false;
			s.SetLifeSpan(60);
			GE.SpriteAdd("UFOScore",s,LAYER_MENU);

			data.UFO.pos.y = 0;
			data.UFO.pos.x = GE.CanvasWidth;
			data.UFO.vel.x = 0;
			data.UFO.collided = false;
			soundStop(SOUND_UFO);
			soundPlay(SOUND_UFO_HIT);
			data.scoreValue += shotCount*100+50;

		}

		if ( data.UFO.vel.x > 0 )
			if ( data.UFO.pos.x > GE.CanvasWidth)
			{
				data.UFO.vel.x = 0;
				soundStop(SOUND_UFO);
			}

		if ( data.UFO.vel.x < 0 )
			if ( data.UFO.pos.x < -data.UFO.bitmap.width)
			{
				data.UFO.vel.x = 0;
				soundStop(SOUND_UFO);
			}

		if ( prevcol * 11 + prevrow >= movecol * 11 + moverow )
		{
			data.soundMoveIndex+=1;
			data.soundMoveIndex%=4;

			soundPlay(data.soundMoveIndex+SOUND_ENEMY_MOVE);
			if ( drop )
				drop = false;
			else
			{
				if ( edgehit )
				{
					drop = true;
					data.enemyDirection = -data.enemyDirection;
				}
			}
			edgehit = false;
		}

		if ( drop )
		{
			data.enemies[moverow][movecol].pos.y += 4;
			if ( data.enemies[moverow][movecol].pos.y > GE.CanvasHeight-32 )
			{
				data.enemies[moverow][movecol].pos.y += 4;
				data.landed = true;
			}
		}
		else
		{
			enemy = data.enemies[moverow][movecol];
			enemy.pos.x += data.enemyDirection * 2;
			if ( !edgehit && data.enemyDirection > 0 && enemy.pos.x > GE.CanvasWidth-12-3 )
				edgehit = true;
			if ( !edgehit && data.enemyDirection < 0 && enemy.pos.x < 3 )
				edgehit = true;
			enemy.costumeAnimator.Set((enemy.costumeAnimator.Get()+1)%2);
		}
			if ( bombWait == 0 )
			{
				if ( enemy.pos.y < GE.CanvasHeight-36 )
					DropBomb(data.bombs,enemy.pos.x + 6, enemy.pos.y+4, bombCounter++);
				bombWait = 4 + Math.floor(Math.random() * 3 );
			}

		HandleBombs(data.bombs);

		HandleEnemyCollisions();
	}

	function HandleBombs(bombs)
	{
		for ( var bomb in bombs )
		{
			if ( bombs[bomb].vel.y != 0 )
			{
				if ( bombs[bomb].pos.y > GE.CanvasHeight-16 || bombs[bomb].collided )
				{
					bombs[bomb].pos.x = -10;
					bombs[bomb].pos.y = -10;
					bombs[bomb].vel.y = 0;
					bombs[bomb].collided = false;
				}
			}
		}
	}

	function DropBomb(bombs,xp,yp)
	{
		// find first available bomb ( y vel is zero )
		for ( var bomb in bombs )
		{
			if ( bombs[bomb].vel.y == 0 )
			{
				bombs[bomb].pos.x = xp;
				bombs[bomb].pos.y = yp;
				bombs[bomb].vel.y = 2;
				return;
			}
		}
	}

	function HandleEnemyCollisions()
	{
		for ( var enemyrowindex in data.enemies )
		{
			var enemyrow = data.enemies[enemyrowindex];
			for ( var enemy in enemyrow )
			{
				if ( !enemyrow[enemy].died )
				{
					if ( enemyrow[enemy].collided )
					{
						data.enemiesAlive--;
						enemyrow[enemy].died = true;
						var sp = Sprite(enemyrow[enemy].pos.x,enemyrow[enemy].pos.y,CreateEnemyExplosionCostume());
						sp.SetLifeSpan(10);
						GE.SpriteAdd("EnemyExplosion",sp,LAYER_EXPLOSION);

						soundPlay(SOUND_ENEMY_EXPLODE);
						data.scoreValue += enemyrow[enemy].scoreValue;
					}
				}
			}
		}
	}

	return obj;
}

// helper functions
function DisplayScore(data)
{
	GE.DrawRect(0,0,GE.CanvasWidth,8,0);

	var colour = 7+8;

	var tmpStr = "00000" + data.score;
	tmpStr = tmpStr.substring(tmpStr.length-5);

	GE.WriteString("SCORE",0,0,colour);
	GE.WriteString(tmpStr,6*8,0,colour);

	tmpStr = "00000" + data.hiscore;
	tmpStr = tmpStr.substring(tmpStr.length-5);

	GE.WriteString("HI",GE.CanvasWidth-8*8,0,colour);
	GE.WriteString(tmpStr,GE.CanvasWidth-8*5,0,colour);
}

// helper functions
function DisplayLives(data)
{
	GE.DrawRect(0,GE.CanvasHeight-8,8*8,8,0);
	tmpStr = "" + data.lives + " ";
	GE.WriteString("LIVES",0,GE.CanvasHeight-8,7+8);
	GE.WriteString(tmpStr,7*8,GE.CanvasHeight-8,7+8);
}

function InitPlayerSprites()
{
	// init player
	var sp = Sprite(16, GE.CanvasHeight-32, CreatePlayerCostume());
	sp.SetAnimatorLoop();
	sp.costumeAnimator.Stop();
	sp.SetAnimatorFrame(0);
	GE.SpriteAdd("Player",sp,LAYER_PLAYER);

	// init shot
	var sp = Sprite(0, -16, CreateShotCostume());
	sp.SetAnimatorLoop();
	sp.costumeAnimator.Stop();
	sp.SetAnimatorFrame(0);
	GE.SpriteAdd("Shot",sp,LAYER_SHOT);
}

function InitEnemyWave(levelIn)
{
	var enemies = new Array();
	var level = levelIn;
	if (level > 8 )
		level = 8;
	for ( var y = 0; y < 6; y++ )
	{
		var enemyCostume1= CreateEnemyCostume1()
		var enemyCostume2=	 CreateEnemyCostume2()
		var enemyCostume3= CreateEnemyCostume3()
		var enemyRow = new Array();
		for ( var x = 0; x < 11; x++ )
		{
			var ypos = y*16+62+8*level
			var sp = Sprite(x*18+16, ypos, enemyCostume3);
			sp.scoreValue = 20;
			if ( y < 1 )
			{
				sp = Sprite(x*18+16, ypos, enemyCostume1);
				sp.scoreValue = 40;
			}
			if ( y > 2 )
			{
				sp = Sprite(x*18+16, ypos, enemyCostume2);
				sp.scoreValue = 10;
			}
			sp.SetAnimatorLoop();
			sp.costumeAnimator.Stop();
			sp.SetAnimatorFrame(0);
			GE.SpriteAdd("Enemy:"+x+"/"+y,sp,LAYER_ENEMY);
			enemyRow.push(sp);
		}
		enemies.push(enemyRow);
	}
	return enemies;
}

function CreatePlayerCostume()
{
var cs=Costume();
cs.SetItem(0,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,15,15,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,15,15,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
]));
cs.SetItem(1,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,-1,-1,15,-1,-1,15,-1,-1,-1,-1,-1
,-1,-1,15,-1,-1,15,-1,15,-1,15,-1,-1,15,-1,-1,-1
,15,-1,-1,15,-1,-1,15,15,15,-1,-1,15,-1,-1,15,-1
,-1,15,-1,-1,15,-1,-1,15,-1,-1,15,-1,-1,15,-1,-1
,15,-1,15,-1,15,15,-1,15,-1,15,15,-1,15,-1,15,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
]));
cs.SetItem(2,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,-1,15,-1,15,-1,15,-1,-1,-1,-1,-1
,-1,15,-1,-1,15,-1,-1,15,-1,-1,15,-1,-1,15,-1,-1
,-1,-1,15,-1,-1,15,-1,15,-1,15,-1,-1,15,-1,-1,-1
,15,-1,-1,15,-1,15,15,15,15,15,-1,15,-1,-1,15,-1
,-1,-1,-1,15,15,-1,15,15,15,-1,15,15,-1,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,-1
]));

	return cs;
}

function CreateShotCostume()
{
	var cs=Costume();
	var bm = Bitmap(1,8);
	bm.DrawRect(0,0,2,6,15);

	cs.SetItem(0,bm);

	return cs;
}

function CreateShieldCostume()
{
	var cs=Costume();
	var bm = Bitmap(24,16);
	bm.DrawRect(0,0,22,16,15);

	bm.DrawLine(0,0,4,0,-1);
	bm.DrawLine(0,1,3,1,-1);
	bm.DrawLine(0,2,2,2,-1);
	bm.DrawLine(0,3,1,3,-1);
	bm.SetPixel(0,4,-1);

	bm.DrawLine(21,0,17,0,-1);
	bm.DrawLine(21,1,18,1,-1);
	bm.DrawLine(21,2,19,2,-1);
	bm.DrawLine(21,3,20,3,-1);
	bm.SetPixel(21,4,-1);

	bm.DrawLine(4,15,21-4,15,-1);
	bm.DrawLine(4,14,21-4,14,-1);
	bm.DrawLine(5,13,21-5,13,-1);
	bm.DrawLine(6,12,21-6,12,-1);

	cs.SetItem(0,bm);

	return cs;
}

function CreateEnemyExplosionCostume()
{
var cs=Costume();
cs.SetItem(0,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,15,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1
,-1,15,-1,-1,-1,15,-1,15,-1,-1,-1,15,-1,-1,-1,-1
,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1
,-1,-1,-1,15,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1,-1
,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,15,15,-1,-1,-1
,-1,-1,-1,15,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1
,-1,15,-1,-1,-1,15,-1,15,-1,-1,-1,15,-1,-1,-1,-1
,-1,-1,-1,-1,15,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));

return cs;
}

function CreateEnemyCostume1()
{
var cs=Costume();
cs.SetItem(0,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,-1,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,15,15,15,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,-1,15,15,-1,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,-1,15,15,-1,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,15,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,-1,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));
cs.SetItem(1,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,-1,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,15,15,15,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,-1,15,15,-1,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,-1,15,15,-1,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,15,-1,15,-1,-1,15,-1,15,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));

	return cs;
}

function CreateEnemyCostume2()
{
var cs=Costume();
cs.SetItem(0,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,15,15,15,15,15,-1,-1,-1,-1,-1,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,-1,-1,15,15,15,-1,-1,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,-1,-1,-1,15,15,-1,15,-1,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,-1,-1,-1,-1,-1,15,15,-1,-1,-1,-1,-1
,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,15,15,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));
cs.SetItem(1,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,15,15,15,15,15,-1,-1,-1,-1,-1,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,-1,-1,15,15,15,-1,-1,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,-1,-1,15,15,-1,-1,15,-1,-1,15,15,-1,-1,-1,-1,-1
,-1,15,15,-1,-1,-1,-1,-1,-1,-1,15,15,-1,-1,-1,-1
,-1,-1,15,15,-1,-1,-1,-1,-1,15,15,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));

	return cs;
}

function CreateEnemyCostume3()
{
var cs=Costume();
cs.SetItem(0,
Bitmap(16,16).PixelsFromArray([-1,-1,15,15,-1,-1,-1,-1,-1,15,15,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,15,-1,15,15,15,-1,15,15,-1,-1,-1,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1,-1
,-1,15,-1,15,15,15,15,15,15,15,-1,15,-1,-1,-1,-1
,-1,15,-1,15,15,15,15,15,15,15,-1,15,-1,-1,-1,-1
,-1,15,-1,15,-1,-1,-1,-1,-1,15,-1,15,-1,-1,-1,-1
,-1,-1,-1,-1,15,15,-1,15,15,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));
cs.SetItem(1,
Bitmap(16,16).PixelsFromArray([-1,15,-1,15,-1,-1,-1,-1,-1,15,-1,15,-1,-1,-1,-1
,-1,15,-1,-1,15,-1,-1,-1,15,-1,-1,15,-1,-1,-1,-1
,-1,15,-1,15,15,15,15,15,15,15,-1,15,-1,-1,-1,-1
,-1,15,15,15,-1,15,15,15,-1,15,15,15,-1,-1,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1,-1
,-1,15,15,15,15,15,15,15,15,15,15,15,-1,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,15,-1,-1,-1,-1,-1
,-1,-1,-1,15,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1,-1
,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));

	return cs;
}

function CreateEnemyBombCostume()
{
	var cs=Costume();
	var bm = Bitmap(1,6);
	bm.DrawRect(0,0,2,6,15);

	cs.SetItem(0,bm);

	return cs;
}

function CreateUFOCostume()
{
var cs=Costume();
cs.SetItem(0,
Bitmap(16,16).PixelsFromArray([
-1,-1,-1,-1,-1,15,15,15,15,15,15,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
,-1,15,15,-1,15,15,-1,15,15,-1,15,15,-1,15,15,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
,-1,-1,15,15,15,-1,-1,15,15,-1,-1,15,15,15,-1,-1
,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1
]));
cs.SetItem(1,
Bitmap(16,16).PixelsFromArray([
-1,-1,-1,-1,-1,15,15,15,15,15,15,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
,-1,15,15,15,-1,15,15,-1,15,15,-1,15,15,-1,15,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
,-1,-1,15,15,15,-1,-1,15,15,-1,-1,15,15,15,-1,-1
,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1
]));
cs.SetItem(2,
Bitmap(16,16).PixelsFromArray([
-1,-1,-1,-1,-1,15,15,15,15,15,15,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,15,15,15,15,15,15,15,-1,-1,-1
,-1,-1,15,15,15,15,15,15,15,15,15,15,15,15,-1,-1
,-1,15,-1,15,15,-1,15,15,-1,15,15,-1,15,15,15,-1
,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
,-1,-1,15,15,15,-1,-1,15,15,-1,-1,15,15,15,-1,-1
,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1
]));
	return cs;
}

function CreateSoundButton()
{
var cs=Costume();
cs.SetItem(0,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1
,-1,-1,-1,-1,15,15,-1,-1,15,-1,-1,-1,15,-1,-1,-1
,-1,-1,-1,15,15,15,-1,-1,-1,15,-1,-1,15,-1,-1,-1
,-1,-1,-1,15,15,15,-1,15,-1,-1,15,-1,-1,15,-1,-1
,-1,15,15,15,15,15,-1,-1,15,-1,15,-1,-1,15,-1,-1
,-1,15,15,15,15,15,-1,-1,15,-1,15,-1,-1,15,-1,-1
,-1,15,15,15,15,15,-1,-1,15,-1,15,-1,-1,15,-1,-1
,-1,15,15,15,15,15,-1,-1,15,-1,15,-1,-1,15,-1,-1
,-1,-1,-1,15,15,15,-1,15,-1,-1,15,-1,-1,15,-1,-1
,-1,-1,-1,15,15,15,-1,-1,-1,15,-1,-1,15,-1,-1,-1
,-1,-1,-1,-1,15,15,-1,-1,15,-1,-1,-1,15,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));
cs.SetItem(1,
Bitmap(16,16).PixelsFromArray([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,15,15,15,-1,-1,15,-1,-1,-1,-1,15,-1,-1
,-1,15,15,15,15,15,-1,-1,-1,15,-1,-1,15,-1,-1,-1
,-1,15,15,15,15,15,-1,-1,-1,-1,15,15,-1,-1,-1,-1
,-1,15,15,15,15,15,-1,-1,-1,-1,15,15,-1,-1,-1,-1
,-1,15,15,15,15,15,-1,-1,-1,15,-1,-1,15,-1,-1,-1
,-1,-1,-1,15,15,15,-1,-1,15,-1,-1,-1,-1,15,-1,-1
,-1,-1,-1,15,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
]));

return cs;
}
// Handle various ways to press fire
var firePressed = false;
var spacekeyDown = false;

function FirePressed ()
{
	if (GE.mouseClicked )
		return true;

	if ( GE.GetKeyState(GE.KEY_SPACE))
	{
		if ( !spacekeyDown )
		{
			spacekeyDown = true;
			return true;
		}
	}
	else
	{
		spacekeyDown = false;
	}
	return false;
}

GameEngine16Colour = function(canvasIn,updateFunction,drawFunction)
{
	"use strict";
	var obj = {};
	var useWebFont = false;

	if ( typeof(updateFunction) != "function" )
		updateFunction = function(){};

	if ( typeof(drawFunction) != "function" )
		drawFunction = function(){};

	var CANVAS_WIDTH = 320; //320;
	var CANVAS_HEIGHT = 240; // 240;

	var FPS = 30;
	var updateRate = Math.floor(1000 / FPS);

	var canvas = canvasIn;

	var colours = new Array();

	var charSet = {};

	obj.mousePos = new Vector();
	obj.mouseClicked = false;
	obj.mouseTouchFire = false;
	obj.mouseUnclicked = false;
	obj.mouseDown = false;

	obj.CanvasWidth = CANVAS_WIDTH;
	obj.CanvasHeight = CANVAS_HEIGHT;

	canvas.width = CANVAS_WIDTH;
	canvas.height = CANVAS_HEIGHT;

	obj.sprites = new Array(); 	// key = spritename, value = layer
	obj.layers = new Array();	// key = spritename, value = sprite

	obj.collisionRules = new Array();

	var ctx = null;
	if(canvas.getContext )
	{
		ctx = canvas.getContext('2d');
	}

	if (ctx === null )
	{
		return null;
	}

	var screenImageData = ctx.getImageData(0,0,CANVAS_WIDTH,CANVAS_HEIGHT);

	for ( var pixel = 0; pixel < CANVAS_WIDTH*CANVAS_HEIGHT; pixel+=1)
	{
		screenImageData.data[pixel*4+3] = 255;
	}

	var screenBitmap = Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);
	screenBitmap.DrawRect(0,0,CANVAS_WIDTH,CANVAS_HEIGHT,0);

	var tempScreenBitmap= Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);
	var prevScreenBitmap= Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);

	function InitColours()
	{
		colours[0] = Colour(0,0,0);
		colours[1] = Colour(128,0,0);
		colours[2] = Colour(0,128,0);
		colours[3] = Colour(0,0,128);
		colours[4] = Colour(128,128,0);
		colours[5] = Colour(128,0,128);
		colours[6] = Colour(0,128,128);
		colours[7] = Colour(128,128,128);

		colours[8] = Colour(64,64,64);
		colours[9] = Colour(255,0,0);
		colours[10] = Colour(0,255,0);
		colours[11] = Colour(0,0,255);
		colours[12] = Colour(255,255,0);
		colours[13] = Colour(255,0,255);
		colours[14] = Colour(0,255,255);
		colours[15] = Colour(255,255,255);
	}

	InitColours();

	function InitCharSet()
	{
		charSet = CharSet(8,8);

		for ( var charVal = 32; charVal < 128; charVal++)
		{
			var charArray = new Array();

			var character = String.fromCharCode(charVal);
			ctx.fillStyle = "#000000";
			ctx.fillRect(0,0,10,10);
			ctx.font = "7px lucida console";
			ctx.fillStyle = "#FFFFFF";
			ctx.font = "Bold 9px monospace";
			ctx.fillText(character,0,7);

			var lineZeroBlank = true;
			for ( var y = 0; y < 10; y++)
			{
				var charLine = "";
				for ( var x = 0; x < 8; x++)
				{
					var p = ctx.getImageData(x, y, 2, 2).data;
					if ( p[0] > 125 )
						charLine += "*";
					else
						charLine += " ";
				}
				charArray.push(charLine);

				if ( y == 8 && charLine != "        ")
				{
					var tmp = new Array();
					for ( var l = 1; l < 9; l++)
						tmp.push(charArray[l]);
					charArray = tmp;
				}
			}
			charSet.SetChar(character,charArray);
		}
		if ( !useWebFont )
		{
		charSet.SetChar("!",[
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"        ",
			"   *    ",
			"        "]);

		charSet.SetChar("?",[
			" *****  ",
			"*     * ",
			"    **  ",
			"   *    ",
			"   *    ",
			"        ",
			"   *    ",
			"        "]);

		charSet.SetChar("\\",[
			"*       ",
			" *      ",
			"  *     ",
			"   *    ",
			"    *   ",
			"     *  ",
			"      * ",
			"        "]);

		charSet.SetChar("\"",[
			"  * *   ",
			"  * *   ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        "]);

		charSet.SetChar("#",[
			" *   *  ",
			"******* ",
			" *   *  ",
			" *   *  ",
			" *   *  ",
			"******* ",
			" *   *  ",
			"        "]);

		charSet.SetChar("$",[
			"   *    ",
			" *****  ",
			"*  *    ",
			" *****  ",
			"   *  * ",
			" *****  ",
			"   *    ",
			"        "]);

		charSet.SetChar("%",[
			" *    * ",
			"* *  *  ",
			" *  *   ",
			"   *    ",
			"  *  *  ",
			" *  * * ",
			"*    *  ",
			"        "]);

		charSet.SetChar("&",[
			"  ***   ",
			" *   *  ",
			"  * *   ",
			"  **    ",
			" *  *   ",
			"*    *  ",
			" **** * ",
			"        "]);

		charSet.SetChar("'",[
			"   *    ",
			"   *    ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        "]);

		charSet.SetChar("(",[
			"   *    ",
			"  *     ",
			" *      ",
			" *      ",
			" *      ",
			"  *     ",
			"   *    ",
			"        "]);

		charSet.SetChar(")",[
			"   *    ",
			"    *   ",
			"     *  ",
			"     *  ",
			"     *  ",
			"    *   ",
			"   *    ",
			"        "]);

		charSet.SetChar("*",[
			"        ",
			"*   *   ",
			" * *    ",
			"  *     ",
			" * *    ",
			"*   *   ",
			"        ",
			"        "]);

		charSet.SetChar("+",[
			"        ",
			"   *    ",
			"   *    ",
			" *****  ",
			"   *    ",
			"   *    ",
			"        ",
			"        "]);

		charSet.SetChar(",",[
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"   *    ",
			"   *    ",
			"  *     "]);

		charSet.SetChar("-",[
			"        ",
			"        ",
			"        ",
			" *****  ",
			"        ",
			"        ",
			"        ",
			"        "]);

		charSet.SetChar(".",[
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"   *    ",
			"        "]);

		charSet.SetChar("/",[
			"      * ",
			"     *  ",
			"    *   ",
			"   *    ",
			"  *     ",
			" *      ",
			"*       ",
			"        "]);

		charSet.SetChar("0",[
			" *****  ",
			"*     * ",
			"*   * * ",
			"*  *  *",
			"* *   * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("1",[
			"   *    ",
			"  **    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"  ***   ",
			"        "]);

		charSet.SetChar("2",[
			" *****  ",
			"*     * ",
			"     *  ",
			"    *   ",
			"  **    ",
			" *      ",
			"******* ",
			"        "]);

		charSet.SetChar("3",[
			" *****  ",
			"*     * ",
			"      * ",
			"  ****  ",
			"      * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("4",[
			" *      ",
			" *      ",
			" *      ",
			" *  *   ",
			" ****** ",
			"    *   ",
			"    *   ",
			"        "]);

		charSet.SetChar("5",[
			"******* ",
			"*       ",
			"*       ",
			"******  ",
			"      * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("6",[
			" *****  ",
			"*     * ",
			"*       ",
			"******  ",
			"*     * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("7",[
			"******* ",
			"      * ",
			"     *  ",
			"     *  ",
			"    *   ",
			"    *   ",
			"    *   ",
			"        "]);

		charSet.SetChar("8",[
			" *****  ",
			"*     * ",
			"*     * ",
			" *****  ",
			"*     * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("9",[
			" *****  ",
			"*     * ",
			"*     * ",
			" ****** ",
			"      * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar(":",[
			"        ",
			"        ",
			"   *    ",
			"        ",
			"   *    ",
			"        ",
			"        ",
			"        "]);

		charSet.SetChar(";",[
			"        ",
			"        ",
			"   *    ",
			"        ",
			"   *    ",
			"  *     ",
			"        ",
			"        "]);

		charSet.SetChar("<",[
			"        ",
			"    **  ",
			"  **    ",
			" *      ",
			"  **    ",
			"    **  ",
			"        ",
			"        "]);

		charSet.SetChar("=",[
			"        ",
			"        ",
			" *****  ",
			"        ",
			" *****  ",
			"        ",
			"        ",
			"        "]);

		charSet.SetChar(">",[
			"        ",
			" **     ",
			"   **   ",
			"     *  ",
			"   **   ",
			" **     ",
			"        ",
			"        "]);

		charSet.SetChar("A",[
			" *****  ",
			"*     * ",
			"*     * ",
			"******* ",
			"*     * ",
			"*     * ",
			"*     * ",
			"        "]);

		charSet.SetChar("B",[
			"******  ",
			"*     * ",
			"*     * ",
			"******* ",
			"*     * ",
			"*     * ",
			"******  ",
			"        "]);

		charSet.SetChar("C",[
			" *****  ",
			"*     * ",
			"*       ",
			"*       ",
			"*       ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("D",[
			"******  ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"******  ",
			"        "]);

		charSet.SetChar("E",[
			"******* ",
			"*       ",
			"*       ",
			"*****   ",
			"*       ",
			"*       ",
			"******* ",
			"        "]);

		charSet.SetChar("F",[
			"******* ",
			"*       ",
			"*       ",
			"*****   ",
			"*       ",
			"*       ",
			"*       ",
			"        "]);

		charSet.SetChar("G",[
			" *****  ",
			"*     * ",
			"*       ",
			"*  **** ",
			"*     * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("H",[
			"*     * ",
			"*     * ",
			"*     * ",
			"******* ",
			"*     * ",
			"*     * ",
			"*     * ",
			"        "]);

		charSet.SetChar("I",[
			"  ***   ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"  ***   ",
			"        "]);

		charSet.SetChar("J",[
			"******* ",
			"      * ",
			"      * ",
			"      * ",
			"*     * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("K",[
			"*     * ",
			"*   **  ",
			"* **    ",
			"**      ",
			"* **    ",
			"*   **  ",
			"*     * ",
			"        "]);

		charSet.SetChar("L",[
			"*       ",
			"*       ",
			"*       ",
			"*       ",
			"*       ",
			"*       ",
			"******* ",
			"        "]);

		charSet.SetChar("M",[
			"*     * ",
			"**   ** ",
			"* * * * ",
			"*  *  * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"        "]);

		charSet.SetChar("N",[
			"*     * ",
			"**    * ",
			"* *   * ",
			"*  *  * ",
			"*   * * ",
			"*    ** ",
			"*     * ",
			"        "]);

		charSet.SetChar("O",[
			" *****  ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("P",[
			" *****  ",
			"*     * ",
			"*     * ",
			"******  ",
			"*       ",
			"*       ",
			"*       ",
			"        "]);

		charSet.SetChar("Q",[
			" *****  ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*   * * ",
			"**   *  ",
			" **** * ",
			"        "]);

		charSet.SetChar("R",[
			"******  ",
			"*     * ",
			"*     * ",
			"******  ",
			"*     * ",
			"*     * ",
			"*     * ",
			"        "]);

		charSet.SetChar("S",[
			" *****  ",
			"*     * ",
			"*       ",
			" *****  ",
			"      * ",
			"*     * ",
			" *****  ",
			"        "]);

		charSet.SetChar("T",[
			"******* ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"        "]);

		charSet.SetChar("U",[
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			" ****** "]);

		charSet.SetChar("V",[
			"*     * ",
			"*     * ",
			" *   *  ",
			" *   *  ",
			"  * *   ",
			"  * *   ",
			"   *    ",
			"        "]);

		charSet.SetChar("W",[
			"*     * ",
			"*     * ",
			"*     * ",
			"*     * ",
			"*  *  * ",
			"* * * * ",
			"**   ** ",
			"        "]);

		charSet.SetChar("X",[
			"*     * ",
			" *   *  ",
			"  * *   ",
			"   *    ",
			"  * *   ",
			" *   *  ",
			"*     * ",
			"        "]);

		charSet.SetChar("Y",[
			"*     * ",
			" *   *  ",
			"  * *   ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"        "]);

		charSet.SetChar("Z",[
			"******* ",
			"     *  ",
			"    *   ",
			"   *    ",
			"  *     ",
			" *      ",
			"******* ",
			"        "]);

		charSet.SetChar("[",[
			"  ***   ",
			"  *     ",
			"  *     ",
			"  *     ",
			"  *     ",
			"  *     ",
			"  ***   ",
			"        "]);

		charSet.SetChar("\\",[
			"*       ",
			" *      ",
			"  *     ",
			"   *    ",
			"    *   ",
			"     *  ",
			"      * ",
			"        "]);

		charSet.SetChar("]",[
			"  ***   ",
			"    *   ",
			"    *   ",
			"    *   ",
			"    *   ",
			"    *   ",
			"  ***   ",
			"        "]);

		charSet.SetChar("^",[
			"   *    ",
			"  * *   ",
			" *   *  ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        "]);

		charSet.SetChar("_",[
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"********"]);

		charSet.SetChar("`",[
			"  *     ",
			"   *    ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        ",
			"        "]);

		charSet.SetChar("a",[
			"        ",
			"        ",
			" ****   ",
			"     *  ",
			" *****  ",
			"*    *  ",
			" **** * ",
			"        "]);

		charSet.SetChar("b",[
			"*       ",
			"*       ",
			"*****   ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			"*****   ",
			"        "]);

		charSet.SetChar("c",[
			"        ",
			"        ",
			" ****   ",
			"*       ",
			"*       ",
			"*       ",
			" ****   ",
			"        "]);

		charSet.SetChar("d",[
			"     *  ",
			"     *  ",
			" *****  ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			" *****  ",
			"        "]);

		charSet.SetChar("e",[
			"        ",
			"        ",
			" ****   ",
			"*    *  ",
			"******  ",
			"*       ",
			" ****   ",
			"        "]);

		charSet.SetChar("f",[
			"  ***   ",
			" *      ",
			" *      ",
			"****    ",
			" *      ",
			" *      ",
			" *      ",
			"        "]);

		charSet.SetChar("g",[
			"        ",
			"        ",
			" ****   ",
			"*    *  ",
			"*    *  ",
			" *****  ",
			"     *  ",
			" ****   "]);

		charSet.SetChar("h",[
			"*       ",
			"*       ",
			"*       ",
			"*****   ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			"        "]);

		charSet.SetChar("i",[
			"        ",
			"  *     ",
			"        ",
			"  *     ",
			"  *     ",
			"  *     ",
			"  *     ",
			"        "]);

		charSet.SetChar("j",[
			"        ",
			"     *  ",
			"        ",
			"    **  ",
			"     *  ",
			"     *  ",
			"     *  ",
			" ****   "]);

		charSet.SetChar("k",[
			"*       ",
			"*       ",
			"*  *    ",
			"* *     ",
			"***     ",
			"*  *    ",
			"*   *   ",
			"        "]);

		charSet.SetChar("l",[
			"*       ",
			"*       ",
			"*       ",
			"*       ",
			"*       ",
			"*       ",
			" ***    ",
			"        "]);

		charSet.SetChar("m",[
			"        ",
			"        ",
			"*** **  ",
			"*  *  * ",
			"*  *  * ",
			"*  *  * ",
			"*  *  * ",
			"        "]);

		charSet.SetChar("n",[
			"        ",
			"        ",
			"*****   ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			"        "]);

		charSet.SetChar("o",[
			"        ",
			"        ",
			" ****   ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			" ****   ",
			"        "]);

		charSet.SetChar("p",[
			"        ",
			"        ",
			"*****   ",
			"*    *  ",
			"*    *  ",
			"*****   ",
			"*       ",
			"*       "]);

		charSet.SetChar("q",[
			"        ",
			"        ",
			" *****  ",
			"*    *  ",
			"*    *  ",
			" *****  ",
			"     *  ",
			"     *  "]);

		charSet.SetChar("r",[
			"        ",
			"        ",
			" ****   ",
			"*       ",
			"*       ",
			"*       ",
			"*       ",
			"        "]);

		charSet.SetChar("s",[
			"        ",
			"        ",
			" ****   ",
			"*       ",
			" ****   ",
			"     *  ",
			" ****   ",
			"        "]);

		charSet.SetChar("t",[
			"        ",
			" *      ",
			"***     ",
			" *      ",
			" *      ",
			" *      ",
			"  ***   ",
			"        "]);

		charSet.SetChar("u",[
			"        ",
			"        ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			" *****  ",
			"        "]);

		charSet.SetChar("v",[
			"        ",
			"        ",
			"*    *  ",
			"*    *  ",
			" *  *   ",
			" *  *   ",
			"  **    ",
			"        "]);

		charSet.SetChar("w",[
			"        ",
			"        ",
			"*  *  * ",
			"*  *  * ",
			"*  *  * ",
			"*  *  * ",
			" ** **  ",
			"        "]);

		charSet.SetChar("x",[
			"        ",
			"        ",
			"*    *  ",
			" *  *   ",
			"  **    ",
			" *  *   ",
			"*    *  ",
			"        "]);

		charSet.SetChar("y",[
			"        ",
			"        ",
			"*    *  ",
			"*    *  ",
			"*    *  ",
			" *****  ",
			"     *  ",
			" ****   "]);

		charSet.SetChar("z",[
			"        ",
			"        ",
			"*****   ",
			"   *    ",
			"  *     ",
			" *      ",
			"*****   ",
			"        "]);

		charSet.SetChar("{",[
			"    *   ",
			"   *    ",
			"   *    ",
			"  *     ",
			"   *    ",
			"   *    ",
			"    *   ",
			"        "]);

		charSet.SetChar("|",[
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"   *    ",
			"        "]);

		charSet.SetChar("}",[
			"  *     ",
			"   *    ",
			"   *    ",
			"    *   ",
			"   *    ",
			"   *    ",
			"  *     ",
			"        "]);

		charSet.SetChar("~",[
			"        ",
			"        ",
			"      * ",
			" *****  ",
			"*       ",
			"        ",
			"        ",
			"        "]);
		}
	}

	obj.CharSet = function()
	{
		return charSet;
	}

	InitCharSet();

	var dwCurrentTime=0;
	var dwLastUpdateTime=(new Date).getTime();
	var dwTimeCarriedOver = 0;
	var dwElapsedTime=0;

	obj.Main = function()
	{
		dwCurrentTime = (new Date).getTime();
		dwElapsedTime = dwCurrentTime - dwLastUpdateTime + dwTimeCarriedOver;
		if(dwElapsedTime >= updateRate)
		{
			drawFunction();
			obj.Draw();

			var frames = Math.floor(dwElapsedTime / updateRate );
			dwTimeCarriedOver = dwElapsedTime % updateRate;
			dwLastUpdateTime = dwCurrentTime;
			for ( var f = 0; f < frames; f++)
			{
				updateFunction();
				obj.Update();
				obj.mouseClicked = false;
				obj.mouseUnclicked = false;
				obj.mouseTouchFire = false;
				obj.mouseSwipe.x = 0;
				obj.mouseSwipe.y = 0;
				obj.CheckCollisions();
			}
		}
	}

	var timer = Timer(FPS, obj.Main);

	obj.Stop = function()
	{
		timer.Stop();
	}

	obj.writeToCanvas = function(bitmap)
	{
		var sourceArray = bitmap.GetPixelArray();
		var prevArray = prevScreenBitmap.GetPixelArray();
		var offset = 0;

		for ( var p = 0; p < sourceArray.length; p++ )
		{
			if ( sourceArray[p] != prevArray[p])
			{
				prevArray[p] = sourceArray[p];
				var colour = colours[sourceArray[p]];
				screenImageData.data[offset] = colour.red;
				screenImageData.data[offset+1] = colour.green;
				screenImageData.data[offset+2] = colour.blue;
			}

			offset+=4;
		}
		ctx.putImageData(screenImageData,0,0);

	}

	obj.Update = function()
	{
		for ( var l in obj.layers )
		{
			var currentLayer = obj.layers[l];
			for ( var i in currentLayer )
			{
				var s = currentLayer[i];
				if ( s )
				{
					s.Update(tempScreenBitmap.width, tempScreenBitmap.height);
					if( s.died )
						SpriteDeleteForReal(s.name);
				}
			}
		}
	}

	obj.CustomResolution=function(width, height)
	{
		CANVAS_WIDTH = width;
		CANVAS_HEIGHT = height;

		obj.CanvasWidth = CANVAS_WIDTH;
		obj.CanvasHeight = CANVAS_HEIGHT;

		canvas.width = CANVAS_WIDTH;
		canvas.height = CANVAS_HEIGHT;

		screenImageData = ctx.getImageData(0,0,CANVAS_WIDTH,CANVAS_HEIGHT);

		for ( var pixel = 0; pixel < CANVAS_WIDTH*CANVAS_HEIGHT; pixel+=1)
		{
			screenImageData.data[pixel*4+3] = 255;
		}

		screenBitmap = Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);
		screenBitmap.DrawRect(0,0,CANVAS_WIDTH,CANVAS_HEIGHT,0);

		tempScreenBitmap= Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);
		prevScreenBitmap= Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);

	}

	obj.PortraitMode = function()
	{
		if (CANVAS_WIDTH > CANVAS_HEIGHT )
		{
			var tmp = CANVAS_WIDTH;
			CANVAS_WIDTH = CANVAS_HEIGHT;
			CANVAS_HEIGHT = tmp;

			obj.CanvasWidth = CANVAS_WIDTH;
			obj.CanvasHeight = CANVAS_HEIGHT;

			canvas.width = CANVAS_WIDTH;
			canvas.height = CANVAS_HEIGHT;

			screenImageData = ctx.getImageData(0,0,CANVAS_WIDTH,CANVAS_HEIGHT);

			for ( var pixel = 0; pixel < CANVAS_WIDTH*CANVAS_HEIGHT; pixel+=1)
			{
				screenImageData.data[pixel*4+3] = 255;
			}

			screenBitmap = Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);
			screenBitmap.DrawRect(0,0,CANVAS_WIDTH,CANVAS_HEIGHT,0);

			tempScreenBitmap= Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);
			prevScreenBitmap= Bitmap(CANVAS_WIDTH,CANVAS_HEIGHT);

		}
	}

	obj.CheckCollisions = function()
	{
		for ( var r in obj.collisionRules )
		{
			var currentLayer = obj.layers[r];
			CheckCollisionsForLayer(currentLayer, obj.collisionRules[r]);
		}
	}

	function CheckCollisionsForLayer(layer, collisionRule)
	{
		for ( var spritePos in layer )
		{
			var sprite = layer[spritePos];
			if ( sprite )
			{
				sprite.collided = false;
				for ( var targetLayer in collisionRule)
				{
					CheckSpriteCollisionWithLayer(sprite, obj.layers[targetLayer]);
				}
			}
		}
	}

	function CheckSpriteCollisionWithLayer(sprite, layer)
	{
		if ( layer )
		{
			for ( var spritePos in layer )
			{
				var targetSprite = layer[spritePos];
				if( sprite.IsCollidedWith(targetSprite.name))
				{
					sprite.collided = true;
					targetSprite.collided = true;
				}
			}
		}
	}

	obj.CountSpritesInLayer=function(layerName)
	{
		var count = 0;

		var layer = obj.layers[layerName];

		for ( var spritePos in layer )
		{
			var sprite = layer[spritePos];
			if ( sprite )
			{
				count++;
			}
		}
		return count;
	}

	obj.Draw = function()
	{
		// copy the screenBitmap and place all the sprites on it

		// Cloning has been replaced with a permanent temp bitmap and
		// contents just copied over. This seems to yield significant
		// speed improvement in chrome.
		var size = screenBitmap.width * screenBitmap.height;
		var targetArray = tempScreenBitmap.GetPixelArray();
		var sourceArray = screenBitmap.GetPixelArray();
		for (  var p = 0; p < size; p++)
		{
			targetArray[p] = sourceArray[p];
		}

		for ( var l in obj.layers )
		{
			var currentLayer = obj.layers[l];
			for ( var i in currentLayer )
			{
				var spr = currentLayer[i];
				if ( spr )
					tempScreenBitmap.Paste(spr.pos.x,spr.pos.y, spr.bitmap )
			}
		}
		obj.writeToCanvas(tempScreenBitmap);
	}

	obj.DrawPixel = function(pixelX, pixelY,colour)
	{
		screenBitmap.SetPixel(Math.floor(pixelX),Math.floor(pixelY),colour);
	}

	obj.Clear = function(colour)
	{
		screenBitmap.Clear(colour);
	}

	obj.DrawLine = function(startX, startY, endX, endY, colour)
	{
		screenBitmap.DrawLine(startX, startY, endX, endY, colour)
	}

	obj.DrawRect = function(xPos,yPos,width,height,colour)
	{
		screenBitmap.DrawRect(xPos, yPos, width, height, colour)
	}

	obj.DrawCircle = function(xPos,yPos,radius,colour)
	{
		screenBitmap.DrawCircle(xPos,yPos,radius,colour)
	}

	obj.Scroll = function(xMovement,yMovement)
	{
		screenBitmap.Scroll(xMovement,yMovement);
	}

	obj.FlipX = function()
	{
		screenBitmap = screenBitmap.FlipX();
	}

	obj.FlipY = function()
	{
		screenBitmap = screenBitmap.FlipY();
	}

	obj.Paste = function(xPos,yPos,bitmap)
	{
		screenBitmap.Paste(xPos,yPos,bitmap);
	}

	obj.DefineColour = function(colour,r,g,b)
	{
		colours[colour] = Colour(r,g,b);
	}

	function CleanUpArray(arrayObj)
	{
		var returnArray = new Array();

		for ( var i in arrayObj)
		{
			if ( arrayObj[i] != null )
			{
				returnArray[i] = arrayObj[i];
			}
		}
		return returnArray;
	}

	function CountItemsInArray(arrayObj)
	{
		var count = 0;
		for ( var i in arrayObj )
		{
			count++;
		}

		return count;
	}

	obj.SpriteAdd = function(name,sprite, layer)
	{
		obj.SpriteDelete(name);

		obj.sprites[name] = layer;

		if (obj.layers[layer] == null )
		{
			obj.layers[layer] = new Array();
		}

		var newLayer = obj.layers[layer];
		newLayer[name] = sprite;
		sprite.name = name;
	}

	function SpriteDeleteForReal(name)
	{
		var spriteLayer = obj.sprites[name];
		if ( spriteLayer )
		{
			obj.layers[spriteLayer][name] = null;
			obj.layers[spriteLayer] = CleanUpArray(obj.layers[spriteLayer]);
			if ( CountItemsInArray(obj.layers[spriteLayer]) == 0 )
			{
				obj.layers[spriteLayer] = null;
				obj.layers = CleanUpArray(obj.layers);
			}
			obj.sprites[name] = null;
			obj.sprites = CleanUpArray(obj.sprites);
		}
	}

	obj.SpriteDelete=function(name)
	{
		var spriteLayer = obj.sprites[name];
		if ( spriteLayer )
		{
			if ( obj.layers[spriteLayer] )
				obj.layers[spriteLayer][name].died = true;
		}
	}

	obj.DeleteSpritesInLayer=function(spriteLayer)
	{
		if ( spriteLayer )
		{
			if ( obj.layers[spriteLayer] )
			{
				for ( var i in obj.layers[spriteLayer] )
				{
					obj.layers[spriteLayer][i].died = true;
				}
			}
		}
	}

	obj.SpriteGet = function(name)
	{
		var spriteLayer = obj.layers[obj.sprites[name]];
		if ( spriteLayer )
			return spriteLayer[name];
		return null;
	}

	obj.CollisionRuleAdd = function(sourceLayer, targetLayer)
	{
		if ( obj.collisionRules[sourceLayer] == null )
		{
			obj.collisionRules[sourceLayer] = new Array();
		}

		if (obj.collisionRules[sourceLayer][targetLayer] == null )
		{
			obj.collisionRules[sourceLayer][targetLayer] = targetLayer;
		}
	}

	obj.CollisionRuleDelete = function(sourceLayer, targetLayer)
	{
		if ( obj.collisionRules[sourceLayer] )
		{
			if ( obj.collisionRules[sourceLayer][targetLayer] )
			{
				obj.collisionRules[sourceLayer][targetLayer] = null;
				obj.collisionRules[sourceLayer] = CleanUpArray(obj.collisionRules[sourceLayer]);
			}
			if ( CountItemsInArray(obj.collisionRules[sourceLayer]) == 0 )
			{
				obj.collisionRules[sourceLayer] = null;
				obj.collisionRules = CleanUpArray(obj.collisionRules);
			}
		}
	}

	obj.WriteChar = function(char,xPos,yPos,colour,xbig,ybig)
	{
		charSet.WriteChar(char,screenBitmap,xPos,yPos,colour,xbig,ybig)
	}

	obj.WriteString = function(text,xPos,yPos,colour,xbig,ybig)
	{
		charSet.WriteString(text,screenBitmap,xPos,yPos,colour,xbig,ybig)
	}

	obj.WriteStringCentered=function(text,yPos,colour,xbig,ybig)
	{
		screenBitmap.WriteStringCentered(text,charSet, yPos,colour,xbig,ybig);
	}

	var mouseDownPos = Vector(0,0);
	var mouseUpPos = Vector(0,0);
	obj.mouseSwipe = Vector(0,0);

	function SetMousePos(xp,yp)
	{
		var bbox = canvas.getBoundingClientRect();
		obj.mousePos.x = (xp - bbox.left) * ( canvas.width / bbox.width);
		obj.mousePos.y = (yp - bbox.top) * ( canvas.height / bbox.height);
	}

	canvas.addEventListener("mousemove", function(evt){
		SetMousePos(evt.clientX,evt.clientY);
		evt.preventDefault();
	});

	canvas.addEventListener("touchmove", function(evt){
		SetMousePos(evt.targetTouches[0].pageX,evt.targetTouches[0].pageY);
		evt.preventDefault();
	});

	canvas.addEventListener("mousedown", function(evt){
		obj.mouseClicked = true;
		obj.mouseDown = true;
		mouseDownPos.x = obj.mousePos.x;
		mouseDownPos.y = obj.mousePos.y;
		evt.preventDefault();
	});

	canvas.addEventListener("touchstart", function(evt){
		SetMousePos(evt.targetTouches[0].pageX,evt.targetTouches[0].pageY);
		obj.mouseClicked = true;
		obj.mouseDown = true;
		mouseDownPos.x = obj.mousePos.x;
		mouseDownPos.y = obj.mousePos.y;
		if ( evt.targetTouches > 1 )
			obj.mouseTouchFire = true;
		evt.preventDefault();
	});

	canvas.addEventListener("mouseup", function(evt){
		obj.mouseDown = false;
		obj.mouseUnclicked = true;
		mouseUpPos.x = obj.mousePos.x;
		mouseUpPos.y = obj.mousePos.y;
		obj.mouseSwipe.x = mouseUpPos.x - mouseDownPos.x;
		obj.mouseSwipe.y = mouseUpPos.y - mouseDownPos.y;
		evt.preventDefault();
	});

	canvas.addEventListener("touchend", function(evt){
		obj.mouseDown = false;
		obj.mouseUnclicked = true;
		mouseUpPos.x = obj.mousePos.x;
		mouseUpPos.y = obj.mousePos.y;
		obj.mouseSwipe.x = mouseUpPos.x - mouseDownPos.x;
		obj.mouseSwipe.y = mouseUpPos.y - mouseDownPos.y;
		evt.preventDefault();
	});

	var tiltable = false;
	obj.tiltX = null;
	obj.tiltY = null;
	obj.tiltZ = null;

	var tiltXBase = 0;
	var tiltYBase = 0;
	var tiltZBase = 0;

	obj.Tilt=function(values)
	{
		obj.tiltX = values[0];
		obj.tiltY = values[1];
		obj.tiltZ = values[2];
		if ( tiltable == false && obj.tiltX != null )
		{
			tiltable = true;
			obj.TiltCalibrate();
		}
	}

	obj.TiltCalibrate = function()
	{
		tiltXBase = obj.tiltX;
		tiltYBase = obj.tiltY;
		tiltZBase = obj.tiltZ;
	}

	obj.Tiltable = function()
	{
		return tiltable;
	}

	obj.GetTiltX=function()
	{
		 var upsidedown = 1;
		 if(obj.tiltZ > 0 )
			upsidedown = -1;
		if ( window.innerWidth < window.innerHeight )
			return (obj.tiltX-tiltXBase);
		else
			return ( obj.tiltY - tiltYBase )*upsidedown;
	}

	// Tilt events
	if (window.DeviceOrientationEvent)
	{
		window.addEventListener("deviceorientation", function () {
        obj.Tilt([event.gamma,event.beta, event.gamma]);
		}, true);
	}

	// keyboard handler
	var keycodes = [];

	obj.GetKeyState=function(keycode)
	{
		return keycodes[keycode];
	}

	document.addEventListener("keydown", function(evt){
		keycodes[evt.keyCode] = true;
	});

	document.addEventListener("keyup", function(evt){
		keycodes[evt.keyCode] = false;
	});

	obj.KEY_ENTER = 13;

	obj.KEY_ESCAPE = 27;

	obj.KEY_SPACE = 32;

	obj.KEY_LEFT = 37;
	obj.KEY_UP = 38;
	obj.KEY_RIGHT = 39;
	obj.KEY_DOWN = 40;

	obj.KEY_0 = 48;
	obj.KEY_1 = 49;
	obj.KEY_2 = 50;
	obj.KEY_3 = 51;
	obj.KEY_4 = 52;
	obj.KEY_5 = 53;
	obj.KEY_6 = 54;
	obj.KEY_7 = 55;
	obj.KEY_8 = 56;
	obj.KEY_9 = 57;

	obj.KEY_A = 65;
	obj.KEY_B = 66;
	obj.KEY_C = 67;
	obj.KEY_D = 68;
	obj.KEY_E = 69;
	obj.KEY_F = 70;
	obj.KEY_G = 71;
	obj.KEY_H = 72;
	obj.KEY_I = 73;
	obj.KEY_J = 74;
	obj.KEY_K = 75;
	obj.KEY_L = 76;
	obj.KEY_M = 77;
	obj.KEY_N = 78;
	obj.KEY_O = 79;
	obj.KEY_P = 80;
	obj.KEY_Q = 81;
	obj.KEY_R = 82;
	obj.KEY_S = 83;
	obj.KEY_T = 84;
	obj.KEY_U = 85;
	obj.KEY_V = 86;
	obj.KEY_W = 87;
	obj.KEY_X = 88;
	obj.KEY_Y = 89;
	obj.KEY_Z = 90;

	return obj;
}

Timer = function(fps,callFunction)
{
	var obj = {}

	var fps = fps;
	var callFunction = callFunction

	var timerId = setInterval ( callFunction, Math.floor(1000/fps));

	obj.Stop = function()
	{
		clearInterval(timerId);
	}

	return obj;
}

Colour = function(red,green,blue)
{
	obj = {}
	obj.red = red;
	obj.green = green;
	obj.blue = blue;

	return obj;
}

var BaseBitmap = {};

Bitmap = function(width,height)
{
	var obj = Object.create(BaseBitmap);
	obj.width = width;
	obj.height = height;

	var pixels = new Array()
	var scrollXFraction = 0.0;
	var scrollYFraction = 0.0;

	obj.GetPixelArray = function ()
	{
		return pixels;
	}

	obj.SetPixelArray = function (newArray)
	{
		pixels = newArray;
	}

	obj.GetPixel = function (xpos, ypos)
	{
		if (xpos >= 0 && xpos < obj.width && ypos >= 0 && ypos < obj.height)
		{
			return pixels[ypos*width+xpos];
		}
		return 0;
	}

	obj.SetPixel = function (xpos,ypos,colour)
	{
		if (xpos >= 0 && xpos < obj.width && ypos >= 0 && ypos < obj.height)
		{
			pixels[Math.floor(ypos)*width+Math.floor(xpos)] = colour;
		}
	}

	obj.Clear = function (colour)
	{
		for ( var ypos = 0; ypos < obj.height; ypos++)
		{
			for ( var xpos = 0; xpos < obj.width; xpos++)
			{
				obj.SetPixel(xpos,ypos,colour);
			}
		}
	}

	obj.PixelsFromArray = function(pixelsIn)
	{
		var minPixels = Math.min(obj.width*obj.height,pixelsIn.length);

		for(var p = 0; p < minPixels; p++)
		{
			pixels[p] = pixelsIn[p];
		}
		return obj;
	}

	obj.DrawLine = function(startX, startY, endX, endY, colour)
	{
		var xLen = endX - startX;
		var yLen = endY - startY;
		var maxPoints = Math.max(Math.abs(xLen), Math.abs(yLen))

		var xDisp = xLen / maxPoints;
		var yDisp = yLen / maxPoints;

		for ( var p = 0; p <= maxPoints; p++)
		{
			obj.SetPixel(startX + p*xDisp+0.5, startY + p*yDisp+0.5, colour)
		}
	}

	obj.DrawRect = function(xPos,yPos,width,height,colour)
	{
		for(var yDisp = 0; yDisp < height; yDisp++ )
		{
			obj.DrawLine(xPos, yPos+yDisp, xPos+width-1, yPos+yDisp,colour)
		}
	}

	obj.DrawCircle = function(xPos,yPos,radius,colour)
	{
		var radiusSquared = radius * radius;
		var xOff;

		for( var yOff = 0.0; yOff < radius; yOff+= 0.5)
		{
			xOff = Math.sqrt(radiusSquared-(yOff*yOff));
			obj.DrawLine(xPos-xOff+.5,yPos+yOff+.5,xPos+xOff+.5,yPos+yOff,colour)
			obj.DrawLine(xPos-xOff+.5,yPos-yOff+.5,xPos+xOff+.5,yPos-yOff,colour)
		}
	}

	obj.Scroll = function(xMovement,yMovement)
	{
		scrollXFraction += xMovement;
		scrollYFraction += yMovement;

		var xMove = Math.round(scrollXFraction);
		var yMove = Math.round(scrollYFraction);

		if(xMove == 0 && yMove == 0)
		{
			return;
		}

		scrollXFraction -= xMove;
		scrollYFraction -= yMove;

		var newPixels = new Array();

		var targetXPos = xMove;
		if(targetXPos < 0)
		{
			targetXPos += obj.width;
		}

		var targetYPos = yMove
		if(targetYPos < 0)
		{
			targetYPos += obj.height;
		}

		for(var yPos = 0; yPos < obj.height; yPos++)
		{
			for(var xPos = 0; xPos < obj.width; xPos++)
			{
				newPixels[yPos*obj.width+xPos] = pixels[targetYPos*obj.width+targetXPos];
				targetXPos += 1
				if(targetXPos >= obj.width)
				{
					targetXPos -= obj.width;
				}
			}
			targetYPos += 1
			if(targetYPos >= obj.height)
			{
				targetYPos -= obj.height;
			}
		}

		pixels = newPixels;
	}

	// Not ready for relacing scroll function yet. Does not wrap pixels.
	obj.ScrollNew = function(xMovement,yMovement)
	{
		scrollXFraction -= xMovement;
		scrollYFraction -= yMovement;

		var xMove = Math.round(scrollXFraction);
		var yMove = Math.round(scrollYFraction);

		if(xMove == 0 && yMove == 0)
		{
			return;
		}

		scrollXFraction -= xMove;
		scrollYFraction -= yMove;

		var newBitmap = obj.Clone();

		obj.Clear(-1);

		var targetXPos = xMove;
		if(targetXPos < 0)
		{
			targetXPos += obj.width;
		}

		var targetYPos = yMove
		if(targetYPos < 0)
		{
			targetYPos += obj.height;
		}

		obj.Paste(xMove,yMove,newBitmap);
		if(targetYPos > 0 )
			obj.Paste(xMove,yMove-obj.height,newBitmap);
		if(targetYPos < 0 )
		obj.Paste(xMove,yMove+obj.height,newBitmap);
	}

	obj.FlipX = function ()
	{
		var newBitmap = Bitmap(obj.width,obj.height);

		for(var yPos = 0; yPos < obj.height; yPos++)
		{
			for(var xPos = 0; xPos < obj.width; xPos++)
			{
				newBitmap.SetPixel(xPos,yPos,obj.GetPixel(obj.width-1-xPos,yPos));
			}
		}
		return newBitmap;
	}

	obj.FlipY = function()
	{
		var newBitmap = Bitmap(obj.width,obj.height);

		for(var yPos = 0; yPos < obj.height; yPos++)
		{
			for(var xPos = 0; xPos < obj.width; xPos++)
			{
				newBitmap.SetPixel(xPos,yPos,obj.GetPixel(xPos,obj.height-1-yPos));
			}
		}
		return newBitmap;
	}

	obj.RotateRight = function()
	{
		var newBitmap = Bitmap(obj.width,obj.height);

		for(var yPos = 0; yPos < obj.height; yPos++)
		{
			for(var xPos = 0; xPos < obj.width; xPos++)
			{
				newBitmap.SetPixel(yPos,xPos,obj.GetPixel(xPos,obj.height-1-yPos));
			}
		}
		return newBitmap;
	}

	obj.RotateLeft = function()
	{
		var newBitmap = Bitmap(obj.width,obj.height);

		for(var yPos = 0; yPos < obj.height; yPos++)
		{
			for(var xPos = 0; xPos < obj.width; xPos++)
			{
				newBitmap.SetPixel(yPos,xPos,obj.GetPixel(obj.width-1-xPos,yPos));
			}
		}
		return newBitmap;
	}

	obj.Clone = function()
	{
		var newBitmap = Bitmap(obj.width,obj.height);

		newBitmap.SetPixelArray([].concat(pixels));

		return newBitmap;
	}

	obj.Paste = function(xp,yp,bitmap)
	{
		var xdisp = Math.round(xp);
		var ydisp = Math.round(yp);

		// find the area of overlap
		// calculate the start y pixel in source and target
		var srcY = 0;
		var destY = 0;
		if (ydisp>0) destY = ydisp;
		if (ydisp<0) srcY = -ydisp;

		// calculate the start x pixel in source and target
		var srcX = 0;
		var destX = 0;
		if (xdisp>0) destX = xdisp;
		if (xdisp<0) srcX = -xdisp;

		// calculate the height of the overlap
		var ydist = obj.height-destY;
		if(bitmap.height-srcY < ydist)
			ydist = bitmap.height-srcY;

		// calculate the width of the overlap
		var xdist = obj.width-destX;
		if(bitmap.width-srcX < xdist)
			xdist = bitmap.width-srcX;

		if ( ydist > 0 && xdist > 0)
		{
			for ( var y = 0; y < ydist; y++)
			{
				for(var xPos = 0; xPos < bitmap.width; xPos++)
				{
					if ( bitmap.GetPixel(xPos,srcY) >= 0 )
					{
						obj.SetPixel(xPos+xdisp,destY,bitmap.GetPixel(xPos,srcY));
					}
				}
				srcY++;
				destY++;
			}
		}
	}

	obj.WriteChar = function(char, charset, xPos,yPos,colour,xbig,ybig)
	{
		charSet.WriteChar(char,obj,xPos,yPos,colour,xbig,ybig);
	}

	obj.WriteString = function(text,charSet, xPos,yPos,colour,xbig,ybig)
	{
		charSet.WriteString(text,obj,xPos,yPos,colour,xbig,ybig);
	}

	obj.WriteStringCentered = function(text, charset, yPos,colour,xbig,ybig)
	{
		if (xbig == undefined) xbig = false;
		if ( xbig )
			charset.WriteString(text,obj,width/2-charset.GetWidth()/2*text.length*2,yPos, colour,xbig,ybig);
		else
			charset.WriteString(text,obj,width/2-charset.GetWidth()/2*text.length,yPos, colour,xbig,ybig);
	}

	obj.Clear(-1);

	return obj;
}

CharSet = function (width, height)
{
	if (width == undefined)
		width = 8;

	if (height == undefined)
		height = 8;

	var obj = {};
	var charData = {};

	obj.GetWidth = function ()
	{
		return width;
	}

	obj.GetHeight = function ()
	{
		return height;
	}
	obj.SetChar = function(char, chardata )
	{
		charData[char] = chardata;
	}

	obj.WriteChar = function(char,bitmap,xPos,yPos,colour,xbig,ybig)
	{
		if(typeof(xbig)==='undefined') xbig = false;
		if(typeof(ybig)==='undefined') ybig = false;
		var rowData = charData[char];
		if ( rowData != undefined )
		{
			for ( var row = 0; row < rowData.length; row++)
			{
				var line = rowData[row];
				for ( var charPos = 0; charPos < line.length; charPos++)
				{
					var chr = line.charAt(charPos);
					if ( chr != " " )
					{
						if ( !xbig && !ybig)
							bitmap.SetPixel(charPos+xPos, row+yPos, colour );

						if ( xbig && !ybig)
						{
							bitmap.SetPixel(charPos*2+xPos, row+yPos, colour );
							bitmap.SetPixel(charPos*2+xPos+1, row+yPos, colour );
						}
						if ( !xbig && ybig)
						{
							bitmap.SetPixel(charPos+xPos, row*2+yPos, colour );
							bitmap.SetPixel(charPos+xPos, row*2+yPos+1, colour );
						}
						if ( xbig && ybig)
						{
							bitmap.SetPixel(charPos*2+xPos, row*2+yPos, colour );
							bitmap.SetPixel(charPos*2+xPos+1, row*2+yPos, colour );
							bitmap.SetPixel(charPos*2+xPos, row*2+yPos+1, colour );
							bitmap.SetPixel(charPos*2+xPos+1, row*2+yPos+1, colour );
						}
					}
				}
			}
		}
	}

	obj.WriteString = function(text,bitmap,xPos,yPos,colour,xbig,ybig)
	{
		if(typeof(xbig)==='undefined') xbig = false;
		for ( var charPos = 0; charPos < text.length; charPos++)
		{
			var chr = text.charAt(charPos);
			if ( xbig)
				obj.WriteChar(chr,bitmap,xPos+charPos*2*width, yPos, colour,xbig,ybig );
			else
				obj.WriteChar(chr,bitmap,xPos+charPos*width, yPos, colour,xbig,ybig );
		}
	}

	return obj;
}

Sprite = function (xp,yp,costumeOrBitmap)
{
	if (costumeOrBitmap === null ) return null;
	var costume = costumeOrBitmap;
	if ( BaseBitmap.isPrototypeOf(costumeOrBitmap))
	{
		costume = Costume();
		costume.SetItem(0,costumeOrBitmap);
	}
	else
	{
		if ( !BaseCostume.isPrototypeOf(costumeOrBitmap)) return null;
	}
	var obj = {};

	var lifeSpan = -1;
	obj.died = false;
	obj.name = "";

	obj.pos = Vector(xp,yp);
	obj.vel = Vector(0,0);
	obj.costume = costume;
	obj.costumeAnimator = Animator([0]);
	obj.bitmap = costume.GetItem(0);
	obj.wrap = false;
	obj.collided = false;
	obj.collidedList = new Array();

	obj.SetLifeSpan = function(newLifeSpan)
	{
		lifeSpan = newLifeSpan;
	}

	obj.SetCostume = function(costume)
	{
		obj.costume = costume;
	}

	obj.SetWrapOn = function()
	{
		obj.wrap = true;
	}

	obj.SetWrapOff = function()
	{
		obj.wrap = false;
	}

	obj.SetAnimator = function(frameList)
	{
		obj.costumeAnimator = Animator(frameList);
	}

	obj.SetAnimatorBounce = function ()
	{
		obj.costumeAnimator.Bounce(obj.costume.Length());
	}

	obj.SetAnimatorLoop = function ()
	{
		obj.costumeAnimator.Loop(obj.costume.Length());
	}

	obj.SetAnimatorFrame = function(frame)
	{
		obj.costumeAnimator.Set(frame);
		obj.bitmap = obj.costume.GetItem(obj.costumeAnimator.Get());
	}

	obj.GetAnimatorFrame = function(frame)
	{
		return obj.costumeAnimator.Get();
	}

	obj.SetCostumeAnimator = function(animator)
	{
		obj.costumeAnimator = animator;
	}

	obj.SetAnimatorUpdateRate = function (updateRate)
	{
		obj.costumeAnimator.SetUpdateRate(updateRate);
	}

	obj.SetAnimatorStop = function ()
	{
		obj.costumeAnimator.Stop();
	}

	obj.SetMovement = function(xm,ym)
	{
		obj.vel.x = xm;
		obj.vel.y = ym;
		return obj;
	}

	obj.IsCollidedWith = function(targetSpriteName)
	{
		var target = GE.SpriteGet(targetSpriteName);

		if ( target == undefined )
			return false;

		// simple bounding box check will do for now
		if ( obj.pos.x < target.pos.x + target.bitmap.width
			&& obj.pos.x + obj.bitmap.width > target.pos.x
			&& obj.pos.y < target.pos.y + target.bitmap.height
			&& obj.pos.y + obj.bitmap.height > target.pos.y )
		{
//			return true;
			var collided = false;
			// now lets do a pixel perfect collision check
			// get the biggest left
			var boxLeftPos = obj.pos.x;
			if ( boxLeftPos < target.pos.x )
				boxLeftPos = target.pos.x;
			// get the smallest right
			var boxRightPos = obj.pos.x + obj.bitmap.width;
			if ( boxRightPos > target.pos.x + target.bitmap.width)
				boxRightPos = target.pos.x + target.bitmap.width;
			// get the biggest top
			var boxTopPos = obj.pos.y;
			if ( boxTopPos < target.pos.y )
				boxTopPos = target.pos.y;
			// get the smallest bottom
			var boxBottomPos = obj.pos.y + obj.bitmap.height;
			if ( boxBottomPos > target.pos.y + target.bitmap.height)
				boxBottomPos = target.pos.y + target.bitmap.height;

			var overlapWidth = boxRightPos - boxLeftPos;
			var overlapHeight = boxBottomPos - boxTopPos;

			var sx = Math.floor(boxLeftPos - obj.pos.x);
			var sy = Math.floor(boxTopPos - obj.pos.y);
			var tx = Math.floor(boxLeftPos - target.pos.x);
			var ty = Math.floor(boxTopPos - target.pos.y);
			for ( var y = 0; y < overlapHeight; y++)
			{
				for ( var x = 0; x < overlapWidth; x++ )
				{
					var p1 = obj.bitmap.GetPixel(sx+x,sy+y);
					var p2 = target.bitmap.GetPixel(tx+x,ty+y);
					if (p1 != -1 && p2 != -1 )
						collided = true;
				}
			}

			return collided;
		}

		return false;
	}

	obj.Update = function (scrWidth, scrHeight)
	{
		obj.pos.x += obj.vel.x;
		obj.pos.y += obj.vel.y;
		if ( obj.wrap )
		{
			if ( obj.pos.x < 0 )
				obj.pos.x += scrWidth;
			if ( obj.pos.y < 0 )
				obj.pos.y += scrHeight;
			if ( obj.pos.x >= scrWidth )
				obj.pos.x -= scrWidth;
			if ( obj.pos.y >= scrHeight )
				obj.pos.y -= scrHeight;
		}

		obj.costumeAnimator.Update();
		obj.bitmap = obj.costume.GetItem(obj.costumeAnimator.Get());
		obj.layerChanged = false;

		if ( lifeSpan != -1 )
		{
			lifeSpan--;
			if (lifeSpan == 0 )
				obj.died = true;
		}
	}

	return obj;
}

Vector = function(x,y)
{
	var obj = {};

	obj.x = x;
	obj.y = y;

	obj.Add = function(v1)
	{
		return Vector(obj.x+v1.x, obj.y+v1.y);
	}

	obj.Subtract = function(v1)
	{
		return Vector(obj.x-v1.x, obj.y-v1.y);
	}

	obj.Multiply = function(v1)
	{
		return Vector(obj.x*v1.x, obj.y*v1.y);
	}

	obj.Magnitude = function()
	{
		return Math.sqrt(obj.x*obj.x + obj.y*obj.y);
	}

	obj.Normalise = function()
	{
		var mag = obj.Magnitude();
		obj.x /= mag;
		obj.y /= mag;
	}

	obj.SetMagnitude = function(newMagnitude)
	{
		obj.Normalise();
		obj.x *= newMagnitude;
		obj.y *= newMagnitude;
	}

	obj.DotProduct = function(v)
	{
		return obj.x * v.x + obj.y * v.y;
	}

	obj.ScalarMultiply = function(scalar)
	{
		obj.x *= scalar;
		obj.y *= scalar;
	}

	return obj;
}

var BaseCostume = {};
Costume = function()
{
	var obj = Object.create(BaseCostume);

	obj.bitmaps = Array();

	obj.SetItem = function (frame, bitmap)
	{
		// try to set a frame to something other than a bitmap will result in nothing happening
		if ( !BaseBitmap.isPrototypeOf(bitmap)) return;
		obj.bitmaps[frame] = bitmap;
	};

	obj.SetItems = function (bitmaps,startFrame)
	{
		var frame = startFrame;
		for(var bitmapPos = 0; bitmapPos < bitmaps.length; bitmapPos++)
		{
			obj.SetItem(frame, bitmaps[bitmapPos]);
			frame++;
		}
	};

	obj.Length = function ()
	{
		return obj.bitmaps.length;
	}

	obj.GetItem = function (frame)
	{
		return obj.bitmaps[frame];
	}

	obj.Update = function()
	{
	}

	return obj;
}

Animator = function (frameList)
{
	var obj = {};
	obj.frame = 0;
	obj.updateRate = 1;
	obj.repeat = true;

	if (frameList == null )
	{
		obj.frameList = new Array();
	}
	else
	{
		obj.frameList = frameList;
	}
	var paused = false;

	obj.Bounce = function (frames)
	{
		var list = new Array();
		var f = 0;
		for ( var i = 0; i < frames; i++)
		{
			list[f] = i;
			f++;
		}
		for ( var i = frames-2; i > 0; i--)
		{
			list[f] = i;
			f++;
		}
		obj.frameList = list;
	}

	obj.Loop = function (frames)
	{
		var list = new Array();
		var f = 0;
		for ( var i = 0; i < frames; i++)
		{
			list[f] = i;
			f++;
		}
		obj.frameList = list;
	}

	obj.Update = function ()
	{
		if ( paused == false )
		{
			var prevFrame = obj.frame;
			obj.frame = (obj.frame + obj.updateRate)  % obj.frameList.length;
			if ( obj.frame < prevFrame )
				if ( !obj.repeat )
				{
					obj.frame = prevFrame;
					obj.Pause();
				}
		}
	}

	obj.Get = function()
	{
		return obj.frameList[Math.floor(obj.frame)];
	}

	obj.Set = function(frame)
	{
		obj.frame = Math.floor(frame % obj.frameList.length);
	}

	obj.SetUpdateRate = function(updateRate)
	{
		obj.updateRate = updateRate;
		obj.waitTime = 0;
	}

	obj.Start = function ()
	{
		frame = 0;
		paused = false;
	}

	obj.Resume = function ()
	{
		paused = false;
	}

	obj.Stop = function ()
	{
		frame = 0;
		paused = true;
	}

	obj.Pause = function ()
	{
		paused = true;
	}

	obj.Resume = function ()
	{
		paused = false;
	}

	return obj;
}

// Experimental code to create sounds.
// Once this is stable the GameEngine will assimilate it

var channels = new Array();
var soundOn = true;

function customSound(bitrate)
{
	this.bitrate = bitrate;
	this.angle = 0;
	this.customSoundData=[];
	this.totalDuration = 0;
	this.dataPos = 0;

	customSound.prototype.add = function(duration, startFreq, endFreq, startVol, endVol, randomFreq, randomRate, square )
	{
		var vol = startVol;
		var freq = startFreq;
		var angleInc = (2*Math.PI)/this.bitrate * freq;
		var freqRand = 0;
		var randomCount = 0;

		if ( randomFreq == undefined )
			randomFreq = 0;

		for ( var x=0; x < this.bitrate*duration; x++)
		{
			vol = (startVol + (endVol - startVol)*x/(this.bitrate*duration));
			this.customSoundData[this.dataPos] = Math.sin(this.angle) * vol;
			if (square )
				if ( this.customSoundData[this.dataPos]  < 0)
					this.customSoundData[this.dataPos] = -vol;
				else
					this.customSoundData[this.dataPos] = vol
			freq = startFreq + (endFreq - startFreq)*x/(this.bitrate*duration);

			if ( randomRate > 0 )
			{
				randomCount--;
				if ( randomCount <= 0 )
				{
					freqRand = Math.random()*randomFreq;
					randomCount=randomRate;
				}
			}

			angleInc = (2*Math.PI)/this.bitrate * (freq+freqRand);
			this.angle+=angleInc;
			if ( this.angle > Math.PI * 2 )
			{
				this.angle -= Math.PI * 2;
			}
			this.dataPos++;
		}
	}

	customSound.prototype.createSound = function(channel)
	{
		var n = this.customSoundData.length;
		var header = "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00********\x01\x00\x08\x00data****";

		// ChunkSize
		var numval = n + 36;
		header=insertLong(header,4,numval);

		numval = this.bitrate; // byterate
		header = insertLong(header,24,numval);

		  // BitRate
		numval = this.bitrate * 8; //44100;
		header = insertLong(header,28,numval);

		  // Subchunk2Size
		numval = n;
		header = insertLong(header,40,numval);

		// Output sound data
		for (var i = 0; i < n; ++i)
		{
			var charCode = Math.round(Math.min(127, Math.max(-127, this.customSoundData[i]))+127);
			header += String.fromCharCode(charCode);
		}

		var h = btoa(header);
		h = 'data:audio/wav;base64,' + h;

		var a = new Audio();
		a.src = h;
		if ( channels[channel] )
			channels[channel].pause();
		channels[channel] = a;
		return a;
	}
}

function soundPlay(channel,loop)
{
	if ( !soundOn )
		return;

	if ( loop )
		channels[channel].loop = loop;
	if (channels[channel].readyState < 1 )
		return;
	channels[channel].currentTime=0;
	channels[channel].play();
}

function soundStop(channel)
{
	if (channels[channel].readyState < 1 )
		return;
	channels[channel].pause();
}

function insertLong(inString, index, inValue)
{
	var retString = inString.substr(0,index);
	for (i = 0; i < 4; ++i) {
	  retString += String.fromCharCode(inValue & 255);
	  inValue = inValue >> 8;
	}
	retString += inString.substr(index+4);
	return retString;
}

GameSatePattern = function (data)
{
	var obj = {};
	var innerStates = new Array();
	var finished = false;
	var nextState;
	var doInit = true;

	obj.Draw = function()
	{
		for( var state in innerStates )
			if ( innerStates[state] )
				innerStates[state].Draw();

		if ( obj.DrawCustom )
			obj.DrawCustom();
	}

	obj.Update = function()
	{
		if ( doInit )
		{
			doInit = false;
			if ( obj.Init )
				obj.Init();
		}

		if ( obj.UpdateCustom)
			obj.UpdateCustom();

		if ( finished )
		{
			for( var state in innerStates )
			{
				if ( innerStates[state] )
				{
					innerStates[state].SetFinished();
					innerStates[state] = innerStates[state].Update();
				}
			}

			if ( obj.TearDown )
				obj.TearDown();

			if ( nextState)
				return nextState;

			return undefined;
		}
		else
			for( var state in innerStates )
				if ( innerStates[state] )
					innerStates[state]  = innerStates[state].Update();

		return this;
	}

	obj.AddInnerState=function(state)
	{
		innerStates.push(state);
	}

	obj.SetNextState=function(state)
	{
		nextState = state;
	}

	obj.SetFinished=function()
	{
		finished = true;
	}

	return obj;
}
// last piece of code is to start the game
init();
</script>

</html>

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required

*