Skip to content

Latest commit

 

History

History
190 lines (154 loc) · 8.13 KB

File metadata and controls

190 lines (154 loc) · 8.13 KB

Domain Context — Code Arena

A programming competition platform for university teams. Problems are platform-hosted (statement + hidden test cases) and evaluated automatically via Judge0.

Key Entities

Entity MongoDB Collection Notes
User users Has teamCode linking to a team
TeamCode teams Unique code, accumulated points, submissions[]
Competition competition Contains teams[] (codes), problems[], scoring, podium[] (if maze mode enabled)
Problem Embedded in Competition difficulty: easy/medium/hard, statement, testCases[] (testcases collection), hidden_instructions (anti-AI)
Submission Embedded in TeamCode status always "AC", time in seconds from competition start
MazeConfig maze_configs nodes[], doors[], startNodeId, goalNodeId
MazeProgress maze_progress currentNodeId, unlockedDoors[], spentPoints, earnedPoints per team

Main Flows

Problems (Classic Flow)

  1. Admin creates a competition (POST /competition/create) with problems and per-difficulty scoring.
  2. A user creates or joins a team (/teams).
  3. The team registers for the competition (POST /competition/join).
  4. During the competition, any team member submits a solution (POST /competition/submission/{competitionId}/{problemId}).
    • Backend calculates points based on competition.scoring[difficulty].
    • Adds points to the team (teams.points += points).
    • Records a submission in teams.submissions[] with time = seconds since competition.date.
  5. The leaderboard is queried in real-time (GET /ranking/{competitionId}).

Maze/Labyrinth (Optional Game Mode)

  1. Admin creates a maze configuration (POST /maze/{competitionId}) with nodes (graph positions) and doors (bidirectional edges, each with a point cost).
  2. Registered teams see the maze in the "Maze" tab with their starting position (startNodeId).
  3. A team spends points earned from problems to unlock doors and move between nodes (POST /maze/{competitionId}/unlock).
    • Each door is bidirectional: can be crossed from either of its two nodes.
    • Backend validates atomically: adjacency, door not already unlocked, sufficient points.
    • Team's maze_progress updates: currentNodeId, spentPoints += door.cost, unlockedDoors[].
  4. Win Condition: When a team reaches goalNodeId, they enter the podium automatically (no need to solve all problems).
    • Podium capacity: min(3, number of registered teams).
    • Only the first 3 teams to reach the goal enter the podium.
  5. Game Over: When the podium is full, competition.status changes to "completed" and no more doors can be unlocked.
    • The winning team is the first to reach the goal (podium[0]).

Business Rules

  • Submissions: Code is validated against problem test cases via Judge0 (see "Submission Validation" below).
  • Scoring: easy/medium/hard → values configurable per competition.
  • Leaderboard: ORDER BY points DESC, totalTime ASC. totalTime = time of team's last submission.
  • Teams: maxMembers defined per team. A team can participate in multiple competitions.
  • Auth: JWT 120-min expiry stored in localStorage. User's teamCode included in token and /auth/verify response.
  • Private Competition: GET /competition/private/{id} returns competition data + authenticated user's team data in a single call.

Submission Validation

The platform supports two code validation modes (determined by environment variables):

  1. Judge0 Mode (when JUDGE0_API_KEY is set):

    • Source code is executed against hidden test cases stored in the testcases collection.
    • Time limits (time_limit) and memory limits (memory_limit) from the problem are enforced.
    • Submission is accepted as "AC" only if all test cases pass.
    • Client sends source_code and language_id.
  2. Fallback Mode (when VALIDATION_CODE is set, but NOT JUDGE0_API_KEY):

    • Simplified validation: client sends a secret code (validation_code) that must match exactly VALIDATION_CODE on the server.
    • Useful for development environments, demos, or when Judge0 integration is unavailable.
    • Does not execute or validate user code.

In both modes, if validation passes, the submission is recorded as "AC" and points are awarded to the team. Submissions cannot be changed afterward.

Maze

  • Doors are bidirectional: can be traversed from either of their two nodes (from_node or to_node).
  • Door cost is fixed and deducted from team's spentPoints when unlocked.
  • availablePoints = earnedPoints - spentPoints. Teams can only spend points they have earned.
  • A team with no unlocked doors has no maze_progress document until attempting to unlock the first door (lazy initialization).
  • The maze is playable only while the competition is active (status = "active"); if competition ends (status = "completed"), no more doors can be unlocked.
  • A team cannot appear twice on the podium: if they already reached the goal, subsequent maze iterations do not count them again.

Real-time Events (MQTT)

Real-time communication uses MQTT with WebSockets as transport. The server publishes events on two distinct channels:

Competition Channel

Topic: {MQTT_TOPIC_PREFIX}/ranking/{competitionId} (e.g. code-arena/ranking/comp-123)

Events affecting all teams in the competition:

  • new_submission: A team solved a problem.

    {
      "event": "new_submission",
      "data": {
        "teamCode": "abc123",
        "problem": "prob-id",
        "points": 100,
        "member": "username",
        "time": 234
      }
    }
  • door_unlocked: A team unlocked a maze door.

    {
      "event": "door_unlocked",
      "data": {
        "teamCode": "abc123",
        "doorId": "door-1",
        "newNode": "node-5",
        "cost": 50
      }
    }
  • team_finished: A team reached the goal and earned a podium place (game continues until podium is full).

    {
      "event": "team_finished",
      "data": {
        "teamCode": "abc123",
        "teamName": "Python Masters",
        "position": 2,
        "podiumTarget": 3
      }
    }
  • game_over: The podium is full (top 3 in maze); game ended for all.

    {
      "event": "game_over",
      "data": {
        "teamCode": "winning-team",
        "teamName": "Winners",
        "podium": [
          {"teamCode": "t1", "teamName": "Gold Team"},
          {"teamCode": "t2", "teamName": "Silver Team"},
          {"teamCode": "t3", "teamName": "Bronze Team"}
        ],
        "goalNodeId": "goal"
      }
    }
  • cheer: A team member sent an encouraging message (visible to all).

    {
      "event": "cheer",
      "data": {
        "teamCode": "abc123",
        "member": "username",
        "message": "Go team!"
      }
    }

Team Channel

Topic: {MQTT_TOPIC_PREFIX}/team/{teamCode} (e.g. code-arena/team/abc123)

Events affecting only members of a specific team, independent of any competition:

  • team_joined_competition: Team registered for a competition (confirms successful registration).
    {
      "event": "team_joined_competition",
      "data": {
        "competitionId": "comp-123",
        "teamCode": "abc123"
      }
    }

Frontend Consumption

  • useCompetitionSocket(competitionId, onMessage): Subscribe to competition channel.
  • useTeamSocket(teamCode, onMessage): Subscribe to team channel.

Both hooks handle automatic reconnection (10-second delay) and detect fatal auth errors (no retry on credential errors).

External Integrations

  • MongoDB: Async Motor client. Locally runs as Docker container (mongo:7). In production, can point to any external instance via MONGO_URL.
  • MQTT: MQTT broker for real-time events (e.g. Mosquitto or managed cloud service). Environment variables: MQTT_HOST, MQTT_WS_PORT, MQTT_WS_PATH, MQTT_USERNAME, MQTT_PASSWORD, MQTT_TOPIC_PREFIX.
  • Judge0: Code execution API (only if JUDGE0_API_KEY is set). Falls back to fallback mode if unavailable.
  • Vercel: Backend and frontend deployment (single vercel.json at root defines both services; automatic deployment on push to main).
  • MongoDB Atlas: Production database (free M0 tier available).