diff --git a/Makefile b/Makefile index 548a210..b6b42b1 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ FIRE_ANIM = $(foreach angle, $(shell seq 0 6 359), build/fire$(angle).png) PEACH_DECAY_ANIM = $(foreach percent, $(shell seq 99 -1 0), build/peach-decay$(percent).png) PEACH_RAINBOW_ANIM = $(foreach percent, $(shell seq 100 2 299), build/peach-rainbow$(percent).png) -IMAGES = $(foreach name, apple wall, public/assets/$(name)32.png) +IMAGES = $(foreach name, apple wall oil, public/assets/$(name)32.png) TILESETS = $(foreach name, hole, public/assets/$(name)-ts.png) ANIMATIONS = $(foreach name, fire peach-decay peach-rainbow, public/assets/$(name)-anim.png) JSON = $(foreach name, snake levelList config metaConfig, public/assets/$(name).json) diff --git a/assets/oil.png b/assets/oil.png new file mode 100644 index 0000000..3eee7fa Binary files /dev/null and b/assets/oil.png differ diff --git a/levels/level5.json b/levels/level5.json index 476c308..79324f8 100644 --- a/levels/level5.json +++ b/levels/level5.json @@ -5,13 +5,13 @@ " iiiiiii i iiiiiii ", " i f i ", " i i ", - " i iiiiiiiiiiiii i ", + " i IIIiiiiiiiIII i ", " i i ", " i f i ", " i i i ", - " i i i ", - " i i i ", - " i i i ", + " I i I ", + " I i I ", + " I i I ", " i i i ", " i i i ", " i i i ", @@ -19,13 +19,13 @@ " i i i ", " i i i ", " i i i ", - " i i i ", - " i i i ", - " i i i ", + " I i I ", + " I i I ", + " I i I ", " i i i ", " i f i ", " i i ", - " i iiiiiiiiiiiii i ", + " i IIIiiiiiiiIII i ", " i i ", " i f i ", " iiiiiii i iiiiiii ", diff --git a/src/js/assets.js b/src/js/assets.js index d034467..fbbdd1c 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -5,6 +5,7 @@ const assetSpecs=[ { name: 'superFruit', filename: 'peach-rainbow-anim.png', type: 'image' }, { name: 'decayFruit', filename: 'peach-decay-anim.png', type: 'image' }, { name: 'wall', filename: 'wall32.png', type: 'image' }, + { name: 'flammable', filename: 'oil32.png', type: 'image' }, { name: 'hole', filename: 'hole-ts.png', type: 'image' }, { name: 'fire', filename: 'fire-anim.png', type: 'image' }, { name: 'snake', filename: 'snake.json', type: 'json' }, diff --git a/src/js/snek.js b/src/js/snek.js index 9d4e23d..cf7d09c 100644 --- a/src/js/snek.js +++ b/src/js/snek.js @@ -1,4 +1,4 @@ -const [EMPTY, FOOD, SUPER_FOOD, DECAY_FOOD, WALL, FIRE, HOLE, HOLE_S, SNAKE]=Array(255).keys(); +const [EMPTY, FOOD, SUPER_FOOD, DECAY_FOOD, WALL, FIRE, FLAMMABLE, FLAMMABLE_S, HOLE, HOLE_S, SNAKE]=Array(255).keys(); class SnekGame { constructor(settings, canvas, rules) { @@ -22,6 +22,7 @@ class SnekGame { case 'w': return WALL; case 'o': return HOLE; case 'i': return FIRE; + case 'I': return FLAMMABLE; } })(); } @@ -65,8 +66,9 @@ class SnekGame { // add the holes if(settings.holes) settings.holes.forEach(([x, y]) => this.world[x][y]=HOLE); - // add the fires + // add the fires and flammable tiles if(settings.fires) settings.fires.forEach(([x, y]) => this.world[x][y]=FIRE); + if(settings.flammable) settings.flammable.forEach(([x, y]) => this.world[x][y]=FLAMMABLE); // add the food settings.food.forEach(([x, y]) => this.world[x][y]=FOOD); @@ -117,7 +119,7 @@ class SnekGame { worldWrap: true, winCondition: 'none', scoreSystem: 'fruit', - netPlay: false, + fireTickSpeed: 10, autoSizeGrow: false, autoSpeedIncrease: false }, rules, settings.rules || {}); @@ -127,6 +129,19 @@ class SnekGame { return Date.now()-this.firstStep; } + getTilesOfType(type) { + return this + .world + .map( + (l, x) => l + .map( + (r, y) => r==type?[x,y]:null + ).filter( + a => a + ) + ).flat(); + } + draw() { const assets=require('assets'); const config=require('config'); @@ -170,6 +185,7 @@ class SnekGame { const wall=assets.get('wall'); const hole=assets.get('hole'); const fire=assets.get('fire'); + const flammable=assets.get('flammable'); const superFruit=assets.get('superFruit'); const decayFruit=assets.get('decayFruit'); const putTile=(x, y, tile) => this.ctx.drawImage( @@ -221,6 +237,11 @@ class SnekGame { // however, the tileset only handles convex shapes } + case FLAMMABLE: + case FLAMMABLE_S: + putTile(x, y, flammable); + break; + case SUPER_FOOD: putTileAnim(x, y, superFruit); break; @@ -228,12 +249,12 @@ class SnekGame { } } - // draw our decaying fruits + // draw our decaying fruits (they have more information than just XY, so they need to be drawn here this.decayFood.forEach(([x, y, birth]) => putTileAnimPercent(x, y, decayFruit, (this.playTime-birth)/2000) ); - // draw our snake + // draw our snake (it gets drawn completely differently, so here it goes) const snake=assets.get('snake'); this.ctx.fillStyle=snake.color; this.ctx.strokeStyle=snake.color; @@ -367,6 +388,9 @@ class SnekGame { case HOLE_S: this.world[tail[0]][tail[1]]=HOLE; break; + case FLAMMABLE_S: + this.world[tail[0]][tail[1]]=FLAMMABLE; + break; default: this.world[tail[0]][tail[1]]=EMPTY; } @@ -416,9 +440,8 @@ class SnekGame { // you eat, you grow case FOOD: - // re-grow the snake + // re-grow the snake partially (can't hit the tail, but it's there for all other intents and purposes this.snake.push(tail); - this.world[tail[0]][tail[1]]=SNAKE; this.length++; // remove the fruit from existence @@ -430,21 +453,9 @@ class SnekGame { // increase score this.score++; - // list empty cells - const getEmptyCells=() => - this.world - .map( - (l, x) => l - .map( - (r, y) => r==EMPTY?[x,y]:null - ).filter( - a => a - ) - ).flat(); - // custom rules if(this.rules.fruitRegrow) { - const emptyCells=getEmptyCells(); + const emptyCells=this.getTilesOfType(EMPTY); const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; this.fruits.push(cell); @@ -453,7 +464,7 @@ class SnekGame { if(this.rules.superFruitGrow) { if(Math.random()<.1) { // 10% chance - const emptyCells=getEmptyCells(); + const emptyCells=this.getTilesOfType(EMPTY); const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; this.world[cell[0]][cell[1]]=SUPER_FOOD; } @@ -461,7 +472,7 @@ class SnekGame { if(this.rules.decayingFruitGrow) { if(Math.random()<.2) { // 20% chance - const emptyCells=getEmptyCells(); + const emptyCells=this.getTilesOfType(EMPTY); const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; this.world[cell[0]][cell[1]]=DECAY_FOOD; this.decayFood.push([cell[0], cell[1], this.playTime]); @@ -479,6 +490,9 @@ class SnekGame { case HOLE: this.world[head[0]][head[1]]=HOLE_S; break; + case FLAMMABLE: + this.world[head[0]][head[1]]=FLAMMABLE_S; + break; default: this.world[head[0]][head[1]]=SNAKE; } @@ -504,6 +518,21 @@ class SnekGame { if(this.tickId%this.rules.autoSizeGrowTicks==0) this.snake.push(tail); } + // fire tick + if(this.tickId%this.rules.fireTickSpeed==0) { + const touchingFire=([x, y]) => { + const surrounding=[ + this.world[x][y-1], + this.world[x][y+1], + (this.world[x-1]||[])[y], + (this.world[x+1]||[])[y] + ]; + return surrounding.some(tile => tile==FIRE); + }; + if(this.getTilesOfType(FLAMMABLE_S).some(touchingFire)) return this.die(); + this.getTilesOfType(FLAMMABLE).filter(touchingFire).forEach(([x, y]) => this.world[x][y]=FIRE); + } + // victory condition if(this.rules.winCondition=='fruit') { if(!this.fruits.length) return this.win();