Making a Simple Game using HTML Canvas and JavaScript
Tags: javascript, webdev, html
Published: 2020-06-11 07:53:44

## Intro
Today we are going to take a look at how to use HTML5 Canvas and JavaScript to make a simple game that I made up. Now, I am sure this is not a totally original game by any means, but it is simple and straightforward. The basics of the game are that you have an undirected graph of nodes. One node starts with all of the values (let’s say they are sour cherry candies -- it's totally arbitrary) and we need to distribute all of the candies evenly to each node of the graph. For example if we have 8 nodes and 16 candies we will need to ensure that each node will receive two candies each.

## Getting Started
I have taken the liberty of making some starting code for you so that we don’t have to work on all of the smallest details, and we can get to making a game faster on [my github.](https://github.com/alexei-dulub/canvas_demo_starter.git)

```bash
git clone https://github.com/alexei-dulub/canvas_demo_starter.git
```

We will be using `http-server` to help us serve our files. If you don’t already have it you can use the following to install it:

```bash
npm install http-server -g
``` 

> Note: if you don't have npm either you can find out how [here](https://www.npmjs.com/get-npm).

This will install everything necessary. Now to get it running you can simply run 

```bash
http-server -p 80
```

You should now be able to connect to `localhost` in your browser which should be displaying the infamous ‘hello world’

## But wait… what does this do?

Glad you asked. Let’s start from the foundation of everything -- the HTML.

```html
<!DOCTYPE html>
 <html>
     <body>
        <script type='module' src='game.js'></script>
     </body>
 </html> 
 ```

 Here we can see we have a very simple HTML skeleton that really only has one import line:

 ```html
<script type='module' src='game.js'></script>
 ```

 This line allows the web page to use the scripts we will be writing throughout the rest of this tutorial. Shall we look at those now? Let's start with the `game.js` file since it is the one we call to in the HTML:

 ```javascript
 import { Logic } from './Logic.js'

 const l = new Logic() 
 ```

 All this file is doing is kicking off the logic of our program by using [ES6 imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) so that we can create a new instance of the `Logic` class. We will look at what that means here in a second, but yeah, we could have done this in the `Logic.js` file imported here; however, if we had a more complicated usecase it is a good idea to separate our setup and our core game logic. So, let's see what we are importing here:

 ```javascript
 export class Logic {
     constructor() {
        this.lastUpdate = performance.now()

        this.updateRate = 32

        this.canvas = document.createElement('canvas')
        this.ctx = this.canvas.getContext('2d')
        this.canvas.width = window.innerWidth
        this.canvas.height = window.innerHeight

        this.update = this.update.bind(this)

        document.body.insertBefore(this.canvas, document.body.childNodes[0])
        this.rAF = requestAnimationFrame(this.update)
     }

     update() {
        if (this.lastUpdate < performance.now() - this.updateRate) {
        this.lastUpdate = performance.now()

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
        this.ctx.font = '90px Arial'
        this.ctx.fillStyle = 'black'
        this.ctx.fillText('hello world', 0, 90)
        }
        this.rAF = requestAnimationFrame(this.update)
     }
 }
```

> Note: `this.update = this.update.bind(this)` allows us to call `this` inside of `update()`

Here first thing you can see is that we are exporting this class. This is what allows us to import it as we saw was the case in `game.js`. Next we have the `constructor()` function which goes all of the initialization of the game logic to run. What is really to note is the following line:

```javascript
this.canvas = document.createElement('canvas')
this.ctx = this.canvas.getContext('2d')
this.canvas.width = window.innerWidth
this.canvas.height = window.innerHeight
```

What we are seeing in these few lines is firstly the creation of the HTML5 Canvas we will be using as our art medium for the duration of this tutorial (and if I have done my part properly for time to come). If you recall there was no `<canvas>` tag in the HTML we made. That's because we made it here!

Next you will see that we are making use of our newly created canvas to `getContext` and said context will be 2 dimensional. The '2d' part isn't important right now, but I'm sure you can probably [guess what it's doing](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext). Then we make use of some built-in JavaScript attributes by setting canvas width and height to that of our browser window.

> Note: we will not be covering resizing here so make sure that you reload if your canvas ever looks funny.

Lastly we need to insert the new element into the HTML and we do so with the following:

```javascript
document.body.insertBefore(this.canvas, document.body.childNodes[0])
```

Now that we have a canvas to play with we can start to examine how we are able to print 'Hello PixelPlex' to the canvas.

```javascript
update() {
    if (this.lastUpdate < performance.now() - this.updateRate) {
    this.lastUpdate = performance.now()

    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
    this.ctx.font = '90px Arial'
    this.ctx.fillStyle = 'black'
    this.ctx.fillText('Hello PixelPlex', 0, 90)
    }
    this.rAF = requestAnimationFrame(this.update)
}
```

This `update()` function is the proverbial heart of our logic as it is what pumps life into any sort of games or animations we create on canvas. While the game we are making today doesn't have a lot of animation (none really) that can easily be changed since we have already given our game this loop.

What is happening here is we have an `if` statement that is checking whether it is time for the script to have canvas render all of the things we want to render. This is how we can manager the smoothness and timing of things in canvas and you can just think of it for now as our 'frames per second.' So, when the statement evaluates to true is when we can render new stuff. This is when the canvas tutorial really begins!

```javascript
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
this.ctx.font = '90px Arial'
this.ctx.fillStyle = 'black'
this.ctx.fillText('hello PixelPlex', 0, 90)
```

Let's start by addressing that we are using the `context` we got earlier in the constructor to do our work. Any changes you want to make to the canvas are really done to its `context` and not to the canvas directly. And you can see that the first thing we are doing is clearing a rectangle starting at the points `(0, 0)` and the size of it is the same size as the canvas. In other words we cleared the entire screen. This is very important as canvas does not do this for you. If you neglect this line you will notice lots of overlap of elements and things especially when you make more complex things.

The next few lines are more straightforward. You can see we are setting the `font` and then the `fillStyle` (which really just means what color you want to fill with), and the lastly we use the `fillText` function that is given what the text will be as well as an `(x, y)` to place the **bottom left corner** of the text. Notice the bolding there. Placing things in canvas is an art and it will be confusing at first when you use this function but it might not be showing up. That could be because of which part canvas uses of the text to place at your desired `(x, y)` so just be wary.

The last thing to cover before we can get started is the last line we see in our `update()` function:

```javascript
this.rAF = requestAnimationFrame(this.update)
```

The sharp of sight out there may notice that this line does not only come from the `update()` function, but also the `constructor()` as well... The reason we find it in the `constructor()` is because what this line does is start the entire loop process. That is why we pass is the `this.update` since we are wanting to use that function for each animation frame. This is also the same reason it is call every time at the end of the `update()` function (regardless of the evaluation of the `if` statement). We call it an animation loop and it can't be a loop if it doesn't loop, right? Basically we need to call the `update()` function at the end of the `update()` function so that it is called over and over again until the end of time or we leave the page. All of that combined is what gives us that foundation of a game using HTML Canvas!

Now... that was a lot if you have never done any of that and are still with me. Pat yourself on the back, and take a quick break by playing around with some of the values in either the `update()` (e.g. play around with where the text is rendered or what it says or what color it is!) and maybe play around with something in the constructor (e.g. what happens if you change the `updateRate` higher or lower?). Try some of those things out, and I will see you in the second half of the tutorial!

## We'll Start Making a Game Now, I Promise

As a recap we are working on making a game that involves distributing value amongst a set of nodes (in our case 8). The nodes are connected to make an undirected graph meaning that the value can flow from either connected node. So, let's start by getting some nodes on our canvas, shall we?

```javascript
export class Node {
    constructor(ctx, x, y) {
        this.ctx = ctx
        this.x = x
        this.y = y
        this.fill = 'red'
        this.size = 50
    }

    draw() {
        this.ctx.fillStyle = this.fill
        this.ctx.beginPath()
        this.ctx.rect(this.x, this.y, this.size, this.size)
        this.ctx.fill()
    }
} 
```

We will start by making a new `Node` class to use. This class will serve as a place for us to implement any functionality we want the nodes to have later on. We see familiar elements such as the `constructor()` but something that is a little different here is the `draw()` function. This is what is to be called inside of the `update()` function in our animation loop. `draw()` is where we define how we want the node to look, and if we want that look to be dynamic we use lots of variables and calls to attributes of `this` so as a `Node` instance changes it will be reflected every time a new frame is rendered. In our draw we are drawing a red rectangle. The process is similar to drawing the 'hello world' from earlier. Since this is supposed to be called in the `update()` function of our `Logic` let's add that now.

```javascript
update() {
    if (this.lastUpdate < performance.now() - this.updateRate) {
        this.lastUpdate = performance.now()

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

        let node = new Node(this.ctx, 200, 200)
        node.draw()
    }
    this.rAF = requestAnimationFrame(this.update);
}
```

The `update()` function in `Logic.js` has replaced the text with making a new `Node` instance and then calling that instance's `draw()` function. Now we should see one singular node (red rectangle) at `(200, 200)`. But we need 7 more nodes in order to make our graph, so let's change that.

```javascript
import { Node } from './Node.js'

constructor() {
    ...;

    this.nodes = []    

    ...;

    this.makeGraph()

    ...;
}

makeGraph() {
    for(let x = 0; x < 8; x++) {
        this.nodes.push(new Node(this.ctx, 100*x, 100))
    }
}
```
> Note: Only the changes to the `constructor()` are show in the snippet above.

In the `constructor()` we have added a new `nodes` attribute for the `Logic` to keep track of, and then we made a new function which gets called after the creation of the array. In `makeGraph()` we are using a `for` loop to add 8 nodes to the array using the counter to change each one's location for visual verification (we can see them all).
Now we can change the drawing of one node to draw all eight of our freshly made nodes as follows:

```javascript
update() {
    ...;

    this.nodes.forEach(node => {
        node.draw()
        return
    })
    
    ...;
}
```

## Remeber the Unit Circle?

So, I won't quizzing on the Unit Circle per se, but we will be dusting off some trigonometry and using JavaScript's built in `Math` library. The formation we have for the nodes right now is all fine and dandy, but it wouldn't really make for a good graph so lets work on arranging the nodes in a circle so we can make some cool patterns later on.

```javascript
makeGraph() {
    let x = this.canvas.width/2
    let y = this.canvas.height/2
    let angle = 360/8
    for(let i = 1; i <= 8; i++) {
        let nX = x + this.radius * Math.cos((angle*i)*Math.PI/180)
        let nY = y + this.radius * Math.sin((angle*i)*Math.PI/180)
        this.nodes.push(new Node(this.ctx, nX, nY))
    }
}
```
> Note: `this.radius` is defined in the `constructor()` as `this.radius=200`

Above is our new example of the `makeGraph()` found in `Logic.js`. This will distribute 8 nodes evenly across a circle (and with some minor modifications it can take a dynamic number of nodes and still spread them evenly!). We start by locating the center of the canvas. We then divide 360 (degrees) by the number of nodes we would like to create. Then we have the `for` loop like before, but this time we the angle of the respective node (`angle*i`) converted into radians (`*Math.PI/180`) and then find the cos/sin and multiply it by the radius of 200. Then we add that value to the x/y to offset it from the center. These calculated values are then used as the location for each node. This functions will become really familiar if you continue to make more complex things in canvas especially of it involves rotation, and it makes for an easy was for something else on the canvas to track the player such as an enemy if it knows the angle between the player and itself. But that's something for another day.


## Connecting the Dots, I mean, Nodes...

Awesome, now that we have our nodes displaying in a somewhat interesting formation, let's connect them both visually and in memory. We will start by adding the following to the `constructor()`:

```javascript
this.connections = []
```

This will help us keep track of each node's connections later on when we start to transfer values. To start making use of this we will make the following function in our `Node` class:

```javascript
addConnection(connection) {
    this.connections.push(connection)
}
```

Simple but so elegant. All we are doing here is adding a `Node` to our connections collection (say that five times fast) so that we can operate on it later. Now that we have the `Nodes` connected via memory let's start to visually connect them on the canvas for our player.

```javascript
getX() {
    return this.x
}

getY() {
    return this.y
}

drawConnection(connection) {
    this.ctx.beginPath()
    this.ctx.moveTo(this.x+(this.size/2), this.y+(this.size/2))
    this.ctx.lineTo(connection.getX()+(this.size/2), connection.getY()+(this.size/2))
    this.ctx.stroke()
}

drawNode() {
    this.ctx.beginPath()
    this.ctx.rect(this.x, this.y, this.size, this.size)
    this.ctx.fill()
}
```
> Note: Changing the fill will come right after this part

This round we have added four new functions to our `Node` class. The first two simply return the `x` or `y` of the respective node, and you can see why when we examine the third function of `drawConnection()`. This is using the same draw pattern we have already seen with canvas but all it is draw is a straight, black line from our node to the connected node. And as you can see it is using the `getX()` and `getY()` we made too. Neat! 

Lastly is a `drawNode()` function which is purely for consistence and clarity. Since we are drawing the connections in their own function I thought it would make sense as well as look better to have the node be drawn in a separate function. You will find when you work on more complex projects that piecing our the rendering will make it easier to find when you want to make a change but the one class may have five moving parts and all of the rendering looks so similar it's hard to find what you're looking for. Sorry for the run on sentence, and no, I've never written anything messy...

That then brings us to what changes in the main `draw()` function now. With the above changes it looks like the following:

```javascript
draw() {
    this.connections.forEach(connection => {
        this.drawConnection(connection)
    })
    this.drawNode()
}
```

It comes down to a simple `forEach` loop on all of the `Node`'s connections and then calling our newly created `drawNode()` function. So, now that we have given the nodes the ability to make connections and draw them let's leverage that in our `Logic`.

```javascript
constructor() {
    ...;

    for(let i = 0; i < 8; i++) {
        this.nodes[i].addConnection(this.nodes[1])
        this.nodes[1].addConnection(this.nodes[i])
        this.nodes[i].addConnection(this.nodes[2])
        this.nodes[2].addConnection(this.nodes[i])
    }
}
```

What we have here is a `for` loop at the end of our `constructor()` and it is calling the `addConnection` function we just made. You may notice we are calling it twice for each relationship (simply swapping the index on the array). This is because we are making an undirected graph so we need both nodes to be aware of their mutual relationship; this is important. Now we should see that we have our nodes and they are connected by lines. Don't worry too much for now if some of the line appear to be over or under the nodes. This has to do with rendering order, and we won't focus on it today.

## Red Light, Green Light

Alright, we have a graph. How about we give the nodes a little more functionality since this is supposed to be a game after all. We will start by giving the player some sort of indication that they are heading in the correct direction? Let's have the nodes change color as they get closer to the solution of the puzzle.

```javascript
constructor(ctx, x, y, ideal) {
    ...;

    this.value = 0
    this.idealValue = ideal
}
```

Here we are changing the `constructor()` slightly so that we can tell the nodes what their ideal value will be, and we also have to track the node's journey to this ideal value by having a `value` attribute that is representative to what the player is trying to balance. And the astute out there will notice that this also means we will have to change how we make the nodes back in the `Logic.js` file.

```javascript
this.nodes.push(new Node(this.ctx, nX, nY, 2))
```

So, now that we have an idea of what value we want as well as what value we are currently at let's change the node's fill based on this new information:

```javascript
setFill() {
    if(this.value/this.idealValue < 0.33) {
        this.ctx.fillStyle = 'red'
    }
    else if(this.value/this.idealValue > 0.33 && this.value/this.idealValue < 0.66) {
        this.ctx.fillStyle = 'orange'
    }
    else if(this.value/this.idealValue > 0.66 && this.value/this.idealValue < 1) {
        this.ctx.fillStyle = 'yellow'
    }
    else if(this.value/this.idealValue === 1) {
        this.ctx.fillStyle = 'green'
    }
    else if(this.value/this.idealValue > 1) {
        this.ctx.fillStyle = 'purple'
    }
}
```

What we are doing here is a series of `if` statements that are looking to see what value the ratio the node's value is compared to its ideal value. So, if it is below 33% the node is red, between 33% and 66% it's orange, between 66% and 100% yellow (you're getting close), and if it is 100% meaning that the value *is* the ideal value then it will turn green. If it is over 100% meaning the node has too much value it is purple. Feel free to make your own color schema or even explore a way to make the coloring more gradual.

Now to make use fo this all we have to do is call `setFill()` in the `drawNode()` function.

```javascript
drawNode() {
    this.setFill()

    ...;
}
```

The way things are set up right now, the game can never be won (unless the ideal is zero then ¯\\\_(ツ)_/¯) but we will need a function so that we can at least set the value of one node to have enough value to solve the puzzle. In the `Node` class we make the following:

```javascript
setValue(val) {
    this.value = val
}
```

Then in the `Logic` class's `constructor()` we have the following line after the loop making the connections:

```javascript
this.nodes[0].setValue(16)
```

Now with all of that we should have mostly red nodes, but one of them will be purple since it 800% of the value it should.

## console.log('click!')

Now that we have most of our visuals set up we can start to add the controls for the player to interact with our masterpiece. We will start by editing the `Logic` class. Let's add the following two items to the `constructor()`:

```javascript
this.selectedNode = null

this.handleClick = this.handleClick.bind(this)

window.addEventListener('click', this.handleClick)
this.canvas.addEventListener('contextmenu', this.handleClick)
```

We've seen the binding to `this`, but something that is new is the `addEventListener`. This is built-in JavaScript, and it allows us to do what JavaScript does best: respond to events happening. What we are responding to here is the 'click' event as in the click of a mouse on our mouse on the browser window. Similarly we listen for the 'contextmenu' event on the canvas. What is that event you ask? It just means a right click on the canvas. If we didn't do this we wouldn't be able to right click. Okay, cool, but `handleClick` isn't a function of `Logic`... yet.

```javascript
handleClick(e) {
    let x = e.clientX
    let y = e.clientY

    if (e.button === 0) {
        this.nodes.forEach(node => {
            if (node.wasClicked(x, y)) {
                let selected = this.nodes.filter(n => n.isSelected)
                let toDeselect = selected[0] ? selected[0] : null
                if (toDeselect) toDeselect.deselect()
                node.select()
                this.selectedNode = node
            }
        })
    }
    else if (e.button === 2) {
        this.nodes.forEach(node => {
            if (node.wasClicked(x, y)) {
                if (this.selectedNode.getValue() > 0 && 
                    this.selectedNode.isConnection(node)) {
                         node.incrementValue()
                         this.selectedNode.decrementValue()
                     }
            }
        })
    }
}
```

In this function we are using the the event object passed to our function (an effect of adding an event listener) so we can know exactly where the user clicked on the window. We then have an `if` statement that will check where it was a left (0) or a right (2) click. If it is a left click we check to see if any of the nodes were selected (more later on). If one was clicked then we deselect the currently selected node, and make the clicked node the selected node. This is our functionality for selecting which node to transfer value from!

When it's a right click we see if a node was clicked. If one was clicked we then check if the selected node even has value to give and if so is the clicked node is a connection of the selected node. If a node passes all of these check the clicked node's value gets increased and the selected node's value will decrease. A transfer of values!

We have this logic implemented in, well, the `Logic` class but there were a lot of functions in there that the `Node` doesn't have. Let's change that. We will start by changing the `Node`'s `constructor()` one more time.

```javascript
constructor(id, ctx, x, y, ideal) {
    this.id = id

    this.isSelected = false

    ...;
}

getId() {
    return this.id
}

deselect() {
    this.isSelected = false
}
```

In order to better keep track of our connections we will need to give the nodes IDs and we will see the in a bit. And naturally that means we will have to also change where we create all of the nodes

```javascript
this.nodes.push(new Node(i, this.ctx, nX, nY, 2))
```
> Note: remember that this is in the `for` loop that declares all of the nodes so `i` is the value from the loop.

Next are mostly simple data manipulation functions:

```javascript
getValue() {
    return this.value
}

decrementValue() {
    this.value -= 1
    console.log(this.value)
}

incrementValue() {
    this.value += 1
    console.log(this.value)
}

isConnection(node) {
    return this.connections.filter(c => c.getId() === node.getId()).length === 1
}
```

The only thing worth noting from this block is the `isConnection()` function where we are returning a `boolean` by filtering the connections a node has which will return a new array with any values that evaluate to true based on the statement given as a parameter. We then compare the length of this 'returned' array (of which we don't actually assign it to) and of that length is 1 it means that the node passed to the function is truly a connection of the current node which results in the return of a `true` otherwise a `false` is returned.

## But How Do I Win?

We are almost there! But we need to make sure that the player knows that they have won. We will start by adding one final function to our `Node` class:

```javascript
isSatisfied() {
    return this.value/this.idealValue === 1
}
```

This will make sure we can check that all of our nodes are happy, because when they are we have achieved the win state. Let's make the `Logic` aware of that now by changing the `update()` function:

```javascript
update() {
    let playerWon = true
    if (this.lastUpdate < performance.now() - this.updateRate) {
        this.lastUpdate = performance.now()

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

        this.nodes.forEach(node => {
            if (playerWon) {
                playerWon = node.isSatisfied()
            }
            node.draw()
        })
        if (!playerWon) {
            this.ctx.fillStyle = 'black'
            this.ctx.font = "90px Arial"
            this.ctx.fillText("You Won!", this.canvas.width*.41, this.canvas.height*.1)
        }
        this.ctx.fillStyle = 'black'
        this.ctx.font = "30px Arial"
        this.ctx.fillText("Left Click to select a node. Right Click on a node to transfer value from the selected node. Try to turn all of the nodes green.", this.canvas.width*.18, this.canvas.height*.95)
    }
    this.rAF = requestAnimationFrame(this.update);
}
```

You can see that when we are rendering each node we also check if they are satisfied. If one node does not meet that qualification it will make the `playerWon` variable `false` meaning it will not display the win message. Conversely if all of the nodes are satisfied then it will remain `true` allowing for the message to be rendered thus informing the user of their victory. You will also notice a message that is alway rendered at the bottom so that we can give the user a bit of guidance on the controls. Feel free to change the message to what you like.

## Conclusion

And that is that! We have made a game using JavaScript and HTML5 Canvas, and that was really only the surface. We covered design, trig, programming practices, and lots of other JavaScripts tid bits. I hope this was a fun and enjoyable experience for you, and that you were also successful and inspired in creating this alongside the tutorial. If there were some bumps you can always check the finished version on my [github](https://github.com/alexei-dulub/canvas_demo). You can also check the commit history to examine my thought process it, and you will find that it closely resembles this tutorial.