2298 lines
52 KiB
JavaScript
2298 lines
52 KiB
JavaScript
<!-- vim: set syntax=javascript : -->
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
<head>
|
|
<!-- Viewport isn't scalable -->
|
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
<meta name="viewport" content="width=devicewidth, minimal-ui" />
|
|
|
|
<!-- for apps on home screen -->
|
|
|
|
<!-- for ios 7 style, multi-resolution icon of 152x152 -->
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-barstyle" content="black-translucent">
|
|
<link rel="apple-touch-icon" href="images/icon-152.png">
|
|
<!-- for Chrome on Android, multi-resolution icon of 196x196 -->
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<link rel="shortcut icon" sizes="196x196" href="images/icon-196.png">
|
|
|
|
<style>
|
|
canvas {
|
|
border:1px solid #d3d3d3;
|
|
background-color: #000000;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body onload="startGame()">
|
|
<script>
|
|
|
|
// game vars
|
|
var things = [];
|
|
var score = 0;
|
|
var overdesc = "";
|
|
var curpath = [];
|
|
var pathdir = -1;
|
|
var pathvalid = false;
|
|
var curlevel = 1;
|
|
|
|
var lastmx = -1, lastmy = -1;
|
|
|
|
|
|
// goal types
|
|
|
|
|
|
var GOALVERB = {
|
|
'food': 'Eat',
|
|
'points': 'Earn',
|
|
'parades': 'Form',
|
|
'llamas': 'Clear',
|
|
'cats': 'Clear',
|
|
};
|
|
|
|
var MAXDIRS = 4;
|
|
var DIRXMOD = [ 0, 1, 0, -1 ];
|
|
var DIRYMOD = [ -1, 0, 1, 0 ];
|
|
|
|
var SCREENW = 480;
|
|
var SCREENH = 640;
|
|
|
|
var GRAVITY = 0.5;
|
|
var GRIDSIZE = 80;
|
|
var THINGSIZE = 64;
|
|
|
|
var TEXTSIZE = 16; // in points
|
|
var TEXTSPEED = 0.5;
|
|
var TEXTFADESPEED = 0.05;
|
|
var TEXTTIME = 35;
|
|
|
|
var HELPLINEWIDTH=4;
|
|
var HELPARROWSIZE=15;
|
|
var HELPTITLESIZE = 18;
|
|
var HELPTEXTSIZE = 12;
|
|
|
|
var GOALTEXTSIZE = 16;
|
|
|
|
var TITLETEXTSIZE = 36;
|
|
var TITLECREDITTEXTSIZE = 16;
|
|
var TITLESTARTTEXTSIZE = 26;
|
|
|
|
var PARADESPEED=14;
|
|
|
|
var EXPLODETICKS=20;
|
|
var EXPLODEGAIN= (THINGSIZE*2) / EXPLODETICKS;
|
|
var EXPLODEFADESPEED = 1.0 / EXPLODETICKS;
|
|
|
|
var LINEWIDTH=2;
|
|
var CROSSWIDTH=2;
|
|
|
|
|
|
var GRIDW = 5;
|
|
var GRIDH = 5;
|
|
|
|
var PATHARROWSIZE = 10;
|
|
|
|
var PARADELENGTH = 3;
|
|
|
|
var BOARDX = (SCREENW - (GRIDW * GRIDSIZE)) / 2;
|
|
var BOARDY = 64;
|
|
|
|
var FOODPOINTS = 10;
|
|
var LLAMAPOINTS = 100;
|
|
var CATPOINTS = 25;
|
|
var SLEEPYCATPOINTS = 5;
|
|
|
|
var overdesc = "";
|
|
|
|
var llamatext = "llama";
|
|
|
|
var image = new Array();
|
|
|
|
function loadimage(name, filename) {
|
|
image[name] = new Image();
|
|
image[name].src = filename;
|
|
}
|
|
|
|
function getgridthing(gridx, gridy) {
|
|
var i;
|
|
if (things == undefined) return null;
|
|
|
|
// only include non-animating things
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if ((things[i].gridx == gridx) && (things[i].gridy == gridy)) {
|
|
if (!things[i].isanimating()) {
|
|
return things[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getthingxy(x, y) {
|
|
var i,gridx,gridy;
|
|
var thing;
|
|
// use thing coords
|
|
/*
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if ((x >= things[i].x) && (x <= things[i].x + THINGSIZE-1) &&
|
|
(y >= things[i].y) && (y <= things[i].y + THINGSIZE-1)) {
|
|
return things[i];
|
|
}
|
|
}
|
|
*/
|
|
// use grid coords
|
|
gridx = Math.floor(x / GRIDSIZE);
|
|
gridy = Math.floor(y / GRIDSIZE);
|
|
|
|
return getgridthing(gridx, gridy);
|
|
}
|
|
|
|
function clearpath() {
|
|
curpath = [];
|
|
pathdir = -1;
|
|
pathvalid = false;
|
|
}
|
|
|
|
function pathcomplete() {
|
|
// get path type
|
|
switch(getpathtype()) {
|
|
case "chomp":
|
|
if ((curpath.length >= 2) &&
|
|
(curpath[0].type == "cat") &&
|
|
(curpath[0].eaten == false)) {
|
|
var i;
|
|
var ok = true;
|
|
// everything else is food?
|
|
for (i = 1; i < curpath.length; i++) {
|
|
if (curpath[i].type != "food") {
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
if (ok) {
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case "parade":
|
|
// long enough?
|
|
if (curpath.length >= PARADELENGTH) {
|
|
var count = 0;
|
|
var i;
|
|
// includes <= 1 llama?
|
|
for (i = 1; i < curpath.length; i++) {
|
|
if (curpath[i].type == "llama") {
|
|
count++;
|
|
}
|
|
}
|
|
if (count <= 1) {
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function backspacepath() {
|
|
// remove last thing in path
|
|
curpath.splice(curpath.length-1, 1);
|
|
|
|
validatepath();
|
|
|
|
dumppath("Reduced path to: ",curpath);
|
|
}
|
|
|
|
function addtopath(what) {
|
|
console.log("addpath() " + what.name);
|
|
|
|
//dumppath("addpath pre: ", curpath);
|
|
|
|
curpath.push(what);
|
|
|
|
validatepath();
|
|
|
|
if (curpath.length == 1) {
|
|
console.log("Starting path with " + what.name);
|
|
} else {
|
|
dumppath("Cur path is: ",curpath);
|
|
}
|
|
}
|
|
|
|
function validatepath() {
|
|
if (pathcomplete()) {
|
|
pathvalid = true;
|
|
} else {
|
|
pathvalid = false;
|
|
}
|
|
|
|
if (curpath.length == 2) {
|
|
pathdir = getdir(curpath[0], curpath[1]);
|
|
}
|
|
}
|
|
|
|
function pathcontains(what) {
|
|
var i;
|
|
if (curpath == undefined) return false;
|
|
|
|
for (i = 0; i < curpath.length; i += 1) {
|
|
if (curpath[i] == what) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isvalidpath(mypath) {
|
|
var valid = true;
|
|
var firstcat = false;
|
|
var fcount = false;
|
|
var lcount = 0;
|
|
var i;
|
|
|
|
for (i = 0; i < mypath.length - 1; i++) {
|
|
var thisone,nextone;
|
|
thisone = mypath[i];
|
|
/*
|
|
if (i == mypath.length - 1) { // last one
|
|
nextone = null;
|
|
} else {
|
|
nextone = mypath[i+1];
|
|
}
|
|
*/
|
|
nextone = mypath[i+1];
|
|
|
|
if (thisone.type == "food") {
|
|
fcount++;
|
|
}
|
|
if (thisone.type == "llama") {
|
|
lcount++;
|
|
}
|
|
|
|
if ((thisone.type == "cat") && nextone.type == "cat") {
|
|
// lines of cats are ok
|
|
} else if ((thisone.type == "cat") && nextone.type == "llama") {
|
|
// cat -> llama is only okay if there are no other llamas or food in the path
|
|
if (lcount >= 1) {
|
|
return false;
|
|
}
|
|
if (fcount >= 1) {
|
|
return false;
|
|
}
|
|
} else if ((thisone.type == "llama") && nextone.type == "cat") {
|
|
// ok
|
|
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "food")) {
|
|
// first cat -> food is ok
|
|
firstcat = true;
|
|
} else if ((i != 0) && firstcat && (thisone.type == "food")) {
|
|
// not the first one, first one was a cat, this one is food
|
|
} else {
|
|
// not ok
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
// would adding 'overthing' to our existing path result in a valid path?
|
|
function canextendpath(overthing) {
|
|
var pathtype;
|
|
|
|
if (!overthing) return false;
|
|
|
|
pathtype = getpathtype();
|
|
|
|
if ( isadjacent(overthing, curpath[curpath.length-1]) && // adjacent to last thing in path?
|
|
!pathcontains(overthing) && // path doesn't already contain this?
|
|
(pathtype == "parade" || isinpathdir(overthing)) // path in a straight line
|
|
) {
|
|
// create a fake new path containing this.
|
|
var fakepath = curpath.slice();
|
|
|
|
fakepath.push(overthing);
|
|
if (isvalidpath(fakepath)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isadjacent(thing1, thing2) {
|
|
// is thing1 adjacent to thing2?
|
|
var newgridx,newgridy;
|
|
var i;
|
|
|
|
if (thing1 == thing2) return false;
|
|
|
|
for (i = 0; i < MAXDIRS; i++) {
|
|
newgridx = thing1.gridx + DIRXMOD[i];
|
|
newgridy = thing1.gridy + DIRYMOD[i];
|
|
if ((thing2.gridx == newgridx) && (thing2.gridy == newgridy)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isinpathdir(what) {
|
|
var thisdir;
|
|
if (curpath.length <= 1) {
|
|
return true;
|
|
}
|
|
|
|
// get dir from last cell to what
|
|
thisdir = getdir(curpath[curpath.length - 1], what);
|
|
if ((thisdir != -1) && (thisdir == pathdir)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getdir(thing1, thing2) {
|
|
var i;
|
|
for (i = 0; i < MAXDIRS; i++) {
|
|
var newgx = thing1.gridx + DIRXMOD[i];
|
|
var newgy = thing1.gridy + DIRYMOD[i];
|
|
|
|
if ((thing2.gridx == newgx) && (thing2.gridy == newgy)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function startGame() {
|
|
|
|
loadimage('cat', 'images/cat.png');
|
|
loadimage('catfull', 'images/catfull.png');
|
|
loadimage('catscared', 'images/catscared.png');
|
|
loadimage('llama', 'images/alpaca.png');
|
|
loadimage('cheese', 'images/cheese.png');
|
|
loadimage('title', 'images/title.png');
|
|
|
|
game.init();
|
|
game.initlevels();
|
|
|
|
window.addEventListener('load', game.init, false);
|
|
window.addEventListener('resize', game.resize, false);
|
|
|
|
|
|
mainloop();
|
|
}
|
|
|
|
// valid types:
|
|
// none
|
|
// chomp
|
|
// parade
|
|
function getpathtype() {
|
|
if (curpath.length <= 1) return "none"
|
|
|
|
if (curpath[0].type == "cat") {
|
|
if (curpath[1].type == "food") {
|
|
return "chomp";
|
|
} else if ((curpath[1].type == "cat") || (curpath[1].type == "llama")) {
|
|
return "parade";
|
|
}
|
|
}
|
|
return "none";
|
|
}
|
|
|
|
function dumppath(prefix,arr) {
|
|
var str;
|
|
var i;
|
|
str = "";
|
|
for (i = 0; i < arr.length; i++) {
|
|
str = str + " " + arr[i].name;
|
|
}
|
|
console.log(prefix + str);
|
|
}
|
|
|
|
function thingsfalling() {
|
|
for (i = 0; i < things.length; i += 1) {
|
|
/*
|
|
switch (things[i].state) {
|
|
case "parade": // ok
|
|
case "chomp": // ok
|
|
case "stop": //ok
|
|
break;
|
|
default: // anything else
|
|
return true;
|
|
}
|
|
*/
|
|
if (things[i].state == "fall") {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function thingsmoving() {
|
|
var i;
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if (things[i].state == "parade") {
|
|
return "parade";
|
|
}
|
|
}
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if (things[i].state == "explode") {
|
|
return "chomp";
|
|
}
|
|
}
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if (things[i].state != "stop") {
|
|
return "other";
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function canstartpath(what) {
|
|
if (what.type != "cat") {
|
|
// only cats can start paths
|
|
console.log("not a cat");
|
|
return false;
|
|
}
|
|
|
|
if (isadjacenttotype(what, "llama")) {
|
|
// cats adjacent to llamas are frozen
|
|
console.log("next to a llama");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function clearthings() {
|
|
while (things.length > 0) {
|
|
things.pop();
|
|
}
|
|
things = [];
|
|
}
|
|
|
|
|
|
function getmousexy(event) {
|
|
var e,scale;
|
|
var adjustx, adjusty;
|
|
|
|
scale = game.curw / SCREENW;
|
|
|
|
if (event.type == "touchup") {
|
|
adjustx = (event.changedTouches[0].pageX - BOARDX) / scale;
|
|
adjusty = (event.changedTouches[0].pageY - BOARDY) / scale;
|
|
} else if (event.touches != undefined) {
|
|
adjustx = (event.touches[0].clientX - BOARDX) / scale;
|
|
adjusty = (event.touches[0].clientY - BOARDY) / scale;
|
|
} else {
|
|
adjustx = (event.pageX - BOARDX) / scale;
|
|
adjusty = (event.pageY - BOARDY) / scale;
|
|
}
|
|
|
|
return [ adjustx, adjusty ];
|
|
}
|
|
|
|
function isadjacenttotype(what, wanttype) {
|
|
var newgridx,newgridy;
|
|
var i;
|
|
|
|
for (i = 0; i < MAXDIRS; i++) {
|
|
newgridx = what.gridx + DIRXMOD[i];
|
|
newgridy = what.gridy + DIRYMOD[i];
|
|
|
|
if (isonscreen(newgridx,newgridy)) {
|
|
adjthing = getgridthing(newgridx, newgridy);
|
|
if ((adjthing != undefined) && (adjthing.type == wanttype)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function drawline(ctx,x1,y1,x2,y2,col,width) {
|
|
ctx.strokeStyle = col;
|
|
ctx.lineWidth = width;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1, y1);
|
|
ctx.lineTo(x2, y2);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function addcommas(num) {
|
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
function hashelp(lev) {
|
|
// past last level!
|
|
if (lev >= game.levels.length) return false;
|
|
|
|
return game.levels[lev].hashelp;
|
|
}
|
|
|
|
function drawcross(x, y, x2, y2, col, width) {
|
|
// cross out
|
|
ctx.fillStyle = col
|
|
ctx.lineWidth = width;
|
|
ctx.strokeStyle = col;
|
|
// NW -> SE
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y);
|
|
ctx.lineTo(x2, y2);
|
|
ctx.stroke();
|
|
|
|
// SW -> NE
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y2);
|
|
ctx.lineTo(x2, y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function drawarrow(ctx,x1,y1,x2,y2,col,width, arrowsize) {
|
|
drawline(ctx, x1, y1, x2, y2, col, width);
|
|
drawarrowhead(ctx, x1, y1, x2, y2, col, arrowsize);
|
|
}
|
|
|
|
function drawarrowhead(ctx, x1, y1, x2, y2, col, size) {
|
|
var startrads,endrads;
|
|
var size2;
|
|
size2 = size * 1.5;
|
|
|
|
startrads = Math.atan((y2 - y1) / (x2 - x1));
|
|
if (x2 >= x1) {
|
|
startrads += -90*Math.PI/180;
|
|
} else {
|
|
startrads += 90*Math.PI/180;
|
|
}
|
|
|
|
endrads = Math.atan((y2 - y1) / (x2 - x1));
|
|
if (x2 >= x1) {
|
|
endrads += 90 * Math.PI / 180;
|
|
} else {
|
|
endrads += -90 * Math.PI / 180;
|
|
}
|
|
|
|
ctx.strokeStyle = col;
|
|
ctx.fillStyle = col;
|
|
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.translate(x2,y2);
|
|
ctx.rotate(endrads);
|
|
ctx.moveTo(0,0);
|
|
ctx.lineTo(size,size2);
|
|
ctx.lineTo(-size,size2);
|
|
ctx.closePath();
|
|
ctx.restore();
|
|
ctx.fill();
|
|
}
|
|
|
|
var game = {
|
|
ratio: null,
|
|
curw: null,
|
|
curh: null,
|
|
levels: null,
|
|
|
|
canvas : document.createElement("canvas"),
|
|
|
|
init : function() {
|
|
this.ratio = SCREENW / SCREENH;
|
|
this.curw = SCREENW;
|
|
this.curh = SCREENH;
|
|
|
|
this.canvas.width = SCREENW;
|
|
this.canvas.height = SCREENH;
|
|
|
|
this.context = this.canvas.getContext("2d");
|
|
|
|
|
|
// find ipad/android/iphone so we can hide the address bar
|
|
this.ua = navigator.userAgent.toLowerCase();
|
|
this.android = this.ua.indexOf('android') > -1 ? true : false;
|
|
this.ios = ( this.ua.indexOf('iphone') > -1 || this.ua.indexOf('ipad') > -1 ) ? true : false;
|
|
|
|
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
|
|
|
|
/*
|
|
// try to go full screen?
|
|
var body = document.documentElement;
|
|
if (body.requestFullscreen) {
|
|
body.requestFullscreen();
|
|
} else if (body.webkitrequestFullscreen) {
|
|
body.webkitrequestFullscreen();
|
|
} else if (body.mozrequestFullscreen) {
|
|
body.mozrequestFullscreen();
|
|
} else if (body.msrequestFullscreen) {
|
|
body.msrequestFullscreen();
|
|
}
|
|
*/
|
|
|
|
this.resize();
|
|
|
|
window.requestAnimFrame = (function(){
|
|
return window.requestAnimationFrame ||
|
|
window.webkitRequestAnimationFrame ||
|
|
window.mozRequestAnimationFrame ||
|
|
window.oRequestAnimationFrame ||
|
|
window.msRequestAnimationFrame ||
|
|
function( callback ){
|
|
window.setTimeout(callback, 1000 / 60);
|
|
};
|
|
})();
|
|
|
|
//this.interval = setInterval(mainloop, 20);
|
|
|
|
this.canvas.addEventListener('mousedown', this.handlemousedown, false);
|
|
this.canvas.addEventListener('mouseup', this.handlemouseup, false);
|
|
this.canvas.addEventListener('mouseout', this.handlemouseup, false);
|
|
this.canvas.addEventListener('mousemove', this.handlemousemove, false);
|
|
|
|
this.canvas.addEventListener('touchstart', this.handlemousedown, false);
|
|
this.canvas.addEventListener('touchend', this.handlemouseup, false);
|
|
this.canvas.addEventListener('touchcancel', this.handlemouseup, false);
|
|
this.canvas.addEventListener('touchleave', this.handlemouseup, false);
|
|
this.canvas.addEventListener('touchmove', this.handlemousemove, false);
|
|
|
|
window.addEventListener('keypress', this.handlekeypress, false);
|
|
|
|
this.setstate("title");
|
|
},
|
|
|
|
resize : function() {
|
|
this.curh = window.innerHeight;
|
|
this.curw = this.curh * this.ratio;
|
|
|
|
// hide address bar on phones
|
|
if (this.android || this.ios) {
|
|
document.body.style.height = (window.innerHeight + 50) + 'px';
|
|
}
|
|
|
|
// scale canvas
|
|
this.canvas.style.width = this.curw + 'px';
|
|
this.canvas.style.height = this.curh + 'px';
|
|
|
|
window.setTimeout(function() {
|
|
window.scrollTo(0, 1);
|
|
}, 1);
|
|
},
|
|
|
|
|
|
initgamevars : function() {
|
|
score = 0;
|
|
this.frameNo = 0;
|
|
},
|
|
|
|
initlevelvars : function() {
|
|
// kill any existing objects
|
|
clearthings();
|
|
|
|
overdesc = "";
|
|
curpath = [];
|
|
pathdir = -1;
|
|
pathvalid = false;
|
|
},
|
|
|
|
addlevel: function (lev, hashelp) {
|
|
var mylevel;
|
|
mylevel = new Object();
|
|
mylevel.hashelp = hashelp;
|
|
mylevel.goals = new Array();
|
|
mylevel.thinglist = new Array();
|
|
this.levels[lev] = mylevel;
|
|
},
|
|
|
|
// thingtype pct
|
|
addlevelthings: function (lev) {
|
|
var i,idx;
|
|
for (i = 1 ; i < arguments.length; i += 2) {
|
|
idx = this.levels[lev].thinglist.push(new Object()) - 1;
|
|
this.levels[lev].thinglist[idx].type = arguments[i];
|
|
this.levels[lev].thinglist[idx].pct = arguments[i+1];
|
|
}
|
|
},
|
|
|
|
// goal1type goal1count goal2type goal2count etc...
|
|
addlevelgoals : function (lev) {
|
|
var i,idx;
|
|
for (i = 1 ; i < arguments.length; i += 2) {
|
|
idx = this.levels[lev].goals.push(new Object()) - 1;
|
|
this.levels[lev].goals[idx].type = arguments[i];
|
|
this.levels[lev].goals[idx].count = arguments[i+1];
|
|
this.levels[lev].goals[idx].progress = 0;
|
|
}
|
|
},
|
|
|
|
// earn progress towards goals
|
|
progress : function(type, amt) {
|
|
var i;
|
|
|
|
// past last level!
|
|
if (curlevel >= game.levels.length) return false;
|
|
|
|
console.log("progress()");
|
|
for (i = 0 ; i < this.levels[curlevel].goals.length; i++ ) {
|
|
console.log("this goal type is " + this.levels[curlevel].goals[i].type);
|
|
if (this.levels[curlevel].goals[i].type == type) {
|
|
this.levels[curlevel].goals[i].progress += amt;
|
|
if (this.levels[curlevel].goals[i].progress >= this.levels[curlevel].goals[i].count) {
|
|
this.levels[curlevel].goals[i].progress = this.levels[curlevel].goals[i].count;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
initlevels : function ( ) {
|
|
var mylevel,i,n;
|
|
|
|
console.log("doing level init");
|
|
this.levels = [];
|
|
this.addlevel(1, true);
|
|
this.addlevelgoals(1, "food", 10);
|
|
this.addlevelthings(1, "cat", 45, "food", 100);
|
|
|
|
this.addlevel(2, false);
|
|
this.addlevelgoals(2, "points", 200);
|
|
this.addlevelthings(1, "cat", 45, "food", 100);
|
|
|
|
this.addlevel(3, false);
|
|
this.addlevelgoals(3, "points", 500);
|
|
|
|
/*
|
|
for (i = 1; i < this.levels.length; i++) {
|
|
console.log("Level " + (i) + " goals:");
|
|
for (n = 0; n < this.levels[i].goals.length; n++) {
|
|
console.log(GOALVERB[this.levels[i].goals[n].type] + " " +
|
|
this.levels[i].goals[n].count + " " +
|
|
this.levels[i].goals[n].type);
|
|
}
|
|
}
|
|
*/
|
|
},
|
|
|
|
startlevel : function() {
|
|
this.initlevelvars();
|
|
this.populategrid();
|
|
|
|
this.state = "running";
|
|
},
|
|
|
|
populategrid : function() {
|
|
var i;
|
|
while (!anyvalidmoves()) {
|
|
var m;
|
|
|
|
console.log("populating grid...");
|
|
clearthings();
|
|
|
|
// populate initial things
|
|
for (y = 0; y < GRIDH; y++) {
|
|
for (x = 0; x < GRIDW; x++) {
|
|
// start off above the grid
|
|
things.push(new thing(x, y, "random"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// now move everything up so they'll fall down into the initial
|
|
// positions.
|
|
for (i = 0; i < things.length; i++) {
|
|
things[i].gridy -= GRIDH;
|
|
things[i].updatexy();
|
|
}
|
|
},
|
|
|
|
|
|
clear : function() {
|
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
},
|
|
|
|
drawtop : function() {
|
|
var texttodraw = overdesc;
|
|
var col = "#aaaaaa";
|
|
|
|
// clear
|
|
this.context.clearRect(0, 0, this.canvas.width-1, BOARDY-1);
|
|
|
|
if (game.state == "running") {
|
|
var y1 = 6;
|
|
var y2 = 32;
|
|
|
|
// show level
|
|
this.context.font = "16pt Futura";
|
|
this.context.textAlign = "center";
|
|
this.context.textBaseline = "top";
|
|
this.context.fillStyle = "#00aaee";
|
|
this.context.fillText("Level " + curlevel, SCREENW/2, y1);
|
|
|
|
// show score
|
|
this.context.font = "16pt Futura";
|
|
this.context.textAlign = "left";
|
|
this.context.textBaseline = "top";
|
|
this.context.fillStyle = "white";
|
|
this.context.fillText("Score: " + addcommas(score), 16, y2);
|
|
|
|
switch (thingsmoving()) {
|
|
case "parade":
|
|
col = "#00ff00";
|
|
texttodraw = "CAT PARADE!"
|
|
break;
|
|
case "chomp":
|
|
col = "#00cc00";
|
|
texttodraw = "Chomp!"
|
|
break;
|
|
default:
|
|
// show current object in path
|
|
if ((curpath != undefined) && (curpath.length >= 1)) {
|
|
var lastone = curpath[curpath.length - 1];
|
|
texttodraw = lastone.getdesc();
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
if (texttodraw != "") {
|
|
this.context.font = "16pt Futura";
|
|
this.context.textAlign = "right";
|
|
this.context.textBaseline = "top";
|
|
this.context.fillStyle = col;
|
|
this.context.fillText(texttodraw, SCREENW-16, y2);
|
|
}
|
|
} else if (game.state == "gameover") {
|
|
this.context.textAlign = "center";
|
|
this.context.textBaseline = "top";
|
|
shadowtext(this.context, "GAME OVER", 20, "red", SCREENW / 2, 5);
|
|
shadowtext(this.context, "Final Score: " + addcommas(score), 16, "white", SCREENW / 2, 35);
|
|
} else if (game.state == "levelcomplete") {
|
|
this.context.textAlign = "center";
|
|
this.context.textBaseline = "top";
|
|
shadowtext(this.context, "LEVEL COMPLETE!", 20, "#00ee00", SCREENW / 2, 5);
|
|
shadowtext(this.context, "Score: " + addcommas(score), 16, "white", SCREENW / 2, 35);
|
|
|
|
}
|
|
},
|
|
|
|
drawbottom : function() {
|
|
var bx = 0,by = BOARDY + GRIDH*GRIDSIZE + 15;
|
|
var bw = SCREENW, bh = SCREENH - by;
|
|
var x,y,ctx;
|
|
var margin = 10;
|
|
var indent = 50;
|
|
var boxindent = 20;
|
|
ctx = this.context;
|
|
|
|
if (game.state == "running") {
|
|
var texth = GOALTEXTSIZE+10;
|
|
var gradient;
|
|
var borderwidth = 5;
|
|
var goaltitlecol = "#00aaee";
|
|
|
|
// background
|
|
gradient = this.context.createLinearGradient(0, 0, bw, bh);
|
|
gradient.addColorStop(0, "#0000ff");
|
|
gradient.addColorStop(1, "#000055");
|
|
|
|
// clear
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(bx, by, bw, bh);
|
|
|
|
//outline
|
|
ctx.strokeStyle = "cyan";
|
|
ctx.lineWidth = 5;
|
|
ctx.beginPath();
|
|
ctx.rect(bx+(borderwidth/2), by+(borderwidth/2), bw-(borderwidth/2), bh-(borderwidth/2));
|
|
ctx.stroke();
|
|
|
|
// draw goals
|
|
x = bx + margin;
|
|
y = by + margin;
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "top";
|
|
shadowtext(ctx, "Goals:", GOALTEXTSIZE,goaltitlecol, x , y); y += texth;
|
|
|
|
if (curlevel < this.levels.length) {
|
|
for (n = 0; n < this.levels[curlevel].goals.length; n++) {
|
|
var goalcol = "#aaee00";
|
|
var goalmet = false;
|
|
|
|
if (this.levels[curlevel].goals[n].progress >= this.levels[curlevel].goals[n].count) {
|
|
goalmet = true;
|
|
}
|
|
|
|
if (goalmet) {
|
|
goalcol = "#00ddff";
|
|
} else {
|
|
goalcol = "#aaee00";
|
|
}
|
|
|
|
// goal name
|
|
var goaltext = GOALVERB[this.levels[curlevel].goals[n].type] + " " +
|
|
this.levels[curlevel].goals[n].count + " " +
|
|
this.levels[curlevel].goals[n].type;
|
|
|
|
var progtext = "[" + this.levels[curlevel].goals[n].progress + " / " +
|
|
this.levels[curlevel].goals[n].count + "]";
|
|
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "top";
|
|
shadowtext(ctx, goaltext, GOALTEXTSIZE,goalcol, x+indent, y);
|
|
|
|
ctx.textAlign = "right";
|
|
ctx.textBaseline = "top";
|
|
shadowtext(ctx, progtext, GOALTEXTSIZE,goalcol, SCREENW-indent, y);
|
|
|
|
// checkbox
|
|
var boxsize = texth-8;
|
|
var boxx = x+boxindent;
|
|
var boxy = y + texth/2 - boxsize/2;
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 3;
|
|
ctx.strokeStyle = "black";
|
|
ctx.rect(boxx,boxy,boxsize,boxsize); // shadow1
|
|
ctx.stroke();
|
|
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeStyle = goalcol;
|
|
ctx.rect(boxx,boxy,boxsize,boxsize); // shadow1
|
|
ctx.stroke();
|
|
|
|
// ticked ?
|
|
if (goalmet) {
|
|
//drawcross(boxx,boxy, boxx+boxsize-1, boxy+boxsize-1, goalcol, 2);
|
|
var tickleftx = boxx + boxsize/4;
|
|
var ticklefty = boxy + boxsize/2;
|
|
var tickbasex = boxx + boxsize/2;
|
|
var tickbasey = boxy+boxsize-boxsize/8;
|
|
var tickrightx = boxx + boxsize - boxsize/5;
|
|
var tickrighty = boxy + boxsize/8;
|
|
drawline(ctx, tickleftx,ticklefty, tickbasex, tickbasey, goalcol, 2);
|
|
drawline(ctx, tickbasex, tickbasey, tickrightx, tickrighty, goalcol, 2);
|
|
}
|
|
|
|
y += texth;
|
|
}
|
|
} else {
|
|
// past last level
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "top";
|
|
shadowtext(ctx, "(none)", GOALTEXTSIZE,goaltitlecol, x+indent, y);
|
|
}
|
|
} else if (game.state == "levelcomplete") {
|
|
this.context.textAlign = "center";
|
|
this.context.textBaseline = "bottom";
|
|
shadowtext(this.context, "Tap for next level", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, by + bh/2);
|
|
}
|
|
},
|
|
|
|
drawtitle : function() {
|
|
var ratio,w,h;
|
|
var img = image['title'];
|
|
var gradient;
|
|
|
|
// background
|
|
gradient = this.context.createLinearGradient(0, 0, 0, SCREENH);
|
|
gradient.addColorStop(0, "black");
|
|
gradient.addColorStop(1, "blue");
|
|
|
|
this.context.fillStyle = gradient;
|
|
this.context.fillRect(0, 0, SCREENW, SCREENH);
|
|
|
|
// title image
|
|
ratio = SCREENW / img.width;
|
|
w = img.width * ratio;
|
|
h = img.height * ratio;
|
|
this.context.drawImage(img, SCREENW/16, SCREENH - h, w, h);
|
|
|
|
// text
|
|
this.context.textAlign = "center";
|
|
this.context.textBaseline = "top";
|
|
shadowtext(this.context, "Cat Parade", TITLETEXTSIZE,"red", SCREENW / 2, 5);
|
|
shadowtext(this.context, "rpearce, 2016", TITLECREDITTEXTSIZE, "#00aaee", SCREENW / 2, 64);
|
|
|
|
this.context.textBaseline = "bottom";
|
|
shadowtext(this.context, "Tap to start", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, SCREENH - 64);
|
|
},
|
|
|
|
// return TRUE if there is no help for this level
|
|
drawhelp : function() {
|
|
var divamt = 2;
|
|
var imgsize = THINGSIZE / divamt;
|
|
var gridsize = GRIDSIZE / divamt;
|
|
var gradient,ctx,x,y,x2,y2,curx,cury;
|
|
var row1y,row2y;
|
|
var i;
|
|
var linex = [];
|
|
var liney = [];
|
|
|
|
var textyspace = HELPTEXTSIZE * 1.5;
|
|
var textxspace = 10;
|
|
|
|
var midpoint1 = SCREENW/8 * 5;
|
|
var midpoint2 = SCREENW/8 * 4;
|
|
|
|
ctx = this.context;
|
|
|
|
// background
|
|
gradient = this.context.createLinearGradient(0, 0, 0, SCREENH);
|
|
gradient.addColorStop(0, "black");
|
|
gradient.addColorStop(1, "blue");
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, SCREENW, SCREENH);
|
|
|
|
if (curlevel == 1) {
|
|
// top text
|
|
cury = textyspace*2;
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "bottom";
|
|
shadowtext(ctx, "Instructions", HELPTITLESIZE,"#00aaee", SCREENW / 2, cury);
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
shadowtext(ctx, "Drag a cat to eat cheese.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
|
|
shadowtext(ctx, " - Cats can eat any amount of cheese, but only in a straight line.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
|
|
shadowtext(ctx, " - Cats get tired after eating and cannot eat again.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
|
|
|
|
// help on eating
|
|
// row 1
|
|
x = imgsize;
|
|
y = cury;
|
|
row1y = y;
|
|
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
|
|
|
|
x2 = x + gridsize;
|
|
y2 = y;
|
|
ctx.drawImage(image['cheese'], x2, y2, imgsize, imgsize);
|
|
|
|
ctx.strokeStyle = "green";
|
|
ctx.lineWidth = LINEWIDTH;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + imgsize/2, y + imgsize/2);
|
|
ctx.lineTo(x2 + imgsize/2, y2 + imgsize/2);
|
|
ctx.stroke();
|
|
|
|
drawarrow(ctx, x + (imgsize/2), y + (imgsize/2),
|
|
x2 + (imgsize/2), y2 + (imgsize/2), "green", LINEWIDTH, PATHARROWSIZE);
|
|
|
|
cury = y2 + gridsize;
|
|
|
|
// row 2
|
|
x = imgsize;
|
|
y = cury;
|
|
row2y = y;
|
|
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
|
|
|
|
x2 = x + gridsize;
|
|
y2 = y;
|
|
ctx.drawImage(image['cheese'], x2, y2, imgsize, imgsize);
|
|
|
|
x2 = x2 + gridsize;
|
|
y2 = y;
|
|
ctx.drawImage(image['cheese'], x2, y2, imgsize, imgsize);
|
|
|
|
x2 = x2 + gridsize;
|
|
y2 = y;
|
|
ctx.drawImage(image['cheese'], x2, y2, imgsize, imgsize);
|
|
|
|
drawarrow(ctx, x + (imgsize/2), y + (imgsize/2),
|
|
x2 + (imgsize/2), y2 + (imgsize/2), "green", LINEWIDTH, PATHARROWSIZE);
|
|
|
|
cury = y2 + gridsize;
|
|
|
|
// arrow to middle
|
|
x = x2 + gridsize;
|
|
y = row1y + (imgsize);
|
|
|
|
x2 = x + gridsize*1.5;
|
|
y2 = y;
|
|
|
|
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
|
|
|
|
// middle
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "top";
|
|
|
|
x = midpoint1;
|
|
y = row1y+10;
|
|
shadowtext(ctx, "+" + FOODPOINTS + " points", HELPTEXTSIZE,"#cccc00", x, y);
|
|
y = row2y-10;
|
|
shadowtext(ctx, "per cheese", HELPTEXTSIZE,"#cccc00", x, y);
|
|
|
|
|
|
// arrow to right
|
|
x = midpoint1 + gridsize + 10;
|
|
y = row1y + (imgsize);
|
|
|
|
x2 = SCREENW - imgsize*2;
|
|
y2 = y;
|
|
|
|
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
|
|
|
|
// right
|
|
x = SCREENW - imgsize*2;
|
|
y = row1y;
|
|
ctx.drawImage(image['catfull'], x, y, imgsize, imgsize);
|
|
y = row2y;
|
|
ctx.drawImage(image['catfull'], x, y, imgsize, imgsize);
|
|
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
|
|
|
|
// LLAMA HELP
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
shadowtext(ctx, "Cats are scared of " + llamatext + "s and can't move when near them.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
|
|
x = (SCREENW / 2) - gridsize/2 - gridsize;
|
|
y = cury;
|
|
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
|
|
|
|
x += gridsize;
|
|
y = cury;
|
|
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
|
|
|
|
x += gridsize;
|
|
y = cury;
|
|
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
|
|
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
|
|
// PARADE HELP
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
shadowtext(ctx, "Drag a path through multiple cats to start a parade.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
shadowtext(ctx, " - Parades can turn corners.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
shadowtext(ctx, " - Parades can include one " + llamatext + " only.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
|
|
// top line of parade
|
|
x = imgsize;
|
|
y = cury;
|
|
row1y = y;
|
|
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
|
|
linex[0] = x + imgsize/2;
|
|
liney[0] = y + imgsize/2;
|
|
x += gridsize;
|
|
|
|
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
|
|
linex[3] = x + imgsize/2;
|
|
liney[3] = y + imgsize/2;
|
|
x += gridsize;
|
|
|
|
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
|
|
linex[4] = x + imgsize/2;
|
|
liney[4] = y + imgsize/2;
|
|
x += gridsize;
|
|
|
|
x = imgsize;
|
|
y += gridsize;
|
|
row2y = y;
|
|
|
|
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
|
|
linex[1] = x + imgsize/2;
|
|
liney[1] = y + imgsize/2;
|
|
x += gridsize;
|
|
|
|
ctx.drawImage(image['catfull'], x, y, imgsize, imgsize);
|
|
linex[2] = x + imgsize/2;
|
|
liney[2] = y + imgsize/2;
|
|
x += gridsize;
|
|
|
|
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
|
|
linex[5] = x + imgsize/2;
|
|
liney[5] = y + imgsize/2;
|
|
cury = y + textyspace;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
drawline(ctx, linex[i], liney[i], linex[i+1], liney[i+1], "green", LINEWIDTH);
|
|
}
|
|
drawarrow(ctx, linex[4], liney[4], linex[5], liney[5], "green", LINEWIDTH, PATHARROWSIZE);
|
|
|
|
// arrow to middle
|
|
x = x + gridsize;
|
|
y = row1y + (imgsize);
|
|
|
|
x2 = midpoint2 - 10;
|
|
y2 = y;
|
|
|
|
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
|
|
|
|
// explain points for parades
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "top";
|
|
x = midpoint2;
|
|
y = row1y;
|
|
shadowtext(ctx, "+" + CATPOINTS + " points per cat", HELPTEXTSIZE,"#cccc00", x, y);
|
|
y += textyspace;
|
|
shadowtext(ctx, "+" + SLEEPYCATPOINTS + " points per sleepy cat", HELPTEXTSIZE,"#cccc00", x, y);
|
|
y += textyspace;
|
|
shadowtext(ctx, "+" + LLAMAPOINTS + " points per " + llamatext, HELPTEXTSIZE,"#cccc00", x, y);
|
|
|
|
cury = y + textyspace;
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
cury += textyspace;
|
|
|
|
// GAME OVER HELP
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
shadowtext(ctx, "The game ends when there are no valid moves left.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
|
|
cury += textyspace;
|
|
|
|
|
|
ctx.textAlign = "center";
|
|
this.context.textBaseline = "bottom";
|
|
shadowtext(this.context, "Tap to start", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, SCREENH - textyspace*2);
|
|
} else {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
drawgrid : function() {
|
|
this.context.strokeStyle = "black";
|
|
this.context.beginPath();
|
|
// draw grid
|
|
for (y = 0; y < GRIDH*GRIDSIZE; y += GRIDSIZE) {
|
|
for (x = 0; x < GRIDW*GRIDSIZE; x += GRIDSIZE) {
|
|
this.context.rect(BOARDX + x, BOARDY + y, GRIDSIZE-1, GRIDSIZE-1);
|
|
}
|
|
}
|
|
|
|
this.context.stroke();
|
|
},
|
|
|
|
drawpath : function() {
|
|
var ctx = this.context;
|
|
var col;
|
|
var i;
|
|
|
|
if ((curpath == undefined) || (curpath.length < 1)) {
|
|
return;
|
|
}
|
|
|
|
if (pathvalid) {
|
|
col = "green";
|
|
} else {
|
|
col = "red";
|
|
}
|
|
ctx.strokeStyle = col;
|
|
ctx.lineWidth = LINEWIDTH;
|
|
|
|
// draw line to show current path
|
|
ctx.beginPath();
|
|
ctx.moveTo(BOARDX + curpath[0].x + THINGSIZE/2, BOARDY + curpath[0].y + THINGSIZE/2);
|
|
for (i = 1; i < curpath.length; i++) {
|
|
ctx.lineTo(BOARDX + curpath[i].x + THINGSIZE/2, BOARDY + curpath[i].y + THINGSIZE/2);
|
|
}
|
|
ctx.stroke();
|
|
|
|
if (curpath.length >= 2) {
|
|
x1 = curpath[curpath.length-2].x + (THINGSIZE/2);
|
|
y1 = curpath[curpath.length-2].y + (THINGSIZE/2);
|
|
|
|
x2 = curpath[curpath.length-1].x + (THINGSIZE/2);
|
|
y2 = curpath[curpath.length-1].y + (THINGSIZE/2);
|
|
|
|
drawarrowhead(ctx, BOARDX + x1, BOARDY + y1, BOARDX + x2, BOARDY + y2, col, PATHARROWSIZE);
|
|
}
|
|
|
|
},
|
|
|
|
/*
|
|
handleclick : function(event) {
|
|
// make sure nothing is moving
|
|
if (thingsmoving()) return;
|
|
|
|
// did you click on an object?
|
|
var clickedthing = getthingxy(event.pageX,event.pageY);
|
|
if (clickedthing) {
|
|
clickedthing.kill();
|
|
}
|
|
},
|
|
*/
|
|
|
|
handlekeypress : function(event) {
|
|
var ch;
|
|
ch = String.fromCharCode(event.keyCode || event.charCode);
|
|
console.log("pressed " + ch);
|
|
if (ch == 'a') {
|
|
console.log("ending game");
|
|
game.state = "gameover";
|
|
} else if (ch == 'n') {
|
|
game.state = "levelcomplete";
|
|
}
|
|
},
|
|
|
|
handlemousedown : function(event) {
|
|
var adjustx, adjusty;
|
|
event.preventDefault();
|
|
|
|
if (game.state == "running") {
|
|
var coords,adjustx,adjusty;
|
|
coords = getmousexy(event);
|
|
adjustx = coords[0];
|
|
adjusty = coords[1];
|
|
|
|
// remember coords for touch screens
|
|
if (event.type == "touchstart") {
|
|
lastmx = adjustx;
|
|
lastmy = adjusty;
|
|
}
|
|
|
|
// ignore multi-touch
|
|
if ((curpath != undefined) && curpath.length >= 1) return;
|
|
|
|
// make sure nothing is moving
|
|
if (thingsfalling()) return;
|
|
|
|
// clear existing path
|
|
clearpath();
|
|
|
|
// did you click on an object?
|
|
var onthing = getthingxy(adjustx, adjusty);
|
|
if (onthing) {
|
|
if (canstartpath(onthing)) {
|
|
console.log("Initial click on " + onthing.name);
|
|
addtopath(onthing);
|
|
} else {
|
|
// just show description
|
|
overdesc = onthing.getdesc();
|
|
}
|
|
}
|
|
} else if (game.state == "gameover") {
|
|
game.setstate("title");
|
|
} else if (game.state == "title") {
|
|
curlevel = 1;
|
|
game.initgamevars();
|
|
game.helporstartlev();
|
|
} else if (game.state == "help") {
|
|
game.startlevel();
|
|
} else if (game.state == "levelcomplete") {
|
|
curlevel++;
|
|
game.helporstartlev();
|
|
}
|
|
},
|
|
|
|
helporstartlev : function() {
|
|
if (hashelp(curlevel)) {
|
|
game.setstate("help");
|
|
} else {
|
|
game.startlevel();
|
|
}
|
|
},
|
|
|
|
setstate : function(newstate) {
|
|
if (newstate == "help") {
|
|
// decide which word to use
|
|
if (onein(2)) {
|
|
llamatext = "llama";
|
|
} else {
|
|
llamatext = "alpaca";
|
|
}
|
|
}
|
|
this.state = newstate;
|
|
},
|
|
|
|
|
|
handlemousemove : function(event) {
|
|
var coords,adjustx,adjusty;
|
|
coords = getmousexy(event);
|
|
adjustx = coords[0];
|
|
adjusty = coords[1];
|
|
|
|
// remember coords for touch screens
|
|
if (event.type == "touchmove") {
|
|
lastmx = adjustx;
|
|
lastmy = adjusty;
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
if (game.state != "running") return;
|
|
|
|
// make sure nothing is moving
|
|
if (thingsmoving()) return;
|
|
|
|
// existing path?
|
|
if (curpath != undefined && curpath.length > 0) {
|
|
var overthing = getthingxy(adjustx, adjusty);
|
|
var lastinpath,secondlast;
|
|
|
|
if (curpath == undefined) {
|
|
lastinpath = null;
|
|
} else {
|
|
lastinpath = curpath[curpath.length-1];
|
|
if (curpath.length >= 2) {
|
|
secondlast = curpath[curpath.length-2];
|
|
} else {
|
|
secondlast = null;
|
|
}
|
|
}
|
|
|
|
if ((secondlast != undefined) && (overthing == secondlast)) {
|
|
backspacepath();
|
|
} else if (canextendpath(overthing)) {
|
|
// add it to the path
|
|
addtopath(overthing);
|
|
} else if (pathcontains(overthing) && pathcomplete()) {
|
|
pathvalid = true;
|
|
} else {
|
|
pathvalid = false;
|
|
}
|
|
}
|
|
},
|
|
|
|
handlemouseup : function(event) {
|
|
var ok = true;
|
|
var adjustx, adjusty;
|
|
var ptype;
|
|
var points = 0;
|
|
var overthing = null;
|
|
|
|
event.preventDefault();
|
|
|
|
if (game.state != "running") return;
|
|
|
|
ptype = getpathtype();
|
|
|
|
// special case for touch screens, as the 'touchend' events don't have x/y
|
|
if ((event.type == "touchend") || (event.type == "touchleave") || (event.type == "touchcancel")) {
|
|
adjustx = lastmx;
|
|
adjusty = lastmy;
|
|
} else {
|
|
coords = getmousexy(event);
|
|
adjustx = coords[0];
|
|
adjusty = coords[1];
|
|
}
|
|
|
|
overthing = getthingxy(adjustx, adjusty);
|
|
|
|
overdesc = "";
|
|
|
|
if ((curpath == undefined) || (curpath.length == 0)) {
|
|
console.log("mouseup() - no path");
|
|
ok = false;
|
|
} else if (thingsmoving()) {
|
|
console.log("mouseup() - things are moving");
|
|
ok = false;
|
|
} else if (!isvalidpath(curpath)) {
|
|
console.log("mouseup() - path isn't valid");
|
|
ok = false;
|
|
} else if (!pathcomplete(curpath)) {
|
|
console.log("mouseup() - path ("+ ptype + ") isn't complete");
|
|
ok = false;
|
|
} else if ((overthing == undefined) || !pathcontains(overthing)) {
|
|
console.log("mouseup() - not over something in path");
|
|
ok = false;
|
|
}
|
|
|
|
if (!ok) {
|
|
clearpath();
|
|
return;
|
|
}
|
|
|
|
// figure out path type
|
|
points = 0;
|
|
switch(ptype) {
|
|
case "chomp":
|
|
// killall except first and last
|
|
if (curpath.length > 2) {
|
|
var i;
|
|
for (i = 1; i < curpath.length - 1; i++) {
|
|
curpath[i].givepoints();
|
|
curpath[i].addabove();
|
|
//curpath[i].kill();
|
|
curpath[i].startexplode();
|
|
}
|
|
}
|
|
|
|
curpath[curpath.length-1].givepoints();
|
|
|
|
// first one chomps last one
|
|
curpath[0].chomp(curpath[curpath.length - 1]);
|
|
break;
|
|
case "parade":
|
|
if (curpath.length >= 2) { // should always be true
|
|
var i;
|
|
// everything in the path exits via a parade
|
|
for (i = 0; i < curpath.length; i++) {
|
|
curpath[i].givepoints();
|
|
curpath[i].startparade();
|
|
}
|
|
clearpath();
|
|
} else {
|
|
// just kill everything in path
|
|
while (curpath != undefined && curpath.length > 0) {
|
|
curpath[0].givepoints();
|
|
curpath[0].addabove();
|
|
curpath[0].kill();
|
|
curpath.splice(0, 1);
|
|
}
|
|
}
|
|
game.progress("parades", 1);
|
|
break;
|
|
}
|
|
|
|
clearpath();
|
|
},
|
|
}
|
|
|
|
function onein(num) {
|
|
var roll;
|
|
roll = Math.floor(Math.random() * num);
|
|
if (roll == 0) return true;
|
|
return false;
|
|
}
|
|
|
|
function getrandomcolour() {
|
|
var letters = '0123456789ABCDEF';
|
|
var col = '#';
|
|
for (var i = 0; i < 6; i++ ) {
|
|
col += letters[Math.floor(Math.random() * 16)];
|
|
}
|
|
return col;
|
|
}
|
|
|
|
function getrandomname() {
|
|
var letters = '0123456789';
|
|
var name = "";
|
|
for (var i = 0; i < 3; i++ ) {
|
|
name += letters[Math.floor(Math.random() * 10)];
|
|
}
|
|
return name;
|
|
}
|
|
|
|
function isonscreen(x,y) {
|
|
if ((x < 0) || (y < 0) || (x >= GRIDW) || (y >= GRIDH)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function shadowtext(ctx, text, size, col, x, y) {
|
|
ctx.font = size + "pt Futura";
|
|
// shadows
|
|
ctx.fillStyle = "black";
|
|
ctx.fillText(text, x-1, y-1);
|
|
ctx.fillText(text, x+1, y);
|
|
ctx.fillText(text, x+1, y+1);
|
|
ctx.fillText(text, x-1, y+1);
|
|
// real text
|
|
ctx.fillStyle = col;
|
|
ctx.fillText(text, x, y);
|
|
}
|
|
|
|
function getrandomtype() {
|
|
var pct,type = null,i;
|
|
var thinglist;
|
|
|
|
if (curlevel >= game.levels.length) {
|
|
thinglist = null;
|
|
} else {
|
|
thinglist = game.levels[curlevel].thinglist;
|
|
}
|
|
if (thinglist == undefined || thinglist.length == 0) {
|
|
// default thinglist
|
|
thinglist = [
|
|
{
|
|
type: 'cat',
|
|
pct: 45
|
|
},
|
|
{
|
|
type: 'food',
|
|
pct: 90
|
|
},
|
|
{
|
|
type: 'llama',
|
|
pct: 100
|
|
}
|
|
];
|
|
}
|
|
|
|
/*
|
|
console.log("thinglist length is " + thinglist.length);
|
|
for (i = 0; i < thinglist.length; i++) {
|
|
console.log(" thing " + i + " is " + thinglist[i].type + " pct " + thinglist[i].pct);
|
|
}
|
|
*/
|
|
|
|
pct = Math.random() * 100;
|
|
for (i = 0; i < thinglist.length; i++) {
|
|
if (pct <= thinglist[i].pct) {
|
|
type = thinglist[i].type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (type == null) {
|
|
console.log("couldn't find type! pct is " + pct);
|
|
type = 'cat';
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function coord(x,y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
|
|
function thing(gridx, gridy, type, text) {
|
|
this.opacity = 1.0;
|
|
|
|
if (type == "random") {
|
|
type = getrandomtype();
|
|
}
|
|
|
|
this.type = type;
|
|
switch (this.type) {
|
|
case "cat":
|
|
this.color = "#b5dea8";
|
|
break;
|
|
case "food":
|
|
this.color = "#d8db03";
|
|
break;
|
|
case "llama":
|
|
this.color = "#cccccc";
|
|
break;
|
|
case "text":
|
|
this.color = "#00cc00";
|
|
break;
|
|
default: // should never happen
|
|
this.color = getrandomcolour();
|
|
break;
|
|
}
|
|
|
|
if (this.type == "text") {
|
|
this.size = TEXTSIZE;
|
|
} else {
|
|
this.size = THINGSIZE;
|
|
}
|
|
this.lifetime = 0;
|
|
|
|
if (text == undefined) {
|
|
this.name = type + "-" + getrandomname();
|
|
} else {
|
|
this.name = text;
|
|
}
|
|
|
|
this.gridx = gridx;
|
|
if (gridy == "top") {
|
|
var i;
|
|
highest = 999;
|
|
// find highest
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if ((things[i].gridx == gridx) &&
|
|
(things[i].gridy <= highest)) {
|
|
highest = things[i].gridy-1;
|
|
}
|
|
}
|
|
gridy = highest;
|
|
//console.log("adding thing at gridy = " + gridy);
|
|
}
|
|
this.gridy = gridy;
|
|
this.yspeed = 0;
|
|
if (this.type == "text") {
|
|
this.state = "text";
|
|
} else {
|
|
this.state = "stop";
|
|
}
|
|
|
|
this.path = [];
|
|
this.pathspeed = PARADESPEED;
|
|
|
|
this.expcount = 0;
|
|
this.expmax = 0;
|
|
|
|
if (this.type == "text" ) {
|
|
this.x = gridx * GRIDSIZE + (GRIDSIZE/2);
|
|
this.y = gridy * GRIDSIZE + (GRIDSIZE/2) - 10; // TODO: fix to 10!
|
|
} else {
|
|
this.x = gridx * GRIDSIZE + (GRIDSIZE/2) - (this.size/2);
|
|
this.y = gridy * GRIDSIZE + (GRIDSIZE/2) - (this.size/2);
|
|
}
|
|
|
|
this.eaten = false;
|
|
|
|
this.pushpath = function(x,y) {
|
|
this.path.push(new coord(x, y));
|
|
}
|
|
|
|
this.poppath = function() {
|
|
this.path.splice(0, 1);
|
|
}
|
|
|
|
// are there any valid moves starting with this thing?
|
|
this.hasvalidmoves = function() {
|
|
var i,newx,newy;
|
|
|
|
if (this.isanimating()) {
|
|
return false;
|
|
}
|
|
if (this.type != "cat") {
|
|
return false;
|
|
}
|
|
if (isadjacenttotype(this, "llama")) {
|
|
return false;
|
|
}
|
|
|
|
// chomp
|
|
if (isadjacenttotype(this, "food") && !this.eaten) {
|
|
return true;
|
|
}
|
|
|
|
// parade - this is the hardest to check for.
|
|
// check for any adjacent cats or llamas
|
|
// cat: if it also has an adjacent cat/llama, we're good
|
|
// llama: if it also has an adjacent cat, we're good
|
|
for (i = 0; i < MAXDIRS; i++) {
|
|
var adj;
|
|
newx = this.gridx + DIRXMOD[i];
|
|
newy = this.gridy + DIRYMOD[i];
|
|
if (isonscreen(newx, newy)) {
|
|
adj = getgridthing(newx, newy);
|
|
if (adj != undefined) {
|
|
if (adj.type == "cat") {
|
|
// adjacent cat
|
|
if (isadjacenttotype(adj, "llama") ||
|
|
isadjacenttotype(adj, "cat")) {
|
|
return true;
|
|
}
|
|
} else if (adj.type == "llama") {
|
|
// adjacent llama
|
|
if (isadjacenttotype(adj, "cat")) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
this.givepoints = function() {
|
|
var points = 0;
|
|
if (this.type == "food") {
|
|
points = FOODPOINTS;
|
|
game.progress("food", 1);
|
|
} else if (this.type == "llama") {
|
|
points = LLAMAPOINTS;
|
|
game.progress("llamas", 1);
|
|
} else if (this.type == "cat") {
|
|
if (this.eaten == true) {
|
|
points = SLEEPYCATPOINTS;
|
|
} else {
|
|
points = CATPOINTS;
|
|
}
|
|
game.progress("cats", 1);
|
|
}
|
|
score += points;
|
|
game.progress("points", points);
|
|
|
|
// add animation
|
|
things.push(new thing(this.gridx, this.gridy, "text", "+" + points));
|
|
}
|
|
|
|
|
|
this.getdesc = function() {
|
|
var desc = "";
|
|
if (this.type == "cat") {
|
|
if (isadjacenttotype(this, "llama")) {
|
|
if (this.eaten == true) {
|
|
desc = "scared sleepy cat";
|
|
} else {
|
|
desc = "scared cat";
|
|
}
|
|
} else if (this.eaten == true) {
|
|
desc = "sleepy cat";
|
|
} else {
|
|
desc = "cat";
|
|
}
|
|
} else if (this.type == "llama") {
|
|
var num;
|
|
num = (Math.floor(game.frameNo / 50)) % 2;
|
|
if (num == 0) {
|
|
desc = "llama";
|
|
} else {
|
|
desc = "alpaca";
|
|
}
|
|
} else if (this.type == "food") {
|
|
desc = "cheese";
|
|
}
|
|
return desc;
|
|
}
|
|
|
|
this.draw = function() {
|
|
var yoff;
|
|
var inpath = false;
|
|
var howbig,myx,myy;
|
|
|
|
ctx = game.context;
|
|
|
|
// check whether I'm in the drawn path
|
|
if (pathcontains(this)) {
|
|
inpath = true;
|
|
}
|
|
|
|
// set opacity
|
|
ctx.globalAlpha = this.opacity;
|
|
|
|
// draw myself
|
|
if (this.type == "text") {
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "center";
|
|
shadowtext(ctx, this.name, TEXTSIZE, this.color, BOARDX + this.x,BOARDY + this.y);
|
|
} else {
|
|
if (this.type == "cat") {
|
|
var myimage;
|
|
|
|
if (this.state == "exploding") {
|
|
}
|
|
|
|
if (isadjacenttotype(this, "llama")) {
|
|
myimage = image['catscared'];
|
|
} else if (this.eaten == true) {
|
|
myimage = image['catfull'];
|
|
} else {
|
|
myimage = image['cat'];
|
|
}
|
|
} else if (this.type == "llama") {
|
|
myimage = image['llama'];
|
|
} else if (this.type == "food") {
|
|
myimage = image['cheese'];
|
|
}
|
|
howbig = this.size;
|
|
myx = this.x;
|
|
myy = this.y;
|
|
if (inpath) {
|
|
var growpct = 50;
|
|
howbig = howbig * ((100+growpct) / 100);
|
|
myx -= (growpct/2/100) * this.size;
|
|
myy -= (growpct/2/100) * this.size;
|
|
}
|
|
|
|
ctx.drawImage(myimage, BOARDX + myx, BOARDY + myy, howbig, howbig);
|
|
}
|
|
|
|
/*
|
|
} else {
|
|
ctx.fillStyle = this.color;
|
|
ctx.fillRect(BOARDX + this.x, BOARDY + this.y, this.size, this.size);
|
|
}
|
|
*/
|
|
|
|
// frozen cat?
|
|
/*
|
|
if ((this.type == "cat") && !this.isanimating()) {
|
|
if (isadjacenttotype(this, "llama")) {
|
|
// cross out
|
|
ctx.fillStyle = "red";
|
|
ctx.lineWidth = CROSSWIDTH;
|
|
ctx.strokeStyle = "red";
|
|
// NW -> SE
|
|
ctx.beginPath();
|
|
ctx.moveTo(BOARDX + this.x, BOARDY + this.y);
|
|
ctx.lineTo(BOARDX + this.x + this.size - 1, BOARDY + this.y + this.size - 1);
|
|
ctx.stroke();
|
|
|
|
// SW -> NE
|
|
ctx.beginPath();
|
|
ctx.moveTo(BOARDX + this.x, BOARDY + this.y + this.size - 1);
|
|
ctx.lineTo(BOARDX + this.x + this.size - 1, BOARDY + this.y);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
*/
|
|
|
|
// draw text on me
|
|
/*
|
|
if (this.type == "cat") {
|
|
} else if (this.type == "llama") {
|
|
} else {
|
|
ctx.fillStyle = "black";
|
|
ctx.fillText(this.name, BOARDX + this.x + 10, BOARDY + this.y + (THINGSIZE/2));
|
|
ctx.fillText(this.eaten ? "FULL" : "", BOARDX + this.x + 10, BOARDY + this.y + (THINGSIZE/2) + 10);
|
|
}
|
|
*/
|
|
|
|
// back to full opacity
|
|
ctx.globalAlpha = 1.0;
|
|
|
|
/*
|
|
// path outline
|
|
if (inpath && this.state != "parade" && this.state != "explode") {
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 1;
|
|
ctx.fillStyle = "black";
|
|
// outline it
|
|
if (pathvalid) {
|
|
ctx.strokeStyle = "green";
|
|
} else {
|
|
ctx.strokeStyle = "red";
|
|
}
|
|
ctx.rect(BOARDX + this.x, BOARDY + this.y, this.size, this.size);
|
|
ctx.stroke();
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
this.issametype = function(otherthing) {
|
|
if (otherthing == undefined) return false;
|
|
if (otherthing.type == this.type) return true;
|
|
return false;
|
|
}
|
|
|
|
this.snaptogrid = function() {
|
|
if (this.y % (GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2)) != 0) {
|
|
this.y = this.gridy * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
|
|
}
|
|
}
|
|
|
|
this.getstoppedbelowthing = function() {
|
|
var bt;
|
|
bt = getgridthing(this.gridx, this.gridy + 1);
|
|
if (bt && bt.state != "fall") {
|
|
return bt;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
this.addabove = function() {
|
|
// add a new cat above us
|
|
things.push(new thing(this.gridx, "top", "random"));
|
|
}
|
|
|
|
this.kill = function() {
|
|
// kill ourselves
|
|
var idx = things.indexOf(this);
|
|
things.splice(idx, 1);
|
|
}
|
|
|
|
this.updatexy = function() {
|
|
this.x = this.gridx * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
|
|
this.y = this.gridy * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
|
|
}
|
|
|
|
this.setgridxy = function(x,y) {
|
|
this.gridx = x;
|
|
this.gridy = y;
|
|
this.updatexy();
|
|
}
|
|
|
|
this.chomp = function(food) {
|
|
var origgx,origgy;
|
|
// remember cat loc
|
|
origgx = this.gridx;
|
|
origgy = this.gridy;
|
|
|
|
// add new object above cat location
|
|
this.addabove();
|
|
|
|
// move cat to food location
|
|
this.setgridxy(food.gridx,food.gridy);
|
|
|
|
|
|
// move food to cat location (so new object will
|
|
// be added in the correct column.
|
|
//food.setgridxy(origgx, origgy);
|
|
|
|
// make food explode
|
|
//food.addabove();
|
|
//food.kill();
|
|
food.startexplode();
|
|
|
|
// mark that we've eaten something
|
|
this.eaten = true;
|
|
|
|
console.log("chomp");
|
|
}
|
|
|
|
this.isanimating = function() {
|
|
if (this.type == "text") return true;
|
|
if (this.state == "explode") return true;
|
|
if (this.state == "parade") return true;
|
|
return false;
|
|
}
|
|
|
|
this.startexplode = function() {
|
|
this.expcount=1;
|
|
this.expmax=EXPLODETICKS;
|
|
this.state = "explode";
|
|
}
|
|
|
|
this.startparade = function() {
|
|
var i,startidx=-1;
|
|
// find our position in the current path
|
|
for (i = 0; i < curpath.length; i++) {
|
|
if (curpath[i] == this) {
|
|
startidx = i;
|
|
break;
|
|
}
|
|
}
|
|
if (startidx == -1) {
|
|
// not in path?!
|
|
return;
|
|
}
|
|
|
|
// populate our own movement path.
|
|
for (i = startidx; i < curpath.length; i++) {
|
|
var nextidx;
|
|
// add waypoint of next cell in selected path
|
|
nextidx = i+1;
|
|
if (nextidx >= curpath.length) {
|
|
var dir;
|
|
// we're at the end. move off the screen in the direction of the path.
|
|
|
|
// find dir from previous to us...
|
|
// don't worry about checking for being the first element cause
|
|
// parades can't be 1 in length;
|
|
dir = getdir(curpath[i-1],curpath[i]);
|
|
this.pushpath(curpath[i].x + (DIRXMOD[dir]*GRIDSIZE*GRIDW),
|
|
curpath[i].y + (DIRYMOD[dir]*GRIDSIZE*GRIDH));
|
|
} else {
|
|
this.pushpath(curpath[nextidx].x, curpath[nextidx].y);
|
|
}
|
|
}
|
|
console.log("Starting parade: " + this.name + " path len is [" + this.path.length + "]");
|
|
|
|
this.addabove(); // add cat above
|
|
this.state = "parade"; // go to parade mode
|
|
|
|
}
|
|
|
|
this.calcgridxy = function() {
|
|
this.gridx = Math.floor(this.x / GRIDSIZE);
|
|
this.gridy = Math.floor(this.y / GRIDSIZE);
|
|
}
|
|
|
|
this.move = function() {
|
|
// not at bottom and nothing fixed below us?
|
|
var dofall = false;
|
|
var belowthing = null,atbottom = false;
|
|
|
|
if (this.type == "text") {
|
|
// slowly move up
|
|
this.y -= TEXTSPEED;
|
|
this.lifetime++;
|
|
if (this.lifetime >= TEXTTIME) {
|
|
this.opacity -= TEXTFADESPEED;
|
|
if (this.opacity <= 0) {
|
|
this.kill();
|
|
}
|
|
}
|
|
} else if (this.state == "explode") {
|
|
this.expcount++;
|
|
if (this.expcount >= this.expmax) {
|
|
this.kill();
|
|
} else {
|
|
// get bigger
|
|
this.size += EXPLODEGAIN;
|
|
this.opacity -= EXPLODEFADESPEED;
|
|
|
|
if (this.opacity < 0) this.opacity = 0;
|
|
|
|
// adjust x/y
|
|
this.x = (this.gridx * GRIDSIZE) + (GRIDSIZE/2) - (this.size/2);
|
|
this.y = (this.gridy * GRIDSIZE) + (GRIDSIZE/2) - (this.size/2);
|
|
|
|
}
|
|
} else if (this.state == "parade") {
|
|
// move towards next cell in path
|
|
var nextx = this.path[0].x;
|
|
var nexty = this.path[0].y;
|
|
var xdone = 0, ydone = 0;
|
|
|
|
if (this.x < nextx) {
|
|
this.x += this.pathspeed;
|
|
if (this.x > nextx) xdone = true;
|
|
} else if (this.x > nextx) {
|
|
this.x -= this.pathspeed;
|
|
if (this.x < nextx) xdone = true;
|
|
} else {
|
|
xdone = true;
|
|
}
|
|
|
|
if (this.y < nexty) {
|
|
this.y += this.pathspeed;
|
|
if (this.y > nexty) ydone = true;
|
|
} else if (this.y > nexty) {
|
|
this.y -= this.pathspeed;
|
|
if (this.y < nexty) ydone = true;
|
|
} else {
|
|
ydone = true;
|
|
}
|
|
|
|
this.calcgridxy();
|
|
|
|
// at destination?
|
|
if (xdone && ydone) {
|
|
this.poppath();
|
|
// path finished?
|
|
if (this.path == undefined || this.path.length == 0) {
|
|
this.kill();
|
|
}
|
|
}
|
|
} else {
|
|
// regular gravity
|
|
if ((this.gridy >= GRIDH-1)) {
|
|
atbottom = true;
|
|
}
|
|
if (!atbottom && !this.getstoppedbelowthing()) {
|
|
// accelerate
|
|
this.yspeed += GRAVITY;
|
|
|
|
// move
|
|
this.y += this.yspeed;
|
|
|
|
// don't go below bottom of screen
|
|
this.hitBottom();
|
|
|
|
|
|
// calc new gridx / gridy
|
|
this.calcgridxy();
|
|
|
|
this.state = "fall";
|
|
}
|
|
|
|
// hit something?
|
|
if (atbottom || this.getstoppedbelowthing()) {
|
|
// something below us.
|
|
|
|
// stop
|
|
this.yspeed = 0;
|
|
|
|
// snap to grid
|
|
this.snaptogrid();
|
|
|
|
this.state = "stop";
|
|
}
|
|
}
|
|
}
|
|
|
|
this.hitBottom = function() {
|
|
var rockbottom = GRIDSIZE * GRIDH;
|
|
if (this.y > rockbottom) {
|
|
this.y = rockbottom;
|
|
this.yspeed = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
function mainloop() {
|
|
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
|
|
var i;
|
|
|
|
requestAnimFrame(mainloop);
|
|
|
|
game.frameNo += 1;
|
|
|
|
game.clear();
|
|
if (game.state == "title") {
|
|
game.drawtitle();
|
|
} else if (game.state == "help") {
|
|
if (game.drawhelp()) {
|
|
}
|
|
} else {
|
|
game.drawgrid(); // draw grid
|
|
// move objects
|
|
for (i = 0; i < things.length; i += 1) {
|
|
things[i].move();
|
|
}
|
|
// move and draw non-animating objects
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if (!things[i].isanimating()) {
|
|
things[i].draw();
|
|
}
|
|
}
|
|
// move and draw animating objects
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if (things[i].isanimating()) {
|
|
things[i].draw();
|
|
}
|
|
}
|
|
// draw top of canvas (score etc)
|
|
game.drawtop();
|
|
|
|
// draw bottom of canvas (goals)
|
|
game.drawbottom();
|
|
|
|
// draw dragged arrow
|
|
game.drawpath();
|
|
|
|
// check for game over and level over
|
|
if (!thingsmoving()) {
|
|
if (levelfinished()) {
|
|
game.state = "levelcomplete";
|
|
} else if (!anyvalidmoves()) {
|
|
game.state = "gameover";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function levelfinished() {
|
|
var i;
|
|
// past last level!
|
|
if (curlevel >= game.levels.length) return false;
|
|
|
|
for (i = 0 ; i < game.levels[curlevel].goals.length; i++ ) {
|
|
if (game.levels[curlevel].goals[i].progress < game.levels[curlevel].goals[i].count) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function anyvalidmoves() {
|
|
var gotmoves = false;
|
|
var i;
|
|
|
|
if (things == undefined || things.length == 0) return false;
|
|
|
|
for (i = 0; i < things.length; i += 1) {
|
|
if (things[i].hasvalidmoves()) {
|
|
gotmoves = true;
|
|
break;
|
|
}
|
|
}
|
|
return gotmoves;
|
|
}
|
|
|
|
function everyinterval(n) {
|
|
if ((game.frameNo / n) % 1 == 0) {return true;}
|
|
return false;
|
|
}
|
|
|
|
//<button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)">ACCELERATE</button>
|
|
</script>
|
|
<br>
|
|
</body>
|
|
</html>
|
|
|
|
|