|
|
|
@ -1,6 +1,4 @@ |
|
|
|
|
const [EMPTY, FOOD, WALL, SNAKE]=Array(4).keys(); |
|
|
|
|
|
|
|
|
|
const ifNaN=(v, r) => isNaN(v)?r:v; |
|
|
|
|
const [EMPTY, FOOD, WALL, HOLE, HOLE_S, SNAKE]=Array(6).keys(); |
|
|
|
|
|
|
|
|
|
class SnekGame { |
|
|
|
|
constructor(settings, canvas, rules) { |
|
|
|
@ -20,11 +18,12 @@ class SnekGame { |
|
|
|
|
case ' ': return EMPTY; |
|
|
|
|
case 'f': return FOOD; |
|
|
|
|
case 'w': return WALL; |
|
|
|
|
case 'o': return HOLE; |
|
|
|
|
} |
|
|
|
|
})(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// extract the dimensions
|
|
|
|
|
this.dimensions=[this.world.length, this.world[0].length]; |
|
|
|
|
|
|
|
|
@ -51,6 +50,9 @@ class SnekGame { |
|
|
|
|
// add the walls
|
|
|
|
|
if(settings.walls) settings.walls.forEach(([x, y]) => this.world[x][y]=WALL); |
|
|
|
|
|
|
|
|
|
// add the holes
|
|
|
|
|
if(settings.holes) settings.holes.forEach(([x, y]) => this.world[x][y]=HOLE); |
|
|
|
|
|
|
|
|
|
// add the food
|
|
|
|
|
settings.food.forEach(([x, y]) => this.world[x][y]=FOOD); |
|
|
|
|
this.fruits=[...settings.food]; |
|
|
|
@ -62,9 +64,13 @@ class SnekGame { |
|
|
|
|
|
|
|
|
|
// get the head and initial direction
|
|
|
|
|
this.head=[...settings.snake[0]]; |
|
|
|
|
this.direction=[ |
|
|
|
|
ifNaN(settings.snake[0][0]-settings.snake[1][0], 1), |
|
|
|
|
ifNaN(settings.snake[0][1]-settings.snake[1][1], 0) |
|
|
|
|
if(settings.snake.length>=2) this.direction=[ |
|
|
|
|
settings.snake[0][0]-settings.snake[1][0], |
|
|
|
|
settings.snake[0][1]-settings.snake[1][1] |
|
|
|
|
]; |
|
|
|
|
else this.direction=[ |
|
|
|
|
1, |
|
|
|
|
0 |
|
|
|
|
]; |
|
|
|
|
this.lastDirection=this.direction |
|
|
|
|
|
|
|
|
@ -115,18 +121,46 @@ class SnekGame { |
|
|
|
|
|
|
|
|
|
// draw our walls
|
|
|
|
|
const wall=assets.get('wall'); |
|
|
|
|
for(let x=0; x<this.dimensions[0]; x++) { |
|
|
|
|
for(let y=0; y<this.dimensions[1]; y++) { |
|
|
|
|
switch(this.world[x][y]) { |
|
|
|
|
case WALL: |
|
|
|
|
this.ctx.drawImage( |
|
|
|
|
wall, |
|
|
|
|
const hole=assets.get('hole'); |
|
|
|
|
|
|
|
|
|
const putTile=(x, y, tile) => this.ctx.drawImage( |
|
|
|
|
tile, |
|
|
|
|
offsetX+cellSize*x, |
|
|
|
|
offsetY+cellSize*y, |
|
|
|
|
cellSize, |
|
|
|
|
cellSize |
|
|
|
|
); |
|
|
|
|
const checkAdj=(x, y) => { |
|
|
|
|
let adj={}; |
|
|
|
|
adj.u=this.world[x][y-1]; |
|
|
|
|
adj.d=this.world[x][y+1]; |
|
|
|
|
adj.l=(this.world[x-1] || [])[y]; |
|
|
|
|
adj.r=(this.world[x+1] || [])[y]; |
|
|
|
|
adj.ul=(this.world[x-1] || [])[y-1]; |
|
|
|
|
adj.ur=(this.world[x+1] || [])[y-1]; |
|
|
|
|
adj.dl=(this.world[x-1] || [])[y+1]; |
|
|
|
|
adj.dr=(this.world[x+1] || [])[y+1]; |
|
|
|
|
return adj; |
|
|
|
|
}; |
|
|
|
|
for(let x=0; x<this.dimensions[0]; x++) { |
|
|
|
|
for(let y=0; y<this.dimensions[1]; y++) { |
|
|
|
|
switch(this.world[x][y]) { |
|
|
|
|
case WALL: |
|
|
|
|
putTile(x, y, wall); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case HOLE:
|
|
|
|
|
case HOLE_S: { |
|
|
|
|
putTile(x, y, hole.base); |
|
|
|
|
let adj=checkAdj(x, y); |
|
|
|
|
Object |
|
|
|
|
.keys(adj) |
|
|
|
|
.filter(k => adj[k]==HOLE || adj[k]==HOLE_S) |
|
|
|
|
.forEach(k => putTile(x, y, hole[k])); |
|
|
|
|
break; |
|
|
|
|
// technically, this works for all shapes
|
|
|
|
|
// however, the tileset only handles convex shapes
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -198,7 +232,13 @@ class SnekGame { |
|
|
|
|
|
|
|
|
|
// get our tail out of the way
|
|
|
|
|
const tail=this.snake.pop(); |
|
|
|
|
switch(this.world[tail[0]][tail[1]]) { |
|
|
|
|
case HOLE_S: |
|
|
|
|
this.world[tail[0]][tail[1]]=HOLE; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
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]) { |
|
|
|
@ -214,8 +254,21 @@ class SnekGame { |
|
|
|
|
// you hit, you die
|
|
|
|
|
case WALL: |
|
|
|
|
case SNAKE: |
|
|
|
|
case HOLE_S: |
|
|
|
|
return this.die(); |
|
|
|
|
|
|
|
|
|
// if either 3 consecutive segments or the whole snake is on a hole, you die
|
|
|
|
|
case HOLE: |
|
|
|
|
if( |
|
|
|
|
this.snake.length==0 || |
|
|
|
|
this.snake.length==1 && |
|
|
|
|
this.world[this.snake[0][0]][this.snake[0][1]]==HOLE_S || |
|
|
|
|
this.snake.length>=2 && |
|
|
|
|
this.world[this.snake[0][0]][this.snake[0][1]]==HOLE_S && |
|
|
|
|
this.world[this.snake[1][0]][this.snake[1][1]]==HOLE_S |
|
|
|
|
) return this.die(); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
// you eat, you don't die
|
|
|
|
|
case FOOD: |
|
|
|
|
// re-grow the snake
|
|
|
|
@ -251,7 +304,13 @@ class SnekGame { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// move our head forward
|
|
|
|
|
switch(this.world[head[0]][head[1]]) { |
|
|
|
|
case HOLE: |
|
|
|
|
this.world[head[0]][head[1]]=HOLE_S; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
this.world[head[0]][head[1]]=SNAKE; |
|
|
|
|
} |
|
|
|
|
this.snake.unshift(head); |
|
|
|
|
|
|
|
|
|
// automatic speed increase
|
|
|
|
@ -301,6 +360,7 @@ class SnekGame { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
handleInputs(inputs) { |
|
|
|
|
// change direction if the input is valid
|
|
|
|
|
const trySet=(dir) => { |
|
|
|
|
if(!dir.every((e, i) => e==this.lastDirection[i] || e==-this.lastDirection[i])) { |
|
|
|
|
this.direction=dir; |
|
|
|
@ -308,6 +368,7 @@ class SnekGame { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// reduce buffer duration
|
|
|
|
|
Object |
|
|
|
|
.keys(inputs) |
|
|
|
|
.forEach(k => { |
|
|
|
@ -318,11 +379,13 @@ class SnekGame { |
|
|
|
|
else inputs[k]=v; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// try all inputs in order and unbuffer them if valid
|
|
|
|
|
if(inputs.left && trySet([-1, 0])) return delete inputs.left; |
|
|
|
|
else if(inputs.right && trySet([ 1, 0])) return delete inputs.right; |
|
|
|
|
else if(inputs.up && trySet([ 0,-1])) return delete inputs.up; |
|
|
|
|
else if(inputs.down && trySet([ 0, 1])) return delete inputs.down; |
|
|
|
|
|
|
|
|
|
// buffering might be disabled
|
|
|
|
|
if(inputs.clearBuffer) { |
|
|
|
|
Object |
|
|
|
|
.keys(inputs) |
|
|
|
|