One of the old arcade machines I played a lot was a Game called Asteroids. Several years ago I wrote ( almost completed ) a clone of the original. After all I have learnt since I decided to write a Asteroids style game as an 8bit game.
I only gave myself a week and I wanted to go for the experience of the game instead of a straight copy.
You only get three lives, the UFO appears more often. Rocks score very few points 10 for the big ones, 20 for the medium ones and 40 for the small. The big points are for killing a UFO which get you a tidy 500 points.
How much can you score ?

Have fun.

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

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

Canvas
{
	position:fixed;
}

/* make the video stretch to fill the screen in WebKit */
    :-webkit-full-screen #myCanvas {
      width: 100%;
      height: 100%;
  }

#counter
{
	position:absolute;
	bottom:0;
	right:0;
	color:#ffffff;
	display:inline;
}

#focus
{
	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_TILE = 10;
var LAYER_MENU = 10;
var LAYER_ROCKS = 3;
var LAYER_FLAME = 4;
var LAYER_PLAYER = 5;
var LAYER_SHOT = 6;
var LAYER_UFO = 7;
var LAYER_UFO_SHOT = 8;

var SOUND_SHOT = 0;	// have to create 5 to try to play whenever shots are fired quickly
var SOUND_ROCK_EXPLODE_L = 5;
var SOUND_ROCK_EXPLODE_M = 6;
var SOUND_ROCK_EXPLODE_S = 7;
var SOUND_UFO = 8			// reserve 4
var SOUND_UFO_HIT = 12
var SOUND_PLAYER_HIT = 13
var SOUND_PLAYER_THRUST = 14; // reserve 2 to try and keep sound playing without gaps
var SOUND_STATIC = 15;

var MAX_FLAMES = 20;
var MAX_SHOTS = 10;
var MAX_LARGE_ROCKS = 7;

var skipSplash = false;

var currentStage;

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

		// 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()
{
	if ( GE.GetKeyState(GE.KEY_P))
	{
		var c=document.getElementById('myCanvas');
		 window.location = c.toDataURL("image/png");
	}
	currentStage = currentStage.Update();
}

// 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.lives = 0;

	// graphics
	obj.player;
	obj.shots;
	obj.rocks;
	obj.flames;
	obj.UFO;

	return obj;
}

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

	return obj;
}

function PlayData()
{
	var obj = {};
	obj.finished = false;
	obj.scoreValue = 0;
	obj.lives = 0;
	obj.rocks;
	obj.playerData;
	obj.rocksData;
	obj.UFOData;

	return obj;
}

function PlayerData()
{
	var obj = {};
	obj.finished = false;
	obj.scoreValue = 0;
	obj.player;
	obj.shots;
	obj.nextShot;
	obj.lives;
	obj.inputLeft;
	obj.inputRight;
	obj.inputThrust;
	obj.inputShoot;

	return obj;
}

function RocksData()
{
	var obj = {};
	obj.scoreValue = 0;
	obj.rocks;

	return obj;
}

function UFOData()
{
	var obj = {};
	obj.scoreValue = 0;
	obj.UFO;
	obj.UFOshots;
	obj.nextUFOShot;

	return obj;
}

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

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

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

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

	obj.TearDown = function()
	{
		GE.Clear(0);
	}

	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()
	{
		obj.SetNextState(SplashLogo(data));

		soundStatic = new customSound(24000);
		soundStatic.add(5.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();
	}

	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.TearDown=function()
	{
		soundStop(SOUND_STATIC);
	}

	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("Rocks in Spaace",GE.CanvasHeight/2+32,15,false,true);
	}

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

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

	return obj;
}

