Introduction
In this article, we will learn how we can make a Tic-Tac-Toe online game using React JS as the front end and Node JS (Socket.io) as the backend server.
First of all, for those unaware of Node JS Socket.io, Node JS is a server side programming language that runs on a V8 engine and Socket.io npm module which provides a facility for bi-directional communication between the server and client using websockets. In short, socket.io provides a way for the client to connect to the server, then afterward the client and server can communicate for a long time without the same connection.
For this example, you should have knowledge of Node JS and React Js. So let’s start guys.
Output
Prerequisite
-
Node JS : https://nodejs.org/en/
-
Socket.io : https://socket.io/
-
Getting Started React Native: https://reactnative.dev/docs/getting-started
-
Bootstrap for React JS : https://react-bootstrap.github.io/
-
Socket.io client for React JS : https://www.npmjs.com/package/socket.io-client
Project Repos
Server : https://github.com/myvsparth/tic-tac-toe-server
ReactJS Front End : https://github.com/myvsparth/react-js-tic-tac-toe
First, create a server for the tic tac toe game.
Clone my git repo for the full source code. Please go through the comment lines of the source code so you can understand how it works. Below is the index.js file commented source code:
- const server = require('http').createServer();
- const io = require('socket.io')(server);
- const PORT = 4444;
- const HOST = "127.0.0.1";
- var players = {};
- var sockets = {};
- var games = {};
- var winCombinations = [
- [[0, 0], [0, 1], [0, 2]],
- [[1, 0], [1, 1], [1, 2]],
- [[2, 0], [2, 1], [2, 2]],
- [[0, 0], [1, 0], [2, 0]],
- [[0, 1], [1, 1], [2, 1]],
- [[0, 2], [1, 2], [2, 2]],
- [[0, 0], [1, 1], [2, 2]],
- [[0, 2], [1, 1], [2, 0]]
- ];
-
-
- io.on('connection', client => {
- console.log("connected : " + client.id);
- client.emit('connected', { "id": client.id });
-
-
- client.on('checkUserDetail', data => {
- var flag = false;
- for (var id in sockets) {
- if (sockets[id].mobile_number === data.mobileNumber) {
- flag = true;
- break;
- }
- }
- if (!flag) {
- sockets[client.id] = {
- mobile_number: data.mobileNumber,
- is_playing: false,
- game_id: null
- };
-
- var flag1 = false;
- for (var id in players) {
- if (id === data.mobileNumber) {
- flag1 = true;
- break;
- }
- }
- if (!flag1) {
- players[data.mobileNumber] = {
- played: 0,
- won: 0,
- draw: 0
- };
- }
-
- }
- client.emit('checkUserDetailResponse', !flag);
- });
-
-
- client.on('getOpponents', data => {
- var response = [];
- for (var id in sockets) {
- if (id !== client.id && !sockets[id].is_playing) {
- response.push({
- id: id,
- mobile_number: sockets[id].mobile_number,
- played: players[sockets[id].mobile_number].played,
- won: players[sockets[id].mobile_number].won,
- draw: players[sockets[id].mobile_number].draw
- });
- }
- }
- client.emit('getOpponentsResponse', response);
- client.broadcast.emit('newOpponentAdded', {
- id: client.id,
- mobile_number: sockets[client.id].mobile_number,
- played: players[sockets[client.id].mobile_number].played,
- won: players[sockets[client.id].mobile_number].won,
- draw: players[sockets[client.id].mobile_number].draw
- });
- });
-
-
- client.on('selectOpponent', data => {
- var response = { status: false, message: "Opponent is playing with someone else." };
- if (!sockets[data.id].is_playing) {
- var gameId = uuidv4();
- sockets[data.id].is_playing = true;
- sockets[client.id].is_playing = true;
- sockets[data.id].game_id = gameId;
- sockets[client.id].game_id = gameId;
- players[sockets[data.id].mobile_number].played = players[sockets[data.id].mobile_number].played + 1;
- players[sockets[client.id].mobile_number].played = players[sockets[client.id].mobile_number].played + 1;
-
- games[gameId] = {
- player1: client.id,
- player2: data.id,
- whose_turn: client.id,
- playboard: [["", "", ""], ["", "", ""], ["", "", ""]],
- game_status: "ongoing",
- game_winner: null,
- winning_combination: []
- };
- games[gameId][client.id] = {
- mobile_number: sockets[client.id].mobile_number,
- sign: "x",
- played: players[sockets[client.id].mobile_number].played,
- won: players[sockets[client.id].mobile_number].won,
- draw: players[sockets[client.id].mobile_number].draw
- };
- games[gameId][data.id] = {
- mobile_number: sockets[data.id].mobile_number,
- sign: "o",
- played: players[sockets[data.id].mobile_number].played,
- won: players[sockets[data.id].mobile_number].won,
- draw: players[sockets[data.id].mobile_number].draw
- };
- io.sockets.connected[client.id].join(gameId);
- io.sockets.connected[data.id].join(gameId);
- io.emit('excludePlayers', [client.id, data.id]);
- io.to(gameId).emit('gameStarted', { status: true, game_id: gameId, game_data: games[gameId] });
-
- }
- });
-
- var gameBetweenSeconds = 10;
- var gameBetweenInterval = null;
-
-
- client.on('selectCell', data => {
- games[data.gameId].playboard[data.i][data.j] = games[data.gameId][games[data.gameId].whose_turn].sign;
-
- var isDraw = true;
- for (let i = 0; i < 3; i++) {
- for (let j = 0; j < 3; j++) {
- if (games[data.gameId].playboard[i][j] == "") {
- isDraw = false;
- break;
- }
- }
- }
- if (isDraw)
- games[data.gameId].game_status = "draw";
-
-
- for (let i = 0; i < winCombinations.length; i++) {
- var tempComb = games[data.gameId].playboard[winCombinations[i][0][0]][winCombinations[i][0][1]] + games[data.gameId].playboard[winCombinations[i][1][0]][winCombinations[i][1][1]] + games[data.gameId].playboard[winCombinations[i][2][0]][winCombinations[i][2][1]];
- if (tempComb === "xxx" || tempComb === "ooo") {
- games[data.gameId].game_winner = games[data.gameId].whose_turn;
- games[data.gameId].game_status = "won";
- games[data.gameId].winning_combination = [[winCombinations[i][0][0], winCombinations[i][0][1]], [winCombinations[i][1][0], winCombinations[i][1][1]], [winCombinations[i][2][0], winCombinations[i][2][1]]];
- players[games[data.gameId][games[data.gameId].game_winner].mobile_number].won++;
- }
- }
- if (games[data.gameId].game_status == "draw") {
- players[games[data.gameId][games[data.gameId].player1].mobile_number].draw++;
- players[games[data.gameId][games[data.gameId].player2].mobile_number].draw++;
- }
- games[data.gameId].whose_turn = games[data.gameId].whose_turn == games[data.gameId].player1 ? games[data.gameId].player2 : games[data.gameId].player1;
- io.to(data.gameId).emit('selectCellResponse', games[data.gameId]);
-
- if (games[data.gameId].game_status == "draw" || games[data.gameId].game_status == "won") {
- gameBetweenSeconds = 10;
- gameBetweenInterval = setInterval(() => {
- gameBetweenSeconds--;
- io.to(data.gameId).emit('gameInterval', gameBetweenSeconds);
- if (gameBetweenSeconds == 0) {
- clearInterval(gameBetweenInterval);
-
- var gameId = uuidv4();
- sockets[games[data.gameId].player1].game_id = gameId;
- sockets[games[data.gameId].player2].game_id = gameId;
- players[sockets[games[data.gameId].player1].mobile_number].played = players[sockets[games[data.gameId].player1].mobile_number].played + 1;
- players[sockets[games[data.gameId].player2].mobile_number].played = players[sockets[games[data.gameId].player2].mobile_number].played + 1;
-
- games[gameId] = {
- player1: games[data.gameId].player1,
- player2: games[data.gameId].player2,
- whose_turn: games[data.gameId].game_status == "won" ? games[data.gameId].game_winner : games[data.gameId].whose_turn,
- playboard: [["", "", ""], ["", "", ""], ["", "", ""]],
- game_status: "ongoing",
- game_winner: null,
- winning_combination: []
- };
- games[gameId][games[data.gameId].player1] = {
- mobile_number: sockets[games[data.gameId].player1].mobile_number,
- sign: "x",
- played: players[sockets[games[data.gameId].player1].mobile_number].played,
- won: players[sockets[games[data.gameId].player1].mobile_number].won,
- draw: players[sockets[games[data.gameId].player1].mobile_number].draw
- };
- games[gameId][games[data.gameId].player2] = {
- mobile_number: sockets[games[data.gameId].player2].mobile_number,
- sign: "o",
- played: players[sockets[games[data.gameId].player2].mobile_number].played,
- won: players[sockets[games[data.gameId].player2].mobile_number].won,
- draw: players[sockets[games[data.gameId].player2].mobile_number].draw
- };
- io.sockets.connected[games[data.gameId].player1].join(gameId);
- io.sockets.connected[games[data.gameId].player2].join(gameId);
-
- io.to(gameId).emit('nextGameData', { status: true, game_id: gameId, game_data: games[gameId] });
-
- io.sockets.connected[games[data.gameId].player1].leave(data.gameId);
- io.sockets.connected[games[data.gameId].player2].leave(data.gameId);
- delete games[data.gameId];
- }
- }, 1000);
- }
-
- });
-
-
- client.on('disconnect', () => {
- console.log("disconnect : " + client.id);
- if (typeof sockets[client.id] != "undefined") {
- if (sockets[client.id].is_playing) {
-
- io.to(sockets[client.id].game_id).emit('opponentLeft', {});
- players[sockets[games[sockets[client.id].game_id].player1].mobile_number].played--;
- players[sockets[games[sockets[client.id].game_id].player2].mobile_number].played--;
- io.sockets.connected[client.id == games[sockets[client.id].game_id].player1 ? games[sockets[client.id].game_id].player2 : games[sockets[client.id].game_id].player1].leave(sockets[client.id].game_id);
- delete games[sockets[client.id].game_id];
- }
- }
- delete sockets[client.id];
- client.broadcast.emit('opponentDisconnected', {
- id: client.id
- });
- });
- });
-
-
- server.listen(PORT, HOST);
- console.log("listening to : " + HOST + ":" + PORT);
-
-
-
- function uuidv4() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
- var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
- return v.toString(16);
- });
- }
-
To run the server first go to the project directory
-
Now your server is ready, so now take a look at how front end with react js works
Front end with React JS
To run the project:
-
I will explain important topics here and you can clone repo for full source code.
-
So first I have created react js project named as “react-js-tic-tac-toe” using the below commands:
-
Then installed bootstrap dependency
-
Then installed socket.io client dependency
-
Then started project using
-
If you are using git repo then you do not need to follow above steps just but go to project root and run the below command:
In App.js, I have made a connection with the server using the below code:
- this.state = {
- endpoint: "http://127.0.0.1:4444",
- socket: null,
- isGameStarted: false,
- gameId:null,
- gameData: null,
- };
- componentDidMount() {
- const { endpoint } = this.state;
- const socket = socketIOClient(endpoint);
- socket.on("connected", data => {
- console.log(data);
- this.setState({ socket: socket })
- });
- }
Repos
Conclusion
In this article, we learned how to create a tic tac toe game using React js and Node js socket.io