← DOOM results

DOOM

Qwen 3.6 27B Fable Fusion 711 Uncensored Heretic NM DAU NEO MAX MTP GGUF · typescript

Initial view of DOOM

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 48,427 bytes · SHA-256 ae95bae62585
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dungeon Doom - Retro FPS</title>
<style>
/* ── css: layout ── */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  background: #000;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  font-family: 'Courier New', monospace;
}
#game-container {
  position: relative;
  width: 960px;
  height: 640px;
  border: 3px solid #333;
  image-rendering: pixelated;
}
#game-canvas {
  width: 100%;
  height: 100%;
  display: block;
  background: #000;
}
/* ── css: hud ── */
#hud {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 70px;
  background: linear-gradient(to top, rgba(0,0,0,0.9), rgba(0,0,0,0.6));
  border-top: 2px solid #444;
  display: flex;
  align-items: center;
  justify-content: space-around;
  padding: 0 20px;
  color: #fff;
  font-size: 14px;
}

#health-bar {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 120px;
}

#health-bar span {
  font-size: 11px;
  color: #aaa;
  text-transform: uppercase;
}

#health-fill {
  height: 16px;
  width: 100%;
  background: #4caf50;
  border: 1px solid #333;
  transition: width 0.2s, background-color 0.3s;
}

#weapon-display {
  font-size: 16px;
  font-weight: bold;
  color: #ff9800;
  text-shadow: 0 0 10px rgba(255,152,0,0.5);
}

#ammo-display {
  font-size: 16px;
  color: #fff;
}

#score-display {
  font-size: 16px;
  color: #ff4444;
}
/* ── css: overlay ── */
#message-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 80px;
}

#message-text {
  font-size: 16px;
  font-weight: bold;
  color: #ff0;
  text-shadow: 0 0 10px rgba(255,255,0,0.8), 2px 2px 0 #000;
  text-align: center;
  max-width: 600px;
}

/* Controls hint */
#controls-hint {
  position: absolute;
  top: 10px;
  left: 10px;
  color: rgba(255,255,255,0.4);
  font-size: 10px;
  line-height: 1.4;
  pointer-events: none;
}
</style>
</head>
<body>
<div id="game-container"><div id="controls-hint">
WASD/Arrows: Move &amp; Turn<br>
Space: Shoot<br>
1-3: Switch Weapons<br>
R: Respawn<br>
Click to lock mouse
</div>
  <canvas id="game-canvas"></canvas>
  <div id="hud">
    <div id="health-bar">
      <span>HEALTH</span>
      <div id="health-fill"></div>
    </div>
    <div id="weapon-display">PISTOL</div>
    <div id="ammo-display">Ammo: 50</div>
    <div id="score-display">Kills: 0</div>
  </div>
  <div id="message-overlay">
    <div id="message-text"></div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./level":"src/level.ts","./renderer":"src/renderer.ts","./game":"src/game.ts","./types":"src/types.ts"},"src/level.ts":{"./types":"src/types.ts"},"src/renderer.ts":{"./types":"src/types.ts","./level":"src/level.ts"},"src/game.ts":{"./types":"src/types.ts","./level":"src/level.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 level_1 = require("./level");
