Sure, here's a detailed README.md for your Node.js chess game project using Express, EJS, Socket.io, and Chess.js:
# Chess Game
This is a real-time chess game built with Node.js, Express, EJS, Socket.io, and Chess.js. It allows two players to play chess against each other and supports spectators to watch the game.
## Table of Contents
1. [Introduction](#introduction)
2. [Features](#features)
3. [Installation](#installation)
4. [Usage](#usage)
5. [File Structure](#file-structure)
6. [Technologies Used](#technologies-used)
7. [Contributing](#contributing)
8. [License](#license)
## Introduction
This project demonstrates how to build a real-time multiplayer chess game using Node.js for the backend, Express for server-side logic, EJS for templating, Socket.io for real-time communication, and Chess.js for the chess logic. The game supports multiple roles: white player, black player, and spectators.
## Features
- Real-time gameplay with Socket.io
- Chess logic handled by Chess.js
- Responsive design with Tailwind CSS
- Multiple player roles (white, black, and spectator)
- Interactive drag-and-drop chess pieces
## Installation
1. **Clone the repository:**
```sh
git clone https://github.com/yourusername/chess-game.git
cd chess-game
```
2. **Install dependencies:**
```sh
npm install
```
3. **Run the server:**
```sh
npm start
```
4. Open your browser and navigate to `http://localhost:5000`.
## Usage
- The first user to connect will be assigned the white player role.
- The second user to connect will be assigned the black player role.
- Additional users will join as spectators.
- Players can drag and drop pieces to make moves. The moves will be validated and broadcasted to all connected clients in real-time.
## File Structure
```plaintext
.
├── public
│ └── javaScript
│ └── chessGame.js # Client-side JavaScript
├── views
│ └── index.ejs # EJS template for the main page
├── .gitignore # Git ignore file
├── package.json # Project metadata and dependencies
├── server.js # Main server file
└── README.md # Project readme file- Node.js: JavaScript runtime for server-side development.
- Express: Web application framework for Node.js.
- EJS: Templating engine for generating HTML markup with plain JavaScript.
- Socket.io: Library for real-time web applications.
- Chess.js: JavaScript library for chess move generation/validation, piece placement/movement, and check/checkmate/stalemate detection.
- Tailwind CSS: Utility-first CSS framework for styling.
Contributions are welcome! Please fork the repository and submit a pull request for any changes you'd like to make.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/YourFeature) - Commit your Changes (
git commit -m 'Add some YourFeature') - Push to the Branch (
git push origin feature/YourFeature) - Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.
Here's a brief overview of the main components of this project:
This file sets up the server using Express, configures Socket.io for real-time communication, and initializes the chess game logic.
const express = require("express");
const http = require("http");
const { Chess } = require("chess.js");
const socket = require("socket.io");
const path = require("path");
const port = 5000;
const app = express();
const server = http.createServer(app);
const io = socket(server);
const chess = new Chess();
let players = {};
let currentPlayer = "w";
app.set("view engine", "ejs");
app.use(express.static(path.join(__dirname, "public")));
app.get("/", (req, res) => {
res.render("index", { title: "Chess Game" });
});
io.on("connection", (uniqueSocket) => {
console.log("A user joined.");
if (!players.white) {
players.white = uniqueSocket.id;
uniqueSocket.emit("playerRole", "w");
} else if (!players.black) {
players.black = uniqueSocket.id;
uniqueSocket.emit("playerRole", "b");
} else {
uniqueSocket.emit("spectatorRole");
}
uniqueSocket.on("disconnect", () => {
console.log("A user disconnected.");
if (players.white === uniqueSocket.id) {
delete players.white;
} else if (players.black === uniqueSocket.id) {
delete players.black;
}
});
uniqueSocket.on("move", (move) => {
try {
if (
(chess.turn() === "w" && players.white !== uniqueSocket.id) ||
(chess.turn() === "b" && players.black !== uniqueSocket.id)
)
return;
const result = chess.move(move);
if (result) {
currentPlayer = chess.turn();
io.emit("move", move);
io.emit("boardState", chess.fen());
} else {
console.log(`Invalid move: ${move}`);
uniqueSocket.emit("invalidMove", move);
}
} catch (error) {
console.log(error);
uniqueSocket.emit("invalidMove", move);
}
});
});
server.listen(port, () => {
console.log("Server is running on port " + port);
});This file handles the client-side logic for rendering the chessboard, managing drag-and-drop events, and communicating with the server via Socket.io.
const socket = io();
const chess = new Chess();
const boardElement = document.querySelector(".chessboard");
let draggedPiece = null;
let sourceSquare = null;
let playerRole = null;
const renderBoard = () => {
const board = chess.board();
boardElement.innerHTML = "";
board.forEach((row, rowIndex) => {
row.forEach((square, squareIndex) => {
const squareElement = document.createElement("div");
squareElement.classList.add(
"square",
(rowIndex + squareIndex) % 2 === 0 ? "light" : "dark"
);
squareElement.dataset.row = rowIndex;
squareElement.dataset.col = squareIndex;
if (square) {
const pieceElement = document.createElement("div");
pieceElement.classList.add(
"piece",
square.color === "w" ? "white" : "black"
);
pieceElement.innerText = getPieceUnicode(square.type, square.color);
pieceElement.draggable = playerRole === square.color;
pieceElement.addEventListener("dragstart", (e) => {
if (pieceElement.draggable) {
draggedPiece = pieceElement;
sourceSquare = { row: rowIndex, col: squareIndex };
e.dataTransfer.setData("text/plain", "");
console.log(`Drag started: ${pieceElement.innerText}`);
}
});
pieceElement.addEventListener("dragend", (e) => {
draggedPiece = null;
sourceSquare = null;
e.dataTransfer.setData("text/plain", "");
console.log(`Drag ended`);
});
squareElement.appendChild(pieceElement);
}
squareElement.addEventListener("dragover", (e) => {
e.preventDefault();
});
squareElement.addEventListener("drop", (e) => {
e.preventDefault();
if (draggedPiece) {
const targetSquare = {
row: Number(squareElement.dataset.row),
col: Number(squareElement.dataset.col),
};
handleMove(sourceSquare, targetSquare);
}
});
boardElement.appendChild(squareElement);
});
});
if (playerRole === "b") {
boardElement.classList.add("flipped");
} else {
boardElement.classList.remove("flipped");
}
};
const handleMove = (source, target) => {
const move = {
from: `${String.fromCharCode(97 + source.col)}${8 - source.row}`,
to: `${String.fromCharCode(97 + target.col)}${8 - target.row}`,
promotion: "q",
};
console.log(
`Move from ${source.row},${source.col} to ${target.row},${target.col}`
);
socket.emit("move", move);
};
const getPieceUnicode = (type, color) => {
const unicodeMap = {
p: "♟",
r: "♜",
n: "♞",
b: "♝",
q: "♛",
k: "♚",
P: "♙",
R: "♖",
N: "♘",
B: "♗",
Q: "♕",
K: "♔",
};
return color === "w"
? unicodeMap[type.toUpperCase()]
: unicodeMap[type.toLowerCase()];
};
socket.on("playerRole", (role) => {
playerRole = role;
console.log(`Player role set: ${role}`);
renderBoard();
});
socket.on("spectatorRole", () => {
playerRole = null;
console.log(`Spect
ator role set`);
renderBoard();
});
socket.on("boardState", (fen) => {
chess.load(fen);
renderBoard();
});
socket.on("move", (move) => {
chess.move(move);
console.log(`Move received: ${move}`);
renderBoard();
});
renderBoard();This file contains the HTML template for the main page, which includes the chessboard and necessary script references.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chess Game</title>
<link rel="stylesheet" href="https://cdn.tailwindcss.com" />
<style>
.chessboard {
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
width: 400px;
transform: rotate(0deg);
}
.flipped {
transform: rotate(180deg);
}
.square {
display: flex;
justify-content: center;
align-items: center;
}
.square.light {
background-color: #f0d9b5;
}
.square.dark {
background-color: #b58863;
}
.piece {
font-size: 36px;
cursor: pointer;
}
.piece.white {
color: white;
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 1));
}
.piece.black {
color: black;
}
.piece.draggable {
cursor: grab;
}
.dragging {
opacity: 0.5;
}
.flipped .piece {
transform: rotate(180deg);
}
@media (max-width: 400px) {
.chessboard {
width: 100%;
}
}
</style>
</head>
<body>
<div class="w-full h-screen flex items-center justify-center bg-zinc-900">
<div class="chessboard"></div>
</div>
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js"></script>
<script src="/javaScript/chessGame.js"></script>
</body>
</html>Distributed under the MIT License. See LICENSE for more information.
This `README.md` provides an overview of the project, instructions for setting it up, usage guidelines, a description of the file structure, and other relevant information. Adjust the repository URL and other details as needed for your specific project.