S4: extinde suita cu gameplay bomberman + overworld + audio (24/24)
3 teste noi commitate (mutate din scratch in suita): - audio S1: beep._ctx 'running' dupa Incepe aventura (era NO_CTX) - overworld: mers cu tastatura (ArrowRight) + iesire blocata pana la final - arcade bomberman: bomba sparge cutie, BFS AI se apropie, respawn pastreaza progres Arbore AGENTS.md/CLAUDE.md/tests actualizat 21→24 (14 @regresie + 10 @campanie). Iteratia 2 COMPLETA (S1+S2+S3+S4). Board: TODOS.md S4 [x]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,9 +20,9 @@ sursa de adevăr tehnică pentru agenți.
|
|||||||
python3 -m http.server 8000
|
python3 -m http.server 8000
|
||||||
|
|
||||||
# Teste (Playwright; fără package.json commitat — vezi tests/AGENTS.md):
|
# Teste (Playwright; fără package.json commitat — vezi tests/AGENTS.md):
|
||||||
npx playwright test tests/smoke.mjs # suita completă: 21/21
|
npx playwright test tests/smoke.mjs # suita completă: 24/24
|
||||||
npx playwright test tests/smoke.mjs --grep @regresie # regresie: 13
|
npx playwright test tests/smoke.mjs --grep @regresie # regresie: 14
|
||||||
npx playwright test tests/smoke.mjs --grep @campanie # campanie E2E: 8
|
npx playwright test tests/smoke.mjs --grep @campanie # campanie E2E: 10
|
||||||
```
|
```
|
||||||
|
|
||||||
## Durable Rules (repo-wide)
|
## Durable Rules (repo-wide)
|
||||||
|
|||||||
67
CLAUDE.md
67
CLAUDE.md
@@ -1,60 +1,23 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
Ghid pentru Claude Code în acest repo.
|
||||||
|
|
||||||
## Ce este
|
## Sursa de adevăr: arborele AGENTS.md
|
||||||
|
|
||||||
Generator de jocuri escape room: un singur fișier HTML (`escape-builder.html`), fără backend, fără build, fără dependențe. Același set de puzzle-uri se exportă în 5 stiluri de joc diferite (clasic/quiz, terminal retro, arcade pixel, story chat, point-and-click).
|
Adevărul tehnic (arhitectură, reguli de editare, testare) trăiește în arborele `AGENTS.md`, nu aici.
|
||||||
|
Înainte să editezi un path, citește lanțul DOX din rădăcină:
|
||||||
|
|
||||||
## Dezvoltare
|
- [`AGENTS.md`](AGENTS.md) — root: ce e produsul, Quick Reference, Durable Rules (zero-dep, un singur
|
||||||
|
fișier, backslash dublu, sentinel `__CFG__`, demo-uri generate, `libJS`/`SNIP` partajate, dispatch
|
||||||
|
pe 6 motoare).
|
||||||
|
- [`tests/AGENTS.md`](tests/AGENTS.md) — harness Playwright (fără `package.json` commitat, `npx`,
|
||||||
|
tag-uri `@regresie`/`@campanie`, 24/24).
|
||||||
|
- `scratch/AGENTS.md` — prototipuri & QA (gitignored).
|
||||||
|
|
||||||
Nu există build sau lint. Produsul (HTML files) e vanilla HTML/CSS/JS, zero-dependențe.
|
Documente de conținut: `DESIGN.md` (design campanie), `TODOS.md` (backlog), `HANDOFF.md` (handoff sesiune),
|
||||||
|
`README.md` (utilizator final).
|
||||||
|
|
||||||
```bash
|
## Specific Claude Code
|
||||||
# Verificare: deschide direct în browser (merge de pe file://)
|
|
||||||
# sau servește local:
|
|
||||||
python3 -m http.server 8000
|
|
||||||
```
|
|
||||||
|
|
||||||
Testarea manuală: deschide `escape-builder.html`, schimbă "Stil joc" și verifică preview-ul live (iframe), apoi exportă și verifică jocul standalone. Testele automate → vezi `## Testing`.
|
- Regulile de mediu și modul non-interactiv (`claude -p`) sunt în `/workspace/CLAUDE.md` (părinte).
|
||||||
|
- Skill relevant: `/dox` — bootstrap/update al acestui arbore `AGENTS.md`.
|
||||||
## Arhitectură
|
|
||||||
|
|
||||||
Toată aplicația trăiește în `escape-builder.html`, organizată în secțiuni comentate (`stare`, `editor`, `preview`, `template-urile jocului exportat`):
|
|
||||||
|
|
||||||
- **Stare**: obiectul `state` (titlu, poveste, culoare, `style`, listă `puzzles`), persistat automat în `localStorage` sub cheia `escape-builder-v1`. Export/import ca JSON.
|
|
||||||
- **Editor** (stânga): formularele scriu direct în `state` via `data-g`; orice modificare cheamă `onChange()` → persist + refresh preview cu debounce 400ms.
|
|
||||||
- **Preview** (dreapta): `refreshPreview()` setează `iframe.srcdoc = gameHTML(cleanState())` — preview-ul este exact jocul exportat, jucabil.
|
|
||||||
- **Generare joc**: `gameHTML(cfg)` face dispatch pe `cfg.style` către cele 5 motoare: `gameClassic`, `gameTerminal`, `gameArcade`, `gameChat`, `gamePoint`. Fiecare motor returnează un string HTML complet, standalone (jocul exportat merge offline).
|
|
||||||
|
|
||||||
Cod partajat între motoare (injectat în HTML-ul generat):
|
|
||||||
- `libJS(cfg)`: `CFG` (config serializat), `norm` (normalizare răspuns: fără diacritice/majuscule, virgulă→punct), `checkAnswer`, `starsFor` (3/2/1 stele după încercări/indiciu), `finalWord` (literele puzzle-urilor formează cuvântul final), `beep` (WebAudio), `confetti`.
|
|
||||||
- `SNIP.*`: fragmente de template partajate — `baseCss`, modal de întrebare (`modalCss/Html/Js`, folosit de arcade și point) și ecranul final (`finalCss/Html/Js`).
|
|
||||||
|
|
||||||
Tipuri de puzzle: `free` (răspuns liber), `tf` (adevărat/fals), `choice` (variante pe linii separate în `choices`, cea corectă prefixată cu `*`).
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Harness Playwright în `tests/smoke.mjs`. **Nu există `package.json` commitat** — produsul rămâne zero-dependențe. Instalare dev o singură dată:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm i -D @playwright/test && npx playwright install chromium
|
|
||||||
```
|
|
||||||
|
|
||||||
Rulare (fără npm scripts — direct `npx`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx playwright test tests/smoke.mjs --grep "@regresie" # regresie: 13 teste
|
|
||||||
npx playwright test tests/smoke.mjs --grep "@campanie" # campanie E2E: 8 teste
|
|
||||||
npx playwright test tests/smoke.mjs # suita completă: 21 teste
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status curent:** 21/21 trec (13 `@regresie` + 8 `@campanie`).
|
|
||||||
|
|
||||||
## Atenție la editare
|
|
||||||
|
|
||||||
- Motoarele de joc sunt template literals mari — backslash-urile din codul generat trebuie dublate (`\\u0300`, `\\n`), iar codul generat folosește `var`/`function` clasic intenționat.
|
|
||||||
- O schimbare în `libJS`/`SNIP` afectează toate cele 5 motoare; verifică fiecare stil în preview.
|
|
||||||
- `exemplu-*.html` sunt jocuri demo exportate din builder (câte unul per stil). Nu le edita manual — după modificări la motoare, regenerează-le prin exportul din builder.
|
|
||||||
- `index.html` e doar pagina de landing care leagă builder-ul și demo-urile.
|
|
||||||
- `node_modules/` (gitignored) e doar dev tooling Playwright. Produsul (fișierele HTML) rămâne zero-dependențe — merge offline de pe `file://`.
|
|
||||||
|
|||||||
8
TODOS.md
8
TODOS.md
@@ -54,10 +54,12 @@ portează în `escape-builder.html` (un singur fișier, integrare secvențială)
|
|||||||
dpad fizic; Chat header frosted + bule distincte + tile reward; Point fundal distinct +
|
dpad fizic; Chat header frosted + bule distincte + tile reward; Point fundal distinct +
|
||||||
fix contrast `.note` + ușă glow. `prefers-reduced-motion` peste tot. Toate 5 demo-uri
|
fix contrast `.note` + ușă glow. `prefers-reduced-motion` peste tot. Toate 5 demo-uri
|
||||||
regenerate. Smoke 21/21 + capturi pe fiecare stil.
|
regenerate. Smoke 21/21 + capturi pe fiecare stil.
|
||||||
- [!] **S4 — extinde `tests/smoke.mjs`** *(blocat de S3)* — bomberman, hartă, audio, regresie.
|
- [x] **S4 — extinde `tests/smoke.mjs`** *(GATA — 24/24)* — 3 teste noi: audio S1 (ctx running),
|
||||||
|
navigare overworld (mers tastatură + ieșire blocată), bomberman gameplay (bombă/AI/respawn).
|
||||||
|
Arbore AGENTS.md actualizat 21→24.
|
||||||
|
|
||||||
**Stare la 2026-06-13:** PR1 livrat (`a42c960`, suita 21/21). Iterația 2 = neîncepută;
|
**Stare la 2026-06-13:** PR1 livrat (`a42c960`). **Iterația 2 COMPLETĂ** — S1+S2+S3+S4 livrate
|
||||||
`scratch/` are doar artefacte PR1, fără proto-uri noi.
|
și verificate. Suita 24/24. Comituri: S1 `52f97af`, S2c `a9f3065`, S3 `4454df9` (+pas1/pas2).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
37
tests/AGENTS.md
Normal file
37
tests/AGENTS.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# tests/ — Harness Playwright
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Smoke + regresie + campanie E2E pentru jocurile generate. Verifică faptic: fiecare stil se rezolvă
|
||||||
|
până la ecranul final, fără erori de consolă.
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
- `tests/smoke.mjs` — unicul fișier de teste (~24 teste).
|
||||||
|
- `playwright.config.mjs` (la root, **gitignored**) — config dev.
|
||||||
|
|
||||||
|
## Local Contracts
|
||||||
|
- **NU commita `package.json` / `package-lock.json` / `playwright.config.mjs`** — produsul rămâne
|
||||||
|
zero-dependențe. Instalarea dev e o singură dată: `npm i -D @playwright/test && npx playwright install chromium`.
|
||||||
|
- **Fără npm scripts** — se rulează direct cu `npx`.
|
||||||
|
- **Teste pe `file://`** — helper-ul `fileURL(name)` mapează cale relativă la `file://`; campania scrie
|
||||||
|
HTML temp generat via builder (`gameHTML`) și-l încarcă de pe `file://`.
|
||||||
|
- **Zero erori consolă = invariant.** `trackErrors(page)` colectează `console.error` + `pageerror`;
|
||||||
|
fiecare test asertează `errors.length === 0` la final.
|
||||||
|
- **Tag-uri:** `@regresie` (14 — exemplu-*.html + edge cases + mobil 320px + regenerare via gameHTML +
|
||||||
|
bomberman gameplay) și `@campanie` (10 — intro→hartă→camere→final, resume, cameră moartă,
|
||||||
|
idempotență ușă, `$`/`$&`, beep, mobil, audio S1, navigare overworld).
|
||||||
|
- **Status țintă: 24/24 PASS.**
|
||||||
|
|
||||||
|
## Work Guidance
|
||||||
|
- După modificări la motoare (`escape-builder.html`): rulează suita completă; extinde `@regresie` dacă
|
||||||
|
adaugi/schimbi un stil, `@campanie` pentru contractul de montare.
|
||||||
|
- Nu testa pe screenshot-uri de pixeli — asertează stare/text/erori.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
```bash
|
||||||
|
npx playwright test tests/smoke.mjs # 24/24
|
||||||
|
npx playwright test tests/smoke.mjs --grep @regresie
|
||||||
|
npx playwright test tests/smoke.mjs --grep @campanie
|
||||||
|
```
|
||||||
|
|
||||||
|
## Child DOX Index
|
||||||
|
(none — leaf)
|
||||||
108
tests/smoke.mjs
108
tests/smoke.mjs
@@ -944,4 +944,112 @@ test.describe('Campanie E2E @campanie', () => {
|
|||||||
expect(errors, errors.join('\n')).toHaveLength(0);
|
expect(errors, errors.join('\n')).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
// Test 9 (S4): Audio — AudioContext deblocat la "Incepe aventura" (S1)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
test('audio — AudioContext deblocat la Incepe aventura (S1) @campanie',
|
||||||
|
async ({ page }) => {
|
||||||
|
const cfg = campaignCfg(3, 'classic');
|
||||||
|
const tmpPath = await writeCampaignHtml(page, cfg, 'audio');
|
||||||
|
const gp = await page.context().newPage();
|
||||||
|
try {
|
||||||
|
await gp.goto('file://' + tmpPath);
|
||||||
|
// Inainte de gest: ctx inexistent (creat lazy)
|
||||||
|
const before = await gp.evaluate(
|
||||||
|
() => (window.beep && window.beep._ctx) ? window.beep._ctx.state : 'NO_CTX'
|
||||||
|
);
|
||||||
|
expect(before, 'ctx nu trebuie sa existe inainte de gest').toBe('NO_CTX');
|
||||||
|
// Gestul pe parinte deblocheaza ctx-ul
|
||||||
|
await gp.locator('#btn-start').click();
|
||||||
|
await gp.waitForTimeout(200);
|
||||||
|
const after = await gp.evaluate(
|
||||||
|
() => (window.beep && window.beep._ctx) ? window.beep._ctx.state : 'NO_CTX'
|
||||||
|
);
|
||||||
|
expect(after, 'ctx trebuie running dupa Incepe aventura').toBe('running');
|
||||||
|
} finally {
|
||||||
|
await gp.close();
|
||||||
|
try { unlinkSync(tmpPath); } catch (_) {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
// Test 10 (S4): Overworld — mers cu tastatura + iesire blocata pana la final
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
test('overworld — mers cu tastatura + iesire blocata pana la final @campanie',
|
||||||
|
async ({ page }) => {
|
||||||
|
const cfg = campaignCfg(3, 'classic');
|
||||||
|
const tmpPath = await writeCampaignHtml(page, cfg, 'ow-nav');
|
||||||
|
const gp = await page.context().newPage();
|
||||||
|
const gameErrors = trackErrors(gp);
|
||||||
|
try {
|
||||||
|
await gp.goto('file://' + tmpPath);
|
||||||
|
await gp.locator('#btn-start').click();
|
||||||
|
await waitOverworld(gp);
|
||||||
|
|
||||||
|
// Mers cu tastatura: ArrowRight muta jucatorul o celula la dreapta
|
||||||
|
const p0 = await gp.evaluate(() => window.__ow.state.player);
|
||||||
|
await gp.locator('body').press('ArrowRight');
|
||||||
|
const p1 = await gp.evaluate(() => window.__ow.state.player);
|
||||||
|
expect(p1.col, 'jucatorul nu s-a miscat cu tastatura').toBe(p0.col + 1);
|
||||||
|
|
||||||
|
// Iesirea (steag) e blocata pana la rezolvarea tuturor camerelor
|
||||||
|
expect(await gp.evaluate(() => window.__ow.state.allDone)).toBe(false);
|
||||||
|
await gp.evaluate(() => window.__ow.enterExit());
|
||||||
|
const finaleShown = await gp.evaluate(
|
||||||
|
() => document.getElementById('finale')?.classList.contains('show')
|
||||||
|
);
|
||||||
|
expect(finaleShown, 'finalul nu trebuie sa apara cu iesirea blocata').toBe(false);
|
||||||
|
} finally {
|
||||||
|
await gp.close();
|
||||||
|
try { unlinkSync(tmpPath); } catch (_) {}
|
||||||
|
}
|
||||||
|
expect(gameErrors, gameErrors.join('\n')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
// Test 11 (S4): Arcade Bomberman — bomba/AI/respawn pe demo-ul generat
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
test('arcade bomberman — bomba sparge cutie + AI urmareste + respawn pastreaza progres @regresie',
|
||||||
|
async ({ page }) => {
|
||||||
|
const errors = trackErrors(page);
|
||||||
|
await page.goto(fileURL('exemplu-arcade.html'));
|
||||||
|
await page.waitForFunction(() => typeof window.__game !== 'undefined', { timeout: 5000 });
|
||||||
|
await page.evaluate(() => { window.__seed = 42; window.__game.restartWithSeed(42); });
|
||||||
|
|
||||||
|
// Stare initiala: 3 vieti, dusmani prezenti
|
||||||
|
const init = await page.evaluate(() => ({ lives: window.__game.lives, enemies: window.__game.enemiesCount }));
|
||||||
|
expect(init.lives).toBe(3);
|
||||||
|
expect(init.enemies).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Bomba sparge o cutie adiacenta
|
||||||
|
await page.evaluate(() => window.__game.setTile(2, 1, 2));
|
||||||
|
expect(await page.evaluate(() => window.__game.getTile(2, 1))).toBe(2);
|
||||||
|
await page.evaluate(() => { window.__game.placeBomb(); window.__game.explodeAllBombs(); });
|
||||||
|
expect(await page.evaluate(() => window.__game.getTile(2, 1)), 'cutia nu s-a distrus').toBe(0);
|
||||||
|
|
||||||
|
// AI: cu cale libera, BFS returneaza un pas care se apropie de jucator
|
||||||
|
const step = await page.evaluate(() => {
|
||||||
|
const m = window.__game.map;
|
||||||
|
for (let y = 0; y < m.length; y++) for (let x = 0; x < m[0].length; x++) if (m[y][x] === 2) window.__game.setTile(x, y, 0);
|
||||||
|
return window.__game.bfsStep(7, 7, 1, 1);
|
||||||
|
});
|
||||||
|
expect(step, 'BFS nu a gasit cale spre jucator').not.toBeNull();
|
||||||
|
expect(Math.abs(step.x - 1) + Math.abs(step.y - 1)).toBeLessThan((7 - 1) + (7 - 1));
|
||||||
|
|
||||||
|
// Respawn pastreaza progresul puzzle
|
||||||
|
await page.evaluate(() => window.__game.solveDoor(0));
|
||||||
|
await page.evaluate(() => window.__game.killPlayer());
|
||||||
|
await page.waitForTimeout(1800);
|
||||||
|
const st = await page.evaluate(() => ({
|
||||||
|
lives: window.__game.lives,
|
||||||
|
solved: window.__game.puzzleProgress.doorsSolved[0],
|
||||||
|
alive: window.__game.player.alive
|
||||||
|
}));
|
||||||
|
expect(st.lives, 'viata nu a scazut').toBe(2);
|
||||||
|
expect(st.solved, 'progresul puzzle s-a pierdut la respawn').toBe(true);
|
||||||
|
expect(st.alive, 'jucatorul nu a respawnat').toBe(true);
|
||||||
|
|
||||||
|
expect(errors, errors.join('\n')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user