můžeš mi vytvořit jednoduchou 2d hru ale než jí začneš dělat ptej se mě na otázky aby ti bylo jasné jak má vypadat a jaký má být styl hry


claude claude-code-guide Explore general-purpose Plan statusline-setup

- claude: Catch-all for any task that doesn't fit a more specific agent. FleetView's default when no agent name is typed. (Tools: *)

- claude-code-guide: Use this agent when the user asks questions ("Can Claude...", "Does Claude...", "How do I...") about: (1) Claude Code (the CLI tool) - features, hooks, slash commands, MCP servers, settings, IDE integrations, keyboard shortcuts; (2) Claude Agent SDK - building custom agents; (3) Claude API (formerly Anthropic API) - Messages API for directly passing messages to Claude, Tool Runner (`client.beta.messages.tool_runner`) for running an agentic loop over your own tools, manual tool-use loops, Managed Agents for server-hosted agents with a managed sandbox, prompt caching, and general Anthropic SDK usage; (4) Claude Tag (Claude in Slack) - what it is, setting it up for a Slack workspace, `/install-slack-app`; (5) `claude plugin eval` (writing and running plugin eval suites, its JSON/report, sandbox, CI, early-access enablement) and the `/skill-doctor` report. **IMPORTANT:** Before spawning a new agent, check if there is already a running or recently completed claude-code-guide agent that you can continue via SendMessage. (Tools: Bash, Read, WebFetch, WebSearch)

- Explore: Read-only search agent for broad fan-out searches — when answering means sweeping many files, directories, or naming conventions and you only need the conclusion, not the file dumps. It reads excerpts rather than whole files, so it locates code; it doesn't review or audit it. Specify search breadth: "medium" for moderate exploration, "very thorough" for multiple locations and naming conventions. (Tools: All tools except Agent, Artifact, ExitPlanMode, Edit, Write, NotebookEdit)

- general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. (Tools: *)

- Plan: Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. (Tools: All tools except Agent, Artifact, ExitPlanMode, Edit, Write, NotebookEdit)

- statusline-setup: Use this agent to configure the user's Claude Code status line setting. (Tools: Read, Edit)


- design: Create a design canvas - a multi-artboard visual design published as an Artifact that runs Claude Design's canvas editor (an early preview of Claude Design inside Claude Code). You DRAFT the design as .dc.html artboards laid out on one pan/zoom canvas; where saving is enabled for the user's account they refine every element visually (click-to-select, a properties panel, inline text editing, undo/redo) and Save publishes a new version for everyone, otherwise they get a view-and-export (PNG/PDF) preview of your draft. Good for UI mockups and screen flows, landing pages, marketing and social graphics, and print pieces - posters, flyers, brochures as single-page artboards; memos and reports as one flowing artboard. Use when someone wants a design, mockup, wireframe, UI or screen design, landing page, poster, flyer, brochure, banner, card, one-pager, or any visual layout they would rather tweak by hand than in code. Only for CREATING or re-seeding a canvas; an existing one is edited in its published Artifact.
- dataviz: Use this skill whenever you are about to create ANY chart, graph, plot, dashboard, or data visualization, in ANY output medium — an HTML or React artifact, inline SVG, plotting code in any library (matplotlib, plotly, d3, Recharts, …), an image/PNG you will render and upload, or a chart shared into Slack. Read it BEFORE writing the first line of chart code, choosing chart colors, building a stat tile / meter / KPI row, or laying out a dashboard. Produces visualizations that read as one system — elegant, accessible, consistent in light and dark — using a brand-neutral placeholder palette you swap for your own. Teaches a design-system-agnostic method: a form heuristic, a color formula with a runnable validator, mark specs, and interaction rules. A validated default palette is documented in `references/palette.md` — swap that file's values for your brand's. Triggers on: "chart", "graph", "plot", "data viz", "visualization", "dashboard", "analytics", "visualize data", "categorical colors", "sequential / diverging palette", "stat tile", "sparkline", "heatmap", "legend", "axis", "tooltip", "chart colors", "color by series".
- artifact-design: Design guidance and fundamentals for Artifacts. - Load before writing any artifact, including a skill-instructed Markdown one - Markdown is never a shortcut past the design pass.
- artifact-diagramming: Diagramming know-how for Artifacts - when a picture earns its place, how to draw one that shows the real mechanism, and the inline-SVG mechanics that keep it legible in both themes.
- artifact-capabilities: Runtime capabilities a published Artifact page can be granted — behavior static HTML cannot provide on its own, such as the page reading live or connected data, remembering what people do on it (a poll, a sign-up sheet, a checklist, a document edited in place — it saves new versions of itself), keeping state shared across viewers, knowing who is viewing, asking Claude a question of its own, storing files people add, or handing the viewer a file to save. Serves this user's live capability roster and the typed call definitions. Load it whenever the user asks for an artifact needing any such runtime behavior.
- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, suggest the /config command.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- code-review: Review the current diff, or a PR number/branch/path target, for correctness bugs and reuse/simplification/efficiency cleanups at the given effort level (low/medium: fewer, high-confidence findings; high→max: broader coverage, may include uncertain findings; ultra: deep multi-agent review in the cloud); with no level given, it reuses the level you typed last. Pass --comment to post findings as inline PR comments, or --fix to apply the findings to the working tree after the review. For ultra on a GitHub.com PR target, --post asks to post the finished review’s findings to the PR as a single comment from the user’s GitHub account (not a review; the launch dialog still confirms in interactive sessions, while non-interactive mode posts on the flag alone) and --no-post hides that option.
- simplify: Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- schedule: Create, update, list, or run scheduled cloud agents (routines) that execute on a cron schedule. - When the user wants to schedule a recurring cloud agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/routines. Also use when the user wants a one-time scheduled run ("run this once at 3pm", "remind me to check X tomorrow").
- claude-api: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.
TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).
SKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).
- claude-in-chrome: Automates your Chrome browser to interact with web pages - clicking elements, filling forms, capturing screenshots, reading console logs, and navigating sites. Opens pages in new tabs within your existing Chrome session. Requires site-level permissions before executing (configured in the extension). - When the user wants to interact with web pages, automate browser tasks, capture screenshots, read console logs, or perform any browser-based actions. Always invoke BEFORE attempting to use any mcp__claude-in-chrome__* tools.
- run: Launch and drive this project's app to see a change working. Use when asked to run, start, or screenshot the app, or to confirm a change works in the real app (not just tests). First looks for a project skill that already covers launching the app; otherwise falls back to built-in patterns per project type (CLI, server, TUI, Electron, browser-driven, library).
- init: Initialize a new CLAUDE.md file with codebase documentation
- security-review: Complete a security review of the pending changes on the current branch

design dataviz artifact-design artifact-diagramming artifact-capabilities update-config keybindings-help code-review simplify fewer-permission-prompts loop schedule claude-api claude-in-chrome run init security-review


<total_tokens>15000000 tokens left</total_tokens>


Jasně, rád ti udělám 2D hru. Než se do toho pustím, potřebuju vědět pár věcí:


AskUserQuestion

Question: Jaký žánr hry chceš?

Choices:
Skákačka, běháš zleva doprava, vyhýbáš se nepřátelům a propastem (styl Mario)
Sbíráš předměty a uhýbáš překážkám, čím dál tím rychleji (styl Flappy Bird / Dino)
Ovládáš loď/postavu, střílíš na nepřátele, vyhýbáš se projektilům (space shooter)
Klasický had, nebo jednoduchá logická hra (2048, memory apod.)

Question: Kde má hra běžet?

Choices:
Jeden HTML soubor, spustíš dvojklikem nebo jako Artifact. Nejjednodušší na sdílení.
Desktop okno, potřebuje nainstalovaný Python a knihovnu pygame

Question: Jaký vizuální styl preferuješ?

Choices:
Barevné čtverce, kruhy, čisté pozadí. Rychlé a přehledné.
8-bit vzhled, pixelové sprity, výrazné barvy
Tmavé pozadí, svítící objekty, moderní vibe

Question: Jak náročná/rozsáhlá má hra být?

Choices:
Jedna obrazovka, jeden herní princip, skóre a game over
Více levelů nebo narůstající obtížnost, power-upy, zvuky


The user answered: "Jaký žánr hry chceš?"="Plošinovka", "Kde má hra běžet?"="prostě otevíratelnej soubor", "Jaký vizuální styl preferuješ?"="Minimalistický / geometrický", "Jak náročná/rozsáhlá má hra být?"="Střední". Read the answers carefully — they may request clarification, changes, or that you not proceed — and follow what they actually say.

Question: Jaký žánr hry chceš?

Choices:
Skákačka, běháš zleva doprava, vyhýbáš se nepřátelům a propastem (styl Mario)
Sbíráš předměty a uhýbáš překážkám, čím dál tím rychleji (styl Flappy Bird / Dino)
Ovládáš loď/postavu, střílíš na nepřátele, vyhýbáš se projektilům (space shooter)
Klasický had, nebo jednoduchá logická hra (2048, memory apod.)

Question: Kde má hra běžet?

Choices:
Jeden HTML soubor, spustíš dvojklikem nebo jako Artifact. Nejjednodušší na sdílení.
Desktop okno, potřebuje nainstalovaný Python a knihovnu pygame

Question: Jaký vizuální styl preferuješ?

Choices:
Barevné čtverce, kruhy, čisté pozadí. Rychlé a přehledné.
8-bit vzhled, pixelové sprity, výrazné barvy
Tmavé pozadí, svítící objekty, moderní vibe

Question: Jak náročná/rozsáhlá má hra být?

Choices:
Jedna obrazovka, jeden herní princip, skóre a game over
Více levelů nebo narůstající obtížnost, power-upy, zvuky


<total_tokens>14971603 tokens left</total_tokens>


Question: Jak má být hra strukturovaná?

Choices:
3–5 pevných levelů s cílem na konci, postupně těžší. Máš pocit progrese.
Jedna procedurálně generovaná trať, která zrychluje. Hraješ na skóre.

Question: Co bude hlavní herní výzva?

Choices:
Pohybující se protivníci, skočíš na ně nebo se jim vyhneš
Přeskákání mezer, pohyblivé plošiny
Sbíráš mince pro skóre / odemykání
Např. dvojskůk, dočasná nesmrtelnost, zrychlení

Question: Ovládání a životy?

Choices:
Klasika. Při pádu/zásahu ztratíš život, restart levelu po vyčerpání.
Hardcore, okamžitý restart levelu od checkpointu


The user answered: "Jak má být hra strukturovaná?"="kombnace na začátku zvolení mez 2 módy", "Co bude hlavní herní výzva?"="Sběratelné mince, Nepřátelé, Propasti a plošiny", "Ovládání a životy?"="Šipky/WASD + mezerník, 3 životy". Read the answers carefully — they may request clarification, changes, or that you not proceed — and follow what they actually say.

Question: Jak má být hra strukturovaná?

Choices:
3–5 pevných levelů s cílem na konci, postupně těžší. Máš pocit progrese.
Jedna procedurálně generovaná trať, která zrychluje. Hraješ na skóre.

Question: Co bude hlavní herní výzva?

Choices:
Pohybující se protivníci, skočíš na ně nebo se jim vyhneš
Přeskákání mezer, pohyblivé plošiny
Sbíráš mince pro skóre / odemykání
Např. dvojskůk, dočasná nesmrtelnost, zrychlení

Question: Ovládání a životy?

Choices:
Klasika. Při pádu/zásahu ztratíš život, restart levelu po vyčerpání.
Hardcore, okamžitý restart levelu od checkpointu

Sběratelné mince, Nepřátelé, Propasti a plošiny


<total_tokens>14969756 tokens left</total_tokens>


Write

<!doctype html>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hranatý svět — 2D plošinovka</title>

Hranatý svět

  <canvas id="game" width="900" height="540"></canvas>

    Pohyb ← → / A D  ·  Skok Space / ↑ / W  ·  Pauza P  ·  Restart R

    Skoč nepříteli na hlavu a zničíš ho. Sbírej mince. Nespadni do propasti.


File created successfully at: /home/tonda/hra.html (file state is current in your context — no need to Read it back)

<!doctype html>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hranatý svět — 2D plošinovka</title>

Hranatý svět

  <canvas id="game" width="900" height="540"></canvas>

    Pohyb ← → / A D  ·  Skok Space / ↑ / W  ·  Pauza P  ·  Restart R

    Skoč nepříteli na hlavu a zničíš ho. Sbírej mince. Nespadni do propasti.


<total_tokens>14919688 tokens left</total_tokens>


Bash

cd /tmp/claude-1000/-home-tonda/f4c9c9f7-23b2-4e1a-860a-276c7bb05d77/scratchpad && node -e "
const fs=require('fs');
const html=fs.readFileSync('/home/tonda/hra.html','utf8');
const m=html.match(/<script>([\s\S]*)<\/script>/);
fs.writeFileSync('check.js', m[1]);
" && node --check check.js && echo "SYNTAX OK"

