My First HTML5 Game

 Are you ready? For our first tutorial we will make an online mass multiplayer Legend of Zelda in the style of TT Games Lego Edition.

You know, while that sounds unbeatably cool, I think we should start with something a little bit easier. We will actually make a game that nobody has ever heard of. It is a game I like to call, Tic Tac Toe.

To begin, you will need a plain text editor, like Notepad, and a web browser. If you are reading this, then I know you have a web browser.

Here is an example of what we want to make.


Your browser does not support the canvas element.

Let's begin. Create a new text document and name it TicTacToe.html.

To set up the canvas element, edit your new document by adding in this code.

<!doctype html>
<html>
<head>
<title>Tic Tac Toe</title>
</head>
<body>
<canvas id="canvas" width="400" height="500">Your browser does not support the canvas element.</canvas>
</body>
</html>


 Next, we need to create a background image. I used Blender and Gimp to make mine, but you can use any art program you like. I saved my background image as tictactoe.png, and this will serve as our playing board. Try to make your image the same size as the canvas element.

Let's add some script to our document to draw our background image to the canvas element.

<!doctype html>
<html>
<head>
<title>Tic Tac Toe</title>
</head>
<body>
<canvas id="canvas" width="400" height="500">Your browser does not support the canvas element.</canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const board = new Image();
  board.src = "tictactoe.png";
  board.width = "400";
  board.height = "500";
  board.onload = function() {
    drawBoard();
  };
function drawBoard() {
  ctx.drawImage(board, 0, 0);
}
</script>
</body>

You can see inside a script element, I  added a constant variable named canvas. It refers to the canvas element. Then I added a constant variable called ctx. It refers to the canvas's 2d API. Last I added a constant variable called board. It is my image file.

I defined some attributes, src which is the URL and file name, width, height, and a function call when the load event is performed. It calls a function I wrote named drawBoard, which at this point only calls the API's built in drawImage function.

The three parameters of drawImage are the constant variable board, which is my image, and the x,y locations to draw the image, which I have set to 0 each, since I am simply filling the entire canvas with my image.

Now remember to test your code before moving on to the next part. Make sure all is working fine.

The next part of the game is to add game state variables. These will track things like, who's turn it is, and if a space on the board contains an X or an O.


<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

var space = ['b','b','b','b','b','b','b','b','b'];
var text = "It is x's turn";
var turn = 'x';
var turnCount = 0;
var gamePlay = true;

const board = new Image();
  board.src = "tictactoe.png";
  board.width = "400";
  board.height = "500";
  board.onload = function() {
    drawBoard();
  };

These variables are not constant. They will change values throughout the game play.

The first is an array called space. It has exactly nine elements. Each element will represent one of the nine spaces of the Tic Tac Toe board. Right now, each element has a value of 'b', which I used to mean a blank space.

Next I have a variable called text. It contains the message that I display under the Tic Tac Toe board. Right now it is telling who's turn it is.

The next variable is called turn, and I can't remember what that one is for.

But then we have turnCount. It will keep track of the number of spaces that have been taken, and will let us know when the game is over.

The last variable called gamePlay, will be set to false when the game is over.

Now let's write some text to the canvas.


const board = new Image();
  board.src = "tictactoe.png";
  board.width = "400";
  board.height = "500";
  board.onload = function() {
    drawBoard();
  };
  
function drawBoard() {
  ctx.drawImage(board, 0, 0);
  drawText();
}

function drawText() {
  ctx.textAlign = "center";
  ctx.fillStyle = "black";
  ctx.fillText(text, 200, 460);
}

</script>
</body>

We write a new function called drawText. We will make the call to drawText from inside our drawBoard function.

Let's talk about what the drawText function does.

We are taking advantage of the built in 2d API here. The ctx I keep referring to is my constant variable from up above that grants access to the API. The textAlign variable is set to center so our text will be balanced at it's middle. The fillStyle is the color of course.

Now the fillText is a method that writes solid text. There are three parameters. The first is the text to display. I have a variable from up above called text which is storing the message, "It is x's turn." The next two parameters are the x and y locations of where to display the text. I am starting 200 pixels from the left side and 460 pixels down from the top. You may want to adjust those values depending on your background image.

And when we test the code... wow that looks terrible. Fantastic! But it seems I am not a fan of the default font, which is 10px sans-serif. It looked too small and very uninteresting to me.

So, this is the part where we change the font style and size. Add in this next line of code.


function drawText() {
  ctx.textAlign = "center";
  ctx.fillStyle = "black";
  ctx.font ="30px sans-serif";
  ctx.fillText(text, 200, 460);
}

