Skip to content

Repository files navigation

CSClash Arena โš”๏ธ

A real-time 1v1 competitive quiz engine for core Computer Science subjects โ€” built for placement prep.

Battle your peers in timed 1v1 CS quizzes covering OS, DBMS, CN, and OOPs. A server-authoritative game engine ensures fair play, an Elo-based matchmaking system guarantees balanced opponents, and an intelligent bot engine keeps wait times near zero.

๐Ÿ”— Live Demo: https://lunchbreak.onrender.com ๐Ÿ“ฆ Repository: https://github.com/sherlock-hashed/LunchBreak


๐Ÿ“‹ Table of Contents


๐ŸŽฏ Problem Statement

Engineering students preparing for placement interviews lack an engaging, competitive platform to practice core Computer Science MCQs. Existing quiz apps are:

  • Solo-only โ€” No real opponent means no competitive pressure or urgency
  • Unsecured โ€” Answers can be inspected via browser DevTools
  • Static โ€” No adaptive difficulty or skill-based matching
  • Boring โ€” No gamification, rankings, or progression systems

๐Ÿ’ก Solution

CSClash Arena solves this by providing a real-time, multiplayer quiz experience:

Problem CSClash Solution
No competitive pressure Live 1v1 matches with real-time score updates
Answer leaking via DevTools Server-authoritative engine โ€” answers never reach the client
No skill-based matching Elo rating system with progressive queue expansion
Long wait times Intelligent bot fallback within 15 seconds
No progression system XP, streaks, rank tiers (Explorer โ†’ Elite), leaderboards

๐Ÿ›  Tech Stack

Layer Technology
Frontend React 18, TypeScript, Vite (SWC), Tailwind CSS, Shadcn UI, Recharts
Backend Node.js, Express.js
Real-Time Socket.io (WebSocket + polling fallback)
Database MongoDB Atlas (Mongoose ODM)
Authentication Firebase Google OAuth + JWT (custom-issued)
Containerization Docker (multi-stage build)
Deployment Render (unified container)
UI Components Radix UI primitives, Lucide React icons, Framer Motion

โœจ Features

๐ŸŽฎ Game Modes

  • Blitz (60s) โ€” Fast-paced 10-question matches
  • Rapid (90s) โ€” Extended matches for deeper questions
  • Training โ€” Solo practice without rating impact
  • Arena (Custom Rooms) โ€” Private 1v1 with configurable settings (subject, duration, question type, rating impact)

๐Ÿง  Core CS Subjects

  • Operating Systems (OS) โ€” Process scheduling, memory management, deadlocks
  • Database Management (DBMS) โ€” SQL, normalization, transactions, indexing
  • Computer Networks (CN) โ€” OSI model, TCP/IP, routing, protocols
  • Object-Oriented Programming (OOPs) โ€” Polymorphism, inheritance, design patterns

๐Ÿ“Š Analytics & Progression

  • Elo Rating System โ€” Separate ratings for Blitz, Rapid, and Arena modes
  • XP & Rank Tiers โ€” Explorer โ†’ Scholar โ†’ Specialist โ†’ Master โ†’ Elite
  • Skill Radar Chart โ€” Subject-wise accuracy and speed visualization
  • Match History โ€” Detailed per-question breakdown with response times
  • Score Progression Charts โ€” Cumulative score graphs per match
  • Topic-wise Performance โ€” Accuracy breakdown by CS topic
  • Leaderboard โ€” Global rankings filterable by mode (Blitz/Rapid/Overall XP)

๐Ÿค– Intelligent Bot System

  • Adaptive Difficulty โ€” Bot accuracy and response speed scale with user Elo
  • Realistic Behavior โ€” Randomized response delays with configurable variation
  • Themed Names โ€” Kernel_King, Cache_Master, Socket_Sage, Thread_Titan, etc.

๐Ÿ›ก๏ธ Security

  • Server-Authoritative Scoring โ€” Answers validated server-side only
  • Payload Sanitization โ€” Correct answer indices stripped before client broadcast
  • JWT Authentication โ€” Stateless session management with httpOnly cookies
  • Google OAuth โ€” Secure SSO via Firebase with automatic account creation

๐Ÿ”Œ Real-Time Features

  • Live Score Updates โ€” Instant feedback on opponent's progress
  • Reconnection Handling โ€” 5-second grace period for network drops
  • Online Player Count โ€” Live count of connected users
  • Custom Room Sharing โ€” 6-character room codes with link sharing

๐Ÿ— System Architecture

