|  |  |  | @ -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'); | 
			
		
	
		
			
				
					|  |  |  |  | 		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: | 
			
		
	
		
			
				
					|  |  |  |  | 						this.ctx.drawImage( | 
			
		
	
		
			
				
					|  |  |  |  | 							wall, | 
			
		
	
		
			
				
					|  |  |  |  | 							offsetX+cellSize*x, | 
			
		
	
		
			
				
					|  |  |  |  | 							offsetY+cellSize*y, | 
			
		
	
		
			
				
					|  |  |  |  | 							cellSize, | 
			
		
	
		
			
				
					|  |  |  |  | 							cellSize | 
			
		
	
		
			
				
					|  |  |  |  | 						); | 
			
		
	
		
			
				
					|  |  |  |  | 						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(); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.world[tail[0]][tail[1]]=EMPTY; | 
			
		
	
		
			
				
					|  |  |  |  | 		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
 | 
			
		
	
		
			
				
					|  |  |  |  | 		this.world[head[0]][head[1]]=SNAKE; | 
			
		
	
		
			
				
					|  |  |  |  | 		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) | 
			
		
	
	
		
			
				
					|  |  |  | 
 |