Extract inline script and syntax-check it


Error: Exit code 127
/bin/bash: line 6: node: command not found


<total_tokens>14918071 tokens left</total_tokens>


which deno bun qjs node nodejs 2>/dev/null; ls /usr/bin | grep -iE 'node|deno|bun|js' ; echo "---"; which google-chrome chromium chromium-browser firefox 2>/dev/null

Look for a JS runtime or browser


Exit code 1
bunzip2
json_pp
---


<total_tokens>14917807 tokens left</total_tokens>


Hotovo — hra je v souboru **`/home/tonda/hra.html`**. Otevři ho dvojklikem (spustí se v prohlížeči), nic dalšího není potřeba, je to jeden samostatný soubor bez závislostí.

Pozn.: v tomhle prostředí není prohlížeč ani node, takže jsem hru nemohl reálně spustit — jen jsem pečlivě prošel kód. Kdyby něco zlobilo, napiš co a opravím to.

## Co jsem udělal

**Žánr a styl:** 2D plošinovka, minimalistická geometrie — hráč je modrý zaoblený čtverec, nepřátelé červené, mince žluté kolečko, plošiny tmavě modré dlaždice se světlejší horní hranou, tmavé pozadí s jemným parallaxem. Generovaný zvuk (skok, mince, dupnutí, zásah, fanfára).

**Menu na startu** – výběr ze 2 režimů (šipky + Enter, nebo klávesa `1`/`2`, nebo myš):
1. **Příběhové levely** – 4 ručně navržené levely (Rozcvička → Mezery → Výstup → Trhliny), postupně těžší, s vlajkou jako cílem. Mince i skóre se sčítají napříč levely, 3 životy platí na celý průchod.
2. **Nekonečný běh** – procedurálně generovaná trať, kamera se sama posouvá a postupně zrychluje, hraješ na vzdálenost + mince. Když zůstaneš za okrajem obrazovky nebo spadneš, ztratíš život.

**Mechaniky:** chůze/běh se setrvačností, skok s „coyote time“ a bufferem (odpouští nepřesné načasování), variabilní výška skoku, dupnutí na nepřítele shora ho zničí (jinak tě zraní), sbírání mincí, propasti a plošiny, nepřátelé hlídkují a otáčí se na krajích. 3 životy, blikání po zásahu, screen-shake.

**Ovládání:** `←→`/`A D` pohyb · `Space`/`↑`/`W` skok · `P` pauza · `R` restart levelu · `Esc` zpět do menu.

## Jak si hru upravit

Levely jsou nahoře v souboru v poli `LEVELS` — čistá data:
- `pits: [[20,22], ...]` — sloupce bez podlahy (propasti)
- `platforms: [[sloupec, řádek, délka]]`
- `coins` / `enemies: [[sloupec, řádek]]` (nepřítel spadne na nejbližší zem pod sebou)
- `playerStart`, `goal`, `width`

Fyziku (gravitace, síla skoku, rychlost) najdeš v konstantách `GRAV` a ve funkci `updatePlayer`, barvy v objektu `COL`, obtížnost nekonečného režimu ve `genChunk` / `updateCamera`.


You wanted a simple 2D platformer. I built it as a single file at /home/tonda/hra.html: minimalist geometric style, menu with two modes (4 hand-made levels plus endless run), coins, enemies, pits, 3 lives. Next: open it in a browser and test. (disable recaps in /config)


nejde mi to otevřít


ls -la /home/tonda/hra.html; echo "---python---"; which python3 python 2>/dev/null; echo "---ip---"; hostname -I 2>/dev/null; echo "---display---"; echo "DISPLAY=$DISPLAY"; which xdg-open 2>/dev/null

Check file, python, network, GUI availability


Error: Exit code 1
-rw-rw-r-- 1 tonda tonda 28386 Aug 27 13:11 /home/tonda/hra.html
---python---
/usr/bin/python3
---ip---
192.168.5.32
---display---
DISPLAY=


<total_tokens>14999548 tokens left</total_tokens>


nohup python3 -m http.server 8000 --bind 0.0.0.0 > /tmp/claude-1000/-home-tonda/f4c9c9f7-23b2-4e1a-860a-276c7bb05d77/scratchpad/httpd.log 2>&1 &
sleep 1; curl -sS -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8000/hra.html; cat /tmp/claude-1000/-home-tonda/f4c9c9f7-23b2-4e1a-860a-276c7bb05d77/scratchpad/httpd.log

Start HTTP server and verify it responds


Error: Permission for this action was denied by the Claude Code auto mode classifier. Reason: Blocked by classifier. If you have other tasks that don't depend on this action, continue working on those. IMPORTANT: You *may* attempt to accomplish this action using other tools that might naturally be used to accomplish this goal, e.g. using head instead of cat. But you *should not* attempt to work around this denial in malicious ways, e.g. do not use your ability to run tests to execute non-test actions. You should only try to work around this restriction in reasonable ways that do not attempt to bypass the intent behind this denial. If you believe this capability is essential to complete the user's request, STOP and explain to the user what you were trying to do and why you need this permission. Let the user decide how to proceed. To allow this type of action in the future, the user can add a Bash permission rule to their settings.


<total_tokens>14998888 tokens left</total_tokens>


Aha, teď je jasné, proč to nejde. `/home/tonda` je **headless linuxový server** (192.168.5.32, běží na Proxmoxu) — nemá grafické prostředí ani prohlížeč, takže tam není co „dvojklikem otevřít". Hru musíš spustit v prohlížeči na svém počítači (notebook/desktop). Tři možnosti:

## 1) Spustit web server na serveru (nejrychlejší)

Napiš do promptu tohle (prefix `!` to spustí tady v session):

```
! cd /home/tonda && nohup python3 -m http.server 8000 --bind 0.0.0.0 > /tmp/httpd.log 2>&1 & disown
```

Pak na svém počítači otevři v prohlížeči:

```
http://192.168.5.32:8000/hra.html
```

(Předpokládám, že tvůj počítač je na stejné síti 192.168.5.x. Server zastavíš přes `! pkill -f http.server`.)

## 2) Zkopírovat soubor k sobě

Ze svého počítače:

```
scp tonda@192.168.5.32:/home/tonda/hra.html .
```

A pak ten `hra.html` otevři dvojklikem lokálně.

## 3) Přehodit do sdílené složky

Pokud má server nějaký Samba/NFS share nebo webroot (`/var/www/html`), řekni kam, a soubor tam zkopíruju.

---

Který způsob ti sedí? Když mi řekneš, jak se na ten server obvykle připojuješ (SSH z Windows/Mac/Linux?), navedu tě přesně.


spusť ten web server sám


python3 -m http.server 8000 --bind 0.0.0.0

Serve /home/tonda over HTTP on port 8000


Command running in background with ID: bouguomya. Output is being written to: /tmp/claude-1000/-home-tonda/f4c9c9f7-23b2-4e1a-860a-276c7bb05d77/tasks/bouguomya.output. You will be notified when it completes. To check interim output, use Read on that file path.


<total_tokens>14999697 tokens left</total_tokens>


sleep 1; curl -sS -o /dev/null -w "hra.html -> HTTP %{http_code}\n" http://localhost:8000/hra.html

Verify server responds


hra.html -> HTTP 200


<total_tokens>14999413 tokens left</total_tokens>


Web server běží. Otevři si na svém počítači v prohlížeči:

```
http://192.168.5.32:8000/hra.html
```

Poznámky:
- Musíš být na stejné síti (192.168.5.x). Pokud stránka nenaběhne, může to blokovat firewall na serveru — zkus `! sudo ufw allow 8000` nebo mi napiš, jak se připojuješ.
- Server běží na pozadí v téhle session. Zastavíš ho, když mi řekneš (nebo `! pkill -f "http.server 8000"`).
- Když se session ukončí, server spadne taky — pro trvalý běh použij ten `nohup … & disown` z předchozí zprávy.

Dej vědět, jestli hra naběhla a jak se hraje.


Goal: build and run a simple 2D platformer for you. The game is done in /home/tonda/hra.html and an HTTP server is running. Next: open http://192.168.5.32:8000/hra.html in your browser and confirm it loads. (disable recaps in /config)


ano jede to vše vpořádku můžu to s tebou upravit


Jasně, pojďme to ladit. Soubor můžu upravovat rovnou — ty pak jen dáš v prohlížeči **refresh** (F5) a změna je tam, server běží dál.

Řekni, co chceš změnit. Napadá mě spousta směrů:

- **Obtížnost / pocit z hraní** — síla a výška skoku, rychlost běhu, gravitace, setrvačnost
- **Levely** — přidat/upravit, jiné rozložení propastí, víc nepřátel, delší
- **Nekonečný režim** — rychlost zrychlování, četnost propastí a nepřátel
- **Vizuál** — barvy, velikost hráče, pozadí, efekty (částice u mincí, lepší animace)
- **Nový obsah** — pohyblivé plošiny, checkpointy, více typů nepřátel, dvojskok jako power-up, časomíra
- **Zvuk / hudba** — jednoduchá podkresová melodie

Co tě štve nebo co bys chtěl přidat jako první?


v nekonečném módu když mě zeď ze zadu dožene tak mě zabije


Chápu — teď tě levý okraj obrazovky zabije „potichu", není tam nic vidět, takže to působí nefér. Jak to chceš mít?


AskUserQuestion

Question: Co s dobíhajícím okrajem v nekonečném módu?

Choices:
Přidám vlevo vizuální „zeď smrti“, která se blíží. Dotek = ztráta života (ne okamžitý konec), obrazovka zabličká když je blízko.
Zeď tě jen tlačí dopředu (nemůžeš za ni). Život ztratíš jen když spadneš do propasti.
Žádné dobíhání. Hraješ vlastním tempem, skóre je za vzdálenost + mince. Zrychlování pryč.
Zůstane insta-kill, ale zeď pojede pomaleji a zrychluje mírněji — víc času reagovat.


Your questions have been answered: "Co s dobíhajícím okrajem v nekonečném módu?"="Vidět zeď + varování". You can now continue with these answers in mind.

Question: Co s dobíhajícím okrajem v nekonečném módu?

Choices:
Přidám vlevo vizuální „zeď smrti“, která se blíží. Dotek = ztráta života (ne okamžitý konec), obrazovka zabličká když je blízko.
Zeď tě jen tlačí dopředu (nemůžeš za ni). Život ztratíš jen když spadneš do propasti.
Žádné dobíhání. Hraješ vlastním tempem, skóre je za vzdálenost + mince. Zrychlování pryč.
Zůstane insta-kill, ale zeď pojede pomaleji a zrychluje mírněji — víc času reagovat.


<total_tokens>14998816 tokens left</total_tokens>


300	/* ------------------------------------------------------------------ */
301	const player = {
302	  x: 0, y: 0, w: 26, h: 32,
303	  vx: 0, vy: 0, onGround: false,
304	  coyote: 0, jumpBuf: 0, face: 1, invuln: 0,
305	};
306
307	function updatePlayer(){
308	  if (player.invuln > 0) player.invuln--;
309
310	  const left  = down('ArrowLeft', 'KeyA');
311	  const right = down('ArrowRight', 'KeyD');
312	  const accel = 0.9, fric = 0.80, maxSpd = 4.4;
313
314	  if (left && !right){ player.vx -= accel; player.face = -1; }
315	  else if (right && !left){ player.vx += accel; player.face = 1; }
316	  else player.vx *= fric;
317	  player.vx = clamp(player.vx, -maxSpd, maxSpd);
318
319	  if (hit('ArrowUp', 'KeyW', 'Space')) player.jumpBuf = 9;
320	  if (player.jumpBuf > 0) player.jumpBuf--;
321	  if (player.coyote > 0) player.coyote--;
322
323	  if (player.jumpBuf > 0 && player.coyote > 0){
324	    player.vy = -13.5;
325	    player.jumpBuf = player.coyote = 0;
326	    player.onGround = false;
327	    S.jump();
328	  }
329	  // nižší skok při krátkém stisku
330	  if (!down('ArrowUp', 'KeyW', 'Space') && player.vy < -4) player.vy = -4;
331
332	  player.vy = Math.min(player.vy + GRAV, MAX_FALL);
333
334	  player.onGround = false;
335	  player.x += player.vx; resolve('x');
336	  player.y += player.vy; resolve('y');
337
338	  if (player.onGround) player.coyote = 7;
339
340	  // hranice světa
341	  if (world.width) player.x = clamp(player.x, 0, world.width*TILE - player.w);
342	  else if (player.x < camX - 2) player.x = camX - 2;
343	}
344
345	function resolve(axis){
346	  const c1 = Math.floor(player.x / TILE);
347	  const c2 = Math.floor((player.x + player.w - 1) / TILE);
348	  const r1 = Math.floor(player.y / TILE);
349	  const r2 = Math.floor((player.y + player.h - 1) / TILE);
350
351	  for (let r = r1; r <= r2; r++){
352	    for (let c = c1; c <= c2; c++){
353	      if (!world.isSolid(c, r)) continue;
354	      if (axis === 'x'){
355	        if (player.vx > 0)      player.x = c*TILE - player.w;
356	        else if (player.vx < 0) player.x = c*TILE + TILE;
357	        player.vx = 0;
358	      } else {
359	        if (player.vy > 0){ player.y = r*TILE - player.h; player.onGround = true; }
360	        else if (player.vy < 0) player.y = r*TILE + TILE;
361	        player.vy = 0;
362	      }
363	    }
364	  }
365	}
366
367	/* ------------------------------------------------------------------ */
368	/*  Nepřátelé                                                         */
369	/* ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ */
const player = {
  x: 0, y: 0, w: 26, h: 32,
  vx: 0, vy: 0, onGround: false,
  coyote: 0, jumpBuf: 0, face: 1, invuln: 0,
};