Here we did not change the style, but we did at least make it a bit bigger.

What I would suggest is search the internet for free TTF files until you find one you like. A TTF file, or True Type Format, is a font file.

I found a very beautiful Victorian Theater font called Quentincaps-owxKz.ttf, but you will not get to see that font in my demo version up above. It seems blogger does not allow me to upload TTF files, so I ended up using one of their built in fonts for my demo, called Fredericka the Great. It is okay I suppose.

Once you find a font you like, you will need to write some code that lets you use your TTF file. Here is an example of mine.


<script>

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

//load a font
const f = new FontFace("MyFont", "url(Quentincaps-owxKz.ttf)");
f.load().then(function(font) {
  //Add font on the html page
  document.fonts.add(font);
});

var space = ['b','b','b','b','b','b','b','b','b'];
var text = "It is x's turn";
var turn = 'x';
var turnCount = 0;
var gamePlay = true;

const board = new Image();
  board.src = "tictactoe.png";
  board.width = "400";
  board.height = "500";
  board.onload = function() {
    drawBoard();
  };
  
function drawBoard() {
  ctx.drawImage(board, 0, 0);
  drawText();
}

function drawText() {
  ctx.textAlign = "center";
  ctx.fillStyle = "black";
  ctx.font = "30px MyFont";
  ctx.fillText(text, 200, 460);
}

</script>

Ok, you will want to change the Quentincaps-owxKz.ttf to the name of your TTF file, but then you should be good to go.

Test your code. Does it look a lot better? If not, then you did something wrong. But I am moving on now.

What we want to do now, is make our game respond to mouse clicks. We will add an event attribute to the canvas element.

<body>
<canvas id="canvas" width="400" height="500" onclick="placeMove(event)">
Your browser does not support the canvas element.</canvas>

The onclick event attribute will call a function named placeMove when you click the canvas with your mouse. But we don't have a function named placeMove. So let's write one. In your script with your other functions add this.


function drawBoard() {
  ctx.drawImage(board, 0, 0);
  drawText();
}

function placeMove(event) {
  if(gamePlay == true) {
    alert(event.x + ", " + event.y);
  }
}

function drawText() {
  ctx.textAlign = "center";
  ctx.fillStyle = "black";
  ctx.font = "30px MyFont";
  ctx.fillText(text, 200, 460);
}

The first thing this function checks for, is if our variable called gamePlay is equal to true. If not then the game is over and the function does nothing. But if gamePlay is true, then we get an alert message.

The parameter of our placeMove function is event. It refers to the mouse click, and event is a keyword, I didn't pick that name myself. It is an [object PointerEvent].

Anyways, when you click in the canvas, we have an alert message that tells you where the x and y location coordinates are of that click that just happened. Test this code.

Did you notice any problems? I did. My canvas is only 400 pixels wide, but when I click on the very right edge of the canvas, it says 408. And the top left corner should be my 0, 0 location, but I am getting 8, 8. What is going on here?

So it seems the mouse click event information is in reference to the entire client area, not just the canvas element. And since the canvas element isn't shoved tight up against the top left corner of the window, I end up with that little bit of offset. We need to fix that. Try adding this to your function.

function placeMove(event) {
  if(gamePlay == true) {
    let rect = canvas.getBoundingClientRect();
    let locX = event.clientX - rect.left;
    let locY = event.clientY - rect.top;
    alert(locX + ", " + locY);
  }
}

Now test your code. Isn't that better? All we did was subtract the location of the canvas from the event data.

Now that we know we have good location values, it is time to do something useful with them, like determine which space on the game board the event took place in. Let's try this.

