added core Snake code
This commit is contained in:
		
							parent
							
								
									fe2902cdea
								
							
						
					
					
						commit
						7362b4dc5c
					
				
					 17 changed files with 477 additions and 6 deletions
				
			
		
							
								
								
									
										25
									
								
								public/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								public/index.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
	<head>
 | 
			
		||||
		<title>Snek</title>
 | 
			
		||||
		<meta charset="UTF-8">
 | 
			
		||||
		<link rel="favicon" href="favicon.ico">
 | 
			
		||||
		<link rel="stylesheet" href="css/snek.css">
 | 
			
		||||
		<script src="js/snek.js"></script>
 | 
			
		||||
	</head>
 | 
			
		||||
	<body>
 | 
			
		||||
		<header>
 | 
			
		||||
			<img src="assets/icon256.png">
 | 
			
		||||
			<h1>Snek</h1>
 | 
			
		||||
			<h2>A simple Snake</h2>
 | 
			
		||||
		</header>
 | 
			
		||||
		<main>
 | 
			
		||||
			<img src="assets/apple32.png">
 | 
			
		||||
		</main>
 | 
			
		||||
		<footer>
 | 
			
		||||
			<img src="assets/icon32.png">
 | 
			
		||||
			<p>Snek by <a href="https://codinget.me">Codinget</a>
 | 
			
		||||
			<p>Original <a href="https://perso.liris.cnrs.fr/pierre-antoine.champin/enseignement/intro-js/s6.html">subject</a> by <a href="https://perso.liris.cnrs.fr/pierre-antoine.champin/">P.A. Champin</a></p>
 | 
			
		||||
		</footer>
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										193
									
								
								public/js/snek.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								public/js/snek.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,193 @@
 | 
			
		|||
const [EMPTY, FOOD, WALL, SNAKE]=Array(4).keys();
 | 
			
		||||
 | 
			
		||||
const ifNaN=(v, r) => isNaN(v)?r:v;
 | 
			
		||||
 | 
			
		||||
class SnekGame {
 | 
			
		||||
	constructor(settings, canvas) {
 | 
			
		||||
		// build the world
 | 
			
		||||
		this.dimensions=[...settings.dimensions];
 | 
			
		||||
		this.world=Array(settings.dimensions[0])
 | 
			
		||||
			.forEach((_, i, a) => a[i]=Array(settings.dimensions[1]).fill(EMPTY));
 | 
			
		||||
		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);
 | 
			
		||||
 | 
			
		||||
		// setup the delay
 | 
			
		||||
		this.delay=settings.delay;
 | 
			
		||||
 | 
			
		||||
		// 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)
 | 
			
