upgraded engine with input buffering and added arcade & survival

This commit is contained in:
Nathan DECHER 2020-03-25 19:29:55 +01:00
parent 03e0c97280
commit de390dff8a
5 changed files with 74 additions and 18 deletions

View file

@ -20,6 +20,7 @@
"fruitRegrow": true, "fruitRegrow": true,
"speedIncrease": true, "speedIncrease": true,
"speedMultiplier": 0.9, "speedMultiplier": 0.9,
"speedCap": 50,
"worldWrap": true "worldWrap": true
}, },
"levelFilename": "arcade-<l>.json", "levelFilename": "arcade-<l>.json",

View file

@ -3,5 +3,6 @@
"join": "round", "join": "round",
"cap": "round", "cap": "round",
"headSize": 0.8, "headSize": 0.8,
"tailSize": 0.4 "tailSize": 0.4,
"tailWrapSize": 0.1
} }

12
levels/arcade-arcade.json Normal file
View file

@ -0,0 +1,12 @@
{
"dimensions": [32, 32],
"delay": 200,
"food": [
[16, 16]
],
"snake": [
[16, 12],
[16, 11],
[16, 10]
]
}

View file

@ -0,0 +1,16 @@
{
"dimensions": [32, 32],
"delay": 200,
"food": [],
"snake": [
[16, 12],
[16, 11],
[16, 10]
],
"rules": {
"autoSpeedIncrease": true,
"autoSpeadIncreaseTicks": 10,
"autoSizeGrow": true,
"autoSizeGrowTicks": 100
}
}

View file

@ -1,5 +1,3 @@
const assets=require('assets');
const [EMPTY, FOOD, WALL, SNAKE]=Array(4).keys(); const [EMPTY, FOOD, WALL, SNAKE]=Array(4).keys();
const ifNaN=(v, r) => isNaN(v)?r:v; const ifNaN=(v, r) => isNaN(v)?r:v;
@ -51,7 +49,7 @@ class SnekGame {
} }
// add the walls // add the walls
settings.walls.forEach(([x, y]) => this.world[x][y]=WALL); if(settings.walls) settings.walls.forEach(([x, y]) => this.world[x][y]=WALL);
// add the food // add the food
settings.food.forEach(([x, y]) => this.world[x][y]=FOOD); settings.food.forEach(([x, y]) => this.world[x][y]=FOOD);
@ -68,6 +66,7 @@ class SnekGame {
ifNaN(settings.snake[0][0]-settings.snake[1][0], 1), ifNaN(settings.snake[0][0]-settings.snake[1][0], 1),
ifNaN(settings.snake[0][1]-settings.snake[1][1], 0) ifNaN(settings.snake[0][1]-settings.snake[1][1], 0)
]; ];
this.lastDirection=this.direction
// store the snake // store the snake
this.snake=[...settings.snake]; this.snake=[...settings.snake];
@ -75,7 +74,6 @@ class SnekGame {
// get our canvas, like, if we want to actually draw // get our canvas, like, if we want to actually draw
this.canvas=canvas; this.canvas=canvas;
this.ctx=canvas.getContext('2d'); this.ctx=canvas.getContext('2d');
//TODO this.gl=canvas.getContext('webgl');
// load the custom rules // load the custom rules
this.rules=Object.assign({ this.rules=Object.assign({
@ -93,6 +91,8 @@ class SnekGame {
} }
draw() { draw() {
const assets=require('assets');
// clear the canvas, because it's easier than having to deal with everything // clear the canvas, because it's easier than having to deal with everything
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
@ -150,11 +150,22 @@ class SnekGame {
this.ctx.fill(); this.ctx.fill();
this.ctx.beginPath(); this.ctx.beginPath();
this.snake.forEach(([x, y], i) => { this.snake.forEach(([x, y], i, a) => {
this.ctx.lineTo( this.ctx.lineTo(
offsetX+cellSize*(x+1/2), offsetX+cellSize*(x+1/2),
offsetY+cellSize*(y+1/2) offsetY+cellSize*(y+1/2)
); );
if(i!=0 && Math.hypot(x-a[i-1][0], y-a[i-1][1])>1) {
this.ctx.lineWidth=cellSize*snake.tailWrapSize;
} else {
this.ctx.lineWidth=cellSize*snake.tailSize;
}
this.ctx.stroke();
this.ctx.beginPath()
this.ctx.moveTo(
offsetX+cellSize*(x+1/2),
offsetY+cellSize*(y+1/2)
);
}); });
this.ctx.stroke(); this.ctx.stroke();
@ -174,6 +185,9 @@ class SnekGame {
} }
step() { step() {
this.tickId++;
this.lastDirection=this.direction;
// compute our new head // compute our new head
const head=[ const head=[
this.snake[0][0]+this.direction[0], this.snake[0][0]+this.direction[0],
@ -227,12 +241,27 @@ class SnekGame {
this.fruits.push(cell); this.fruits.push(cell);
this.world[cell[0]][cell[1]]=FOOD; this.world[cell[0]][cell[1]]=FOOD;
} }
if(this.rules.speedIncrease) {
this.delay*=this.rules.speedMultiplier;
if(this.delay<this.rules.speedCap) this.delay=this.rules.speedCap;
}
} }
// move our head forward // move our head forward
this.world[head[0]][head[1]]=SNAKE; this.world[head[0]][head[1]]=SNAKE;
this.snake.unshift(head); this.snake.unshift(head);
// automatic speed increase
if(this.rules.autoSpeedIncrease) {
if(this.delay>50 && this.tickId%this.rules.autoSpeadIncreaseTicks==0) this.delay--;
}
// automatic size grow
if(this.rules.autoSizeGrow) {
if(this.tickId%this.rules.autoSizeGrowTicks==0) this.snake.push(tail);
}
// victory condition // victory condition
if(this.rules.winCondition=='fruit') { if(this.rules.winCondition=='fruit') {
if(!this.fruits.length) return this.win(); if(!this.fruits.length) return this.win();
@ -271,23 +300,20 @@ class SnekGame {
handleInputs(inputs) { handleInputs(inputs) {
const trySet=(dir) => { const trySet=(dir) => {
if(!(this.direction[0]==-dir[0] && this.direction[1]==-dir[1])) this.direction=dir; if(!dir.every((e, i) => e==this.lastDirection[i] || e==-this.lastDirection[i])) {
this.direction=dir;
return true;
}
} }
if(inputs.left) { if(inputs.left && trySet([-1, 0])) return delete inputs.left;
trySet([-1, 0]); else if(inputs.right && trySet([ 1, 0])) return delete inputs.right;
} else if(inputs.right) { else if(inputs.up && trySet([ 0,-1])) return delete inputs.up;
trySet([ 1, 0]); else if(inputs.down && trySet([ 0, 1])) return delete inputs.down;
} else if(inputs.up) {
trySet([ 0,-1]);
} else if(inputs.down) {
trySet([ 0, 1]);
}
Object.keys(inputs).forEach(k => delete inputs[k]);
} }
start() { start() {
this.firstStep=Date.now(); this.firstStep=Date.now();
this.tickId=0;
this.playing=true; this.playing=true;
requestAnimationFrame(() => this.tick()); requestAnimationFrame(() => this.tick());
} }