A simple snake game in pure HTML and JavaScript, part 2

You should first read part 1 of the tutorial.

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.

The game loop

First thing we’ll do is to code the game loop. The game loop is a generic term for the code that constantly runs in the background inside games. In this way, games are different from most other programs in that they require no input from the user to do things. Think about it, most computer programs do not do anything unless you click a key, move the mouse or tap a button. But games do something even when the user is just looking at the screen. The code that runs constantly in the background, is called the game loop.

To run the game loop, we will introduce a new function, that we call 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.

What we need to do next, 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 = '';
            }
        }
    }

    // This function calls itself, with a timeout of 1000 milliseconds
    setTimeout(gameLoop, 1000);
}

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.

View this in a new tab

Now that the initial snake is visible, we need to make it to move around the board.

Moving the snake

To make the snake move, we need to extend the game loop.

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:

    // Update position depending on which direction the snake is moving.
    switch (snakeDirection) {
        case 'Up':    snakeY--; break;
        case 'Down':  snakeY++; break;
        case 'Left':  snakeX--; break;
        case 'Right': snakeX++; break;
    }

    // Update the board at the new snake position
    board[snakeY][snakeX].snake = 1;

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.

When you run the code, this should be the result:

View this in a new tab
Most 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.

We create a new function to do this, called enterKey

    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:

<body onload="initGame()" onkeydown="enterKey(event)">

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;
        }

        // This prevents the arrow keys from scrolling the window
        event.preventDefault();
    }

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.

Try out controlling the snake in a new tab

The walls

Let’s fix that bug with the walls that makes the game crash, and we’ll have a contrallable 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.

// Check for walls, and restart if we collide with any
if (snakeX < 0 || snakeY < 0 || snakeX >= boardWidth || snakeY >= boardHeight) {
    startGame()
}

// Update the board at the new snake position
board[snakeY][snakeX].snake = 1;

This version of the game is now playable, without crashing. Try it out by clicking below and steering using the arrow keys:

View this in a new tab
Click the frame to fcosu. You can now steer the snake around using the arrow keys. The game will reset if you collide with the walls.

Of course, the snake still does not ‘disappear’ from the board as we move around. So one thing left on the todo list.

Making the snake disappear

To remove the snake, we need to keep track of the tail.

We can use the board to do this, 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.

We then simply decrease this as we iterate over the board.

We need to make a few small changes to accomodate this. First, we need to replace the assignments board[snakeY][snakeX].snake = 1; to assign the snakeLength instead. So find the two places with that code and replace with: board[snakeY][snakeX].snake = snakeLength;

Now, instead of just storing if there is a snake on the board, we store the remaining length of the snake on that cell.

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 className, but we also decrease the value of cell.snake by 1.

The update code inside the game loop will look like this:

var cell = board[y][x];

if (cell.snake > 0) {
    cell.element.className = 'snake';
    cell.snake -= 1;
}
else {
    cell.element.className = '';
}

Try out the result below, I also decreased the setTimeout call to 1000 / snakeLength so the game is faster:

View this in a new tab
You can now steer 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 part 3.

Progressive Web Apps on iOS are here

The fundamentals for making a PWA is finally supported on iOS. Maximiliano Firtman has wrote up the basic sets of technologies now supported (and a long list of what is still missing).

So you are right, you can now install apps on iOS without App Store approval. That’s probably one reason Apple didn’t mention at all about this new ability; they might not want to confuse users. Not even the release notes on Safari mention the technologies.

Overselling the abilties of PWAs on iOS slightly — it’s not all the way there to making a real app yet!

Creating a CloudSQL server instance from a database dump

For a project based on the Google Cloud Platform recently, one of the very first things that came up was how to easily set up a new database from scratch on the platform from the command line, so that we could automate it to quickly set up new development environments.

To be able to do this, you must install the Google Cloud Command Line Tools as well as login to your account on the command line.

Configuration

These are the variables that will be used for the script with reasonable defaults.

