oneshotlm
Compare all models on this prompt →

Dangerous Dave platformer · qwen3.8-27b

Build a playable tile-based platformer on a canvas in the Dangerous Dave style: navigate a level to grab a trophy and then reach the exit, avoiding hazards like fire and water, with jumping and simple enemies. Procedural graphics only, no external assets.

⛨ sandboxed Open in new tab ↗
transcript · 14 messages
user

Build a playable tile-based platformer on a canvas in the Dangerous Dave style: navigate a level to grab a trophy and then reach the exit, avoiding hazards like fire and water, with jumping and simple enemies. Procedural graphics only, no external assets.

assistant
thinking
Create a Dangerous Dave–style platformer in a single index.html. Tile-based, collect a trophy, then exit opens, fire/water hazards, simple enemies, procedural graphics. Let's write a solid, playable game with keyboard controls (arrow keys/WASD, space to jump), a level grid, camera scrolling, particle effects if possible.

Design:
- Tile size 32. Level is 120 columns x 20 rows, or a 2-screen horizontal scrolling.
- Tiles: 0 empty, 1 ground/brick, 2 metal platform (one-way?), 3 fire hazard, 4 water hazard (pit), 5 trophy, 6 exit door, 7 spikes, 8 enemies (patrolling).
- Physics: gravity, jumping, horizontal movement, one-way platforms (jump through from below? keep it simple: pass-through platforms that only collide when falling).
- Fire: damage when overlapping the fire tile area. Water: instant death unless standing.
- Exit locked until trophy collected; show a status icon.
- Death: reset to start point, increment death counter, maybe save high score in memory.
- Player drawn procedurally: little Dave with a hat, simple animation.
- Enemies: walking types, flip at edges or walls.

Level: create the layout as a string. Make a level of width 100 and height 18. Camera follows the player.

Let's write the code.

Main implementation details:
- Grid rows as strings. Chars: '#'=brick, '='=metal one-way, '^'=spike, '~'=water, 'f'=fire (animated flame), 'T'=trophy, 'D'=door/exit, 'E'=enemy, 'P'=player start.
- Physics loop with a fixed timestep or dt.
- Collision: AABB against tiles, standard axis-separated resolution.
- One-way: only collide when falling and the player's previous-frame bottom was above the top of the tile.
- Fire: if the player's AABB overlaps a fire tile, dead (fire occupies the upper half of the tile? Dangerous Dave's fire burns on top of tiles; make the fire hazard be the entire tile region minus the bottom 4px? Simpler: the entire tile).
- Water: if the player overlaps the water tile region (bottom 24px), dead.
- Fire animation: flickering flames with sin noise.
- Death animation: player explodes (particles), respawn after a delay.
- Victory: touch the door with the trophy → win screen, restart with R.

HUD: deaths, trophy state.

Draw tiles procedurally:
- Brick: brown/orange with mortar lines.
- Metal: gray with rivets.
- Ground below bricks: maybe just use bricks for everything; that's fine.
- Water: blue with animated waves.
- Spike: gray triangles.
- Fire: gradient of yellow/orange/red flame shapes.
- Trophy: gold cup shape.
- Door: rectangle with an arrow, glowing when open.

Player: draw a small person with a hat, legs animate when moving, flip when facing.

Enemies: simple red robots with eyes, walking.

Write about 500 lines. Let's make it.

Level design: width around 110. Make a string of height 18 rows. Make sure there's a path.

Let's define the level (each row a string of the same length). To reduce errors, either build it in JS by joining strings of varying lengths, or pad them. Just design it carefully.

Simpler: rows of a string array, pad to max length. Design:

Row indices 0..17. Ground mostly on rows 16-17.

Let me try:

```
..........................................................................................................
..........................................................................................................
..............................................................T..............................................
............................................................[##]..............................................
```

Writing by hand is error-prone but fine. Or, programmatically generate the level: start with ground, dig pits (water), place platforms, fire columns, enemies. Programmatic generation gives more control. Let's build it in code:

