|  |  |  | @ -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(); | 
			
		
	
	
		
			
				
					|  |  |  | 
 |