function BuildGraphics(data)
{
	var obj = GameSatePattern(data);
	var frame = 0;
	var playerCostume;
	var bmrockl;
	var bmrockm;
	var bmrocks;

	obj.Init=function()
	{
		obj.SetNextState(MainMenu(data));

		var cs=Costume()
		var menuBitmap = Bitmap(8*18,16);
		menuBitmap.Clear(0);

		var blankBitmap = menuBitmap.Clone();

		menuBitmap.WriteString("Building Graphics",GE.CharSet(),4,4,15);
		cs.SetItem(0,menuBitmap);
		cs.SetItem(1,blankBitmap);
		var sp = Sprite((GE.CanvasWidth-menuBitmap.width)/2, (GE.CanvasHeight-8)/2, cs );
		sp.SetAnimatorLoop();
		sp.SetAnimatorUpdateRate(.1);
		GE.SpriteAdd("buildinggraphics", sp, LAYER_MENU);
		InitBackground();
		data.rocks = {};
		data.shots = {};
		data.flames = {};

		data.UFOshots = {};

		playerCostume = Costume();
		var bm = Bitmap(8,8);
		for ( var y = 1; y < 8; y++ )
		{
			bm.DrawLine(0,y,bm.width-1,Math.floor(bm.width/2),15);
			bm.DrawLine(0,2,0,bm.width-2,-1);
		}
		playerCostume.SetItem(0,bm);
		data.player = Sprite(GE.CanvasWidth/2, GE.CanvasHeight/2, playerCostume);
		data.player.wrap = true;

		bmrockl = CreateRockL();
		bmrockm = CreateRockM();
		bmrocks = CreateRockS();
	}

	function CreateRockL()
	{
		var bm = Bitmap(32,32);
		bm.DrawCircle(15.5,15.5,16,7);
		bm.DrawCircle(15.5,15.5,14,8);

		return bm;
	}

	function CreateRockM()
	{
		var cs = Costume();
		var bm = Bitmap(16,16);
		bm.DrawCircle(7.5,7.5,8,7);
		bm.DrawCircle(7.5,7.5,6,8);
		cs.SetItem(0,bm);

		bm = Bitmap(16,16);
		bm.DrawCircle(7.5,7.5,8,7);
		bm.DrawCircle(7.5,7.5,6,8);
		cs.SetItem(1,bm);

		return cs;
	}

	function CreateRockS()
	{
		var cs = Costume();
		var bm = Bitmap(8,8);
		bm.DrawCircle(3.5,3.5,4,7);
		bm.DrawCircle(3.5,3.5,2,8);

		cs.SetItem(0,bm);

		bm = Bitmap(8,8);
		bm.DrawCircle(3.5,3.5,4,7);
		bm.DrawCircle(3.5,3.5,2,8);
		cs.SetItem(1,bm);

		return cs;
	}

	obj.UpdateCustom=function()
	{
		if ( frame == 0 )
		{
			var cs = Costume();
			var bm = Bitmap(16,16);
//			bm.Clear(1);
			bm.DrawLine(6,4,15-6,4,13);
			bm.DrawLine(4,5,15-4,5,13);
			bm.DrawLine(3,6,15-3,6,13);

//			GE.DrawLine(1,3,15-1,3,13);
			bm.DrawRect(0,7,16,3,13);

			bm.DrawLine(4,10,15-4,10,13);
			bm.DrawLine(6,11,15-6,11,13);

//			bm.Clear(13);
			cs.SetItem(0,bm);
			sp = Sprite(0, 0,cs);
			sp.wrap = false;
			data.UFO = sp;
		}

		if ( frame > 35 && frame > MAX_FLAMES && frame > MAX_SHOTS && frame > MAX_LARGE_ROCKS*2)
		{
			data.player.SetAnimatorLoop();
			data.player.SetAnimatorStop();
			obj.SetFinished();
		}
		playerCostume.SetItem(frame,playerCostume.GetItem(0).RotateAngle(frame*10));

		var bm;
		var sp;

		if ( frame < MAX_FLAMES )
		{
			// create flames
			bm = Bitmap(2,2);
			bm.Clear(Math.floor(Math.random()*7)+1);
			sp = Sprite(0, 0,bm);
			sp.wrap = true;
//			GE.SpriteAdd("flames-"+frame,sp,LAYER_ROCKS);
			data.flames["flame-"+frame] = sp;
		}

		if ( frame < MAX_SHOTS )
		{
			// create shots
			bm = Bitmap(2,2);
			bm.SetPixel(1,1,10);
			bm.Clear(15);
			sp = Sprite(0, 0,bm);
			sp.wrap = true;
			sp.dieoncollision = true;
//			GE.SpriteAdd("shot-"+frame,sp,LAYER_SHOTS);
			data.shots["shot-"+frame] = sp;

			// create UFO shots
			bm = Bitmap(2,2);
			bm.SetPixel(1,1,10);
			bm.Clear(15);
			sp = Sprite(0, 0,bm);
			sp.wrap = true;
			sp.dieoncollision = true;
//			GE.SpriteAdd("UFOshot-"+frame,sp,LAYER_UFO_SHOTS);
			data.UFOshots["UFOshot-"+frame] = sp;
		}

		if ( frame < MAX_LARGE_ROCKS )
		{
			// create big asteroids
			sp = Sprite(Math.random()*GE.CanvasWidth, Math.random()*GE.CanvasHeight,bmrockl);
			sp.vel.x = Math.random()*1-.5;
			sp.vel.y = Math.random()*1-.5;
			sp.wrap = true;
			sp.died = true;
			data.rocks["rockl-"+frame] = sp;
		}
		if ( frame < MAX_LARGE_ROCKS*2 )
		{
			// create medium asteroids
			sp = Sprite(Math.random()*GE.CanvasWidth, Math.random()*GE.CanvasHeight,bmrockm.GetItem(0));
			sp.vel.x = Math.random()*2-1;
			sp.vel.y = Math.random()*2-1;
			sp.wrap = true;
			sp.died = true;
			data.rocks["rockm-"+frame+"-1"] = sp;

			sp = Sprite(Math.random()*GE.CanvasWidth, Math.random()*GE.CanvasHeight,bmrockm.GetItem(1));
			sp.vel.x = Math.random()*2-1;
			sp.vel.y = Math.random()*2-1;
			sp.wrap = true;
			sp.died = true;
			data.rocks["rockm-"+frame+"-2"] = sp;

			sp = Sprite(Math.random()*GE.CanvasWidth, Math.random()*GE.CanvasHeight,bmrocks.GetItem(0));
			sp.vel.x = Math.random()*4-2;
			sp.vel.y = Math.random()*4-2;
			sp.wrap = true;
			sp.died = true;
			data.rocks["rocks-"+frame+"-1-1"] = sp;

			sp = Sprite(Math.random()*GE.CanvasWidth, Math.random()*GE.CanvasHeight,bmrocks.GetItem(1));
			sp.vel.x = Math.random()*4-2;
			sp.vel.y = Math.random()*4-2;
			sp.wrap = true;
			sp.died = true;
			data.rocks["rocks-"+frame+"-2-1"] = sp;
		}
		frame++;
	}

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

	return obj;
}

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

	obj.Init = function ()
	{
		var title = "Rocks in Spaace";
		var textWidth = 15*16;
		var menuBitmap = Bitmap(textWidth+16,20);

		var msg = "SpaceBar or click to Begin"
		var width = msg.length*8;
		var menuBitmap2 = Bitmap(width+8,20);

		var menuBitmap3 = Bitmap(270,75);
		menuBitmap3.Clear(6+8);
		menuBitmap3.DrawRect(2,2,menuBitmap3.width-4, menuBitmap3.height-4,6);
		menuBitmap3.WriteString("Cursor Left    - Rotate Left",GE.CharSet(),20,10,15 );
		menuBitmap3.WriteString("Cursor Right   - Rotate Right",GE.CharSet(),20,25,15 );
		menuBitmap3.WriteString("Cursor Up or X - Thrust",GE.CharSet(),20,40,15 );
		menuBitmap3.WriteString("Z              - Fire",GE.CharSet(),20,55,15 );

		menuBitmap.DrawRect(0,0,menuBitmap.width, menuBitmap.height,2);
		var xoffset = (menuBitmap.width - textWidth)/2;
		menuBitmap.WriteString(title,GE.CharSet(),xoffset-2,2,8,true,true);
		menuBitmap.WriteString(title,GE.CharSet(),xoffset,4,14,true,true);

		var textColour = 15;

		menuBitmap2.DrawRect(0,0,menuBitmap2.width, menuBitmap2.height,1);
		menuBitmap2.WriteStringCentered(msg,GE.CharSet(),3,textColour,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, 200, menuBitmap2 );
		GE.SpriteAdd("MainMenu2", sp, LAYER_MENU);

		sp = Sprite((GE.CanvasWidth-menuBitmap3.width)/2, 70, menuBitmap3 );
		GE.SpriteAdd("MainMenu3", sp, LAYER_MENU);

//		DisplayScore(data);
	}

	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);

	obj.Init=function()
	{
		data.score = 0;

		data.playData = PlayData();
		data.playData.player = data.player;
		data.playData.shots = data.shots;
		data.playData.rocks = data.rocks;
		data.playData.flames = data.flames;
		data.playData.UFO = data.UFO;
		data.playData.UFOshots = data.UFOshots;

		obj.AddInnerState(PlayNewGame(data.playData));

		DisplayScore(data);

		// Hint :
		// duration, startFreq, endFreq, startVol, endVol, randomFreq, randomRate, square 

		sound = new customSound(24000);
		sound.add(.5, 2000, 1000, 100, 0, 500, 2,false );
		sound.createSound(SOUND_SHOT);
		sound.createSound(SOUND_SHOT+1);
		sound.createSound(SOUND_SHOT+2);
		sound.createSound(SOUND_SHOT+3);
		sound.createSound(SOUND_SHOT+4);

		sound = new customSound(24000);
		sound.add(.75, 0, 0, 100, 0, 1000,20,false );
		sound.createSound(SOUND_ROCK_EXPLODE_L);

		sound = new customSound(24000);
		sound.add(.6, 0, 0, 100, 0, 1000,10,false );
		sound.createSound(SOUND_ROCK_EXPLODE_M);

		sound = new customSound(24000);
		sound.add(.5, 0, 0, 100, 0, 1000,7,false );
		sound.createSound(SOUND_ROCK_EXPLODE_S);

		sound = new customSound(24000);
		sound.add(2, 2000, 2000, 50, 50, 4000,2,false );
		sound.createSound(SOUND_PLAYER_THRUST);
		sound.createSound(SOUND_PLAYER_THRUST+1);

		sound = new customSound(24000);
		sound.add(2.5, 0, 0, 100, 0, 1000,50,false );
		sound.createSound(SOUND_PLAYER_HIT);

		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.add(.1, 400, 1100, 50, 50, 0, 0,true );
		sound.add(.1, 1100, 400, 50, 50, 0, 0,true );
		sound.add(.1, 400, 1100, 50, 50, 0, 0,true );
		sound.add(.1, 1100, 400, 50, 50, 0, 0,true );
		sound.add(.1, 400, 1100, 50, 50, 0, 0,true );
		sound.add(.1, 1100, 400, 50, 50, 0, 0,true );
		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.createSound(SOUND_UFO+1);
	}

	obj.DrawCustom = function()
	{
		DisplayScore(data);
	}

	obj.UpdateCustom = function()
	{
		data.lives = data.playData.lives;

		if ( data.playData.finished )
			obj.SetFinished();
		obj.SetNextState(MainMenu(data));

		if ( data.playData.scoreValue != 0 )
		{
			data.score += data.playData.scoreValue;
			if ( data.score > data.hiscore )
				data.hiscore = data.score;
			data.playData.scoreValue = 0;
		}
	}

	return obj;
}

