Its a puzzle game thats appeared recently and seems to have that certain something.
I figured its something I could write quite quickly…So I did.

This should work on mobiles or tablets as there is no sound. I also implemented rudimentary swipe logic.

Have fun.

<html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
	
	<title>2048</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_TILE = 1;
var LAYER_MENU = 1;

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 = GameInitBackground();
		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()
{
	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;
	
	return obj;
}

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

function PlayData()
{
	var obj = {};
	obj.finished = false;
	obj.tilesMoved = false;
	obj.tileSet;
	obj.tileSetUndo;
	obj.scoreValue = 0;
	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;

	obj.UpdateCustom = function()
	{
		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.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("2048",GE.CanvasHeight/2+32,15,false,true);
	}

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

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

	obj.TearDown=function()
	{
		GE.DrawRect(0,0,GE.CanvasWidth, GE.CanvasHeight,0);
	}

	return obj;
}

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

		GE.DrawRect(18,18,50*4+4,50*4+4,8);

		// draw tiles for score and hi score
		var scoreXPos = 18+50*4+2+8;
		GE.DrawRect(scoreXPos,18+1,75,75-2,2);
		GE.DrawRect(scoreXPos+1,18,75-2,75,2);
		
		GE.DrawRect(scoreXPos,18+80+1,75,75-2,2);
		GE.DrawRect(scoreXPos+1,18+80,75-2,75,2);

		GE.WriteString("SCORE",scoreXPos + 16,18+4,14,false,true);
		GE.WriteString("HI-SCORE",scoreXPos + 5,98+4,14,false,true);
	
		// restart
		GE.DrawRect(scoreXPos,180+1,75,16-2,2);
		GE.DrawRect(scoreXPos+1,180,75-2,16,2);
		GE.WriteString("Undo",scoreXPos + 24,180+4,14,false,false);
		//redo
		GE.DrawRect(scoreXPos,205+1,75,16-2,2);
		GE.DrawRect(scoreXPos+1,205,75-2,16,2);
		GE.WriteString("Restart",scoreXPos + 12,205+4,14,false,false);
		
		obj.SetNextState(MainMenu(data));
		obj.SetFinished();
	}

	return obj;
}

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

		var menuBitmap = Bitmap(204,204);

		menuBitmap.DrawRect(0,0,menuBitmap.width, menuBitmap.height,7);

		menuBitmap.WriteStringCentered("2048",GE.CharSet(),4,0,true,true);

		var textColour = 0;
		menuBitmap.WriteString("Instructions",GE.CharSet(),4,30,textColour);
		menuBitmap.WriteString("============",GE.CharSet(),4,40,textColour);
		menuBitmap.WriteString("Slide tiles",GE.CharSet(),4,50,textColour);
		menuBitmap.WriteString("Combine same tiles",GE.CharSet(),4,60,textColour);
		menuBitmap.WriteString("Reach 2048",GE.CharSet(),4,70,textColour);
		menuBitmap.WriteString("Simple!",GE.CharSet(),4,80,textColour);
		menuBitmap.WriteString("Controls",GE.CharSet(),4,100,textColour);
		menuBitmap.WriteString("========",GE.CharSet(),4,110,textColour);
		menuBitmap.WriteString("Cursor keys",GE.CharSet(),4,120,textColour);
		menuBitmap.WriteString("Swipe",GE.CharSet(),4,130,textColour);

		menuBitmap.WriteStringCentered("Press Space, Tap or click",GE.CharSet(),160,textColour,false,true);
		menuBitmap.WriteStringCentered("to Begin",GE.CharSet(),180,textColour,false,true);

		var sp = Sprite(18, 18, menuBitmap );
		GE.SpriteAdd("MainMenu", sp, LAYER_MENU);
	}

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

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

	return obj;
}

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

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

		obj.SetNextState(MainMenu(data));
	}

	obj.UpdateCustom = function()
	{
		if ( playData.finished )
			obj.SetFinished();
		
		if ( playData.scoreValue != 0 )
		{
			data.score += playData.scoreValue;
			playData.scoreValue = 0;
			if ( data.score > data.hiscore )
				data.hiscore = data.score;
			DisplayScore(data);
		}
	}

	return obj;
}

