math-doom / index.html
math3craft's picture
Add 2 files
adaf9e8 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Math Doom</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: 'Courier New', monospace;
background-color: #000;
color: #fff;
display: flex;
flex-direction: column;
height: 100vh;
}
#gameContainer {
position: relative;
flex-grow: 1;
overflow: hidden;
}
#canvas {
display: block;
background-color: #000;
cursor: none;
}
#menu {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
}
#menu h1 {
font-size: 3em;
color: #ff0000;
margin-bottom: 0.5em;
text-shadow: 0 0 10px #ff0000;
}
.menu-button {
background-color: #ff0000;
color: #000;
border: none;
padding: 15px 30px;
margin: 10px;
font-size: 1.5em;
cursor: pointer;
font-family: 'Courier New', monospace;
font-weight: bold;
transition: all 0.3s;
border-radius: 5px;
}
.menu-button:hover {
background-color: #ffffff;
transform: scale(1.05);
}
#hud {
position: absolute;
bottom: 20px;
left: 20px;
font-size: 1.5em;
color: #fff;
text-shadow: 2px 2px 2px #000;
display: flex;
flex-direction: column;
gap: 10px;
}
#health, #ammo, #weapon {
display: flex;
align-items: center;
gap: 10px;
}
#health span, #ammo span {
color: #ff0000;
font-weight: bold;
}
#weaponName {
color: #ffcc00;
font-weight: bold;
}
#levelInfo {
position: absolute;
top: 20px;
left: 20px;
font-size: 1.2em;
color: #fff;
text-shadow: 2px 2px 2px #000;
}
#levelInfo span {
color: #00ff00;
font-weight: bold;
}
#enemyCount {
position: absolute;
top: 20px;
right: 20px;
font-size: 1.2em;
color: #fff;
text-shadow: 2px 2px 2px #000;
}
#enemyCount span {
color: #ff0000;
font-weight: bold;
}
#mathPopup {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.9);
border: 2px solid #ff0000;
padding: 20px;
border-radius: 10px;
display: none;
flex-direction: column;
align-items: center;
z-index: 20;
}
#mathPopup p {
margin: 0 0 20px 0;
font-size: 1.5em;
color: #fff;
}
#mathAnswer {
font-size: 1.2em;
padding: 10px;
width: 200px;
text-align: center;
margin-bottom: 10px;
background-color: #333;
border: 1px solid #ff0000;
color: #fff;
}
#mathSubmit {
padding: 10px 20px;
background-color: #ff0000;
color: #000;
border: none;
font-weight: bold;
cursor: pointer;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
pointer-events: none;
z-index: 5;
}
#crosshair::before, #crosshair::after {
content: '';
position: absolute;
background-color: #ff0000;
}
#crosshair::before {
width: 2px;
height: 20px;
left: 9px;
top: 0;
}
#crosshair::after {
width: 20px;
height: 2px;
left: 0;
top: 9px;
}
#deathScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 15;
}
#deathScreen h1 {
font-size: 3em;
color: #ff0000;
margin-bottom: 0.5em;
}
#winScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 15;
}
#winScreen h1 {
font-size: 3em;
color: #00ff00;
margin-bottom: 0.5em;
}
#damageIndicator {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 0, 0, 0.3);
display: none;
pointer-events: none;
z-index: 5;
}
#fpsCounter {
position: absolute;
top: 10px;
right: 10px;
color: #00ff00;
font-size: 0.8em;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="canvas"></canvas>
<div id="crosshair"></div>
<div id="hud">
<div id="health">HEALTH: <span id="healthValue">100</span></div>
<div id="ammo">AMMO: <span id="ammoValue">50</span></div>
<div id="weapon">WEAPON: <span id="weaponName">PISTOL</span></div>
</div>
<div id="levelInfo">LEVEL: <span id="levelValue">1</span></div>
<div id="enemyCount">ENEMIES: <span id="enemyCountValue">5</span></div>
<div id="mathPopup">
<p id="mathQuestion">What is 5 + 3?</p>
<input type="text" id="mathAnswer" placeholder="Enter answer">
<button id="mathSubmit">SUBMIT</button>
</div>
<div id="menu">
<h1>MATH DOOM</h1>
<button class="menu-button" id="startButton">START GAME</button>
<button class="menu-button" id="howToButton">HOW TO PLAY</button>
</div>
<div id="deathScreen">
<h1>YOU DIED</h1>
<button class="menu-button" id="restartButton">TRY AGAIN</button>
<button class="menu-button" id="menuButton">MAIN MENU</button>
</div>
<div id="winScreen">
<h1>VICTORY!</h1>
<button class="menu-button" id="nextLevelButton">NEXT LEVEL</button>
<button class="menu-button" id="menuButtonWin">MAIN MENU</button>
</div>
<div id="damageIndicator"></div>
</div>
<script>
// Game constants
const GAME_WIDTH = 800;
const GAME_HEIGHT = 600;
const PLAYER_SPEED = 5;
const ROTATION_SPEED = 0.05;
const FOV = 60 * Math.PI / 180;
const WALL_HEIGHT = 100;
const CEILING_COLOR = '#333333';
const FLOOR_COLOR = '#222222';
const WALL_COLORS = ['#880000', '#008800', '#000088', '#888800', '#880088', '#008888'];
const ENEMY_COLORS = ['#ff0000', '#ff6666', '#cc0000'];
const PICKUP_COLORS = {
health: '#00ff00',
ammo: '#ffff00',
weapon: '#ffffff'
};
// Game variables
let canvas, ctx;
let player = {
x: 50,
y: 50,
angle: 0,
health: 100,
maxHealth: 100,
ammo: 50,
maxAmmo: 100,
weapons: ['PISTOL'],
currentWeapon: 0,
damageTaken: 0,
visible: false
};
let map = [];
let enemies = [];
let pickups = [];
let rays = [];
let isMouseLocked = false;
let isGameRunning = false;
let currentLevel = 1;
let enemiesLeft = 0;
let mouseSensitivity = 0.002;
let lastTime = 0;
let fps = 0;
let deltaTime = 0;
let keys = {};
let showFPS = false;
let mathTimeout = null;
let currentPickup = null;
// Weapon stats
const weapons = [
{
name: 'PISTOL',
damage: 10,
fireRate: 500, // ms between shots
ammoCost: 1,
range: 500,
lastShot: 0,
spread: 0.05,
ammoType: 'bullet'
},
{
name: 'SHOTGUN',
damage: 30,
fireRate: 1000,
ammoCost: 5,
range: 300,
lastShot: 0,
spread: 0.2,
ammoType: 'shell'
},
{
name: 'MACHINE GUN',
damage: 5,
fireRate: 100,
ammoCost: 1,
range: 600,
lastShot: 0,
spread: 0.1,
ammoType: 'bullet'
}
];
// Initialize game
function init() {
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
canvas.width = GAME_WIDTH;
canvas.height = GAME_HEIGHT;
setupEventListeners();
generateLevel(1);
renderMenu();
// Start game loop
requestAnimationFrame(gameLoop);
}
// Set up event listeners
function setupEventListeners() {
// Mouse events
canvas.addEventListener('click', () => {
if (isGameRunning && !isMouseLocked) {
canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock ||
canvas.webkitRequestPointerLock;
canvas.requestPointerLock();
}
});
document.addEventListener('pointerlockchange', lockChange, false);
document.addEventListener('mozpointerlockchange', lockChange, false);
document.addEventListener('webkitpointerlockchange', lockChange, false);
function lockChange() {
isMouseLocked = document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas ||
document.webkitPointerLockElement === canvas;
}
document.addEventListener('mousemove', (e) => {
if (isMouseLocked && isGameRunning) {
player.angle += e.movementX * mouseSensitivity;
}
});
// Keyboard events
document.addEventListener('keydown', (e) => {
keys[e.key] = true;
if (e.key === 'f') {
showFPS = !showFPS;
}
});
document.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// Menu buttons
document.getElementById('startButton').addEventListener('click', startGame);
document.getElementById('howToButton').addEventListener('click', showHowToPlay);
document.getElementById('restartButton').addEventListener('click', restartGame);
document.getElementById('menuButton').addEventListener('click', showMenu);
document.getElementById('menuButtonWin').addEventListener('click', showMenu);
document.getElementById('nextLevelButton').addEventListener('click', nextLevel);
document.getElementById('mathSubmit').addEventListener('click', checkMathAnswer);
}
// Game loop
function gameLoop(timestamp) {
deltaTime = timestamp - lastTime;
lastTime = timestamp;
fps = 1000 / deltaTime;
if (isGameRunning) {
update();
render();
}
requestAnimationFrame(gameLoop);
}
// Update game state
function update() {
// Player movement
if (keys['w'] || keys['ArrowUp']) {
const newX = player.x + Math.cos(player.angle) * PLAYER_SPEED;
const newY = player.y + Math.sin(player.angle) * PLAYER_SPEED;
if (!isWall(newX, player.y)) player.x = newX;
if (!isWall(player.x, newY)) player.y = newY;
}
if (keys['s'] || keys['ArrowDown']) {
const newX = player.x - Math.cos(player.angle) * PLAYER_SPEED;
const newY = player.y - Math.sin(player.angle) * PLAYER_SPEED;
if (!isWall(newX, player.y)) player.x = newX;
if (!isWall(player.x, newY)) player.y = newY;
}
if (keys['a'] || keys['ArrowLeft']) {
const newX = player.x - Math.cos(player.angle + Math.PI/2) * PLAYER_SPEED;
const newY = player.y - Math.sin(player.angle + Math.PI/2) * PLAYER_SPEED;
if (!isWall(newX, player.y)) player.x = newX;
if (!isWall(player.x, newY)) player.y = newY;
}
if (keys['d'] || keys['ArrowRight']) {
const newX = player.x + Math.cos(player.angle + Math.PI/2) * PLAYER_SPEED;
const newY = player.y + Math.sin(player.angle + Math.PI/2) * PLAYER_SPEED;
if (!isWall(newX, player.y)) player.x = newX;
if (!isWall(player.x, newY)) player.y = newY;
}
// Weapon switching
if (keys['1'] && player.weapons.includes('PISTOL')) {
player.currentWeapon = 0;
updateHUD();
}
if (keys['2'] && player.weapons.includes('SHOTGUN')) {
player.currentWeapon = 1;
updateHUD();
}
if (keys['3'] && player.weapons.includes('MACHINE GUN')) {
player.currentWeapon = 2;
updateHUD();
}
// Shooting
if (keys[' '] && Date.now() - weapons[player.currentWeapon].lastShot > weapons[player.currentWeapon].fireRate) {
if (player.ammo >= weapons[player.currentWeapon].ammoCost) {
shoot();
} else {
// Play empty sound or show message
}
}
// Enemy AI and actions
updateEnemies();
// Check for pickups
checkPickups();
// Damage indicator
if (player.damageTaken > 0) {
const damageIndicator = document.getElementById('damageIndicator');
damageIndicator.style.display = 'block';
damageIndicator.style.opacity = player.damageTaken / 100;
player.damageTaken = Math.max(0, player.damageTaken - 1);
} else {
document.getElementById('damageIndicator').style.display = 'none';
}
// Check win/lose conditions
if (player.health <= 0) {
gameOver();
} else if (enemiesLeft <= 0) {
levelComplete();
}
}
// Render game
function render() {
// Clear canvas
ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
// Draw ceiling and floor
drawCeilingAndFloor();
// Cast rays and draw walls
castRays();
// Draw enemies
drawEnemies();
// Draw pickups
drawPickups();
// Draw minimap (for debugging)
if (keys['m']) {
drawMinimap();
}
// FPS counter
if (showFPS) {
ctx.fillStyle = '#ffffff';
ctx.font = '16px Arial';
ctx.fillText(`FPS: ${Math.round(fps)}`, 10, 20);
}
}
// Draw ceiling and floor
function drawCeilingAndFloor() {
// Ceiling
ctx.fillStyle = CEILING_COLOR;
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT / 2);
// Floor
ctx.fillStyle = FLOOR_COLOR;
ctx.fillRect(0, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT / 2);
}
// Cast rays for 3D effect
function castRays() {
rays = [];
const rayCount = GAME_WIDTH;
for (let x = 0; x < rayCount; x++) {
const rayAngle = player.angle - FOV / 2 + (x / rayCount) * FOV;
const ray = castRay(player.x, player.y, rayAngle);
rays.push(ray);
// Draw wall slice
const distance = ray.distance * Math.cos(ray.angle - player.angle); // Fix fisheye
const wallHeight = (WALL_HEIGHT / distance) * 277; // Magic number for proper scaling
ctx.fillStyle = WALL_COLORS[ray.colorIndex];
ctx.fillRect(x, (GAME_HEIGHT - wallHeight) / 2, 1, wallHeight);
}
}
// Cast a single ray
function castRay(x, y, angle) {
// Check both vertical and horizontal grid lines
let vCollision = checkVerticalCollision(x, y, angle);
let hCollision = checkHorizontalCollision(x, y, angle);
// Use the closest collision
let collision;
if (!vCollision) {
collision = hCollision;
} else if (!hCollision) {
collision = vCollision;
} else {
collision = vCollision.distance < hCollision.distance ? vCollision : hCollision;
}
return {
x: collision.x,
y: collision.y,
distance: collision.distance,
angle: angle,
vertical: collision.vertical,
colorIndex: collision.colorIndex
};
}
// Check for vertical grid line collisions
function checkVerticalCollision(x, y, angle) {
const right = Math.abs(Math.floor((angle - Math.PI / 2) / Math.PI) % 2) === 1;
const firstX = right ?
Math.floor(x / 50) * 50 + 50 :
Math.ceil(x / 50) * 50 - 50;
const firstY = y + (firstX - x) * Math.tan(angle);
const deltaX = right ? 50 : -50;
const deltaY = deltaX * Math.tan(angle);
let nextX = firstX;
let nextY = firstY;
while (nextX >= 0 && nextX <= map[0].length * 50 &&
nextY >= 0 && nextY <= map.length * 50) {
const cellX = right ? Math.floor(nextX / 50) : Math.floor(nextX / 50) - 1;
const cellY = Math.floor(nextY / 50);
if (cellX >= 0 && cellX < map[0].length &&
cellY >= 0 && cellY < map.length &&
map[cellY][cellX] > 0) {
const distance = Math.sqrt(Math.pow(nextX - x, 2) + Math.pow(nextY - y, 2));
return {
x: nextX,
y: nextY,
distance: distance,
vertical: true,
colorIndex: map[cellY][cellX] - 1
};
}
nextX += deltaX;
nextY += deltaY;
}
return null;
}
// Check for horizontal grid line collisions
function checkHorizontalCollision(x, y, angle) {
const up = Math.abs(Math.floor(angle / Math.PI) % 2) === 0;
const firstY = up ?
Math.floor(y / 50) * 50 - 1 :
Math.ceil(y / 50) * 50 + 1;
const firstX = x + (firstY - y) / Math.tan(angle);
const deltaY = up ? -50 : 50;
const deltaX = deltaY / Math.tan(angle);
let nextX = firstX;
let nextY = firstY;
while (nextX >= 0 && nextX <= map[0].length * 50 &&
nextY >= 0 && nextY <= map.length * 50) {
const cellX = Math.floor(nextX / 50);
const cellY = up ? Math.floor(nextY / 50) - 1 : Math.floor(nextY / 50);
if (cellX >= 0 && cellX < map[0].length &&
cellY >= 0 && cellY < map.length &&
map[cellY][cellX] > 0) {
const distance = Math.sqrt(Math.pow(nextX - x, 2) + Math.pow(nextY - y, 2));
return {
x: nextX,
y: nextY,
distance: distance,
vertical: false,
colorIndex: map[cellY][cellX] - 1
};
}
nextX += deltaX;
nextY += deltaY;
}
return null;
}
// Draw enemies
function drawEnemies() {
for (let enemy of enemies) {
if (enemy.health <= 0) continue;
// Calculate angle between player and enemy
const angleToEnemy = Math.atan2(enemy.y - player.y, enemy.x - player.x) - player.angle;
// Normalize angle
let normalizedAngle = angleToEnemy;
while (normalizedAngle > Math.PI) normalizedAngle -= 2 * Math.PI;
while (normalizedAngle < -Math.PI) normalizedAngle += 2 * Math.PI;
// Check if enemy is in player's FOV
if (Math.abs(normalizedAngle) < FOV / 2) {
// Check if there's a wall between player and enemy
const distToEnemy = Math.sqrt(Math.pow(enemy.x - player.x, 2) + Math.pow(enemy.y - player.y, 2));
const ray = castRay(player.x, player.y, player.angle + normalizedAngle);
if (ray.distance > distToEnemy) {
// Enemy is visible, draw it
const enemyHeight = (WALL_HEIGHT * 1.5) / distToEnemy * 277; // Scale similarly to walls
const screenX = (normalizedAngle + FOV / 2) / FOV * GAME_WIDTH;
// Draw enemy
ctx.fillStyle = ENEMY_COLORS[enemy.type];
ctx.fillRect(
screenX - enemyHeight / 4,
(GAME_HEIGHT - enemyHeight) / 2,
enemyHeight / 2,
enemyHeight
);
// Health bar
ctx.fillStyle = '#ff0000';
ctx.fillRect(
screenX - enemyHeight / 4,
(GAME_HEIGHT - enemyHeight) / 2 - 10,
enemyHeight / 2,
5
);
ctx.fillStyle = '#00ff00';
ctx.fillRect(
screenX - enemyHeight / 4,
(GAME_HEIGHT - enemyHeight) / 2 - 10,
enemyHeight / 2 * (enemy.health / enemy.maxHealth),
5
);
}
}
}
}
// Draw pickups
function drawPickups() {
for (let pickup of pickups) {
// Calculate angle between player and pickup
const angleToPickup = Math.atan2(pickup.y - player.y, pickup.x - player.x) - player.angle;
// Normalize angle
let normalizedAngle = angleToPickup;
while (normalizedAngle > Math.PI) normalizedAngle -= 2 * Math.PI;
while (normalizedAngle < -Math.PI) normalizedAngle += 2 * Math.PI;
// Check if pickup is in player's FOV
if (Math.abs(normalizedAngle) < FOV / 2) {
// Check if there's a wall between player and pickup
const distToPickup = Math.sqrt(Math.pow(pickup.x - player.x, 2) + Math.pow(pickup.y - player.y, 2));
const ray = castRay(player.x, player.y, player.angle + normalizedAngle);
if (ray.distance > distToPickup) {
// Pickup is visible, draw it
const pickupHeight = (WALL_HEIGHT * 0.5) / distToPickup * 277;
const screenX = (normalizedAngle + FOV / 2) / FOV * GAME_WIDTH;
// Draw pickup
ctx.fillStyle = pickup.color;
ctx.beginPath();
ctx.arc(
screenX,
GAME_HEIGHT / 2,
pickupHeight / 3,
0,
Math.PI * 2
);
ctx.fill();
// Draw icon based on type
ctx.fillStyle = '#000000';
ctx.font = `${pickupHeight / 2}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
let symbol = '';
if (pickup.type === 'health') symbol = '+';
else if (pickup.type === 'ammo') symbol = 'A';
else if (pickup.type === 'weapon') symbol = 'W';
ctx.fillText(symbol, screenX, GAME_HEIGHT / 2);
}
}
}
}
// Draw minimap (for debugging)
function drawMinimap() {
const minimapSize = 200;
const cellSize = minimapSize / Math.max(map.length, map[0].length);
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(10, 10, minimapSize, minimapSize);
// Draw walls
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[y].length; x++) {
if (map[y][x] > 0) {
ctx.fillStyle = WALL_COLORS[map[y][x] - 1];
ctx.fillRect(10 + x * cellSize, 10 + y * cellSize, cellSize, cellSize);
}
}
}
// Draw player
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(
10 + player.x / 50 * cellSize,
10 + player.y / 50 * cellSize,
cellSize / 2,
0,
Math.PI * 2
);
ctx.fill();
// Draw player direction
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(
10 + player.x / 50 * cellSize,
10 + player.y / 50 * cellSize
);
ctx.lineTo(
10 + (player.x + Math.cos(player.angle) * 20) / 50 * cellSize,
10 + (player.y + Math.sin(player.angle) * 20) / 50 * cellSize
);
ctx.stroke();
// Draw enemies
for (let enemy of enemies) {
if (enemy.health <= 0) continue;
ctx.fillStyle = ENEMY_COLORS[enemy.type];
ctx.beginPath();
ctx.arc(
10 + enemy.x / 50 * cellSize,
10 + enemy.y / 50 * cellSize,
cellSize / 2,
0,
Math.PI * 2
);
ctx.fill();
}
// Draw pickups
for (let pickup of pickups) {
ctx.fillStyle = pickup.color;
ctx.beginPath();
ctx.arc(
10 + pickup.x / 50 * cellSize,
10 + pickup.y / 50 * cellSize,
cellSize / 3,
0,
Math.PI * 2
);
ctx.fill();
}
}
// Check if a position is a wall
function isWall(x, y) {
const cellX = Math.floor(x / 50);
const cellY = Math.floor(y / 50);
if (cellX < 0 || cellX >= map[0].length || cellY < 0 || cellY >= map.length) {
return true;
}
return map[cellY][cellX] > 0;
}
// Shoot weapon
function shoot() {
const weapon = weapons[player.currentWeapon];
// Check if player has enough ammo
if (player.ammo < weapon.ammoCost) return;
// Consume ammo
player.ammo -= weapon.ammoCost;
weapon.lastShot = Date.now();
// Add muzzle flash effect (simple for now)
ctx.fillStyle = '#ffff00';
ctx.fillRect(GAME_WIDTH / 2 - 20, GAME_HEIGHT / 2 - 20, 40, 40);
setTimeout(() => {
// Flash disappears quickly
}, 50);
// Hit check
for (let i = 0; i < (player.currentWeapon === 1 ? 5 : 1); i++) { // Shotgun shoots multiple pellets
const spreadAngle = (Math.random() - 0.5) * weapon.spread;
const shootAngle = player.angle + spreadAngle;
// Cast ray to check for hits
const hit = castRay(player.x, player.y, shootAngle);
// Check if we hit an enemy
for (let enemy of enemies) {
if (enemy.health <= 0) continue;
// Calculate distance from ray to enemy
const distanceToEnemy = Math.sqrt(Math.pow(hit.x - enemy.x, 2) + Math.pow(hit.y - enemy.y, 2));
// If enemy is close to hit point and not behind a wall
if (distanceToEnemy < 30 && hit.distance >= Math.sqrt(Math.pow(enemy.x - player.x, 2) + Math.pow(enemy.y - player.y, 2))) {
// Hit the enemy
enemy.health -= weapon.damage * (player.currentWeapon === 1 ? 0.8 : 1); // Shotgun pellets do less damage individually
// If enemy died, increase score
if (enemy.health <= 0) {
enemiesLeft--;
document.getElementById('enemyCountValue').textContent = enemiesLeft;
}
break;
}
}
}
updateHUD();
}
// Update enemies
function updateEnemies() {
for (let enemy of enemies) {
if (enemy.health <= 0) continue;
// Simple AI: move toward player if visible
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Check if player is visible
const angleToPlayer = Math.atan2(dy, dx);
const ray = castRay(enemy.x, enemy.y, angleToPlayer);
if (ray.distance >= distance - 10) { // Player is visible
// Move toward player
enemy.x += Math.cos(angleToPlayer) * enemy.speed;
enemy.y += Math.sin(angleToPlayer) * enemy.speed;
// Simple attack: damage player if close enough
if (distance < 50) {
player.health -= enemy.damage;
player.damageTaken = 50; // Show damage indicator
updateHUD();
}
} else {
// Wander randomly
enemy.x += Math.cos(enemy.direction) * enemy.speed / 2;
enemy.y += Math.sin(enemy.direction) * enemy.speed / 2;
// Randomly change direction
if (Math.random() < 0.01) {
enemy.direction = Math.random() * Math.PI * 2;
}
}
// Collision with walls
if (isWall(enemy.x, enemy.y)) {
// Move away from walls
enemy.x -= Math.cos(angleToPlayer) * enemy.speed;
enemy.y -= Math.sin(angleToPlayer) * enemy.speed;
enemy.direction = Math.random() * Math.PI * 2;
}
}
}
// Check for pickups
function checkPickups() {
if (currentPickup) return; // Already trying to pick up something
for (let i = 0; i < pickups.length; i++) {
const pickup = pickups[i];
const distance = Math.sqrt(Math.pow(pickup.x - player.x, 2) + Math.pow(pickup.y - player.y, 2));
if (distance < 30) { // Close enough to pickup
currentPickup = pickup;
showMathPopup(pickup.type);
break;
}
}
}
// Show math popup for pickup
function showMathPopup(type) {
const mathPopup = document.getElementById('mathPopup');
const mathQuestion = document.getElementById('mathQuestion');
const mathAnswer = document.getElementById('mathAnswer');
let question, answer;
const difficulty = Math.min(currentLevel, 5);
// Generate math question based on current level (gets harder as level increases)
if (difficulty <= 2) {
// Addition and subtraction
const a = Math.floor(Math.random() * 10) + 1;
const b = Math.floor(Math.random() * 10) + 1;
const op = Math.random() > 0.5 ? '+' : '-';
question = `${a} ${op} ${b} = ?`;
answer = op === '+' ? a + b : a - b;
} else if (difficulty <= 4) {
// Multiplication
const a = Math.floor(Math.random() * 10) + 1;
const b = Math.floor(Math.random() * 5) + 1;
question = `${a} × ${b} = ?`;
answer = a * b;
} else {
// Division
const a = Math.floor(Math.random() * 10) + 1;
const b = Math.floor(Math.random() * 5) + 1;
const c = a * b;
question = `${c} ÷ ${b} = ?`;
answer = a;
}
mathQuestion.textContent = question;
mathAnswer.dataset.answer = answer;
mathAnswer.value = '';
mathPopup.style.display = 'flex';
mathAnswer.focus();
// Set timeout to auto-close if player doesn't answer
if (mathTimeout) clearTimeout(mathTimeout);
mathTimeout = setTimeout(() => {
mathPopup.style.display = 'none';
currentPickup = null;
}, 5000);
}
// Check math answer for pickup
function checkMathAnswer() {
const mathAnswer = document.getElementById('mathAnswer');
const correctAnswer = mathAnswer.dataset.answer;
const userAnswer = mathAnswer.value.trim();
document.getElementById('mathPopup').style.display = 'none';
if (userAnswer === correctAnswer) {
// Correct answer - give pickup
const pickup = currentPickup;
if (pickup.type === 'health') {
player.health = Math.min(player.health + pickup.value, player.maxHealth);
} else if (pickup.type === 'ammo') {
player.ammo = Math.min(player.ammo + pickup.value, player.maxAmmo);
} else if (pickup.type === 'weapon') {
if (!player.weapons.includes(pickup.weaponName)) {
player.weapons.push(pickup.weaponName);
}
player.currentWeapon = weapons.findIndex(w => w.name === pickup.weaponName);
player.ammo = Math.min(player.ammo + pickup.ammoBonus, player.maxAmmo);
}
// Remove pickup
const index = pickups.indexOf(pickup);
if (index !== -1) {
pickups.splice(index, 1);
}
updateHUD();
}
currentPickup = null;
if (mathTimeout) clearTimeout(mathTimeout);
}
// Update HUD
function updateHUD() {
document.getElementById('healthValue').textContent = player.health;
document.getElementById('ammoValue').textContent = player.ammo;
document.getElementById('weaponName').textContent = weapons[player.currentWeapon].name;
}
// Generate level
function generateLevel(level) {
// Reset game state
player.x = 50;
player.y = 50;
player.angle = 0;
player.health = 100;
player.ammo = 50;
// Default weapons
player.weapons = ['PISTOL'];
player.currentWeapon = 0;
enemies = [];
pickups = [];
// Create maze-like map
map = [];
const size = 10 + level * 2; // Increase size with level
// Initialize empty map
for (let y = 0; y < size; y++) {
map[y] = [];
for (let x = 0; x < size; x++) {
map[y][x] = 0;
}
}
// Add walls
for (let y = 0; y < size; y++) {
map[y][0] = 1 + Math.floor(Math.random() * 3);
map[y][size-1] = 1 + Math.floor(Math.random() * 3);
}
for (let x = 0; x < size; x++) {
map[0][x] = 1 + Math.floor(Math.random() * 3);
map[size-1][x] = 1 + Math.floor(Math.random() * 3);
}
// Add random walls inside
for (let i = 0; i < size * 2; i++) {
const x = Math.floor(Math.random() * (size - 2)) + 1;
const y = Math.floor(Math.random() * (size - 2)) + 1;
const length = Math.floor(Math.random() * 3) + 1;
const horizontal = Math.random() > 0.5;
for (let j = 0; j < length; j++) {
const nx = horizontal ? x + j : x;
const ny = horizontal ? y : y + j;
if (nx < size && ny < size) {
map[ny][nx] = 1 + Math.floor(Math.random() * 3);
}
}
}
// Make sure player start is open
map[1][1] = 0;
map[1][2] = 0;
map[2][1] = 0;
// Add enemies
enemiesLeft = 3 + level * 2;
for (let i = 0; i < enemiesLeft; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
y = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
} while (isWall(x, y));
enemies.push({
x: x,
y: y,
health: 30 + level * 10,
maxHealth: 30 + level * 10,
damage: 5 + level,
speed: 1 + level * 0.2,
direction: Math.random() * Math.PI * 2,
type: Math.min(Math.floor(Math.random() * level), ENEMY_COLORS.length - 1)
});
}
// Add health pickups
for (let i = 0; i < 2 + level; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
y = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
} while (isWall(x, y));
pickups.push({
x: x,
y: y,
type: 'health',
value: 25,
color: PICKUP_COLORS.health
});
}
// Add ammo pickups
for (let i = 0; i < 2 + level; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
y = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
} while (isWall(x, y));
pickups.push({
x: x,
y: y,
type: 'ammo',
value: 20,
color: PICKUP_COLORS.ammo
});
}
// Add weapon pickups (only if not already has them)
if (level > 1 && !player.weapons.includes('SHOTGUN')) {
let x, y;
do {
x = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
y = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
} while (isWall(x, y));
pickups.push({
x: x,
y: y,
type: 'weapon',
weaponName: 'SHOTGUN',
ammoBonus: 10,
color: PICKUP_COLORS.weapon
});
}
if (level > 3 && !player.weapons.includes('MACHINE GUN')) {
let x, y;
do {
x = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
y = Math.floor(Math.random() * (size - 4) + 2) * 50 + 25;
} while (isWall(x, y));
pickups.push({
x: x,
y: y,
type: 'weapon',
weaponName: 'MACHINE GUN',
ammoBonus: 30,
color: PICKUP_COLORS.weapon
});
}
// Update HUD
document.getElementById('levelValue').textContent = level;
document.getElementById('enemyCountValue').textContent = enemiesLeft;
updateHUD();
}
// Start game
function startGame() {
document.getElementById('menu').style.display = 'none';
isGameRunning = true;
currentLevel = 1;
generateLevel(currentLevel);
// Lock mouse pointer
canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock ||
canvas.webkitRequestPointerLock;
canvas.requestPointerLock();
}
// Restart game
function restartGame() {
document.getElementById('deathScreen').style.display = 'none';
isGameRunning = true;
generateLevel(currentLevel);
// Lock mouse pointer
canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock ||
canvas.webkitRequestPointerLock;
canvas.requestPointerLock();
}
// Next level
function nextLevel() {
document.getElementById('winScreen').style.display = 'none';
isGameRunning = true;
currentLevel++;
generateLevel(currentLevel);
// Lock mouse pointer
canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock ||
canvas.webkitRequestPointerLock;
canvas.requestPointerLock();
}
// Show menu
function showMenu() {
document.getElementById('menu').style.display = 'flex';
document.getElementById('deathScreen').style.display = 'none';
document.getElementById('winScreen').style.display = 'none';
isGameRunning = false;
// Unlock mouse pointer
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
}
// Show how to play
function showHowToPlay() {
alert(
"HOW TO PLAY MATH DOOM:\n" +
"------------------------\n" +
"1. Move with W, A, S, D or arrow keys\n" +
"2. Aim with mouse and shoot with SPACE\n" +
"3. Switch weapons with 1, 2, 3 keys\n" +
"4. To pick up items, solve math problems\n" +
"5. Kill all enemies to complete the level\n" +
"6. Math problems get harder each level\n" +
"\n" +
"TIP: Shotgun is good for close range, machine gun for long range!"
);
}
// Game over
function gameOver() {
document.getElementById('deathScreen').style.display = 'flex';
isGameRunning = false;
// Unlock mouse pointer
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
}
// Level complete
function levelComplete() {
document.getElementById('winScreen').style.display = 'flex';
isGameRunning = false;
// Unlock mouse pointer
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
}
// Initialize game when page loads
window.onload = init;
</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 <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>