Quiet a few years ago there was a game that caught my attention. It’s called GimmieFrictionBaby.
The game has just one control and that is a fire button.
Its not fast paced but strangely addictive.

Search for the name and you will find the original.

This is my simple copy.

Enjoy! and if you don’t enjoy then take the code and make it enjoyable.

<html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
	
	<title>Gimmie Friction Baby</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>
	<canvas id="myCanvas">Sorry... your browser does not support the canvas element.</canvas>
	<p id="counter">counter</p>
</body>
<script>

// Description : This is a clone of the winner of a "Casual Gampley Design Competition"
// "Gimme Friction Baby" was written by Wouter Visser of The Netherlands

// 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 currentStage;
init = function()
{
	var canvas = document.getElementById('myCanvas');
	if (canvas.getContext)
	{
		// got to have the game engine 
		GE = GameEngine16Colour(canvas,update,draw);
		GE.PortraitMode();
		currentStage = StageGame().Init();
//		currentStage = StageGame().MainMenu();
		
		// when the window resizes we want to resize the canvas
		window.onresize = function() { doResize(); };
		doResize();
	}
}

// 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.update();
	if ( currentStage.finished )
		currentStage = currentStage.nextStage();
}

// This just makes sure the canvas is as big as possible
// given the window size.
function doResize()
{
	var canvas = document.getElementById('myCanvas');
	var screenHeight = window.innerHeight;
	var screenWidth = window.innerWidth;
	if ( GE.CanvasWidth > GE.CanvasHeight)
	{
		if (screenWidth*3/4 < screenHeight)
		{
			screenHeight = Math.floor(screenWidth*3/4);
		}
		else
		{
			screenWidth = Math.floor(screenHeight*4/3);
		}
	}
	else
	{
		if (screenHeight*3/4 < screenWidth)
		{
			screenWidth = Math.floor(screenHeight*3/4);
		}
		else
		{
			screenHeight = Math.floor(screenWidth*4/3);
		}
	}
	canvas.style.height = screenHeight+"px";
	canvas.style.width = screenWidth+"px";
	canvas.style.marginLeft = (window.innerWidth-screenWidth)/2;
	canvas.style.marginTop = Math.floor((window.innerHeight-screenHeight)/2)+1;
	
	// fudge to remove adddress bar on mobile browsers
	window.scrollTo(0, 0);
	window.scrollTo(0, 1);
}

var soundBounce;

