A full-stack multiplayer Tic-Tac-Toe web application built with React (TypeScript), Node.js, PostgreSQL, and Socket.IO, featuring real-time gameplay, JWT authentication, game history tracking, and a modern UI using Material UI.
- Multiplayer real-time gameplay using Socket.IO
- JWT Authentication & secure access
- Create & join game rooms using Room ID
- Turn-based game validation (prevents cheating / invalid moves)
- Automatic win / draw detection
- Game restart system with player confirmation + countdown
- Side swapping after restart (X โ O)
- Persistent move tracking in database (
game_moves) - Match history page (Win / Loss / Draw results)
- Replay system (load game moves from database)
- Modern responsive UI built with Material UI
- Sound effects + confetti celebration
- React
- TypeScript
- Material UI
- Axios
- Socket.IO Client
- Node.js
- Express.js
- TypeScript
- Socket.IO
- JWT Authentication
- PostgreSQL
- Tables:
usersgamesgame_moves
tic-tac-toe/
โ
โโโ frontend/ # React frontend
โ โโโ src/
โ โ โโโ api/ # Axios/Fetch configurations
โ โ โโโ assets/ # Styles, images, and fonts
โ โ โโโ components/ # Reusable UI components
โ โ โโโ hooks/ # Custom React hooks
โ โ โโโ pages/ # Main screen views
โ โ โโโ utils/ # Helper functions
โ โโโ package.json
โ
โโโ backend/ # Node.js backend
โ โโโ src/
โ โ โโโ config/ # Database and env configurations
โ โ โโโ controllers/ # Request handlers
โ โ โโโ middleware/ # Auth and validation logic
โ โ โโโ models/ # Database schemas
โ โ โโโ routes/ # API endpoints
โ โ โโโ sockets/ # Socket.IO event logic
โ โ โโโ utils/ # Shared utility functions
โ โ โโโ app.ts # Express app setup
โ โ โโโ server.ts # Server entry point
โ โโโ package.json
โ
โโโ README.md
git clone https://github.com/savaxc/tic-tac-toe-multiplayer.git
cd tic-tac-toe-multiplayer#Create a PostgreSQL database:
CREATE DATABASE tictactoe;#Then create the required tables (example):
CREATE TABLE games (
id SERIAL PRIMARY KEY,
room_id VARCHAR(255) NOT NULL,
player_x_user_id INT NOT NULL,
player_o_user_id INT,
winner VARCHAR(10),
created_at TIMESTAMP DEFAULT NOW(),
finished_at TIMESTAMP
);
CREATE TABLE game_moves (
id SERIAL PRIMARY KEY,
game_id INT NOT NULL REFERENCES games(id) ON DELETE CASCADE,
move_index INT NOT NULL,
board TEXT NOT NULL
);#Go to backend(server) folder:
cd server
npm install#Create .env file inside /backend:
PORT=8080
JWT_SECRET=your_secret_key
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=tictactoe
DB_PORT=5432#Run backend:
npm run dev#Backend will run on:
http://localhost:8080#Go to frontend(client) folder:
cd ../frontend
npm install#Run frontend
npm run dev#Frontend will run on:
http://localhost:5173This project uses JWT authentication.
- Users must log in to generate a token.
- Token is stored in localStorage.
- Socket.IO connection requires JWT token via handshake.
- Create a Game: Player 1 creates a new game session.
- Room ID: The system generates a unique Room ID.
- Join: Player 2 enters the Room ID to join the match.
- Gameplay: Players take turns placing their symbols (X or O).
- Detection: The system automatically detects the Winner or a Draw.
- Storage: All game results are automatically stored in the database.
- Restart: Restart option allows both players to restart with a confirmation system.
The game features a synchronized restart mechanism to ensure both players are ready for a rematch.
- Mutual Confirmation: Restarting requires approval from both players.
- Countdown Timer: A timer starts as soon as the first player requests a restart.
-
New Match Logic: Once both players confirm:
- The old game is closed.
- A new game is created within the same room.
-
Symbol Swap: Players automatically swap symbols (
$X \leftrightarrow O$ ) for the new round.
Each player has access to a personalized history of their past performance.
You can track your progress through the following match data:
- Opponent Username: See who you played against.
- Match Result: Clear indicators of WIN, LOSS, or DRAW.
- Date and Time: Precise timestamp of when the match took place.
Stored moves allow replaying a full game by fetching:
GET /game/:gameId/movesCreate Game
POST /game/createGet Game History
GET /game/historyGet Game Moves (Replay)
GET /game/:gameId/movesThe real-time communication is handled via Socket.IO events to ensure seamless synchronization between players.
These events are sent from the player's browser to the server.
| Event | Description |
|---|---|
joinRoom |
Sent when a player enters a Room ID to join a session. |
playerMove |
Sent when a player clicks on a cell to place their symbol. |
gameOver |
Triggered when a terminal state (Win/Draw) is reached locally. |
requestRestart |
Sent when a player initiates or confirms a rematch request. |
The client listens for these events to update the UI in real-time.
| Event | Description |
|---|---|
assignSymbol |
Informs the player if they are playing as X or O. |
opponentMove |
Notifies the client of the specific move made by the other player. |
opponentConnected |
Sent when the second player successfully enters the room. |
opponentLeft |
Alerts the player if their opponent disconnects during the game. |
restartCountdown |
Triggers the visual timer when the first restart request is made. |
restartConfirmed |
Notifies both clients to reset the board and swap symbols. |
restartCanceled |
Sent if the countdown expires without a mutual confirmation. |
gameFinished |
Finalizes the match state and triggers the database storage. |
๐ Full-Stack Developer (React + Node.js + PostgreSQL)