Jump to content

Snake


ShadowMage
 Share

Recommended Posts

Hey guys,I've been experimenting with the HTML Canvas and I've managed to build the classic Snake game (or Nibbles if that's the name you know it by)I just wanted to show it off and see what you guys think. I built this from scratch. There are a few little quirks but for the most part it works pretty well.

<!DOCTYPE html><html><head><title>Snake</title><script type='text/javascript'>//<![CDATA[var msgBox = null;window.onload = function() {	msgBox = document.getElementById('message');	reset_game();}var gameBoard = {	field: null,	snakeTrail: null,	width: 35,	height: 35,	scale: 8,	map: []}var snake;var food = {x: 0, y: 0};var gameStarted, gameOver;var targetFPS;var score;var boostSpd = 3;var foodValue = 10;function reset_game() {	targetFPS = 3;	score = 0;	gameStarted = false;	gameOver = false;	init_gameBoard();	init_snake();	bind_keys();}function init_gameBoard() {	gameBoard.field = document.getElementById('gameBoard');	gameBoard.snakeTrail = document.getElementById('snakeTrail');	var boardW = gameBoard.width*gameBoard.scale;	var boardH = gameBoard.height*gameBoard.scale;	//-- Resize canvas dimensions - internal	gameBoard.field.width = gameBoard.snakeTrail.width = boardW;	gameBoard.field.height = gameBoard.snakeTrail.height = boardH;	//-- Resize canvas dimensions - physical (CSS)	gameBoard.field.style.width = gameBoard.snakeTrail.style.width = boardW+'px';	gameBoard.field.style.height = gameBoard.snakeTrail.style.height = boardH+'px';	reset_map();	//Place the food on the map	place_food();}function reset_map() {	for (var y=0; y<gameBoard.width; y++) {		gameBoard.map[y] = [];		for (var x=0; x<gameBoard.height; x++) {			gameBoard.map[y][x] = 0;		}	}}function bind_keys() {	//In Chrome and Safari, arrow keys only register on keydown/keyup events.	//Letter keys must be bound to onkeypress.  The spacebar seems to work on	//either keypress or keydown/keyup.	document.onkeydown = function(e) {		var k = get_key(e);				switch (k) {			case 32: //Spacebar, start game if it isn't already				if (!gameStarted || gameOver) {					reset_game();					gameStarted = true;					gameCycle();				}				msgBox.style.display = 'none';				break;			case 38: // up, move snake up				if (snake.dir!='d') snake.facing = 'u';				break;			case 40: // down, move snake down				if (snake.dir!='u') snake.facing = 'd';				break;			case 37: // left, move snake left				if (snake.dir!='r') snake.facing = 'l';				break;			case 39: // right, move snake right				if (snake.dir!='l') snake.facing = 'r';				break;		}	}	document.onkeypress = function(e) {		var k = get_key(e);		if (k == 98) { //the letter 'b'			if (!snake.boost) {				targetFPS+=boostSpd;				snake.boost = true;				setTimeout(function() { targetFPS-=boostSpd; snake.boost=false; }, 4000);			}		}	}}function get_key(e) {	e = e || window.event;	return e.keyCode || e.which;}function place_food() {	var brush = gameBoard.field.getContext('2d');	var foodX, foodY;	do {		foodX = Math.floor(Math.random()*gameBoard.width);		foodY = Math.floor(Math.random()*gameBoard.height);	} while (gameBoard.map[foodY][foodX] != 0);	brush.clearRect((food.x*gameBoard.scale), (food.y*gameBoard.scale), gameBoard.scale, gameBoard.scale);	food.x = foodX;	food.y = foodY;	gameBoard.map[foodY][foodX] = 2;	brush.fillStyle = '#CC0033';	brush.beginPath();	//Must supply last argument (true) for Opera to function	brush.arc((foodX*gameBoard.scale)+(gameBoard.scale/2), (foodY*gameBoard.scale)+(gameBoard.scale/2), gameBoard.scale/2, 0, Math.PI*2, true);	brush.closePath();	brush.fill();}function init_snake() {	snake = {		length: 4, //Number of segments		speed: 1, //Movement speed		dir: 'r', //Direction of current movement		facing: 'r', //Direction snake will move on the next cycle		boost: false, //Indicates whether or not boost is active		segments: [] //Array of segments	}	var start = {x: 5, y: 5, dir: 'r'};	snake.segments[0] = {x: start.x, y: start.y};	snake.dir = start.dir;	var prevSegment, newX, newY, step = -1;	for (var s=1; s<snake.length; s++) {		prevSegment = (s>0)?snake.segments[s-1]:snake.segments[0];		newX = prevSegment.x;		newY = prevSegment.y;		if (snake.dir == 'r' || snake.dir == 'l') {			step = (snake.dir=='r')?-1:1;			newX += step;		} else if (snake.dir == 'd' || snake.dir == 'u') {			step = (snake.dir=='d')?-1:1;			newY += step;		}		snake.segments[s] = {x: newX, y: newY};	}	draw_snake();}function draw_snake() {	var brush = gameBoard.snakeTrail.getContext('2d');		snake.segments.forEach(function(segment) {		brush.fillStyle = '#CCCCCC';		brush.fillRect(segment.x*gameBoard.scale, segment.y*gameBoard.scale, gameBoard.scale, gameBoard.scale);		gameBoard.map[segment.y][segment.x] = 1;	});}function gameCycle() {	if (!gameOver) {		moveSnake();		setTimeout(gameCycle, 1000/targetFPS);	} else {		msgBox.innerHTML = 'GAME OVER<div style="font-size: 10pt;">Press Spacebar to start over</div>';		msgBox.style.display = 'inline';		msgBox.style.color = 'red';	}}function moveSnake() {	var newX = snake.segments[0].x;	var newY = snake.segments[0].y;	var step = snake.speed;	if (snake.facing == 'r' || snake.facing == 'l') {		if (snake.facing=='l') step*=-1;		newX += step;	} else if (snake.facing == 'd' || snake.facing == 'u') {		if (snake.facing=='u') step*=-1;		newY += step;	}	//Set the current direction to the current facing	snake.dir = snake.facing;	//Check to see if the new position is outside the boundaries or occupied by a snake segment	if ((newX >= gameBoard.width) || (newX < 0) || (newY >= gameBoard.height) || (newY < 0) || (gameBoard.map[newY][newX] == 1)) {		gameOver = true;	} else {		var foundFood = (gameBoard.map[newY][newX]==2)?true:false;		if (foundFood) {			//Add a new segment			var newSegment = snake.segments[snake.segments.length-1];			newSegment = {x: newSegment.x, y: newSegment.y};			snake.segments.push(newSegment);			//Increment score			score += foodValue;			document.getElementById('score').innerHTML = score;			//Increase game speed			if (score%100 == 0) {				targetFPS+=2;			}			//Place new food			place_food();		}		//Move each segment down the array and remove the segment from the map		for (var s=snake.segments.length-1; s>0; s--) {			gameBoard.map[snake.segments[s].y][snake.segments[s].x] = 0;			var brush = gameBoard.snakeTrail.getContext('2d');			brush.clearRect(snake.segments[s].x*gameBoard.scale, snake.segments[s].y*gameBoard.scale, gameBoard.scale, gameBoard.scale);			snake.segments[s]=snake.segments[s-1];		}		//Set the new coordinates for the head segment		snake.segments[0] = {x: newX, y: newY};		//Redraw the snake		draw_snake();	}}//]]></script><style type='text/css'>canvas {	position: absolute;	border: 3px solid transparent;}#gameBoard {	border: 3px inset #44CC77;}#message {	position: absolute;	top: 100px;	left: 50px;	color: #22AA55;	font-size: 14pt;	font-weight: bold;}.header {	font-weight: bold;}#info {	position: relative;	top: 295px;}#scoreboard {	font-size: 18pt;	color: #2255AA;}#controls {	margin-top: 15px;}</style></head><body><canvas id='gameBoard'>Sorry! Your browser doesn't support canvas!</canvas><canvas id='snakeTrail'>Sorry! Your browser doesn't support canvas!</canvas><span id='message'>Press Spacebar to start</span><div id='info'>	<div id='scoreboard'>		<span class='header'>Score:</span>		<span id='score'>0</span>	</div>	<div id='controls'>		<div>			<span class='header'>Move Up:</span>			<span>Up arrow</span>		</div><div>			<span class='header'>Move Down:</span>			<span>Down arrow</span>		</div><div>			<span class='header'>Move Left:</span>			<span>Left arrow</span>		</div><div>			<span class='header'>Move Right:</span>			<span>Right arrow</span>		</div><div>			<span class='header'>Speed Boost:</span>			<span>B</span>		</div>	<div></div></body></html>

Edited by ShadowMage
Link to comment
Share on other sites

It's a good start! I've always wanted to attack this challenge, but I've never mustered the courage up to this point. Firefox was the only browser in which it would work well. It worked fine until I got to a score of 80 and then the snake stopped. In IE 9, it wouldn't do anything (even after allowing "ActiveX controls"). In Safari, the snake went crawled straight off the screen without listening to me telling him to "turn". Possibly a problem with capturing the keyboard events. In Opera, I could drive the snake fine, but no dots showed up. It looks like it needs a little more work, but as I said it is a good start!

Link to comment
Share on other sites

Yeah, I've never tested in anything but FF so I'm not surprised it has some issues in other browsers. :) Suppose I should have mentioned that...The reason the snake stopped suddenly is because of how the "food" is placed. Occasionally, its position is generated outside of the map coordinates and throws an error when the script tries to place it. I haven't figured out how to prevent that yet.Thanks for your input!

