catparade/cat.html

4597 lines
114 KiB
HTML
Raw Normal View History

<!-- vim: set syntax=javascript : -->
2016-08-18 14:14:48 +10:00
<!DOCTYPE html>
2016-08-18 14:14:48 +10:00
<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-au" />
<!-- <meta name="viewport" content="width=device-width, minimal-ui" /> -->
2016-08-20 11:34:25 +10:00
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!-- for apps on home screen -->
<!-- for ios 7 style, multi-resolution icon of 152x152 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-barstyle" content="black-translucent">
<link rel="apple-touch-icon" href="images/icon-152.png">
<!-- for Chrome on Android, multi-resolution icon of 196x196 -->
<meta name="mobile-web-app-capable" content="yes">
<link rel="shortcut icon" sizes="196x196" href="images/icon-196.png">
2016-08-18 14:14:48 +10:00
<style>
canvas {
border:1px solid #d3d3d3;
background-color: #000000;
2016-08-18 14:14:48 +10:00
}
@font-face {
font-family: "BlueStone";
src: url("BlueStone.ttf");
}
2016-08-18 14:14:48 +10:00
</style>
</head>
<body onload="startGame()">
<script>
var FONT = "BlueStone";
// game vars
2016-08-18 14:14:48 +10:00
var things = [];
var score = 0;
var multiplier = 1;
var overdesc = "";
2016-08-18 14:54:54 +10:00
var curpath = [];
var pathdir = -1;
var pathdoor = null;
2016-08-18 16:18:35 +10:00
var pathvalid = false;
var curlevel = 1;
var lastmx = -1, lastmy = -1;
var mbdown = false;
2016-08-25 20:01:45 +10:00
var FLASHSPEED = 0.05;
// for background
var catalpha = 1.0;
var ctx = null;
// goal types
var GOALVERB = {
'food': 'Eat',
'turns': 'Survive',
'points': 'Earn',
'parades': 'Form',
'llamas': 'Clear',
'cats': 'Clear',
'goats': 'Clear',
'doors': 'Enter',
'sun cycles': 'Wait out',
};
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_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;
2016-08-18 14:54:54 +10:00
var MAXDIRS = 4;
var DIRXMOD = [ 0, 1, 0, -1 ];
var DIRYMOD = [ -1, 0, 1, 0 ];
var MAXWINIMAGES = 8;
var WINIMGZOOMSPEED = 0.1;
var SCREENW = 480;
var SCREENH = 640;
2016-08-18 14:14:48 +10:00
var GRAVITY = 0.5;
var TEXTSIZE = 16; // in points
var TEXTSIZEMULTIPLIER = 28; // in points
var TEXTSPEED = 0.5;
var TEXTFADESPEED = 0.05;
var TEXTTIME = 35;
2016-08-25 20:01:45 +10:00
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 = 12; // in points
var HELPLINEWIDTH=4;
var HELPARROWSIZE=15;
var HELPTITLESIZE = 18;
var HELPSUBTITLESIZE = 14;
//var HELPTEXTSIZE = 12;
var HELPTEXTSIZE = 9;
var GOALTEXTSIZE = 16;
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=5;
2016-08-18 14:14:48 +10:00
var LEVSELGRIDW = 5;
var LEVSELGRIDH = 6;
var PATHARROWSIZE = 10;
var PATHLINECOLGOOD = "#00ee00";
var PATHLINECOLBAD = "#ee0000";
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 overdesc = "";
var llamatext = "llama";
var image = new Array();
var winimg = null;
function loadimage(name, filename) {
image[name] = new Image();
image[name].src = filename;
}
2016-08-18 14:14:48 +10:00
function getgridthing(gridx, gridy) {
var i;
if (things == undefined) return null;
// only include non-animating things
2016-08-18 14:14:48 +10:00
for (i = 0; i < things.length; i += 1) {
if ((things[i].gridx == gridx) && (things[i].gridy == gridy)) {
if (!things[i].isanimating()) {
return things[i];
}
2016-08-18 14:14:48 +10:00
}
}
return null;
}
function getthingxy(x, y) {
var i,gridx,gridy;
var thing;
// use thing coords
/*
2016-08-18 14:14:48 +10:00
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);
2016-08-18 14:14:48 +10:00
return getgridthing(gridx, gridy);
2016-08-18 14:14:48 +10:00
}
2016-08-18 16:18:35 +10:00
function clearpath() {
curpath = [];
pathdir = -1;
2016-08-18 16:18:35 +10:00
pathvalid = false;
}
function pathcomplete() {
// get path type
switch(getpathtype()) {
case "chomp":
if ((curpath.length >= 2) &&
(curpath[0].type == "cat") &&
!curpath[0].issleepy()) {
var i;
var ok = true;
// everything else is food?
for (i = 1; i < curpath.length; i++) {
if (curpath[i].type != "food") {
ok = false;
break;
}
}
if (ok) {
return true;
}
}
break;
case "parade":
// long enough?
if (curpath.length >= PARADELENGTH) {
var lcount = 0,gcount = 0;
var i;
// includes <= 1 llama?
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;
}
return false;
}
function backspacepath() {
// remove last thing in path
curpath.splice(curpath.length-1, 1);
validatepath();
dumppath("Reduced path to: ",curpath);
}
2016-08-18 16:18:35 +10:00
function addtopath(what) {
console.log("addpath() " + what.name);
//dumppath("addpath pre: ", curpath);
2016-08-18 16:18:35 +10:00
curpath.push(what);
validatepath();
/*
2016-08-18 16:18:35 +10:00
if (curpath.length == 1) {
console.log("Starting path with " + what.name);
} else {
dumppath("Cur path is: ",curpath);
}
*/
2016-08-18 16:18:35 +10:00
}
function validatepath() {
if (pathcomplete()) {
pathvalid = true;
} else {
pathvalid = false;
}
if (curpath.length == 2) {
pathdir = getdir(curpath[0], curpath[1]);
}
}
2016-08-18 14:54:54 +10:00
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;
}
2016-08-18 16:18:35 +10:00
function isvalidpath(mypath) {
var valid = true;
var firstcat = false;
var fcount = 0;
var lcount = 0;
var gcount = 0;
var count = 0;
var i;
2016-08-18 16:18:35 +10:00
for (i = 0; i < mypath.length - 1; i++) {
var thisone,nextone;
thisone = mypath[i];
/*
if (i == mypath.length - 1) { // last one
nextone = null;
} else {
nextone = mypath[i+1];
}
*/
nextone = mypath[i+1];
if (thisone.type == "food") {
fcount++;
}
if (thisone.type == "llama") {
lcount++;
}
if (thisone.type == "goat") {
gcount++;
}
count++;
if ((thisone.type == "cat") && (nextone.type == "cat")) {
// no parades on level 1
if (curlevel == 1) {
return false;
}
// ...but otherwise lines of cats are ok
} else if ((thisone.type == "goat") && (nextone.type == "cat" || nextone.type == "llama" || nextone.type == "goat")) {
// goat can go to llama or cat
// ok
} else if ((nextone.type == "door") && (count >= PARADELENGTH) && (fcount == 0) && (count >= 3) &&
(thisone.type == "goat" || thisone.type == "cat" || thisone.type == "llama")) {
// completed parades can extend into doors
} else if ((thisone.type == "cat") && nextone.type == "llama") {
// 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") {
// cat -> goat is okay
} else if ((thisone.type == "llama") && (nextone.type == "cat" || 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")) {
// llama -> llama okay if we have a goat in the path
if (!gcount) {
return false;
}
2016-08-18 16:18:35 +10:00
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "food")) {
// first cat -> food is ok
firstcat = true;
} else if ((i != 0) && firstcat && (thisone.type == "food")) {
// not the first one, first one was a cat, this one is food
2016-08-18 16:18:35 +10:00
} 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;
2016-08-18 16:18:35 +10:00
if (!overthing) return false;
pathtype = getpathtype();
if ( isadjacent(overthing, curpath[curpath.length-1]) && // adjacent to last thing in path?
!pathcontains(overthing) && // path doesn't already contain this?
(pathtype == "parade" || isinpathdir(overthing)) // path in a straight line
) {
2016-08-18 16:18:35 +10:00
// create a fake new path containing this.
var fakepath = curpath.slice();
2016-08-18 16:18:35 +10:00
fakepath.push(overthing);
if (isvalidpath(fakepath)) {
return true;
}
}
return false;
}
2016-08-18 14:54:54 +10:00
function isadjacent(thing1, thing2) {
// is thing1 adjacent to thing2?
var newgridx,newgridy;
var i;
2016-08-18 14:54:54 +10:00
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;
2016-08-18 14:14:48 +10:00
}
}
return -1;
}
function startGame() {
loadimage('cat', 'images/cat.png');
loadimage('catfull', 'images/catfull.png');
loadimage('catscared', 'images/catscared.png');
loadimage('llama', 'images/alpaca.png');
loadimage('cheese', 'images/cheese.png');
loadimage('title', 'images/title.png');
loadimage('goat', 'images/goat.png');
loadimage('door', 'images/door.png');
loadimage('sunlight', 'images/sunlight.png');
loadimage('lock', 'images/lock.png');
loadimage('catwalkl', 'images/catwalkl.png');
loadimage('catwalkr', 'images/catwalkr.png');
loadimage('starfull', 'images/starfull.png');
loadimage('starempty', 'images/starempty.png');
game.init();
game.initlevels();
playerdata.load();
//window.addEventListener('load', game.init, false);
window.addEventListener('resize', game.resize, false);
mainloop();
}
2016-08-18 14:14:48 +10:00
// valid types:
// none
// chomp
// parade
function getpathtype() {
if (curpath.length <= 1) return "none"
if (curpath[0].type == "cat") {
if (curpath[1].type == "food") {
return "chomp";
} else if ((curpath[1].type == "cat") || (curpath[1].type == "llama") || (curpath[1].type == "goat")) {
return "parade";
}
}
return "none";
2016-08-18 14:14:48 +10:00
}
2016-08-18 16:18:35 +10:00
function dumppath(prefix,arr) {
2016-08-18 14:54:54 +10:00
var str;
var i;
2016-08-18 14:54:54 +10:00
str = "";
2016-08-18 16:18:35 +10:00
for (i = 0; i < arr.length; i++) {
str = str + " " + arr[i].name;
2016-08-18 14:54:54 +10:00
}
2016-08-18 16:18:35 +10:00
console.log(prefix + str);
2016-08-18 14:54:54 +10:00
}
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;
}
2016-08-18 14:54:54 +10:00
function thingsmoving() {
var i;
2016-08-20 11:34:25 +10:00
for (i = 0; i < things.length; i += 1) {
if ((things[i].state == "parade") || (things[i].state == "shrink")) {
2016-08-20 11:34:25 +10:00
return "parade";
}
}
for (i = 0; i < things.length; i += 1) {
if (things[i].state == "explode") {
return "chomp";
}
}
2016-08-18 14:54:54 +10:00
for (i = 0; i < things.length; i += 1) {
if (things[i].state != "stop") {
2016-08-20 11:34:25 +10:00
return "other";
2016-08-18 14:54:54 +10:00
}
}
return false;
}
function canstartpath(what) {
if (what.type != "cat") {
// only cats can start paths
console.log("not a cat");
return false;
}
if (isadjacenttotype(what, "llama")) {
// cats adjacent to llamas are frozen
console.log("next to a llama");
return false;
}
return true;
}
function clearthings() {
while (things.length > 0) {
things.pop();
}
things = [];
}
function getmousexy(event) {
var e,scale;
var 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) {
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)) {
return true;
}
}
}
return false;
}
function drawline(ctx,x1,y1,x2,y2,col,width) {
ctx.strokeStyle = col;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
2016-08-21 07:56:41 +10:00
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 countthingsoftype(type) {
var i,count=0;
for (i = 0; i < things.length; i++) {
if (things[i].type == type) {
count++;
}
}
return count;
}
var playerdata = {
levscore: [],
saveall : function() {
if (game.cheat) return;
this.savetotstars();
for (i = 0; i < this.levscore.length; i++) {
savelevscore(i);
}
},
savetotstars : function() {
// save max level
if (game.cheat) return;
localStorage.setItem('totstars',this.totstars);
},
savelevscore : function(lev) {
if (game.cheat) return;
localStorage.setItem("levscore" + lev, this.levscore[lev]);
},
load : function() {
var num,i;
// local star count
num = localStorage.getItem('totstars');
if (num != undefined) {
this.totstars = num;
console.log("playerdata.load() - totstars is " + this.totstars);
}
// load level hiscores
for (i = 0; i < game.levels.length; i++) {
num = localStorage.getItem("levscore" + i);
if (num == undefined) {
this.levscore[i] = 0;
} else {
this.levscore[i] = num;
console.log("playerdata.load() - lev " + i + " score is " + num);
}
}
// 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 < game.levels.length; i++) {
localStorage.removeItem("levscore" + i);
}
console.log("cleared local storage.");
},
settotstars : function(num) {
console.log("playerdata() - setting total stars to " + num);
this.totstars = num;
this.savetotstars();
},
setlevscore : function(lev, num) {
this.levscore[lev] = num;
this.savelevscore(lev);
},
};
var game = {
ratio: null,
curw: null,
curh: null,
levels: null,
state: "title",
cheat: 0,
winimgsize: 0,
frameNo: 0,
2016-08-25 20:01:45 +10:00
screenflash: 0,
canvas: null,
2016-08-25 20:01:45 +10:00
addfirework : function() {
this.screenflash = 1.0;
},
init : function() {
this.canvas = document.createElement("canvas");
this.ratio = SCREENW / SCREENH;
this.curw = SCREENW;
this.curh = SCREENH;
this.canvas.width = SCREENW;
this.canvas.height = SCREENH;
this.context = this.canvas.getContext("2d");
ctx = this.context;
// find ipad/android/iphone so we can hide the address bar
this.ua = navigator.userAgent.toLowerCase();
this.android = this.ua.indexOf('android') > -1 ? true : false;
this.ios = ( this.ua.indexOf('iphone') > -1 || this.ua.indexOf('ipad') > -1 ) ? true : false;
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
// try to go full screen?
var body = document.documentElement;
if (body.requestFullscreen) {
body.requestFullscreen();
} else if (body.webkitrequestFullscreen) {
body.webkitrequestFullscreen();
} else if (body.mozrequestFullscreen) {
body.mozrequestFullscreen();
} else if (body.msrequestFullscreen) {
body.msrequestFullscreen();
}
this.resize();
//if (game.ios) {
// this.interval = setInterval(mainloop, 20);
//} else {
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(mainloop, 1000 / 60);
};
})();
//}
this.canvas.addEventListener('mousedown', this.handlemousedown, false);
this.canvas.addEventListener('mouseup', this.handlemouseup, false);
this.canvas.addEventListener('mouseout', this.handlemouseup, false);
this.canvas.addEventListener('mousemove', this.handlemousemove, false);
this.canvas.addEventListener('touchstart', this.handlemousedown, false);
this.canvas.addEventListener('touchend', this.handlemouseup, false);
this.canvas.addEventListener('touchcancel', this.handlemouseup, false);
this.canvas.addEventListener('touchleave', this.handlemouseup, false);
this.canvas.addEventListener('touchmove', this.handlemousemove, false);
window.addEventListener('keypress', this.handlekeypress, false);
this.setstate("title");
},
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;
}
}
},
resize : function() {
game.curh = window.innerHeight;
game.curw = game.curh * game.ratio;
// hide address bar on phones
if (game.android || game.ios) {
document.body.style.height = (window.innerHeight + 50) + 'px';
}
// scale canvas
////this.canvas.style.width = this.curw + 'px';
//this.canvas.style.height = this.curh + 'px';
// this.canvas.style.width = this.curw + 'px';
// this.canvas.style.height = this.curh + 'px';
console.log("game.canvas = " + game.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;
multiplier = 1;
this.frameNo = 0;
},
initlevelvars : function() {
// 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;
}
},
addlevel : function (lev, hashelp) {
var mylevel;
mylevel = new Object();
mylevel.hashelp = hashelp;
mylevel.goals = new Array();
mylevel.thinglist = new Array();
mylevel.forcelist = new Array();
mylevel.allowedthings = new Array();
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;
} else {
// default to size of previous one
mylevel.gridsize = this.levels[lev-1].gridsize;
mylevel.thingsize = this.levels[lev-1].thingsize;
mylevel.gridw = this.levels[lev-1].gridw;
mylevel.gridh = this.levels[lev-1].gridh;
mylevel.boardx = this.levels[lev-1].boardx;
}
this.levels[lev] = mylevel;
playerdata.levscore[lev] = 0;
},
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 (lev) {
var i,idx;
for (i = 1 ; i < arguments.length; i += 2) {
idx = this.levels[lev].thinglist.push(new Object()) - 1;
this.levels[lev].thinglist[idx].type = arguments[i];
this.levels[lev].thinglist[idx].pct = arguments[i+1];
}
},
// thingtype howmany
addlevelforcethings: function (lev) {
var i,idx;
for (i = 1 ; 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 (lev) {
var i;
for (i = 1 ; i < arguments.length; i++) {
this.levels[lev].allowedthings.push(arguments[i]);
}
},
// goal1type goal1count goal2type goal2count etc...
addlevelgoals : function (lev) {
var i,idx;
for (i = 1 ; i < arguments.length; i += 2) {
idx = this.levels[lev].goals.push(new Object()) - 1;
this.levels[lev].goals[idx].type = arguments[i];
this.levels[lev].goals[idx].count = arguments[i+1];
this.levels[lev].goals[idx].progress = 0;
}
},
// earn progress towards goals
progress : function(type, amt) {
var i;
// past last level!
if (curlevel >= game.levels.length) return false;
//console.log("progress()");
for (i = 0 ; i < this.levels[curlevel].goals.length; i++ ) {
//console.log("this goal type is " + this.levels[curlevel].goals[i].type);
if (this.levels[curlevel].goals[i].type == type) {
this.levels[curlevel].goals[i].progress += amt;
if (this.levels[curlevel].goals[i].progress >= this.levels[curlevel].goals[i].count) {
2016-08-25 20:01:45 +10:00
this.levels[curlevel].goals[i].progress = this.levels[curlevel].goals[i].count;
2016-08-25 20:01:45 +10:00
// add goal animation - fireworks ?
//things.push(new thing(2,2, "text", "Goal!"));
this.addfirework();
}
}
}
},
calcstars : function (lev, points) {
var i;
for (i = 2; i >= 0; i--) {
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;
},
calcstarpoints : function( lev ) {
var i,num;
var min = 0,pointsgoal = 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;
break;
case "llamas":
// actually a bit more than this, since you need cats to get rid of llamas.
min += (LLAMAPOINTS) * this.levels[lev].goals[i].count;
break;
case "cats": // use smaller amount of catpoints
min += Math.min(CATPOINTS, SLEEPYCATPOINTS) * this.levels[lev].goals[i].count;
break;
case "goats":
min += GOATPOINTS * this.levels[lev].goals[i].count;
break;
case "turns":
// 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 "doors":
// double the value of a minimum-score parade
min += Math.min(CATPOINTS, SLEEPYCATPOINTS) * 3 * 2 * this.levels[lev].goals[i].count;
break;
case "sun cycles":
min += SLEEPYCATPOINTS*5 * this.levels[lev].goals[i].count;
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;
}
}
*/
// 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) * 2;
min *= ratio;
}
this.levels[lev].starpoints = new Array();
this.levels[lev].starpoints[0] = Math.floor(Math.max(min, pointsgoal));
this.levels[lev].starpoints[1] = Math.floor(Math.max(min * 2, pointsgoal*1.5));
this.levels[lev].starpoints[2] = Math.floor(Math.max(min * 3, pointsgoal*2));
},
initlevels : function ( ) {
var mylevel,i,n;
console.log("doing level init");
this.levels = [];
this.addlevel(1, true);
this.addlevelgoals(1, "food", 5);
this.setstarpoints(1, 50, 60, 80);
this.addlevelthings(1, "cat", 50, "food", 50);
this.addlevel(2, true);
this.addlevelgoals(2, "cats", 10);
this.addlevelgoals(2, "parades", 3);
this.addlevelthings(2, "cat", 45, "food", 55);
this.addlevel(3, true);
this.addlevelallowedthings(3, "cat", "food", "llama");
this.addlevelgoals(3, "llamas", 3);
this.addlevel(4, true);
this.addlevelgoals(4, "llamas", 5);
this.addlevelgoals(4, "food", 5);
this.addlevelallowedthings(4, "cat", "food", "llama");
this.addlevel(5, false);
this.addlevelgridwid(5, 6);
this.addlevelgoals(5, "turns", 10);
//this.addlevelgoals(5, "points", 600);
this.addlevelallowedthings(5, "cat", "food", "llama");
// introduce goats!
this.addlevel(6, true);
this.addlevelgoals(6, "goats", 3);
this.addlevelthings(6, "goat", 10, "cat", 50, "food", 30, "llama", 10); // higher than normal goat chance
this.addlevelforcethings(6, "goat", 1);
this.addlevel(7, false);
this.addlevelallowedthings(7, "goat", "cat", "food", "llama");
this.addlevelgoals(7, "turns", 15);
this.addlevelgoals(7, "llamas", 2);
this.addlevelgoals(7, "goats", 2);
this.addlevel(8, false);
this.addlevelallowedthings(8,"goat", "cat", "food", "llama");
this.addlevelgoals(8, "turns", 20);
this.addlevelgoals(8, "llamas", 3);
// introduce doors
this.addlevel(9, true);
this.addlevelallowedthings(9,"goat", "cat", "food", "llama", "door");
this.addlevelgoals(9, "doors", 1);
this.addlevelforcethings(9, "door", 1);
this.addlevel(10, false);
this.addlevelgridwid(10, 7);
this.addlevelallowedthings(10,"goat", "cat", "food", "llama", "door");
//this.addlevelgoals(10, "points", 2000);
this.addlevelgoals(10, "turns", 15);
// introduce sunlight
this.addlevel(11, true);
this.addlevelallowedthings(11,"goat", "cat", "food", "llama", "door", "sunlight");
this.addlevelgoals(11, "turns", 10);
this.addlevelgoals(11, "sun cycles", 1);
this.addlevelforcethings(11, "sunlight", 1);
for (i = 1; i < this.levels.length; i++) {
var extrastars;
// calc star point cutoffs
if (this.levels[i].starpoints == undefined) {
this.calcstarpoints(i);
}
// calc stars to unlock
extrastars = Math.floor(i / 5);
this.levels[i].starsrequired = (i-1) + extrastars;
}
/*
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;
console.log("populating grid...");
clearthings();
// populate initial things
for (y = 0; y < GRIDH; y++) {
for (x = 0; x < GRIDW; x++) {
// start off above the grid
things.push(new thing(x, y, "random"));
}
}
// 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;
idx = rnd(things.length);
things[idx].type = wanttype;
}
}
}
}
// now move everything up so they'll fall down into the initial
// positions.
for (i = 0; i < things.length; i++) {
things[i].gridy -= GRIDH;
things[i].updatexy();
}
},
2016-08-25 20:01:45 +10:00
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;
},
2016-08-18 14:14:48 +10:00
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
drawtop : function() {
var texttodraw = overdesc;
2016-08-20 11:34:25 +10:00
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 = 26;
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);
// show star targets
var starsize = 10;
var starindent = 16;
2016-08-25 20:01:45 +10:00
var startextcol = "#eeee00";
var startextcoldark = "#999900";
x = SCREENW/4;
for (i = 0; i < 3; i++) {
var n,starx,stars = "";
for (n = 0; n < (i+1); n++) {
/*
if (n == 0) {
starx = (SCREENW/4)
} else if
drawcross(ctx, starx, y, starx + starsize, y + starsize, startextcol, 2);
starx += starsize*1.5;
*/
stars = stars + "\u2605"
}
ctx.textAlign = "center";
ctx.textBaseline = "middle";
2016-08-25 20:01:45 +10:00
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 "chomp":
col = "#00cc00";
texttodraw = "Chomp!"
break;
default:
// show current object in path
if ((curpath != undefined) && (curpath.length >= 1)) {
var lastone = curpath[curpath.length - 1];
texttodraw = lastone.getdesc();
}
break;
}
if (texttodraw != "") {
/*
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") {
this.context.textAlign = "center";
this.context.textBaseline = "top";
shadowtext(this.context, "No more moves!", 20, "red", SCREENW / 2, 5);
2016-08-21 07:56:41 +10:00
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 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 = "cyan";
ctx.lineWidth = BOTTOMBORDERWIDTH;
ctx.beginPath();
ctx.rect(bx+(borderwidth/2), by+(borderwidth/2), bw-(borderwidth/2), bh-(borderwidth/2));
ctx.stroke();
// draw goals
x = bx + margin;
y = by + margin;
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, "Goals:", GOALTEXTSIZE,goaltitlecol, x , y); y += texth;
if (curlevel < this.levels.length) {
for (n = 0; n < this.levels[curlevel].goals.length; n++) {
var goalcol = "#aaee00";
var goalmet = false;
2016-08-25 20:01:45 +10:00
// 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 name
var goaltext = GOALVERB[this.levels[curlevel].goals[n].type] + " " +
this.levels[curlevel].goals[n].count + " " +
this.levels[curlevel].goals[n].type;
var progtext = "[" + this.levels[curlevel].goals[n].progress + " / " +
this.levels[curlevel].goals[n].count + "]";
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, goaltext, GOALTEXTSIZE,goalcol, x+indent, y);
ctx.textAlign = "right";
ctx.textBaseline = "top";
shadowtext(ctx, progtext, GOALTEXTSIZE,goalcol, SCREENW-indent, y);
2016-08-25 20:01:45 +10:00
// 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;
// background
/*
gradient = this.context.createLinearGradient(0, 0, 0, SCREENH);
gradient.addColorStop(0, "black");
gradient.addColorStop(1, "blue");
this.context.fillStyle = gradient;
*/
this.context.fillStyle = "#005051";
this.context.fillRect(0, 0, SCREENW, SCREENH);
// moving cats
2016-08-25 20:01:45 +10:00
catscale = 1;
catspeed = 0.5;
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);
for (y = -catsize*2; y < SCREENH; y += catsize*2) {
for (x = -catsize*2; x < SCREENW+catsize*2; x += catsize) {
//ctx.fillStyle = "red";
//ctx.fillRect(x +(catsize/4)+off, y +(catsize/4)+off, catsize/2, catsize/2);
this.context.drawImage(image['catwalkr'], x +(catsize/4)+off*catspeed, y +(catsize/4),
image['catwalkr'].width*catscale,
image['catwalkr'].height*catscale);
this.context.drawImage(image['catwalkl'], x +(catsize/4)-off*catspeed, y +(catsize/4)+catsize,
image['catwalkl'].width*catscale,
image['catwalkl'].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 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);
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++ ) {
total += game.calcstars(i, playerdata.getlevscore(i));
}
return total;
},
levellocked : function (lev) {
if (playerdata.totstars < game.levels[lev].starsrequired) 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;
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";
//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) {
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Drag a cat to eat cheese.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Cats can eat any amount of cheese.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, "Cats can only eat cheese in a straight line.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
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;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
// LLAMA HELP
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats are scared of " + llamatext + "s and can't move when near them.", 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;
// PARADE HELP
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 turn corners.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
shadowtext(ctx, " - Parades can include one " + 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,pointscol, x, y);
y += HELPTEXTYSPACE;
shadowtext(ctx, "+" + SLEEPYCATPOINTS + " points per sleepy cat", HELPTEXTSIZE,pointscol, 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;
// GAME OVER HELP
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "You fail the level if there are no valid moves left.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
ctx.textAlign = "center";
this.context.textBaseline = "bottom";
shadowtext(this.context, "Tap to start", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, SCREENH - HELPTEXTYSPACE*2);
} else 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;
2016-08-18 14:14:48 +10:00
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;
2016-08-18 14:14:48 +10:00
// 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 progress to the next level!", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
cury += HELPTEXTYSPACE;
// GAME OVER HELP
cury = this.drawhelpsubtitle(ctx, "End of Game", cury);
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "The game ends when there are no valid moves left.", 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['cat'], 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 + 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;
} else if (curlevel == 3) {
var llamatitle = llamatext[0].toUpperCase() + llamatext.substring(1);
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;
} 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;
} 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 llamas.", 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;
cury = y;
shadowtext(ctx, "Goats can't start parades. 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, "After reaching the the bottom of the play area, suns will set.", HELPTEXTSIZE,helpcol, textxspace, cury);
cury += HELPTEXTYSPACE;
}
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 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);
// grid
ctx.globalAlpha = gridalpha;
for (y = 0; y < LEVSELGRIDH*GRIDSIZE; y += GRIDSIZE) {
for (x = 0; x < LEVSELGRIDW*GRIDSIZE; x += GRIDSIZE) {
if (this.levelexists(levnum)) {
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 ((playerdata.levscore[levnum] > 0) && !this.levellocked(levnum)) {
gridgrad.addColorStop(0, "#00ee00");
} else {
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();
if (this.levellocked(levnum)) {
// locked
ctx.drawImage(image['lock'],
LEVSEL_X + x + GRIDSIZE/2 - image['lock'].width/3,
LEVSEL_Y + y + GRIDSIZE/3 - image['lock'].height/3,
GRIDSIZE/2, GRIDSIZE/2);
// # stars to unlock
ctx.drawImage(image['starfull'],
LEVSEL_X + x + GRIDSIZE/2 - STARWID_LEVSEL_LOCKED*1.25,
LEVSEL_Y + y + GRIDSIZE - (GRIDSIZE/4) - (STARWID_LEVSEL_LOCKED/2),
STARWID_LEVSEL_LOCKED, STARWID_LEVSEL_LOCKED);
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, this.levels[levnum].starsrequired,
TEXTSIZELEVSTARS, starcol,
LEVSEL_X + x + GRIDSIZE/2, LEVSEL_Y + y + GRIDSIZE - (GRIDSIZE/4) - (STARWID_LEVSEL_LOCKED/2));
} else {
var hiscore,starcount,stars = "";
var levy = LEVSEL_Y + y + GRIDSIZE/4;
var scorey = LEVSEL_Y + y + GRIDSIZE - 5;
var stary = levy + ((scorey - levy) / 2); // half way between
// show level num and hiscore
ctx.textAlign = "center";
ctx.textBaseline = "middle";
shadowtext(ctx, levnum, TEXTSIZELEVSELECT, textcol,
LEVSEL_X + x + GRIDSIZE/2,levy);
// 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) {
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);
},
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);
}
},
2016-08-18 14:54:54 +10:00
/*
2016-08-18 14:14:48 +10:00
handleclick : function(event) {
2016-08-18 14:54:54 +10:00
// make sure nothing is moving
if (thingsmoving()) return;
2016-08-18 14:14:48 +10:00
// did you click on an object?
var clickedthing = getthingxy(event.pageX,event.pageY);
if (clickedthing) {
clickedthing.kill();
}
2016-08-18 14:54:54 +10:00
},
*/
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 == 'c') {
playerdata.clearall();
2016-08-25 20:01:45 +10:00
} else if (ch == 't') {
game.addfirework();
} 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;
playerdata.settotstars(9999);
console.log("cheat() - set tot stars to " + playerdata.totstars);
2016-08-20 11:34:25 +10:00
}
},
2016-08-20 11:34:25 +10:00
handlemousedown : function(event) {
var coords,adjustx,adjusty,realx,realy;
2016-08-20 11:34:25 +10:00
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
2016-08-18 14:54:54 +10:00
// make sure nothing is moving
if (thingsfalling()) return;
2016-08-18 14:54:54 +10:00
// clear existing path
clearpath();
// did you click on an object?
var onthing = getthingxy(adjustx, adjusty);
if (onthing) {
if (canstartpath(onthing)) {
console.log("Initial click on " + onthing.name);
addtopath(onthing);
} else {
// just show description
overdesc = onthing.getdesc();
}
}
} else if (game.state == "gameover") {
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)) {
/* TODO: this currently works on ios */
//game.setstate("title");
wipe.start("title", "out", 15);
} else {
/* TODO: this DOESN'T works on ios */
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);
levsel = (gridy*LEVSELGRIDW) + gridx + 1;
if (game.levelexists(levsel) && !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);
2016-08-18 14:54:54 +10:00
}
},
setstate : function(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(MAXWINIMAGES);
winimg.src = "images/win" + n + ".png";
this.winimgsize = 0.1;
wipe.start("", "in", 50);
} else if (newstate == "levselect") {
// reset grid size
GRIDSIZE = DEF_GRIDSIZE;
THINGSIZE = DEF_THINGSIZE;
GRIDW = DEF_GRIDW;
GRIDH = DEF_GRIDH;
// count player stars
playerdata.settotstars(game.countplayerstars());
// start fade in
wipe.start("", "in", 15);
} else if (newstate == "title") {
// start fade in
wipe.start("", "in", 15);
}
this.state = newstate;
},
2016-08-20 11:34:25 +10:00
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
2016-08-18 14:54:54 +10:00
// make sure nothing is moving
if (thingsmoving()) return;
// existing path?
if (curpath != undefined && curpath.length > 0) {
var overthing = getthingxy(adjustx, adjusty);
var lastinpath,secondlast;
2016-08-18 16:18:35 +10:00
if (curpath == undefined) {
lastinpath = null;
} else {
lastinpath = curpath[curpath.length-1];
if (curpath.length >= 2) {
secondlast = curpath[curpath.length-2];
} else {
secondlast = null;
}
2016-08-18 16:18:35 +10:00
}
2016-08-18 14:54:54 +10:00
if ((secondlast != undefined) && (overthing == secondlast)) {
backspacepath();
} else if (canextendpath(overthing)) {
2016-08-18 14:54:54 +10:00
// add it to the path
2016-08-18 16:18:35 +10:00
addtopath(overthing);
} else if (pathcontains(overthing) && pathcomplete()) {
2016-08-18 16:18:35 +10:00
pathvalid = true;
} else {
pathvalid = false;
2016-08-18 14:54:54 +10:00
}
}
},
handlemouseup : function(event) {
2016-08-18 16:18:35 +10:00
var ok = true;
var adjustx, adjusty,realx,realy;
var ptype;
var points = 0;
2016-08-20 11:34:25 +10:00
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");
2016-08-18 16:18:35 +10:00
ok = false;
} else if (!isvalidpath(curpath)) {
//console.log("mouseup() - path isn't valid");
2016-08-18 16:18:35 +10:00
ok = false;
} else if (!pathcomplete(curpath)) {
//console.log("mouseup() - path ("+ ptype + ") isn't complete");
ok = false;
2016-08-18 16:18:35 +10:00
} else if ((overthing == undefined) || !pathcontains(overthing)) {
//console.log("mouseup() - not over something in path");
2016-08-18 16:18:35 +10:00
ok = false;
}
if (!ok) {
clearpath();
return;
}
2016-08-18 14:54:54 +10:00
// figure out path type
points = 0;
switch(ptype) {
case "chomp":
// killall except first and last
if (curpath.length > 2) {
var i;
for (i = 1; i < curpath.length - 1; i++) {
curpath[i].givepoints();
curpath[i].addabove();
//curpath[i].kill();
curpath[i].startexplode();
}
}
curpath[curpath.length-1].givepoints();
// first one chomps last one
curpath[0].chomp(curpath[curpath.length - 1]);
game.progress("turns", 1);
break;
case "parade":
multiplier = 1;
if (curpath.length >= 2) { // should always be true
var i;
// everything in the path exits via a parade
pathdoor = pathcontainstype("door");
if (pathdoor) multiplier++;
for (i = 0; i < curpath.length; i++) {
// ... except doors
if (curpath[i].type == "door") {
game.progress("doors", 1);
console.log("path has doors!");
// add animation
things.push(new thing(curpath[i].gridx, curpath[i].gridy, "text", "x" + multiplier));
} else {
curpath[i].givepoints();
curpath[i].startparade();
}
}
clearpath();
} else {
// just kill everything in path
while (curpath != undefined && curpath.length > 0) {
curpath[0].givepoints();
curpath[0].addabove();
curpath[0].kill();
curpath.splice(0, 1);
}
}
multiplier = 1;
game.progress("parades", 1);
game.progress("turns", 1);
break;
2016-08-18 14:54:54 +10:00
}
2016-08-18 16:18:35 +10:00
clearpath();
// things that happen once per turn.
game.turneffects();
2016-08-18 14:54:54 +10:00
},
2016-08-18 14:14:48 +10:00
}
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 onein(num) {
var roll;
roll = Math.floor(Math.random() * num);
if (roll == 0) return true;
return false;
}
2016-08-18 14:14:48 +10:00
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';
2016-08-18 16:18:35 +10:00
var name = "";
2016-08-18 14:14:48 +10:00
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);
}
2016-08-18 16:18:35 +10:00
function getrandomtype() {
var roll,tot,type = null,i;
var thinglist,maxroll = 0;
var speciallist = null;
if (curlevel >= game.levels.length) {
thinglist = null;
} else {
thinglist = game.levels[curlevel].thinglist;
}
// construct a list of permitted special things
var poss = [ "sunlight" ] ;
for (i in poss ) {
if (!game.isbanned(curlevel, poss[i])) {
if (speciallist == undefined) {
speciallist = [];
}
speciallist.push(poss[i]);
}
}
console.log("speciallist is " + speciallist);
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) {
console.log("specials are possible");
thinglist.push({ type: 'special', pct: 10 } );
}
if (!game.isbanned(curlevel, 'goat')) thinglist.push({ type: 'goat', pct: 10 } );
if (!game.isbanned(curlevel, 'llama')) thinglist.push({ type: 'llama', pct: 20 } );
if (!game.isbanned(curlevel, 'food')) thinglist.push({ type: 'food', pct: 40 } );
if (!game.isbanned(curlevel, 'cat')) thinglist.push({ type: 'cat', pct: 45 } );
}
for (i = 0; i < thinglist.length; i++) {
maxroll += thinglist[i].pct;
//console.log(thinglist[i].type + ": " + thinglist[i].pct + " [" + maxroll + "]");
}
for (i = 0; i < thinglist.length; i++) {
console.log(thinglist[i].type + ": " + ((thinglist[i].pct / maxroll)*100) + "% [roll " + maxroll + "/" + maxroll + "]");
}
roll = rnd(maxroll);
tot = 0;
for (i = 0; i < thinglist.length; i++) {
tot += thinglist[i].pct;
if (roll <= tot) {
console.log("rolled " + roll + "/" + maxroll + " --> " + thinglist[i].type + " [<= " + tot + "]");
type = thinglist[i].type;
break;
}
}
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)];
2016-08-18 16:18:35 +10:00
}
return type;
}
function coord(x,y) {
this.x = x;
this.y = y;
}
function thing(gridx, gridy, type, text) {
this.opacity = 1.0;
this.isnew = true;
2016-08-18 14:14:48 +10:00
2016-08-18 16:18:35 +10:00
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;
2016-08-18 16:18:35 +10:00
}
this.type = type;
switch (this.type) {
case "cat":
this.color = "#b5dea8";
break;
case "food":
this.color = "#d8db03";
break;
case "llama":
this.color = "#cccccc";
break;
case "text":
if (this.name.indexOf("x") == 0) {
this.color = "#00dddd";
} else {
this.color = "#00cc00";
}
break;
default: // should never happen
this.color = getrandomcolour();
break;
2016-08-18 16:18:35 +10:00
}
if (this.type == "text") {
if (this.name.indexOf("x") == 0) {
this.size = TEXTSIZEMULTIPLIER;
} else {
this.size = TEXTSIZE;
}
} else {
this.size = THINGSIZE;
}
this.lifetime = 0;
2016-08-18 14:14:48 +10:00
this.gridx = gridx;
if (gridy == "top") {
var i;
2016-08-18 14:14:48 +10:00
highest = 999;
// find highest
for (i = 0; i < things.length; i += 1) {
if ((things[i].gridx == gridx) &&
2016-08-18 14:54:54 +10:00
(things[i].gridy <= highest)) {
highest = things[i].gridy-1;
2016-08-18 14:14:48 +10:00
}
}
gridy = highest;
//console.log("adding thing at gridy = " + gridy);
}
2016-08-18 14:54:54 +10:00
this.gridy = gridy;
2016-08-18 14:14:48 +10:00
this.yspeed = 0;
if (this.type == "text") {
this.state = "text";
} else {
this.state = "stop";
}
this.path = [];
this.pathspeed = PARADESPEED;
this.expcount = 0;
this.expmax = 0;
this.eaten = 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);
}
this.issleepy = function() {
if (this.type == "cat") {
if (this.eaten == true) {
return true;
} else if (isadjacenttotype(this, "sunlight")) {
return true;
}
}
return false;
}
this.canfall = function() {
// doors can't fall after their initial drop
if (this.type == "door") {
if ((this.gridy >= 0) && (this.new == false)) {
return false;
}
}
return true;
}
2016-08-18 14:14:48 +10:00
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") {
return false;
}
if (isadjacenttotype(this, "llama")) {
return false;
}
// chomp
if (isadjacenttotype(this, "food") && !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
for (i = 0; i < MAXDIRS; i++) {
var adj;
newx = this.gridx + DIRXMOD[i];
newy = this.gridy + DIRYMOD[i];
if (isonscreen(newx, newy)) {
adj = getgridthing(newx, newy);
if (adj != undefined) {
if (adj.type == "cat") {
// adjacent cat
if (isadjacenttotype(adj, "llama", this) ||
isadjacenttotype(adj, "cat", this) ||
isadjacenttotype(adj, "goat", this)) {
return true;
}
} else if (adj.type == "llama") {
// adjacent llama
if (isadjacenttotype(adj, "cat", this) ||
isadjacenttotype(adj, "goat", this)) {
return true;
}
}
}
}
}
return false;
}
this.givepoints = function() {
var points = 0;
if (this.type == "food") {
points = FOODPOINTS;
game.progress("food", 1);
} else if (this.type == "llama") {
points = LLAMAPOINTS;
game.progress("llamas", 1);
} else if (this.type == "cat") {
if (this.issleepy()) {
points = SLEEPYCATPOINTS;
} else {
points = CATPOINTS;
}
game.progress("cats", 1);
} else if (this.type == "goat") {
points = GOATPOINTS;
game.progress("goats", 1);
}
points *= multiplier;
if (points > 0) {
score += points;
game.progress("points", points);
// add animation
things.push(new thing(this.gridx, this.gridy, "text", "+" + points));
}
}
this.getdesc = function() {
var desc = "";
if (this.type == "cat") {
if (isadjacenttotype(this, "llama")) {
if (this.issleepy() == true) {
2016-08-20 11:34:25 +10:00
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;
}
2016-08-18 14:14:48 +10:00
this.draw = function() {
var yoff;
2016-08-18 14:54:54 +10:00
var inpath = false;
var howbig,myx,myy;
2016-08-18 14:54:54 +10:00
//ctx = game.context;
2016-08-18 14:14:48 +10:00
// check whether I'm in the drawn path
if (pathcontains(this)) {
inpath = true;
}
// set opacity
if (!wipe.isactive()) {
ctx.globalAlpha = this.opacity;
}
2016-08-18 14:54:54 +10:00
// 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 == "cat") {
var myimage;
if (this.state == "parade") {
myimage = image['cat'];
} else if (isadjacenttotype(this, "llama")) {
myimage = image['catscared'];
} else if (this.issleepy() == true) {
myimage = image['catfull'];
} else {
myimage = image['cat'];
}
} else if (this.type == "llama") {
myimage = image['llama'];
} else if (this.type == "food") {
myimage = image['cheese'];
} else if (this.type == "goat") {
myimage = image['goat'];
} else if (this.type == "door") {
myimage = image['door'];
} else if (this.type == "sunlight") {
myimage = image['sunlight'];
} else {
console.log("ERROR - no image for type " + this.type);
console.log(this);
}
howbig = this.size;
myx = this.x;
myy = this.y;
if (inpath) {
var growpct = 50;
howbig = howbig * ((100+growpct) / 100);
myx -= (growpct/2/100) * this.size;
myy -= (growpct/2/100) * this.size;
}
/* 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);
}
/*
} else {
ctx.fillStyle = this.color;
ctx.fillRect(BOARDX + this.x, BOARDY + this.y, this.size, this.size);
}
*/
2016-08-18 14:14:48 +10:00
// frozen cat?
/*
if ((this.type == "cat") && !this.isanimating()) {
if (isadjacenttotype(this, "llama")) {
// cross out
ctx.fillStyle = "red";
ctx.lineWidth = CROSSWIDTH;
ctx.strokeStyle = "red";
// NW -> SE
ctx.beginPath();
ctx.moveTo(BOARDX + this.x, BOARDY + this.y);
ctx.lineTo(BOARDX + this.x + this.size - 1, BOARDY + this.y + this.size - 1);
ctx.stroke();
// SW -> NE
ctx.beginPath();
ctx.moveTo(BOARDX + this.x, BOARDY + this.y + this.size - 1);
ctx.lineTo(BOARDX + this.x + this.size - 1, BOARDY + this.y);
ctx.stroke();
}
}
*/
// draw text on me
/*
if (this.type == "cat") {
} else if (this.type == "llama") {
} else {
ctx.fillStyle = "black";
ctx.fillText(this.name, BOARDX + this.x + 10, BOARDY + this.y + (THINGSIZE/2));
ctx.fillText(this.eaten ? "FULL" : "", BOARDX + this.x + 10, BOARDY + this.y + (THINGSIZE/2) + 10);
2016-08-18 14:54:54 +10:00
}
*/
2016-08-18 14:54:54 +10:00
// back to full opacity
if (!wipe.isactive()) {
ctx.globalAlpha = 1.0;
}
2016-08-18 14:54:54 +10:00
/*
// path outline
if (inpath && this.state != "parade" && this.state != "explode") {
2016-08-18 14:54:54 +10:00
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = "black";
// outline it
2016-08-18 16:18:35 +10:00
if (pathvalid) {
ctx.strokeStyle = "green";
} else {
ctx.strokeStyle = "red";
}
ctx.rect(BOARDX + this.x, BOARDY + this.y, this.size, this.size);
2016-08-18 14:54:54 +10:00
ctx.stroke();
}
*/
2016-08-18 14:54:54 +10:00
2016-08-18 14:14:48 +10:00
}
2016-08-18 16:18:35 +10:00
this.issametype = function(otherthing) {
if (otherthing == undefined) return false;
if (otherthing.type == this.type) return true;
return false;
}
// return TRUE if we did something
2016-08-18 14:14:48 +10:00
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.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;
2016-08-18 14:14:48 +10:00
}
if (this.x != snaptox) {
this.x = snaptox;
var moved = true;
}
return moved;
2016-08-18 14:14:48 +10:00
}
this.getstoppedbelowthing = function() {
var bt;
bt = getgridthing(this.gridx, this.gridy + 1);
if (bt && bt.state != "fall") {
return bt;
}
return null;
}
this.addabove = function() {
2016-08-18 14:14:48 +10:00
// add a new cat above us
2016-08-18 16:18:35 +10:00
things.push(new thing(this.gridx, "top", "random"));
}
2016-08-18 14:14:48 +10:00
this.kill = function() {
2016-08-18 14:14:48 +10:00
// kill ourselves
var idx = things.indexOf(this);
things.splice(idx, 1);
}
this.updatexy = function() {
this.x = this.gridx * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
this.y = this.gridy * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
}
this.setgridxy = function(x,y) {
this.gridx = x;
this.gridy = y;
this.updatexy();
}
this.chomp = function(food) {
var origgx,origgy;
// remember cat loc
origgx = this.gridx;
origgy = this.gridy;
// add new object above cat location
this.addabove();
// move cat to food location
this.setgridxy(food.gridx,food.gridy);
// move food to cat location (so new object will
// be added in the correct column.
//food.setgridxy(origgx, origgy);
// make food explode
//food.addabove();
//food.kill();
food.startexplode();
// mark that we've eaten something
this.eaten = true;
console.log("chomp");
}
this.isanimating = function() {
if (this.type == "text") return true;
if (this.state == "explode") return true;
if (this.state == "shrink") return true;
if (this.state == "parade") return true;
return false;
}
this.startexplode = function() {
this.expcount=1;
this.expmax=EXPLODETICKS;
this.state = "explode";
}
this.startshrink = function() {
this.expcount=1;
this.expmax=SHRINKTICKS;
this.state = "shrink";
}
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);
}
2016-08-18 14:14:48 +10:00
this.move = function() {
// not at bottom and nothing fixed below us?
var dofall = false;
var belowthing = null,atbottom = false;
2016-08-25 20:01:45 +10:00
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 -= EXPLODEFADESPEED;
2016-08-18 14:14:48 +10:00
if (this.opacity < 0) this.opacity = 0;
2016-08-18 14:14:48 +10:00
// adjust x/y
this.x = (this.gridx * GRIDSIZE) + (GRIDSIZE/2) - (this.size/2);
this.y = (this.gridy * GRIDSIZE) + (GRIDSIZE/2) - (this.size/2);
2016-08-18 14:14:48 +10:00
}
2016-08-25 20:01:45 +10:00
} 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") {
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);
}
} 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;
game.dirty = true; // need to redraw
2016-08-18 14:14:48 +10:00
// special case: door.
if ((nextx == -1) && (nexty == -1)) {
this.path = [];
this.startshrink();
} else {
if (this.x < nextx) {
this.x += this.pathspeed;
if (this.x > nextx) xdone = true;
} else if (this.x > nextx) {
this.x -= this.pathspeed;
if (this.x < nextx) xdone = true;
} else {
xdone = true;
}
if (this.y < nexty) {
this.y += this.pathspeed;
if (this.y > nexty) ydone = true;
} else if (this.y > nexty) {
this.y -= this.pathspeed;
if (this.y < nexty) ydone = true;
} else {
ydone = true;
}
2016-08-18 14:14:48 +10:00
this.calcgridxy();
2016-08-18 14:14:48 +10:00
// at destination?
if (xdone && ydone) {
this.poppath();
// path finished?
if (this.path == undefined || this.path.length == 0) {
if (this.state == "catparade" ){
this.kill();
} else {
this.state = "stop";
}
}
}
}
} else {
// regular gravity
if ((this.gridy >= GRIDH-1)) {
atbottom = true;
}
if (!atbottom && !this.getstoppedbelowthing() && this.canfall()) {
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;
2016-08-18 14:14:48 +10:00
// snap to grid
if (this.snaptogrid()) {
game.dirty = true; // need to redraw
}
this.state = "stop";
}
}
2016-08-18 14:14:48 +10:00
}
}
function mainloop() {
2016-08-18 14:14:48 +10:00
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
var i;
2016-08-18 14:14:48 +10:00
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 == "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();
}
gridalpha = wipe.getval(1.0, "up", "down", 1.0);
ctx.globalAlpha = gridalpha;
if (game.dirty || wipe.isactive()) {
// clear
game.clear();
// draw grid
game.drawgrid();
// draw non-animating objects
for (i = 0; i < things.length; i += 1) {
if (!things[i].isanimating()) {
things[i].draw();
}
}
// draw animating objects
for (i = 0; i < things.length; i += 1) {
if (things[i].isanimating()) {
things[i].draw();
}
}
// draw top of canvas (score etc)
game.drawtop();
// draw bottom of canvas (goals)
game.drawbottom();
if ((game.state == "levelcomplete") || (game.state == "gameover")) {
// draw buttons
game.drawcontinuebutton();
}
// draw dragged arrow
game.drawpath();
2016-08-25 20:01:45 +10:00
// 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 (levelfinished()) {
// record score
if (score > playerdata.levscore[curlevel]) {
playerdata.setlevscore(curlevel, score);
}
game.setstate("levelcomplete");
} else if (!anyvalidmoves()) {
game.setstate("gameover");
}
// mark things as not new
for (i = 0; i < things.length; i += 1) {
if (things[i].gridy >= 0) things[i].isnew = false;
}
// suns move down once
for (i = 0; i < things.length; i += 1) {
if (things[i].type == "sunlight" && things[i].gridy >= 0 && !things[i].isnew &&
things[i].state != "swapping" && things[i].counter == 0) {
if ((things[i].gridy >= GRIDH-1)) {
// at bottom - disappear
things[i].addabove();
things[i].startexplode();
game.progress("sun cycles", 1);
} else {
var thingbelow;
// move down
things[i].pushpath(things[i].x, things[i].y + GRIDSIZE);
things[i].state = "swapping";
things[i].counter = 1;
thingbelow = getgridthing(things[i].gridx, things[i].gridy+1);
if (thingbelow != undefined && thingbelow.state != "swapping" &&
thingbelow.type != "sunlight") {
thingbelow.pushpath(things[i].x, things[i].y);
thingbelow.state = "swapping";
}
}
}
}
}
}
}
2016-08-18 14:14:48 +10:00
}
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;
}
2016-08-18 14:14:48 +10:00
//<button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)">ACCELERATE</button>
</script>
<br>
</body>
</html>