Architecture Overview

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        DOCKER CONTAINER                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚                   Node.js / Express Server                    โ”‚  โ”‚
โ”‚  โ”‚                       (Port 5000)                             โ”‚  โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚  โ”‚
โ”‚  โ”‚  โ”‚   REST API       โ”‚  โ”‚        Socket.io Server            โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”‚   /api/auth      โ”‚  โ”‚                                    โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”‚   /api/users     โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”‚   /api/matches   โ”‚  โ”‚  โ”‚Matchmakerโ”‚  โ”‚  Game Engine   โ”‚  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”‚   /api/questions  โ”‚  โ”‚  โ”‚  Queue   โ”‚  โ”‚ (In-Memory Map)โ”‚  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”‚   /api/admin     โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚       โ”‚                 โ”‚          โ”‚ โ”‚  โ”‚
โ”‚  โ”‚                       โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚  โ”‚  Rating  โ”‚  โ”‚  Bot Engine   โ”‚  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”‚  Static Files   โ”‚  โ”‚  โ”‚  Engine  โ”‚  โ”‚ (ELO-scaled)  โ”‚  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ”‚  (React dist/)  โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚ โ”‚  โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                              โ”‚                                      โ”‚
โ”‚                              โ–ผ                                      โ”‚
โ”‚                   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                              โ”‚
โ”‚                   โ”‚  MongoDB Atlas   โ”‚                              โ”‚
โ”‚                   โ”‚  (Users, Matches โ”‚                              โ”‚
โ”‚                   โ”‚   Questions)     โ”‚                              โ”‚
โ”‚                   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Why Monolithic?

The application is deployed as a single unified container where Express serves both the API and the compiled React dist/ folder. This eliminates:

  • CORS configuration complexity
  • Multi-service orchestration overhead
  • Optimal for free-tier PAAS constraints (512MB RAM on Render)

๐Ÿ“ High-Level Design (HLD)

flowchart TB
    subgraph Client["React Frontend"]
        A[AuthContext] --> B[SocketContext]
        B --> C[Arena Page]
        B --> D[MatchScreen]
        B --> E[CustomRoom]
        B --> F[MatchResults]
        B --> G[Profile]
        B --> H[Leaderboard]
    end

    subgraph Server["Node.js Backend"]
        I[Express REST API]
        J[Socket.io Handler]
        K[Matchmaking Service]
        L[Game Engine]
        M[Bot Engine]
        N[Rating Engine]
        O[Room Manager]
    end

    subgraph Database["MongoDB Atlas"]
        P[(Users)]
        Q[(Matches)]
        R[(Questions)]
    end

    subgraph Auth["Firebase"]
        S[Google OAuth]
    end

    C -->|join-queue| J
    D -->|submit-answer| J
    E -->|create/join room| J
    J --> K
    K -->|match found| L
    K -->|no opponent 15s| M
    L -->|match end| N
    L -->|save results| Q
    N -->|update ratings| P
    I -->|CRUD| P
    I -->|fetch questions| R
    A -->|Google SSO| S
    S -->|idToken| I
    O -->|room state| J
Loading

Request Flow

sequenceDiagram
    participant U as User (React)
    participant S as Socket.io Server
    participant MM as Matchmaking
    participant GE as Game Engine
    participant BE as Bot Engine
    participant DB as MongoDB

    U->>S: join-queue (subject, mode)
    S->>MM: addToQueue(player)
    MM->>MM: findMatch() every 1s

    alt Human opponent found
        MM->>S: match-found (both players)
    else No opponent after 15s
        MM->>BE: createBot(playerElo)
        BE-->>MM: botProfile
        MM->>S: match-found (player + bot)
    end

    S->>GE: createMatch(roomId, p1, p2, questions)
    GE->>GE: Store in activeMatches Map
    GE->>GE: startMatchTimer (1s ticks)

    loop Every answer
        U->>S: submit-answer (roomId, questionIdx, optionIdx)
        S->>GE: submitAnswer()
        GE->>GE: Validate against in-memory cache
        GE-->>U: answer-result (correct, score, streak)
    end

    GE->>GE: Timer expires โ†’ endMatch()
    GE->>DB: Match.create() โ€” single batch write
    GE->>DB: User.findByIdAndUpdate() โ€” atomic $inc/$set
    GE-->>U: match-ended (final stats)
    GE->>GE: activeMatches.delete(roomId)
Loading

๐Ÿ”ฌ Low-Level Design (LLD)

In-Memory State Management

The game engine avoids database writes during active gameplay. All match state lives in RAM:

// server/services/gameEngine.js
const activeMatches = new Map();  // roomId โ†’ MatchState