// the following are inner states to the play state

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

	obj.Init = function()
	{
		if ( GE.Tiltable())
			GE.TiltCalibrate();

		data.player.SetAnimatorFrame(27);
		GE.SpriteAdd("player",data.player,LAYER_PLAYER);
		obj.SetNextState(PlayGame(data));

		GE.CollisionRuleAdd(LAYER_ROCKS, LAYER_PLAYER);
		GE.CollisionRuleAdd(LAYER_ROCKS, LAYER_SHOT);

		GE.CollisionRuleAdd(LAYER_UFO_SHOT,LAYER_PLAYER);
		GE.CollisionRuleAdd(LAYER_UFO,LAYER_PLAYER);
		GE.CollisionRuleAdd(LAYER_UFO_SHOT,LAYER_SHOT);
		GE.CollisionRuleAdd(LAYER_UFO,LAYER_SHOT);

		GE.CollisionRuleAdd(LAYER_ROCKS,LAYER_UFO_SHOT);
		GE.CollisionRuleAdd(LAYER_ROCKS,LAYER_UFO);

		data.scoreValue = 0;

		obj.SetFinished();
	}

	return obj;
}

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

	var player;
	var firePressed;

	obj.Init=function()
	{
		obj.SetNextState(PlayGameOver(data));
		firePressed = false;

		data.playerData = PlayerData();
		data.playerData.shots = data.shots;
		data.playerData.flames = data.flames;
		data.playerData.player = data.player;
		obj.AddInnerState( PlayerInit(data.playerData) );

		data.rocksData = RocksData();
		data.rocksData.rocks = data.rocks; 

		obj.AddInnerState( RocksInit(data.rocksData) );

		data.UFOData = UFOData();
		data.UFOData.UFO = data.UFO;
		data.UFOData.UFOshots = data.UFOshots; 

		obj.AddInnerState( UFOInit(data.UFOData) );
	}

	obj.DrawCustom=function()
	{
	}

	obj.UpdateCustom=function()
	{
		data.lives = data.playerData.lives;

		if ( GE.GetKeyState(GE.KEY_Z) )
		{
			if ( !firePressed )
			{
				firePressed = true;
				data.playerData.inputShoot = true;
			}
		}
		else
		{
			firePressed = false;
		}

		if ( GE.GetKeyState(GE.KEY_UP) || GE.GetKeyState(GE.KEY_X) )
		{
			data.playerData.inputThrust = true;
		}

		if ( GE.GetKeyState(GE.KEY_LEFT) )
		{
			data.playerData.inputLeft = true;
		}

		if ( GE.GetKeyState(GE.KEY_RIGHT) )
		{
			data.playerData.inputRight = true;
		}

		if ( data.playerData.finished )
			obj.SetFinished();

		if ( data.rocksData.scoreValue != 0 )
		{
			data.scoreValue += data.rocksData.scoreValue;
			data.rocksData.scoreValue = 0;
		}

		if ( data.UFOData.scoreValue != 0 )
		{
			data.scoreValue += data.UFOData.scoreValue;
			data.UFOData.scoreValue = 0;
		}
	}

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(PlayerGetReady(data));
		data.lives = 3;
		data.scoreValue = 0;
		obj.SetFinished();
	}

	return obj;
}

