catparade/cat.html

8172 lines
205 KiB
JavaScript

<!DOCTYPE html>
<html>
<head>
<!-- need utf8 for unicode chars -->
<meta http-equiv="content-type" content="text/html; charset=UTF8">
<!-- Viewport isn't scalable -->
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1, user-scalable=0, minimal-ui">
<title>Cat Parade</title>
<!-- 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-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="images/icon-152.png">
<!-- for Chrome on Android, multi-resolution icon of 192x192 -->
<meta name="mobile-web-app-capable" content="yes">
<link rel="manifest" href="manifest.json">
<link rel="icon" sizes="192x192" href="images/icon-192.png">
<style>
body {
margin: 0;
padding: 0;
background: #000000;
}
canvas {
display: block;
margin: 0 auto;
background-color: #000000;
}
@font-face {
font-family: "BlueStone";
src: url("BlueStone.ttf");
}
</style>
</head>
<body style="margin: 0; padding:0;" bgcolor="#000000" onload="startGame()">
<canvas></canvas>
<script>
var FONT = "BlueStone";
// canvas style did have : border:1px solid #d3d3d3;
var debug = false;
// game vars
var things = [];
var score = 0;
var globmulti = 0;
var overdesc = "";
var curpath = [];
var pathdir = -1;
var pathdoor = null;
var pathvalid = false;
var curlevel = 1;
var lastmx = -1, lastmy = -1;
var scorecols = [ "#cccc00", "#00cc00", "#00dddd", "#dd00dd" ];
var mbdown = false;
var FLASHSPEED = 0.05;
var TURNSBARSPEEDUP = 0.02;
var TURNSBARSPEEDDOWN = 0.01;
var BAGCAPACITY = 6; // how many cats to pop a bag
var BRICKHP = 6; // how many cats it takes to remove a brick
var CURTAINHP = 2; // how many climbs it takes to remove a curtain
var SLASHDIST = 3; // space between slash marks on bricks
// for background
var catalpha = 1.0;
var ctx = null;
// goal types
var GOALVERB = {
'turn': 'Survive',
//'points': 'Earn',
//parades': 'Form',
'food': 'Eat',
'llama': 'Clear',
'cat': 'Clear',
'goat': 'Clear',
'door': 'Enter',
'sunlight': 'Wait out',
'bag': 'Burst',
'toad': 'Slap',
'whitecat': 'Attack',
'brick': 'Break',
'curtain': 'Shred',
'ambush': 'Launch',
};
var GOALNAME = {
'whitecat': 'white cat',
'sunlight': 'sun',
};
var prizetypes = [
"tissues", "shears", "magiccarpet",
];
var wipe = {
to: "",
dir: "",
count: 0,
active: false,
isactive : function() {
if (this.active) {
return this.dir;
}
return false;
},
// howin and howoutout should be 'up' or 'down'
getval : function(fullamt, howin, howout, def) {
var num,how;
if (this.active) {
if (this.dir == "in") {
how = howin;
} else if (this.dir == "out") {
how = howout;
}
} else {
return def;
}
num = (this.count / this.max) * fullamt;
if (how == "down") {
num = fullamt - num;
}
return num;
},
start : function(nextstate, dir, howlong) {
this.to = nextstate;
this.count = 0;
this.max = howlong;
this.dir = dir;
this.active = true;
},
tick : function() {
this.count++;
if (this.count >= this.max) {
var nextstate = this.to;
this.active = false;
this.to = "";
this.count = 0;
if (nextstate != "") {
game.setstate(nextstate);
}
}
},
};
//var FULLSTAR = "\u2605";
//var EMPTYSTAR = "\u2606";
var STARWID_TOPGOALS = 16;
var STARWID_LEVSEL = 16;
var STARWID_LEVSEL_LOCKED = 16;
var STARWID_LEVSEL_TOP = 24;
var STARWID_ENDLEV = 32;
var DEF_GRIDSIZE = 80;
var DEF_THINGSIZE = 64;
var DEF_GRIDW = 5;
var DEF_GRIDH = 5;
var GRIDSIZE = null;
var THINGSIZE = null;
var GRIDW = null;
var GRIDH = null;
var FIREWORKSIZE = 5;
var FIREWORKFADESPEED = 0.015;
var FIREWORKSHARDS = 32;
var FIREWORKCOUNT = 4;
var FIREWORKTRAILLEN = 5;
var MAXDIRS = 4;
var DIRXMOD = [ 0, 1, 0, -1 ];
var DIRYMOD = [ -1, 0, 1, 0 ];
var DIRNAME = [ "n", "e", "s", "w"];
var MAXDIAGDIRS = 8;
var DIAGDIRXMOD = [ 0, 1, 1, 1, 0, -1, -1, -1 ];
var DIAGDIRYMOD = [ -1, -1, 0, 1, 1, 1, 0, -1 ];
var DIAGDIRNAME = [ "n", "ne", "e", "se", "s", "sw", "w", "nw"];
var NUMWINIMAGES = 26;
var WINIMGZOOMSPEED = 0.1;
var SCREENW = 480;
var SCREENH = 640;
var GRAVITY = 0.5;
var TURNSLEFTTEXTSIZE = 12;
var TEXTSIZE = 16; // in points
var TEXTSIZEMULTIPLIER = 28; // in points
var TEXTSPEED = 0.5;
var TEXTFADESPEED = 0.05;
var TEXTTIME = 35;
var TEXTSIZEGOALGET = 38; // in points
var LEVELTEXTSIZE = 12;
var SCORETEXTSIZE = 14;
var STARPOINTTEXTSIZE = 10;
var TITLESIZELEVSELECT = 18; // in points
var TEXTSIZELEVSELECT = 20; // in points
var TEXTSIZETOTSTARS = 16; // in points
var TEXTSIZELEVSCORE = 8; // in points
var TEXTSIZELEVSTARS = 10; // in points
var TEXTSIZELEVSCROLL = 22; // in points
var HELPLINEWIDTH=4;
var HELPARROWSIZE=15;
var HELPTITLESIZE = 18;
var HELPSUBTITLESIZE = 14;
//var HELPTEXTSIZE = 12;
var HELPTEXTSIZE = 9;
var GOALTEXTSIZE = 14;
var HELPTEXTYSPACE = HELPTEXTSIZE * 1.8;
var TITLETEXTSIZE = 36;
var TITLECREDITTEXTSIZE = 16;
var TITLESTARTTEXTSIZE = 26;
var BACKTOTITLESIZE = 20; // game over button
var TAPBUTTONSIZE = 26; // game over button
var TAPBUTTONSIZEC = 20; // levcomplete button
var TAPBUTTONSIZESTAR = 26; // levcomplete button
var PARADESPEED=12;
var EXPLODETICKS=20;
var EXPLODEGAIN= (DEF_THINGSIZE*2) / EXPLODETICKS;
var EXPLODEFADESPEED = 1.0 / EXPLODETICKS;
var SHRINKTICKS=20;
var SHRINKLOSE= (DEF_THINGSIZE / SHRINKTICKS);
var LINEWIDTH=2;
var CROSSWIDTH=2;
var BOTTOMBORDERWIDTH=3;
var LEVSELGRIDW = 5;
var LEVSELGRIDH = 6;
var PATHARROWSIZE = 10;
var PATHLINECOLGOOD = "#00ee00";
var PATHLINECOLBAD = "#ee0000";
var FALLARROWSIZE = 4;
var PARADELENGTH = 3;
var LEVSEL_X = Math.floor((SCREENW - (DEF_GRIDW * DEF_GRIDSIZE)) / 2);
var LEVSEL_Y = 64;
var DEF_BOARDX = Math.floor((SCREENW - (DEF_GRIDW * DEF_GRIDSIZE)) /2 );
var BOARDX = DEF_BOARDX;
var BOARDY = 64;
// 'tap to continue' button
var tapx,tapy,tapw,taph;
// back to title screen buton
var levsel_contx = BOARDX;
var levsel_conty = BOARDY + (DEF_GRIDSIZE*DEF_GRIDH) + DEF_GRIDSIZE + DEF_GRIDSIZE/4;
var levsel_contw = SCREENW - (BOARDX*2);
var levsel_conth = (DEF_GRIDSIZE/2);
var FOODPOINTS = 10;
var LLAMAPOINTS = 100;
var GOATPOINTS = 50;
var CATPOINTS = 20;
var SLEEPYCATPOINTS = 40;
var CATBOXPOINTS = 40;
var overdesc = "";
var llamatext = "llama";
var image = new Array();
var winimg = null;
var nimages = 0;
var maximages = 0;
function loadimage(name, filename) {
image[name] = new Image();
image[name].onload = game.incloadprogress;
image[name].src = filename;
}
function gridxyhasthingtype(gridx, gridy, wanttype) {
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) && (things[i].type == wanttype)) {
if (!things[i].isanimating()) {
return things[i];
}
}
}
return null;
}
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].issleepy()) ||
(curpath[0].type == "box" && curpath[0].boxfull) ) ) {
var i;
var ok = true,lastok = false;
// everything else except last is food?
for (i = 1; i < curpath.length-1; i++) {
if (curpath[i].type != "food") {
ok = false;
break;
}
}
// last one is food?
if (curpath[curpath.length-1].type == "food") {
lastok = true;
} else if (curpath[curpath.length-1].type == "box" &&
!curpath[curpath.length-1].boxfull &&
curpath[0].type == "cat") {
// last one is empty box, and first one was a cat?
lastok = true;
}
if (ok && lastok) {
return true;
}
}
break;
case "slap":
if ((curpath.length == 2) &&
(curpath[0].type == "cat") &&
(curpath[1].type == "toad") &&
!curpath[0].issleepy()) {
return true;
}
break;
case "attack":
if ((curpath.length == 2) &&
(curpath[0].type == "cat") &&
(curpath[1].type == "whitecat") &&
!curpath[0].issleepy()) {
return true;
}
break;
case "parade":
// long enough?
if (curpath.length >= PARADELENGTH) {
var lcount = 0,gcount = 0;
var i;
// includes <= 1 llama or has a goat?
for (i = 1; i < curpath.length; i++) {
if (curpath[i].type == "llama") {
lcount++;
} else if (curpath[i].type == "goat") {
gcount++;
}
}
if ((lcount <= 1) || (gcount)) {
return true;
}
}
break;
case "climb":
if ((curpath[0].type == "cat") &&
(curpath[1].type == "curtain") &&
(curpath[curpath.length-1].type != "curtain") &&
!curpath[0].issleepy()) {
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;
}
// return sfirst thing of matching type
function pathcontainstype(type) {
var i;
if (curpath == undefined) return false;
for (i = 0; i < curpath.length; i += 1) {
if (curpath[i].type == type) {
return curpath[i];
}
}
return null;
}
function catcolmatches(a, b) {
var cola,colb;
if (a.type == "box") {
cola = null;
} else {
cola = a.catcol;
}
if (b.type == "box") {
colb = null;
} else {
colb = b.catcol;
}
if (cola == undefined) return true;
if (colb == undefined) return true;
if (cola == colb) return true;
return false;
}
function isvalidpath(mypath) {
var valid = true;
var fcount = 0;
var lcount = 0;
var gcount = 0;
var count = 0;
var ccount = 0;
var i;
//var startcol = null;
var firstone = null;
for (i = 0; i < mypath.length - 1; i++) {
var thisone,nextone,dirtonext;
thisone = mypath[i];
/*
if (i == mypath.length - 1) { // last one
nextone = null;
} else {
nextone = mypath[i+1];
}
*/
if (i == 0) {
/*
if (thisone.type == "box") {
startcol = null;
} else {
startcol = thisone.catcol;
}
*/
firstone = thisone;
}
nextone = mypath[i+1];
dirtonext = getdir(thisone, nextone);
if (thisone.type == "food") {
fcount++;
}
if (thisone.type == "llama") {
lcount++;
}
if (thisone.type == "goat") {
gcount++;
}
if (thisone.type == "curtain") {
ccount++;
}
count++;
if ((thisone.type == "cat") && (nextone.type == "cat") && catcolmatches(nextone, firstone) && (ccount == 0)) {
// no parades on level 1
if (curlevel == 1) {
return false;
}
// ...but otherwise lines of cats are ok if they're the right colour
} else if ((thisone.type == "cat") && (nextone.type == "box") && (nextone.boxfull == true) && (ccount == 0)) {
// full boxes can be part of parades, colour doesn't matter
} else if (thisone.type == "box" && thisone.boxfull &&
((nextone.type == "cat" && catcolmatches(nextone, firstone)) || nextone.type == "llama" || nextone.type == "goat")) {
// full boxes can be part of parades, colour doesn't matter
} else if (thisone.type == "box" && thisone.boxfull &&
nextone.type == "box" && nextone.boxfull) {
// full boxes can parade on to other full boxes
} else if ((thisone.type == "goat") && (ccount == 0) &&
((nextone.type == "cat" && catcolmatches(nextone, firstone)) || nextone.type == "llama" || nextone.type == "goat") || (nextone.type == "box" && nextone.boxfull == true)) {
// goat can go to llama or correctly coloured cat or cat-in-box
// ok
} else if ((nextone.type == "door") && (count >= PARADELENGTH) && (fcount == 0) && (ccount == 0) && (count >= 3) &&
(thisone.type == "goat" || thisone.type == "cat" || thisone.type == "llama" || (thisone.type == "box" && thisone.boxfull == true))) {
// completed parades can extend into doors
} else if ((thisone.type == "cat") && nextone.type == "llama" && (ccount == 0)) {
// no parades on level 1
if (curlevel == 1) {
return false;
}
// cat -> llama is only okay if:
// 1. there is no food in the path
if (fcount >= 1) {
return false;
}
// and either:
// 2a. there are no other llamas in the path
if (lcount >= 1) {
// or
// 2b. there is a goat in the path
if (!gcount) {
return false;
}
}
} else if ((thisone.type == "cat") && nextone.type == "goat" && (ccount == 0)) {
// cat -> goat is okay
} else if ((thisone.type == "llama") && (ccount == 0) &&
((nextone.type == "cat" && catcolmatches(nextone, firstone)) || nextone.type == "goat")) {
// no parades on level 1
if (curlevel == 1) {
return false;
}
// ...but otherwise llama -> goat/cat is okay
} else if ((thisone.type == "llama") && (nextone.type == "llama") && !ccount) {
// llama -> llama okay if we have a goat in the path
if (!gcount) {
return false;
}
} else if (thisone.type == "curtain") {
// anywhere from a curtain is okay. (direction checked in canextendpath())
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "food")) {
// first cat -> food is ok
} else if ((i == 0) && (thisone.type == "box") && thisone.boxfull && (nextone.type == "food")) {
// first full box -> food is ok
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "toad")) {
// first cat -> toad is ok
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "curtain")) {
// first cat -> curtain is ok if we're going UP/DOWN only
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "whitecat")) {
// first cat -> whitecat is ok
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "box") && !nextone.boxfull) {
// first cat -> box is ok
} else if ((i != 0) && (firstone.type == "cat" || firstone.type == "box") && (ccount == 0) && (thisone.type == "food") && (nextone.type == "food")) {
// not the first one, first one was a cat/box, this one and next are food
} else if ((i != 0) && (firstone.type == "cat") && (ccount == 0) && (thisone.type == "food") &&
(nextone.type == "box") && !nextone.boxfull) {
// not the first one, first one was a cat, food -> box
} 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;
if (!isongrid(overthing.gridx, overthing.gridy)) {
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() {
game.init();
game.initlevels();
playerdata.load();
//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") || (curpath[1].type == "box" && !curpath[1].boxfull)) {
return "chomp";
} else if (curpath[1].type == "toad") {
return "slap";
} else if (curpath[1].type == "curtain") {
return "climb";
} else if (curpath[1].type == "whitecat") {
return "attack";
} else if ((curpath[1].type == "cat") || (curpath[1].type == "llama") || (curpath[1].type == "goat")
|| (curpath[1].type == "box" && curpath[1].boxfull) ) {
return "parade";
}
} else if (curpath[0].type == "box") {
if (curpath[1].type == "food") {
return "chomp";
} else if ((curpath[1].type == "cat") || (curpath[1].type == "llama") || (curpath[1].type == "goat")
|| (curpath[1].type == "box" && curpath[1].boxfull) ) {
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") || (things[i].state == "shrink")) {
return "parade";
}
}
for (i = 0; i < things.length; i += 1) {
if (things[i].state == "explode") {
return things[i].explodereason;
}
}
for (i = 0; i < things.length; i += 1) {
if (things[i].state != "stop") {
return "other";
}
}
return false;
}
function canstartpath(what) {
if (what.type == "cat") {
// cats can start paths
} else if (what.type == "box" && what.boxfull) {
// full boxes can start paths
} else {
console.log("not a cat");
return false;
}
//if (isadjacenttotype(what, "llama")) {
if (what.isscared()) {
// 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 x,y;
var adjustx, adjusty;
var xoff=0,yoff=0;
var element = game.canvas;
scale = game.curw / SCREENW;
//var rect = game.canvas.getBoundingClientRect();
if (element.offsetParent !== undefined) {
do {
xoff += element.offsetLeft;
yoff += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
// Also add the <html> offsets in case there's a position:fixed bar
var _stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(game.canvas, null)['paddingLeft'], 10) || 0;
var _stylePaddingTop = parseInt(document.defaultView.getComputedStyle(game.canvas, null)['paddingTop'], 10) || 0;
var _styleBorderLeft = parseInt(document.defaultView.getComputedStyle(game.canvas, null)['borderLeftWidth'], 10) || 0;
var _styleBorderTop = parseInt(document.defaultView.getComputedStyle(game.canvas, null)['borderTopWidth'], 10) || 0;
var html = document.body.parentNode;
var _htmlTop = html.offsetTop;
var _htmlLeft = html.offsetLeft;
xoff += _stylePaddingLeft + _styleBorderLeft + _htmlLeft;
yoff += _stylePaddingTop + _styleBorderTop + _htmlTop;
if (event.type == "touchup") {
//x = (event.changedTouches[0].pageX - xoff) / scale;
//y = (event.changedTouches[0].pageY - yoff) / scale;
//adjustx = (event.changedTouches[0].pageX - xoff - BOARDX) / scale;
//adjusty = (event.changedTouches[0].pageY - yoff -BOARDY) / scale;
x = (event.touches[0].clientX - xoff) / scale;
y = (event.touches[0].clientY - yoff) / scale;
//adjustx = (event.changedTouches[0].clientX - xoff - BOARDX) / scale;
//adjusty = (event.changedTouches[0].clientY - yoff -BOARDY) / scale;
} else if (event.touches != undefined) {
x = (event.touches[0].clientX - xoff) / scale;
y = (event.touches[0].clientY - yoff) / scale;
//adjustx = (event.touches[0].clientX - xoff - BOARDX) / scale;
//adjusty = (event.touches[0].clientY - yoff - BOARDY) / scale;
} else {
x = (event.pageX - xoff) / scale;
y = (event.pageY - yoff) / scale;
//adjustx = (event.pageX - xoff - BOARDX) / scale;
//adjusty = (event.pageY - yoff - BOARDY) / scale;
}
if (game.state == "levselect") {
adjustx = x - LEVSEL_X;
adjusty = y - LEVSEL_Y;
} else {
adjustx = x - BOARDX;
adjusty = y - BOARDY;
}
return [ adjustx, adjusty, x, y ];
}
//
function isadjacenttotype(what, wanttype, exceptionthing, catcol, boxfull) {
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) && (adjthing != exceptionthing)) {
var ok = true;
if (catcol != undefined) {
if (adjthing.catcol != what.catcol) {
ok = false;
}
}
if (boxfull != undefined) {
if (adjthing.boxfull != boxfull) {
ok = false;
}
}
if (ok) {
return true;
}
}
}
}
return false;
}
function addslash(slashes, w, h) {
slashes.push(Math.floor(rnd(w))); // x1
slashes.push(Math.floor(rnd(h))); // y1
slashes.push(Math.floor(rnd(w))); // x2
slashes.push(Math.floor(rnd(h))); // y2
}
function drawslashes(ctx, x, y, slashes) {
var i,n;
if (slashes == undefined || slashes.length <= 0) {
return;
}
for (i = 0; i < slashes.length; i+= 4) {
var dx,dy,xstep,ystep,angle;
// get angle of line
dx = slashes[i+2] - slashes[i];
dy = slashes[i+1] - slashes[i+3];
//console.log("dxy is " + dx + "," + dy);
angle = Math.atan2(dy,dx);
//angle *= 180/Math.PI; // degrees
// add 90 degrees
angle += 90;
// back to radians
//angle *= Math.PI/180;
xstep = Math.cos(angle);
ystep = Math.sin(angle);
//console.log("line " + i + " - angle is " + (angle * 180/Math.PI) + " steps are " + xstep + "," + ystep);
for (n = 0; n < 3; n++) {
var modx,mody;
modx = xstep * n * SLASHDIST;
mody = ystep * n * SLASHDIST;
drawline(ctx, x + slashes[i] + modx, y + slashes[i+1] + mody,
x + slashes[i+2] + modx, y + slashes[i+3] + mody,
"black", 1);
}
}
}
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 drawtick(ctx, x1, y1, x2, y2, col,wid) {
var w,h;
w = x2 - x1;
h = y2 - y1;
var tickleftx = x1 + w/4;
var ticklefty = y1 + h/2;
var tickbasex = x1 + w/2;
var tickbasey = y1+h-h/8;
var tickrightx = x1 + w - w/5;
var tickrighty = y1 + h/8;
drawline(ctx, tickleftx,ticklefty, tickbasex, tickbasey, col, wid);
drawline(ctx, tickbasex, tickbasey, tickrightx, tickrighty, col, wid);
}
function drawcross(ctx, 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();
}
function countalivethingsoftype(type) {
var i,count=0;
for (i = 0; i < things.length; i++) {
if (things[i].type == type) {
if (!things[i].isanimating()) {
count++;
}
}
}
return count;
}
function countthingsoftype(type) {
var i,count=0;
for (i = 0; i < things.length; i++) {
if (things[i].type == type) {
count++;
}
}
return count;
}
var playerdata = {
totstars: 0,
levscore: [],
saveall : function() {
if (game.cheat) return;
console.log("*** saving tot stars = " + this.totstars);
// save max level
localStorage.setItem('totstars',this.totstars);
// save level scores
localStorage.setItem("levscores", JSON.stringify(this.levscore));
},
load : function() {
var num,i,temp;
// local star count
num = localStorage.getItem('totstars');
if (num != undefined) {
this.totstars = num;
console.log("playerdata.load() - totstars is " + this.totstars);
} else {
console.log("playerdata.load() - couldn't find totstars");
this.totstars = 0;
}
// load scores
temp = localStorage.getItem("levscores");
if (temp == undefined) {
this.levscore = [];
} else {
this.levscore = JSON.parse(temp);
}
/*
// save level scores
localStorage.setItem("levscores", JSON.stringify(this.levscore));
// load level hiscores
for (i = 0; i < game.levels.length; i++) {
num = localStorage.getItem("levscore" + i);
if (num == undefined) {
this.levscore[i] = 0;
} else {
//console.log("load() - lev " + i + " score " + num);
this.levscore[i] = num;
}
console.log("load() - lev " + i + " score is " + this.levscore[i]);
}
*/
// calculate player stars based on scores
this.settotstars(game.countplayerstars());
},
getlevscore : function (lev) {
if (this.levscore[lev] == undefined) {
return null;
}
return this.levscore[lev];
},
clearall : function() {
//localStorage.removeItem("maxlevel"); // old one
/*
localStorage.removeItem("totstars");
for (i = 0; i < 100; i++) {
localStorage.removeItem("levscore" + i);
}
*/
localStorage.clear();
console.log("cleared local storage.");
},
settotstars : function(num) {
console.log("settotstars() - setting total stars to " + num);
this.totstars = num;
this.saveall();
},
setlevscore : function(lev, num) {
this.levscore[lev] = num;
this.saveall();
},
};
var game = {
ratio: null,
curw: null,
curh: null,
levels: null,
state: "",
explodereason: "",
cheat: 0,
winimgsize: 0,
frameNo: 0,
turnsleft: 0,
fontloaded: false,
levseloff: 0,
resetcount: 0,
rng: null,
goatdir: 1,
goatx: 0,
goaty: 0,
goalbooster: 0, // extra chance of a goal object appearing
turnsbarpct: 0,
screenflash: 0,
newestlevel: null,
canvas: null,
setrndseed : function(seed) {
if (this.rng != undefined) {
delete this.rng;
}
this.rng = new RNG(seed);
},
seedrnd : function(min, max) {
return this.rng.rndrange(min, max);
},
checkfont : function() {
var tempctx = this.temp.getContext("2d");
var result = false;
tempctx.font = TITLETEXTSIZE + "pt " + FONT;
tempctx.fillText("www", 0, 0);
try {
result = document.fonts.check(TITLETEXTSIZE + "pt " + FONT);
}
catch(err) {
console.log("document.fonts.check() not found, just assuming font is okay.");
result = true;
}
if (result) {
console.log("font ok");
}
game.fontloaded = result;
return result;
},
addfireworks : function() {
var delay = 0;
for (i = 0; i < FIREWORKCOUNT; i++) {
this.addfirework(delay);
delay += 5;
}
},
addfirework : function(delay) {
var i,nshards = FIREWORKSHARDS;
var x,y,col;
switch (rnd(6)) {
case 0: col = "#ff0000"; break;
case 1: col = "#00ff00"; break;
case 2: col = "#0000ff"; break;
case 3: col = "#ffff00"; break;
case 4: col = "#00ffff"; break;
case 5: col = "#ff00ff"; break;
default: col = "#ff0000"; break;
}
x = rnd(GRIDW);
y = rnd(GRIDH);
//console.log("adding fw at " + x + "," + y);
for (i = 0; i < nshards; i++) {
things.push(new thing(x, y, "firework", "firework-text", col));
if (delay != undefined) {
things[things.length-1].delay = delay;
}
}
},
addflash : function() {
this.screenflash = 1.0;
game.dirty = true;
},
addbagpop : function(gridy) {
var i,nshards = 10;
game.progress("bag", 1);
for (i = 0; i < nshards; i++) {
things.push(new thing(GRIDW, gridy, "bagpop"));
}
},
fullscreen : function(i) {
// go full-screen
if (i.requestFullscreen) {
i.requestFullscreen();
} else if (i.webkitRequestFullscreen) {
i.webkitRequestFullscreen();
} else if (i.mozRequestFullScreen) {
i.mozRequestFullScreen();
} else if (i.msRequestFullscreen) {
i.msRequestFullscreen();
}
},
incloadprogress : function() {
/*
setTimeout(function () {
nimages++;
console.log(nimages + " / " + maximages + " images loaded.");
}, (rnd(3)+1) * 1000);
*/
nimages++;
//console.log(nimages + " / " + maximages + " images loaded.");
},
drawloader : function() {
var bgcol = "#008c8c";
var pct = nimages / maximages;
ctx.fillStyle = bgcol;
this.context.fillRect(0, 0, this.canvas.width, pct * this.canvas.height);
this.dirty = true;
},
init : function() {
//this.canvas = document.createElement("canvas");
this.canvas = document.getElementsByTagName('canvas')[0];
this.stargoalbanner = document.createElement("canvas");
this.temp = document.createElement("canvas");
this.hratio = SCREENH / SCREENW;
this.wratio = SCREENW / SCREENH;
this.curw = SCREENW;
this.curh = SCREENH;
this.stargoalbanner.width = SCREENW;
this.stargoalbanner.height = STARWID_TOPGOALS;
this.canvas.width = SCREENW;
this.canvas.height = SCREENH;
this.context = this.canvas.getContext("2d");
ctx = this.context;
//this.fullscreen(this.canvas);
// 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;
if (this.ios || this.android) {
FIREWORKSHARDS /= 2;
}
// don't need this now that we statically define the canvas.
//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(mainloop, 1000 / 60);
};
})();
if (debug) {
window.onerror = function (errorMsg, url, lineNumber) {
alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber);
};
}
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);
game.setstate("loader");
},
isbanned : function(lev, what) {
var i;
// no list of allowed things means everything is allowed.
if (this.levels[lev].allowedthings == undefined) {
return false;
}
for (i = 0 ; i < this.levels[lev].allowedthings.length; i++) {
if (this.levels[lev].allowedthings.indexOf(what) != -1) {
// ok
return false;
}
}
return true;
},
turneffects : function() {
var i;
for (i = 0; i < things.length; i += 1) {
// suns can move again
if (things[i].type == "sunlight" && things[i].counter == 1) {
things[i].counter = 0;
}
}
game.progress("turn", 1);
game.turnsleft--;
if (game.turnsleft < 0) game.turnsleft = 0;
game.dirty = true;
game.goalbooster = 0;
},
resize : function() {
if (window.innerWidth > window.innerHeight) {
console.log("scaling to wid");
// scale to width
game.curh = window.innerHeight;
game.curw = game.curh * game.wratio;
} else {
console.log("scaling to height");
// scale to width
game.curw = window.innerWidth - 16;
game.curh = game.curw * game.hratio;
}
// hide address bar on phones
if (game.android || game.ios) {
//document.body.style.height = (window.innerHeight + 50) + 'px';
document.body.style.height = (game.curh + 50) + 'px';
}
// scale canvas
game.canvas.style.width = game.curw + 'px';
game.canvas.style.height = game.curh + 'px';
window.setTimeout(function() {
window.scrollTo(0, 1);
}, 1);
},
initgamevars : function() {
score = 0;
globmulti = 0;
this.frameNo = 0;
this.goalbooster = 0;
},
initlevelvars : function() {
var i;
var nbags;
// kill any existing objects
clearthings();
overdesc = "";
clearpath();
// set grid size
if (game.levels[curlevel].gridsize == undefined) {
GRIDSIZE = DEF_GRIDSIZE;
THINGSIZE = DEF_THINGSIZE;
GRIDW = DEF_GRIDW;
GRIDH = DEF_GRIDH;
BOARDX = DEF_BOARDX;
} else {
GRIDSIZE = game.levels[curlevel].gridsize;
THINGSIZE = game.levels[curlevel].thingsize;
GRIDW = game.levels[curlevel].gridw;
GRIDH = game.levels[curlevel].gridh;
BOARDX = game.levels[curlevel].boardx;
}
// add shopping bags
game.levels[curlevel].bags = [];
// 0-4 = no bags
// 5 - 14 = 1 bag
// 15 - 24 = 2 bags
// 25 - 34 = 3 bags
nbags = game.getmaxbags(curlevel);
for (i = 0; i < nbags; i++) {
this.addlevelbag(curlevel);
}
},
getmaxbags : function(lev) {
var max;
max = Math.floor((lev+5) / 10)
if (max > 4) max = 4;
return max;
},
// x1, x2, y2, x3, y3....
addlevelbricks : function () {
var lev,newbrick,i;
lev = this.newestlevel;
for (i = 0 ; i < arguments.length; i += 2) {
newbrick = new Object();
newbrick.gridx = arguments[i];
newbrick.gridy = arguments[i+1];
this.levels[lev].bricks.push(newbrick);
}
},
addlevelbag : function (lev) {
var mybag,y;
mybag = new Object();
y = rnd(GRIDH);
while (getbagaty(y)) {
y = rnd(GRIDH);
}
mybag.y = y;
mybag.cats = 0;
mybag.prize = "";
mybag.capacity = this.levels[lev].gridw;
mybag.prize = prizetypes[rnd(prizetypes.length)];
this.levels[lev].bags.push(mybag);
},
addlevel : function (lev, hashelp) {
var mylevel;
var plev;
// find previous level
for (plev = lev-1; plev >= 0; plev--) {
if (this.levels[plev] != undefined) break;
}
mylevel = new Object();
mylevel.hashelp = hashelp;
mylevel.goals = new Array();
mylevel.thinglist = new Array();
mylevel.forcelist = new Array();
mylevel.maxturns = lev + 4; // default
mylevel.bags = [];
mylevel.bricks = [];
mylevel.random = false;
if (lev == 1) {
mylevel.gridsize = DEF_GRIDSIZE;
mylevel.thingsize = DEF_THINGSIZE;
mylevel.gridw = DEF_GRIDW;
mylevel.gridh = DEF_GRIDH;
mylevel.boardx = (SCREENW - (DEF_GRIDW * DEF_GRIDSIZE))/2;
mylevel.catcols = 1;
} else {
// default to size of previous one
mylevel.gridsize = this.levels[plev].gridsize;
mylevel.thingsize = this.levels[plev].thingsize;
mylevel.gridw = this.levels[plev].gridw;
mylevel.gridh = this.levels[plev].gridh;
mylevel.boardx = this.levels[plev].boardx;
// default to # cat colours from previous one
mylevel.catcols = this.levels[plev].catcols;
}
mylevel.allowedthings = new Array();
if (lev > 1) {
// default to allowed things from previous level.
for (i = 0; i < this.levels[plev].allowedthings.length; i++) {
mylevel.allowedthings.push(this.levels[plev].allowedthings[i]);
}
}
this.levels[lev] = mylevel;
playerdata.levscore[lev] = 0;
this.newestlevel = lev;
},
levhasgoal : function(lev, gtype) {
var i;
for (i = 0; i < game.levels[lev].goals.length; i++) {
if (game.levels[lev].goals[i].type == gtype) {
return true;
}
}
return false;
},
genrandomlevel : function (lev) {
var ngoals,i,n,pctleft;
var nbricks = 0,bricktype, xmin,ymin,xmax,ymax,totbricks = 0, wantbricks;
var gw,gh;
// set random seed based on level
game.setrndseed(lev+50);
ngoals = game.seedrnd(2,3);
// first create the level
if (game.levels[lev] == undefined) {
game.addlevel(lev, false);
}
gw = game.levels[lev].gridw;
gh = game.levels[lev].gridh;
// generate goals
pctleft = 100;
for (i = 0; i < ngoals; i++) {
var allgoals = [],goal,goalpct;
// make list of all possible goals
for (goal in GOALVERB) {
if (!game.levhasgoal(lev, goal) && (goal != "turn")) {
allgoals.push(goal);
}
}
// pick a random goal
goal = allgoals[game.seedrnd(0,allgoals.length-1)];
// generate a percentage of max count for this goal
// this will be replaced with an actual number in a second.
if (i == ngoals-1) {
// last one
goalpct = pctleft;
} else {
goalpct = game.seedrnd(pctleft/4,pctleft/2);
}
game.addlevelgoals(goal, goalpct);
}
for (i = 0; i < ngoals; i++) {
var mm,min,max,count;
// turn goal percentages into actual numbers
mm = game.getgoalminmax(lev, game.levels[lev].goals[i].type);
min = mm[0];
max = mm[1];
//console.log("genrandomlev() - lev " + lev + " goal " + i + " type=" + game.levels[lev].goals[i].type + " min="+min + " max=" + max);
count = min + Math.floor((game.levels[lev].goals[i].count / 100) * (max - min))
if (count < 1) count = 1;
game.levels[lev].goals[i].count = count;
}
// add random bricks - 66% chance
nbricks = 0;
brickpos = new Array();
bricktype = game.seedrnd(1,4);
//bricktype = 4;
if (game.seedrnd(1,3) > 1) {
wantbricks = true;
} else {
wantbricks = false;
}
//wantbricks = true;
if (wantbricks) {
switch (bricktype) {
case 1: // random
nbricks = game.seedrnd(1,8); // ie. max 8
xmin = 0;
ymin = 0;
xmax = gw-1;
ymax = gh-1;
break;
case 2: // vert flip
nbricks = game.seedrnd(1,4); // ie. max 8
xmin = 0;
ymin = 0;
xmax = gw-1;
if (gh % 2 == 0) {
ymax = Math.floor(gh/2)-1;
} else {
ymax = Math.floor(gh/2);
}
break;
case 3: // horz flip
nbricks = game.seedrnd(1,4); // ie. max 8
xmin = 0;
ymin = 0;
if (gw % 2 == 0) {
xmax = Math.floor(gw/2)-1;
} else {
xmax = Math.floor(gw/2);
}
ymax = gw-1;
break;
case 4: // quads
nbricks = game.seedrnd(1,3); // ie. max 12
xmin = 0;
ymin = 0;
if (gw % 2 == 0) {
xmax = Math.floor(gw/2)-1;
} else {
xmax = Math.floor(gw/2);
}
if (gh % 2 == 0) {
ymax = Math.floor(gh/2)-1;
} else {
ymax = Math.floor(gh/2);
}
break;
}
}
// console.log("bricktype = " + bricktype + " nbricks = " + nbricks);
// generate coords
bricksleft = nbricks;
for (n = 0; n < nbricks; n++) {
var x,y,coord,found = true;
while (found) {
found = false;
// pick random x/y
x = game.seedrnd(0,xmax);
y = game.seedrnd(0,ymax);
// make sure its not used
for (i = 0; i < brickpos.length; i++) {
if ((brickpos[i].x == x) && (brickpos[i].y == y)){
found = true;
}
}
}
coord = new Object();
coord.x = x;
coord.y = y;
brickpos.push(coord);
//console.log("--- brick at " + x + "," + y);
}
//console.log("--- gw is " + gw + " gh is " + gh);
// place bricks
totbricks = 0;
for (i = 0; i < nbricks; i++) {
var newx, newy;
// add initial one
game.addlevelbricks(brickpos[i].x, brickpos[i].y);
totbricks++;
switch (bricktype) {
case 1: // normal
break;
case 2: // vert flip
newy = -1;
if (gh % 2 == 0) {
newy = (ymax+1) + (ymax - brickpos[i].y);
} else {
if (brickpos[i].y != ymax) { // don't flip on middle line
newy = ymax + (ymax - brickpos[i].y);
}
}
if (newy != -1) {
//console.log(" " + brickpos[i].x + "," + brickpos[i].y + " flipped to " + brickpos[i].x + "," + newy);
game.addlevelbricks(brickpos[i].x, newy);
totbricks++;
}
break;
case 3: // horz flip
newx = -1;
if (gw % 2 == 0) {
newx = (xmax+1) + (xmax - brickpos[i].x);
} else {
if (brickpos[i].x != xmax) { // don't flip on middle line
newx = xmax + (xmax - brickpos[i].x);
}
}
if (newx != -1) {
game.addlevelbricks(newx, brickpos[i].y);
totbricks++;
}
break;
case 4: // quads (ie. flip x, y, and both)
// flip y
newy = -1;
if (gh % 2 == 0) {
newy = (ymax+1) + (ymax - brickpos[i].y);
} else {
if (brickpos[i].y != ymax) { // don't flip on middle line
newy = (ymax) + (ymax - brickpos[i].y);
}
}
if (newy != -1) {
game.addlevelbricks(brickpos[i].x, newy);
totbricks++;
}
// flip x
newx = -1;
if (gw % 2 == 0) {
newx = ((xmax)+1) + (xmax - brickpos[i].x);
} else {
if (brickpos[i].x != xmax) { // don't flip on middle line
newx = (xmax) + (xmax - brickpos[i].x);
}
}
if (newx != -1) {
game.addlevelbricks(newx, brickpos[i].y);
totbricks++;
}
// flip x + y
if (newx == -1) newx = brickpos[i].x;
if (newy == -1) newy = brickpos[i].y;
if ((newx != brickpos[i].x) || (newy != brickpos[i].y)) {
game.addlevelbricks(newx, newy);
totbricks++;
}
break;
}
}
// add forced things based on goals
for (i = 0; i < ngoals; i++) {
switch (game.levels[lev].goals[i].type) {
case "brick":
if (totbricks < game.levels[lev].goals[i].count ) {
var extras = game.levels[lev].goals[i].count - totbricks;
game.addlevelforcethings(game.levels[lev].goals[i].type, extras);
}
break;
}
}
// calculate star point cutoffs, max turns, etc
game.calclevparams(lev);
game.levels[lev].random = true;
},
addlevelgridwid : function (lev, newwid) {
var ratio;
ratio = newwid / DEF_GRIDW;
//console.log("lev " + lev + " newwid " + newwid + " ratio " + ratio);
this.levels[lev].gridsize = DEF_GRIDSIZE / ratio;
this.levels[lev].thingsize = DEF_THINGSIZE / ratio;
this.levels[lev].gridw = DEF_GRIDW * ratio;
this.levels[lev].gridh = DEF_GRIDH * ratio;
this.levels[lev].boardx = Math.floor((SCREENW - (this.levels[lev].gridw * this.levels[lev].gridsize)) / 2);
},
// thingtype pct
addlevelthings: function () {
var i,idx,lev;
lev = this.newestlevel;
for (i = 0 ; 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];
}
},
// thingtype howmany
addlevelforcethings: function () {
var i,idx,lev;
lev = this.newestlevel;
for (i = 0 ; i < arguments.length; i += 2) {
idx = this.levels[lev].forcelist.push(new Object()) - 1;
this.levels[lev].forcelist[idx].type = arguments[i];
this.levels[lev].forcelist[idx].howmany = arguments[i+1];
}
},
clearlevelprogress : function (lev) {
var i,idx;
for (i = 0 ; i < this.levels[lev].goals.length; i++) {
this.levels[lev].goals[i].progress = 0;
}
},
// thing1, thing2, etc
addlevelallowedthings : function () {
var i,lev;
lev = this.newestlevel;
for (i = 0 ; i < arguments.length; i++) {
this.levels[lev].allowedthings.push(arguments[i]);
}
},
setlevelcatcols : function (numcols) {
var lev;
lev = this.levels.length - 1;
this.levels[lev].catcols = numcols;
},
// goal1type goal1count goal2type goal2count etc...
addlevelgoals : function () {
var i,idx,lev;
lev = this.newestlevel;
for (i = 0 ; 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;
// make sure the level has enough bags!
if (arguments[i] == "bag") {
while (this.levels[lev].nbags < arguments[i+1]) {
this.addlevelbag(lev);
}
}
}
},
// earn progress towards goals
progress : function(type, amt) {
var i;
if (game.state != "running") return false;
// 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);
// already met the goal?
if (this.levels[curlevel].goals[i].progress >= this.levels[curlevel].goals[i].count) {
continue;
}
// goal matches what we just progressed in?
if (this.levels[curlevel].goals[i].type == type) {
// increase progress.
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;
// add goal animation - fireworks ?
game.addflash();
game.addfireworks();
}
}
}
},
calcstars : function (lev, points) {
var i;
for (i = 2; i >= 0; i--) {
if (this.levels[lev] == undefined) {
console.log("** error - lev " + lev + " is undefined");
}
if (points >= this.levels[lev].starpoints[i]) {
return (i+1);
}
}
return 0;
},
/*
calcstartext : function (nstars) {
var i;
var met = "", notmet = "";
for (i = 0; i < nstars; i++) {
met = met + FULLSTAR;
}
for (; i < 3; i++) {
notmet = notmet + EMPTYSTAR;
}
return [ met, notmet ] ;
},
*/
setstarpoints : function( lev, p1, p2, p3 ) {
this.levels[lev].starpoints = new Array();
this.levels[lev].starpoints[0] = p1;
this.levels[lev].starpoints[1] = p2;
this.levels[lev].starpoints[2] = p3;
},
getgoalminmax : function (lev, gtype) {
var min = 1,max = -1;
switch (gtype) {
case "food":
min = lev * 0.5;
max = lev * 1.2;
break;
case "llama":
min = lev * 0.16;
max = lev * 0.5;
break;
case "cat":
min = lev * 0.8;
max = lev * 1.5;
break;
case "goat":
min = lev * 0.17;
max = lev * 0.25;
break;
case "brick":
min = lev * 0.03;
max = lev * 0.1;
break;
case "door":
min = lev * 0.03;
max = lev * 0.1;
break;
case "sunlight":
min = lev * 0.06;
max = lev * 0.16;
break;
case "bag":
min = 1;
max = game.getmaxbags(lev);
break;
case "toad":
min = lev * 0.06;
max = lev * 0.15;
break;
default: // should never happen
min = 1;
max = lev * 0.2;
break;
}
min = Math.floor(min);
max = Math.floor(max);
if (min < 1) min = 1;
if (max < min) max = min;
return [ min, max];
},
calclevparams : function( lev ) {
var i,num;
var min = 0,pointsgoal = false;
var turnsreq = 0;
var forceturnsreq = false;
// calculate minimum points required to win
// first pass
for (i = 0; i < this.levels[lev].goals.length; i++) {
switch (this.levels[lev].goals[i].type) {
case "food":
min += FOODPOINTS * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * 0.5;
break;
case "llama":
// actually a bit more than this, since you need cats to get rid of llamas.
min += (LLAMAPOINTS) * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * 1.5;
break;
case "cat": // use smaller amount of catpoints
min += Math.min(CATPOINTS, SLEEPYCATPOINTS) * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count / 3;
break;
case "goat":
min += GOATPOINTS * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * 1;
break;
case "brick":
min += Math.min(CATPOINTS, SLEEPYCATPOINTS) * (BRICKHP/3) * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * (BRICKHP/3); // avg parades to kill it, maybe a bit less
break;
case "turn":
// assume you'll get a parade on most turns.
num = (Math.min(CATPOINTS, SLEEPYCATPOINTS) * this.levels[lev].goals[i].count);
if (!game.isbanned(curlevel, "door")) {
num *= 1.5;
}
min += num;
break;
case "door":
// double the value of a minimum-score parade
min += Math.min(CATPOINTS, SLEEPYCATPOINTS) * 3 * 2 * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * 1;
break;
case "sunlight":
min += Math.min(CATPOINTS, SLEEPYCATPOINTS)*this.levels[lev].gridh * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].gridh;
break;
case "curtain":
min += Math.min(CATPOINTS, SLEEPYCATPOINTS) * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * 1.2;
break;
case "bag":
min += Math.min(CATPOINTS, SLEEPYCATPOINTS) * BAGCAPACITY * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * 1.2;
break;
case "toad":
min += Math.min(CATPOINTS, SLEEPYCATPOINTS)*this.levels[lev].gridh * this.levels[lev].goals[i].count;
turnsreq += this.levels[lev].goals[i].count * 2;
break;
}
}
//
// second pass - overrides
for (i = 0; i < this.levels[lev].goals.length; i++) {
switch (this.levels[lev].goals[i].type) {
/*
case "points":
pointsgoal = this.levels[lev].goals[i].count;
break;
*/
case "turn":
turnsreq = this.levels[lev].goals[i].count;
forceturnsreq = true;
break;
}
}
// adjust for level size
if (this.levels[lev].gridsize != DEF_GRIDSIZE) {
var ratio;
ratio = (this.levels[lev].gridw * this.levels[lev].gridh) /
(DEF_GRIDW * DEF_GRIDH);
min *= ratio;
}
this.levels[lev].starpoints = new Array();
this.levels[lev].starpoints[0] = Math.floor(min);
this.levels[lev].starpoints[1] = Math.floor(min * 2);
this.levels[lev].starpoints[2] = Math.floor(min * 3);
if ((turnsreq > this.levels[lev].maxturns) || forceturnsreq) {
this.levels[lev].maxturns = turnsreq;
}
// must be an integer
this.levels[lev].maxturns = Math.floor(this.levels[lev].maxturns);
/*
// calc stars to unlock
// must use 'lev-1' so that level 1 requires no stars.
// 1.2 * level num
if (lev < 5) {
this.levels[lev].starsrequired = Math.floor(1.2 * (lev-1));
} else {
this.levels[lev].starsrequired = Math.floor(1.5 * (lev-1));
}
*/
},
getstarsrequired : function (lev) {
var req;
// calc stars to unlock
// must use 'lev-1' so that level 1 requires no stars.
// 1.2 * level num
if (lev < 5) {
req = Math.floor(1.2 * (lev-1));
} else {
req = Math.floor(1.5 * (lev-1));
}
return req;
},
initlevels : function ( ) {
var mylevel,i,n;
console.log("doing level init");
this.levels = [];
this.addlevel(1, true);
this.addlevelgoals("food", 5);
this.addlevelallowedthings("cat", "food");
this.setstarpoints(1, 50, 60, 80);
this.addlevelthings( "cat", 50, "food", 50);
this.addlevel(2, true);
this.addlevelgoals("cat", 12);
this.addlevelthings("cat", 45, "food", 55);
this.addlevel(3, true);
this.addlevelallowedthings("llama");
this.addlevelgoals("llama", 4);
this.addlevel(4, true);
this.addlevelgoals("llama", 5);
this.addlevelgoals("food", 5);
this.addlevel(5, true);
this.addlevelgridwid(5, 6);
this.addlevelgoals("bag", 1);
this.addlevelgoals("turn", 10);
//this.addlevelgoals(5, "points", 600);
// introduce goats!
this.addlevel(6, true);
this.addlevelallowedthings("goat");
this.addlevelgoals("goat", 3);
this.addlevelthings("goat", 10, "cat", 50, "food", 30, "llama", 10); // higher than normal goat chance
this.addlevelforcethings("goat", 1);
this.addlevel(7, false);
this.addlevelgoals("llama", 6);
this.addlevelgoals("goat", 3);
this.addlevel(8, false);
this.addlevelgoals("llama", 6);
this.addlevelgoals("goat", 4);
this.addlevelgoals("food", 5);
// introduce doors
this.addlevel(9, true);
this.addlevelallowedthings("door");
this.addlevelgoals("door", 1);
this.addlevelgoals("llama", 5);
this.addlevelgoals("cat", 13);
this.addlevelforcethings("door", 1);
this.addlevel(10, false);
this.addlevelgridwid(10, 7);
this.addlevelgoals("turn", 15);
// introduce sunlight
this.addlevel(11, true);
this.addlevelallowedthings("sunlight");
this.addlevelgoals("cat", 13);
this.addlevelgoals("food", 5);
this.addlevelgoals("sunlight", 1);
this.addlevelforcethings("sunlight", 1);
// introduce whitecat
this.addlevel(12, true);
this.addlevelallowedthings("whitecat");
this.addlevelgoals("cat", 15);
this.addlevelgoals("whitecat", 1);
this.addlevelgoals("llama", 6);
this.addlevelforcethings("whitecat", 1);
// introduce toad
this.addlevel(13, true);
this.addlevelallowedthings("toad");
this.addlevelgoals("toad", 1);
this.addlevelgoals("cat", 20);
this.addlevelgoals("llama", 5);
this.addlevelforcethings("toad", 1);
this.addlevel(14, false);
this.addlevelgoals("cat", 25);
this.addlevelgoals("llama", 6);
this.addlevelgoals("food", 15);
// introduce bricks
this.addlevel(15, true);
this.addlevelgridwid(15, 8);
this.addlevelallowedthings("brick");
this.addlevelgoals("brick", 2);
this.addlevelbricks(3, 3,
4, 4
);
this.addlevel(16, false);
this.addlevelbricks(3, 3, 4, 3,
3, 4, 4, 4
);
this.addlevelgoals("brick", 4);
this.addlevel(17, false);
this.addlevelbricks(0,4, 1,4, 2,4, 3,4, 4,4, 5,4, 6,4, 7,4);
this.addlevelgoals("cat", 25);
this.addlevelgoals("llama", 6);
// introduce other coloured cats.
this.addlevel(18, true);
this.setlevelcatcols(2);
this.addlevelgoals("cat", 30);
this.addlevel(19, false);
this.addlevelbricks(2,2, 3,2, 4,2, 5,2,
2,3, 5,3,
2,4, 5,4,
2,5, 3,5, 4,5, 5,5);
this.addlevelgoals("cat", 20);
this.addlevelgoals("llama", 3);
this.addlevelgoals("brick", 3);
this.addlevel(20, false);
this.addlevelgridwid(20, 9);
this.addlevelbricks( 1,0, 5,0, 6,0, 7,0,
0,1, 2,1, 5,1,
0,2, 1,2, 2,2, 5,2, 6,2, 7,2,
0,3, 2,3, 7,3,
5,4, 6,4, 7,4,
3,6, 5,6,
3,7, 4,7, 5,7,
3,8, 5,8 );
this.addlevelgoals("food", 25);
this.addlevel(21, true);
this.addlevelallowedthings("curtain");
this.addlevelforcethings("curtain", 2);
this.addlevelgoals("curtain", 2, "cat", 30);
this.addlevel(22, false);
this.addlevelgoals("curtain", 2, "bag", 2);
this.addlevel(23, false);
this.addlevelgoals("cat", 20, "food", 20);
this.addlevel(24, true); // introduce boxes
this.addlevelallowedthings("box");
this.addlevelforcethings("box", 2);
this.addlevelgoals("ambush", 5, "cat", 20);
this.addlevel(25, false);
this.addlevelbricks( 3,4, 5,4 );
this.addlevelgoals("ambush", 2, "door", 3, "cat", 20);
this.addlevel(26, false);
this.addlevelbricks(8,0,
8,1,
8,2,
8,3,
8,4,
8,5,
8,6,
8,7,
8,8);
this.addlevelgoals("cat", 30, "bag", 2);
this.addlevel(27, false);
this.addlevelbricks( 4,3,
3,4,4,4,5,4,
4,5);
this.addlevelgoals("brick", 3, "ambush", 1, "llama", 15);
/*
hard brick pattern
this.addlevelbricks(2, 2, 5, 2,
2, 5, 5, 5
);
*/
for (i = 1; i < this.levels.length; i++) {
var extrastars;
// calc star point cutoffs
if (this.levels[i].starpoints == undefined) {
this.calclevparams(i);
}
}
/*
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.setstate("running");
this.dirty = true;
},
*/
populategrid : function() {
var i;
while (!anyvalidmoves()) {
var x,y,found,i;
console.log("populating grid...");
clearthings();
// add bricks
for (i = 0; i < game.levels[curlevel].bricks.length; i++) {
x = game.levels[curlevel].bricks[i].gridx;
y = game.levels[curlevel].bricks[i].gridy;
things.push(new thing(x, y, "brick"));
console.log("adding brick at " + x + "," + y);
}
// populate initial things in all other spots
for (y = 0; y < GRIDH; y++) {
for (x = 0; x < GRIDW; x++) {
if (!getgridthing(x, y)) {
things.push(new thing(x, y, "random"));
}
}
}
// fix up problems
donesomething=true;
while (donesomething) {
donesomething=false;
// force items
if (game.levels[curlevel].forcelist != undefined) {
for (i = 0; i < game.levels[curlevel].forcelist.length; i++) {
var wanttype,wantnum;
wanttype = game.levels[curlevel].forcelist[i].type;
wantnum = game.levels[curlevel].forcelist[i].howmany;
// while we don't have enough...
while (countthingsoftype(wanttype) < wantnum) {
// add one.
var idx = -1;
// don't overwrite bricks
while (idx == -1 || things[idx].type == "brick") {
idx = rnd(things.length);
}
things[idx].type = wanttype;
things[idx].init();
donesomething = true;
}
}
}
// ensure no matches to start with
for (i = 0; i < things.length; i++) {
if (matchthreefrom(things[i])) {
things[i].type = getrandomtype(); // change its type
donesomething=true;
}
}
}
}
// now move everything up so they'll fall down into the initial
// positions.
for (i = 0; i < things.length; i++) {
things[i].matched = false; // un-match it.
things[i].gridy -= GRIDH;
things[i].updatexy();
}
},
drawflash : function() {
if (!this.screenflash) return;
game.dirty = true;
ctx.globalAlpha = this.screenflash;
this.screenflash -= FLASHSPEED;
if (this.screenflash < 0) this.screenflash = 0;
ctx.fillStyle = "#ffffff";
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
ctx.globalAlpha = 1.0;
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
drawtop : function() {
var texttodraw = overdesc;
var col = "#aaaaaa";
var i,n;
//ctx = this.context;
// clear
ctx.clearRect(0, 0, this.canvas.width-1, BOARDY-1);
if (game.state == "running") {
var y1 = 10;
var y2 = 20;
var y3 = 45;
var x,y = 10;
// show level
/*
ctx.font = "16pt Futura";
ctx.textAlign = "center";
ctx.textBaseline = "top";
ctx.fillStyle = "#00aaee";
ctx.fillText("Level " + curlevel, SCREENW/2, y1);
*/
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, "Level " + curlevel, LEVELTEXTSIZE, "#00aaee", SCREENW/2, y1);
// just blit pre-generated star goal targets
ctx.drawImage(this.stargoalbanner, 0, y2, this.stargoalbanner.width, this.stargoalbanner.height);
/*
var starsize = 10;
var starindent = 16;
var startextcol = "#eeee00";
var startextcoldark = "#999900";
x = SCREENW/4;
// TODO: use star image instead.
for (i = 0; i < 3; i++) {
var n,starx,stars = "";
for (n = 0; n < (i+1); n++) {
stars = stars + "\u2605"
}
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, stars + " = " + addcommas(game.levels[curlevel].starpoints[i]), STARPOINTTEXTSIZE,
(score >= game.levels[curlevel].starpoints[i]) ? startextcol : startextcoldark,
x, y2);
x += (SCREENW / 4);
}
*/
// show score
ctx.textAlign = "left";
ctx.textBaseline = "middle";
shadowtext(ctx, "Score: " + addcommas(score), SCORETEXTSIZE, "white", 16, y3);
/*
ctx.font = "16pt Futura";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillStyle = "white";
ctx.fillText("Score: " + addcommas(score), 16, y2);
*/
switch (thingsmoving()) {
case "parade":
col = "#00ff00";
texttodraw = "CAT PARADE!";
break;
case "climb":
col = "#00ff00";
texttodraw = "Climb";
break;
case "chomp":
col = "#00cc00";
texttodraw = "Chomp!";
break;
case "pow":
col = "#00cc00";
texttodraw = "Attack!";
break;
case "slap":
col = "#00cc00";
texttodraw = "Slap!";
break;
case "matchthree":
col = "#00cc00";
texttodraw = "OVERLOAD!";
break;
case "sunlight":
// do nothing
break;
case "shears":
col = "#00cc00";
texttodraw = "Shears!";
// do nothing
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 != "") {
/*
ctx.font = "16pt Futura";
ctx.textAlign = "right";
ctx.textBaseline = "top";
ctx.fillStyle = col;
ctx.fillText(texttodraw, SCREENW-16, y2);
*/
ctx.textAlign = "right";
ctx.textBaseline = "middle";
shadowtext(ctx, texttodraw, SCORETEXTSIZE, col, SCREENW-16, y3);
}
} else if (game.state == "gameover") {
var text;
if (game.turnsleft <= 0) {
text = "Out of moves!";
} else {
text = "No valid moves!";
}
this.context.textAlign = "center";
this.context.textBaseline = "top";
shadowtext(this.context, text, 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 + 25;
var bw = SCREENW - BOTTOMBORDERWIDTH, bh = SCREENH - by - BOTTOMBORDERWIDTH;
var x,y;
var margin = 10;
var indent = 50;
var progindent = 25;
var boxindent = 20;
//ctx = this.context;
if ((game.state == "running") || (game.state == "levelcomplete") || (game.state == "gameover")) {
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");
ctx.fillStyle = gradient;
ctx.fillRect(bx, by, bw, bh);
//outline
ctx.strokeStyle = "white";
ctx.lineWidth = BOTTOMBORDERWIDTH;
ctx.beginPath();
ctx.rect(bx+(borderwidth/2)-1, by+(borderwidth/2)-1, 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;
// for checkbox
var boxsize = texth-8;
var boxx = x+boxindent;
var boxy = y + texth/2 - boxsize/2;
if (this.levels[curlevel].goals[n].progress >= this.levels[curlevel].goals[n].count) {
goalmet = true;
}
if (goalmet) {
goalcol = "#00ddff";
} else {
goalcol = "#aaee00";
}
// goal description
var ess = "s";
if (this.levels[curlevel].goals[n].count == 1) {
ess = "";
} else if (this.levels[curlevel].goals[n].type == "food") {
ess = "";
} else if (this.levels[curlevel].goals[n].type == "ambush") {
var ess = "es";
}
var goalname;
if (GOALNAME[this.levels[curlevel].goals[n].type] == undefined) {
goalname = this.levels[curlevel].goals[n].type;
} else {
goalname = GOALNAME[this.levels[curlevel].goals[n].type];
}
var goaltext = GOALVERB[this.levels[curlevel].goals[n].type] + " " +
this.levels[curlevel].goals[n].count + " " +
goalname + ess;
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-progindent, y);
// completed?
if (goalmet) {
var crossouth = 4;
ctx.beginPath();
ctx.fillStyle = goalcol;
ctx.fillRect(x+indent,boxy+boxsize/2-crossouth/2,SCREENW - indent*2 - x, crossouth);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.rect(x+indent,boxy+boxsize/2-crossouth/2,SCREENW - indent*2 - x, crossouth);
ctx.stroke();
}
// checkbox
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) {
drawtick(ctx, boxx, boxy, boxx + boxsize-1, boxy + boxsize-1, goalcol, 2);
}
y += texth;
}
y += texth;
} else {
// past last level
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, "(none)", GOALTEXTSIZE,goaltitlecol, x+indent, y);
}
}
},
zoomwinimg : function() {
var orig;
orig = this.winimgsize;
this.winimgsize += WINIMGZOOMSPEED;
if (this.winimgsize > 1) this.winimgsize = 1;
if (this.winimgsize != orig) {
// recalc image position
tapx = (SCREENW/2) - ((tapw * this.winimgsize)/2);
tapy = BOARDY + ((GRIDH*GRIDSIZE)/2) - ((taph*this.winimgsize)/2);
game.dirty = true;
}
},
drawcontinuebutton : function() {
var borderwidth = 5;
//var /ctx = this.context;
var col,y;
if (game.state == "gameover") {
//fill
ctx.beginPath();
ctx.fillStyle = "black";
ctx.fillRect(tapx+(borderwidth/2), tapy+(borderwidth/2), tapw-(borderwidth), taph-(borderwidth/2));
ctx.stroke();
} else {
var zoomw,zoomh;
// draw winning cat image
//console.log("drawing win img. tapx:" + tapx + " tapy:" + tapy + " tapw:" + tapw + " taph:" + taph);
zoomw = tapw * game.winimgsize;
zoomh = taph * game.winimgsize;
this.context.drawImage(winimg, tapx, tapy, zoomw, zoomh);
}
//outline
if (game.state == "gameover") {
col = "#dd0000";
ctx.beginPath();
ctx.strokeStyle = col;
ctx.lineWidth = 5;
ctx.rect(tapx+(borderwidth/2), tapy+(borderwidth/2), tapw-(borderwidth), taph-(borderwidth/2));
ctx.stroke();
} else if (game.winimgsize >= 1) {
col = "#00dd00";
ctx.beginPath();
ctx.strokeStyle = col;
ctx.lineWidth = 5;
ctx.rect(tapx+(borderwidth/2), tapy+(borderwidth/2), tapw-(borderwidth), taph-(borderwidth/2));
ctx.stroke();
}
ctx.textAlign = "center";
if (game.state == "gameover") {
y = tapy + taph/2;
ctx.textBaseline = "middle";
shadowtext(ctx, "Tap here to continue", TAPBUTTONSIZE, col, SCREENW / 2, y);
} else if (game.winimgsize >= 1) {
var nstars;
ctx.textBaseline = "bottom";
y = tapy + taph - 5 - TAPBUTTONSIZE*1.5;
shadowtext(ctx, "Tap to continue", TAPBUTTONSIZEC, col, SCREENW / 2, y);
y += TAPBUTTONSIZEC;
nstars = game.calcstars(curlevel, score);
game.drawstars(ctx, SCREENW / 2, y, TAPBUTTONSIZESTAR, nstars, STARWID_ENDLEV);
//console.log("earned " + nstars + " stars");
}
},
drawstars : function(ctx, x, y, size, stars, starwid) {
var i;
var starcol = "#dddd00";
var starspace = starwid / 10;
var sx;
sx = x - (starwid+starspace);
for (i = 1;i <= 3; i++) {
var thiscol,imgname;
if (stars >= i) {
imgname = "starfull";
} else {
imgname = "starempty";
}
ctx.textAlign = "center";
ctx.textBaseline = "middle";
this.context.drawImage(image[imgname], sx - (starwid/2), y - (starwid/2), starwid, starwid);
sx += starwid + starspace;
}
},
drawbg : function() {
var gradient,x,y,off;
var catscale,catsize,catspeed;
var wantalpha,alphaspeed;
var titlebg = "#008c8c";
var origtitlebg = "#005051";
// background
/*
gradient = this.context.createLinearGradient(0, 0, 0, SCREENH);
gradient.addColorStop(0, "black");
gradient.addColorStop(1, "blue");
this.context.fillStyle = gradient;
*/
this.context.fillStyle = titlebg;
this.context.fillRect(0, 0, SCREENW, SCREENH);
// moving cats
catscale = 1;
catspeed = 1;
alphaspeed = 0.02;
catsize = DEF_GRIDSIZE * catscale;
if (game.state == "help" ){
wantalpha = 0.2;
} else if (game.state == "levselect") {
wantalpha = 0.6;
} else {
wantalpha = 1.0;
}
if (catalpha > wantalpha) {
catalpha -= alphaspeed;
if (catalpha < wantalpha) catalpha = wantalpha;
} else if (catalpha < wantalpha) {
catalpha += alphaspeed;
if (catalpha > wantalpha) catalpha = wantalpha;
}
ctx.globalAlpha = catalpha;
off = game.frameNo % (catsize / catspeed);
if (off == 0) {
var goatmove = false;
this.goatx += this.goatdir;
if (this.goatx >= (SCREENW/catsize) && this.goatdir > 0) {
goatmove = true;
// change dirs
} else if (this.goatx < 0 && this.goatdir < 0) {
goatmove = true;
}
if (goatmove) {
this.placetitlegoat(false);
}
}
//console.log(this.goatx + "," + this.goaty);
//console.log(off);
for (y = -catsize*2; y < SCREENH; y += catsize*2) {
for (x = -catsize*2; x < SCREENW+catsize*2; x += catsize) {
var iname;
/*
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.rect(x, y, catsize,catsize);
ctx.stroke();
*/
//ctx.fillStyle = "red";
//ctx.fillRect(x +(catsize/4)+off, y +(catsize/4)+off, catsize/2, catsize/2);
if ((this.goatdir == 1) && ((y/catsize) == this.goaty) && (x / catsize) == this.goatx) {
iname = 'goatwalkr';
} else {
iname = 'catwalkr';
}
this.context.drawImage(image[iname], x +(catsize/4)+off*catspeed, y +(catsize/2)-image[iname].height/2,
image[iname].width*catscale,
image[iname].height*catscale);
if ((this.goatdir == -1) && ((y+catsize)/catsize == this.goaty) && (x / catsize) == this.goatx) {
iname = 'goatwalkl';
} else {
iname = 'catwalkl';
}
this.context.drawImage(image[iname], x +(catsize/4)-off*catspeed, y +(catsize/2)-image[iname].height/2+catsize,
image[iname].width*catscale,
image[iname].height*catscale);
}
}
ctx.globalAlpha = 1.0;
},
drawtitle : function() {
var ratio,w,h;
var img = image['title'];
var catyoff = 0;
var titlexoff = 0;
var titley = 10;
var creditsy = 64;
var birthdayy = 96;
var textgrad;
// background
this.drawbg();
// big cat
ratio = SCREENW / img.width;
w = img.width * ratio;
h = img.height * ratio;
catyoff = wipe.getval(h, "down", "up", 0);
this.context.drawImage(img, SCREENW/16, SCREENH - h + catyoff, w, h);
// text
this.context.textAlign = "center";
this.context.textBaseline = "top";
titlexoff = wipe.getval(SCREENW, "down", "up", 0);
shadowtext(this.context, "Cat Parade", TITLETEXTSIZE,"red", SCREENW / 2 - titlexoff, titley, "#ff0000", "#ffff00", "white");
shadowtext(this.context, "rpearce, 2016", TITLECREDITTEXTSIZE, "#00aaee", SCREENW / 2 - titlexoff, creditsy);
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth()+1;
var yy = today.getFullYear();
// 11th sep
if (dd == 11 && mm == 9) {
shadowtext(this.context, "HAPPY BIRTHDAY BETH!", TITLECREDITTEXTSIZE, "#ff0000", SCREENW / 2 - titlexoff, birthdayy);
}
this.context.textBaseline = "bottom";
shadowtext(this.context, "Tap to start", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2 + titlexoff, SCREENH - 64);
},
levelexists : function (lev) {
if (lev > 0 && lev < game.levels.length) {
return true;
}
return false;
},
countplayerstars : function () {
var i;
var total = 0;
for (i = 1 ; i < game.levels.length; i++ ) {
//console.log("countplayerstars() - lev " + i + " score " + playerdata.getlevscore(i) + " stars " + game.calcstars(i, playerdata.getlevscore(i)));
total += game.calcstars(i, playerdata.getlevscore(i));
}
return total;
},
levellocked : function (lev) {
if (playerdata.totstars < game.getstarsrequired(lev)) return true;
return false;
},
// returns new y val
drawhelpsubtitle : function(ctx, text, y) {
var subtitlecol = "#cc00cc";
var xindent = 10;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
shadowtext(ctx, text, HELPSUBTITLESIZE,subtitlecol, SCREENW / 2, y);
return (y + HELPTEXTYSPACE*1.5);
},
// 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,x,y,x2,y2,curx,cury;
var row1y,row2y,row3y,row4y;
var i;
var linex = [];
var liney = [];
var textxspace = 10;
var midpoint1 = SCREENW/8 * 5;
var midpoint2 = SCREENW/8 * 4;
var titlecol = "#00ccff";
var helpcol = "#00cc00";
var pointscoldark = "#888800";
var pointscol = "#dddd00";
var llamatitle = llamatext[0].toUpperCase() + llamatext.substring(1);
var aoran;
if (llamatext[0] == 'a') aoran = "an";
else aoran = "a";
//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);
*/
this.drawbg();
// top text
cury = HELPTEXTYSPACE*2;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Level " + curlevel + ": Instructions", HELPTITLESIZE,titlecol, SCREENW / 2, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
// original help text for reference. maybe use this
// for a 'continuous mode' later.
if (curlevel == 1) {
cury = this.drawhelpsubtitle(ctx, "Chomping Food", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Drag a cat to eat cheese.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE*1.5;
shadowtext(ctx, "Cats can eat any amount of cheese, but cannot turn", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "corners whilst doing so.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE*1.5;
shadowtext(ctx, "Cats get tired after eating and cannot eat again.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// 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 = PATHLINECOLGOOD;
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), PATHLINECOLGOOD, 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), PATHLINECOLGOOD, 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,pointscol, x, y);
y = row2y-10;
shadowtext(ctx, "per cheese", HELPTEXTSIZE,pointscol, 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 += HELPTEXTYSPACE;
// GOAL HELP
cury = this.drawhelpsubtitle(ctx, "Goals", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Each level has one of more goals to fulfil.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Complete all goals to complete the level!", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
// GAME OVER HELP
cury = this.drawhelpsubtitle(ctx, "Failure", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "If you run out of valid moves, or the turn counter", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "reaches zero, you must try the level again.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
} else if (curlevel == 2) {
// PARADE HELP
cury = this.drawhelpsubtitle(ctx, "Cat Parades", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Drag a path through multiple cats to start a parade.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Parades can change directions as many times as you like.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// 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['cat'], x, y, imgsize, imgsize);
linex[3] = x + imgsize/2;
liney[3] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], 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['cheese'], x, y, imgsize, imgsize);
linex[5] = x + imgsize/2;
liney[5] = y + imgsize/2;
cury = y + HELPTEXTYSPACE;
for (i = 0; i < 3; i++) {
drawline(ctx, linex[i], liney[i], linex[i+1], liney[i+1], PATHLINECOLGOOD, LINEWIDTH);
}
drawarrow(ctx, linex[3], liney[3], linex[4], liney[4], PATHLINECOLGOOD, 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 + HELPTEXTYSPACE/2;
shadowtext(ctx, "+" + CATPOINTS + " points per cat", HELPTEXTSIZE,pointscol, x, y);
y += HELPTEXTYSPACE;
shadowtext(ctx, "+" + SLEEPYCATPOINTS + " points per sleepy cat", HELPTEXTSIZE,pointscol, x, y);
cury = y + HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury = this.drawhelpsubtitle(ctx, "Score Multiplier", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Longer parades score more points.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of second parade - 5 cats
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
x = imgsize;
y += gridsize;
row2y = y;
// 2nd line of second parade - 3 cats
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[3] = x + imgsize/2;
liney[3] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[2] = x + imgsize/2;
liney[2] = y + imgsize/2;
x = imgsize;
y += gridsize;
row3y = y;
// 3rd line of second parade - 3 cats
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[4] = x + imgsize/2;
liney[4] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[5] = x + imgsize/2;
liney[5] = y + imgsize/2;
x += gridsize;
x = imgsize;
y += gridsize;
row4y = y;
// 4th line of second parade - 3 cats
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[7] = x + imgsize/2;
liney[7] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[6] = x + imgsize/2;
liney[6] = y + imgsize/2;
cury = y + HELPTEXTYSPACE;
// path lines
for (i = 0; i < 6; i++) {
drawline(ctx, linex[i], liney[i], linex[i+1], liney[i+1], PATHLINECOLGOOD, LINEWIDTH);
}
drawarrow(ctx, linex[6], liney[6], linex[7], liney[7], PATHLINECOLGOOD, 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 multipliers
ctx.textAlign = "left";
ctx.textBaseline = "top";
x = midpoint2;
y = y - HELPTEXTYSPACE/2;
shadowtext(ctx, "Cats 1-5 = x1", HELPTEXTSIZE,pointscol, x, y);
y += HELPTEXTYSPACE;
x = midpoint2;
y = row3y + HELPTEXTYSPACE/2;
shadowtext(ctx, "Cats 6-8 = x2", HELPTEXTSIZE,pointscol, x, y);
x = midpoint2;
y = row4y + HELPTEXTYSPACE/2;
shadowtext(ctx, "Cats 9-11 = x3", HELPTEXTSIZE,pointscol, x, y);
x = midpoint2;
y += HELPTEXTYSPACE;
y += HELPTEXTYSPACE;
shadowtext(ctx, "...etc", HELPTEXTSIZE,pointscol, x, y);
cury = y + HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
} else if (curlevel == 3) {
cury = this.drawhelpsubtitle(ctx, llamatitle + "s", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats are scared of " + llamatext + "s and can't move while nearby.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
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 += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury = this.drawhelpsubtitle(ctx, "Cat + " + llamatitle + " Parades", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Each cat parade may include a single " + llamatext + " only.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// 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 + HELPTEXTYSPACE;
for (i = 0; i < 4; i++) {
drawline(ctx, linex[i], liney[i], linex[i+1], liney[i+1], PATHLINECOLGOOD, LINEWIDTH);
}
drawarrow(ctx, linex[4], liney[4], linex[5], liney[5], PATHLINECOLGOOD, 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,pointscoldark, x, y);
y += HELPTEXTYSPACE;
shadowtext(ctx, "+" + SLEEPYCATPOINTS + " points per sleepy cat", HELPTEXTSIZE,pointscoldark, x, y);
y += HELPTEXTYSPACE;
shadowtext(ctx, "+" + LLAMAPOINTS + " points per " + llamatext, HELPTEXTSIZE,pointscol, x, y);
cury = y + HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury = this.drawhelpsubtitle(ctx, llamatitle + " Overload", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Three or more " + llamatext + "s in a row defies reality.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "They will vanish in " + aoran + " " + llamatitle + " Overload.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
// top line of llama overload
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
row2y = y;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
// arrow to middle
x = x + gridsize;
y = row1y + (imgsize);
x2 = midpoint2 - 10;
y2 = y;
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
// top line of post-overload
x = midpoint2;
y = row1y;
//ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
//ctx.drawImage(image['cheese'], 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[4] = x + imgsize/2;
liney[4] = y + imgsize/2;
x += gridsize;
x = midpoint2;
y = row2y;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
linex[3] = x + imgsize/2;
liney[3] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[5] = x + imgsize/2;
liney[5] = y + imgsize/2;
// falling down liens
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], "#660000", LINEWIDTH, FALLARROWSIZE);
drawarrow(ctx, linex[2], liney[2], linex[3], liney[3], "#660000", LINEWIDTH, FALLARROWSIZE);
drawarrow(ctx, linex[4], liney[4], linex[5], liney[5], "#660000", LINEWIDTH, FALLARROWSIZE);
} else if (curlevel == 4) {
cury = this.drawhelpsubtitle(ctx, "Later Levels", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "As you complete levels, more features will be introduced.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "For example:", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, "Goats (level 6)", HELPTEXTSIZE, pointscol, THINGSIZE*1.5, cury + THINGSIZE/2 - HELPTEXTYSPACE/2);
ctx.drawImage(image['goat'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
shadowtext(ctx, "Doors (level 9)", HELPTEXTSIZE,pointscol, THINGSIZE*1.5, cury + THINGSIZE/2 - HELPTEXTYSPACE/2);
ctx.drawImage(image['door'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
shadowtext(ctx, "Sunlight (level 11)", HELPTEXTSIZE,pointscol, THINGSIZE*1.5, cury + THINGSIZE/2 - HELPTEXTYSPACE/2);
ctx.drawImage(image['sunlight'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
shadowtext(ctx, "White cat (level 12)", HELPTEXTSIZE,pointscol, THINGSIZE*1.5, cury + THINGSIZE/2 - HELPTEXTYSPACE/2);
ctx.drawImage(image['whitecat'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
shadowtext(ctx, "Toad (level 13)", HELPTEXTSIZE,pointscol, THINGSIZE*1.5, cury + THINGSIZE/2 - HELPTEXTYSPACE/2);
ctx.drawImage(image['toad'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
} else if (curlevel == 5) {
cury = this.drawhelpsubtitle(ctx, "Shopping Bags", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cat find shopping bags irresistable.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Lead a cat parade into a shopping bag to fill it up.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of bag demo
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['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['bag'], x, y + imgsize/2 - imgsize*0.1, imgsize, imgsize*0.2);
x += gridsize;
drawarrow(ctx, linex[0], liney[0],
linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to next one
drawarrow(ctx, midpoint2 - gridsize, y+gridsize/2,
midpoint2 + gridsize*0.75, y+gridsize/2, "red", HELPLINEWIDTH, HELPARROWSIZE);
// next one
x = midpoint2 + gridsize;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['bag'], x, y + imgsize/2 - imgsize*0.5, imgsize, imgsize);
x += gridsize;
y += gridsize;
y += gridsize;
cury = y;
shadowtext(ctx, "Full shopping bags explode to reveal a random prize.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Tap the prize for a special effect!", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
ctx.textAlign = "left";
ctx.textBaseline = "top";
y = cury + THINGSIZE/4 - HELPTEXTYSPACE/2;
shadowtext(ctx, "Tissue Box", HELPTEXTSIZE, pointscol,
THINGSIZE*1.5, y);
shadowtext(ctx, "All sleeping cats wake up.", HELPTEXTSIZE, pointscoldark,
THINGSIZE*1.5, y + HELPTEXTYSPACE);
ctx.drawImage(image['tissues'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
y = cury + THINGSIZE/4 - HELPTEXTYSPACE/2;
shadowtext(ctx, "Shears", HELPTEXTSIZE, pointscol,
THINGSIZE*1.5, y);
shadowtext(ctx, "All " + llamatext + "s immediately run away.", HELPTEXTSIZE, pointscoldark,
THINGSIZE*1.5, y + HELPTEXTYSPACE);
ctx.drawImage(image['shears'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
y = cury + THINGSIZE/4 - HELPTEXTYSPACE/2;
shadowtext(ctx, "Magic Carpet", HELPTEXTSIZE, pointscol,
THINGSIZE*1.5, y);
shadowtext(ctx, "Cats are given a magic fez.",
HELPTEXTSIZE, pointscoldark,
THINGSIZE*1.5, y + HELPTEXTYSPACE);
shadowtext(ctx, "Fez-wearing cats are not scared of " + llamatext + "s.",
HELPTEXTSIZE, pointscoldark,
THINGSIZE*1.5, y + HELPTEXTYSPACE*2);
ctx.drawImage(image['magiccarpet'], textxspace, cury, THINGSIZE, THINGSIZE);
cury += GRIDSIZE;
} else if (curlevel == 6) {
cury = this.drawhelpsubtitle(ctx, "Goats", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Goats and cats can gang up on " + llamatext + "s.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Parades with a goat can contain any number of " + llamatext + "s.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// 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;
// middle line of parade
x = imgsize;
y += gridsize;
row2y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['catscared'], 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;
// bottom line of parade
x = imgsize;
y += gridsize;
row3y = y;
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
linex[8] = x + imgsize/2;
liney[8] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
linex[7] = x + imgsize/2;
liney[7] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
linex[6] = x + imgsize/2;
liney[6] = y + imgsize/2;
// lines
cury = y + HELPTEXTYSPACE;
// parade lines
for (i = 0; i < 7; i++) {
drawline(ctx, linex[i], liney[i], linex[i+1], liney[i+1], PATHLINECOLGOOD, LINEWIDTH);
}
drawarrow(ctx, linex[7], liney[7], linex[8], liney[8], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
x = x + gridsize;
y = row2y + imgsize/2;
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 + HELPTEXTYSPACE/2;
shadowtext(ctx, "+" + CATPOINTS + " points per cat", HELPTEXTSIZE,pointscoldark, x, y);
y += HELPTEXTYSPACE;
shadowtext(ctx, "+" + SLEEPYCATPOINTS + " points per sleepy cat", HELPTEXTSIZE,pointscoldark, x, y);
y += HELPTEXTYSPACE;
shadowtext(ctx, "+" + LLAMAPOINTS + " points per " + llamatext, HELPTEXTSIZE,pointscoldark, x, y);
y += HELPTEXTYSPACE;
shadowtext(ctx, "+" + GOATPOINTS + " points per goat", HELPTEXTSIZE,pointscol, x, y);
y += HELPTEXTYSPACE;
y += HELPTEXTYSPACE;
y += HELPTEXTYSPACE;
y += HELPTEXTYSPACE;
y += HELPTEXTYSPACE;
cury = y;
shadowtext(ctx, "Goats can't start parades.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Parades must start with a cat.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE * 1.5;
// bad example
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize*2;
y += gridsize;
cury = y;
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLBAD, LINEWIDTH, PATHARROWSIZE);
drawcross(ctx, x+imgsize/8, row1y+imgsize/8, x + imgsize-imgsize/8, row1y + imgsize-imgsize/8, PATHLINECOLBAD, 3);
// good example
x = imgsize;
row2y = y;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize*2;
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
drawtick(ctx, x, row2y, x + imgsize, row2y + imgsize, PATHLINECOLGOOD, 3);
y += gridsize;
cury = y;
} else if (curlevel == 9) {
cury = this.drawhelpsubtitle(ctx, "Doors", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats love escaping outside through doors.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Ending a parade on a door will score double points!", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// 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;
// middle line of parade
x = imgsize;
y += gridsize;
row2y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
linex[2] = x + imgsize/2;
liney[2] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['door'], x, y, imgsize, imgsize);
linex[5] = x + imgsize/2;
liney[5] = y + imgsize/2;
// parade lines
cury = y + HELPTEXTYSPACE;
for (i = 0; i < 5; i++) {
drawline(ctx, linex[i], liney[i], linex[i+1], liney[i+1], PATHLINECOLGOOD, LINEWIDTH);
}
drawarrow(ctx, linex[4], liney[4], linex[5], liney[5], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
x = x + gridsize;
y = row2y;
x2 = midpoint2 - 10;
y2 = y;
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
// explain points for doors
ctx.textAlign = "left";
ctx.textBaseline = "top";
x = midpoint2;
y = row2y - HELPTEXTYSPACE/2;
shadowtext(ctx, "x2 points", HELPTEXTSIZE,pointscol, x, y);
cury = y;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Doors don't fall down like other objects.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "They are only removed when a parade enters them.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of door fall example
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['door'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
// middle line of parade
x = imgsize;
y += gridsize;
row2y = y;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
// cat eating food line
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
x = x + gridsize;
y = row2y;
x2 = midpoint2 - 10;
y2 = y;
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
// top line of part 2 of door fall example
x = midpoint2;
y = cury;
row1y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['door'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
// middle line of parade
x = midpoint2;
y += gridsize;
row2y = y;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize*2;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
} else if (curlevel == 11) {
cury = this.drawhelpsubtitle(ctx, "Sunlight", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats love luxuriating in sunlight.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Any cats next to a sun will fall asleep.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
x = (SCREENW / 2) - gridsize/2 - gridsize;
y = cury;
ctx.drawImage(image['catfull'], x, y, imgsize, imgsize);
x += gridsize;
y = cury;
ctx.drawImage(image['sunlight'], x, y, imgsize, imgsize);
x += gridsize;
y = cury;
ctx.drawImage(image['catfull'], x, y, imgsize, imgsize);
cury += gridsize;
x = (SCREENW / 2) - gridsize/2 - gridsize;
y = cury;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
y = cury;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
y = cury;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Each turn, suns swap places with the object below.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Cats will awaken once out of the sunlight.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
x = (SCREENW / 2) - gridsize/2 - gridsize;
y = cury;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
y = cury;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
y = cury;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
cury += gridsize;
x = (SCREENW / 2) - gridsize/2 - gridsize;
y = cury;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
y = cury;
ctx.drawImage(image['sunlight'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
y = cury;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], "#dd0000", LINEWIDTH, PATHARROWSIZE);
drawarrow(ctx, linex[1], liney[1], linex[0], liney[0], "#dd0000", LINEWIDTH, PATHARROWSIZE);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Suns set after reaching the the bottom of the play area.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
} else if (curlevel == 12) {
cury = this.drawhelpsubtitle(ctx, "White Stuffed Cats", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats and white stuffed cats like fighting.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Fights clear out all objects around the white cat.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of white cat demo
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['whitecat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x = imgsize;
y += gridsize;
// line to show path
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to next frame
drawarrow(ctx, x + gridsize*1.5, y, x + gridsize*1.5, y + gridsize, "red", HELPLINEWIDTH, HELPARROWSIZE);
cury = y + gridsize*1.5;
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
cury = y;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats must be awake to attack white cats.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
} else if (curlevel == 13) {
cury = this.drawhelpsubtitle(ctx, "Toads", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "For some reason, cats like slapping toads off things.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Slapped toads will drop down, clearing objects below.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of toad demo
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['toad'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x = imgsize;
y += gridsize;
// line to show path
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to next frame
drawarrow(ctx, x + gridsize*1.5, y, x + gridsize*1.5, y + gridsize, "red", HELPLINEWIDTH, HELPARROWSIZE);
cury = y + gridsize*1.5;
// frame 1
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
linex[0] = x + imgsize/2;
liney[0] = y ;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
x = imgsize;
y += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x = imgsize;
y += gridsize;
x += gridsize;
linex[1] = x + imgsize/2;
liney[1] = y;
ctx.drawImage(image['toad'], x, y, imgsize, imgsize);
// line to toad falling
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], "#dddd00", LINEWIDTH, PATHARROWSIZE);
cury = y;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats must be awake to slap toads.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
} else if (curlevel == 15) {
var tempslashes;
cury = this.drawhelpsubtitle(ctx, "Bricks", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Like doors, bricks don't fall downwards.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Hit bricks with parades to damage them.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
// top line of brick demo
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['goat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['brick'], x, y, imgsize, imgsize);
linex[2] = x + imgsize/2;
liney[2] = y + imgsize/2;
x += gridsize;
x = imgsize;
y += gridsize;
row2y = y;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
// line to show path
drawline(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH);
drawarrow(ctx, linex[1], liney[1], linex[2], liney[2], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
drawarrow(ctx, x + gridsize/2, row2y, midpoint2 - 10, row2y, "red", HELPLINEWIDTH, HELPARROWSIZE);
// damage explanation
ctx.textAlign = "left";
ctx.textBaseline = "middle";
shadowtext(ctx, "(3 damage)", HELPTEXTSIZE,"#dddd00", midpoint2 + gridsize*3 , row2y);
x = midpoint2;
y = cury;
row1y = y;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['brick'], x, y, imgsize, imgsize);
tempslashes = [ imgsize * 0.10, imgsize * 0.25, imgsize * 0.90, imgsize * 0.75 ];
drawslashes(ctx, x, y, tempslashes);
x += gridsize;
x = midpoint2;
y += gridsize;
row2y = y;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
cury = y;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "After " + BRICKHP + " damage, bricks will break.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of brick breaking demo
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['catscared'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['brick'], x, y, imgsize, imgsize);
tempslashes = [ imgsize * 0.20, imgsize * 0.15, imgsize * 0.90, imgsize * 0.75,
imgsize * 0.20, imgsize * 0.75, imgsize * 0.90, imgsize * 0.15,
imgsize * 0.33, imgsize * 0.25, imgsize * 0.80, imgsize * 0.25,
imgsize * 0.33, imgsize * 0.65, imgsize * 0.65, imgsize * 0.65 ];
drawslashes(ctx, x, y, tempslashes);
linex[2] = x + imgsize/2;
liney[2] = y + imgsize/2;
x += gridsize;
x = imgsize;
y += gridsize;
row2y = y;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
// line to show path
drawline(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH);
drawarrow(ctx, linex[1], liney[1], linex[2], liney[2], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
x = x + gridsize/2;
y = row2y;
x2 = midpoint2 - 10;
y2 = y;
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
x = midpoint2;
y = cury;
row1y = y;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
x = midpoint2;
y += gridsize;
row2y = y;
//ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
cury = y;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Toads and white cats can also break bricks.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
} else if (curlevel == 18) {
var tempslashes;
cury = this.drawhelpsubtitle(ctx, "Multi-coloured Cats", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats can only form parades with cats of the same colour.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE * 1.5;
// bad example
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['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize*2;
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLBAD, LINEWIDTH, PATHARROWSIZE);
drawcross(ctx, x+imgsize/8, row1y+imgsize/8, x + imgsize-imgsize/8, row1y + imgsize-imgsize/8, PATHLINECOLBAD, 3);
y += gridsize;
cury = y;
// good example 1
x = imgsize;
row2y = y;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize*2;
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
drawtick(ctx, x, row2y, x + imgsize, row2y + imgsize, PATHLINECOLGOOD, 3);
y += gridsize;
cury = y;
// good example 2
x = imgsize;
row2y = y;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize*2;
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
drawtick(ctx, x, row2y, x + imgsize, row2y + imgsize, PATHLINECOLGOOD, 3);
} else if (curlevel == 21) {
var tempslashes;
cury = this.drawhelpsubtitle(ctx, "Curtains", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats like climbing curtains.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Climb across curtains to swap places with", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "anything on the other side.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "This also causes the curtains to become damaged.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE * 1.5;
// top line of curtain demo
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['curtain'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['curtain'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
// line to show path
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
y = row1y + imgsize/2;
x2 = x + gridsize*2;
y2 = row1y + imgsize/2;
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
/////////////
x = x2 + 10;
y = y2 - imgsize/2;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['curtainshred'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['curtainshred'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['catfull'], x, y, imgsize, imgsize);
x += gridsize;
cury = y + gridsize;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Climbing a damaged curtain will shred and destroy it!", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
// top line of curtain blow up demo
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['curtainshred'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['curtainshred'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
// line to show path
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
y = row1y + imgsize/2;
x2 = x + gridsize*2;
y2 = row1y + imgsize/2;
drawarrow(ctx, x, y, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
/////////////
x = x2 + 10;
y = y2 - imgsize/2;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['catfull'], x, y, imgsize, imgsize);
x += gridsize;
} else if (curlevel == 24) {
var tempslashes;
cury = this.drawhelpsubtitle(ctx, "Boxes", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Boxes are fantastic. Cats can enter adjacent boxes,", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "and can even do so after eating food.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of box demo - cat -> box
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['box'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
// cat -> cheese -> box
y += gridsize;
x = imgsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[2] = x + imgsize/2;
liney[2] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['box'], x, y, imgsize, imgsize);
linex[3] = x + imgsize/2;
liney[3] = y + imgsize/2;
x += gridsize;
// line to show path
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
drawarrow(ctx, linex[2], liney[2], linex[3], liney[3], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
// arrow to middle
y = row1y + imgsize/2;
x2 = x + gridsize*2;
y2 = row1y + imgsize/2 + gridsize/2;
drawarrow(ctx, x, y2, x2, y2, "red", HELPLINEWIDTH, HELPARROWSIZE);
// rhs - cats in boxes
x = x2 + imgsize/2;
y = row1y;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
x += gridsize;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
// rhs - cats in boxes
x = x2 + imgsize/2;
y = row1y;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
y += gridsize;
x = x2 + imgsize/2;
x += gridsize;
x += gridsize;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
cury = y + gridsize*1.5;
shadowtext(ctx, "Cats in boxes can chomp food without getting tired.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Cats in boxes can join parades of any colour.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Parades started by a cat-in-a-box may contain", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "cats of any colour.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of box parade demo
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
linex[0] = x + imgsize/2;
liney[0] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
linex[1] = x + imgsize/2;
liney[1] = y + imgsize/2;
x += gridsize;
// next line
x = imgsize;
y += gridsize
row2y = y;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
linex[2] = x + imgsize/2;
liney[2] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[3] = x + imgsize/2;
liney[3] = y + imgsize/2;
// fourth example
x = midpoint2;
y = row1y;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
linex[4] = x + imgsize/2;
liney[4] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
linex[5] = x + imgsize/2;
liney[5] = y + imgsize/2;
x += gridsize;
// next line of 4th example
x = midpoint2;
y += gridsize;
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
linex[6] = x + imgsize/2;
liney[6] = y + imgsize/2;
x += gridsize;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['cat1'], x, y, imgsize, imgsize);
linex[7] = x + imgsize/2;
liney[7] = y + imgsize/2;
x += gridsize;
drawarrow(ctx, linex[0], liney[0], linex[1], liney[1], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
drawarrow(ctx, linex[2], liney[2], linex[3], liney[3], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
drawarrow(ctx, linex[4], liney[4], linex[5], liney[5], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
drawarrow(ctx, linex[6], liney[6], linex[7], liney[7], PATHLINECOLGOOD, LINEWIDTH, PATHARROWSIZE);
cury = y + gridsize*1.5;
shadowtext(ctx, "Cats in boxes will automatically ambush " + llamatext + "s!", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
// top line of box parade demo
x = imgsize;
y = cury;
row1y = y;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['llama'], x, y, imgsize, imgsize);
x += gridsize;
// arrow to middle
y = row1y + imgsize/2;
x2 = x + gridsize*2;
drawarrow(ctx, x, y, x2, y, "red", HELPLINEWIDTH, HELPARROWSIZE);
// rhs - pow!
x = x2 + imgsize/2;
y = row1y;
ctx.drawImage(image['cheese'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['boxback'], x, y, imgsize, imgsize);
ctx.drawImage(image['cat'], x, y, imgsize, imgsize);
ctx.drawImage(image['boxfront'], x, y, imgsize, imgsize);
x += gridsize;
ctx.drawImage(image['pow'], x, y, imgsize, imgsize);
x += gridsize;
}
ctx.textAlign = "center";
this.context.textBaseline = "bottom";
shadowtext(this.context, "Tap to start", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, SCREENH - HELPTEXTYSPACE*2);
if (!hashelp(curlevel)) {
return true;
}
return false;
},
drawlevselect : function() {
var titley = 5;
var pstarsy = 32;
var x,y,i;
var levnum = 1;
var levcol = "#555500";
//var gridcol = "#aaaa00";
var gridcol = "#000000";
var crosscol = "#bbbbbb";
var textcol = "#dddd00";
var scrollcol = "#dddd00";
var titlecol = "#00aaee";
var scorecol = "#00dd00";
var starcol = "#dddd00";
var buttontextcol = "#bbbbbb";
var gridblack = "#111111";
var gridgrad,buttongrad;
var titleyoff = 0;
var buttonyoff = 0;
var gridalpha = 1.0;
var topstarsize =
//ctx = this.context;
// background
this.drawbg();
ctx.textAlign = "center";
ctx.textBaseline = "top";
titleyoff = wipe.getval(titley*5, "down", "up", 0);
shadowtext(ctx, "Level Selection", TITLESIZELEVSELECT,titlecol, SCREENW / 2, titley - titleyoff);
ctx.drawImage(image['starfull'], LEVSEL_X, pstarsy - titleyoff, STARWID_LEVSEL_TOP, STARWID_LEVSEL_TOP);
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, playerdata.totstars, TEXTSIZETOTSTARS,starcol, LEVSEL_X + STARWID_LEVSEL_TOP*1.2, pstarsy - titleyoff);
gridalpha = wipe.getval(1.0, "up", "down", 1.0);
levnum = 1 + game.levseloff;
// grid
ctx.globalAlpha = gridalpha;
for (y = 0; y < LEVSELGRIDH*GRIDSIZE; y += GRIDSIZE) {
for (x = 0; x < LEVSELGRIDW*GRIDSIZE; x += GRIDSIZE) {
var levy = LEVSEL_Y + y + GRIDSIZE/4;
var randomlev = false;
var nextsize = GRIDSIZE * 0.75;
// "prev" ?
if (x == 0 && y == 0 && game.levseloff > 0) {
/*
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = gridcol;
gridgrad = this.context.createLinearGradient(x, y, LEVSEL_X+x+GRIDSIZE, LEVSEL_Y+y+GRIDSIZE);
gridgrad.addColorStop(0, "#eeee00");
gridgrad.addColorStop(1, gridblack);
ctx.fillStyle = gridgrad;
ctx.fillRect(LEVSEL_X + x, LEVSEL_Y + y, GRIDSIZE-1, GRIDSIZE-1);
ctx.stroke();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, "<<", TEXTSIZELEVSCROLL, scrollcol,
LEVSEL_X + x + GRIDSIZE/2, LEVSEL_Y + y + GRIDSIZE/2);
*/
ctx.drawImage(image['prevlev'],
LEVSEL_X + x + GRIDSIZE/2 - nextsize/2, LEVSEL_Y + y + GRIDSIZE/2 - nextsize/2,
nextsize,nextsize);
continue;
} else if (y >= (LEVSELGRIDH*GRIDSIZE - GRIDSIZE) && x >= (LEVSELGRIDW*GRIDSIZE - GRIDSIZE)) {
// "next" ?
/*
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = gridcol;
gridgrad = this.context.createLinearGradient(x, y, LEVSEL_X+x+GRIDSIZE, LEVSEL_Y+y+GRIDSIZE);
gridgrad.addColorStop(0, "#eeee00");
gridgrad.addColorStop(1, gridblack);
ctx.fillStyle = gridgrad;
ctx.fillRect(LEVSEL_X + x, LEVSEL_Y + y, GRIDSIZE-1, GRIDSIZE-1);
ctx.stroke();
*/
if ( !this.levellocked(levnum)) {
ctx.drawImage(image['nextlev'],
LEVSEL_X + x + GRIDSIZE/2 - nextsize/2, LEVSEL_Y + y + GRIDSIZE/2 - nextsize/2,
nextsize,nextsize);
} else {
//console.log("levle " + (levnum) + " is locked!");
}
/*
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, ">>", TEXTSIZELEVSCROLL, scrollcol,
LEVSEL_X + x + GRIDSIZE/2, LEVSEL_Y + y + GRIDSIZE/2);
*/
continue;
}
//if (this.levelexists(levnum)) {
// create it if required.
if (!game.levelexists(levnum)) {
var n;
// create levels up to this one
for (n = 1; n <= levnum; n++) {
if (!game.levelexists(n)) {
game.genrandomlevel(n);
}
}
}
randomlev = game.levels[levnum].random;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = gridcol;
gridgrad = this.context.createLinearGradient(x, y, LEVSEL_X+x+GRIDSIZE, LEVSEL_Y+y+GRIDSIZE);
//gridgrad.addColorStop(0, "#eeee00");
if ( this.levellocked(levnum)) {
gridgrad.addColorStop(0, "#eeeeee");
} else {
// unlocked
gridgrad.addColorStop(0, "#00ee00");
}
gridgrad.addColorStop(1, gridblack);
ctx.fillStyle = gridgrad;
ctx.fillRect(LEVSEL_X + x, LEVSEL_Y + y, GRIDSIZE-1, GRIDSIZE-1);
ctx.stroke();
if (this.levellocked(levnum)) {
var lockh = GRIDSIZE / 2.8;
var lockw = (lockh / image['lock'].height) * image['lock'].width;
var lockx = LEVSEL_X + x + GRIDSIZE/2 - lockw/2;
var locky = LEVSEL_Y + y + TEXTSIZELEVSELECT*1.5;
// show level num
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, levnum, TEXTSIZELEVSELECT,
//randomlev ? "red" : textcol,
textcol,
LEVSEL_X + x + GRIDSIZE/2,levy);
// locked
ctx.drawImage(image['lock'], lockx, locky, lockw, lockh);
// # stars to unlock
var starreqx = LEVSEL_X + x + GRIDSIZE/2;
//var starreqy = LEVSEL_Y + y + GRIDSIZE - (GRIDSIZE/4)
// - (STARWID_LEVSEL_LOCKED/2);
//var starreqy = locky + lockh * 0.9;
var starreqy = LEVSEL_Y + y + GRIDSIZE - TEXTSIZELEVSTARS*2;
ctx.drawImage(image['stargrey'],
LEVSEL_X + x + GRIDSIZE/2 - STARWID_LEVSEL_LOCKED*1.25,
//LEVSEL_Y + y + GRIDSIZE - (GRIDSIZE/4) - (STARWID_LEVSEL_LOCKED/2),
starreqy,
STARWID_LEVSEL_LOCKED, STARWID_LEVSEL_LOCKED);
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, game.getstarsrequired(levnum),
TEXTSIZELEVSTARS, "#cccccc",
starreqx, starreqy);
} else {
var hiscore,starcount,stars = "";
var scorey = LEVSEL_Y + y + GRIDSIZE - 5;
var stary = levy + ((scorey - levy) / 2); // half way between
// show level num
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, levnum, TEXTSIZELEVSELECT,
//randomlev ? "red" : textcol,
textcol,
LEVSEL_X + x + GRIDSIZE/2,levy);
// show hiscore
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// get player hiscore
hiscore = playerdata.getlevscore(levnum);
// show stars
if (hiscore != undefined) {
starcount = game.calcstars(levnum, hiscore);
} else {
starcount = 0;
}
game.drawstars(ctx, LEVSEL_X + x + GRIDSIZE/2, stary, TEXTSIZELEVSTARS, starcount, STARWID_LEVSEL);
/*
stars = game.calcstartext(starcount);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, stars, TEXTSIZELEVSTARS, starcol,
LEVSEL_X + x + GRIDSIZE/2,stary);
*/
// show hiscore
hiscore = playerdata.getlevscore(levnum);
if (hiscore == undefined) hiscore = 0;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
if (hiscore == 0) {
shadowtext(ctx, "NEW!", TEXTSIZELEVSCORE, "#ee44ee",
LEVSEL_X + x + GRIDSIZE/2,scorey);
} else {
hiscore = addcommas(hiscore) + "pts";
shadowtext(ctx, hiscore, TEXTSIZELEVSCORE, scorecol,
LEVSEL_X + x + GRIDSIZE/2,scorey);
}
}
//} else {
// level doesn't exist
/*
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = gridcol;
gridgrad = this.context.createLinearGradient(x, y, LEVSEL_X+x+GRIDSIZE, LEVSEL_Y+y+GRIDSIZE);
gridgrad.addColorStop(0, "#eeeeee");
gridgrad.addColorStop(1, gridblack);
ctx.fillStyle = gridgrad;
ctx.fillRect(LEVSEL_X + x, LEVSEL_Y + y, GRIDSIZE-1, GRIDSIZE-1);
ctx.stroke();
// cross it out
drawcross(ctx,LEVSEL_X + x, LEVSEL_Y + y, LEVSEL_X + x + GRIDSIZE-1, BOARDY + y + GRIDSIZE-1, crosscol, 3);
*/
//}
// outline square
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = gridcol;
ctx.rect(LEVSEL_X + x, LEVSEL_Y + y, GRIDSIZE-1, GRIDSIZE-1);
ctx.stroke();
levnum++;
}
}
ctx.globalAlpha = 1.0;
buttongrad = this.context.createLinearGradient(0, 0, levsel_contx + levsel_contw, 0);
buttongrad.addColorStop(0, "#999999");
buttongrad.addColorStop(1, "black");
if (wipe.isactive()) {
var fullamt = SCREENH - levsel_conty;
buttonyoff = fullamt - ((wipe.count / wipe.max) * fullamt);
}
buttonyoff = wipe.getval(SCREENH - levsel_conty, "down", "up", 0);
ctx.beginPath();
ctx.fillStyle = buttongrad;
ctx.fillRect(levsel_contx, levsel_conty + buttonyoff, levsel_contw, levsel_conth);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = gridcol;
ctx.rect(levsel_contx, levsel_conty + buttonyoff, levsel_contw, levsel_conth);
ctx.stroke();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, "Back to title screen", BACKTOTITLESIZE, buttontextcol,
levsel_contx + levsel_contw/2, levsel_conty + levsel_conth/2 + buttonyoff);
},
moveturnsleftbar : function() {
var turnspct;
// turnspct = how high the bar should be
// game.turnsbarpct = how high the bar _is_
turnspct = (game.turnsleft / game.levels[curlevel].maxturns);
// move it towards what it should be.
if (game.turnsbarpct > turnspct) {
game.turnsbarpct -= TURNSBARSPEEDDOWN;
if (game.turnsbarpct < turnspct) game.turnsbarpct = turnspct;
game.dirty = true;
} else if (game.turnsbarpct < turnspct) {
game.turnsbarpct += TURNSBARSPEEDUP;
if (game.turnsbarpct > turnspct) game.turnsbarpct = turnspct;
game.dirty = true;
}
},
drawsides : function() {
var x,y,w,h,y2,h2,i;
var turnscol = "#00dddd";
/// left - turns left
x = 0;
y = BOARDY;
w = BOARDX-1;
h = GRIDH * GRIDSIZE;
h2 = game.turnsbarpct * (h-2);
//y2 = y + (h - game.turnsbarh);
y2 = y + (h - h2) -1;
// black background
ctx.beginPath();
ctx.fillStyle = "black";
ctx.fillRect(x+1,y+1,w-1,h-1);
ctx.stroke();
// turns left bar
if (game.turnsleft > 0) {
var gradient;
// blue gradient bar showing turns left
ctx.beginPath();
gradient = ctx.createLinearGradient(0, 0, 0, h2);
gradient.addColorStop(0, "#0000ff");
gradient.addColorStop(1, "#000066");
ctx.fillStyle = gradient;
ctx.fillRect(x+1,y2,w-1,h2);
ctx.stroke();
// turns left text
ctx.textAlign = "center";
ctx.textBaseline = "top";
shadowtext(ctx, Math.floor(game.turnsleft), TURNSLEFTTEXTSIZE, turnscol, x + (w/2), y2 + 2);
shadowtext(ctx, "turn" + (game.turnsleft == 1) ? "" : "s", TURNSLEFTTEXTSIZE, turnscol, x + (w/2), y2 + 2 +
TURNSLEFTTEXTSIZE);
}
// border
ctx.beginPath();
ctx.strokeStyle = "#dddddd";
ctx.lineWidth = 2;
ctx.rect(x+1,y,w,h);
ctx.stroke();
// right: bags
var SMALLBAGAMT = 0.2;
if (game.levels[curlevel].bags != undefined) {
for (i = 0; i < game.levels[curlevel].bags.length; i++) {
var bagh,thisbag;
thisbag = game.levels[curlevel].bags[i];
x = BOARDX + (GRIDW * GRIDSIZE);
y = BOARDY + (thisbag.y * GRIDSIZE);
if (thisbag.cats < thisbag.capacity) {
bagh = THINGSIZE*SMALLBAGAMT; // base height
bagh += (thisbag.cats / thisbag.capacity) * (THINGSIZE*(1-SMALLBAGAMT)); // increase based on fullness
ctx.drawImage(image['bag'], x, y + GRIDSIZE/2 - bagh/2, THINGSIZE, bagh);
} else if (thisbag.prize != "") { // not collected yet
var size = SCREENW - (GRIDW*GRIDSIZE + BOARDX) ;
// bag is open!
ctx.drawImage(image[thisbag.prize], x, y + GRIDSIZE/2 - size/2, size, size);
}
}
}
},
drawgrid : function() {
/*
this.context.strokeStyle = "red";
this.context.lineWidth = 2;
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 = PATHLINECOLGOOD;
} else {
col = PATHLINECOLBAD;
}
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("chear() - ending game");
game.setstate("gameover");
game.cheat = 1;
} else if (ch == 'i') {
var what;
what = getthingxy(lastmx, lastmy);
if (what != undefined) {
console.log("info for " + what.name);
console.log(what);
}
} else if (ch == 'c') {
playerdata.clearall();
} else if (ch == 'f') {
var i;
for (i = 0; i < things.length; i++) {
if (things[i].type == "cat") things[i].gotfez = true;
}
game.dirty = true;
} else if (ch == 't') {
game.addflash();
game.addfireworks();
} else if (ch == 'p') {
var what;
console.log("changing last clicked thing thing at " + lastmx/GRIDSIZE + "," + lastmy/GRIDSIZE );
what = getthingxy(lastmx, lastmy);
if (what != undefined) {
what.name = "xxx changed xx";
what.type = "cat";
what.catcol = 0;
game.dirty = true;
}
} else if (ch == 'l') {
var what;
console.log("changing last clicked thing thing at " + lastmx/GRIDSIZE + "," + lastmy/GRIDSIZE );
what = getthingxy(lastmx, lastmy);
if (what != undefined) {
what.name = "xxx changed xx";
what.type = "llama";
game.dirty = true;
}
} else if (ch == 'b') {
var what;
console.log("changing last clicked thing thing at " + lastmx/GRIDSIZE + "," + lastmy/GRIDSIZE );
what = getthingxy(lastmx, lastmy);
if (what != undefined) {
what.name = "xxx changed xx";
what.type = "box";
game.dirty = true;
}
} else if (ch == 'm') {
var what;
console.log("checking for matches from " + lastmx/GRIDSIZE + "," + lastmy/GRIDSIZE );
what = getthingxy(lastmx, lastmy);
if (what != undefined) {
if (matchthreefrom(what, true)) {
console.log("--found match");
} else {
console.log("--no match");
}
}
} else if (ch == 'n') {
var i;
score += 10;
for (i = 0 ; i < game.levels[curlevel].goals.length; i++ ) {
game.levels[curlevel].goals[i].progress = game.levels[curlevel].goals[i].count;
}
game.cheat = 1;
console.log("cheat() - finishingl level");
} else if (ch == 'u') {
game.cheat = 1;
if (playerdata.totstars < 43) {
playerdata.settotstars(43);
} else {
playerdata.settotstars(85);
}
console.log("cheat() - set tot stars to " + playerdata.totstars);
}
},
handlemousedown : function(event) {
var coords,adjustx,adjusty,realx,realy;
event.preventDefault();
mbdown = true;
coords = getmousexy(event);
adjustx = coords[0];
adjusty = coords[1];
realx = coords[2];
realy = coords[3];
// remember coords for touch screens
if (event.type == "touchstart") {
lastmx = adjustx;
lastmy = adjusty;
}
if (wipe.isactive()) {
return;
}
if (game.state == "running") {
// ignore multi-touch
//if ((curpath != undefined) && curpath.length >= 1) return;
game.dirty = true; // need to redraw
// 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 {
var bag;
// did you click on a bag?
bag = getbagaty(Math.floor(adjusty / GRIDSIZE));
if ((bag != undefined) && (adjustx > GRIDSIZE*GRIDW)) {
if (bag.cats >= bag.capacity) {
collectprize(bag);
}
}
}
} else if (game.state == "gameover") {
if ((realx >= tapx) && (realx <= tapx + tapw) &&
(realy >= tapy) && (realy <= tapy + taph)) {
game.clearlevelprogress(curlevel);
wipe.start("levselect", "out", 20);
//game.setstate("levselect");
}
} else if (game.state == "title") {
wipe.start("levselect", "out", 15);
} else if (game.state == "levselect") {
// find where you clicked
if ((realx >= levsel_contx) && (realx <= levsel_contx + levsel_contw) &&
(realy >= levsel_conty) && (realy <= levsel_conty + levsel_conth)) {
// back to title screen
wipe.start("title", "out", 15);
} else {
var gridx,gridy,levsel;
gridx = Math.floor(adjustx / GRIDSIZE);
gridy = Math.floor(adjusty / GRIDSIZE);
//gridx = Math.floor((realx - BOARDX) / GRIDSIZE);
//gridy = Math.floor((realy - BOARDY) / GRIDSIZE);
if ((gridy == -1) && (gridx == 4)) {
game.resetcount++;
console.log(game.resetcount);
if (game.resetcount >= 10) {
if (confirm("Clear game data?")) {
playerdata.clearall();
playerdata.load();
}
game.resetcount = 0;
}
} else if ((gridx == LEVSELGRIDW-1) && (gridy == LEVSELGRIDH - 1)) {
var inc,nextlev;
// "next"
if (game.levseloff == 0) {
inc = ((LEVSELGRIDW*LEVSELGRIDH)-1);
} else {
inc = ((LEVSELGRIDW*LEVSELGRIDH)-2);
}
nextlev = curlevel + game.levseloff + inc;
if (!game.levellocked(nextlev)) {
game.levseloff += inc;
} else {
console.log("level " + nextlev + " locked = " + game.levellocked(nextlev));
}
} else if (game.levseloff > 0 && gridx == 0 && gridy == 0) {
var dec;
// "prev"
if (game.levseloff == 29) {
dec = ((LEVSELGRIDW*LEVSELGRIDH)-1);
} else {
dec = ((LEVSELGRIDW*LEVSELGRIDH)-2);
}
game.levseloff -= dec;
if (game.levseloff < 0) game.levseloff = 0;
} else {
levsel = game.levseloff + (gridy*LEVSELGRIDW) + gridx + 1;
if (game.levseloff > 0) levsel--;
if (levsel >= 1) {
if (!game.levellocked(levsel)) {
curlevel = levsel;
game.initgamevars();
game.helporstartlev();
}
}
}
}
} else if (game.state == "help") {
game.setstate("running");
} else if (game.state == "levelcomplete") {
if ((realx >= tapx) && (realx <= tapx + tapw) &&
(realy >= tapy) && (realy <= tapy + taph)) {
// clear goals in case we do this level again
game.clearlevelprogress(curlevel);
game.setstate("levselect");
}
}
},
helporstartlev : function() {
if (hashelp(curlevel)) {
wipe.start("help", "out", 15);
//game.setstate("help");
} else {
//game.startlevel();
wipe.start("running", "out", 15);
}
},
generatestargoalbanner : function() {
var i,x,y;
var topctx = this.stargoalbanner.getContext("2d");
// show star targets
var starsize = STARWID_TOPGOALS;
var spacewid = starsize / 2;
var starindent = 16;
var startextcol = "#eeee00";
var startextcoldark = "#999900";
x = SCREENW/4;
y = 0;
// clear top buffer
topctx.clearRect(0, 0, this.stargoalbanner.width, this.stargoalbanner.height);
// for each points goal, generate a temp image and paste it.
for (i = 0; i < 3; i++) {
var xx = 0,yy = 0;
var n;
var textw,pointtext;
// create third temp buffer for this star images + points
var a = document.createElement("canvas");
var actx = a.getContext("2d");
// figure out how big text will be
pointtext = addcommas(game.levels[curlevel].starpoints[i]);
textw = gettextw(actx, pointtext, STARPOINTTEXTSIZE);
// size third temp buffer based on text width
a.width = starsize*3 + spacewid + textw;
a.height = this.stargoalbanner.height;
a.id = "a";
actx.clearRect(0, 0, a.width, a.height);
for (n = 0; n < (i+1); n++) {
// draw star on to third buffer
actx.drawImage(image['starfull'], xx, yy, starsize, starsize);
xx += starsize;
}
xx += starsize/4;
// draw text on to third buffer
actx.textAlign = "left";
actx.textBaseline = "top";
shadowtext(actx, pointtext, STARPOINTTEXTSIZE,
(score >= game.levels[curlevel].starpoints[i]) ? startextcol : startextcoldark,
xx, yy);
// blit third buffer to top buffer then move along
//console.log("i: " + i + ", x = " + x + " y = " + y + " a.dims = " + a.width + "," + a.height);
topctx.drawImage(a, x - a.width/2, y, a.width, a.height);
x += (SCREENW / 4);
// remove temp third buffer
//document.removeChild(document.getElementById("a"));
}
this.dirty = true;
},
placetitlegoat : function(randomx) {
// randomly place the title screen goat
this.goaty = rnd((SCREENH / DEF_GRIDSIZE)-1);
if (this.goaty % 2 == 0) this.goatdir = 1;
else this.goatdir = -1;
if (randomx) {
this.goatx = rnd((SCREENW / DEF_GRIDSIZE)-1);
} else {
if (this.goatdir == 1) {
this.goatx = -1;
} else {
this.goatx = 6;
}
}
//console.log("goat coords " + this.goatx + "," + this.goaty + " dir " + this.goatdir);
},
setstate : function(newstate) {
console.log("setstate() = " + newstate);
if (newstate != this.state) {
this.dirty = true;
}
if (newstate == "help") {
// decide which word to use
if (onein(2)) {
llamatext = "llama";
} else {
llamatext = "alpaca";
}
}
if (newstate == "gameover") {
tapx = 0, tapy = BOARDY + (GRIDH/2)*GRIDSIZE;
tapw = SCREENW, taph = GRIDSIZE;
} else if (newstate == "levelcomplete") {
var newsize;
//tapx = 0, tapy = BOARDY;
//tapw = SCREENW, taph = GRIDH*GRIDSIZE;
// calc size of continue button based on image size
newsize = getimgsize(winimg.width,winimg.height, SCREENW, GRIDH*GRIDSIZE);
tapw = newsize.width;
taph = newsize.height;
//tapx = (SCREENW / 2) - (tapw/2);
//tapy = BOARDY + ((GRIDH*GRIDSIZE)/2) - (taph/2);
tapx = 0;
tapy = BOARDY;
} else if (newstate == "running") {
this.initlevelvars();
this.populategrid();
// get a new cat picture for if we win
if (winimg != undefined) delete winimg;
winimg = new Image();
//var n = rnd(NUMWINIMAGES);
var n = curlevel % NUMWINIMAGES;
winimg.src = "images/win" + n + ".png";
this.winimgsize = 0.1;
// generate starpoint goal banner for top of screen
game.generatestargoalbanner();
// reset turns left
game.turnsleft = game.levels[curlevel].maxturns;
// start turns left bar at bottom so it ticks up.
game.turnsbarpct = 0;
wipe.start("", "in", 50);
} else if (newstate == "levselect") {
// reset counters for clearing data
game.resetcount = 0;
// reset grid size
GRIDSIZE = DEF_GRIDSIZE;
THINGSIZE = DEF_THINGSIZE;
GRIDW = DEF_GRIDW;
GRIDH = DEF_GRIDH;
console.log("going to levsel state, game.countplayerstars() is " + game.countplayerstars());
// count player stars
playerdata.settotstars(game.countplayerstars());
// start fade in
wipe.start("", "in", 15);
} else if (newstate == "title") {
this.placetitlegoat(true);
// start fade in
wipe.start("", "in", 15);
} else if (newstate == "loader") {
// start loading images
var i;
imagenames = [ 'cat', 'catfull', 'catscared',
'cat1', 'catfull1', 'catscared1',
'llama', 'cheese', 'title',
'goat', 'lock','catwalkl','catwalkr','goatwalkl','goatwalkr',
'starfull','starempty', 'stargrey', 'curtain','curtainshred', 'curtainfall',
'door', 'sunlight', 'toad', 'whitecat', // special things
'box', 'boxfront', 'boxback',
'nextlev','prevlev',
'brick', // obstacles
'bag', 'bagpop', 'fez', 'pow', 'brickpop', // effects
'tissues', 'shears', 'magiccarpet' ];
nimages = 0;
maximages = 0;
for (i in imagenames) {
maximages++;
loadimage(imagenames[i], 'images/' + imagenames[i] + '.png');
}
}
this.state = newstate;
},
handlemousemove : function(event) {
var coords,adjustx,adjusty,realx,realy;
coords = getmousexy(event);
adjustx = coords[0];
adjusty = coords[1];
realx = coords[2];
realy = coords[3];
// remember coords for touch screens
//if (event.type == "touchmove") {
lastmx = adjustx;
lastmy = adjusty;
//}
event.preventDefault();
if (!mbdown) return;
if (game.state != "running") return;
game.dirty = true; // need to redraw
// 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,i,curtaindie;
var adjustx, adjusty,realx,realy;
var ptype;
var points = 0;
var overthing = null;
event.preventDefault();
mbdown = false;
if (game.state != "running") return;
game.dirty = true; // need to redraw
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];
realx = coords[2];
realy = coords[3];
}
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("chomp");
}
}
curpath[curpath.length-1].givepoints();
// first one chomps last one (or enters it, if it's a box)
if (curpath[curpath.length - 1].type == "box") {
curpath[0].enterbox(curpath[curpath.length - 1]);
} else {
curpath[0].chomp(curpath[curpath.length - 1]);
}
break;
case "climb": // cat climbs up the curtain
// all curtains lose hp
curtaindie = false;
for (i = 1; i < curpath.length-1; i++) {
curpath[i].losehp();
if (curpath[i].hp <= 0) {
curtaindie = true;
}
}
// first cat swaps places with last object
curpath[0].pushpath(curpath[curpath.length-1].x, curpath[curpath.length-1].y);
curpath[0].state = "swapping";
curpath[0].counter = 1;
// ...then becomes sleepy
curpath[0].sleepy = true;
curpath[curpath.length-1].pushpath(curpath[0].x, curpath[0].y);
curpath[curpath.length-1].state = "swapping";
if (curtaindie) {
// if any curtains died, _all_ curtains die
for (i = 1; i < curpath.length-1; i++) {
while (curpath[i].hp > 0) {
curpath[i].losehp();
}
}
}
break;
case "slap": // toad drops down.
curpath[0].slap(curpath[curpath.length - 1]);
break;
case "attack": // whitecat and 8 surrounding things are cleared
curpath[0].attack(curpath[curpath.length - 1]);
break;
case "parade":
globmulti = 0;
if (curpath.length >= 2) { // should always be true
var i;
// everything in the path exits via a parade
pathdoor = pathcontainstype("door");
if (pathdoor) globmulti++;
for (i = 0; i < curpath.length; i++) {
// ... except doors
if (curpath[i].type == "door") {
//game.progress("door", 1);
console.log("path has doors!");
// add animation
things.push(new thing(curpath[i].gridx, curpath[i].gridy, "text", "x" + (globmulti+1)));
} else {
curpath[i].givepoints(i);
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);
}
}
globmulti = 0;
break;
}
clearpath();
// things that happen once per turn.
game.turneffects();
},
}
// Seeded random numbers, from http://stackoverflow.com/questions/424292/seedable-javascript-random-number-generator
function RNG(seed) {
// LCG using GCC's constants
this.m = 0x80000000; // 2**31;
this.a = 1103515245;
this.c = 12345;
this.state = seed ? seed : Math.floor(Math.random() * (this.m-1));
}
RNG.prototype.rndint = function() {
this.state = (this.a * this.state + this.c) % this.m;
return this.state;
}
RNG.prototype.rndfloat = function() {
// returns in range [0,1]
return this.rndint() / (this.m - 1);
}
RNG.prototype.rndrange = function(start, end) {
// returns in range [start, end): including start, excluding end
// can't modulu nextInt because of weak randomness in lower bits
var rangeSize = end - start;
var randomUnder1 = this.rndint() / this.m;
return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.rndchoice = function(array) {
return array[this.rndrange(0, array.length)];
}
function getimgsize(srcWidth, srcHeight, maxWidth, maxHeight) {
var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
return { width: srcWidth*ratio, height: srcHeight*ratio };
}
// returns number in range 0 .. num-1
function rnd(num) {
var roll;
roll = Math.floor(Math.random() * num);
return roll;
}
function rndfloat(num) {
var roll;
roll = Math.random() * num;
return roll;
}
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 gettextw(ctx, text, size) {
ctx.font = size + "pt " + FONT;
return ctx.measureText(text).width + 2; // add space for shadow
}
// extra args are gradient
function shadowtext(ctx, text, size, col, x, y) {
var i,textcol,gradidx = 0;
if (arguments.length > 6) {
var y1,y2,adjsize,gradinc;
adjsize = size*2;
if (game.context.textBaseline == "top") {
y1 = y;
y2 = y + adjsize;
} else {
y1 = y - adjsize/2;
y2 = y + adjsize/2;
}
textcol = game.context.createLinearGradient(0, y1, 0, y2);
gradinc = 1.0 / (arguments.length - 6-1);
for (i = 6 ; i < arguments.length; i ++) {
textcol.addColorStop(gradidx, arguments[i]);
gradidx += gradinc;
}
} else {
textcol = col;
}
ctx.font = size + "pt " + FONT;
// 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 = textcol;
ctx.fillText(text, x, y);
}
function collectprize(bag) {
var i;
if (bag.prize == "") return;
game.addflash();
switch (bag.prize) {
case "tissues": // all sleepy cats wake up
for (i = 0; i < things.length; i++) {
if (things[i].type == "cat") {
things[i].sleepy = false;
}
}
break;
case "shears": // clear all llamas
for (i = 0; i < things.length; i++) {
if (things[i].type == "llama") {
things[i].givepoints();
things[i].addabove();
things[i].startexplode("shears");
}
}
break;
case "magiccarpet":
for (i = 0; i < things.length; i++) {
if (things[i].type == "cat") things[i].gotfez = true;
}
break;
}
game.dirty = true;
// mark bag as collected
bag.prize = "";
}
function getbagaty(gridy) {
var i;
if (game.levels[curlevel].bags != undefined) {
for (i = 0; i < game.levels[curlevel].bags.length; i++) {
if (game.levels[curlevel].bags[i].y == gridy) {
return game.levels[curlevel].bags[i];
}
}
}
return null;
}
function getrandomtype() {
var roll,tot,type = null,i;
var thinglist,maxroll = 0;
var speciallist = null;
var dodb = false;
var goalthings;
if (curlevel >= game.levels.length) {
thinglist = null;
} else {
thinglist = game.levels[curlevel].thinglist;
}
// construct a list of permitted special things
var poss = [ "door", "sunlight", "toad", "whitecat", "curtain" ] ;
for (i in poss ) {
if (!game.isbanned(curlevel, poss[i])) {
if (speciallist == undefined) {
speciallist = [];
}
speciallist.push(poss[i]);
}
}
//if (dodb) console.log("speciallist is " + speciallist);
// normally we use default chances for objects, but some levels
// have special pre-defined chances.
if (thinglist == undefined || thinglist.length == 0) {
var f;
thinglist = new Array();
// default thinglist
// must be sorted from low to high!
//if (!game.isbanned(curlevel, 'door')) thinglist.push({ type: 'door', pct: 10 } );
if (speciallist != undefined) {
if (dodb) console.log("specials are possible");
thinglist.push({ type: 'special', pct: 5 } );
}
if (!game.isbanned(curlevel, 'box')) thinglist.push({ type: 'box', pct: 5 } );
if (!game.isbanned(curlevel, 'goat')) thinglist.push({ type: 'goat', pct: 12 } );
if (!game.isbanned(curlevel, 'llama')) thinglist.push({ type: 'llama', pct: 10 } );
if (!game.isbanned(curlevel, 'food')) thinglist.push({ type: 'food', pct: 40 } );
if (!game.isbanned(curlevel, 'cat')) thinglist.push({ type: 'cat', pct: 45 } );
}
goalthings = [];
// increase the chance of something appearing if
// a. it's a goal
// b. we don't have any
if (game.state == "running") {
var n;
for (i = 0; i < thinglist.length; i++) {
for (n = 0 ; n < game.levels[curlevel].goals.length; n++ ) {
var matchesgoal = false;
var thingtype = thinglist[i].type
var goaltype = game.levels[curlevel].goals[n].type;
var lookforthing;
if (goaltype == "ambush") {
goaltype = "box"; // need 'box' objects for this
}
if (game.levels[curlevel].goals[n].progress < game.levels[curlevel].goals[n].count) {
if (goaltype == thingtype) {
matchesgoal = true;
lookforthing = thingtype;
} else if ((thingtype == "special") &&
speciallist.indexOf(goaltype) != -1) {
// change it.
lookforthing = goaltype;
matchesgoal = true;
}
}
if (matchesgoal) {
var count;
if (dodb) console.log("****** " + thingtype + " matches goal.");
// check if any exist
count = countalivethingsoftype(lookforthing);
if (count <= 1) {
console.log("****** no things of type " + lookforthing);
// extra chance!
//thinglist[i].pct *= goalbooster;
game.goalbooster += 15;
if (thinglist[i].pct < game.goalbooster) thinglist[i].pct = game.goalbooster;
console.log("extra chance of " + thingtype + "(" + lookforthing + ") - " + game.goalbooster);
dodb = true;
goalthings.push(thingtype);
// if the thing in question is 'special',
// cheat on the random check later to make sure we
// only get the right kind of special thing.
if (thingtype == "special") {
speciallist.splice(0, speciallist.length);
speciallist.push(goaltype);
}
}
}
}
}
}
for (i = 0; i < thinglist.length; i++) {
maxroll += thinglist[i].pct;
//console.log(thinglist[i].type + ": " + thinglist[i].pct + " [" + maxroll + "]");
}
if (dodb) {
for (i = 0; i < thinglist.length; i++) {
var logline;
logline = thinglist[i].type + ": " + ((thinglist[i].pct / maxroll)*100) + "% [roll " + thinglist[i].pct + "/" + maxroll + "]";
if (thinglist[i].type == "special") {
logline = logline + " - " + speciallist;
}
console.log(logline);
}
}
roll = rnd(maxroll);
tot = 0;
for (i = 0; i < thinglist.length; i++) {
tot += thinglist[i].pct;
if (roll <= tot) {
if (dodb) console.log("rolled " + roll + "/" + maxroll + " --> " + thinglist[i].type + " [<= " + tot + "]");
type = thinglist[i].type;
break;
}
}
if (goalthings.indexOf(type) != -1) {
console.log("got type '" + type + "' - goalthing is '" + goalthings + "' - resetting goalbooster");
game.goalbooster = 0;
}
if (type == null || type == undefined) {
console.log("couldn't find type! roll is " + roll);
type = 'cat';
} else if (type == "special") {
// pick from list of specials
type = speciallist[rnd(speciallist.length)];
}
return type;
}
function coord(x,y) {
this.x = x;
this.y = y;
}
function thing(gridx, gridy, type, text, col) {
this.opacity = 1.0;
this.isnew = true;
this.matched = false; // temp for 'match3' things
this.boxfull = false; // for box objects
this.gotfez = false; // for powerups
this.slashes = []; // drawing slashes on bricks when damaged
// list x,y coords. 0=x1 1=y2 3=x2 4=y2 etc.
this.lastx = []; // for firework trails
this.lasty = [];
if (type == "random") {
type = getrandomtype();
//console.log("got random type "+ type);
}
// used for various things
if (type == "sunlight") {
this.counter = 1;
} else {
this.counter = 0;
}
if (type == undefined) {
console.log("type is now " + type);
debugger;
}
if (text == undefined) {
this.name = type + "-" + getrandomname();
} else {
this.name = text;
}
this.type = type;
if (this.type == "cat") {
this.catcol = rnd(game.levels[curlevel].catcols);
//console.log("added cat with col = " + this.catcol + " / " + game.levels[curlevel].catcols);
} else {
this.catcol = null;
}
if (col == undefined) {
switch (this.type) {
case "cat":
this.color = "#b5dea8";
break;
case "food":
this.color = "#d8db03";
break;
case "llama":
this.color = "#cccccc";
break;
case "text":
// default, might ver overridden
if (this.name.indexOf("x") == 0) {
this.color = "#00dddd";
} else {
this.color = "#00cc00";
}
break;
default: // should never happen
this.color = getrandomcolour();
break;
}
} else {
this.color = col;
}
if (this.type == "text") {
if (this.name.indexOf("x") == 0) {
this.size = TEXTSIZEMULTIPLIER;
} else {
this.size = TEXTSIZE;
}
} else if (this.type == "firework") {
this.size = FIREWORKSIZE;
} else {
this.size = THINGSIZE;
}
this.lifetime = 0;
if (this.type == "brick") {
this.hp = BRICKHP;
} else if (this.type == "curtain") {
this.hp = CURTAINHP;
} else {
this.hp = -1;
}
this.init = function() {
if (this.type == "brick") {
this.hp = BRICKHP;
} else if (this.type == "curtain") {
this.hp = CURTAINHP;
} else {
this.hp = -1;
}
}
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.delay = 0; // for fireworks
this.expcount = 0;
this.expfadespeed = 0;
this.expmax = 0;
if (this.type == "bagpop") {
this.yspeed = 0 - rndfloat(8);
this.xspeed = 0 - rndfloat(16) - 1;
} else if (this.type == "curtainfall") {
this.yspeed = 0 - rndfloat(8);
this.xspeed = rndfloat(8) - 4;
} else if (this.type == "brickpop") {
this.yspeed = 0 - rndfloat(8);
this.xspeed = rndfloat(32) - 16;
} else if (this.type == "firework") {
this.yspeed = 0 - rndfloat(16);
this.xspeed = rndfloat(16) - 8;
} else {
this.yspeed = 0;
this.xspeed = 0;
}
if (this.type == "text") {
this.state = "text";
} else if (this.type == "pow") {
this.state = "explode";
} else {
this.state = "stop";
}
this.path = [];
this.pathspeed = PARADESPEED;
this.sleepy = false;
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);
}
if (this.type == "pow") {
this.expcount=1;
this.expfadespeed = EXPLODEFADESPEED/2;
this.expmax=EXPLODETICKS*2;
this.state = "explode";
this.explodereason = "pow";
}
this.setname = function() {
this.name = type + "-" + getrandomname();
}
this.issleepy = function() {
if (this.type == "cat") {
if (this.sleepy == true) {
return true;
} else if (isadjacenttotype(this, "sunlight")) {
return true;
}
}
return false;
}
this.canfall = function() {
// doors and bricks can't fall after their initial drop
if ((this.type == "door") || (this.type == "brick")) {
if ((this.gridy >= 0) && (this.isnew == false)) {
return false;
}
}
return true;
}
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.gridy < 0) {
return false;
}
if (this.isanimating()) {
return false;
}
if (this.type != "cat") {
if (this.type == "box" && !this.boxfull) {
} else {
return false;
}
}
//if (isadjacenttotype(this, "llama")) {
if (this.type == "cat" && this.isscared()) {
return false;
}
// chomp
if (isadjacenttotype(this, "food")) {
if (this.type == "box" || !this.issleepy()) {
return true;
}
}
// parade - this is the hardest to check for.
// check for any adjacent cats or llamas or goats
// cat: if it also has an adjacent cat/llama/goat, we're good
// llama: if it also has an adjacent cat/goat, we're good
if ((this.type == "cat") || (this.type == "box" && this.boxfull)) {
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") && catcolmatches(adj,this)) {
// cat -> adjacent cat
if (isadjacenttotype(adj, "llama", this) ||
isadjacenttotype(adj, "cat", this, this.catcol) ||
isadjacenttotype(adj, "goat", this)) {
return true;
}
if (isadjacenttotype(adj, "box", this, null, true)) {
// cat -> adj cat -> full box
return true;
}
} else if (adj.type == "goat") {
// cat -> adjacent goat
if (isadjacenttotype(adj, "llama", this) ||
isadjacenttotype(adj, "cat", this, this.catcol) ||
isadjacenttotype(adj, "goat", this)) {
return true;
}
if (isadjacenttotype(adj, "box", this, null, true)) {
// cat -> adj goat -> full box
return true;
}
} else if (adj.type == "llama") {
// cat -> adjacent llama
if (isadjacenttotype(adj, "cat", this, this.catcol) ||
isadjacenttotype(adj, "goat", this)) {
return true;
}
if (isadjacenttotype(adj, "box", this, null, true)) {
// cat -> llama -> full box
return true;
}
} else if (adj.type == "box") {
// cat -> adjacent box
if (adj.boxfull) {
// full box - can continue a parade?
if (isadjacenttotype(adj, "cat", this, this.catcol) ||
isadjacenttotype(adj, "goat", this) ||
isadjacenttotype(adj, "llama", this)) {
return true;
}
} else {
// we can enter the box.
return true;
}
} else if (adj.type == "curtain") {
var nx2,ny2;
// next step along still on the grid?
nx2 = adj.gridx + DIRXMOD[i];
ny2 = adj.gridy + DIRYMOD[i];
if (isonscreen(nx2, ny2)) return true;
}
}
}
}
}
return false;
}
this.givepoints = function(pathpos) {
var points = 0;
var prestars,poststars;
var thismulti = 1;
var colidx = 0;
prestars = game.calcstars(curlevel, score);
if (pathpos == undefined) {
pathpos = 0;
}
if (this.type == "food") {
points = FOODPOINTS;
//game.progress("food", 1);
} else if (this.type == "llama") {
points = LLAMAPOINTS;
//game.progress("llama", 1);
} else if (this.type == "box") {
points = CATBOXPOINTS;
} else if (this.type == "cat") {
if (this.issleepy()) {
points = SLEEPYCATPOINTS;
} else {
points = CATPOINTS;
}
// fez multiplier
if (this.gotfez) {
points *= 2;
}
//game.progress("cat", 1);
} else if (this.type == "goat") {
points = GOATPOINTS;
//game.progress("goat", 1);
}
// multiplier for path position
thismulti = Math.floor((pathpos+1) / 3); // ie. 1-5=x1 6-8=x2 9-11=x3 etc
if (thismulti < 1) thismulti = 1;
// global multiplier (doors etc)
thismulti += globmulti;
points *= thismulti;
if (points > 0) {
//console.log("scoring " + points + " points for " + this.name + "(globmulti is " + thismulti + ")" );
score += points;
//game.progress("points", points);
// add animation
colidx = thismulti;
if (colidx > (scorecols.length-1)) colidx = scorecols.length-1;
things.push(new thing(this.gridx, this.gridy, "text", "+" + points, scorecols[colidx]));
poststars = game.calcstars(curlevel, score);
if (poststars != prestars) {
// regenerate star colors at top, if required
game.generatestargoalbanner();
}
}
}
this.isscared = function() {
if (this.type != "cat") return false;
if (this.isanimating()) return false;
if (isadjacenttotype(this, "llama")) {
if (!this.gotfez) {
return true;
}
}
return false;
}
this.getdesc = function() {
var desc = "";
if (this.type == "cat") {
if (this.isscared()) {
if (this.issleepy() == true) {
desc = "scared sleepy cat";
} else {
desc = "scared cat";
}
} else if (this.issleepy() == 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;
}
// z is just used for drawing front/back of boxes
this.draw = function(z) {
var yoff;
var inpath = false;
var howbig,myx,myy;
var fhowbig,fw,fh,fx,fy;
//ctx = game.context;
// don't draw delaying things
if (this.delay > 0) {
return;
}
// check whether I'm in the drawn path
if (pathcontains(this)) {
inpath = true;
}
// set opacity
if (!wipe.isactive()) {
ctx.globalAlpha = this.opacity;
}
// draw myself
if (this.type == "text") {
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, this.name, this.size, this.color, BOARDX + this.x,BOARDY + this.y);
} else if (this.type == "firework") {
var myx,myy;
// draw trail
if (this.lastx != undefined) {
var i;
for (i = 0; i < this.lastx.length ; i++) {
var newopac = this.opacity + ((i+1)*FIREWORKFADESPEED);
var nextx,netxy;
if (newopac > 1) newopac = 1;
/*
if (i == 0) {
nextx = this.x - this.size/2;
nexty = this.y - this.size/2;
} else {
nextx = this.lastx[i-1] - this.size/2;
nexty = this.lasty[i-1] - this.size/2;
}
*/
myx = this.lastx[i] - this.size/2;
myy = this.lasty[i] - this.size/2;
//console.log("fw line: " + myx + "," + myy + " -> " + nextx + "," + nexty);
ctx.globalAlpha = newopac;
ctx.fillStyle = this.color;
ctx.lineWidth = this.size;
ctx.fillRect(BOARDX + myx, BOARDY + myy, this.size, this.size);
/*
drawline(ctx, BOARDX + myx, BOARDY + myy,
BOARDX + nextx, BOARDY + nexty,
this.col, this.size);
*/
ctx.globalAlpha = 1.0;
}
}
myx = this.x - this.size/2;
myy = this.y - this.size/2;
ctx.globalAlpha = this.opacity;
ctx.fillStyle = this.color;
ctx.fillRect(BOARDX + myx, BOARDY + myy, this.size, this.size);
ctx.globalAlpha = 1.0;
} else {
var myimage,myimage2 = null;
if (this.type == "cat") {
var imgname;
if (this.state == "parade") {
imgname = 'cat';
} else if (this.isscared()) {
imgname = 'catscared';
} else if (this.issleepy() == true) {
imgname = 'catfull';
} else {
imgname = 'cat';
}
if (this.catcol > 0) {
// ie. catfull1, catfull2 etc
imgname = imgname + this.catcol;
}
myimage = image[imgname];
} else if (this.type == "box") {
if (z == 0) {
var imgname2;
imgname = 'boxback';
if (this.boxfull) {
imgname2 = 'cat';
if (this.catcol > 0) {
imgname2 = imgname2 + this.catcol;
}
myimage2 = image[imgname2];
}
} else {
imgname = 'boxfront';
}
myimage = image[imgname];
} else if (this.type == "curtain") {
if (this.hp == CURTAINHP) {
imgname = 'curtain';
} else {
imgname = 'curtainshred';
}
myimage = image[imgname];
} else if (this.type == "food") {
myimage = image['cheese'];
} else {
myimage = image[this.type];
}
if (myimage == undefined || myimage == null) {
console.log("ERROR - no image for type " + this.type);
console.log(this);
}
howbig = this.size;
fw = (image['fez'].width / myimage.width) * this.size;
fh = (image['fez'].height / myimage.height) * this.size;
myx = this.x;
myy = this.y;
fx = (this.gridx * GRIDSIZE) + (GRIDSIZE/2) - (fw/2);
fy = this.y;
if (inpath) {
var growpct = 50;
howbig = howbig * ((100+growpct) / 100);
myx -= (growpct/2/100) * this.size;
myy -= (growpct/2/100) * this.size;
fw = fw * ((100+growpct) / 100);
fh = fh * ((100+growpct) / 100);
fx = (this.gridx * GRIDSIZE) + (GRIDSIZE/2) - (fw/2);
fy -= (growpct/2/100) * this.size;
}
/* hilight non-snapped things
var snapto = Math.floor((this.gridy * GRIDSIZE) + (GRIDSIZE/2) - (howbig/2));
if (myy != snapto) {
console.log("not aligned");
ctx.fillStyle = "green";
ctx.fillRect(BOARDX + myx, BOARDY + myy, howbig, howbig);
}
*/
ctx.drawImage(myimage, BOARDX + myx, BOARDY + myy, howbig, howbig);
if (myimage2 != undefined) {
ctx.drawImage(myimage2, BOARDX + myx, BOARDY + myy, howbig, howbig);
}
// draw slashes on bricks
drawslashes(ctx, BOARDX + myx, BOARDY + myy, this.slashes);
if (this.gotfez) {
// fw = 0.78 * howbig;
// fh = 0.625 * howbig;
// centre
//fx = BOARDX + (this.gridx * GRIDSIZE) + (GRIDSIZE/2) - (fw/2);
//fy = BOARDY + (this.gridy * GRIDSIZE) + (fh/6);
ctx.drawImage(image['fez'], BOARDX + fx, BOARDY + fy, fw, fh);
}
}
/*
} 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.sleepy ? "FULL" : "", BOARDX + this.x + 10, BOARDY + this.y + (THINGSIZE/2) + 10);
}
*/
// back to full opacity
if (!wipe.isactive()) {
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;
}
// return TRUE if we did something
this.snaptogrid = function() {
var moved = false;
var snaptox = Math.floor(this.gridx * GRIDSIZE + (GRIDSIZE/2) - (this.size/2));
var snaptoy = Math.floor(this.gridy * GRIDSIZE + (GRIDSIZE/2) - (this.size/2));
if (this.isanimating()) {
return false;
}
if (this.state != "fall" && this.state != "stop") {
return false;
}
//var snapto = (GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
//if (this.y % (GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2)) != 0) {
//if (this.y % snapto != 0) {
if (this.y != snaptoy) {
this.y = snaptoy;
var moved = true;
}
if (this.x != snaptox) {
this.x = snaptox;
var moved = true;
}
return moved;
}
this.getstoppedbelowthing = function() {
var bt,i;
bt = getgridthing(this.gridx, this.gridy + 1);
if (bt && bt.state != "fall") {
return bt;
}
// look for things movng to the place below.
for (i = 0; i < things.length; i += 1) {
if (things[i] != this && things[i].state == "swapping" && things[i].path != undefined) {
var n;
for (n = 0; n < things[i].path.length; n++) {
var gx,gy;
gx = Math.floor(things[i].path[n].x / GRIDSIZE);
gy = Math.floor(things[i].path[n].y / GRIDSIZE);
// get grid coords
if (gx == this.gridx && gy == this.gridy + 1) {
return things[i];
}
}
}
}
return null;
}
this.addabove = function() {
// add a new cat above us
things.push(new thing(this.gridx, "top", "random"));
}
this.kill = function() {
// kill ourselves
game.progress(this.type, 1);
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.slap = function(toad) {
var origgx,origgy;
// remember cat loc
origgx = this.gridx;
origgy = this.gridy;
// add new object above cat location
//this.addabove();
// move cat to toad location
//this.setgridxy(toad.gridx,toad.gridy);
// add new object above toad location
toad.addabove();
// toad falls down, smashing everything under it
toad.state = "slapped";
// add "pow"
things.push(new thing(toad.gridx, toad.gridy, "pow"));
//game.progress("toad", 1);
console.log("slap");
}
this.attack = function(whitecat) {
var i,adj;
// add new object above cat location
//this.addabove();
// move cat to toad location
//this.setgridxy(toad.gridx,toad.gridy);
for (i = 0; i < MAXDIAGDIRS; i++) {
var newx,newy;
newx = whitecat.gridx + DIAGDIRXMOD[i];
newy = whitecat.gridy + DIAGDIRYMOD[i];
adj = getgridthing(newx, newy);
if (adj != undefined) {
adj.addabove();
adj.givepoints();
adj.startexplode();
}
}
// add "pow"
things.push(new thing(whitecat.gridx, whitecat.gridy, "pow"));
// whitecat itself goes too.
whitecat.addabove();
whitecat.startexplode();
//game.progress("whitecat", 1);
console.log("whitecat");
}
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("chomp");
// mark that we've eaten something
if (this.type == "cat") {
this.sleepy = true;
}
console.log("chomp");
}
this.isanimating = function() {
if (this.type == "text") return true;
if (this.type == "firework") return true;
if (this.type == "bagpop") return true;
if (this.type == "brickpop") return true;
if (this.type == "curtainfall") return true;
if (this.state == "explode") return true;
if (this.state == "shrink") return true;
if (this.state == "parade") return true;
return false;
}
this.shredcurtain = function() {
var i,nshards = 7;
for (i = 0; i < nshards; i++) {
things.push(new thing(this.gridx, this.gridy, "curtainfall"));
}
this.givepoints();
this.addabove();
this.kill();
}
this.breakbrick = function() {
var i,nshards = 7;
for (i = 0; i < nshards; i++) {
things.push(new thing(this.gridx, this.gridy, "brickpop"));
}
this.givepoints();
this.addabove();
this.kill();
}
this.losehp = function() {
this.hp--;
console.log(this.name + " hp is at " + this.hp);
if (this.hp <= 0) {
game.dirty = true; // need to redraw
if (this.type == "brick") {
this.breakbrick();
} else {
this.shredcurtain();
}
} else {
// add a slash from a random x/y to a random x/y
addslash(this.slashes, THINGSIZE, THINGSIZE);
}
}
this.startexplode = function(why) {
if (this.type == "brick") {
// break it instead
this.breakbrick();
} else {
this.expcount=1;
this.expmax=EXPLODETICKS;
this.expfadespeed = EXPLODEFADESPEED;
this.state = "explode";
this.explodereason = why;
}
}
this.startshrink = function() {
this.expcount=1;
this.expmax=SHRINKTICKS;
this.state = "shrink";
this.xspeed = 0;
this.yspeed = 0;
}
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;
if (pathdoor != undefined) {
// we're at a door. vanish.
this.pushpath(-1, -1);
} else {
// 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 == "firework" && this.delay > 0) {
this.delay--;
} else if ((this.type == "bagpop") || (this.type == "brickpop") || (this.type == "firework") || (this.type == "curtainfall")) {
game.dirty = true; // need to redraw
// regular gravity
if (this.type == "firework") {
if (this.y >= SCREENH) {
atbottom = true;
}
} else {
if ((this.gridy >= GRIDH-1)) {
atbottom = true;
}
}
if (!atbottom) {
game.dirty = true; // need to redraw
// accelerate
this.yspeed += GRAVITY;
// remember previous positions
if (this.type == "firework") {
// add current position to trail
this.lastx.push(this.x);
this.lasty.push(this.y);
// remove old positions from trail
while (this.lastx.length >= FIREWORKTRAILLEN) {
this.lastx.splice(0, 1);
}
while (this.lasty.length >= FIREWORKTRAILLEN) {
this.lasty.splice(0, 1);
}
}
// move
this.y += this.yspeed;
this.x += this.xspeed;
// don't go below bottom of screen
if (this.type == "firework") {
if (this.y >= SCREENH) {
atbottom = true;
}
} else {
if (this.y > GRIDSIZE * GRIDH) {
atbottom = true;
}
}
// calc new gridx / gridy
this.calcgridxy();
if (this.type != "firework") {
if ((this.gridy >= GRIDH-1)) {
atbottom = true;
}
}
}
// fade
if (this.type == "firework") {
this.opacity -= FIREWORKFADESPEED;
if (this.opacity <= 0) {
this.opacity = 0;
atbottom = true;
}
}
if (atbottom) {
this.kill();
}
} else if (this.state == "explode") {
game.dirty = true; // need to redraw
this.expcount++;
if (this.expcount >= this.expmax) {
this.kill();
} else {
// get bigger
this.size += EXPLODEGAIN;
this.opacity -= this.expfadespeed;
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.type == "text") {
game.dirty = true; // need to redraw
// 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 == "shrink") {
var xcutoff;
game.dirty = true; // need to redraw
this.expcount++;
if (this.expcount >= this.expmax) {
this.kill();
} else {
// get smaller
this.size -= SHRINKLOSE;
if (this.size < 1) this.size == 1;
// adjust x/y
this.x = (this.gridx * GRIDSIZE) + (GRIDSIZE/2) - (this.size/2);
this.y = (this.gridy * GRIDSIZE) + (GRIDSIZE/2) - (this.size/2);
// going in to a bag ?
xcutoff = SCREENW - this.size*2;
if (this.x > xcutoff) {
this.x = xcutoff;
}
}
} else if ((this.state == "parade") || (this.state == "swapping")) {
// move towards next cell in path
var nextx = this.path[0].x;
var nexty = this.path[0].y;
var xdone = 0, ydone = 0;
var goingright = false;
game.dirty = true; // need to redraw
// special case: door.
if ((nextx == -1) && (nexty == -1)) {
this.path = [];
this.startshrink();
} else {
if (this.x < nextx) {
goingright = true;
this.x += this.pathspeed;
if (this.x >= nextx) {
this.x = nextx;
xdone = true;
}
} else if (this.x > nextx) {
this.x -= this.pathspeed;
if (this.x <= nextx) {
this.x = nextx;
xdone = true;
}
} else {
xdone = true;
}
if (this.y < nexty) {
this.y += this.pathspeed;
if (this.y >= nexty) {
this.y = nexty;
ydone = true;
}
} else if (this.y > nexty) {
this.y -= this.pathspeed;
if (this.y <= nexty) {
this.y = nexty;
ydone = true;
}
} else {
ydone = true;
}
this.calcgridxy();
// cat parade into a bag or wall?
if (this.state == "parade") {
var bag;
// hit a bag?
bag = getbagaty(this.gridy);
if (goingright && (this.gridx >= GRIDW) && (bag != undefined)) {
if (bag.cats < bag.capacity) {
this.path = [];
this.startshrink();
bag.cats++;
if (bag.cats >= bag.capacity) {
game.dirty = true; // need to redraw
game.addbagpop(bag.y);
}
}
} else {
var brick;
brick = gridxyhasthingtype(this.gridx, this.gridy, "brick");
if (brick != undefined) {
// parade cat explodes.
this.path = [];
this.startexplode();
brick.losehp();
}
}
}
// at destination?
if (xdone && ydone) {
this.poppath();
//console.log("poppath for " + this.name + " - new len is " + this.path.length);
// path finished?
if (this.path == undefined || this.path.length == 0) {
if (this.state == "parade" ){
//console.log("calling kill for parading " + this.type);
this.kill();
} else {
this.state = "stop";
}
}
}
}
} else if (this.state == "slapped") {
var below;
// fall, knocking things below down too
below = this.getstoppedbelowthing();
if (below) {
// anything below vanishes
below.addabove();
below.givepoints();
below.startexplode();
game.dirty = true; // need to redraw
}
if (!atbottom) {
game.dirty = true; // need to redraw
// accelerate
this.yspeed += GRAVITY;
// move
this.y += this.yspeed;
// don't go below bottom of screen
if (this.y > GRIDSIZE * GRIDH) {
//this.y = GRIDSIZE * GRIDH;
this.kill();
} else {
// calc new gridx / gridy
this.calcgridxy();
}
}
} else {
var below,willfall = false;
below = this.getstoppedbelowthing();
// regular gravity
if ((this.gridy >= GRIDH-1)) {
atbottom = true;
}
if (!atbottom && !below && this.canfall()) {
willfall = true;
}
if (willfall) {
game.dirty = true; // need to redraw
// accelerate
this.yspeed += GRAVITY;
// move
this.y += this.yspeed;
this.state = "fall";
// don't go below bottom of screen
if (this.y > GRIDSIZE * GRIDH) {
//this.y = GRIDSIZE * GRIDH;
atbottom = true;
this.state = "stop";
}
// calc new gridx / gridy
this.calcgridxy();
if ((this.gridy >= GRIDH-1)) {
atbottom = true;
this.state = "stop";
}
}
// hit something?
if (atbottom || this.getstoppedbelowthing()) {
// something below us.
// stop
this.yspeed = 0;
// snap to grid
if (this.snaptogrid()) {
game.dirty = true; // need to redraw
}
this.state = "stop";
}
}
}
this.enterbox = function(box) {
game.dirty = true;
box.boxfull = true;
box.catcol = this.catcol;
// this cat vanishes
this.addabove();
this.kill();
}
}
function mainloop() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
var i;
requestAnimFrame(mainloop);
game.frameNo += 1;
if (wipe.isactive()) {
wipe.tick();
}
if (game.state == "title") {
game.clear();
game.drawtitle();
///game.setstate("levselect");
} else if (game.state == "loader") {
game.clear();
game.drawloader();
game.checkfont();
if (nimages >= maximages) {
// has font loaded?
if (game.fontloaded) {
game.setstate("title");
}
}
} else if (game.state == "help") {
game.clear();
game.drawhelp();
} else if (game.state == "levselect") {
game.clear();
game.drawlevselect();
} else if ((game.state == "running") || (game.state == "gameover") || (game.state == "levelcomplete")) {
var gridalpha = 1.0;
// move objects
for (i = 0; i < things.length; i += 1) {
things[i].move();
}
// move "turns left" bar towards its correct location.
game.moveturnsleftbar();
gridalpha = wipe.getval(1.0, "up", "down", 1.0);
ctx.globalAlpha = gridalpha;
if (game.dirty || wipe.isactive() || (game.screenflash > 0)) {
// clear
game.clear();
// draw grid
game.drawgrid();
// draw turns left
game.drawsides();
// draw non-animating objects
for (i = 0; i < things.length; i += 1) {
if (!things[i].isanimating() && things[i].type != "firework") {
things[i].draw(0);
}
}
// draw non-animating box tops
for (i = 0; i < things.length; i += 1) {
if (!things[i].isanimating() && things[i].type == "box") {
things[i].draw(1);
}
}
// draw animating objects
for (i = 0; i < things.length; i += 1) {
if (things[i].isanimating() && things[i].type != "firework") {
things[i].draw(0);
if (things[i].type == "box") {
// draw top as well
things[i].draw(1);
}
}
}
// draw top of canvas (score etc)
game.drawtop();
// draw bottom of canvas (goals)
game.drawbottom();
if (game.state == "gameover") {
// draw buttons
game.drawcontinuebutton();
} else if (game.state == "levelcomplete") {
// draw buttons
game.drawcontinuebutton();
// spawn new fireworks
if (countthingsoftype("firework") < (FIREWORKSHARDS*2)) {
game.addfirework();
}
}
// draw fireworks and box tops
for (i = 0; i < things.length; i += 1) {
if (things[i].type == "firework") {
things[i].draw(1);
}
}
// draw dragged arrow
game.drawpath();
// flash
game.drawflash();
}
ctx.globalAlpha = 1.0;
game.dirty = false;
if (game.state == "levelcomplete") {
// make image bigger
game.zoomwinimg();
}
// check for game over and level over
if (game.state == "running") {
if (!thingsmoving()) {
var i;
if (pathdoor != undefined) {
// still a path door needing to be closed/destroyed?
pathdoor.givepoints();
pathdoor.addabove();
pathdoor.startshrink();
pathdoor = null;
} else if (matchthree()) {
// matched three things in a row?
// if so, don't finish level yet.
} else if (levelfinished()) {
// important: check for this BEFORE gameover.
// otherwise levels with turn limits won't work.
// record score
if (playerdata.levscore[curlevel] == null || score > playerdata.levscore[curlevel]) {
playerdata.setlevscore(curlevel, score);
}
game.setstate("levelcomplete");
} else if (!anyvalidmoves()) {
game.setstate("gameover");
} else if (game.turnsleft <= 0) {
game.setstate("gameover");
}
// mark things as not new
for (i = 0; i < things.length; i += 1) {
if ((things[i].gridy >= 0) && !things[i].isanimating()) things[i].isnew = false;
}
// suns move down once
for (i = 0; i < things.length; i += 1) {
if (things[i].type == "sunlight" &&
isonscreen(things[i].gridx, things[i].gridy) && !things[i].isnew &&
things[i].state != "swapping" && things[i].counter == 0) {
var thingbelow,willdie = false;
thingbelow = getgridthing(things[i].gridx, things[i].gridy+1);
if (thingbelow != undefined && thingbelow.type == "brick") {
// bricks stop sun
willdie = true;
} else if ((things[i].gridy >= GRIDH-1)) {
// at bottom - disappear
willdie = true;
}
if (willdie) {
// at bottom - disappear
things[i].addabove();
things[i].startexplode("sun");
//game.progress("sun", 1);
} else {
// move down (swap with thing below us)
things[i].pushpath(things[i].x, things[i].y + GRIDSIZE);
things[i].state = "swapping";
things[i].counter = 1;
if (thingbelow != undefined && thingbelow.state != "swapping" &&
thingbelow.type != "sunlight") {
thingbelow.pushpath(things[i].x, things[i].y);
thingbelow.state = "swapping";
}
}
}
}
}
}
}
}
function canmatchthree(what) {
if (what.isanimating()) return false; // ignore moving things, though there shouldnt be any anyway
if (!isonscreen(what.gridx, what.gridy)) return false;
switch (what.type) {
case "llama": return true;
}
return false;
}
function isongrid(x, y) {
if (x >= 0 && x < GRIDW && y >= 0 && y < GRIDH) {
return true;
}
return false;
}
function matchthreefrom(what, locdb) {
var setthings = [];
var n,gotmatch = false;
if (locdb == undefined) debug = false;
if (!canmatchthree(what)) return false;
if (what.matched) return false; // ignore things already in sets of 3
for (n = 0; n < MAXDIRS; n++) {
var gotone = true;
if (locdb) console.log("matchthreefrom() - checking " + DIRNAME[n]);
setthings = [];
setthings.push(what);
while ( gotone ) {
var newx,newy;
gotone = false;
newx = setthings[setthings.length-1].gridx + DIRXMOD[n];
newy = setthings[setthings.length-1].gridy + DIRYMOD[n];
if (isongrid(newx, newy)) {
newthing = getgridthing(newx, newy);
if (newthing != null) {
if (locdb) console.log(" -> " + newthing.type);
if (canmatchthree(newthing) &&
newthing.type == setthings[setthings.length-1].type) {
// matched
setthings.push(newthing);
gotone = true;
if (locdb) console.log(" -> matches!");
} else {
if (locdb) console.log(" doesn't match.");
}
} else {
if (locdb) console.log(" nothing there.");
}
} else {
if (locdb) console.log(" off grid.");
}
}
if (locdb) console.log("matched " + setthings.length);
if (setthings.length >= 3) {
var nn;
// matched!
for (nn = 0; nn < setthings.length; nn++) {
//console.log(" "+setthings[nn].name + " x=",setthings[nn].x + ",y=" + setthings[nn].y);
setthings[nn].matched = true;
}
gotmatch = true;
}
setthings = [];
}
return gotmatch;
}
function matchthree() {
var i,n;
var checklen = 3;
var matches = 0;
// for each grid pos, search n/s/e/w for matches of 3 the same.
for (i = 0; i < things.length; i++) {
if (matchthreefrom(things[i])) matches++;
}
// clear anything which matched
for (i = 0; i < things.length; i++) {
if (things[i].matched) {
things[i].givepoints();
things[i].addabove();
things[i].startexplode("matchthree");
things[i].matched = false;
}
}
// also check for ambushes
for (i = 0; i < things.length; i++) {
if (!things[i].isanimating() && things[i].type == "llama" &&
isadjacenttotype(things[i], "box", null, null, true)) {
things[i].matched = true;
}
}
for (i = 0; i < things.length; i++) {
if (things[i].matched) {
things[i].matched = false;
// ambush!
things[i].givepoints();
things[i].addabove();
// add "pow"
things.push(new thing(things[i].gridx, things[i].gridy, "pow"));
things[i].kill();
game.progress("ambush", 1);
}
}
}
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;
}
//<button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)">ACCELERATE</button>
</script>
<br>
</body>
</html>
<!-- vim: set syntax=javascript : -->