function placeMove(event) {
  if(gamePlay == true) {
    let rect = canvas.getBoundingClientRect();
    let locX = event.clientX - rect.left;
    let locY = event.clientY - rect.top;
    if(locX < 130 && locY < 130) {
      alert("space 0");
    }
  }

The coordinates I supplied in this if statement match with my game board image. You may have to alter the values to match your game board. But these coordinates aim at the top left space on the game board. Now if you try to click anywhere in the game board, you only get an alert message if it is over the top left space.

Let's make an X appear in that space.

To do this, we will change the value of the first element in our array variable called space, from 'b' to 'x'. Then write a function that draws the value of that element over the game board image.

function placeMove(event) {
  if(gamePlay == true) {
    let rect = canvas.getBoundingClientRect();
    let locX = event.clientX - rect.left;
    let locY = event.clientY - rect.top;
    if(locX < 130 && locY < 130) {
      if(space[0] == 'b') {
        space[0] = turn;
      }
    }
    drawBoard();
  }

First we check to make sure that space[0] is blank. Then we assign the value of turn to space[0]

Notice after changing the value of our array, we call drawBoard again. Let's change the drawBoard function.

function drawBoard() {
  ctx.drawImage(board, 0, 0);
  drawPicks();
  drawText();
}

We added a call to drawPicks. Let's write a drawPicks function into our script. 

function drawPicks() {
  ctx.textAlign = "start";
  ctx.font = "90px MyFont";
  ctx.fillStyle = "black";
  if(space[0] != 'b') {
    ctx.fillText(space[0], 60, 120);
  }
}

This function checks that space[0] is not blank. Then draws the value of space[0] over our game board. 

Placing the X into that space means it is now O's turn. We need to change the value of turn and text.

 Time to write another function.

function nextTurn() {
  if(turn == 'x') {
    turn = 'o';
    text = "It is o's turn";
  }
  else {
    turn = 'x';
    text = "It is x's turn";
  }
}

This function makes sure our text variable is displaying the correct turn information and that the value of turn gets updated.

Now add a call to nextTurn in our placeMove function.

function placeMove(event) {
  if(gamePlay == true) {
    let rect = canvas.getBoundingClientRect();
    let locX = event.clientX - rect.left;
    let locY = event.clientY - rect.top;
    if(locX < 130 && locY < 130) {
      if(space[0] == 'b') {
        space[0] = turn;
        nextTurn();
      }
    }
    drawBoard();
  }
}

Test this code and we should see that when we click on the top left space, we place an X there and it is now O's turn.

Now we know the entire process for picking a space. When we set it up for all nine spaces, we get code that looks something like this.

<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

//load a the font
const f = new FontFace("MyFont", "url(Quentincaps-owxKz.ttf)");
f.load().then(function(font) {
  //Add font on the html page
  document.fonts.add(font);
});

var space = ['b','b','b','b','b','b','b','b','b'];
var text = "It is x's turn";
var turn = 'x';
var turnCount = 0;
var gamePlay = true;

const board = new Image();
board.src = "tictactoe.png";
board.width = "400";
board.height = "500";
board.onload = function() {
  drawBoard();
};

function drawBoard() {
  ctx.drawImage(board, 0, 0);
  drawPicks();
  drawText();
}

function placeMove(event) {
  if(gamePlay == true) {
    let rect = canvas.getBoundingClientRect();
    let locX = event.clientX - rect.left;
    let locY = event.clientY - rect.top;
    if(locX < 130 && locY < 130) {
      if(space[0] == 'b') {
        space[0] = turn;
        nextTurn();
      }
    }
    if(locX > 145 && locX < 255 && locY < 130) {
      if(space[1] == 'b') {
        space[1] = turn;
        nextTurn();
      }
    }
    if(locX > 275 && locY < 130) {
      if(space[2] == 'b') {
        space[2] = turn;
        nextTurn();
      }
    }
    if(locX < 130 && locY > 148 && locY < 262) {
      if(space[3] == 'b') {
        space[3] = turn;
        nextTurn();
      }
    }
    if(locX > 145 && locX < 255 && locY > 148 && locY < 262) {
      if(space[4] == 'b') {
        space[4] = turn;
        nextTurn();
      }
    }
    if(locX > 275 && locY > 148 && locY < 262) {
      if(space[5] == 'b') {
        space[5] = turn;
        nextTurn();
      }
    }
    if(locX < 130 && locY > 280) {
      if(space[6] == 'b') {
        space[6] = turn;
        nextTurn();
      }
    }
    if(locX > 145 && locX < 255 && locY > 280) {
      if(space[7] == 'b') {
        space[7] = turn;
        nextTurn();
      }
    }
    if(locX > 275 && locY > 280) {
      if(space[8] == 'b') {
        space[8] = turn;
        nextTurn();
      }
    }
    drawBoard();
  }
}

function nextTurn() {
  if(turn == 'x') {
    turn = 'o';
    text = "It is o's turn";
  }
  else {
    turn = 'x';
    text = "It is x's turn";
  }
}

function drawPicks() {
  ctx.textAlign = "start";
  ctx.font = "90px MyFont";
  ctx.fillStyle = "black";
  if(space[0] != 'b') {
    ctx.fillText(space[0], 60, 120);
  }
  if(space[1] != 'b') {
    ctx.fillText(space[1], 180, 120);
  }
  if(space[2] != 'b') {
    ctx.fillText(space[2], 300, 120);
  }
  if(space[3] != 'b') {
    ctx.fillText(space[3], 60, 250);
  }
  if(space[4] != 'b') {
    ctx.fillText(space[4], 180, 250);
  }
  if(space[5] != 'b') {
    ctx.fillText(space[5], 300, 250);
  }
  if(space[6] != 'b') {
    ctx.fillText(space[6], 60, 380);
  }
  if(space[7] != 'b') {
    ctx.fillText(space[7], 180, 380);
  }
  if(space[8] != 'b') {
    ctx.fillText(space[8], 300, 380);
  }
}

function drawText() {
  ctx.textAlign = "center";
  ctx.fillStyle = "black";
  ctx.font = "30px MyFont";
  ctx.fillText(text, 200, 460);
}

</script>

The last thing to do is write some code that knows when the game is over and if someone has won or not.

Let's write a function called checkForWin.

function checkForWin() {
  ctx.textAlign = "start";
  ctx.font = "90px MyFont";
  ctx.fillStyle = "red";
  //Horizontal wins
  if(space[0] != 'b' && space[0] == space[1] && space[0] == space[2]) {
    text = "The " + space[0] + "'s win";
    ctx.fillText(space[0], 60, 120);
    ctx.fillText(space[1], 180, 120);
    ctx.fillText(space[2], 300, 120);
    gamePlay = false;
    return;
  }
  if(space[3] != 'b' && space[3] == space[4] && space[3] == space[5]) {
    text = "The " + space[3] + "'s win";
    ctx.fillText(space[3], 60, 250);
    ctx.fillText(space[4], 180, 250);
    ctx.fillText(space[5], 300, 250);
    gamePlay = false;
    return;
  }
  if(space[6] != 'b' && space[6] == space[7] && space[6] == space[8]) {
    text = "The " + space[6] + "'s win";
    ctx.fillText(space[6], 60, 380);
    ctx.fillText(space[7], 180, 380);
    ctx.fillText(space[8], 300, 380);
    gamePlay = false;
    return;
  }
  //Verticle wins
  if(space[0] != 'b' && space[0] == space[3] && space[0] == space[6]) {
    text = "The " + space[0] + "'s win";
    ctx.fillText(space[0], 60, 120);
    ctx.fillText(space[3], 60, 250);
    ctx.fillText(space[6], 60, 380);
    gamePlay = false;
    return;
  }
  if(space[1] != 'b' && space[1] == space[4] && space[1] == space[7]) {
    text = "The " + space[1] + "'s win";
    ctx.fillText(space[1], 180, 120);
    ctx.fillText(space[4], 180, 250);
    ctx.fillText(space[7], 180, 380);
    gamePlay = false;
    return;
  }
  if(space[2] != 'b' && space[2] == space[5] && space[2] == space[8]) {
    text = "The " + space[2] + "'s win";
    ctx.fillText(space[2], 300, 120);
    ctx.fillText(space[5], 300, 250);
    ctx.fillText(space[8], 300, 380);
    gamePlay = false;
    return;
  }
  //diagonal wins
  if(space[0] != 'b' && space[0] == space[4] && space[0] == space[8]) {
    text = "The " + space[0] + "'s win";
    ctx.fillText(space[0], 60, 120);
    ctx.fillText(space[4], 180, 250);
    ctx.fillText(space[8], 300, 380);
    gamePlay = false;
    return;
  }
  if(space[6] != 'b' && space[6] == space[4] && space[6] == space[2]) {
    text = "The " + space[6] + "'s win";
    ctx.fillText(space[6], 60, 380);
    ctx.fillText(space[4], 180, 250);
    ctx.fillText(space[2], 300, 120);
    gamePlay = false;
    return;
  }
}

This function checks the top row first, then the middle row, and then the bottom row. Then it checks the vertical columns for three in a row. And finally it checks diagonally for three in a row.

Now the game knows when someone has won, but it does not know when the game ends in a tie. Let's add this to our code inside the function called nextTurn.

function nextTurn() {
  if(turn == 'x') {
    turn = 'o';
    text = "It is o's turn";
  }
  else {
    turn = 'x';
    text = "It is x's turn";
  }
  turnCount++;
  if(turnCount > 8) {
    text = "It's a tie";
  }
}

Now test your game. It should be complete.

Thank you for reading this really long tutorial. I hope you had fun making your first HTML5 game.

Join me for my next tutorial as we turn this two player Tic Tac Toe game into a one player game against the computer's AI. I think it will be fun to teach the computer how to play Tic Tac Toe.

Next Topic: Adding Some AI

 


Comments

Popular Posts