Tic-Tac-Toe — Tutorial

English Русский 中文

Step-by-step tutorial (English)

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.

1) What you'll get

  • A working Tic-Tac-Toe game (browser-based) that stores its shared state in Firebase Realtime Database (RTDB).
  • A static website you can host on GitHub Pages.

2) Prerequisites

  1. A modern browser (Chrome, Edge, Firefox).
  2. Node.js & npm (for installing firebase-tools). Download from nodejs.org.
  3. git and a GitHub account (for GitHub Pages), or a Firebase project if you want to use Firebase Hosting.

3) Project files

Create index.html and js/index1.js with the code provided below (the game is on the right column).

4) How to run locally

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.

5) Firebase setup — create a new project and enable Realtime Database

  1. Go to Firebase Console and click "Add project". Follow the wizard to create a project.
  2. In the left menu choose Realtime DatabaseCreate database. Start in test mode (for development). This sets rules to allow reads/writes temporarily — don't use test mode in production.
  3. In the project settings (gear icon) go to Your apps and add a Web app. You'll get a Firebase config object — copy it. Replace the example config in js/index1.js with yours if you want to use your own project. The demo includes an example config (for demonstration only).

6) Install firebase-tools (CLI)

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.

7) Deploy options

A) Deploy to GitHub Pages

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/.

B) Deploy to Firebase Hosting

# after firebase init hosting
firebase deploy --only hosting

Firebase will print the hosting URL where your site is available.

8) Security note

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.

9) Troubleshooting

  • If the game doesn't connect, open browser console (F12) and look for errors. Check that your Firebase config matches your project and that RTDB is enabled.
  • If two players cannot join, check the data at /games/game1 in the Realtime Database inspector in Firebase Console.

10) Source code (client)

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.

1) Что вы получите

  • Работающая игра Крестики-Нолики в браузере, состояние хранится в Firebase Realtime Database.
  • Статический сайт, готовый для размещения на GitHub Pages.

2) Требования

  1. Современный браузер (Chrome, Edge, Firefox).
  2. Node.js и npm (для установки firebase-tools). Скачать: nodejs.org.
  3. git и аккаунт GitHub (для GitHub Pages) или проект в Firebase (если вы хотите хостить на Firebase Hosting).

3) Файлы проекта

Создайте index.html и js/index1.js с кодом (игра находится в правой колонке).

4) Как запустить локально

Так как используются ES модули и importmap, файлы нужно открывать по HTTP. Самые простые варианты:

# Вариант A: лёгкий http сервер
npx http-server -c-1

# Вариант B: Python 3 (если установлен)
python -m http.server 8000

Откройте http://localhost:8080 (или порт, который укажет сервер).

5) Настройка Firebase — проект и Realtime Database

  1. Перейдите в Firebase Console, нажмите «Add project» и создайте проект.
  2. В меню выберите Realtime DatabaseCreate database. Для разработки можно выбрать test mode (временное разрешение на чтение/запись).
  3. В настройках проекта добавьте Web-приложение и получите конфиг — вставьте его в js/index1.js.

6) Установка firebase-tools

npm install -g firebase-tools
firebase login
firebase init hosting

7) Деплой

A) GitHub Pages

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).

B) Firebase Hosting

firebase deploy --only hosting

8) Безопасность

Test mode открывает базу данных — в продакшене настройте правила и аутентификацию.

9) Отладка

  • Откройте консоль браузера (F12) для ошибок.
  • Проверяйте содержимое /games/game1 в консоли Firebase.

逐步教程(中文)

本页提供面向初学者的完整教程:在本地运行井字棋演示,创建 Firebase Realtime Database,并部署到 GitHub Pages 或 Firebase Hosting。

1)成果

  • 一个将共享状态存储在 Firebase Realtime Database 中的浏览器井字棋游戏。
  • 一个可托管在 GitHub Pages 的静态网站。

2)先决条件

  1. 现代浏览器(Chrome/Edge/Firefox)。
  2. 安装 Node.js 和 npm(用于安装 firebase-tools)。下载:nodejs.org
  3. git 与 GitHub 账号(用于 GitHub Pages)或 Firebase 项目(用于 Firebase Hosting)。

3)项目文件

创建 index.htmljs/index1.js,其代码已放在页面右侧(演示区)。

4)本地运行

由于使用了 ES modules 与 importmap,必须通过 HTTP 提供文件。常见方法:

# 选项 A:使用 http-server(推荐)
npx http-server -c-1

# 选项 B:如果安装了 Python 3
python -m http.server 8000

5)Firebase 设置

  1. 访问 Firebase 控制台 并创建项目。
  2. 在左侧选择 Realtime DatabaseCreate database,开发期间可选择 test mode。
  3. 在项目设置里添加 Web 应用,复制项目配置并替换 js/index1.js 中的配置(如果你想使用自己的项目)。

6)安装 firebase-tools

npm install -g firebase-tools
firebase login
firebase init hosting

7)部署

A)GitHub Pages

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 分支)。

B)Firebase Hosting

firebase deploy --only hosting

8)安全提醒

测试模式会打开数据库访问。投入生产前,请配置数据库规则与鉴权。

Play: Tic-Tac-Toe

Open this page in two browser windows to play across them (each window becomes a player).

Files

index.html (this file) — includes UI and tutorial.

js/index1.js — game logic and Firebase connector (below).

js/index1.js
// 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:

  • The JS file uses ES modules and top-level await. It works in modern browsers when served via HTTP (not opened as file://).
  • If you prefer not to use the included demo Firebase config, replace 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.