The first part of this article was totally dedicated to Nodejs server setup and building the socket events that would help our application to exchange the moves played between the players.The last part of the application is the front end, and needless to say that we will write in Angular as the title Multiplayer Tic-Tac-Toe Game using Angular, Nodejs suggests.Well, you know now what we are going to do in this article, the real question is how we will implement? Don’t worry that’s what we are going to do here down the road.
But before implementing this game one should know how it works? what are the wins condition? and how only two players play this game.If you know about all these things then good for you (Which I already know that you are familiar with it) and if you are not, please,search Googlefor tic-tac-toe and play till you understand the rules and how it works.
1. How does it work?
Before getting into details, let’s first break this application different part and understand how it will work. Also, by building each part we will ultimately complete our application.
=>So first thing first we will connect the players to respective socket rooms. Now here we will give a freedom to create a new room for each player and then he can ask his opponent to join that room. Now, this has nothing to do with game logic and we are using socket Rooms just to separate the payers from each other and to learn how to use rooms in socket.io.
=>Once opponent selects the room, he/she was told to, we will show them 9×9 Grid where they will play.
=>Once both the players are on the Ground and ready to fight, then we will subscribe to an event that event is nothing butreceive-move
event which we have already created on the server side using Nodejs.
=>Thereceive-move
event will emit the move played by the opponent and on the client side Angular will listen to it and render the played move on on the Grid.
=>Well,receive-move
event will emit only if someone starts the game, here we will give a privilege to only that player who has created a room (Say player 1) and invited another player (Say, player 2). So the player 1 one will play the first move and then thereceive-move
event will get triggered. So now you all know player Player 1 will always start the game.
=>So whenever Player 1 or Player 2 will play, we will emit an event from client to server and that event will besend-move
event, In this event, we will send below-listed parameters,
roomNumber
=>Needs to explanationsplayedText
=>This will value that we are going to render on the Grid i.eXORO.position
=>Position will the Grid number, example 1,3 or may 8 but below 9.playedGameGrid
=>Now this is a key parameter and this parameter will decide the winner of the game.movesPlayed
=>This will the number of moves played by a respective player in order to check if a match is over between two players or not.
=>Again I would like to remind you all as per client-side perspective,receive-move
event will listen to the event emitted by server andsend-move
event will emit the event from client to the server.
Now if you still have any doubts regarding the workflow read it again, If still, you have something that I have not covered above then drop a comment below and let me know.
2. Setting up our Application
We will set up our application with theAngular CLI. If you don’t haveAngular CLIinstalled on your machine run the below command to install it globally. Trust me it will make your development super fast.npm install -g angular-cli
To create a new Angular project Run below command. This command will create all the necessary files, download all the required external dependencies and do all of the setup work for us.
ng new xandzero
3. An Angular Serice
Now create appSerice in app folder using angular-cli, and write down the below code.In the below code we have definer and methods that we will consume in our component.
=>The gameGrid constant will be used to render the Grid on the Page and I kinda believe rest of the method are self-explanatory, even if they are not we will understand them when we will consume them in our component.
app.service.ts:
/* * @author Shashank Tiwari * Multiplayer Tic-Tac-Toe Game using Angular, Nodejs */import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Http, Response, Headers, RequestOptions } from '@angular/http'; /* Importing from rxjs library starts*/import { Subject } from 'rxjs/Subject'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; /* Importing from rxjs library ends*/ import * as io from 'socket.io-client'; @Injectable() export class AppService { /* Const and variable for Tic Tac Toe Game starts. */public gameGrid = <Array<Object>>[[1, 2, 3], [4, 5, 6], [7, 8, 9]]; /* Const and variable for Tic Tac Toe Game ends.*//* Const and variable for SocketEvent and HTTP call starts. */private BASE_URL = 'http://localhost:4000'; public socket; private headerOptions = new RequestOptions({ headers: new Headers({ 'Content-Type': 'application/json;charset=UTF-8' }) }); /* Const and variable for SocketEvent and HTTP call ends. */constructor(private http: HttpClient) {} /** * This method will call the HTTP request to get the Total room count and Available rooms to play */public getRoomStats() { return new Promise(resolve => { this.http.get(`http://localhost:4000/getRoomStats`).subscribe(data => { resolve(data); }); }); } /* * Method to connect the users to socket */connectSocket() { this.socket = io(this.BASE_URL); } /* Method to receive rooms-available event.*/getRoomsAvailable(): any { const observable = new Observable(observer => { this.socket.on('rooms-available', (data) => { observer.next( data ); }); return () => { this.socket.disconnect(); }; }); return observable; } /* Method to create new room, create-room event.*/createNewRoom(): any { this.socket.emit('create-room', { 'createroom': 1 }); const observable = new Observable(observer => { this.socket.on('new-room', (data) => { observer.next( data ); }); return () => { this.socket.disconnect(); }; }); return observable; } /* Method to join new room, create-room event.*/joinNewRoom(roomNumber): any { this.socket.emit('join-room', { 'roomNumber': roomNumber }); } /* Method to receive start-game event.*/startGame(): any { const observable = new Observable(observer => { this.socket.on('start-game', (data) => { observer.next( data ); }); return () => { this.socket.disconnect(); }; }); return observable; } /* Method to join new room, create-room event.*/sendPlayerMove(params): any { this.socket.emit('send-move', params); } /* Method to receive start-game event.*/receivePlayerMove(): any { const observable = new Observable(observer => { this.socket.on('receive-move', (data) => { observer.next( data ); }); return () => { this.socket.disconnect(); }; }); return observable; } /* Event to check the if any player is leaving the game */playerLeft(): any { const observable = new Observable(observer => { this.socket.on('room-disconnect', (data) => { observer.next( data ); }); return () => { this.socket.disconnect(); }; }); return observable; } }
4. Implementing Multiplayer Tic-Tac-Toe Game using Angular, Nodejs
The time has come to write something in our component file, so let’s start from markups. Openapp.component.html
file and write doen the below code.
The Markup
=>The markup is divided into two parts, The first part is will render the Grid and other useful stuff, which will show the different component of the page. The Second part is Modal pop up which will show the interface to join and create a new room for users.
=>In the first part I have used some properties ofAppComponent
class in order to display the Grid and to toggle the players turn while playing the game.
=>To render the played character we will userenderPlayedText()
method which eventually uses an array, In which we will push values according to the position which was clicked and the which player clicked it.
=>And then comes the second part the Modal Popup.The modal also divided into two blocks. In the first block, a user can Join into a new room or can create a new room by clicking on create new room button.
app.component.html:
<!-- @author Shashank Tiwari Multiplayer Tic-Tac-Toe Game using Angular, Nodejs --> <!-- A Play Ground, where User can with each other starts --> <div class="container"> <div class="row heading-row"> <div class="col-md-12 text-center app-heading"> <h2>{{title}}</h2> </div> <div class="col-md-6 text-center"> <div class="player-heading" [class.current-player]="displayPlayerTurn"> Player 1 </div> </div> <div class="col-md-6 text-center"> <div class="player-heading" [class.current-player]="!displayPlayerTurn"> Player 2 </div> </div> </div> <div class="row game-row"> <div class="text-center game-container"> <table id="game" class="game-grid" #game> <tr *ngFor="let iterationNumber of gameGrid"> <td *ngFor="let number of iterationNumber" class="grid-{{number}}" id="grid{{number}}" (click)="play(number)"> {{ renderPlayedText(number)}} </td> </tr> </table> <br/> <h6 class="text-center">You are in Room Number {{roomNumber}}.</h6> </div> </div> </div> <!-- A Play Ground, where User can with each other ends --> <!-- Modal to create and select Rooms starts --> <ng-template #content> <div class="modal-header"> <span>Total Rooms :{{totalRooms}}</span> </div> <div class="modal-body"> <div class="container"> <div class="join-game" [hidden]="roomNumber > 0 ? true :false"> <div class="room-list"> <h6 class="text-center">Available Rooms to Join</h6> <ul class="nav nav-pills"> <li class="nav-item room-number" *ngFor="let number of emptyRooms"> <a class="nav-link active" title="Join room Number {{number}}" (click)="joinRoom(number)">#{{number}}</a> </li> </ul> </div> <div class="create-room text-center"> <h5> <span>OR</span> </h5> <button class="btn btn-primary" (click)="createRoom()"> Create New Room</button> </div> </div> <br> <div class="player-waitng" [hidden]="roomNumber > 0 ? false :true"> <h2 class="text-center">Waiting for player to join</h2> <br/> <img class="game-loader" src="assets/img/loader.gif"> <br/> <br/> <h6 class="text-center">You are in Room Number {{roomNumber}}, Tell your friend to join Room Number {{roomNumber}}</h6> </div> </div> </div> </ng-template> <!-- Modal to create and select Rooms ends -->
2 The Component class
Now let’s end this puzzle by adding the last piece of it, Openapp.component.ts
file and write below the code, The below code is the whole and sole of the application so we need understand it properly and here I will try to explain each method of it and reason behind it.Below is the whole codeapp.component.ts
file,I know it’s too long but we will understand all the key methods and other factors.
=>The imports, I have imported theAppService
class and then I have used ng-bootstrap.
=>Then under the AppComponent class, there are some variables which we will require when a player will start a new game. The uses of these variables are listed below.
- gameGrid =>To display the Game Grid.
- playedGameGrid =>This will contain the information about the which grid is filled by which player and by using this array we will determine the winner.
- movesPlayed =>This will count the moved played a player.
- displayPlayerTurn =>This variable will show the player turn to play.
- myTurn =>This variable will disable the current player’s turn if he tried to play again and again.
- whoWillStart =>By using this variable we will decide which player will play first.
=>Now let’s start with methods which we are going use and defined. starting fromngOnInit()
method, The first we did that there is, we opened modal to display a number of rooms and button to create new rooms and just after that, we made HTTP call to get all these details.
=>After that we connected the players to socket server.
=>Then we have subscribed four events by calling the methods of AppService class., which I have shown below.
getRoomsAvailable()
=>This method of AppService class, which basically means we have subscribed to an event which will emit the event from the server side. This event will update the number of rooms available to play when a player creates a new room the server will emit this event.startGame()
=>This method will give an update to the players that your game is started.receivePlayerMove()
=>This method is subscribed to an event which will carry the moved played by each player.playerLeft()
=>This method will subscribe an event, which will notify the player that the opponent has left the game and automatically refresh the page.
=>AfterngOnInit()
method, ThejoinRoom()
method will send an event to the server along with the room number as a parameter. Once the server will listen to the event, it will emit the appropriate response.
=>ThecreateRoom()
method will obviously create a new on the server and in return, it will give the room number.
=>opponentMove()
will be called by the method, whenreceivePlayerMove()
method will listen to any event emitted from the server.
=>Theplay()
method will play it’s part when a player tries to play by click on the Grid. It will basically emit the event to the server by passing the appropriate parameter. Also in this method, we will push the data to intoplayedGameGrid
variable.
/* * @author Shashank Tiwari * Multiplayer Tic-Tac-Toe Game using Angular, Nodejs */import { Component, Renderer, ViewChild , OnInit } from '@angular/core'; import { AppService } from './app.service'; import { NgbModal, NgbModalOptions } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [AppService] }) export class AppComponent implements OnInit { /* * Initial Game Variables and constants starts */private title = 'Realtime Tic Tac Toe Using Angular 5 & Socket.IO — Rooms and Namespaces'; private gameGrid = <Array<Object>>[]; private playedGameGrid = <Array<Object>>[]; private movesPlayed = <number>0; private displayPlayerTurn = <Boolean>true; private myTurn = <Boolean>true; private whoWillStart = <Boolean>true; /* * Initial Game Variables and constants starts */ /*Bootstrap modal Options starts*/@ViewChild('content') private content; private modalOption: NgbModalOptions = {}; /* Bootstrap modal Options ends */ /*socket related Variable,ng-models and constant starts*/private totalRooms = <Number> 0; private emptyRooms = <Array<number>> []; private roomNumber = <Number> 0; private playedText = <string>''; private whoseTurn = 'X'; /*socket related Variable,ng-models and constant starts*/ constructor( private _renderer: Renderer, private modalService: NgbModal, private appService: AppService, ) { this.gameGrid = appService.gameGrid; } ngOnInit() { /*Code to display the Modal start*/this.modalOption.backdrop = 'static'; this.modalOption.keyboard = false; this.modalOption.size = 'lg'; const localModalRef = this.modalService.open(this.content, this.modalOption); /*Code to display the Modal start*/ // connect the player to the socket this.appService.connectSocket(); // HTTP call to get Empty rooms and total room numbers this.appService.getRoomStats().then(response => { this.totalRooms = response['totalRoomCount']; this.emptyRooms = response['emptyRooms']; }); // Socket evenet will total available rooms to play. this.appService.getRoomsAvailable().subscribe(response => { this.totalRooms = response['totalRoomCount']; this.emptyRooms = response['emptyRooms']; }); // Socket evenet to start a new Game this.appService.startGame().subscribe((response) => { localModalRef.close(); this.roomNumber = response['roomNumber']; }); // Socket event will receive the Opponent player's Move this.appService.receivePlayerMove().subscribe((response) => { this.opponentMove(response); }); // Socket event to check if any player left the room, if yes then reload the page. this.appService.playerLeft().subscribe((response) => { alert('Player Left'); window.location.reload(); }); } /** * Method to join the new Room by passing Romm Number * @param roomNumber */joinRoom(roomNumber) { this.myTurn = false; this.whoWillStart = false; this.whoseTurn = 'O'; this.appService.joinNewRoom(roomNumber); } /** * Method create new room */createRoom() { this.myTurn = true; this.whoseTurn = 'X'; this.whoWillStart = true; this.appService.createNewRoom().subscribe( (response) => { this.roomNumber = response.roomNumber; }); } /** * This method will be called by the socket event subscriber to make the Opponent players moves * @param params */opponentMove(params) { this.displayPlayerTurn = !this.displayPlayerTurn ? true : false; if (params['winner'] === null) { this.playedGameGrid[params['position']] = { position: params['position'], player: params['playedText'] }; this.myTurn = true; }else { alert(params['winner']); this.resetGame(); } } /** * This method will be called when the current user tries to play his/her move * Also we will send the socket event to the server. * @param number */play(number) { if (!this.myTurn) { return; } this.movesPlayed += 1; this.playedGameGrid[number] = { position: number, player: this.whoseTurn }; this.appService.sendPlayerMove({ 'roomNumber' : this.roomNumber, 'playedText': this.whoseTurn, 'position' : number, 'playedGameGrid': this.playedGameGrid, 'movesPlayed' : this.movesPlayed }); this.myTurn = false; this.displayPlayerTurn = !this.displayPlayerTurn ? true : false; } /** * This method will be used to render the data between the Grids. * @param number */renderPlayedText(number) { if (this.playedGameGrid[number] === undefined) { return ''; }else { this.playedText = this.playedGameGrid[number]['player']; return this.playedText; } } /** * As the name suggests here in this method we will reset the game. */resetGame() { this.playedGameGrid = []; this.gameGrid = []; this.gameGrid = this.appService.gameGrid; this.movesPlayed = 0; if (this.whoWillStart) { this.myTurn = true; this.displayPlayerTurn = true; this.whoseTurn = 'X'; }else { this.displayPlayerTurn = true; this.whoseTurn = 'O'; } } }
5. Conclusion
Well, that was the end of this article, Again I would like to summarise it by saying all the things we have learned here.
- We created socket server using socket.io rooms along with it we wrote logic to decide the winner of the game on the server side.
- On the client, we implemented the methodology to create a new room and join an existing room.
- And then we implemented full-fledged real-time Multiplayer Tic-Tac-Toe Game using Angular, Nodejs.
Now if you have any questions or doubts please feel free to comment and let me know in the below comment box, I will be happy to answer them And if you have any suggestion to improve this game then also do let me know also, This project is available on GitHub.
Github Links:
Client-side game in Angular: Github link
Nodejs server for Game: Github Link
Till then enjoy the game.
i implemented this source code but my modal pop up is not even opening. Help me pls
Hi Muhammad, what error you are getting, can you please post it here ?