{"slug": "how-i-built-a-real-time-robot-battle-simulator-from-scratch-logic-arena", "title": "How I Built a Real-Time Robot Battle Simulator from Scratch — Logic Arena", "summary": "This article details the development of Logic Arena, a real-time robot battle simulator built from scratch by a computer science student using TypeScript. The project features a custom scripting language called AliScript, a 60fps physics engine, and required solving numerous technical challenges including state synchronization, performance bottlenecks, and production deployment issues. The platform allows players to write code that controls robots in real-time combat, transforming competitive programming into an interactive battle experience.", "body_md": "*A CS student's journey from a blank repo to a production platform with a custom scripting language, 60fps physics engine, and 3,000+ lines of battle-tested TypeScript.*\n\n## The Idea\n\nI wanted to build something that made competitive programming **fun**. Not another LeetCode clone. Something where your algorithm doesn't just pass a test case — it **fights**.\n\nThe concept: players write code to control robots. The robots battle in real-time. Your logic vs the world.\n\nThat's Logic Arena.\n\n## Starting From Zero — The Engine\n\nThe first challenge was building a game engine from scratch. No Unity, no Phaser — pure TypeScript.\n\nVersion 0.2.0 was just two robots moving in a canvas. But getting there required solving the first real technical scar:\n\n**The Singleton Problem.** Two NestJS services were sharing the game engine — or so I thought. They each had their own instance, so state was completely desynced. The fix: `@Global()`\n\ndecorator to enforce a single shared engine across the entire module.\n\n```\n@Global()\n@Module({\n  providers: [GameService],\n  exports: [GameService],\n})\nexport class GameModule {}\n```\n\nOne decorator. Three hours of debugging.\n\n## Building AliScript — A Custom Scripting Language\n\nThis was the craziest decision I made. Instead of using JavaScript or Lua for robot scripting, I built my own language.\n\n**Why?** I wanted players to think algorithmically, not just copy-paste code. AliScript had to be simple enough for beginners but powerful enough to reward expert thinking.\n\nThe pipeline:\n\n```\nString Script → Lexer → Tokens → Parser → AST → Server Evaluator → Robot Actions\n```\n\nFirst version supported basic conditionals:\n\n```\nIF CAN_SEE_ENEMY\n  FIRE\nEND\nMOVE\n```\n\nBy v2.4, it became Turing-complete-ish:\n\n```\nWHILE TRUE DO\n  IF CAN_SEE_ENEMY AND MY_ENERGY > 30 DO\n    BROADCAST(NEAREST_VISIBLE_X)\n    BURST_FIRE\n  ELSE IF IN_STASIS DO\n    WAIT\n  ELSE DO\n    SCAN\n    PATHFIND\n  END\nEND\n```\n\nThe hardest bug? **Operator precedence.**\n\n`2 + 3 * 4`\n\nwas evaluating to `20`\n\ninstead of `14`\n\n. Every multiplication-heavy script was silently computing wrong values. The fix required splitting `parseBinaryExpression()`\n\ninto two separate precedence levels — `parseAddition()`\n\nand `parseMultiply()`\n\n.\n\n## The Performance Crisis\n\nBy v1.3.0, the arena was running at ~15fps. Profiling revealed the culprit:\n\n**3,862ms scripting bottleneck** — the main thread was choking on `useState`\n\nupdates 60 times per second.\n\nThe fix was a dual-state architecture:\n\n```\n// Before: useState causes re-render on every tick\nconst [gameState, setGameState] = useState(null);\n\n// After: useRef for game logic, throttled state for UI only\nconst gameStateRef = useRef(null); // updates at 60fps, zero re-renders\nconst [uiState, setUiState] = useState(null); // updates at 10fps, DOM only\n```\n\nScripting time dropped from 3,862ms to near zero.\n\n## Six Performance Fixes in One Release\n\nBy v2.5.0, I ran a full profiling audit and found six simultaneous bottlenecks:\n\n**1. Obstacles in every WebSocket payload** — static data sent 10x/sec for no reason. Fix: initialize once, strip from all subsequent payloads.\n\n**2. 30 WebGL draw calls for obstacles** — rewrote to `THREE.InstancedMesh`\n\n. 30 draw calls → 4 draw calls.\n\n**3. 10 useFrame JS callbacks for obstacle animations** — moved all pulse math to GPU via fragment shaders. 0 JS callbacks per frame.\n\n**4. Server memory leak** — replay snapshots were deep-cloning unboundedly. Added ring buffer capped at 300 objects.\n\n**5. The Ghost Match Massacre** — when players closed their browser, the physics engine kept running at full speed indefinitely. Hundreds of ghost matches accumulating CPU in silence. Fix: wired disconnect lifecycle to stop the engine the moment the last player leaves.\n\n## The Deployment Nightmare\n\nGoing from localhost to production at logicarena.dev was a gauntlet.\n\n**The 2.57GB Docker Context Bomb** — first build transferred 2.57GB to the Docker daemon because `node_modules`\n\nwasn't excluded. Fixed `.dockerignore`\n\n, dropped build context to 15MB.\n\n**The Prisma Ghost Engine** — Alpine Linux refused to execute the Windows-compiled Prisma binary. Added `linux-musl`\n\nto `binaryTargets`\n\n.\n\n**The WebSocket CORS Wall** — every Socket.IO connection rejected because the gateway was hardcoded to `localhost:3000`\n\n. Extended CORS array, fixed Nginx WebSocket proxy headers.\n\n**The Silent Postman** — DigitalOcean silently blocks outbound SMTP on new Droplets. Zero errors, zero delivery, just void.\n\n## The Architecture Today\n\n```\nlogic-arena/\n├── apps/\n│   ├── client/     # Next.js 16, React Three Fiber, PWA\n│   └── server/     # NestJS 11, Socket.io, JWT\n└── packages/\n    ├── engine/     # Custom TypeScript physics engine\n    └── logic-parser/ # AliScript lexer, parser, evaluator\n```\n\n## What I Learned\n\n**1. Architecture decisions compound.** Every \"quick fix\" that bypassed the module system created three bugs later.\n\n**2. Profile before optimizing.** The 3,862ms bottleneck was invisible until I actually measured it.\n\n**3. Custom languages are feasible.** An AST evaluator is just a recursive tree walker — intimidating name, straightforward implementation.\n\n**4. Ship early, iterate fast.** Logic Arena went from 2 robots on a canvas to a 60-level algorithmic campaign in 4 months of solo development.\n\n## Try It\n\n**Live:** [logicarena.dev](https://logicarena.dev) — no account required, join as guest.\n\n**GitHub:** [Ali-Haggag7/logic-arena](https://github.com/Ali-Haggag7/logic-arena)\n\nWrite your first script, watch your robot fight, and tell me what you think in the comments!", "url": "https://wpnews.pro/news/how-i-built-a-real-time-robot-battle-simulator-from-scratch-logic-arena", "canonical_source": "https://dev.to/alihaggag7/how-i-built-a-real-time-robot-battle-simulator-from-scratch-logic-arena-1pnk", "published_at": "2026-05-23 20:42:31+00:00", "updated_at": "2026-05-23 21:02:28.413695+00:00", "lang": "en", "topics": ["robotics", "developer-tools", "open-source", "startups", "products"], "entities": ["Logic Arena", "AliScript", "NestJS", "TypeScript", "Unity", "Phaser", "LeetCode", "GameService"], "alternates": {"html": "https://wpnews.pro/news/how-i-built-a-real-time-robot-battle-simulator-from-scratch-logic-arena", "markdown": "https://wpnews.pro/news/how-i-built-a-real-time-robot-battle-simulator-from-scratch-logic-arena.md", "text": "https://wpnews.pro/news/how-i-built-a-real-time-robot-battle-simulator-from-scratch-logic-arena.txt", "jsonld": "https://wpnews.pro/news/how-i-built-a-real-time-robot-battle-simulator-from-scratch-logic-arena.jsonld"}}