"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TetrisGame = void 0;
// Colors for each tetromino type
const CYAN = { mode: 'palette', value: 6 };
const YELLOW = { mode: 'palette', value: 3 };
const MAGENTA = { mode: 'palette', value: 5 };
const GREEN = { mode: 'palette', value: 2 };
const RED = { mode: 'palette', value: 1 };
const ORANGE = { mode: 'palette', value: 208 };
const BLUE = { mode: 'palette', value: 4 };
const DEFAULT = { mode: 'default', value: 0 };
const DARK_GRAY = { mode: 'palette', value: 8 };
const WHITE = { mode: 'palette', value: 7 };
const PIECE_COLORS = {
    I: CYAN,
    O: YELLOW,
    T: MAGENTA,
    S: GREEN,
    Z: RED,
    L: ORANGE,
    J: BLUE,
};
// Each tetromino defined as relative (x, y) offsets from a pivot
const PIECE_SHAPES = {
    I: [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 2, y: 0 }],
    O: [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }],
    T: [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: -1 }],
    S: [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }, { x: 1, y: -1 }],
    Z: [{ x: -1, y: -1 }, { x: 0, y: -1 }, { x: 0, y: 0 }, { x: 1, y: 0 }],
    L: [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: -1 }],
    J: [{ x: -1, y: -1 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }],
};
const ALL_PIECES = ['I', 'O', 'T', 'S', 'Z', 'L', 'J'];
const BOARD_WIDTH = 10;
const BOARD_HEIGHT = 20;
// Scoring table: index 0 unused, 1-4 lines
const LINE_SCORES = [0, 100, 300, 500, 800];
const LOCK_DELAY_MS = 200;
const MAX_LOCK_RESETS = 10;
class TetrisGame {
    name = 'Tetris';
    panelWidth = 0;
    panelHeight = 0;
    // The board stores placed (locked) cells only
    board = [];
    current = null;
    score = 0;
    level = 1;
    linesCleared = 0;
    _paused = false;
    _gameOver = false;
    dropAccumulator = 0;
    bag = [];
    nextType = null;
    locking = false;
    lockDelayAccumulator = 0;
    lockDelayResets = 0;
    init(width, height) {
        this.panelWidth = Math.max(14, width);
        this.panelHeight = Math.max(10, height);
        this.reset();
    }
    tick(deltaMs) {
        if (this._paused || this._gameOver || !this.current)
            return;
        if (this.locking) {
            if (this.canMoveDown()) {
                this.locking = false;
                this.lockDelayAccumulator = 0;
                this.lockDelayResets = 0;
            }
            else {
                this.lockDelayAccumulator += deltaMs;
                if (this.lockDelayAccumulator >= LOCK_DELAY_MS) {
                    this.lockPiece();
                }
                return;
            }
        }
        this.dropAccumulator += deltaMs;
        const interval = this.dropInterval();
        if (this.dropAccumulator >= interval) {
            this.dropAccumulator -= interval;
            this.moveDown();
        }
    }
    handleInput(key) {
        if (key === 'pause') {
            this._paused ? this.resume() : this.pause();
            return;
        }
        if (this._gameOver) {
            if (key === 'reset' || key === 'space')
                this.reset();
            return;
        }
        if (this._paused)
            return;
        if (!this.current)
            return;
        switch (key) {
            case 'left':
                if (this.tryMove(-1, 0))
                    this.resetLockDelay();
                break;
            case 'right':
                if (this.tryMove(1, 0))
                    this.resetLockDelay();
                break;
            case 'down':
                this.moveDown();
                this.dropAccumulator = 0;
                break;
            case 'up':
                if (this.tryRotate())
                    this.resetLockDelay();
                break;
            case 'space':
                this.hardDrop();
                break;
        }
    }
    getState() {
        const grid = this.buildGrid();
        let statusMessage;
        if (this._gameOver) {
            statusMessage = 'GAME OVER - Press SPACE or R';
        }
        else if (this._paused) {
            statusMessage = 'PAUSED - Ctrl+Space to resume';
        }
        else {
            statusMessage = `Lv ${this.level} | Lines ${this.linesCleared}`;
        }
        return {
            grid,
            score: this.score,
            status: this._gameOver ? 'gameover' : this._paused ? 'paused' : 'playing',
            statusMessage,
        };
    }
    resize(width, height) {
        this.panelWidth = Math.max(14, width);
        this.panelHeight = Math.max(10, height);
    }
    isPaused() { return this._paused; }
    pause() { this._paused = true; }
    resume() { if (!this._gameOver)
        this._paused = false; }
    isGameOver() { return this._gameOver; }
    reset() {
        this._gameOver = false;
        this._paused = false;
        this.score = 0;
        this.level = 1;
        this.linesCleared = 0;
        this.dropAccumulator = 0;
        this.locking = false;
        this.lockDelayAccumulator = 0;
        this.lockDelayResets = 0;
        this.bag = [];
        this.nextType = null;
        // Initialize empty board
        this.board = [];
        for (let r = 0; r < BOARD_HEIGHT; r++) {
            this.board[r] = new Array(BOARD_WIDTH).fill(null);
        }
        this.spawnPiece();
    }
    // ---- Private: piece management ----
    nextPieceType() {
        if (this.bag.length === 0) {
            // 7-bag randomizer: shuffle all 7 pieces
            this.bag = [...ALL_PIECES];
            for (let i = this.bag.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [this.bag[i], this.bag[j]] = [this.bag[j], this.bag[i]];
            }
        }
        return this.bag.pop();
    }
    spawnPiece() {
        const type = this.nextType ?? this.nextPieceType();
        this.nextType = this.nextPieceType();
        const blocks = PIECE_SHAPES[type].map(p => ({ ...p }));
        const pos = { x: Math.floor(BOARD_WIDTH / 2), y: 1 };
        this.current = { type, blocks, pos };
        this.dropAccumulator = 0;
        if (!this.isValidPosition(this.current.blocks, this.current.pos)) {
            this._gameOver = true;
            this.current = null;
        }
    }
    absoluteBlocks(blocks, pos) {
        return blocks.map(b => ({ x: b.x + pos.x, y: b.y + pos.y }));
    }
    isValidPosition(blocks, pos) {
        for (const b of blocks) {
            const ax = b.x + pos.x;
            const ay = b.y + pos.y;
            if (ax < 0 || ax >= BOARD_WIDTH || ay >= BOARD_HEIGHT)
                return false;
            // Allow blocks above the top (y < 0) during spawn
            if (ay >= 0 && this.board[ay][ax] !== null)
                return false;
        }
        return true;
    }
    tryMove(dx, dy) {
        if (!this.current)
            return false;
        const newPos = { x: this.current.pos.x + dx, y: this.current.pos.y + dy };
        if (this.isValidPosition(this.current.blocks, newPos)) {
            this.current.pos = newPos;
            return true;
        }
        return false;
    }
    moveDown() {
        if (!this.tryMove(0, 1)) {
            if (!this.locking) {
                this.locking = true;
                this.lockDelayAccumulator = 0;
                this.lockDelayResets = 0;
            }
        }
    }
    hardDrop() {
        if (!this.current)
            return;
        while (this.tryMove(0, 1)) { /* keep dropping */ }
        this.lockPiece();
    }
    tryRotate() {
        if (!this.current || this.current.type === 'O')
            return false;
        // Rotate clockwise: (x, y) -> (-y, x)
        const rotated = this.current.blocks.map(b => ({ x: -b.y, y: b.x }));
        // Try basic rotation
        if (this.isValidPosition(rotated, this.current.pos)) {
            this.current.blocks = rotated;
            return true;
        }
        // Basic wall kicks: try shifting left, right, up
        const kicks = [{ x: -1, y: 0 }, { x: 1, y: 0 }, { x: 0, y: -1 },
            { x: -2, y: 0 }, { x: 2, y: 0 }];
        for (const kick of kicks) {
            const kickedPos = { x: this.current.pos.x + kick.x, y: this.current.pos.y + kick.y };
            if (this.isValidPosition(rotated, kickedPos)) {
                this.current.blocks = rotated;
                this.current.pos = kickedPos;
                return true;
            }
        }
        return false;
    }
    lockPiece() {
        if (!this.current)
            return;
        this.locking = false;
        this.lockDelayAccumulator = 0;
        this.lockDelayResets = 0;
        const abs = this.absoluteBlocks(this.current.blocks, this.current.pos);
        const color = PIECE_COLORS[this.current.type];
        for (const p of abs) {
            if (p.y >= 0 && p.y < BOARD_HEIGHT && p.x >= 0 && p.x < BOARD_WIDTH) {
                this.board[p.y][p.x] = { color };
            }
        }
        this.current = null;
        this.clearLines();
        this.spawnPiece();
    }
    clearLines() {
        let cleared = 0;
        for (let r = BOARD_HEIGHT - 1; r >= 0; r--) {
            if (this.board[r].every(cell => cell !== null)) {
                this.board.splice(r, 1);
                this.board.unshift(new Array(BOARD_WIDTH).fill(null));
                cleared++;
                r++; // re-check this row since rows shifted down
            }
        }
        if (cleared > 0) {
            const idx = Math.min(cleared, 4);
            this.score += LINE_SCORES[idx] * this.level;
            this.linesCleared += cleared;
            this.level = Math.floor(this.linesCleared / 10) + 1;
        }
    }
    canMoveDown() {
        if (!this.current)
            return false;
        const newPos = { x: this.current.pos.x, y: this.current.pos.y + 1 };
        return this.isValidPosition(this.current.blocks, newPos);
    }
    resetLockDelay() {
        if (this.locking && this.lockDelayResets < MAX_LOCK_RESETS) {
            this.lockDelayAccumulator = 0;
            this.lockDelayResets++;
        }
    }
    dropInterval() {
        // Start at 500ms, decrease by 25ms per level, floor at 100ms
        return Math.max(100, 500 - (this.level - 1) * 25);
    }
    // ---- Private: rendering ----
    buildGrid() {
        const emptyCell = { char: ' ', fg: DEFAULT, bg: DEFAULT };
        const grid = [];
        for (let r = 0; r < this.panelHeight; r++) {
            grid[r] = [];
            for (let c = 0; c < this.panelWidth; c++) {
                grid[r][c] = { ...emptyCell };
            }
        }
        // Calculate offsets to center the playfield + border in the panel
        // Playfield is BOARD_WIDTH wide, plus 2 for left/right border chars
        const fieldDisplayWidth = BOARD_WIDTH + 2;
        const fieldDisplayHeight = BOARD_HEIGHT + 2; // top/bottom borders
        const offsetX = Math.max(0, Math.floor((this.panelWidth - fieldDisplayWidth) / 2));
        const offsetY = Math.max(0, Math.floor((this.panelHeight - fieldDisplayHeight) / 2));
        // Draw border
        this.drawBorder(grid, offsetX, offsetY, fieldDisplayWidth, fieldDisplayHeight);
        // Draw board contents (placed blocks)
        for (let r = 0; r < BOARD_HEIGHT; r++) {
            for (let c = 0; c < BOARD_WIDTH; c++) {
                const gr = offsetY + 1 + r;
                const gc = offsetX + 1 + c;
                if (gr < 0 || gr >= this.panelHeight || gc < 0 || gc >= this.panelWidth)
                    continue;
                const cell = this.board[r]?.[c];
                if (cell) {
                    grid[gr][gc] = { char: '█', fg: cell.color, bg: DEFAULT };
                }
                else {
                    // Subtle dot pattern for empty playfield
                    grid[gr][gc] = { char: '·', fg: DARK_GRAY, bg: DEFAULT };
                }
            }
        }
        // Draw current falling piece
        if (this.current) {
            const abs = this.absoluteBlocks(this.current.blocks, this.current.pos);
            const color = PIECE_COLORS[this.current.type];
            for (const p of abs) {
                if (p.y < 0 || p.y >= BOARD_HEIGHT)
                    continue;
                const gr = offsetY + 1 + p.y;
                const gc = offsetX + 1 + p.x;
                if (gr >= 0 && gr < this.panelHeight && gc >= 0 && gc < this.panelWidth) {
                    grid[gr][gc] = { char: '█', fg: color, bg: DEFAULT };
                }
            }
            // Draw ghost piece (drop preview)
            const ghostPos = this.findGhostPos();
            if (ghostPos) {
                const ghostAbs = this.absoluteBlocks(this.current.blocks, ghostPos);
                for (const p of ghostAbs) {
                    if (p.y < 0 || p.y >= BOARD_HEIGHT)
                        continue;
                    const gr = offsetY + 1 + p.y;
                    const gc = offsetX + 1 + p.x;
                    if (gr >= 0 && gr < this.panelHeight && gc >= 0 && gc < this.panelWidth) {
                        const isActual = abs.some(a => a.x === p.x && a.y === p.y);
                        if (!isActual) {
                            grid[gr][gc] = { char: '░', fg: DARK_GRAY, bg: DEFAULT };
                        }
                    }
                }
            }
        }
        // Draw next-piece preview to the right of the board
        if (this.nextType) {
            const previewX = offsetX + fieldDisplayWidth + 1;
            const previewY = offsetY + 1;
            const label = 'Next:';
            for (let i = 0; i < label.length && previewX + i < this.panelWidth; i++) {
                if (previewY >= 0 && previewY < this.panelHeight) {
                    grid[previewY][previewX + i] = { char: label[i], fg: WHITE, bg: DEFAULT };
                }
            }
            const nextBlocks = PIECE_SHAPES[this.nextType];
            const nextColor = PIECE_COLORS[this.nextType];
            for (const b of nextBlocks) {
                const gr = previewY + 2 + b.y;
                const gc = previewX + 1 + b.x;
                if (gr >= 0 && gr < this.panelHeight && gc >= 0 && gc < this.panelWidth) {
                    grid[gr][gc] = { char: '█', fg: nextColor, bg: DEFAULT };
                }
            }
        }
        return grid;
    }
    drawBorder(grid, ox, oy, w, h) {
        const borderFg = WHITE;
        const set = (r, c, ch) => {
            if (r >= 0 && r < this.panelHeight && c >= 0 && c < this.panelWidth) {
                grid[r][c] = { char: ch, fg: borderFg, bg: DEFAULT };
            }
        };
        // Corners
        set(oy, ox, '┌');
        set(oy, ox + w - 1, '┐');
        set(oy + h - 1, ox, '└');
        set(oy + h - 1, ox + w - 1, '┘');
        // Top and bottom
        for (let c = 1; c < w - 1; c++) {
            set(oy, ox + c, '─');
            set(oy + h - 1, ox + c, '─');
        }
        // Left and right
        for (let r = 1; r < h - 1; r++) {
            set(oy + r, ox, '│');
            set(oy + r, ox + w - 1, '│');
        }
    }
    findGhostPos() {
        if (!this.current)
            return null;
        const ghost = { ...this.current.pos };
        while (this.isValidPosition(this.current.blocks, { x: ghost.x, y: ghost.y + 1 })) {
            ghost.y++;
        }
        // Only show ghost if it's below the current position
        if (ghost.y === this.current.pos.y)
            return null;
        return ghost;
    }
}
exports.TetrisGame = TetrisGame;
//# sourceMappingURL=tetris.js.map