Creating a simple AI game

pic

This is a quick and easy tutorial for making a simple AI game in HTML5: The famous Ping Pong Game. I made this game in a course in the university, and I would like to share with you my simplified code.

Let’s walk through game.js. You can also download and play here: pingpong.

 1 – Request Animation Frame

window.requestAnimFrame = (function(callback) { 
return window.requestAnimationFrame || 
window.webkitRequestAnimationFrame || 
window.mozRequestAnimationFrame || 
window.oRequestAnimationFrame || 
window.msRequestAnimationFrame || 
function(callback) { window.setTimeout(callback, 1000 / 60); }; 
}); 

All credits go to Paul Irish. This function requests a new animation frame each time interval according to the browser (cross-browser). I recommend that you check his website and read the explanation.
In short, without this function the game will animate slowly and poorly.
More info at: Mozilla Developer Network.

2 – Creating the canvas

var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 550;
canvas.height = 400;
document.body.appendChild(canvas);

The first thing we need to do is create a canvas element. This can be done either in HTML or in JavaScript. Here I did it in JavaScript. When we have the element we get a reference to its context, which we use to issue drawing commands. Then we set its dimensions, and add it to document so it’ll appear in the page. more on canvas

3 – Defining the graphics

// Background image
var bgReady = false;
var bgImage = new Image();
bgImage.onload = function () {
 bgReady = true;
};
bgImage.src = "images/background.png";

Every game needs some graphics, so let’s add up some images. This is done here in the most simple way, that’s why it’s just an Image and not a class.

We do this for all of the graphics,  we need: background, paddles and ball.

bgReady is used so we can draw the image when it’s safe, because drawing it before it’s fully loaded will throw an error. (the same goes to paddleReady and ballReady).

4 – Game Objects

var player_paddle = {
 speed: 5, //movement in pixels per second
 score: 0,
 x: 480,
 y: 100
}

var ai_paddle = {
 speed: 2, //this is the AI trick
 score: 0,
 x: 10,
 y: 100
}

var ball = {
 x: canvas.width / 2,
 y: canvas.height / 2,
 speed_X: 3,
 speed_Y: 3
}

Now we define some variables we’ll need to use later.
player_paddleis the paddle that the user is going to control, it has speed which is how fast it’ll move in pixels per second, score which is the score of the player and x and y that are the initial position of the paddle.
ai_paddle is the same thing used for the computer.
Lastly, ball has x and y like before with the addition of speed on both the x-axis and the y-axis, if we don’t do this the ball won’t move diagonally.

The Trick

As you can notice, the speed of the ai_paddle is less than the speed of the ball which itself is less than the speed of the player_paddle. This is the AI Trick. The computer knows the exact position of the ball so it can hit it back. But if this is so, the user won’t win. And what kind of game does not allow winning? That is why the speed of the computer’s paddle must be lower than the one of the ball and of the player_paddle, so it may seem that the computer can’t hit the ball but the truth is that the computer cannot follow up with the ball.

5 – Player Input

// Handle keyboard controls
var keysDown = {};

addEventListener("keydown", function (e) {
 keysDown[e.keyCode] = true;
}, false);

addEventListener("keyup", function (e) {
 delete keysDown[e.keyCode];
}, false);

Here we are storing the user input in a variable keysDown for later use in the game. keysDown  stores any event’s keyCode  (the keys, here it’s up and down). If a key code is in the object, the user is currently pressing that key.

6 – New Round

// Reset the game when any of the two paddles score
var reset = function () {
 ball.x = canvas.width / 2;
 ball.y = canvas.height / 2;
}

The reset function is called to begin a new round (serve). It places the ball in the center of the screen.

7 – Update Game

