|
|
@ -5,11 +5,15 @@ const [EMPTY, FOOD, WALL, SNAKE]=Array(4).keys(); |
|
|
|
const ifNaN=(v, r) => isNaN(v)?r:v; |
|
|
|
const ifNaN=(v, r) => isNaN(v)?r:v; |
|
|
|
|
|
|
|
|
|
|
|
class SnekGame { |
|
|
|
class SnekGame { |
|
|
|
constructor(settings, canvas) { |
|
|
|
constructor(settings, canvas, rules) { |
|
|
|
// build the world
|
|
|
|
// build the world
|
|
|
|
this.dimensions=[...settings.dimensions]; |
|
|
|
this.dimensions=[...settings.dimensions]; |
|
|
|
this.world=Array(settings.dimensions[0]) |
|
|
|
this.world=Array(settings.dimensions[0]); |
|
|
|
.forEach((_, i, a) => a[i]=Array(settings.dimensions[1]).fill(EMPTY)); |
|
|
|
for(let i=0; i<settings.dimensions[0]; i++) { |
|
|
|
|
|
|
|
this.world[i]=Array(settings.dimensions[1]); |
|
|
|
|
|
|
|
this.world[i].fill(EMPTY); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
console.log(this); |
|
|
|
settings.walls.forEach(([x, y]) => this.world[x][y]=WALL); |
|
|
|
settings.walls.forEach(([x, y]) => this.world[x][y]=WALL); |
|
|
|
settings.food.forEach(([x, y]) => this.world[x][y]=FOOD); |
|
|
|
settings.food.forEach(([x, y]) => this.world[x][y]=FOOD); |
|
|
|
settings.snake.forEach(([x, y]) => this.world[x][y]=SNAKE); |
|
|
|
settings.snake.forEach(([x, y]) => this.world[x][y]=SNAKE); |
|
|
@ -20,8 +24,8 @@ class SnekGame { |
|
|
|
// get the head and initial direction
|
|
|
|
// get the head and initial direction
|
|
|
|
this.head=[...settings.snake[0]]; |
|
|
|
this.head=[...settings.snake[0]]; |
|
|
|
this.direction=[ |
|
|
|
this.direction=[ |
|
|
|
ifNaN(settings.snake[1][0]-settings.snake[0][0], 1), |
|
|
|
ifNaN(settings.snake[0][0]-settings.snake[1][0], 1), |
|
|
|
ifNaN(settings.snake[1][1]-settings.snake[0][1], 0) |
|
|
|
ifNaN(settings.snake[0][1]-settings.snake[1][1], 0) |
|
|
|
]; |
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
// get the snake and the fruits themselves
|
|
|
|
// get the snake and the fruits themselves
|
|
|
@ -32,24 +36,40 @@ class SnekGame { |
|
|
|
this.canvas=canvas; |
|
|
|
this.canvas=canvas; |
|
|
|
this.ctx=canvas.getContext('2d'); |
|
|
|
this.ctx=canvas.getContext('2d'); |
|
|
|
//TODO this.gl=canvas.getContext('webgl');
|
|
|
|
//TODO this.gl=canvas.getContext('webgl');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// load the custom rules
|
|
|
|
|
|
|
|
this.rules=Object.assign({ |
|
|
|
|
|
|
|
fruitRegrow: true, |
|
|
|
|
|
|
|
speedIncrease: true, |
|
|
|
|
|
|
|
worldWrap: true, |
|
|
|
|
|
|
|
winCondition: 'none', |
|
|
|
|
|
|
|
scoreSystem: 'fruit' |
|
|
|
|
|
|
|
}, rules, settings); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
draw() { |
|
|
|
draw() { |
|
|
|
// 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.with, this.canvas.height); |
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
|
|
|
|
|
|
|
|
// get the cell size and offset
|
|
|
|
// get the cell size and offset
|
|
|
|
const cellSize=Math.min( |
|
|
|
const cellSize=Math.min( |
|
|
|
this.canvas.with/this.dimensions[0], |
|
|
|
this.canvas.width/this.dimensions[0], |
|
|
|
this.canvas.height/this.dimensions[1] |
|
|
|
this.canvas.height/this.dimensions[1] |
|
|
|
); |
|
|
|
); |
|
|
|
const offsetX=(this.canvas.width-cellSize*this.dimensions[0])/2; |
|
|
|
const offsetX=(this.canvas.width-cellSize*this.dimensions[0])/2; |
|
|
|
const offsetY=(this.canvas.height-cellSize*this.dimensions[1])/2; |
|
|
|
const offsetY=(this.canvas.height-cellSize*this.dimensions[1])/2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// draw the border around our game area
|
|
|
|
|
|
|
|
this.ctx.fillStyle='black'; |
|
|
|
|
|
|
|
this.ctx.fillRect(0, 0, this.canvas.width, offsetY); |
|
|
|
|
|
|
|
this.ctx.fillRect(0, 0, offsetX, this.canvas.height); |
|
|
|
|
|
|
|
this.ctx.fillRect(offsetX+cellSize*this.dimensions[0], 0, offsetX, this.canvas.height); |
|
|
|
|
|
|
|
this.ctx.fillRect(0, offsetY+cellSize*this.dimensions[1], this.canvas.width, offsetY); |
|
|
|
|
|
|
|
|
|
|
|
// draw our walls
|
|
|
|
// draw our walls
|
|
|
|
const wall=assets.get('wall'); |
|
|
|
const wall=assets.get('wall'); |
|
|
|
for(let x=0; x<this.dimensions[0]; x++) { |
|
|
|
for(let x=0; x<this.dimensions[0]; x++) { |
|
|
|
for(let y=0; x<this.dimensions[1]; y++) { |
|
|
|
for(let y=0; y<this.dimensions[1]; y++) { |
|
|
|
switch(this.world[x][y]) { |
|
|
|
switch(this.world[x][y]) { |
|
|
|
case WALL: |
|
|
|
case WALL: |
|
|
|
this.ctx.drawImage( |
|
|
|
this.ctx.drawImage( |
|
|
@ -95,7 +115,7 @@ class SnekGame { |
|
|
|
|
|
|
|
|
|
|
|
// our fruit has a nice animation to it between .8 and 1.2 scale
|
|
|
|
// our fruit has a nice animation to it between .8 and 1.2 scale
|
|
|
|
const ms=Date.now(); |
|
|
|
const ms=Date.now(); |
|
|
|
const fruitScale=Math.sin(ms/1000*Math.PI)*.2+1 |
|
|
|
const fruitScale=Math.sin(ms/400*Math.PI)*.2+1 |
|
|
|
const fruit=assets.get('fruit'); |
|
|
|
const fruit=assets.get('fruit'); |
|
|
|
this.fruits.forEach(([x, y]) => { |
|
|
|
this.fruits.forEach(([x, y]) => { |
|
|
|
this.ctx.drawImage( |
|
|
|
this.ctx.drawImage( |
|
|
@ -119,6 +139,16 @@ class SnekGame { |
|
|
|
const tail=this.snake.pop(); |
|
|
|
const tail=this.snake.pop(); |
|
|
|
this.world[tail[0]][tail[1]]=EMPTY; |
|
|
|
this.world[tail[0]][tail[1]]=EMPTY; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check for out of world conditions
|
|
|
|
|
|
|
|
if(head[0]<0 || head[0]>=this.dimensions[0] || head[1]<0 || head[1]>=this.dimensions[1]) { |
|
|
|
|
|
|
|
if(this.rules.worldWrap) { |
|
|
|
|
|
|
|
head[0]=(head[0]+this.dimensions[0])%this.dimensions[0]; |
|
|
|
|
|
|
|
head[1]=(head[1]+this.dimensions[1])%this.dimensions[1]; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return this.die(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
switch(this.world[head[0]][head[1]]) { |
|
|
|
switch(this.world[head[0]][head[1]]) { |
|
|
|
// you hit, you die
|
|
|
|
// you hit, you die
|
|
|
|
case WALL: |
|
|
|
case WALL: |
|
|
@ -126,7 +156,7 @@ class SnekGame { |
|
|
|
return this.die(); |
|
|
|
return this.die(); |
|
|
|
|
|
|
|
|
|
|
|
// you eat, you don't die
|
|
|
|
// you eat, you don't die
|
|
|
|
case FRUIT: |
|
|
|
case FOOD: |
|
|
|
// re-grow the snake
|
|
|
|
// re-grow the snake
|
|
|
|
this.snake.push(tail); |
|
|
|
this.snake.push(tail); |
|
|
|
this.world[tail[0]][tail[1]]=SNAKE; |
|
|
|
this.world[tail[0]][tail[1]]=SNAKE; |
|
|
@ -141,7 +171,7 @@ class SnekGame { |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// custom rules
|
|
|
|
// custom rules
|
|
|
|
if(this.rules.regrowFruits) { |
|
|
|
if(this.rules.fruitRegrow) { |
|
|
|
const emptyCells=this.world |
|
|
|
const emptyCells=this.world |
|
|
|
.map( |
|
|
|
.map( |
|
|
|
(l, x) => l |
|
|
|
(l, x) => l |
|
|
@ -153,7 +183,7 @@ class SnekGame { |
|
|
|
).flat(); |
|
|
|
).flat(); |
|
|
|
const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; |
|
|
|
const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; |
|
|
|
this.fruits.push(cell); |
|
|
|
this.fruits.push(cell); |
|
|
|
this.world[cell[0]][cell[1]]=FRUIT; |
|
|
|
this.world[cell[0]][cell[1]]=FOOD; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -166,12 +196,14 @@ class SnekGame { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
tick() { |
|
|
|
tick() { |
|
|
|
|
|
|
|
if(!this.playing) return; |
|
|
|
if(!this.lastStep) this.lastStep=this.firstStep; |
|
|
|
if(!this.lastStep) this.lastStep=this.firstStep; |
|
|
|
if(this.lastStep+delay<Date.now()) { |
|
|
|
this.draw(); |
|
|
|
this.lastStep+=delay; |
|
|
|
if(this.callback) this.callback(); |
|
|
|
|
|
|
|
if(this.lastStep+this.delay<Date.now()) { |
|
|
|
|
|
|
|
this.lastStep+=this.delay; |
|
|
|
this.step(); |
|
|
|
this.step(); |
|
|
|
} |
|
|
|
} |
|
|
|
this.draw(); |
|
|
|
|
|
|
|
requestAnimationFrame(() => this.tick()); |
|
|
|
requestAnimationFrame(() => this.tick()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -188,8 +220,26 @@ class SnekGame { |
|
|
|
console.log("You bad lol"); |
|
|
|
console.log("You bad lol"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleInputs(inputs) { |
|
|
|
|
|
|
|
const trySet=(dir) => { |
|
|
|
|
|
|
|
if(!(this.direction[0]==-dir[0] && this.direction[1]==-dir[1])) this.direction=dir; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if(inputs.left) { |
|
|
|
|
|
|
|
trySet([-1, 0]); |
|
|
|
|
|
|
|
} else if(inputs.right) { |
|
|
|
|
|
|
|
trySet([ 1, 0]); |
|
|
|
|
|
|
|
} 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.playing=true; |
|
|
|
requestAnimationFrame(() => this.tick()); |
|
|
|
requestAnimationFrame(() => this.tick()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|