function updatePlayer(){
  if (player.invuln > 0) player.invuln--;

  const left  = down('ArrowLeft', 'KeyA');
  const right = down('ArrowRight', 'KeyD');
  const accel = 0.9, fric = 0.80, maxSpd = 4.4;

  if (left && !right){ player.vx -= accel; player.face = -1; }
  else if (right && !left){ player.vx += accel; player.face = 1; }
  else player.vx *= fric;
  player.vx = clamp(player.vx, -maxSpd, maxSpd);

  if (hit('ArrowUp', 'KeyW', 'Space')) player.jumpBuf = 9;
  if (player.jumpBuf > 0) player.jumpBuf--;
  if (player.coyote > 0) player.coyote--;

  if (player.jumpBuf > 0 && player.coyote > 0){
    player.vy = -13.5;
    player.jumpBuf = player.coyote = 0;
    player.onGround = false;
    S.jump();
  }
  // nižší skok při krátkém stisku
  if (!down('ArrowUp', 'KeyW', 'Space') && player.vy < -4) player.vy = -4;

  player.vy = Math.min(player.vy + GRAV, MAX_FALL);

  player.onGround = false;
  player.x += player.vx; resolve('x');
  player.y += player.vy; resolve('y');

  if (player.onGround) player.coyote = 7;

  // hranice světa
  if (world.width) player.x = clamp(player.x, 0, world.width*TILE - player.w);
  else if (player.x < camX - 2) player.x = camX - 2;
}

function resolve(axis){
  const c1 = Math.floor(player.x / TILE);
  const c2 = Math.floor((player.x + player.w - 1) / TILE);
  const r1 = Math.floor(player.y / TILE);
  const r2 = Math.floor((player.y + player.h - 1) / TILE);

  for (let r = r1; r <= r2; r++){
    for (let c = c1; c <= c2; c++){
      if (!world.isSolid(c, r)) continue;
      if (axis === 'x'){
        if (player.vx > 0)      player.x = c*TILE - player.w;
        else if (player.vx < 0) player.x = c*TILE + TILE;
        player.vx = 0;
      } else {
        if (player.vy > 0){ player.y = r*TILE - player.h; player.onGround = true; }
        else if (player.vy < 0) player.y = r*TILE + TILE;
        player.vy = 0;
      }
    }
  }
}

/* ------------------------------------------------------------------ */
/*  Nepřátelé                                                         */
/* ------------------------------------------------------------------ */


<total_tokens>14991308 tokens left</total_tokens>


420	    else { state = 'levelclear'; clearT = 100; }
421	  }
422	  // pád mimo svět
423	  if (player.y > (ROWS + 1) * TILE) hurtPlayer();
424	}
425
426	function hurtPlayer(){
427	  if (player.invuln > 0) return;
428	  lives--;
429	  shake = 9;
430	  S.hurt();
431	  if (lives <= 0){ state = 'over'; return; }
432	  player.invuln = 100;
433	  respawn();
434	}
435
436	function respawn(){
437	  player.vx = player.vy = 0;
438	  if (mode === 'levels'){
439	    player.x = world.playerStart.x;
440	    player.y = world.playerStart.y;
441	    camX = clamp(player.x - W*0.42, 0, world.width*TILE - W);
442	  } else {
443	    const col = Math.floor((camX + W*0.35) / TILE);
444	    let g = world.firstSolidRow(col, 3);
445	    if (g >= ROWS){
446	      for (let c = col - 2; c < col + 4; c++) for (let r = 12; r < ROWS; r++) world.setSolid(c, r);
447	      g = 12;
448	    }
449	    player.x = col*TILE;
450	    player.y = g*TILE - player.h - 2;
451	    camX = Math.max(0, camX - W*0.15);
452	    world.enemies = world.enemies.filter(e => Math.abs(e.x - player.x) > 130);
453	  }
454	}
455
456	/* ------------------------------------------------------------------ */
457	/*  Kamera                                                            */
458	/* ------------------------------------------------------------------ */
459	function updateCamera(){
460	  if (mode === 'endless'){
461	    if (!endlessStarted){
462	      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
463	      camX = Math.max(0, player.x + player.w/2 - W*0.42);
464	      generateAhead(camX);
465	      return;
466	    }
467	    scrollSpeed = 1.4 + Math.min(distance / 260, 3.4);
468	    camX += scrollSpeed;
469	    const follow = player.x + player.w/2 - W*0.42;
470	    if (follow > camX) camX = follow;
471	    distance = Math.floor(camX / TILE);
472	    generateAhead(camX);
473	    pruneWorld();
474	    world.enemies = world.enemies.filter(e => e.x > camX - 300);
475	    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);
476	    if (player.x + player.w < camX + 4) hurtPlayer();
477	    return;
478	  }
479	  const target = player.x + player.w/2 - W*0.42;
480	  camX += (target - camX) * 0.14;
481	  camX = clamp(camX, 0, world.width*TILE - W);
482	}
483
484	/* ------------------------------------------------------------------ */
485	/*  Stavy hry                                                         */
486	/* ------------------------------------------------------------------ */
487	let state = 'menu';      // menu | play | paused | levelclear | over | win
488	let mode  = 'levels';    // levels | endless
489	let menuSel = 0;
490	let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
491	let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
492	let clearT = 0, shake = 0;
493
494	function startGame(m){
495	  unlockAudio();
496	  mode = m;
497	  lives = 3; coinsCollected = 0; score = 0; distance = 0; shake = 0;
498	  if (m === 'levels'){ levelIndex = 0; loadLevel(0); }
499	  else loadEndless();
500	  state = 'play';
501	}
502
503	function loadLevel(i){
504	  world = buildLevel(LEVELS[i]);
505	  player.x = world.playerStart.x;
506	  player.y = world.playerStart.y;
507	  player.vx = player.vy = 0; player.invuln = 40;
508	  camX = clamp(player.x - W*0.42, 0, world.width*TILE - W);
509	  camY = 0;
510	}
511
512	function update(){
513	  if (state === 'menu'){
514	    if (hit('ArrowUp', 'ArrowDown', 'KeyW', 'KeyS')) menuSel = 1 - menuSel;
515	    if (hit('Digit1')) return startGame('levels');
516	    if (hit('Digit2')) return startGame('endless');
517	    if (hit('Enter', 'Space')) return startGame(menuSel === 0 ? 'levels' : 'endless');
518	    return;
519	  }
520	  if (state === 'over' || state === 'win'){
521	    if (hit('Enter', 'Space', 'Escape')) state = 'menu';
522	    return;
523	  }
524	  if (state === 'paused'){
525	    if (hit('KeyP', 'Enter')) state = 'play';
526	    if (hit('Escape')) state = 'menu';
527	    return;
528	  }
529	  if (state === 'levelclear'){
530	    if (--clearT <= 0){ loadLevel(levelIndex); state = 'play'; }
531	    return;
532	  }
533
534	  // --- state === 'play' ---
535	  if (hit('Escape')) { state = 'menu'; return; }
536	  if (hit('KeyP'))   { state = 'paused'; return; }
537	  if (hit('KeyR'))   { mode === 'levels' ? loadLevel(levelIndex) : loadEndless(); return; }
538
539	  if (shake > 0) shake *= 0.88;
540
541	  updatePlayer();
542	  for (const e of world.enemies) updateEnemy(e);
543	  handleInteractions();
544	  if (state === 'play') updateCamera();
545	}
546
547	/* ------------------------------------------------------------------ */
548	/*  Vykreslení                                                        */
549	/* ------------------------------------------------------------------ */

    else { state = 'levelclear'; clearT = 100; }
  }
  // pád mimo svět
  if (player.y > (ROWS + 1) * TILE) hurtPlayer();
}

function hurtPlayer(){
  if (player.invuln > 0) return;
  lives--;
  shake = 9;
  S.hurt();
  if (lives <= 0){ state = 'over'; return; }
  player.invuln = 100;
  respawn();
}

function respawn(){
  player.vx = player.vy = 0;
  if (mode === 'levels'){
    player.x = world.playerStart.x;
    player.y = world.playerStart.y;
    camX = clamp(player.x - W*0.42, 0, world.width*TILE - W);
  } else {
    const col = Math.floor((camX + W*0.35) / TILE);
    let g = world.firstSolidRow(col, 3);
    if (g >= ROWS){
      for (let c = col - 2; c < col + 4; c++) for (let r = 12; r < ROWS; r++) world.setSolid(c, r);
      g = 12;
    }
    player.x = col*TILE;
    player.y = g*TILE - player.h - 2;
    camX = Math.max(0, camX - W*0.15);
    world.enemies = world.enemies.filter(e => Math.abs(e.x - player.x) > 130);
  }
}

/* ------------------------------------------------------------------ */
/*  Kamera                                                            */
/* ------------------------------------------------------------------ */
function updateCamera(){
  if (mode === 'endless'){
    if (!endlessStarted){
      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
      camX = Math.max(0, player.x + player.w/2 - W*0.42);
      generateAhead(camX);
      return;
    }
    scrollSpeed = 1.4 + Math.min(distance / 260, 3.4);
    camX += scrollSpeed;
    const follow = player.x + player.w/2 - W*0.42;
    if (follow > camX) camX = follow;
    distance = Math.floor(camX / TILE);
    generateAhead(camX);
    pruneWorld();
    world.enemies = world.enemies.filter(e => e.x > camX - 300);
    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);
    if (player.x + player.w < camX + 4) hurtPlayer();
    return;
  }
  const target = player.x + player.w/2 - W*0.42;
  camX += (target - camX) * 0.14;
  camX = clamp(camX, 0, world.width*TILE - W);
}

/* ------------------------------------------------------------------ */
/*  Stavy hry                                                         */
/* ------------------------------------------------------------------ */
let state = 'menu';      // menu | play | paused | levelclear | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let clearT = 0, shake = 0;

function startGame(m){
  unlockAudio();
  mode = m;
  lives = 3; coinsCollected = 0; score = 0; distance = 0; shake = 0;
  if (m === 'levels'){ levelIndex = 0; loadLevel(0); }
  else loadEndless();
  state = 'play';
}

function loadLevel(i){
  world = buildLevel(LEVELS[i]);
  player.x = world.playerStart.x;
  player.y = world.playerStart.y;
  player.vx = player.vy = 0; player.invuln = 40;
  camX = clamp(player.x - W*0.42, 0, world.width*TILE - W);
  camY = 0;
}

function update(){
  if (state === 'menu'){
    if (hit('ArrowUp', 'ArrowDown', 'KeyW', 'KeyS')) menuSel = 1 - menuSel;
    if (hit('Digit1')) return startGame('levels');
    if (hit('Digit2')) return startGame('endless');
    if (hit('Enter', 'Space')) return startGame(menuSel === 0 ? 'levels' : 'endless');
    return;
  }
  if (state === 'over' || state === 'win'){
    if (hit('Enter', 'Space', 'Escape')) state = 'menu';
    return;
  }
  if (state === 'paused'){
    if (hit('KeyP', 'Enter')) state = 'play';
    if (hit('Escape')) state = 'menu';
    return;
  }
  if (state === 'levelclear'){
    if (--clearT <= 0){ loadLevel(levelIndex); state = 'play'; }
    return;
  }

  // --- state === 'play' ---
  if (hit('Escape')) { state = 'menu'; return; }
  if (hit('KeyP'))   { state = 'paused'; return; }
  if (hit('KeyR'))   { mode === 'levels' ? loadLevel(levelIndex) : loadEndless(); return; }

  if (shake > 0) shake *= 0.88;

  updatePlayer();
  for (const e of world.enemies) updateEnemy(e);
  handleInteractions();
  if (state === 'play') updateCamera();
}

/* ------------------------------------------------------------------ */
/*  Vykreslení                                                        */
/* ------------------------------------------------------------------ */


