added flammable tiles (closes #1)

This commit is contained in:
Nathan DECHER 2020-04-07 14:37:15 +02:00
parent a87b4679f4
commit ea79ba1dfa
5 changed files with 61 additions and 31 deletions

View file

@ -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_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) 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) TILESETS = $(foreach name, hole, public/assets/$(name)-ts.png)
ANIMATIONS = $(foreach name, fire peach-decay peach-rainbow, public/assets/$(name)-anim.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) JSON = $(foreach name, snake levelList config metaConfig, public/assets/$(name).json)

BIN
assets/oil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View file

@ -5,13 +5,13 @@
" iiiiiii i iiiiiii ", " iiiiiii i iiiiiii ",
" i f i ", " i f i ",
" i i ", " i i ",
" i iiiiiiiiiiiii i ", " i IIIiiiiiiiIII i ",
" i i ", " i i ",
" i f 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 ",
" 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 i i ", " I i I ",
" i i i ", " i i i ",
" i f i ", " i f i ",
" i i ", " i i ",
" i iiiiiiiiiiiii i ", " i IIIiiiiiiiIII i ",
" i i ", " i i ",
" i f i ", " i f i ",
" iiiiiii i iiiiiii ", " iiiiiii i iiiiiii ",

View file

@ -5,6 +5,7 @@ const assetSpecs=[
{ name: 'superFruit', filename: 'peach-rainbow-anim.png', type: 'image' }, { name: 'superFruit', filename: 'peach-rainbow-anim.png', type: 'image' },
{ name: 'decayFruit', filename: 'peach-decay-anim.png', type: 'image' }, { name: 'decayFruit', filename: 'peach-decay-anim.png', type: 'image' },
{ name: 'wall', filename: 'wall32.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: 'hole', filename: 'hole-ts.png', type: 'image' },
{ name: 'fire', filename: 'fire-anim.png', type: 'image' }, { name: 'fire', filename: 'fire-anim.png', type: 'image' },
{ name: 'snake', filename: 'snake.json', type: 'json' }, { name: 'snake', filename: 'snake.json', type: 'json' },

View file

@ -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 { class SnekGame {
constructor(settings, canvas, rules) { constructor(settings, canvas, rules) {
@ -22,6 +22,7 @@ class SnekGame {
case 'w': return WALL; case 'w': return WALL;
case 'o': return HOLE; case 'o': return HOLE;
case 'i': return FIRE; case 'i': return FIRE;
case 'I': return FLAMMABLE;
} }
})(); })();
} }
@ -65,8 +66,9 @@ class SnekGame {
// add the holes // add the holes
if(settings.holes) settings.holes.forEach(([x, y]) => this.world[x][y]=HOLE); 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.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 // add the food
settings.food.forEach(([x, y]) => this.world[x][y]=FOOD); settings.food.forEach(([x, y]) => this.world[x][y]=FOOD);
@ -117,7 +119,7 @@ class SnekGame {
worldWrap: true, worldWrap: true,
winCondition: 'none', winCondition: 'none',
scoreSystem: 'fruit', scoreSystem: 'fruit',
netPlay: false, fireTickSpeed: 10,
autoSizeGrow: false, autoSizeGrow: false,
autoSpeedIncrease: false autoSpeedIncrease: false
}, rules, settings.rules || {}); }, rules, settings.rules || {});
@ -127,6 +129,19 @@ class SnekGame {
return Date.now()-this.firstStep; 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() { draw() {
const assets=require('assets'); const assets=require('assets');
const config=require('config'); const config=require('config');
@ -170,6 +185,7 @@ class SnekGame {
const wall=assets.get('wall'); const wall=assets.get('wall');
const hole=assets.get('hole'); const hole=assets.get('hole');
const fire=assets.get('fire'); const fire=assets.get('fire');
const flammable=assets.get('flammable');
const superFruit=assets.get('superFruit'); const superFruit=assets.get('superFruit');
const decayFruit=assets.get('decayFruit'); const decayFruit=assets.get('decayFruit');
const putTile=(x, y, tile) => this.ctx.drawImage( const putTile=(x, y, tile) => this.ctx.drawImage(
@ -221,6 +237,11 @@ class SnekGame {
// however, the tileset only handles convex shapes // however, the tileset only handles convex shapes
} }
case FLAMMABLE:
case FLAMMABLE_S:
putTile(x, y, flammable);
break;
case SUPER_FOOD: case SUPER_FOOD:
putTileAnim(x, y, superFruit); putTileAnim(x, y, superFruit);
break; 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]) => this.decayFood.forEach(([x, y, birth]) =>
putTileAnimPercent(x, y, decayFruit, (this.playTime-birth)/2000) 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'); const snake=assets.get('snake');
this.ctx.fillStyle=snake.color; this.ctx.fillStyle=snake.color;
this.ctx.strokeStyle=snake.color; this.ctx.strokeStyle=snake.color;
@ -367,6 +388,9 @@ class SnekGame {
case HOLE_S: case HOLE_S:
this.world[tail[0]][tail[1]]=HOLE; this.world[tail[0]][tail[1]]=HOLE;
break; break;
case FLAMMABLE_S:
this.world[tail[0]][tail[1]]=FLAMMABLE;
break;
default: default:
this.world[tail[0]][tail[1]]=EMPTY; this.world[tail[0]][tail[1]]=EMPTY;
} }
@ -416,9 +440,8 @@ class SnekGame {
// you eat, you grow // you eat, you grow
case FOOD: 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.snake.push(tail);
this.world[tail[0]][tail[1]]=SNAKE;
this.length++; this.length++;
// remove the fruit from existence // remove the fruit from existence
@ -430,21 +453,9 @@ class SnekGame {
// increase score // increase score
this.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 // custom rules
if(this.rules.fruitRegrow) { if(this.rules.fruitRegrow) {
const emptyCells=getEmptyCells(); const emptyCells=this.getTilesOfType(EMPTY);
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);
@ -453,7 +464,7 @@ class SnekGame {
if(this.rules.superFruitGrow) { if(this.rules.superFruitGrow) {
if(Math.random()<.1) { // 10% chance if(Math.random()<.1) { // 10% chance
const emptyCells=getEmptyCells(); const emptyCells=this.getTilesOfType(EMPTY);
const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)];
this.world[cell[0]][cell[1]]=SUPER_FOOD; this.world[cell[0]][cell[1]]=SUPER_FOOD;
} }
@ -461,7 +472,7 @@ class SnekGame {
if(this.rules.decayingFruitGrow) { if(this.rules.decayingFruitGrow) {
if(Math.random()<.2) { // 20% chance if(Math.random()<.2) { // 20% chance
const emptyCells=getEmptyCells(); const emptyCells=this.getTilesOfType(EMPTY);
const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)];
this.world[cell[0]][cell[1]]=DECAY_FOOD; this.world[cell[0]][cell[1]]=DECAY_FOOD;
this.decayFood.push([cell[0], cell[1], this.playTime]); this.decayFood.push([cell[0], cell[1], this.playTime]);
@ -479,6 +490,9 @@ class SnekGame {
case HOLE: case HOLE:
this.world[head[0]][head[1]]=HOLE_S; this.world[head[0]][head[1]]=HOLE_S;
break; break;
case FLAMMABLE:
this.world[head[0]][head[1]]=FLAMMABLE_S;
break;
default: default:
this.world[head[0]][head[1]]=SNAKE; this.world[head[0]][head[1]]=SNAKE;
} }
@ -504,6 +518,21 @@ class SnekGame {
if(this.tickId%this.rules.autoSizeGrowTicks==0) this.snake.push(tail); 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 // 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();