var ctx = null;
var sprites = null, spritesLoaded = false;
var gameTime = 0, lastFrameTime = 0;
var currentSecond = 0, frameCount = 0, framesLastSecond = 0;
var finishedTime = 0;
var offsetX = 0;
var offsetY = 100;
var grid = [];
var visibleFace = null;
var activeFaces = [];
var gameState = {
screen : 'menu',
mode : 'easy',
newBest : false
};
var mouseState = {
x : 0,
y : 0,
click : null
};
var spriteSheet = {
src : "sprites.png",
spriteW : 28,
spriteH : 28,
cols : 10,
rows : 6
};
var difficulties = {
easy : {
name : "Easy",
bestTime : 0,
width : 4,
height : 4,
menuBox : [0,0]
},
medium : {
name : "Medium",
bestTime : 0,
width : 6,
height : 6,
menuBox : [0,0]
},
hard : {
name : "Hard",
bestTime : 0,
width : 8,
height : 7,
menuBox : [0,0]
}
};
function Face(x, y, spriteX, spriteY)
{
this.spritePos = [(spriteX * spriteSheet.spriteW),
(spriteY * spriteSheet.spriteH)];
this.pos = [(x * spriteSheet.spriteW),
(y * spriteSheet.spriteH)];
this.typeID = (spriteY * spriteSheet.cols) + spriteX;
this.currentState = 'hidden';
this.stateChanged = 0;
this.active = false;
}
Face.prototype.update = function()
{
if(this.currentState=='incorrect' &&
(gameTime - this.stateChanged) > 700)
{
this.currentState = 'hidden';
this.stateChanged = gameTime;
this.active = false;
}
};
Face.prototype.click = function()
{
if(this.currentState=='correct') { return; }
if(visibleFace==this)
{
this.currentState = 'hidden';
this.stateChanged = 0;
visibleFace = null;
return;
}
if(visibleFace==null)
{
visibleFace = this;
this.currentState = 'visible';
this.stateChange = gameTime;
}
else if(visibleFace.typeID==this.typeID)
{
this.currentState = 'correct';
this.stateChanged = gameTime;
visibleFace.currentState = 'correct';
visibleFace.stateChanged = gameTime;
visibleFace = null;
checkState();
}
else
{
this.currentState = 'incorrect';
this.stateChanged = gameTime;
this.active = true;
visibleFace.currentState = 'incorrect';
visibleFace.stateChanged = gameTime;
visibleFace.active = true;
activeFaces.push(this);
activeFaces.push(visibleFace);
visibleFace = null;
}
};
function updateGame()
{
if(gameState.screen=='menu')
{
if(mouseState.click!=null)
{
for(var d in difficulties)
{
if(mouseState.click[1]>=difficulties[d].menuBox[0] &&
mouseState.click[1]<=difficulties[d].menuBox[1])
{
startLevel(d);
break;
}
}
mouseState.click = null;
}
}
else if(gameState.screen=='won')
{
if(mouseState.click!=null)
{
gameState.screen = 'menu';
mouseState.click = null;
}
}
else
{
for(var x in activeFaces) { activeFaces[x].update(); }
activeFaces = activeFaces.filter(function(e) {
return e.active;
});
if(mouseState.click!=null)
{
var cDiff = difficulties[gameState.mode];
if(mouseState.click[0]>=offsetX &&
mouseState.click[1]>=offsetY &&
mouseState.click[0]<=(cDiff.width*spriteSheet.spriteW)+offsetX &&
mouseState.click[1]<=(cDiff.height*spriteSheet.spriteH)+offsetY)
{
var gridX = Math.floor((mouseState.click[0]-offsetX) /
spriteSheet.spriteW);
var gridY = Math.floor((mouseState.click[1]-offsetY) /
spriteSheet.spriteH);
grid[((gridY*cDiff.width)+gridX)].click();
}
else if(mouseState.click[1] >= 380)
{
gameState.screen = 'menu';
}
}
mouseState.click = null;
}
}
function checkState()
{
var allCorrect = true;
for(var g in grid)
{
if(grid[g].currentState!='correct')
{
allCorrect = false;
break;
}
}
if(allCorrect)
{
gameState.screen = 'won';
if(difficulties[gameState.mode].bestTime==0 ||
gameTime < difficulties[gameState.mode].bestTime)
{
difficulties[gameState.mode].bestTime = gameTime;
gameState.newBest = true;
}
finishedTime = gameTime;
}
}
function startLevel(diff)
{
gameTime = 0;
lastFrameTime = 0;
gameState.screen = 'playing';
gameState.newBest = false;
gameState.mode = diff;
visibleFace = null;
activeFaces.length = 0;
grid.length = 0;
offsetX = Math.floor((document.getElementById('game').width -
(difficulties[diff].width * spriteSheet.spriteW)) / 2);
offsetY = Math.floor((document.getElementById('game').height -
(difficulties[diff].height * spriteSheet.spriteH)) / 2);
var faceTypes = [];
for(var i = 0; i < (spriteSheet.cols * spriteSheet.rows); i++)
{
faceTypes.push(i);
}
var gridPlaces = [];
for(var i = 0; i < (difficulties[diff].width *
difficulties[diff].height); i++)
{
grid.push(null);
gridPlaces.push(i);
}
for(var i = 0; i < Math.floor(
(difficulties[diff].width * difficulties[diff].height) / 2); i++)
{
var idxF = Math.floor(Math.random() * faceTypes.length);
var idxFace = faceTypes[idxF];
for(var f = 0; f < 2; f++)
{
var idx = Math.floor(Math.random() * gridPlaces.length);
var idxPlace = gridPlaces[idx];
grid[idxPlace] = new Face(
idxPlace % difficulties[diff].width,
Math.floor(idxPlace / difficulties[diff].width),
idxFace % spriteSheet.cols,
Math.floor(idxFace / spriteSheet.cols));
gridPlaces.splice(idx, 1);
}
faceTypes.splice(idxF, 1);
}
}
window.onload = function()
{
ctx = document.getElementById('game').getContext('2d');
// Event listeners
document.getElementById('game').addEventListener('click', function(e) {
var pos = realPos(e.pageX, e.pageY);
mouseState.click = pos;
});
document.getElementById('game').addEventListener('mousemove',
function(e) {
var pos = realPos(e.pageX, e.pageY);
mouseState.x = pos[0];
mouseState.y = pos[1];
});
// Load our spritesheet
sprites = new Image();
sprites.onerror = function()
{
ctx = null;
alert("Failed loading sprite sheet.");
};
sprites.onload = function()
{
console.log("Sprites loaded");
spritesLoaded = true;
};
sprites.src = spriteSheet.src;
requestAnimationFrame(drawGame);
};
function drawMenu()
{
ctx.textAlign = 'center';
ctx.font = "bold 20pt sans-serif";
ctx.fillStyle = "#000000";
var y = 100;
for(var d in difficulties)
{
var mouseOver = (mouseState.y>=(y-20) && mouseState.y<=(y+10));
if(mouseOver) { ctx.fillStyle = "#000099"; }
difficulties[d].menuBox = [y-20, y+10];
ctx.fillText(difficulties[d].name, 150, y);
y+= 80;
if(mouseOver) { ctx.fillStyle = "#000000"; }
}
var y = 120;
ctx.font = "italic 12pt sans-serif";
for(var d in difficulties)
{
if(difficulties[d].bestTime==0)
{
ctx.fillText("No best time", 150, y);
}
else
{
var t = difficulties[d].bestTime;
var bestTime = "";
if((t/1000)>=60)
{
bestTime = Math.floor((t/1000)/60) + ":";
t = t % (60000);
}
bestTime+= Math.floor(t/1000) +
"." + (t%1000);
ctx.fillText("Best time " + bestTime, 150, y);
}
y+= 80;
}
}
function drawPlaying()
{
ctx.textAlign = 'center';
ctx.font = "bold 20pt sans-serif";
ctx.fillStyle = "#000000";
ctx.fillText(difficulties[gameState.mode].name, 150, 25);
ctx.textAlign = 'left';
ctx.font = "italic 12pt sans-serif";
var t = gameTime;
var cTime = "Time ";
if((t/1000)>=60)
{
cTime+= Math.floor((t/1000)/60) + ":";
t = t % (60000);
}
cTime+= (Math.floor(t/1000) < 9 ? "0" : "") + Math.floor(t/1000);
ctx.fillText(cTime, 100, 45);
ctx.fillStyle = "#dddd00";
for(var g in grid)
{
if(grid[g].currentState=='hidden')
{
ctx.beginPath();
ctx.arc(offsetX+grid[g].pos[0] + (spriteSheet.spriteW/2),
offsetY+grid[g].pos[1] + (spriteSheet.spriteH/2),
Math.round(spriteSheet.spriteW/2), 0, Math.PI*2);
ctx.closePath();
ctx.fill();
}
else
{
ctx.drawImage(sprites,
grid[g].spritePos[0], grid[g].spritePos[1],
spriteSheet.spriteW, spriteSheet.spriteH,
offsetX+grid[g].pos[0], offsetY+grid[g].pos[1],
spriteSheet.spriteW, spriteSheet.spriteH);
}
}
ctx.fillStyle = "#000000";
ctx.textAlign = 'right';
ctx.fillText("<< Back to menu", 290, 390);
}
function drawWon()
{
ctx.textAlign = 'center';
ctx.font = "bold 20pt sans-serif";
ctx.fillStyle = "#000000";
ctx.fillText(difficulties[gameState.mode].name, 150, 100);
ctx.font = "italic 12pt sans-serif";
var t = finishedTime;
var cTime = "Completed in ";
if((t/1000)>=60)
{
cTime+= Math.floor((t/1000)/60) + ":";
t = t % (60000);
}
cTime+= (Math.floor(t/1000) < 9 ? "0" : "") + Math.floor(t/1000);
ctx.fillText(cTime, 150, 120);
if(gameState.newBest)
{
ctx.fillText("New best time!", 150, 140);
}
else
{
t = difficulties[gameState.mode].bestTime;
cTime = "Best time ";
if((t/1000)>=60)
{
cTime+= Math.floor((t/1000)/60) + ":";
t = t % (60000);
}
cTime+= (Math.floor(t/1000) < 9 ? "0" : "") +
Math.floor(t/1000);
ctx.fillText(cTime, 150, 140);
}
ctx.fillText("(Click to jump to menu)", 150, 200);
}
function drawGame()
{
if(ctx==null) { return; }
if(!spritesLoaded) { requestAnimationFrame(drawGame); return; }
// Frame & update related timing
var currentFrameTime = Date.now();
if(lastFrameTime==0) { lastFrameTime = currentFrameTime; }
var timeElapsed = currentFrameTime - lastFrameTime;
gameTime+= timeElapsed;
// Update game
updateGame();
// Frame counting
var sec = Math.floor(Date.now()/1000);
if(sec!=currentSecond)
{
currentSecond = sec;
framesLastSecond = frameCount;
frameCount = 1;
}
else { frameCount++; }
// Clear canvas
ctx.fillStyle = "#ddddee";
ctx.fillRect(0, 0, 300, 400);
// Draw the current screen
if(gameState.screen=='won') { drawWon(); }
else if(gameState.screen=='playing') { drawPlaying(); }
else if(gameState.screen=='menu') { drawMenu(); }
// Draw the frame count
ctx.textAlign = "left";
ctx.font = "10pt sans-serif";
ctx.fillStyle = "#000000";
ctx.fillText("Frames: " + framesLastSecond, 5, 15);
// Update the lastFrameTime
lastFrameTime = currentFrameTime;
// Wait for the next frame...
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];
}