Here's the second post about developing an online card game (this is a continuation of Building an online card game with node.js and socket.io episode 1).
Unfortunately I couldn't spend much time over the past few weeks on this project, however I did make progress. Please don't forget that this tutorial is also a learning curve for me, I try to include my findings, and I learn a lot as I go along with this project. At the moment I'm also considering adding CoffeeScript and underscore.js to the project, as they have useful methods that I believe would help me achieve what I'm after.
As part of this post, I am introducing a websocket and I'll pass information between the server and the client and I'll also utilise the JavaScript file that I've created in the previous part of the series.
I will work with the following 3 files:
game.js
- the file that holds the logic for creating a pack of cards, shuffling it and dealing cardsserver.js
- the server that will serve the clients connected to itindex.html
- the clientLet's start by defining what server.js should do:
In light of this, let's start by adding a few 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 help function to get the size of an object. As you can see I'm creating the players hash/object, the reason for this is because I'd like to create an array with my own indexes. The "problem" is that JavaScript's length
only works on numerically indexed arrays. Here's a 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
This is the expected behaviour, adding the following function however helps and returns the size of the object (that is - 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 bit of the server.js file is the bit which sets up the websocket and makes sure that connections are accepted. The logic flow is that the user opens index.html, adds a username, that will in turn emit a message to the server that the player is ready, a deal button will be enabled, that gets 5 cards from the pack that we created in the code 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);
});
});
Please note highlighted line. The difference between client.emit()
and socket.sockets.emit()
is very significant and important in our case. The latter send the update message to all the connected clients whereas the former only sends a message to the newly connected clients. This will allow us to update the remaining card count for the player who is already connected.
The player object has the format of player["unique identifier"] = "name of player"
which makes it really easy to then delete the right player upon disconnect.
Let's have a look at our index.html - first the "simple" bit - 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-->
<script src="jquery.js"></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 that you can place 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 by running a few, standard jQuery calls to disable the buttons that we don't need now. The interesting bit is when we hit the 'I'm ready' button - we emit a message to the server and we send the player's name as a parameter: socket.emit("addPlayer", player);
, which corresponds to the "addPlayer" method found in the server.
Here it is in action:
There are multiple issues with this version of the code, I know it, and I'll be addressing them as I keep on developing this application -- as mentioned before, maybe by introducing other JS libraries. I have learnt a lot about websockets (and JavaScript as well) while producing this post. Until next time!