var update = function (modifier) {

This is the update function and is called every single interval execution. In this function we are moving the objects.

The modifier argument is a time-based number based on 1. Because every object is moving by pixels per second, if one second has passed, the player_paddle‘s speed will be multiplied by 1 which means that it will have moved 5 pixels in that second. The same goes to the ai_paddle and the ball. This function gets called so rapidly that the modifier value will typically be very low, but using this pattern will ensure that the objects will move the same speed no matter how fast (or slowly) the script is running. Let’s check the movement of the objects:

  //ball movement -----------------------
 ball.x = ball.x + ball.speed_X;
 ball.y = ball.y + ball.speed_Y;
 
 if (ball.x >= canvas.width) { //ai_paddle scored
 ai_paddle.score++;
 reset()
 }

 if (ball.x <= 0) { //player_paddle scored    player_paddle.score++;     reset()     }    if (ball.y >= canvas.height - ballImage.height 
 || ball.y <= 0) {
 ball.speed_Y = -1 * ball.speed_Y;
 }

The ball is always moving according to its speed which is programmed as speed_X and speed_X. (So the ball could also move diagonally). If the ball ball hits the canvas’s right edge the computer wins one point and reciprocally if it hits the left edge the player wins one point, and in both cases the round is reset.
Now, we want to limit the ball inside the canvas that is why if the ball hit the upper or lower edge of the canvas it will move in the opposite side according to the y-axis. ball.speed_Y = -1 * ball.speed_Y; . We put canvas.height - ballImage.height because we are checking when the bottom of the ball is hitting the canvas not the top (check Note below).

//player_paddle movement -------------
 if (38 in keysDown) { // Player holding UP
 if (player_paddle.y > 0)
 player_paddle.y -= player_paddle.speed;
 }

 if (40 in keysDown) { // Player holding DOWN
 if (player_paddle.y < canvas.height - paddleImage.height)
 player_paddle.y += player_paddle.speed;
 }

The player is moving according to the user’s input, so if the user hit UP (which has the keycode of 38) the player_paddle moves up.

  //ai_paddle movement ----------------- 
 //following the ball
 if (ai_paddle.y < ball.y) {
  if (ai_paddle.y < canvas.height - paddleImage.height)      ai_paddle.y += ai_paddle.speed;    } else {      if (ai_paddle.y > 0)
 ai_paddle.y -= ai_paddle.speed;
 }

The computer is always following the ball, so its x and y are changing according to the ball’s x and y.

In both cases (player_paddle and ai_paddle) we are limiting the paddle inside the canvas. The paddles can’t go up more than the beginning of the canvas (player_paddle.y > 0), and can’t go down more than the end of the canvas. But if we put player_paddle.y < canvas.heigh whole paddle will pass the canvas and stop when the exact top of it hit the canvas’ height. So we write player_paddle.y < canvas.height - paddleImage.height, to get the height of the whole object, in this case it is an image.

Note:

untitled

Collision

 // collision
 if (
 ball.x < player_paddle.x + paddleImage.width &&  ball.x + ballImage.width > player_paddle.x &&
 ball.y < player_paddle.y + paddleImage.height &&  ballImage.height + ball.y > player_paddle.y
 ||
 ball.x < ai_paddle.x + paddleImage.width &&  ball.x + ballImage.width > ai_paddle.x &&
 ball.y < ai_paddle.y + paddleImage.height &&  ballImage.height + ball.y > ai_paddle.y) {
 ball.speed_X = -1 * ball.speed_X;
 }
};

This part is where the ball hit any of the two paddles. If it does the ball will move in the opposite way according to the x-axis. To understand more  collision of two 2D objects I recommend checking this link.

8 – Draw Everything

var draw = function () {
 if (bgReady) {
 ctx.drawImage(bgImage, 0, 0);
 }

 if (paddleReady) {
 ctx.drawImage(paddleImage, player_paddle.x, player_paddle.y);
 ctx.drawImage(paddleImage, ai_paddle.x, ai_paddle.y);
 }

 if (ballReady) {
 ctx.drawImage(ballImage, ball.x, ball.y);
 }

 // Score
 ctx.fillStyle = "rgb(250, 250, 250)";
 ctx.font = "30px AR HERMANN";
 ctx.textAlign = "left";
 ctx.textBaseline = "top";
 ctx.fillText("AI: " + ai_paddle.score +  "\t\tPlayer: " + player_paddle.score, 
              20, 
              10);
};

Here we are drawing everything to the screen. First we take the background image and draw it to the canvas. Repeat for the paddles and ball. Note that the order is important, as any image drawn to the canvas will draw over the pixels under it. 

Next we change some properties on the context related to how to draw the font, and we make a call to fillText to display the score. And we are done drawing.

9 – The main game loop

var main = function () {
 var now = Date.now();
 var delta = now - then;

 update(delta / 1000);
 draw();

 then = now;

 // Request to do this again 
 requestAnimationFrame(main);
};

The main game loop is what controls the flow of the game. First we want to get the current timestamp so we can calculate how many milliseconds have passed since the last interval (the delta). We get the modifier to send to update by dividing by 1000 (the number of milliseconds in one second). Then we call draw and record the timestamp.

10 – Play Game

var then = Date.now();
reset();
main();

We are almost done!  First we set our timestamp (with the variable then) . Then we call reset to start a new round, finally we call the main game loop.

So that’s it! I hope that my code was understandable and that you can benefit from this. Happy Coding!

Further References

If you’re new to HTML5 canvas games I recommend checking Lost Decade Games post: How to make a simple HTML5 Canvas game.

2 thoughts on “Creating a simple AI game

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s