Tutorial: Multiplayer Strategy game in Javascript, Part 1

This is part 1 of a tutorial on making a multiplayer strategy game in JavaScript; this part is the result of an one hour live coding session (video here, in Spanish, with lots of background noise and fuzzy code). You can download the source code or play the game online.

What is this about?

We are going to build a multiplayer game players can connect via their browsers; it will be a semi real time strategy game where players struggle to control the territory on the map. This is what it’s going to look like when you finish this tutorial:

First version of Kingdom of Elias

Requirements

For this tutorial you are going to need the following:

  • NodeJS: You can download this for any platform from here. It should come bundled with npm (node package manager)
  • A web server: Anything capable of handling static pages will do. Apache comes bundled with a lot of OS and if not should be easy to install. You can also set up a simple http server using node. (it’s easier than you think)

The Server

The server is a nodejs app powered by socket.io; it will allow players to connect via internet and play with each other. For our example we’ll begin with a single JS file, named index.js.

First off, we require the libraries needed to initialize socket.io; we need an express app to quickly initialize an http server, which we then use to power our socket.io object.

var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);

Now that we have an “io” object, we can use its ‘on’ method to define how to react to different events; in this case we are responding to the base, ‘connection’ message, which is sent automatically by each client connecting, this will allow us to confirm we are set up correctly.

io.on('connection', function(socket){
  console.log('Someone connected to us');
});

Finally, we set up our http server to listen for http connections on a port

server.listen(3001, function(){
  console.log('listening on *:3001  ');
});

In order for our server to work, we need to install the required modules; there are several ways to do this using npm

npm init
npm install express --save
npm install socket.io --save

This will create a package.json file and add the required libraries to it. After it you can run the server:

node index.js

If everything goes well, you’ll see the “listening” message in the console.

The client

The client is what players use to connect to the server and interact with other players; in our case it will be a simple, static HTML page which can be served by any http server. Let’s name it index.html

<html>
<head>
<script src = "lib/socket.io.js"></script>
<body>
  <script>
    var socket = io('http://localhost:3001');
  </script>
</body>
</html>

What are we doing here? we are just including the socket.io client library and then creating a client io object, which we will then be able to use to talk with the server. For now we are just connecting to the server, which happens when the object is created.

Note: You can obtain the socket.io.js in several ways; if you installed the socket.io module for the server via npm install, you can find the socket.io.js file inside the node_modules/socket.io/node_modules directory. You can also download it from here

Now let’s put our index.html file on a location served by any http server, and browse to it using a browser. Make sure the node server is running!

Just by browsing to the app, you’ll see a message pop up in the node console notifying a player has connected!

Showing the map

Now that we have some basic infrastructure in place, it’s time to start building a game. The first thing we are going to do is create the map for the players to play on.

In order to do this, we must come up with a way to persist the data for the world so that all players can interact with a shared map. This is normally done in a permanent storage such as a database, but for the sake of simplicity let’s initially store it in memory.

We are going to create a new JS module and call it model.js

var model = {
  map: []
}

for (var x = 0; x < 20; x++){
  model.map[x] = [];
  for (var y = 0; y < 20; y++)
    model.map[x][y] = false;
}
module.exports = model;

Our model starts pretty simple; we have a map object which is a two dimensional array of 20×20, preinitialized with false; each object on the array represents a region on the map, which is going to have an owner.

Let’s make this model object accessible to our server, on index.js

var model = require('./model');

Now, let’s create a message handler in the server to deliver this data so that the clients can show it to the player

io.on('connection', function(socket){
  console.log('Someone connected to us');
  [...]

  socket.on('getMap', function(){
    socket.emit('heresTheMap', {map: model.map});
  });

We are using the “on” method of the socket object to respond to a “getMap” message; note that we are not using the “io” object but the “socket” object, which means this message will be processed in the context of the particular socket that connected to the server; this is what we commonly do for messages meant to be consumed by clients.

Now, inside the function that is executed when we receive a “getMap” message, all that we are doing is emitting a new message back to the client. This message contains the map from the model.

Let’s go back to our client (index.html), let’s add some code to emit the “getMap” message and catch back the “heresTheMap” message

  var socket = io('http://localhost:3001');
  socket.emit('getMap');
  socket.on('heresTheMap', function (response){
    var map = response.map;
    alert('Map width: '+map.length);
  });

Now let’s restart the server and refresh our page. We should get a “Map width: 20” message.

That’s cool, now we’ve got the map data in our client, it’s almost like that “map” object was transported from the server into us! of course there’s a lot of things going on there, but they are all hidden from us by socket.io.

Using this “map” object, we can display the map to the player. For this first version let’s just create an html table where each cell represents a region. Let’s first define some basic styles to ensure the table looks like a grid:


<style type="text/css">
  td {
    width: 30px;
    height: 30px;
  }
</style>

and then, instead of showing the map width, let’s create a table with the map data:

  socket.on('heresTheMap', function (response){
    var map = response.map;
    var html = '<table>';
    for (var y = 0; y < map[0].length; y++){
      html += '<tr>';
      for (var x = 0; x < map.length; x++){
        var cellColor = map[x][y] ? map[x][y] : 'green';
        html += '<td style = "background-color: '+cellColor+'" ></td>';
      }
      html += '</tr>';
    }
    html += '</table>';
    document.getElementById('playArea').innerHTML = html;
  });

Finally, let’s create an element in our page which will contain the map.


<div id='playArea'></div>

With this in place, after we reload the page we should see a grid of green blocks. Green represents nature, and in this case it represents a region that hasn’t been taken yet. This is corresponds to a object in our model that no player has claimed.

Conquering regions

So far, socket.io has allowed us to send a message to the server and get back a response; that’s good, but it isn’t much different than what we can with basic HTTP calls, is it? may be a bit more succinct and clean, but not very different.

And here is where things get interesting: basic HTTP requests are a unidirectional model, the client initiates a request and the server responds back, there’s no way for the server to reach for it’s clients on its own. Using websockets via socket.io allows us to establish a bidirectional communication channel between the server and its clients, allowing the server to start messages on its own.

That’s just what we are going to do next, we want the players to conquer regions and when that happens all clients connected to the server gotta know, so that the can update the map with the new owner of the region.

Let’s first change the client so that it sends a message to the server when the player clicks a region.

function conquer(x,y){
  socket.emit("conquer", {x: x, y:y});
}

And now, let’s modify our map rendering routine so this function gets called when the user clicks the grid.

  socket.on('heresTheMap', function (response){
    var map = response.map;
    var html = '<table>';
    for (var y = 0; y < map[0].length; y++){
      html += '<tr>';
      for (var x = 0; x < map.length; x++){
        var cellColor = map[x][y] ? map[x][y] : 'green';
        html += '<td style = "background-color: '+cellColor+'" onclick = "conquer('+x+', '+y+')"></td>'
      }
      html += '</tr>';
    }
    html += '</table>';
    document.getElementById('playArea').innerHTML = html;
  });

Let’s now go back to the server; we need to add a way to identify the territories of a given player; for now let’s assign him a random color once he connects.

var colors = {};
io.on('connection', function(socket){
  [...]
  switch (Math.floor(Math.random()*3)){
    case 0:
    colors[socket.id] = 'yellow';
    break;
    case 1:

    colors[socket.id] = 'blue';
    break;
    case 2:

    colors[socket.id] = 'red';
    break;
  }

We are going to use the colors object to store the color we assigned to each player (indexed by his socket id).

Now we need to add a new message handler for “conquer”.

io.on('connection', function(socket){
  [...]

  socket.on('conquer', function(where){
    var conquerObject = {
      id: socket.id,
      x: where.x,
      y: where.y,
      color: colors[socket.id]
    };
    socket.broadcast.emit('conquered', conquerObject);
    socket.emit('conquered', conquerObject);
    model.map[where.x][where.y] = colors[socket.id];
  });

});

We are doing the following here:

  1. Create an object with the info regarding the newly conquered region
  2. Broadcasting this object to ALL the clients currently connected
  3. Also emitting the message back to the player who conquered the region
  4. Saving the new conquest in our model

Back to the client, we must respond to the “conquered” message to paint the region based on the color of its new conqueror; first let’s modify the map rendering routine to be able to reference each individual cell by it’s id:

socket.on('heresTheMap', function (response){
    var map = response.map;
    var html = '<table>';
    for (var y = 0; y < map[0].length; y++){
      html += '<tr>';
      for (var x = 0; x < map.length; x++){
        var cellColor = map[x][y] ? map[x][y] : 'green';
        html += '<td id = "cell'+x+'-'+y+'" style = "background-color: '+cellColor+'" onclick = "conquer('+x+', '+y+')"></td>'
      }
      html += '</tr>';
    }
    html += '</table>'; 
    document.getElementById('playArea').innerHTML = html; 
});

And then, let’s add the “conquered” message handler proper

socket.on('conquered', function(conquerInfo){
  document.getElementById('cell'+conquerInfo.x+"-"+conquerInfo.y).style.backgroundColor = conquerInfo.color;
});

Let’s now reload our server and client, and by clicking on any of the blocks on the map, we will claim the region as ours!

We can also now invite our friends to play the game, they just have to browse to your http server and there,  you’ll be competing for the map in no time!

Note: Remember to set the address to your node server in your index.html file! localhost won’t work for other players joining the game since then their clients will try to establish a socket connection with their own machines 😉

That’s it for now! Now go and assemble your own version, tweak and change some things around and let me know how it goes 🙂

In part 2 we are going to add some game logic and graphics.

Advertisements

3 thoughts on “Tutorial: Multiplayer Strategy game in Javascript, Part 1

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