|  |  |  | @ -5,11 +5,15 @@ const [EMPTY, FOOD, WALL, SNAKE]=Array(4).keys(); | 
			
		
	
		
			
				
					|  |  |  |  | const ifNaN=(v, r) => isNaN(v)?r:v; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 	constructor(settings, canvas) { | 
			
		
	
		
			
				
					|  |  |  |  | 	constructor(settings, canvas, rules) { | 
			
		
	
		
			
				
					|  |  |  |  | 		// build the world
 | 
			
		
	
		
			
				
					|  |  |  |  | 		this.dimensions=[...settings.dimensions]; | 
			
		
	
		
			
				
					|  |  |  |  | 		this.world=Array(settings.dimensions[0]) | 
			
		
	
		
			
				
					|  |  |  |  | 			.forEach((_, i, a) => a[i]=Array(settings.dimensions[1]).fill(EMPTY)); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.world=Array(settings.dimensions[0]); | 
			
		
	
		
			
				
					|  |  |  |  | 		for(let i=0; i<settings.dimensions[0]; i++) { | 
			
		
	
		
			
				
					|  |  |  |  | 			this.world[i]=Array(settings.dimensions[1]); | 
			
		
	
		
			
				
					|  |  |  |  | 			this.world[i].fill(EMPTY); | 
			
		
	
		
			
				
					|  |  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  |  | 		console.log(this); | 
			
		
	
		
			
				
					|  |  |  |  | 		settings.walls.forEach(([x, y]) => this.world[x][y]=WALL); | 
			
		
	
		
			
				
					|  |  |  |  | 		settings.food.forEach(([x, y]) => this.world[x][y]=FOOD); | 
			
		
	
		
			
				
					|  |  |  |  | 		settings.snake.forEach(([x, y]) => this.world[x][y]=SNAKE); | 
			
		
	
	
		
			
				
					|  |  |  | @ -20,8 +24,8 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 		// get the head and initial direction
 | 
			
		
	
		
			
				
					|  |  |  |  | 		this.head=[...settings.snake[0]]; | 
			
		
	
		
			
				
					|  |  |  |  | 		this.direction=[ | 
			
		
	
		
			
				
					|  |  |  |  | 			ifNaN(settings.snake[1][0]-settings.snake[0][0], 1), | 
			
		
	
		
			
				
					|  |  |  |  | 			ifNaN(settings.snake[1][1]-settings.snake[0][1], 0) | 
			
		
	
		
			
				
					|  |  |  |  | 			ifNaN(settings.snake[0][0]-settings.snake[1][0], 1), | 
			
		
	
		
			
				
					|  |  |  |  | 			ifNaN(settings.snake[0][1]-settings.snake[1][1], 0) | 
			
		
	
		
			
				
					|  |  |  |  | 		]; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 		// get the snake and the fruits themselves
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -32,24 +36,40 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 		this.canvas=canvas; | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx=canvas.getContext('2d'); | 
			
		
	
		
			
				
					|  |  |  |  | 		//TODO this.gl=canvas.getContext('webgl');
 | 
			
		
	
		
			
				
					|  |  |  |  | 		
 | 
			
		
	
		
			
				
					|  |  |  |  | 		// load the custom rules
 | 
			
		
	
		
			
				
					|  |  |  |  | 		this.rules=Object.assign({ | 
			
		
	
		
			
				
					|  |  |  |  | 			fruitRegrow: true, | 
			
		
	
		
			
				
					|  |  |  |  | 			speedIncrease: true, | 
			
		
	
		
			
				
					|  |  |  |  | 			worldWrap: true, | 
			
		
	
		
			
				
					|  |  |  |  | 			winCondition: 'none', | 
			
		
	
		
			
				
					|  |  |  |  | 			scoreSystem: 'fruit' | 
			
		
	
		
			
				
					|  |  |  |  | 		}, rules, settings); | 
			
		
	
		
			
				
					|  |  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 	draw() { | 
			
		
	
		
			
				
					|  |  |  |  | 		// clear the canvas, because it's easier than having to deal with everything
 | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx.clearRect(0, 0, this.canvas.with, this.canvas.height); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 		// get the cell size and offset
 | 
			
		
	
		
			
				
					|  |  |  |  | 		const cellSize=Math.min( | 
			
		
	
		
			
				
					|  |  |  |  | 			this.canvas.with/this.dimensions[0], | 
			
		
	
		
			
				
					|  |  |  |  | 			this.canvas.width/this.dimensions[0], | 
			
		
	
		
			
				
					|  |  |  |  | 			this.canvas.height/this.dimensions[1] | 
			
		
	
		
			
				
					|  |  |  |  | 		); | 
			
		
	
		
			
				
					|  |  |  |  | 		const offsetX=(this.canvas.width-cellSize*this.dimensions[0])/2; | 
			
		
	
		
			
				
					|  |  |  |  | 		const offsetY=(this.canvas.height-cellSize*this.dimensions[1])/2; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 		// draw the border around our game area
 | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx.fillStyle='black'; | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx.fillRect(0, 0, this.canvas.width, offsetY); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx.fillRect(0, 0, offsetX, this.canvas.height); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx.fillRect(offsetX+cellSize*this.dimensions[0], 0, offsetX, this.canvas.height); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.ctx.fillRect(0, offsetY+cellSize*this.dimensions[1], this.canvas.width, offsetY); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 		// draw our walls
 | 
			
		
	
		
			
				
					|  |  |  |  | 		const wall=assets.get('wall'); | 
			
		
	
		
			
				
					|  |  |  |  | 		for(let x=0; x<this.dimensions[0]; x++) { | 
			
		
	
		
			
				
					|  |  |  |  | 			for(let y=0; x<this.dimensions[1]; y++) { | 
			
		
	
		
			
				
					|  |  |  |  | 			for(let y=0; y<this.dimensions[1]; y++) { | 
			
		
	
		
			
				
					|  |  |  |  | 				switch(this.world[x][y]) { | 
			
		
	
		
			
				
					|  |  |  |  | 					case WALL: | 
			
		
	
		
			
				
					|  |  |  |  | 						this.ctx.drawImage( | 
			
		
	
	
		
			
				
					|  |  |  | @ -95,7 +115,7 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 		// our fruit has a nice animation to it between .8 and 1.2 scale
 | 
			
		
	
		
			
				
					|  |  |  |  | 		const ms=Date.now(); | 
			
		
	
		
			
				
					|  |  |  |  | 		const fruitScale=Math.sin(ms/1000*Math.PI)*.2+1 | 
			
		
	
		
			
				
					|  |  |  |  | 		const fruitScale=Math.sin(ms/400*Math.PI)*.2+1 | 
			
		
	
		
			
				
					|  |  |  |  | 		const fruit=assets.get('fruit'); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.fruits.forEach(([x, y]) => { | 
			
		
	
		
			
				
					|  |  |  |  | 			this.ctx.drawImage( | 
			
		
	
	
		
			
				
					|  |  |  | @ -119,6 +139,16 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 		const tail=this.snake.pop(); | 
			
		
	
		
			
				
					|  |  |  |  | 		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]) { | 
			
		
	
		
			
				
					|  |  |  |  | 			if(this.rules.worldWrap) { | 
			
		
	
		
			
				
					|  |  |  |  | 				head[0]=(head[0]+this.dimensions[0])%this.dimensions[0]; | 
			
		
	
		
			
				
					|  |  |  |  | 				head[1]=(head[1]+this.dimensions[1])%this.dimensions[1]; | 
			
		
	
		
			
				
					|  |  |  |  | 			} else { | 
			
		
	
		
			
				
					|  |  |  |  | 				return this.die(); | 
			
		
	
		
			
				
					|  |  |  |  | 			} | 
			
		
	
		
			
				
					|  |  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 		switch(this.world[head[0]][head[1]]) { | 
			
		
	
		
			
				
					|  |  |  |  | 			// you hit, you die
 | 
			
		
	
		
			
				
					|  |  |  |  | 			case WALL: | 
			
		
	
	
		
			
				
					|  |  |  | @ -126,7 +156,7 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 				return this.die(); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 			// you eat, you don't die
 | 
			
		
	
		
			
				
					|  |  |  |  | 			case FRUIT: | 
			
		
	
		
			
				
					|  |  |  |  | 			case FOOD: | 
			
		
	
		
			
				
					|  |  |  |  | 				// re-grow the snake
 | 
			
		
	
		
			
				
					|  |  |  |  | 				this.snake.push(tail); | 
			
		
	
		
			
				
					|  |  |  |  | 				this.world[tail[0]][tail[1]]=SNAKE; | 
			
		
	
	
		
			
				
					|  |  |  | @ -141,7 +171,7 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 				); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 				// custom rules
 | 
			
		
	
		
			
				
					|  |  |  |  | 				if(this.rules.regrowFruits) { | 
			
		
	
		
			
				
					|  |  |  |  | 				if(this.rules.fruitRegrow) { | 
			
		
	
		
			
				
					|  |  |  |  | 					const emptyCells=this.world | 
			
		
	
		
			
				
					|  |  |  |  | 						.map( | 
			
		
	
		
			
				
					|  |  |  |  | 							(l, x) => l | 
			
		
	
	
		
			
				
					|  |  |  | @ -153,7 +183,7 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 						).flat(); | 
			
		
	
		
			
				
					|  |  |  |  | 					const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; | 
			
		
	
		
			
				
					|  |  |  |  | 					this.fruits.push(cell); | 
			
		
	
		
			
				
					|  |  |  |  | 					this.world[cell[0]][cell[1]]=FRUIT; | 
			
		
	
		
			
				
					|  |  |  |  | 					this.world[cell[0]][cell[1]]=FOOD; | 
			
		
	
		
			
				
					|  |  |  |  | 				} | 
			
		
	
		
			
				
					|  |  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -166,12 +196,14 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 	tick() { | 
			
		
	
		
			
				
					|  |  |  |  | 		if(!this.playing) return; | 
			
		
	
		
			
				
					|  |  |  |  | 		if(!this.lastStep) this.lastStep=this.firstStep; | 
			
		
	
		
			
				
					|  |  |  |  | 		if(this.lastStep+delay<Date.now()) { | 
			
		
	
		
			
				
					|  |  |  |  | 			this.lastStep+=delay; | 
			
		
	
		
			
				
					|  |  |  |  | 		this.draw(); | 
			
		
	
		
			
				
					|  |  |  |  | 		if(this.callback) this.callback(); | 
			
		
	
		
			
				
					|  |  |  |  | 		if(this.lastStep+this.delay<Date.now()) { | 
			
		
	
		
			
				
					|  |  |  |  | 			this.lastStep+=this.delay; | 
			
		
	
		
			
				
					|  |  |  |  | 			this.step(); | 
			
		
	
		
			
				
					|  |  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  |  | 		this.draw(); | 
			
		
	
		
			
				
					|  |  |  |  | 		requestAnimationFrame(() => this.tick()); | 
			
		
	
		
			
				
					|  |  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -188,8 +220,26 @@ class SnekGame { | 
			
		
	
		
			
				
					|  |  |  |  | 		console.log("You bad lol"); | 
			
		
	
		
			
				
					|  |  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 	handleInputs(inputs) { | 
			
		
	
		
			
				
					|  |  |  |  | 		const trySet=(dir) => { | 
			
		
	
		
			
				
					|  |  |  |  | 			if(!(this.direction[0]==-dir[0] && this.direction[1]==-dir[1])) this.direction=dir; | 
			
		
	
		
			
				
					|  |  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  |  | 		if(inputs.left) { | 
			
		
	
		
			
				
					|  |  |  |  | 			trySet([-1, 0]); | 
			
		
	
		
			
				
					|  |  |  |  | 		} else if(inputs.right) { | 
			
		
	
		
			
				
					|  |  |  |  | 			trySet([ 1, 0]); | 
			
		
	
		
			
				
					|  |  |  |  | 		} else if(inputs.up) { | 
			
		
	
		
			
				
					|  |  |  |  | 			trySet([ 0,-1]); | 
			
		
	
		
			
				
					|  |  |  |  | 		} else if(inputs.down) { | 
			
		
	
		
			
				
					|  |  |  |  | 			trySet([ 0, 1]); | 
			
		
	
		
			
				
					|  |  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 		Object.keys(inputs).forEach(k => delete inputs[k]); | 
			
		
	
		
			
				
					|  |  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 	start() { | 
			
		
	
		
			
				
					|  |  |  |  | 		this.firstStep=Date.now(); | 
			
		
	
		
			
				
					|  |  |  |  | 		this.playing=true; | 
			
		
	
		
			
				
					|  |  |  |  | 		requestAnimationFrame(() => this.tick()); | 
			
		
	
		
			
				
					|  |  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
	
		
			
				
					|  |  |  | 
 |