const renderer_1 = require("./renderer");
const game_1 = require("./game");
let canvas = document.getElementById('game-canvas');
let ctx = canvas.getContext('2d', { alpha: false });
// Use low internal resolution for retro feel
const GAME_WIDTH = 320;
const GAME_HEIGHT = 240;
canvas.width = GAME_WIDTH;
canvas.height = GAME_HEIGHT;
let gameState = (0, level_1.createInitialState)();
let input = {
    forward: false,
    backward: false,
    turnLeft: false,
    turnRight: false,
    shooting: false,
    weapon1: false,
    weapon2: false,
    weapon3: false,
    respawn: false,
};
let weaponStates = {
    0: (0, game_1.createWeaponState)(),
};
let frameCount = 0;
let lastShootState = false;
let keysJustPressed = new Set();
// Input handling
window.addEventListener('keydown', (e) => {
    keysJustPressed.add(e.key.toLowerCase());
    switch (e.key.toLowerCase()) {
        case 'w':
        case 'arrowup':
            input.forward = true;
            break;
        case 's':
        case 'arrowdown':
            input.backward = true;
            break;
        case 'a':
        case 'arrowleft':
            input.turnLeft = true;
            break;
        case 'd':
        case 'arrowright':
            input.turnRight = true;
            break;
        case ' ':
            input.shooting = true;
            break;
        case '1':
            input.weapon1 = true;
            break;
        case '2':
            input.weapon2 = true;
            break;
        case '3':
            input.weapon3 = true;
            break;
        case 'r':
            input.respawn = true;
            break;
    }
});
window.addEventListener('keyup', (e) => {
    switch (e.key.toLowerCase()) {
        case 'w':
        case 'arrowup':
            input.forward = false;
            break;
        case 's':
        case 'arrowdown':
            input.backward = false;
            break;
        case 'a':
        case 'arrowleft':
            input.turnLeft = false;
            break;
        case 'd':
        case 'arrowright':
            input.turnRight = false;
            break;
        case ' ':
            input.shooting = false;
            lastShootState = false;
            break;
        case '1':
            input.weapon1 = false;
            break;
        case '2':
            input.weapon2 = false;
            break;
        case '3':
            input.weapon3 = false;
            break;
        case 'r':
            input.respawn = false;
            break;
    }
});
// Mouse lock for turning
canvas.addEventListener('click', () => {
    canvas.requestPointerLock?.();
});
document.addEventListener('pointerlockchange', () => {
    // Pointer lock state handled below
});
document.addEventListener('mousemove', (e) => {
    if (document.pointerLockElement === canvas) {
        gameState.player.angle += e.movementX * 0.002;
    }
});
// Update HUD
function updateHUD() {
    const player = gameState.player;
    // Health bar
    const healthFill = document.getElementById('health-fill');
    if (healthFill) {
        const healthPercent = Math.max(0, (player.health / player.maxHealth) * 100);
        healthFill.style.width = healthPercent + '%';
        if (healthPercent > 60) {
            healthFill.style.backgroundColor = '#4caf50';
        }
        else if (healthPercent > 30) {
            healthFill.style.backgroundColor = '#ff9800';
        }
        else {
            healthFill.style.backgroundColor = '#f44336';
        }
    }
    // Weapon display
    const weaponDisplay = document.getElementById('weapon-display');
    if (weaponDisplay && player.weapons[player.currentWeapon]) {
        const weapon = player.weapons[player.currentWeapon];
        const weaponName = { 0: 'PISTOL', 1: 'SHOTGUN', 2: 'MACHINE GUN' }[weapon.type] || 'PISTOL';
        weaponDisplay.textContent = weaponName;
    }
    // Ammo display
    const ammoDisplay = document.getElementById('ammo-display');
    if (ammoDisplay && player.weapons[player.currentWeapon]) {
        const weapon = player.weapons[player.currentWeapon];
        ammoDisplay.textContent = `Ammo: ${weapon.ammo}`;
    }
    // Score display
    const scoreDisplay = document.getElementById('score-display');
    if (scoreDisplay) {
        scoreDisplay.textContent = `Kills: ${player.kills}`;
    }
}
// Main game loop
function gameLoop(timestamp) {
    // Process
    gameState.frameCount++;
    // Weapon state cleanup for removed weapons
    gameState.player.weapons.forEach((w, i) => {
        if (!weaponStates[i]) {
            weaponStates[i] = (0, game_1.createWeaponState)();
        }
    });
    (0, game_1.processInput)(input, gameState, 16);
    (0, game_1.updateWeapons)(input, gameState, weaponStates);
    (0, game_1.updateEnemies)(gameState);
    (0, game_1.updatePickups)(gameState);
    (0, game_1.updateMessages)(gameState);
    // Get current weapon state
    const currentWs = weaponStates[gameState.player.currentWeapon] || (0, game_1.createWeaponState)();
    // Render
    (0, renderer_1.renderGame)(ctx, gameState, GAME_WIDTH, GAME_HEIGHT, frameCount, currentWs.muzzleFlash, currentWs.flashTimer);
    updateHUD();
    frameCount++;
    requestAnimationFrame(gameLoop);
}
// Start
requestAnimationFrame(gameLoop);
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ENEMY_CONFIG = exports.PICKUP_CONFIG = exports.WEAPON_CONFIG = exports.PickupType = exports.EnemyType = exports.WeaponType = void 0;
var WeaponType;
(function (WeaponType) {
    WeaponType[WeaponType["Pistol"] = 0] = "Pistol";
    WeaponType[WeaponType["Shotgun"] = 1] = "Shotgun";
    WeaponType[WeaponType["MachineGun"] = 2] = "MachineGun";
})(WeaponType || (exports.WeaponType = WeaponType = {}));
var EnemyType;
(function (EnemyType) {
    EnemyType[EnemyType["Grunt"] = 0] = "Grunt";
    EnemyType[EnemyType["Soldier"] = 1] = "Soldier";
    EnemyType[EnemyType["Brute"] = 2] = "Brute";
})(EnemyType || (exports.EnemyType = EnemyType = {}));
var PickupType;
(function (PickupType) {
    PickupType[PickupType["HealthSmall"] = 0] = "HealthSmall";
    PickupType[PickupType["HealthLarge"] = 1] = "HealthLarge";
    PickupType[PickupType["AmmoPistol"] = 2] = "AmmoPistol";
    PickupType[PickupType["AmmoShotgun"] = 3] = "AmmoShotgun";
    PickupType[PickupType["AmmoMachineGun"] = 4] = "AmmoMachineGun";
    PickupType[PickupType["WeaponShotgun"] = 5] = "WeaponShotgun";
    PickupType[PickupType["WeaponMachineGun"] = 6] = "WeaponMachineGun";
})(PickupType || (exports.PickupType = PickupType = {}));
exports.WEAPON_CONFIG = {
    [WeaponType.Pistol]: { name: 'PISTOL', damage: 15, fireRate: 20, spread: 0.03, pellets: 1, reloadTime: 15, autoFire: false },
    [WeaponType.Shotgun]: { name: 'SHOTGUN', damage: 8, fireRate: 35, spread: 0.12, pellets: 8, reloadTime: 30, autoFire: false },
    [WeaponType.MachineGun]: { name: 'MACHINE GUN', damage: 10, fireRate: 5, spread: 0.08, pellets: 1, reloadTime: 0, autoFire: true }
};
exports.PICKUP_CONFIG = {
    [PickupType.HealthSmall]: { name: 'Health +25', healthAmount: 25, ammoAmount: 0 },
    [PickupType.HealthLarge]: { name: 'Health +100', healthAmount: 100, ammoAmount: 0 },
    [PickupType.AmmoPistol]: { name: 'Pistol Ammo', healthAmount: 0, ammoAmount: 30 },
    [PickupType.AmmoShotgun]: { name: 'Shotgun Shells', healthAmount: 0, ammoAmount: 10 },
    [PickupType.AmmoMachineGun]: { name: 'Machine Gun Ammo', healthAmount: 0, ammoAmount: 50 },
    [PickupType.WeaponShotgun]: { name: 'Shotgun', healthAmount: 0, ammoAmount: 10 },
    [PickupType.WeaponMachineGun]: { name: 'Machine Gun', healthAmount: 0, ammoAmount: 50 }
};
exports.ENEMY_CONFIG = {
    [EnemyType.Grunt]: { name: 'Grunt', health: 40, speed: 0.02, damage: 8, attackRange: 1.5, attackRate: 40 },
    [EnemyType.Soldier]: { name: 'Soldier', health: 80, speed: 0.025, damage: 15, attackRange: 3.0, attackRate: 30 },
    [EnemyType.Brute]: { name: 'Brute', health: 150, speed: 0.015, damage: 25, attackRange: 1.8, attackRate: 50 }
};
};

