67

Pasted Oct 24, 2026, 6:19 PM1 file346 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgsmxcvmfepbmym
untitled
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Pure Red Room - Survival Mode</title>
    <style>
        body { margin: 0; padding: 0; overflow: hidden; background-color: #000; font-family: sans-serif; user-select: none; }
        canvas { display: block; width: 100vw; height: 100vh; image-rendering: pixelated; }
        #instructions {
            position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
            color: #ff3333; font-size: 20px; text-align: center;
            text-shadow: 2px 2px 4px #000; font-weight: bold; pointer-events: none;
            z-index: 10;
        }
        #crosshair {
            position: absolute; top: 50%; left: 50%;
            width: 6px; height: 6px; background: rgba(255,255,255,0.4);
            transform: translate(-50%, -50%); border-radius: 50%; pointer-events: none; z-index: 5;
        }
        #timer {
            position: absolute; top: 70px; left: 50%; transform: translateX(-50%);
            color: #ffffff; font-size: 32px; font-family: monospace; font-weight: bold;
            text-shadow: 2px 2px 4px #000; pointer-events: none; z-index: 10;
        }
    </style>
</head>
<body>
    <div id="crosshair"></div>
    <div id="instructions">3D RED ROOM (Survival)<br>WASD to Move | Move Mouse to Look Around | SPACEBAR to Jump</div>
    <div id="timer">Time: 0.0s / 10.0s</div>
    <canvas id="gameCanvas"></canvas>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const timerElement = document.getElementById('timer');

        function resize() {
            canvas.width = window.innerWidth / 2;
            canvas.height = window.innerHeight / 2;
        }
        window.addEventListener('resize', resize);
        resize();

        const player = {
            x: 12.0, 
            y: 12.0,
            dirX: 1,
            dirY: 0,
            planeX: 0,
            planeY: 1.2, 
            moveSpeed: 5.0, 
            pitch: 0,
            z: 0,            
            zVelocity: 0,    
            isJumping: false
        };

        // Monster properties matching the Raycaster engine setup
        const monster = {
            x: 2.0, // Spawn far away near the top corner of the room
            y: 2.0,
            moveSpeed: 3.5, 
            size: 0.4 // Collision/visual thickness factor
        };

        // Game State Engine variables
        let survivalTime = 0;
        const timeLimit = 10.0;
        let gameOver = false;
        let gameWon = false;

        const gravity = -22.0;  
        const jumpForce = 7.0;  

        const mapLayout = [
            "111111111111111111111111",
            "100000000000000000000001",
            "102002002002002002002001",
            "100000000000000000000001",
            "100000000000000000000001",
            "100200020002000200020001",
            "100000000000000000000001",
            "100000000000000000000001",
            "102002002002002002002001",
            "100000000000000000000001",
            "100000000000000000000001",
            "100200020002000200020001",
            "100000000000000000000001",
            "100000000000000000000001",
            "102002002002002002002001",
            "100000000000000000000001",
            "100000000000000000000001",
            "100200020002000200020001",
            "100000000000000000000001",
            "100000000000000000000001",
            "102002002002002002002001",
            "100000000000000000000001",
            "100000000000000000000001",
            "111111111111111111111111"
        ];

        const map = mapLayout.map(row => row.split('').map(Number));
        const mapMaxIdx = map.length - 1;
        const keys = {};

        window.addEventListener('keydown', e => {
            keys[e.key.toLowerCase()] = true;
            if (e.key === ' ' && !player.isJumping && !gameOver && !gameWon) {
                player.zVelocity = jumpForce;
                player.isJumping = true;
            }
            // Press R to restart game seamlessly
            if (e.key.toLowerCase() === 'r' && (gameOver || gameWon)) {
                resetGame();
            }
        });
        window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);

        canvas.addEventListener('click', () => {
            canvas.requestPointerLock();
        });

        window.addEventListener('mousemove', e => {
            if (document.pointerLockElement !== canvas) return;
            if (gameOver || gameWon) return;
            
            const rotation = e.movementX * 0.003; 
            if (rotation !== 0) {
                const oldDirX = player.dirX;
                player.dirX = player.dirX * Math.cos(rotation) - player.dirY * Math.sin(rotation);
                player.dirY = oldDirX * Math.sin(rotation) + player.dirY * Math.cos(rotation);
                const oldPlaneX = player.planeX;
                player.planeX = player.planeX * Math.cos(rotation) - player.planeY * Math.sin(rotation);
                player.planeY = oldPlaneX * Math.sin(rotation) + player.planeY * Math.cos(rotation);
            }

            player.pitch -= e.movementY * 0.4;
            const pitchLimit = canvas.height * 0.75;
            player.pitch = Math.max(-pitchLimit, Math.min(pitchLimit, player.pitch));
        });

        function resetGame() {
            player.x = 12.0;
            player.y = 12.0;
            player.z = 0;
            player.zVelocity = 0;
            player.isJumping = false;
            monster.x = 2.0;
            monster.y = 2.0;
            survivalTime = 0;
            gameOver = false;
            gameWon = false;
            timerElement.style.color = '#ffffff';
            timerElement.innerHTML = `Time: 0.0s / ${timeLimit.toFixed(1)}s`;
        }

        let lastTime = performance.now();
        const zBuffer = new Array(canvas.width); // Depth array tracking for correct sprite rendering

        function loop() {
            requestAnimationFrame(loop);

            const now = performance.now();
            const delta = Math.min((now - lastTime) / 1000, 0.1);
            lastTime = now;

            // --- Game State Tracking & AI Engine Run ---
            if (!gameOver && !gameWon) {
                survivalTime += delta;
                timerElement.innerHTML = `Time: ${Math.min(survivalTime, timeLimit).toFixed(1)}s / ${timeLimit.toFixed(1)}s`;

                if (survivalTime >= timeLimit) {
                    gameWon = true;
                    timerElement.innerHTML = 'YOU SURVIVED! [Press R to Restart]';
                    timerElement.style.color = '#00ff00';
                }

                // AI Tracking Vector Arithmetic (Move towards player coordinate values)
                let toPlayerX = player.x - monster.x;
                let toPlayerY = player.y - monster.y;
                let distToPlayer = Math.sqrt(toPlayerX * toPlayerX + toPlayerY * toPlayerY);

                if (distToPlayer > 0) {
                    monster.x += (toPlayerX / distToPlayer) * monster.moveSpeed * delta;
                    monster.y += (toPlayerY / distToPlayer) * monster.moveSpeed * delta;
                }

                // Check Instant Kill Distance Condition (Radius hit box test)
                if (distToPlayer < 0.6) {
                    gameOver = true;
                    timerElement.innerHTML = 'YOU DIED! [Press R to Restart]';
                    timerElement.style.color = '#ff0000';
                }
            }

            // Player physics updating loops
            if (player.isJumping) {
                player.zVelocity += gravity * delta; 
                player.z += player.zVelocity * delta;

                if (player.z <= 0) {
                    player.z = 0;
                    player.zVelocity = 0;
                    player.isJumping = false;
                }
            }

            let moveX = 0;
            let moveY = 0;

            if (!gameOver && !gameWon) {
                if (keys['w'] || keys['arrowup']) {
                    moveX += player.dirX * player.moveSpeed * delta;
                    moveY += player.dirY * player.moveSpeed * delta;
                }
                if (keys['s'] || keys['arrowdown']) {
                    moveX -= player.dirX * player.moveSpeed * delta;
                    moveY -= player.dirY * player.moveSpeed * delta;
                }
                if (keys['a'] || keys['arrowleft']) {
                    moveX -= player.planeX * player.moveSpeed * delta;
                    moveY -= player.planeY * player.moveSpeed * delta;
                }
                if (keys['d'] || keys['arrowright']) {
                    moveX += player.planeX * player.moveSpeed * delta;
                    moveY += player.planeY * player.moveSpeed * delta;
                }
            }

            const buffer = 0.3;
            const signX = moveX > 0 ? buffer : -buffer;
            const signY = moveY > 0 ? buffer : -buffer;

            const nextGridX = Math.max(0, Math.min(mapMaxIdx, Math.floor(player.x + moveX + signX)));
            const currentGridY = Math.max(0, Math.min(mapMaxIdx, Math.floor(player.y)));
            const tileX = map[nextGridX][currentGridY];
            if (tileX === 0 || (tileX === 2 && player.z > 0.35)) player.x += moveX;

            const currentGridX = Math.max(0, Math.min(mapMaxIdx, Math.floor(player.x)));
            const nextGridY = Math.max(0, Math.min(mapMaxIdx, Math.floor(player.y + moveY + signY)));
            const tileY = map[currentGridX][nextGridY];
            if (tileY === 0 || (tileY === 2 && player.z > 0.35)) player.y += moveY;

            const jumpHeightOffset = Math.floor(player.z * (canvas.height * 0.4));
            const horizon = Math.floor(canvas.height / 2 + player.pitch + jumpHeightOffset);
Use code with caution.
// Render Background Base Colors (Dynamically turn room intense red on player death state)
ctx.fillStyle = gameOver ? '#2b0000' : '#400000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = gameOver ? '#7a0000' : '#7a0000';
if (horizon > 0) {
ctx.fillRect(0, 0, canvas.width, Math.min(canvas.height, horizon));
}
// --- Raycasting Engine Loop ---
for (let x = 0; x < canvas.width; x++) {
const cameraX = 2 * x / canvas.width - 1;
const rayDirX = player.dirX + player.planeX * cameraX;
const rayDirY = player.dirY + player.planeY * cameraX;
let mapX = Math.floor(player.x);
let mapY = Math.floor(player.y);
const deltaDistX = Math.abs(1 / (rayDirX || 0.00001));
const deltaDistY = Math.abs(1 / (rayDirY || 0.00001));
let sideDistX, sideDistY, stepX, stepY, hit = 0, side;
if (rayDirX < 0) { stepX = -1; sideDistX = (player.x - mapX) * deltaDistX; }
else { stepX = 1; sideDistX = (mapX + 1.0 - player.x) * deltaDistX; }
if (rayDirY < 0) { stepY = -1; sideDistY = (player.y - mapY) * deltaDistY; }
else { stepY = 1; sideDistY = (mapY + 1.0 - player.y) * deltaDistY; }
while (hit === 0) {
if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; }
else { sideDistY += deltaDistY; mapY += stepY; side = 1; }
if (mapX < 0 || mapX > mapMaxIdx || mapY < 0 || mapY > mapMaxIdx) break;
if (map[mapX][mapY] > 0) {
if (map[mapX][mapY] === 2) {
let wallIntersectionZ = 0;
let perpWallDistTest = (side === 0) ? (sideDistX - deltaDistX) : (sideDistY - deltaDistY);
let intersectX = (side === 0) ? (player.y + perpWallDistTest * rayDirY) : (player.x + perpWallDistTest * rayDirX);
intersectX -= Math.floor(intersectX);
if (player.z <= 0.35) hit = 1;
} else {
hit = 1;
}
}
}
let perpWallDist;
if (side === 0) perpWallDist = (mapX - player.x + (1 - stepX) / 2) / rayDirX;
else perpWallDist = (mapY - player.y + (1 - stepY) / 2) / rayDirY;
if (perpWallDist <= 0) perpWallDist = 0.01;
zBuffer[x] = perpWallDist; // Stashing standard line wall depth tracking
let lineHeight = Math.floor(canvas.height / perpWallDist);
let drawStart = -lineHeight / 2 + horizon;
let drawEnd = lineHeight / 2 + horizon;
let colorStr = '#ff0000';
if (map[mapX] && map[mapX][mapY] === 2) {
colorStr = side === 1 ? '#990000' : '#b30000';
} else {
colorStr = side === 1 ? '#cc0000' : '#e60000';
}
ctx.fillStyle = colorStr;
ctx.fillRect(x, Math.max(0, Math.floor(drawStart)), 1, Math.max(0, Math.floor(drawEnd - drawStart)));
}
// --- 3D Sprite Raycaster Engine Calculation (Rendering the Monster Lime Green) ---
const spriteX = monster.x - player.x;
const spriteY = monster.y - player.y;
// Matrix transformation inverse calculations
const invDet = 1.0 / (player.planeX * player.dirY - player.dirX * player.planeY);
const transformX = invDet * (player.dirY * spriteX - player.dirX * spriteY);
const transformY = invDet * (-player.planeY * spriteX + player.planeX * spriteY); // depth Z space
if (transformY > 0.1) {
const spriteScreenX = Math.floor((canvas.width / 2) * (1 + transformX / transformY));
// Scale height relative to distance and factor pitch/jump positions
const spriteHeight = Math.abs(Math.floor(canvas.height / transformY));
let drawStartV = -spriteHeight / 2 + horizon;
let drawEndV = spriteHeight / 2 + horizon;
const spriteWidth = Math.abs(Math.floor(canvas.height / transformY));
let drawStartX = Math.floor(-spriteWidth / 2 + spriteScreenX);
let drawEndX = Math.floor(spriteWidth / 2 + spriteScreenX);
// Column loop drawing the 2D Monster matrix onto canvas slices
for (let stripe = drawStartX; stripe < drawEndX; stripe++) {
if (stripe >= 0 && stripe < canvas.width && transformY < zBuffer[stripe]) {
ctx.fillStyle = '#00ff00'; // Lime green color rendering
ctx.fillRect(stripe, Math.max(0, Math.floor(drawStartV)), 1, Math.max(0, Math.floor(drawEndV - drawStartV)));
}
}
}
}
// Initialize engine script execution
resetGame();
requestAnimationFrame(loop);



### Integrated Mechanics Checklist
* **Correct 3D Sprite Raycasting:** The monster is mapped inside a matrix transformation loop. It won't clip through solid level architecture awkwardly; instead, it is hidden behind walls because it reads the depth values straight from the runtime `zBuffer`.
* **Framerate Speed Optimization:** The player tracking logic relies on variable delta clock times (`delta`). The monster will consistently close distances smoothly regardless of screen configurations.
* **Instant Death & Safe Spawning:** The monster initiates calculations safely in the upper-left corner coordinates `(2, 2)` so you have enough real estate to run away immediately upon execution. If it gets within `0.6` grid units, it ends the execution loop.
* **Key Interactivity:** Pressing **`R`** instantly restores position properties back to default baselines seamlessly.

<FollowUp>
Let me know if you want to alter anything else in your game:
* Would you like the monster to **speed up over time** to make the final seconds harder?
* Do you want to add **obstacles or walls** inside the open field to hide from it?
</FollowUp>

15,019 chars · codepastes.com