*create different levels with goals

*display goals at bottom of screen
*implement progress() to earn progress towards goals
*go to next level after meeting all goals
This commit is contained in:
Rob Pearce 2016-08-21 09:32:57 +10:00
parent 5cc296e61f
commit b0f3ec030c
2 changed files with 305 additions and 24 deletions

289
cat.html
View File

@ -36,9 +36,22 @@ var overdesc = "";
var curpath = []; var curpath = [];
var pathdir = -1; var pathdir = -1;
var pathvalid = false; var pathvalid = false;
var curlevel = 1;
var lastmx = -1, lastmy = -1; var lastmx = -1, lastmy = -1;
// goal types
var GOALVERB = {
'food': 'Eat',
'points': 'Earn',
'parades': 'Form',
'llamas': 'Clear',
'cats': 'Clear',
};
var MAXDIRS = 4; var MAXDIRS = 4;
var DIRXMOD = [ 0, 1, 0, -1 ]; var DIRXMOD = [ 0, 1, 0, -1 ];
var DIRYMOD = [ -1, 0, 1, 0 ]; var DIRYMOD = [ -1, 0, 1, 0 ];
@ -60,6 +73,8 @@ var HELPARROWSIZE=15;
var HELPTITLESIZE = 18; var HELPTITLESIZE = 18;
var HELPTEXTSIZE = 12; var HELPTEXTSIZE = 12;
var GOALTEXTSIZE = 16;
var TITLETEXTSIZE = 36; var TITLETEXTSIZE = 36;
var TITLECREDITTEXTSIZE = 16; var TITLECREDITTEXTSIZE = 16;
var TITLESTARTTEXTSIZE = 26; var TITLESTARTTEXTSIZE = 26;
@ -357,6 +372,7 @@ function startGame() {
loadimage('title', 'images/title.png'); loadimage('title', 'images/title.png');
game.init(); game.init();
game.initlevels();
window.addEventListener('load', game.init, false); window.addEventListener('load', game.init, false);
window.addEventListener('resize', game.resize, false); window.addEventListener('resize', game.resize, false);
@ -507,6 +523,24 @@ function addcommas(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} }
function drawcross(x, y, x2, y2, col, width) {
// cross out
ctx.fillStyle = col
ctx.lineWidth = width;
ctx.strokeStyle = col;
// NW -> SE
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x2, y2);
ctx.stroke();
// SW -> NE
ctx.beginPath();
ctx.moveTo(x, y2);
ctx.lineTo(x2, y);
ctx.stroke();
}
function drawarrow(ctx,x1,y1,x2,y2,col,width, arrowsize) { function drawarrow(ctx,x1,y1,x2,y2,col,width, arrowsize) {
drawline(ctx, x1, y1, x2, y2, col, width); drawline(ctx, x1, y1, x2, y2, col, width);
drawarrowhead(ctx, x1, y1, x2, y2, col, arrowsize); drawarrowhead(ctx, x1, y1, x2, y2, col, arrowsize);
@ -547,11 +581,10 @@ function drawarrowhead(ctx, x1, y1, x2, y2, col, size) {
} }
var game = { var game = {
ratio: null, ratio: null,
curw: null, curw: null,
curh: null, curh: null,
levels: null,
canvas : document.createElement("canvas"), canvas : document.createElement("canvas"),
@ -638,22 +671,84 @@ var game = {
initgamevars : function() { initgamevars : function() {
score = 0;
},
initlevelvars : function() {
// kill any existing objects // kill any existing objects
clearthings(); clearthings();
score = 0;
overdesc = ""; overdesc = "";
curpath = []; curpath = [];
pathdir = -1; pathdir = -1;
pathvalid = false; pathvalid = false;
}, },
start : function() { // goal1type goal1count goal2type goal2count etc...
var x,y; addlevel : function () {
this.frameNo = 0; var i,mylevel,idx;
mylevel = new Object();
mylevel.goals = new Array();
for (i = 0 ; i < arguments.length; i += 2) {
idx = mylevel.goals.push(new Object()) - 1;
mylevel.goals[idx].type = arguments[i];
mylevel.goals[idx].count = arguments[i+1];
mylevel.goals[idx].progress = 0;
}
this.levels.push(mylevel);
},
this.initgamevars(); // earn progress towards goals
progress : function(type, amt) {
var i;
// past last level!
if (curlevel >= game.levels.length) return false;
console.log("progress()");
for (i = 0 ; i < this.levels[curlevel].goals.length; i++ ) {
console.log("this goal type is " + this.levels[curlevel].goals[i].type);
if (this.levels[curlevel].goals[i].type == type) {
this.levels[curlevel].goals[i].progress += amt;
if (this.levels[curlevel].goals[i].progress >= this.levels[curlevel].goals[i].count) {
this.levels[curlevel].goals[i].progress = this.levels[curlevel].goals[i].count;
}
}
}
},
initlevels : function ( ) {
var mylevel,i,n;
console.log("doing level init");
this.levels = [];
this.addlevel(); // dummy level with 0 goals
this.addlevel("food", 10);
this.addlevel("points", 200);
/*
for (i = 0; i < this.levels.length; i++) {
console.log("Level " + (i+1) + " 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);
}
}
*/
},
nextlevel : function() {
curlevel++;
this.initlevelvars();
this.populategrid();
this.state = "running";
},
populategrid : function() {
var i;
while (!anyvalidmoves()) { while (!anyvalidmoves()) {
var m; var m;
clearthings(); clearthings();
@ -673,6 +768,17 @@ var game = {
things[i].gridy -= GRIDH; things[i].gridy -= GRIDH;
things[i].updatexy(); things[i].updatexy();
} }
},
startgame : function() {
var x,y;
this.frameNo = 0;
this.initgamevars();
this.initlevelvars();
this.populategrid();
curlevel = 1;
this.state = "running"; this.state = "running";
}, },
@ -689,12 +795,22 @@ var game = {
this.context.clearRect(0, 0, this.canvas.width-1, BOARDY-1); this.context.clearRect(0, 0, this.canvas.width-1, BOARDY-1);
if (game.state == "running") { if (game.state == "running") {
var y1 = 6;
var y2 = 32;
// show level
this.context.font = "16pt Futura";
this.context.textAlign = "center";
this.context.textBaseline = "top";
this.context.fillStyle = "#00aaee";
this.context.fillText("Level " + curlevel, SCREENW/2, y1);
// show score // show score
this.context.font = "16pt Futura"; this.context.font = "16pt Futura";
this.context.textAlign = "left"; this.context.textAlign = "left";
this.context.textBaseline = "top"; this.context.textBaseline = "top";
this.context.fillStyle = "white"; this.context.fillStyle = "white";
this.context.fillText("Score: " + addcommas(score), 16, 16); this.context.fillText("Score: " + addcommas(score), 16, y2);
switch (thingsmoving()) { switch (thingsmoving()) {
case "parade": case "parade":
@ -720,17 +836,135 @@ var game = {
this.context.textAlign = "right"; this.context.textAlign = "right";
this.context.textBaseline = "top"; this.context.textBaseline = "top";
this.context.fillStyle = col; this.context.fillStyle = col;
this.context.fillText(texttodraw, SCREENW-16, 16); this.context.fillText(texttodraw, SCREENW-16, y2);
} }
} else if (game.state == "gameover") { } else if (game.state == "gameover") {
this.context.textAlign = "center"; this.context.textAlign = "center";
this.context.textBaseline = "top"; this.context.textBaseline = "top";
shadowtext(this.context, "GAME OVER", 20, "red", SCREENW / 2, 5); shadowtext(this.context, "GAME OVER", 20, "red", SCREENW / 2, 5);
shadowtext(this.context, "Final Score: " + addcommas(score), 16, "white", SCREENW / 2, 35); shadowtext(this.context, "Final Score: " + addcommas(score), 16, "white", SCREENW / 2, 35);
} else if (game.state == "levelcomplete") {
this.context.textAlign = "center";
this.context.textBaseline = "top";
shadowtext(this.context, "LEVEL COMPLETE!", 20, "#00ee00", SCREENW / 2, 5);
shadowtext(this.context, "Score: " + addcommas(score), 16, "white", SCREENW / 2, 35);
} }
}, },
drawbottom : function() {
var bx = 0,by = BOARDY + GRIDH*GRIDSIZE + 15;
var bw = SCREENW, bh = SCREENH - by;
var x,y,ctx;
var margin = 10;
var indent = 50;
var boxindent = 20;
ctx = this.context;
if (game.state == "running") {
var texth = GOALTEXTSIZE+10;
var gradient;
var borderwidth = 5;
var goaltitlecol = "#00aaee";
// background
gradient = this.context.createLinearGradient(0, 0, bw, bh);
gradient.addColorStop(0, "#0000ff");
gradient.addColorStop(1, "#000055");
// clear
ctx.fillStyle = gradient;
ctx.fillRect(bx, by, bw, bh);
//outline
ctx.strokeStyle = "cyan";
ctx.lineWidth = 5;
ctx.beginPath();
ctx.rect(bx+(borderwidth/2), by+(borderwidth/2), bw-(borderwidth/2), bh-(borderwidth/2));
ctx.stroke();
// draw goals
x = bx + margin;
y = by + margin;
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, "Goals:", GOALTEXTSIZE,goaltitlecol, x , y); y += texth;
if (curlevel < this.levels.length) {
for (n = 0; n < this.levels[curlevel].goals.length; n++) {
var goalcol = "#aaee00";
var goalmet = false;
if (this.levels[curlevel].goals[n].progress >= this.levels[curlevel].goals[n].count) {
goalmet = true;
}
if (goalmet) {
goalcol = "#00ddff";
} else {
goalcol = "#aaee00";
}
// goal name
var goaltext = GOALVERB[this.levels[curlevel].goals[n].type] + " " +
this.levels[curlevel].goals[n].count + " " +
this.levels[curlevel].goals[n].type;
var progtext = "[" + this.levels[curlevel].goals[n].progress + " / " +
this.levels[curlevel].goals[n].count + "]";
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, goaltext, GOALTEXTSIZE,goalcol, x+indent, y);
ctx.textAlign = "right";
ctx.textBaseline = "top";
shadowtext(ctx, progtext, GOALTEXTSIZE,goalcol, SCREENW-indent, y);
// checkbox
var boxsize = texth-8;
var boxx = x+boxindent;
var boxy = y + texth/2 - boxsize/2;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = "black";
ctx.rect(boxx,boxy,boxsize,boxsize); // shadow1
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = goalcol;
ctx.rect(boxx,boxy,boxsize,boxsize); // shadow1
ctx.stroke();
// ticked ?
if (goalmet) {
//drawcross(boxx,boxy, boxx+boxsize-1, boxy+boxsize-1, goalcol, 2);
var tickleftx = boxx + boxsize/4;
var ticklefty = boxy + boxsize/2;
var tickbasex = boxx + boxsize/2;
var tickbasey = boxy+boxsize-boxsize/8;
var tickrightx = boxx + boxsize - boxsize/5;
var tickrighty = boxy + boxsize/8;
drawline(ctx, tickleftx,ticklefty, tickbasex, tickbasey, goalcol, 2);
drawline(ctx, tickbasex, tickbasey, tickrightx, tickrighty, goalcol, 2);
}
y += texth;
}
} else {
// past last level
ctx.textAlign = "left";
ctx.textBaseline = "top";
shadowtext(ctx, "(none)", GOALTEXTSIZE,goaltitlecol, x+indent, y);
}
} else if (game.state == "levelcomplete") {
this.context.textAlign = "center";
this.context.textBaseline = "bottom";
shadowtext(this.context, "Tap for next level", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, by + bh/2);
}
},
drawtitle : function() { drawtitle : function() {
var ratio,w,h; var ratio,w,h;
var img = image['title']; var img = image['title'];
@ -1079,6 +1313,8 @@ var game = {
if (ch == 'a') { if (ch == 'a') {
console.log("ending game"); console.log("ending game");
game.state = "gameover"; game.state = "gameover";
} else if (ch == 'n') {
game.state = "levelcomplete";
} }
}, },
@ -1123,7 +1359,11 @@ var game = {
} else if (game.state == "title") { } else if (game.state == "title") {
game.setstate("help"); game.setstate("help");
} else if (game.state == "help") { } else if (game.state == "help") {
game.start(); game.startgame();
} else if (game.state == "levelcomplete") {
// TODO : show help for this level
// TODO: start next levle
game.nextlevel();
} }
}, },
@ -1275,6 +1515,7 @@ var game = {
curpath.splice(0, 1); curpath.splice(0, 1);
} }
} }
game.progress("parades", 1);
break; break;
} }
@ -1483,16 +1724,20 @@ function thing(gridx, gridy, type, text) {
var points = 0; var points = 0;
if (this.type == "food") { if (this.type == "food") {
points = FOODPOINTS; points = FOODPOINTS;
game.progress("food", 1);
} else if (this.type == "llama") { } else if (this.type == "llama") {
points = LLAMAPOINTS; points = LLAMAPOINTS;
game.progress("llamas", 1);
} else if (this.type == "cat") { } else if (this.type == "cat") {
if (this.eaten == true) { if (this.eaten == true) {
points = SLEEPYCATPOINTS; points = SLEEPYCATPOINTS;
} else { } else {
points = CATPOINTS; points = CATPOINTS;
} }
game.progress("cats", 1);
} }
score += points; score += points;
game.progress("points", points);
// add animation // add animation
things.push(new thing(this.gridx, this.gridy, "text", "+" + points)); things.push(new thing(this.gridx, this.gridy, "text", "+" + points));
@ -1915,21 +2160,39 @@ function mainloop() {
things[i].draw(); things[i].draw();
} }
} }
// hide top of canvads // draw top of canvas (score etc)
game.drawtop(); game.drawtop();
// draw bottom of canvas (goals)
game.drawbottom();
// draw dragged arrow // draw dragged arrow
game.drawpath(); game.drawpath();
// check for valid moves // check for game over and level over
if (!thingsmoving()) { if (!thingsmoving()) {
if (!anyvalidmoves()) { if (levelfinished()) {
game.state = "levelcomplete";
} else if (!anyvalidmoves()) {
game.state = "gameover"; game.state = "gameover";
} }
} }
} }
} }
function levelfinished() {
var i;
// past last level!
if (curlevel >= game.levels.length) return false;
for (i = 0 ; i < game.levels[curlevel].goals.length; i++ ) {
if (game.levels[curlevel].goals[i].progress < game.levels[curlevel].goals[i].count) {
return false;
}
}
return true;
}
function anyvalidmoves() { function anyvalidmoves() {
var gotmoves = false; var gotmoves = false;
var i; var i;

40
todo
View File

@ -29,20 +29,36 @@ phone fixes as per http://www.html5rocks.com/en/mobile/touch/
https://www.smashingmagazine.com/2012/10/design-your-own-mobile-game/ https://www.smashingmagazine.com/2012/10/design-your-own-mobile-game/
*put commas in score *create diff levels with goals
*display goals at bottom of screen
*implement progress() to earn progress towards goals
*go to next level after meeting all goals
different levels with goals implement level-specific help text
eat x cheese when starting a levle, go to 'help text' state if it has one.
get x parades if not, just start the level
get x llamas
break x obstacles
get x goats
etc.
later levels have multiple goals
use the bottom of the screen to show level goals (make current 'help text' the text for level 1)
title screen -> level select Level selection screen
grid of numbers for each level
1 2 3 4 5
6 7 8 9 10
...etc
scrolling background of cat paws ?
custom thing chance ratios for each level
1: no llamas
2: no llamas
3: llamas
better level complete animation
zoom in random cat picture?
title screen -> level select -> help -> startgame
certain levels have help screens (ie. lev 1 is initial help) certain levels have help screens (ie. lev 1 is initial help)
@ -50,6 +66,8 @@ remember which level you're up to
after each level, show cat picture. after each level, show cat picture.
complex goal: form x parades of length y
sounds: sounds:
chomp chomp