bomberman: sunete (sfx), raza initiala 1, powerup-uri raza/bombe
Feedback user: nu se aud sunete, raza prea mare, lipsesc powerup-urile. - sfx(type) WebAudio local in arcade: bomb/explosion/enemy/powerup/death; beep(ok) din libJS ramane pentru raspuns corect/gresit. - raza fixa EXPLOSION_RANGE=3 -> bombRange variabil de la BASE_RANGE=1 (Bomberman clasic); maxBombs de la BASE_BOMBS=1. - powerup-uri: la spargerea cutiei, sansa 0.32 sa cada flacara (raza+1) sau bomba (bombe+1); ridicate mergand pe ele; HUD arata bombe/raza. - fix: powerup-ul cadea pe celula cutiei si checkExplosionHits il stergea instant -> colectez brokenBoxes, drop dupa checkExplosionHits. Hooks __game: powerups/bombRange/maxBombs/dropPowerupAt. Smoke 27/27. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,8 +20,8 @@ sursa de adevăr tehnică pentru agenți.
|
||||
python3 -m http.server 8000
|
||||
|
||||
# Teste (Playwright; fără package.json commitat — vezi tests/AGENTS.md):
|
||||
npx playwright test tests/smoke.mjs # suita completă: 26/26
|
||||
npx playwright test tests/smoke.mjs --grep @regresie # regresie: 14
|
||||
npx playwright test tests/smoke.mjs # suita completă: 27/27
|
||||
npx playwright test tests/smoke.mjs --grep @regresie # regresie: 15
|
||||
npx playwright test tests/smoke.mjs --grep @campanie # campanie E2E: 12
|
||||
```
|
||||
|
||||
|
||||
14
TODOS.md
14
TODOS.md
@@ -20,6 +20,20 @@ Referință plan complet: `~/.gstack/projects/romfast-escape-builder/ceo-plans/2
|
||||
**PR2 livrat (2026-06-13):** audio camere `651025b`, voce `da93d84`, unificare `ab11089`, a11y (acest commit).
|
||||
Rămas din Etapa 2: D7 (migrare classic pe libJS+SNIP) + muzică timer (T10) + Adventure Mode v0.
|
||||
|
||||
### [x] Bomberman polish (feedback user 2026-06-13) — LIVRAT
|
||||
Trei probleme raportate + o lipsă, toate în `gameArcade` (`escape-builder.html`):
|
||||
- **Fără sunete în joc** → adăugat `sfx(type)` (WebAudio local în iframe, deblocat de gesturile din
|
||||
arcade): `bomb` (plasare), `explosion` (zgomot filtrat lowpass + thump sine), `enemy` (dușman ucis),
|
||||
`powerup` (arpegiu), `death`. `beep(ok)` din libJS rămâne pt. răspuns corect/greșit.
|
||||
- **Rază prea mare** → `EXPLOSION_RANGE=3` const → `bombRange` variabil pornind de la `BASE_RANGE=1`
|
||||
(Bomberman clasic). Similar `maxBombs` de la `BASE_BOMBS=1`.
|
||||
- **Fără powerup-uri** → la spargerea unei cutii, șansă `POWERUP_CHANCE=0.32` să cadă 🔥 (rază+1) sau
|
||||
💣 (bombe+1). Ridicate mergând pe ele; persistă peste respawn, reset la `init()`. HUD arată 💣/🔥.
|
||||
- **Bug prins** (drop=0 inițial): powerup-ul cădea pe celula cutiei, iar `checkExplosionHits` îl ștergea
|
||||
instant ca fiind „pe o celulă de explozie". Fix: colectez `brokenBoxes`, dau drop DUPĂ `checkExplosionHits`.
|
||||
Teste noi: smoke #27 (rază 1 + drop supraviețuiește + pickup crește rază/bombe). Hooks `__game`:
|
||||
`powerups`/`bombRange`/`maxBombs`/`dropPowerupAt`. Verificat: smoke 27/27 + live (drop ~30%, 0 erori).
|
||||
|
||||
---
|
||||
|
||||
## ▶ BOARD ACTIV — Iterația 2 (Adventure Mode / restyle)
|
||||
|
||||
@@ -1046,7 +1046,7 @@ ${SNIP.baseCss}${SNIP.modalCss}${SNIP.finalCss}
|
||||
<h1>${esc(cfg.title)}</h1>
|
||||
<div id="hud"><span id="hudStep"></span><span id="hudStars"></span><div id="hudLetters"></div></div>
|
||||
<canvas id="cv"></canvas>
|
||||
<div class="help">Sageti / WASD = misca, Space sau 💣 = bomba. Sparge cutiile, evita dusmanii. Usile rosii = intrebari; cufarul auriu = scaparea.</div>
|
||||
<div class="help">Sageti / WASD = misca, Space sau 💣 = bomba. Sparge cutiile (uneori cad bonusuri: 🔥 raza, 💣 bombe in plus), evita dusmanii. Usile rosii = intrebari; cufarul auriu = scaparea.</div>
|
||||
<div id="dpad"><button data-d="L" aria-label="Stanga">◀</button><button data-d="U" aria-label="Sus">▲</button><button data-d="D" aria-label="Jos">▼</button><button data-d="R" aria-label="Dreapta">▶</button><button id="btnBomb" aria-label="Pune bomba">💣</button></div>
|
||||
<div id="goOverlay"><div id="goCard"><div id="goMsg"></div><button id="goRestart">Incearca din nou</button></div></div>
|
||||
${SNIP.modalHtml}
|
||||
@@ -1062,12 +1062,37 @@ window.__seed = __seed;
|
||||
function makePRNG(seed){ var s = seed >>> 0; return function(){ s |= 0; s = (s + 0x6d2b79f5) | 0; var t = Math.imul(s ^ (s >>> 15), 1 | s); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }
|
||||
var rng = makePRNG(__seed);
|
||||
|
||||
/* ----- Efecte sonore arcade (WebAudio local; deblocat de gesturile din iframe) -----
|
||||
beep(ok) din libJS ramane pentru raspuns corect/gresit; sfx() adauga bomba/explozie/powerup. */
|
||||
function sfx(type){
|
||||
try {
|
||||
var actx = sfx.ctx || (sfx.ctx = new (window.AudioContext || window.webkitAudioContext)());
|
||||
if (actx.state === 'suspended') actx.resume();
|
||||
var t = actx.currentTime;
|
||||
function tone(wave, f0, f1, dur, vol){ var o = actx.createOscillator(), g = actx.createGain(); o.type = wave; o.frequency.setValueAtTime(f0, t); if (f1 !== f0) o.frequency.exponentialRampToValueAtTime(f1, t + dur); g.gain.setValueAtTime(vol, t); g.gain.exponentialRampToValueAtTime(0.0008, t + dur); o.connect(g); g.connect(actx.destination); o.start(t); o.stop(t + dur + 0.02); }
|
||||
if (type === 'bomb'){ tone('square', 440, 150, 0.1, 0.07); }
|
||||
else if (type === 'explosion'){
|
||||
var dur = 0.45, sr = actx.sampleRate, buf = actx.createBuffer(1, Math.floor(sr * dur), sr), data = buf.getChannelData(0);
|
||||
for (var i = 0; i < data.length; i++){ var k = 1 - i / data.length; data[i] = (Math.random() * 2 - 1) * k * k; }
|
||||
var src = actx.createBufferSource(); src.buffer = buf;
|
||||
var lp = actx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.setValueAtTime(1100, t); lp.frequency.exponentialRampToValueAtTime(180, t + dur);
|
||||
var g = actx.createGain(); g.gain.setValueAtTime(0.38, t); g.gain.exponentialRampToValueAtTime(0.0008, t + dur);
|
||||
src.connect(lp); lp.connect(g); g.connect(actx.destination); src.start(t);
|
||||
tone('sine', 130, 42, 0.34, 0.3);
|
||||
}
|
||||
else if (type === 'enemy'){ tone('square', 200, 520, 0.14, 0.08); }
|
||||
else if (type === 'powerup'){ var fs = [523, 659, 784, 1047]; for (var p = 0; p < fs.length; p++){ var o = actx.createOscillator(), gg = actx.createGain(); o.type = 'triangle'; o.frequency.value = fs[p]; gg.gain.setValueAtTime(0.08, t + p * 0.06); gg.gain.exponentialRampToValueAtTime(0.0008, t + p * 0.06 + 0.13); o.connect(gg); gg.connect(actx.destination); o.start(t + p * 0.06); o.stop(t + p * 0.06 + 0.15); } }
|
||||
else if (type === 'death'){ tone('sawtooth', 330, 55, 0.5, 0.12); }
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
var GW = 15, GH = 13, TS = 36;
|
||||
var T_FLOOR = 0, T_WALL = 1, T_BOX = 2, T_DOOR = 3, T_CHEST = 4;
|
||||
var BOMB_TIMER = 2400, EXPLOSION_TIME = 500, EXPLOSION_RANGE = 3, ENEMY_INTERVAL = 600, RESPAWN_DELAY = 1500, INVINCIBLE_TIME = 2000, MAX_LIVES = 3;
|
||||
var BOMB_TIMER = 2400, EXPLOSION_TIME = 500, BASE_RANGE = 1, BASE_BOMBS = 1, POWERUP_CHANCE = 0.32, ENEMY_INTERVAL = 600, RESPAWN_DELAY = 1500, INVINCIBLE_TIME = 2000, MAX_LIVES = 3;
|
||||
var NUM_DOORS = N, NUM_ENEMIES = Math.max(2, Math.min(4, N + 1));
|
||||
var P_RANGE = 'range', P_BOMB = 'bomb';
|
||||
|
||||
var map, player, enemies, bombs, explosions, lives, gameOver, gameWon, puzzleProgress, doorMeta, chestPos;
|
||||
var map, player, enemies, bombs, explosions, lives, gameOver, gameWon, puzzleProgress, doorMeta, chestPos, powerups, bombRange, maxBombs;
|
||||
var animFrame, lastTime = 0, enemyTimer = 0, invincibleTimer = 0, bombIdCounter = 0;
|
||||
|
||||
var cv = el('cv'); cv.width = GW * TS; cv.height = GH * TS;
|
||||
@@ -1107,7 +1132,7 @@ function init(){
|
||||
shuffle(ec);
|
||||
enemies = [];
|
||||
for (var i = 0; i < NUM_ENEMIES && i < ec.length; i++) enemies.push({ x: ec[i].x, y: ec[i].y, alive: true, id: i });
|
||||
bombs = []; explosions = []; lives = MAX_LIVES; gameOver = false; gameWon = false; enemyTimer = 0; invincibleTimer = 0; lastTime = 0;
|
||||
bombs = []; explosions = []; powerups = []; bombRange = BASE_RANGE; maxBombs = BASE_BOMBS; lives = MAX_LIVES; gameOver = false; gameWon = false; enemyTimer = 0; invincibleTimer = 0; lastTime = 0;
|
||||
if (!puzzleProgress) puzzleProgress = { doorsSolved: [] };
|
||||
for (var dd = 0; dd < doorMeta.length; dd++) if (puzzleProgress.doorsSolved[dd]) map[doorMeta[dd].y][doorMeta[dd].x] = T_FLOOR;
|
||||
hideGameOver();
|
||||
@@ -1131,7 +1156,7 @@ el('goRestart').onclick = function(){ init(); };
|
||||
function updateHud(){
|
||||
var solved = puzzleProgress ? puzzleProgress.doorsSolved.filter(Boolean).length : 0;
|
||||
var alive = enemies ? enemies.filter(function(e){ return e.alive; }).length : 0;
|
||||
el('hudStep').textContent = '\\u2764\\ufe0f ' + lives + ' \\ud83d\\udc7e ' + alive + ' \\ud83d\\udd13 ' + solved + '/' + N;
|
||||
el('hudStep').textContent = '\\u2764\\ufe0f ' + lives + ' \\ud83d\\udc7e ' + alive + ' \\ud83d\\udd13 ' + solved + '/' + N + ' \\ud83d\\udca3' + (maxBombs || 1) + ' \\ud83d\\udd25' + (bombRange || 1);
|
||||
el('hudStars').textContent = totalStars + ' \\u2605';
|
||||
var hb = el('hudLetters'); hb.innerHTML = '';
|
||||
for (var j = 0; j < N; j++){ var L = (CFG.puzzles[j].letter || '').trim(); if (!L) continue; var s = document.createElement('span'); if (puzzleProgress && puzzleProgress.doorsSolved[j]){ s.textContent = L.toUpperCase(); s.className = 'won'; } else s.textContent = '?'; hb.appendChild(s); }
|
||||
@@ -1140,27 +1165,47 @@ function updateHud(){
|
||||
/* ----- Bombe + explozii în lanț ----- */
|
||||
function placeBomb(){
|
||||
if (!player.alive || gameOver || gameWon || modalOpen()) return;
|
||||
if (bombs.length >= 1) return;
|
||||
if (bombs.length >= maxBombs) return;
|
||||
for (var i = 0; i < bombs.length; i++) if (bombs[i].x === player.x && bombs[i].y === player.y) return;
|
||||
bombs.push({ x: player.x, y: player.y, timer: BOMB_TIMER, id: bombIdCounter++ });
|
||||
sfx('bomb');
|
||||
updateHud();
|
||||
}
|
||||
function explodeBomb(bomb){
|
||||
bombs = bombs.filter(function(b){ return b.id !== bomb.id; });
|
||||
var cells = [{ x: bomb.x, y: bomb.y }];
|
||||
var brokenBoxes = [];
|
||||
var dirs = [[1,0],[-1,0],[0,1],[0,-1]];
|
||||
for (var d = 0; d < dirs.length; d++){ var dx = dirs[d][0], dy = dirs[d][1]; for (var r = 1; r <= EXPLOSION_RANGE; r++){ var cx = bomb.x + dx * r, cy = bomb.y + dy * r; if (cx < 0 || cy < 0 || cx >= GW || cy >= GH) break; var t = map[cy][cx]; if (t === T_WALL) break; cells.push({ x: cx, y: cy }); if (t === T_BOX){ map[cy][cx] = T_FLOOR; break; } if (t === T_DOOR || t === T_CHEST) break; } }
|
||||
for (var d = 0; d < dirs.length; d++){ var dx = dirs[d][0], dy = dirs[d][1]; for (var r = 1; r <= bombRange; r++){ var cx = bomb.x + dx * r, cy = bomb.y + dy * r; if (cx < 0 || cy < 0 || cx >= GW || cy >= GH) break; var t = map[cy][cx]; if (t === T_WALL) break; cells.push({ x: cx, y: cy }); if (t === T_BOX){ map[cy][cx] = T_FLOOR; brokenBoxes.push({ x: cx, y: cy }); break; } if (t === T_DOOR || t === T_CHEST) break; } }
|
||||
explosions.push({ cells: cells, endTime: performance.now() + EXPLOSION_TIME });
|
||||
sfx('explosion');
|
||||
var chain = bombs.slice();
|
||||
for (var i = 0; i < chain.length; i++){ var bb = chain[i]; for (var c = 0; c < cells.length; c++) if (cells[c].x === bb.x && cells[c].y === bb.y){ explodeBomb(bb); break; } }
|
||||
checkExplosionHits(cells);
|
||||
/* drop DUPA checkExplosionHits: altfel powerup-ul de pe celula cutiei e sters instant de filtrul de explozie */
|
||||
for (var bx = 0; bx < brokenBoxes.length; bx++) maybeDropPowerup(brokenBoxes[bx].x, brokenBoxes[bx].y);
|
||||
updateHud();
|
||||
}
|
||||
function checkExplosionHits(cells){
|
||||
for (var c = 0; c < cells.length; c++){ var cx = cells[c].x, cy = cells[c].y; for (var i = 0; i < enemies.length; i++) if (enemies[i].alive && enemies[i].x === cx && enemies[i].y === cy) enemies[i].alive = false; if (player.alive && !player.invincible && player.x === cx && player.y === cy) killPlayer(); }
|
||||
for (var c = 0; c < cells.length; c++){ var cx = cells[c].x, cy = cells[c].y;
|
||||
for (var i = 0; i < enemies.length; i++) if (enemies[i].alive && enemies[i].x === cx && enemies[i].y === cy){ enemies[i].alive = false; sfx('enemy'); }
|
||||
powerups = powerups.filter(function(p){ return !(p.x === cx && p.y === cy); });
|
||||
if (player.alive && !player.invincible && player.x === cx && player.y === cy) killPlayer();
|
||||
}
|
||||
}
|
||||
function maybeDropPowerup(x, y){
|
||||
if (rng() >= POWERUP_CHANCE) return;
|
||||
powerups.push({ x: x, y: y, type: rng() < 0.5 ? P_RANGE : P_BOMB });
|
||||
}
|
||||
function pickupPowerup(){
|
||||
for (var i = 0; i < powerups.length; i++) if (powerups[i].x === player.x && powerups[i].y === player.y){
|
||||
if (powerups[i].type === P_RANGE) bombRange++; else maxBombs++;
|
||||
powerups.splice(i, 1); sfx('powerup'); updateHud(); return;
|
||||
}
|
||||
}
|
||||
function killPlayer(){
|
||||
if (!player.alive) return;
|
||||
player.alive = false; lives--; updateHud();
|
||||
player.alive = false; lives--; sfx('death'); updateHud();
|
||||
setTimeout(function(){ if (lives > 0) respawn(); else showGameOver(); }, RESPAWN_DELAY);
|
||||
}
|
||||
|
||||
@@ -1177,6 +1222,7 @@ function movePlayer(dir){
|
||||
if (t === T_DOOR){ for (var d = 0; d < doorMeta.length; d++) if (doorMeta[d].x === nx && doorMeta[d].y === ny){ if (!puzzleProgress.doorsSolved[d]) openPuzzle(d, onDoorSolved); return; } return; }
|
||||
if (t === T_CHEST){ player.x = nx; player.y = ny; gameWon = true; showFinal(); return; }
|
||||
player.x = nx; player.y = ny;
|
||||
pickupPowerup();
|
||||
checkPlayerEnemyCollision();
|
||||
}
|
||||
function onDoorSolved(id){
|
||||
@@ -1225,6 +1271,7 @@ function draw(now){
|
||||
else if (t === T_CHEST){ drawFloor(px, py, x, y, isExp); drawChest(px, py); }
|
||||
else drawFloor(px, py, x, y, isExp);
|
||||
}
|
||||
for (var pu = 0; pu < powerups.length; pu++) drawPowerup(powerups[pu], now);
|
||||
for (var bi = 0; bi < bombs.length; bi++) drawBomb(bombs[bi], now);
|
||||
for (var en = 0; en < enemies.length; en++) if (enemies[en].alive) drawEnemy(enemies[en]);
|
||||
if (player.alive) drawPlayer(now);
|
||||
@@ -1235,6 +1282,7 @@ function drawBox(px, py, isExp){ if (isExp){ ctx.fillStyle = '#f97316'; ctx.fill
|
||||
function drawDoor(px, py){ ctx.fillStyle = '#9f1239'; ctx.fillRect(px+3, py+2, TS-6, TS-4); ctx.fillStyle = '#e11d48'; ctx.fillRect(px+6, py+5, TS-12, TS-10); ctx.fillStyle = '#fbbf24'; ctx.fillRect(px+TS/2-2, py+TS/2-2, 4, 7); }
|
||||
function drawChest(px, py){ ctx.fillStyle = '#92400e'; ctx.fillRect(px+4, py+8, TS-8, TS-14); ctx.fillStyle = '#f59e0b'; ctx.fillRect(px+4, py+8, TS-8, 8); ctx.fillStyle = '#fde68a'; ctx.fillRect(px+TS/2-3, py+11, 6, 10); ctx.fillStyle = '#fbbf24'; ctx.fillRect(px+TS/2-2, py+13, 4, 4); }
|
||||
function drawBomb(bomb, now){ var px = bomb.x * TS, py = bomb.y * TS; var pulse = Math.sin(now / 150) * 0.3 + 0.7; ctx.fillStyle = 'rgba(30,10,10,' + pulse + ')'; ctx.beginPath(); ctx.arc(px + TS/2, py + TS/2, TS/2 - 4, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = '#f87171'; ctx.lineWidth = 2; ctx.stroke(); ctx.strokeStyle = '#f59e0b'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(px + TS/2 + 4, py + 6); ctx.quadraticCurveTo(px + TS/2 + 10, py + 2, px + TS/2 + 7, py - 2); ctx.stroke(); }
|
||||
function drawPowerup(p, now){ var px = p.x * TS, py = p.y * TS, cx = px + TS/2, cy = py + TS/2, pulse = Math.sin(now / 200) * 0.12 + 0.88; var isR = p.type === P_RANGE; ctx.fillStyle = isR ? 'rgba(249,115,22,.25)' : 'rgba(59,130,246,.25)'; ctx.fillRect(px + 3, py + 3, TS - 6, TS - 6); ctx.fillStyle = isR ? '#f97316' : '#3b82f6'; ctx.beginPath(); ctx.arc(cx, cy, (TS/2 - 6) * pulse, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#fff'; if (isR){ ctx.beginPath(); ctx.moveTo(cx, cy - 7); ctx.lineTo(cx + 5, cy + 6); ctx.lineTo(cx, cy + 2); ctx.lineTo(cx - 5, cy + 6); ctx.closePath(); ctx.fill(); } else { ctx.beginPath(); ctx.arc(cx, cy + 1, 5, 0, Math.PI*2); ctx.fill(); ctx.fillRect(cx - 1, cy - 8, 2, 4); } }
|
||||
function drawEnemy(e){ var px = e.x * TS, py = e.y * TS; ctx.fillStyle = '#dc2626'; ctx.fillRect(px+6, py+8, TS-12, TS-12); ctx.fillStyle = '#fff'; ctx.fillRect(px+9, py+12, 4, 4); ctx.fillRect(px+TS-13, py+12, 4, 4); ctx.fillStyle = '#000'; ctx.fillRect(px+10, py+13, 2, 2); ctx.fillRect(px+TS-12, py+13, 2, 2); ctx.fillStyle = '#b91c1c'; ctx.fillRect(px+8, py+TS-8, 4, 5); ctx.fillRect(px+TS-12, py+TS-8, 4, 5); }
|
||||
function drawPlayer(now){ var px = player.x * TS, py = player.y * TS; if (player.invincible && Math.floor(now / 100) % 2 === 0) return; ctx.fillStyle = CFG.color || '#6d28d9'; ctx.fillRect(px+6, py+5, TS-12, TS-10); ctx.fillStyle = '#fff'; ctx.fillRect(px+10, py+10, 4, 4); ctx.fillRect(px+TS-14, py+10, 4, 4); ctx.fillStyle = '#000'; ctx.fillRect(px+11, py+11, 2, 2); ctx.fillRect(px+TS-13, py+11, 2, 2); ctx.fillStyle = '#fff'; ctx.fillRect(px+11, py+TS-12, TS-22, 3); }
|
||||
|
||||
@@ -1254,6 +1302,10 @@ window.__game = {
|
||||
get enemiesCount(){ return enemies ? enemies.filter(function(e){ return e.alive; }).length : 0; },
|
||||
get puzzleProgress(){ return puzzleProgress; },
|
||||
get bombs(){ return bombs ? bombs.slice() : []; },
|
||||
get powerups(){ return powerups ? powerups.slice() : []; },
|
||||
get bombRange(){ return bombRange; },
|
||||
get maxBombs(){ return maxBombs; },
|
||||
dropPowerupAt: function(x, y, type){ powerups.push({ x: x, y: y, type: type || P_RANGE }); },
|
||||
get gameOver(){ return gameOver; },
|
||||
get gameWon(){ return gameWon; },
|
||||
get player(){ return player ? { x: player.x, y: player.y, alive: player.alive, invincible: player.invincible } : null; },
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<h1>Comoara ascunsa</h1>
|
||||
<div id="hud"><span id="hudStep"></span><span id="hudStars"></span><div id="hudLetters"></div></div>
|
||||
<canvas id="cv"></canvas>
|
||||
<div class="help">Sageti / WASD = misca, Space sau 💣 = bomba. Sparge cutiile, evita dusmanii. Usile rosii = intrebari; cufarul auriu = scaparea.</div>
|
||||
<div class="help">Sageti / WASD = misca, Space sau 💣 = bomba. Sparge cutiile (uneori cad bonusuri: 🔥 raza, 💣 bombe in plus), evita dusmanii. Usile rosii = intrebari; cufarul auriu = scaparea.</div>
|
||||
<div id="dpad"><button data-d="L" aria-label="Stanga">◀</button><button data-d="U" aria-label="Sus">▲</button><button data-d="D" aria-label="Jos">▼</button><button data-d="R" aria-label="Dreapta">▶</button><button id="btnBomb" aria-label="Pune bomba">💣</button></div>
|
||||
<div id="goOverlay"><div id="goCard"><div id="goMsg"></div><button id="goRestart">Incearca din nou</button></div></div>
|
||||
<div id="mOverlay"><div id="mCard">
|
||||
@@ -111,12 +111,37 @@ window.__seed = __seed;
|
||||
function makePRNG(seed){ var s = seed >>> 0; return function(){ s |= 0; s = (s + 0x6d2b79f5) | 0; var t = Math.imul(s ^ (s >>> 15), 1 | s); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }
|
||||
var rng = makePRNG(__seed);
|
||||
|
||||
/* ----- Efecte sonore arcade (WebAudio local; deblocat de gesturile din iframe) -----
|
||||
beep(ok) din libJS ramane pentru raspuns corect/gresit; sfx() adauga bomba/explozie/powerup. */
|
||||
function sfx(type){
|
||||
try {
|
||||
var actx = sfx.ctx || (sfx.ctx = new (window.AudioContext || window.webkitAudioContext)());
|
||||
if (actx.state === 'suspended') actx.resume();
|
||||
var t = actx.currentTime;
|
||||
function tone(wave, f0, f1, dur, vol){ var o = actx.createOscillator(), g = actx.createGain(); o.type = wave; o.frequency.setValueAtTime(f0, t); if (f1 !== f0) o.frequency.exponentialRampToValueAtTime(f1, t + dur); g.gain.setValueAtTime(vol, t); g.gain.exponentialRampToValueAtTime(0.0008, t + dur); o.connect(g); g.connect(actx.destination); o.start(t); o.stop(t + dur + 0.02); }
|
||||
if (type === 'bomb'){ tone('square', 440, 150, 0.1, 0.07); }
|
||||
else if (type === 'explosion'){
|
||||
var dur = 0.45, sr = actx.sampleRate, buf = actx.createBuffer(1, Math.floor(sr * dur), sr), data = buf.getChannelData(0);
|
||||
for (var i = 0; i < data.length; i++){ var k = 1 - i / data.length; data[i] = (Math.random() * 2 - 1) * k * k; }
|
||||
var src = actx.createBufferSource(); src.buffer = buf;
|
||||
var lp = actx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.setValueAtTime(1100, t); lp.frequency.exponentialRampToValueAtTime(180, t + dur);
|
||||
var g = actx.createGain(); g.gain.setValueAtTime(0.38, t); g.gain.exponentialRampToValueAtTime(0.0008, t + dur);
|
||||
src.connect(lp); lp.connect(g); g.connect(actx.destination); src.start(t);
|
||||
tone('sine', 130, 42, 0.34, 0.3);
|
||||
}
|
||||
else if (type === 'enemy'){ tone('square', 200, 520, 0.14, 0.08); }
|
||||
else if (type === 'powerup'){ var fs = [523, 659, 784, 1047]; for (var p = 0; p < fs.length; p++){ var o = actx.createOscillator(), gg = actx.createGain(); o.type = 'triangle'; o.frequency.value = fs[p]; gg.gain.setValueAtTime(0.08, t + p * 0.06); gg.gain.exponentialRampToValueAtTime(0.0008, t + p * 0.06 + 0.13); o.connect(gg); gg.connect(actx.destination); o.start(t + p * 0.06); o.stop(t + p * 0.06 + 0.15); } }
|
||||
else if (type === 'death'){ tone('sawtooth', 330, 55, 0.5, 0.12); }
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
var GW = 15, GH = 13, TS = 36;
|
||||
var T_FLOOR = 0, T_WALL = 1, T_BOX = 2, T_DOOR = 3, T_CHEST = 4;
|
||||
var BOMB_TIMER = 2400, EXPLOSION_TIME = 500, EXPLOSION_RANGE = 3, ENEMY_INTERVAL = 600, RESPAWN_DELAY = 1500, INVINCIBLE_TIME = 2000, MAX_LIVES = 3;
|
||||
var BOMB_TIMER = 2400, EXPLOSION_TIME = 500, BASE_RANGE = 1, BASE_BOMBS = 1, POWERUP_CHANCE = 0.32, ENEMY_INTERVAL = 600, RESPAWN_DELAY = 1500, INVINCIBLE_TIME = 2000, MAX_LIVES = 3;
|
||||
var NUM_DOORS = N, NUM_ENEMIES = Math.max(2, Math.min(4, N + 1));
|
||||
var P_RANGE = 'range', P_BOMB = 'bomb';
|
||||
|
||||
var map, player, enemies, bombs, explosions, lives, gameOver, gameWon, puzzleProgress, doorMeta, chestPos;
|
||||
var map, player, enemies, bombs, explosions, lives, gameOver, gameWon, puzzleProgress, doorMeta, chestPos, powerups, bombRange, maxBombs;
|
||||
var animFrame, lastTime = 0, enemyTimer = 0, invincibleTimer = 0, bombIdCounter = 0;
|
||||
|
||||
var cv = el('cv'); cv.width = GW * TS; cv.height = GH * TS;
|
||||
@@ -156,7 +181,7 @@ function init(){
|
||||
shuffle(ec);
|
||||
enemies = [];
|
||||
for (var i = 0; i < NUM_ENEMIES && i < ec.length; i++) enemies.push({ x: ec[i].x, y: ec[i].y, alive: true, id: i });
|
||||
bombs = []; explosions = []; lives = MAX_LIVES; gameOver = false; gameWon = false; enemyTimer = 0; invincibleTimer = 0; lastTime = 0;
|
||||
bombs = []; explosions = []; powerups = []; bombRange = BASE_RANGE; maxBombs = BASE_BOMBS; lives = MAX_LIVES; gameOver = false; gameWon = false; enemyTimer = 0; invincibleTimer = 0; lastTime = 0;
|
||||
if (!puzzleProgress) puzzleProgress = { doorsSolved: [] };
|
||||
for (var dd = 0; dd < doorMeta.length; dd++) if (puzzleProgress.doorsSolved[dd]) map[doorMeta[dd].y][doorMeta[dd].x] = T_FLOOR;
|
||||
hideGameOver();
|
||||
@@ -180,7 +205,7 @@ el('goRestart').onclick = function(){ init(); };
|
||||
function updateHud(){
|
||||
var solved = puzzleProgress ? puzzleProgress.doorsSolved.filter(Boolean).length : 0;
|
||||
var alive = enemies ? enemies.filter(function(e){ return e.alive; }).length : 0;
|
||||
el('hudStep').textContent = '\u2764\ufe0f ' + lives + ' \ud83d\udc7e ' + alive + ' \ud83d\udd13 ' + solved + '/' + N;
|
||||
el('hudStep').textContent = '\u2764\ufe0f ' + lives + ' \ud83d\udc7e ' + alive + ' \ud83d\udd13 ' + solved + '/' + N + ' \ud83d\udca3' + (maxBombs || 1) + ' \ud83d\udd25' + (bombRange || 1);
|
||||
el('hudStars').textContent = totalStars + ' \u2605';
|
||||
var hb = el('hudLetters'); hb.innerHTML = '';
|
||||
for (var j = 0; j < N; j++){ var L = (CFG.puzzles[j].letter || '').trim(); if (!L) continue; var s = document.createElement('span'); if (puzzleProgress && puzzleProgress.doorsSolved[j]){ s.textContent = L.toUpperCase(); s.className = 'won'; } else s.textContent = '?'; hb.appendChild(s); }
|
||||
@@ -189,27 +214,47 @@ function updateHud(){
|
||||
/* ----- Bombe + explozii în lanț ----- */
|
||||
function placeBomb(){
|
||||
if (!player.alive || gameOver || gameWon || modalOpen()) return;
|
||||
if (bombs.length >= 1) return;
|
||||
if (bombs.length >= maxBombs) return;
|
||||
for (var i = 0; i < bombs.length; i++) if (bombs[i].x === player.x && bombs[i].y === player.y) return;
|
||||
bombs.push({ x: player.x, y: player.y, timer: BOMB_TIMER, id: bombIdCounter++ });
|
||||
sfx('bomb');
|
||||
updateHud();
|
||||
}
|
||||
function explodeBomb(bomb){
|
||||
bombs = bombs.filter(function(b){ return b.id !== bomb.id; });
|
||||
var cells = [{ x: bomb.x, y: bomb.y }];
|
||||
var brokenBoxes = [];
|
||||
var dirs = [[1,0],[-1,0],[0,1],[0,-1]];
|
||||
for (var d = 0; d < dirs.length; d++){ var dx = dirs[d][0], dy = dirs[d][1]; for (var r = 1; r <= EXPLOSION_RANGE; r++){ var cx = bomb.x + dx * r, cy = bomb.y + dy * r; if (cx < 0 || cy < 0 || cx >= GW || cy >= GH) break; var t = map[cy][cx]; if (t === T_WALL) break; cells.push({ x: cx, y: cy }); if (t === T_BOX){ map[cy][cx] = T_FLOOR; break; } if (t === T_DOOR || t === T_CHEST) break; } }
|
||||
for (var d = 0; d < dirs.length; d++){ var dx = dirs[d][0], dy = dirs[d][1]; for (var r = 1; r <= bombRange; r++){ var cx = bomb.x + dx * r, cy = bomb.y + dy * r; if (cx < 0 || cy < 0 || cx >= GW || cy >= GH) break; var t = map[cy][cx]; if (t === T_WALL) break; cells.push({ x: cx, y: cy }); if (t === T_BOX){ map[cy][cx] = T_FLOOR; brokenBoxes.push({ x: cx, y: cy }); break; } if (t === T_DOOR || t === T_CHEST) break; } }
|
||||
explosions.push({ cells: cells, endTime: performance.now() + EXPLOSION_TIME });
|
||||
sfx('explosion');
|
||||
var chain = bombs.slice();
|
||||
for (var i = 0; i < chain.length; i++){ var bb = chain[i]; for (var c = 0; c < cells.length; c++) if (cells[c].x === bb.x && cells[c].y === bb.y){ explodeBomb(bb); break; } }
|
||||
checkExplosionHits(cells);
|
||||
/* drop DUPA checkExplosionHits: altfel powerup-ul de pe celula cutiei e sters instant de filtrul de explozie */
|
||||
for (var bx = 0; bx < brokenBoxes.length; bx++) maybeDropPowerup(brokenBoxes[bx].x, brokenBoxes[bx].y);
|
||||
updateHud();
|
||||
}
|
||||
function checkExplosionHits(cells){
|
||||
for (var c = 0; c < cells.length; c++){ var cx = cells[c].x, cy = cells[c].y; for (var i = 0; i < enemies.length; i++) if (enemies[i].alive && enemies[i].x === cx && enemies[i].y === cy) enemies[i].alive = false; if (player.alive && !player.invincible && player.x === cx && player.y === cy) killPlayer(); }
|
||||
for (var c = 0; c < cells.length; c++){ var cx = cells[c].x, cy = cells[c].y;
|
||||
for (var i = 0; i < enemies.length; i++) if (enemies[i].alive && enemies[i].x === cx && enemies[i].y === cy){ enemies[i].alive = false; sfx('enemy'); }
|
||||
powerups = powerups.filter(function(p){ return !(p.x === cx && p.y === cy); });
|
||||
if (player.alive && !player.invincible && player.x === cx && player.y === cy) killPlayer();
|
||||
}
|
||||
}
|
||||
function maybeDropPowerup(x, y){
|
||||
if (rng() >= POWERUP_CHANCE) return;
|
||||
powerups.push({ x: x, y: y, type: rng() < 0.5 ? P_RANGE : P_BOMB });
|
||||
}
|
||||
function pickupPowerup(){
|
||||
for (var i = 0; i < powerups.length; i++) if (powerups[i].x === player.x && powerups[i].y === player.y){
|
||||
if (powerups[i].type === P_RANGE) bombRange++; else maxBombs++;
|
||||
powerups.splice(i, 1); sfx('powerup'); updateHud(); return;
|
||||
}
|
||||
}
|
||||
function killPlayer(){
|
||||
if (!player.alive) return;
|
||||
player.alive = false; lives--; updateHud();
|
||||
player.alive = false; lives--; sfx('death'); updateHud();
|
||||
setTimeout(function(){ if (lives > 0) respawn(); else showGameOver(); }, RESPAWN_DELAY);
|
||||
}
|
||||
|
||||
@@ -226,6 +271,7 @@ function movePlayer(dir){
|
||||
if (t === T_DOOR){ for (var d = 0; d < doorMeta.length; d++) if (doorMeta[d].x === nx && doorMeta[d].y === ny){ if (!puzzleProgress.doorsSolved[d]) openPuzzle(d, onDoorSolved); return; } return; }
|
||||
if (t === T_CHEST){ player.x = nx; player.y = ny; gameWon = true; showFinal(); return; }
|
||||
player.x = nx; player.y = ny;
|
||||
pickupPowerup();
|
||||
checkPlayerEnemyCollision();
|
||||
}
|
||||
function onDoorSolved(id){
|
||||
@@ -274,6 +320,7 @@ function draw(now){
|
||||
else if (t === T_CHEST){ drawFloor(px, py, x, y, isExp); drawChest(px, py); }
|
||||
else drawFloor(px, py, x, y, isExp);
|
||||
}
|
||||
for (var pu = 0; pu < powerups.length; pu++) drawPowerup(powerups[pu], now);
|
||||
for (var bi = 0; bi < bombs.length; bi++) drawBomb(bombs[bi], now);
|
||||
for (var en = 0; en < enemies.length; en++) if (enemies[en].alive) drawEnemy(enemies[en]);
|
||||
if (player.alive) drawPlayer(now);
|
||||
@@ -284,6 +331,7 @@ function drawBox(px, py, isExp){ if (isExp){ ctx.fillStyle = '#f97316'; ctx.fill
|
||||
function drawDoor(px, py){ ctx.fillStyle = '#9f1239'; ctx.fillRect(px+3, py+2, TS-6, TS-4); ctx.fillStyle = '#e11d48'; ctx.fillRect(px+6, py+5, TS-12, TS-10); ctx.fillStyle = '#fbbf24'; ctx.fillRect(px+TS/2-2, py+TS/2-2, 4, 7); }
|
||||
function drawChest(px, py){ ctx.fillStyle = '#92400e'; ctx.fillRect(px+4, py+8, TS-8, TS-14); ctx.fillStyle = '#f59e0b'; ctx.fillRect(px+4, py+8, TS-8, 8); ctx.fillStyle = '#fde68a'; ctx.fillRect(px+TS/2-3, py+11, 6, 10); ctx.fillStyle = '#fbbf24'; ctx.fillRect(px+TS/2-2, py+13, 4, 4); }
|
||||
function drawBomb(bomb, now){ var px = bomb.x * TS, py = bomb.y * TS; var pulse = Math.sin(now / 150) * 0.3 + 0.7; ctx.fillStyle = 'rgba(30,10,10,' + pulse + ')'; ctx.beginPath(); ctx.arc(px + TS/2, py + TS/2, TS/2 - 4, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = '#f87171'; ctx.lineWidth = 2; ctx.stroke(); ctx.strokeStyle = '#f59e0b'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(px + TS/2 + 4, py + 6); ctx.quadraticCurveTo(px + TS/2 + 10, py + 2, px + TS/2 + 7, py - 2); ctx.stroke(); }
|
||||
function drawPowerup(p, now){ var px = p.x * TS, py = p.y * TS, cx = px + TS/2, cy = py + TS/2, pulse = Math.sin(now / 200) * 0.12 + 0.88; var isR = p.type === P_RANGE; ctx.fillStyle = isR ? 'rgba(249,115,22,.25)' : 'rgba(59,130,246,.25)'; ctx.fillRect(px + 3, py + 3, TS - 6, TS - 6); ctx.fillStyle = isR ? '#f97316' : '#3b82f6'; ctx.beginPath(); ctx.arc(cx, cy, (TS/2 - 6) * pulse, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#fff'; if (isR){ ctx.beginPath(); ctx.moveTo(cx, cy - 7); ctx.lineTo(cx + 5, cy + 6); ctx.lineTo(cx, cy + 2); ctx.lineTo(cx - 5, cy + 6); ctx.closePath(); ctx.fill(); } else { ctx.beginPath(); ctx.arc(cx, cy + 1, 5, 0, Math.PI*2); ctx.fill(); ctx.fillRect(cx - 1, cy - 8, 2, 4); } }
|
||||
function drawEnemy(e){ var px = e.x * TS, py = e.y * TS; ctx.fillStyle = '#dc2626'; ctx.fillRect(px+6, py+8, TS-12, TS-12); ctx.fillStyle = '#fff'; ctx.fillRect(px+9, py+12, 4, 4); ctx.fillRect(px+TS-13, py+12, 4, 4); ctx.fillStyle = '#000'; ctx.fillRect(px+10, py+13, 2, 2); ctx.fillRect(px+TS-12, py+13, 2, 2); ctx.fillStyle = '#b91c1c'; ctx.fillRect(px+8, py+TS-8, 4, 5); ctx.fillRect(px+TS-12, py+TS-8, 4, 5); }
|
||||
function drawPlayer(now){ var px = player.x * TS, py = player.y * TS; if (player.invincible && Math.floor(now / 100) % 2 === 0) return; ctx.fillStyle = CFG.color || '#6d28d9'; ctx.fillRect(px+6, py+5, TS-12, TS-10); ctx.fillStyle = '#fff'; ctx.fillRect(px+10, py+10, 4, 4); ctx.fillRect(px+TS-14, py+10, 4, 4); ctx.fillStyle = '#000'; ctx.fillRect(px+11, py+11, 2, 2); ctx.fillRect(px+TS-13, py+11, 2, 2); ctx.fillStyle = '#fff'; ctx.fillRect(px+11, py+TS-12, TS-22, 3); }
|
||||
|
||||
@@ -303,6 +351,10 @@ window.__game = {
|
||||
get enemiesCount(){ return enemies ? enemies.filter(function(e){ return e.alive; }).length : 0; },
|
||||
get puzzleProgress(){ return puzzleProgress; },
|
||||
get bombs(){ return bombs ? bombs.slice() : []; },
|
||||
get powerups(){ return powerups ? powerups.slice() : []; },
|
||||
get bombRange(){ return bombRange; },
|
||||
get maxBombs(){ return maxBombs; },
|
||||
dropPowerupAt: function(x, y, type){ powerups.push({ x: x, y: y, type: type || P_RANGE }); },
|
||||
get gameOver(){ return gameOver; },
|
||||
get gameWon(){ return gameWon; },
|
||||
get player(){ return player ? { x: player.x, y: player.y, alive: player.alive, invincible: player.invincible } : null; },
|
||||
|
||||
@@ -5,7 +5,7 @@ Smoke + regresie + campanie E2E pentru jocurile generate. Verifică faptic: fiec
|
||||
până la ecranul final, fără erori de consolă.
|
||||
|
||||
## Ownership
|
||||
- `tests/smoke.mjs` — unicul fișier de teste (~26 teste).
|
||||
- `tests/smoke.mjs` — unicul fișier de teste (~27 teste).
|
||||
- `playwright.config.mjs` (la root, **gitignored**) — config dev.
|
||||
|
||||
## Local Contracts
|
||||
@@ -16,11 +16,11 @@ până la ecranul final, fără erori de consolă.
|
||||
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` (12 — intro→hartă→camere→final, resume, cameră moartă,
|
||||
idempotență ușă, `$`/`$&`, beep, mobil, audio S1, voce/narațiune D10, a11y tap/aria/reduced-motion,
|
||||
navigare overworld).
|
||||
- **Status țintă: 26/26 PASS.**
|
||||
- **Tag-uri:** `@regresie` (15 — exemplu-*.html + edge cases + mobil 320px + regenerare via gameHTML +
|
||||
bomberman gameplay + bomberman rază/powerup-uri) și `@campanie` (12 — intro→hartă→camere→final, resume,
|
||||
cameră moartă, idempotență ușă, `$`/`$&`, beep, mobil, audio S1, voce/narațiune D10,
|
||||
a11y tap/aria/reduced-motion, navigare overworld).
|
||||
- **Status țintă: 27/27 PASS.**
|
||||
|
||||
## Work Guidance
|
||||
- După modificări la motoare (`escape-builder.html`): rulează suita completă; extinde `@regresie` dacă
|
||||
@@ -29,7 +29,7 @@ până la ecranul final, fără erori de consolă.
|
||||
|
||||
## Verification
|
||||
```bash
|
||||
npx playwright test tests/smoke.mjs # 26/26
|
||||
npx playwright test tests/smoke.mjs # 27/27
|
||||
npx playwright test tests/smoke.mjs --grep @regresie
|
||||
npx playwright test tests/smoke.mjs --grep @campanie
|
||||
```
|
||||
|
||||
@@ -1190,4 +1190,59 @@ test.describe('Campanie E2E @campanie', () => {
|
||||
expect(errors, errors.join('\n')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('arcade bomberman — raza initiala 1 + powerup-uri (drop la cutie, pickup creste raza/bombe) @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.__game.restartWithSeed(11));
|
||||
|
||||
// Raza initiala = 1, max bombe = 1
|
||||
const base = await page.evaluate(() => ({ r: window.__game.bombRange, m: window.__game.maxBombs }));
|
||||
expect(base.r, 'raza initiala trebuie sa fie 1').toBe(1);
|
||||
expect(base.m, 'numar bombe initial trebuie sa fie 1').toBe(1);
|
||||
|
||||
// Raza 1: cutie la distanta 2 ramane intacta (player mutat departe de explozie)
|
||||
await page.evaluate(() => {
|
||||
window.__game.teleportPlayer(1, 1);
|
||||
window.__game.setTile(2, 1, 2); window.__game.setTile(3, 1, 2);
|
||||
window.__game.placeBomb(); window.__game.teleportPlayer(11, 11); window.__game.explodeAllBombs();
|
||||
});
|
||||
expect(await page.evaluate(() => window.__game.getTile(2, 1)), 'cutia la distanta 1 trebuie spartá').toBe(0);
|
||||
expect(await page.evaluate(() => window.__game.getTile(3, 1)), 'raza 1: cutia la distanta 2 trebuie intactá').toBe(2);
|
||||
|
||||
// Drop: peste multe cutii sparte apar powerup-uri care SUPRAVIETUIESC exploziei care le-a creat.
|
||||
// (Bug-ul prins: powerup-ul cadea pe celula cutiei si checkExplosionHits il stergea instant.)
|
||||
const dropRounds = await page.evaluate(() => {
|
||||
window.__game.restartWithSeed(11);
|
||||
let rounds = 0;
|
||||
for (let i = 0; i < 60; i++) {
|
||||
window.__game.teleportPlayer(1, 1); window.__game.setTile(2, 1, 2);
|
||||
window.__game.placeBomb(); window.__game.teleportPlayer(11, 11); window.__game.explodeAllBombs();
|
||||
// ridica orice powerup ramas (mut player pe el) ca sa nu fie sters de explozia urmatoare
|
||||
window.__game.powerups.slice().forEach(p => { if (p.x === 2 && p.y === 1) rounds++; window.__game.teleportPlayer(p.x, p.y); window.__game.movePlayer('R'); window.__game.movePlayer('L'); });
|
||||
}
|
||||
return rounds;
|
||||
});
|
||||
expect(dropRounds, 'niciun powerup nu a supravietuit pe celula cutiei sparte').toBeGreaterThan(0);
|
||||
|
||||
// Pickup: range creste bombRange, bomb creste maxBombs; powerup consumat
|
||||
const pick = await page.evaluate(() => {
|
||||
window.__game.restartWithSeed(11);
|
||||
window.__game.setTile(2, 1, 0); window.__game.setTile(3, 1, 0);
|
||||
window.__game.teleportPlayer(1, 1);
|
||||
window.__game.dropPowerupAt(2, 1, 'range'); window.__game.movePlayer('R');
|
||||
const a = { r: window.__game.bombRange, pu: window.__game.powerups.length };
|
||||
window.__game.dropPowerupAt(3, 1, 'bomb'); window.__game.movePlayer('R');
|
||||
const c = { m: window.__game.maxBombs, pu: window.__game.powerups.length };
|
||||
return { a, c };
|
||||
});
|
||||
expect(pick.a.r, 'pickup range nu a crescut bombRange').toBe(2);
|
||||
expect(pick.a.pu, 'powerup range nu a fost consumat').toBe(0);
|
||||
expect(pick.c.m, 'pickup bomb nu a crescut maxBombs').toBe(2);
|
||||
expect(pick.c.pu, 'powerup bomb nu a fost consumat').toBe(0);
|
||||
|
||||
expect(errors, errors.join('\n')).toHaveLength(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user