tic-tac-toe-multiplayer-short-polling GitHub Details, Stars and Alternatives | OpenRepoFinder
orDaor / repository
tic-tac-toe-multiplayer-short-polling
Play tic-tac-toe vs another player. Invite a Friend with a link in a private room or join a random one. Built with Node.js/Express, MongoDB and MVC design. Short server polling of 2s is used for fetching game status data. No frontend framework is used.
After cloning the repo, move onto the project root directory and run
npm install
to install the packages of the project.
Then start the node server with nodemon (live reload feature) on port 3000
npm start
or simply run
node app.js
To locally serve the web page enter in the browser
http://localhost:3000
Features
Play tic-tac-toe vs another player. Invite a Friend with a link in a private room or join a random one.
This application is built with HTML, CSS, JS, Node.js/Express and MongoDB. Short server polling of 2s is used for fetching game status data. No frontend framework is used.
HOW DOES IT WORK?
There is one main web page, where the DOM gets continuously updated by JavaScript.
Game Rooms
Any time, a player can join a new room by choosing his/her name. The room will be either a random one or a private one where he/she can invite another player by generating an invitation link. The room id and the player number to which a client is mapped to (game data) are contained in the user session.
Every time a player performs a game play action, like a game move for example, an ajax request is sent to the server, which will save this action in the database in the room mapped to the user session. The ajax response will contain the updated game room data fetched from the database, which will be useful for updating the DOM accordingly.
A player can generate a unique invitation link, to invite a friend to play with him/her by pushing the Invite Friend button. A private room will be created, and the player who generated this room will be the first player joining it. When the invited player opens the invitation link, he/she will be asked to choose a name for joining the room. When two players play in a private room, they can alwayse access the invitation link pointing to that room for continuing the game where they left.
Game Play
When a player is assigned a room and he/she is the first player to join that room, he/she will send requests periodically to get the other player data (name and symbol). Aajax short polling of 2 seconds is used. Periodic player data fetching will stop as soon as the server responds with NON falsy value, containing the other player data, which will be useful for updating the DOM accordingly.
When a player is waiting for his/her turn, he/she requests periodically the game status of his room on the server. Aajax short polling of 2 seconds is used. Periodic game status fetching will stop as soon as the server responds with NON falsy value, containing the updated game status data, which will be useful for updating the DOM accordingly.
A player waiting for his/her turn, will be notified if the other player left the room for joining a new one.
After 2 players finish a match in one room, they can choose to restart the game in the same room, or they can join a new one.
Game State
Only the backend code contains the actual game status of a game room, and only the backend is able to decide whether a player won or the game ended with a draw. What the user sees on the game reflects exaclty the game data stored and processed in the backend.
Inactive Rooms
When the connection with the database is established, an asynchronous process will start which once an hour will remove inactive rooms from the database.
MVC BACKEND DESIGN: ROUTES AND CONTROLLERS
Routes are groupped in these sets:
base
game config
game play
game room
player
A controller is defined for each route set (except for the base routes), and each controller contains its controller actions. In the following are described the end-points handled by the different controller actions:
game config controller:
GET: “/game” → get the main game page, which lets a user enter his/her name for joining a random room or a new private one to which invite a friend with an invitation link.
GET: “/game/new/friend/roomId” → get the page from which a user can enter his/her name to join the private room of a friend who shared this invitation link with him/her.
game play controller (ajax):
GET: “/game/room” → the user requests the current game status of the room to which his/her session is mapped.
POST: “/game/status” → the user requests to update the game status when performing a game move.
POST: “/game/restart” → the user requests to restart the game with the same other player in the room he/she is currently in, after game is over.
game room controller (ajax):
POST: “/game/new” → a user requests to join a random room.
POST: “/game/new/friend” → a user requests a new private room where to invite a friend with an invitation link. The link is pointing to this private room in the database.
POST: “/game/new/friend/roomId” → a user requests to join a private room after he/she got an invitation link.
player controller (ajax):
GET: “/player/other” → a player fetches the data of the other player in the room he/she is currently playing in.
MONGODB DATABASE
NoSQL MondoDB database is used for storing sessions and game rooms:
MondoDB database → tic-tac-toe
MondoDB collections in tic-tac-toe database:
sessions
rooms
single MondoDB document in sessions collection → a standard client session. In the client session, we write to which game room the client is assigned, and which player number he/she is there (1 or 2):
_id (ObjectId) → id of the MongoDB session document.
...
gameData:
roomId (string)
playerNumber (number)
single MondoDB document in rooms collection → an object of class Room representing a game room for 2 players.
MVC BACKEND DESIGN: MODELS
The following are the classes defined in the backend for handling game rooms:
Room
Represents a game room which contains 2 players and a game status with a tic-tac-toe game board:
properties:
players (array of 2 objects of class Player)
gameStatus (GameStatus class object)
available (bool) → a room is available if at least one player slot is not occupied (empty).
creationDate (Date class object)
lastChangeDate (Date class object) → when a room is created, the last change date it is equal to creation date. Then, every time the game room data are updated, the last change date updates accordingly.
blocked (bool) → a room gets blocked when:
after a player leaves it, so that no other clients can be assigned to this room in the future.
when a client finds a random room as available and wants to “book” it. During the process where the client is being assigned to this room, the room remains blocked, in order that it could not be assigned to other clients.
owned (bool) → private. A client creates a private room for inviting there another friend with a link.
roomId (bool) → id of the corresponding room document in the database.
NOTE: a room is defined as random and good to be assigned to one client when it is the first room in the database which matches the following conditions:
room.available = TRUE
room.blocked = FALSE
room.owned = FALSE
NOTE: the room assigning process for a client is made of the following steps:
a random room document in the database is found and “booked” (blocked). If no room is found a new one is created.
an available (empty) player slot is found in the room.
a Player class object is entered in the available player slot of the room with the player name chosen by the user.
the room document in the database is updated with the new data. If a new room is created, a new room document is inserted in the database.
the room id together with the available player slot number in the room are assigned to the game data in the user session.
methods:
(static) createEmpty(isPrivate) → creates a new empty room object, when no random room is found.
(static) fromMongoDBDocumentToRoom(document) → converts a MondoDB room document to a Room class object.
Player
Represents a specific player inside of a room:
properties:
name (string) → player name.
symbol (string) → X or O.
number (number) → 1 or 2.
hasTurn (bool) → whether it is this player turn or not.
restartingRequest (bool)→ request for restarting the game after game over, in the current room with the same other player.
methods:
(static) fromMongoDBDocumentToPlayer → convert MondoDB player document to a Player class object.
GameMove
Represents a move made by a certain player at a certain date, on a specified position in the game board of a room:
properties:
playerNumber (number) → number of the player who made this move.
coord (array of 2 numbers) → coordinates (row and column) in game board where th move is set.
date (Date class object)→ date this move was made.
methods:
(static) fromMongoDBDocumentToGameMove() → converts MondoDB game move document to a GameMove class object.
GameStatus
Contains all the game state data in a specific room:
properties:
board (array of 3 arrays of length 3) → matrix where values are 1, 2 or 0 (each value represents the player number who did the move).
lastMove (Game
(static) findAvailableAndBlock(roomIdToSkip) → finds first available room and books it. Skips the room with the given id.
(static) findById(roomId) → fetches from the database a room with the given id.
(static) findByIdAndCheckAccessRights(roomId, sessionGameData) → checks whether a client can be assigned or access this room based on the game data of his/her session.
(static) deleteByFilter(query) → deletes all rooms in the database matching the filter.
(static) deleteInactiveRoomsCiclically(delay, maxInactiveTime) → deletes every “delay” milliseconds from the database all the rooms which have been inactive for too much time.
(static) blockById(roomId) → when a player leaves a room , it will block it, so that no other player can join it.
isAvailable() → checks if at least 1 player slot is available (empty) in this room.
save() → saves a new game room in the database which has values equal to this room object; or updates the values of an existing room in the database with the values of this room.
getPlayer(playerNumber) → returns a specific player of the room.
addPlayer(Player) → occupies a player slot with a specific Player object.
removePlayer(playerNumber) → removes one player in this room (empties the player slot).
setPlayersTurn(playerNumber) → updates player turns info in the room given that the player with playerNumber made a successfull game move.
isPlayerSlotAvailable(playerNumber) → checks if a player slot is empty.
getAvailablePlayerSlot() → checks which player slot number in this room is available (empty).
getAvailableGameSymbol() → returnes the symbol not used by players in the room yet.
InitGameRestart() → sets game restart requests for this room.
isGameRestartInitialized() → checks if a game restart in this room was already requested.
handleGameRestart(playerNumber) → handles game restart requests.
fromRoomToMongoDBDocument() → converts a Room class object to a room MongoDB document.