Ahhh.. Tetris. How could I have forgotten to write this game.

Its a puzzle game that everyone was playing at one time.

Here’s my take on it. It took a little longer than expected and to be honest the code could do with a re-write.
Still… This is a game ‘sketch’ so I shouldn’t expect the code to be perfect.
It works and I learnt a lot for the future so job done.

Have fun.

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

	<title>Tetrizza</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 = 2;
var LAYER_BUTTON = 3;

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 = MainMenu(GameData());
			InitBackground();
		}
		else
			currentStage = Splash(GameData());

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

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

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

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

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

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

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

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

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

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

}

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

	return obj;
}

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

	return obj;
}

function PlayData()
{
	var obj = {};
	obj.finished = false;
	obj.playArea;
	obj.scoreValue;

	return obj;
}

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

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

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

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

	obj.TearDown = function()
	{
		InitBackground();
	}

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

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

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

	return obj;
}

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

	obj.Init = function ()
	{
		var menuBitmap = Bitmap(140,20);
		var menuBitmap2 = Bitmap(220,160);

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

		var textColour = 15;

		var msg = "SpaceBar or click to Begin"
		var width = msg.length*8;
		menuBitmap2.DrawRect((menuBitmap2.width-width)/2,110,width,16,1);
		menuBitmap2.WriteStringCentered(msg,GE.CharSet(),110,textColour,false,true);

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

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

		DisplayScore(data);
	}

	obj.UpdateCustom=function()
	{

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

	}

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

	return obj;
}

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

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

		InitBackground();

		DisplayScore(data);
	}

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

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

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

	return obj;
}

// the following are inner states to the play state

