Scan to open on your phone
Point a camera at the QR code - it opens this exact paste, no app needed.
https://codepastes.com/zzgsmxcvmfepbmymuntitledPlain Text
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