// the following are inner states to the play state

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

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

	return obj;
}

var TILES_TOP = 20;
var TILES_LEFT = 20;
var TILE_SIZE = 50;

function boardCell(x,y)
{
	var obj = {};
	obj.costumeIndex = 0;
	obj.merged = false;
	var sprite = Sprite(TILES_LEFT + TILE_SIZE * x, TILES_TOP + TILE_SIZE * y, CreateTilesCostume());
	GE.SpriteAdd("Tile:"+x+"/"+y,sprite,1);

	obj.SetSprite=function(index)
	{
		sprite.SetAnimator([index]);
		sprite.pos.x = TILES_LEFT + TILE_SIZE * x;
		sprite.pos.y = TILES_TOP + TILE_SIZE * y;
		obj.costumeIndex = index;
	}

	return obj;
}

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

	obj.Init = function()
	{
		data.scoreValue = 0;
		data.tilesMoved = false;
		data.tileSet = new Array();
		for ( var y = 0; y < 4; y++ )
		{
			var tileRow = new Array();
			for ( var x=0; x < 4; x++ )
			{
				tileRow.push(boardCell(x,y));
			}
			data.tileSet.push(tileRow);
		}

		AddTile(data.tileSet)

		// Create the undo tileset as well
		data.tileSetUndo = new Array();
		for ( var y = 0; y < 4; y++ )
		{
			var tileRow = new Array();
			for ( var x=0; x < 4; x++ )
			{
				tileRow.push(data.tileSet[y][x].costumeIndex);
			}
			data.tileSetUndo.push(tileRow);
		}



		if ( GE.Tiltable())
			GE.TiltCalibrate();
			
		obj.SetNextState(PlayGame(data));
		obj.SetFinished();

	}

	function AddTile(tileSet)
	{
		var counter = Math.floor(Math.random()*16)+1;

		var xp=0;
		var yp=0;
		var available = 0;
		var found = false;
		
		for ( y = 0; y < 4; y++ )
		{
			for ( x = 0; x < 4; x++ )
			{
				if ( tileSet[y][x].costumeIndex == 0 )
					available++;
			}
		}
		
		if (available == 0 )
			return false;

		while (!found)
		{
			if ( tileSet[yp][xp].costumeIndex == 0 )
			{
				counter--;
				if ( counter == 0 )
				{
					found=true;
					break;
				}
			}

			xp++;
			if ( xp > 3 )
			{
				xp=0;
				yp++;
				if ( yp > 3 )
					yp = 0;
			}
		}

		tileSet[yp][xp].SetSprite(1);
		return true;
	}
	
	return obj;
}