		||||
		];
 | 
			
		||||
 | 
			
		||||
		// get the snake and the fruits themselves
 | 
			
		||||
		this.snake=[...settings.snake];
 | 
			
		||||
		this.fruits=[...settings.food];
 | 
			
		||||
 | 
			
		||||
		// get our canvas, like, if we want to actually draw
 | 
			
		||||
		this.canvas=canvas;
 | 
			
		||||
		this.ctx=canvas.getContext('2d');
 | 
			
		||||
		//TODO this.gl=canvas.getContext('webgl');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
		// get the cell size and offset
 | 
			
		||||
		const cellSize=Math.min(
 | 
			
		||||
			this.canvas.with/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 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++) {
 | 
			
		||||
				switch(this.world[x][y]) {
 | 
			
		||||
					case WALL:
 | 
			
		||||
						this.ctx.drawImage(
 | 
			
		||||
							wall,
 | 
			
		||||
							offsetX+cellSize*x,
 | 
			
		||||
							offsetY+cellSize*y,
 | 
			
		||||
							cellSize,
 | 
			
		||||
							cellSize
 | 
			
		||||
						);
 | 
			
		||||
						break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// draw our snake
 | 
			
		||||
		const snake=Assets.get('snake');
 | 
			
		||||
		this.ctx.fillStyle=snake.color;
 | 
			
		||||
		this.ctx.strokeStyle=snake.color;
 | 
			
		||||
		this.ctx.lineCap=snake.cap;
 | 
			
		||||
		this.ctx.lineJoin=snake.join;
 | 
			
		||||
		this.ctx.lineWidth=cellSize*snake.tailSize;
 | 
			
		||||
 | 
			
		||||
		this.ctx.beginPath();
 | 
			
		||||
		this.ctx.ellipse(
 | 
			
		||||
			offsetX+cellSize*(this.snake[0][0]+1/2),
 | 
			
		||||
			offsetY+cellSize*(this.snake[0][1]+1/2),
 | 
			
		||||
			cellSize/2*snake.headSize,
 | 
			
		||||
			cellSize/2*snake.headSize,
 | 
			
		||||
			0,
 | 
			
		||||
			0,
 | 
			
		||||
			Math.PI*2
 | 
			
		||||
		);
 | 
			
		||||
		this.ctx.fill();
 | 
			
		||||
 | 
			
		||||
		this.ctx.beginPath();
 | 
			
		||||
		this.snake.forEach(([x, y], i) => {
 | 
			
		||||
			this.ctx.lineTo(
 | 
			
		||||
				offsetX+cellSize*(x+1/2),
 | 
			
		||||
				offsetY+cellSize*(y+1/2)
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
		this.ctx.stroke();
 | 
			
		||||
 | 
			
		||||
		// 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 fruit=Assets.get('fruit');
 | 
			
		||||
		this.fruits.forEach(([x, y]) => {
 | 
			
		||||
			this.ctx.drawImage(
 | 
			
		||||
				fruit,
 | 
			
		||||
				offsetX+cellSize*x+(1-fruitScale)*cellSize/2,
 | 
			
		||||
				offsetY+cellSize*x+(1-fruitScale)*cellSize/2,
 | 
			
		||||
				cellSize*fruitScale,
 | 
			
		||||
				cellSize*fruitScale
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	step() {
 | 
			
		||||
		// compute our new head
 | 
			
		||||
		const head=[
 | 
			
		||||
			this.snake[0][0]+this.direction[0],
 | 
			
		||||
			this.snake[0][1]+this.direction[1]
 | 
			
		||||
		];
 | 
			
		||||
 | 
			
		||||
		// get our tail out of the way
 | 
			
		||||
		const tail=this.snake.pop();
 | 
			
		||||
		this.world[tail[0]][tail[1]]=EMPTY;
 | 
			
		||||
 | 
			
		||||
		switch(this.world[head[0]][head[1]]) {
 | 
			
		||||
			// you hit, you die
 | 
			
		||||
			case WALL:
 | 
			
		||||
			case SNAKE:
 | 
			
		||||
				return this.die();
 | 
			
		||||
 | 
			
		||||
			// you eat, you don't die
 | 
			
		||||
			case FRUIT:
 | 
			
		||||
				// re-grow the snake
 | 
			
		||||
				this.snake.push(tail);
 | 
			
		||||
				this.world[tail[0]][tail[1]]=SNAKE;
 | 
			
		||||
 | 
			
		||||
				// remove the fruit from existence
 | 
			
		||||
				this.world[head[0]][head[1]]=SNAKE;
 | 
			
		||||
				this.fruits.splice(
 | 
			
		||||
					this.fruits.find(
 | 
			
		||||
						([x, y]) => x==head[0] && y==head[1]
 | 
			
		||||
					),
 | 
			
		||||
					1
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				// custom rules
 | 
			
		||||
				if(this.rules.regrowFruits) {
 | 
			
		||||
					const emptyCells=this.world
 | 
			
		||||
						.map(
 | 
			
		||||
							(l, x) => l
 | 
			
		||||
								.map(
 | 
			
		||||
									(r, y) => r==EMPTY?[x,y]:null
 | 
			
		||||
								).filter(
 | 
			
		||||
									a => a 
 | 
			
		||||
								)
 | 
			
		||||
						).flat();
 | 
			
		||||
					const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)];
 | 
			
		||||
					this.fruits.push(cell);
 | 
			
		||||
					this.world[cell[0]][cell[1]]=FRUIT;
 | 
			
		||||
				}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// move our head forward
 | 
			
		||||
		this.world[head[0]][head[1]]=SNAKE;
 | 
			
		||||
		this.snake.unshift(head);
 | 
			
		||||
 | 
			
		||||
		// victory condition
 | 
			
		||||
		if(!this.fruits.length) return this.win();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tick() {
 | 
			
		||||
		if(!this.lastStep) this.lastStep=this.firstStep;
 | 
			
		||||
		if(this.lastStep+delay<Date.now()) {
 | 
			
		||||
			this.lastStep+=delay;
 | 
			
		||||
			this.step();
 | 
			
		||||
		}
 | 
			
		||||
		this.draw();
 | 
			
		||||
		requestAnimationFrame(() => this.tick());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	win() {
 | 
			
		||||
		this.playing=false;
 | 
			
		||||
		// you gud lol
 | 
			
		||||
		console.log("You gud lol");
 | 
			
		||||
		console.log(`Won in ${(Date.now()-this.firstStep)/1000} seconds`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	die() {
 | 
			
		||||
		this.playing=false;
 | 
			
		||||
		// you bad lol
 | 
			
		||||
		console.log("You bad lol");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start() {
 | 
			
		||||
		this.firstStep=Date.now();
 | 
			
		||||
		requestAnimationFrame(() => this.tick());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue