JavaScript Games Autores
JavaScript Games Autores
Many people who are starting out with a new project get caught up in deciding on
technologies, what frameworks to use and what database to pick. When often —
especially if you are a beginner — it is a better idea to just get started, and make
something with the tools you know.
This is the first part of a tutorial that will show how you can build a snake game in
less than a hundred lines of JavaScript, without any frameworks. There is some
HTML and CSS in addition, but nothing scary. Any beginner to the web platform
should be able to go through the guide, and experienced developers will learn
something new as well.
Start with an empty HTML document and open it in Chrome. Insert the following
code that just sets up a HTML document with style / script tags and a function to
set up the game.
<!DOCTYPE html5>
<html>
<head>
<title>Snake in pure HTML/JavaScript/CSS</title>
<style>
html {
text-align: center;
font-family: Helvetica, Arial, Helvetica, sans-serif;
}
</style>
</head>
<body onload="initGame()">
<h1>Simple Snake</h1>
<div id="board"></div>
<script>
function initGame() {
}
</script>
</body>
</html>
Let’s start from the top: The head tag. In here we have a title to put a document
title on the page, as well as a style tag containing styling to center the contents of
the page horizontally and change the font to a sans-serif one.
The the body element, which contains a h1 element with the title of the game and
a single div element that will contain the game board. We have it tagged with the
id ‘board’ so we can access the element from JavaScript. We also have a script
that will soon contain all of our game logic.
The body element also has an onload handler, which calls the initGame
function defined in the script tag. This means that once the document is completely
loaded, the code inside initGame will run. There is nothing there yet, so lets’ get
started!
Note: If you view the source code on all examples, there will be a script tag at the bottom that
handles integration with the iframe when the examples are included on this page. You can inspect it
if you like, but it is not necessary to understand it.
<script>
const board = [];
const boardWidth = 26, boardHeight = 16;
function initGame() {
// Logic to come
}
</script>
To make it simple, we’ll build the entire board out of div elements, and will style
these to look like cells, apples and the snake. We create these diivs by iterating
over the Y and X axes, creating columns and rows of items:
function initGame() {
for (var y = 0; y < boardHeight; ++y) {
for (var x = 0; x < boardWidth; ++x) {
var cell = {};
}
}
}
Each cell will be defined by an empty object {} . In this object we will put the state
for each cell.
We need to fetch the board from the DOM (remember the <div
id="board></div>" ?). We do this using document.getElementById and store
it in a variable. This is done outside the loops.
function initGame() {
const boardElement = document.getElementById('board');
Finally, we need to store all these cells inside the board array of arrays. We do this
by creating an array for every row, and push ing the cells into this row in the inner
loop, and every row into the board array for every outer loop, giving us our final
initGame function:
function initGame() {
for (var y = 0; y < boardHeight; ++y) {
var row = [];
for (var x = 0; x < boardWidth; ++x) {
var cell = {};
To make the divs visible, we assign them a black background color, a width and a
height. We do this using CSS, by adding some code inside the <style> tag in the
template. To style the cells we use the #board div selector. This targets all the
div elements inside the element with the id board .
If you try only this they will all lay out in a loooong column down the page that is 24
pixels wide. To fix this, we need to float the elements. This moves them around so
that there is not a line break after every element. Instead the page will now display
as rows and columns of solid blocks.
#board div {
background-color: black;
border: 1px solid grey;
box-sizing: border-box;
float: left;
width: 24px;
height: 24px;
}
The box-sizing property is necessary so that the width of the borders don’t
make the cells wider than 24px. You can read up on box-sizing if you are curious
about this.
If you view the above example, you will notice that the grid is not square,. We also
need to limit the board in width, so that the grid is only 26 cells wide. We specify
the width of #board to be 26 * 24px, and use the calc function in CSS to not
have to calculate this number manually. The margin: auto means that the board
will be centered horizontally on-screen.
#board {
width: calc(26 * 24px);
margin: auto;
}
Next we need to add the snake to the board. We start by defining the properties
the snake has in this game, there are a few:
Direction — The snake is moving either left, right, up or down. We can store this as
strings 'Up' , 'Down' , 'Left' and ‘ Right ’.
Position — Defined as a X and Y position on the board. Note that 0, 0 will be top left
on the board and 25, 15 will be bottom right.
Length — Length of the snake in segments.
We also need to store the tail of the snake. To keep track of this, we store the
information about the tail in the board. So we’ll add a property snake to every cell
on the board.
We’ll give it an integer value of 0 to denote that the snake is not on the space (at
the start of the game).
The snake will start moving upwards, with a length of 5 segments. It will start in the
center of the board (divide width and height by 2), we need to use Math.floor
here to round down, since if the board width is 25, 12.5 is not a meaningful position
on the grid.
function startGame() {
// Default position for the snake in the middle of the
board.
snakeX = Math.floor(boardWidth / 2);
snakeY = Math.floor(boardHeight / 2);
snakeLength = 5;
snakeDirection = 'Up';
Why create a new function for this? We could just use default values for global
variables? The reason is we also need to do the same thing when the player dies
to reset the game state, by putting it in a function we can reuse.
In other words: initGame will only run once for the entire application, but
startGame will run multiple times.
Add a call to startGame from the end initGame (before the final } ). With this
addition, we’re done with the first part of the tutorial.
Wrapping up
Next time, we will make the snake visible on the board and implement keyboard
controls, so that you can steer it around the board using the keyboard.
This is the second part of our quest to make a simple Snake game in the browser.
In this part, we will code the game loop, rendering and controls for the game.
We left off with an empty game board in part 1, with the game state set up. Now it’s
time to start implementing the game logic. You can view the result from part 1 here,
right click → View Source to see the code.
To run the game loop; we will create a new function. We call it simply gameLoop .
This function will run repeatedly during the duration of the game. It will handle
rendering and moving the snake around the board.
function gameLoop() {
// This function calls itself, with a timeout of 1000
milliseconds
setTimeout(gameLoop, 1000);
}
The setTimeout call will the the function again, recursively, every second so that
any code inside will run repeatedly in the background.
We also need to start the loop by adding an initial call to gameLoop at the end of
the startGame function:
function startGame() {
// ... code from before
startGame();
The next step is to iterate over the entire board and update the DOM (ie. the HTML
div elements we created in part 1) with a different CSS class if the corresponding
cell contains the snake. We do this by looping over the Y and X axes, and checking
if the board has a snake , if so, we set the element’s class to "snake" . Else we
remove any class set.
function gameLoop() {
// Loop over the entire board, and update every cell
for (var y = 0; y < boardHeight; ++y) {
for (var x = 0; x < boardWidth; ++x) {
var cell = board[y][x];
if (cell.snake) {
cell.element.className = 'snake';
}
else {
cell.element.className = '';
}
}
}
We also need to style the snake class, so it looks like a snake (green). Add the
following inside the <style> tag to do that:
#board .snake {
background-color: green;
}
Now when we refresh the page, the center element on the board should be green.
It should look like the example below.
Now that the initial snake is visible, we need to make it to move around the board.
Before we update the board, we should look at what direction the snake is heading.
Using the direction, we update the position of the snake (the snakeX and snakeY
global variables) accordingly. Then we also need to update the board (the
board[y][x].snake variable). When the board is updated, it will automatically
update div element with the snake class, because the code that does that will
follow immediately after our check.
So what does this look like in code? We have the snakeDirection global
variable to define the direction the snake is heading, right now it always has the
value "Up" , but we also need to check for "Left" , "Right" , "Down" . For each
direction, we change the value of either snakeX or snakeY by 1. Changing
snakeY will move the snake either up or down, changing snakeX will move the
snake left or right.
Remember that snakeX = 0 and snakeY = 0 is the upper left of the board. So to
make the snake move right, we increase snakeX . To make it go down, we
increase snakeY . And the reverse for left and up. In code, it looks like this:
After updating the snake position, we also update the board at the new position to
set the snake as present. This code should be inserted inside gameLoop before
the double for-loops.
View this in a new tabMost likely this appears as a green line from the center and
up. Refresh the page to see the snake slowly move up.
As you can see, the snake moves up the board slowly, since the direction is always
"Up" , the next step will be to make it possible to change the direction of the snake.
Handling input
Next step is handling input. Input will be done using the keyboard (sorry, mobile
readers). To capture this we need to bind the onKeyDown event. We check what
key was pressed and update the snakeDirection accordingly.
function enterKey(event) {
// Update direction depending on key hit
}
We also need to add the onkeydown event handler to the body element. Here we
already have one event handler to initializes the game, so add another one that
handles the key presses:
Inside the function, we will use the another switch statement to check what key
was pressed — in this case we check for all the arrow keys — and update the
direction of the snake. This will then automatically get picked up by the game loop
and the we will be able to control the snake!
function enterKey(event) {
// Update direction depending on key hit
switch (event.key) {
case 'ArrowUp': snakeDirection = 'Up'; break;
case 'ArrowDown': snakeDirection = 'Down'; break;
case 'ArrowLeft': snakeDirection = 'Left'; break;
case 'ArrowRight': snakeDirection = 'Right'; break;
default: return;
}
The call to preventDefault is necessary, so that pressing the arrow keys do not
perform their default action (that scrolls the page up/down/left/right).
Try the link below to try it out, note that if you collide with the walls, the game will
crash and you can’t play any more. If that happens, refresh to restart the game.
The walls
Let’s fix that bug with the walls that makes the game crash, and we’ll have a
controllable snake game!
To check for the walls, we simply need to add an if statement inside the game
loop after updating the snake position. This will check if the new position is outside
the board by comparing against 0 (snake can’t be on a negative position) and
boardWidth / boardHeight .
If we detect that the snake is outside the board, we restart the game by calling
startGame , this will reset both the position, length and direction of the snake.
We also need to update startGame to clear the board from the snake, so the old
snake is not visible when the game restarts. Add the following for loop inside
startGame to do that:
Now we can play the game without it crashing. Try it out by clicking below and
playing using the arrow keys:
View this in a new tabClick the frame to focus. You can now steer the snake
around using the arrow keys. The game will reset if you collide with the walls.
However, the snake remains on the board as we move around. So we have one
thing left on the todo list.
We can use the board to store this information. So rather than trying to keep track
of where different snake segments are on the board. We store how many more
iterations of the game loops the Snake should be visible in every cell.
Now, instead of just storing if there is a snake on the board, we store the remaining
length of the snake on that cell.
The next part is changing the loop over the entire board. Instead of checking for
cell.snake , we want to check if the value is greater than zero. If so, there is a
snake on that cell, so we still set the snake class, but we also decrease the value
of cell.snake by 1.
The update code inside the game loop will look like this:
if (cell.snake > 0) {
cell.element.className = 'snake';
cell.snake -= 1;
}
else {
cell.element.className = '';
}
View this in a new tabYou can now control the snake around using the arrow keys,
and it disappears as you move.
Now we have an almost playable version of the game. All that remains is collecting
those delicious apples (and not being able to move through yourself). We’ll write
this code in the next part.
It’s time to complete our small snake game (long timeout between posts due to
work). Last time we made the snake move on the board and added keyboard
controls. Now all that remains is picking up the apples and collision detection with
the tail.
Placing apples
First step is placing the apples on the board.
function placeApple() {
// A random coordinate for the apple
var appleX = Math.floor(Math.random() * boardWidth);
var appleY = Math.floor(Math.random() * boardHeight);
board[appleY][appleX].apple = 1;
}
Ta place an apple on the board when the game starts, update the startGame
function. Before the final } brace, insert a call to the placeApple() function.
We also need to update the loop in this function to clear all apples on the board.
The end of the startGame function will thus look like this, with a call to the new
function and an updated loop:
// Clear board
for (var y = 0; y < boardHeight; ++y) {
for (var x = 0; x < boardWidth; ++x) {
board[y][x].snake = 0;
board[y][x].apple = 0;
}
}
We also need to check in the render loop for the apple value (the same place we
check the board for the snake value) and set a different CSS class if there is an
apple on that spot. In other words, we update the gameLoop function with the
following else if statement.
if (cell.snake > 0) {
cell.element.className = 'snake';
cell.snake -= 1;
}
else if (cell.apple === 1) {
cell.element.className = 'apple';
}
else {
cell.element.className = '';
}
And finally, add a matching CSS class, to style the apple red on the board:
#board .apple {
background-color: red;
}
Once these changes have been made, the red apple should appear on the board.
You can also look at the source of the below sample.
View this in a new tabThere should now be a red apple on the board, but we can't
collect it.
The final piece of the puzzle, will be to pick up the apples and tail collisions.
Collecting apples
To check if the snake collided with an apple, we will update the game loop with
another if statement. Add this after checking if we collided with any walls (as
before):
// Collect apples
if (board[snakeY][snakeX].apple === 1) {
snakeLength++;
board[snakeY][snakeX].apple = 0;
placeApple()
}
We check if an apple is present on the cell we are moving to, if that is the case we
increase the length of the snake, remove the apple and place a new apple on the
board. This makes the game playable:
View this in a new tabYou can now collect apples, and make the snake grow.
This requires a very similar check to the one we had for apples, but we instead
check if the snake property is set. If it is, we just restart the game. The following if
statement goes before the one we added before to pick up apples:
// Tail collision
if (board[snakeY][snakeX].snake > 0) {
startGame();
}
Mission success
And there you have it, a complete snake game made entirely in simple HTML,
JavaScript and CSS. Try it out below:
After revelling in your success, we can note there are still bugs to be fixed:
After fixing the bugs, try to extend the game. Here are some ideas on what can be
done:
Replace the snake and Apple with cooler graphics, try using background-
image .
Golden apples that give three times the length extension, but disappear if you
don’t pick it up fast enough.
A score display to show the user how many apples they picked up.
Continuing on score, a high score table. And maybe store it in localStorage
so it persists?
Only start the game after the user gives some input after dying.
Mobile support is atrocious, try to make it playable on an iPhone!
Maybe don’t using 1000 / snakeLength for timeout, as the game quickly
becomes impossible due to the speed. Try some other formula that is more
fair.
Si candidato-> Casado pregunte el ingreso del Candidato y si este tiene ingreso<4500 el sistema
pregunte el ingreso de su esposo y se le sume y el importe de la suma debe de ser >= 5,500
E
gh the code is extensible enough that adding more shouldn’t be an issue.
Have fun!
prefix
infix
postfix
Prefix / Scheme
+ *5 8 + 5 - 1 4
Results
Tick Input Output Rest Error
// known operators
var operators = {
'+': function(a, b) { return a + b; },
'-': function(a, b) { return a - b; },
'*': function(a, b) { return a * b; },
'/': function(a, b) { return a / b; },
};
// return at my level
return {
value: f(left.value, right.value),
rest: right.rest
};
}
}
step(input);
Infix
4 + 2 *7 - 1 *5 +
Results
Tick Command Reducing Error
// known operators
var operators = {
'+': function(a, b) { return a + b; },
'-': function(a, b) { return a - b; },
'*': function(a, b) { return a * b; },
'/': function(a, b) { return a / b; },
};
var precedence = [
['*', '/'],
['+', '-']
]
newInput.push("" + f(
parseFloat(input[reduceAt - 1]),
parseFloat(input[reduceAt + 1])
));
input = newInput;
}
Postfix / RPN
4 2 7 *+ 1 6 - 5 *
Results
Tick Command Stack Error
// known operators
var operators = {
'+': function(a, b) { return a + b; },
'-': function(a, b) { return a - b; },
'*': function(a, b) { return a * b; },
'/': function(a, b) { return a / b; },
};
// known operator
if (cmd in operators) {
// get the function
var f = operators[cmd];
// sanity check
if (stack.length < f.length) {
error = 'not enough arguments';
break;
}
Hopefully this helps give some insight into what I was talking about in yesterday’s post. I
have to admit, I’m actually starting to like Javascript. It’s a bit strange at times, but it does
have a nice functional flavor which is always fun.
If you’d like to download the entire source code, you can do so here: source code
// known operators
var operators = {
'+': function(a, b) { return a + b; },
'-': function(a, b) { return a - b; },
'*': function(a, b) { return a * b; },
'/': function(a, b) { return a / b; },
};
var precedence = [
['*', '/'],
['+', '-']
]
// evalute a list assuming prefix notation
function evalPrefix(input) {
// process at this level
// return the result of this level and what we didn't use
// return a null value if we fail at any point
function step(current) {
// directly return numbers
if (!isNaN(parseFloat(current[0]))) {
return {
value: parseFloat(current[0]),
rest: current.slice(1)
};
}
// otherwise, we're going to have to recur
else {
var f = operators[current[0]];
// recur for left, use that rest for right
var left = step(current.slice(1));
if (left.value == null) return {value: null, rest: []};
var right = step(left.rest);
if (right.value == null) return {value: null, rest: []};
// return at my level
return {
value: f(left.value, right.value),
rest: right.rest
};
}
}
return step(input).value;
}
// evaluate a list assuming infix notation
function evalInfix(input) {
// process until we are done
while (input.length > 1) {
// find the first operator at the lowest level
var reduceAt = 0;
var found = false;
for (var i = 0; i < precedence.length; i++) {
for (var j = 1; j < input.length - 1; j++) {
if ($.inArray(input[j], precedence[i]) >= 0) {
reduceAt = j;
found = true;
break;
}
}
if (found) break;
}
// if we didn't find one, bail
if (!found) return;
// otherwise, reduce that operator
var newInput = [];
var f = operators[input[reduceAt]];
for (var i = 0; i < reduceAt - 1; i++)
newInput.push(input[i]);
newInput.push("" + f(
parseFloat(input[reduceAt - 1]),
parseFloat(input[reduceAt + 1])
));
for (var i = reduceAt + 2; i < input.length; i++)
newInput.push(input[i]);
input = newInput;
}
return input;
}
// evaluate a list assuming postfix notation
function evalPostfix(intpu) {
// run through all commands in the input
var stack = [];
for (var i = 0; i < input.length; i++) {
var cmd = input[i];
// known operator
if (cmd in operators) {
// get the function
var f = operators[cmd];
// sanity check
if (stack.length < f.length) {
error = 'not enough arguments';
break;
}
// get the correct number of arguments
var args = [];
for (var j = 0; j < f.length; j++)
args.unshift(stack.shift());
// apply and push back onto the stack
// note: the first argument to apply is 'this'
stack.unshift(f.apply(undefined, args));
}
// anything else, push onto the stack as either a number or string
else {
stack.unshift(isNaN(parseFloat(cmd)) ? cmd : parseFloat(cmd));
}
}
return stack[0];
}