function PlayerGetReady(data)
{
	var obj = GameSatePattern(data);
	var waitTimer = 4*30;

	obj.Init=function()
	{
		obj.SetNextState(PlayerPlay(data));

		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, (GE.CanvasHeight-8)/2, cs );
		sp.SetAnimatorLoop();
		sp.SetAnimatorUpdateRate(.1);
		GE.SpriteAdd("GetReady", sp, LAYER_MENU);
//		InitBackground();
		GE.SpriteAdd("player",data.player,LAYER_PLAYER);
	}

	obj.UpdateCustom=function()
	{
		waitTimer--;
		if ( waitTimer == 0 )
			obj.SetFinished();

	}

	obj.TearDown=function()
	{
		GE.SpriteDelete("GetReady");
		data.player.collided = false;
		data.player.died = false;
		data.player.pos.x = GE.CanvasWidth/2
		data.player.pos.y = GE.CanvasHeight/2
		data.player.vel.x = 0;
		data.player.vel.y = 0;
		data.player.SetAnimatorFrame(27);
		GE.SpriteAdd("player",data.player,LAYER_PLAYER);
	}

	return obj;
}

function PlayerPlay(data)
{
	var obj = GameSatePattern(data);
	var nextShotSound = SOUND_SHOT;

	var thrustSoundCounter = -1;

	obj.Init=function()
	{
		data.nextShot = 0;
		data.nextFlame = 0;
		obj.SetNextState(PlayerDied(data));

		data.inputLeft = false;
		data.inputRight = false;
		data.inputShoot = false;
		data.inputThrust = false;
	}

	obj.UpdateCustom=function()
	{
		if ( data.inputShoot )
		{
			var angle = data.player.GetAnimatorFrame() * 10;
			angle = angle * (Math.PI / 180 );

			var x = 3*Math.cos(angle);
			var y = 3*Math.sin(angle);
			data.nextShot = (data.nextShot+1)%MAX_SHOTS;
			var shotIndex = "shot-" + data.nextShot;
			var shot = data.shots[shotIndex];
			shot.pos.x = data.player.pos.x+4;
			shot.pos.y = data.player.pos.y+4;
			shot.vel.x = x;
			shot.vel.y = y;
			shot.died = false;
			shot.collided = false;
			shot.SetLifeSpan(90);
			GE.SpriteAdd ( shotIndex, shot, LAYER_SHOT);
			soundPlay(nextShotSound);
			nextShotSound++;
			if ( nextShotSound > SOUND_SHOT+4)
				nextShotSound = SOUND_SHOT;
		}

		if ( data.inputThrust )
		{
//			if ( channels[SOUND_PLAYER_THRUST].paused && !channels[SOUND_PLAYER_THRUST].ended && channels[SOUND_PLAYER_THRUST].currentTime == 0  )
//			soundPlay(SOUND_PLAYER_THRUST,false);
			if ( thrustSoundCounter % 60 == 0)
				soundPlay(SOUND_PLAYER_THRUST,false);
			if ( thrustSoundCounter % 60 == 30)
				soundPlay(SOUND_PLAYER_THRUST+1,false);
			thrustSoundCounter = (thrustSoundCounter + 1 ) % 60;

			var angle = data.player.GetAnimatorFrame() * 10;
			angle = angle * (Math.PI / 180 );

			var x = .1*Math.cos(angle);
			var y = .1*Math.sin(angle);
			data.player.vel.x += x;
			data.player.vel.y += y;

			angle += Math.random() * .5 -.25 + Math.PI;
			var speed = Math.random()*.5 + .5;
			x = speed*Math.cos(angle);
			y = speed*Math.sin(angle);
			data.nextFlame = (data.nextFlame+1)%MAX_FLAMES;
			var flameIndex = "flame-" + data.nextFlame;
			var flame = data.flames[flameIndex];
			flame.pos.x = data.player.pos.x+4;
			flame.pos.y = data.player.pos.y+4;
			flame.vel.x = data.player.vel.x + x;
			flame.vel.y = data.player.vel.y + y;
			flame.died = false;
			flame.collided = false;
			flame.SetLifeSpan(60);
			GE.SpriteAdd ( flameIndex, flame, LAYER_FLAME);
		}
		else
		{
			thrustSoundCounter = 0;
			soundStop(SOUND_PLAYER_THRUST);
			soundStop(SOUND_PLAYER_THRUST+1);
		}

		if ( data.inputLeft )
		{
			var frame = data.player.GetAnimatorFrame()-1;
			if (frame < 0 )
				frame += 36;
			data.player.SetAnimatorFrame(frame);
		}

		if ( data.inputRight )
		{
			var frame = data.player.GetAnimatorFrame()+1;
			if (frame > 35 )
				frame -= 36;
			data.player.SetAnimatorFrame(frame);
		}

		if ( data.player.collided )
		{
			data.player.died = true;
			soundPlay(SOUND_PLAYER_HIT);
			obj.SetFinished();
		}

//		for ( var shotIndex in data.shots )
//		{
//			var sp = data.shots[shotIndex];
//			if ( sp.died == false && sp.collided )
//			{
//				sp.died = true;
//			}
//		}

		data.inputLeft = false;
		data.inputRight = false;
		data.inputShoot = false;
		data.inputThrust = false;
	}

	obj.TearDown=function()
	{
	}

	return obj;
}

