Dangerous Dave platformer · lfm-2.5-24b-a2b
No runnable output Cost / run $0.0175 Runtime 105.3s
Prompt for this run
Build a playable tile-based platformer on a canvas in a Dangerous Dave-inspired style. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Provide one completable level with solid platforms, a trophy, an exit, fire and water hazards, and at least one patrolling enemy. Draw everything procedurally. - Use Left/Right or A/D to move and Space to jump. Display the controls. Apply gravity, landing, and solid-wall collision; jumping is allowed only while grounded. - The player must collect the trophy before the exit completes the level. Reaching the exit without it gives visible feedback and keeps the level active. - Touching a hazard or enemy causes a loss state. Reaching the exit with the trophy causes a win state. - Provide Restart after either outcome and during play. Restart restores the level, trophy, enemies, and player position. Keep the level's route physically reachable. Verify: Test movement and a jump near the starting area. Check that the player lands on a nearby solid surface and that the trophy and exit are visibly identifiable. Use Restart and check that the initial player and collectible state return.
This one-shot run produced no runnable artifact.
Error: agent produced no index.html
The transcript below shows what the model did — read it to see why the one shot fell short.
TAKE IT TO CODE
This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.
RUN
METRICS
Transcript · 7 messages
Build a playable tile-based platformer on a canvas in a Dangerous Dave-inspired style. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Provide one completable level with solid platforms, a trophy, an exit, fire and water hazards, and at least one patrolling enemy. Draw everything procedurally. - Use Left/Right or A/D to move and Space to jump. Display the controls. Apply gravity, landing, and solid-wall collision; jumping is allowed only while grounded. - The player must collect the trophy before the exit completes the level. Reaching the exit without it gives visible feedback and keeps the level active. - Touching a hazard or enemy causes a loss state. Reaching the exit with the trophy causes a win state. - Provide Restart after either outcome and during play. Restart restores the level, trophy, enemies, and player position. Keep the level's route physically reachable. Verify: Test movement and a jump near the starting area. Check that the player lands on a nearby solid surface and that the trophy and exit are visibly identifiable. Use Restart and check that the initial player and collectible state return.
thinking
We'll build a playable tile-based platformer in a Dangerous Dave-inspired style. Let me plan this out:
1. **HTML structure**: A canvas element, some UI overlay for controls and status
2. **CSS**: Styling for the page, canvas border, HUD
3. **JavaScript**:
- Procedural level generation with solid platforms, trophy, exit, fire hazard, water hazard, patrolling enemy
- Player physics: gravity, jumping (only when grounded), collision detection
- Collectible trophy must be collected before reaching exit
- Hazards cause loss state
- Exit: win if trophy collected, lose if reached without it
- Restart functionality
Let me design the level:
- Grid-based (tile size of 32x32 or similar)
- Platforms made of solid tiles
- Trophy as a collectible item
- Exit as a goal tile
- Fire hazard (red lava-like tiles) - death on touch
- Water hazard (blue tiles) - death on touch
- Enemy that patrols back and forth on platforms
Player:
- Rectangle character
- Left/Right or A/D to move
- Space to jump
- Gravity applied each frame
- Ground collision
Let me think about the level layout. I'll use a tile grid, say 20x15 tiles. Each tile is 32px.
Level design:
- Ground at y=12 (bottom area)
- Some floating platforms
- Trophy placed somewhere reachable
- Exit at the far right
- Fire hazard somewhere
- Water hazard somewhere
- Enemy patrolling on a platform
Let me write the code. I'll create a self-contained index.html file.
Key components:
1. Canvas rendering
2. Level data (array of tiles)
3. Player object with position, velocity, state
4. Game loop with update and render
5. Input handling
6. Collision detection
7. State management (playing, won, lost, restart)
Let me define the tile types:
- 0: empty
- 1: solid ground/platform
- 2: fire hazard
- 3: water hazard
- 4: trophy
- 5: exit
- 6: enemy platform (where enemy walks)
Actually, let me simplify:
- Solid: 1
- Fire: 2
- Water: 3
- Trophy: 4
- Exit: 5
- Enemy walkway: 6 (enemy spawns here and walks between two points)
Player:
- Position (x, y) in pixels
- Velocity (vx, vy)
- Width/height (e.g., 24x32)
Movement:
- Left/Right or A/D: vx = -speed or +speed
- Space: if grounded, vy = jump_velocity
Gravity: vy += gravity * dt
Collision:
- Check horizontal movement first, then vertical
- For each tile the player overlaps, check if it's solid
- If landing on top of a solid tile, set vy = 0 and place on top
Grounded check: is the player standing on a solid tile below them?
Let me think about the level layout more concretely. Canvas size: let's make it 800x600. Tile size 32px means 25x18 tiles.
Level (grid coordinates):
Row 0 (top): mostly sky
Rows 1-3: air
Row 4: ground platform
...
Let me sketch a simple but fun level:
```
Y
14: . . . . . . . . . . . . . . . . . . . . . . . .
13: . . . . . . . . . . . . . . . . . . . . . . . .
12: . . . . . . . . . . . . . . . . . . . . . . . .
11: . . . . . . . . . . . . . . . . . . . . . . . .
10: . . . . . . . . . . . . . . . . . . . . . . . .
9: . . . . . . . . . . . . . . . . . . . . . . . .
8: ############### ################## ##################
7: # # # #
6: # ###### # # #
5: # # # # #
4: #### # # #
3: . . . . . . . . . . . . . . . . . . . . . . . .
2: . . . . . . . . . . . . . . . . . . . . . . . .
1: . . . . . . . . . . . . . . . . . . . . . . .
0: . . . . . . . . . . . . . . . . . . . . . .
```
Hmm, let me keep it simpler. I'll define the level programmatically with specific tile positions.
Actually, let me just hardcode the level array. It's easier.
Canvas: 800x600, tile size 32.
Grid: 25 columns x 18 rows.
Level design:
- Ground: row 12 (y=12*32=384) from x=0 to x=24 (full width)
- Some gaps in the ground
- Floating platforms above
- Trophy at some position
- Exit at the right side
- Fire hazard on a platform
- Water hazard on a platform
- Enemy walking on a platform
Let me define the grid. I'll use an array of strings or numbers.
Let me write the code now. I'll create a comprehensive single-file HTML.
Game states: 'play' | 'win' | 'lose' | 'restart'
Restart resets everything.
Controls display: "A D / ← → : Move | Space: Jump"
Let me start coding:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dangerous Dave Style Platformer</title>
<style>
body { margin: 0; background: #1a1a2e; display: flex; justify-content: center; align-items: center; height: 100vh; font-family: monospace; }
canvas { border: 3px solid #444; background: #2d2d3d; }
#hud { position: absolute; top: 10px; left: 10px; color: #fff; font-size: 18px; text-shadow: 1px 1px 2px #000; }
</style>
</head>
<body>
<div id="hud">← → / A D: Move | Space: Jump | Collect Trophy! Reach Exit to Win</div>
<canvas id="game"></canvas>
<script>
// ... game code ...
</script>
</body>
</html>
```
Now the JavaScript. Let me think through the level design.
Tile map (25 wide x 18 tall). Each cell: 0=empty, 1=ground/solid, 2=fire, 3=water, 4=trophy, 5=exit, 6=enemy walkway.
Level layout:
Row 17 (top, y=17*32=544): sky
Row 16: sky
Row 15: sky
Row 14: sky
Row 13: sky
Row 12: ground segment? Let me put ground starting at row 12.
Actually, let me think in terms of pixel coordinates. Canvas 800x600. Tile 32px. So grid is 25x18 (800/32=25, 576/32=18).
Let me define the level as a 2D array. I'll write it out:
```javascript
const LEVEL_TILE = 32;
const COLS = 25;
const ROWS = 18;
// Define level: 0=empty, 1=solid, 2=fire, 3=water, 4=trophy, 5=exit, 6=enemy walkway
const level = [
// Row 0
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
// ... etc
];
```
This is going to be tedious. Let me instead generate the level procedurally with functions, and then hardcode key positions.
Actually, for a good platformer, let me design the level manually with a few key features:
**Ground floor (row 12):**
- Full ground from x=0 to x=24 (solid)
- But with some gaps for the player to jump over? No, let's keep it simple: full ground at bottom.
**Platforms:**
- Platform at y=9 (row 9), x=4 to x=10 (floating platform)
- Platform at y=6 (row 6), x=8 to x=14
- Platform at y=3 (row 3), x=4 to x=20
**Trophy:** At row 3, x=16 (on the high platform)
**Exit:** At row 12, x=22 (at the edge of the ground)
**Fire hazard:** On the platform at row 6, x=12-14 (a few tiles of fire)
**Water hazard:** On the platform at row 9, x=16-18
**Enemy:** Patrolling on the platform at row 3, x=4 to x=16. Enemy starts at x=4, walks to x=16, then back.
Let me refine:
Ground: row 12 (y=384), all columns 0-24 are solid (1).
Floating platforms:
1. Platform A: row 9 (y=288), columns 5-11 (7 tiles)
2. Platform B: row 6 (y=192), columns 9-15 (7 tiles)
3. Platform C: row 3 (y=96), columns 3-19 (17 tiles)
Treasures: Trophy at row 3, column 16 (on platform C).
Exit: row 12, column 22 (on ground, near the right edge).
Fire hazard: row 6, columns 10-12 (fire tiles).
Water hazard: row 9, columns 14-16 (water tiles).
Enemy: Walks on platform C (row 3), from x=4 to x=20. Enemy size: 24x24 pixels.
Player start: (4, 340) - on the ground, slightly left of center.
Wait, the ground is at y=384 (row 12). Player should start on the ground: y = 384 - playerHeight = 384 - 32 = 352. So player starts at (4, 352).
Let me adjust the enemy path: from x=4 to x=20 on row 3.
Now, let me think about the physics:
- Gravity: 0.5 per frame (or 0.6)
- Jump velocity: -10
- Max speed: 4
- Player size: 24x32 (width x height)
Movement:
- Left/Right: vx = -4 or 4
- Space: if grounded, vy = -10
Collision:
- After moving horizontally, check for collisions with solid tiles. Push player out.
- Then move vertically, check for collisions. If hitting solid from above, set vy=0 and place on top.
Grounded check: is there a solid tile directly below the player's feet?
Let me implement this carefully.
For the level, I'll create a 2D array. Let me write it out:
```javascript
const T = { EMPTY: 0, SOLID: 1, FIRE: 2, WATER: 3, TROPHY: 4, EXIT: 5, ENEMY: 6 };
let grid = [];
for (let r = 0; r < ROWS; r++) {
grid[r] = [];
for (let c = 0; c < COLS; c++) {
grid[r][c] = T.EMPTY;
}
}
```
Then fill in the level:
Ground (row 12, all columns):
```javascript
for (let c = 0; c < COLS; c++) grid[12][c] = T.SOLID;
```
Floating platforms:
```javascript
// Platform A: row 9, cols 5-11
for (let c = 5; c <= 11; c++) grid[9][c] = T.SOLID;
// Platform B: row 6, cols 9-15
for (let c = 9; c <= 15; c++) grid[6][c] = T.SOLID;
// Platform C: row 3, cols 3-19
for (let c = 3; c <= 19; c++) grid[3][c] = T.SOLID;
```
Trophy:
```javascript
grid[3][16] = T.TROPHY;
```
Exit:
```javascript
grid[12][22] = T.EXIT;
```
Fire hazard:
```javascript
for (let c = 10; c <= 12; c++) grid[6][c] = T.FIRE;
```
Water hazard:
```javascript
for (let c = 14; c <= 16; c++) grid[9][c] = T.WATER;
```
Enemy walkway:
```javascript
for (let c = 4; c <= 20; c++) grid[3][c] = T.ENEMY;
```
Player start:
```javascript
player.x = 4;
player.y = 352; // on the ground
```
Enemy initial position:
```javascript
enemy.x = 4;
enemy.y = 64; // row 3 * 32 = 96... wait, row 3 is y=96. Player is at y=352.
```
Hmm, enemy y = 3 * 32 = 96. The enemy is on platform C which is at row 3 (y=96). Good.
Enemy patrol: moves between x=4 and x=20. Speed: 1 pixel per frame.
Now, let me think about the game loop:
```javascript
function update(dt) {
if (state !== 'play') return;
// Input
if (keys.left || keys.a) player.vx = -speed;
else if (keys.right || keys.d) player.vx = speed;
else player.vx = 0;
if (keys.space && grounded) {
player.vy = jumpVelocity;
}
// Apply gravity
player.vy += gravity;
// Move X
player.x += player.vx;
// Collision X
resolveCollisionX();
// Move Y
player.y += player.vy;
// Collision Y
resolveCollisionY();
// Check collectibles
checkCollectibles();
// Check hazards
checkHazards();
// Check exit
checkExit();
}
```
Collision resolution:
```javascript
function resolveCollisionX() {
const halfW = PLAYER_W / 2;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === T.SOLID) {
const tileLeft = c * TILE;
const tileTop = r * TILE;
// Player rectangle
const px = player.x - halfW;
const py = player.y - PLAYER_H / 2;
// Check overlap
if (player.x + halfW > tileLeft && player.x - halfW < tileLeft + TILE &&
player.y + PLAYER_H / 2 > tileTop && player.y - PLAYER_H / 2 < tileTop + TILE) {
// Resolve
if (player.vx > 0) {
player.x = tileLeft - halfW;
} else if (player.vx < 0) {
player.x = tileLeft + TILE - PLAYER_W;
}
}
}
}
}
}
```
Hmm, this approach has issues with the order of operations. A better approach is to check each tile the player overlaps after moving and push them out.
Actually, let me use a simpler approach: after moving, check all solid tiles and determine the correct position.
Let me think about this more carefully. The standard approach for tile-based platformers:
1. Move horizontally
2. Check horizontal collisions, resolve
3. Move vertically
4. Check vertical collisions, resolve
For horizontal:
```javascript
const oldX = player.x;
player.x += player.vx;
const halfW = PLAYER_W / 2;
const halfH = PLAYER_H / 2;
let onGround = false;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === T.SOLID) {
const tileLeft = c * TILE;
const tileRight = tileLeft + TILE;
const tileTop = r * TILE;
const tileBottom = tileTop + TILE;
// Check if player overlaps this tile
if (player.x + halfW > tileLeft && player.x - halfW < tileRight &&
player.y + halfH > tileTop && player.y - halfH < tileBottom) {
if (player.vx > 0) {
player.x = tileLeft - halfW;
} else if (player.vx < 0) {
player.x = tileRight - PLAYER_W;
}
}
}
}
}
```
For vertical:
```javascript
const oldY = player.y;
player.y += player.vy;
let landed = false;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === T.SOLID) {
const tileLeft = c * TILE;
const tileRight = tileLeft + TILE;
const tileTop = r * TILE;
const tileBottom = tileTop + TILE;
if (player.x + halfW > tileLeft && player.x - halfW < tileRight &&
player.y + halfH > tileTop && player.y - halfH < tileBottom) {
if (player.vy > 0) {
player.y = tileTop - halfH;
player.vy = 0;
landed = true;
} else if (player.vy < 0) {
player.y = tileBottom - halfH;
player.vy = 0;
}
}
}
}
}
if (landed) grounded = true;
```
This should work reasonably well.
Now, the grounded check: after vertical collision, if we landed on something, set grounded = true.
For the enemy:
- Enemy has its own position and patrol range
- If player collides with enemy while not grounded, player takes damage (lose state)
- Actually, the spec says "touching a hazard or enemy causes a loss state"
- So if player touches enemy, lose
For the trophy:
- If player overlaps trophy, set trophyCollected = true
- If trophyCollected and player reaches exit, win
For the exit:
- If player overlaps exit and trophyCollected, win
- If player overlaps exit and not trophyCollected, lose (with feedback)
Restart:
- Reset player position, velocity, grounded flag, trophy collected flag, enemy position
- Rebuild the level (or just reset the grid)
Let me also add visual feedback:
- When losing: show "GAME OVER" text, prompt to restart
- When winning: show "YOU WIN!" text, prompt to restart
- During play: show controls
Let me also draw the level nicely:
- Ground: brown
- Platforms: gray
- Fire: red/orange
- Water: blue
- Trophy: gold
- Exit: green
- Enemy: red/pink
- Player: white square with eyes
Now let me write the complete code. I'll make it clean and functional.
Let me reconsider the level layout to make sure it's fun and playable:
Ground: row 12 (y=384), columns 0-24 (full width)
- This gives the player a base.
Platforms:
- P1: row 9 (y=288), columns 5-11 (7 tiles) - mid-air platform
- P2: row 6 (y=192), columns 9-15 (7 tiles) - higher up
- P3: row 3 (y=96), columns 3-19 (17 tiles) - top platform
Trophy: at row 3, column 16 (on P3)
Exit: at row 12, column 22 (on ground, near right edge)
Fire hazard: on P2, columns 10-12 (fire tiles)
Water hazard: on P1, columns 14-16 (water tiles)
Enemy: walks on P3, from x=4 to x=20.
Player start: (4, 352) - on the ground, left side.
Jump test: player can jump and land on the ground. Near the start, there's no hazard.
Let me verify the physics:
- Gravity: 0.6
- Jump velocity: -11
- Max speed: 4
- Player size: 24x32
With gravity 0.6, the player falls 0.6 per frame. From y=352 to y=384 is 32 pixels, so about 53 frames to fall. That's about 0.88 seconds at 60fps. Reasonable.
Jump: vy = -11, so it goes up about 11/0.6 ≈ 18 frames up, then falls back. Total jump arc about 36 frames ≈ 0.6 seconds. Good.
Enemy speed: 1 pixel/frame, so it takes 16 frames to cross from x=4 to x=20. That's fine.
Now, let me write the complete HTML file.
I need to be careful about the canvas size. Canvas 800x600. Tile size 32. Grid 25x18.
Let me write the code now.
Actually, let me reconsider the enemy. The enemy should be visible and have a patrol pattern. I'll draw it as a simple red square with eyes.
Also, I want to make sure the level is physically reachable. The player starts at (4, 352) on the ground. They can jump onto P1 (row 9, columns 5-11). From P1, they can jump to P2 (row 6, columns 9-15). From P2, they can jump to P3 (row 3, columns 3-19). The enemy walks on P3.
The trophy is at (16, 96) on P3. The exit is at (22, 384) on the ground.
Path: ground → jump up to P1 → jump to P2 → jump to P3 → collect trophy → go to exit.
There are hazards:
- Fire on P2 (columns 10-12): player should avoid or die
- Water on P1 (columns 14-16): player should avoid or die
The enemy on P3: if player touches it while not grounded, they lose. Since the enemy is on the same platform as the trophy, the player needs to be careful.
OK, let me write the code. I'll make it robust.
Let me also add a simple HUD showing the current state: "Collect the golden trophy! Reach the green exit to win."
And control hints: "← → / A D: Move | Space: Jump"
Now, let me write the complete index.html:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dangerous Dave Platformer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: 'Courier New', monospace;
color: #eee;
}
canvas {
border: 3px solid #555;
border-radius: 4px;
background: #2d2d3d;
}
#info {
margin-top: 10px;
font-size: 16px;
color: #aaa;
}
</style>
</head>
<body>
<canvas id="game" width="800" height="600"></canvas>
<div id="info">← → / A D: Move | Space: Jump | Collect the GOLD trophy! Reach the GREEN exit to win</div>
<script>
// ... game code ...
</script>
</body>
</html>
```
Now the JavaScript. Let me structure it:
```javascript
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const TILE = 32;
const COLS = 25;
const ROWS = 18;
// Tile types
const EMPTY = 0, SOLID = 1, FIRE = 2, WATER = 3, TROPHY = 4, EXIT = 5, ENEMY = 6;
// Game state
let grid = [];
let player;
let enemy;
let trophyCollected = false;
let state = 'play'; // 'play', 'win', 'lose'
let keys = {};
let grounded = false;
// Physics
const GRAVITY = 0.6;
const JUMP_VEL = -11;
const SPEED = 4;
const PLAYER_W = 24;
const PLAYER_H = 32;
// Level generation
function initLevel() {
grid = [];
for (let r = 0; r < ROWS; r++) {
grid[r] = [];
for (let c = 0; c < COLS; c++) {
grid[r][c] = EMPTY;
}
}
// Ground
for (let c = 0; c < COLS; c++) grid[12][c] = SOLID;
// Floating platforms
// Platform A: row 9, cols 5-11
for (let c = 5; c <= 11; c++) grid[9][c] = SOLID;
// Platform B: row 6, cols 9-15
for (let c = 9; c <= 15; c++) grid[6][c] = SOLID;
// Platform C: row 3, cols 3-19
for (let c = 3; c <= 19; c++) grid[3][c] = SOLID;
// Trophy
grid[3][16] = TROPHY;
// Exit
grid[12][22] = EXIT;
// Fire hazard
for (let c = 10; c <= 12; c++) grid[6][c] = FIRE;
// Water hazard
for (let c = 14; c <= 16; c++) grid[9][c] = WATER;
// Enemy walkway
for (let c = 4; c <= 20; c++) grid[3][c] = ENEMY;
// Player start
player = {
x: 4,
y: 352,
vx: 0,
vy: 0,
w: PLAYER_W,
h: PLAYER_H
};
// Enemy
enemy = {
x: 4,
y: 96,
w: 24,
h: 24,
vx: 1,
rangeStart: 4,
rangeEnd: 20,
alive: true
};
}
function resetGame() {
initLevel();
state = 'play';
trophyCollected = false;
}
// Input handling
document.addEventListener('keydown', e => {
keys[e.key] = true;
if (e.key === ' ') e.preventDefault();
});
document.addEventListener('keyup', e => {
keys[e.key] = false;
});
// Collision helpers
function getTileAt(x, y) {
const col = Math.floor(x / TILE);
const row = Math.floor(y / TILE);
if (row < 0 || row >= ROWS || col < 0 || col >= COLS) return null;
return grid[row][col];
}
function isSolidAt(x, y) {
const tile = getTileAt(x, y);
return tile === SOLID || tile === FIRE || tile === WATER || tile === ENEMY;
}
// Collision resolution
function resolveCollisions() {
const halfW = player.w / 2;
const halfH = player.h / 2;
// Horizontal
player.x += player.vx;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === SOLID) {
const tl = c * TILE;
const tr = tl + TILE;
const top = r * TILE;
const bot = top + TILE;
if (player.x + halfW > tl && player.x - halfW < tr &&
player.y + halfH > top && player.y - halfH < bot) {
if (player.vx > 0) {
player.x = tl - halfW;
} else if (player.vx < 0) {
player.x = tr - player.w;
}
}
}
}
}
// Vertical
player.y += player.vy;
grounded = false;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === SOLID) {
const tl = c * TILE;
const tr = tl + TILE;
const top = r * TILE;
const bot = top + TILE;
if (player.x + halfW > tl && player.x - halfW < tr &&
player.y + halfH > top && player.y - halfH < bot) {
if (player.vy > 0) {
player.y = top - halfH;
player.vy = 0;
grounded = true;
} else if (player.vy < 0) {
player.y = bot - halfH;
player.vy = 0;
}
}
}
}
}
}
// Check if player is grounded
function checkGrounded() {
const halfW = player.w / 2;
const halfH = player.h / 2;
// Check tile directly below
const belowY = player.y + halfH;
const tileRow = Math.floor(belowY / TILE);
if (tileRow >= 0 && tileRow < ROWS) {
for (let c = Math.floor((player.x - halfW) / TILE); c <= Math.floor((player.x + halfW) / TILE); c++) {
if (grid[tileRow][c] === SOLID) return true;
}
}
return false;
}
// Update player
function updatePlayer() {
// Movement
if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
player.vx = -SPEED;
} else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
player.vx = SPEED;
} else {
player.vx = 0;
}
// Jump
if ((keys[' '] || keys['Space']) && grounded) {
player.vy = JUMP_VEL;
grounded = false;
}
// Gravity
player.vy += GRAVITY;
// Collision
resolveCollisions();
// Update grounded after collision
grounded = checkGrounded();
}
// Check collectibles
function checkCollectibles() {
const px = player.x;
const py = player.y;
const halfW = player.w / 2;
const halfH = player.h / 2;
// Trophy
if (grid[Math.floor(py / TILE)][Math.floor(px / TILE)] === TROPHY) {
trophyCollected = true;
grid[Math.floor(py / TILE)][Math.floor(px / TILE)] = EMPTY;
}
// Enemy collision
if (enemy.alive) {
if (px + halfW > enemy.x && px < enemy.x + enemy.w &&
py + halfH > enemy.y && py < enemy.y + enemy.h) {
// Player hit enemy
gameOver(false);
}
}
}
// Check hazards
function checkHazards() {
if (!enemy.alive) return;
const px = player.x;
const py = player.y;
const halfW = player.w / 2;
const halfH = player.h / 2;
// Fire
if (grid[Math.floor(py / TILE)][Math.floor(px / TILE)] === FIRE) {
gameOver(false);
}
// Water
if (grid[Math.floor(py / TILE)][Math.floor(px / TILE)] === WATER) {
gameOver(false);
}
}
// Check exit
function checkExit() {
const tx = Math.floor(player.x / TILE);
const ty = Math.floor(player.y / TILE);
if (grid[ty][tx] === EXIT) {
if (trophyCollected) {
gameWin();
} else {
gameOver(false);
}
}
}
// Game state transitions
function gameWin() {
state = 'win';
showScreen('You collected the trophy and reached the exit! You win!', '#4caf50');
}
function gameLose(win) {
state = 'lose';
showScreen(win ? 'You reached the exit without the trophy! Game Over.' : 'You were caught by a hazard! Game Over.', '#f44336');
}
function showScreen(message, color) {
// Draw text overlay
ctx.fillStyle = color;
ctx.font = 'bold 28px Courier New';
ctx.textAlign = 'center';
ctx.fillText(message, canvas.width / 2, canvas.height / 2 - 20);
ctx.font = '16px Courier New';
ctx.fillStyle = '#aaa';
ctx.fillText('Press R to restart', canvas.width / 2, canvas.height / 2 + 30);
}
// Main update
function update() {
if (state !== 'play') return;
updatePlayer();
checkCollectibles();
checkHazards();
checkExit();
}
// Render
function render() {
// Clear
ctx.fillStyle = '#2d2d3d';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw grid
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const tile = grid[r][c];
const x = c * TILE;
const y = r * TILE;
if (tile === SOLID) {
ctx.fillStyle = '#8b7355';
ctx.fillRect(x, y, TILE, TILE);
} else if (tile === FIRE) {
ctx.fillStyle = '#ff6600';
ctx.fillRect(x, y, TILE, TILE);
} else if (tile === WATER) {
ctx.fillStyle = '#4488dd';
ctx.fillRect(x, y, TILE, TILE);
} else if (tile === TROPHY) {
ctx.fillStyle = '#ffd700';
ctx.fillRect(x + 4, y + 4, TILE - 8, TILE - 8);
} else if (tile === EXIT) {
ctx.fillStyle = '#00cc66';
ctx.fillRect(x + 6, y + 6, TILE - 12, TILE - 12);
} else if (tile === ENEMY) {
ctx.fillStyle = '#ff3366';
ctx.fillRect(x + 2, y + 2, TILE - 4, TILE - 4);
}
}
}
// Draw platforms (solid tiles that aren't hazards/enemies)
// Actually, I already drew solid tiles. The fire/water/trophy/exit/enemy are drawn on top.
// Need to draw enemy separately since I'm drawing it againThe output file index.html has not been written, or is empty. Write the complete app to index.html in the current directory now. Use the remaining time to verify it, then finish. The original 15-minute deadline still applies.
thinking
We need to build a playable tile-based platformer on canvas in a Dangerous Dave-inspired style. Requirements:
1. One completable level with:
- Solid platforms
- A trophy (collectible)
- An exit
- Fire and water hazards
- At least one patrolling enemy
2. Controls: Left/Right or A/D to move, Space to jump. Display controls. Gravity, landing, solid-wall collision. Jump only when grounded.
3. Player must collect the trophy before reaching the exit. Reaching exit without trophy = visible feedback, level stays active.
4. Touching hazard or enemy = loss state. Reaching exit with trophy = win state.
5. Restart available after either outcome and during play. Restart restores level, trophy, enemies, player position. Keep route physically reachable.
6. Verify: Test movement and jump near start. Check player lands on solid surface. Trophy and exit visibly identifiable.
The file should be self-contained: index.html with inline CSS and JS, no external assets/CDNs.
Let me design this:
**Game design:**
- Canvas: maybe 800x600 or similar.
- Tile size: 32px.
- Level layout: procedural generation of platforms, but I'll hand-craft a simple level.
**Level layout (hand-crafted):**
- Ground: a few platforms at different heights.
- Trophy: placed on a high platform.
- Exit: at the right side, maybe a door.
- Fire hazard: red tiles (like lava) that kill.
- Water hazard: blue tiles that kill.
- Enemy: a patrolling enemy (e.g., a slime-like creature) that moves back and forth on a platform.
**Player:**
- Rectangle (e.g., 28x36).
- Movement: left/right arrows or A/D.
- Jump: Space.
- Gravity: constant downward acceleration.
- Collision detection with solid tiles.
**State machine:**
- Playing, Win, Loss, Restarting.
**Restart:** Press R or click restart button.
Let me think about the level layout:
Canvas: 800x600. Tile size 32. So 25 tiles wide, 18 tiles tall.
Ground level: y=560 (bottom is 600). Platforms at various heights.
Let me design a simple level:
Ground (y=560): from x=0 to x=24 (full ground).
Platforms:
- Platform at y=480, x=100 to x=130 (mid-level platform)
- Platform at y=400, x=150 to x=180
- Platform at y=320, x=120 to x=160
- Platform at y=240, x=80 to x=110
- Platform at y=200, x=60 to x=90
Trophy: on top of a platform at y=190 (so trophy at y=185, x=65).
Exit: at x=220, y=560 (a door/portal). Actually, let's make the exit a special tile at the right edge. Maybe an exit portal at x=240, y=560.
Fire hazard: a row of fire tiles at y=500, x=140 to x=160 (lava stream).
Water hazard: a pool of water at y=520, x=50 to x=70.
Enemy: patrolling on the ground platform at y=560, x=30 to x=50 (moving left-right).
Let me think more carefully. The level needs to be completable: player starts on ground, can reach trophy, then exit.
Actually, let me make it simpler and more robust:
**Layout (tile coordinates, 0-indexed):**
Ground: y=560, x=0..24 (solid ground)
Platforms (solid):
1. (x=100, y=480) size 40x16 → x=100..139, y=480..495
2. (x=150, y=400) size 30x16 → x=150..179, y=400..415
3. (x=120, y=320) size 40x16 → x=120..159, y=320..335
4. (x=80, y=240) size 30x16 → x=80..109, y=240..255
5. (x=60, y=180) size 30x16 → x=60..89, y=180..195
Trophy: at (x=65, y=175) — on top of platform 4.
Exit: at (x=230, y=560) — a special tile at the right edge.
Fire hazard: row of fire tiles at y=510, x=140..170 (lava stream across the middle).
Water hazard: pool at y=530, x=40..70 (blue tiles).
Enemy: patrolling on platform 1 (x=100..139, y=480). Enemy moves between x=100 and x=139.
Player start: (x=20, y=540) on the ground.
Controls display: "Arrow Keys / A,D to move | Space to jump"
Win condition: player touches exit tile while holding trophy → win message + restart option.
Loss condition: touching fire/water/enemy → lose state + restart.
Restart: press R or click Restart button.
Now, let me code this up.
**Physics:**
- Gravity: 0.8 per frame (or similar).
- Jump velocity: -12.
- Terminal velocity? Not needed for simple game.
- Collision: check if player rectangle overlaps any solid tile.
**Collision resolution:**
- Move X, check collisions, resolve.
- Move Y, check collisions, resolve.
- For each axis, separate axis technique.
**Tile representation:**
- 2D array or list of solid rectangles.
- Solid tiles: ground, platforms.
- Hazard tiles: fire (red), water (blue).
- Trophy: special tile.
- Exit: special tile.
Let me structure the code:
```html
<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; background: #222; display: flex; justify-content: center; align-items: center; height: 100vh; }
canvas { border: 2px solid #555; background: #333; }
#ui { color: white; font-family: monospace; text-align: center; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="ui">...</div>
<script>
// Game code
</script>
</body>
</html>
```
Let me write the full implementation.
**Player object:**
- x, y (top-left of sprite)
- width, height
- velocityY
- grounded (boolean)
- alive (true/false)
- hasTrophy (boolean)
**Enemy object:**
- x, y
- width, height
- speed (horizontal)
- direction
- patrol bounds
**Level data:**
- tiles: 2D array or list of solid/hazard/trophy/exit positions.
I'll use a simple approach: a list of solid platform rects, hazard rects, and item positions.
Let me define the level as arrays:
```javascript
const TILE = 32;
const COLS = 25; // 0..24
const ROWS = 19; // 0..18 (y from 0 to 576)
// Ground: y=560..595 (rows 17.5..18.5?)
// Let me use pixel coordinates directly.
```
Actually, let me use pixel coordinates for everything. Canvas 800x600.
Ground: y=560, width=800, height=40 (from 560 to 600).
Platforms:
1. {x: 100, y: 480, w: 40, h: 16}
2. {x: 150, y: 400, w: 30, h: 16}
3. {x: 120, y: 320, w: 40, h: 16}
4. {x: 80, y: 240, w: 30, h: 16}
5. {x: 60, y: 180, w: 30, h: 16}
Trophy: at (65, 175) — a small sprite.
Exit: at (240, 560) — a special tile.
Hazards:
- Fire: row of fire tiles at y=510, x=140..170 (tiles: 140,142,144,...,170)
- Water: pool at y=530, x=40..70
Enemy:
- Patrol on platform 1: x from 100 to 139, y=480. Moves back and forth.
Player:
- Start: x=20, y=540 (on ground, 20 pixels above ground level 560)
Controls display: "← → / A D : Move | Space : Jump"
Win/Loss messages displayed on canvas or overlay.
Let me code this step by step.
**Game loop:**
- Update: handle input, apply gravity, move, collide.
- Draw: clear canvas, draw tiles, player, enemies, HUD.
**Collision detection:**
For each solid tile (platform, ground), check overlap with player rectangle. If overlap, resolve by pushing player out.
Simplest approach: for each solid rect, if player overlaps, set grounded=false and adjust position.
Better approach: separate axes. But for a simple platformer, we can do:
1. Apply horizontal movement.
2. Check horizontal collisions, resolve.
3. Apply vertical movement (gravity).
4. Check vertical collisions, resolve, set grounded=true if vertical collision was from above.
Let me implement this.
**Player state:**
```javascript
let player = {
x: 20,
y: 540,
w: 28,
h: 36,
vx: 0,
vy: 0,
grounded: false,
hasTrophy: false,
alive: true
};
```
**Enemy:**
```javascript
let enemies = [{
x: 100,
y: 480,
w: 28,
h: 28,
speed: 1.5,
dir: 1,
minX: 100,
maxX: 139
}];
```
**Level data:**
```javascript
const platforms = [
{x: 0, y: 560, w: 800, h: 40}, // ground
{x: 100, y: 480, w: 40, h: 16},
{x: 150, y: 400, w: 30, h: 16},
{x: 120, y: 320, w: 40, h: 16},
{x: 80, y: 240, w: 30, h: 16},
{x: 60, y: 180, w: 30, h: 16},
];
```
Treasures:
```javascript
const trophy = {x: 65, y: 175, w: 20, h: 20};
```
Exit:
```javascript
const exit = {x: 240, y: 560, w: 30, h: 30};
```
Hazards:
```javascript
const fire = []; // array of {x, y, w, h}
for (let x = 140; x <= 170; x += 2) fire.push({x, y: 510, w: 2, h: 32});
const water = [];
for (let x = 40; x <= 70; x += 2) water.push({x, y: 530, w: 2, h: 32});
```
Enemy patrol:
```javascript
const enemy = {
x: 100,
y: 480,
w: 28,
h: 28,
speed: 1.5,
dir: 1,
minX: 100,
maxX: 139
};
```
**Input handling:**
- keydown/keyup for ArrowLeft/ArrowRight/A/D/Space.
- Space: if grounded, set vy = -12 (jump impulse).
**Win/Loss:**
- If player touches exit AND hasTrophy: win.
- If player touches fire/water/enemy: lose (unless already dead? No, death resets).
- After win/loss: show message, enable restart.
**Restart:**
- Reset all state: player position, velocity, enemies, hasTrophy, alive.
- Show restart button or prompt.
Let me think about the visual design:
- Background: dark gray/black.
- Platforms: brown/wood color.
- Ground: darker brown.
- Trophy: gold/yellow.
- Exit: green portal.
- Fire: red/orange.
- Water: blue.
- Player: a simple colored rectangle (maybe red).
- Enemy: red square.
Colors:
- Ground: #5c3a1e (brown)
- Platform: #8b5e3c
- Trophy: #ffd700 (gold)
- Exit: #00ff00 (green)
- Fire: #ff4444
- Water: #4488ff
- Player: #ff4444 (red)
- Enemy: #cc0000 (dark red)
**UI:**
- Top-left: "Controls: ← → / A D: Move | Space: Jump"
- Center: game canvas
- Bottom: restart button
Let me write the complete HTML file now.
I'll structure the JavaScript:
```javascript
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = 800, H = 600;
canvas.width = W;
canvas.height = H;
// Game state
let state = 'playing'; // 'playing', 'win', 'loss'
let player, enemies, trophy, exit, hazards, keys;
// Input
const keys = {};
document.addEventListener('keydown', e => {
keys[e.code] = true;
if (e.code === 'KeyR' && (state === 'win' || state === 'loss')) {
restart();
}
});
document.addEventListener('keyup', e => {
keys[e.code] = false;
});
function restart() {
state = 'playing';
player = {
x: 20, y: 540, w: 28, h: 36,
vx: 0, vy: 0,
grounded: false,
hasTrophy: false,
alive: true
};
enemies = [{
x: 100, y: 480, w: 28, h: 28,
speed: 1.5, dir: 1,
minX: 100, maxX: 139
}];
trophy = {x: 65, y: 175, w: 20, h: 20};
exit = {x: 240, y: 560, w: 30, h: 30};
hazards = [];
// ... recreate fire/water
}
function update() {
if (state !== 'playing') return;
// Horizontal movement
let moveX = 0;
if (keys['ArrowLeft'] || keys['KeyA']) moveX = -5;
if (keys['ArrowRight'] || keys['KeyD']) moveX = 5;
player.vx = moveX * 0.8;
player.x += moveX;
// Horizontal collision
checkCollisions(player, platforms);
// Vertical movement (gravity)
player.vy += 0.6;
player.y += player.vy;
// Vertical collision
checkCollisions(player, platforms);
// Enemy patrol
updateEnemies();
// Check hazards
if (!player.alive) return;
// Trophy collection
if (rectsOverlap(player, trophy)) {
player.hasTrophy = true;
}
// Exit check
if (rectsOverlap(player, exit)) {
if (player.hasTrophy) {
state = 'win';
} else {
state = 'loss';
}
}
}
function checkCollisions(rect, list) {
for (let tile of list) {
if (rectOverlaps(rect, tile)) {
// Resolve
// Determine overlap direction
const overlapLeft = rect.x + rect.w - tile.x;
const overlapRight = (rect.x + rect.w) - tile.x - tile.w;
const overlapTop = rect.y + rect.h - tile.y;
const overlapBottom = (rect.y + rect.h) - tile.y - tile.h;
// Find smallest overlap
const minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
if (minOverlap === overlapTop || minOverlap === overlapBottom) {
// Vertical
if (overlapTop < overlapBottom) {
rect.y = tile.y - rect.h;
rect.vy = 0;
player.grounded = false;
} else {
rect.y = tile.y + tile.h;
rect.vy = 0;
player.grounded = true;
}
} else {
// Horizontal
if (overlapLeft < overlapRight) {
rect.x = tile.x - rect.w;
} else {
rect.x = tile.x + tile.w;
}
rect.vx = 0;
}
}
}
}
```
Hmm, the collision resolution is getting complex. Let me simplify.
Actually, for a simple platformer, I can use a simpler approach:
- For each solid tile, if player overlaps, push player out based on the axis of smallest penetration.
But there's a subtlety: if the player is falling and hits the top of a platform, we want to set grounded=true and stop falling. If the player is moving horizontally into a wall, stop horizontal movement.
Let me write a cleaner version:
```javascript
function rectOverlaps(a, b) {
return a.x < b.x + b.w && a.x + a.w > b.x &&
a.y < b.y + b.h && a.y + a.h > b.y;
}
function resolveCollisions(rect, solids) {
let onGround = false;
for (let tile of solids) {
if (!rectOverlaps(rect, tile)) continue;
// Calculate overlap
let overlapLeft = rect.x + rect.w - tile.x;
let overlapRight = (rect.x + rect.w) - tile.x - tile.w;
let overlapTop = rect.y + rect.h - tile.y;
let overlapBottom = (rect.y + rect.h) - tile.y - tile.h;
let minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
if (minOverlap === overlapTop) {
rect.y = tile.y - rect.h;
rect.vy = 0;
onGround = true;
} else if (minOverlap === overlapBottom) {
rect.y = tile.y + tile.h;
rect.vy = 0;
onGround = false;
} else if (minOverlap === overlapLeft) {
rect.x = tile.x - rect.w;
rect.vx = 0;
} else {
rect.x = tile.x + tile.w;
rect.vx = 0;
}
}
player.grounded = onGround;
return onGround;
}
```
This should work reasonably well.
**Enemy update:**
```javascript
function updateEnemies() {
for (let e of enemies) {
e.x += e.speed * e.dir;
if (e.x <= e.minX) { e.dir = 1; e.x = e.minX; }
if (e.x + e.w >= e.maxX) { e.dir = -1; e.x = e.maxX - e.w; }
}
}
```
**Drawing:**
- Draw background (dark)
- Draw platforms (brown rectangles)
- Draw fire tiles (red lines)
- Draw water tiles (blue lines)
- Draw trophy (gold square with diamond shape?)
- Draw exit (green rectangle with X?)
- Draw player (red rectangle)
- Draw enemies (red squares)
- Draw HUD (trophy status, instructions)
**Win/Loss screen:**
- Overlay text on canvas: "You Win!" or "You Lose!"
- Restart button drawn on canvas or as HTML element.
Let me also add a restart button as HTML below the canvas.
Now, let me write the complete file.
Actually, let me reconsider the level layout to make sure it's completable:
Player starts at (20, 540) on ground at y=560.
- Ground spans x=0 to x=24 (actually full width 800, but let's keep it simple: ground is a full-width platform from x=0 to x=800 at y=560).
Wait, if ground is full width, the player can just walk right. But there are hazards in the way. Let me keep the ground as a full floor, and place hazards on platforms above.
Actually, for a better challenge, let me have the ground as a full floor, and the player can walk right past the enemy. The enemy patrols on a platform at y=480, x=100..139.
Trophy is on platform at y=320 (x=120..159). Player needs to jump up to it.
Exit is at x=240, y=560 (right side).
Fire hazard: y=510, x=140..170 — this is a lava stream that the player must avoid when jumping over or going around.
Water hazard: y=530, x=40..70 — a pool the player must avoid.
So the path: player walks right, jumps up to trophy platform, collects trophy, then continues right, avoiding fire and water, and reaches exit.
Let me verify the layout makes sense:
- Ground: y=560..596 (height 36)
- Player starts at y=540 (6 pixels above ground, can fall onto ground)
- Platform at y=480: player needs to jump 80 pixels high. With jump velocity -12 and gravity 0.6, time to peak: v/g = 12/0.6 = 20 frames. Height: v^2/(2g) = 144/1.2 = 120 pixels. That's enough to reach y=360. So the platform at y=480 is reachable (player falls from 540 to 480 = 60 pixels, which is less than 120, so they can land on it).
Wait, actually the player starts at y=540. If they jump, they go up to about y=540 + 120 = 660? No wait, let me recalculate.
Initial vy = 0. Jump sets vy = -12. Gravity adds 0.6 per frame downward.
Position: y(t) = y0 + vy*t + 0.5*g*t^2. With g=0.6, y(t) = 540 - 12t + 0.3t^2.
Peak at t = 12/0.6 = 20. y_peak = 540 - 240 + 0.3*400 = 540 - 240 + 120 = 420.
So the player can reach y=420. Platform at y=480: the player would hit the platform at y=480 (top surface). From y=540, falling 60 pixels to y=480. Time to fall: solve 540 - 12t + 0.3t^2 = 480 → 0.3t^2 - 12t + 60 = 0 → t = [12 ± sqrt(144 - 72)] / 0.6 = [12 ± sqrt(72)] / 0.6 ≈ [12 ± 8.49] / 0.6. t ≈ 3.5 or 35.5. First impact at t≈3.5 frames. At that point vy = -12 + 0.6*3.5 = -7.9 (still falling). So the player lands on the platform at y=480 with some downward velocity. Good.
Then from y=480, they can jump again to reach y=360 (platform at y=320). Distance 160 pixels. Peak at 80 pixels above platform. So they can reach y=320. Good.
From y=320, they can jump to the trophy at y=175 (platform at y=180, trophy on top at y=175). Distance 145 pixels. Peak at 72.5 pixels above. They can reach it.
After collecting the trophy, they need to get to the exit at x=240, y=560. The fire hazard is at x=140..170, y=510. The player is at y≈175 after collecting the trophy. To reach the exit, they need to go down. There's no platform at y=560 except the ground. The ground is at y=560. So the player needs to descend from y=175 to y=560. That's a big drop. They can just fall down (no hazards at those x positions? Fire is at x=140..170, water at x=40..70).
So the path: walk right from start, jump up to trophy platform, collect trophy, then fall down to ground and walk right to exit. The fire hazard at x=140..170 is at y=510 — if the player falls through that area, they'd touch the fire tiles. But the fire tiles are only 32 pixels tall (2-pixel tiles spaced by 2 pixels). Actually, I defined them as w=2, h=32. So they're thin horizontal lines. The player is 36 pixels tall. If the player's bottom is at y=510, they'd overlap with the fire tile. As the player falls from y=175 to y=560, they pass through y=510. If their bottom is around 510, they touch the fire. But since the fire is only 32px tall and the player is 36px tall, there might be a gap. Let me adjust: make the fire tiles slightly taller or position them so the player can pass through.
Actually, for simplicity, let me make the fire hazard a solid block that the player must avoid. The player can jump over it? The fire tiles are at y=510, and the player is at y=175. The player falls. If the fire tiles are at y=510..542 (32px), and the player's feet are at y=510, they'd collide. But the player's feet at y=510 means the player's top is at y=474. The player falls from y=175 to y=474 (above the fire). Then from y=474 to y=560, they pass through y=510. At that point, the player's feet would be at y=510, meaning they're standing on the fire. That's bad.
To fix this, I could make the fire hazard a gap in the floor — like a pit with lava at the bottom. Or I could make the fire a horizontal barrier that the player must jump over. Since the player is 36px tall and the fire is 32px tall, the player can't jump over it (jump height ~120px, but the gap between fire and ground is... hmm).
Alternative: make the fire hazard a series of blocks that the player must not touch. The player can fall past them if there's space. Let me adjust the layout:
- Ground: y=560
- Platforms: y=480, y=400, y=320, y=240, y=180
- Trophy: y=175 (on platform at y=180)
- Exit: y=560, x=240
- Fire: at y=510, x=140..170 — but make it a thin line that the player can jump over? The player's jump height is about 120px. From y=175, jumping up 120px reaches y=45. So the player can easily jump over the fire at y=510 (the fire is below them). Wait, the fire is at y=510, which is below the player's current position (y=175). The player is above the fire. The fire is on the ground side. So the player falls from y=175 down to y=560. They pass through y=510. If the fire tiles occupy y=510..542, the player's feet at y=510 would collide.
To avoid this, I can make the fire tiles shorter, like h=16, and position them such that there's a gap. Or I can make the fire a pit: no floor at x=140..170, y=480..560, and put fire visuals on the sides. That's more complex.
Simpler approach: make the fire hazard a horizontal barrier at a specific height that the player can jump over. For example, fire at y=350, spanning x=140..170, with height 32. The player is at y=175, jumps up to y=420 (max jump height from ground). From y=175, they can reach y=420. The fire at y=350 is within that range. If the player jumps from the trophy platform (y=175+36=211), they can reach y=211+120=331. So they can reach y=331, which is above the fire at y=350? No, 331 < 350, so they'd hit the fire.
Hmm, let me redesign the level to avoid these issues. Let me make the fire hazard a small obstacle that's easy to avoid, or place it where the player naturally passes.
Actually, let me simplify: the fire hazard is a horizontal line of fire tiles at y=450, x=140..170. The player can jump over it from the ground platform at y=480? No, that's higher than the fire.
OK, let me take a step back. The key requirement is: "fire and water hazards" — they need to exist and cause loss on contact. They don't need to be perfectly positioned. Let me make them simple:
- Fire: a row of red squares at y=450, x=140..170 (height 32). The player can jump over them if they're on a platform above. Since the player can jump about 120px high, from the ground (y=560) they can reach y=440. So they can land on the fire tiles! Bad.
Let me make the fire at a lower height, like y=520. The player falls from y=175 to y=560. They pass through y=520. If the fire is at y=520..552, the player's feet at y=520 collide.
Solution: make the fire tiles shorter (h=16) and positioned so the player's feet don't overlap. Or make the fire a "pit" — no floor, and the fire is visual decoration on the sides of the pit. The player falls into the pit and dies.
Let me try this: the ground has a gap from x=130 to x=160 (a pit). The fire is at the bottom of the pit (y=560..592). The player falls into the pit and dies. But then the exit is at x=240, which is past the gap. The player needs to cross the gap. They can jump across.
Hmm, this is getting complicated. Let me simplify the whole level:
**Revised level:**
Canvas: 800x600.
Ground: full width at y=560, h=40 (from 560 to 600).
Platforms:
1. (x=100, y=460, w=50, h=16) — mid-level platform
2. (x=160, y=380, w=40, h=16)
3. (x=120, y=300, w=50, h=16)
4. (x=80, y=220, w=40, h=16)
Trophy: on platform 3 at (x=135, y=295) — trophy sprite at y=295.
Exit: at (x=240, y=560) — portal.
Fire hazard: a row of fire at y=450, x=140..180 (thin, h=16). The player can jump over it from platform 1 (y=460)? No, the platform is at y=460, and the fire is at y=450 — the fire is below the platform. The player standing on the platform at y=460 wouldn't touch the fire at y=450 (the platform is solid from y=460 to 476). The fire is at y=450..466, which is below the platform. So no collision.
But the player falls from the trophy platform (y=295) down to the ground. They pass through y=450. If the fire is at y=450, the player's feet at y=450 would collide. Unless the fire tiles are only h=8 or so, and the player's feet are at y=450 which is above the fire's top... Let me calculate: fire at y=450, h=16 → covers y=450..466. Player's feet at y=450 means the player's top is at y=414. The player is falling from above. When the player's bottom reaches y=450, they overlap with the fire. So they'd die.
To avoid this, I'll make the fire hazard a small obstacle that's easy to avoid: place it where the player doesn't normally go. Or make the fire a "wall" segment that the player must jump over, like a horizontal bar at y=450 with a gap underneath.
Actually, you know what, let me just make the fire and water hazards simple visual elements that the player can avoid by staying on platforms. The fire at y=450 will be below the player's path if the player stays on platforms. The player falls from the trophy platform (y=295) to the ground (y=560). They pass through y=450. If there's fire at y=450, they die.
Okay, final decision: I'll make the fire hazard a horizontal strip at y=450, but I'll make the player's jump arc such that they clear it. The player's max jump height is about 120px. From the trophy platform at y=295, the player can reach y=175 (top of trophy platform) + 120 = 295? No wait.
Let me recalculate jump physics:
- vy_initial = -12 (jump impulse)
- gravity = 0.6 per frame
- Max height: vy^2 / (2g) = 144 / 1.2 = 120 pixels.
- From y=295, the player can reach y=295-120=175 (the top of the lower platform).
So the player can jump from the trophy platform (y=295) up to y=175. They cannot reach y=450 from there. So if the fire is at y=450, the player never goes near it. Good!
But the player falls from y=175 to y=560. They pass through y=450. If there's fire at y=450, they die.
So I need to either:
1. Place fire somewhere the player doesn't pass through (e.g., on a platform the player doesn't use), or
2. Make the fire a pit (no floor) that the player avoids by jumping.
Let me go with option 2: the ground has a gap, and the fire is at the bottom of the gap. The player must jump across the gap. The exit is on the other side.
Revised ground:
- x=0..60: ground
- gap: x=60..100 (no ground)
- x=100..800: ground
Fire hazard: at the bottom of the gap, y=560..592, x=62..98. The player falls into the fire pit and dies.
But then the player needs to jump across the gap to reach the exit at x=240. The gap is from x=60 to x=100 (40 pixels wide). The player's width is 28. They can jump across if they have enough horizontal speed. They can run and jump.
This is a good design! Let me finalize:
**Level layout:**
Ground segments:
- (0, 560, 800, 40) — full ground from x=0 to x=800? No, let me split it.
Actually, let me keep it simple:
- Ground: x=0..59, y=560..600 (left segment)
- Gap: x=60..100, y=560..600 (empty, fire at bottom)
- Ground: x=101..800, y=560..600 (right segment)
Fire pit: at x=62..98, y=560..600 (fire tiles on the floor of the pit).
Water hazard: a pool on the ground segment at x=40..60? But that's on the left ground. Let me put water at x=30..50, y=560..572 (a small puddle on the ground). The player can step on it and die. So the player must avoid stepping on it.
Trophy: on a platform at y=300, x=120..170.
Exit: at x=250, y=560 (portal on the right ground).
Enemy: patrols on the right ground segment, x=200..300, y=560.
Player start: x=20, y=540 (on the left ground).
Movement: left to right. Player walks right, encounters the gap (fire pit) at x=60..100. They need to jump across. The gap is 40px wide. The player's width is 28. With running speed, they can jump across.
Jump height: 120px. The gap is at ground level. The player jumps from theThe output file index.html has not been written, or is empty. Write the complete app to index.html in the current directory now. Use the remaining time to verify it, then finish. The original 15-minute deadline still applies.
thinking
We need to build a playable tile-based platformer on canvas in a Dangerous Dave-inspired style. Requirements:
- HTML, CSS, JS all inline
- Write to index.html in current directory
- No external assets, CDNs, network requests
- No third-party libraries
Required behavior:
- One completable level with solid platforms, a trophy, an exit, fire and water hazards, at least one patrolling enemy
- Procedurally drawn everything
- Left/Right or A/D to move, Space to jump
- Display controls
- Gravity, landing, solid-wall collision; jumping only when grounded
- Player must collect the trophy before the exit completes the level. Reaching exit without trophy gives visible feedback and keeps level active
- Touching hazard or enemy = loss state
- Reaching exit with trophy = win state
- Restart after either outcome, restores level, trophy, enemies, player position. Keep route physically reachable
Verification: Test movement and jump near starting area. Check player lands on solid surface. Trophy and exit visibly identifiable.
Let me design this:
**Game design:**
- Canvas size: maybe 800x600
- Tile-based grid (e.g., 32px per tile)
- Level layout: platforms made of tiles, trophy as a special tile, exit as a special tile, fire hazard (red tiles), water hazard (blue tiles), enemies (patrolling - simple AI that walks back and forth on platforms)
**Player:**
- Rectangle, can move left/right, jump
- Gravity pulls down
- Collision detection with solid platforms
- Grounded when standing on a solid tile
**Level layout:**
Let me create a level like this (using a grid):
- Ground: row 0 is ground? Or platforms at various heights
- Let's make it a side-scrolling platformer (horizontal play area)
Let me think about the layout:
Canvas: 800x600
Tile size: 32px
Grid: 25 rows x 24 columns (800/32=25, 768/32=24)
Level design:
- Row 0: ground (solid)
- Some platforms above
- Trophy somewhere
- Exit somewhere
- Fire hazard (red) and water hazard (blue) as hazards
- Enemy patrolling on a platform
Let me sketch the grid:
Row 0: ground (all solid)
Row 1: air
Row 2: air
Row 3: ground
...
Actually, let me keep it simpler. The level is a single screen (not scrolling). The player moves left/right within the level bounds.
Level layout (0-indexed rows from top):
Row 0: ground (solid)
Row 1: ground
Row 2: ground
Row 3: ground
Row 4: ground
Row 5: ground
Row 6: ground
Row 7: ground
Row 8: ground
Row 9: ground
Row 10: ground
Row 11: ground
Row 12: ground
Row 13: ground
Row 14: ground
Row 15: ground
Row 16: ground
Row 17: ground
Row 18: ground
Row 19: ground
Row 20: ground
Row 21: ground
Row 22: ground
Row 23: ground
Row 24: ground
Hmm, that's just flat ground. I need platforms.
Let me design a more interesting level:
Ground at bottom rows (say rows 20-24 are ground).
Platforms at various heights.
Let me define the grid as 25 rows x 24 cols (768x768... no, 800x600).
Actually, let's use 800x600 canvas, tile size 32:
- Width: 800/32 = 25 tiles
- Height: 600/32 ≈ 18.75 → 19 tiles
So grid: 19 rows x 25 columns.
Level design (row 0 is top):
Row 0: air
Row 1: air
Row 2: air
Row 3: air
Row 4: air
Row 5: air
Row 6: air
Row 7: air
Row 8: air
Row 9: air
Row 10: air
Row 11: air
Row 12: air
Row 13: air
Row 14: air
Row 15: air
Row 16: air
Row 17: ground (solid)
Row 18: ground (solid)
Hmm, let me think about a fun level. Let me place platforms:
Ground: row 17 (bottom two rows solid)
Platforms:
- Platform at row 15, cols 5-12 (ground-level platform)
- Platform at row 13, cols 8-16
- Platform at row 11, cols 3-10
- Platform at row 9, cols 6-14
- Platform at row 7, cols 10-18
- Platform at row 5, cols 2-12
- Platform at row 3, cols 5-15
- Platform at row 1, cols 8-20
Trophy: at row 1, col 12 (or somewhere accessible)
Exit: at row 1, col 22
Fire hazard: red tiles at some positions
Water hazard: blue tiles at some positions
Enemy: patrolling on row 5 platform (cols 6-10 area)
Let me define the grid programmatically. I'll create a 2D array where:
- 0 = air
- 1 = solid ground/platform
- 2 = fire hazard
- 3 = water hazard
- 4 = trophy
- 5 = exit
- 6 = enemy (on a platform)
Actually, for the enemy, I'll have a separate entity list rather than a tile. The enemy patrols on a specific platform row.
Let me write the code structure:
```html
<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #333; }
canvas { border: 2px solid #555; }
</style>
</head>
<body>
<canvas id="game" width="800" height="600"></canvas>
<script>
// Game code here
</script>
</body>
</html>
```
JavaScript game loop:
- Update physics
- Draw everything
- Handle input
Player:
- x, y (in pixels, but aligned to tile grid)
- velocity y
- grounded flag
- position: tile-based, so x and y are multiples of tile size
Movement:
- Left/Right or A/D: set horizontal velocity
- Space: if grounded, set vertical velocity to jump velocity
Gravity: constant downward acceleration
Collision:
- Move horizontally, check collisions with solid tiles, resolve
- Move vertically, check collisions, resolve
- Grounded when touching solid below
Let me implement tile-based collision. The player occupies a rectangle (tile-sized). For simplicity, treat the player as a single tile (32x32) or slightly larger.
Actually, let me make the player 2 tiles wide (64x32) for better control. But for collision, I'll check the player's bounding box against solid tiles.
Simpler approach: player is 1 tile (32x32) for collision. Movement is in tile units.
Let me code it up:
```javascript
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const TILE = 32;
const COLS = 25;
const ROWS = 19;
let grid = [];
function createGrid() {
// Initialize all air
for (let r = 0; r < ROWS; r++) {
grid[r] = [];
for (let c = 0; c < COLS; c++) {
grid[r][c] = 0;
}
}
}
// Fill ground
for (let c = 0; c < COLS; c++) {
grid[17][c] = 1; // ground
grid[18][c] = 1;
}
```
Wait, I need to be careful about the ground. Let me make the ground at the bottom: rows 17 and 18 (the last two rows). Actually, with 19 rows (0-18), rows 17 and 18 are the bottom two.
Let me finalize the level layout:
Ground: rows 17-18, all columns (solid)
Platforms (solid tiles):
- Row 15: cols 5-12 (8 tiles)
- Row 13: cols 8-16 (9 tiles)
- Row 11: cols 3-10 (8 tiles)
- Row 9: cols 6-14 (9 tiles)
- Row 7: cols 10-18 (9 tiles)
- Row 5: cols 2-12 (11 tiles)
- Row 3: cols 5-15 (11 tiles)
- Row 1: cols 8-20 (13 tiles)
Trophy: at row 1, col 14 (tile)
Exit: at row 1, col 22 (tile)
Fire hazard: row 5, cols 4-6 (3 tiles) - red
Water hazard: row 7, cols 14-16 (3 tiles) - blue
Enemy: patrols on row 5 platform, between cols 6-10. Enemy starts at col 6, moves right then left.
Enemy patrol: moves horizontally on its platform row. When it reaches the edge, reverses direction.
Now, let me think about the visual design:
- Background: dark green (Dangerous Dave style? Actually Dangerous Dave has a dark background)
- Platforms: brown/tan color
- Trophy: gold/yellow
- Exit: green ring
- Fire hazard: red
- Water hazard: blue
- Player: red square or character
- Enemy: purple/red creature
Colors:
- Ground: #8B4513 (saddle brown)
- Platform: #A0522D (sienna)
- Fire: #FF4500
- Water: #1E90FF
- Trophy: #FFD700
- Exit: #00FF00 (bright green)
- Player: #FF4444 (red)
- Enemy: #FF00AA (magenta/pink)
UI:
- Show controls: "Arrow Keys / A,D: Move | Space: Jump"
- Show status: "Trophy: 0/1" or something
- Show messages: "Lost!" or "You Win!"
Game states:
- PLAYING
- LOST
- WIN
Restart: press R or click button.
Let me write the full code now.
Player physics:
- gravity = 0.5 (pixels per frame^2)
- jumpVelocity = -10
- maxFallSpeed = 10
Movement speed: 3 pixels per frame (about 1 tile per frame)
Collision detection:
- After moving horizontally, check each tile the player overlaps. If any solid tile, push player out.
- Same for vertical.
Let me implement a function to check if a tile at (tx, ty) is solid:
```javascript
function isSolid(r, c) {
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return false;
return grid[r][c] === 1;
}
```
Player position: x, y in pixels (aligned to tiles, so multiples of TILE).
Collision resolution:
```javascript
function resolveCollision() {
// Horizontal
player.x += player.vx;
for (let c = Math.floor(player.x / TILE); c <= Math.floor((player.x + player.width) / TILE); c++) {
if (isSolid(math.floor(player.y / TILE), c)) {
player.x -= player.vx; // push back
break;
}
}
// Vertical
player.y += player.vy;
player.grounded = false;
for (let r = Math.floor(player.y / TILE); r <= Math.floor((player.y + player.height) / TILE); r++) {
if (isSolid(r, Math.floor(player.x / TILE))) {
player.y -= player.vy;
player.grounded = true;
break;
}
}
}
```
Wait, the order matters. Standard approach: move X, resolve X collisions; move Y, resolve Y collisions.
Let me refine:
```javascript
function updatePlayer() {
// Input
if (keys.left || keys.a) player.vx = -PLAYER_SPEED;
else if (keys.right || keys.d) player.vx = PLAYER_SPEED;
else player.vx = 0;
if (keys.space && player.grounded) {
player.vy = JUMP_VELOCITY;
player.grounded = false;
}
// Gravity
player.vy += GRAVITY;
if (player.vy > MAX_FALL) player.vy = MAX_FALL;
// Move X
player.x += player.vx;
resolveHorizontal();
// Move Y
player.y += player.vy;
resolveVertical();
// Clamp to grid
player.x = Math.max(0, Math.min(COLS * TILE - player.width, player.x));
player.y = Math.max(0, Math.min(ROWS * TILE - player.height, player.y));
}
```
For the trophy collection: if player overlaps trophy tile, increment trophy count and mark trophy as collected.
For exit: if player overlaps exit tile and trophy collected, trigger win.
For hazards: if player overlaps fire/water tile, trigger loss.
Let me code the complete thing now.
I'll also add a simple HUD showing:
- "Controls: Arrow Keys / A,D to move, Space to jump"
- "Trophy: 0/1" or "Trophy: Collected: X"
- Status message area
Let me write the HTML file.
Actually, let me reconsider the level layout to make sure it's fun and reachable:
Ground: rows 17-18 (bottom two rows, all columns 0-24)
Player starts at row 17, col 5 (on the ground, near the first platform).
Player start: x=5*32=160, y=17*32=544 (on the ground).
Platforms:
- Row 15: cols 5-12 (solid)
- Row 13: cols 8-16
- Row 11: cols 3-10
- Row 9: cols 6-14
- Row 7: cols 10-18
- Row 5: cols 2-12
- Row 3: cols 5-15
- Row 1: cols 8-20
Trophy at row 1, col 14
Exit at row 1, col 22
Fire hazard: row 5, cols 4-6 (red tiles)
Water hazard: row 7, cols 14-16 (blue tiles)
Enemy: patrols on row 5 platform, from col 6 to col 10 (enemy starts at col 6, moves right to col 10, then back)
The enemy needs a platform to walk on. Row 5 has a platform from col 2-12, so the enemy can walk from col 6 to col 10.
Enemy behavior: move at speed 1 tile per second (or 2 pixels per frame). Reverse at edges.
Now, let me write the code. I'll make it clean and well-commented.
Let me also add a restart button or key (R key).
State management:
- gameState: 'playing', 'lost', 'win'
- trophyCollected: boolean
- playerPos: reset to start
Let me write the complete index.html now.
I'll structure the JavaScript:
1. Constants and setup
2. Grid generation
3. Entity definitions (player, enemy)
4. Input handling
5. Game logic (update, draw)
6. Main loop
7. Initialization and restart
Let me write it out.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tile Platformer</title>
<style>
body {
margin: 0;
background: #222;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: monospace;
color: #fff;
}
canvas {
border: 2px solid #555;
border-radius: 4px;
}
#hud {
margin-top: 8px;
font-size: 16px;
color: #aaa;
}
#message {
margin-top: 10px;
font-size: 20px;
font-weight: bold;
}
</style>
</head>
<body>
<canvas id="game" width="800" height="600"></canvas>
<div id="hud">Controls: Arrow Keys / A,D to move | Space to jump</div>
<div id="message"></div>
<script>
// ... game code ...
</script>
</body>
</html>
```
Now the JavaScript. Let me write it carefully.
```javascript
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const TILE = 32;
const COLS = 25;
const ROWS = 19;
// Game states
const STATE_PLAYING = 0;
const STATE_LOST = 1;
const STATE_WIN = 2;
let gameState = STATE_PLAYING;
let trophyCollected = false;
let player;
let enemies = [];
let platforms = [];
let trophyPos = null;
let exitPos = null;
let fireHazards = [];
let waterHazards = [];
// Player
const PLAYER_W = TILE * 2; // 64px wide
const PLAYER_H = TILE * 1.5; // 48px tall
const PLAYER_SPEED = 3;
const JUMP_VEL = -10;
const GRAVITY = 0.6;
const MAX_FALL = 12;
// Input
const keys = { left: false, right: false, up: false };
// Initialize level
function initLevel() {
// Create empty grid
grid = [];
for (let r = 0; r < ROWS; r++) {
grid[r] = [];
for (let c = 0; c < COLS; c++) {
grid[r][c] = 0; // 0=air, 1=ground/platform
}
}
// Ground (rows 17-18)
for (let c = 0; c < COLS; c++) {
grid[17][c] = 1;
grid[18][c] = 1;
}
// Platforms
function addPlatform(row, startCol, endCol) {
for (let c = startCol; c <= endCol; c++) {
grid[row][c] = 1;
}
}
addPlatform(15, 5, 12);
addPlatform(13, 8, 16);
addPlatform(11, 3, 10);
addPlatform(9, 6, 14);
addPlatform(7, 10, 18);
addPlatform(5, 2, 12);
addPlatform(3, 5, 15);
addPlatform(1, 8, 20);
// Trophy
trophyPos = { row: 1, col: 14 };
grid[trophyPos.row][trophyPos.col] = 4; // 4=trophy
// Exit
exitPos = { row: 1, col: 22 };
grid[exitPos.row][exitPos.col] = 5; // 5=exit
// Fire hazards
fireHazards = [
{ row: 5, col: 4 }, { row: 5, col: 5 }, { row: 5, col: 6 }
];
// Water hazards
waterHazards = [
{ row: 7, col: 14 }, { row: 7, col: 15 }, { row: 7, col: 16 }
];
// Enemy (patrols on row 5)
enemies = [{
x: 6 * TILE,
y: 5 * TILE + PLAYER_H,
startX: 6 * TILE,
endX: 10 * TILE,
vx: 1.5,
dir: 1,
alive: true
}];
// Player start
player = {
x: 6 * TILE,
y: 17 * TILE,
vx: 0,
vy: 0,
grounded: false
};
}
// Check if a tile is solid
function isSolid(r, c) {
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return false;
return grid[r][c] === 1;
}
// Draw a tile
function drawTile(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, TILE, TILE);
}
// Draw the level
function drawLevel() {
// Background
ctx.fillStyle = '#1a3a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Platforms
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === 1) {
drawTile(c * TILE, r * TILE, '#8B5A2B');
}
}
}
// Hazards
for (const f of fireHazards) {
drawTile(f.col * TILE, f.row * TILE, '#FF4500');
}
for (const w of waterHazards) {
drawTile(w.col * TILE, w.row * TILE, '#1E90FF');
}
// Trophy
if (trophyPos) {
drawTile(trophyPos.col * TILE, trophyPos.row * TILE, '#FFD700');
}
// Exit
if (exitPos) {
drawTile(exitPos.col * TILE, exitPos.row * TILE, '#00FF00');
}
// Enemies
for (const e of enemies) {
if (!e.alive) continue;
drawTile(e.x, e.y, '#FF00AA');
// Simple enemy eyes
ctx.fillStyle = '#000';
ctx.fillRect(e.x + 4, e.y + 8, 4, 4);
ctx.fillRect(e.x + 12, e.y + 8, 4, 4);
}
// Player
const p = player;
ctx.fillStyle = '#FF4444';
ctx.fillRect(p.x, p.y, PLAYER_W, PLAYER_H);
// Player eyes
ctx.fillStyle = '#fff';
ctx.fillRect(p.x + 8, p.y + 10, 6, 6);
}
// Collision resolution
function resolveCollisions() {
const p = player;
const halfW = PLAYER_W / 2;
const halfH = PLAYER_H / 2;
// Horizontal
p.x += p.vx;
const leftCol = Math.floor(p.x / TILE);
const rightCol = Math.floor((p.x + PLAYER_W) / TILE);
for (let c = leftCol; c <= rightCol; c++) {
if (isSolid(p.y / TILE, c)) {
p.x -= p.vx;
break;
}
}
// Vertical
p.y += p.vy;
p.grounded = false;
const topRow = Math.floor(p.y / TILE);
const bottomRow = Math.floor((p.y + PLAYER_H) / TILE);
for (let r = topRow; r <= bottomRow; r++) {
if (isSolid(r, Math.floor(p.x / TILE))) {
p.y -= p.vy;
if (p.vy > 0) p.grounded = true;
break;
}
}
// Clamp to grid
p.x = Math.max(0, Math.min(COLS * TILE - PLAYER_W, p.x));
p.y = Math.max(0, Math.min(ROWS * TILE - PLAYER_H, p.y));
}
// Check trophy collection
function checkTrophy() {
if (!trophyPos) return false;
const px = Math.floor(player.x / TILE);
const py = Math.floor(player.y / TILE);
if (py === trophyPos.row && px === trophyPos.col) {
trophyCollected = true;
return true;
}
return false;
}
// Check hazard contact
function checkHazard() {
const px = Math.floor(player.x / TILE);
const py = Math.floor(player.y / TILE);
// Fire
for (const f of fireHazards) {
if (f.row === py && f.col >= px && f.col <= px + 1) {
return true;
}
}
// Water
for (const w of waterHazards) {
if (w.row === py && w.col >= px && f.col <= px + 1) {
return true;
}
}
return false;
}
// Check exit
function checkExit() {
if (!exitPos) return false;
const px = Math.floor(player.x / TILE);
const py = Math.floor(player.y / TILE);
return (px === exitPos.col && py === exitPos.row);
}
// Update player
function updatePlayer() {
if (gameState !== STATE_PLAYING) return;
// Movement
if (keys.left || keys.a) player.vx = -PLAYER_SPEED;
else if (keys.right || keys.d) player.vx = PLAYER_SPEED;
else player.vx = 0;
// Jump
if ((keys.up || keys.space) && player.grounded) {
player.vy = JUMP_VEL;
player.grounded = false;
}
// Gravity
player.vy += GRAVITY;
if (player.vy > MAX_FALL) player.vy = MAX_FALL;
// Move and collide
resolveCollisions();
// Collect trophy
checkTrophy();
// Check hazards
if (checkHazard()) {
gameState = STATE_LOST;
}
// Check exit
if (checkExit()) {
if (trophyCollected) {
gameState = STATE_WIN;
} else {
gameState = STATE_LOST;
}
}
// Enemy AI
for (const e of enemies) {
if (!e.alive) continue;
// Patrol
e.x += e.vx;
if (e.x > e.endX || e.x < e.startX) {
e.vx *= -1;
}
// Keep enemy on platform
e.y = 5 * TILE + PLAYER_H;
}
}
// Restart game
function restart() {
gameState = STATE_PLAYING;
trophyCollected = false;
player = {
x: 6 * TILE,
y: 17 * TILE,
vx: 0,
vy: 0,
grounded: false
};
enemies = [{
x: 6 * TILE,
y: 5 * TILE + PLAYER_H,
startX: 6 * TILE,
endX: 10 * TILE,
vx: 1.5,
dir: 1,
alive: true
}];
initLevel();
}
// Input handling
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
keys.left = true;
}
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
keys.right = true;
}
if (e.key === ' ' || e.key === 'ArrowUp') {
keys.up = true;
}
if (e.key === 'r' || e.key === 'R') {
restart();
}
});
document.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
keys.left = false;
}
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
keys.right = false;
}
if (e.key === ' ' || e.key === 'ArrowUp') {
keys.up = false;
}
});
// Main loop
function gameLoop() {
if (gameState === STATE_LOST || gameState === STATE_WIN) {
// Still draw the scene
}
updatePlayer();
drawLevel();
requestAnimationFrame(gameLoop);
}
// Start
initLevel();
gameLoop();
```
Wait, there's a bug in `checkHazard` for water - I wrote `f.col` instead of `w.col`. Let me fix that.
Also, I should double-check the collision logic. The player's y position: when standing on the ground (row 17), the player's y should be such that the bottom of the player is at row 17 * TILE. So player.y = 17 * TILE (top of player). The ground is at rows 17 and 18. The player stands on row 17's tiles.
In the collision check, `isSolid(p.y / TILE, c)` checks the tile at the player's top edge. That's not quite right. The player's feet are at p.y + PLAYER_H. The ground tile at row 17 covers y from 17*TILE to 18*TILE. So the player's bottom should be at or below 17*TILE to be grounded.
Let me adjust: the player's y is the top of the player. Ground is at rows 17-18. For the player to be grounded, player.y + PLAYER_H >= 17 * TILE and player.y <= 17 * TILE (i.e., the player is standing on the ground).
Actually, for collision, I'm checking tiles the player overlaps. The player occupies [x, x+PLAyer_W] x [y, y+PLY_H]. A solid tile at row r spans [r*TILE, (r+1)*TILE]. Overlap occurs when these intervals intersect.
For vertical collision, I check rows from topRow to bottomRow. If any of those rows is solid, the player is colliding. Then I push the player up so they're no longer overlapping.
This should work reasonably well. Let me test mentally:
- Player starts at y = 17*32 = 544. Player height 48. So player occupies y 544-592.
- Ground is at rows 17-18 (tiles 544-576 and 576-608).
- Player's bottom (592) > ground top (544), so overlap exists. The loop checks rows 17 and 18, finds solid, pushes player up so y = 544 (top of player aligns with top of ground tile). Good, grounded = true.
When the player jumps, vy becomes negative, player.y decreases. When falling, vy positive, player.y increases. When hitting the ground from above, the bottom of the player hits the top of the ground tile, pushing player up to y=544.
This seems correct.
Now, about the enemy: the enemy's y position is set to 5*TILE + PLAYER_H = 160 + 48 = 208. The enemy sprite is drawn at (e.x, e.y) which is the top-left of the enemy rectangle. The enemy is 32x32. The platform at row 5 is at y=160. The enemy is at y=208, which is 48 pixels below the platform. That's wrong - the enemy should be ON the platform.
Let me fix: enemy y should be 5*TILE (top of platform row 5) = 160. But then the enemy is drawn at y=160, and its bottom is at 192. The platform at row 5 spans y=160-192. So the enemy sits on the platform. Good.
But wait, the enemy's y in the update is changed by vx only (horizontal movement). The enemy doesn't move vertically. So I should set e.y = 5 * TILE (160) initially and keep it fixed.
Let me also make the enemy visible: draw a simple shape. Maybe a rectangle with eyes.
Now, about the trophy and exit drawing: I'm using colors. The trophy is gold, exit is green. These should be visually identifiable.
Let me also add a HUD that shows the trophy status: "Trophy: 0/1" or "Trophy: Collected".
And a message area for "Lost!" or "You Win!".
Let me also handle the case where the player falls off the bottom of the screen. If player.y > ROWS * TILE, they fall into a pit → lose.
Let me add that to the update: if player.y > ROWS * TILE, gameState = STATE_LOST.
Now, let me write the complete file. I'll also make sure the canvas size is 800x600.
One more thing: the player's collision with the trophy. The trophy is at tile (1, 14). The player needs to be standing on that tile. Since the trophy is drawn as a tile, the player collides with it if their bounding box overlaps the tile. My checkTrophy function checks if the player's tile position matches the trophy's tile position. That should work.
Similarly for the exit.
Let me also make the enemy patrol range correct. Enemy starts at x=6*32=192, endX=10*32=320. It moves right, then reverses at x=320, then moves left, reversing at x=192. This keeps it on the platform at row 5 (cols 2-12). The platform at row 5 spans cols 2-12 (64-384 pixels). The enemy moves from 192 to 320, which goes beyond the platform (12*32=384). Wait, endX=320 is within the platform (col 10 = 320). The platform ends at col 12 = 384. So the enemy can move from col 6 to col 10 (192 to 320), which is within the platform. Good.
But when the enemy reaches x=320 and reverses, it will go back to col 6. Fine.
Now, let me write the final code. I'll be careful with all the details.
Actually, let me reconsider the player's starting position. The ground is at rows 17-18. The player starts at y=17*32=544. But the ground is 2 tiles thick (rows 17 and 18). The player stands on row 17. Good.
But wait, the player's y should be such that the player's bottom is at the top of the ground tile. If the ground tile is at row 17 (y=544 to 576), and the player is 48px tall, then player.y = 544 means the player occupies y=544 to 592. The overlap with the ground tile (544-576) is 544-576, which is 32 pixels. The player's bottom (592) extends below the ground. But since the ground continues to row 18 (576-608), the player is actually overlapping both ground tiles. That's fine - the collision check will find solid tiles and push the player up so the bottom aligns with the top of the ground.
Actually, when the player is standing on the ground, the collision should push the player so that player.y + PLAYER_H == 17*32 (the top of the ground tile). So player.y should be 544. And the overlap check: the player's y range [544, 592] overlaps with ground row 17 [544, 576] and row 18 [576, 608]. The loop finds row 17 as solid, pushes player up by vx (which is 0), so no change. The grounded flag is set. Good.
When the player jumps, vy becomes -10. Player.y decreases. When the player's bottom (player.y + 48) goes above 544, no overlap with ground, so no collision. When the player descends again, the bottom hits the ground, overlap detected, push up, grounded=true.
This works.
Now, let me also think about the "restart" functionality. Pressing R should call restart(). The restart function reinitializes everything.
Let me also add a visual indicator for the controls in the HUD.
Alright, let me write the complete index.html file now.
Actually, I realize I