// MatchState structure:
{
  roomId: "uuid-v4",
  player1: { id, username, socketId, score: 0, answers: [], streak: 0 },
  player2: { id, username, socketId, score: 0, answers: [], streak: 0 },
  questions: [...],           // Full questions WITH correct answers
  currentQuestion: 0,
  timeLeft: 60,               // Decremented every 1s via setInterval
  status: "active",
  botTimeouts: [],             // Scheduled bot responses (clearable)
}

Why Maps over MongoDB?

  • Reading/writing to RAM = ~0ms latency
  • MongoDB write = ~50-200ms network round trip
  • A 60-second match with 2 players generates ~20+ score mutations
  • Using Maps reduces database writes from ~20+ per match to exactly 1 (at match end)

Matchmaking Queue Architecture

// server/services/matchmaking.js
const queues = new Map();          // queueKey โ†’ [QueueEntry]
const pendingMatches = new Map();  // pendingId โ†’ PendingMatch
const botTimers = new Map();       // odlomerId โ†’ TimeoutId

// Queue expansion logic:
// Second 0-5:   ยฑ50 Elo range
// Second 5-10:  ยฑ150 Elo range
// Second 10-15: ยฑ350 Elo range
// Second 15+:   Bot injection (guaranteed match)

Answer Validation Pipeline

flowchart LR
    A["Client clicks Option B"] -->|"emit: submit-answer"| B["Socket Handler"]
    B --> C["Game Engine"]
    C --> D{"Compare optionIdx vs correct answer in activeMatches Map"}
    D -->|"Match"| E["score += 4, streak++, emit answer-result"]
    D -->|"No Match"| F["score -= 1, streak = 0, emit answer-result"]
Loading

Detailed flow:

  1. Client emits submit-answer with { roomId, questionIdx: 3, optionIdx: 1 }
  2. Server looks up activeMatches.get(roomId).questions[3].correct
  3. Compares optionIdx === correct โ†’ calculates score
  4. Emits answer-result with { correct: true/false, score, streak, oppScore }

Note: The client never receives questions[i].correct. It is stripped out in socketHandler.js before the match-found event is emitted.

Disconnect Recovery Sequence

sequenceDiagram
    participant U as User Browser
    participant S as Socket Server
    participant GE as Game Engine

    U->>U: Page refresh / network drop
    S->>S: 'disconnect' event fired
    S->>GE: handleDisconnect(socketId)
    GE->>GE: Start 5s grace timer

    alt User reconnects within 5s
        U->>S: New socket connection
        U->>S: rejoin-match (roomId)
        S->>GE: rejoinMatch(userId, newSocketId)
        GE->>GE: Cancel grace timer
        GE->>GE: Update socketId in activeMatches
        GE-->>U: rejoin-success (current match state)
    else 5s timer expires
        GE->>GE: Auto-forfeit disconnected player
        GE->>GE: endMatch() โ†’ save to MongoDB
    end
Loading

๐Ÿ—„ Database Schema

User Schema

