This page contains a complete beginner-friendly tutorial that shows how to run the Tic-Tac-Toe demo locally, create a Firebase Realtime Database, and deploy to GitHub Pages or Firebase Hosting.
Create index.html and js/index1.js with the code provided below (the game is on the right column).
Because the demo uses ES modules and an import map, you must serve files over HTTP. The simplest options:
# Option A: serve with a small static server (recommended)
npx http-server -c-1
# Option B: use Python 3 (if installed)
python -m http.server 8000
Open http://localhost:8080 (or port printed by the server). The game will load.
js/index1.js with yours if you want to use your own project. The demo includes an example config (for demonstration only).Open a terminal and run:
npm install -g firebase-tools
Then login to your account:
firebase login
Now you can initialize Firebase Hosting inside the project folder (optional — for Firebase Hosting deploy):
firebase init hosting
Follow prompts: select your project, choose "public" directory as public (or keep root), single-page app? answer N because this is not a SPA routing app unless you want it, and don't overwrite your index.html if asked.
Typical steps (from project root):
git init
git add .
git commit -m "Initial commit"
# create a repo on GitHub, then:
git remote add origin https://github.com/USERNAME/REPO.git
git branch -M main
git push -u origin main
# Enable GitHub Pages:
# - In repo Settings → Pages → Source select branch 'main' and folder '/' or 'gh-pages' branch.
GitHub Pages will publish your site (you may need to enable Pages in repo settings). URL will be https://USERNAME.github.io/REPO/.
# after firebase init hosting
firebase deploy --only hosting
Firebase will print the hosting URL where your site is available.
Using test mode for Realtime Database leaves data open for a while. For production, set proper database rules and consider using Authentication so only authorized users can change state.
/games/game1 in the Realtime Database inspector in Firebase Console.Below are the two files used by the site.
index.html (already this page) — the playable demo is to the right.js/index1.js// See the separate file js/index1.js (provided on the right column / file)
На этой странице — полное руководство для начинающих: запуск локально, создание Firebase Realtime Database и деплой на GitHub Pages или Firebase Hosting.
Создайте index.html и js/index1.js с кодом (игра находится в правой колонке).
Так как используются ES модули и importmap, файлы нужно открывать по HTTP. Самые простые варианты:
# Вариант A: лёгкий http сервер
npx http-server -c-1
# Вариант B: Python 3 (если установлен)
python -m http.server 8000
Откройте http://localhost:8080 (или порт, который укажет сервер).
js/index1.js.npm install -g firebase-tools
firebase login
firebase init hosting
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/USERNAME/REPO.git
git branch -M main
git push -u origin main
# В Settings → Pages включите публикацию (branch main).
firebase deploy --only hosting
Test mode открывает базу данных — в продакшене настройте правила и аутентификацию.
本页提供面向初学者的完整教程:在本地运行井字棋演示,创建 Firebase Realtime Database,并部署到 GitHub Pages 或 Firebase Hosting。
创建 index.html 与 js/index1.js,其代码已放在页面右侧(演示区)。
由于使用了 ES modules 与 importmap,必须通过 HTTP 提供文件。常见方法:
# 选项 A:使用 http-server(推荐)
npx http-server -c-1
# 选项 B:如果安装了 Python 3
python -m http.server 8000
js/index1.js 中的配置(如果你想使用自己的项目)。npm install -g firebase-tools
firebase login
firebase init hosting
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/USERNAME/REPO.git
git branch -M main
git push -u origin main
# 在 GitHub 仓库设置中启用 Pages(选择 main 分支)。
firebase deploy --only hosting
测试模式会打开数据库访问。投入生产前,请配置数据库规则与鉴权。
Open this page in two browser windows to play across them (each window becomes a player).
index.html (this file) — includes UI and tutorial.
js/index1.js — game logic and Firebase connector (below).
// This is the client JS (save as js/index1.js)
import { initializeApp } from "firebase/app";
import { getDatabase, ref, set, get, onValue, update } from "firebase/database";
const firebaseConfig = {
// IMPORTANT: Replace this with your own Firebase config from Project Settings -> SDK setup for web
apiKey: "AIzaSyAOQeReL7V3h5LtGC141xX2NtA82p9InHM",
authDomain: "tic-tac-toe-network-v1-0-js.firebaseapp.com",
databaseURL: "https://tic-tac-toe-network-v1-0-js-default-rtdb.europe-west1.firebasedatabase.app",
projectId: "tic-tac-toe-network-v1-0-js",
storageBucket: "tic-tac-toe-network-v1-0-js.firebasestorage.app",
messagingSenderId: "966053462012",
appId: "1:966053462012:web:5495e05cd7220d7992bc79",
measurementId: "G-4H2WE6VF8L"
};
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
// Game state
const gameId = "game1"; // demo — a single shared game
const gameRef = ref(db, "games/" + gameId);
const boardDiv = document.getElementById("board");
const status = document.getElementById("status");
const restartBtn = document.getElementById("restart");
// Generate a simple unique id for this client
const myId = "player-" + Date.now();
let myPlayer = null;
// Initialize board UI
const cells = [];
for (let i = 0; i < 9; i++) {
const cell = document.createElement("div");
cell.classList.add("cell");
cell.dataset.index = i;
cell.addEventListener("click", () => makeMove(i));
boardDiv.appendChild(cell);
cells.push(cell);
}
// Initialize game in RTDB if not exists
async function initGame() {
const snapshot = await get(gameRef);
if (!snapshot.exists()) {
await set(gameRef, {
board: ["", "", "", "", "", "", "", "", ""],
turn: "X",
winner: null
});
}
}
initGame();
// Join game
async function joinGame() {
const snapshot = await get(gameRef);
const data = snapshot.val() || {};
if (!data.playerX) {
myPlayer = "X";
await update(gameRef, { playerX: myId });
} else if (!data.playerO) {
myPlayer = "O";
await update(gameRef, { playerO: myId });
} else {
alert("Game is full");
}
}
await joinGame();
restartBtn.addEventListener("click", async () => {
await set(gameRef, {
board: ["", "", "", "", "", "", "", "", ""],
turn: "X",
winner: null
});
});
// Listen to game changes
onValue(gameRef, (snapshot) => {
const data = snapshot.val();
if (!data) return;
data.board.forEach((v, i) => cells[i].textContent = v);
if (data.winner) {
status.textContent = data.winner === "Draw" ? "Draw!" : `Winner: ${data.winner}`;
} else {
status.textContent = `Turn: ${data.turn}`;
}
});
// Make a move
async function makeMove(index) {
const snapshot = await get(gameRef);
const data = snapshot.val();
if (!data || data.winner) return;
// Only current player can move
if (data.turn !== myPlayer) return;
// Check if cell is empty
if (data.board[index] !== "") return;
// Make move
const newBoard = [...data.board];
newBoard[index] = data.turn;
// Check winner
const winner = checkWinner(newBoard);
// Update database
await update(gameRef, {
board: newBoard,
turn: data.turn === "X" ? "O" : "X",
winner: winner
});
}
// Check winner
function checkWinner(b) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8], // rows
[0, 3, 6],
[1, 4, 7],
[2, 5, 8], // cols
[0, 4, 8],
[2, 4, 6] // diagonals
];
for (const [a, b1, c] of lines) {
if (b[a] && b[a] === b[b1] && b[a] === b[c]) return b[a];
}
return b.includes("") ? null : "Draw";
}
Notes:
await. It works in modern browsers when served via HTTP (not opened as file://).firebaseConfig with your own project's config from Firebase Console.Language switch: open this page with ?lang=en, ?lang=ru, or ?lang=zh to show English, Russian or Chinese tutorial content directly.