- W=120, H=18.
- Ground: rows 15,16,17 filled with '#' except for pits where water goes: define pits as ranges of columns, dig through ground; water fills the bottom 2 rows of the pit.
- Place bricks/platforms ('=') at various positions to cross pits.
- Fire: placed on top of a ground column (a tile above ground, mark 'f' at row 14, animate flames).
- Spikes on some platforms.
- Trophy: on top of a brick block at a high location.
- Door: in a walled room at the far right.
- Enemies on the ground.

Pits: cols [20-24], [45-50], [70-76], [95-100]. Water at the bottom of pits (rows 15-17) or maybe a shallow water row 16-17.

Platforms: '=' at row 12 for cols 18-26 (crossing the pit), row 11 for 44-52, etc. Fire on the ground between the pit and the wall.

Trophy: on top of a brick column at col 60, rows 12-14 '###' with T at 11? Reachability check: player jump height ~ let's set jump velocity -520, gravity 1500 → h = v²/(2g) ≈ 90px ≈ 2.8 tiles. So a jump can clear about 2 tiles up to a platform. Keep steps ≤2 tiles.

Door: at col 112, ground; walls at col 115..? Door tile 'D' at row 14 (above ground at row 15).

Player start col 2, standing on ground row 15 → player's y above row 15.

Enemies: place 'E' tiles on the ground at various cols; convert to enemy entities.

Fire: col 30 row 14 (above ground), col 65 row 14, col 90 row 14. Maybe also add a fire on top of a platform.

Spikes: on top of some platforms: row 11 with '^' on top of a platform at row 12? Place '^' on top of the ground at cols 55-56, forcing a platform jump over them.

Let's write the code with a helper:

```js
const W=120,H=18,T=32;
const g = Array.from({length:H},()=>Array(W).fill(0));
function fill(x0,y0,x1,y1,c){...}
```

Then:
- fill(0,15,W,17,1) ground.
- Pits: for ranges [[20,24],[45,50],[70,76],[95,101]]: fill(x,15,x,17,0); water: fill(x,16,x,17,4) (shallow, 2 deep).
- Platform across pit 1: fill(18,12,26,12,2); pit spans 20-24, platform at row 12 covers 18-26.
- Platform across pit 2: fill(44,12,52,12,2).
- Platform across pit 3: fill(68,12,78,12,2).
- Platform across pit 4: fill(94,12,103,12,2).
- Brick step-ups: fill(14,13,16,14,1) 2 high? That blocks the path at cols 14-16... player can jump over (max jump ~2.8 tiles, block height 2, OK). Actually make it a 1-wide column: fill(28,13,28,14,1) with fire next to it? Hmm.
- Fire: g[14][30]=3, g[14][36]=3? Fire above ground; player must jump over (fire tile is full tile at row 14; jump-over is possible).
- Spikes: g[14][55]=5, g[14][56]=5 (spikes on top of ground), plus g[14][82]=5, g[14][83]=5.
- Mid-course platforms with enemies: fill(38,11,41,11,2) with enemy? Enemies on platforms: place enemies at ground positions: cols 12, 40, 58, 63, 88.
- Trophy area: brick block: fill(60,13,62,14,1), fill(60,11,62,12,1)? That's a 4-high wall at cols 60-62 — blocks the path. Instead: make the trophy float high up: T at (61,9), platform below? Make a 2-tier ladder: fill(58,13,58,13,1) step 1, fill(60,11,62,11,1) shelf, T at (61,10). Player: ground → step (2 up? row 13 = 2 above the row 15 floor... standing on top of row 13 means feet at y=14*T; from the ground (feet at 15T) need to jump 1 tile up to row 13? Row 13 brick top surface is at y=13*T; feet at ground 15T → 2 tiles up. OK. Then from the shelf at row 11 (top 11T) need another 2 up, feet at 13T → OK. Trophy above the shelf at row 10.

But the shelf at rows 11 cols 60-62 floats in the air; that's fine. Add a step at col 58 row 13, and another at 59? Player at 15T, jumps 2 up to stand on row 13 block, then 2 up to row 11 shelf. Good.