erDiagram
    USER {
        ObjectId _id PK
        String name
        String username UK
        String email UK
        String password
        String googleId
        String avatar
        String gender
        String bio
        String college
        String country
        Object socials
        Object rating
        Object stats
        Number xp
        Number peakRating
        Date createdAt
    }

    USER ||--o{ MATCH : "plays in"
Loading
rating: {
  blitz:  { type: Number, default: 1200 },
  rapid:  { type: Number, default: 1200 },
  arena:  { type: Number, default: 1200 },
}

stats: {
  matchesPlayed: Number,
  wins: Number,
  losses: Number,
  draws: Number,
  bestStreak: Number,
}

Match Schema

erDiagram
    MATCH {
        ObjectId _id PK
        String roomId UK
        String mode
        String subject
        Boolean isBot
        Object player1
        Object player2
        String winner
        Number duration
        Object xpAwarded
        String status
        Date createdAt
    }

    MATCH }o--|| USER : "player1.userId"
    MATCH }o--o| USER : "player2.userId"
    MATCH }o--o{ QUESTION : "responses.questionId"
Loading
// Each player sub-document:
player1: {
  userId: ObjectId (ref: User),
  username: String,
  score: Number,
  correct: Number,
  wrong: Number,
  skipped: Number,
  accuracy: Number,
  avgResponseTime: Number,
  fastestResponse: Number,
  slowestResponse: Number,
  streak: Number,
  ratingBefore: Number,
  ratingAfter: Number,
  responses: [{
    questionId: ObjectId (ref: Question),
    selectedOption: Number,
    correct: Boolean,
    responseTime: Number,
  }]
}

Question Schema

{
  text: String,                        // "What is a deadlock?"
  options: [String],                   // Exactly 4 options
  correct: Number,                     // Index 0-3 (NEVER sent to client)
  subject: ["OS", "DBMS", "CN", "OOPs"],
  topic: String,                       // "Process Synchronization"
  difficulty: ["Easy", "Medium", "Hard"],
  type: ["MCQ", "MSQ", "Case Based Scenario"],
  explanation: String,                 // Shown post-match for wrong answers
  tags: [String],
}
// Compound index: { subject, topic, difficulty, type }

๐Ÿ“ก API Reference

Authentication

Method Endpoint Auth Description
POST /api/auth/register Public Register with email/password
POST /api/auth/login Public Login with email/password
POST /api/auth/google Public Google OAuth (Firebase idToken)
POST /api/auth/logout Private Clear session cookie
GET /api/auth/me Private Get current user

Users

Method Endpoint Auth Description
GET /api/users/leaderboard Public Global leaderboard (paginated)
GET /api/users/search?q= Private Search users by name/username/college
GET /api/users/public/:id Private Public profile (view-only)
GET /api/users/:id Private Full user profile
PUT /api/users/:id Private Update own profile
GET /api/users/:id/stats Private Subject-wise aggregated stats
GET /api/users/:id/radar Private Skill radar data (per-subject accuracy/speed)

Matches

Method Endpoint Auth Description
GET /api/matches/user/:userId Private Match history (paginated)
GET /api/matches/:roomId Private Single match details (populated)
GET /api/matches/:roomId/analytics Private Topic-wise breakdown + time trends

Questions

Method Endpoint Auth Description
GET /api/questions Public Query questions (filter by subject/topic/difficulty)
GET /api/questions/random Public Random question set
GET /api/questions/topics Public Available topics per subject
POST /api/questions Private Add a new question

Admin

Method Endpoint Auth Description
POST /api/admin/login Public Admin authentication
GET /api/admin/stats Admin Dashboard stats (users, matches, avg ratings)
GET /api/admin/users Admin Paginated user list with search

๐Ÿ”Œ Socket.io Events

Client โ†’ Server

Event Payload Description
join-queue { subject, mode } Enter matchmaking queue
leave-queue โ€” Exit matchmaking queue
match-ready { roomId, opponent, mode } Acknowledge match found
submit-answer { roomId, questionIdx, optionIdx } Submit answer to current question
rejoin-match { roomId } Rejoin after disconnect
create-room โ€” Create custom room
join-room { roomCode } Join existing custom room
room-settings { roomCode, settings } Update room settings (host only)
start-room-match { roomCode } Start custom room match
leave-room { roomCode } Leave custom room

Server โ†’ Client

Event Payload Description
match-found { roomId, opponent, questions, mode, isBot } Match created, navigate to game
answer-result { correct, score, streak, oppScore } Answer validation result
timer-update { timeLeft } 1-second timer tick
match-ended { winner, stats, ratingChanges, xp } Match complete with full analytics
opponent-answered { oppScore, oppCorrect, oppAnswered } Live opponent progress
rejoin-success { matchState } Successful reconnection
room-created { roomCode, settings, players } Room created confirmation
room-joined { roomCode, settings, players } Joined room confirmation
player-joined { players } New player entered room
settings-updated { settings } Room settings changed
online-count { count } Live connected user count

๐Ÿ“Š Scoring & Rating Engine

Scoring Rules

Action Points
Correct answer +4
Wrong answer -1
Skipped question 0
Minimum score 0 (no negatives)

Elo Rating Calculation

Uses the standard Elo rating system with K-factor = 32:

Expected Score = 1 / (1 + 10^((OpponentRating - PlayerRating) / 400))
New Rating = OldRating + K ร— (ActualScore - ExpectedScore)
  • Win: Actual = 1.0
  • Loss: Actual = 0.0
  • Draw: Actual = 0.5
  • Floor: Rating cannot drop below 100

XP Calculation

XP = (score ร— 2) + (accuracy ร— 0.5) + (bestStreak ร— 5) + (isWin ? 25 : 0)

Rank Tiers

Tier Elo Range Color
Explorer 0 โ€“ 1199 Gray
Scholar 1200 โ€“ 1499 Green
Specialist 1500 โ€“ 1799 Blue
Master 1800 โ€“ 1999 Gold
Elite 2000+ Red

๐Ÿค– Bot Engine

When no human opponent is found within 15 seconds, the system injects an ELO-scaled bot:

Player Elo Bot Accuracy Avg Response Time Variation
โ‰ฅ 2000 (Elite) 88% 1.5s ยฑ30%
โ‰ฅ 1600 (Specialist+) 80% 2.0s ยฑ30%
โ‰ฅ 1200 (Scholar+) 70% 2.5s ยฑ30%
< 1200 (Explorer) 60% 3.0s ยฑ30%

Bot Elo is generated within ยฑ100 of the human player's rating.

Bot Names: Kernel_King, Deadlock_Daemon, Cache_Master, Query_Queen, Stack_Sentinel, Mutex_Mind, Pipe_Phantom, Byte_Baron, Thread_Titan, Algo_Oracle, Logic_Lynx, Socket_Sage, Heap_Hawk, Node_Ninja


๐Ÿ›ก Security Architecture

flowchart TD
    A[User Login] -->|Firebase Google OAuth| B[Firebase returns idToken]
    B -->|POST /api/auth/google| C[Server validates & creates JWT]
    C -->|Set httpOnly cookie + return token| D[Client stores in localStorage]
    D -->|Every API request| E[Axios interceptor adds Bearer token]
    D -->|Socket connection| F[Socket auth middleware verifies JWT]

    G[Match Start] -->|Server fetches questions from MongoDB| H[Full questions with .correct field]
    H -->|Strip .correct from each question| I[Sanitized questions sent to client]
    I -->|Client displays options| J[User clicks option]
    J -->|submit-answer: only sends optionIdx| K[Server compares vs in-memory .correct]
    K -->|Result| L[Client receives correct/wrong boolean]
Loading

Key Security Measures

  1. Answer Obfuscation โ€” The correct field is removed from question payloads before Socket emission
  2. Server-Side Validation โ€” All scoring happens on the server; client cannot manipulate scores
  3. JWT Authentication โ€” Tokens issued by the server, verified on every protected route
  4. httpOnly Cookies โ€” Token cookies are not accessible via JavaScript (XSS-resistant)
  5. Password Hashing โ€” bcrypt with salt rounds for email/password users
  6. Input Validation โ€” Mongoose schema validators + controller-level validation
  7. Admin Isolation โ€” Separate JWT with role: "admin" and 4-hour expiry

๐Ÿ“ Project Structure

code-clash-arena/
โ”œโ”€โ”€ server/                          # Backend (Node.js + Express)
โ”‚   โ”œโ”€โ”€ index.js                     # Entry point โ€” Express + Socket.io + static serving
โ”‚   โ”œโ”€โ”€ config/
โ”‚   โ”‚   โ””โ”€โ”€ db.js                    # MongoDB connection
โ”‚   โ”œโ”€โ”€ middleware/
โ”‚   โ”‚   โ””โ”€โ”€ auth.js                  # JWT verification middleware
โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ”œโ”€โ”€ User.js                  # User schema (Elo, stats, socials)
โ”‚   โ”‚   โ”œโ”€โ”€ Match.js                 # Match ledger (per-question responses)
โ”‚   โ”‚   โ””โ”€โ”€ Question.js              # Question bank (subject, topic, difficulty)
โ”‚   โ”œโ”€โ”€ controllers/
โ”‚   โ”‚   โ”œโ”€โ”€ authController.js        # Register, Login, Google OAuth, Logout
โ”‚   โ”‚   โ”œโ”€โ”€ userController.js        # Profile, leaderboard, subject stats, skill radar
โ”‚   โ”‚   โ”œโ”€โ”€ matchController.js       # Match history, analytics
โ”‚   โ”‚   โ”œโ”€โ”€ questionController.js    # Question CRUD, random sets
โ”‚   โ”‚   โ””โ”€โ”€ adminController.js       # Admin dashboard stats
โ”‚   โ”œโ”€โ”€ routes/
โ”‚   โ”‚   โ”œโ”€โ”€ authRoutes.js
โ”‚   โ”‚   โ”œโ”€โ”€ userRoutes.js
โ”‚   โ”‚   โ”œโ”€โ”€ matchRoutes.js
โ”‚   โ”‚   โ”œโ”€โ”€ questionRoutes.js
โ”‚   โ”‚   โ””โ”€โ”€ adminRoutes.js
โ”‚   โ”œโ”€โ”€ socket/
โ”‚   โ”‚   โ””โ”€โ”€ socketHandler.js         # Central Socket.io event hub
โ”‚   โ””โ”€โ”€ services/
โ”‚       โ”œโ”€โ”€ matchmaking.js           # Queue management + bot fallback
โ”‚       โ”œโ”€โ”€ gameEngine.js            # In-memory match state + timer loops
โ”‚       โ”œโ”€โ”€ ratingEngine.js          # Elo calculation + XP + rank tiers
โ”‚       โ”œโ”€โ”€ botEngine.js             # ELO-scaled bot behavior
โ”‚       โ””โ”€โ”€ roomManager.js           # Custom room lifecycle
โ”‚
โ”œโ”€โ”€ src/                             # Frontend (React + TypeScript)
โ”‚   โ”œโ”€โ”€ App.tsx                      # Router + context providers
โ”‚   โ”œโ”€โ”€ contexts/
โ”‚   โ”‚   โ”œโ”€โ”€ AuthContext.jsx          # Auth state + Firebase integration
โ”‚   โ”‚   โ””โ”€โ”€ SocketContext.jsx        # Socket.io client lifecycle
โ”‚   โ”œโ”€โ”€ pages/
โ”‚   โ”‚   โ”œโ”€โ”€ Index.tsx                # Landing page
โ”‚   โ”‚   โ”œโ”€โ”€ Arena.tsx                # Mode/subject selection + queue trigger
โ”‚   โ”‚   โ”œโ”€โ”€ MatchScreen.tsx          # Live game interface
โ”‚   โ”‚   โ”œโ”€โ”€ MatchResults.tsx         # Post-match analytics + charts
โ”‚   โ”‚   โ”œโ”€โ”€ Profile.tsx              # User stats, Elo history, match history
โ”‚   โ”‚   โ”œโ”€โ”€ Leaderboard.tsx          # Global rankings
โ”‚   โ”‚   โ”œโ”€โ”€ CustomRoom.tsx           # Create/join private rooms
โ”‚   โ”‚   โ”œโ”€โ”€ Login.tsx
โ”‚   โ”‚   โ””โ”€โ”€ Signup.tsx
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ Navbar.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ Footer.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ LoadingScreen.tsx        # Matchmaking queue UI
โ”‚   โ”‚   โ”œโ”€โ”€ SkillRadarChart.tsx      # SVG radar visualization
โ”‚   โ”‚   โ””โ”€โ”€ ui/                      # Shadcn UI primitives
โ”‚   โ”œโ”€โ”€ services/
โ”‚   โ”‚   โ””โ”€โ”€ api.js                   # Axios instance with JWT interceptor
โ”‚   โ””โ”€โ”€ lib/
โ”‚       โ”œโ”€โ”€ firebase.js              # Firebase config + Google sign-in
โ”‚       โ””โ”€โ”€ utils.ts                 # Tailwind merge helper (cn)
โ”‚
โ”œโ”€โ”€ Dockerfile                       # Multi-stage build (React โ†’ Express)
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ vite.config.ts
โ”œโ”€โ”€ tailwind.config.ts
โ””โ”€โ”€ tsconfig.json

โš™๏ธ Installation & Setup

Prerequisites

  • Node.js โ‰ฅ 18.x
  • npm โ‰ฅ 9.x
  • MongoDB (local or Atlas connection string)
  • Firebase Project (for Google OAuth)

1. Clone the repository

git clone https://github.com/sherlock-hashed/LunchBreak.git
cd LunchBreak

2. Install dependencies

# Frontend dependencies
npm install

# Backend dependencies
cd server && npm install && cd ..

3. Configure environment variables

Create a .env file in the project root:

# Backend
MONGO_URI=mongodb+srv://<user>:<pass>@cluster.mongodb.net/csclash
JWT_SECRET=your_jwt_secret_key
PORT=5000
ADMIN_EMAIL=your_admin_email
ADMIN_PASSWORD=your_admin_password

# Frontend (Vite โ€” must be prefixed with VITE_)
VITE_FIREBASE_API_KEY=your_firebase_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
VITE_FIREBASE_APP_ID=your_app_id

4. Run in development mode

# Terminal 1 โ€” Backend
cd server && node index.js

# Terminal 2 โ€” Frontend
npm run dev
  • Frontend: http://localhost:5173
  • Backend API: http://localhost:5000/api

5. Seed questions (optional)

Add questions to MongoDB via the API:

curl -X POST http://localhost:5000/api/questions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT" \
  -d '{
    "text": "What is a deadlock?",
    "options": ["A", "B", "C", "D"],
    "correct": 2,
    "subject": "OS",
    "topic": "Process Synchronization",
    "difficulty": "Medium",
    "type": "MCQ"
  }'