// ── module: src/level.ts ──
__mods["src/level.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.levelMap = exports.MAP_HEIGHT = exports.MAP_WIDTH = void 0;
exports.createInitialState = createInitialState;
exports.isWall = isWall;
exports.distance = distance;
exports.canSee = canSee;
const types_1 = require("./types");
exports.MAP_WIDTH = 24;
exports.MAP_HEIGHT = 24;
exports.levelMap = [
    [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, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1],
    [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
    [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
    [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 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, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1],
    [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1],
    [1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 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, 1, 1],
];
// Enemy spawn points (x, y in map coordinates)
const enemySpawns = [
    { x: 5.5, y: 5.5, type: types_1.EnemyType.Grunt },
    { x: 18.5, y: 5.5, type: types_1.EnemyType.Grunt },
    { x: 3.5, y: 9.5, type: types_1.EnemyType.Grunt },
    { x: 20.5, y: 9.5, type: types_1.EnemyType.Soldier },
    { x: 10.5, y: 8.5, type: types_1.EnemyType.Grunt },
    { x: 14.5, y: 15.5, type: types_1.EnemyType.Grunt },
    { x: 5.5, y: 18.5, type: types_1.EnemyType.Soldier },
    { x: 18.5, y: 18.5, type: types_1.EnemyType.Soldier },
    { x: 11.5, y: 19.5, type: types_1.EnemyType.Brute },
    { x: 12.5, y: 19.5, type: types_1.EnemyType.Brute },
];
// Pickup spawn points
const pickupSpawns = [
    { x: 2.5, y: 2.5, type: types_1.PickupType.HealthSmall },
    { x: 11.5, y: 2.5, type: types_1.PickupType.AmmoPistol },
    { x: 21.5, y: 2.5, type: types_1.PickupType.HealthSmall },
    { x: 6.5, y: 7.5, type: types_1.PickupType.WeaponShotgun },
    { x: 17.5, y: 7.5, type: types_1.PickupType.AmmoShotgun },
    { x: 2.5, y: 14.5, type: types_1.PickupType.WeaponMachineGun },
    { x: 21.5, y: 14.5, type: types_1.PickupType.AmmoMachineGun },
    { x: 11.5, y: 13.5, type: types_1.PickupType.HealthLarge },
    { x: 19.5, y: 21.5, type: types_1.PickupType.HealthLarge },
];
function createEnemy(spawn) {
    const config = {
        [types_1.EnemyType.Grunt]: { health: 40, speed: 0.015, damage: 8, attackRange: 1.5, attackRate: 45 },
        [types_1.EnemyType.Soldier]: { health: 80, speed: 0.02, damage: 15, attackRange: 4.0, attackRate: 35 },
        [types_1.EnemyType.Brute]: { health: 180, speed: 0.01, damage: 25, attackRange: 1.8, attackRate: 60 },
    }[spawn.type];
    return {
        x: spawn.x,
        y: spawn.y,
        health: config.health,
        maxHealth: config.health,
        alive: true,
        type: spawn.type,
        speed: config.speed,
        damage: config.damage,
        attackRange: config.attackRange,
        lastAttack: 0,
        frame: 0,
        state: 'idle',
    };
}
function createPickup(spawn) {
    return {
        x: spawn.x,
        y: spawn.y,
        type: spawn.type,
        alive: true,
        bobOffset: Math.random() * Math.PI * 2,
    };
}
function createInitialState() {
    return {
        player: {
            x: 12.5,
            y: 22.5,
            angle: Math.PI,
            health: 100,
            maxHealth: 100,
            weapons: [
                { type: 0, ammo: 50, maxAmmo: 999 },
            ],
            currentWeapon: 0,
            kills: 0,
            alive: true,
            invulnerable: 0,
        },
        enemies: enemySpawns.map(createEnemy),
        pickups: pickupSpawns.map(createPickup),
        map: exports.levelMap,
        width: exports.MAP_WIDTH,
        height: exports.MAP_HEIGHT,
        frameCount: 0,
        gameOver: false,
        won: false,
        message: '',
        messageTimer: 0,
    };
}
function isWall(x, y, map) {
    const mx = Math.floor(x);
    const my = Math.floor(y);
    if (mx < 0 || mx >= exports.MAP_WIDTH || my < 0 || my >= exports.MAP_HEIGHT)
        return true;
    return map[my][mx] !== 0;
}
function distance(x1, y1, x2, y2) {
    return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function canSee(x1, y1, x2, y2, map) {
    const dist = distance(x1, y1, x2, y2);
    const steps = Math.ceil(dist * 8);
    for (let i = 1; i < steps; i++) {
        const t = i / steps;
        const cx = x1 + (x2 - x1) * t;
        const cy = y1 + (y2 - y1) * t;
        if (isWall(cx, cy, map))
            return false;
    }
    return true;
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.castRay = castRay;
exports.renderGame = renderGame;
const types_1 = require("./types");
const level_1 = require("./level");
function castRay(ox, oy, angle, map) {
    const sin = Math.sin(angle);
    const cos = Math.cos(angle);
    let x = ox;
    let y = oy;
    const step = 0.02;
    let dist = 0;
    let wallType = 0;
    let side = 0; // 0 = vertical wall, 1 = horizontal wall
    for (let i = 0; i < 1000; i++) {
        x += cos * step;
        y += sin * step;
        dist += step;
        const mx = Math.floor(x);
        const my = Math.floor(y);
        if (mx < 0 || mx >= map[0].length || my < 0 || my >= map.length) {
            return { dist: 100, wallType: 1, side: 0, hitX: x, hitY: y };
        }
        const cell = map[my][mx];
        if (cell > 0) {
            // Determine side
            const fracX = x - mx;
            const fracY = y - my;
            if (fracX < 0.05 || fracX > 0.95) {
                side = 0;
            }
            else {
                side = 1;
            }
            return { dist, wallType: cell, side, hitX: x, hitY: y };
        }
    }
    return { dist: 100, wallType: 1, side: 0, hitX: x, hitY: y };
}
function renderGame(ctx, state, width, height, weaponFrame, muzzleFlash, flashFrame) {
    const player = state.player;
    const map = state.map;
    const fov = Math.PI / 3;
    const numRays = width;
    const wallHeight = height;
    // Clear sky (top half - dark)
    ctx.fillStyle = '#1a1a2e';
    ctx.fillRect(0, 0, width, height / 2);
    // Clear floor (bottom half)
    ctx.fillStyle = '#2a2a2a';
    ctx.fillRect(0, height / 2, width, height / 2);
    // Wall buffer for depth testing
    const zBuffer = new Float32Array(numRays);
    // Cast rays
    for (let i = 0; i < numRays; i++) {
        const rayAngle = player.angle - fov / 2 + (i / numRays) * fov;
        const result = castRay(player.x, player.y, rayAngle, map);
        // Fix fisheye
        const correctedDist = result.dist * Math.cos(rayAngle - player.angle);
        zBuffer[i] = correctedDist;
        const wallScale = wallHeight / correctedDist;
        const wallTop = (height - wallScale) / 2;
        // Color based on wall type and side
        let baseColor;
        if (result.wallType === 1) {
            baseColor = sideDarkened('#8B4513', result.side);
        }
        else if (result.wallType === 2) {
            baseColor = sideDarkened('#4a4a6a', result.side);
        }
        else {
            baseColor = sideDarkened('#555', result.side);
        }
        // Distance fog
        const fogFactor = Math.min(1, correctedDist / 15);
        baseColor = applyFog(baseColor, fogFactor);
        ctx.fillStyle = baseColor;
        ctx.fillRect(i, wallTop, 1, wallScale);
    }
    // Render sprites (enemies and pickups)
    const sprites = [];
    state.enemies.forEach(enemy => {
        if (!enemy.alive)
            return;
        const d = (0, level_1.distance)(player.x, player.y, enemy.x, enemy.y);
        sprites.push({ x: enemy.x, y: enemy.y, dist: d, type: 'enemy', data: enemy });
    });
    state.pickups.forEach(pickup => {
        if (!pickup.alive)
            return;
        const d = (0, level_1.distance)(player.x, player.y, pickup.x, pickup.y);
        sprites.push({ x: pickup.x, y: pickup.y, dist: d, type: 'pickup', data: pickup });
    });
    // Sort by distance (far first)
    sprites.sort((a, b) => b.dist - a.dist);
    // Render sprites
    sprites.forEach(sprite => {
        const dx = sprite.x - player.x;
        const dy = sprite.y - player.y;
        // Transform sprite position relative to player
        const angleToSprite = Math.atan2(dy, dx);
        let angleDiff = angleToSprite - player.angle;
        while (angleDiff > Math.PI)
            angleDiff -= Math.PI * 2;
        while (angleDiff < -Math.PI)
            angleDiff += Math.PI * 2;
        if (Math.abs(angleDiff) > fov * 1.2)
            return;
        const correctedDist = sprite.dist * Math.cos(angleDiff);
        if (correctedDist < 0.3)
            return;
        const screenX = (0.5 + angleDiff / fov) * width;
        const spriteHeight = (height / correctedDist) * (sprite.type === 'enemy' ? 0.9 : 0.5);
        const spriteWidth = spriteHeight * 0.7;
        const spriteTop = (height - spriteHeight) / 2 + (sprite.type === 'pickup' ? spriteHeight * 0.15 : 0);
        // Check if visible
        const rayIndex = Math.floor(screenX);
        if (rayIndex >= 0 && rayIndex < numRays && correctedDist < zBuffer[rayIndex] && (0, level_1.canSee)(player.x, player.y, sprite.x, sprite.y, map)) {
            if (sprite.type === 'enemy') {
                drawEnemySprite(ctx, sprite.data, screenX, spriteTop, spriteWidth, spriteHeight, correctedDist);
            }
            else {
                drawPickupSprite(ctx, sprite.data, screenX, spriteTop, spriteWidth, spriteHeight, correctedDist, state.frameCount);
            }
        }
    });
    // Draw weapon
    drawWeapon(ctx, width, height, player, weaponFrame, muzzleFlash, flashFrame);
    // Crosshair
    ctx.strokeStyle = 'rgba(0, 255, 0, 0.6)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(width / 2 - 8, height / 2);
    ctx.lineTo(width / 2 + 8, height / 2);
    ctx.moveTo(width / 2, height / 2 - 8);
    ctx.lineTo(width / 2, height / 2 + 8);
    ctx.stroke();
    ctx.fillStyle = 'rgba(0, 255, 0, 0.8)';
    ctx.fillRect(width / 2 - 1, height / 2 - 1, 2, 2);
    // Damage flash
    if (state.player.invulnerable > 0 && state.player.invulnerable % 6 < 3) {
        ctx.fillStyle = 'rgba(255, 0, 0, 0.2)';
        ctx.fillRect(0, 0, width, height);
    }
    // Message overlay
    if (state.messageTimer > 0 && state.message) {
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fillRect(0, height * 0.3, width, height * 0.15);
        ctx.fillStyle = '#ff0';
        ctx.font = 'bold 24px "Courier New", monospace';
        ctx.textAlign = 'center';
        ctx.fillText(state.message, width / 2, height * 0.4);
    }
    // Game over / Win screens
    if (state.gameOver) {
        ctx.fillStyle = 'rgba(139, 0, 0, 0.7)';
        ctx.fillRect(0, 0, width, height);
        ctx.fillStyle = '#fff';
        ctx.font = 'bold 48px "Courier New", monospace';
        ctx.textAlign = 'center';
        ctx.fillText('YOU DIED', width / 2, height / 2 - 30);
        ctx.font = '24px "Courier New", monospace';
        ctx.fillText('Press R to Respawn', width / 2, height / 2 + 20);
        ctx.font = '18px "Courier New", monospace';
        ctx.fillText(`Kills: ${state.player.kills}`, width / 2, height / 2 + 55);
    }
    if (state.won) {
        ctx.fillStyle = 'rgba(0, 50, 0, 0.7)';
        ctx.fillRect(0, 0, width, height);
        ctx.fillStyle = '#0f0';
        ctx.font = 'bold 48px "Courier New", monospace';
        ctx.textAlign = 'center';
        ctx.fillText('VICTORY!', width / 2, height / 2 - 30);
        ctx.font = '24px "Courier New", monospace';
        ctx.fillText('All enemies eliminated!', width / 2, height / 2 + 10);
        ctx.font = '20px "Courier New", monospace';
        ctx.fillText(`Final Kills: ${state.player.kills}`, width / 2, height / 2 + 45);
        ctx.fillText('Press R to Play Again', width / 2, height / 2 + 80);
    }
}
function sideDarkened(color, side) {
    if (side === 1) {
        const rgb = hexToRgb(color);
        return `rgb(${rgb.r * 0.7}, ${rgb.g * 0.7}, ${rgb.b * 0.7})`;
    }
    return color;
}
function applyFog(color, factor) {
    if (factor <= 0)
        return color;
    const rgb = parseColor(color);
    const fog = { r: 10, g: 10, b: 20 };
    const r = Math.round(rgb.r * (1 - factor) + fog.r * factor);
    const g = Math.round(rgb.g * (1 - factor) + fog.g * factor);
    const b = Math.round(rgb.b * (1 - factor) + fog.b * factor);
    return `rgb(${r}, ${g}, ${b})`;
}
function hexToRgb(hex) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16),
    } : { r: 128, g: 128, b: 128 };
}
function parseColor(color) {
    const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
    if (rgbMatch) {
        return {
            r: parseInt(rgbMatch[1]),
            g: parseInt(rgbMatch[2]),
            b: parseInt(rgbMatch[3]),
        };
    }
    return hexToRgb(color);
}
function drawEnemySprite(ctx, enemy, screenX, spriteTop, width, height, dist) {
    const fogFactor = Math.min(1, dist / 15);
    // Different colors for enemy types
    let bodyColor, headColor, eyeColor;
    switch (enemy.type) {
        case types_1.EnemyType.Grunt:
            bodyColor = '#8B4513';
            headColor = '#DEB887';
            eyeColor = '#ff0000';
            break;
        case types_1.EnemyType.Soldier:
            bodyColor = '#2F4F4F';
            headColor = '#8B7355';
            eyeColor = '#ff4400';
            break;
        case types_1.EnemyType.Brute:
            bodyColor = '#8B0000';
            headColor = '#CD853F';
            eyeColor = '#ffff00';
            break;
        default:
            bodyColor = '#8B4513';
            headColor = '#DEB887';
            eyeColor = '#ff0000';
    }
    // Darken with fog
    const darken = (c) => {
        const rgb = hexToRgb(c);
        const r = Math.round(rgb.r * (1 - fogFactor));
        const g = Math.round(rgb.g * (1 - fogFactor));
        const b = Math.round(rgb.b * (1 - fogFactor));
        return `rgb(${r}, ${g}, ${b})`;
    };
    const cx = screenX;
    const bobOffset = Math.sin(enemy.frame * 0.1) * (height * 0.02);
    // Body
    ctx.fillStyle = darken(bodyColor);
    ctx.fillRect(cx - width * 0.3, spriteTop + height * 0.35 + bobOffset, width * 0.6, height * 0.4);
    // Head
    ctx.fillStyle = darken(headColor);
    ctx.beginPath();
    ctx.arc(cx, spriteTop + height * 0.2 + bobOffset, width * 0.2, 0, Math.PI * 2);
    ctx.fill();
    // Eyes (glowing)
    const eyeBrightness = 1 - fogFactor * 0.7;
    ctx.fillStyle = `rgba(255, 0, 0, ${eyeBrightness})`;
    ctx.fillRect(cx - width * 0.08, spriteTop + height * 0.15 + bobOffset, width * 0.06, height * 0.04);
    ctx.fillRect(cx + width * 0.02, spriteTop + height * 0.15 + bobOffset, width * 0.06, height * 0.04);
    // Arms
    ctx.fillStyle = darken(bodyColor);
    ctx.fillRect(cx - width * 0.45, spriteTop + height * 0.38 + bobOffset, width * 0.15, height * 0.15);
    ctx.fillRect(cx + width * 0.3, spriteTop + height * 0.38 + bobOffset, width * 0.15, height * 0.15);
    // Health bar
    if (enemy.health < enemy.maxHealth) {
        const barWidth = width * 0.6;
        const barHeight = height * 0.03;
        const barX = cx - barWidth / 2;
        const barY = spriteTop + bobOffset - height * 0.05;
        ctx.fillStyle = '#333';
        ctx.fillRect(barX, barY, barWidth, barHeight);
        const healthRatio = enemy.health / enemy.maxHealth;
        ctx.fillStyle = healthRatio > 0.5 ? '#0f0' : healthRatio > 0.25 ? '#ff0' : '#f00';
        ctx.fillRect(barX, barY, barWidth * healthRatio, barHeight);
    }
    enemy.frame++;
}
function drawPickupSprite(ctx, pickup, screenX, spriteTop, width, height, dist, frameCount) {
    const bob = Math.sin((frameCount + pickup.bobOffset) * 0.05) * (height * 0.1);
    const glow = Math.sin((frameCount + pickup.bobOffset) * 0.08) * 0.3 + 0.7;
    const cx = screenX;
    const cy = spriteTop + height * 0.5 + bob;
    const config = types_1.PICKUP_CONFIG[pickup.type];
    let color;
    let symbol;
    switch (pickup.type) {
        case types_1.PickupType.HealthSmall:
        case types_1.PickupType.HealthLarge:
            color = '#ff3333';
            symbol = '+';
            break;
        case types_1.PickupType.AmmoPistol:
        case types_1.PickupType.AmmoShotgun:
        case types_1.PickupType.AmmoMachineGun:
            color = '#ffcc00';
            symbol = '★';
            break;
        case types_1.PickupType.WeaponShotgun:
            color = '#8888ff';
            symbol = 'S';
            break;
        case types_1.PickupType.WeaponMachineGun:
            color = '#88ff88';
            symbol = 'M';
            break;
        default:
            color = '#ffff00';
            symbol = '?';
    }
    // Glow effect
    ctx.fillStyle = color.replace(')', ', 0.3)').replace('rgb', 'rgba');
    ctx.beginPath();
    ctx.arc(cx, cy, width * 0.35 * glow, 0, Math.PI * 2);
    ctx.fill();
    // Box
    ctx.fillStyle = color;
    ctx.fillRect(cx - width * 0.25, cy - height * 0.2, width * 0.5, height * 0.4);
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 1;
    ctx.strokeRect(cx - width * 0.25, cy - height * 0.2, width * 0.5, height * 0.4);
    // Symbol
    ctx.fillStyle = '#000';
    ctx.font = `bold ${Math.max(8, width * 0.5)}px "Courier New"`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(symbol, cx, cy);
}
function drawWeapon(ctx, width, height, player, weaponFrame, muzzleFlash, flashFrame) {
    const weaponIndex = player.currentWeapon;
    if (weaponIndex >= player.weapons.length)
        return;
    const weapon = player.weapons[weaponIndex];
    const config = types_1.WEAPON_CONFIG[weapon.type];
    const weaponBobX = Math.sin(weaponFrame * 0.05) * 4;
    const weaponBobY = Math.abs(Math.cos(weaponFrame * 0.05)) * 3;
    const baseX = width / 2 + weaponBobX;
    const baseY = height - 50 + weaponBobY;
    ctx.save();
    ctx.translate(baseX, baseY);
    switch (weapon.type) {
        case types_1.WeaponType.Pistol:
            drawPistol(ctx, muzzleFlash, flashFrame);
            break;
        case types_1.WeaponType.Shotgun:
            drawShotgun(ctx, muzzleFlash, flashFrame);
            break;
        case types_1.WeaponType.MachineGun:
            drawMachineGun(ctx, muzzleFlash, flashFrame);
            break;
    }
    ctx.restore();
    // Ammo counter for current weapon
    ctx.fillStyle = '#fff';
    ctx.font = '18px "Courier New", monospace';
    ctx.textAlign = 'right';
    ctx.fillText(`${weapon.ammo}`, width - 20, height - 80);
}
function drawPistol(ctx, muzzleFlash, flashFrame) {
    // Handle
    ctx.fillStyle = '#3a3a3a';
    ctx.fillRect(-8, 30, 16, 40);
    // Barrel
    ctx.fillStyle = '#5a5a5a';
    ctx.fillRect(-6, -30, 12, 60);
    ctx.fillStyle = '#7a7a7a';
    ctx.fillRect(-4, -30, 8, 60);
    // Muzzle flash
    if (muzzleFlash) {
        const size = 15 + Math.sin(flashFrame * 1.5) * 5;
        ctx.fillStyle = '#ffff00';
        ctx.beginPath();
        ctx.arc(0, -35, size, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#ffffff';
        ctx.beginPath();
        ctx.arc(0, -35, size * 0.5, 0, Math.PI * 2);
        ctx.fill();
    }
}
function drawShotgun(ctx, muzzleFlash, flashFrame) {
    // Stock
    ctx.fillStyle = '#654321';
    ctx.beginPath();
    ctx.moveTo(-15, 50);
    ctx.lineTo(15, 50);
    ctx.lineTo(10, 70);
    ctx.lineTo(-10, 70);
    ctx.closePath();
    ctx.fill();
    // Body
    ctx.fillStyle = '#4a4a4a';
    ctx.fillRect(-20, 10, 40, 40);
    // Barrels
    ctx.fillStyle = '#5a5a5a';
    ctx.fillRect(-12, -40, 8, 50);
    ctx.fillRect(4, -40, 8, 50);
    ctx.fillStyle = '#7a7a7a';
    ctx.fillRect(-11, -40, 6, 50);
    ctx.fillRect(5, -40, 6, 50);
    // Muzzle flash (wider)
    if (muzzleFlash) {
        const size = 25 + Math.sin(flashFrame * 1.5) * 10;
        ctx.fillStyle = '#ff8800';
        ctx.beginPath();
        ctx.arc(-8, -45, size, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(8, -45, size * 0.8, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#ffffff';
        ctx.beginPath();
        ctx.arc(0, -48, size * 0.4, 0, Math.PI * 2);
        ctx.fill();
    }
}
function drawMachineGun(ctx, muzzleFlash, flashFrame) {
    // Body
    ctx.fillStyle = '#3a3a3a';
    ctx.fillRect(-25, 20, 50, 25);
    // Barrel assembly
    ctx.fillStyle = '#5a5a5a';
    ctx.fillRect(-18, -30, 36, 50);
    ctx.fillStyle = '#6a6a6a';
    ctx.fillRect(-15, -30, 30, 50);
    // Barrel grooves
    ctx.fillStyle = '#4a4a4a';
    ctx.fillRect(-16, -15, 32, 2);
    ctx.fillRect(-16, -5, 32, 2);
    // Magazine
    ctx.fillStyle = '#2a2a2a';
    ctx.fillRect(-5, 45, 10, 25);
    // Muzzle flash
    if (muzzleFlash) {
        const size = 12 + Math.sin(flashFrame * 2) * 6;
        ctx.fillStyle = '#ffaa00';
        ctx.beginPath();
        ctx.arc(0, -35, size, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#ffffff';
        ctx.beginPath();
        ctx.arc(0, -35, size * 0.5, 0, Math.PI * 2);
        ctx.fill();
        // Tracers effect
        for (let i = 0; i < 3; i++) {
            const angle = -0.1 + (i - 1) * 0.1;
            ctx.strokeStyle = '#ffcc00';
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.moveTo(0, -35);
            ctx.lineTo(Math.sin(angle) * 40, -35 + Math.cos(angle) * 40);
            ctx.stroke();
        }
    }
}
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWeaponState = createWeaponState;
exports.processInput = processInput;
exports.updateWeapons = updateWeapons;
exports.updateEnemies = updateEnemies;
exports.updatePickups = updatePickups;
exports.updateMessages = updateMessages;
exports.respawnGame = respawnGame;
const types_1 = require("./types");
const level_1 = require("./level");
function createWeaponState() {
    return { fireTimer: 0, muzzleFlash: false, flashTimer: 0 };
}
function processInput(input, state, dt) {
    const player = state.player;
    // Respawn
    if ((state.gameOver || state.won) && input.respawn) {
        respawnGame(state);
    }
    if (state.gameOver || state.won || !player.alive)
        return;
    const moveSpeed = 0.06;
    const turnSpeed = 0.04;
    // Movement
    let dx = 0;
    let dy = 0;
    if (input.forward) {
        dx += Math.cos(player.angle) * moveSpeed;
        dy += Math.sin(player.angle) * moveSpeed;
    }
    if (input.backward) {
        dx -= Math.cos(player.angle) * moveSpeed * 0.7;
        dy -= Math.sin(player.angle) * moveSpeed * 0.7;
    }
    // Collision detection with sliding
    if (!(0, level_1.isWall)(player.x + dx, player.y, state.map)) {
        player.x += dx;
    }
    if (!(0, level_1.isWall)(player.x, player.y + dy, state.map)) {
        player.y += dy;
    }
    // Turning
    if (input.turnLeft)
        player.angle -= turnSpeed;
    if (input.turnRight)
        player.angle += turnSpeed;
    // Keep player in bounds
    player.x = Math.max(0.5, Math.min(state.width - 0.5, player.x));
    player.y = Math.max(0.5, Math.min(state.height - 0.5, player.y));
    // Invulnerability timer
    if (player.invulnerable > 0) {
        player.invulnerable--;
    }
}
function updateWeapons(input, state, weaponStates) {
    const player = state.player;
    if (!player.alive)
        return;
    // Weapon switching
    if (input.weapon1 && player.weapons[0]) {
        player.currentWeapon = 0;
    }
    else if (input.weapon2 && player.weapons.length > 1 && player.weapons[1]) {
        player.currentWeapon = 1;
    }
    else if (input.weapon3 && player.weapons.length > 2 && player.weapons[2]) {
        player.currentWeapon = 2;
    }
    const weaponIndex = player.currentWeapon;
    if (weaponIndex >= player.weapons.length)
        return;
    const weapon = player.weapons[weaponIndex];
    const config = types_1.WEAPON_CONFIG[weapon.type];
    const ws = weaponStates[weaponIndex] || createWeaponState();
    // Update fire timer
    if (ws.fireTimer > 0)
        ws.fireTimer--;
    if (ws.flashTimer > 0)
        ws.flashTimer--;
    if (ws.flashTimer <= 0)
        ws.muzzleFlash = false;
    // Shooting
    const canShoot = ws.fireTimer <= 0 && weapon.ammo > 0;
    const shouldShoot = input.shooting;
    if (canShoot && shouldShoot) {
        fireWeapon(state, weapon, config, ws);
        ws.muzzleFlash = true;
        ws.flashTimer = 3;
        ws.fireTimer = config.fireRate;
        if (!config.autoFire) {
            // Semi-auto: need to release and press again
            // Handled by input state being frame-based
        }
    }
    // Auto-fire holds
    if (config.autoFire && shouldShoot && ws.fireTimer > 0 && weapon.ammo > 0) {
        // Continue firing next frame when timer expires
    }
    weaponStates[weaponIndex] = ws;
}
function fireWeapon(state, weapon, config, ws) {
    const player = state.player;
    weapon.ammo--;
    for (let p = 0; p < config.pellets; p++) {
        const spreadAngle = player.angle + (Math.random() - 0.5) * config.spread * 2;
        // Raycast for this pellet
        let hitEnemy = null;
        let hitDist = Infinity;
        // Check walls first
        const rayResult = castRayForHit(player.x, player.y, spreadAngle, state.map);
        // Check enemies
        for (let i = 0; i < state.enemies.length; i++) {
            const enemy = state.enemies[i];
            if (!enemy.alive)
                continue;
            const dist = (0, level_1.distance)(player.x, player.y, enemy.x, enemy.y);
            const angleToEnemy = Math.atan2(enemy.y - player.y, enemy.x - player.x);
            let angleDiff = angleToEnemy - spreadAngle;
            while (angleDiff > Math.PI)
                angleDiff -= Math.PI * 2;
            while (angleDiff < -Math.PI)
                angleDiff += Math.PI * 2;
            const hitRadius = 0.35; // enemy hitbox radius
            const angularWidth = Math.atan2(hitRadius, dist);
            if (Math.abs(angleDiff) < angularWidth && dist < rayResult.dist && (0, level_1.canSee)(player.x, player.y, enemy.x, enemy.y, state.map)) {
                if (dist < hitDist) {
                    hitDist = dist;
                    hitEnemy = i;
                }
            }
        }
        if (hitEnemy !== null) {
            const enemy = state.enemies[hitEnemy];
            enemy.health -= config.damage;
            if (enemy.health <= 0) {
                enemy.alive = false;
                player.kills++;
                showMessage(state, `Enemy eliminated! (${player.kills}/${countEnemies(state.enemies)})`);
                // Check win condition
                if (countAliveEnemies(state.enemies) === 0) {
                    state.won = true;
                    showMessage(state, 'VICTORY! All enemies eliminated!');
                }
            }
        }
    }
}
function castRayForHit(ox, oy, angle, map) {
    const sin = Math.sin(angle);
    const cos = Math.cos(angle);
    const step = 0.05;
    for (let i = 0; i < 200; i++) {
        const x = ox + cos * step * i;
        const y = oy + sin * step * i;
        const mx = Math.floor(x);
        const my = Math.floor(y);
        if (mx < 0 || mx >= map[0].length || my < 0 || my >= map.length) {
            return { dist: i * step };
        }
        if (map[my][mx] > 0) {
            return { dist: i * step };
        }
    }
    return { dist: 100 };
}
function updateEnemies(state) {
    const player = state.player;
    state.enemies.forEach(enemy => {
        if (!enemy.alive)
            return;
        const distToPlayer = (0, level_1.distance)(enemy.x, enemy.y, player.x, player.y);
        const canSeePlayer = (0, level_1.canSee)(enemy.x, enemy.y, player.x, player.y, state.map);
        // State machine
        if (distToPlayer < enemy.attackRange + 2 && canSeePlayer) {
            enemy.state = 'chasing';
        }
        else if (distToPlayer < 10 && canSeePlayer) {
            enemy.state = 'chasing';
        }
        else {
            enemy.state = 'idle';
        }
        if (enemy.state === 'chasing') {
            // Move toward player
            const angleToPlayer = Math.atan2(player.y - enemy.y, player.x - enemy.x);
            if (distToPlayer > enemy.attackRange) {
                const dx = Math.cos(angleToPlayer) * enemy.speed;
                const dy = Math.sin(angleToPlayer) * enemy.speed;
                // Simple collision with sliding
                if (!(0, level_1.isWall)(enemy.x + dx, enemy.y, state.map)) {
                    enemy.x += dx;
                }
                else if (!(0, level_1.isWall)(enemy.x, enemy.y + dy, state.map)) {
                    enemy.y += dy;
                }
            }
            // Attack player
            if (distToPlayer <= enemy.attackRange && canSeePlayer && player.alive && player.invulnerable <= 0) {
                const timeSinceLastAttack = state.frameCount - enemy.lastAttack;
                const attackRate = getEnemyAttackRate(enemy.type);
                if (timeSinceLastAttack >= attackRate) {
                    damagePlayer(state, enemy.damage);
                    enemy.lastAttack = state.frameCount;
                    enemy.state = 'attacking';
                }
            }
        }
        if (enemy.state === 'attacking') {
            if (state.frameCount - enemy.lastAttack > 15) {
                enemy.state = 'chasing';
            }
        }
        enemy.frame++;
    });
}
function getEnemyAttackRate(type) {
    switch (type) {
        case types_1.EnemyType.Grunt: return 45;
        case types_1.EnemyType.Soldier: return 35;
        case types_1.EnemyType.Brute: return 60;
        default: return 45;
    }
}
function damagePlayer(state, amount) {
    const player = state.player;
    if (!player.alive)
        return;
    if (player.invulnerable > 0)
        return;
    player.health -= amount;
    player.invulnerable = 30;
    if (player.health <= 0) {
        player.health = 0;
        player.alive = false;
        state.gameOver = true;
        showMessage(state, 'YOU DIED - Press R to Respawn');
    }
}
function updatePickups(state) {
    const player = state.player;
    if (!player.alive)
        return;
    state.pickups.forEach(pickup => {
        if (!pickup.alive)
            return;
        const dist = (0, level_1.distance)(player.x, player.y, pickup.x, pickup.y);
        if (dist < 0.6) {
            collectPickup(state, pickup);
        }
    });
}
function collectPickup(state, pickup) {
    const player = state.player;
    const config = types_1.PICKUP_CONFIG[pickup.type];
    switch (pickup.type) {
        case types_1.PickupType.HealthSmall:
        case types_1.PickupType.HealthLarge:
            player.health = Math.min(player.maxHealth, player.health + config.healthAmount);
            showMessage(state, `+${config.healthAmount} Health`);
            break;
        case types_1.PickupType.AmmoPistol:
            ensureWeapon(player, types_1.WeaponType.Pistol);
            player.weapons[0].ammo = Math.min(player.weapons[0].maxAmmo, player.weapons[0].ammo + config.ammoAmount);
            showMessage(state, `+${config.ammoAmount} Pistol Ammo`);
            break;
        case types_1.PickupType.AmmoShotgun:
            ensureWeapon(player, types_1.WeaponType.Shotgun);
            const sgIdx = player.weapons.findIndex(w => w.type === types_1.WeaponType.Shotgun);
            player.weapons[sgIdx].ammo = Math.min(player.weapons[sgIdx].maxAmmo, player.weapons[sgIdx].ammo + config.ammoAmount);
            showMessage(state, `+${config.ammoAmount} Shotgun Shells`);
            break;
        case types_1.PickupType.AmmoMachineGun:
            ensureWeapon(player, types_1.WeaponType.MachineGun);
            const mgIdx = player.weapons.findIndex(w => w.type === types_1.WeaponType.MachineGun);
            player.weapons[mgIdx].ammo = Math.min(player.weapons[mgIdx].maxAmmo, player.weapons[mgIdx].ammo + config.ammoAmount);
            showMessage(state, `+${config.ammoAmount} Machine Gun Ammo`);
            break;
        case types_1.PickupType.WeaponShotgun:
            if (!player.weapons.find(w => w.type === types_1.WeaponType.Shotgun)) {
                player.weapons.push({ type: types_1.WeaponType.Shotgun, ammo: config.ammoAmount, maxAmmo: 50 });
                player.currentWeapon = player.weapons.length - 1;
            }
            showMessage(state, 'Acquired Shotgun!');
            break;
        case types_1.PickupType.WeaponMachineGun:
            if (!player.weapons.find(w => w.type === types_1.WeaponType.MachineGun)) {
                player.weapons.push({ type: types_1.WeaponType.MachineGun, ammo: config.ammoAmount, maxAmmo: 150 });
                player.currentWeapon = player.weapons.length - 1;
            }
            showMessage(state, 'Acquired Machine Gun!');
            break;
    }
    pickup.alive = false;
}
function ensureWeapon(player, type) {
    if (!player.weapons.find(w => w.type === type)) {
        // Auto-add weapon if picking up ammo but don't have it
        player.weapons.push({ type, ammo: 0, maxAmmo: type === types_1.WeaponType.Pistol ? 999 : type === types_1.WeaponType.Shotgun ? 50 : 150 });
    }
}
function updateMessages(state) {
    if (state.messageTimer > 0) {
        state.messageTimer--;
        if (state.messageTimer <= 0) {
            state.message = '';
        }
    }
}
function showMessage(state, msg) {
    state.message = msg;
    state.messageTimer = 120; // 2 seconds at 60fps
}
function countEnemies(enemies) {
    return enemies.length;
}
function countAliveEnemies(enemies) {
    return enemies.filter(e => e.alive).length;
}
function respawnGame(state) {
    // Reset player
    state.player = {
        x: 12.5,
        y: 22.5,
        angle: Math.PI,
        health: 100,
        maxHealth: 100,
        weapons: [{ type: types_1.WeaponType.Pistol, ammo: 50, maxAmmo: 999 }],
        currentWeapon: 0,
        kills: state.player.kills,
        alive: true,
        invulnerable: 90, // 1.5 seconds invulnerable on respawn
    };
    // Revive enemies
    state.enemies.forEach(enemy => {
        enemy.alive = true;
        enemy.health = enemy.maxHealth;
    });
    // Restore pickups
    state.pickups.forEach(pickup => {
        pickup.alive = true;
    });
    state.gameOver = false;
    state.won = false;
    state.message = '';
    state.messageTimer = 0;
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-Fable-Fusion-711-Uncensored-Heretic-NM-DAU-NEO-MAX-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"c028431f-2675-4303-b6a8-d64056b53122","tokensIn":664055,"tokensOut":20091,"tokensTotal":684146,"cost":0,"turns":34,"toolCalls":33,"failedToolCalls":0,"timestamp":"2026-08-06T23:29:13.100Z"} -->