GLM 모델로 플레이 가능한 HTML5 미니 게임 생성하기
GLM 모델로 플레이 가능한 HTML5 미니 게임 생성하기 (Generate a playable HTML5 mini game using the GLM model via the Mistral API)
Mistral API를 통해 zai-glm-5-2(GLM) 모델을 사용해서 단일 프롬프트로 완전하고 플레이 가능한 HTML5 던전 크롤러(dungeon crawler) 게임을 만들고, 흔한 게임 메커닉 문제를 자동으로 검토·수정하는 쿡북이에요.
출처: 문서
본문
사전 요구 사항 (Prerequisites)
이 쿡북을 완료하려면 다음이 필요해요:
- Python 3.10+
- Mistral 계정과 API 키
환경 설정 (Environment setup)
설치 (Install)
Mistral Python SDK와 .env 파일에서 API 키를 로드하기 위한 python-dotenv를 설치하세요:
pip install mistralai python-dotenv
필요한 환경 변수 (Required environment variables)
이 쿡북을 완료하려면 Mistral API 키가 필요해요. Studio에서 API 키 섹션으로 이동해서 새 API 키를 만드세요.
프로젝트 루트에 .env를 만들고 Mistral API 키를 추가하세요:
MISTRAL_API_KEY=your-mistral-api-key
Step 1 — 클라이언트 초기화 (Initialize the client)
프로젝트 디렉터리에 generate_game.py를 만드세요:
touch generate_game.py
파일을 열고 import와 클라이언트 초기화를 추가하세요. 클라이언트는 GLM이 몇 분이 걸릴 수 있는 큰 코드 출력(1000줄 이상)을 생성하므로 timeout_ms=600_000(10분)과 일치하는 httpx 타임아웃을 설정해요. 나머지 단계들은 프롬프트, 생성, 검토 루프, 편집 모드, 로컬 서버를 구성해요.
"""Generate a playable HTML5 mini game using the GLM model via the Mistral API."""
import argparse
import functools
import http.server
import os
import re
import webbrowser
from pathlib import Path
from dotenv import load_dotenv
import httpx
from mistralai.client import Mistral
load_dotenv()
# Step 1 — Initialize the client
# GLM generates large code outputs that can take several minutes. The default
# httpx timeout is too short, so set it to 10 minutes to match timeout_ms.
client = Mistral(
api_key=os.environ["MISTRAL_API_KEY"],
timeout_ms=600_000,
client=httpx.Client(follow_redirects=True, timeout=httpx.Timeout(600.0)),
)
# Step 2 — Craft the game prompt
# Step 3 — Generate the game
# Step 4 — Review and fix the game
# Step 5 — Edit the game
# Step 6 — Serve the game locally
def main():
# Step 7 — Tie it all together in main
pass
if __name__ == "__main__":
main()
Step 2 — 게임 프롬프트 만들기 (Craft the game prompt)
프롬프트는 두 부분으로 구성돼요. 시스템 메시지는 출력 형식을 제한해요 — 단일 HTML 파일, 외부 의존성 없음, Canvas 렌더링. 사용자 메시지는 게임을 설명하고 시작 화면이나 게임 오버 로직 같은 기능을 모델이 빠뜨리지 않도록 구체적인 요구 사항을 나열해요.
"game engineering requirements"(게임 엔지니어링 요구 사항) 블록이 핵심 추가 요소예요. GLM은 때때로 메커닉이 깨진 게임 — 죽일 수 없는 적, 없는 충돌 감지, 벽 안에서 생성되는 적 — 을 만들 수 있어요. 이 요구 사항들은 실패하기 쉬운 시스템을 어떻게 구현할지 모델에게 알려줘요.
main 함수 위에 다음을 추가하세요:
# Step 2 — Craft the game prompt
# Be specific about mechanics, controls, visuals, and scope.
# The more detail you provide, the better the generated game.
GAME_DESCRIPTION = (
"A top-down dungeon crawler. The player navigates procedurally generated "
"rooms connected by doorways. Each room contains enemies that patrol and "
"chase the player on sight. Defeating enemies drops health potions or score "
"pickups. The player has a melee attack (spacebar) and 3 lives. Generate at "
"least 5 connected rooms. Show a minimap in the corner."
)
# The system message constrains the output format (single HTML file, no
# external dependencies, Canvas rendering). The user message describes the
# game and lists concrete requirements so the model doesn't omit features.
# The "Game engineering requirements" block addresses common failure modes
# like broken collision detection, enemies that can't be killed, and missing
# spawn logic.
def build_game_prompt(game_description: str) -> tuple[str, str]:
"""Build the system and user prompts for game generation."""
system_prompt = (
"You are an expert game developer. You produce complete, self-contained "
"HTML files with embedded CSS and JavaScript. Never use external CDNs, "
"libraries, or dependencies. Use HTML5 Canvas for rendering. The game "
"must be fully playable in any modern browser by opening the HTML file directly."
)
user_prompt = (
f"Create a complete, playable game: {game_description}\n\n"
"Requirements:\n"
"- Single HTML file with all CSS and JS embedded\n"
"- No external dependencies, CDNs, or imports\n"
"- Use HTML5 Canvas for rendering\n"
"- Keyboard controls (arrow keys or WASD)\n"
"- Include a start screen with instructions\n"
"- Track and display score and health/lives\n"
"- Include game-over and restart logic\n"
"- Use requestAnimationFrame for the game loop\n"
"- Add colors, simple shapes, or pixel art for visuals\n\n"
"Game engineering requirements (follow these exactly):\n"
"- Collision detection: implement rectangle or circle collision checks. "
"Every entity (player, enemies, projectiles, items) must have x, y, "
"width, and height properties used in collision tests.\n"
"- Enemy health: every enemy must have a numeric health property that "
"decreases when the player attacks. Remove the enemy when health "
"reaches 0.\n"
"- Combat feedback: when the player attacks, check collision against "
"every enemy in range. On hit, decrease enemy health and show visual "
"feedback (flash, particle, or color change).\n"
"- Valid spawning: enemies must spawn on valid floor positions, never "
"inside walls or on top of the player. Validate positions before "
"placing.\n"
"- Game loop integrity: the update function must call enemy AI, "
"collision detection, and rendering every frame. Never skip a step.\n"
"- Input handling: use keydown/keyup events with a keys-pressed "
"object (e.g., `keys = {}`) that tracks which keys are currently held. "
"Check this object each frame in the update loop.\n\n"
"Return the complete HTML file inside a single ```html code fence."
)
return system_prompt, user_prompt
엔지니어링 요구 사항은 여섯 가지 실패 모드를 다뤄요:
| Requirement | What it prevents |
|---|---|
| Collision detection | Entities passing through each other |
| Enemy health | Enemies that can't be killed |
| Combat feedback | Attacks that don't register hits |
| Valid spawning | Enemies stuck in walls or overlapping the player |
| Game loop integrity | Systems that stop updating mid-game |
| Input handling | Dropped key presses or stuck movement |
Step 3 — 게임 생성 (Generate the game)
프롬프트를 GLM에 보내고 응답에서 HTML을 추출해요. 모델은 자신의 출력을 ```html 코드 펜스로 감싸요. extract_html 함수는 이를 파싱하고, 펜스가 없으면 DOCTYPE 기반 추출로 폴백해요.
main 위에 extract_html과 generate_game 함수를 추가하세요:
# Step 3 — Extract HTML from the response
# The model wraps its output in a ```html code fence. This function extracts
# the HTML content, falling back to DOCTYPE-based extraction if no fence is found.
def extract_html(text: str) -> str:
"""Extract HTML content from the model response."""
# Try fenced code block first
match = re.search(r"```html\s*\n(.*?)```", text, re.DOTALL)
if match:
return match.group(1).strip()
# Fall back to DOCTYPE extraction
match = re.search(r"(<!DOCTYPE.*?</html>)", text, re.DOTALL | re.IGNORECASE)
if match:
return match.group(1).strip()
raise ValueError("No HTML content found in the model response.")
# Wraps the GLM call and HTML extraction into a single function.
def generate_game(system_prompt: str, user_prompt: str) -> str:
"""Call GLM to generate a game and return the extracted HTML."""
response = client.chat.complete(
model="zai-glm-5-2",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
)
return extract_html(response.choices[0].message.content)
Step 4 — 게임 검토와 수정 (Review and fix the game)
좋은 프롬프트가 있어도 생성된 게임에 버그가 있을 때가 있어요. 이는 두 종류로 나뉘어요:
- 런타임 오류 (Runtime errors) — 스폰 함수가 정의되지 않은 속성에 접근하거나, 초기화 코드가 인자가 빠진 함수를 호출하거나, 충돌 검사가 잘못된 객체를 참조해요. 게임이
TypeError로 충돌해요. - 깨진 로직 (Broken logic) — 적이 데미지를 받지만 게임에서 제거되지 않거나, 공격이 연결되지만 시각적 피드백이 없거나, 게임 루프가 시스템을 건너뛰어요. 게임은 실행되지만 제대로 플레이되지 않아요.
이 단계는 HTML을 mistral-medium-latest에 보내서 두 가지를 모두 검사하는 두 부분짜리 검토를 수행해요. 문제가 발견되면 HTML과 문제 설명을 GLM에 다시 보내서 목표된 수정(targeted fix)을 받아요. 검토가 통과할 때까지 루프가 실행되며, 최대 5회 시도돼요.
이것은 서로 다른 강점을 위해 두 모델을 사용해요:
mistral-medium-latest는 코드를 검토해요. 런타임 오류를 위해 실행 경로를 추적하고, 올바름을 위해 게임 로직을 끝까지 따라가요.zai-glm-5-2(GLM)는 코드를 수정해요. 게임을 생성한 것과 같은 모델이라 코드베이스를 이해해요.
main 위에 review_game과 fix_game 함수를 추가하세요:
# Step 4 — Review and fix the game
# After generation, send the HTML to mistral-medium-latest for a structured
# review. If issues are found, send the HTML and issue list back to GLM for
# a targeted fix. This loop runs up to 2 times.
def review_game(html_content: str) -> str | None:
"""Review generated HTML for common game mechanic issues.
Returns a string describing the issues found, or None if no issues.
"""
review_prompt = (
"You are a game QA engineer. Review the following HTML5 game for "
"both runtime errors and broken game logic.\n\n"
"PART 1 — RUNTIME ERRORS\n"
"Trace these code paths from call site to implementation. Verify "
"that every variable and property referenced actually exists. A "
"function that accesses undefined properties is a FAIL.\n\n"
"1. Initialization: Trace the startup path. Does every function "
"called during init receive the arguments it expects? Are arrays "
"and objects initialized before being accessed?\n"
"2. Spawning: Trace the enemy spawn function. Does it access "
"properties (like room.x, room.width) that actually exist on the "
"objects passed to it?\n"
"3. Game loop: Does the update/render loop call functions with "
"correct arguments? Does it access properties on objects that "
"might be undefined?\n"
"4. Room transitions: When the player moves to a new room, are "
"all references updated correctly?\n\n"
"PART 2 — GAME LOGIC\n"
"Trace these mechanics end-to-end. It's not enough for the code "
"to exist — follow the logic and confirm it produces the correct "
"outcome.\n\n"
"5. Enemy death: Trace from player attack to enemy removal. Does "
"the attack decrease enemy health? When health reaches 0, is the "
"enemy actually removed from the array/list so it stops rendering "
"and updating? A health property that decreases but never triggers "
"removal is a FAIL.\n"
"6. Collision detection: Are collision checks called with the "
"correct coordinates and dimensions? Do entities have the x, y, "
"width, height properties the checks reference?\n"
"7. Combat feedback: When the player attacks and hits an enemy, "
"is there any visual feedback (flash, color change, particle)? "
"An attack that silently reduces health with no indication is a "
"FAIL.\n"
"8. Input handling: Are keydown/keyup events tracked in a "
"keys-pressed object checked each frame? A system that only uses "
"keydown without tracking held keys will miss continuous input.\n\n"
"If ALL checks pass, respond with exactly: PASS\n\n"
"If any check fails, describe the specific bug: which function, "
"which property or logic path, and what goes wrong. Do not include "
"the game code in your response.\n\n"
f"```html\n{html_content}\n```"
)
response = client.chat.complete(
model="mistral-medium-latest",
messages=[{"role": "user", "content": review_prompt}],
)
result = response.choices[0].message.content.strip()
# The model may add preamble before "PASS" or say "PASS - all checks passed",
# so check whether the entire response is short and contains PASS.
if len(result) < 100 and "PASS" in result.upper():
return None
return result
def fix_game(html_content: str, issues: str) -> str:
"""Send the HTML and issue list back to GLM for a targeted fix."""
fix_prompt = (
"The following HTML5 game has specific issues that need fixing. "
"Fix ONLY the listed issues. Keep everything else unchanged.\n\n"
f"Issues to fix:\n{issues}\n\n"
f"Game code:\n```html\n{html_content}\n```\n\n"
"Return the complete fixed HTML file inside a single ```html code fence."
)
response = client.chat.complete(
model="zai-glm-5-2",
messages=[
{
"role": "system",
"content": (
"You are an expert game developer. Fix the specific issues "
"listed in the game code. Return the complete, corrected "
"HTML file. Do not remove working features."
),
},
{"role": "user", "content": fix_prompt},
],
)
return extract_html(response.choices[0].message.content)
검토-수정 루프가 main에 어떻게 통합되는지는 Step 7에서 볼 수 있어요. main은 검토가 통과할 때까지(최대 5회 시도) review_game과 fix_game을 루프로 호출해요.
Step 5 — 게임 편집 (Edit the game)
생성된 게임이 대부분 맞지만 특정 문제 — 적이 데미지를 받지 않거나, 미니맵이 없거나, 움직임이 이상하거나 — 가 있을 수 있어요. 처음부터 다시 생성하는 대신, --edit 플래그로 무엇이 잘못됐는지 설명하고 목표된 수정을 받을 수 있어요.
main 위에 edit_game 함수를 추가하세요:
# Step 5 — Edit the game
# The --edit flag lets users describe what's wrong with an existing game
# and get a targeted fix without regenerating from scratch.
def edit_game(html_content: str, user_feedback: str) -> str:
"""Send existing game HTML and user feedback to GLM for a targeted fix."""
edit_prompt = (
"The following HTML5 game needs changes based on user feedback. "
"Apply the requested changes while keeping everything else intact.\n\n"
f"User feedback: {user_feedback}\n\n"
f"Current game code:\n```html\n{html_content}\n```\n\n"
"Return the complete updated HTML file inside a single ```html code fence."
)
response = client.chat.complete(
model="zai-glm-5-2",
messages=[
{
"role": "system",
"content": (
"You are an expert game developer. Apply the user's "
"requested changes to the game code. Return the complete, "
"updated HTML file. Do not remove working features."
),
},
{"role": "user", "content": edit_prompt},
],
)
return extract_html(response.choices[0].message.content)
사용법:
python generate_game.py --edit "enemies don't take damage when I attack them"
이 명령은 기존 game.html을 읽고, 여러분의 피드백과 함께 GLM에 보내고, 수정된 내용으로 파일을 덮어써요.
Step 6 — 게임 저장 및 서빙 (Save and serve the game)
브라우저에서 file:// URL을 여는 것은 JavaScript 실행을 깨뜨릴 수 있는 보안 제한을 유발해요. 로컬 HTTP 서버는 이를 완전히 피해요.
main 위에 serve_and_open 함수를 추가하세요:
# Step 6 — Serve the game locally
# Opening file:// URLs triggers browser security restrictions. A local HTTP
# server avoids this and lets the game run without issues.
def serve_and_open(directory: Path, filename: str, port: int = 8000):
"""Start a local HTTP server and open the game in the default browser."""
handler = functools.partial(
http.server.SimpleHTTPRequestHandler, directory=str(directory)
)
server = http.server.HTTPServer(("localhost", port), handler)
url = f"http://localhost:{port}/{filename}"
print(f"Serving game at {url}")
print("Press Ctrl+C to stop the server.")
webbrowser.open(url)
server.serve_forever()
Step 7 — main에서 모두 묶기 (Tie it all together in main)
Step 1의 main에 있는 플레이스홀더를 전체 오케스트레이션으로 교체하세요. 이 함수는 이전의 모든 단계를 묶어요 — 인자를 파싱하고, 생성·검토 함수를 호출하고, 편집 모드를 처리하고, 결과를 서빙해요:
def main():
# Step 7 — Tie it all together in main
parser = argparse.ArgumentParser(description="Generate or edit an HTML5 game.")
parser.add_argument(
"--edit",
type=str,
help="Edit an existing game.html. Describe what to fix.",
)
args = parser.parse_args()
output = Path("game.html")
if args.edit:
# Step 5 — Edit mode: read existing game and apply fixes
if not output.exists():
print(f"Error: {output} not found. Generate a game first.")
return
print(f"Editing game: {args.edit}")
html_content = output.read_text(encoding="utf-8")
html_content = edit_game(html_content, args.edit)
else:
# Step 2 — Craft the prompt
system_prompt, user_prompt = build_game_prompt(GAME_DESCRIPTION)
# Step 3 — Generate the game
print(f"Generating game: {GAME_DESCRIPTION}")
print("This may take a few minutes...")
html_content = generate_game(system_prompt, user_prompt)
# Step 4 — Review and fix
max_attempts = 5
for attempt in range(max_attempts):
print(f"Reviewing game (attempt {attempt + 1}/{max_attempts})...")
issues = review_game(html_content)
if issues is None:
print("Review passed.")
break
print(f"Issues found:\n{issues}")
print("Fixing issues...")
html_content = fix_game(html_content, issues)
else:
print(f"Applied {max_attempts} rounds of fixes. Saving best result.")
output.write_text(html_content, encoding="utf-8")
print(f"Game saved to {output.resolve()}")
# Step 6 — Serve the game locally
serve_and_open(output.resolve().parent, output.name)
실행 (Run)
스크립트를 실행하세요:
python generate_game.py
스크립트는 GLM을 호출하고, 게임을 깨진 메커닉이 있는지 검토하고, 발견한 문제를 수정하고(최대 5라운드), HTML을 game.html에 저장하고, 브라우저에서 엽니다.
예시 출력:
Generating game: A top-down dungeon crawler. The player navigates procedurally generated rooms...
This may take a few minutes...
Reviewing game (attempt 1/5)...
Issues found:
3. Combat: The attack function does not check collision against enemies. Pressing spacebar sets an attack flag but no damage is applied.
4. Spawning: Enemies are placed at random positions without checking for wall overlap.
Fixing issues...
Reviewing game (attempt 2/5)...
Review passed.
Game saved to /Users/you/glm_game_generator/game.html
Serving game at http://localhost:8000/game.html
Press Ctrl+C to stop the server.
기존 게임의 특정 문제를 수정하려면:
python generate_game.py --edit "the minimap doesn't update when I move to a new room"
다른 게임 시도 (Try different games)
GAME_DESCRIPTION을 바꿔서 다른 게임을 만드세요. 몇 가지 아이디어예요:
스페이스 슈터 (Space shooter):
GAME_DESCRIPTION = (
"A vertical-scrolling space shooter. The player controls a ship at the bottom "
"of the screen, moves left/right with arrow keys, and shoots with spacebar. "
"Waves of enemy ships descend from the top with different movement patterns. "
"Power-ups drop from destroyed enemies: rapid fire, shield, triple shot. "
"Track score and display a high-score counter."
)
브레이크아웃 클론 (Breakout clone):
GAME_DESCRIPTION = (
"A Breakout/Arkanoid clone. The player controls a paddle at the bottom with "
"left/right arrow keys. A ball bounces around the screen destroying colored "
"bricks. Different brick colors take different numbers of hits. Some bricks "
"drop power-ups: wider paddle, multi-ball, sticky paddle. Include 3 levels "
"with different brick layouts."
)
타워 디펜스 (Tower defense):
GAME_DESCRIPTION = (
"A tower defense game. Enemies follow a winding path from the top-left to "
"the bottom-right. Click on empty tiles adjacent to the path to place towers. "
"Three tower types: arrow (fast, low damage), cannon (slow, splash damage), "
"and ice (slows enemies). Earn gold from defeated enemies to buy more towers. "
"Survive 10 waves with increasing difficulty."
)
전체 스크립트 (Complete script)
참고용으로, 모든 단계를 결합한 전체 스크립트입니다:
"""Generate a playable HTML5 mini game using the GLM model via the Mistral API."""
import argparse
import functools
import http.server
import os
import re
import webbrowser
from pathlib import Path
from dotenv import load_dotenv
import httpx
from mistralai.client import Mistral
load_dotenv()
# Step 1 — Initialize the client
# GLM generates large code outputs that can take several minutes. The default
# httpx timeout is too short, so set it to 10 minutes to match timeout_ms.
client = Mistral(
api_key=os.environ["MISTRAL_API_KEY"],
timeout_ms=600_000,
client=httpx.Client(follow_redirects=True, timeout=httpx.Timeout(600.0)),
)
# Step 2 — Craft the game prompt
# Be specific about mechanics, controls, visuals, and scope.
# The more detail you provide, the better the generated game.
GAME_DESCRIPTION = (
"A top-down dungeon crawler. The player navigates procedurally generated "
"rooms connected by doorways. Each room contains enemies that patrol and "
"chase the player on sight. Defeating enemies drops health potions or score "
"pickups. The player has a melee attack (spacebar) and 3 lives. Generate at "
"least 5 connected rooms. Show a minimap in the corner."
)
# The system message constrains the output format (single HTML file, no
# external dependencies, Canvas rendering). The user message describes the
# game and lists concrete requirements so the model doesn't omit features.
# The "Game engineering requirements" block addresses common failure modes
# like broken collision detection, enemies that can't be killed, and missing
# spawn logic.
def build_game_prompt(game_description: str) -> tuple[str, str]:
"""Build the system and user prompts for game generation."""
system_prompt = (
"You are an expert game developer. You produce complete, self-contained "
"HTML files with embedded CSS and JavaScript. Never use external CDNs, "
"libraries, or dependencies. Use HTML5 Canvas for rendering. The game "
"must be fully playable in any modern browser by opening the HTML file directly."
)
user_prompt = (
f"Create a complete, playable game: {game_description}\n\n"
"Requirements:\n"
"- Single HTML file with all CSS and JS embedded\n"
"- No external dependencies, CDNs, or imports\n"
"- Use HTML5 Canvas for rendering\n"
"- Keyboard controls (arrow keys or WASD)\n"
"- Include a start screen with instructions\n"
"- Track and display score and health/lives\n"
"- Include game-over and restart logic\n"
"- Use requestAnimationFrame for the game loop\n"
"- Add colors, simple shapes, or pixel art for visuals\n\n"
"Game engineering requirements (follow these exactly):\n"
"- Collision detection: implement rectangle or circle collision checks. "
"Every entity (player, enemies, projectiles, items) must have x, y, "
"width, and height properties used in collision tests.\n"
"- Enemy health: every enemy must have a numeric health property that "
"decreases when the player attacks. Remove the enemy when health "
"reaches 0.\n"
"- Combat feedback: when the player attacks, check collision against "
"every enemy in range. On hit, decrease enemy health and show visual "
"feedback (flash, particle, or color change).\n"
"- Valid spawning: enemies must spawn on valid floor positions, never "
"inside walls or on top of the player. Validate positions before "
"placing.\n"
"- Game loop integrity: the update function must call enemy AI, "
"collision detection, and rendering every frame. Never skip a step.\n"
"- Input handling: use keydown/keyup events with a keys-pressed "
"object (e.g., `keys = {}`) that tracks which keys are currently held. "
"Check this object each frame in the update loop.\n\n"
"Return the complete HTML file inside a single ```html code fence."
)
return system_prompt, user_prompt
# Step 3 — Generate the game
# The model wraps its output in a ```html code fence. This function extracts
# the HTML content, falling back to DOCTYPE-based extraction if no fence is found.
def extract_html(text: str) -> str:
"""Extract HTML content from the model response."""
# Try fenced code block first
match = re.search(r"```html\s*\n(.*?)```", text, re.DOTALL)
if match:
return match.group(1).strip()
# Fall back to DOCTYPE extraction
match = re.search(r"(<!DOCTYPE.*?</html>)", text, re.DOTALL | re.IGNORECASE)
if match:
return match.group(1).strip()
raise ValueError("No HTML content found in the model response.")
# Wraps the GLM call and HTML extraction into a single function.
def generate_game(system_prompt: str, user_prompt: str) -> str:
"""Call GLM to generate a game and return the extracted HTML."""
response = client.chat.complete(
model="zai-glm-5-2",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
)
return extract_html(response.choices[0].message.content)
# Step 4 — Review and fix the game
# After generation, send the HTML to mistral-medium-latest for a structured
# review. If issues are found, send the HTML and issue list back to GLM for
# a targeted fix. This loop runs up to 2 times.
def review_game(html_content: str) -> str | None:
"""Review generated HTML for common game mechanic issues.
Returns a string describing the issues found, or None if no issues.
"""
review_prompt = (
"You are a game QA engineer. Review the following HTML5 game for "
"both runtime errors and broken game logic.\n\n"
"PART 1 — RUNTIME ERRORS\n"
"Trace these code paths from call site to implementation. Verify "
"that every variable and property referenced actually exists. A "
"function that accesses undefined properties is a FAIL.\n\n"
"1. Initialization: Trace the startup path. Does every function "
"called during init receive the arguments it expects? Are arrays "
"and objects initialized before being accessed?\n"
"2. Spawning: Trace the enemy spawn function. Does it access "
"properties (like room.x, room.width) that actually exist on the "
"objects passed to it?\n"
"3. Game loop: Does the update/render loop call functions with "
"correct arguments? Does it access properties on objects that "
"might be undefined?\n"
"4. Room transitions: When the player moves to a new room, are "
"all references updated correctly?\n\n"
"PART 2 — GAME LOGIC\n"
"Trace these mechanics end-to-end. It's not enough for the code "
"to exist — follow the logic and confirm it produces the correct "
"outcome.\n\n"
"5. Enemy death: Trace from player attack to enemy removal. Does "
"the attack decrease enemy health? When health reaches 0, is the "
"enemy actually removed from the array/list so it stops rendering "
"and updating? A health property that decreases but never triggers "
"removal is a FAIL.\n"
"6. Collision detection: Are collision checks called with the "
"correct coordinates and dimensions? Do entities have the x, y, "
"width, height properties the checks reference?\n"
"7. Combat feedback: When the player attacks and hits an enemy, "
"is there any visual feedback (flash, color change, particle)? "
"An attack that silently reduces health with no indication is a "
"FAIL.\n"
"8. Input handling: Are keydown/keyup events tracked in a "
"keys-pressed object checked each frame? A system that only uses "
"keydown without tracking held keys will miss continuous input.\n\n"
"If ALL checks pass, respond with exactly: PASS\n\n"
"If any check fails, describe the specific bug: which function, "
"which property or logic path, and what goes wrong. Do not include "
"the game code in your response.\n\n"
f"```html\n{html_content}\n```"
)
response = client.chat.complete(
model="mistral-medium-latest",
messages=[{"role": "user", "content": review_prompt}],
)
result = response.choices[0].message.content.strip()
# The model may add preamble before "PASS" or say "PASS - all checks passed",
# so check whether the entire response is short and contains PASS.
if len(result) < 100 and "PASS" in result.upper():
return None
return result
def fix_game(html_content: str, issues: str) -> str:
"""Send the HTML and issue list back to GLM for a targeted fix."""
fix_prompt = (
"The following HTML5 game has specific issues that need fixing. "
"Fix ONLY the listed issues. Keep everything else unchanged.\n\n"
f"Issues to fix:\n{issues}\n\n"
f"Game code:\n```html\n{html_content}\n```\n\n"
"Return the complete fixed HTML file inside a single ```html code fence."
)
response = client.chat.complete(
model="zai-glm-5-2",
messages=[
{
"role": "system",
"content": (
"You are an expert game developer. Fix the specific issues "
"listed in the game code. Return the complete, corrected "
"HTML file. Do not remove working features."
),
},
{"role": "user", "content": fix_prompt},
],
)
return extract_html(response.choices[0].message.content)
# Step 5 — Edit the game
# The --edit flag lets users describe what's wrong with an existing game
# and get a targeted fix without regenerating from scratch.
def edit_game(html_content: str, user_feedback: str) -> str:
"""Send existing game HTML and user feedback to GLM for a targeted fix."""
edit_prompt = (
"The following HTML5 game needs changes based on user feedback. "
"Apply the requested changes while keeping everything else intact.\n\n"
f"User feedback: {user_feedback}\n\n"
f"Current game code:\n```html\n{html_content}\n```\n\n"
"Return the complete updated HTML file inside a single ```html code fence."
)
response = client.chat.complete(
model="zai-glm-5-2",
messages=[
{
"role": "system",
"content": (
"You are an expert game developer. Apply the user's "
"requested changes to the game code. Return the complete, "
"updated HTML file. Do not remove working features."
),
},
{"role": "user", "content": edit_prompt},
],
)
return extract_html(response.choices[0].message.content)
# Step 6 — Serve the game locally
# Opening file:// URLs triggers browser security restrictions. A local HTTP
# server avoids this and lets the game run without issues.
def serve_and_open(directory: Path, filename: str, port: int = 8000):
"""Start a local HTTP server and open the game in the default browser."""
handler = functools.partial(
http.server.SimpleHTTPRequestHandler, directory=str(directory)
)
server = http.server.HTTPServer(("localhost", port), handler)
url = f"http://localhost:{port}/{filename}"
print(f"Serving game at {url}")
print("Press Ctrl+C to stop the server.")
webbrowser.open(url)
server.serve_forever()
# Step 7 — Tie it all together in main
def main():
parser = argparse.ArgumentParser(description="Generate or edit an HTML5 game.")
parser.add_argument(
"--edit",
type=str,
help="Edit an existing game.html. Describe what to fix.",
)
args = parser.parse_args()
output = Path("game.html")
if args.edit:
# Step 5 — Edit mode: read existing game and apply fixes
if not output.exists():
print(f"Error: {output} not found. Generate a game first.")
return
print(f"Editing game: {args.edit}")
html_content = output.read_text(encoding="utf-8")
html_content = edit_game(html_content, args.edit)
else:
# Step 2 — Craft the prompt
system_prompt, user_prompt = build_game_prompt(GAME_DESCRIPTION)
# Step 3 — Generate the game
print(f"Generating game: {GAME_DESCRIPTION}")
print("This may take a few minutes...")
html_content = generate_game(system_prompt, user_prompt)
# Step 4 — Review and fix
max_attempts = 5
for attempt in range(max_attempts):
print(f"Reviewing game (attempt {attempt + 1}/{max_attempts})...")
issues = review_game(html_content)
if issues is None:
print("Review passed.")
break
print(f"Issues found:\n{issues}")
print("Fixing issues...")
html_content = fix_game(html_content, issues)
else:
print(f"Applied {max_attempts} rounds of fixes. Saving best result.")
output.write_text(html_content, encoding="utf-8")
print(f"Game saved to {output.resolve()}")
# Step 6 — Serve the game locally
serve_and_open(output.resolve().parent, output.name)
if __name__ == "__main__":
main()
요약 (Summary)
이 쿡북은 GLM을 자동 품질 검사가 있는 코드 생성 엔진으로 사용하는 방법을 보여줬어요 — 상세한 게임 설명을 보내고, 깨진 메커닉이 없는지 출력을 검토하고, 문제를 자동으로 수정하고, 목표된 편집으로 기존 게임을 반복 개선해요.
만든 것 (What you built):
- 텍스트 설명을 플레이 가능한 HTML5 Canvas 게임으로 바꾸는 게임 생성 스크립트
- 흔한 실패 모드(깨진 충돌, 죽일 수 없는 적, 잘못된 스폰)를 방지하는 엔지니어링 요구 사항이 있는 프롬프트 구조
mistral-medium-latest로 게임을 QA하고 GLM으로 문제를 패치하는 자동 검토-수정 루프- 처음부터 다시 생성하지 않고 특정 문제를 수정하는 편집 모드(
--edit) - 브라우저 보안 제한 없이 생성된 게임을 서빙하는 로컬 HTTP 서버
사용한 Mistral 기능 (Mistral features used):
- 코드 생성을 위한
zai-glm-5-2모델을 사용한 Chat completions API - 코드 검토를 위한
mistral-medium-latest를 사용한 Chat completions API - 구조화된 프롬프팅을 위한 system/user 메시지 역할
- 긴 코드 생성을 위한 확장 타임아웃 (
timeout_ms와httpx.Timeout)
여러분 자신의 게임 아이디어를 설명해서 GLM이 무엇을 만드는지 확인해 보세요. 사용 가능한 모델에 대한 자세한 내용은 모델 문서를 참고하세요.