๐Ÿš€ Deployment

Docker (Render)

The project uses a multi-stage Docker build:

  1. Stage 1 (frontend-builder): Installs npm dependencies + runs npm run build to compile React into dist/
  2. Stage 2 (production): Copies only server/ + dist/ into a clean Node 18 Alpine image
# Build locally
docker build -t csclash-arena .

# Run locally
docker run -p 5000:5000 --env-file .env csclash-arena

On Render, set the following:

  • Environment: Docker
  • Build Args: All VITE_FIREBASE_* variables (injected at build time)
  • Environment Variables: MONGO_URI, JWT_SECRET, PORT

The Express server automatically detects NODE_ENV=production and serves the React dist/ folder as static files.


๐ŸŽฎ Usage

  1. Sign Up / Login โ€” Create an account or use Google OAuth
  2. Select Mode โ€” Choose Blitz (60s), Rapid (90s), or Training
  3. Pick Subject โ€” OS, DBMS, CN, OOPs, or Mixed
  4. Queue Up โ€” The matchmaking system finds an opponent or spawns a bot
  5. Battle โ€” Answer questions as fast and accurately as possible
  6. Review Results โ€” See per-question breakdown, Elo changes, and performance tips
  7. Track Progress โ€” View your profile, Elo history chart, and skill radar
  8. Challenge Friends โ€” Create a Custom Room and share the 6-character code
  9. Climb Ranks โ€” Reach Elite tier on the global leaderboard

