Updated match data callback to interpret { game_over: true, winner: -1 } as a draw.
Added winner = "draw" UI state for display and disabling board interactions.
Updated status text in Board component to show “Draw!” when applicable.
Adjusted winner highlighting logic to avoid highlighting any symbol during draw.
Ensured ongoing games always set winner = null for consistent behavior.
tic-tac-toe-ui — Multiplayer Game Client (React + TypeScript + Vite)
A fully functional multiplayer Tic-Tac-Toe game client built using React + TypeScript, powered by Nakama WebSocket real-time networking, and delivered as a tiny production-optimized Vite build (served via BusyBox/Docker).
This UI communicates with the authoritative backend (tic-tac-toe) to deliver a secure, synced, cheat-proof multiplayer experience.
🎮 Overview
This repository contains the front-end implementation of the Tic-Tac-Toe multiplayer platform.
The client supports:
- Device-based authentication
- Full matchmaking lifecycle
- Real-time gameplay with WebSockets
- Authoritative state rendering
- Leaderboard browsing
- Game result screens
- A Vite-powered environment system for dynamic host/SSL selection
This UI is production-ready and deployable to any server or container environment.
⭐ Features
- React + TypeScript UI
- WebSocket real-time gameplay using Nakama JS
- Matchmaking flow: queue → ticket → match → gameplay
- Authoritative state updates (OpCode 2)
- Secure device authentication (device UUID → session)
- Leaderboard view over Nakama's leaderboard API
- Production Docker image: Node → Vite → BusyBox
- Environment-based configuration for host/SSL
🧩 Architecture
Frontend System Diagram
flowchart LR
User[Browser] --> UI[React + TS + Vite]
UI -->|WebSocket| Nakama
UI -->|HTTP| Nakama
UI --> Leaderboard[Leaderboard API]
UI --> Matchmaking[Matchmaker API]
🛠 Tech Stack
- React 18 (TypeScript)
- Vite.js (build system)
- Nakama JavaScript Client
- Plain CSS for styling
- WebSockets (SSL / non-SSL selectable)
- Docker (multi-stage build)
🔧 Environment Variables (Vite)
These are injected at build time:
VITE_WS_HOST=nakama.aetoskia.com
VITE_WS_PORT=443
VITE_WS_SKEY=secret
VITE_WS_SSL=true
Meaning:
- VITE_WS_HOST → Nakama host (domain or IP)
- VITE_WS_PORT → Port for WebSocket/API
- VITE_WS_SKEY → Nakama server key
- VITE_WS_SSL →
truefor wss://,falsefor ws://
🔌 Runtime Flow
Authentication
- UI generates a device UUID
- Calls
client.authenticateDevice() - Stores session in React state
Matchmaking
- User selects mode (classic / blitz)
- joins the matchmaking queue
- Waits for matchmaker ticket
- Auto-joins the match when assigned
Gameplay
- User sends moves via OpCode 1
- Server validates + broadcasts authoritative board via OpCode 2
- UI re-renders board state from server packets
End of Game
- Player sees win/lose/draw
- Can return to home or matchmaking
🎨 Styling
Styling uses plain CSS via a single styles.css file.
Simple, responsive layout using Flexbox.
🐳 Docker (Production Build)
Dockerfile Overview
# Stage 1: Build
FROM node:20-alpine AS builder
# Set working directory
WORKDIR /app
# Copy package.json and package-lock.json (or yarn.lock)
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy the rest of the app
COPY . .
# Build arguments
ARG VITE_WS_HOST
ARG VITE_WS_PORT
ARG VITE_WS_SKEY
ARG VITE_WS_SSL
# Export them as actual environment variables (Vite needs ENV)
ENV VITE_WS_HOST=${VITE_WS_HOST}
ENV VITE_WS_PORT=${VITE_WS_PORT}
ENV VITE_WS_SSL=${VITE_WS_SSL}
# Build
RUN npm run build
# Stage 2: Static file server (BusyBox)
FROM busybox:latest
WORKDIR /app
# Copy only build frontend files
COPY --from=builder /app/dist /app
# Expose port
EXPOSE 3000
# Default command
CMD ["busybox", "httpd", "-f", "-p", "3000"]
Produces an extremely lightweight production image.
🧪 Testing
Manual testing validated:
- Full matchmaking loop
- Game state sync
- Invalid move handling (server rejections)
- Disconnect behaviour
- Leaderboard retrieval
Pending:
- Stress tests
- Mobile responsiveness
- Reconnect logic
📈 Deployment
Supported:
- Docker on any Linux host
- Raspberry Pi (ARM)
- Google Cloud Run / Compute Engine
- Traefik reverse proxy via
games.aetoskia.com
Example Deployment via Docker
docker run -d \
-p 3003:3003 \
--restart always \
tic-tac-toe-ui:latest
Traefik HTTPS routes:
- games.aetoskia.com → UI
🗺️ Roadmap
- Rematch flow
- Reconnect/resume after refresh
- Improved animations
- Mobile UI redesign
- Centralized error handling