function PlayerDied(data)
{
	var obj = GameSatePattern(data);
	var waitTimer = 4*30;

	obj.Init=function()
	{
				soundStop(SOUND_PLAYER_THRUST);
				soundStop(SOUND_PLAYER_THRUST+1);
		data.lives--;
		if ( data.lives > 0 )
		{
			obj.SetNextState(PlayerGetReady(data));
		}
		else
		{
			data.finished = true;
			obj.SetNextState(undefined);
		}

		// Create explosion from the flame sprites
		for ( var f = 0; f < MAX_FLAMES; f++ )
		{
			angle = Math.random() * Math.PI*2;
			var speed = Math.random()*.5 + .1;
			x = speed*Math.cos(angle);
			y = speed*Math.sin(angle);
			var flameIndex = "flame-" + f;
			var flame = data.flames[flameIndex];
			flame.pos.x = data.player.pos.x+4;
			flame.pos.y = data.player.pos.y+4;
			flame.vel.x = data.player.vel.x/4 + x;
			flame.vel.y = data.player.vel.y/4 + y;
			flame.died = false;
			flame.collided = false;
			flame.SetLifeSpan(120);
			GE.SpriteAdd ( flameIndex, flame, LAYER_FLAME);
		}
	}

	obj.UpdateCustom=function()
	{
		waitTimer--;
		if ( waitTimer == 0 )
			obj.SetFinished();

	}

	obj.TearDown=function()
	{
	}

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(RocksCreateField(data));
		data.scoreValue = 0;
		for ( var rockIndex in data.rocks)
		{
			data.rocks[rockIndex].died = true;
		}
	}

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

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(RocksPlay(data));

		for ( var r =0; r < MAX_LARGE_ROCKS; r++ )
		{
			var name = "rockl-"+r;
			var rock = data.rocks[name];
			if ( Math.random() < .5 )
			{
				rock.pos.x = 0;
				rock.pos.y = Math.random() * GE.CanvasHeight;
			}
			else
			{
				rock.pos.x = Math.random() * GE.CanvasWidth;
				rock.pos.y = 0;
			}
			var angle = Math.random() * Math.PI * 2;
			rock.vel.x = Math.cos(angle)*.5;
			rock.vel.y = Math.sin(angle)*.5;
			rock.died = false;
			rock.collided = false;
			GE.SpriteAdd(name,rock,LAYER_ROCKS);
		}
	}

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

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

	obj.Init=function()
	{
		obj.SetNextState(RocksLevelComplete(data));
	}

	obj.UpdateCustom=function()
	{
		var rockCount = 0;
		for ( var rockIndex in data.rocks )
		{
			var sp = data.rocks[rockIndex];
			if ( !sp.died )
				rockCount++;
			if ( sp.died == false && sp.collided )
			{
				var name = rockIndex;
				var scoreValue = 0;

				if ( name[4] == "m" )
				{
					soundPlay(SOUND_ROCK_EXPLODE_M);

					scoreValue += 20;

					var newname = name.substr(0,4) + "s" + name.substr(5);
					var newsp = data.rocks[newname + "-1"];

					var angle = Math.random() * Math.PI * 2;
					newsp.vel.x = Math.cos(angle)*2;
					newsp.vel.y = Math.sin(angle)*2;

					newsp.pos.x = sp.pos.x;
					newsp.pos.y = sp.pos.y;
					newsp.died = false;
					newsp.collided = false;
					GE.SpriteAdd ( newname + "-1", newsp,LAYER_ROCKS);
				}
				if ( name[4] == "l" )
				{
					soundPlay(SOUND_ROCK_EXPLODE_L);

					scoreValue += 10;

					var newname = name.substr(0,4) + "m" + name.substr(5);
					var newsp = data.rocks[newname + "-1"];

					var angle = Math.random() * Math.PI * 2;
					newsp.vel.x = Math.cos(angle)*1;
					newsp.vel.y = Math.sin(angle)*1;

					newsp.pos.x = sp.pos.x;
					newsp.pos.y = sp.pos.y;

					newsp.died = false;
					newsp.collided = false;
					GE.SpriteAdd ( newname + "-1", newsp,LAYER_ROCKS);

					angle = Math.random() * Math.PI * 2;

					newsp = data.rocks[newname + "-2"];
					newsp.vel.x = Math.cos(angle)*1;
					newsp.vel.y = Math.sin(angle)*1;

					newsp.pos.x = sp.pos.x;
					newsp.pos.y = sp.pos.y;

					newsp.died = false;
					newsp.collided = false;
					GE.SpriteAdd ( newname + "-2", newsp,LAYER_ROCKS);
				}
				if ( name[4] == "s" )
				{
					soundPlay(SOUND_ROCK_EXPLODE_S);

					scoreValue += 40;
				}
				if ( sp.collided == LAYER_PLAYER || sp.collided == LAYER_SHOT )
					data.scoreValue += scoreValue;

				sp.died = true;
			}
		}
		if ( rockCount == 0 )
			obj.SetFinished();
	}

	return obj;
}

function RocksLevelComplete(data)
{
	var obj = GameSatePattern(data);
	var waitTimer = 4*30;

	obj.Init=function()
	{
		obj.SetNextState(RocksCreateField(data));
	}

	obj.UpdateCustom=function()
	{
		waitTimer--;
		if ( waitTimer == 0 )
			obj.SetFinished();

	}

	obj.TearDown=function()
	{
	}

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(UFOWait(data));
		data.scoreValue = 0;
		data.waitTimer = 0;
		data.nextUFOShot = 0;
	}

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

	return obj;
}

function UFOWait(data)
{
	var obj = GameSatePattern(data);
	var waitTimer = 30*10;

	obj.Init=function()
	{
		obj.SetNextState(UFOPlay(data));
	}

	obj.UpdateCustom=function()
	{
		waitTimer--;
		if ( waitTimer == 0 )
			obj.SetFinished();

	}

	obj.TearDown=function()
	{
	}

	return obj;
}

function UFOPlay(data)
{
	var obj = GameSatePattern(data);
	var soundCount = 0;
	var soundOffset = 0;
	var shotCounter = 0;

	obj.Init=function()
	{
		obj.SetNextState(UFOWait(data));
		if ( Math.random() > .5 )
		{
			data.UFO.pos.x = GE.CanvasWidth;
			data.UFO.vel.x = -1;
		}
		else
		{
			data.UFO.pos.x = 1;
			data.UFO.vel.x = 1;
		}
		data.UFO.pos.y = Math.floor(Math.random()*GE.CanvasHeight/2);
		data.UFO.vel.y = 0;
		data.UFO.collided = false;
		data.UFO.died = false;
		GE.SpriteAdd("UFO", data.UFO,LAYER_UFO);

		soundCount = 0;
	}

	obj.UpdateCustom=function()
	{
		if ( soundCount == 0 )
		{
			soundPlay(SOUND_UFO+soundOffset);
			soundOffset = (soundOffset + 1 ) % 2
		}
		soundCount = (soundCount+1 ) % 30;

		if ( data.UFO.vel.x < 0 )
		{
			if ( data.UFO.pos.x < 1) //-data.UFO.bitmap.width)
				obj.SetFinished();
		}
		else
		{
			if ( data.UFO.pos.x > GE.CanvasWidth-2) //-data.UFO.bitmap.width)
				obj.SetFinished();
		}
		if ( Math.random() > .95 )
		{
			data.UFO.vel.y = Math.round(Math.random()*3 ) - 2;
		}
		if ( data.UFO.vel.y < 0 && data.UFO.pos.y < -data.UFO.bitmap.height/2)
			data.UFO.pos.y += GE.CanvasHeight;
		if ( data.UFO.vel.y > 0 && data.UFO.pos.y > GE.CanvasHeight-data.UFO.bitmap.height/2)
			data.UFO.pos.y -= GE.CanvasHeight;

		shotCounter++;
		if ( shotCounter > 30 )
		{
			shotCounter=0;

			var angle = Math.random()*Math.PI*2;

			var x = 2*Math.cos(angle);
			var y = 2*Math.sin(angle);
			data.nextUFOShot = (data.nextUFOShot+1)%MAX_SHOTS;
			var shotIndex = "UFOshot-" + data.nextUFOShot;
			var shot = data.UFOshots[shotIndex];
			shot.pos.x = data.UFO.pos.x+8;
			shot.pos.y = data.UFO.pos.y+8;
			shot.vel.x = x;
			shot.vel.y = y;
			shot.died = false;
			shot.collided = false;
			shot.SetLifeSpan(90);
			GE.SpriteAdd ( shotIndex, shot, LAYER_UFO_SHOT);
		}

		if ( data.UFO.collided )
		{
			if ( data.UFO.collided == LAYER_PLAYER || data.UFO.collided == LAYER_SHOT )
				data.scoreValue += 500;

			data.UFO.died = true;
			obj.SetFinished();
		}
	}

	obj.TearDown=function()
	{
		soundStop(SOUND_UFO);
		soundStop(SOUND_UFO+1);
	}

	return obj;
}

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

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

		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, (GE.CanvasHeight-8)/2, 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");
	}

	return obj;
}

