DOOM
Qwen 3.6 27B MTP GGUF · typescript

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 53,839 bytes · SHA-256 604fe74a3707
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Doom</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
#app { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; }
canvas { display: block; image-rendering: pixelated; cursor: none; }
#overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
display: none; justify-content: center; align-items: center;
flex-direction: column; z-index: 10; background: rgba(0,0,0,0.8);
color: #fff; font-family: monospace;
}
#overlay h1 { font-size: 48px; margin-bottom: 16px; }
#overlay p { font-size: 20px; margin-bottom: 8px; }
#overlay button {
margin-top: 20px; padding: 12px 32px; font-size: 20px;
font-family: monospace; background: #c00; color: #fff; border: none;
cursor: pointer; text-transform: uppercase;
}
#overlay button:hover { background: #f00; }
#start-screen {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
display: flex; justify-content: center; align-items: center;
flex-direction: column; z-index: 20; background: #000;
color: #fff; font-family: monospace;
}
#start-screen h1 {
font-size: 72px; color: #c00; text-shadow: 0 0 20px #f00;
margin-bottom: 24px; letter-spacing: 8px;
}
#start-screen p { font-size: 18px; margin-bottom: 4px; color: #aaa; }
#start-screen button {
margin-top: 32px; padding: 16px 48px; font-size: 24px;
font-family: monospace; background: #c00; color: #fff; border: none;
cursor: pointer; text-transform: uppercase; animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 10px #c00; }
50% { box-shadow: 0 0 30px #f00; }
}
</style>
</head>
<body>
<div id="app">
<canvas id="game"></canvas>
<div id="overlay">
<h1 id="overlay-title"></h1>
<p id="overlay-msg"></p>
<p id="overlay-score"></p>
<button id="overlay-btn">Continue</button>
</div>
<div id="start-screen">
<h1>DOOM</h1>
<p>WASD - Move | Mouse - Aim | Click - Shoot</p>
<p>1/2/3 - Switch Weapons | R - Reload</p>
<button id="start-btn">ENTER HELL</button>
</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./game":"src/game.ts","./input":"src/input.ts","./player":"src/player.ts","./enemies":"src/enemies.ts","./renderer":"src/renderer.ts","./sound":"src/sound.ts"},"src/map.ts":{"./types":"src/types.ts"},"src/raycast.ts":{"./types":"src/types.ts","./map":"src/map.ts"},"src/game.ts":{"./types":"src/types.ts","./map":"src/map.ts"},"src/player.ts":{"./types":"src/types.ts","./raycast":"src/raycast.ts","./game":"src/game.ts","./input":"src/input.ts","./sound":"src/sound.ts"},"src/enemies.ts":{"./types":"src/types.ts","./raycast":"src/raycast.ts","./game":"src/game.ts","./sound":"src/sound.ts"},"src/renderer.ts":{"./types":"src/types.ts","./raycast":"src/raycast.ts"}};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
const game_1 = require("./game");
const input_1 = require("./input");
const player_1 = require("./player");
const enemies_1 = require("./enemies");
const renderer_1 = require("./renderer");
const sound_1 = require("./sound");
let game;
let canvas;
let ctx;
let lastTime = 0;
let animFrameId = 0;
const overlay = document.getElementById('overlay');
const overlayTitle = document.getElementById('overlay-title');
const overlayMsg = document.getElementById('overlay-msg');
const overlayScore = document.getElementById('overlay-score');
const overlayBtn = document.getElementById('overlay-btn');
const startScreen = document.getElementById('start-screen');
const startBtn = document.getElementById('start-btn');
function init() {
canvas = document.getElementById('game');
canvas.width = 640;
canvas.height = 400;
// Scale canvas to fit window while keeping aspect ratio
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
ctx = canvas.getContext('2d');
game = (0, game_1.createGame)();
(0, input_1.setupInput)(canvas);
startBtn.addEventListener('click', startGame);
overlayBtn.addEventListener('click', handleOverlayButton);
requestAnimationFrame(gameLoop);
}
function resizeCanvas() {
const ar = 640 / 400;
let w = window.innerWidth;
let h = window.innerHeight;
if (w / h > ar) {
w = h * ar;
}
else {
h = w / ar;
}
canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`;
}
function startGame() {
startScreen.style.display = 'none';
game.phase = 'playing';
canvas.requestPointerLock();
}
function handleOverlayButton() {
if (game.phase === 'dead') {
(0, game_1.respawnPlayer)(game);
}
else if (game.phase === 'won') {
game = (0, game_1.createGame)();
game.phase = 'playing';
}
overlay.style.display = 'none';
canvas.requestPointerLock();
}
function update(dt) {
game.time += dt;
if (game.phase === 'dead') {
game.deadTimer--;
if (game.deadTimer <= 0) {
overlayTitle.textContent = 'YOU DIED';
overlayTitle.style.color = '#f00';
overlayMsg.textContent = `${game.player.kills} enemies killed`;
overlayScore.textContent = `Score: ${game.player.score}`;
overlayBtn.textContent = 'RESPAWN';
overlay.style.display = 'flex';
}
return;
}
if (game.phase === 'won') {
if (game.deadTimer > 0) {
game.deadTimer--;
}
else if (game.deadTimer === -1) {
overlayTitle.textContent = 'LEVEL COMPLETE!';
overlayTitle.style.color = '#0f0';
overlayMsg.textContent = `${game.player.kills} enemies eliminated`;
overlayScore.textContent = `Final Score: ${game.player.score}`;
overlayBtn.textContent = 'PLAY AGAIN';
overlay.style.display = 'flex';
game.deadTimer = -2;
}
return;
}
// Update player
(0, player_1.updatePlayer)(game, dt);
// Update weapons
for (let i = 0; i < 3; i++) {
if (i !== game.player.currentWeapon)
continue;
(0, player_1.updateWeapon)(game, i);
}
// Update enemies
(0, enemies_1.updateEnemies)(game);
// Enemy projectiles / damage
for (const e of game.enemies) {
if (!e.alive)
continue;
// Enemies that are in "attack" range do damage via the enemy system
}
// Pickup collection
const p = game.player;
for (const pk of game.pickups) {
if (!pk.active || pk.collected)
continue;
const dx = pk.x - p.x;
const dy = pk.y - p.y;
if (dx * dx + dy * dy < 0.5) {
pk.collected = true;
pk.active = false;
(0, sound_1.playPickup)();
if (pk.type === 'health' && p.health < p.maxHealth) {
p.health = Math.min(p.maxHealth, p.health + 25);
for (let i = 0; i < 5; i++)
(0, game_1.spawnParticle)(game, pk.x, pk.y, '#0f0');
}
else if (pk.type === 'ammo_pistol') {
const w = p.weapons[types_1.WeaponType.Pistol];
w.reserve = Math.min(w.reserve + 12, 72);
for (let i = 0; i < 5; i++)
(0, game_1.spawnParticle)(game, pk.x, pk.y, '#ff0');
}
else if (pk.type === 'ammo_shotgun') {
// Equip shotgun if not yet
if (!p.weapons[types_1.WeaponType.Shotgun].equipped) {
p.weapons[types_1.WeaponType.Shotgun].equipped = true;
}
const w = p.weapons[types_1.WeaponType.Shotgun];
w.reserve = Math.min(w.reserve + 8, 32);
for (let i = 0; i < 5; i++)
(0, game_1.spawnParticle)(game, pk.x, pk.y, '#f80');
}
}
}
// Update particles
for (let i = game.particles.length - 1; i >= 0; i--) {
const pt = game.particles[i];
pt.x += pt.vx;
pt.y += pt.vy;
pt.life--;
if (pt.life <= 0)
game.particles.splice(i, 1);
}
// Win condition: all enemies dead
const aliveCount = game.enemies.filter(e => e.alive).length;
if (aliveCount === 0 && game.phase === 'playing') {
game.phase = 'won';
game.deadTimer = 60; // Delay before showing overlay
}
}
function gameLoop(timestamp) {
const dt = Math.min(32, timestamp - lastTime);
lastTime = timestamp;
update(dt);
(0, renderer_1.render)(game, ctx);
animFrameId = requestAnimationFrame(gameLoop);
}
init();
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TILE = exports.HALF_FOV = exports.FOV = exports.EnemyType = exports.WeaponType = void 0;
var WeaponType;
(function (WeaponType) {
WeaponType[WeaponType["Fist"] = 0] = "Fist";
WeaponType[WeaponType["Pistol"] = 1] = "Pistol";
WeaponType[WeaponType["Shotgun"] = 2] = "Shotgun";
})(WeaponType || (exports.WeaponType = WeaponType = {}));
var EnemyType;
(function (EnemyType) {
EnemyType[EnemyType["Imp"] = 0] = "Imp";
EnemyType[EnemyType["Demon"] = 1] = "Demon";
EnemyType[EnemyType["Commander"] = 2] = "Commander";
})(EnemyType || (exports.EnemyType = EnemyType = {}));
exports.FOV = Math.PI / 3;
exports.HALF_FOV = exports.FOV / 2;
exports.TILE = 1;
};
// ── module: src/map.ts ──
__mods["src/map.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAP_HEIGHT = exports.MAP_WIDTH = exports.MAP_LAYOUT = void 0;
exports.createEnemies = createEnemies;
exports.createPickups = createPickups;
const types_1 = require("./types");
// 0=floor, 1=stone, 2=red brick, 3=blue brick, 4=metal, 5=gate(exit)
// Player starts at P, enemies at I, D, C, pickups at H, A
exports.MAP_LAYOUT = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 2, 2, 2, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 1],
[1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1],
];
exports.MAP_WIDTH = exports.MAP_LAYOUT[0].length;
exports.MAP_HEIGHT = exports.MAP_LAYOUT.length;
function createEnemies() {
const enemies = [];
const spawns = [
{ x: 5.5, y: 4.5, type: types_1.EnemyType.Imp },
{ x: 10.5, y: 3.5, type: types_1.EnemyType.Imp },
{ x: 18.5, y: 5.5, type: types_1.EnemyType.Demon },
{ x: 15.5, y: 2.5, type: types_1.EnemyType.Imp },
{ x: 7.5, y: 8.5, type: types_1.EnemyType.Commander },
{ x: 12.5, y: 10.5, type: types_1.EnemyType.Imp },
{ x: 3.5, y: 12.5, type: types_1.EnemyType.Demon },
{ x: 20.5, y: 10.5, type: types_1.EnemyType.Imp },
{ x: 10.5, y: 14.5, type: types_1.EnemyType.Commander },
{ x: 4.5, y: 16.5, type: types_1.EnemyType.Imp },
{ x: 16.5, y: 14.5, type: types_1.EnemyType.Demon },
{ x: 21.5, y: 17.5, type: types_1.EnemyType.Imp },
{ x: 8.5, y: 18.5, type: types_1.EnemyType.Commander },
{ x: 14.5, y: 18.5, type: types_1.EnemyType.Demon },
];
for (const s of spawns) {
const stats = s.type === types_1.EnemyType.Imp
? { health: 30, speed: 0.02, damage: 5, fireRate: 60, alertDist: 8 }
: s.type === types_1.EnemyType.Demon
? { health: 60, speed: 0.035, damage: 10, fireRate: 40, alertDist: 10 }
: { health: 100, speed: 0.015, damage: 15, fireRate: 50, alertDist: 12 };
enemies.push({
x: s.x, y: s.y,
type: s.type,
health: stats.health,
maxHealth: stats.health,
alive: true,
speed: stats.speed,
damage: stats.damage,
fireRate: stats.fireRate,
fireTimer: Math.floor(Math.random() * stats.fireRate),
state: 'idle',
alertDist: stats.alertDist,
seePlayer: false,
animFrame: 0,
animTimer: 0,
hitFlash: 0,
deathTimer: 0,
});
}
return enemies;
}
function createPickups() {
return [
{ x: 3.5, y: 3.5, type: 'health', active: true, collected: false, animPhase: 0 },
{ x: 8.5, y: 3.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
{ x: 14.5, y: 3.5, type: 'ammo_shotgun', active: true, collected: false, animPhase: 0 },
{ x: 6.5, y: 7.5, type: 'health', active: true, collected: false, animPhase: 0 },
{ x: 12.5, y: 7.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
{ x: 3.5, y: 10.5, type: 'ammo_shotgun', active: true, collected: false, animPhase: 0 },
{ x: 18.5, y: 14.5, type: 'health', active: true, collected: false, animPhase: 0 },
{ x: 20.5, y: 3.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
{ x: 10.5, y: 18.5, type: 'ammo_shotgun', active: true, collected: false, animPhase: 0 },
{ x: 15.5, y: 10.5, type: 'health', active: true, collected: false, animPhase: 0 },
{ x: 5.5, y: 14.5, type: 'ammo_pistol', active: true, collected: false, animPhase: 0 },
{ x: 22.5, y: 18.5, type: 'health', active: true, collected: false, animPhase: 0 },
];
}
};
// ── module: src/raycast.ts ──
__mods["src/raycast.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.castRay = castRay;
exports.canSee = canSee;
exports.isWall = isWall;
const map_1 = require("./map");
function castRay(ox, oy, angle) {
const sin = Math.sin(angle);
const cos = Math.cos(angle);
const dx = cos;
const dy = sin;
// DDA algorithm
let mapX = Math.floor(ox);
let mapY = Math.floor(oy);
const deltaDistX = Math.abs(1 / (dx || 0.0001));
const deltaDistY = Math.abs(1 / (dy || 0.0001));
let stepX, stepY;
let sideDistX, sideDistY;
if (dx < 0) {
stepX = -1;
sideDistX = (ox - mapX) * deltaDistX;
}
else {
stepX = 1;
sideDistX = (mapX + 1 - ox) * deltaDistX;
}
if (dy < 0) {
stepY = -1;
sideDistY = (oy - mapY) * deltaDistY;
}
else {
stepY = 1;
sideDistY = (mapY + 1 - oy) * deltaDistY;
}
let side = 0;
let maxSteps = 64;
while (maxSteps-- > 0) {
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
}
else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
if (mapX < 0 || mapX >= map_1.MAP_WIDTH || mapY < 0 || mapY >= map_1.MAP_HEIGHT) {
return { dist: 64, wallX: 0, side, wallType: 0 };
}
const wallType = map_1.MAP_LAYOUT[mapY][mapX];
if (wallType > 0) {
let perpDist;
if (side === 0) {
perpDist = (mapX - ox + (1 - stepX) / 2) / (dx || 0.0001);
}
else {
perpDist = (mapY - oy + (1 - stepY) / 2) / (dy || 0.0001);
}
const absDist = Math.abs(perpDist);
// Texture coordinate
let wallX;
if (side === 0) {
wallX = oy + perpDist * (sin / (cos || 0.0001));
}
else {
wallX = ox + perpDist * (cos / (sin || 0.0001));
}
wallX -= Math.floor(wallX);
return { dist: absDist, wallX, side, wallType };
}
}
return { dist: 64, wallX: 0, side, wallType: 0 };
}
function canSee(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const dist = Math.sqrt(dx * dx + dy * dy);
const steps = Math.ceil(dist * 4);
const sx = dx / steps;
const sy = dy / steps;
for (let i = 0; i < steps; i++) {
const cx = x1 + sx * i;
const cy = y1 + sy * i;
const mx = Math.floor(cx);
const my = Math.floor(cy);
if (mx >= 0 && mx < map_1.MAP_WIDTH && my >= 0 && my < map_1.MAP_HEIGHT) {
if (map_1.MAP_LAYOUT[my][mx] > 0)
return false;
}
}
return true;
}
function isWall(x, y) {
const mx = Math.floor(x);
const my = Math.floor(y);
if (mx < 0 || mx >= map_1.MAP_WIDTH || my < 0 || my >= map_1.MAP_HEIGHT)
return true;
return map_1.MAP_LAYOUT[my][mx] > 0;
}
};
// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayer = createPlayer;
exports.createGame = createGame;
exports.spawnParticle = spawnParticle;
exports.respawnPlayer = respawnPlayer;
exports.tryEquipShotgun = tryEquipShotgun;
const types_1 = require("./types");
const map_1 = require("./map");
function createPlayer() {
return {
x: 1.5,
y: 1.5,
angle: 0,
health: 100,
maxHealth: 100,
alive: true,
score: 0,
kills: 0,
weapons: [
{ equipped: true, ammo: Infinity, maxAmmo: Infinity, reserve: 0, fireTimer: 0, reloadTimer: 0, reloading: false },
{ equipped: true, ammo: 12, maxAmmo: 12, reserve: 36, fireTimer: 0, reloadTimer: 0, reloading: false },
{ equipped: false, ammo: 8, maxAmmo: 8, reserve: 16, fireTimer: 0, reloadTimer: 0, reloading: false },
],
currentWeapon: 0,
damageFlash: 0,
bobPhase: 0,
};
}
function createGame() {
return {
map: map_1.MAP_LAYOUT,
mapWidth: map_1.MAP_WIDTH,
mapHeight: map_1.MAP_HEIGHT,
player: createPlayer(),
enemies: (0, map_1.createEnemies)(),
pickups: (0, map_1.createPickups)(),
particles: [],
time: 0,
phase: 'start',
deadTimer: 0,
totalEnemies: 0,
totalKills: 0,
levelNum: 1,
};
}
function spawnParticle(game, x, y, color) {
const angle = Math.random() * Math.PI * 2;
const speed = 0.02 + Math.random() * 0.04;
game.particles.push({
x, y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 30 + Math.random() * 20,
maxLife: 50,
color,
size: 2 + Math.random() * 3,
});
}
function respawnPlayer(game) {
const p = game.player;
p.x = 1.5;
p.y = 1.5;
p.angle = 0;
p.health = p.maxHealth;
p.alive = true;
p.damageFlash = 0;
// Reset all enemies
for (const e of game.enemies) {
if (!e.alive) {
const spawns = [
{ x: 5.5, y: 4.5, type: types_1.EnemyType.Imp },
{ x: 10.5, y: 3.5, type: types_1.EnemyType.Imp },
{ x: 18.5, y: 5.5, type: types_1.EnemyType.Demon },
{ x: 15.5, y: 2.5, type: types_1.EnemyType.Imp },
{ x: 7.5, y: 8.5, type: types_1.EnemyType.Commander },
{ x: 12.5, y: 10.5, type: types_1.EnemyType.Imp },
{ x: 3.5, y: 12.5, type: types_1.EnemyType.Demon },
{ x: 20.5, y: 10.5, type: types_1.EnemyType.Imp },
{ x: 10.5, y: 14.5, type: types_1.EnemyType.Commander },
{ x: 4.5, y: 16.5, type: types_1.EnemyType.Imp },
{ x: 16.5, y: 14.5, type: types_1.EnemyType.Demon },
{ x: 21.5, y: 17.5, type: types_1.EnemyType.Imp },
{ x: 8.5, y: 18.5, type: types_1.EnemyType.Commander },
{ x: 14.5, y: 18.5, type: types_1.EnemyType.Demon },
];
for (let i = 0; i < spawns.length; i++) {
if (e === game.enemies[i]) {
const s = spawns[i];
e.x = s.x;
e.y = s.y;
e.type = s.type;
const stats = s.type === types_1.EnemyType.Imp
? { health: 30, speed: 0.02, damage: 5 }
: s.type === types_1.EnemyType.Demon
? { health: 60, speed: 0.035, damage: 10 }
: { health: 100, speed: 0.015, damage: 15 };
e.health = stats.health;
e.maxHealth = stats.health;
e.speed = stats.speed;
e.damage = stats.damage;
e.alive = true;
e.deathTimer = 0;
break;
}
}
}
}
// Respawn collected pickups
for (const p of game.pickups) {
if (p.collected) {
p.collected = false;
p.active = true;
}
}
game.phase = 'playing';
}
function tryEquipShotgun(game) {
const w = game.player.weapons[types_1.WeaponType.Shotgun];
if (w.reserve > 0 || w.ammo < w.maxAmmo) {
if (w.reserve > 0) {
const needed = w.maxAmmo - w.ammo;
const take = Math.min(needed, w.reserve);
w.ammo += take;
w.reserve -= take;
}
}
}
};
// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupInput = setupInput;
exports.consumeMouseDX = consumeMouseDX;
exports.consumeMouseClick = consumeMouseClick;
exports.isKeyDown = isKeyDown;
const keys = {};
let mouseDX = 0;
let mouseDown = false;
let mouseClicked = false;
function setupInput(canvas) {
document.addEventListener('keydown', (e) => {
keys[e.key.toLowerCase()] = true;
if (['w', 'a', 's', 'd', ' '].includes(e.key.toLowerCase()))
e.preventDefault();
});
document.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
});
canvas.addEventListener('click', () => {
if (!document.pointerLockElement) {
canvas.requestPointerLock();
}
});
document.addEventListener('pointerlockchange', () => {
// track pointer lock state
});
document.addEventListener('mousemove', (e) => {
if (document.pointerLockElement === canvas) {
mouseDX += e.movementX;
}
});
document.addEventListener('mousedown', (e) => {
if (e.button === 0)
mouseDown = true;
});
document.addEventListener('mouseup', (e) => {
if (e.button === 0)
mouseDown = false;
});
}
function consumeMouseDX() {
const v = mouseDX;
mouseDX = 0;
return v;
}
function consumeMouseClick() {
const v = mouseDown;
return v;
}
function isKeyDown(key) {
return !!keys[key.toLowerCase()];
}
};
// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updatePlayer = updatePlayer;
exports.updateWeapon = updateWeapon;
exports.damageEnemy = damageEnemy;
const types_1 = require("./types");
const raycast_1 = require("./raycast");
const game_1 = require("./game");
const input_1 = require("./input");
const sound_1 = require("./sound");
const MOVE_SPEED = 0.06;
const TURN_SPEED = 0.003;
function updatePlayer(game, dt) {
const p = game.player;
if (!p.alive)
return;
// Mouse look
const mouseDelta = (0, input_1.consumeMouseDX)();
p.angle += mouseDelta * TURN_SPEED;
let moveX = 0;
let moveY = 0;
const cos = Math.cos(p.angle);
const sin = Math.sin(p.angle);
let moving = false;
if ((0, input_1.isKeyDown)('w') || (0, input_1.isKeyDown)('arrowup')) {
moveX += cos * MOVE_SPEED;
moveY += sin * MOVE_SPEED;
moving = true;
}
if ((0, input_1.isKeyDown)('s') || (0, input_1.isKeyDown)('arrowdown')) {
moveX -= cos * MOVE_SPEED;
moveY -= sin * MOVE_SPEED;
moving = true;
}
if ((0, input_1.isKeyDown)('a') || (0, input_1.isKeyDown)('arrowleft')) {
moveX += sin * MOVE_SPEED;
moveY -= cos * MOVE_SPEED;
moving = true;
}
if ((0, input_1.isKeyDown)('d') || (0, input_1.isKeyDown)('arrowright')) {
moveX -= sin * MOVE_SPEED;
moveY += cos * MOVE_SPEED;
moving = true;
}
// Collision with radius
const r = 0.2;
const nx = p.x + moveX;
const ny = p.y + moveY;
if (!(0, raycast_1.isWall)(nx - (moveX > 0 ? r : -r), p.y))
p.x = nx;
if (!(0, raycast_1.isWall)(p.x, ny - (moveY > 0 ? r : -r)))
p.y = ny;
// Head bob
if (moving) {
p.bobPhase += 0.15;
}
// Damage flash
if (p.damageFlash > 0)
p.damageFlash -= 1;
// Weapon switch
if ((0, input_1.isKeyDown)('1'))
p.currentWeapon = types_1.WeaponType.Fist;
if ((0, input_1.isKeyDown)('2'))
p.currentWeapon = types_1.WeaponType.Pistol;
if ((0, input_1.isKeyDown)('3') && p.weapons[types_1.WeaponType.Shotgun].equipped)
p.currentWeapon = types_1.WeaponType.Shotgun;
// Reload
if ((0, input_1.isKeyDown)('r')) {
const w = p.weapons[p.currentWeapon];
if (p.currentWeapon === types_1.WeaponType.Pistol && w.reserve > 0 && !w.reloading) {
w.reloading = true;
w.reloadTimer = 30;
}
if (p.currentWeapon === types_1.WeaponType.Shotgun && (w.reserve > 0 || w.ammo < w.maxAmmo) && !w.reloading) {
w.reloading = true;
w.reloadTimer = 45;
}
}
// Shooting
updateWeapon(game, p.currentWeapon);
}
function updateWeapon(game, weapon) {
const p = game.player;
const w = p.weapons[weapon];
if (!p.alive)
return;
// Reload
if (w.reloading) {
w.reloadTimer--;
if (w.reloadTimer <= 0) {
w.reloading = false;
if (weapon === types_1.WeaponType.Pistol) {
const needed = w.maxAmmo - w.ammo;
const take = Math.min(needed, w.reserve);
w.ammo += take;
w.reserve -= take;
}
if (weapon === types_1.WeaponType.Shotgun) {
(0, game_1.tryEquipShotgun)(game);
}
}
return;
}
if (w.fireTimer > 0)
w.fireTimer--;
if (mouseDown()) {
if (weapon === types_1.WeaponType.Fist) {
if (w.fireTimer <= 0) {
fireFist(game);
w.fireTimer = 15;
}
}
else if (weapon === types_1.WeaponType.Pistol) {
if (w.fireTimer <= 0 && w.ammo > 0) {
firePistol(game);
w.fireTimer = 12;
w.ammo--;
}
}
else if (weapon === types_1.WeaponType.Shotgun) {
if (w.fireTimer <= 0 && w.ammo > 0) {
fireShotgun(game);
w.fireTimer = 30;
w.ammo--;
}
}
}
}
const input_2 = require("./input");
function mouseDown() {
return (0, input_2.consumeMouseClick)();
}
function fireFist(game) {
const p = game.player;
// Melee attack - check enemies in front
for (const e of game.enemies) {
if (!e.alive)
continue;
const dx = e.x - p.x;
const dy = e.y - p.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 1.5) {
const angleToEnemy = Math.atan2(dy, dx);
let angleDiff = angleToEnemy - p.angle;
while (angleDiff > Math.PI)
angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI)
angleDiff += Math.PI * 2;
if (Math.abs(angleDiff) < 0.4) {
damageEnemy(game, e, 15);
(0, sound_1.playFist)();
// Knockback
const kb = 0.5;
e.x += Math.cos(angleToEnemy) * kb;
e.y += Math.sin(angleToEnemy) * kb;
}
}
}
}
function firePistol(game) {
(0, sound_1.playShoot)();
const p = game.player;
const hits = findEnemiesInCone(game, p.x, p.y, p.angle, 0.03, 20);
for (const hit of hits) {
damageEnemy(game, hit.enemy, 20 + Math.floor(Math.random() * 10));
for (let i = 0; i < 3; i++) {
(0, game_1.spawnParticle)(game, p.x, p.y, '#ff0');
}
}
// Muzzle flash even if no hit
for (let i = 0; i < 3; i++) {
(0, game_1.spawnParticle)(game, p.x, p.y, '#ff0');
}
}
function fireShotgun(game) {
(0, sound_1.playShotgun)();
const p = game.player;
// Spread shot - multiple pellets
for (let i = 0; i < 5; i++) {
const spread = (Math.random() - 0.5) * 0.08;
const hits = findEnemiesInCone(game, p.x, p.y, p.angle + spread, 0.02, 15);
for (const hit of hits) {
damageEnemy(game, hit.enemy, 12 + Math.floor(Math.random() * 8));
}
}
// Muzzle flash
for (let i = 0; i < 8; i++) {
(0, game_1.spawnParticle)(game, p.x, p.y, '#f80');
}
}
function findEnemiesInCone(game, px, py, angle, cone, maxDist) {
const hits = [];
const sorted = [...game.enemies]
.filter(e => e.alive)
.map(e => ({ enemy: e, dist: Math.sqrt((e.x - px) ** 2 + (e.y - py) ** 2) }))
.sort((a, b) => a.dist - b.dist);
for (const hit of sorted) {
if (hit.dist > maxDist)
continue;
const dx = hit.enemy.x - px;
const dy = hit.enemy.y - py;
let angleToEnemy = Math.atan2(dy, dx) - angle;
while (angleToEnemy > Math.PI)
angleToEnemy -= Math.PI * 2;
while (angleToEnemy < -Math.PI)
angleToEnemy += Math.PI * 2;
if (Math.abs(angleToEnemy) < cone) {
if ((0, raycast_1.canSee)(px, py, hit.enemy.x, hit.enemy.y)) {
hits.push(hit);
}
}
}
return hits;
}
function damageEnemy(game, enemy, damage) {
enemy.health -= damage;
enemy.hitFlash = 8;
if (enemy.health <= 0) {
enemy.alive = false;
enemy.deathTimer = 30;
game.player.score += enemy.type === types_1.EnemyType.Commander ? 300 :
enemy.type === types_1.EnemyType.Demon ? 200 : 100;
game.player.kills++;
game.totalKills++;
(0, sound_1.playEnemyDie)();
// Blood particles
for (let i = 0; i < 10; i++) {
(0, game_1.spawnParticle)(game, enemy.x, enemy.y, '#c00');
}
}
}
};
// ── module: src/enemies.ts ──
__mods["src/enemies.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateEnemies = updateEnemies;
exports.damagePlayer = damagePlayer;
const raycast_1 = require("./raycast");
const game_1 = require("./game");
const sound_1 = require("./sound");
const ATTACK_RANGE = 1.5;
function updateEnemies(game) {
const p = game.player;
if (!p.alive)
return;
for (const e of game.enemies) {
if (!e.alive) {
if (e.deathTimer > 0)
e.deathTimer--;
continue;
}
// Hit flash decay
if (e.hitFlash > 0)
e.hitFlash--;
// Animation
e.animTimer++;
if (e.animTimer > 10) {
e.animTimer = 0;
e.animFrame = (e.animFrame + 1) % 2;
}
const dx = p.x - e.x;
const dy = p.y - e.y;
const dist = Math.sqrt(dx * dx + dy * dy);
// Line of sight check
e.seePlayer = dist < e.alertDist && (0, raycast_1.canSee)(e.x, e.y, p.x, p.y);
// State machine
if (e.seePlayer) {
e.state = dist < ATTACK_RANGE ? 'attack' : 'chase';
}
else if (!e.seePlayer && dist < e.alertDist * 0.3) {
// Still chase if very close
e.state = 'chase';
}
switch (e.state) {
case 'chase':
chasePlayer(game, e, dist, dx, dy);
break;
case 'attack':
meleeAttack(game, e, dist);
break;
}
}
}
function chasePlayer(game, e, dist, dx, dy) {
if (dist < 0.8)
return;
const angle = Math.atan2(dy, dx);
const moveX = Math.cos(angle) * e.speed;
const moveY = Math.sin(angle) * e.speed;
const r = 0.25;
const nx = e.x + moveX;
const ny = e.y + moveY;
if (!(0, raycast_1.isWall)(nx - (moveX > 0 ? r : -r), e.y))
e.x = nx;
if (!(0, raycast_1.isWall)(e.x, ny - (moveY > 0 ? r : -r)))
e.y = ny;
// Try to shoot
e.fireTimer--;
if (e.fireTimer <= 0 && dist < e.alertDist * 0.8) {
e.fireTimer = e.fireRate + Math.floor(Math.random() * 20);
const p = game.player;
const hitChance = Math.max(0.12, 0.6 - dist * 0.04);
(0, sound_1.playEnemyShoot)();
if (Math.random() < hitChance) {
damagePlayer(game, e.damage);
}
for (let i = 0; i < 3; i++) {
(0, game_1.spawnParticle)(game, e.x, e.y, '#f80');
}
}
}
function meleeAttack(game, e, dist) {
e.fireTimer--;
if (e.fireTimer <= 0 && dist < ATTACK_RANGE + 0.5) {
e.fireTimer = e.fireRate;
damagePlayer(game, e.damage);
for (let i = 0; i < 5; i++) {
(0, game_1.spawnParticle)(game, e.x, e.y, '#f80');
}
}
else if (dist > ATTACK_RANGE + 1) {
e.state = 'chase';
}
}
function damagePlayer(game, damage) {
const p = game.player;
if (!p.alive)
return;
p.health -= damage;
p.damageFlash = 10;
if (p.health <= 0) {
p.health = 0;
p.alive = false;
game.phase = 'dead';
game.deadTimer = 120;
(0, sound_1.playDeath)();
}
}
};
// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.render = render;
const types_1 = require("./types");
const raycast_1 = require("./raycast");
const W = 640;
const H = 400;
function render(game, ctx) {
const p = game.player;
const bob = Math.sin(p.bobPhase) * 4;
ctx.imageSmoothingEnabled = false;
// Sky
const sky = ctx.createLinearGradient(0, 0, 0, H / 2 + bob);
sky.addColorStop(0, '#1a0500');
sky.addColorStop(1, '#4a1000');
ctx.fillStyle = sky;
ctx.fillRect(0, 0, W, H / 2 + bob);
// Floor
const flr = ctx.createLinearGradient(0, H / 2 + bob, 0, H);
flr.addColorStop(0, '#2a2a2a');
flr.addColorStop(1, '#111');
ctx.fillStyle = flr;
ctx.fillRect(0, H / 2 + bob, W, H / 2 - bob);
const zbuf = new Float64Array(W);
// ── Walls ──
for (let x = 0; x < W; x++) {
const rayAngle = p.angle - Math.PI / 6 + (x / W) * (Math.PI / 3);
const hit = (0, raycast_1.castRay)(p.x, p.y, rayAngle);
const perpDist = hit.dist;
zbuf[x] = perpDist;
const lineH = Math.min(H * 3, H / (perpDist || 0.01));
const ds = Math.max(0, (H - lineH) / 2 + bob);
const de = Math.min(H, (H + lineH) / 2 + bob);
const shade = Math.max(0.12, 1 - perpDist / 16);
const wc = wallColor(hit.wallType, hit.side, shade, hit.wallX);
ctx.fillStyle = wc;
ctx.fillRect(x, ds, 1, de - ds);
// Top edge highlight
ctx.fillStyle = lighten(wc, 0.15);
ctx.fillRect(x, ds, 1, Math.max(1, lineH * 0.02));
}
// ── Sprites ──
renderSprites(game, p, zbuf, bob, ctx);
// ── Weapon ──
renderWeapon(game, p, bob, ctx);
// ── HUD ──
renderHUD(game, p, ctx);
// ── Damage flash ──
if (p.damageFlash > 0) {
ctx.fillStyle = `rgba(255,0,0,${p.damageFlash / 18})`;
ctx.fillRect(0, 0, W, H);
}
// ── Crosshair ──
ctx.strokeStyle = '#0f0';
ctx.lineWidth = 1;
const cx = W / 2, cy = H / 2;
ctx.beginPath();
ctx.moveTo(cx - 7, cy);
ctx.lineTo(cx - 2, cy);
ctx.moveTo(cx + 2, cy);
ctx.lineTo(cx + 7, cy);
ctx.moveTo(cx, cy - 7);
ctx.lineTo(cx, cy - 2);
ctx.moveTo(cx, cy + 2);
ctx.lineTo(cx, cy + 7);
ctx.stroke();
}
function renderSprites(g, p, zbuf, bob, ctx) {
const list = [];
for (const e of g.enemies) {
if (!e.alive && e.deathTimer <= 0)
continue;
list.push({ x: e.x, y: e.y, d: dist(p, e), tag: 'e', data: e });
}
for (const pk of g.pickups) {
if (!pk.active || pk.collected)
continue;
list.push({ x: pk.x, y: pk.y, d: dist(p, pk), tag: 'p', data: pk });
}
list.sort((a, b) => b.d - a.d);
for (const s of list) {
const dx = s.x - p.x, dy = s.y - p.y;
let ang = Math.atan2(dy, dx) - p.angle;
while (ang > Math.PI)
ang -= Math.PI * 2;
while (ang < -Math.PI)
ang += Math.PI * 2;
if (Math.abs(ang) > Math.PI / 3)
continue;
const sx = (0.5 + ang / (Math.PI / 3)) * W;
const pd = s.d * Math.cos(ang);
if (pd < 0.2)
continue;
const sh = Math.min(H, H / pd);
if (s.tag === 'e')
drawEnemy(s.data, sx, sh, pd, zbuf, bob, ctx);
else
drawPickup(s.data, sx, sh, pd, zbuf, bob, g, ctx);
}
// particles
for (const pt of g.particles) {
const dx = pt.x - p.x, dy = pt.y - p.y;
let ang = Math.atan2(dy, dx) - p.angle;
while (ang > Math.PI)
ang -= Math.PI * 2;
while (ang < -Math.PI)
ang += Math.PI * 2;
if (Math.abs(ang) > Math.PI / 3)
continue;
const sx = (0.5 + ang / (Math.PI / 3)) * W;
const pd = Math.sqrt(dx * dx + dy * dy) * Math.cos(ang);
if (pd < 0.1)
continue;
const sz = pt.size * H / pd;
const sy = H / 2 + bob;
ctx.globalAlpha = pt.life / pt.maxLife;
ctx.fillStyle = pt.color;
ctx.fillRect(sx - sz / 2, sy - sz / 2, sz, sz);
ctx.globalAlpha = 1;
}
}
function drawEnemy(e, sx, sh, d, zbuf, bob, ctx) {
const sw = sh * 0.6;
const x0 = Math.max(0, Math.floor(sx - sw / 2));
const x1 = Math.min(W, Math.floor(sx + sw / 2));
const baseY = Math.floor(H / 2 + sh * 0.05 + bob);
const topY = Math.max(0, baseY - sh * 0.65);
const botY = Math.min(H, baseY + sh * 0.35);
const shade = Math.max(0.2, 1 - d / 15);
const deadScale = e.deathTimer > 0 ? (1 - e.deathTimer / 30) * 0.5 + 0.5 : 1;
const hTop = Math.floor(topY + (botY - topY) * (1 - deadScale) * 0.5);
for (let s = x0; s < x1; s++) {
if (d >= zbuf[s])
continue;
const t = (s - x0) / (x1 - x0);
const c = 0.5, bw = 0.35;
if (Math.abs(t - c) > bw)
continue;
const col = e.hitFlash > 0 ? [255, 255, 255] :
!e.alive ? [80, 30, 30] :
e.type === types_1.EnemyType.Imp ? [160, 80, 40] :
e.type === types_1.EnemyType.Demon ? [170, 40, 40] :
[60, 100, 60];
const r = Math.floor(col[0] * shade);
const gn = Math.floor(col[1] * shade);
const b = Math.floor(col[2] * shade);
// Head area
const headEnd = hTop + (botY - hTop) * 0.3;
const headW = bw * 0.65;
if (Math.abs(t - c) < headW && hTop < headEnd) {
ctx.fillStyle = `rgb(${Math.floor(r * 1.1)},${Math.floor(gn * 1.1)},${Math.floor(b * 1.1)})`;
ctx.fillRect(s, hTop, 1, headEnd - hTop);
// Eyes
if (e.alive && (Math.abs(t - c + 0.08) < 0.03 || Math.abs(t - c - 0.08) < 0.03)) {
const eyeCol = e.type === types_1.EnemyType.Commander ? '255,40,40' : '255,255,0';
ctx.fillStyle = `rgb(${eyeCol})`;
ctx.fillRect(s, hTop + (headEnd - hTop) * 0.5, 1, headEnd - hTop);
}
}
// Body
if (headEnd < botY) {
ctx.fillStyle = `rgb(${r},${gn},${b})`;
ctx.fillRect(s, headEnd, 1, botY - headEnd);
}
// Health bar (above sprite, only visible close)
if (e.alive && shade > 0.3) {
const hbW = sw * 0.5;
const hbX = sx - hbW / 2;
const hbY = hTop - 6;
if (s >= Math.floor(hbX) && s <= Math.ceil(hbX + hbW)) {
const lt = (s - hbX) / hbW;
ctx.fillStyle = '#400';
ctx.fillRect(s, hbY, 1, 3);
if (lt < e.health / e.maxHealth) {
const hp = e.health / e.maxHealth;
ctx.fillStyle = hp > 0.5 ? '#0c0' : hp > 0.25 ? '#cc0' : '#f00';
ctx.fillRect(s, hbY, 1, 3);
}
}
}
}
}
function drawPickup(pk, sx, sh, d, zbuf, bob, g, ctx) {
const sz = Math.max(4, sh * 0.2);
const px = sz * 0.4;
const bOff = Math.sin(g.time * 0.08 + pk.x * 3) * sz * 0.15;
const cy = H / 2 + sh * 0.12 + bob + bOff;
const shade = Math.max(0.3, 1 - d / 12);
const col = pk.type === 'health' ? [0, 255, 0] :
pk.type === 'ammo_pistol' ? [255, 255, 0] :
[255, 140, 0];
const x0 = Math.max(0, Math.floor(sx - px));
const x1 = Math.min(W, Math.floor(sx + px));
const y0 = Math.max(0, Math.floor(cy - sz / 2));
const y1 = Math.min(H, Math.floor(cy + sz / 2));
for (let s = x0; s < x1; s++) {
if (d >= zbuf[s])
continue;
ctx.fillStyle = `rgb(${Math.floor(col[0] * shade)},${Math.floor(col[1] * shade)},${Math.floor(col[2] * shade)})`;
ctx.fillRect(s, y0, 1, y1 - y0);
}
// Glow ring
ctx.globalAlpha = 0.2;
ctx.fillStyle = `rgb(${col[0]},${col[1]},${col[2]})`;
ctx.fillRect(x0 - 3, y0 - 3, (x1 - x0) + 6, (y1 - y0) + 6);
ctx.globalAlpha = 1;
// Label
if (shade > 0.5) {
ctx.font = `${Math.max(8, Math.floor(sz * 0.8))}px monospace`;
ctx.textAlign = 'center';
ctx.fillStyle = '#fff';
const lbl = pk.type === 'health' ? '+' : pk.type === 'ammo_pistol' ? 'P' : 'S';
ctx.fillText(lbl, sx, cy + sz * 0.35);
ctx.textAlign = 'left';
}
}
// ── Weapon ──
function renderWeapon(g, p, bob, ctx) {
const wep = p.currentWeapon;
const w = p.weapons[wep];
const cx = W / 2;
const wbx = Math.sin(p.bobPhase) * 8;
const wby = Math.abs(Math.cos(p.bobPhase)) * 5;
let fireKick = 0;
if (w.fireTimer > (wep === types_1.WeaponType.Shotgun ? 20 : 5))
fireKick = -18;
let reloadOff = 0;
if (w.reloading)
reloadOff = Math.sin(w.reloadTimer * 0.2) * 40;
const bx = cx + wbx;
const by = H - 80 + wby + fireKick + reloadOff;
ctx.save();
if (wep === types_1.WeaponType.Fist) {
ctx.fillStyle = '#b87';
ctx.fillRect(bx - 18, by - 15, 36, 55);
ctx.fillStyle = '#965';
ctx.fillRect(bx - 22, by + 40, 44, 35);
}
else if (wep === types_1.WeaponType.Pistol) {
ctx.fillStyle = '#444';
ctx.fillRect(bx - 9, by - 5, 18, 55);
ctx.fillStyle = '#333';
ctx.fillRect(bx - 7, by + 50, 14, 30);
ctx.fillStyle = '#222';
ctx.fillRect(bx - 5, by - 35, 10, 30);
if (w.fireTimer > 10) {
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.arc(bx, by - 40, 14, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#f80';
ctx.beginPath();
ctx.arc(bx, by - 40, 7, 0, Math.PI * 2);
ctx.fill();
}
}
else {
ctx.fillStyle = '#444';
ctx.fillRect(bx - 14, by - 10, 28, 60);
ctx.fillStyle = '#543';
ctx.fillRect(bx - 10, by + 50, 20, 30);
ctx.fillStyle = '#333';
ctx.fillRect(bx - 7, by - 55, 14, 45);
ctx.fillStyle = '#654';
ctx.fillRect(bx - 12, by + 12, 24, 8);
if (w.fireTimer > 22) {
ctx.fillStyle = '#f80';
ctx.beginPath();
ctx.arc(bx, by - 60, 22, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.arc(bx, by - 60, 12, 0, Math.PI * 2);
ctx.fill();
}
}
ctx.restore();
}
// ── HUD ──
function renderHUD(g, p, ctx) {
const barH = 40;
ctx.fillStyle = 'rgba(0,0,0,0.75)';
ctx.fillRect(0, H - barH, W, barH);
ctx.fillStyle = '#555';
ctx.fillRect(0, H - barH, W, 2);
ctx.font = 'bold 16px monospace';
ctx.textAlign = 'left';
// Health
ctx.fillStyle = p.health > 50 ? '#0f0' : p.health > 25 ? '#ff0' : '#f00';
ctx.fillText(`♥ ${p.health}`, 10, H - 14);
// Weapon name & ammo
const w = p.weapons[p.currentWeapon];
const names = ['FIST', 'PISTOL', 'SHOTGUN'];
ctx.fillStyle = '#999';
ctx.font = '12px monospace';
ctx.fillText(names[p.currentWeapon], 100, H - 26);
ctx.fillStyle = w.ammo === Infinity ? '#fff' : w.ammo > 0 ? '#ff0' : '#f00';
ctx.font = 'bold 16px monospace';
ctx.fillText(w.ammo === Infinity ? '∞' : `${w.ammo} / ${w.reserve}`, 100, H - 8);
// Score & kills
ctx.textAlign = 'center';
ctx.fillStyle = '#fff';
ctx.fillText(`SCORE: ${p.score}`, W / 2, H - 14);
ctx.fillStyle = '#a88';
ctx.font = '11px monospace';
ctx.fillText(`KILLS: ${p.kills}`, W / 2, H - 28);
// Enemies alive
const alive = g.enemies.filter(e => e.alive).length;
ctx.fillStyle = alive === 0 ? '#0f0' : '#f88';
ctx.fillText(`ENEMIES: ${alive}`, W / 2, H - 8);
// Weapon slots
ctx.textAlign = 'left';
ctx.font = '10px monospace';
for (let i = 0; i < 3; i++) {
const sx = W - 135 + i * 42;
ctx.fillStyle = i === p.currentWeapon ? '#c00' : '#333';
ctx.fillRect(sx, H - barH - 22, 34, 18);
ctx.fillStyle = '#fff';
ctx.fillText(`${i + 1}: ${['F', 'P', 'S'][i]}`, sx + 4, H - barH - 9);
}
// Minimap
drawMinimap(g, ctx);
}
function drawMinimap(g, ctx) {
const ms = 3; // tile size in pixels
const mw = g.mapWidth * ms;
const mh = g.mapHeight * ms;
const mx = W - mw - 8;
const my = 8;
ctx.globalAlpha = 0.6;
ctx.fillStyle = '#000';
ctx.fillRect(mx - 2, my - 2, mw + 4, mh + 4);
for (let y = 0; y < g.mapHeight; y++) {
for (let x = 0; x < g.mapWidth; x++) {
const t = g.map[y][x];
if (t > 0) {
ctx.fillStyle = t === 5 ? '#0f0' : t === 4 ? '#888' : '#555';
ctx.fillRect(mx + x * ms, my + y * ms, ms, ms);
}
}
}
// Enemies
for (const e of g.enemies) {
if (!e.alive)
continue;
ctx.fillStyle = '#f00';
ctx.fillRect(mx + e.x * ms - 1, my + e.y * ms - 1, 2, 2);
}
// Player
ctx.fillStyle = '#0f0';
ctx.fillRect(mx + g.player.x * ms - 2, my + g.player.y * ms - 2, 4, 4);
// Direction indicator
ctx.strokeStyle = '#0f0';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(mx + g.player.x * ms, my + g.player.y * ms);
ctx.lineTo(mx + (g.player.x + Math.cos(g.player.angle) * 1.5) * ms, my + (g.player.y + Math.sin(g.player.angle) * 1.5) * ms);
ctx.stroke();
ctx.globalAlpha = 1;
}
// ── Helpers ──
function dist(a, b) {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
function wallColor(type, side, shade, wx) {
const [r0, g0, b0] = type === 1 ? [120, 120, 120] :
type === 2 ? [130, 60, 40] :
type === 3 ? [40, 50, 120] :
type === 4 ? [90, 90, 90] :
type === 5 ? [20, 120, 20] :
[200, 0, 0];
let s = shade;
if (side)
s *= 0.75;
// Brick pattern
if (type <= 3) {
if (Math.abs(wx - 0.5) < 0.04 || Math.abs(wx) < 0.04)
s *= 0.82;
}
if (type === 4) {
if (Math.abs(wx - 0.5) < 0.1)
s *= 0.9;
}
return `rgb(${Math.floor(r0 * s)},${Math.floor(g0 * s)},${Math.floor(b0 * s)})`;
}
function lighten(hex, amt) {
// Parse rgb(r,g,b) or rgb()
const m = hex.match(/(\d+)/g);
if (!m)
return hex;
const r = Math.min(255, +m[0] + 60 * amt);
const g = Math.min(255, +m[1] + 60 * amt);
const b = Math.min(255, +m[2] + 60 * amt);
return `rgb(${r},${g},${b})`;
}
};
// ── module: src/sound.ts ──
__mods["src/sound.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.playShoot = playShoot;
exports.playShotgun = playShotgun;
exports.playFist = playFist;
exports.playHit = playHit;
exports.playPickup = playPickup;
exports.playDeath = playDeath;
exports.playEnemyDie = playEnemyDie;
exports.playEnemyShoot = playEnemyShoot;
// Procedural sound effects using Web Audio API
let audioCtx = null;
function ensureAudioCtx() {
if (!audioCtx) {
audioCtx = new AudioContext();
}
return audioCtx;
}
function playNoise(ctx, duration, vol, freq, type = 'square') {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, ctx.currentTime);
gain.gain.setValueAtTime(vol, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + duration);
}
function playNoiseBurst(ctx, duration, vol) {
const bufferSize = ctx.sampleRate * duration;
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
}
const source = ctx.createBufferSource();
source.buffer = buffer;
const gain = ctx.createGain();
gain.gain.setValueAtTime(vol, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
source.connect(gain);
gain.connect(ctx.destination);
source.start();
}
function playShoot() {
const ctx = ensureAudioCtx();
playNoiseBurst(ctx, 0.1, 0.3);
playNoise(ctx, 0.08, 0.2, 150, 'sawtooth');
}
function playShotgun() {
const ctx = ensureAudioCtx();
playNoiseBurst(ctx, 0.2, 0.5);
playNoise(ctx, 0.15, 0.3, 80, 'sawtooth');
}
function playFist() {
const ctx = ensureAudioCtx();
playNoise(ctx, 0.06, 0.2, 200, 'square');
}
function playHit() {
const ctx = ensureAudioCtx();
playNoise(ctx, 0.1, 0.15, 300, 'sawtooth');
}
function playPickup() {
const ctx = ensureAudioCtx();
playNoise(ctx, 0.1, 0.2, 800, 'sine');
setTimeout(() => {
const c = ensureAudioCtx();
playNoise(c, 0.1, 0.15, 1200, 'sine');
}, 80);
}
function playDeath() {
const ctx = ensureAudioCtx();
playNoise(ctx, 0.5, 0.3, 200, 'sawtooth');
playNoise(ctx, 0.4, 0.2, 100, 'square');
}
function playEnemyDie() {
const ctx = ensureAudioCtx();
playNoise(ctx, 0.3, 0.2, 150, 'sawtooth');
playNoise(ctx, 0.2, 0.15, 80, 'square');
}
function playEnemyShoot() {
const ctx = ensureAudioCtx();
playNoise(ctx, 0.15, 0.15, 400, 'square');
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"63126259-e86b-45e2-bf54-0d10de1a55d0","tokensIn":1350853,"tokensOut":32387,"tokensTotal":1383240,"cost":0,"turns":41,"toolCalls":46,"failedToolCalls":3,"timestamp":"2026-08-02T03:38:09.838Z"} -->