*cat eating food should take its place

*parade must be at least 4
*cat can only eat food once, but can eat multiple foods in one go
*dont let chomp lines change dir (but parades can)
*allow reversing a line
*draw arrow for lines
*llama
        *cats next to it can't move
        *cat parades can include one (but only one).
*draw cats in centre of grid squares, not top left
This commit is contained in:
Rob Pearce 2016-08-19 17:46:39 +10:00
parent a73ff6ce6d
commit 12f79e8b45
3 changed files with 498 additions and 61 deletions

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
*.app
*.zip
.bash_history
.svn
*.dSYM
*.swp
*.sw*
.DS_Store
scripts/level*.png
*~
# Local work dirs
staging/
orig/
# test files
orig.html

442
cat.html
View File

@ -17,6 +17,7 @@ var things = [];
var myScore; var myScore;
var curpath = []; var curpath = [];
var pathdir = -1;
var pathvalid = false; var pathvalid = false;
var MAXDIRS = 4; var MAXDIRS = 4;
@ -27,11 +28,18 @@ var GRAVITY = 0.5;
var GRIDSIZE = 80; var GRIDSIZE = 80;
var THINGSIZE = 64; var THINGSIZE = 64;
var LINEWIDTH=2;
var CROSSWIDTH=2;
var GRIDW = 5; var GRIDW = 5;
var GRIDH = 5; var GRIDH = 5;
var PARADELENGTH = 3;
function getgridthing(gridx, gridy) { function getgridthing(gridx, gridy) {
var i; var i;
if (things == undefined) return null;
for (i = 0; i < things.length; i += 1) { for (i = 0; i < things.length; i += 1) {
if ((things[i].gridx == gridx) && (things[i].gridy == gridy)) { if ((things[i].gridx == gridx) && (things[i].gridy == gridy)) {
return things[i]; return things[i];
@ -55,17 +63,70 @@ function getthingxy(x, y) {
function clearpath() { function clearpath() {
curpath = []; curpath = [];
pathdir = -1;
pathvalid = false; pathvalid = false;
} }
function pathcomplete() {
// get path type
switch(getpathtype()) {
case "chomp":
if ((curpath.length >= 2) &&
(curpath[0].type == "cat") &&
(curpath[0].eaten == false)) {
var i;
var ok = true;
// everything else is food?
for (i = 1; i < curpath.length; i++) {
if (curpath[i].type != "food") {
ok = false;
break;
}
}
if (ok) {
return true;
}
}
break;
case "parade":
// long enough?
if (curpath.length >= PARADELENGTH) {
var count = 0;
var i;
// includes <= 1 llama?
for (i = 1; i < curpath.length; i++) {
if (curpath[i].type == "llama") {
count++;
}
}
if (count <= 1) {
return true;
}
}
break;
}
return false;
}
function backspacepath() {
// remove last thing in path
curpath.splice(curpath.length-1, 1);
validatepath();
dumppath("Reduced path to: ",curpath);
}
function addtopath(what) { function addtopath(what) {
console.log("addpath() " + what.name); console.log("addpath() " + what.name);
dumppath("addpath pre: ", curpath); //dumppath("addpath pre: ", curpath);
curpath.push(what); curpath.push(what);
pathvalid = true; validatepath();
if (curpath.length == 1) { if (curpath.length == 1) {
console.log("Starting path with " + what.name); console.log("Starting path with " + what.name);
} else { } else {
@ -73,6 +134,18 @@ function addtopath(what) {
} }
} }
function validatepath() {
if (pathcomplete()) {
pathvalid = true;
} else {
pathvalid = false;
}
if (curpath.length == 2) {
pathdir = getdir(curpath[0], curpath[1]);
}
}
function pathcontains(what) { function pathcontains(what) {
var i; var i;
if (curpath == undefined) return false; if (curpath == undefined) return false;
@ -87,6 +160,10 @@ function pathcontains(what) {
function isvalidpath(mypath) { function isvalidpath(mypath) {
var valid = true; var valid = true;
var firstcat = false;
var fcount = false;
var lcount = 0;
var i;
for (i = 0; i < mypath.length - 1; i++) { for (i = 0; i < mypath.length - 1; i++) {
var thisone,nextone; var thisone,nextone;
@ -100,18 +177,36 @@ function isvalidpath(mypath) {
*/ */
nextone = mypath[i+1]; nextone = mypath[i+1];
if (thisone.type == "food") {
fcount++;
}
if (thisone.type == "llama") {
lcount++;
}
if ((thisone.type == "cat") && nextone.type == "cat") { if ((thisone.type == "cat") && nextone.type == "cat") {
// lines of cats are ok // lines of cats are ok
} else if ((thisone.type == "cat") && nextone.type == "llama") {
// cat -> llama is only okay if there are no other llamas or food in the path
if (lcount >= 1) {
return false;
}
if (fcount >= 1) {
return false;
}
} else if ((thisone.type == "llama") && nextone.type == "cat") {
// ok
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "food")) { } else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "food")) {
// first cat -> food is ok // first cat -> food is ok
firstcat = true;
} else if ((i != 0) && firstcat && (thisone.type == "food")) {
// not the first one, first one was a cat, this one is food
} else { } else {
// not ok // not ok
valid = false; valid = false;
break; break;
} }
} }
//var pr = "isvalidpath()? " + valid + " ";
//dumppath(pr, mypath);
return valid; return valid;
} }
@ -119,9 +214,10 @@ function isvalidpath(mypath) {
function canextendpath(overthing) { function canextendpath(overthing) {
if (!overthing) return false; if (!overthing) return false;
if (overthing && if ( isadjacent(overthing, curpath[curpath.length-1]) && // adjacent to last thing in path?
isadjacent(overthing, curpath[curpath.length-1]) && // adjacent to last thing in path? !pathcontains(overthing) && // path doesn't already contain this?
!pathcontains(overthing) ) { // path doesn't already contain this? (getpathtype() == "parade" || isinpathdir(overthing)) // path in a straight line
) {
// create a fake new path containing this. // create a fake new path containing this.
var fakepath = curpath.slice(); var fakepath = curpath.slice();
fakepath.push(overthing); fakepath.push(overthing);
@ -136,6 +232,7 @@ function canextendpath(overthing) {
function isadjacent(thing1, thing2) { function isadjacent(thing1, thing2) {
// is thing1 adjacent to thing2? // is thing1 adjacent to thing2?
var newgridx,newgridy; var newgridx,newgridy;
var i;
if (thing1 == thing2) return false; if (thing1 == thing2) return false;
@ -149,19 +246,57 @@ function isadjacent(thing1, thing2) {
return false; return false;
} }
function startGame() { function isinpathdir(what) {
var x,y; var thisdir;
for (y = 0; y < GRIDH; y++) { if (curpath.length <= 1) {
for (x = 0; x < GRIDW; x++) { return true;
things.push(new thing(x, y, "random"));
}
} }
myGameArea.start(); // get dir from last cell to what
thisdir = getdir(curpath[curpath.length - 1], what);
if ((thisdir != -1) && (thisdir == pathdir)) {
return true;
}
return false;
}
function getdir(thing1, thing2) {
var i;
for (i = 0; i < MAXDIRS; i++) {
var newgx = thing1.gridx + DIRXMOD[i];
var newgy = thing1.gridy + DIRYMOD[i];
if ((thing2.gridx == newgx) && (thing2.gridy == newgy)) {
return i;
}
}
return -1;
}
function startGame() {
game.start();
}
// valid types:
// none
// chomp
// parade
function getpathtype() {
if (curpath.length <= 1) return "none"
if (curpath[0].type == "cat") {
if (curpath[1].type == "food") {
return "chomp";
} else if ((curpath[1].type == "cat") || (curpath[1].type == "llama")) {
return "parade";
}
}
return "none";
} }
function dumppath(prefix,arr) { function dumppath(prefix,arr) {
var str; var str;
var i;
str = ""; str = "";
for (i = 0; i < arr.length; i++) { for (i = 0; i < arr.length; i++) {
str = str + " " + arr[i].name; str = str + " " + arr[i].name;
@ -170,6 +305,7 @@ function dumppath(prefix,arr) {
} }
function thingsmoving() { function thingsmoving() {
var i;
for (i = 0; i < things.length; i += 1) { for (i = 0; i < things.length; i += 1) {
if (things[i].state != "stop") { if (things[i].state != "stop") {
return true; return true;
@ -178,9 +314,77 @@ function thingsmoving() {
return false; return false;
} }
var myGameArea = { 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 isadjacenttotype(what, wanttype) {
var newgridx,newgridy;
var i;
for (i = 0; i < MAXDIRS; i++) {
newgridx = what.gridx + DIRXMOD[i];
newgridy = what.gridy + DIRYMOD[i];
if (isonscreen(newgridx,newgridy)) {
adjthing = getgridthing(newgridx, newgridy);
if ((adjthing != undefined) && (adjthing.type == wanttype)) {
return true;
}
}
}
return false;
}
function drawarrowhead(ctx, x1, y1, x2, y2, col) {
var startrads,endrads;
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(10,15);
ctx.lineTo(-10,15);
ctx.closePath();
ctx.restore();
ctx.fill();
}
var game = {
canvas : document.createElement("canvas"), canvas : document.createElement("canvas"),
start : function() { start : function() {
var x,y;
this.canvas.width = 480; this.canvas.width = 480;
this.canvas.height = 640; this.canvas.height = 640;
this.context = this.canvas.getContext("2d"); this.context = this.canvas.getContext("2d");
@ -192,6 +396,14 @@ var myGameArea = {
this.canvas.addEventListener('mousedown', this.handlemousedown, false); this.canvas.addEventListener('mousedown', this.handlemousedown, false);
this.canvas.addEventListener('mouseup', this.handlemouseup, false); this.canvas.addEventListener('mouseup', this.handlemouseup, false);
this.canvas.addEventListener('mousemove', this.handlemousemove, false); this.canvas.addEventListener('mousemove', this.handlemousemove, false);
// populate inital things
for (y = 0; y < GRIDH; y++) {
for (x = 0; x < GRIDW; x++) {
// start off above the grid
things.push(new thing(x, y - GRIDH, "random"));
}
}
}, },
clear : function() { clear : function() {
@ -201,6 +413,7 @@ var myGameArea = {
draw : function() { draw : function() {
this.context.strokeStyle = "black"; this.context.strokeStyle = "black";
this.context.beginPath(); this.context.beginPath();
// draw grid
for (y = 0; y < GRIDH*GRIDSIZE; y += GRIDSIZE) { for (y = 0; y < GRIDH*GRIDSIZE; y += GRIDSIZE) {
for (x = 0; x < GRIDW*GRIDSIZE; x += GRIDSIZE) { for (x = 0; x < GRIDW*GRIDSIZE; x += GRIDSIZE) {
this.context.rect(x, y, GRIDSIZE-1, GRIDSIZE-1); this.context.rect(x, y, GRIDSIZE-1, GRIDSIZE-1);
@ -210,6 +423,43 @@ var myGameArea = {
this.context.stroke(); this.context.stroke();
}, },
drawpath : function() {
var ctx = this.context;
var col;
var i;
if ((curpath == undefined) || (curpath.length < 1)) {
return;
}
if (pathvalid) {
col = "green";
} else {
col = "red";
}
ctx.strokeStyle = col;
ctx.lineWidth = LINEWIDTH;
// draw line
ctx.beginPath();
ctx.moveTo(curpath[0].x + THINGSIZE/2, curpath[0].y + THINGSIZE/2);
for (i = 1; i < curpath.length; i++) {
ctx.lineTo(curpath[i].x + THINGSIZE/2, 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, x1, y1, x2, y2, col);
}
},
/* /*
handleclick : function(event) { handleclick : function(event) {
// make sure nothing is moving // make sure nothing is moving
@ -232,7 +482,7 @@ var myGameArea = {
// did you click on an object? // did you click on an object?
var onthing = getthingxy(event.pageX,event.pageY); var onthing = getthingxy(event.pageX,event.pageY);
if (onthing) { if (onthing && canstartpath(onthing)) {
console.log("Initial click on " + onthing.name); console.log("Initial click on " + onthing.name);
addtopath(onthing); addtopath(onthing);
} }
@ -245,17 +495,25 @@ console.log("Initial click on " + onthing.name);
// existing path? // existing path?
if (curpath != undefined && curpath.length > 0) { if (curpath != undefined && curpath.length > 0) {
var overthing = getthingxy(event.pageX,event.pageY); var overthing = getthingxy(event.pageX,event.pageY);
var lastinpath; var lastinpath,secondlast;
if (curpath == undefined) { if (curpath == undefined) {
lastinpath = null; lastinpath = null;
} else { } else {
lastinpath = curpath[curpath.length-1]; lastinpath = curpath[curpath.length-1];
if (curpath.length >= 2) {
secondlast = curpath[curpath.length-2];
} else {
secondlast = null;
}
} }
if (canextendpath(overthing)) { if ((secondlast != undefined) && (overthing == secondlast)) {
backspacepath();
} else if (canextendpath(overthing)) {
// add it to the path // add it to the path
addtopath(overthing); addtopath(overthing);
} else if (pathcontains(overthing)) { } else if (pathcontains(overthing) && pathcomplete()) {
pathvalid = true; pathvalid = true;
} else { } else {
pathvalid = false; pathvalid = false;
@ -266,12 +524,20 @@ console.log("Initial click on " + onthing.name);
handlemouseup : function(event) { handlemouseup : function(event) {
var ok = true; var ok = true;
var overthing = getthingxy(event.pageX,event.pageY); var overthing = getthingxy(event.pageX,event.pageY);
if (thingsmoving()) { var ptype = getpathtype();
if ((curpath == undefined) || (curpath.length == 0)) {
console.log("mouseup() - no path");
ok = false;
} else if (thingsmoving()) {
console.log("mouseup() - things are moving"); console.log("mouseup() - things are moving");
ok = false; ok = false;
} else if (!isvalidpath(curpath)) { } else if (!isvalidpath(curpath)) {
console.log("mouseup() - path isn't valid"); console.log("mouseup() - path isn't valid");
ok = false; ok = false;
} else if (!pathcomplete(curpath)) {
console.log("mouseup() - path ("+ ptype + ") isn't complete");
ok = false;
} else if ((overthing == undefined) || !pathcontains(overthing)) { } else if ((overthing == undefined) || !pathcontains(overthing)) {
console.log("mouseup() - not over something in path"); console.log("mouseup() - not over something in path");
ok = false; ok = false;
@ -282,11 +548,29 @@ console.log("Initial click on " + onthing.name);
return; return;
} }
// figure out path type
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].kill();
}
}
// first one chomps last one
curpath[0].chomp(curpath[curpath.length - 1]);
break;
case "parade":
// kill all in path // kill all in path
while (curpath != undefined && curpath.length > 0) { while (curpath != undefined && curpath.length > 0) {
curpath[0].kill(); curpath[0].kill();
curpath.splice(0, 1); curpath.splice(0, 1);
} }
break;
}
clearpath(); clearpath();
}, },
} }
@ -310,13 +594,22 @@ function getrandomname() {
return name; return name;
} }
function isonscreen(x,y) {
if ((x < 0) || (y < 0) || (x >= GRIDW) || (y >= GRIDH)) {
return false;
}
return true;
}
function getrandomtype() { function getrandomtype() {
var pct,type; var pct,type;
pct = Math.random() * 100; pct = Math.random() * 100;
if ( pct < 50) { if ( pct < 45) {
type = "cat"; type = "cat";
} else { } else if ( pct < 90) {
type = "food"; type = "food";
} else {
type = "llama";
} }
return type; return type;
} }
@ -330,18 +623,26 @@ function thing(gridx, gridy, type) {
} }
this.type = type; this.type = type;
if (this.type == "cat") { switch (this.type) {
case "cat":
this.color = "#b5dea8"; this.color = "#b5dea8";
} else if (type == "food") { break;
case "food":
this.color = "#d8db03"; this.color = "#d8db03";
} else { break;
case "llama":
this.color = "#cccccc";
break;
default: // should never happen
this.color = getrandomcolour(); this.color = getrandomcolour();
break;
} }
this.name = type + "-" + getrandomname(); this.name = type + "-" + getrandomname();
this.gridx = gridx; this.gridx = gridx;
if (gridy == "top") { if (gridy == "top") {
var i;
highest = 999; highest = 999;
// find highest // find highest
for (i = 0; i < things.length; i += 1) { for (i = 0; i < things.length; i += 1) {
@ -356,33 +657,58 @@ function thing(gridx, gridy, type) {
this.gridy = gridy; this.gridy = gridy;
this.yspeed = 0; this.yspeed = 0;
this.state = "stop"; this.state = "stop";
this.x = gridx * GRIDSIZE;
this.y = gridy * GRIDSIZE; this.x = gridx * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
this.y = gridy * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
this.eaten = false;
this.draw = function() { this.draw = function() {
var yoff; var yoff;
var inpath = false; var inpath = false;
ctx = myGameArea.context; ctx = game.context;
ctx.fillStyle = this.color;
// draw myself // draw myself
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height); ctx.fillRect(this.x, this.y, this.width, this.height);
// frozen cat?
if (this.type == "cat") {
if (isadjacenttotype(this, "llama")) {
// cross out
ctx.fillStyle = "red";
ctx.lineWidth = CROSSWIDTH;
ctx.strokeStyle = "red";
// NW -> SE
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + THINGSIZE - 1, this.y + THINGSIZE - 1);
ctx.stroke();
// SW -> NE
ctx.beginPath();
ctx.moveTo(this.x, this.y + THINGSIZE - 1);
ctx.lineTo(this.x + THINGSIZE - 1, this.y);
ctx.stroke();
}
}
// highlight? // highlight?
if (pathcontains(this)) { if (pathcontains(this)) {
inpath = true; inpath = true;
} }
// draw text // draw text
ctx.fillStyle = "black"; ctx.fillStyle = "black";
ctx.fillText(this.name, this.x + 10, this.y + (THINGSIZE/2)); ctx.fillText(this.name, this.x + 10, this.y + (THINGSIZE/2));
ctx.fillText(this.state, this.x + 10, this.y + (THINGSIZE/2) + 10); ctx.fillText(this.eaten ? "FULL" : "", this.x + 10, this.y + (THINGSIZE/2) + 10);
// path outline
if (inpath) { if (inpath) {
ctx.beginPath(); ctx.beginPath();
ctx.fillText("PATH", this.x + 10, this.y + (THINGSIZE/2) + 20); ctx.lineWidth = 1;
ctx.fillStyle = "black";
// outline it
if (pathvalid) { if (pathvalid) {
ctx.strokeStyle = "green"; ctx.strokeStyle = "green";
} else { } else {
@ -401,8 +727,8 @@ function thing(gridx, gridy, type) {
} }
this.snaptogrid = function() { this.snaptogrid = function() {
if (this.y % GRIDSIZE != 0) { if (this.y % (GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2)) != 0) {
this.y = this.gridy * GRIDSIZE; this.y = this.gridy * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
} }
} }
@ -425,6 +751,39 @@ function thing(gridx, gridy, type) {
things.splice(idx, 1); 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;
// 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);
// kill food
food.kill();
// mark that we've eaten something
this.eaten = true;
console.log("chomp");
}
this.move = function() { this.move = function() {
// not at bottom and nothing fixed below us? // not at bottom and nothing fixed below us?
var dofall = false; var dofall = false;
@ -475,22 +834,25 @@ function thing(gridx, gridy, type) {
function updateGameArea() { function updateGameArea() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap; var x, height, gap, minHeight, maxHeight, minGap, maxGap;
var i;
myGameArea.clear(); game.clear();
myGameArea.draw(); game.draw();
myGameArea.frameNo += 1; game.frameNo += 1;
for (i = 0; i < things.length; i += 1) { for (i = 0; i < things.length; i += 1) {
things[i].draw();
things[i].move(); things[i].move();
things[i].draw();
} }
//myScore.text="SCORE: " + myGameArea.frameNo; game.drawpath();
//myScore.text="SCORE: " + game.frameNo;
//myScore.draw(); //myScore.draw();
//myGamePiece.move(); //myGamePiece.move();
//myGamePiece.update(); //myGamePiece.update();
} }
function everyinterval(n) { function everyinterval(n) {
if ((myGameArea.frameNo / n) % 1 == 0) {return true;} if ((game.frameNo / n) % 1 == 0) {return true;}
return false; return false;
} }

60
todo
View File

@ -77,8 +77,64 @@ start:
*click one, rest fall down and a new one is generated *click one, rest fall down and a new one is generated
*drag across multi, all disappear, rest fall, new are generated *drag across multi, all disappear, rest fall, new are generated
*don't allow clicks until all grid locations are full *don't allow clicks until all grid locations are full
*allow cat->food or cat->...->cat *allow cat->food or cat->...->cat
cat eating food should take its place *cat eating food should take its place
*parade must be at least 4
*cat can only eat food once, but can eat multiple foods in one go
*dont let chomp lines change dir (but parades can)
*allow reversing a line
*draw arrow for lines
*llama
*cats next to it can't move
*cat parades can include one (but only one).
*draw cats in centre of grid squares, not top left
offset entire grid downwards to allow space for a header
add pictures of:
cat
fat cat
scared cat
llama
food
make path part of game object
make things part of game object
ideas:
llamas don't appear after the start, you just have to get rid of them all?
ensure that start of game has at least one valid move!
show score up the top
Score: (+20) 100
points for chomp
points for parade
points for each empty cat
points for each full cat
extra points for llama
length 3 = 1x
length 4 = 2x
length 5 = 3x
multiplier for length
animated points gaining
text fading out
animate parades
animate chomps
end if no valid moves
after each move:
are all cats locked? if so, game over.
for each cell,
for each dir,
check paths for all lengths
if none found, game over