function PlayArea (width, height)
{
	var obj = {};
	var currentShape = 1;
	var nextShape = 1;
	var shapeStats = [0,0,0,0,0,0,0,0]
	var currentRotation = 1;
	var currentShapeX = 2;
	var currentShapeY = -3;
	var grid;
	var blockCostumes;
	var tetronimos;
	var teronimoCostumes;
	var finished;
	var blockCount;

	obj.bitmap = Bitmap(10*width,10*height);

	obj.Init = function()
	{
		grid = new Array();
		for ( var y = 0; y < height; y++ )
		{
			var row = new Array();
			for ( var x = 0; x < width; x++ )
			{
				row.push(-1);
			}
			grid.push(row);
		}
		blockCostumes = CreateTetronimoBlockCostumes();
		tetronimos = CreateTetrominos();

		tetronimoCostumes = CreateTetronimoCostumes();

		currentShape = PickRandomShape();
		nextShape = PickRandomShape();

		blockCount = 0;
		finished = false;
	}

	function PickRandomShape()
	{
		return  Math.floor(Math.random()*7);
	}

	obj.RotateLeft=function()
	{
		var rotate = currentRotation - 1;
		if ( rotate < 0 ) rotate = 3;
		var shape = tetronimos[currentShape][rotate];
		if ( !IsCollision(currentShapeX,currentShapeY,shape) )
		{
			currentRotation = rotate;
		}
	}

	obj.RotateRight=function()
	{
		var rotate = currentRotation + 1;
		if ( rotate > 3 ) rotate = 0;
		var shape = tetronimos[currentShape][rotate];
		if ( !IsCollision(currentShapeX,currentShapeY,shape) )
		{
			currentRotation = rotate;
		}
	}

	obj.MoveLeft=function()
	{
		var shape = tetronimos[currentShape][currentRotation];
		if ( !IsCollision(currentShapeX-1,currentShapeY,shape) )
		{
			currentShapeX -= 1;
		}
	}

	obj.MoveRight=function()
	{
		var shape = tetronimos[currentShape][currentRotation];
		if ( !IsCollision(currentShapeX+1,currentShapeY,shape) )
		{
			currentShapeX += 1;
		}
	}

	obj.GetLevel=function()
	{
		return Math.floor(blockCount/20)
	}

	obj.GetBlockCount=function()
	{
		return blockCount;
	}

	obj.IsFinished=function()
	{
		return finished;
	}

	function IsCollision(xpos,ypos,shape)
	{
		for ( var rowPos=0; rowPos < shape.length; rowPos++ )
		{
			var row = shape[rowPos];
			for ( var charPos = 0; charPos < row.length; charPos++)
			{
				var chr = row.charAt(charPos);
				if ( chr != ' ' )
				{
					if ( ypos+rowPos >= height )
						return true;
					if ( xpos+charPos < 0 || xpos+charPos >= width )
						return true;
					if ( ypos+rowPos >= 0 && ypos+rowPos < height)
						if ( grid[ypos+rowPos][xpos+charPos] != -1 )
						{
							if ( ypos+rowPos == 0 )
								finished = true;
							return true;
						}
				}
			}
		}
		return false;
	}

	obj.GetStatForShape = function(shape)
	{
		return shapeStats[shape];
	}

	obj.GetNextShapeBitmap=function()
	{
		return tetronimoCostumes[nextShape].GetItem(0);
	}

	obj.Drop=function()
	{
		var scoreValue = 0;
		var shape = tetronimos[currentShape][currentRotation];

		var canDrop = !IsCollision(currentShapeX,currentShapeY+1,shape);
		if ( !canDrop )
		{
			scoreValue += 10;
			// paste shape onto grid
			for ( var rowPos=0; rowPos < shape.length; rowPos++ )
			{
				if ( currentShapeY+rowPos >= 0 )
				{
					var row = shape[rowPos];
					for ( var charPos = 0; charPos < row.length; charPos++)
					{
						var chr = row.charAt(charPos);
						if ( chr != ' ' )
							grid[currentShapeY+rowPos][currentShapeX+charPos]=currentShape;
					}
				}
			}

			// See if any rows can be removed
			// this is gonna be really slow in inefficient for now
			// I just want a working game for now
			var scoreMultiplier = 0;
			for ( var y = grid.length-1; y > 0; y-- )
			{
				var counter = 0;
				var row = grid[y];

				for ( var x = 0; x < row.length; x++ )
				{
					if ( row[x] != -1 )
						counter++;
				}
				if ( counter == row.length )
				{
					scoreMultiplier++;
					// blank out the row
					for ( var x = 0; x < row.length; x++ )
						row[x] = -1;

					// shift down 1 row
					var newGrid = new Array ();
					newGrid.push ( row );
					for ( var ynew = 0; ynew < grid.length; ynew++ )
					{
						if  (ynew != y )
						{
							newGrid.push(grid[ynew]);
						}
					}
					grid = newGrid;
					y+= 1;
				}
			}
			scoreValue += 100*scoreMultiplier * scoreMultiplier;

			// create a new shape
			shapeStats[currentShape]++;
			currentShape = nextShape;
			currentRotation = 0;
			currentShapeY = -4;
			currentShapeX = ( width -4 ) / 2 ;

			blockCount++;
			nextShape = PickRandomShape();
		}
		currentShapeY += 1;
		return scoreValue;
	}

	obj.Draw=function()
	{
		obj.bitmap.DrawRect(0,0,width*10,height*10,-1);
		for ( var y=0; y < grid.length; y++ )
		{
			var row = grid[y];
			for ( var x = 0; x < row.length; x++ )
			{
				if ( row[x] != -1 )
				{
					obj.bitmap.Paste(x*10,y*10,blockCostumes.GetItem(row[x]));
				}
			}
		}
		// now draw the current shape
		obj.bitmap.Paste(currentShapeX*10,currentShapeY*10,tetronimoCostumes[currentShape].GetItem(currentRotation));
	}

	obj.Init();

	return obj;
}

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

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

		var xPos = (GE.CanvasWidth / 2 ) - 5*10;
		var yPos = (GE.CanvasHeight / 2 ) - 10*10;

		data.playArea = PlayArea(10,20);

		var sp = Sprite(xPos,yPos,data.playArea.bitmap);
		GE.SpriteAdd("PlayArea",sp,1);

		obj.SetNextState(PlayNewGame(data));

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

		var blankBitmap = menuBitmap.Clone();

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

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

	}

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

	return obj;
}

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

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

		obj.SetNextState(PlayGame(data));
		obj.SetFinished();

	}

	return obj;
}

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

	var player;
	var tetronimos = CreateTetrominos();
	var rotation = 0;
	var pauseTimer = 30;
	var leftPressed;
	var rightPressed;
	var zPressed;
	var yPressed;

	obj.Init=function()
	{
		obj.SetNextState(PlayGameOver(data));
		leftPressed = false;
		rightPressed = false;
		zPressed = false;
		yPressed = false;
	}

	obj.DrawCustom=function()
	{
		data.playArea.Draw();
	}

	obj.UpdateCustom=function()
	{
		if ( GE.GetKeyState(GE.KEY_DOWN))
		{
			data.scoreValue += data.playArea.Drop();
			if (data.playArea.IsFinished())
			{
				obj.SetFinished();
			}
		}
		else
		{
			pauseTimer--;
			if ( pauseTimer == 0 )
			{
				var level = data.playArea.GetLevel();
				if ( level < 20 )
					pauseTimer = 20-level;
				else
					pauseTimer = 1;

				data.scoreValue += data.playArea.Drop();
				if (data.playArea.IsFinished())
				{
					obj.SetFinished();
				}
			}
		}

		if ( GE.GetKeyState(GE.KEY_LEFT) )
		{
			if ( !leftPressed )
			{
				data.playArea.MoveLeft();
				leftPressed = true;
			}
		}
		else
		{
			leftPressed = false;
		}

		if ( GE.GetKeyState(GE.KEY_RIGHT) )
		{
			if ( !rightPressed )
			{
				data.playArea.MoveRight();
				rightPressed = true;
			}
		}
		else
		{
			rightPressed = false;
		}

		if ( GE.GetKeyState(GE.KEY_Z) )
		{
			if ( !zPressed )
			{
				data.playArea.RotateLeft();
				zPressed = true;
			}
		}
		else
		{
			zPressed = false;
		}

		if ( GE.GetKeyState(GE.KEY_X) )
		{
			if ( !xPressed )
			{
				data.playArea.RotateRight();
				xPressed = true;
			}
		}
		else
		{
			xPressed = false;
		}
	}

	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 tmpStr = "000000" + data.score;
	tmpStr = tmpStr.substring(tmpStr.length-7);

	GE.DrawRect(32,0,13*8,16,0);
	GE.WriteString("SCORE:"+tmpStr,32,0,15,false,true);

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