GCLOUD_DEV_PROJECT="$(gcloud config get-value project)"
CLOUDSQL_DEV_BUCKET="cloudsql-devbucket-$(whoami)"
CLOUDSQL_INSTANCE="cloudsql-dev-$(whoami)"
CLOUDSQL_DB_NAME=application

We need the name for a temporary Google Storage bucket to store the dump in, the name for the CloudSQL instance we should create, the Google Cloud project to fetch the data from (here we use the current project by calling the gcloud command line tool) as well as a name for the database to create on the SQL server.

Bucket to hold SQL database dump

To be able to import a SQL dump into CloudSQL, it needs to be uploaded to a Google Storage bucket. If you already have one for the project, you can reuse it. If not, create a new bucket to hold this data (change the region eu to your own):

gsutil mb -c nearline -l eu \
    -p $GCLOUD_DEV_PROJECT gs://$CLOUDSQL_DEV_BUCKET

Then to copy your local database dump (we assume the name is: a_mysql_dump.sql) up to Google Storage, run the following command:

gsutil cp a_mysql_dump.sql gs://$CLOUDSQL_DEV_BUCKET/dump.sql

Create SQL instance and database

To create the database, you use the create command from the gcloud command line tools. Here you specify the instance size, what region the instance should be in, and what version of MySQL (or Postgres) the server should run. You can easily change this to match your needs.

gcloud sql instances create $CLOUDSQL_INSTANCE \
    --tier=db-g1-small \
    --region=europe-west3 \
    --database-version=MYSQL_5_7

Then to create the database on the server, you need to run another command.

gcloud sql databases create $CLOUDSQL_DB_NAME \
    --instance $CLOUDSQL_INSTANCE

If you are doing the setup from a script, it usually takes 10-15 seconds before the instance accepts incoming SQL connections. So add a sleep 20 timeout after this command if you are making a script.

You should also set a root password that you can use to login in to the instance;

gcloud sql users set-password root % \
    --instance $CLOUDSQL_INSTANCE --password "AGreatPassword"

You can use this command to set the password for any SQL user, simple change the root % to the user you wish to update.

Give access to service account

You need to give access to the service account on the CloudSQL server, to the storage bucket where the database dump is stored. You do this by first fetching the service account email of the SQL server:

# This gives you email with a some extra text
gcloud sql instances describe $CLOUDSQL_INSTANCE | grep serviceAccountEmailAddress

# This will only give you the email (using cut)
MYSQL_SERVICE_ACCOUNT=$(gcloud sql instances describe $CLOUDSQL_INSTANCE | grep serviceAccountEmailAddress | cut -d" " -f 2)

The email will be some gibberish like: lh6srs65thehhcwv22hv33jygu@dirty-banana-80.iam.gserviceaccount.com

To grant access to the bucket, run the following two commands. You need to give write access to the bucket, and read access to the database dump file.

gsutil acl ch -u $(MYSQL_SERVICE_ACCOUNT):W gs://$CLOUDSQL_DEV_BUCKET
gsutil acl ch -u $(MYSQL_SERVICE_ACCOUNT):R gs://$CLOUDSQL_DEV_BUCKET/dump.sql

(it will not work without write access to the bucket, belive me I’ve tried)

Import database

After all this setup, we are finally ready to import the dump into the CloudSQL database. This is done by running gcloud sql import pointing it the the file we uploaded to the storage bucket:

yes | gcloud sql import sql $CLOUDSQL_INSTANCE $MYSQL_IMPORT_DUMP

If this results in the incredibly unhelpful ERROR: (gcloud.sql.import.sql) ERROR_RDBMS this means there is an error in your SQL database file (or that you did not grant the proper permissions to the Google Storage bucket). Unfortunately there is no real way to get the actual error from the server. You can try importing the dump to a local MySQL server to see the error.

And that’s it! You should now have a fresh CloudSQL server, with your database dump loaded. Enjoy!

A simple snake game in pure HTML and JavaScript, part 1

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.

Getting started with Snake

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>

View this in a new tab

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.

Creating the board

We’ll start out by creating the game board.

