catparade/cat.html

867 lines
17 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
canvas {
border:1px solid #d3d3d3;
background-color: #f1f1f1;
}
</style>
</head>
<body onload="startGame()">
<script>
var myGamePiece;
var things = [];
var myScore;
var curpath = [];
var pathdir = -1;
var pathvalid = false;
var MAXDIRS = 4;
var DIRXMOD = [ 0, 1, 0, -1 ];
var DIRYMOD = [ -1, 0, 1, 0 ];
var GRAVITY = 0.5;
var GRIDSIZE = 80;
var THINGSIZE = 64;
var LINEWIDTH=2;
var CROSSWIDTH=2;
var GRIDW = 5;
var GRIDH = 5;
var PARADELENGTH = 3;
function getgridthing(gridx, gridy) {
var i;
if (things == undefined) return null;
for (i = 0; i < things.length; i += 1) {
if ((things[i].gridx == gridx) && (things[i].gridy == gridy)) {
return things[i];
}
}
return null;
}
function getthingxy(x, y) {
var i;
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];
}
}
return null;
}
function clearpath() {
curpath = [];
pathdir = -1;
pathvalid = false;
}
function pathcomplete() {
// get path type
switch(getpathtype()) {
case "chomp":
if ((curpath.length >= 2) &&
(curpath[0].type == "cat") &&
(curpath[0].eaten == false)) {
var i;
var ok = true;
// everything else is food?
for (i = 1; i < curpath.length; i++) {
if (curpath[i].type != "food") {
ok = false;
break;
}
}
if (ok) {
return true;
}
}
break;
case "parade":
// long enough?
if (curpath.length >= PARADELENGTH) {
var count = 0;
var i;
// includes <= 1 llama?
for (i = 1; i < curpath.length; i++) {
if (curpath[i].type == "llama") {
count++;
}
}
if (count <= 1) {
return true;
}
}
break;
}
return false;
}
function backspacepath() {
// remove last thing in path
curpath.splice(curpath.length-1, 1);
validatepath();
dumppath("Reduced path to: ",curpath);
}
function addtopath(what) {
console.log("addpath() " + what.name);
//dumppath("addpath pre: ", curpath);
curpath.push(what);
validatepath();
if (curpath.length == 1) {
console.log("Starting path with " + what.name);
} else {
dumppath("Cur path is: ",curpath);
}
}
function validatepath() {
if (pathcomplete()) {
pathvalid = true;
} else {
pathvalid = false;
}
if (curpath.length == 2) {
pathdir = getdir(curpath[0], curpath[1]);
}
}
function pathcontains(what) {
var i;
if (curpath == undefined) return false;
for (i = 0; i < curpath.length; i += 1) {
if (curpath[i] == what) {
return true;
}
}
return false;
}
function isvalidpath(mypath) {
var valid = true;
var firstcat = false;
var fcount = false;
var lcount = 0;
var i;
for (i = 0; i < mypath.length - 1; i++) {
var thisone,nextone;
thisone = mypath[i];
/*
if (i == mypath.length - 1) { // last one
nextone = null;
} else {
nextone = mypath[i+1];
}
*/
nextone = mypath[i+1];
if (thisone.type == "food") {
fcount++;
}
if (thisone.type == "llama") {
lcount++;
}
if ((thisone.type == "cat") && nextone.type == "cat") {
// lines of cats are ok
} else if ((thisone.type == "cat") && nextone.type == "llama") {
// cat -> llama is only okay if there are no other llamas or food in the path
if (lcount >= 1) {
return false;
}
if (fcount >= 1) {
return false;
}
} else if ((thisone.type == "llama") && nextone.type == "cat") {
// ok
} else if ((i == 0) && (thisone.type == "cat") && (nextone.type == "food")) {
// first cat -> food is ok
firstcat = true;
} else if ((i != 0) && firstcat && (thisone.type == "food")) {
// not the first one, first one was a cat, this one is food
} else {
// not ok
valid = false;
break;
}
}
return valid;
}
// would adding 'overthing' to our existing path result in a valid path?
function canextendpath(overthing) {
if (!overthing) return false;
if ( isadjacent(overthing, curpath[curpath.length-1]) && // adjacent to last thing in path?
!pathcontains(overthing) && // path doesn't already contain this?
(getpathtype() == "parade" || isinpathdir(overthing)) // path in a straight line
) {
// create a fake new path containing this.
var fakepath = curpath.slice();
fakepath.push(overthing);
if (isvalidpath(fakepath)) {
return true;
}
}
return false;
}
function isadjacent(thing1, thing2) {
// is thing1 adjacent to thing2?
var newgridx,newgridy;
var i;
if (thing1 == thing2) return false;
for (i = 0; i < MAXDIRS; i++) {
newgridx = thing1.gridx + DIRXMOD[i];
newgridy = thing1.gridy + DIRYMOD[i];
if ((thing2.gridx == newgridx) && (thing2.gridy == newgridy)) {
return true;
}
}
return false;
}
function isinpathdir(what) {
var thisdir;
if (curpath.length <= 1) {
return true;
}
// get dir from last cell to what
thisdir = getdir(curpath[curpath.length - 1], what);
if ((thisdir != -1) && (thisdir == pathdir)) {
return true;
}
return false;
}
function getdir(thing1, thing2) {
var i;
for (i = 0; i < MAXDIRS; i++) {
var newgx = thing1.gridx + DIRXMOD[i];
var newgy = thing1.gridy + DIRYMOD[i];
if ((thing2.gridx == newgx) && (thing2.gridy == newgy)) {
return i;
}
}
return -1;
}
function startGame() {
game.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) {
var str;
var i;
str = "";
for (i = 0; i < arr.length; i++) {
str = str + " " + arr[i].name;
}
console.log(prefix + str);
}
function thingsmoving() {
var i;
for (i = 0; i < things.length; i += 1) {
if (things[i].state != "stop") {
return true;
}
}
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 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"),
start : function() {
var x,y;
this.canvas.width = 480;
this.canvas.height = 640;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
this.canvas.addEventListener('click', this.handleclick, false);
this.canvas.addEventListener('mousedown', this.handlemousedown, false);
this.canvas.addEventListener('mouseup', this.handlemouseup, 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() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
draw : function() {
this.context.strokeStyle = "black";
this.context.beginPath();
// draw grid
for (y = 0; y < GRIDH*GRIDSIZE; y += GRIDSIZE) {
for (x = 0; x < GRIDW*GRIDSIZE; x += GRIDSIZE) {
this.context.rect(x, y, GRIDSIZE-1, GRIDSIZE-1);
}
}
this.context.stroke();
},
drawpath : function() {
var ctx = this.context;
var col;
var i;
if ((curpath == undefined) || (curpath.length < 1)) {
return;
}
if (pathvalid) {
col = "green";
} else {
col = "red";
}
ctx.strokeStyle = col;
ctx.lineWidth = LINEWIDTH;
// draw line
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) {
// make sure nothing is moving
if (thingsmoving()) return;
// did you click on an object?
var clickedthing = getthingxy(event.pageX,event.pageY);
if (clickedthing) {
clickedthing.kill();
}
},
*/
handlemousedown : function(event) {
// make sure nothing is moving
if (thingsmoving()) return;
// clear existing path
clearpath();
// did you click on an object?
var onthing = getthingxy(event.pageX,event.pageY);
if (onthing && canstartpath(onthing)) {
console.log("Initial click on " + onthing.name);
addtopath(onthing);
}
},
handlemousemove : function(event) {
// make sure nothing is moving
if (thingsmoving()) return;
// existing path?
if (curpath != undefined && curpath.length > 0) {
var overthing = getthingxy(event.pageX,event.pageY);
var lastinpath,secondlast;
if (curpath == undefined) {
lastinpath = null;
} else {
lastinpath = curpath[curpath.length-1];
if (curpath.length >= 2) {
secondlast = curpath[curpath.length-2];
} else {
secondlast = null;
}
}
if ((secondlast != undefined) && (overthing == secondlast)) {
backspacepath();
} else if (canextendpath(overthing)) {
// add it to the path
addtopath(overthing);
} else if (pathcontains(overthing) && pathcomplete()) {
pathvalid = true;
} else {
pathvalid = false;
}
}
},
handlemouseup : function(event) {
var ok = true;
var overthing = getthingxy(event.pageX,event.pageY);
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");
ok = false;
} else if (!isvalidpath(curpath)) {
console.log("mouseup() - path isn't valid");
ok = false;
} else if (!pathcomplete(curpath)) {
console.log("mouseup() - path ("+ ptype + ") isn't complete");
ok = false;
} else if ((overthing == undefined) || !pathcontains(overthing)) {
console.log("mouseup() - not over something in path");
ok = false;
}
if (!ok) {
clearpath();
return;
}
// figure out path type
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
while (curpath != undefined && curpath.length > 0) {
curpath[0].kill();
curpath.splice(0, 1);
}
break;
}
clearpath();
},
}
function getrandomcolour() {
var letters = '0123456789ABCDEF';
var col = '#';
for (var i = 0; i < 6; i++ ) {
col += letters[Math.floor(Math.random() * 16)];
}
return col;
}
function getrandomname() {
var letters = '0123456789';
var name = "";
for (var i = 0; i < 3; i++ ) {
name += letters[Math.floor(Math.random() * 10)];
}
return name;
}
function isonscreen(x,y) {
if ((x < 0) || (y < 0) || (x >= GRIDW) || (y >= GRIDH)) {
return false;
}
return true;
}
function getrandomtype() {
var pct,type;
pct = Math.random() * 100;
if ( pct < 45) {
type = "cat";
} else if ( pct < 90) {
type = "food";
} else {
type = "llama";
}
return type;
}
function thing(gridx, gridy, type) {
this.width = THINGSIZE;
this.height = THINGSIZE;
if (type == "random") {
type = getrandomtype();
}
this.type = type;
switch (this.type) {
case "cat":
this.color = "#b5dea8";
break;
case "food":
this.color = "#d8db03";
break;
case "llama":
this.color = "#cccccc";
break;
default: // should never happen
this.color = getrandomcolour();
break;
}
this.name = type + "-" + getrandomname();
this.gridx = gridx;
if (gridy == "top") {
var i;
highest = 999;
// find highest
for (i = 0; i < things.length; i += 1) {
if ((things[i].gridx == gridx) &&
(things[i].gridy <= highest)) {
highest = things[i].gridy-1;
}
}
gridy = highest;
//console.log("adding thing at gridy = " + gridy);
}
this.gridy = gridy;
this.yspeed = 0;
this.state = "stop";
this.x = gridx * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
this.y = gridy * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
this.eaten = false;
this.draw = function() {
var yoff;
var inpath = false;
ctx = game.context;
// draw myself
ctx.fillStyle = this.color;
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?
if (pathcontains(this)) {
inpath = true;
}
// draw text
ctx.fillStyle = "black";
ctx.fillText(this.name, this.x + 10, this.y + (THINGSIZE/2));
ctx.fillText(this.eaten ? "FULL" : "", this.x + 10, this.y + (THINGSIZE/2) + 10);
// path outline
if (inpath) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = "black";
// outline it
if (pathvalid) {
ctx.strokeStyle = "green";
} else {
ctx.strokeStyle = "red";
}
ctx.rect(this.x, this.y, this.width, this.height);
ctx.stroke();
}
}
this.issametype = function(otherthing) {
if (otherthing == undefined) return false;
if (otherthing.type == this.type) return true;
return false;
}
this.snaptogrid = function() {
if (this.y % (GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2)) != 0) {
this.y = this.gridy * GRIDSIZE + (GRIDSIZE/2) - (THINGSIZE/2);
}
}
this.getstoppedbelowthing = function() {
var bt;
bt = getgridthing(this.gridx, this.gridy + 1);
if (bt && bt.state != "fall") {
return bt;
}
return null;
}
this.kill = function() {
// add a new cat above us
things.push(new thing(this.gridx, "top", "random"));
// 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;
// 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() {
// not at bottom and nothing fixed below us?
var dofall = false;
var belowthing = null,atbottom = false;
if ((this.gridy >= GRIDH-1)) {
atbottom = true;
}
if (!atbottom && !this.getstoppedbelowthing()) {
// accelerate
this.yspeed += GRAVITY;
// move
this.y += this.yspeed;
// don't go below bottom of screen
this.hitBottom();
// calc new gridx / gridy
this.gridx = Math.floor(this.x / GRIDSIZE);
this.gridy = Math.floor(this.y / GRIDSIZE);
this.state = "fall";
}
// hit something?
if (atbottom || this.getstoppedbelowthing()) {
// something below us.
// stop
this.yspeed = 0;
// snap to grid
this.snaptogrid();
this.state = "stop";
}
}
this.hitBottom = function() {
var rockbottom = GRIDSIZE * GRIDH;
if (this.y > rockbottom) {
this.y = rockbottom;
this.yspeed = 0;
}
}
}
function updateGameArea() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
var i;
game.clear();
game.draw();
game.frameNo += 1;
for (i = 0; i < things.length; i += 1) {
things[i].move();
things[i].draw();
}
game.drawpath();
//myScore.text="SCORE: " + game.frameNo;
//myScore.draw();
//myGamePiece.move();
//myGamePiece.update();
}
function everyinterval(n) {
if ((game.frameNo / n) % 1 == 0) {return true;}
return false;
}
//<button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)">ACCELERATE</button>
</script>
<br>
<p>Test game</p>
</body>
</html>