function DisplayStats(playArea)
{
	if ( playArea != undefined )
	{
		for ( var s = 0; s < 7; s++ )
		{
			var stat = playArea.GetStatForShape(s);
			if (stat != undefined )
			{
				GE.DrawRect (44,102+16*s,6*8,8,0);
				GE.WriteString (" : "+playArea.GetStatForShape(s),44,102+16*s,15);
			}
		}
		GE.DrawRect (44,102+16*7,6*8,8,0);
		GE.WriteString (" : "+playArea.GetBlockCount(),44,102+16*7,15);

		// also display what the next shape is going to be
		GE.DrawRect (34,44,40,40,0);
		GE.Paste (34,44,playArea.GetNextShapeBitmap());
		GE.DrawRect (0,GE.CanvasHeight-8,GE.CanvasWidth,8,0);
		GE.WriteStringCentered ("Level "+playArea.GetLevel(),GE.CanvasHeight-8,15);
	}
}

function InitBackground(data)
{
	GE.DrawRect(0,0,GE.CanvasWidth, GE.CanvasHeight,0 );

	var blocks = CreateTetronimoBlockCostumes();
	var gridXPos = (GE.CanvasWidth / 2 ) - 5*10;
	var gridYPos = (GE.CanvasHeight / 2 ) - 10*10;
	GE.DrawRect(gridXPos-10,gridYPos,10*10+20,20*10+10,2);
	GE.DrawRect(gridXPos,gridYPos,10*10,20*10,0);

	// show next block
	GE.WriteString ("Next",36,22,15);
	GE.DrawRect ( 25,36-2,54,54,2);
	GE.DrawRect ( 25+2,36,50,50,0);
	GE.WriteString ("Stats",32,90,15);

	for ( var s = 0; s < 7; s++ )
	{
		GE.Paste(8,100+16*s,blocks.GetItem(s));
		GE.WriteString (" :",44,102+16*s,15);
	}
	GE.WriteString ("Total :",4,102+16*7,15);

	var leftPos = GE.CanvasWidth - 98;
	var top = 32;
	GE.WriteString ( "Controls", leftPos+16,top,15);
	top+=32;
	GE.WriteString ( "Left/Right", leftPos,top,15);
	top+=16;
	GE.WriteString ( "Move block", leftPos,top,7);
	top+=16;
	GE.WriteString ( "Down", leftPos,top,15);
	top+=16;
	GE.WriteString ( "Drop", leftPos,top,7);
	top+=16;
	GE.WriteString ( "Z", leftPos,top,15);
	top+=16;
	GE.WriteString ( "Rotate Left", leftPos,top,7);
	top+=16;
	GE.WriteString ( "X", leftPos,top,15);
	top+=16;
	GE.WriteString ( "Rotate Right", leftPos,top,7);
}