- Door: g[14][116]=6 (door above ground at row 15). Walls: fill(114,10,114,14,1) and fill(118,10,118,14,1)? Enclose the door: door at 116, walls at 114 and 118 rows 10-14, that's a 4-high wall; a gap to enter the room at cols 115,117? Room is open from row 10 downward... hmm, wall at 114 blocks entry from the left. Let's make walls at 113 (rows 10-14) and 119 (rows 10-14); room 114-118 open on top? No — the room is open to the sky; player walks in over ground. Then the walls don't block. Whatever: decorative pillars. Actually simpler: no room, just a door on the ground at col 116. Add a couple of decorative pillars at 112 and 120 (rows 10-14) — 5-high pillars block the path unless jumped over... 5 tiles is too high. Pillars 112 rows 13-14 (2 high, jumpable) and 120 rows 13-14.

- Enemy positions (on ground, spawn above row 15): cols 10, 25, 40, 48, 57, 72, 86, 99, 108. Some will patrol across pits? Keep patrol: turn at walls or edges (no tile below ahead). Edge detection prevents falling into pits.

Also fire on top of a platform: g[11][24]=3 above the pit-1 platform, forcing a jump.

Player size: w=20, h=28 (slightly smaller than a tile). Jump tuning: gravity 2200 px/s²? At T=32: jump v=-700, g=2200 → h=111px=3.5 tiles. Air time ~0.64s. Move speed 260 px/s. Feels okay.

Fire hazard: fire at tile (c,r) occupies the region x=c*T+4 to +28, y=r*T to r*T+32 (whole tile). Check AABB overlap → dead.

Water: region y=(r)*T+8 to the bottom? Water tile: top surface at r*T+8; if player's bottom > r*T+10 and horizontally overlapping → dead.

Spike: triangles on top of the ground: hazard region y=r*T+16..r*T+32, overlap → dead.

Camera: clamped to level bounds, follows player with lerp.

Death: 1-second death flag, particles, then reset positions, deaths++.

Win: touch door with trophy → win state, "P to play again." Also show "trophy required" indicator: door has a lock icon; HUD shows a mini trophy icon that lights up when obtained.

Also add a simple score: time? Keep deaths + timer.

Player rendering:
- Body: white shirt / orange? Dave: blue pants, white top? Just draw: orange hat, skin-tone head, white shirt, blue legs animated. Simple rectangles + head circle + hat.
- Flip with dir.
- Legs: two rectangles alternating with a phase based on x movement.

Enemy: red box with dark base, eye, small legs; flip based on direction.

Fire rendering: per-frame flicker using time and column seed: draw 3-4 flame triangles with a gradient (createLinearGradient yellow→red), height varying with sin(t*10 + x).

Water: fill blue rectangle with a lighter wave line using sin.

Trophy: gold cup: base rectangle, stem, cup arc, handles; glimmer.

Door: dark frame, interior black; if unlocked (hasTrophy), a green light + arrow; otherwise a red lock (small padlock) and gray.

Background: dark blue-purple gradient, parallax stars/mountains procedurally (deterministic pseudorandom from column index).

Input: keydown/up for arrows/WASD + space. P pause, R restart.

Fixed timestep: accumulate dt, step at 1/120, capped.

Write it all. Also handle DPR scaling? Keep the canvas at a fixed 960x576 CSS size with device pixel ratio scaling for sharpness. Keep it simple: canvas width 960 height 576 (18 tiles = 576, 30 tiles displayed).

Level W=120 → 3840 px.

Collision function: standard.

One-way platform ('='): collide only when vy>0 and prevBottom <= tileTop+small value.

Let's write the code.

Also, fire above the platform at pit 1: platform row 12 cols 18-26, fire at (24,11)? Fire at row 11 sits above the platform. Player crossing must jump over. Good.

Check pit 2 platform: row 12 cols 44-52, pit 45-50. OK.

Spike at ground row 14 cols 55-56: player must jump. OK.

Fire ground row 14 cols 30, 36; spikes 55-56; spikes 82-83; fire 74 (row 14)? Pit 3 is 70-76 so 74 is inside the pit (water). Put fire at 68? Platform 3 covers 68-78 at row 12. Fire at ground col 80? No, spikes at 82-83. Fire at 80, spikes at 84-85.

Also enemy on top of pit-3 platform? Enemies only on the ground.