๐Ÿง  Key Challenges & Learnings

Challenge 1: Real-Time State Synchronization

Problem: With two players answering questions simultaneously, keeping scores, timers, and question states perfectly synchronized across clients was critical.

Solution: Made the server the single source of truth. All game state lives in an in-memory Map. The server broadcasts state updates via Socket.io events. Clients are purely display layers โ€” they cannot modify game state.

Challenge 2: Preventing Client-Side Cheating

Problem: In a browser-based quiz, anyone can open DevTools, inspect network payloads, and find correct answers.

Solution: The server strips the correct field from all question objects before emitting them to clients. When a player submits an answer, only the optionIdx integer travels over the wire. The server compares it against its own in-memory cache. The client physically cannot know the answer until after submission.

Challenge 3: Handling Network Disconnections Mid-Match

Problem: Users refreshing their browser or experiencing brief network drops would lose their active match.

Solution: Implemented a 5-second grace period. On disconnect, the server starts a timer. If the user reconnects and emits rejoin-match within 5 seconds, their socket ID is updated in the active match map, and the match continues seamlessly. If the timer expires, the disconnected player forfeits.

Challenge 4: Eliminating Queue Wait Time

Problem: With a small initial user base, matchmaking queues could leave players waiting indefinitely.

Solution: Built a progressive queue expansion system: the Elo search range widens every second. After 15 seconds, the system automatically injects a bot opponent whose accuracy and response speed are calibrated to the player's own Elo rating, guaranteeing every player gets a match.

