Wolfenstein raycaster · deepseek-v4-pro
Build a first-person raycasting maze shooter on a canvas in the Wolfenstein 3D style: WASD movement and arrow or mouse turning through a textured grid maze with enemies you can shoot. Procedural textures only, no external assets.
transcript · 7 messages
thinking
The user wants me to build a Wolfenstein 3D-style raycasting maze shooter as a single-page web app. This is a substantial project. Let me plan the architecture:
1. **Raycasting engine**: Cast rays from the player's position to render walls with textured surfaces
2. **Procedural textures**: Generate textures for walls, floors, ceilings using canvas patterns
3. **Maze generation**: Create a grid-based maze procedurally
4. **Enemies**: Sprites rendered in 3D that can be shot
5. **Controls**: WASD movement, arrow keys or mouse for turning
6. **Shooting**: Space or click to shoot
Let me build this step by step:
- Canvas-based rendering
- Ray casting with DDA algorithm
- Wall texturing
- Floor/ceiling casting (or simplified)
- Sprite rendering for enemies
- Minimap
- Maze generation (recursive backtracking or similar)
- Game loop with delta time
- Enemy AI (basic patrol/chase)
- Shooting mechanics
- HUD (health, ammo)
Let me write the complete index.html file.
Key components:
1. **Map**: 2D grid array where each cell is a wall (1) or empty (0)
2. **Player**: position (x, y), direction angle, FOV
3. **Raycasting**: Cast rays across the FOV, find wall hits using DDA
4. **Textures**: Generate procedural textures as ImageData or offscreen canvases
5. **Enemies**: Objects with position, state, rendered as billboard sprites
6. **Shooting**: Ray from player center, check if it hits an enemy
Let me think about the math:
**Raycasting (DDA algorithm)**:
- For each column of the screen (strip), cast a ray
- Ray angle = playerAngle - FOV/2 + column * (FOV / screenWidth)
- Step through grid cells using DDA
- When hitting a wall, calculate distance and wall height
- Draw textured column
**Textures**:
- Procedural patterns: brick, stone, moss, etc.
- Create with canvas 2D operations (rectangles, noise-like patterns)
**Enemies**:
- Store as objects with x, y, health, state
- Sort by distance (far to near)
- Project onto screen, draw as scaled rectangles with texture
**Shooting**:
- Ray from player to center of screen
- Check if it hits any enemy (within some tolerance)
Let me write the full implementation. I'll make it quite complete and polished.
Actually, let me think about the texture generation more carefully. I'll pre-generate several wall textures:
- Different wall types for the maze
- Each as an offscreen canvas
For the maze, I'll use a simple recursive backtracking or just a predefined but interesting layout. Actually, let me use a cellular automata or recursive backtracking maze generator.
Let me structure the code:
```
HTML:
- Canvas element
- Instructions overlay
CSS:
- Dark background, centered canvas
- Styling for UI elements
JS:
- Constants and configuration
- Texture generation
- Maze generation
- Game state (player, enemies, bullets)
- Raycasting render
- Sprite rendering
- Minimap
- Input handling
- Game loop
```
Let me code this up. I'll aim for a solid, playable game.
For maze generation, I'll use a simple approach: start with random walls, then carve out rooms and corridors, or use recursive backtracking on a grid of cells.
Actually, for a maze shooter, I think I'll create a predefined but interesting map layout, or use a procedural maze generator. Let me use recursive backtracking to generate a proper maze, then maybe open up some areas.
Recursive backtracking maze:
- Grid of cells (each cell is 2x2 in the actual map grid, with walls between)
- Start from a cell, mark visited, randomly choose unvisited neighbors, remove wall between them
- This creates a perfect maze
The map will be a 2D array. Let me determine the size. A typical Wolf3D level is 64x64. For a browser game, maybe 32x32 or 40x40. Let me go with something like 31x31 for the maze (odd numbers work better for mazes).
Actually let me simplify: I'll use a cell-based maze generator where each "room" is a 1x1 cell and walls are between them. The actual map grid will have wall cells between room cells.
Let me define:
- Maze cells: N x N grid
- Each cell maps to a 3x3 block in the map (center is open, edges are potential walls/passages)
- Or simpler: map is (2*N+1) x (2*N+1) where even indices are walls/cells and odd indices are passages
Simpler approach: map size 31x31, generate maze with recursive backtracking on 15x15 cell grid (odd positions are cells, even are walls between).
OK, let me just code it up. I'll use a 33x33 map for a 16x16 cell maze.
Let me write the complete code now.
One more thing: for floor and ceiling, I can either do floor casting (computationally expensive) or just do solid colors/gradients. For performance, I'll do solid colors with maybe a simple gradient. Wolfenstein 3D actually had floor/ceiling textures too, but the basic raycasting just does walls. Let me add a simple floor/ceiling effect.
For performance optimization:
- Use ImageData for pixel manipulation
- Pre-compute textures
- Use requestAnimationFrame
- Limit enemy count
Let me write this now. I'll make it comprehensive.
Actually, for the rendering approach, I'll use:
1. Clear canvas
2. Draw ceiling (solid color)
3. Draw floor (solid color)
4. For each column, cast ray, draw wall slice
5. Draw sprites (enemies, projectiles)
6. Draw minimap
7. Draw HUD
For the DDA raycasting, the key steps:
- Calculate ray direction based on player angle and column
- Determine which map square we're in
- Calculate side distances
- Step through map using DDA
- When we hit a wall, calculate the perpendicular distance
- Calculate wall height: screenHeight / perpDist
- Draw the textured column
Let me implement all of this now.
For textures, I'll create several procedural textures:
- Brick wall (red/brown bricks)
- Stone wall (gray stones)
- Mossy stone
- Blue stone (like Wolf3D blue walls)
- Wood
Each texture will be 64x64 pixels, created on an offscreen canvas.
For enemy sprites, I'll create simple procedural sprites too - maybe a red/evil face or shape.
Let me finalize the design and write the code.
I think I'll aim for these features:
- ~33x33 map with maze
- 4-5 texture types
- 6-8 enemies
- Health system
- Ammo system (limited ammo, reload mechanic or just limited shots)
- Score tracking
- Minimap
- Crosshair
- Sound effects? No, no external assets. I could use Web Audio API for simple beeps but that might complicate things. Let me keep it visual only.
- Particle effects on enemy death
Let me write the complete code:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raycaster Maze Shooter</title>
<style>
/* styles */
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
// All game code
</script>
</body>
</html>
```
Let me now write the full implementation. I need to be careful with the math.
Key math:
- Player has x, y (floating point in map coordinates) and angle (radians)
- FOV = 60 degrees = PI/3
- For each screen column i (0 to screenWidth-1):
- cameraX = 2*i/screenWidth - 1 (ranges from -1 to 1)
- rayDirX = dirX + planeX * cameraX
- rayDirY = dirY + planeY * cameraX
where dir = (cos(angle), sin(angle)) and plane = (-sin(angle)*FOVfactor, cos(angle)*FOVfactor)
Actually, the standard way:
- dirX = cos(playerAngle), dirY = sin(playerAngle)
- planeX = -sin(playerAngle) * tan(FOV/2), planeY = cos(playerAngle) * tan(FOV/2)
- rayDirX = dirX + planeX * cameraX
- rayDirY = dirY + planeY * cameraX
This gives the camera plane perpendicular to the direction vector.
DDA:
- deltaDistX = abs(1 / rayDirX) if rayDirX != 0 else very large
- deltaDistY = abs(1 / rayDirY) if rayDirY != 0 else very large
- stepX = rayDirX < 0 ? -1 : 1
- stepY = rayDirY < 0 ? -1 : 1
- sideDistX = (rayDirX < 0 ? (playerX - mapX) : (mapX + 1 - playerX)) * deltaDistX
- sideDistY = (rayDirY < 0 ? (playerY - mapY) : (mapY + 1 - playerY)) * deltaDistY
Then step until hitting a wall, choosing the shorter distance each time.
When a wall is hit:
- perpDist = side == 0 ? (sideDistX - deltaDistX) : (sideDistY - deltaDistY)
- wallHeight = screenHeight / perpDist
- drawStart = -wallHeight/2 + screenHeight/2
- drawEnd = wallHeight/2 + screenHeight/2
- wallX = playerY + perpDist * rayDirY for vertical hit, or playerX + perpDist * rayDirX for horizontal hit
- wallX -= floor(wallX) to get fractional position
- texX = wallX * textureWidth
- For vertical vs horizontal hits, maybe darken one side
Let me also handle fish-eye correction: the perpendicular distance already handles this.
OK I think I have enough understanding. Let me write the complete code now.
One thing: for the maze, I'll use recursive backtracking. The maze grid will be 16x16 cells. The actual map will be 33x33 (16*2+1).
Cell (i, j) in the maze maps to map position (2*i+1, 2*j+1).
Wall between cell (i, j) and (i, j+1) is at map position (2*i+1, 2*j+2).
Wall between cell (i, j) and (i+1, j) is at map position (2*i+2, 2*j+1).
Let me also add some columns/pillars for variety and open up some areas for more interesting gameplay.
Actually, to make it more fun and less claustrophobic, I'll generate the maze and then remove some additional walls to create larger rooms and better gameplay flow.
For enemies, they'll patrol and when they see the player (line of sight), they'll approach.
OK let me just write the code. It's going to be long but comprehensive.
Let me think about the structure more carefully to make sure it all fits together:
```javascript
// === CONSTANTS ===
const SCREEN_WIDTH = 800;
const SCREEN_HEIGHT = 500;
const MAP_WIDTH = 33;
const MAP_HEIGHT = 33;
const TEX_WIDTH = 64;
const TEX_HEIGHT = 64;
const FOV = Math.PI / 3;
const NUM_TEXTURES = 5;
const MAX_ENEMIES = 8;
// === TEXTURE GENERATION ===
// Generate textures[] as offscreen canvases
// === MAZE GENERATION ===
// Generate worldMap[][] using recursive backtracking
// === GAME STATE ===
let player = { x, y, angle, health, ammo, score };
let enemies = [];
let particles = [];
// === INPUT ===
let keys = {};
// Mouse handling for rotation
// === RAYCASTING ===
function castRays() { ... }
// === RENDER ===
function render() {
// 1. Draw floor and ceiling
// 2. Draw walls (from z-buffer)
// 3. Draw sprites (enemies)
// 4. Draw particles
// 5. Draw HUD
// 6. Draw minimap
}
// === GAME LOOP ===
function gameLoop(timestamp) {
handleInput(deltaTime);
updateEnemies(deltaTime);
updateParticles(deltaTime);
render();
requestAnimationFrame(gameLoop);
}
```
Alright, let me write it all out now. This will be a substantial file.
For the shooting mechanic:
- When player shoots, cast a ray straight ahead
- Check if any enemy is close to that ray (within a certain radius)
- If hit, damage enemy
- Create particle effect
- Decrease ammo
For enemy rendering (billboard sprites):
- Calculate enemy position relative to player
- Transform to camera space
- Project to screen
- Draw scaled sprite
- Need z-buffer for correct occlusion with walls
The z-buffer from wall rendering tells us the perpDist for each column. When drawing sprites, we check if the sprite's screen column is behind a wall (farther than the z-buffer value).
Let me implement all of this. I'll try to keep it reasonably clean and well-commented.
Actually, for the sprite z-buffer check, I need to store perpDist for each column during wall rendering.
Let me write the complete implementation now. I'll aim for quality and playability.
I realize I should also handle collision detection - player shouldn't walk through walls. I'll use simple box collision (check if next position is in a wall cell, with some margin).
Also for enemies, they need basic AI:
- Idle: stand still or patrol
- Chase: move toward player if they have line of sight
- Attack: when close enough, damage player
- Pain: when hit, briefly stunned
- Death: fall over, spawn particles
Let me implement a simpler version:
- Enemies have state (idle, chase, attack)
- If player is within detection range and in line of sight, enter chase
- If close enough, attack
- Line of sight: cast a ray from enemy to player, check if it hits any walls
For the minimap, I'll draw it in the corner:
- Small representation of the map
- Player position and direction
- Enemy positions
- Visible area
Alright, writing it now. Let me also think about the canvas size. I want it to be responsive but within the HTML file. I'll set a fixed canvas size that looks good. Maybe 800x500 for the game view.
For the HUD, I'll display:
- Health bar
- Ammo count
- Score
- Crosshair
Let me start coding.
Actually wait, for the textures, I need to be creative with procedural generation. Let me make:
1. **Gray stone blocks** - irregular rectangles with varying shades
2. **Red brick** - brick pattern with mortar lines
3. **Blue stone** - similar to stone but blue-tinted
4. **Mossy stone** - green-tinted irregular pattern
5. **Wood planks** - horizontal lines with grain
Each generated as a 64x64 canvas.
For the enemy sprite, I'll create a simple humanoid-ish shape in red/orange.
For the weapon/gun at the bottom of the screen (like Wolf3D), I could draw a simple gun shape... but that might be complex. Let me keep it simple - just a crosshair and HUD.
Actually, for a more satisfying game feel, let me add a simple gun sprite at the bottom center that animates when shooting. I can draw it procedurally as well.
OK let me stop overthinking and write the code. I'll iterate if needed.
One more consideration: the maze. A 33x33 map with recursive backtracking will create a dense maze. For better gameplay, I should:
1. Generate the maze
2. Remove some random walls to create shortcuts and larger open areas
3. Maybe place some pillars/decorative blocks
Let me finalize and write.
Here's my plan for the file structure:
```
index.html
├── <style> - CSS for canvas and UI
├── <canvas> - game canvas
├── <div> - UI overlays (HUD, messages)
└── <script>
├── Constants
├── Texture generation
├── Maze generation
├── Game state
├── Input handling
├── Raycasting
├── Sprite rendering
├── Enemy AI
├── Particle system
├── HUD rendering
├── Minimap
└── Game loop
```
Let me write it now. I'll focus on making it work correctly.
For the maze, I'll go with a 31x31 or similar. Let me use 31x31 (which is 15*2+1 for a 15x15 cell maze). That gives enough room for exploration.
Actually let me go with MAP_SIZE = 31 for a 15x15 cell maze.
Now, the enemy sprites. I need to project them correctly. The math:
1. Translate enemy position relative to player:
- spriteX = enemy.x - player.x
- spriteY = enemy.y - player.y
2. Transform to camera space:
- invDet = 1.0 / (planeX * dirY - dirX * planeY)
- transformX = invDet * (dirY * spriteX - dirX * spriteY)
- transformY = invDet * (-planeY * spriteX + planeX * spriteY)
3. Screen position:
- spriteScreenX = (screenWidth / 2) * (1 + transformX / transformY)
- spriteHeight = abs(screenHeight / transformY)
- spriteWidth = spriteHeight (for square sprites)
4. Draw the sprite column by column, checking z-buffer
This is the standard approach from lodev's tutorial.
Let me code everything now.
I need to be careful about the camera plane calculation. In Wolfenstein-style raycasting:
- dirX = cos(angle), dirY = sin(angle)
- planeX = -sin(angle) * (aspectRatio * tan(FOV/2))
Actually: planeX = -dirY * tan(FOV/2) * aspectRatio
planeY = dirX * tan(FOV/2) * aspectRatio
Wait, the plane length determines FOV. Usually:
planeLength = tan(FOV/2)
planeX = -dirY * planeLength
planeY = dirX * planeLength
But this gives a 1:1 aspect ratio FOV. For a wider screen, you need to multiply by aspect ratio.
Actually no. The plane vector's length relative to the direction vector determines the FOV. The aspect ratio is handled by the fact that cameraX ranges from -1 to 1 horizontally, and the screen height handles the vertical.
So: planeX = -dirY * tan(FOV/2), planeY = dirX * tan(FOV/2)
And for each column i: cameraX = 2*i/screenWidth - 1
rayDirX = dirX + planeX * cameraX
rayDirY = dirY + planeY * cameraX
This should work correctly.
Now, for the z-buffer, I store the perpendicular distance for each column. When drawing sprites, for each column of the sprite on screen, I check if transformY < zBuffer[screenX].
Let me finalize and write the complete code. It'll be long but self-contained.
I think I'll also add a simple gun/weapon display. Nothing too fancy, just a rectangle at the bottom center that recoils when shooting. Actually, let me keep it simple and focus on the core gameplay.
Let me also add:
- Screen shake on taking damage
- Flash effect on shooting
- Simple sound via Web Audio API oscillator? No, let me keep visual only to avoid complexity.
For the floor, I can fill the lower half with a solid color. For the ceiling, a different solid color. This is simple and fast.
Actually, for a better visual effect, I could do a simple gradient floor (darker near the bottom, lighter near the horizon). Let me do that.
OK, writing the code now. Final plan:
- Canvas: 800x500
- Map: 31x31
- Textures: 5 types, 64x64 each
- Enemies: 6-8
- Features: shooting, health, ammo, score, minimap, particles on death
- Controls: WASD, mouse to look, click to shoot
Let me write it.
Hmm, one more thing about the maze. I want it to feel like a proper level, not just a perfect maze. Let me:
1. Generate a maze
2. Remove some walls to create loops
3. Add a few larger rooms by removing certain walls
4. Place enemies in dead ends or rooms
For the maze generation, I'll do:
- Create a grid of cells (15x15)
- Each cell starts with all walls
- Use recursive backtracking to carve passages
- Then remove ~20% of remaining walls for connectivity
- Convert to the 31x31 map grid
Let me code this up.
Actually, I realize I need to be more careful. The map needs to have a border of walls (the outer edge should be walls) so the player can't walk off the map. So the outer ring of the 31x31 grid should be walls.
In the maze representation:
- Cells are at odd indices (1, 3, 5, ..., 29)
- Walls between cells are at even indices
- The border (indices 0 and 30) is always walls
So for a 15x15 cell maze, the map is 31x31.
Cell (cy, cx) in the maze corresponds to map position (cy*2+1, cx*2+1).
Wall to the right of cell (cy, cx) is at map position (cy*2+1, cx*2+2).
Wall below cell (cy, cx) is at map position (cy*2+2, cx*2+1).
Initially, all cells are separated by walls (all even indices are 1).
The maze generation removes walls between visited cells.
Let me write this now. I've been planning long enough.
For the complete code, I'll structure it as follows. I want to make sure the game is fun and playable.
Oh, one more thing: enemy sprites. I need to draw them procedurally. I'll create a simple sprite on an offscreen canvas - maybe a red/orange creature shape. A simple design:
- Roundish head
- Body
- Maybe two eyes
- When damaged, flash white
For the projectile/enemy death particles, I'll use simple colored rectangles that fade out.
OK, truly writing now. Let me produce the complete file.
I'm going to use a fairly large canvas. Let me go with 960x540 (16:9) for a nice widescreen look. Actually 800x500 is good for fitting on screens. Let me stick with 800x500.
Let me also think about the minimap. I'll place it in the top-left or bottom-left corner. It'll be about 150x150 pixels showing the map, player, enemies, and visible area.
Alright, for the shooting: I'll make it so clicking or pressing space shoots. There should be a cooldown between shots. Each shot consumes 1 ammo. Enemies take 2-3 shots to kill. Ammo can be found... hmm, I won't have pickups. Let me just give the player plenty of ammo (like 50) and each enemy takes 2 shots. There are 8 enemies so 16 shots needed minimum, 50 is fine.
Or even simpler: unlimited ammo but with a cooldown. That way the player focuses on shooting enemies. Let me go with unlimited ammo and a 0.3s cooldown.
Actually no, limited ammo adds tension. Let me give the player 30 ammo, each enemy takes 2 hits, 8 enemies = 16 minimum. So there's some pressure to aim well.
Wait, I can add ammo pickups too. When an enemy dies, they could drop ammo. That would be a nice mechanic.
OK here's my final plan:
- Player starts with 30 ammo, 100 health
- 8 enemies, each has 2 HP
- Enemies drop 5 ammo when killed
- Shooting cooldown: 0.4s
- Enemies deal damage when close (10 damage per second)
- Player dies at 0 HP, game over screen
- Kill all enemies, victory screen
Let me write the code now. For real this time.
I need to be mindful of the code length. This will be a substantial file, probably 600-800 lines. Let me make it clean and functional.
Let me structure the code:
```javascript
// === CONFIGURATION ===
const CONFIG = { ... };
// === TEXTURES ===
function generateTextures() { ... }
// === MAZE ===
function generateMaze() { ... }
// === GAME STATE ===
const state = { ... };
// === INPUT ===
function setupInput() { ... }
// === RAYCASTER ===
function castRay(angle) { ... }
function renderWalls() { ... }
// === SPRITES ===
function renderSprites() { ... }
// === ENEMIES ===
function updateEnemies(dt) { ... }
// === PARTICLES ===
function spawnParticles(x, y, color) { ... }
function updateParticles(dt) { ... }
// === HUD ===
function drawHUD() { ... }
// === MINIMAP ===
function drawMinimap() { ... }
// === GAME LOOP ===
function gameLoop(ts) { ... }
// === INIT ===
function init() { ... }
init();
```
Let me write it all now. I'll try to make the rendering look good with nice colors and effects.
For the texture generation, I'll use canvas 2D drawing:
**Brick texture** (red/brown):
- Background: dark red/brown
- Draw horizontal lines for mortar (gray)
- Draw vertical lines offset every other row for brick pattern
- Add some noise/variation in color
**Stone texture** (gray):
- Random irregular rectangles
- Varying shades of gray
- Some darker lines for cracks
**Blue stone**:
- Similar to stone but blue tones
**Mossy stone**:
- Green-tinted with some dark spots
**Wood**:
- Horizontal grain lines
- Brown with varying shades
For each texture, I'll draw on a 64x64 offscreen canvas, then get the ImageData. During rendering, I'll sample from the ImageData.
Actually, during rendering, it's easier to keep the textures as offscreen canvases and use drawImage with source rectangles. But that's slower. Using ImageData with direct pixel manipulation is faster for the wall rendering.
Hmm, but getImageData and putImageData are expensive too. The standard approach is to use an offscreen canvas for the texture, then during wall rendering, use ctx.drawImage to copy the texture column. But that means I'm calling drawImage for every column of the screen.
Actually, the most efficient way in canvas is:
1. Render everything to an offscreen canvas
2. Then draw the offscreen canvas to the main canvas
Or just use putImageData for the whole screen. Let me use the ImageData approach for walls:
- Create the screen's ImageData once
- For each column, get the texture column from the texture's ImageData
- Copy it to the screen ImageData
- At the end, putImageData on the canvas
This is fast because we're doing one putImageData call per frame.
For the textures, I'll pre-extract the ImageData from each offscreen texture canvas. Then during rendering, I'll read pixels from those ImageData arrays.
Let me finalize this approach.
Actually, there's a simpler approach. Let me use the canvas directly:
1. Fill ceiling color
2. Fill floor color
3. For each column, use ctx.drawImage to draw a slice of the texture scaled to the wall height
4. Then draw sprites
5. Then draw HUD/minimap
This is simpler to code and performs well enough for 800 columns. Each drawImage call is for a 1-pixel-wide slice, which is fast.
But to make it even faster, I can batch columns with the same texture into larger rectangles where possible. However, since each column might have a different height (due to distance), this optimization is limited. Let me just go with the per-column approach.
Actually, for even better performance and code simplicity, let me use the per-column drawImage approach but use an offscreen canvas for the game world, then composite the HUD on top. The drawImage for 1-pixel-wide columns should be fine for modern browsers.
Let me simplify and go with:
- Main game rendered with drawImage per column for walls
- Sprites rendered with drawImage for scaled enemy sprites
- HUD/minimap drawn with normal canvas operations
OK, WRITING THE CODE NOW.
Let me think about one more thing: the enemy sprite. I need to generate a simple sprite procedurally. I'll create a 64x64 offscreen canvas with a simple "enemy" shape. Something like:
- A reddish/orange creature
- Circular body
- Simple face (eyes, mouth)
- Maybe some horns or spikes
When the enemy is hit, I can tint it white briefly.
For the crosshair, I'll draw a simple crosshair in the center of the screen.
For the gun at the bottom, I can draw a simple weapon - maybe just a rectangle representing the gun barrel.
Alright, now I truly am going to write the complete code. Let me produce the entire index.html.
One issue: the game might be quite hard with limited ammo and enemies that can kill you. Let me balance it:
- Player: 100 HP
- Enemy damage: 5 HP per second when in melee range
- Enemies: 8, each with 2 HP
- Player ammo: 30, each kill drops 5 ammo
- Shooting cooldown: 0.3s
- Enemy detection range: 8 map units
- Enemy speed: 1.5 units/sec
- Player speed: 3 units/sec
This should be manageable. The enemies will chase you if they see you, and you need to shoot them before they get too close.
OK HERE WE GO. Writing the full code now.
I need to handle window resize? No, I'll use a fixed canvas size for simplicity.
Hmm, actually for the pointer lock (mouse look), I need to handle:
- Click on canvas to request pointer lock
- While pointer is locked, mouse movement rotates the player
- Click to shoot
- Escape or game over releases pointer lock
Let me implement this properly.
For touch/mobile, I won't optimize since the spec mentions WASD and arrow keys. Desktop only.
Let me also add a start screen or instructions.
OK, truly writing now. Let me produce the full HTML file.
I'll also add:
- Muzzle flash effect (brief white flash at center)
- Enemy death animation (brief flash, then particles)
- Screen slightly reddens when taking damage
- Simple gun bob animation when walking
Alright here is the complete implementation. I'm going to write it all out now.
Let me think about the maze generation one more time. I'll use iterative DFS (stack-based) instead of recursion to avoid any potential stack overflow:
```
Initialize: all walls present
Choose random starting cell, mark visited, push to stack
While stack not empty:
current = stack top
Find unvisited neighbors of current
If found:
Choose random unvisited neighbor
Remove wall between current and neighbor
Mark neighbor visited, push to stack
Else:
Pop from stack
```
Then remove additional walls (~30% of remaining) for better gameplay flow.
Convert to map grid.
Then place enemies at random locations in open cells (where map[ry][rx] == 0), ensuring they're not too close to the player start.
Player starts at a specific location (I'll use the top-left open area or the center of the first cell).
OK NOW THE CODE:/workspace/index.html (26257 bytes)Successfully wrote 26257 bytes to /workspace/index.html
thinking
The file has been written. Let me verify it loads and check for any issues. Let me also think about potential problems:
1. The textures array uses genTexture which returns {canvas, data}. The data is the ImageData.data (Uint8ClampedArray). But I'm using tex.canvas for drawImage, which should work since I store the canvas reference.
2. The render function uses ctx.drawImage(tex.canvas, srcX, 0, 1, TEX_S, col, sy, 1, colH) - this should work for drawing texture columns.
3. I should verify the file exists and check for any obvious issues.
Let me also think about whether the maze generation and game logic work correctly. Let me look at a few potential issues:
- The collision detection might have an issue with the condition `map[Math.floor(player.y)][Math.floor(newX+margin*(moveX>0?1:-1))]===0` - this checks two map cells in the direction of movement, which is good.
- The line of sight function steps at 0.2 intervals which should be fine for ray-marching through the grid.
- Enemy spawning filters out positions too close to the player. Good.
- The shoot function does a ray-march to check for wall occlusion. Good.
Let me also verify the HTML is valid. It seems complete.
One thing I notice: I used `spriteData` which is the raw pixel data from the enemy sprite. In the render function, I access it as `spriteData[(texY*TEX_S+texX)*4]`. This should work correctly.
Let me check for syntax errors by running some checks.{"command":"wc -l /workspace/index.html && head -5 /workspace/index.html && echo \"---\" && tail -5 /workspace/index.html"}789 /workspace/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> --- requestAnimationFrame(gameLoop); })(); </script> </body> </html>
thinking
The file is complete. Let me check for any JavaScript syntax issues by running a quick syntax check.
{"command":"node -e \"\nconst fs = require('fs');\nconst html = fs.readFileSync('/workspace/index.html','utf-8');\nconst scriptMatch = html.Syntax OK
thinking
The JavaScript is syntactically valid. The game is complete. Let me summarize what was built.
The file is complete and syntactically valid. Here's what was built: ## `/workspace/index.html` — Wolfenstein 3D-Style Maze Shooter **Raycasting engine:** - DDA (Digital Differential Analyzer) raycasting across 960 columns at 60° FOV - Per-column z-buffer for correct wall/sprite occlusion - Darker shading on Y-side wall hits for depth perception **5 procedural textures (64×64, generated via offscreen canvas):** - Red brick with mortar lines and noise - Gray stone blocks with cracks - Blue stone blocks - Mossy green stone with organic splotches - Wood planks with grain lines **Procedural maze (31×31 grid):** - Recursive backtracking on a 15×15 cell grid - ~30% of remaining walls randomly removed for looping corridors - Open central arena carved out for combat encounters **Enemies (up to 8):** - Procedural 64×64 sprite (red demon with horns, eyes, arms, legs) - Patrol when idle, chase when they see the player (line-of-sight check) - Melee attack when within range, health bar above each enemy - White hit-flash on damage, particle bursts on death - Drop 5 ammo when killed **Combat & HUD:** - Click or Space to shoot (0.35s cooldown, 30 starting ammo) - Ray-traced bullet with wall occlusion check and enemy hit detection - Health bar (bottom-left), ammo counter + score (bottom-right) - Muzzle flash effect, screen-reddening on damage - Procedural gun at bottom center with recoil animation - Crosshair and minimap (top-right, 140×140px) **Controls:** - **WASD** — move forward/back/strafe - **Mouse** — look around (pointer lock) - **Arrow keys** — alternative turning - **Click / Space / E** — shoot
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
EVALUATION
Textured raycasting maze with enemies, HUD, and minimap; movement, turning, shooting, and ammo/score all respond correctly.
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.