Link to comment
Share on other sites

Updated my code above.The snake should now obey your command in Chrome/Safari. Turns out webkit browsers don't register the arrow keys in the keypress event. I needed to use keydown instead. But then my speed boost didn't work! So, now I have the letter keys registered under the keypress and the arrows and spacebar (spacebar seems to work in both events) on the keydown.I have also fixed the food placement bug (the one that throws the error).I don't have a clue what's causing Opera to malfunction and not display the food...I'm looking into that.EDIT: Oh, forgot to mention that I don't have access to IE 9 so I can't look into why it doesn't work...

Edited by ShadowMage
Link to comment
Share on other sites

Turns out webkit browsers don't register the arrow keys in the keypress event. I needed to use keydown instead. But then my speed boost didn't work! So, now I have the letter keys registered under the keypress and the arrows and spacebar (spacebar seems to work in both events) on the keydown.
Now that you say that, I think I remember having the same issue with keypress/keydown events. It's annoying, but at least the solution isn't that hard.
Link to comment
Share on other sites

Now that you say that, I think I remember having the same issue with keypress/keydown events. It's annoying, but at least the solution isn't that hard.
Yeah, it was actually far easier to track down and fix than the food coordinate issue. :) (Even though the food issue was as simple as changing Math.round to Math.floor.....I just never noticed the difference....:))
Link to comment
Share on other sites