function PlayGame(data)
{
	var obj = GameSatePattern(data);
	var player;
	var shot;
	var aKeyPressed = true;
	var undoButton = Vector (18+50*4+2+8,180);
	var restartButton = Vector (18+50*4+2+8,205);
	

//	obj.AddInnerState(PlayerPlay(data.playerData));

	function AddTile(tileSet)
	{
		var counter = Math.floor(Math.random()*16)+1;

		var xp=0;
		var yp=0;
		var available = 0;
		var found = false;
		
		for ( y = 0; y < 4; y++ )
		{
			for ( x = 0; x < 4; x++ )
			{
				if ( tileSet[y][x].costumeIndex == 0 )
					available++;
			}
		}
		
		if (available == 0 )
			return false;

		while (!found)
		{
			if ( tileSet[yp][xp].costumeIndex == 0 )
			{
				counter--;
				if ( counter == 0 )
				{
					found=true;
					break;
				}
			}

			xp++;
			if ( xp > 3 )
			{
				xp=0;
				yp++;
				if ( yp > 3 )
					yp = 0;
			}
		}

		tileSet[yp][xp].SetSprite(1);
		return true;
	}
	
	obj.Init=function()
	{
		if ( data.tilesMoved )
		{
			if ( AddTile(data.tileSet))
			{
				// if all tiles are onscreen
				var tileCount = 0;
				for ( var y =0; y < 4; y++ )
				{
					for ( var x = 0; x < 4; x++ )
					{
						if ( data.tileSet[y][x].costumeIndex != 0 )
							tileCount++
					}
				}
				if ( tileCount == 16 )
				{
					// and no move is available
					var moveAvailable = false;
					for ( var y =0; y < 4; y++ )
					{
						for ( var x = 0; x < 4; x++ )
						{
							if ( y < 3 )
								if ( data.tileSet[y][x].costumeIndex == data.tileSet[y+1][x].costumeIndex )
									moveAvailable = true;
							if ( x < 3 )
								if ( data.tileSet[y][x].costumeIndex == data.tileSet[y][x+1].costumeIndex )
									moveAvailable = true;
						}
					}
					
					// then end the game
					if ( !moveAvailable )
					{
						obj.SetNextState(PlayGameOver(data));
						obj.SetFinished();
					}
				}
			}
		}
	}

	var PRESSED_NONE = 0;
	var PRESSED_UP = 1;
	var PRESSED_DOWN = 2;
	var PRESSED_LEFT = 3;
	var PRESSED_RIGHT = 4;
	var PRESSED_UNDO = 5;
	var PRESSED_RESTART = 6;

	function Control()
	{
		if ( aKeyPressed )
		{
			if ( !GE.GetKeyState(GE.KEY_UP) 
				&& !GE.GetKeyState(GE.KEY_DOWN) 
				&& !GE.GetKeyState(GE.KEY_LEFT) 
				&& !GE.GetKeyState(GE.KEY_RIGHT)
				&& !GE.GetKeyState(GE.KEY_U)
				&& !GE.GetKeyState(GE.KEY_R) )
				aKeyPressed = false;
		}
		else
		{
			if ( GE.GetKeyState(GE.KEY_UP))
			{
				aKeyPressed = true;
				return PRESSED_UP;
			}
			if ( GE.GetKeyState(GE.KEY_DOWN))
			{
				aKeyPressed = true;
				return PRESSED_DOWN;
			}

			if ( GE.GetKeyState(GE.KEY_LEFT))
			{
				aKeyPressed = true;
				return PRESSED_LEFT;
			}

			if ( GE.GetKeyState(GE.KEY_RIGHT))
			{
				aKeyPressed = true;
				return PRESSED_RIGHT;
			}
			if ( GE.mouseUnclicked )
			{
				var xdist = Math.abs(GE.mouseSwipe.x);
				var ydist = Math.abs(GE.mouseSwipe.y);
				if ( xdist > ydist )
				{
					if ( GE.mouseSwipe.x < -20 )
						return PRESSED_LEFT;
					if ( GE.mouseSwipe.x > 20 )
						return PRESSED_RIGHT;
				}
				else
				{
					if ( GE.mouseSwipe.y < -20 )
						return PRESSED_UP;
					if ( GE.mouseSwipe.y > 20 )
						return PRESSED_DOWN;
				}
			}

			if ( GE.GetKeyState(GE.KEY_U ) )
			{
				aKeyPressed = true;
				return PRESSED_UNDO;
			}

			if ( GE.GetKeyState(GE.KEY_R ) )
			{
				aKeyPressed = true;
				return PRESSED_RESTART;
			}

			if ( GE.mouseClicked )
			{
				if ( GE.mousePos.x >= undoButton.x && GE.mousePos.y >= undoButton.y 
					&& GE.mousePos.x <= undoButton.x+75 && GE.mousePos.y <= undoButton.y+20)
				{
					return PRESSED_UNDO;
				}
				if ( GE.mousePos.x >= restartButton.x && GE.mousePos.y >= restartButton.y 
					&& GE.mousePos.x <= restartButton.x+75 && GE.mousePos.y <= restartButton.y+20)
				{
					return PRESSED_RESTART;
				}
			}

		}
		return PRESSED_NONE;
	}
	
	obj.UpdateCustom=function()
	{
		var control = Control();

		if ( control == PRESSED_NONE )
			return;

		if ( control == PRESSED_UNDO )
		{
			obj.SetNextState(PlayUndo(data));
			obj.SetFinished();
			return;
		}

		if ( control == PRESSED_RESTART )
		{
			obj.SetFinished();
			data.finished = true;
			return;
		}

		// take a copy of the tileset before we move
		for ( var y = 0; y < 4; y++ )
		{
			for ( var x = 0; x < 4; x++ )
			{
				data.tileSetUndo[y][x] = data.tileSet[y][x].costumeIndex;
			}
		}

		if ( control == PRESSED_UP)
		{
			obj.SetNextState(PlayRollUp(data));
			obj.SetFinished();
		}
		if ( control == PRESSED_DOWN)
		{
			obj.SetNextState(PlayRollDown(data));
			obj.SetFinished();
		}
		if ( control == PRESSED_LEFT)
		{
			obj.SetNextState(PlayRollLeft(data));
			obj.SetFinished();
		}
		if ( control == PRESSED_RIGHT)
		{
			obj.SetNextState(PlayRollRight(data));
			obj.SetFinished();
		}
	}

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(PlayGame(data));
		
		for ( var y = 0; y < 4; y++ )
		{
			for ( var x = 0; x < 4; x++ )
			{
				data.tileSet[y][x].SetSprite(data.tileSetUndo[y][x]);
			}
		}
		data.tilesMoved = false;
	}

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

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(PlayGame(data));
		
		for ( var y = 0; y < 4; y++ )
		{
			for ( var x = 0; x < 4; x++ )
			{
				data.tileSet[y][x].merged = false;
			}
		}
		data.tilesMoved = false;
	}

	obj.UpdateCustom=function()
	{
		var tileSet = data.tileSet;
		var tileMoved = false;
		for ( var x = 0; x < 4; x++ )
		{
			for ( var y = 1; y < 4; y++ )
			{
				if ( tileSet[y][x].costumeIndex != 0 && !tileSet[y][x].merged )
				{
					if ( tileSet[y-1][x].costumeIndex == 0 )
					{
						tileSet[y-1][x].SetSprite(tileSet[y][x].costumeIndex);
						tileSet[y][x].SetSprite(0);
						tileMoved = true;
					}
					else
					{
						if ( tileSet[y][x].costumeIndex == tileSet[y-1][x].costumeIndex )
						{
							tileSet[y-1][x].SetSprite(tileSet[y][x].costumeIndex+1);
							tileSet[y][x].SetSprite(0);
							tileMoved = true;
							tileSet[y-1][x].merged = true;
							data.scoreValue += 1 << tileSet[y-1][x].costumeIndex;
						}
					}
				}
			}
		}
		if ( tileMoved )
			data.tilesMoved = true;

		if ( !tileMoved )
			obj.SetFinished();
	}

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(PlayGame(data));
		
		for ( var y = 0; y < 4; y++ )
		{
			for ( var x = 0; x < 4; x++ )
			{
				data.tileSet[y][x].merged = false;
			}
		}
		data.tilesMoved = false;
	}

	obj.UpdateCustom=function()
	{
		var tileSet = data.tileSet;
		var tileMoved = false;
		for ( var x = 0; x < 4; x++ )
		{
			for ( var y = 2; y >= 0; y-- )
			{
				if ( tileSet[y][x].costumeIndex != 0 && !tileSet[y][x].merged )
				{
					if ( tileSet[y+1][x].costumeIndex == 0 )
					{
						tileSet[y+1][x].SetSprite(tileSet[y][x].costumeIndex);
						tileSet[y][x].SetSprite(0);
						tileMoved = true;
					}
					else
					{
						if ( tileSet[y][x].costumeIndex == tileSet[y+1][x].costumeIndex )
						{
							tileSet[y+1][x].SetSprite(tileSet[y][x].costumeIndex+1);
							tileSet[y][x].SetSprite(0);
							tileMoved = true;
							tileSet[y+1][x].merged = true;
							data.scoreValue += 1 << tileSet[y+1][x].costumeIndex;
						}
					}
				}
			}
		}
		if ( tileMoved )
			data.tilesMoved = true;

			if ( !tileMoved )
			obj.SetFinished();
	}

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(PlayGame(data));
		
		for ( var x = 0; x < 4; x++ )
		{
			for ( var y = 0; y < 4; y++ )
			{
				data.tileSet[y][x].merged = false;
			}
		}
		data.tilesMoved = false;
	}

	obj.UpdateCustom=function()
	{
		var tileSet = data.tileSet;
		var tileMoved = false;
		for ( var y = 0; y < 4; y++ )
		{
			for ( var x = 1; x < 4; x++ )
			{
				if ( tileSet[y][x].costumeIndex != 0 && !tileSet[y][x].merged )
				{
					if ( tileSet[y][x-1].costumeIndex == 0 )
					{
						tileSet[y][x-1].SetSprite(tileSet[y][x].costumeIndex);
						tileSet[y][x].SetSprite(0);
						tileMoved = true;
					}
					else
					{
						if ( tileSet[y][x].costumeIndex == tileSet[y][x-1].costumeIndex )
						{
							tileSet[y][x-1].SetSprite(tileSet[y][x].costumeIndex+1);
							tileSet[y][x].SetSprite(0);
							tileMoved = true;
							tileSet[y][x-1].merged = true;
							data.scoreValue += 1 << tileSet[y][x-1].costumeIndex;
						}
					}
				}
			}
		}
		if ( tileMoved )
			data.tilesMoved = true;

			if ( !tileMoved )
			obj.SetFinished();
	}

	return obj;
}

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

	obj.Init=function()
	{
		obj.SetNextState(PlayGame(data));
		
		for ( var x = 0; x < 4; x++ )
		{
			for ( var y = 0; y < 4; y++ )
			{
				data.tileSet[y][x].merged = false;
			}
		}
		data.tilesMoved = false;
	}

	obj.UpdateCustom=function()
	{
		var tileSet = data.tileSet;
		var tileMoved = false;
		for ( var y = 0; y < 4; y++ )
		{
			for ( var x = 2; x >= 0; x-- )
			{
				if ( tileSet[y][x].costumeIndex != 0 && !tileSet[y][x].merged )
				{
					if ( tileSet[y][x+1].costumeIndex == 0 )
					{
						tileSet[y][x+1].SetSprite(tileSet[y][x].costumeIndex);
						tileSet[y][x].SetSprite(0);
						tileMoved = true;
					}
					else
					{
						if ( tileSet[y][x].costumeIndex == tileSet[y][x+1].costumeIndex )
						{
							tileSet[y][x+1].SetSprite(tileSet[y][x].costumeIndex+1);
							tileSet[y][x].SetSprite(0);
							tileMoved = true;
							tileSet[y][x+1].merged = true;
							data.scoreValue += 1 << tileSet[y][x+1].costumeIndex;
						}
					}
				}
			}
		}
		if ( tileMoved )
			data.tilesMoved = true;

			if ( !tileMoved )
			obj.SetFinished();
	}

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

	return obj;
}

// helper functions
function DisplayScore(data)
{
	var scoreXPos = 18+50*4+2+8;

	var tmpStr = "00000" + data.score;
	tmpStr = tmpStr.substring(tmpStr.length-5);
	GE.DrawRect(scoreXPos + 16,18+4+30,5*8,16,2);
	GE.WriteString(tmpStr,scoreXPos + 16,18+4+30,15,false,true);

	tmpStr = "00000" + data.hiscore;
	tmpStr = tmpStr.substring(tmpStr.length-5);
	GE.DrawRect(scoreXPos + 16,98+4+30,5*8,16,2);
	GE.WriteString(tmpStr,scoreXPos + 16,98+4+30,15,false,true);
}

function CreateTilesCostume()
{
	var cs=Costume();

	var bm = Bitmap(48,48);
	cs.SetItem(0,bm);

var scIndex = 1;
for ( var shift=1; shift <13; shift++ )
{
	var num = 1 << shift;
	var bm = Bitmap(48,48);
	bm.DrawRect(2,0,44,48,(scIndex & 7)+1);
	bm.DrawRect(0,2,48,44,(scIndex & 7)+1);
	bm.WriteStringCentered(""+num,GE.CharSet(),18,15,false,true);
	cs.SetItem(scIndex,bm);
	scIndex++;
}


	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);
		
		// 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]);
		}
	}
	
	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 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)
	{
		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.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 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.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.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.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;
		}
	}

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

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

*