*title screen

*show final score on game over
*help text
This commit is contained in:
Rob Pearce 2016-08-20 15:12:32 +10:00
parent 85b1b0ca0d
commit 2aad5e2a93
2 changed files with 438 additions and 68 deletions

495
cat.html
View File

@ -9,18 +9,15 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=devicewidth, minimal-ui" /> <meta name="viewport" content="width=devicewidth, minimal-ui" />
<!-- for apps on home screen --> <!-- for apps on home screen -->
<!-- for ios 7 style, multi-resolution icon of 152x152 --> <!-- for ios 7 style, multi-resolution icon of 152x152 -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-barstyle" content="black-translucent"> <meta name="apple-mobile-web-app-status-barstyle" content="black-translucent">
<link rel="apple-touch-icon" href="icon-152.png"> <link rel="apple-touch-icon" href="images/icon-152.png">
<!-- for Chrome on Android, multi-resolution icon of 196x196 --> <!-- for Chrome on Android, multi-resolution icon of 196x196 -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<link rel="shortcut icon" sizes="196x196" href="icon-196.png"> <link rel="shortcut icon" sizes="196x196" href="images/icon-196.png">
<style> <style>
canvas { canvas {
@ -53,12 +50,21 @@ var GRAVITY = 0.5;
var GRIDSIZE = 80; var GRIDSIZE = 80;
var THINGSIZE = 64; var THINGSIZE = 64;
var TEXTSIZE = 36; // in points var TEXTSIZE = 16; // in points
var TEXTSPEED = 0.5; var TEXTSPEED = 0.5;
var TEXTFADESPEED = 0.05; var TEXTFADESPEED = 0.05;
var TEXTTIME = 35; var TEXTTIME = 35;
var PARADESPEED=16; var HELPLINEWIDTH=4;
var HELPARROWSIZE=15;
var HELPTITLESIZE = 18;
var HELPTEXTSIZE = 12;
var TITLETEXTSIZE = 36;
var TITLECREDITTEXTSIZE = 16;
var TITLESTARTTEXTSIZE = 26;
var PARADESPEED=14;
var EXPLODETICKS=20; var EXPLODETICKS=20;
var EXPLODEGAIN= (THINGSIZE*2) / EXPLODETICKS; var EXPLODEGAIN= (THINGSIZE*2) / EXPLODETICKS;
@ -67,16 +73,26 @@ var EXPLODEFADESPEED = 1.0 / EXPLODETICKS;
var LINEWIDTH=2; var LINEWIDTH=2;
var CROSSWIDTH=2; var CROSSWIDTH=2;
var GRIDW = 5; var GRIDW = 5;
var GRIDH = 5; var GRIDH = 5;
var PATHARROWSIZE = 10;
var PARADELENGTH = 3; var PARADELENGTH = 3;
var BOARDX = (SCREENW - (GRIDW * GRIDSIZE)) / 2; var BOARDX = (SCREENW - (GRIDW * GRIDSIZE)) / 2;
var BOARDY = 64; var BOARDY = 64;
var FOODPOINTS = 10;
var LLAMAPOINTS = 100;
var CATPOINTS = 25;
var SLEEPYCATPOINTS = 5;
var overdesc = ""; var overdesc = "";
var llamatext = "llama";
var image = new Array(); var image = new Array();
function loadimage(name, filename) { function loadimage(name, filename) {
@ -101,15 +117,22 @@ function getgridthing(gridx, gridy) {
} }
function getthingxy(x, y) { function getthingxy(x, y) {
var i; var i,gridx,gridy;
var thing;
// use thing coords
/*
for (i = 0; i < things.length; i += 1) { for (i = 0; i < things.length; i += 1) {
if ((x >= things[i].x) && (x <= things[i].x + THINGSIZE-1) && if ((x >= things[i].x) && (x <= things[i].x + THINGSIZE-1) &&
(y >= things[i].y) && (y <= things[i].y + THINGSIZE-1)) { (y >= things[i].y) && (y <= things[i].y + THINGSIZE-1)) {
return things[i]; return things[i];
} }
} }
*/
// use grid coords
gridx = Math.floor(x / GRIDSIZE);
gridy = Math.floor(y / GRIDSIZE);
return null; return getgridthing(gridx, gridy);
} }
function clearpath() { function clearpath() {
@ -331,11 +354,15 @@ function startGame() {
loadimage('catscared', 'images/catscared.png'); loadimage('catscared', 'images/catscared.png');
loadimage('llama', 'images/alpaca.png'); loadimage('llama', 'images/alpaca.png');
loadimage('cheese', 'images/cheese.png'); loadimage('cheese', 'images/cheese.png');
loadimage('title', 'images/title.png');
game.init(); game.init();
window.addEventListener('load', game.init, false); window.addEventListener('load', game.init, false);
window.addEventListener('resize', game.resize, false); window.addEventListener('resize', game.resize, false);
mainloop();
} }
// valid types: // valid types:
@ -467,8 +494,24 @@ function isadjacenttotype(what, wanttype) {
return false; return false;
} }
function drawarrowhead(ctx, x1, y1, x2, y2, col) { function drawline(ctx,x1,y1,x2,y2,col,width) {
ctx.strokeStyle = col;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function 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 startrads,endrads;
var size2;
size2 = size * 1.5;
startrads = Math.atan((y2 - y1) / (x2 - x1)); startrads = Math.atan((y2 - y1) / (x2 - x1));
if (x2 >= x1) { if (x2 >= x1) {
@ -492,8 +535,8 @@ function drawarrowhead(ctx, x1, y1, x2, y2, col) {
ctx.translate(x2,y2); ctx.translate(x2,y2);
ctx.rotate(endrads); ctx.rotate(endrads);
ctx.moveTo(0,0); ctx.moveTo(0,0);
ctx.lineTo(10,15); ctx.lineTo(size,size2);
ctx.lineTo(-10,15); ctx.lineTo(-size,size2);
ctx.closePath(); ctx.closePath();
ctx.restore(); ctx.restore();
ctx.fill(); ctx.fill();
@ -542,9 +585,19 @@ var game = {
this.resize(); this.resize();
this.interval = setInterval(updateGameArea, 20); window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
//this.interval = setInterval(mainloop, 20);
// this.canvas.addEventListener('click', this.handleclick, false);
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('mouseout', this.handlemouseup, false); this.canvas.addEventListener('mouseout', this.handlemouseup, false);
@ -556,9 +609,9 @@ var game = {
this.canvas.addEventListener('touchleave', this.handlemouseup, false); this.canvas.addEventListener('touchleave', this.handlemouseup, false);
this.canvas.addEventListener('touchmove', this.handlemousemove, false); this.canvas.addEventListener('touchmove', this.handlemousemove, false);
this.canvas.addEventListener('keypress', this.handlekeypress, false); window.addEventListener('keypress', this.handlekeypress, false);
this.start(); this.setstate("title");
}, },
resize : function() { resize : function() {
@ -597,7 +650,6 @@ var game = {
this.initgamevars(); this.initgamevars();
while (!anyvalidmoves()) { while (!anyvalidmoves()) {
var m; var m;
clearthings(); clearthings();
@ -667,15 +719,292 @@ var game = {
this.context.fillText(texttodraw, SCREENW-16, 16); this.context.fillText(texttodraw, SCREENW-16, 16);
} }
} else if (game.state == "gameover") { } else if (game.state == "gameover") {
this.context.font = "20pt Futura";
this.context.textAlign = "center"; this.context.textAlign = "center";
this.context.textBaseline = "top"; this.context.textBaseline = "top";
this.context.fillStyle = "red"; shadowtext(this.context, "GAME OVER", 20, "red", SCREENW / 2, 5);
this.context.fillText("GAME OVER", SCREENW / 2, 16); shadowtext(this.context, "Final Score: " + score, 16, "white", SCREENW / 2, 35);
} }
}, },
drawtitle : function() {
var ratio,w,h;
var img = image['title'];
var gradient;
// background
gradient = this.context.createLinearGradient(0, 0, 0, SCREENH);
gradient.addColorStop(0, "black");
gradient.addColorStop(1, "blue");
this.context.fillStyle = gradient;
this.context.fillRect(0, 0, SCREENW, SCREENH);
// image
ratio = SCREENW / img.width;
w = img.width * ratio;
h = img.height * ratio;
this.context.drawImage(img, 0, SCREENH - h, w, h);
// text
this.context.textAlign = "center";
this.context.textBaseline = "top";
shadowtext(this.context, "Cat Parade", TITLETEXTSIZE,"red", SCREENW / 2, 5);
shadowtext(this.context, "rpearce, 2016", TITLECREDITTEXTSIZE, "#00aaee", SCREENW / 2, 64);
this.context.textBaseline = "bottom";
shadowtext(this.context, "Tap to start", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, SCREENH - 64);
},
drawhelp : function() {
var divamt = 2;
var imgsize = THINGSIZE / divamt;
var gridsize = GRIDSIZE / divamt;
var gradient,ctx,x,y,x2,y2,curx,cury;
var row1y,row2y;
var i;
var linex = [];
var liney = [];
var textyspace = HELPTEXTSIZE * 1.5;
var textxspace = 10;
var midpoint1 = SCREENW/8 * 5;
var midpoint2 = SCREENW/8 * 4;
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);
// top text
cury = textyspace*2;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Instructions", HELPTITLESIZE,"#00aaee", SCREENW / 2, cury);
cury += textyspace;
cury += textyspace;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Drag a cat to eat cheese.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
shadowtext(ctx, " - Cats can eat any amount of cheese, but only in a straight line.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
shadowtext(ctx, " - Cats get tired after eating and cannot eat again.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
// 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 = "green";
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), "green", 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), "green", 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,"#cccc00", x, y);
y = row2y-10;
shadowtext(ctx, "per cheese", HELPTEXTSIZE,"#cccc00", 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 += textyspace;
cury += textyspace;
cury += textyspace;
// LLAMA HELP
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Cats are scared of " + llamatext + "s and can't move when near them.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
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 += textyspace;
cury += textyspace;
cury += textyspace;
cury += textyspace;
// PARADE HELP
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "Drag a path through multiple cats to start a parade.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
shadowtext(ctx, " - Parades can turn corners.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
shadowtext(ctx, " - Parades can include one " + llamatext + " only.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
// 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 + textyspace;
for (i = 0; i < 4; i++) {
drawline(ctx, linex[i], liney[i], linex[i+1], liney[i+1], "green", LINEWIDTH);
}
drawarrow(ctx, linex[4], liney[4], linex[5], liney[5], "green", 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,"#cccc00", x, y);
y += textyspace;
shadowtext(ctx, "+" + SLEEPYCATPOINTS + " points per sleepy cat", HELPTEXTSIZE,"#cccc00", x, y);
y += textyspace;
shadowtext(ctx, "+" + LLAMAPOINTS + " points per " + llamatext, HELPTEXTSIZE,"#cccc00", x, y);
cury = y + textyspace;
cury += textyspace;
cury += textyspace;
cury += textyspace;
cury += textyspace;
// GAME OVER HELP
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
shadowtext(ctx, "The game ends when there are no valid moves left.", HELPTEXTSIZE,"#00cc00", textxspace, cury);
cury += textyspace;
ctx.textAlign = "center";
this.context.textBaseline = "bottom";
shadowtext(this.context, "Tap to start", TITLESTARTTEXTSIZE, "#00dd00", SCREENW / 2, SCREENH - textyspace*2);
},
drawgrid : function() { drawgrid : function() {
this.context.strokeStyle = "black"; this.context.strokeStyle = "black";
this.context.beginPath(); this.context.beginPath();
@ -721,7 +1050,7 @@ var game = {
x2 = curpath[curpath.length-1].x + (THINGSIZE/2); x2 = curpath[curpath.length-1].x + (THINGSIZE/2);
y2 = curpath[curpath.length-1].y + (THINGSIZE/2); y2 = curpath[curpath.length-1].y + (THINGSIZE/2);
drawarrowhead(ctx, BOARDX + x1, BOARDY + y1, BOARDX + x2, BOARDY + y2, col); drawarrowhead(ctx, BOARDX + x1, BOARDY + y1, BOARDX + x2, BOARDY + y2, col, PATHARROWSIZE);
} }
}, },
@ -786,10 +1115,26 @@ var game = {
} }
} }
} else if (game.state == "gameover") { } else if (game.state == "gameover") {
game.setstate("title");
} else if (game.state == "title") {
game.setstate("help");
} else if (game.state == "help") {
game.start(); game.start();
} }
}, },
setstate : function(newstate) {
if (newstate == "help") {
// decide which word to use
if (onein(2)) {
llamatext = "llama";
} else {
llamatext = "alpaca";
}
}
this.state = newstate;
},
handlemousemove : function(event) { handlemousemove : function(event) {
var coords,adjustx,adjusty; var coords,adjustx,adjusty;
@ -933,6 +1278,12 @@ var game = {
}, },
} }
function onein(num) {
var roll;
roll = Math.floor(Math.random() * num);
if (roll == 0) return true;
return false;
}
function getrandomcolour() { function getrandomcolour() {
var letters = '0123456789ABCDEF'; var letters = '0123456789ABCDEF';
@ -959,6 +1310,19 @@ function isonscreen(x,y) {
return true; return true;
} }
function shadowtext(ctx, text, size, col, x, y) {
ctx.font = size + "pt Futura";
// 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 = col;
ctx.fillText(text, x, y);
}
function getrandomtype() { function getrandomtype() {
var pct,type; var pct,type;
pct = Math.random() * 100; pct = Math.random() * 100;
@ -1114,14 +1478,14 @@ function thing(gridx, gridy, type, text) {
this.givepoints = function() { this.givepoints = function() {
var points = 0; var points = 0;
if (this.type == "food") { if (this.type == "food") {
points = 10; points = FOODPOINTS;
} else if (this.type == "llama") { } else if (this.type == "llama") {
points = 100; points = LLAMAPOINTS;
} else if (this.type == "cat") { } else if (this.type == "cat") {
if (this.eaten == true) { if (this.eaten == true) {
points = 5; points = SLEEPYCATPOINTS;
} else { } else {
points = 25; points = CATPOINTS;
} }
} }
score += points; score += points;
@ -1178,15 +1542,7 @@ function thing(gridx, gridy, type, text) {
if (this.type == "text") { if (this.type == "text") {
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "center"; ctx.textBaseline = "center";
// shadows shadowtext(ctx, this.name, TEXTSIZE, this.color, BOARDX + this.x,BOARDY + this.y);
ctx.fillStyle = "black";
ctx.fillText(this.name, BOARDX + this.x-1, BOARDY + this.y-1);
ctx.fillText(this.name, BOARDX + this.x+1, BOARDY + this.y);
ctx.fillText(this.name, BOARDX + this.x+1, BOARDY + this.y+1);
ctx.fillText(this.name, BOARDX + this.x-1, BOARDY + this.y+1);
// real text
ctx.fillStyle = this.color;
ctx.fillText(this.name, BOARDX + this.x, BOARDY + this.y);
} else { } else {
if (this.type == "cat") { if (this.type == "cat") {
var myimage; var myimage;
@ -1524,45 +1880,50 @@ function thing(gridx, gridy, type, text) {
} }
} }
function updateGameArea() { function mainloop() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap; var x, height, gap, minHeight, maxHeight, minGap, maxGap;
var i; var i;
game.clear(); requestAnimFrame(mainloop);
game.drawgrid(); // draw grid
game.frameNo += 1; game.frameNo += 1;
// move objects
for (i = 0; i < things.length; i += 1) { game.clear();
things[i].move(); if (game.state == "title") {
} game.drawtitle();
// move and draw non-animating objects } else if (game.state == "help") {
for (i = 0; i < things.length; i += 1) { game.drawhelp();
if (!things[i].isanimating()) { } else {
things[i].draw(); game.drawgrid(); // draw grid
// move objects
for (i = 0; i < things.length; i += 1) {
things[i].move();
}
// move and draw non-animating objects
for (i = 0; i < things.length; i += 1) {
if (!things[i].isanimating()) {
things[i].draw();
}
}
// move and draw animating objects
for (i = 0; i < things.length; i += 1) {
if (things[i].isanimating()) {
things[i].draw();
}
}
// hide top of canvads
game.drawtop();
// draw dragged arrow
game.drawpath();
// check for valid moves
if (!thingsmoving()) {
if (!anyvalidmoves()) {
game.state = "gameover";
}
} }
} }
// move and draw animating objects
for (i = 0; i < things.length; i += 1) {
if (things[i].isanimating()) {
things[i].draw();
}
}
// hide top of canvads
game.drawtop();
// draw dragged arrow
game.drawpath();
// check for valid moves
if (!thingsmoving()) {
if (!anyvalidmoves()) {
game.state = "gameover";
}
}
//myScore.text="SCORE: " + game.frameNo;
//myScore.draw();
} }
function anyvalidmoves() { function anyvalidmoves() {

11
todo
View File

@ -29,7 +29,16 @@ 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/
title screen *title screen
*show final score on game over
*help text
bath time then next level after getting sufficient llamas?
then each level, you need to get more llamas
what does bath time give you?
use requestanimframe use requestanimframe