The game has a glitch, if you press a perpendicular direction and then the opposite direction before the snake moves to the next point in the grid then you automatically lose.

Link to comment
Share on other sites

The game has a glitch, if you press a perpendicular direction and then the opposite direction before the snake moves to the next point in the grid then you automatically lose.
Yep, I noticed that too. I haven't decided how I want to deal with that.I thought about adding instructions to an array and executing them one by one, but that would cause other problems, like pushing the wrong direction and not being able to immediately correct it, or inputting a long list of commands (ie, hitting five different arrow keys) and getting trapped into that sequence possibly leading to game over....If you have any suggestions, I'd be happy to take them...:)
Link to comment
Share on other sites

Was just doing some late night work and remember I had downloaded this. Fun little distraction. Obviously a work in progress, but pretty cool. :)

Edited by thescientist
Link to comment
Share on other sites

Updated the code in the original post.Fixed the Opera bug that prevented the 'food' placement. The issue is with the arc() function (found on this page). Opera apparently requires the last argument, which specifies whether to draw the arc clockwise or counter-clockwise. In other browsers the argument is optional.Also fixed the issue Ingolme mentioned about the snake being able to run into himself when you press a perpendicular direction and then immediately press the direction opposite the snake's travel. For this I ended up making a new property of the snake, called 'facing', which stores the direction the snake wants to move (ie, which arrow was pressed) and checks that against the snake's current direction. If snake.facing is not the opposite of snake.dir, the snake is moved that direction, otherwise he continues in the current direction.

Was just doing some late night work and remember I had downloaded this. Fun little distraction.
That's the whole point of these kind of games, isn't it? :)
Link to comment
Share on other sites

  • 3 weeks later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...