<total_tokens>14989947 tokens left</total_tokens>


549	/* ------------------------------------------------------------------ */
550	const COL = {
551	  tile:'#2a3350', tileTop:'#41527f', player:'#5cc8ff', enemy:'#ff5c72',
552	  coin:'#ffd45c', flag:'#4ade80', dark:'#0c0e16',
553	};
554
555	function draw(){
556	  ctx.clearRect(0, 0, W, H);
557
558	  const g = ctx.createLinearGradient(0, 0, 0, H);
559	  g.addColorStop(0, '#141829'); g.addColorStop(1, '#0b0d15');
560	  ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);
561
562	  drawParallax();
563
564	  const sx = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;
565	  const sy = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;
566
567	  ctx.save();
568	  ctx.translate(Math.round(-camX + sx), Math.round(-camY + sy));
569
570	  if (world){
571	    const c0 = Math.floor(camX / TILE) - 1;
572	    const c1 = c0 + Math.ceil(W / TILE) + 2;
573	    for (let c = c0; c <= c1; c++)
574	      for (let r = 0; r < ROWS; r++)
575	        if (world.isSolid(c, r)) drawTile(c, r);
576
577	    for (const co of world.coins) if (!co.got) drawCoin(co);
578	    if (world.goal) drawGoal(world.goal);
579	    for (const e of world.enemies) drawEnemy(e);
580	    if (state !== 'levelclear') drawPlayer();
581	  }
582	  ctx.restore();
583
584	  if (world) drawHUD();
585
586	  if (state === 'menu')            drawMenu();
587	  else if (state === 'paused')     overlay('PAUZA', 'P nebo Enter – pokračovat   ·   Esc – menu');
588	  else if (state === 'levelclear') overlay('LEVEL ' + levelIndex + ' HOTOV', 'Sbíráš dál… mincí: ' + coinsCollected);
589	  else if (state === 'over')       overlay('KONEC HRY', statLine() + '   ·   Enter – menu');
590	  else if (state === 'win')        overlay('DOKONČENO! 🎉', statLine() + '   ·   Enter – menu');
591	}
592
593	function drawParallax(){
594	  ctx.save();
595	  ctx.globalAlpha = 0.05;
596	  ctx.fillStyle = '#6ea8ff';
597	  const p = camX * 0.25;
598	  for (let i = -1; i < 8; i++){
599	    const x = i * 250 - (p % 250);
600	    ctx.save();
601	    ctx.translate(x, 130 + (i & 1 ? 150 : 0));
602	    ctx.rotate(0.5);
603	    ctx.fillRect(-55, -55, 110, 110);
604	    ctx.restore();
605	  }
606	  ctx.restore();
607	}
608
609	function drawTile(c, r){
610	  const x = c*TILE, y = r*TILE;
611	  const open = !world.isSolid(c, r - 1);
612	  ctx.fillStyle = COL.tile;
613	  roundRect(x + 1, y + 1, TILE - 2, TILE - 2, open ? 7 : 2);
614	  ctx.fill();
615	  if (open){
616	    ctx.fillStyle = COL.tileTop;
617	    roundRect(x + 1, y + 1, TILE - 2, 6, 3);
618	    ctx.fill();
619	  }
620	}
621
622	function drawPlayer(){
623	  if (player.invuln > 0 && Math.floor(player.invuln / 4) % 2) return;
624	  const { x, y, w, h } = player;
625	  ctx.fillStyle = COL.player;
626	  roundRect(x, y, w, h, 7); ctx.fill();
627	  ctx.fillStyle = COL.dark;
628	  ctx.fillRect(x + w/2 + player.face * 5 - 2, y + 9, 4, 7);

/* ------------------------------------------------------------------ */
const COL = {
  tile:'#2a3350', tileTop:'#41527f', player:'#5cc8ff', enemy:'#ff5c72',
  coin:'#ffd45c', flag:'#4ade80', dark:'#0c0e16',
};

function draw(){
  ctx.clearRect(0, 0, W, H);

  const g = ctx.createLinearGradient(0, 0, 0, H);
  g.addColorStop(0, '#141829'); g.addColorStop(1, '#0b0d15');
  ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);

  drawParallax();

  const sx = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;
  const sy = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;

  ctx.save();
  ctx.translate(Math.round(-camX + sx), Math.round(-camY + sy));

  if (world){
    const c0 = Math.floor(camX / TILE) - 1;
    const c1 = c0 + Math.ceil(W / TILE) + 2;
    for (let c = c0; c <= c1; c++)
      for (let r = 0; r < ROWS; r++)
        if (world.isSolid(c, r)) drawTile(c, r);

    for (const co of world.coins) if (!co.got) drawCoin(co);
    if (world.goal) drawGoal(world.goal);
    for (const e of world.enemies) drawEnemy(e);
    if (state !== 'levelclear') drawPlayer();
  }
  ctx.restore();

  if (world) drawHUD();

  if (state === 'menu')            drawMenu();
  else if (state === 'paused')     overlay('PAUZA', 'P nebo Enter – pokračovat   ·   Esc – menu');
  else if (state === 'levelclear') overlay('LEVEL ' + levelIndex + ' HOTOV', 'Sbíráš dál… mincí: ' + coinsCollected);
  else if (state === 'over')       overlay('KONEC HRY', statLine() + '   ·   Enter – menu');
  else if (state === 'win')        overlay('DOKONČENO! 🎉', statLine() + '   ·   Enter – menu');
}

function drawParallax(){
  ctx.save();
  ctx.globalAlpha = 0.05;
  ctx.fillStyle = '#6ea8ff';
  const p = camX * 0.25;
  for (let i = -1; i < 8; i++){
    const x = i * 250 - (p % 250);
    ctx.save();
    ctx.translate(x, 130 + (i & 1 ? 150 : 0));
    ctx.rotate(0.5);
    ctx.fillRect(-55, -55, 110, 110);
    ctx.restore();
  }
  ctx.restore();
}

function drawTile(c, r){
  const x = c*TILE, y = r*TILE;
  const open = !world.isSolid(c, r - 1);
  ctx.fillStyle = COL.tile;
  roundRect(x + 1, y + 1, TILE - 2, TILE - 2, open ? 7 : 2);
  ctx.fill();
  if (open){
    ctx.fillStyle = COL.tileTop;
    roundRect(x + 1, y + 1, TILE - 2, 6, 3);
    ctx.fill();
  }
}

function drawPlayer(){
  if (player.invuln > 0 && Math.floor(player.invuln / 4) % 2) return;
  const { x, y, w, h } = player;
  ctx.fillStyle = COL.player;
  roundRect(x, y, w, h, 7); ctx.fill();
  ctx.fillStyle = COL.dark;
  ctx.fillRect(x + w/2 + player.face * 5 - 2, y + 9, 4, 7);


<total_tokens>14987608 tokens left</total_tokens>


Teď to upravím — přidám viditelnou „zeď smrti", dotek = ztráta života (ne konec), a červené blikání když je blízko.


Edit

let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let clearT = 0, shake = 0;

let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu
let clearT = 0, shake = 0;


The file /home/tonda/hra.html has been updated successfully. (file state is current in your context — no need to Read it back)

let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let clearT = 0, shake = 0;

let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu
let clearT = 0, shake = 0;

let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;

+let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu


<total_tokens>14985706 tokens left</total_tokens>


  // hranice světa
  if (world.width) player.x = clamp(player.x, 0, world.width*TILE - player.w);
  else if (player.x < camX - 2) player.x = camX - 2;
}

  // hranice světa (v nekonečném módu vlevo nic – tam si tě hlídá zeď)
  if (world.width) player.x = clamp(player.x, 0, world.width*TILE - player.w);
}


  // hranice světa
  if (world.width) player.x = clamp(player.x, 0, world.width*TILE - player.w);
  else if (player.x < camX - 2) player.x = camX - 2;
}

  // hranice světa (v nekonečném módu vlevo nic – tam si tě hlídá zeď)
  if (world.width) player.x = clamp(player.x, 0, world.width*TILE - player.w);
}

+  // hranice světa (v nekonečném módu vlevo nic – tam si tě hlídá zeď)

-  else if (player.x < camX - 2) player.x = camX - 2;


<total_tokens>14985354 tokens left</total_tokens>


    player.x = col*TILE;
    player.y = g*TILE - player.h - 2;
    camX = Math.max(0, camX - W*0.15);
    world.enemies = world.enemies.filter(e => Math.abs(e.x - player.x) > 130);

    player.x = col*TILE;
    player.y = g*TILE - player.h - 2;
    camX = Math.max(0, player.x - W*0.4);
    wallX = player.x - W*0.75;                     // odstrč zeď daleko dozadu
    world.enemies = world.enemies.filter(e => Math.abs(e.x - player.x) > 130);


    player.x = col*TILE;
    player.y = g*TILE - player.h - 2;
    camX = Math.max(0, camX - W*0.15);
    world.enemies = world.enemies.filter(e => Math.abs(e.x - player.x) > 130);

    player.x = col*TILE;
    player.y = g*TILE - player.h - 2;
    camX = Math.max(0, player.x - W*0.4);
    wallX = player.x - W*0.75;                     // odstrč zeď daleko dozadu
    world.enemies = world.enemies.filter(e => Math.abs(e.x - player.x) > 130);

+    camX = Math.max(0, player.x - W*0.4);

+    wallX = player.x - W*0.75;                     // odstrč zeď daleko dozadu


<total_tokens>14984910 tokens left</total_tokens>


    if (!endlessStarted){
      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
      camX = Math.max(0, player.x + player.w/2 - W*0.42);
      generateAhead(camX);
      return;
    }
    scrollSpeed = 1.4 + Math.min(distance / 260, 3.4);
    camX += scrollSpeed;
    const follow = player.x + player.w/2 - W*0.42;
    if (follow > camX) camX = follow;
    distance = Math.floor(camX / TILE);
    generateAhead(camX);
    pruneWorld();
    world.enemies = world.enemies.filter(e => e.x > camX - 300);
    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);
    if (player.x + player.w < camX + 4) hurtPlayer();
    return;

    if (!endlessStarted){
      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
      camX = Math.max(0, player.x + player.w/2 - W*0.42);
      wallX = camX - TILE*9;
      generateAhead(camX);
      return;
    }
    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje
    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);
    wallX += wallSpeed;

    const follow = player.x + player.w/2 - W*0.42;
    camX += (follow - camX) * 0.16;
    if (camX < wallX - 30) camX = wallX - 30;      // zeď tlačí kameru
    if (camX < 0) camX = 0;
    if (camX - wallX > W*1.5) wallX = camX - W*1.5; // zeď nezůstane moc pozadu

    distance = Math.floor(camX / TILE);
    generateAhead(camX);
    pruneWorld();
    world.enemies = world.enemies.filter(e => e.x > camX - 300);
    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);

    const wgap = player.x - wallX;
    if (wgap <= 0 && player.invuln <= 0) hurtPlayer();
    else if (wgap < 150 && pruneT % 15 === 0) beep(150, 0.05, 'square', 0.03);
    return;


    if (!endlessStarted){
      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
      camX = Math.max(0, player.x + player.w/2 - W*0.42);
      generateAhead(camX);
      return;
    }
    scrollSpeed = 1.4 + Math.min(distance / 260, 3.4);
    camX += scrollSpeed;
    const follow = player.x + player.w/2 - W*0.42;
    if (follow > camX) camX = follow;
    distance = Math.floor(camX / TILE);
    generateAhead(camX);
    pruneWorld();
    world.enemies = world.enemies.filter(e => e.x > camX - 300);
    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);
    if (player.x + player.w < camX + 4) hurtPlayer();
    return;

    if (!endlessStarted){
      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
      camX = Math.max(0, player.x + player.w/2 - W*0.42);
      wallX = camX - TILE*9;
      generateAhead(camX);
      return;
    }
    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje
    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);
    wallX += wallSpeed;

    const follow = player.x + player.w/2 - W*0.42;
    camX += (follow - camX) * 0.16;
    if (camX < wallX - 30) camX = wallX - 30;      // zeď tlačí kameru
    if (camX < 0) camX = 0;
    if (camX - wallX > W*1.5) wallX = camX - W*1.5; // zeď nezůstane moc pozadu

    distance = Math.floor(camX / TILE);
    generateAhead(camX);
    pruneWorld();
    world.enemies = world.enemies.filter(e => e.x > camX - 300);
    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);

    const wgap = player.x - wallX;
    if (wgap <= 0 && player.invuln <= 0) hurtPlayer();
    else if (wgap < 150 && pruneT % 15 === 0) beep(150, 0.05, 'square', 0.03);
    return;

-    scrollSpeed = 1.4 + Math.min(distance / 260, 3.4);

+    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje

+    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);

+    if (camX < wallX - 30) camX = wallX - 30;      // zeď tlačí kameru

