From c0356500637e2830dd856b87bd3c043135642d5a Mon Sep 17 00:00:00 2001 From: Oleg Date: Sat, 1 Aug 2026 03:35:31 +0000 Subject: [PATCH] Maze: Pac-Man-style dots, ghosts, and continuous movement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full redesign of the Maze template (same templateType: "maze" — plain text column, no migration needed): dots (*) to eat, power pellets (O) that frighten ghosts, ghost spawns (G, 0-4), and continuous glide movement with buffered turns instead of one-cell-per-keypress. Win condition confirmed with the user explicitly: collect every dot and pellet, then reach the exit — kept the exit tile rather than switching to classic Pac-Man's "no exit, board-clear wins," since this app's maze identity is built around it. Ghosts chase via BFS pathfinding (reusing the same BFS shape the maze's own reachability check already used, just retargeted at the live player position each tick) and flee via simple greedy distance-maximizing when frightened. The reachability check itself generalized from "is the exit reachable" to "is every dot, pellet, and the exit reachable" — same guarantee, now covering the actual win condition instead of just the exit. Verified precisely: a maze with a reachable exit but one walled-off dot is correctly rejected. Continuous movement is tick-based, not real physics: a repeating timer steps the player in the buffered direction (or continues straight) if open, tweening the sprite's pixel position between cells for a glide feel rather than snapping. Same 3-lives-on-capture, coarse-grained resume (position + collected keys + lives, ghosts reset to spawn on reload) as the other templates' resume behavior. The one existing maze game on production validated cleanly against the new schema unchanged (lives defaults to 3, its dots were already reachable) — no data migration needed this time. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH --- src/components/builders/maze-builder.tsx | 44 +- src/lib/ai/generate.ts | 2 +- src/lib/games/maze.ts | 83 +++- src/lib/games/scenes/maze-scene.ts | 517 +++++++++++++++++------ 4 files changed, 490 insertions(+), 156 deletions(-) diff --git a/src/components/builders/maze-builder.tsx b/src/components/builders/maze-builder.tsx index d1062ee..47a2fad 100644 --- a/src/components/builders/maze-builder.tsx +++ b/src/components/builders/maze-builder.tsx @@ -23,13 +23,14 @@ export function MazeBuilder({ const [description, setDescription] = useState(initialDescription ?? ""); const [gridText, setGridText] = useState((initialDefinition?.grid ?? DEFAULT_MAZE_DEFINITION.grid).join("\n")); const [timeLimitSec, setTimeLimitSec] = useState(initialDefinition?.timeLimitSec ?? 0); + const [lives, setLives] = useState(initialDefinition?.lives ?? DEFAULT_MAZE_DEFINITION.lives); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); async function handleSave() { setError(null); const grid = gridText.split("\n").map((row) => row.trimEnd()).filter((row) => row.length > 0); - const definition: MazeDefinition = { grid, timeLimitSec }; + const definition: MazeDefinition = { grid, timeLimitSec, lives }; const parsed = mazeDefinitionSchema.safeParse(definition); if (!parsed.success) { setError(parsed.error.issues[0]?.message ?? "Проверьте лабиринт — что-то заполнено неверно"); @@ -88,24 +89,39 @@ export function MazeBuilder({ className="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm outline-none focus:border-accent" /> - +
+ + +
- Лабиринт — каждая строка ряд. # стена,{" "} + Лабиринт в стиле Pac-Man — каждая строка ряд. # стена,{" "} . пол, S старт (один),{" "} - E выход, * предмет. + E выход, * точка,{" "} + O усиливающая точка (пугает привидений),{" "} + G призрак (до 4). Нужно собрать все точки и дойти до выхода.