// helper functions
function DisplayScore(data)
{
	var tmpStr = "0" + data.lives;
	tmpStr = tmpStr.substring(tmpStr.length-1);
	GE.DrawRect(0,0,7*8,8,0);
	GE.WriteString("LIVES:"+tmpStr,0,0,15,false,false);

	var tmpStr = "000000" + data.score;
	tmpStr = tmpStr.substring(tmpStr.length-6);

	GE.DrawRect(10*8,0,12*8,8,0);
	GE.WriteString("SCORE:"+tmpStr,10*8,0,15,false,false);

	tmpStr = "000000" + data.hiscore;
	tmpStr = tmpStr.substring(tmpStr.length-6);
	GE.DrawRect(GE.CanvasWidth-11*8-32,0,15*8,8,0);
	GE.WriteString("HI-SCORE:"+tmpStr,GE.CanvasWidth-11*8-32,0,15,false,false);
}

function InitBackground(data)
{
//	return;
	for ( var s = 0; s < 250; s++ )
	{
		var x = Math.random() * GE.CanvasWidth;
		var y = Math.random() * GE.CanvasHeight;
		GE.DrawRect(x,y,1, 1,Math.floor(Math.random()*7)+8 );
	}
	for ( var s = 0; s < 25; s++ )
	{
		var x = Math.floor(Math.random() * GE.CanvasWidth);
		var y = Math.floor(Math.random() * GE.CanvasHeight);
		GE.DrawRect(x,y,2, 2,Math.floor(Math.random()*7+8 ));
	}
}

// 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;
	}

//	ctx.imageSmoothingEnabled = false;

	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);

		// experimental colours
		// mid black and dark primary
		colours[16] = TertiaryMix(colours[7],colours[7],32);
		colours[17] = TertiaryMix(colours[1],colours[1],64);
		colours[18] = TertiaryMix(colours[2],colours[2],64);
		colours[19] = TertiaryMix(colours[3],colours[3],64);
		// secondary
		colours[20] = TertiaryMix(colours[4],colours[4],64);
		colours[21] = TertiaryMix(colours[5],colours[5],64);
		colours[22] = TertiaryMix(colours[6],colours[6],64);

		// tertiary colours
		colours[23] = TertiaryMix(colours[1],colours[4],128);
		colours[24] = TertiaryMix(colours[1],colours[5],128);
//		colours[18] = TertiaryMix(colours[1],colours[6],128);

		colours[25] = TertiaryMix(colours[2],colours[4],128);
//		colours[20] = TertiaryMix(colours[2],colours[5],128);
		colours[26] = TertiaryMix(colours[2],colours[6],128);

//		colours[22] = TertiaryMix(colours[3],colours[4],128);
		colours[27] = TertiaryMix(colours[3],colours[5],128);
		colours[28] = TertiaryMix(colours[3],colours[6],128);

		colours[29] = TertiaryMix(colours[4],colours[5],128);
		colours[30] = TertiaryMix(colours[4],colours[6],128);

//		colours[28] = TertiaryMix(colours[5],colours[4],128);
//		colours[29] = TertiaryMix(colours[5],colours[6],128);

//		colours[30] = TertiaryMix(colours[6],colours[4],128);
		colours[31] = TertiaryMix(colours[6],colours[5],128);

		// bright
		colours[32] = TertiaryMix(colours[15],colours[15],192);
		colours[33] = TertiaryMix(colours[9],colours[9],192);
		colours[34] = TertiaryMix(colours[10],colours[10],192);
		colours[35] = TertiaryMix(colours[11],colours[11],192);
		// secondary
		colours[36] = TertiaryMix(colours[12],colours[12],192);
		colours[37] = TertiaryMix(colours[13],colours[13],192);
		colours[38] = TertiaryMix(colours[14],colours[14],192);

//		colours[31] = TertiaryMix(colours[7],colours[7],96);

		colours[39] = TertiaryMix(colours[9],colours[12],255);
		colours[40] = TertiaryMix(colours[9],colours[13],255);
//		colours[34] = TertiaryMix(colours[9],colours[14],255);

		colours[41] = TertiaryMix(colours[10],colours[12],255);
//		colours[36] = TertiaryMix(colours[10],colours[13],255);
		colours[42] = TertiaryMix(colours[10],colours[14],255);

//		colours[38] = TertiaryMix(colours[11],colours[12],255);
		colours[43] = TertiaryMix(colours[11],colours[13],255);
		colours[44] = TertiaryMix(colours[11],colours[14],255);

		colours[45] = TertiaryMix(colours[12],colours[13],255);
		colours[46] = TertiaryMix(colours[12],colours[14],255);

