clone / index.html
Greats's picture
Add 2 files
848ea3b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOOM Style Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000;
font-family: 'Courier New', monospace;
color: white;
touch-action: none;
}
#gameCanvas {
display: block;
width: 100%;
height: 100%;
}
#ui {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 20px;
box-sizing: border-box;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
display: flex;
flex-direction: column;
align-items: center;
}
#healthAmmo {
display: flex;
justify-content: space-between;
width: 100%;
max-width: 600px;
margin-bottom: 10px;
}
#weapon {
font-size: 24px;
margin-bottom: 10px;
text-shadow: 0 0 5px red;
}
#startScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: red;
text-align: center;
z-index: 10;
}
#startButton {
padding: 15px 30px;
font-size: 20px;
background-color: #8B0000;
color: white;
border: 2px solid #FF0000;
border-radius: 5px;
cursor: pointer;
margin-top: 30px;
font-family: 'Courier New', monospace;
text-transform: uppercase;
letter-spacing: 2px;
}
#startButton:hover {
background-color: #FF0000;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
transform: translate(-50%, -50%);
pointer-events: none;
}
#crosshair::before, #crosshair::after {
content: '';
position: absolute;
background-color: red;
}
#crosshair::before {
width: 20px;
height: 2px;
top: 9px;
left: 0;
}
#crosshair::after {
width: 2px;
height: 20px;
left: 9px;
top: 0;
}
#gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
color: red;
text-align: center;
z-index: 10;
}
#restartButton {
padding: 15px 30px;
font-size: 20px;
background-color: #8B0000;
color: white;
border: 2px solid #FF0000;
border-radius: 5px;
cursor: pointer;
margin-top: 30px;
font-family: 'Courier New', monospace;
text-transform: uppercase;
letter-spacing: 2px;
}
#restartButton:hover {
background-color: #FF0000;
}
#hud {
position: absolute;
top: 10px;
left: 10px;
font-size: 16px;
color: white;
text-shadow: 0 0 5px black;
}
#enemiesLeft {
position: absolute;
top: 10px;
right: 10px;
font-size: 16px;
color: white;
text-shadow: 0 0 5px black;
}
.health-bar, .ammo-bar {
width: 200px;
height: 20px;
border: 2px solid #333;
border-radius: 3px;
overflow: hidden;
position: relative;
}
.health-fill {
height: 100%;
background: linear-gradient(to right, #8B0000, #FF0000);
transition: width 0.3s;
}
.ammo-fill {
height: 100%;
background: linear-gradient(to right, #006400, #00FF00);
transition: width 0.3s;
}
#weaponImage {
width: 200px;
height: 100px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
margin-bottom: 10px;
}
#bloodEffect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 0, 0, 0);
pointer-events: none;
transition: background-color 0.1s;
z-index: 5;
}
#damageIndicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
color: red;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
text-shadow: 0 0 5px black;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="crosshair"></div>
<div id="bloodEffect"></div>
<div id="damageIndicator">HIT!</div>
<div id="hud">
<div>Level: <span id="level">1</span></div>
<div>Kills: <span id="kills">0</span></div>
</div>
<div id="enemiesLeft">
Enemies: <span id="enemiesCount">0</span>
</div>
<div id="ui">
<div id="weaponImage"></div>
<div id="weapon">PISTOL</div>
<div id="healthAmmo">
<div>
<div>HEALTH</div>
<div class="health-bar">
<div class="health-fill" id="healthBar"></div>
</div>
</div>
<div>
<div>AMMO</div>
<div class="ammo-bar">
<div class="ammo-fill" id="ammoBar"></div>
</div>
</div>
</div>
</div>
<div id="startScreen">
<h1 class="text-4xl font-bold mb-4">DOOM STYLE GAME</h1>
<p class="text-xl mb-8">KILL ALL DEMONS TO ADVANCE TO THE NEXT LEVEL</p>
<p class="mb-2">WASD - Move</p>
<p class="mb-2">Mouse - Look and Shoot</p>
<p class="mb-2">R - Reload</p>
<p class="mb-2">1-3 - Switch Weapons</p>
<p class="mb-2">Space - Jump</p>
<button id="startButton">START GAME</button>
</div>
<div id="gameOverScreen">
<h1 class="text-4xl font-bold mb-4">GAME OVER</h1>
<p class="text-xl mb-2">You killed <span id="finalKills">0</span> demons</p>
<p class="text-xl mb-8">Reached level <span id="finalLevel">1</span></p>
<button id="restartButton">TRY AGAIN</button>
</div>
<script>
// Game setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const startScreen = document.getElementById('startScreen');
const startButton = document.getElementById('startButton');
const gameOverScreen = document.getElementById('gameOverScreen');
const restartButton = document.getElementById('restartButton');
const weaponImage = document.getElementById('weaponImage');
const weaponDisplay = document.getElementById('weapon');
const healthBar = document.getElementById('healthBar');
const ammoBar = document.getElementById('ammoBar');
const levelDisplay = document.getElementById('level');
const killsDisplay = document.getElementById('kills');
const enemiesCountDisplay = document.getElementById('enemiesCount');
const finalKillsDisplay = document.getElementById('finalKills');
const finalLevelDisplay = document.getElementById('finalLevel');
const bloodEffect = document.getElementById('bloodEffect');
const damageIndicator = document.getElementById('damageIndicator');
// Set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Game state
let gameRunning = false;
let level = 1;
let kills = 0;
let enemiesLeft = 0;
// Player
const player = {
x: 1.5,
y: 1.5,
dirX: -1,
dirY: 0,
planeX: 0,
planeY: 0.66,
moveSpeed: 0.05,
rotSpeed: 0.03,
health: 100,
weapons: [
{
name: "PISTOL",
damage: 25,
ammo: 12,
maxAmmo: 12,
reloadTime: 1000,
fireRate: 500,
range: 10,
accuracy: 0.95,
color: "#888",
image: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 100'%3E%3Crect x='20' y='40' width='120' height='20' fill='%23ccc'/%3E%3Crect x='140' y='30' width='40' height='40' fill='%23aaa'/%3E%3Crect x='180' y='40' width='10' height='20' fill='%23888'/%3E%3C/svg%3E"
},
{
name: "SHOTGUN",
damage: 50,
ammo: 6,
maxAmmo: 6,
reloadTime: 1500,
fireRate: 1000,
range: 5,
accuracy: 0.7,
color: "#964B00",
image: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 100'%3E%3Crect x='20' y='40' width='150' height='20' fill='%23b87333'/%3E%3Crect x='170' y='20' width='20' height='60' fill='%238B4513'/%3E%3C/svg%3E"
},
{
name: "CHAINGUN",
damage: 15,
ammo: 50,
maxAmmo: 50,
reloadTime: 2000,
fireRate: 100,
range: 15,
accuracy: 0.85,
color: "#333",
image: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 100'%3E%3Crect x='20' y='40' width='150' height='20' fill='%23444'/%3E%3Crect x='170' y='30' width='20' height='40' fill='%23222'/%3E%3Ccircle cx='40' cy='50' r='15' fill='%23555'/%3E%3C/svg%3E"
}
],
currentWeapon: 0,
lastShot: 0,
reloading: false,
isMoving: false,
isShooting: false,
jumpHeight: 0,
isJumping: false
};
// Map (1 = wall, 0 = empty space)
let map = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];
// Wall textures (colors for simplicity)
const wallTextures = [
'#8B0000', // Red brick
'#006400', // Green stone
'#00008B', // Blue metal
'#4B0082', // Purple tech
'#8B4513' // Brown wood
];
// Enemies
let enemies = [];
// Generate level
function generateLevel() {
// Clear previous enemies
enemies = [];
// Generate maze-like map
const size = 10 + level * 2;
map = Array(size).fill().map(() => Array(size).fill(1));
// Carve out maze
const stack = [];
const visited = Array(size).fill().map(() => Array(size).fill(false));
// Start at player position
player.x = 1.5;
player.y = 1.5;
map[1][1] = 0;
visited[1][1] = true;
stack.push([1, 1]);
while (stack.length > 0) {
const [x, y] = stack[stack.length - 1];
const directions = [
[0, 1], [1, 0], [0, -1], [-1, 0]
].sort(() => Math.random() - 0.5);
let moved = false;
for (const [dx, dy] of directions) {
const nx = x + dx * 2;
const ny = y + dy * 2;
if (nx > 0 && nx < size - 1 && ny > 0 && ny < size - 1 && !visited[nx][ny]) {
map[x + dx][y + dy] = 0;
map[nx][ny] = 0;
visited[nx][ny] = true;
stack.push([nx, ny]);
moved = true;
break;
}
}
if (!moved) {
stack.pop();
}
}
// Add some random walls
for (let i = 1; i < size - 1; i++) {
for (let j = 1; j < size - 1; j++) {
if (map[i][j] === 0 && Math.random() < 0.1) {
map[i][j] = 2 + Math.floor(Math.random() * (wallTextures.length - 1));
}
}
}
// Place exit
const exitX = size - 2;
const exitY = size - 2;
map[exitX][exitY] = 0;
// Place enemies
enemiesLeft = 5 + level * 3;
for (let i = 0; i < enemiesLeft; i++) {
let x, y;
do {
x = 1 + Math.floor(Math.random() * (size - 2));
y = 1 + Math.floor(Math.random() * (size - 2));
} while (map[x][y] !== 0 || (x === 1 && y === 1) || (x === exitX && y === exitY));
enemies.push({
x: x + 0.5,
y: y + 0.5,
health: 50 + level * 10,
speed: 0.02 + level * 0.005,
damage: 10 + level * 2,
color: `hsl(${Math.random() * 60}, 100%, 50%)`,
lastAttack: 0,
attackCooldown: 1000,
size: 0.5
});
}
// Update UI
enemiesCountDisplay.textContent = enemiesLeft;
}
// Raycasting
function castRays() {
const width = canvas.width;
const height = canvas.height;
for (let x = 0; x < width; x++) {
// Calculate ray position and direction
const cameraX = 2 * x / width - 1;
const rayDirX = player.dirX + player.planeX * cameraX;
const rayDirY = player.dirY + player.planeY * cameraX;
// Which box of the map we're in
let mapX = Math.floor(player.x);
let mapY = Math.floor(player.y);
// Length of ray from current position to next x or y-side
let sideDistX, sideDistY;
// Length of ray from one x or y-side to next x or y-side
const deltaDistX = Math.abs(1 / rayDirX);
const deltaDistY = Math.abs(1 / rayDirY);
// Direction to step in x or y direction (either +1 or -1)
let stepX, stepY;
// Was a wall hit?
let hit = false;
// Was a wall hit that was NS or EW?
let side;
// Distance to wall
let perpWallDist;
// Calculate step and initial sideDist
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;
}
// Perform DDA (Digital Differential Analysis)
while (!hit) {
// Jump to next map square, either in x-direction, or in y-direction
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
// Check if ray has hit a wall
if (mapX < 0 || mapX >= map.length || mapY < 0 || mapY >= map[0].length) {
hit = true;
} else if (map[mapX][mapY] > 0) {
hit = true;
}
}
// Calculate distance projected on camera direction
if (side === 0) {
perpWallDist = (mapX - player.x + (1 - stepX) / 2) / rayDirX;
} else {
perpWallDist = (mapY - player.y + (1 - stepY) / 2) / rayDirY;
}
// Calculate height of line to draw on screen
let lineHeight = Math.floor(height / perpWallDist);
// Calculate lowest and highest pixel to fill in current stripe
let drawStart = -lineHeight / 2 + height / 2;
if (drawStart < 0) drawStart = 0;
let drawEnd = lineHeight / 2 + height / 2;
if (drawEnd >= height) drawEnd = height - 1;
// Choose wall color based on map value
let color;
if (mapX < 0 || mapX >= map.length || mapY < 0 || mapY >= map[0].length) {
color = '#000';
} else {
const wallType = map[mapX][mapY];
color = wallTextures[wallType - 1] || '#FFF';
}
// Give x and y sides different brightness
if (side === 1) {
color = shadeColor(color, -30);
}
// Draw the wall slice
ctx.fillStyle = color;
ctx.fillRect(x, drawStart + player.jumpHeight, 1, drawEnd - drawStart);
// Draw floor (simplified)
ctx.fillStyle = '#333';
ctx.fillRect(x, drawEnd + player.jumpHeight, 1, height - drawEnd);
}
}
// Draw enemies
function drawEnemies() {
const width = canvas.width;
const height = canvas.height;
// Sort enemies by distance (for proper rendering order)
enemies.sort((a, b) => {
const distA = Math.pow(player.x - a.x, 2) + Math.pow(player.y - a.y, 2);
const distB = Math.pow(player.x - b.x, 2) + Math.pow(player.y - b.y, 2);
return distB - distA;
});
for (const enemy of enemies) {
// Calculate enemy position relative to player
const relX = enemy.x - player.x;
const relY = enemy.y - player.y;
// Transform enemy position with inverse camera matrix
const invDet = 1.0 / (player.planeX * player.dirY - player.dirX * player.planeY);
const transformX = invDet * (player.dirY * relX - player.dirX * relY);
const transformY = invDet * (-player.planeY * relX + player.planeX * relY);
// Don't draw enemies behind the player
if (transformY <= 0) continue;
// Calculate sprite position on screen
const spriteScreenX = Math.floor((width / 2) * (1 + transformX / transformY));
// Calculate sprite dimensions
const spriteHeight = Math.abs(Math.floor(height / transformY));
const spriteWidth = spriteHeight;
// Calculate drawing coordinates
let drawStartX = -spriteWidth / 2 + spriteScreenX;
let drawEndX = spriteWidth / 2 + spriteScreenX;
let drawStartY = -spriteHeight / 2 + height / 2;
let drawEndY = spriteHeight / 2 + height / 2;
// Clamp to screen bounds
if (drawStartX < 0) drawStartX = 0;
if (drawEndX >= width) drawEndX = width - 1;
if (drawStartY < 0) drawStartY = 0;
if (drawEndY >= height) drawEndY = height - 1;
// Draw enemy
for (let stripe = drawStartX; stripe < drawEndX; stripe++) {
const texX = Math.floor((stripe - (-spriteWidth / 2 + spriteScreenX)) * enemy.size / spriteWidth);
if (transformY > 0 && stripe > 0 && stripe < width) {
for (let y = drawStartY; y < drawEndY; y++) {
const d = (y - (-spriteHeight / 2 + height / 2)) * 256 / spriteHeight;
const texY = Math.floor(d * enemy.size / spriteHeight);
// Simple enemy drawing (just a colored rectangle)
if (texX >= 0 && texX < enemy.size * 100 && texY >= 0 && texY < enemy.size * 100) {
ctx.fillStyle = enemy.color;
ctx.fillRect(stripe, y + player.jumpHeight, 1, 1);
}
}
}
}
}
}
// Draw weapon
function drawWeapon() {
const weapon = player.weapons[player.currentWeapon];
weaponImage.style.backgroundImage = `url("${weapon.image}")`;
weaponDisplay.textContent = weapon.name;
// Weapon bob when moving
let bobOffset = 0;
if (player.isMoving) {
bobOffset = Math.sin(Date.now() / 100) * 5;
}
// Weapon recoil when shooting
let recoilOffset = 0;
if (player.isShooting) {
recoilOffset = Math.sin(Date.now() / 50) * 10;
}
weaponImage.style.transform = `translateY(${bobOffset + recoilOffset}px)`;
}
// Update HUD
function updateHUD() {
const weapon = player.weapons[player.currentWeapon];
healthBar.style.width = `${player.health}%`;
ammoBar.style.width = `${(weapon.ammo / weapon.maxAmmo) * 100}%`;
levelDisplay.textContent = level;
killsDisplay.textContent = kills;
enemiesCountDisplay.textContent = enemiesLeft;
// Blood effect when hurt
if (player.health < 30) {
bloodEffect.style.backgroundColor = `rgba(255, 0, 0, ${0.3 - (player.health / 100)})`;
} else {
bloodEffect.style.backgroundColor = 'rgba(255, 0, 0, 0)';
}
}
// Move player
function movePlayer() {
if (!gameRunning) return;
// Movement speed
const moveSpeed = player.moveSpeed;
const rotSpeed = player.rotSpeed;
// Rotate left/right
if (keys.ArrowLeft) {
const oldDirX = player.dirX;
player.dirX = player.dirX * Math.cos(rotSpeed) - player.dirY * Math.sin(rotSpeed);
player.dirY = oldDirX * Math.sin(rotSpeed) + player.dirY * Math.cos(rotSpeed);
const oldPlaneX = player.planeX;
player.planeX = player.planeX * Math.cos(rotSpeed) - player.planeY * Math.sin(rotSpeed);
player.planeY = oldPlaneX * Math.sin(rotSpeed) + player.planeY * Math.cos(rotSpeed);
}
if (keys.ArrowRight) {
const oldDirX = player.dirX;
player.dirX = player.dirX * Math.cos(-rotSpeed) - player.dirY * Math.sin(-rotSpeed);
player.dirY = oldDirX * Math.sin(-rotSpeed) + player.dirY * Math.cos(-rotSpeed);
const oldPlaneX = player.planeX;
player.planeX = player.planeX * Math.cos(-rotSpeed) - player.planeY * Math.sin(-rotSpeed);
player.planeY = oldPlaneX * Math.sin(-rotSpeed) + player.planeY * Math.cos(-rotSpeed);
}
// Move forward/backward
let moveX = 0, moveY = 0;
player.isMoving = false;
if (keys.w) {
moveX += player.dirX * moveSpeed;
moveY += player.dirY * moveSpeed;
player.isMoving = true;
}
if (keys.s) {
moveX -= player.dirX * moveSpeed;
moveY -= player.dirY * moveSpeed;
player.isMoving = true;
}
// Strafe left/right
if (keys.a) {
moveX -= player.planeX * moveSpeed;
moveY -= player.planeY * moveSpeed;
player.isMoving = true;
}
if (keys.d) {
moveX += player.planeX * moveSpeed;
moveY += player.planeY * moveSpeed;
player.isMoving = true;
}
// Jump
if (keys[' '] && !player.isJumping) {
player.isJumping = true;
player.jumpHeight = -20;
}
// Handle jumping
if (player.isJumping) {
player.jumpHeight += 2;
if (player.jumpHeight >= 0) {
player.jumpHeight = 0;
player.isJumping = false;
}
}
// Collision detection
if (map[Math.floor(player.x + moveX)][Math.floor(player.y)] === 0) {
player.x += moveX;
}
if (map[Math.floor(player.x)][Math.floor(player.y + moveY)] === 0) {
player.y += moveY;
}
// Check if player reached exit
if (Math.floor(player.x) === map.length - 2 && Math.floor(player.y) === map[0].length - 2) {
if (enemiesLeft === 0) {
level++;
generateLevel();
}
}
}
// Update enemies
function updateEnemies() {
const now = Date.now();
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
// Move towards player
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0.5) { // Don't get too close
enemy.x += (dx / dist) * enemy.speed;
enemy.y += (dy / dist) * enemy.speed;
// Simple collision with walls
if (map[Math.floor(enemy.x)][Math.floor(enemy.y)] !== 0) {
enemy.x -= (dx / dist) * enemy.speed;
enemy.y -= (dy / dist) * enemy.speed;
}
}
// Attack player if close enough
if (dist < 1.5 && now - enemy.lastAttack > enemy.attackCooldown) {
player.health -= enemy.damage;
enemy.lastAttack = now;
// Show damage indicator
damageIndicator.style.opacity = 1;
setTimeout(() => {
damageIndicator.style.opacity = 0;
}, 300);
// Blood effect
bloodEffect.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
setTimeout(() => {
bloodEffect.style.backgroundColor = 'rgba(255, 0, 0, 0)';
}, 100);
// Check if player died
if (player.health <= 0) {
gameOver();
}
}
// Remove dead enemies
if (enemy.health <= 0) {
enemies.splice(i, 1);
enemiesLeft--;
kills++;
// Update UI
enemiesCountDisplay.textContent = enemiesLeft;
killsDisplay.textContent = kills;
}
}
}
// Shoot
function shoot() {
const now = Date.now();
const weapon = player.weapons[player.currentWeapon];
// Check if can shoot
if (now - player.lastShot < weapon.fireRate || player.reloading || weapon.ammo <= 0) {
return;
}
player.lastShot = now;
player.isShooting = true;
setTimeout(() => {
player.isShooting = false;
}, 100);
// Reduce ammo
weapon.ammo--;
// Check for hits
for (let i = 0; i < enemies.length; i++) {
const enemy = enemies[i];
// Calculate angle to enemy
const dx = enemy.x - player.x;
const dy = enemy.y - player.y;
const dist = Math.sqrt(dx * dx + dy * dy);
// Angle between player direction and enemy
const playerAngle = Math.atan2(player.dirY, player.dirX);
const enemyAngle = Math.atan2(dy, dx);
let angleDiff = Math.abs(playerAngle - enemyAngle);
// Normalize angle
if (angleDiff > Math.PI) {
angleDiff = 2 * Math.PI - angleDiff;
}
// Check if enemy is in front and within range
if (dist < weapon.range && angleDiff < 0.5 * weapon.accuracy) {
// Hit the enemy
enemy.health -= weapon.damage;
// Show damage indicator
damageIndicator.style.opacity = 1;
setTimeout(() => {
damageIndicator.style.opacity = 0;
}, 300);
// Enemy hit effect
enemy.color = `hsl(${Math.random() * 60}, 100%, 70%)`;
setTimeout(() => {
enemy.color = `hsl(${Math.random() * 60}, 100%, 50%)`;
}, 100);
}
}
// Reload if out of ammo
if (weapon.ammo <= 0) {
reload();
}
}
// Reload
function reload() {
if (player.reloading) return;
const weapon = player.weapons[player.currentWeapon];
if (weapon.ammo === weapon.maxAmmo) return;
player.reloading = true;
setTimeout(() => {
weapon.ammo = weapon.maxAmmo;
player.reloading = false;
}, weapon.reloadTime);
}
// Game over
function gameOver() {
gameRunning = false;
finalKillsDisplay.textContent = kills;
finalLevelDisplay.textContent = level;
gameOverScreen.style.display = 'flex';
}
// Game loop
function gameLoop() {
if (!gameRunning) return;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw ceiling (simplified)
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, canvas.width, canvas.height / 2);
// Cast rays and draw walls
castRays();
// Draw enemies
drawEnemies();
// Update player and enemies
movePlayer();
updateEnemies();
// Draw weapon
drawWeapon();
// Update HUD
updateHUD();
// Request next frame
requestAnimationFrame(gameLoop);
}
// Keyboard controls
const keys = {
w: false,
a: false,
s: false,
d: false,
' ': false,
ArrowLeft: false,
ArrowRight: false,
r: false
};
document.addEventListener('keydown', (e) => {
if (e.key in keys) keys[e.key] = true;
// Switch weapons
if (e.key >= '1' && e.key <= '3') {
player.currentWeapon = parseInt(e.key) - 1;
}
// Reload
if (e.key === 'r') {
reload();
}
});
document.addEventListener('keyup', (e) => {
if (e.key in keys) keys[e.key] = false;
});
// Mouse controls
let mouseX = 0;
let mouseDown = false;
canvas.addEventListener('mousedown', () => {
mouseDown = true;
if (gameRunning) shoot();
});
canvas.addEventListener('mouseup', () => {
mouseDown = false;
});
canvas.addEventListener('mousemove', (e) => {
if (!gameRunning) return;
const movementX = e.movementX || 0;
// Rotate view based on mouse movement
if (movementX !== 0) {
const rotSpeed = 0.002 * movementX;
const oldDirX = player.dirX;
player.dirX = player.dirX * Math.cos(rotSpeed) - player.dirY * Math.sin(rotSpeed);
player.dirY = oldDirX * Math.sin(rotSpeed) + player.dirY * Math.cos(rotSpeed);
const oldPlaneX = player.planeX;
player.planeX = player.planeX * Math.cos(rotSpeed) - player.planeY * Math.sin(rotSpeed);
player.planeY = oldPlaneX * Math.sin(rotSpeed) + player.planeY * Math.cos(rotSpeed);
}
});
// Touch controls for mobile
let touchStartX = 0;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
if (gameRunning) shoot();
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!gameRunning) return;
const touchX = e.touches[0].clientX;
const movementX = (touchX - touchStartX) * 0.1;
touchStartX = touchX;
// Rotate view based on touch movement
if (movementX !== 0) {
const rotSpeed = 0.002 * movementX;
const oldDirX = player.dirX;
player.dirX = player.dirX * Math.cos(rotSpeed) - player.dirY * Math.sin(rotSpeed);
player.dirY = oldDirX * Math.sin(rotSpeed) + player.dirY * Math.cos(rotSpeed);
const oldPlaneX = player.planeX;
player.planeX = player.planeX * Math.cos(rotSpeed) - player.planeY * Math.sin(rotSpeed);
player.planeY = oldPlaneX * Math.sin(rotSpeed) + player.planeY * Math.cos(rotSpeed);
}
});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
});
// Start game
function startGame() {
startScreen.style.display = 'none';
gameRunning = true;
// Reset game state
level = 1;
kills = 0;
player.health = 100;
player.currentWeapon = 0;
player.weapons.forEach(w => w.ammo = w.maxAmmo);
// Generate level
generateLevel();
// Start game loop
gameLoop();
}
// Restart game
function restartGame() {
gameOverScreen.style.display = 'none';
startGame();
}
// Start button event
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', restartGame);
// Handle window resize
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// Helper function to shade colors
function shadeColor(color, percent) {
let R = parseInt(color.substring(1, 3), 16);
let G = parseInt(color.substring(3, 5), 16);
let B = parseInt(color.substring(5, 7), 16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = R < 255 ? R : 255;
G = G < 255 ? G : 255;
B = B < 255 ? B : 255;
const RR = R.toString(16).length === 1 ? '0' + R.toString(16) : R.toString(16);
const GG = G.toString(16).length === 1 ? '0' + G.toString(16) : G.toString(16);
const BB = B.toString(16).length === 1 ? '0' + B.toString(16) : B.toString(16);
return '#' + RR + GG + BB;
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Greats/clone" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>