Enemy edge/wall check: each step, check the tile ahead at foot level; if it's a wall, flip; if the tile below the forward position is not solid and not one-way → flip.

Trophy pickup: AABB overlap → hasTrophy=true, sparkle particles.

HUD: draw in top-left of canvas: "DEATHS: n", a trophy icon that's lit or gray, "EXIT LOCKED/OPEN", timer.

Now let's write the file.

Player physics details:
- ax = speed acceleration for snappy feel: target velocity approach.
- coyote time + jump buffer for better feel.
- variable jump: on release, if vy<0 then vy*=0.4.

Respawn: reset player to start point, preserve hasTrophy? In Dave, dying loses progress; keep trophy loss (hasTrophy=false, trophy back on the map). Deaths++.

Timer counts up; stop on win.

Let's write the code carefully.

```js
const cvs=document.getElementById('c'),ctx=cvs.getContext('2d');
const T=32,W=120,H=18;
const TILE={EMPTY:0,BRICK:1,METAL:2,FIRE:3,WATER:4,SPIKE:5,TROPHY:6,DOOR:7};
```

Build level:

```js
const g=[];
for(let y=0;y<H;y++){g.push(new Array(W).fill(0));}
function fill(x0,y0,x1,y1,c){for(let y=y0;y<=y1;y++)for(let x=x0;x<=x1;x++)if(x>=0&&x<W&&y>=0&&y<H)g[y][x]=c;}
function set(x,y,c){g[y][x]=c;}
fill(0,15,W-1,17,1);
const pits=[[20,24],[45,50],[70,76],[95,101]];
for(const [a,b] of pits){fill(a,15,b,17,0);fill(a,16,b,17,4);}
fill(18,12,26,12,2);
fill(44,12,52,12,2);
fill(68,12,78,12,2);
fill(94,12,103,12,2);
set(24,11,3); // fire above pit-1 platform
set(30,14,3);set(36,14,3);
set(80,14,3);
set(55,14,5);set(56,14,5);set(84,14,5);set(85,14,5);
// trophy tower
set(58,13,1);
fill(60,11,62,11,1);
set(61,10,6);
// pillars
fill(112,13,112,14,1);fill(120,13,120,14,1);
set(116,14,7);
const enemyCols=[10,25,40,48,58,72,88,99,108,64];
```

Wait, enemy at 58: there's a step block at (58,13) — the enemy at col 58 ground will collide with the brick at 58,13? Enemy is on the ground at row 15, block is at row 13 above, no issue, enemy can walk under it. OK.

Also check the pit at 45-50 and the enemy at 48 — it's inside the pit (no ground). Move 48 → 52. And the enemy at 99: pit 4 is 95-101, inside the pit. Move to 104? Pillar at 120 is fine. Enemies: [10,25,40,52,58,72,88,104,108]. 72 is in pit 3 (70-76)! Move to 80? Fire is 80. Use 66. List: [10,25,40,52,66,88,104,108]. Also 66 is near the pit (70) — the enemy turns at the edge. Good.

Enemy at 104: pit 4 ends at 101, ground 102-120. Pillars 112,120 → patrols between them.

Physics collision: player w=20, h=30. Actually h=30 < 32, so it can pass through 1-tile gaps.

Solid tiles: BRICK, METAL (also solid from above for one-way? metal as one-way platform '='). Make METAL one-way (jump-through from below, land on top). Good for the crossing platforms — the player can also jump up through it from below.

One-way check: for tiles at feet level: vy>=0 and prevBottom <= y*T+2 (allow a small epsilon... using prevBottom <= ty + 0.5? use ty*T + 1). Implementation: bottom = y+h; if vy>=0 and prevBottom <= ty+1 → land.

Solid collision (brick): axis-separated:

moveX: x += vx*dt; check tiles overlapping the AABB; if solid, push out.
moveY similar; set onGround.

Hazard overlap: iterate tiles in the player's bounding box (slightly inset); if FIRE → dead (with fire region: use the full tile, or maybe inset 2px on each side for fairness). If WATER → dead if bottom > waterTop+6. If SPIKE → dead if bottom > ty*T+18.

Trophy tile: if overlap → collect.

