Back to Projects

KhasarMunkh / tetris

0

A real-time multiplayer Tetris game with ELO matchmaking, garbage attack mechanics, and SRS rotation, built with React, TS, Socket.IO, and MongoDB.

KhasarMunkh

Multiplayer Tetris

A real-time multiplayer Tetris game with ELO matchmaking, garbage attack mechanics, and SRS rotation, built with React, TS, Socket.IO, and MongoDB.

TSReactNode.jsSocket.IOMongoDBMultiplayerGameVite

Tetris DUEL

A real-time 1v1 multiplayer Tetris game with ELO-based matchmaking and leaderboards. Built with an authoritative server architecture — the server runs all game logic and broadcasts state to both players via WebSockets.

Features

  • Real-time 1v1 matches — FIFO matchmaking queue pairs players instantly
  • Authoritative server — all game logic runs server-side to prevent cheating
  • ELO ranking system — K=32 standard ELO with persistent ratings across matches
  • Garbage attacks — clearing 2+ lines sends cleared - 1 garbage rows to your opponent
  • Ghost piece — semi-transparent preview showing where your piece will land
  • Hold piece — press C to stash a piece for later
  • 5-piece lookahead — see the next 3 pieces in your queue
  • DAS/ARR — Delayed Auto Shift (150ms) and Auto Repeat Rate (30ms) for responsive movement
  • SRS rotation — standard Super Rotation System with wall kick tables
  • Leaderboard — top 10 players with ELO, wins, and losses

Controls

ActionKey
Move LeftA / Arrow Left
Move RightD / Arrow Right
Soft DropS / Arrow Down
Rotate CWW / Arrow Up
Hard DropSpace
Hold PieceC

Tech Stack

LayerTechnology
FrontendReact 19, TypeScript, Tailwind CSS 4, Vite 7
BackendNode.js, Express, Socket.IO
DatabaseMongoDB (Mongoose ODM)
DeploymentDocker, docker-compose

Getting Started

Prerequisites

  • Node.js 20+
  • MongoDB (local instance or Atlas connection string)

Setup

git clone https://github.com/your-username/multiplayer-tetris.git
cd multiplayer-tetris
npm install

Create a .env file from the example:

cp .env.example .env

Then edit .env with your MongoDB connection string:

PORT=3000
MONGO_URI=mongodb://localhost:27017/tetris

Development

Run the backend and frontend in separate terminals:

npm run dev:server   # Express + Socket.IO on port 3000
npm run dev:client   # Vite dev server on port 5173 (proxies API/WS to backend)

Open http://localhost:5173 in two browser tabs, enter usernames, and hit Play.

Production

npm run build   # Type-check + bundle frontend into dist/
npm start       # Serves the app on port 3000

Docker

Run the full stack (app + MongoDB) with a single command:

docker compose up --build

This starts the app on port 3000 with a MongoDB 7 instance. Data is persisted in a named volume.

Project Structure

├── server.js                 # Express + Socket.IO server, game loop, matchmaking
├── Player.js                 # Server-side player state (board, tetromino, score, garbage)
├── Board.js                  # Server-side grid logic and collision detection
├── Tetromino.js              # Server-side piece movement and SRS rotation
├── TetrominoShapes.js        # Piece definitions and color palette
├── models/
│   └── User.js               # Mongoose schema with ELO calculation
├── src/
│   ├── App.tsx               # Root component with screen routing
│   ├── main.tsx              # React entry point
│   ├── style.css             # Tailwind CSS base styles
│   ├── types.ts              # Shared TypeScript types for socket packets
│   ├── hooks/
│   │   ├── useGame.ts        # Game state management and socket event handling
│   │   └── useSocket.ts      # Socket.IO connection singleton
│   ├── classes/
│   │   ├── Board.ts          # Client-side board rendering
│   │   ├── Tetromino.ts      # Client-side piece rendering and ghost calculation
│   │   └── TetrominoShapes.ts# Piece shapes and color palette
│   └── components/
│       ├── game/
│       │   └── GameCanvas.tsx # Canvas rendering, keyboard input, DAS/ARR
│       └── screens/
│           ├── MenuScreen.tsx
│           ├── QueueScreen.tsx
│           ├── GameScreen.tsx
│           ├── GameOverScreen.tsx
│           └── LeaderboardScreen.tsx
├── Dockerfile                # Multi-stage build (builder → production)
├── docker-compose.yml        # App + MongoDB 7 with health checks
└── vite.config.ts            # Dev proxy for API and WebSocket

Architecture

┌─────────────┐  Socket.IO  ┌──────────────────────┐  Mongoose  ┌─────────┐
│  React SPA  │◄───────────►│  Node.js Server      │◄──────────►│ MongoDB │
│  (Canvas)   │  update/    │  - Game loop (500ms)  │            │         │
│             │  input      │  - Matchmaking queue  │            │  Users  │
└─────────────┘             │  - Collision/scoring  │            │  (ELO)  │
                            └──────────────────────┘            └─────────┘

The server is the single source of truth. Clients send input operations (L, R, D, CW, HD, H) and receive the full game state on every tick and after every input. This authoritative model means the client is purely a renderer — it never computes game logic.

Socket Events

EventDirectionPayload
queueMatchClient → ServerUsername string
startGameServer → ClientInitial boards, tetrominoes, and queues
inputClient → Server{ op, seq } — operation + sequence number
updateServer → ClientFull state for both players
gameOverServer → ClientWinner ID and final scores

REST API

EndpointDescription
GET /api/leaderboard/:usernameTop 10 players + requesting user's rank

Available Scripts

CommandDescription
npm run dev:serverRun backend with --watch for auto-reload
npm run dev:clientRun Vite dev server with HMR
npm run buildType-check and build frontend for production
npm run typecheckRun TypeScript type checking only
npm startRun production server (serves from dist/)
npm run previewPreview production build locally via Vite

License

MIT