+    if (camX - wallX > W*1.5) wallX = camX - W*1.5; // zeď nezůstane moc pozadu

-    if (player.x + player.w < camX + 4) hurtPlayer();

+    if (wgap <= 0 && player.invuln <= 0) hurtPlayer();

const target = player.x + player.w/2 - W*0.42;


<total_tokens>14983865 tokens left</total_tokens>


196	  for (const [c,r,len] of (d.platforms || []))
197	    for (let i = 0; i < len; i++) w.setSolid(c + i, r);
198
199	  for (const [c,r] of (d.coins || []))
200	    w.coins.push({ x: c*TILE + TILE/2, y: r*TILE + TILE/2, got: false });
201
202	  for (const [c,r] of (d.enemies || [])){
203	    const g = w.firstSolidRow(c, r);              // spadni na nejbližší zem
204	    if (g >= ROWS) continue;
205	    w.enemies.push(mkEnemy(c*TILE + 4, g*TILE - 24));
206	  }
207
208	  const ps = w.firstSolidRow(d.playerStart[0], d.playerStart[1]);
209	  w.playerStart = { x: d.playerStart[0]*TILE, y: ps*TILE - player.h };
210
211	  const gg = w.firstSolidRow(d.goal[0], d.goal[1]);

  for (const [c,r,len] of (d.platforms || []))
    for (let i = 0; i < len; i++) w.setSolid(c + i, r);

  for (const [c,r] of (d.coins || []))
    w.coins.push({ x: c*TILE + TILE/2, y: r*TILE + TILE/2, got: false });

  for (const [c,r] of (d.enemies || [])){
    const g = w.firstSolidRow(c, r);              // spadni na nejbližší zem
    if (g >= ROWS) continue;
    w.enemies.push(mkEnemy(c*TILE + 4, g*TILE - 24));
  }

  const ps = w.firstSolidRow(d.playerStart[0], d.playerStart[1]);
  w.playerStart = { x: d.playerStart[0]*TILE, y: ps*TILE - player.h };

  const gg = w.firstSolidRow(d.goal[0], d.goal[1]);


<total_tokens>14983657 tokens left</total_tokens>


218	/* ------------------------------------------------------------------ */
219	let genCol = 0, genRow = 12;
220
221	function loadEndless(){
222	  world = new World();
223	  world.width = 0;
224	  genCol = 0; genRow = 12;
225	  generateAhead(0);
226	  player.x = 3*TILE;
227	  player.y = 11*TILE - player.h;
228	  player.vx = player.vy = 0; player.invuln = 40;
229	  camX = 0; camY = 0;
230	  scrollSpeed = 1.4;
231	  endlessStarted = false;
232	}
233
234	function generateAhead(cx){
235	  const need = Math.floor((cx + W) / TILE) + 22;

/* ------------------------------------------------------------------ */
let genCol = 0, genRow = 12;

function loadEndless(){
  world = new World();
  world.width = 0;
  genCol = 0; genRow = 12;
  generateAhead(0);
  player.x = 3*TILE;
  player.y = 11*TILE - player.h;
  player.vx = player.vy = 0; player.invuln = 40;
  camX = 0; camY = 0;
  scrollSpeed = 1.4;
  endlessStarted = false;
}

function generateAhead(cx){
  const need = Math.floor((cx + W) / TILE) + 22;


<total_tokens>14983151 tokens left</total_tokens>


  camX = 0; camY = 0;
  scrollSpeed = 1.4;
  endlessStarted = false;
}

  camX = 0; camY = 0;
  scrollSpeed = 1.4;
  wallSpeed = 1.3;
  wallX = -TILE*12;
  endlessStarted = false;
}


  camX = 0; camY = 0;
  scrollSpeed = 1.4;
  endlessStarted = false;
}

  camX = 0; camY = 0;
  scrollSpeed = 1.4;
  wallSpeed = 1.3;
  wallX = -TILE*12;
  endlessStarted = false;
}

player.vx = player.vy = 0; player.invuln = 40;


<total_tokens>14982617 tokens left</total_tokens>


    for (const e of world.enemies) drawEnemy(e);
    if (state !== 'levelclear') drawPlayer();
  }
  ctx.restore();

  if (world) drawHUD();

    for (const e of world.enemies) drawEnemy(e);
    if (state !== 'levelclear') drawPlayer();
    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();
  }
  ctx.restore();

  // varovné blikání, když je zeď blízko
  if (mode === 'endless' && state === 'play' && endlessStarted){
    const gap = player.x - wallX;
    if (gap < 240){
      const a = clamp(1 - gap / 240, 0, 1);
      const pulse = 0.45 + 0.3 * Math.sin(performance.now() / 90);
      const grd = ctx.createLinearGradient(0, 0, W * 0.55, 0);
      grd.addColorStop(0, 'rgba(255,45,65,' + (a * pulse).toFixed(3) + ')');
      grd.addColorStop(1, 'rgba(255,45,65,0)');
      ctx.fillStyle = grd;
      ctx.fillRect(0, 0, W * 0.55, H);
    }
  }

  if (world) drawHUD();


    for (const e of world.enemies) drawEnemy(e);
    if (state !== 'levelclear') drawPlayer();
  }
  ctx.restore();

  if (world) drawHUD();

    for (const e of world.enemies) drawEnemy(e);
    if (state !== 'levelclear') drawPlayer();
    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();
  }
  ctx.restore();

  // varovné blikání, když je zeď blízko
  if (mode === 'endless' && state === 'play' && endlessStarted){
    const gap = player.x - wallX;
    if (gap < 240){
      const a = clamp(1 - gap / 240, 0, 1);
      const pulse = 0.45 + 0.3 * Math.sin(performance.now() / 90);
      const grd = ctx.createLinearGradient(0, 0, W * 0.55, 0);
      grd.addColorStop(0, 'rgba(255,45,65,' + (a * pulse).toFixed(3) + ')');
      grd.addColorStop(1, 'rgba(255,45,65,0)');
      ctx.fillStyle = grd;
      ctx.fillRect(0, 0, W * 0.55, H);
    }
  }

  if (world) drawHUD();

+    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();

+  // varovné blikání, když je zeď blízko

+  if (mode === 'endless' && state === 'play' && endlessStarted){

+      const pulse = 0.45 + 0.3 * Math.sin(performance.now() / 90);

+      const grd = ctx.createLinearGradient(0, 0, W * 0.55, 0);

+      grd.addColorStop(0, 'rgba(255,45,65,' + (a * pulse).toFixed(3) + ')');

if (state === 'menu')            drawMenu();


<total_tokens>14981921 tokens left</total_tokens>


function drawWall(){
  const wx = wallX, top = -160, h = (ROWS + 6) * TILE;
  ctx.fillStyle = '#6f0f1e';
  ctx.fillRect(wx - 2400, top, 2400, h);
  ctx.fillStyle = 'rgba(255,70,90,.9)';
  ctx.fillRect(wx - 5, top, 5, h);
  const t = performance.now() / 110;
  ctx.fillStyle = 'rgba(255,150,165,.5)';
  for (let y = top; y < top + h; y += 36){
    const o = (t * 10) % 36;
    ctx.beginPath();
    ctx.moveTo(wx - 22, y + o);
    ctx.lineTo(wx - 4,  y + o + 11);
    ctx.lineTo(wx - 22, y + o + 22);
    ctx.closePath();
    ctx.fill();
  }
}

function drawParallax(){


function drawWall(){
  const wx = wallX, top = -160, h = (ROWS + 6) * TILE;
  ctx.fillStyle = '#6f0f1e';
  ctx.fillRect(wx - 2400, top, 2400, h);
  ctx.fillStyle = 'rgba(255,70,90,.9)';
  ctx.fillRect(wx - 5, top, 5, h);
  const t = performance.now() / 110;
  ctx.fillStyle = 'rgba(255,150,165,.5)';
  for (let y = top; y < top + h; y += 36){
    const o = (t * 10) % 36;
    ctx.beginPath();
    ctx.moveTo(wx - 22, y + o);
    ctx.lineTo(wx - 4,  y + o + 11);
    ctx.lineTo(wx - 22, y + o + 22);
    ctx.closePath();
    ctx.fill();
  }
}

function drawParallax(){

else if (state === 'win')        overlay('DOKONČENO! 🎉', statLine() + '   ·   Enter – menu');

+  const wx = wallX, top = -160, h = (ROWS + 6) * TILE;

+  ctx.fillRect(wx - 2400, top, 2400, h);

+  for (let y = top; y < top + h; y += 36){


<total_tokens>14981375 tokens left</total_tokens>


570
571	  const g = ctx.createLinearGradient(0, 0, 0, H);
572	  g.addColorStop(0, '#141829'); g.addColorStop(1, '#0b0d15');
573	  ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);
574
575	  drawParallax();
576
577	  const sx = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;
578	  const sy = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;
579
580	  ctx.save();
581	  ctx.translate(Math.round(-camX + sx), Math.round(-camY + sy));
582
583	  if (world){
584	    const c0 = Math.floor(camX / TILE) - 1;
585	    const c1 = c0 + Math.ceil(W / TILE) + 2;
586	    for (let c = c0; c <= c1; c++)
587	      for (let r = 0; r < ROWS; r++)
588	        if (world.isSolid(c, r)) drawTile(c, r);
589
590	    for (const co of world.coins) if (!co.got) drawCoin(co);
591	    if (world.goal) drawGoal(world.goal);
592	    for (const e of world.enemies) drawEnemy(e);
593	    if (state !== 'levelclear') drawPlayer();
594	    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();
595	  }
596	  ctx.restore();
597
598	  // varovné blikání, když je zeď blízko
599	  if (mode === 'endless' && state === 'play' && endlessStarted){
600	    const gap = player.x - wallX;
601	    if (gap < 240){
602	      const a = clamp(1 - gap / 240, 0, 1);
603	      const pulse = 0.45 + 0.3 * Math.sin(performance.now() / 90);
604	      const grd = ctx.createLinearGradient(0, 0, W * 0.55, 0);
605	      grd.addColorStop(0, 'rgba(255,45,65,' + (a * pulse).toFixed(3) + ')');
606	      grd.addColorStop(1, 'rgba(255,45,65,0)');
607	      ctx.fillStyle = grd;
608	      ctx.fillRect(0, 0, W * 0.55, H);
609	    }
610	  }
611
612	  if (world) drawHUD();
613
614	  if (state === 'menu')            drawMenu();

  const g = ctx.createLinearGradient(0, 0, 0, H);
  g.addColorStop(0, '#141829'); g.addColorStop(1, '#0b0d15');
  ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);

  drawParallax();

  const sx = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;
  const sy = shake > 0.4 ? (Math.random() - 0.5) * shake : 0;

  ctx.save();
  ctx.translate(Math.round(-camX + sx), Math.round(-camY + sy));

  if (world){
    const c0 = Math.floor(camX / TILE) - 1;
    const c1 = c0 + Math.ceil(W / TILE) + 2;
    for (let c = c0; c <= c1; c++)
      for (let r = 0; r < ROWS; r++)
        if (world.isSolid(c, r)) drawTile(c, r);

    for (const co of world.coins) if (!co.got) drawCoin(co);
    if (world.goal) drawGoal(world.goal);
    for (const e of world.enemies) drawEnemy(e);
    if (state !== 'levelclear') drawPlayer();
    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();
  }
  ctx.restore();

  // varovné blikání, když je zeď blízko
  if (mode === 'endless' && state === 'play' && endlessStarted){
    const gap = player.x - wallX;
    if (gap < 240){
      const a = clamp(1 - gap / 240, 0, 1);
      const pulse = 0.45 + 0.3 * Math.sin(performance.now() / 90);
      const grd = ctx.createLinearGradient(0, 0, W * 0.55, 0);
      grd.addColorStop(0, 'rgba(255,45,65,' + (a * pulse).toFixed(3) + ')');
      grd.addColorStop(1, 'rgba(255,45,65,0)');
      ctx.fillStyle = grd;
      ctx.fillRect(0, 0, W * 0.55, H);
    }
  }

  if (world) drawHUD();

  if (state === 'menu')            drawMenu();


<total_tokens>14979531 tokens left</total_tokens>


461	function updateCamera(){
462	  if (mode === 'endless'){
463	    if (!endlessStarted){
464	      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
465	      camX = Math.max(0, player.x + player.w/2 - W*0.42);
466	      wallX = camX - TILE*9;
467	      generateAhead(camX);
468	      return;
469	    }
470	    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje
471	    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);
472	    wallX += wallSpeed;
473
474	    const follow = player.x + player.w/2 - W*0.42;
475	    camX += (follow - camX) * 0.16;
476	    if (camX < wallX - 30) camX = wallX - 30;      // zeď tlačí kameru
477	    if (camX < 0) camX = 0;
478	    if (camX - wallX > W*1.5) wallX = camX - W*1.5; // zeď nezůstane moc pozadu
479
480	    distance = Math.floor(camX / TILE);
481	    generateAhead(camX);
482	    pruneWorld();
483	    world.enemies = world.enemies.filter(e => e.x > camX - 300);
484	    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);
485
486	    const wgap = player.x - wallX;
487	    if (wgap <= 0 && player.invuln <= 0) hurtPlayer();
488	    else if (wgap < 150 && pruneT % 15 === 0) beep(150, 0.05, 'square', 0.03);
489	    return;
490	  }
491	  const target = player.x + player.w/2 - W*0.42;
492	  camX += (target - camX) * 0.14;
493	  camX = clamp(camX, 0, world.width*TILE - W);
494	}
495
496	/* ------------------------------------------------------------------ */
497	/*  Stavy hry                                                         */
498	/* ------------------------------------------------------------------ */
499	let state = 'menu';      // menu | play | paused | levelclear | over | win
500	let mode  = 'levels';    // levels | endless
501	let menuSel = 0;
502	let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
503	let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
504	let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu

function updateCamera(){
  if (mode === 'endless'){
    if (!endlessStarted){
      if (down('ArrowRight', 'KeyD') || player.x > 5*TILE) endlessStarted = true;
      camX = Math.max(0, player.x + player.w/2 - W*0.42);
      wallX = camX - TILE*9;
      generateAhead(camX);
      return;
    }
    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje
    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);
    wallX += wallSpeed;

    const follow = player.x + player.w/2 - W*0.42;
    camX += (follow - camX) * 0.16;
    if (camX < wallX - 30) camX = wallX - 30;      // zeď tlačí kameru
    if (camX < 0) camX = 0;
    if (camX - wallX > W*1.5) wallX = camX - W*1.5; // zeď nezůstane moc pozadu

    distance = Math.floor(camX / TILE);
    generateAhead(camX);
    pruneWorld();
    world.enemies = world.enemies.filter(e => e.x > camX - 300);
    world.coins   = world.coins.filter(c => !c.got && c.x > camX - 200);

    const wgap = player.x - wallX;
    if (wgap <= 0 && player.invuln <= 0) hurtPlayer();
    else if (wgap < 150 && pruneT % 15 === 0) beep(150, 0.05, 'square', 0.03);
    return;
  }
  const target = player.x + player.w/2 - W*0.42;
  camX += (target - camX) * 0.14;
  camX = clamp(camX, 0, world.width*TILE - W);
}

/* ------------------------------------------------------------------ */
/*  Stavy hry                                                         */
/* ------------------------------------------------------------------ */
let state = 'menu';      // menu | play | paused | levelclear | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu


<total_tokens>14978144 tokens left</total_tokens>


Hotovo — dej v prohlížeči **F5** a zkus nekonečný mód.

Co se změnilo:

- **Vidíš zeď** — tmavě rudá stěna s pohyblivými hroty, která se sune zleva a s ušlou vzdáleností zrychluje.
- **Dotek = ztráta 1 života**, ne okamžitý konec. Zeď se po zásahu odrazí skoro o celou obrazovku zpět a máš ~1,5 s nezranitelnosti.
- **Varování** — když je zeď blízko, levá půlka obrazovky červeně pulzuje; od cca 150 px navíc pípá.
- Když jí utečeš daleko dopředu, drží se max 1,5 obrazovky za tebou a hned zase hrozí, jakmile zpomalíš.

Ladicí hodnoty (kdyby byla moc/málo rychlá) jsou ve `updateCamera`:
- `wallSpeed = 1.3 + Math.min(distance / 280, 3.3)` — `1.3` = startovní rychlost, `280` = jak rychle zrychluje (větší = pomaleji), `3.3` = strop
- `wallX = player.x - W*0.75` v `respawn` — jak daleko se odrazí po zásahu


ještě uprav příběhové levely když dokončím level tak můžu si něco dokoupit za nazbírané mince které jsem sezbíral napřílad přidat jeden život 10mincí když to budu chtít udělat podruhé tak dvojnásobek a potřetí zas dvojnásobek předchozí ceny atd.později přidáme další položky k nakoupení


<total_tokens>15000000 tokens left</total_tokens>


405	    if (!rectOverlap(player.x, player.y, player.w, player.h, e.x, e.y, e.w, e.h)) continue;
406	    const stomp = player.vy > 1.5 && (player.y + player.h) - e.y < 18;
407	    if (stomp){
408	      e.dead = true; e.t = 0; player.vy = -10; score += 50; shake = 4; S.stomp();
409	    } else {
410	      hurtPlayer();
411	    }
412	  }
413	  // cíl
414	  if (world.goal && mode === 'levels' &&
415	      rectOverlap(player.x, player.y, player.w, player.h,
416	                  world.goal.x, world.goal.y - TILE*2.2, TILE, TILE*2.2)){
417	    S.win();
418	    score += 200 + lives * 100;
419	    levelIndex++;
420	    if (levelIndex >= LEVELS.length){ state = 'win'; }
421	    else { state = 'levelclear'; clearT = 100; }
422	  }
423	  // pád mimo svět
424	  if (player.y > (ROWS + 1) * TILE) hurtPlayer();
425	}
426

    if (!rectOverlap(player.x, player.y, player.w, player.h, e.x, e.y, e.w, e.h)) continue;
    const stomp = player.vy > 1.5 && (player.y + player.h) - e.y < 18;
    if (stomp){
      e.dead = true; e.t = 0; player.vy = -10; score += 50; shake = 4; S.stomp();
    } else {
      hurtPlayer();
    }
  }
  // cíl
  if (world.goal && mode === 'levels' &&
      rectOverlap(player.x, player.y, player.w, player.h,
                  world.goal.x, world.goal.y - TILE*2.2, TILE, TILE*2.2)){
    S.win();
    score += 200 + lives * 100;
    levelIndex++;
    if (levelIndex >= LEVELS.length){ state = 'win'; }
    else { state = 'levelclear'; clearT = 100; }
  }
  // pád mimo svět
  if (player.y > (ROWS + 1) * TILE) hurtPlayer();
}


<total_tokens>14993998 tokens left</total_tokens>


ještě uprav příběhové levely když dokončím level tak můžu si něco dokoupit za nazbírané mince které jsem sezbíral napřílad přidat jeden život 10mincí když to budu chtít udělat podruhé tak dvojnásobek…


    if (levelIndex >= LEVELS.length){ state = 'win'; }
    else { state = 'levelclear'; clearT = 100; }
  }

    if (levelIndex >= LEVELS.length){ state = 'win'; }
    else { state = 'shop'; shopSel = 0; }
  }


    if (levelIndex >= LEVELS.length){ state = 'win'; }
    else { state = 'levelclear'; clearT = 100; }
  }

    if (levelIndex >= LEVELS.length){ state = 'win'; }
    else { state = 'shop'; shopSel = 0; }
  }

-    else { state = 'levelclear'; clearT = 100; }

+    else { state = 'shop'; shopSel = 0; }

if (player.y > (ROWS + 1) * TILE) hurtPlayer();


<total_tokens>14993325 tokens left</total_tokens>


let state = 'menu';      // menu | play | paused | levelclear | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu
let clearT = 0, shake = 0;

let state = 'menu';      // menu | play | paused | shop | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu
let shake = 0;
const MAX_LIVES = 5;

/* --- Obchod mezi levely (později přibudou další položky) --- */
let shopSel = 0;
const SHOP_X = W/2 - 235, SHOP_W = 470, SHOP_ROW_H = 62, SHOP_GAP = 10, SHOP_Y0 = 185;
const shopBtnY = () => SHOP_Y0 + SHOP.length * (SHOP_ROW_H + SHOP_GAP) + 6;

const SHOP = [
  {
    name: 'Život navíc',
    desc: '+1 život (max ' + MAX_LIVES + ')',
    base: 10,
    bought: 0,
    price(){ return this.base * Math.pow(2, this.bought); },
    canBuy(){ return lives < MAX_LIVES && coinsCollected >= this.price(); },
    apply(){ lives++; },
  },
];

function buyShop(i){
  const it = SHOP[i];
  if (!it) return;
  if (!it.canBuy()){ beep(120, 0.09, 'square', 0.04); return; }
  coinsCollected -= it.price();
  it.bought++;
  it.apply();
  S.coin();
}
function shopContinue(){
  loadLevel(levelIndex);   // levelIndex je už zvýšený
  state = 'play';
}


let state = 'menu';      // menu | play | paused | levelclear | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu
let clearT = 0, shake = 0;

let state = 'menu';      // menu | play | paused | shop | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;
let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;
let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu
let shake = 0;
const MAX_LIVES = 5;

/* --- Obchod mezi levely (později přibudou další položky) --- */
let shopSel = 0;
const SHOP_X = W/2 - 235, SHOP_W = 470, SHOP_ROW_H = 62, SHOP_GAP = 10, SHOP_Y0 = 185;
const shopBtnY = () => SHOP_Y0 + SHOP.length * (SHOP_ROW_H + SHOP_GAP) + 6;

const SHOP = [
  {
    name: 'Život navíc',
    desc: '+1 život (max ' + MAX_LIVES + ')',
    base: 10,
    bought: 0,
    price(){ return this.base * Math.pow(2, this.bought); },
    canBuy(){ return lives < MAX_LIVES && coinsCollected >= this.price(); },
    apply(){ lives++; },
  },
];

function buyShop(i){
  const it = SHOP[i];
  if (!it) return;
  if (!it.canBuy()){ beep(120, 0.09, 'square', 0.04); return; }
  coinsCollected -= it.price();
  it.bought++;
  it.apply();
  S.coin();
}
function shopContinue(){
  loadLevel(levelIndex);   // levelIndex je už zvýšený
  state = 'play';
}

/*  Stavy hry                                                         */

-let state = 'menu';      // menu | play | paused | levelclear | over | win

+let state = 'menu';      // menu | play | paused | shop | over | win

+/* --- Obchod mezi levely (později přibudou další položky) --- */

+const SHOP_X = W/2 - 235, SHOP_W = 470, SHOP_ROW_H = 62, SHOP_GAP = 10, SHOP_Y0 = 185;

+const shopBtnY = () => SHOP_Y0 + SHOP.length * (SHOP_ROW_H + SHOP_GAP) + 6;

+    desc: '+1 život (max ' + MAX_LIVES + ')',

+    price(){ return this.base * Math.pow(2, this.bought); },

+    canBuy(){ return lives < MAX_LIVES && coinsCollected >= this.price(); },

+  loadLevel(levelIndex);   // levelIndex je už zvýšený


<total_tokens>14992272 tokens left</total_tokens>


  lives = 3; coinsCollected = 0; score = 0; distance = 0; shake = 0;
  if (m === 'levels'){ levelIndex = 0; loadLevel(0); }

  lives = 3; coinsCollected = 0; score = 0; distance = 0; shake = 0;
  shopSel = 0;
  SHOP.forEach(it => it.bought = 0);
  if (m === 'levels'){ levelIndex = 0; loadLevel(0); }


  lives = 3; coinsCollected = 0; score = 0; distance = 0; shake = 0;
  if (m === 'levels'){ levelIndex = 0; loadLevel(0); }

  lives = 3; coinsCollected = 0; score = 0; distance = 0; shake = 0;
  shopSel = 0;
  SHOP.forEach(it => it.bought = 0);
  if (m === 'levels'){ levelIndex = 0; loadLevel(0); }


<total_tokens>14991657 tokens left</total_tokens>


  if (state === 'levelclear'){
    if (--clearT <= 0){ loadLevel(levelIndex); state = 'play'; }
    return;
  }

  if (state === 'shop'){
    if (hit('ArrowUp', 'KeyW'))   shopSel = (shopSel - 1 + SHOP.length) % SHOP.length;
    if (hit('ArrowDown', 'KeyS')) shopSel = (shopSel + 1) % SHOP.length;
    if (hit('Enter')) buyShop(shopSel);
    for (let i = 0; i < SHOP.length && i < 9; i++)
      if (hit('Digit' + (i + 1))) buyShop(i);
    if (hit('Space')) shopContinue();
    return;
  }


  if (state === 'levelclear'){
    if (--clearT <= 0){ loadLevel(levelIndex); state = 'play'; }
    return;
  }

  if (state === 'shop'){
    if (hit('ArrowUp', 'KeyW'))   shopSel = (shopSel - 1 + SHOP.length) % SHOP.length;
    if (hit('ArrowDown', 'KeyS')) shopSel = (shopSel + 1) % SHOP.length;
    if (hit('Enter')) buyShop(shopSel);
    for (let i = 0; i < SHOP.length && i < 9; i++)
      if (hit('Digit' + (i + 1))) buyShop(i);
    if (hit('Space')) shopContinue();
    return;
  }

-    if (--clearT <= 0){ loadLevel(levelIndex); state = 'play'; }