Door: if overlap and hasTrophy → win.

Now let's write it all. Particle array too.

Player rendering (procedural):
- Position px,py (top-left), w20 h30.
- Legs: blue, 2 legs 6 wide, alternating offset by walkPhase.
- Torso: white rectangle.
- Head: skin circle, hat: brim + top (orange).
- Eyes based on facing.
- Dead: skip (particles).

Enemy: 24x26, red dome body, dark eyes, gray feet.

Background: gradient; distant mountains: deterministic from index: for i in 0..: peak height = pseudoRandom. Draw parallax at camX*0.3. Plus twinkling stars.

Timer display, deaths.

Instructions text at the bottom of the page.

Also allow restart at any time with R.

Edge case: player falling off the bottom of the screen → dead (shouldn't happen since the pits are water).

Now let's write the full file.

One more thing: jump height: v=-720, g=2200 → 117px = 3.67 tiles. Enough for 2-tile steps, no problem.

Horizontal jump distance: air time = 2*720/2200 = 0.655s * 260 = 170px ≈ 5.3 tiles. Pit width 5-7 tiles (max 7 for 95-101) — but the platform is directly above, so the player just goes across the platform. Platform is 8-10 wide, fire in the middle — no problem.

Also make sure the player can't get stuck: platform row 12 is 3 tiles above the row 15 ground → jump 3.67 to clear the top of the platform (feet at 12T from 15T) → yes, 3 tiles is needed, max 3.67 — cutting it close but OK.

Fire at (24,11): above the platform; player on the platform (feet at 12T) jumps 1 tile over the fire. No problem.

Now let's write the HTML.

Also handle the door lock: show a "LOCKED" label until the trophy is obtained.

Write the file, then do a syntax check with node? Extract the JS and do a quick check with node --check. Extract the script contents and run it through node.
write /tmp/ws/index.html (20830 bytes)
toolResult

Successfully wrote 20830 bytes to /tmp/ws/index.html

assistant
thinking
(Internal memo: verify JS syntax with node — extract the script and run node --check.)

Now verify the JavaScript parses cleanly:

bash {"command":"cd /tmp/ws && awk '/<script>/{f=1;next}/<\\/script>/{f=0}f' index.html > /tmp/check.js && node --check /tmp/check.js && echo SYN
toolResult

SYNTAX_OK -rw-r--r--. 1 node node 20852 Aug 15 14:03 index.html

assistant
thinking
Also, a sanity check on the logic: the trophy tower at column 58, row 13; the player can jump 2 tiles up from the ground (foot row 15 → 13): jump height 3.67 tiles, OK. Enemy at column 52: pit 2 is 45-50, ground 51 onward — no problem. One bug: inside buildLevel it's referencing enemyCols before it's declared? Is `const enemyCols = []` declared after the buildLevel() call? Order: `buildLevel(); function buildLevel(){...}` — function declarations are hoisted, so buildLevel() executes at the point where that statement is. `const enemyCols = []` is declared in the source after the buildLevel() call... let me check: in my own file, after `buildLevel();` comes `function buildLevel(){...}`, then `const enemyCols = [];`. buildLevel() sets `enemyCols.length = 0` — at the time buildLevel() is executed, enemyCols is in the TDZ (const is declared later in the same scope) → ReferenceError! No wait: the buildLevel() call happens before the const declaration line is executed → TDZ error. Fix required: either move `const enemyCols = []` before buildLevel(), or declare it at the top.

I spotted a TDZ issue — `buildLevel()` is called before `const enemyCols` is declared. Fixing:

edit {"path":"/tmp/ws/index.html","edits":[{"oldText":"const g = [];\nfor (let y = 0; y < H; y++) g.push(new Array(W).fill(0));","newText":"const
toolResult

Successfully replaced 2 block(s) in /tmp/ws/index.html.

assistant
thinking
Let me verify one more time, and if possible, I'll also do a simple runtime smoke test. If a headless browser is available, I could simulate it, but it's probably not installed. At the very least, I'll re-check the syntax, and maybe use node to run the physics logic in a minimal DOM-less environment (stubbing out canvas) to confirm there are no runtime errors on the first few frames.
bash {"command":"cd /tmp/ws && awk '/<script>/{f=1;next}/<\\/script>/{f=0}f' index.html > /tmp/check.js && node --check /tmp/check.js && cat > /t
toolResult

/tmp/smoke.js:12 console.log('SMOKE_OK frames:', i); ^ ReferenceError: i is not defined at Object.<anonymous> (/tmp/smoke.js:12:33) at Module._compile (node:internal/modules/cjs/loader:1871:14) at Object..js (node:internal/modules/cjs/loader:2002:10) at Module.load (node:internal/modules/cjs/loader:1594:32) at Module._load (node:internal/modules/cjs/loader:1396:12) at wrapModuleLoad (node:internal/modules/cjs/loader:255:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 Node.js v24.18.1 Command exited with code 1

assistant
thinking
The game ran 600 frames without error — the only failure is my console.log variable scope. Fix and rerun to confirm.

The game code ran 600 frames without errors — only my test's `console.log` had a scope bug. Fix and re-run:

bash {"command":"cd /tmp && sed -i \"s/for (let i = 0; i < 600 && rafCb; i++) { t += 16.7; const cb = rafCb; rafCb = null; cb(t); }/let frames=0;
toolResult

/tmp/smoke.js:12 console.log('SMOKE_OK frames:', i); ^ ReferenceError: i is not defined at Object.<anonymous> (/tmp/smoke.js:12:33) at Module._compile (node:internal/modules/cjs/loader:1871:14) at Object..js (node:internal/modules/cjs/loader:2002:10) at Module.load (node:internal/modules/cjs/loader:1594:32) at Module._load (node:internal/modules/cjs/loader:1396:12) at wrapModuleLoad (node:internal/modules/cjs/loader:255:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 Node.js v24.18.1 Command exited with code 1

assistant
thinking
The sed didn't get applied to the log line. I'll fix that line directly.
bash {"command":"cd /tmp && sed -i \"s/console.log('SMOKE_OK frames:', i);/console.log('SMOKE_OK frames:', frames);/\" smoke.js && node smoke.js"
toolResult

SMOKE_OK frames: 600

assistant
thinking
600 frames were executed without any issues. Done. Final answer.

Done. `index.html` is in `/tmp/ws` — a fully self-contained, playable Dangerous Dave–style platformer with zero external resources. **How it works:** - **Goal loop:** Grab the gold trophy (on a two-step brick tower mid-level) → the exit door unlocks (red padlock → glowing green up-arrows) → reach it to win. HUD shows deaths, timer, and trophy/exit status. - **Hazards:** animated flickering fire (on the ground and atop a bridge), water-filled pits, spike strips, and patrolling red "creeps" that turn at walls, ledges, and hazards. Stomp an enemy from above to squash it; touch it any other way and you die. - **Physics:** axis-separated AABB tile collision, one-way metal platforms (jump up through, land on top), coyote time, jump buffering, and variable jump height (hold to go higher). - **Procedural graphics only:** brick/metal tiles, sine-waved water, gradient flame sprites, parallax mountains + twinkling stars, a drawn Dave (orange hat, walking legs), spiky enemies, glowing trophy, and particle effects for jumps, stomps, death explosions, and pickups — all pure canvas. - **Controls:** Arrows/WASD move, Space/↑/W jump, R restart, P pause. Fixed 120 Hz timestep, camera lerp with death screen-shake. Verified: `node --check` passes and a headless 600-frame smoke test (stubbed DOM) runs with no runtime errors, including the fixed initialization-order bug where `enemyCols` was referenced before declaration.

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modelqwen3.8-27b
vendorQwen
harnesspi-agent
generated2026-08-15

EVALUATION

3/5 working

Dangerous Dave-style platformer with player, enemies, spikes, water; death particle effects show on collision but level is static/simple.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion0.2%
on click0.3%
on drag0.3%
on wheel0.4%
on enter + space0.6%
on arrow keys0.3%
on w/a/s/d0.4%
frame spread26.5 / 255
console errors0
js errors none

METRICS

runtime1050.3s
tokens in104k
tokens out16k
cached0
cost / run$0.0980