This blog post I posted after two weeks since I posted my previous blog post. This time we will develop a big hit and world-famous indoor game that is “CHESS”
In this programming example, We are going to develop a multiplayer chess application. It is helpful for a newbie who is learning NodeJs and Socket.io.
Prerequisites
- Basic knowledge about NodeJs and ES2015/ES2016
- HandleBarJs template engine
- Basic knowledge of socket.io
- Understand Chess Rules
Code Parts
- Templates
- Socket.io server end controller
- Socket.io client end functions.
Required NodeJs Modules
- socket.io
- hbs
- express
- nodemon => it is just for development mode only.
Directory Structure

Index File
root/index.js
const app = require('./app/aap');
const http = require('http').createServer(app);
const io = require('socket.io')(http);
const socketSever = require('./app/controllers/socketServer');
socketSever(io);
//set port and listen request
const PORT = process.env.PORT || 8080;
http.listen(PORT, () => {
console.log('current server runing on PORT : '+PORT);
});
Application initiliaztion
root/app/index.js
const express = require('express');
const app = express();
const path = require('path');
const hbs = require('hbs');
// set the view engine to use handlebars
app.set('view engine', 'hbs');
app.set('views', path.join(__dirname, 'views'));
app.use('/', express.static(path.join(__dirname, '../public')));
app.get('/', (req, res) => {
//res.send('Chess Application')
res.render('index', {
layout: 'layout',
title: 'Chess Application',
page_title: 'Chees Board'
})
});
module.exports = app;
Template Layouts
app/views/layouts.hbs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/lib/chessboardjs/css/chessboard-1.0.0.min.css"/>
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
</head>
<body>
<div class="container">
{{{body}}}
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
</script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.2/chess.js"></script>
<script src="/lib/chessboardjs/js/chessboard-1.0.0.min.js"></script>
<script src="/js/chess.js"></script>
<script src="/js/socketClient.js"></script>
</body>
</html>
app/view/index.hbs
<div class="row">
<div class="col-md-9">
<h1>{{page_title}}</h1>
<form class="form-inline" id="userNameForm">
<div class="form-group mx-sm-3 mb-2">
<label for="userNameInput" class="mr-2">Your Name</label>
<input type="text" name="userNameInput" id="userNameInput" class="form-control"/>
</div>
<button type="submit" class="btn btn-success mb-2">Add Your Name</button>
</form>
<h3 id="userName"></h3>
<div class="notification"></div>
<div id="chessBoard" style="width:450px;padding-top:50px; margin:auto;"></div>
</div>
<div class="col-md-3">
<h2>Online Players</h2>
<ul class="list-group" id="onlinePlayers">
<li class="list-group-item">
<button type="button" class="btn btn-primary btn-sm">Play Against CPU</button>
</li>
</ul>
</div>
</div>
Socket.io server Controller
app/controllers/socketServer.js
const users = [];
function randomRoomId(){
let roomId = '';
let length = 12;
let randomChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(counter = 0; counter < length; counter++){
roomId += randomChar.charAt(Math.floor(Math.random() * randomChar.length));
}
return roomId;
}
exports = module.exports = function(io){
io.on('connection', (socket) => {
socket.on('submitName', (formData) => {
let userName = formData.name;
let room = randomRoomId();
users.push({
id: socket.id,
name: userName,
room: room
});
// console.log(users);
socket.join(room);
socket.broadcast.emit("roomDetail", {
users: users,
});
});
socket.emit('existingUsers', {
users:users,
currentUserId: socket.id
});
socket.on('sendJoinRequest', (requestData) => {
//console.log(requestData.room);
let user = users.filter(user=>user.id == socket.id)[0];
socket.broadcast.to(requestData.room).emit('joinRequestRecieved', {
id: user.id,
name: user.name,
room: user.room
});
});
socket.on('acceptGameRequest', (requestData) => {
let user = users.filter(user=>user.id == socket.id)[0];
socket.broadcast.to(requestData.room).emit('gameRequestAccepted', {
id: user.id,
name: user.name,
room: user.room,
});
});
socket.on('setOrientation', (requestData) => {
let user = users.filter(user=>user.id == socket.id)[0];
socket.broadcast.to(requestData.room).emit('setOrientationOppnt', {
color: requestData.color,
id: user.id,
name: user.name,
room: user.room,
});
});
socket.on('chessMove', (requestData) => {
console.log(requestData);
socket.broadcast.to(requestData.room).emit('oppntChessMove',{
color: requestData.color,
from: requestData.from,
to: requestData.to,
piece: requestData.piece,
promo: requestData.promo||''
});
});
socket.on('gameWon', (requestData) => {
socket.broadcast.to(requestData.room).emit('oppntWon');
});
socket.on('disconnect', () => {
for(i = 0; i< users.length; i++){
if(users[i].id == socket.id){
users.splice(i,1);
break;
}
}
});
});
}
Socket.io client-end functionality
public/js/socketClient.js
$(function(){
$(document).on('submit', '#userNameForm', function(event){
event.preventDefault();
socket.emit('submitName', {
name: $('#userNameInput').val(),
});
$('#userName').text('Hi '+$('#userNameInput').val());
$('#userNameForm').hide();
$('#userNameInput').val('');
});
socket.on('roomDetail', (roomData) => {
// $('#onlinePlayers').html('');
roomData.users.forEach(user => {
$('#onlinePlayers')
.append($('<li class="list-group-item" id="'+user.id+'">')
.html('<button type="button" data-room="'+user.room+'" class="btn btn-primary btn-sm joinGameRequest">'+user.name+'</button>'));
});
});
socket.on('existingUsers', (userData) => {
// $('#onlinePlayers').html('');
userData.users.forEach(user => {
if(userData.currentUserId != user.id){
$('#onlinePlayers')
.append($('<li class="list-group-item" id="'+user.id+'">')
.html('<button type="button" data-room="'+user.room+'" class="btn btn-primary btn-sm joinGameRequest">'+user.name+'</button>'));
}
});
});
socket.on('joinRequestRecieved', (userData) => {
//console.log(userData);
$('.notification')
.html('<div class="alert alert-success">Recieved a game request from <strong>'+userData.name+'</strong>. <button data-room="'+userData.room+'" class="btn btn-primary btn-sm acceptGameRequest">Accept</button></div>')
});
$(document).on('click', '.joinGameRequest', function(){
socket.emit('sendJoinRequest', {
room: $(this).data('room')
});
$('.notification').html('<div class="alert alert-success">Game request sent.</div>');
});
$(document).on('click', '.acceptGameRequest', function(){
socket.emit('acceptGameRequest', {
room: $(this).data('room')
});
$('.notification')
.html('<div class="alert alert-success">Please wait for game initialize from host.</div>');
});
socket.on('gameRequestAccepted', (userData) => {
//console.log(userData);
$('.notification')
.html('<div class="alert alert-success">Game request accepted from <strong>'+userData.name+'</strong>.</div>');
$('.notification')
.append($('<div class="text-center">'))
.append('Choose rotation. <button data-room="'+userData.room+'" data-color="black" type="button" class="btn btn-primary btn-sm setOrientation">Black</button> or <button data-room="'+userData.room+'" data-color="white" type="button" class="btn btn-primary btn-sm setOrientation">White</button>');
$('#onlinePlayers li#'+userData.id).addClass('active');
});
socket.on('opponentDisconnect',function(){
$('.notification')
.html('<div class="alert alert-success">Opponent left the room</div>');
board.reset();
chess.reset();
});
}(jQuery));
public/js/chess.js
var board = null;
var chess = new Chess();
const boardConfig = {
draggable: true,
dropOffBoard: 'trash',
onDragStart: onDragStart,
onDrop: onDrop,
}
var isMachinePlayer = false;
board = Chessboard('chessBoard', boardConfig);
function onDragStart (source, piece, position, orientation) {
//
// console.log(chess.turn());
if(chess.in_checkmate()){
let confirm = window.confirm("You Lost! Reset the game?");
let room = $('#onlinePlayers li.active button').data('room');
if(confirm){
if(isMachinePlayer){
chess.reset();
board.start();
} else {
//socket.requestNewGame();
socket.emit('gameWon', {
room: room,
});
}
}
}
// do not pick up pieces if the game is over
// or if it's not that side's turn
if ( chess.game_over() ||
(chess.turn() === 'w' && piece.search(/^b/) !== -1) ||
(chess.turn() === 'b' && piece.search(/^w/) !== -1)) {
return false
}
}
function onDrop(source, target, piece, newPos, oldPos, orientation){
// see if the move is legal
let turn = chess.turn();
let room = $('#onlinePlayers li.active button').data('room');
let move = chess.move({
color: turn,
from: source,
to: target,
//promotion: document.getElementById("promote").value
});
// illegal move
if (move === null) return 'snapback';
updateStatus();
//player just end turn, CPU starts searching after a second
if(isMachinePlayer){
//window.setTimeout(chessEngine.prepareAiMove(),500);
}
else {
socket.emit('chessMove', {
room: room,
color: turn,
from: move.from,
to: move.to,
piece: move.piece
});
}
}
function updateStatus(){
let status = "";
let moveColor = "White";
if(chess.turn()=='b'){
moveColor = "Black";
}
if(chess.in_checkmate()==true){
status= "You won, " + moveColor + " is in checkmate";
window.alert(status);
if(isMachinePlayer){
chess.reset();
board.start();
}
return;
} else if(chess.in_draw()){
status = "Game Over, Drawn";
window.alert(status);
return;
}
}
$(function(){
$(document).on('click', '.setOrientation', function(){
socket.emit('setOrientation', {
room: $(this).data('room'),
color: ($(this).data('color') === 'black') ? 'white': 'black'
});
board.orientation( $(this).data('color') );
board.start();
if($(this).data('color') == 'black'){
$('.notification')
.html('<div class="alert alert-success">Great ! Let\'s start game. You choose Black. Wait for White Move.</div>');
}else{
$('.notification')
.html('<div class="alert alert-success">Great ! Let\'s start game. You choose White. Start with First Move.</div>');
}
});
socket.on('setOrientationOppnt', (requestData) => {
//console.log(requestData);
board.orientation(requestData.color);
board.start();
$('#onlinePlayers li#'+requestData.id).addClass('active');
if(requestData.color == 'white'){
$('.notification')
.html('<div class="alert alert-success">Game is initialized by <strong>'+requestData.name+'</strong>. Let\'s start with First Move.</div>');
} else{
$('.notification')
.html('<div class="alert alert-success">Game is initialized by <strong>'+requestData.name+'</strong>. Wait for White Move.</div>');
}
});
socket.on('oppntChessMove', (requestData) => {
console.log(requestData);
let color = requestData.color;
let source = requestData.from;
let target = requestData.to;
let promo = requestData.promo||'';
chess.move({from:source,to:target,promotion:promo});
board.position(chess.fen());
//chess.move(target);
//chess.setFenPosition();
});
socket.on('oppntWon', (requestData) => {
$('.notification')
.html('<div class="alert alert-success">You Won !!</div>');
chess.reset();
board.reset();
});
});
ChessboardJS library implementation
- Download the library files from GitHub.
- Under public/lib/chessboardjs folder. Place JS and Css files.
- Images put them under public/img
Get the full working source code from the GitHub repository.