Challenge 5: Database I/O Overhead

Problem: Writing every score update and answer submission to MongoDB during a live match would create unacceptable latency and overwhelm the database.

Solution: All match state is maintained in JavaScript Map objects in server memory. The database is only written to once โ€” when the match ends โ€” in a single Match.create() call that batch-saves all analytics.


๐Ÿ“ˆ Technical Deep Dives

Match Lifecycle โ€” State Machine

stateDiagram-v2
    [*] --> Idle: User on Arena page
    Idle --> Queued: join-queue emitted
    Queued --> Matched: Human opponent found
    Queued --> BotMatch: 15s timeout, bot injected
    Matched --> Active: Both players emit match-ready
    BotMatch --> Active: match-ready emitted
    Active --> Ended: Timer expires OR all questions answered
    Active --> Disconnected: Socket disconnect
    Disconnected --> Active: Rejoin within 5s
    Disconnected --> Ended: 5s grace period expires
    Ended --> Saved: Match.create + User.update
    Saved --> [*]
Loading

Custom Room Lifecycle

sequenceDiagram
    participant H as Host
    participant S as Server
    participant G as Guest

    H->>S: create-room
    S->>S: Generate 6-char room code
    S->>S: Store in customRooms Map
    S-->>H: room-created (code, settings)

    G->>S: join-room (roomCode)
    S->>S: Validate room exists and not full
    S-->>G: room-joined (settings, players)
    S-->>H: player-joined (updated players list)

    H->>S: room-settings (updated config)
    S-->>H: settings-updated
    S-->>G: settings-updated

    H->>S: start-room-match
    S->>S: Fetch questions from MongoDB
    S->>S: Create match in activeMatches
    S-->>H: match-found (questions, roomId)
    S-->>G: match-found (questions, roomId)
Loading

Bot Engine Decision Flow

flowchart TD
    A["Bot receives question"] --> B{"Player Elo >= 2000?"}
    B -->|Yes| C["accuracy = 88%, avgTime = 1.5s"]
    B -->|No| D{"Player Elo >= 1600?"}
    D -->|Yes| E["accuracy = 80%, avgTime = 2.0s"]
    D -->|No| F{"Player Elo >= 1200?"}
    F -->|Yes| G["accuracy = 70%, avgTime = 2.5s"]
    F -->|No| H["accuracy = 60%, avgTime = 3.0s"]

    C --> I["Apply +/-30% time variation"]
    E --> I
    G --> I
    H --> I

    I --> J{"Math.random < accuracy?"}
    J -->|Yes| K["Select correct answer"]
    J -->|No| L["Select random wrong answer"]
    K --> M["Schedule setTimeout with computed delay"]
    L --> M
    M --> N["Emit submit-answer after delay"]