function CreateTetrominos()
{
	var tetronimos = new Array();
	// I
	tetronimos.push( [[
		"    ",
		"****",
		"    ",
		"    "
		],
		[
		" *  ",
		" *  ",
		" *  ",
		" *  "
		],
		[
		"    ",
		"****",
		"    ",
		"    "
		],
		[
		" *  ",
		" *  ",
		" *  ",
		" *  "
		]]);
	// J
	tetronimos.push([[
		"*   ",
		"*** ",
		"    ",
		"    "
		],
		[
		" ** ",
		" *  ",
		" *  ",
		"    "
		],
		[
		"    ",
		"*** ",
		"  * ",
		"    "
		],
		[
		" *  ",
		" *  ",
		"**  ",
		"    "
		]]);
	// L
	tetronimos.push([[
		"  * ",
		"*** ",
		"    ",
		"    "
		],
		[
		" *  ",
		" *  ",
		" ** ",
		"    "
		],
		[
		"    ",
		"*** ",
		"*   ",
		"    "
		],
		[
		"**  ",
		" *  ",
		" *  ",
		"    "
		]]);
	// O
	tetronimos.push([[
		"    ",
		" ** ",
		" ** ",
		"    "
		],
		[
		"    ",
		" ** ",
		" ** ",
		"    "
		],
		[
		"    ",
		" ** ",
		" ** ",
		"    "
		],
		[
		"    ",
		" ** ",
		" ** ",
		"    "
		]]);
	// S
	tetronimos.push([[
		" ** ",
		"**  ",
		"    ",
		"    "
		],
		[
		" *  ",
		" ** ",
		"  * ",
		"    "
		],
		[
		"    ",
		" ** ",
		"**  ",
		"    "
		],
		[
		" *  ",
		" ** ",
		"  * ",
		"    "
		]]);
	// T
	tetronimos.push([[
		" *  ",
		"*** ",
		"    ",
		"    "
		],
		[
		" *  ",
		" ** ",
		" *  ",
		"    "
		],
		[
		"    ",
		"*** ",
		" *  ",
		"    "
		],
		[
		" *  ",
		"**  ",
		" *  ",
		"    "
		]]);
	// Z
	tetronimos.push([[
		"**  ",
		" ** ",
		"    ",
		"    "
		],
		[
		"  * ",
		" ** ",
		" *  ",
		"    "
		],
		[
		"    ",
		"**  ",
		" ** ",
		"    "
		],
		[
		"  * ",
		" ** ",
		" *  ",
		"    "
		]]);

		return tetronimos;
}

function CreateTetronimoBlockCostumes()
{
	// for tetronimo's IJLOSTZ use colours as used onb the PC port by Vadim Gerisimov
	var cs= Costume();
	cs.SetItem(0,CreateBlockBitmap(1));
	cs.SetItem(1,CreateBlockBitmap(7));
	cs.SetItem(2,CreateBlockBitmap(5));
	cs.SetItem(3,CreateBlockBitmap(3));
	cs.SetItem(4,CreateBlockBitmap(2));
	cs.SetItem(5,CreateBlockBitmap(4));
	cs.SetItem(6,CreateBlockBitmap(6));

	return cs;
}

function CreateBlockBitmap(blockColour)
{
	var bm = Bitmap(10,10);
	bm.DrawRect(0,0,10,10,blockColour);
	bm.DrawLine(0,9,9,9,8);
	bm.DrawLine(9,0,9,9,8);
	bm.DrawLine(0,0,9,0,blockColour+8);
	bm.DrawLine(0,0,0,9,blockColour+8);

	return bm;
}

function CreateTetronimoCostumes()
{
	var tetronimos = CreateTetrominos ();
	var blockCostumes = CreateTetronimoBlockCostumes();
	var tetronimoCostumes = Array();

	for ( var currentShape = 0; currentShape < tetronimos.length; currentShape++ )
	{
		var tetronimo = tetronimos[currentShape];
		var tetronimoCostume = Costume();
		for ( var currentRotation = 0; currentRotation < tetronimo.length; currentRotation++ )
		{
			var bm = Bitmap(40,40);
			var shape = tetronimos[currentShape][currentRotation];
			for ( var rowPos=0; rowPos < shape.length; rowPos++ )
			{
				var row = shape[rowPos];
				for ( var charPos = 0; charPos < row.length; charPos++)
				{
					var chr = row.charAt(charPos);
					if ( chr != ' ' )
						bm.Paste(charPos*10,rowPos*10,blockCostumes.GetItem(currentShape));
				}
			}
			tetronimoCostume.SetItem(currentRotation,bm);
		}
		tetronimoCostumes.push(tetronimoCostume);
	}

	return tetronimoCostumes;
}

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

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

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

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

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

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

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

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

	var canvas = canvasIn;

	var colours = new Array();

	var charSet = {};

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

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

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

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

	obj.collisionRules = new Array();

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

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

//	ctx.imageSmoothingEnabled = false;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	InitColours();

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

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

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

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

//				if ( y == 0 && charLine != "        ")
//					lineZeroBlank = false;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	// [\]^_`abcdefg.....z{|}~
		charSet.SetChar("[",[
			"  ***   ",
			"  *     ",
			"  *     ",
			"  *     ",
			"  *     ",
			"  *     ",
			"  ***   ",
			"        "]);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	InitCharSet();

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

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

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

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

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

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

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

	}

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

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

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

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

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

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

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

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

	}

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

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

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

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

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

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

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

		}
	}

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

	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