/**
 * Super Fight — Multiplayer ASCII fighting game engine.
 *
 * Headless game logic: state, physics, hit detection, sprite rendering,
 * frame composition, and network message encoding. Zero TUI dependencies.
 */
// ── Constants ──────────────────────────────────────────────────────────────
export const VIEWPORT_W = 80;
export const VIEWPORT_H = 40;
export const GROUND_Y = 34; // ground starts here (rows 34-39 are ground)
export const GRAVITY = 0.8; // pixels per tick²
export const JUMP_VY = -8; // initial upward velocity
export const MOVE_SPEED = 2; // pixels per tick
export const PUNCH_RANGE = 2;
export const KICK_RANGE = 3;
export const DAMAGE = 10;
export const MAX_HEALTH = 100;
export const RESPAWN_TIME = 3000; // ms
export const TICK_MS = 66; // ~15 FPS
export const SPRITE_W = 5;
export const SPRITE_H = 5;
export const GAME_MSG_PREFIX = '\x01GAME:';
/** 8 bright ANSI colors for up to 8 players (wraps if more) */
export const PLAYER_COLORS = [
    '\x1b[91m', // bright red
    '\x1b[92m', // bright green
    '\x1b[93m', // bright yellow
    '\x1b[94m', // bright blue
    '\x1b[95m', // bright magenta
    '\x1b[96m', // bright cyan
    '\x1b[97m', // bright white
    '\x1b[33m' // orange-ish (dark yellow)
];
export const RESET = '\x1b[0m';
export function encodeGameMsg(msg) {
    return GAME_MSG_PREFIX + JSON.stringify(msg);
}
export function decodeGameMsg(text) {
    if (!text.startsWith(GAME_MSG_PREFIX))
        return null;
    try {
        return JSON.parse(text.slice(GAME_MSG_PREFIX.length));
    }
    catch {
        return null;
    }
}
// ── Player Factory ─────────────────────────────────────────────────────────
export function createPlayer(displayName, colorIndex, x) {
    return {
        x: x ?? Math.floor(Math.random() * (VIEWPORT_W - SPRITE_W * 2)) + SPRITE_W,
        y: GROUND_Y - SPRITE_H,
        vy: 0,
        action: 'idle',
        facing: 'right',
        health: MAX_HEALTH,
        kills: 0,
        respawnTimer: 0,
        color: PLAYER_COLORS[colorIndex % PLAYER_COLORS.length],
        displayName,
        actionTimer: 0
    };
}
// ── Sprites ────────────────────────────────────────────────────────────────
/**
 * Returns a 5-line array of the ASCII fighter sprite for the given pose.
 * All sprites are exactly SPRITE_W (5) chars wide.
 * `facing` flips the sprite horizontally.
 */
export function renderSprite(action, facing) {
    let lines;
    switch (action) {
        case 'idle':
            lines = ['  O  ', ' /|\\ ', '  |  ', ' / \\ ', '/   \\'];
            break;
        case 'walk-left':
        case 'walk-right':
            lines = ['  O  ', ' /|\\ ', '  |  ', ' / \\ ', '|   |'];
            break;
        case 'jump':
            lines = [' \\O/ ', '  |  ', '  |  ', ' / \\ ', '/   \\'];
            break;
        case 'crouch':
            // Crouch is only 3 lines tall, padded with empty lines at top
            lines = ['     ', '     ', '  O  ', ' /|\\ ', ' /_\\ '];
            break;
        case 'punch':
            lines = ['  O  ', ' /|--', '  |  ', ' / \\ ', '/   \\'];
            break;
        case 'kick':
            lines = ['  O  ', ' /|\\ ', '  |  ', ' /  _', '/    '];
            break;
        case 'hit':
            lines = ['  O  ', '  |  ', ' /|\\ ', '  |  ', ' / \\ '];
            break;
        case 'dead':
            lines = ['     ', '     ', '     ', '     ', '_O/|_'];
            break;
        default:
            lines = ['  O  ', ' /|\\ ', '  |  ', ' / \\ ', '/   \\'];
    }
    // Flip horizontally if facing left
    if (facing === 'left') {
        lines = lines.map((line) => line
            .split('')
            .reverse()
            .map((ch) => {
            if (ch === '/')
                return '\\';
            if (ch === '\\')
                return '/';
            if (ch === '<')
                return '>';
            if (ch === '>')
                return '<';
            return ch;
        })
            .join(''));
    }
    return lines;
}
// ── Frame Renderer ─────────────────────────────────────────────────────────
/**
 * Renders a complete game frame as an array of VIEWPORT_H strings,
 * each exactly VIEWPORT_W chars. No ANSI colors — caller wraps with color.
 * Returns { lines, colors } where colors[row][col] is the ANSI color code.
 */