Loading

Database Write Optimization

flowchart LR
    subgraph NaiveApproach["Naive: Write Every Event"]
        A1["Answer 1"] --> DB1[("MongoDB Write")]
        A2["Answer 2"] --> DB2[("MongoDB Write")]
        A3["Answer 3"] --> DB3[("MongoDB Write")]
        A4["..."] --> DB4[("MongoDB Write")]
        A5["Answer 10"] --> DB5[("MongoDB Write")]
    end

    subgraph CSClashApproach["CSClash: In-Memory + Batch Save"]
        B1["Answer 1"] --> MAP["In-Memory Map"]
        B2["Answer 2"] --> MAP
        B3["Answer 3"] --> MAP
        B4["..."] --> MAP
        B5["Answer 10"] --> MAP
        MAP -->|"Match ends"| DB6[("Single MongoDB Write")]
    end
Loading

Result: 10-20 DB writes per match โ†’ 1 DB write per match. At 100 concurrent matches, this prevents ~2000 unnecessary writes per minute.

Elo Rating โ€” Worked Example

Player A (Rating: 1400) vs Player B (Rating: 1200)
K-factor = 32

Step 1: Expected Scores
  E_A = 1 / (1 + 10^((1200 - 1400) / 400))
      = 1 / (1 + 10^(-0.5))
      = 1 / (1 + 0.316)
      = 0.76 (76% expected win rate)

  E_B = 1 - 0.76 = 0.24

Step 2: Player A wins
  New_A = 1400 + 32 ร— (1.0 - 0.76) = 1400 + 7.7 โ‰ˆ 1408
  New_B = 1200 + 32 ร— (0.0 - 0.24) = 1200 - 7.7 โ‰ˆ 1192

Step 3: If the underdog (B) wins instead
  New_A = 1400 + 32 ร— (0.0 - 0.76) = 1400 - 24.3 โ‰ˆ 1376
  New_B = 1200 + 32 ร— (1.0 - 0.24) = 1200 + 24.3 โ‰ˆ 1224

โ†’ Upsets are rewarded more. Beating a stronger player gives +24 vs +8.

JWT Authentication Middleware Flow

flowchart TD
    A["Incoming Request"] --> B{"Has Authorization header?"}
    B -->|No| C{"Has token cookie?"}
    C -->|No| D["401 Unauthorized"]
    C -->|Yes| E["Extract token from cookie"]
    B -->|Yes| F["Extract token from Bearer header"]
    E --> G{"jwt.verify with JWT_SECRET"}
    F --> G
    G -->|Invalid/Expired| D
    G -->|Valid| H["Decode userId from payload"]
    H --> I["User.findById - exclude password"]
    I --> J{"User exists?"}
    J -->|No| D
    J -->|Yes| K["Attach user to req.user"]
    K --> L["next - proceed to route handler"]
Loading

Matchmaking Queue Expansion โ€” Timeline

Time (s)  Elo Range        Action
โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  0       ยฑ50 of 1400     Queue player, start scanning
  1       ยฑ70             Wider scan
  2       ยฑ90             ...
  5       ยฑ150            Mid-range expansion  
  8       ยฑ210            ...
 10       ยฑ250            Aggressive expansion
 12       ยฑ290            ...
 15       ยฑ350+           ๐Ÿค– BOT INJECTED โ€” match guaranteed

 Result: No player waits more than 15 seconds for a match.

๐Ÿ”ฎ Future Improvements

  • Redis Integration โ€” Move in-memory Maps to Redis for horizontal scaling across multiple server instances
  • WebRTC Voice Chat โ€” Optional voice communication during matches
  • Tournament Mode โ€” Bracket-based elimination tournaments
  • Question Contribution โ€” Community-submitted questions with approval pipeline
  • Mobile App โ€” React Native port with push notifications
  • Performance Analytics โ€” AI-powered study recommendations based on weak topics
  • Rate Limiting โ€” Express rate limiter middleware for API abuse prevention
  • Load Testing โ€” Socket.io stress testing with Artillery/k6
  • Monitoring โ€” Prometheus + Grafana for real-time server metrics

About

A real-time multiplayer computer science quiz game built with MERN, Socket.io, and Docker. Features dynamic ELO matchmaking, algorithmic bots, and global rankings.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages