A gamified DSA practice platform for Thapar Institute of Engineering & Technology
Solve problems → earn a Performance Score → claim territory on a live campus map → challenge rivals in real-time 1v1 contests → climb the leaderboard.
- Overview
- Core Loop
- Features
- Tech Stack
- Architecture
- Territory & Scoring Model
- Anti-Cheating
- Project Structure
- Getting Started
- Available Scripts
- Deployment
- Documentation Site
- Implementation Status
- Team
- License
CodeCraft is a DSA practice platform built exclusively for Thapar students. Unlike a plain problem list (LeetCode/Codeforces-style), every solve is wrapped in a persistent, visible, competitive layer: a fictional interactive grid map of the Thapar campus, where each cell can be owned by a student. Territory is won by solving problems well — and can only change hands through a voluntary, mutually-accepted real-time 1v1 contest, never by force or decay.
Access is gated to verified @thapar.edu accounts (password or Google OAuth), so the leaderboard and map reflect one real, closed cohort rather than an open internet leaderboard.
flowchart LR
A[Browse problems<br/>filter by difficulty/tag] --> B[Write & submit code<br/>Monaco editor]
B --> C{Judged by Piston<br/>sandboxed execution}
C -- WA / TLE / RE --> B
C -- Accepted --> D[Performance Score<br/>computed]
D --> E[Claim / upgrade<br/>a territory cell]
E --> F{Cell contested?}
F -- Challenge accepted --> G[Live 1v1 contest<br/>WebSocket room]
G --> H[Cell ownership<br/>transfers to winner]
F -- No challenge --> I[Climb the leaderboard]
H --> I
Practice & Judging
- Problem browser with difficulty/tag filtering, backed by a 100-problem Kaggle-sourced dataset (50 Easy / 30 Medium / 20 Hard)
- In-browser Monaco editor (same engine as VS Code) with multi-language support
- Submissions run in isolated, self-hosted Piston containers — the backend never executes student code directly
- A structured-JSON test-case harness translates LeetCode-style
{input, expected_output}cases into per-language wrapper code (including custom tree deserialization) before handing the job to Piston - Submission queueing via Redis + BullMQ to keep judge throughput controlled under load
Gamification
- A live, navigable game world of the campus (canvas-rendered): zones are extruded blocks whose height follows their tier, with themed terrain (forest, pond, pitch, track, parking...), streets, and a grid of ownable
TerritoryCells generated via point-in-polygon rasterization of campus zones - You control a commander on the map: walk with WASD/arrows, sprint, or click to auto-travel along streets (A* pathfinding); a spring-follow camera, minimap, fast-travel list, and waypoints make it feel like moving around inside the map
- Fog of war: zones are dimmed until you walk into them (the fast-travel list counts how many you have explored), plus a "Cartographer" achievement
- Level/XP derived from your Performance Score, today's solve pips, a streak flame, a searchable quest-board problem list (status filters, a "next quest" suggestion, code drafts kept per problem), an achievements wall, and a level-up celebration
- Optional synthesized sound effects (off by default)
- Territory tier (Outpost → Settlement → Stronghold → Citadel) is driven by the Performance Score formula, not manual admin assignment
- A soft daily qualifying-problem cap (6/day) so territory farming is bounded without discouraging practice
- Confetti, live capture effects, and toggleable "flavor text" for a lighter tone
Real-Time 1v1 Contests
- Challenge/accept flow:
POST /challenges→ target notified live or on next load →POST /challenges/:id/acceptopens a dedicated WebSocket room - Server-authoritative timers (
startedAt+durationSecondsstored server-side; clients never drive their own countdown) - Disconnect grace period with auto-forfeit on timeout, handled by a dedicated timeout processor
- Contests target a single map cell, and the challenger stakes one of their own: win and you take the target cell, lose and the defender takes your stake, a draw moves nothing — each transfer is atomic, the same path a solo AC uses
- Both cells are locked while the duel is pending or active — solo play and other duels cannot touch them — and are freed when it ends, is declined or expires
Leaderboard & Accounts
- College-wide leaderboard plus per-territory rankings
- Auth via email/password or Google OAuth, both restricted server-side to
@thapar.eduaddresses, backed by JWT sessions
| Layer | Choice | Why |
|---|---|---|
| Frontend | React 19 + TypeScript + Tailwind CSS 4 + Vite (map: custom Canvas 2D engine) | Component-driven UI for the HUD, editor, contest room, and leaderboard; a purpose-built canvas renderer keeps the ~10k-cell map smooth |
| Code Editor | Monaco Editor (@monaco-editor/react) |
VS Code's engine — syntax highlighting, multi-language support, input interception hooks |
| Backend | Node.js + NestJS 11 | Modular controller/service/module architecture; first-class WebSocket gateways and guards |
| Database | PostgreSQL via Prisma 7 | Strongly relational schema (User, Problem, Submission, Territory Cells, Contest); transactional territory/daily-limit writes |
| Real-time | Socket.IO via NestJS Gateways | Dedicated territory and contest namespaces/rooms for isolated, push-based updates |
| Cache / Queue | Redis + BullMQ (ioredis) |
Submission queueing, daily-limit counters, leaderboard caching |
| Code Execution | Piston (self-hosted, Dockerized) | Lightweight, sandboxed, network-isolated multi-language execution — swapped in after Judge0 proved unstable in dev |
| Auth | JWT + Passport (local + Google OAuth20) | Stateless sessions, domain-restricted signup (@thapar.edu) enforced on both auth paths |
| Deployment | Vercel (frontend) · AWS EC2 + Docker + Nginx + pm2 (backend/Piston) · Supabase (Postgres) · Redis Cloud | See DEPLOYMENT.md |
| Docs | MkDocs Material, auto-deployed to GitHub Pages | See Documentation Site |
flowchart TD
Client["React Client\n(Map · Editor · Contest Room · Leaderboard)"]
API["NestJS API\nREST controllers"]
GW["NestJS Gateways\nterritory · contest (Socket.IO)"]
Queue["Redis + BullMQ\nsubmission queue · daily-limit counters"]
DB[("PostgreSQL\nvia Prisma")]
Piston["Piston Workers\nisolated, network-off containers"]
Client <-- REST --> API
Client <-- WebSocket --> GW
API --> DB
API --> Queue
GW --> DB
Queue --> Piston
Piston --> Queue
style Piston fill:#7c3aed,color:#fff
style DB fill:#336791,color:#fff
The backend never executes student code itself — it only ever enqueues a job on Redis/BullMQ and reads back a structured verdict from a Piston worker.
sequenceDiagram
participant U as Student
participant FE as React Client
participant BE as NestJS API
participant Q as Redis / BullMQ
participant P as Piston Worker
participant DB as PostgreSQL
U->>FE: Write code, click Submit
FE->>BE: POST /submissions { code, language, problemId }
BE->>DB: Create Submission (status: queued)
BE->>Q: Enqueue judge job
Q->>P: Dispatch to worker
P->>P: Run against test cases\n(isolated, no network)
P-->>Q: Per-test results
Q-->>BE: Verdict (AC / WA / TLE / RE)
BE->>DB: Write TestResults, update Submission
alt Verdict is AC
BE->>BE: Compute Performance Score
BE->>DB: Assign/upgrade TerritoryCell (if under daily cap)
end
BE-->>FE: WebSocket: verdict + score
sequenceDiagram
participant A as Challenger
participant B as Defender
participant BE as NestJS API
participant GW as Contest Gateway
A->>BE: POST /challenges { toUserId, cellId, problemId? }
BE-->>B: Notify (live socket or next dashboard load)
B->>BE: POST /challenges/:id/accept
BE->>BE: Create Contest + 2 ContestParticipant rows
A->>GW: Join room contest:{id}
B->>GW: Join room contest:{id}
par Live play
A->>BE: Submit solution
B->>BE: Submit solution
end
BE-->>GW: Broadcast verdict + timestamp only\n(never opponent's code)
Note over BE: First correct AC wins.\nDisconnect grace period -> auto-forfeit if not rejoined.
BE->>BE: Determine winner, transfer cell ownership (atomic)
BE-->>GW: Contest resolved
erDiagram
User ||--o{ Submission : submits
User ||--o{ TerritoryCellOwnership : owns
User ||--o{ DailyProgress : tracks
User ||--o{ Contest : "challenges / defends"
Problem ||--o{ Submission : "attempted via"
Submission ||--o| PerformanceScore : scores
Submission }o--o| Contest : "made during"
Territory ||--o{ TerritoryCell : "divided into"
TerritoryCell ||--o{ TerritoryCellOwnership : "owned via"
TerritoryCell ||--o{ Contest : "contested for"
Contest ||--o{ ContestParticipant : has
ContestParticipant }o--|| Submission : "best submission"
Full schema: backend/prisma/schema.prisma · Design rationale: docs/database-design.md
The map is a small game. Everything below is also listed in-app (press ?).
| Input | Action |
|---|---|
W A S D / arrows |
Walk (hold Shift to sprint) |
| Click open ground / right-click | Auto-travel there along streets |
| Click a zone | Inspect it (owners, control meter, leaderboard) |
T |
Travel to the selected zone |
E |
Inspect the zone you are standing in |
Space |
Dive in/out: zoom to the cell grid of the current/selected zone |
| Click a rival's cell (when zoomed in) | Challenge its owner to a 1v1 duel, putting one of your own cells on the line |
Scroll / pinch, + - |
Zoom |
| Drag | Look around (press C to snap back to your commander) |
O |
Whole-campus overview |
F |
Fast-travel list (search, filter, pin a waypoint) |
L |
College leaderboard |
Esc |
Cancel a route / close a panel |
A calm HUD, on purpose: only a location pill, the minimap, three small buttons and the zoom dock are always on screen, and the corner widgets fade while you walk. Zone names are shown only where they fit and never overlap, roof art and outlines stay quiet until you hover or select a block, nothing pulses, and the activity feed carries only news that concerns you (your captures, and rivals moving on zones you hold). Details live in the zone panel, the fast-travel list and the ranks panel.
On touch devices a virtual stick replaces the keyboard. The page deep-links too: /map?territory=<territoryId> walks you to that zone (the dashboard's "Show on map" uses it).
Performance: the renderer draws only what is on screen, caches textures, and watches its own frame rate: on a slow device it drops purely decorative effects and pixel density automatically, then tries to restore them. prefers-reduced-motion disables camera smoothing, parallax and particles.
PerformanceScore = (DifficultyWeight × Correctness) − AttemptsPenalty + TimeEfficiencyBonus
| Component | Rule |
|---|---|
| DifficultyWeight | Easy = 10 · Medium = 25 · Hard = 50 |
| Correctness | Fraction of test cases passed on the final (AC) submission |
| AttemptsPenalty | min(attempts − 1, 5) × (0.05 × DifficultyWeight) — capped so retries aren't punished indefinitely |
| TimeEfficiencyBonus | Up to 0.2 × DifficultyWeight, scaled against that problem's median solve time across all users |
| Score | Territory Tier |
|---|---|
| 0 – 15 | Outpost |
| 15 – 35 | Settlement |
| 35 – 55 | Stronghold |
| 55+ | Citadel |
A soft daily cap (6 qualifying solves/day) stops score/territory accrual for the day without blocking further practice — the judge pipeline stays useful even after the cap is hit. Full rationale: docs/scoring.md.
Designed as a risk-scoring model, not an AI-code detector — no single signal is treated as individually sufficient:
| Signal | Mechanism |
|---|---|
| Paste blocking | Monaco intercepts Ctrl+V/Shift+Insert as a deterrent; bypassable, so its real value is the logged attempt count |
| Keystroke telemetry | Per-user typing baseline (not a fixed WPM threshold) + burst detection for well-formed code appearing too fast |
| Submission diffing | Near-instant empty→complete jumps with no incremental snapshots are the strongest signal |
| Flag lifecycle | 1st = warning, 2nd = monitoring, 3rd = auto 1-week ban + territory reset, pending mandatory admin review |
Status: the full flag-scoring/ban pipeline and admin review queue are designed (see
docs/anti-cheating.md) but not yet implemented server-side — only client-side paste interception exists today. Tab-switching is deliberately not used as a signal (too many legitimate reasons, high false-positive risk).
CodeCraft/
├── backend/ # NestJS API + WebSocket gateways
│ ├── prisma/ # Schema, migrations, seed & grid-generation scripts
│ └── src/
│ ├── auth/ # Password + Google OAuth, JWT, @thapar.edu domain guard
│ ├── problems/ # Problem browsing/filtering
│ ├── judge/ # Piston client + language harnesses
│ ├── submissions/ # Queueing, processing, verdicts
│ ├── scoring/ # Performance Score service
│ ├── territory/ # Grid-cell map, ownership, territory gateway
│ ├── contest/ # Challenge/accept, contest gateway, timeout processor
│ ├── leaderboard/ # College-wide & territory rankings
│ └── common/ # Redis client, shared color utils
├── frontend/ # React + TypeScript + Vite client
│ └── src/
│ ├── auth/ # Login, OAuth callback, protected routes
│ ├── features/map/ # Game world: canvas engine (world/), HUD (hud/), leaderboards
│ ├── features/contest/ # Challenges list, live contest room
│ ├── components/ # Nav, toasts, shared game UI kit (ui/)
│ ├── pages/ # Scoring dashboard (commander profile)
│ └── lib/ # API client, Monaco setup, sockets, toasts
├── judge0/ # Legacy Judge0 config (superseded by Piston, kept for reference)
├── docs/ # Full system design docs (MkDocs source)
├── DEPLOYMENT.md # Production topology + runbook
└── mkdocs.yml
- Node.js ≥ 20
- PostgreSQL (local instance or a hosted URL, e.g. Supabase)
- Redis (local instance or a hosted URL, e.g. Redis Cloud)
- Docker (to run Piston locally)
git clone https://github.com/Ajha2005/CodeCraft.git
cd CodeCraft
cd backend && npm install
cd ../frontend && npm installdocker run -d --name piston-api --restart always -p 2000:2000 \
--privileged -v piston-packages:/piston/packages \
ghcr.io/engineer-man/piston
# Install the language runtimes the judge harness targets
curl -X POST http://localhost:2000/api/v2/packages \
-H "Content-Type: application/json" \
-d '{"language": "python", "version": "3.10.0"}'
curl -X POST http://localhost:2000/api/v2/packages \
-H "Content-Type: application/json" \
-d '{"language": "gcc", "version": "10.2.0"}'backend/.env:
| Variable | Notes |
|---|---|
DATABASE_URL |
Postgres connection string (session pooler if using Supabase — not the transaction pooler) |
REDIS_URL |
redis://default:PASSWORD@host:port |
JWT_SECRET |
openssl rand -base64 32 |
JWT_EXPIRES_IN |
e.g. 1d |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
From Google Cloud Console → Credentials |
GOOGLE_CALLBACK_URL |
http://localhost:3000/auth/google/callback locally |
FRONTEND_URL |
http://localhost:5173 locally |
PORT |
Optional, defaults to 3000 |
frontend/.env (see frontend/.env.example):
| Variable | Notes |
|---|---|
VITE_API_BASE |
http://localhost:3000 locally |
cd backend
npx prisma migrate deploy
npm run seed # seeds the 100-problem dataset
npx tsx prisma/generate-grid.ts # rasterizes the campus map into TerritoryCells# Terminal 1 — backend
cd backend
npm run start:dev
# Terminal 2 — frontend
cd frontend
npm run devFrontend: http://localhost:5173 · Backend: http://localhost:3000
Backend (backend/package.json)
| Script | Purpose |
|---|---|
npm run start:dev |
Start the API in watch mode |
npm run seed |
Seed the problem dataset |
npm run reset-grid |
Regenerate territory cells from the campus map SVG |
npm run lint / format |
ESLint / Prettier |
npm test / test:e2e / test:cov |
Unit / e2e / coverage |
Frontend (frontend/package.json)
| Script | Purpose |
|---|---|
npm run dev |
Vite dev server |
npm run build |
Type-check + production build |
npm run lint |
ESLint |
npm run preview |
Preview the production build |
| Component | Where |
|---|---|
| Frontend | Vercel (root dir frontend, auto-deploys main) |
| Backend | AWS EC2 (Ubuntu), via pm2 behind Nginx |
| Piston | Same EC2 instance, Docker |
| PostgreSQL | Supabase |
| Redis | Redis Cloud |
Full runbook (server recreation, Nginx config, SSL, known gotchas) lives in DEPLOYMENT.md.
The complete system design — vision, requirements, architecture, database design, scoring model, anti-cheating design, real-time contest spec, security checklist, and roadmap — is written up in docs/ and published via MkDocs Material:
📘 https://ajha2005.github.io/CodeCraft/
| Doc | Covers |
|---|---|
vision.md |
Problem statement, scope, actors, use cases |
requirements.md |
Functional & non-functional requirements |
architecture.md |
Stack rationale, data flow, scalability plan |
database-design.md |
Entity/relationship design |
dataset.md |
Problem dataset structure & harness rationale |
scoring.md |
Territory & scoring formula |
anti-cheating.md |
Signal design & flag lifecycle |
contests.md |
1v1 contest & execution architecture |
security.md |
Security controls & test strategy |
roadmap.md |
Phased plan, risks, future scope |
Reflects a scan of the current codebase, not the phase doc's original ordering:
- Auth — email/password + Google OAuth, both gated to
@thapar.edu, JWT sessions - Problem browsing — filtering, dataset-backed detail view
- Judge execution — Piston integration, function-call harness (array/tree inputs), queued via BullMQ
- Scoring engine — Performance Score service, daily soft-cap
- Territory system — grid-cell map, SVG rasterization, live territory gateway
- Real-time 1v1 contests — challenge/accept, contest gateway, disconnect grace period, atomic cell transfer
- Leaderboard — college-wide + territory rankings
3-member team, split by ownership:
| Workstream | Scope |
|---|---|
| Auth | Signup/login, OAuth, institutional email restriction |
| Problem Data & Frontend Shell | Problem model, seed data, problem browsing UI |
| Judge Infrastructure | Piston, Redis/BullMQ, Monaco editor, test-case harness |
See docs/team-progress.md for the detailed workstream log.
No license file is currently published in this repository. Until one is added, all rights are reserved by the authors — add a LICENSE file (e.g. MIT) before any public reuse or distribution.