A Snake Game in JavaScript

The code below implements a simple “Snake” game in JavaScript. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Or, for an online version, visit https://thiscouldbebetter.neocities.org/snakegame.html.

Use the A and D keys to turn the snake left and right. Collect the red “food” squares to increase the length of the snake, and thus the score. The game ends if the head of the snake runs into the outside wall of the playfield or part of the snake’s body.

Snake

<html>
<body>
<div id="divMain"></div>
<script type="text/javascript">

// main

function main()
{
	var level0 = new Level
	(
		new Coords(32, 32) // sizeInCells
	);

	Globals.Instance.initialize
	(
		200, // millisecondsPerTimerTick
		new Coords(128, 128), // viewSizeInPixels
		level0
	);
}

// classes

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;
		return this;
	}

	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

	Coords.prototype.divide = function(other)
	{
		this.x /= other.x;
		this.y /= other.y;
		return this;
	}

	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;
		return this;
	}

	Coords.prototype.equals = function(other)
	{
		var returnValue = 
		(
			this.x == other.x
			&& this.y == other.y
		);
		return returnValue;
	}

	Coords.prototype.floor = function()
	{
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);
		return this;
	}

	Coords.prototype.isInRange = function(max)
	{
		var returnValue = 
		(
			this.x >= 0
			&& this.x <= max.x
			&& this.y >= 0
			&& this.y <= max.y
		);

		return returnValue;
	}

	Coords.prototype.left = function()
	{
		var yPrev = this.y;
		this.y = 0 - this.x;
		this.x = yPrev;
		return this;
	}

	Coords.prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;
		return this;
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;
		return this;
	}

	Coords.prototype.randomize = function()
	{
		this.x = Math.random();
		this.y = Math.random();
		return this;
	}

	Coords.prototype.right = function()
	{
		var yPrev = this.y;
		this.y = this.x;
		this.x = 0 - yPrev;
		return this;
	}

	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;
		return this;
	}
}

function DisplayHelper()
{
	// do nothing	
}
{
	// constants

	DisplayHelper.ColorBackground = "Black";
	DisplayHelper.ColorBorder = "Gray";
	DisplayHelper.ColorFood = "Red"
	DisplayHelper.ColorMover = "LightGreen";

	// methods

	DisplayHelper.prototype.clear = function()
	{
		this.graphics.fillStyle = DisplayHelper.ColorBackground;
		this.graphics.fillRect
		(
			0, 0,
			this.viewSizeInPixels.x,
			this.viewSizeInPixels.y
		);

		this.graphics.strokeStyle = DisplayHelper.ColorBorder;
		this.graphics.strokeRect
		(
			0, 0,
			this.viewSizeInPixels.x,
			this.viewSizeInPixels.y
		);
	}

	DisplayHelper.prototype.drawLevel = function(level)
	{
		this.clear();
		this.drawLevel_Food(level, level.food);
		this.drawLevel_Mover(level, level.mover);
		this.drawLevel_Status(level);
	}

	DisplayHelper.prototype.drawLevel_Food = function(level, food)
	{
		var cellSizeInPixels = this.viewSizeInPixels.clone().divide
		(
			level.sizeInCells
		);
		var cellSizeInPixelsHalf = cellSizeInPixels.clone().divideScalar(2);

		var drawPos = food.pos.clone().multiply
		(
			cellSizeInPixels
		);

		this.graphics.fillStyle = DisplayHelper.ColorFood;
		this.graphics.fillRect
		(
			drawPos.x, drawPos.y, 
			cellSizeInPixels.x, cellSizeInPixels.y
		);
	}

	DisplayHelper.prototype.drawLevel_Mover = function(level, mover)
	{
		var cellSizeInPixels = this.viewSizeInPixels.clone().divide
		(
			level.sizeInCells
		);
		var cellSizeInPixelsHalf = cellSizeInPixels.clone().divideScalar(2);

		this.graphics.strokeStyle = DisplayHelper.ColorMover;
		this.graphics.beginPath();

		var cellPositionsOccupied = mover.cellPositionsOccupied;

		var drawPos = new Coords().overwriteWith
		(
			cellPositionsOccupied[0]
		).multiply
		(
			cellSizeInPixels
		).add
		(
			cellSizeInPixelsHalf
		);

		this.graphics.moveTo(drawPos.x, drawPos.y);

		for (var i = 1; i < cellPositionsOccupied.length; i++)
		{
			var cellPos = cellPositionsOccupied[i];
			
			drawPos.overwriteWith
			(
				cellPos
			).multiply
			(
				cellSizeInPixels
			).add
			(
				cellSizeInPixelsHalf
			);
			this.graphics.lineTo(drawPos.x, drawPos.y);
			
		}

		this.graphics.stroke();
	}

	DisplayHelper.prototype.drawLevel_Status = function(level)
	{
		var cellSizeInPixels = this.viewSizeInPixels.clone().divide
		(
			level.sizeInCells
		);

		var moverLength = level.mover.cellPositionsOccupied.length - 1;
		this.graphics.fillStyle = DisplayHelper.ColorBorder;
		this.graphics.fillText
		(
			"" + moverLength, 
			cellSizeInPixels.x, cellSizeInPixels.y * 2
		);
	}

	DisplayHelper.prototype.initialize = function(viewSizeInPixels)
	{
		this.viewSizeInPixels = viewSizeInPixels;

		var canvas = document.createElement("canvas");
		canvas.width = this.viewSizeInPixels.x;
		canvas.height = this.viewSizeInPixels.y;
		var divMain = document.getElementById("divMain");
		divMain.appendChild(canvas);

		this.graphics = canvas.getContext("2d");
	}
}