//		colours[44] = TertiaryMix(colours[13],colours[12],255);
//		colours[45] = TertiaryMix(colours[13],colours[14],255);

//		colours[46] = TertiaryMix(colours[14],colours[12],255);
		colours[47] = TertiaryMix(colours[14],colours[13],255);

//		colours[47] = TertiaryMix(colours[15],colours[15],192);

		function TertiaryMix (col1,col2,maxValue)
		{
			var red = col1.red + col2.red;
			var green = col1.green + col2.green;
			var blue = col1.blue + col2.blue;

			var br = red;
			if(green > br) br = green;
			if(blue > br) br = blue;

			red = Math.floor(red / br * maxValue);
			green = Math.floor(green / br * maxValue);
			blue = Math.floor(blue / br * maxValue);

			return Colour(red,green,blue);
		}
	}	

	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.font = "14px impact";
			ctx.fillStyle = "#FFFFFF";
			ctx.font = "Bold 9px monospace";
	//		ctx.font = "Normal 9px Arial";
			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 == 0 && charLine != "        ")
//					lineZeroBlank = false;

				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",[
			"******* ",
			"     *  ",
			"    *   ",
			"   *    ",
			"  *     ",
			" *      ",
			"******* ",
			"        "]);

	// [\]^_`abcdefg.....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;
			}
//			else
//			{
//				screenImageData.data[offset] = 0;
//				screenImageData.data[offset+1] = 0;
//				screenImageData.data[offset+2] = 0;
//			}
			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],r);
		}
	}

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

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

	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 bitmap = screenBitmap.Clone();
		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 )
//				bitmap.Paste(s.pos.x,s.pos.y, s.bitmap )
			}
		}
		obj.writeToCanvas(tempScreenBitmap);
//		obj.writeToCanvas(bitmap);
	}

	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)
	{
		SpriteDeleteForReal(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.GetSpritesInLayer = function(spriteLayer)
//	{
//		if ( spriteLayer )
//		{
//			if ( obj.layers[spriteLayer] )
//			{
//				return obj.layers[spriteLayer]
//			}
//		}
//		return null;
//	}

/*
	obj.WriteChar = function(char, charset, xPos,yPos,colour)
	{
		screenBitmap.WriteChar(char, charset, xPos,yPos,colour);
	}

	obj.WriteString = function(text,charset, xPos,yPos,colour)
	{
		screenBitmap.WriteString(text,charset, xPos,yPos,colour);
	}
*/
	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 -1;
	}

	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;

		obj.SetPixel(startX, startY, colour)
		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.Paste(0,0,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.RotateAngle = function (angleRad, centreX, centreY)
	{
		var angle = -(angleRad%360) * (Math.PI / 180 );
		var newBitmap = Bitmap(obj.width,obj.height);
		if ( centreX == undefined )
			centreX = (obj.width - 1) / 2;
		if ( centreY == undefined )
			if ( centreX == undefined )
				centreY = (obj.height - 1) / 2;
			else
				centreY = centreX;

		for(var yPos = 0; yPos < obj.height; yPos++)
		{
			for(var xPos = 0; xPos < obj.width; xPos++)
			{
				var sourceX = Math.round(Math.cos(angle) * ( xPos-centreX ) - Math.sin(angle) * (yPos-centreY) + centreX);
				var sourceY = Math.round(Math.sin(angle) * ( xPos-centreX ) + Math.cos(angle) * (yPos-centreY) + centreY);  

				newBitmap.SetPixel(xPos,yPos,obj.GetPixel(sourceX,sourceY));
			}
		}

		return newBitmap;
	}

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

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

//	for(var yPos = 0; yPos < obj.height; yPos++)
//	{
//		for(var xPos = 0; xPos < obj.width; xPos++)
//		{
//			newBitmap.SetPixel(xPos,yPos,obj.GetPixel(xPos,yPos));
//		}
//	}
		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++;
			}
		}

/*
		for(var yPos = 0; yPos < bitmap.height; yPos++)
		{
			if ( yPos +ydisp >= 0 && yPos+ydisp < obj.height)
			for(var xPos = 0; xPos < bitmap.width; xPos++)
			{
				if ( bitmap.GetPixel(xPos,yPos) >= 0 )
				obj.SetPixel(xPos+xdisp,yPos+ydisp,bitmap.GetPixel(xPos,yPos));
			}
		}
*/
	}

	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.dieoncollision = false;
	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()
	{
		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;
			var widthhalf = obj.bitmap.width/2;
			var heighthalf = obj.bitmap.height/2;
			if ( obj.pos.x < -widthhalf )
				obj.pos.x += scrWidth;
			if ( obj.pos.y < -heighthalf )
				obj.pos.y += scrHeight;
			if ( obj.pos.x >= scrWidth-widthhalf )
				obj.pos.x -= scrWidth;
			if ( obj.pos.y >= scrHeight-heighthalf )
				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;
		}

		if ( obj.collided )
			if ( obj.dieoncollision == true )
				if (!obj.died )
					obj.died = true;
	}

//	obj.Draw = function ()
//	{
//
//	}

	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
	//      insertLong(n);
		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 o= document.getElementById('output');
		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].pause();
	channels[channel].currentTime=0;
	channels[channel].play();
}

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

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