Competitive multiplayer coding game where players pick an AI model and race to build UI components. Prompt your AI, watch your code render live, and outscore your friends. This project was created over the weekend to play with E2B's sandboxing capabilities.
Note: This is still a work in progress - not where I want it to be yet! Check out Planned Features or open an issue with ideas.
I've always enjoyed playing casual multiplayer games like skribbl.io with friends to kill some time. Now that everyone's vibecoding, I thought - why not make a game out of it? Instead of drawing, you prompt. Instead of guessing, you watch code render in real-time. Same energy, new skills.
Why UI components? If the challenge was "implement quicksort", you'd just paste that into the AI and it's done - the algorithm is already explained in words. But with UI, you see a visual reference. You have to describe colors, spacing, animations, interactions - that's where prompting skill actually matters.
| Layer | Technology |
|---|---|
| Frontend | SvelteKit + Svelte 5 |
| Styling | Tailwind CSS v4 |
| Real-time | Cloudflare Durable Objects + WebSocket |
| AI | Vercel AI SDK with OpenRouter |
| Sandboxes | E2B for isolated code execution |
| Validation | Valibot |
Why this stack?
frontend/
├── src/
│ ├── routes/ # SvelteKit pages
│ │ ├── +page.svelte # Home
│ │ ├── create/ # Create room flow
│ │ ├── join/ # Join room flow
│ │ ├── [code]/ # Game room (dynamic route)
│ │ └── api/ # API endpoints
│ ├── lib/
│ │ ├── components/ # Svelte components
│ │ │ ├── game/ # Game UI (Lobby, GameHeader, etc.)
│ │ │ └── challenges/ # Challenge display components
│ │ ├── hooks/ # Svelte 5 runes (useGame, useChat, etc.)
│ │ ├── config/ # Game settings, models, challenges
│ │ ├── game/ # Game logic (scoring)
│ │ ├── utils/ # Utility functions
│ │ ├── validation/ # Valibot schemas
│ │ ├── types/ # TypeScript types
│ │ └── server/ # Server-side logic
│ │ ├── ai/ # AI chat and prompts
│ │ │ ├── agents/ # Judge agents (CodeAnalyzer, VisualMatcher, etc.)
│ │ │ └── tools/ # AI tools (hints)
│ │ ├── e2b/ # E2B sandbox management
│ │ └── do-client.ts # Durable Object RPC client
│ └── app.html
├── worker/
│ └── src/
│ ├── index.ts # Worker entry point
│ └── GameRoom.ts # Durable Object (game state)
├── tests/
│ ├── unit/ # Unit tests (Vitest)
│ ├── integration/ # Integration tests
│ └── e2e/ # E2E tests (Playwright)
├── sandbox/ # E2B sandbox template files
├── wrangler.toml # Cloudflare config
└── package.json
# Install dependencies
bun install
# Set up environment variables
cp .env.example .env
# Edit .env with your API keys
# Run both frontend and worker
bun run dev:all
This starts:
http://localhost:5173http://localhost:8788| Command | Description |
|---|---|
bun run dev |
Start SvelteKit dev server |
bun run dev:worker |
Start Wrangler dev server |
bun run dev:all |
Start both in parallel |
bun run build |
Build for production |
bun run check |
TypeScript + Svelte checks |
bun run lint |
ESLint |
bun run format |
Prettier |
The project uses husky + lint-staged to validate code before commits:
bun run check--fix on staged .ts, .js, .svelte filesCommits are blocked if type errors or unfixable lint errors exist.
| Command | Description |
|---|---|
bun run test |
Run unit/integration tests (Vitest) |
bun run test:e2e:ui |
Playwright UI for non-sandbox tests only |
bun run test:e2e:ui:sandbox |
Playwright UI for sandbox tests only |
| Command | Description |
|---|---|
bun run test:watch |
Run tests in watch mode |
bun run test:coverage |
Run tests with coverage report |
bun run test:e2e:quick |
Run E2E tests excluding @sandbox tests (no E2B) |
bun run test:e2e:sandbox |
Run only @sandbox tests (shared sandbox, sequential) |
Test Categories:
test:e2e:quick) — Lobby, forms, errors, navigation. No E2B API needed, runs fast.test:e2e:sandbox) — Full game flow with real E2B sandboxes. Tests share ONE sandbox via worker-scoped fixture to avoid rate limits.Tests tagged with @sandbox require E2B_API_KEY and spin up real sandboxes.
The project uses GitHub Actions with two workflows:
CI (.github/workflows/ci.yml) — Runs on every push and PR:
test:e2e:quick, no sandbox)Deploy (.github/workflows/deploy.yml) — Runs after CI passes on main:
worker/ or wrangler.toml changed# Required
OPENROUTER_API_KEY=sk-or-...
E2B_API_KEY=e2b_...
# Optional
PUBLIC_DO_URL=http://localhost:8788 # Durable Object URL (default for dev)
Everything runs on Cloudflare:
# Deploy the Durable Object worker (api.vibecodearena.dev)
bun run deploy:worker
# Deploy the SvelteKit app (vibecodearena.dev)
bun run deploy:app
Environment Secrets (set via Wrangler):
# For the Pages app
bun x wrangler pages secret put E2B_API_KEY --project-name vibecode-arena
bun x wrangler pages secret put OPENROUTER_API_KEY --project-name vibecode-arena
WebSocket (game events)
┌──────────────┐ ┌─────────────────────────┐
│ Browser │◄──────────►│ Cloudflare Worker (DO) │
└──────┬───────┘ │ api.vibecodearena.dev │
│ │ - Game state │
│ HTTP │ - Room management │
▼ └───────────▲─────────────┘
┌──────────────┐ │
│ SvelteKit │────────────────────────┘ HTTP (RPC)
│ (Cloudflare │
│ Pages) │───────────► OpenRouter (AI chat)
│ │───────────► E2B (sandboxes)
└──────────────┘
Note: Currently using one E2B sandbox per room (shared by all players). Ideally, each player would have their own sandbox for better isolation. My E2B plan allows only 20 concurrent sandboxes, limiting the app to ~20 simultaneous rooms (or fewer if using per-player sandboxes).
Currently, the judge "agents" (CodeAnalyzer, VisualMatcher, InteractionTester) are single-shot LLM evaluators. The plan is to make them genuinely agentic:
MIT