Image 15-Puzzle mechanics

Javascript source code

var ctx = null;
var tileW = 125, tileH = 100;

var imageURLs	= ["img1.jpg", "img2.jpg", "img3.jpg"];
var images	= [];
var imageTiles	= [];
var imageBest	= [];
var imageIndex	= 0;
var freePos	= [0,0];
var loadCount	= imageURLs.length;
var movesTaken	= 0;

var mouseState = {
	click	: null
};

function Tile()
{
	this.imgPos	= [0, 0];
	this.canvasPos	= [0, 0];
}

function startLevel(idx)
{
	imageIndex		= idx;
	imageTiles.length	= 0;
	movesTaken		= 0;
	
	var yMax = Math.floor(images[idx].height / tileH) - 1;
	var xMax = Math.floor(images[idx].width / tileW) - 1;
	
	freePos = [xMax, yMax];
	
	for(var y = 0; y <= yMax; y++)
	{
		for(var x = 0; x <= xMax; x++)
		{
			if(x==xMax && y==yMax) { break; }
			
			var t		= new Tile();
			t.imgPos	= [x, y];
			t.canvasPos	= [x, y];
			imageTiles.push(t);
		}
	}

	shuffle();
}

function shuffle()
{
	var yMax = Math.floor(images[imageIndex].height / tileH) - 1;
	var xMax = Math.floor(images[imageIndex].width / tileW) - 1;
	
	for(var i = 0; i < 150; i++)
	{
		var r = Math.random();
		var mov = [freePos[0], freePos[1]];
		
		if(r < 0.25 && freePos[1]>0) { freePos[1]--; }
		else if(r < 0.5 && freePos[0]<xMax) { freePos[0]++; }
		else if(r < 0.75 && freePos[1]<yMax) { freePos[1]++; }
		else if(freePos[0]>0) { freePos[0]--; }
		
		for(var it in imageTiles)
		{
			if(imageTiles[it].canvasPos[0]==freePos[0] &&
				imageTiles[it].canvasPos[1]==freePos[1])
			{
				imageTiles[it].canvasPos = [
					mov[0], mov[1]
				];
				break;
			}
		}
	}
}

function checkState()
{
	for(var i in imageTiles)
	{
		if(imageTiles[i].imgPos[0]!=imageTiles[i].canvasPos[0] ||
			imageTiles[i].imgPos[1]!=imageTiles[i].canvasPos[1])
		{
			return;
		}
	}
	
	if(movesTaken<imageBest[imageIndex] || imageBest[imageIndex]==0)
	{
		imageBest[imageIndex] = movesTaken;
	}
	
	startLevel((imageIndex + 1)>=images.length ? 0 : (imageIndex + 1));
}

function updateGame()
{
	if(mouseState.click!=null)
	{
		var tile = [
			Math.floor(mouseState.click[0]/tileW),
			Math.floor(mouseState.click[1]/tileH)
		];
		
		var tileIdx = -1;
		for(var i in imageTiles)
		{
			if(imageTiles[i].canvasPos[0]==tile[0] &&
				imageTiles[i].canvasPos[1]==tile[1])
			{
				tileIdx = i;
				break;
			}
		}
		
		if(tileIdx==-1)
		{
			mouseState.click = null;
			return;
		}
		
		if(tile[0]==freePos[0] && tile[1]==(freePos[1]-1))
		{
			freePos[1]-= 1;
			imageTiles[i].canvasPos[1]+= 1;
			movesTaken++;
		}
		else if(tile[0]==freePos[0] && tile[1]==(freePos[1]+1))
		{
			freePos[1]+= 1;
			imageTiles[i].canvasPos[1]-= 1;
			movesTaken++;
		}
		else if(tile[1]==freePos[1] && tile[0]==(freePos[0]-1))
		{
			freePos[0]-= 1;
			imageTiles[i].canvasPos[0]+= 1;
			movesTaken++;
		}
		else if(tile[1]==freePos[1] && tile[0]==(freePos[0]+1))
		{
			freePos[0]+= 1;
			imageTiles[i].canvasPos[0]-= 1;
			movesTaken++;
		}
		
		mouseState.click = null;
		checkState();
	}
}

window.onload = function()
{
	ctx = document.getElementById('game').getContext('2d');
	ctx.font = "bold 10pt sans-serif";

	// Event listeners
	document.getElementById('game').addEventListener('click', function(e) {
		var pos = realPos(e.pageX, e.pageY);
		mouseState.click = pos;
	});
	
	for(var i in imageURLs)
	{
		var img = new Image();
		
		img.onerror = function()
		{
			ctx = null;
			alert("Failed loading image " + this.src);
		};
		img.onload = function()
		{
			console.log("Image loaded " + this.src);
			loadCount--;
			
			if(loadCount==0)
			{
				startLevel(0);
			}
		};
		img.src = imageURLs[i];
		
		images.push(img);
		imageBest.push(0);
	}
	
	requestAnimationFrame(drawGame);
};

function realPos(x, y)
{
	var p = document.getElementById('game');
	
	do {
		x-= p.offsetLeft;
		y-= p.offsetTop;
		
		p = p.offsetParent;
	} while(p!=null);
	
	return [x, y];
}

function drawGame()
{
	if(ctx==null) { return; }
	if(loadCount>0) { requestAnimationFrame(drawGame); return; }
	
	updateGame();
	
	ctx.fillStyle = "#ffffff";
	ctx.fillRect(0, 0, 400, 300);
	
	for(var i in imageTiles)
	{
		ctx.drawImage(images[imageIndex],
			imageTiles[i].imgPos[0] * tileW,
			imageTiles[i].imgPos[1] * tileH,
			tileW, tileH,
			imageTiles[i].canvasPos[0] * tileW,
			imageTiles[i].canvasPos[1] * tileH,
			tileW, tileH);
	}
	
	ctx.strokeStyle = "#000000";
	
	ctx.strokeText("Moves: " + movesTaken, 10, 20);
	ctx.fillText("Moves: " + movesTaken, 10, 20);
	
	if(imageBest[imageIndex]>0)
	{
		ctx.strokeText("Best: " + imageBest[imageIndex], 10, 30);
		ctx.fillText("Best: " + imageBest[imageIndex], 10, 30);
	}
	
	requestAnimationFrame(drawGame);
}
Page loaded in 0.01 second(s).