The board will be represented as a two-dimensional array, that is, an array of arrays. We create this as a constant variable inside the script tag called board, we also define the width and height of the board with two constant variables. Here I have arbitrarily picked 26 by 16 cells (I know this to be a convenient size to fit on screen).

<script>
const board = [];
const boardWidth = 26, boardHeight = 16;

function initGame() {
    // Logic to come
}
</script>

To make it simple, we will 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.

For event cell, we create a div element on the board via document.createElement. Then we add this cell to the boardElement, meaning every cell will have a matching div in the DOM.

function initGame() {
    const boardElement = document.getElementById('board');

    for (var y = 0; y < boardHeight; ++y) {
        for (var x = 0; x < boardWidth; ++x) {
            var cell = {};
            
            // Create a <div></div> and store it in the cell object
            cell.element = document.createElement('div');
            
            // Add it to the board
            boardElement.appendChild(cell.element);
        }
    }
}

You can use the developer tools for Chrome to look at the document structure, and you will see all the elements.

Screenshot of a very long list of div elements in the Chrome dev tools.
There should be 26 * 16 = 416 div elements in this list.

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 pushing 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 = {};
            
            // Create a <div></div> and store it in the cell object
            cell.element = document.createElement('div');
            
            // Add it to the board
            boardElement.appendChild(cell.element);

            // Add to list of all
            row.push(cell);
        }

        // Add this row to the board
        board.push(row);
    }
}

However, if you run this code, nothing will show up. This is because div elements by default are invisible, we need to give the some styling to make them visible on the page.

To make the divs visible, we assign a black background color, a width and a height. The styling will be done with the selector #board div, to target the all the divs we created with the code above.

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 change the display property to inline-block (the default is display: block). This changes the layout so that the blocks are laid out in a line (like text).

#board div {
    background-color: black;
    border: 1px solid grey;
    box-sizing: border-box;
    display: inline-block;
    width: 24px;
    height: 24px;
}

View this in a new tab

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;
}

We now have a black grid for the snake game!

View this in a new tab

It is not very interesting yet though, because we don’t have any snake on the game board to control.

Keep track of the game state

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 — How long is the snake in segments?

Let’s create global variables for these properties.

// Add this after the board definition
var snakeX;
var snakeY;
var snakeDirection;
var snakeLength;

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).

// Update this inside the initGame function
var cell = {
    snake: 0
};

Start the game

We also need to give some default values to the snake properties. We’ll do that in a new startGame function that. This function will set all the properties to default values 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';

    // Set the center of the board to contain a snake
    board[snakeY][snakeX].snake = 1;
}

Why do we create new function for this, and not just give default values to the global variables? Because we also need to do the same thing when the player dies to reset the game state.

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.

Continue with part 2 of the tutorial.

The Nightmare Letter: A Subject Access Request under GDPR

The demands placed in this letter, does in no way seem unreasonable or demanding. A user should have the right to his or her data, as well as be informed about how it is used.

That we finally get this right enforced by law is very welcome. Regulation is not always bad, this will force some semi-shady companies like Facebook and LinkedIn to be more open about their intentions.

Soulless

The homepod was announced as shipping in two weeks yesterday. The product looks great, and allegedly delivers some amazing sound for it’s slim size. But I can’t help but be struck by how soulless Apple’s press photos are for the product.

Black HomePod speaker on a white shelf, white a white wall behind it.
Fill your home with love.

It’s so grey and bleak. Press photos for the Apple Watch and the iPhone are very human, with focus on how people love and use this product. But for the HomePod, it’s all sterile, white and black, with nothing but white shadows to indicate the presence of anything. Is the product only useable in Jony Ive’s White World?

HomePod in white room, with waves in the air indicating sound.
It's so engaging in the White World.

I hope the music it renders has more life than the marketing, and if previous generation of Apple products are any indication, it will.

Write tests, not too many and mostly integration

This reflects my own view of testing. Namely, that the most important thing with a test is to detect and prevent mistakes.

Most mistakes are not made on the unit-test level (save for solving algorithmic problems). Rather it is when plumbing things together into a whole, that things fail. It is much more common to break a site through a REST API change or through an an integration with a third-party.

subscribe via RSS