Greetings% habrname%!

In
yesterday's article for me, in general, it was expected that the majority will not be interested in nodejs, many will only watch the demo. But unfortunately I did not take into account that the game will be evaluated in the article! Although it was a shame for me to spend so much time writing an article (and that the most important game was written for the article, and not vice versa), I decided to write a sequel today.
Well then! We will work on the bugs and make a working game with everything asked for, but at the same time we will not deviate from the blog topic and consider all the technical issues encountered during the game test.
The first message in the topic was the first bug report from Assargin
habravchanin :
This is great - I manage to click 5-6 times in a row, while the browser and / or Internet slows down and I win automatically after the first turn! No chance of an opponent! :)
And this is in FF 3.6.24, and in chrome 15.0.874.106
And after him from
Morfi :
How so?

')
Here I missed the moment with a double move, well, that's understandable, I had the goal of testing the game so that everything worked. On the other hand, validation was only on the client who walks. Transfer it to the server.
Check who walks on the server
Open our file with the heart of the game modules / tictactoe.js in it we are looking for the turn function, which is the object of the game:
GameItem.prototype.step = function(x, y, user, cb) { if(this.board[x + 'x' + y] !== undefined) return;
As you can see, when we received a move, we checked it only for the employment of the field, now we need to add a check for whose turn, for the beginning, we will introduce a new property for the game object called turn:
var GameItem = function(user, opponent, x, y, stepsToWin) { ...
Add a check in step and switch the move to the opponent:
GameItem.prototype.step = function(x, y, user, cb) { if(this.board[x + 'x' + y] !== undefined || this.getTurn(user) != this.turn) return;
On it the first bug. But after seeing
Morfi's screen, a bug in the UI rushed into my eyes, which did not update the interface, this is where it says 3 times “You play: Zero”. Debug of the game brought out that at the end of the game we disconnect the user from the personal room of the game in socket.io and thereby bring him to the common room for new games. And thus we connect the user to new games, although he did not even have time to press the "new game" button. It can be fixed easily, we will add the entrance to the game by pressing a button, and not as it is now automatically.
End the game without automatically restarting the new one.
Open our client file tictactoe.js which is located in the public folder and add a call to the game launch event to the button, then change the click event to the page reload:
$('#reload').hide().button({icons:{primary:'ui-icon-refresh'}}).click(function() { $('#reload').off('click').click(function(){window.location.reload();}); socket.emit('start'); });
And on the server side of index.js, we take the function of launching the game into a separate event:
socket.on('start', function () { if(Game.users[socket.id] !== undefined) return;
Now we will have no problems with restarting without the knowledge of the user, until he himself clicks to start a new game.
Timeout
Many began to complain that no one walks, it was the greatest evil, perhaps because of the problems that many had with the game, they simply could not walk and the game hung blankly open. As correctly noted,
Evengard needed a timeout. Let's go back to our client file, which we ruled a little earlier public / titactoe.js and add a new variable to it and change the mask function:
var TicTacToe = { gameId: null, turn: null, i: false, socket: null, interval: null,
We will turn on the timer itself at the start of the game and upon receipt of the turn, and turn it off at the end of the game and when receiving the turn before turning it on again. I will not comment on the code, it will be easier to look at the sources on github there everything is clear, we are more interested in server implementation.
Server timeout and learning EventEmitter
We now turn to the server part. Here we need to set the timer correctly so that it works only for the desired game, so we will increase the game object further. But, I promised that this article will be a teaching material, so we will not be doing a simple function, but we will learn what events in NodeJS are, but rather we will get to know EventEmitter more closely.
To work on, you will need to connect it, because this is a separate events module in nodejs.
At the very beginning of the models / tictactoe.js file, we will add an EventEmitter and Util connection, for which we need the last one I will explain a little later.
var util = require('util'), EventEmitter = require("events").EventEmitter;
In this call, we not only connect the module but also create a new object of the exported function.
A bit of theory
Since we are already using socket.io, which is event based, which means that EventEmitter is already connected there, but not declared in our context. To use it, we will prescribe its connection via a new one, while nodejs will not reconnect it, all require are cached, and each repeated call is just an appeal to an already connected module. What EventEmitter is is an object of an event machine, in simple language it is a kind of monitoring of event calls, exactly the same as in regular javascript, but to work with it you need to do a little focus.
Back to practice
First we need to add an event handler to our game objects, which is why I connected another util module. It contains a set of different functions, a detailed description of each of them can be found in the official documentation, and we need only one inherits to add an EventEmitter to TicTacToe and GameItem :
var TicTacToe = module.exports = function() {
Remember I mentioned a certain trick, so here it is. Thus, our objects can now work with their events. It is with them that we will control the game timer.
Add a property for storing the ID timer, as well as our first event handler in GameItem:
var GameItem = function(user, opponent, x, y, stepsToWin) { ...
Pay attention to one important point: there is no timeout event in GameItem, not yet, but now we will add it and add it to the index.js file, why we are doing there I will explain after the code:
socket.on('start', function () { if(Game.users[socket.id] !== undefined) return; Game.start(socket.id.toString(), function(start, gameId, opponent, x, y){ if(start) {
The event handler itself, we carried out 2 layers above, where we are working with websockets. All this is necessary in order to turn to the management of the website and the Game object (TicTacToe) after the event triggers.
A bug with a blink in the opera or my stupid mistake with statistics
User
seriyPS wrote:
I don’t know what it is connected with, but your “game” sends events of “stats” 100 pieces per second. At each such event, a significant part of the UI is redrawn and everything slows down. It is impossible to play (and not with anyone). Uzhoz in general.
And even very professionally approached the problem, having fully studied it. I immediately wrote to the comments that this is really my mistake, and now I will explain what, it's trite, see the code:
io.sockets.on('connection', function (socket) { io.sockets.emit('stats', [ ' : ' + countGames, ' : ' + Object.keys(countPlayers).length, ' : ' + onlineGames, ' : ' + onlinePlayers ]); setInterval(function() { io.sockets.emit('stats', [ ' : ' + countGames, ' : ' + Object.keys(countPlayers).length, ' : ' + onlineGames, ' : ' + onlinePlayers ]); }, 5000); ... });
After establishing the connection of web sockets, we start sending statistics in the interval every 5 seconds, 50 users connected at different times 5 * 50 = 250 statistics requests were sent every 5 seconds by the server. Negily such a bug.
And it could be solved by simply removing the statistics code outside the event of connecting web sockets:
setInterval(function() { io.sockets.emit('stats', [ ' : ' + countGames, ' : ' + Object.keys(countPlayers).length, ' : ' + onlineGames, ' : ' + onlinePlayers ]); }, 5000); io.sockets.on('connection', function (socket) { io.sockets.emit('stats', [ ' : ' + countGames, ' : ' + Object.keys(countPlayers).length, ' : ' + onlineGames, ' : ' + onlinePlayers ]); ... });
Refactoring
On this, the correction of errors was completed and I decided to do refactoring.
In the index.js file, I first of all removed the extra code to disconnect users from rooms, brought it to the function:
io.sockets.on('connection', function (socket) { ... function closeRoom(gameId, opponent) { socket.leave(gameId); io.sockets.socket(opponent).leave(gameId); } ... });
The online players and games counters are completely removed, because they are already in the objects:
var countGames = 0, countPlayers = [], Game = new TicTacToe(); ... io.sockets.emit('stats', [ ' : ' + countGames, ' : ' + Object.keys(countPlayers).length, ' : ' + Object.keys(Game.games).length, ' : ' + Object.keys(Game.users).length ]);
There were still a lot of small things about different changes, the article was already a big one :)
Thank you all who read this epic from the first article to this line!
I also want to add many problems with the game, namely communications, are connected with the problems of the socket.io library, so not everything is as perfect as it may seem, the frequent errors of “client not handshaken” according to google analytics statistics confirm this.
PS: I apologize that there was an active link to the article as early as the 15th, but the article was hidden by a UFO, and I was banned for 7 days for “karmdrocherstvo in posts.” Since it is impossible to discuss the actions of the administration, I will simply keep silent.
To play
Now you can play, to start the game, you need to click "New game":
ivan.zhuravlev.name/game - 6x6 field with 4 moves to win
ivan.zhuravlev.name/game3 - 3x3 field with 3 moves to win
View sources:
github.com/intech/TicTacToeCommit all changes to one:
github.com/intech/TicTacToe/commit/ff3c1d6e9e298a84ab660b52385c378f69f24f01