Building a Multiplayer Tic Tac Toe Game with Firebase & Tailwind CSS
In this blog post, we'll explore a fun and interactive project—a multiplayer Tic Tac Toe game built with Firebase's realtime database, styled using Tailwind CSS, and enhanced with elegant alerts provided by SweetAlert2.
Project Overview
This project demonstrates how to build a real-time multiplayer game using modern web technologies. The key components include:
- Firebase Realtime Database: Handles game state synchronization between multiple players.
- Tailwind CSS: Provides a sleek and responsive UI with minimal effort.
- SweetAlert2: Displays stylish alerts, for example, to announce the winner or show error messages.
How the Game Works
The game allows users to either create a new game session or join an existing one using a unique game code. When a player creates a game, Firebase generates a random game ID and stores the game state (board, current turn, and players' names) in the database. Players take turns by clicking on the cells of a 3x3 grid, and the game automatically checks for a winning combination. If a winner is detected, a SweetAlert2 modal pops up to congratulate the winner and then resets the board for a rematch.
Code Walkthrough
The code is split into three main parts:
- HTML Structure & Styling: Uses Tailwind CSS classes along with custom styles for a modern, glassy look.
- Firebase Configuration & Initialization: Connects to the Firebase project and sets up the realtime database.
- Game Logic: Functions to create a game, join a game, update moves, and check for a winner.
Full Source Code
The complete code is shown below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tic-Tac-Toe Multiplayer</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-database.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
// Firebase Config
const firebaseConfig = {
apiKey: "",
authDomain: "cm",
databaseURL: "",
projectId: "chat-462",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
};
firebase.initializeApp(firebaseConfig);
const db = firebase.database();
</script>
<style>
body { background: #0f172a; color: white; font-family: sans-serif; }
.glass { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border-radius: 15px; padding: 20px; border: 1px solid rgba(255, 255, 255, 0.2); }
.neon-btn { border: 2px solid #38bdf8; box-shadow: 0px 0px 10px #38bdf8; }
.cell { transition: 0.3s; font-size: 2rem; }
.cell:hover { background: rgba(255, 255, 255, 0.2); }
</style>
</head>
<body class="flex flex-col items-center justify-center h-screen">
<div id="menu" class="glass p-8 text-center">
<h1 class="text-4xl font-bold text-cyan-400 mb-4">🔥 Tic-Tac-Toe Multiplayer 🔥</h1>
<div class="w-full max-w-sm min-w-[200px]">
<input id="playerNameInput" class="w-full bg-white placeholder:text-slate-400 text-slate-700 text-sm border border-slate-200 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-blue-500 hover:border-blue-300 shadow-sm focus:shadow" placeholder="Enter your name" />
</div>
<div>
<button class="neon-btn bg-transparent px-5 py-2 rounded-lg m-2 text-cyan-400" onclick="createGame()">🎮 New Game</button>
<button class="neon-btn bg-transparent px-5 py-2 rounded-lg m-2 text-cyan-400" onclick="showJoinGame()">🔑 Join by Code</button>
</div>
<div id="joinDiv" class="hidden mt-4">
<input type="text" id="gameCode" placeholder="Enter Code" class="p-2 rounded-lg text-black">
<button class="neon-btn bg-transparent px-4 py-2 rounded-lg text-cyan-400" onclick="joinGame()">Join</button>
</div>
</div>
<div id="game" class="hidden glass p-8 text-center">
<h2 class="text-2xl font-bold text-cyan-400">Game Code: <span id="gameId"></span></h2>
<h3 class="text-lg mt-2">Player: <span id="playerName"></span> (<span id="playerSymbol"></span>)</h3>
<div class="grid grid-cols-3 gap-2 mx-auto w-64 mt-4">
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(0)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(1)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(2)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(3)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(4)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(5)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(6)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(7)"></div>
<div class="cell bg-gray-700 text-cyan-400 p-8 flex justify-center items-center rounded-lg cursor-pointer" onclick="makeMove(8)"></div>
</div>
<button class="neon-btn bg-transparent px-4 py-2 rounded-lg mt-4 text-cyan-400" onclick="restartGame()">🔄 Rematch</button>
</div>
<script>
let gameId, playerSymbol, playerName;
let gameRef, board = ["", "", "", "", "", "", "", "", ""];
function createGame() {
playerName = document.getElementById("playerNameInput").value;
if (!playerName) return Swal.fire("Error", "Enter your name!", "error");
gameId = Math.floor(100000 + Math.random() * 900000);
gameRef = db.ref("games/" + gameId);
gameRef.set({ board: ["", "", "", "", "", "", "", "", ""], turn: "X", players: { X: playerName } });
startGame("X");
}
function showJoinGame() {
document.getElementById("joinDiv").classList.remove("hidden");
}
function joinGame() {
playerName = document.getElementById("playerNameInput").value;
if (!playerName) return Swal.fire("Error", "Enter your name!", "error");
gameId = document.getElementById("gameCode").value;
gameRef = db.ref("games/" + gameId);
gameRef.once("value", snapshot => {
if (snapshot.exists()) {
gameRef.child("players/O").set(playerName);
startGame("O");
} else {
Swal.fire("Invalid Code!", "Check and try again.", "error");
}
});
}
function startGame(symbol) {
playerSymbol = symbol;
document.getElementById("gameId").innerText = gameId;
document.getElementById("playerName").innerText = playerName;
document.getElementById("playerSymbol").innerText = symbol;
document.getElementById("menu").classList.add("hidden");
document.getElementById("game").classList.remove("hidden");
gameRef.on("value", snapshot => {
let data = snapshot.val();
if (data) {
board = data.board;
updateBoard();
checkWinner();
}
});
}
function makeMove(index) {
gameRef.once("value", snapshot => {
let data = snapshot.val();
if (data.turn !== playerSymbol || board[index] !== "") return;
board[index] = playerSymbol;
gameRef.update({ board: board, turn: playerSymbol === "X" ? "O" : "X" });
});
}
function updateBoard() {
document.querySelectorAll(".cell").forEach((cell, index) => cell.innerText = board[index]);
}
function checkWinner() {
const winPatterns = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
for (let pattern of winPatterns) {
if (board[pattern[0]] && board[pattern[0]] === board[pattern[1]] && board[pattern[0]] === board[pattern[2]]) {
Swal.fire("🎉 Winner!", `Player ${board[pattern[0]]} Wins!`, "success");
gameRef.update({ board: ["", "", "", "", "", "", "", "", ""] });
return;
}
}
}
</script>
</body>
</html>
Conclusion
This project is a fantastic example of combining Firebase for real-time interactivity, Tailwind CSS for a modern UI, and SweetAlert2 for user-friendly alerts. Whether you're just getting started with web development or are looking to enhance your skills with real-time applications, exploring this code can provide plenty of inspiration. Feel free to experiment with the code and add your own twists!