Skip to main content

Online card game with node.js and socket.io - episode 2

4 min read

Older Article

This article was published 13 years ago. Some information may be outdated or no longer applicable.

Here’s the second post about developing an online card game (continuing from Episode 1).

I haven’t had much time to spend on this over the past few weeks, but I did make progress. Remember, this tutorial doubles as a learning curve for me. I try to include my findings as I go. I’m also considering adding CoffeeScript and underscore.js to the project, as they have methods that could help me get where I’m going.

In this post, I’m introducing a websocket. I’ll pass information between the server and the client, and I’ll use the JavaScript file from the previous part.

I’ll work with three files:

  • game.js holds the logic for creating, shuffling, and dealing cards
  • server.js is the server that serves connected clients
  • index.html is the client

Let’s define what server.js should do:

  • Accept connections from the client
  • Add users
  • Remove users when they disconnect
  • Deal 5 cards to each connected user

With that in mind, let’s set up some variables:

var io = require('socket.io'),
  game = require('./game');

var socket = io.listen('1.1.1.1', 1222);
socket.set('log level', 1);

var players = {};
var start = false;
var pack = game.shufflePack(game.createPack());

I’m also adding a helper function to get the size of an object. I’m creating the players hash/object because I want an array with my own indexes. The “problem” is that JavaScript’s length only works on numerically indexed arrays. Quick example:

var a = [];
a.push('e1', 'e2', 'e3');
console.log(a.length); //returns 3

var o = {};
o['name'] = 'Luke';
o['surname'] = 'Skywalker';
o['profession'] = 'Jedi';
console.log(o.length); //returns undefined

That’s expected behaviour. Adding this function returns the size of the object (the number of elements):

Object.size = function (obj) {
  var size = 0,
    key;
  for (key in obj) {
    if (obj.hasOwnProperty(key)) size++;
  }
  return size;
};
//test it
console.log(Object.size(o)); //returns 3

The final piece of server.js sets up the websocket and accepts connections. The flow: the user opens index.html, enters a username, emits a message to the server saying the player is ready, then a deal button is enabled that pulls 5 cards from the pack we created above.

socket.on('connection', function (client) {
  client.on('addPlayer', function (player) {
    players[client.id] = player;
    console.log(
      'Player ' + player + 'with id: ' + client.id + 'has connected.'
    );
    console.log(Object.size(players));
    for (var key in players) {
      console.log('Players: ' + key + ': ' + players[key]);
    }
  });

  client.on('disconnect', function () {
    console.log('Player with id: ' + client.id + 'has disconnected');
    delete players[client.id];
    for (var key in players) {
      console.log('Remaining players: ' + key + ': ' + players[key]);
    }
    //reset pack
    pack = game.shufflePack(game.createPack());
  });

  client.on('dealCards', function () {
    var cards = game.draw(pack, 5, '', true);
    client.emit('showCards', cards);
    socket.sockets.emit('remainingCards', pack.length);
  });
});

Note the highlighted line. The difference between client.emit() and socket.sockets.emit() is significant. The latter sends the update message to all connected clients. The former sends only to the newly connected client. This lets us update the remaining card count for a player who’s already connected.

The player object has the format player["unique identifier"] = "name of player", which makes it easy to delete the right player on disconnect.

Let’s look at index.html. First the HTML markup:

<!DOCTYPE html>
<html>
<head>
  <script src="http://1.1.1.1:1222/socket.io/socket.io.js"><!--because I'm not using localhost, I need to add the IP address-->

    &lt;script src="jquery.js">&lt;/script>



<body>

<h1>This is the client</h1>

<input type="text" id="player"><br />

<p id="welcome"></p>

<input type="button" id="ready" value="I'm ready">

<input type="button" id="deal" value="Deal cards">

<p id="opponents"></p>

<p id="cards"></p><p id="pack"></p>

</body>

</html>

And here’s the JavaScript (place it between <script></script> tags):

var ready = false;
$("#deal").attr("disabled", "disabled");
$("welcome").hide();
var socket = io.connect("http://1.1.1.1:1222");
$("#ready").click(function() {
  var player = $("#player").val();
  console.log(player);
  console.log('called');
  socket.emit("addPlayer", player);
  ready = true;
  $("#deal").removeAttr("disabled");
  $("#ready").attr("disabled", "disabled");
  $("#player").remove();
  $("#welcome").show();
  $("#welcome").text("Welcome, " + player)
  console.log("Ready:" + ready);
});

$("#deal").click(function() {
  if (ready) {
    console.log("dealing cards");
    socket.emit("dealCards");
    socket.emit("getOpponents");
  }
});

socket.on("showCards", function(cards){
  if (ready) {
    $("#cards").text(cards);
    socket.on("displayOpponents", function(opponent){
      $("#opponents").text("Your opponent is: " + opponent);
    });
  }
});

socket.on("remainingCards", function(remaining){
  if (ready) {
    $("#pack").text();
    $("#pack").text("Remaining cards are: " + remaining);
  }
});
});

We start with some standard jQuery calls to disable buttons we don’t need yet. The interesting bit is the “I’m ready” button. We emit a message to the server and send the player’s name as a parameter: socket.emit("addPlayer", player);, which maps to the “addPlayer” method on the server.

Here it is in action:

There are several issues with this version of the code. I know about them and I’ll address them as I keep developing. Maybe by introducing other JS libraries. I’ve learned a lot about websockets (and JavaScript in general) while putting this together. Until next time.