+    if (hit('ArrowUp', 'KeyW'))   shopSel = (shopSel - 1 + SHOP.length) % SHOP.length;

+    if (hit('ArrowDown', 'KeyS')) shopSel = (shopSel + 1) % SHOP.length;

+    if (hit('Enter')) buyShop(shopSel);

+    for (let i = 0; i < SHOP.length && i < 9; i++)

+      if (hit('Digit' + (i + 1))) buyShop(i);


<total_tokens>14991186 tokens left</total_tokens>


    if (state !== 'levelclear') drawPlayer();
    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();

    if (state !== 'shop') drawPlayer();
    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();


    if (state !== 'levelclear') drawPlayer();
    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();

    if (state !== 'shop') drawPlayer();
    if (mode === 'endless' && endlessStarted && wallX > camX - 100) drawWall();

for (const co of world.coins) if (!co.got) drawCoin(co);

for (const e of world.enemies) drawEnemy(e);

-    if (state !== 'levelclear') drawPlayer();

+    if (state !== 'shop') drawPlayer();


<total_tokens>14990764 tokens left</total_tokens>


  if (state === 'menu')            drawMenu();
  else if (state === 'paused')     overlay('PAUZA', 'P nebo Enter – pokračovat   ·   Esc – menu');
  else if (state === 'levelclear') overlay('LEVEL ' + levelIndex + ' HOTOV', 'Sbíráš dál… mincí: ' + coinsCollected);
  else if (state === 'over')       overlay('KONEC HRY', statLine() + '   ·   Enter – menu');

  if (state === 'menu')            drawMenu();
  else if (state === 'paused')     overlay('PAUZA', 'P nebo Enter – pokračovat   ·   Esc – menu');
  else if (state === 'shop')       drawShop();
  else if (state === 'over')       overlay('KONEC HRY', statLine() + '   ·   Enter – menu');


  if (state === 'menu')            drawMenu();
  else if (state === 'paused')     overlay('PAUZA', 'P nebo Enter – pokračovat   ·   Esc – menu');
  else if (state === 'levelclear') overlay('LEVEL ' + levelIndex + ' HOTOV', 'Sbíráš dál… mincí: ' + coinsCollected);
  else if (state === 'over')       overlay('KONEC HRY', statLine() + '   ·   Enter – menu');

  if (state === 'menu')            drawMenu();
  else if (state === 'paused')     overlay('PAUZA', 'P nebo Enter – pokračovat   ·   Esc – menu');
  else if (state === 'shop')       drawShop();
  else if (state === 'over')       overlay('KONEC HRY', statLine() + '   ·   Enter – menu');

if (state === 'menu')            drawMenu();

-  else if (state === 'levelclear') overlay('LEVEL ' + levelIndex + ' HOTOV', 'Sbíráš dál… mincí: ' + coinsCollected);

+  else if (state === 'shop')       drawShop();

else if (state === 'win')        overlay('DOKONČENO! 🎉', statLine() + '   ·   Enter – menu');


<total_tokens>14990257 tokens left</total_tokens>


726	  }
727	  ctx.fillStyle = COL.enemy;
728	  roundRect(e.x, e.y, e.w, e.h, 6); ctx.fill();
729	  ctx.fillStyle = COL.dark;
730	  const d = Math.sign(e.vx) || 1;
731	  ctx.fillRect(e.x + 6 + (d > 0 ? 3 : 0), e.y + 8, 4, 4);
732	  ctx.fillRect(e.x + e.w - 10 + (d > 0 ? 3 : 0), e.y + 8, 4, 4);
733	}
734
735	function drawCoin(co){
736	  const b = Math.sin(performance.now() / 300 + co.x) * 2;
737	  ctx.fillStyle = COL.coin;
738	  ctx.beginPath(); ctx.arc(co.x, co.y + b, 7, 0, Math.PI * 2); ctx.fill();
739	  ctx.strokeStyle = 'rgba(0,0,0,.25)'; ctx.lineWidth = 2;
740	  ctx.beginPath(); ctx.arc(co.x, co.y + b, 7, 0, Math.PI * 2); ctx.stroke();
741	}
742
743	function drawGoal(g){
744	  const topY = g.y - TILE * 2.1;
745	  ctx.fillStyle = '#e8ecf5';
746	  ctx.fillRect(g.x + TILE/2 - 2, topY, 4, TILE * 2.1);
747	  ctx.fillStyle = COL.flag;
748	  ctx.beginPath();
749	  ctx.moveTo(g.x + TILE/2 + 2, topY);
750	  ctx.lineTo(g.x + TILE/2 + 30, topY + 10);
751	  ctx.lineTo(g.x + TILE/2 + 2, topY + 20);
752	  ctx.fill();
753	}
754
755	function drawHUD(){

  }
  ctx.fillStyle = COL.enemy;
  roundRect(e.x, e.y, e.w, e.h, 6); ctx.fill();
  ctx.fillStyle = COL.dark;
  const d = Math.sign(e.vx) || 1;
  ctx.fillRect(e.x + 6 + (d > 0 ? 3 : 0), e.y + 8, 4, 4);
  ctx.fillRect(e.x + e.w - 10 + (d > 0 ? 3 : 0), e.y + 8, 4, 4);
}

function drawCoin(co){
  const b = Math.sin(performance.now() / 300 + co.x) * 2;
  ctx.fillStyle = COL.coin;
  ctx.beginPath(); ctx.arc(co.x, co.y + b, 7, 0, Math.PI * 2); ctx.fill();
  ctx.strokeStyle = 'rgba(0,0,0,.25)'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.arc(co.x, co.y + b, 7, 0, Math.PI * 2); ctx.stroke();
}

function drawGoal(g){
  const topY = g.y - TILE * 2.1;
  ctx.fillStyle = '#e8ecf5';
  ctx.fillRect(g.x + TILE/2 - 2, topY, 4, TILE * 2.1);
  ctx.fillStyle = COL.flag;
  ctx.beginPath();
  ctx.moveTo(g.x + TILE/2 + 2, topY);
  ctx.lineTo(g.x + TILE/2 + 30, topY + 10);
  ctx.lineTo(g.x + TILE/2 + 2, topY + 20);
  ctx.fill();
}

function drawHUD(){


<total_tokens>14990051 tokens left</total_tokens>


755	function drawHUD(){
756	  ctx.save();
757	  for (let i = 0; i < 3; i++){
758	    ctx.fillStyle = i < lives ? COL.enemy : '#333a4f';
759	    roundRect(16 + i * 20, 16, 14, 14, 4); ctx.fill();
760	  }
761	  ctx.fillStyle = '#e8ecf5';
762	  ctx.font = '600 15px system-ui, sans-serif';
763	  ctx.textAlign = 'left';
764	  ctx.fillText('● ' + coinsCollected, 16, 54);
765	  ctx.textAlign = 'right';
766	  if (mode === 'levels'){
767	    ctx.fillText(LEVELS[levelIndex].name + '  ·  ' + (levelIndex + 1) + '/' + LEVELS.length, W - 16, 26);
768	  } else {
769	    ctx.fillText(distance + ' m', W - 16, 26);
770	  }
771	  ctx.fillText('Skóre ' + score, W - 16, 48);
772	  ctx.restore();
773	}
774
775	function statLine(){
776	  return mode === 'levels'
777	    ? 'Skóre ' + score + '  ·  mincí ' + coinsCollected
778	    : 'Vzdálenost ' + distance + ' m  ·  mincí ' + coinsCollected + '  ·  skóre ' + score;
779	}
780
781	function overlay(title, sub){
782	  ctx.save();
783	  ctx.fillStyle = 'rgba(8,10,16,.78)';
784	  ctx.fillRect(0, 0, W, H);

function drawHUD(){
  ctx.save();
  for (let i = 0; i < 3; i++){
    ctx.fillStyle = i < lives ? COL.enemy : '#333a4f';
    roundRect(16 + i * 20, 16, 14, 14, 4); ctx.fill();
  }
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 15px system-ui, sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText('● ' + coinsCollected, 16, 54);
  ctx.textAlign = 'right';
  if (mode === 'levels'){
    ctx.fillText(LEVELS[levelIndex].name + '  ·  ' + (levelIndex + 1) + '/' + LEVELS.length, W - 16, 26);
  } else {
    ctx.fillText(distance + ' m', W - 16, 26);
  }
  ctx.fillText('Skóre ' + score, W - 16, 48);
  ctx.restore();
}

function statLine(){
  return mode === 'levels'
    ? 'Skóre ' + score + '  ·  mincí ' + coinsCollected
    : 'Vzdálenost ' + distance + ' m  ·  mincí ' + coinsCollected + '  ·  skóre ' + score;
}

function overlay(title, sub){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.78)';
  ctx.fillRect(0, 0, W, H);


<total_tokens>14989215 tokens left</total_tokens>


  ctx.save();
  for (let i = 0; i < 3; i++){
    ctx.fillStyle = i < lives ? COL.enemy : '#333a4f';
    roundRect(16 + i * 20, 16, 14, 14, 4); ctx.fill();
  }
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 15px system-ui, sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText('● ' + coinsCollected, 16, 54);

  ctx.save();
  const slots = Math.min(Math.max(3, lives), MAX_LIVES);
  for (let i = 0; i < slots; i++){
    ctx.fillStyle = i < lives ? COL.enemy : '#333a4f';
    roundRect(16 + i * 20, 16, 14, 14, 4); ctx.fill();
  }
  ctx.fillStyle = COL.coin;
  ctx.font = '600 15px system-ui, sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText('● ' + coinsCollected, 16, 54);


  ctx.save();
  for (let i = 0; i < 3; i++){
    ctx.fillStyle = i < lives ? COL.enemy : '#333a4f';
    roundRect(16 + i * 20, 16, 14, 14, 4); ctx.fill();
  }
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 15px system-ui, sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText('● ' + coinsCollected, 16, 54);

  ctx.save();
  const slots = Math.min(Math.max(3, lives), MAX_LIVES);
  for (let i = 0; i < slots; i++){
    ctx.fillStyle = i < lives ? COL.enemy : '#333a4f';
    roundRect(16 + i * 20, 16, 14, 14, 4); ctx.fill();
  }
  ctx.fillStyle = COL.coin;
  ctx.font = '600 15px system-ui, sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText('● ' + coinsCollected, 16, 54);

+  const slots = Math.min(Math.max(3, lives), MAX_LIVES);


<total_tokens>14988070 tokens left</total_tokens>


781
782	function overlay(title, sub){
783	  ctx.save();
784	  ctx.fillStyle = 'rgba(8,10,16,.78)';
785	  ctx.fillRect(0, 0, W, H);
786	  ctx.textAlign = 'center';
787	  ctx.fillStyle = '#e8ecf5';
788	  ctx.font = '700 44px system-ui, sans-serif';
789	  ctx.fillText(title, W/2, H/2 - 6);
790	  ctx.fillStyle = '#8b93a7';
791	  ctx.font = '500 16px system-ui, sans-serif';
792	  ctx.fillText(sub, W/2, H/2 + 30);
793	  ctx.restore();
794	}
795
796	function drawMenu(){
797	  ctx.save();
798	  ctx.fillStyle = 'rgba(8,10,16,.85)';
799	  ctx.fillRect(0, 0, W, H);
800	  ctx.textAlign = 'center';
801
802	  ctx.fillStyle = '#e8ecf5';
803	  ctx.font = '800 46px system-ui, sans-serif';
804	  ctx.fillText('HRANATÝ SVĚT', W/2, H/2 - 96);
805	  ctx.fillStyle = '#8b93a7';
806	  ctx.font = '500 17px system-ui, sans-serif';
807	  ctx.fillText('2D plošinovka — vyber si režim', W/2, H/2 - 64);
808
809	  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', H/2 - 6, menuSel === 0);
810	  menuOption('2', 'Nekonečný běh', 'Náhodná trať, která zrychluje — hraj na skóre', H/2 + 66, menuSel === 1);
811
812	  ctx.fillStyle = '#8b93a7';
813	  ctx.font = '500 14px system-ui, sans-serif';
814	  ctx.fillText('↑ ↓ výběr  ·  Enter start  ·  nebo rovnou klávesa 1 / 2', W/2, H - 46);
815	  ctx.restore();
816	}

function overlay(title, sub){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.78)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 44px system-ui, sans-serif';
  ctx.fillText(title, W/2, H/2 - 6);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 16px system-ui, sans-serif';
  ctx.fillText(sub, W/2, H/2 + 30);
  ctx.restore();
}

function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.85)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 46px system-ui, sans-serif';
  ctx.fillText('HRANATÝ SVĚT', W/2, H/2 - 96);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 17px system-ui, sans-serif';
  ctx.fillText('2D plošinovka — vyber si režim', W/2, H/2 - 64);

  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', H/2 - 6, menuSel === 0);
  menuOption('2', 'Nekonečný běh', 'Náhodná trať, která zrychluje — hraj na skóre', H/2 + 66, menuSel === 1);

  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('↑ ↓ výběr  ·  Enter start  ·  nebo rovnou klávesa 1 / 2', W/2, H - 46);
  ctx.restore();
}