StageGame = function ()
{
	var obj = {};
	obj.finished = false;
	var data;
	
	obj.Data = function()
	{
		var obj = {};
		obj.score = 0;
		obj.hiscore = 0;
		obj.useKeyboard = false;
		obj.cannonVectors = new Array();
		obj.ballNum = 0;
		
		return obj;
	}

	obj.Init = function (dataIn)
	{
		var obj = {};
		var data;
		
		if ( dataIn == undefined )
			data = new Array();
		else
			data = dataIn;
		
		data["game"] = StageGame().Data();

		obj.finished = false;

		obj.init = function()
		{
			data["game"].score = 0;
			data["game"].hiscore = 0;
			data["game"].cannonVectors = Array();
			data["game"].ballsInCourt = Array();
			data["game"].ballNum = 0;
		}

		obj.update = function()
		{
			obj.finished = true;
		}
		obj.draw = function()
		{
		}

		obj.nextStage = function()
		{
			return StageSplash(data); 
//			return MainMenu(data);
		}

		obj.init();

		return obj;
	}

	function StageSplash (dataIn)
	{
		var obj = {};
		obj.finished = false;
		var currentStage;
		var data = dataIn;

		obj.init=function()
		{
			data = dataIn;
			if ( dataIn == undefined )
				data = new Array();
			currentStage = Static(data);
			currentStage.init();
		}

		obj.draw = function()
		{
			currentStage.draw();
		}

		obj.update = function()
		{
			currentStage.update();
			if ( currentStage.finished )
			{
				if ( currentStage.nextStage() == null )
					obj.finished = true;
				else
					currentStage = currentStage.nextStage();
			}
		}

		obj.nextStage = function()
		{
			return InitGame(data);
		}

		// Splash Stages

		function Static(data)
		{
			var obj = {};

			var startTime;
			obj.finished = false;
			var soundStatic = null;
			var soundBeep = null;
			var started = false;
			
			obj.init=function()
			{
				startTime = new Date().getTime();
				soundStatic = new customSound(8000);
				soundStatic.add(2.5, 4000, 4000, 80, 80, 1, 1 );
				soundStatic.createSound().play();

				soundBeep = new customSound(8000);
				soundBeep.add(.3, 550,550,127,127,0,0);
				started = false;
			}

			obj.update=function()
			{
				if ( new Date().getTime() - startTime > 2750 )
				{
					if ( started == false )
					{
						soundBeep.createSound().play();
						started = true;
					}
					GE.DrawRect(0,0,GE.CanvasWidth, GE.CanvasHeight,15);
				}
				else
				{
					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));
						}
					}
				}
				if ( new Date().getTime() - startTime > 3000 )
					obj.finished = true;
			}

			obj.draw=function()
			{
			}

			obj.nextStage = function()
			{
				return LoadScreen(data);
			}

			obj.init();

			return obj;
		}

		function LoadScreen(data)
		{
			var obj = {};

			obj.finished = false;
			var currentStage;
			var soundClick;
			
			var loadData = [ 
				["QB 8bit Game Engine V1.0",0,0,false],
				["",0,0],
				["Ready",0,0],
				["",0,0],
				["load \"GimmieFrictBby\"",30,6],
				["Ready",100,0],
				["",0,0],
				["run",30,6],
				["",50,0]];
			var MSG_TEXT=0;
			var MSG_LINE_PAUSE=1;
			var MSG_CHAR_PAUSE=2;
			
			var msgNum = 0;
			var msgPos = 0;
			var keyboardtimer = 0;
			var xpos = 0;
			var ypos = 0;
			var cursorstate = 0;
			
			
			obj.init=function()
			{
				GE.DrawRect ( 0,0,GE.CanvasWidth, GE.CanvasHeight, 0);
				startTime = new Date().getTime();
				soundClick = new customSound(8000);
				soundClick.add(.005, 1000,2000,127,127,0,0);
			}

			obj.update=function()
			{
				keyboardtimer--;
				if ( keyboardtimer <= 0 )
				{
					GE.DrawRect(xpos,ypos,8,8,0);
					if ( msgPos < loadData[msgNum][MSG_TEXT].length )
					{
						if ( loadData[msgNum][MSG_CHAR_PAUSE] > 0 )
							soundClick.createSound().play();
						GE.WriteString(loadData[msgNum][MSG_TEXT][msgPos],xpos,ypos,15);
						msgPos+=1;
						xpos+=8;
						keyboardtimer = loadData[msgNum][MSG_CHAR_PAUSE]+Math.floor(Math.random()*(loadData[msgNum][MSG_CHAR_PAUSE]));
					}
					else
					{
						msgNum++;
						msgPos=0;
						xpos=0;
						ypos+=8;
						if ( msgNum >= loadData.length )
						{
							obj.finished = true;
							return;
						}
						if ( loadData[msgNum][MSG_CHAR_PAUSE] != 0 )
						{
							GE.WriteString(">",xpos,ypos,15);
							xpos+=8;
						}
						keyboardtimer = loadData[msgNum][MSG_LINE_PAUSE];
					}
				}
				if ( loadData[msgNum][MSG_CHAR_PAUSE] != 0 )
					cursorstate = (cursorstate + 1) % 2;
				else
					cursorSate = 0;
			}

			obj.draw=function()
			{
				GE.DrawRect(xpos,ypos,8,8,cursorstate * 15);
			}
			
			obj.nextStage = function()
			{
				return Logo(data);
			}

			obj.init();

			return obj;
		}

		function Logo (data)
		{
			var obj = {};

			var startTime;
			obj.finished = false;
			var currentStage;
			
			obj.init=function()
			{
				startTime = new Date().getTime();
				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("Gimmie Friction Baby",GE.CanvasHeight/2+32,15,false,true);

				GE.WriteStringCentered("I liked the game so much",GE.CanvasHeight/2+56,9);
				GE.WriteStringCentered("I copied it",GE.CanvasHeight/2+64,9);
			}

			obj.update=function()
			{
				if ( new Date().getTime() - startTime > 3000 )
					obj.finished = true;
			}

			obj.draw=function()
			{
			}

			obj.nextStage = function()
			{
				return null;
			}

			obj.init();

			return obj;
		}

		obj.init();

		return obj;
	}

	function InitGame(data)
	{
		var obj = {}

		obj.finished = false;

		var shotVectors = new Array();
		
		obj.init=function()
		{
			if ( data == undefined )
				data = new Array();

			soundBounce = new customSound(8000);
			soundBounce.add(.005, 1000,2000,127,127,0,0);

			GE.DrawRect(0,0,GE.CanvasWidth, GE.CanvasHeight,0);

			for ( var x=0; x <= GE.CanvasWidth; x+=16)
			{
				GE.DrawLine(x,GE.CanvasWidth,x+8,GE.CanvasWidth,12);
			}

			if ( GE.SpriteGet("turret") == null )
			{
				CreateTurret();
			}
			DrawScore(data["game"].score, data["game"].hiscore);
			obj.finished = true;
		}

		function CreateTurret()
		{
			// create the turret sprite and store all the possible vectors
			var startangle = Math.PI + Math.PI / 16;
			var endangle  = startangle + Math.PI - Math.PI / 16 *2;
			var angleinc = (endangle-startangle) /64;
			var cs = Costume();
			var sprNum = 0;
			for ( var angle = startangle; angle < endangle + 0.001; angle += angleinc )
			{
				var bm = Bitmap(80,80);

				var endx = 40+30*Math.cos(angle);
				var endy = 75+30*Math.sin(angle);
				var x = Math.sin(angle);
				var y = -Math.cos(angle);

				shotVectors.push(Vector(Math.cos(angle),Math.sin(angle)));

				// Draw a wide turret.. sorry... game engine does not support line thickness
				for ( var off = -6; off < 6; off+=0.2)
				{
					bm.DrawLine(endx+x*off,endy+y*off,40+x*off,70+y*off,15);
					bm.DrawLine(40+x*off,75+y*off,endx+x*off,endy+y*off,15);
				}

				bm.DrawCircle(40,75,15,7);
				bm.DrawRect(40-15,75,30,5,7);

				cs.SetItem(sprNum,bm);
				sprNum++;
			}
			
			var sp = Sprite(GE.CanvasWidth/2-40,GE.CanvasHeight-80,cs);
			sp.SetAnimatorBounce();
			GE.SpriteAdd("turret",sp,1);
			data["game"].cannonVectors = shotVectors;
		}

		obj.update = function()
		{
		}
		
		obj.draw = function ()
		{
		}

		obj.nextStage = function()
		{
			return MainMenu(data);
		}

		obj.init();

		return obj;
	}

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

		var spaceDown = false;
		
		obj.init=function()
		{
			if ( data == undefined )
				data = new Array();

			obj.finished = false;

			for ( var x=0; x <= GE.CanvasWidth; x+=16)
			{
				GE.DrawLine(x,GE.CanvasWidth,x+8,GE.CanvasWidth,12);
			}

			DrawScore(data["game"].score, data["game"].hiscore);
		}

		obj.update = function()
		{
			if ( GE.GetKeyState(GE.KEY_SPACE))
			{
				data["game"].useKeyboard = true;
			}

			if ( GE.mouseClicked)
			{
				data["game"].useKeyboard = false;
			}

			if (GE.mouseClicked )
			{
				GE.DrawRect(0,GE.CanvasHeight/2-4,GE.CanvasWidth,8,0);
				obj.finished = true;
			}

			if ( GE.GetKeyState(GE.KEY_SPACE) )
				spaceDown = true;
			
			if (( !GE.GetKeyState(GE.KEY_SPACE) && spaceDown ))
			{
				GE.DrawRect(0,GE.CanvasHeight/2-4,GE.CanvasWidth,8,0);
				obj.finished = true;
			}
			
			if ( obj.finished )
			{
				data["game"].score = 0;
				DrawScore(data["game"].score, data["game"].hiscore);
			}
		}
		
		obj.draw = function ()
		{
			GE.WriteStringCentered("Fire using :       ",40,14);
			GE.WriteStringCentered("  Space Key        ",48,14);
			GE.WriteStringCentered("  Left Mouse Button",56,14);
			GE.WriteStringCentered("  Touch Screen     ",64,14);

			GE.WriteStringCentered("Press Fire to Begin",GE.CanvasWidth-12,10);
		}

		obj.nextStage = function()
		{
			return WaitForShot(data);
		}

		obj.init();
		
		return obj;
	}

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

		obj.init=function()
		{
		}

		obj.update = function()
		{
			var ball = GE.SpriteGet("ball"+data["game"].ballNum);
			if ( (GE.GetKeyState(GE.KEY_SPACE) || GE.mouseClicked))// && (ball == null || (ball.vel.x == 0 && ball.vel.y == 0 )))
			{
				var animframe = GE.SpriteGet("turret").costumeAnimator.frame;
				var frame = GE.SpriteGet("turret").costumeAnimator.Get(animframe);
				var ballVector = data["game"].cannonVectors[frame];
				data["game"].ballNum++;
				NewBall(data["game"].ballNum,ballVector);
				obj.finished = true;
			}
		}

		function NewBall(ballNum,vector)
		{
			var bm = Bitmap(12,12);
			bm.DrawCircle(5,5,6,15);
			var bx = GE.CanvasWidth / 2;
			var by = GE.CanvasHeight-5;
			var sp = Sprite(bx-6+25*vector.x,by-6+25*vector.y,bm);
			sp.vel.x = vector.x*2.7;
			sp.vel.y = vector.y*2.7;
			GE.SpriteAdd("ball"+ballNum,sp,2);
		}

		obj.draw = function()
		{
			DrawBalls(data["game"].ballsInCourt);
		}
		
		obj.nextStage = function()
		{
			return BallMoving(data);
		}

		obj.init();

		return obj;
	}

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

		var gameOver = false;

		obj.init=function()
		{
		}

		obj.update = function()
		{
			var ball = GE.SpriteGet("ball"+data["game"].ballNum);

			var mag = ball.vel.Magnitude()-.01;
			if ( mag < 0 ) 
				mag = 0;
			ball.vel.SetMagnitude(mag);

			var bounced = false;
			
			// check collisions in court
			if ( ball.pos.x + ball.vel.x < 0 )
			{
				ball.vel.x *= -1;
				bounced = true;
			}
			if ( ball.pos.y + ball.vel.y < 0 )
			{
				ball.vel.y *= -1;
				bounced = true;
			}
			if ( ball.pos.x + 12 + ball.vel.x >= GE.CanvasWidth-1 )
			{
				ball.vel.x *= -1;
				bounced = true;
			}
			if ( ball.pos.y + ball.vel.y < 0 )
			{
				ball.vel.y *= -1;
				bounced = true;
			}

			if ( mag < .1 )
			{
				ball.vel.x = 0;
				ball.vel.y = 0;
				obj.finished = true;
			}

			if ( ball.pos.y + ball.vel.y + 12 >= GE.CanvasWidth-1 && ball.vel.y >= 0)
			{
				obj.finished = true;
				gameOver = true;
			}

			var ballsInCourt = data["game"].ballsInCourt;
			for(var ballInfo in ballsInCourt)
			{
				var targetBall = ballsInCourt[ballInfo];
				var ballX = ball.pos.x + ball.vel.x + 6;//ball.bitmap.width/2;
				var ballY = ball.pos.y + ball.vel.y + 6;//ball.bitmap.width/2;
				var xdist = targetBall.xpos - ballX;
				var ydist = targetBall.ypos - ballY;

				var distance = Math.sqrt((ballX+-targetBall.xpos)*(ballX+-targetBall.xpos) + (ballY+-targetBall.ypos)*(ballY-targetBall.ypos))
				var minDistance = 6 + targetBall.radius;

				if (distance < minDistance) 
				{
					// first shift our sphere back until we are not overlapping the other sphere
					var m = Vector(targetBall.xpos - ball.pos.x, targetBall.ypos - ball.pos.y);
					m.Normalise();
					m.ScalarMultiply(minDistance-distance);
					ball.pos.x -= m.x;
					ball.pos.y -= m.y;

					// find the collision normal
					var collisionNormal = Vector(ballX - targetBall.xpos, ballY - targetBall.ypos);
					collisionNormal.Normalise()

					var j = (-2 * (ball.vel.DotProduct(collisionNormal)) / collisionNormal.DotProduct(collisionNormal))

					collisionNormal.x *= j
					collisionNormal.y *= j
					ball.vel.x += collisionNormal.x
					ball.vel.y += collisionNormal.y

					targetBall.lives -= 1;
					if ( targetBall.lives == 0 )
					{
						data["game"].ballsInCourt[ballInfo] = null;
						data["game"].ballsInCourt[ballInfo] = CleanUpArray(data["game"].ballsInCourt[ballInfo]);
						data["game"].score += 1;
						if ( data["game"].score >data["game"].hiscore )
							data["game"].hiscore = data["game"].score;
						DrawScore(data["game"].score, data["game"].hiscore);
					}
					bounced = true;
				}
			}
			if ( bounced )
				soundBounce.createSound().play();
		}

		obj.draw = function()
		{
			DrawBalls(data["game"].ballsInCourt);
		}
		
		obj.nextStage = function()
		{
			if ( gameOver )
			{
				return GameOver(data);
			}
			else
			{
				return BallExpanding(data);
			}
		}

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

		obj.init();

		return obj;
	}

	function BallExpanding (data)
	{
		var obj = {};
		obj.finished = false;
		var gameOver = false;

		obj.init=function()
		{
			var ballsInCourt = data["game"].ballsInCourt;
			var ball = GE.SpriteGet("ball"+data["game"].ballNum);
//			ballsInCourt[data["game"].ballNum] = [data["game"].ballNum,ball.pos.x+ball.bitmap.width/2, ball.pos.y+ball.bitmap.width/2, 6, 3];
			ballsInCourt[data["game"].ballNum] = Sphere(data["game"].ballNum,ball.pos.x+ball.bitmap.width/2, ball.pos.y+ball.bitmap.width/2, 6, 3);
			ball.died = true;
		}

		obj.update = function()
		{
			var ballsInCourt = data["game"].ballsInCourt;
			var ball = ballsInCourt[data["game"].ballNum];
			ball.radius += .5;
			if ( ball.xpos-ball.radius < 0 )
				obj.finished = true;
			if ( ball.xpos+ball.radius >= GE.CanvasWidth-1 )
				obj.finished = true;
			if ( ball.ypos-ball.radius < 0 )
				obj.finished = true;
			if ( ball.ypos+ball.radius >= GE.CanvasWidth-1 )
				obj.finished = true;

			var ballsInCourt = data["game"].ballsInCourt;
			for(var ballInfo in ballsInCourt)
			{
				var targetBall = ballsInCourt[ballInfo];

				var xdist = targetBall.xpos - ball.xpos;
				var ydist = targetBall.ypos - ball.ypos;

				if ( targetBall.ballNumber != ball.ballNumber )
					if ( xdist * xdist + ydist * ydist <= (ball.radius + targetBall.radius) * (ball.radius + targetBall.radius) )
					{
						obj.finished = true;
					}
			}
		}

		obj.draw = function()
		{
			DrawBalls(data["game"].ballsInCourt);
		}
		
		obj.nextStage = function()
		{
			if ( gameOver )
			{
				return GameOver(data);
			}
			else
			{
				return WaitForShot(data);
			}
		}

		obj.init();

		return obj;
	}

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

		var spaceDown = false;

		obj.init=function()
		{
			GE.WriteStringCentered("Game Over",GE.CanvasHeight/2,9,true,true);

			data["game"].ballsInCourt = Array();
			data["game"].ballNum = 0;

			GE.DeleteSpritesInLayer(2)
			obj.finished = true;
		}

		obj.update = function()
		{
		}

		obj.draw = function()
		{
		}
		
		obj.nextStage = function()
		{
			return MainMenu(data);
		}

		obj.init();

		return obj;
	}

	function DrawScore ( score, hiscore )
	{
		GE.DrawRect(0,GE.CanvasHeight-30,3*16,30,0);
		GE.DrawRect(GE.CanvasWidth-3*16,GE.CanvasHeight-30,3*16,30,0);
		
		var tmpStr = "000" + score;
		tmpStr = tmpStr.substring(tmpStr.length-3);

		GE.WriteString("Score",0,GE.CanvasHeight-30,7);
		GE.WriteString(tmpStr,0,GE.CanvasHeight-16,7,true,true);

		tmpStr = "000" + hiscore;
		tmpStr = tmpStr.substring(tmpStr.length-3);

		GE.WriteString("Hi",GE.CanvasWidth-48,GE.CanvasHeight-30,7);
		GE.WriteString(tmpStr,GE.CanvasWidth-48,GE.CanvasHeight-16,7,true,true);
	}

	function DrawBalls(ballsInCourt)
	{
		GE.DrawRect(0,0,GE.CanvasWidth, GE.CanvasWidth,0 );
		var colours = [0,8,7,15];
		for(var ballInfo in ballsInCourt)
		{
			var ball = ballsInCourt[ballInfo];
//			GE.DrawCircle(targetBall[1],targetBall[2],targetBall[3],colours[targetBall[4]]);
			GE.DrawCircle(ball.xpos, ball.ypos, ball.radius, colours[ball.lives]);
		}
	}

	return obj;
}

Sphere = function(ballNumber, xpos, ypos, radius, lives)
{
	var obj = {};
	
	obj.ballNumber = ballNumber;
	obj.xpos = xpos;
	obj.ypos = ypos;
	obj.radius = radius;
	obj.lives = lives;
	
	return obj;
}

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

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

		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)
		{
			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.CheckCollisions();
			}
			drawFunction();
			obj.Draw();
		}
	}

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


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, startNoise, endNoise )
	{
		var vol = startVol;
		var freq = startFreq;
		var angleInc = (2*Math.PI)/this.bitrate * freq;
		var noise = startNoise;
		
		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) + (Math.random()*noise)) * vol; 
			freq = startFreq + (endFreq - startFreq)*x/(this.bitrate*duration);
			noise = startNoise + (endNoise - startNoise)*x/(this.bitrate*duration);

			angleInc = (2*Math.PI)/this.bitrate * freq;
			this.angle+=angleInc;
			if ( this.angle > Math.PI * 2 )
			{
				this.angle -= Math.PI * 2;
			}
			this.dataPos++;
		}
	}
	
	customSound.prototype.createSound = function()
	{
		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;
		return a;
	}
}

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

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