function Food(pos)
{
	this.pos = pos;
}

function Globals()
{
	// do nothing
}
{
	// instances

	Globals.Instance = new Globals();

	// methods

	Globals.prototype.initialize = function
	(
		millisecondsPerTimerTick, viewSizeInPixels, level
	)
	{
		this.displayHelper = new DisplayHelper();
		this.inputHelper = new InputHelper();

		this.displayHelper.initialize(viewSizeInPixels);

		this.level = level;
		this.level.initialize();

		this.timer = setInterval
		(
			this.handleEventTimerTick.bind(this),
			millisecondsPerTimerTick
		);

		this.inputHelper.initialize();
	}

	// events

	Globals.prototype.handleEventTimerTick = function()
	{
		this.level.updateForTimerTick();
	}
}

function InputHelper()
{
	// do nothing
}
{
	InputHelper.prototype.initialize = function()
	{
		document.body.onkeydown = this.handleEventKeyDown.bind(this);
	}

	// events

	InputHelper.prototype.handleEventKeyDown = function(event)
	{
		this.keyCodePressed = event.keyCode;
	}
}

function Level(sizeInCells)
{
	this.sizeInCells = sizeInCells;
	this.sizeInCellsMinusOnes = this.sizeInCells.clone().subtract
	(
		new Coords(1, 1)
	);

	this.mover = null;
	this.food = null;

	this.isOver = false;

}
{
	Level.prototype.initialize = function()
	{
		var moverPosInitial = this.sizeInCells.clone().divideScalar(2).floor();
		var moverForwardInitial = new Coords(1, 0);	
	
		this.mover = new Mover
		(
			"Mover0",
			moverForwardInitial,
			// cellPositionsOccupied
			[ 
				moverPosInitial,
				moverPosInitial.clone().subtract(moverForwardInitial)
			
			] 
		);

		this.foodSpawn();
	}

	Level.prototype.foodSpawn = function()
	{
		var isFoodPosOccupiedByMover = true;

		while (isFoodPosOccupiedByMover == true)
		{
			var foodPos = new Coords().randomize().multiply
			(
				this.sizeInCells
			).floor();

			isFoodPosOccupiedByMover = this.mover.occupiesCellAtPos
			(
				foodPos
			);
		};

		this.food = new Food
		(
			foodPos
		);
	}

	Level.prototype.updateForTimerTick = function()
	{
		if (this.isOver == true)
		{
			return;
		}

		var inputHelper = Globals.Instance.inputHelper;
		var keyCodePressed = inputHelper.keyCodePressed;
		if (keyCodePressed == 65) // a
		{
			this.mover.forward.left();
		}
		else if (keyCodePressed == 68) // d
		{
			this.mover.forward.right();	
		}
		inputHelper.keyCodePressed = null;

		this.mover.updateForTimerTick(this);

		if (this.food == null)
		{
			this.foodSpawn();
		}

		Globals.Instance.displayHelper.drawLevel(this);
	}
}

function Mover(name, forward, cellPositionsOccupied)
{
	this.name = name;
	this.forward = forward;
	this.cellPositionsOccupied = cellPositionsOccupied;
}
{
	Mover.prototype.occupiesCellAtPos = function(cellPosToCheck)
	{
		var returnValue = false;

		for (var i = 0; i < this.cellPositionsOccupied.length; i++)
		{
			var cellPos = this.cellPositionsOccupied[i];
			if (cellPos.equals(cellPosToCheck) == true)
			{
				returnValue = true;
				break;
			}
		}

		return returnValue;
	}

	Mover.prototype.updateForTimerTick = function(level)
	{
		var length = this.cellPositionsOccupied.length;

		var cellPosNext = this.cellPositionsOccupied[0].clone().add
		(
			this.forward
		);

		if (cellPosNext.isInRange(level.sizeInCellsMinusOnes) == false)
		{
			level.isOver = true;
		}

		if (this.occupiesCellAtPos(cellPosNext) == true)
		{
			level.isOver = true;
		}

		this.cellPositionsOccupied.splice(0, 0, cellPosNext);

		var foodPos = level.food.pos;

		if (cellPosNext.equals(foodPos) == true)
		{
			level.food = null;
		}
		else
		{
			this.cellPositionsOccupied.splice(-1, 1);
		}
	}
}

// run

main();

</script>
</body>
</html>

This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

One Response to A Snake Game in JavaScript

  1. Reblogged this on Bits and Pieces of Code and commented:
    Interesting…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s