<total_tokens>14987825 tokens left</total_tokens>


function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.85)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

function drawShop(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.88)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 32px system-ui, sans-serif';
  ctx.fillText('LEVEL ' + levelIndex + ' HOTOV', W/2, 84);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 18px system-ui, sans-serif';
  ctx.fillText('● ' + coinsCollected + ' mincí k utracení', W/2, 116);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('OBCHOD', W/2, 154);

  SHOP.forEach((it, i) => {
    const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);
    const sel = i === shopSel;
    const price = it.price();
    const ok = it.canBuy();

    ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
    roundRect(SHOP_X, y, SHOP_W, SHOP_ROW_H, 12); ctx.fill();
    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
    ctx.lineWidth = 2;
    roundRect(SHOP_X, y, SHOP_W, SHOP_ROW_H, 12); ctx.stroke();

    ctx.textAlign = 'left';
    ctx.fillStyle = '#e8ecf5';
    ctx.font = '700 17px system-ui, sans-serif';
    ctx.fillText((i + 1) + '.   ' + it.name, SHOP_X + 18, y + 26);
    ctx.fillStyle = '#8b93a7';
    ctx.font = '400 13px system-ui, sans-serif';
    ctx.fillText(it.desc + '     ·     koupeno ' + it.bought + '×', SHOP_X + 18, y + 46);

    ctx.textAlign = 'right';
    ctx.fillStyle = ok ? COL.coin : '#ff6b7d';
    ctx.font = '700 18px system-ui, sans-serif';
    ctx.fillText('● ' + price, SHOP_X + SHOP_W - 18, y + 38);
  });

  const by = shopBtnY();
  ctx.fillStyle = 'rgba(74,222,128,.14)';
  roundRect(W/2 - 120, by, 240, 44, 12); ctx.fill();
  ctx.strokeStyle = COL.flag; ctx.lineWidth = 2;
  roundRect(W/2 - 120, by, 240, 44, 12); ctx.stroke();
  ctx.textAlign = 'center';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 16px system-ui, sans-serif';
  ctx.fillText('Pokračovat  →', W/2, by + 28);

  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('↑ ↓ výběr  ·  Enter / číslo = koupit  ·  Mezerník = pokračovat', W/2, H - 34);
  ctx.restore();
}

function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.85)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';


The file /home/tonda/hra.html has been updated successfully. (file state is current in your context — no need to Read it back)

function drawShop(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.88)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 32px system-ui, sans-serif';
  ctx.fillText('LEVEL ' + levelIndex + ' HOTOV', W/2, 84);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 18px system-ui, sans-serif';
  ctx.fillText('● ' + coinsCollected + ' mincí k utracení', W/2, 116);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('OBCHOD', W/2, 154);

  SHOP.forEach((it, i) => {
    const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);
    const sel = i === shopSel;
    const price = it.price();
    const ok = it.canBuy();

    ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
    roundRect(SHOP_X, y, SHOP_W, SHOP_ROW_H, 12); ctx.fill();
    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
    ctx.lineWidth = 2;
    roundRect(SHOP_X, y, SHOP_W, SHOP_ROW_H, 12); ctx.stroke();

    ctx.textAlign = 'left';
    ctx.fillStyle = '#e8ecf5';
    ctx.font = '700 17px system-ui, sans-serif';
    ctx.fillText((i + 1) + '.   ' + it.name, SHOP_X + 18, y + 26);
    ctx.fillStyle = '#8b93a7';
    ctx.font = '400 13px system-ui, sans-serif';
    ctx.fillText(it.desc + '     ·     koupeno ' + it.bought + '×', SHOP_X + 18, y + 46);

    ctx.textAlign = 'right';
    ctx.fillStyle = ok ? COL.coin : '#ff6b7d';
    ctx.font = '700 18px system-ui, sans-serif';
    ctx.fillText('● ' + price, SHOP_X + SHOP_W - 18, y + 38);
  });

  const by = shopBtnY();
  ctx.fillStyle = 'rgba(74,222,128,.14)';
  roundRect(W/2 - 120, by, 240, 44, 12); ctx.fill();
  ctx.strokeStyle = COL.flag; ctx.lineWidth = 2;
  roundRect(W/2 - 120, by, 240, 44, 12); ctx.stroke();
  ctx.textAlign = 'center';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 16px system-ui, sans-serif';
  ctx.fillText('Pokračovat  →', W/2, by + 28);

  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('↑ ↓ výběr  ·  Enter / číslo = koupit  ·  Mezerník = pokračovat', W/2, H - 34);
  ctx.restore();
}

function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.85)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

+  ctx.font = '800 32px system-ui, sans-serif';

+  ctx.fillText('LEVEL ' + levelIndex + ' HOTOV', W/2, 84);

+  ctx.font = '600 18px system-ui, sans-serif';

+  ctx.fillText('● ' + coinsCollected + ' mincí k utracení', W/2, 116);

+  ctx.font = '500 14px system-ui, sans-serif';

+    const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);

+    roundRect(SHOP_X, y, SHOP_W, SHOP_ROW_H, 12); ctx.fill();

+    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';

+    roundRect(SHOP_X, y, SHOP_W, SHOP_ROW_H, 12); ctx.stroke();

+    ctx.font = '700 17px system-ui, sans-serif';

+    ctx.fillText((i + 1) + '.   ' + it.name, SHOP_X + 18, y + 26);

+    ctx.font = '400 13px system-ui, sans-serif';

+    ctx.fillText(it.desc + '     ·     koupeno ' + it.bought + '×', SHOP_X + 18, y + 46);

+    ctx.fillStyle = ok ? COL.coin : '#ff6b7d';

+    ctx.font = '700 18px system-ui, sans-serif';

+    ctx.fillText('● ' + price, SHOP_X + SHOP_W - 18, y + 38);

+  ctx.strokeStyle = COL.flag; ctx.lineWidth = 2;

+  ctx.font = '700 16px system-ui, sans-serif';

+  ctx.fillText('Pokračovat  →', W/2, by + 28);

+  ctx.fillText('↑ ↓ výběr  ·  Enter / číslo = koupit  ·  Mezerník = pokračovat', W/2, H - 34);


<total_tokens>14985470 tokens left</total_tokens>


905	    const ys = [H/2 - 6, H/2 + 66];
906	    for (let i = 0; i < 2; i++)
907	      if (Math.abs(p.y - ys[i]) < 29 && Math.abs(p.x - W/2) < 235)
908	        return startGame(i === 0 ? 'levels' : 'endless');
909	  } else if (state === 'over' || state === 'win'){
910	    state = 'menu';
911	  } else if (state === 'paused'){
912	    state = 'play';
913	  }
914	});
915
916	/* ------------------------------------------------------------------ */
917	/*  Hlavní smyčka – pevný krok 60 Hz                                  */
918	/* ------------------------------------------------------------------ */
919	let last = performance.now(), acc = 0;
920	const STEP = 1000 / 60;
921
922	function frame(now){
923	  acc += Math.min(now - last, 100);
924	  last = now;
925	  let steps = 0;
926	  while (acc >= STEP && steps < 5){
927	    update();
928	    codesHit.clear();
929	    acc -= STEP;
930	    steps++;
931	  }
932	  if (steps === 5) acc = 0;
933	  draw();
934	  requestAnimationFrame(frame);
935	}
936	requestAnimationFrame(frame);
937
938	})();
939	</script>

    const ys = [H/2 - 6, H/2 + 66];
    for (let i = 0; i < 2; i++)
      if (Math.abs(p.y - ys[i]) < 29 && Math.abs(p.x - W/2) < 235)
        return startGame(i === 0 ? 'levels' : 'endless');
  } else if (state === 'over' || state === 'win'){
    state = 'menu';
  } else if (state === 'paused'){
    state = 'play';
  }
});

/* ------------------------------------------------------------------ */
/*  Hlavní smyčka – pevný krok 60 Hz                                  */
/* ------------------------------------------------------------------ */
let last = performance.now(), acc = 0;
const STEP = 1000 / 60;

function frame(now){
  acc += Math.min(now - last, 100);
  last = now;
  let steps = 0;
  while (acc >= STEP && steps < 5){
    update();
    codesHit.clear();
    acc -= STEP;
    steps++;
  }
  if (steps === 5) acc = 0;
  draw();
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

})();
</script>


<total_tokens>14985265 tokens left</total_tokens>


  } else if (state === 'over' || state === 'win'){
    state = 'menu';
  } else if (state === 'paused'){
    state = 'play';
  }
});

  } else if (state === 'shop'){
    for (let i = 0; i < SHOP.length; i++){
      const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);
      if (p.x >= SHOP_X && p.x <= SHOP_X + SHOP_W && p.y >= y && p.y <= y + SHOP_ROW_H)
        return buyShop(i);
    }
    const by = shopBtnY();
    if (p.x >= W/2 - 120 && p.x <= W/2 + 120 && p.y >= by && p.y <= by + 44)
      return shopContinue();
  } else if (state === 'over' || state === 'win'){
    state = 'menu';
  } else if (state === 'paused'){
    state = 'play';
  }
});


  } else if (state === 'shop'){
    for (let i = 0; i < SHOP.length; i++){
      const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);
      if (p.x >= SHOP_X && p.x <= SHOP_X + SHOP_W && p.y >= y && p.y <= y + SHOP_ROW_H)
        return buyShop(i);
    }
    const by = shopBtnY();
    if (p.x >= W/2 - 120 && p.x <= W/2 + 120 && p.y >= by && p.y <= by + 44)
      return shopContinue();
  } else if (state === 'over' || state === 'win'){
    state = 'menu';
  } else if (state === 'paused'){
    state = 'play';
  }
});

return startGame(i === 0 ? 'levels' : 'endless');

+    for (let i = 0; i < SHOP.length; i++){

+      const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);

+      if (p.x >= SHOP_X && p.x <= SHOP_X + SHOP_W && p.y >= y && p.y <= y + SHOP_ROW_H)


<total_tokens>14984287 tokens left</total_tokens>


Bash

grep -n "clearT\|levelclear" hra.html; echo "---exit $?---"

Check for leftover clearT / levelclear references


---exit 1---


<total_tokens>14984036 tokens left</total_tokens>


python3 - <<'EOF'
import re
s=open('hra.html').read()
js=re.search(r'<script>(.*)</script>',s,re.S).group(1)
for ch,op,cl in [('()','(',')'),('{}','{','}'),('[]','[',']')]:
    print(ch, js.count(op), js.count(cl), 'OK' if js.count(op)==js.count(cl) else 'MISMATCH')
# crude check: backticks balanced
print('backticks', js.count('`'), 'OK' if js.count('`')%2==0 else 'ODD')
EOF

Rough bracket balance check on the script


() 677 677 OK
{} 135 135 OK
[] 139 139 OK
backticks 0 OK


<total_tokens>14982732 tokens left</total_tokens>


curl -sS -o /dev/null -w "server: HTTP %{http_code}\n" http://localhost:8000/hra.html

Confirm server still serving


server: HTTP 200


<total_tokens>14982498 tokens left</total_tokens>


Hotovo — dej **F5** a dohraj první level.

## Obchod mezi levely

Po dokončení levelu (kromě posledního) se objeví obrazovka **OBCHOD**:

- **Život navíc** — základní cena **10 mincí**, strop 5 životů
- Každý další nákup té samé položky = **dvojnásobek** předchozí ceny: 10 → 20 → 40 → 80 → …
- Zdvojnásobení se počítá zvlášť pro každou položku a **resetuje se na začátku nové hry**
- Utrácí se z nasbíraných mincí (číslo v HUDu vlevo nahoře, teď zlaté — je to peněženka)

**Ovládání obchodu:** `↑ ↓` výběr · `Enter` nebo číslo `1` = koupit · `Mezerník` = pokračovat · nebo klikni myší.

HUD teď taky ukazuje víc než 3 srdíčka, když si životy dokoupíš.

## Kam přidávat další položky

V souboru je pole `SHOP` (u herních proměnných). Nová položka = jeden objekt:

```js
{
  name: 'Dvojskok',
  desc: 'Umožní skok ve vzduchu',
  base: 25,
  bought: 0,
  price(){ return this.base * Math.pow(2, this.bought); },
  canBuy(){ return coinsCollected >= this.price() && /* další podmínka */ true; },
  apply(){ /* co se stane po koupi */ },
}
```

Obrazovka, ovládání i klikání se přizpůsobí počtu položek automaticky. Řekni, co chceš přidat příště.


zapisovat nejlepší skóré v nekonečném módu a odemykat jím nové barvy na charakter


<total_tokens>15000000 tokens left</total_tokens>