OpenGame: Agent tự code, tự debug game cho đến khi playable
⚡ TL;DR
- Vấn đề: LLM viết game hay bị màn hình đen, nhân vật không chết đúng lúc, scene chuyển là crash
- Giải pháp: Agent tự chạy game trong sandbox, bắt lỗi và fix vòng lặp đến khi playable end-to-end
- Dành cho: Dev muốn thử nhanh một web game mà không cần học Phaser hay game engine nào
- Điểm khác biệt: Game Skill hai tầng, Template Skill scaffold kiến trúc trước khi viết một dòng logic
- Verdict: Early release, chưa có npm package, nhưng 6 demo nặng đô đã chứng minh concept
Nhờ Claude viết một mini game quiz, code về sau vài giây. Mở trình duyệt: màn hình trắng. DevTools không có một lỗi đỏ nào. Code hợp lệ, TypeScript im lặng, không có gì chạy cả.
Ba lần thử, ba lần màn hình trắng. Lần thứ tư tôi tắt chat đi và tự viết tay.
LLM viết code game rất nhanh. Vấn đề là phần lớn code đó không chơi được.
Tại Sao LLM Thường Thất Bại Khi Viết Game
Game state không sống trong một file. Nó trải qua player.ts, scene.ts, gameState.ts, collision callback, event emitter. Khi LLM sửa một chỗ, nó không nhìn thấy mảnh ghép xung quanh.
Đội CUHK MMLab ghi lại ba kiểu vỡ cứ lặp đi lặp lại.
Cross-file inconsistency. LLM thêm một field mới vào playerData nhưng quên cập nhật function đọc nó ở file khác. Code hợp lệ về syntax, TypeScript không báo lỗi, logic bị đứt giữa hai file.
Broken scene wiring. Phaser bắt buộc asset phải load trong preload(), scene phải đăng ký đúng key trước khi chạy. Bỏ sót một bước là màn hình đen, không exception nào được throw. LLM không nhớ lifecycle này đủ chắc để giữ đúng trong suốt một project nhiều file.
Logical incoherence trong game loop. Game loop đòi nhất quán về thời gian: physics tick, collision callback và win/loss state phải ăn khớp nhau. Hình dung như bánh răng đồng hồ, mỗi bánh chạy đúng nhưng không ăn vào nhau thì kim vẫn không chạy. LLM hay tạo ra code không lỗi, nhưng nhân vật không chết khi va chạm, điều kiện thắng không bao giờ trigger.
Các agent thông thường sau đó patch từng lỗi riêng lẻ thay vì sửa lớp integration. Game build được, nhưng không playable.
Game Skill: Hai Tầng Cốt Lõi
OpenGame giải quyết bằng Game Skill, gồm hai phần làm việc tuần tự.
Template Skill chạy trước, trước khi viết một dòng logic nào. Thay vì để LLM tự quyết kiến trúc, agent chọn template phù hợp (Phaser 3, plain canvas, three.js…), scaffold ra project structure chuẩn, đặt đúng lifecycle hooks (preload, create, update), đăng ký đúng scene key. Kiến trúc xây trước, logic điền vào sau. Giống xây nhà: không ai đặt ống điện rồi mới vẽ bản thiết kế. Template library tích lũy mỗi khi một game build thành công.
Debug Skill chạy sau khi code xong. Agent launch game trong sandboxed browser, bắt console errors, integration errors, broken interactions. Tự fix và chạy lại. Vòng lặp đó tiếp tục cho đến khi game chơi được end-to-end. Không phải đến khi code trông đúng, mà đến khi ai đó thực sự chơi được. Giống anh tester ngồi chơi đến khi chết xong mới trả về “passed”.
--> // making it invisible to querySelectorAll. // // This inline script is NOT touched by Rocket Loader (no src, no type attr). // It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());Model mặc định là GameCoder-27B, 27B tham số được train chuyên cho game dev theo ba giai đoạn: nạp vào Phaser docs và game engine APIs, SFT trên trajectory game dev được tuyển chọn, và execution-grounded RL với reward signal từ chính OpenGame-Bench. Nhưng đây là tùy chọn. OpenGame dùng OpenAI-compatible API, nghĩa là swap GPT-4o, Claude hay bất kỳ model nào qua OpenRouter đều được.
Cài Đặt Và Chạy Thử
npm package đang chuẩn bị. Hiện tại cài từ source:
git clone https://github.com/leigest519/OpenGame.git
cd OpenGame
npm install
npm run build
npm link
# opengame giờ có trên PATH
Tạo game đầu tiên:
mkdir my-game && cd my-game
opengame
Agent hỏi prompt. Gõ vào:
Build a 2D platformer where a cat collects yarn balls while avoiding dogs.
Agent plan architecture, scaffold template, viết code, launch trong sandboxed browser, debug cho đến khi playable. Muốn chạy headless, không cần hỏi thêm:
opengame -p "Build a Snake clone with WASD controls and a dark theme." --yolo
Flag --yolo cho phép agent chạy shell commands, cần thiết cho vòng lặp sandbox của Debug Skill. Nếu dùng trong CI hoặc với prompt không tin tưởng, nên dùng GEMINI_SANDBOX=docker để isolate hoàn toàn.
LLM mặc định cấu hình qua biến môi trường:
export OPENAI_API_KEY="sk-..."
export OPENAI_BASE_URL="https://api.openai.com/v1" # tùy chọn
export OPENAI_MODEL="gpt-4o" # tùy chọn
Session cũ muốn tiếp tục:
opengame --continue # tiếp tục session gần nhất
opengame --resume # chọn từ danh sách session cũ
OpenGame Build Được Gì?
Sáu demo trong repo là bằng chứng tốt hơn bất kỳ mô tả nào.
| Demo | Thể loại | Điểm đáng chú ý |
|---|---|---|
| Marvel Avengers: Infinity Strike | Side-scrolling platformer | 3 nhân vật, 3 màn, boss Thanos, pixel art Capcom 90s |
| Harry Potter: Arithmancy Academy | Turn-based card battle | Quiz tích hợp vào cơ chế bài, combo Magic Resonance |
| K.O.F: Celestial Showdown | Fighting quiz 2 người | Hai player trên cùng bàn phím, trả lời đúng gây dame |
| Hajimi Defense: The Tuna Crisis | Tower defense | Mèo cute chặn dưa leo và máy hút bụi robot |
| StarWars: Mandalorian Protocol | Top-down twin-stick shooter | Blaster, Beskar Spear, Jetpack Dash, cơ chế cover |
| Squid Game: Red Light, Green Light | Survival reflex | Cơ chế run/freeze, xác chồng chất lên nhau |
Chạy thử demo có sẵn:
unzip demo_*.zip && cd demo_*
npm install
npm run dev
# Mở http://localhost:5173
KOF Quiz Fighter ấn tượng nhất theo tôi. Hai người chơi trên cùng bàn phím, quiz tích hợp vào cơ chế đánh nhau. Không phải thứ tôi nghĩ ngẫu nhiên xây được từ một prompt trong một session. Hajimi Defense với mèo cute cũng vậy: tower defense chạy được, có wave enemy, có win condition.
OpenGame-Bench: Đo Lường Playability
Build xong không có nghĩa là playable. OpenGame-Bench đánh giá theo ba trục:
| Trục | Câu hỏi |
|---|---|
| Build Health | Game có compile và chạy không lỗi không? |
| Visual Usability | Game có render ra thứ tương tác được không? |
| Intent Alignment | Game có khớp với mô tả ban đầu không? |
Cơ chế: launch game trong headless browser, chạy scripted interactions, dùng VLM judge để verify. Benchmark chạy trên 150 game prompts đa dạng.
GameCoder-27B được train qua vòng tròn khép kín: benchmark sinh model, model chạy benchmark, lỗi từ benchmark feed ngược vào training. Execution-grounded RL với reward signal từ chính playability thực tế, không phải từ code review.
Những Gì Chưa Hoàn Thiện
npm package chưa có. Hiện phải build từ source. Thêm một bước, không vấn đề lớn, nhưng cản người muốn thử nhanh trong 5 phút.
Settings directory vẫn tên .qwen. OpenGame fork từ qwen-code, là fork của Gemini CLI. Khi cài, thư mục cấu hình vẫn tên .qwen. Nếu thấy tài liệu nhắc .qwen thì đúng chỗ rồi, không bị nhầm sang chỗ khác.
Benchmark pipeline TBD. OpenGame-Bench được mô tả chi tiết trong paper nhưng pipeline đánh giá đầy đủ chưa công bố kèm repo. Các con số trong paper là kết quả nội bộ, chưa reproduce được độc lập.
Asset providers BYOK. OpenGame có thể sinh ảnh, video, audio nếu có key riêng. Nhưng mỗi modality cần cấu hình asset provider riêng biệt, không phải out-of-the-box.
Repo phát hành ngày 21/04/2026, chưa đầy ba tuần tính đến lúc bài này viết.
1.916 sao sau chưa đầy ba tuần. Đó là số, không phải nhận xét. Game Skill là ý tưởng đúng hướng và KOF Quiz Fighter với Hajimi Defense là bằng chứng đủ thuyết phục. Từ “demo ấn tượng” đến “tool production-ready” còn là một đoạn đường, đặc biệt khi npm package và benchmark pipeline vẫn TBD. Cũng như cái game quiz của tôi dạo nọ: code trông đúng, nhưng chạy được hay không là chuyện khác.
Nếu muốn thử: github.com/leigest519/OpenGame.
Hoang Yell
Một nhà phát triển phần mềm và là người kể chuyện kỹ thuật. Tôi dành thời gian để khám phá những repository mã nguồn mở thú vị nhất trên GitHub và trình bày chúng dưới dạng những câu chuyện dễ hiểu cho mọi người.