export function renderFrame(state, viewportW = VIEWPORT_W, viewportH = VIEWPORT_H) {
    // Initialize blank frame buffer
    const buf = Array.from({ length: viewportH }, () => Array(viewportW).fill(' '));
    // Color buffer — one ANSI string per cell
    const colorBuf = Array.from({ length: viewportH }, () => Array(viewportW).fill(''));
    // Draw ground (rows GROUND_Y to viewportH-1)
    for (let row = GROUND_Y; row < viewportH; row++) {
        for (let col = 0; col < viewportW; col++) {
            buf[row][col] = row === GROUND_Y ? '=' : '▓';
        }
    }
    // Draw walls
    for (let row = 0; row < viewportH; row++) {
        buf[row][0] = '│';
        buf[row][viewportW - 1] = '│';
    }
    // Sort players by x so overlapping sprites have consistent z-order
    const players = [...state.entries()].sort((a, b) => a[1].x - b[1].x);
    // Draw player sprites
    for (const [, player] of players) {
        const sprite = renderSprite(player.action, player.facing);
        const px = Math.round(player.x);
        const py = Math.round(player.y);
        for (let sy = 0; sy < sprite.length; sy++) {
            const row = py + sy;
            if (row < 1 || row >= GROUND_Y)
                continue; // skip out-of-bounds and ground
            for (let sx = 0; sx < sprite[sy].length; sx++) {
                const col = px + sx;
                if (col < 1 || col >= viewportW - 1)
                    continue; // skip walls
                const ch = sprite[sy][sx];
                if (ch !== ' ') {
                    buf[row][col] = ch;
                    colorBuf[row][col] = player.color;
                }
            }
        }
    }
    // Render status bar at top (row 0)
    let statusStr = '';
    for (const [, player] of players) {
        const healthBar = renderHealthBar(player.health);
        const segment = ` ${player.displayName.slice(0, 8)}:${healthBar} K:${player.kills} `;
        statusStr += segment;
    }
    statusStr = statusStr.slice(0, viewportW);
    for (let i = 0; i < statusStr.length && i < viewportW; i++) {
        buf[0][i] = statusStr[i];
    }
    // Compose final lines with ANSI colors
    const lines = [];
    for (let row = 0; row < viewportH; row++) {
        let line = '';
        let currentColor = '';
        for (let col = 0; col < viewportW; col++) {
            const cellColor = colorBuf[row][col];
            if (cellColor !== currentColor) {
                if (currentColor !== '')
                    line += RESET;
                if (cellColor !== '')
                    line += cellColor;
                currentColor = cellColor;
            }
            line += buf[row][col];
        }
        if (currentColor !== '')
            line += RESET;
        lines.push(line);
    }
    return lines;
}
function renderHealthBar(health) {
    const filled = Math.ceil((health / MAX_HEALTH) * 10);
    return '[' + '█'.repeat(filled) + '░'.repeat(10 - filled) + ']';
}
// ── Physics ────────────────────────────────────────────────────────────────
/**
 * Update a single player's physics for one tick.
 * `dt` is the number of ticks elapsed (usually 1).
 */
export function tickPlayer(player, dt = 1) {
    // Dead players: count down respawn
    if (player.action === 'dead') {
        player.respawnTimer -= TICK_MS * dt;
        if (player.respawnTimer <= 0) {
            player.respawnTimer = 0;
            player.health = MAX_HEALTH;
            player.action = 'idle';
            player.x = Math.floor(Math.random() * (VIEWPORT_W - SPRITE_W * 2)) + SPRITE_W;
            player.y = GROUND_Y - SPRITE_H;
            player.vy = 0;
        }
        return;
    }
    // Decay action timer (for punch/kick/hit animations)
    if (player.actionTimer > 0) {
        player.actionTimer -= dt;
        if (player.actionTimer <= 0) {
            player.actionTimer = 0;
            if (player.action === 'punch' || player.action === 'kick' || player.action === 'hit') {
                player.action = 'idle';
            }
        }
    }
    // Gravity
    const onGround = player.y >= GROUND_Y - SPRITE_H;
    if (!onGround) {
        player.vy += GRAVITY * dt;
        player.y += player.vy * dt;
        // Land on ground
        if (player.y >= GROUND_Y - SPRITE_H) {
            player.y = GROUND_Y - SPRITE_H;
            player.vy = 0;
            if (player.action === 'jump') {
                player.action = 'idle';
            }
        }
    }
    // Movement (only when walking)
    if (player.action === 'walk-left') {
        player.x -= MOVE_SPEED * dt;
        player.facing = 'left';
    }
    else if (player.action === 'walk-right') {
        player.x += MOVE_SPEED * dt;
        player.facing = 'right';
    }
    // Clamp to arena bounds (inside walls)
    player.x = Math.max(1, Math.min(VIEWPORT_W - SPRITE_W - 1, player.x));
}
// ── Hit Detection ──────────────────────────────────────────────────────────
/**
 * Check whether `attacker` is currently hitting `target`.
 * Returns true if the attacker is punching/kicking and the target is in range.
 */
export function checkHit(attacker, target) {
    if (attacker.action !== 'punch' && attacker.action !== 'kick')
        return false;
    if (target.action === 'dead')
        return false;
    if (attacker.actionTimer <= 0)
        return false; // animation expired
    const range = attacker.action === 'punch' ? PUNCH_RANGE : KICK_RANGE;
    // Center-x of each sprite
    const ax = attacker.x + SPRITE_W / 2;
    const tx = target.x + SPRITE_W / 2;
    const dx = tx - ax;
    // Check facing direction
    if (attacker.facing === 'right' && dx < 0)
        return false;
    if (attacker.facing === 'left' && dx > 0)
        return false;
    // Check distance (horizontal range + sprite width overlap)
    if (Math.abs(dx) > SPRITE_W + range)
        return false;
    // Check vertical overlap (must be roughly same height)
    const ay = attacker.y;
    const ty = target.y;
    if (Math.abs(ay - ty) > SPRITE_H)
        return false;
    return true;
}
/**
 * Apply damage to a target. Returns true if the hit killed them.
 */
export function applyDamage(target, amount) {
    if (target.action === 'dead')
        return false;
    target.health -= amount;
    if (target.health <= 0) {
        target.health = 0;
        target.action = 'dead';
        target.respawnTimer = RESPAWN_TIME;
        target.vy = 0;
        return true;
    }
    // Hit stagger animation
    target.action = 'hit';
    target.actionTimer = 3; // 3 ticks of hit stagger
    return false;
}
//# sourceMappingURL=super-fight.js.map