Barsoom (Martian) Go

<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
    <title>Barsoom Go (Running Score)</title>
    <script src=”https://cdn.tailwindcss.com”></script>
    <style>
        @import url(‘https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Lato:wght@400;700&display=swap’);

        body {
            font-family: ‘Lato’, sans-serif;
            background-color: #2a1a15;
            color: #eecda3;
            user-select: none;
        }

        h1, h2, h3, .fantasy-font { font-family: ‘Cinzel’, serif; }

        /* Board Styling */
        .board-grid {
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            gap: 2px;
            background-color: #4a3b32;
            padding: 4px;
            border-radius: 4px;
            box-shadow: 0 10px 20px rgba(0,0,0,0.5);
            max-width: 100%;
            aspect-ratio: 2/1;
        }

        .cell {
            background-color: #8c6245;
            position: relative;
            cursor: pointer;
            aspect-ratio: 1/1;
            display: flex;
            justify-content: center;
            align-items: center;
            transition: background-color 0.2s;
        }

        .cell:hover { background-color: #a37353; }
       
        .board-disabled .cell { cursor: default; }
        .board-disabled .cell:hover { background-color: #8c6245; }

        .cell.null-square {
            background-color: #1a1a1a;
            background-image: repeating-linear-gradient(45deg, #111 0, #111 10px, #222 10px, #222 20px);
            cursor: not-allowed;
        }

        .cell.root-square {
            background-color: #4a6fa5;
            border: 2px solid #8ab4f8;
        }

        .last-move {
            box-shadow: inset 0 0 15px 5px rgba(0, 255, 255, 0.4);
            border: 2px solid cyan;
            z-index: 5;
        }

        /* Pyramids */
        .pyramid {
            width: 0;
            height: 0;
            border-left: solid transparent;
            border-right: solid transparent;
            border-bottom: solid;
            position: absolute;
            transform-origin: 50% 50%;
            filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5));
            pointer-events: none;
        }

        .p1-piece { border-bottom-color: #f0f0f0; }
        .p2-piece { border-bottom-color: #1a1a1a; }

        .size-1 { border-left-width: 6px; border-right-width: 6px; border-bottom-width: 16px; }
        .size-2 { border-left-width: 10px; border-right-width: 10px; border-bottom-width: 24px; }
        .size-3 { border-left-width: 14px; border-right-width: 14px; border-bottom-width: 32px; }

        /* Directional Overlays */
        .direction-overlay {
            position: absolute;
            inset: 0;
            background: rgba(0,0,0,0.3);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10;
        }
        .dir-btn {
            position: absolute;
            width: 20px;
            height: 20px;
            background: #ffd700;
            border-radius: 50%;
            cursor: pointer;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 10px;
            color: black;
            font-weight: bold;
            box-shadow: 0 0 5px black;
        }
        .dir-btn:hover { transform: scale(1.2); }

        /* UI Elements */
        .stash-btn { transition: all 0.2s; }
        .stash-btn.active {
            transform: scale(1.1);
            box-shadow: 0 0 10px #ffd700;
            border-color: #ffd700;
        }
        .stash-btn:disabled {
            opacity: 0.3;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }
       
        .turn-indicator { transition: all 0.3s; }
        .active-turn {
            background-color: rgba(255, 255, 255, 0.1);
            border: 2px solid #ffd700;
        }

        /* Score Animations */
        @keyframes flashGreen {
            0% { color: #ffd700; transform: scale(1); }
            50% { color: #4ade80; transform: scale(1.5); }
            100% { color: #ffd700; transform: scale(1); }
        }
        @keyframes flashRed {
            0% { color: #ffd700; transform: scale(1); }
            50% { color: #f87171; transform: scale(1.5); }
            100% { color: #ffd700; transform: scale(1); }
        }
        .score-gain { animation: flashGreen 0.5s ease-out; }
        .score-loss { animation: flashRed 0.5s ease-out; }

        /* Modal */
        .modal-bg {
            position: fixed; inset: 0; background: rgba(0,0,0,0.9);
            display: flex; justify-content: center; align-items: center; z-index: 50;
        }
        .hidden { display: none !important; }

    </style>
</head>
<body class=”min-h-screen flex flex-col items-center p-4″>

    <header class=”text-center mb-6″>
        <h1 class=”text-4xl text-orange-400 font-bold mb-2 tracking-widest”>BARSOOM GO</h1>
        <p class=”text-sm opacity-70″>Branches, Twigs, and Thorns</p>
    </header>

    <main class=”w-full max-w-4xl flex flex-col md:flex-row gap-6″>

        <!– Player 1 Panel –>
        <div id=”p1-panel” class=”turn-indicator flex-1 bg-neutral-800 p-4 rounded-lg border border-neutral-700 flex flex-col items-center”>
            <h2 class=”text-2xl mb-2 text-white flex items-center gap-2″>
                White <span id=”p1-type-badge” class=”text-xs bg-neutral-600 px-2 py-1 rounded”>HUMAN</span>
            </h2>
            <div class=”text-5xl font-bold mb-4 text-yellow-500 transition-all” id=”p1-score”>5</div>
            <div class=”flex flex-col gap-2 w-full”>
                <button onclick=”selectPiece(0, 1)” id=”p1-s1″ class=”stash-btn w-full py-2 bg-neutral-700 rounded border border-neutral-600 flex justify-between px-4″>
                    <span>Small (1pt)</span> <span id=”p1-c1″ class=”font-bold”>5</span>
                </button>
                <button onclick=”selectPiece(0, 2)” id=”p1-s2″ class=”stash-btn w-full py-2 bg-neutral-700 rounded border border-neutral-600 flex justify-between px-4″>
                    <span>Medium (2pt)</span> <span id=”p1-c2″ class=”font-bold”>5</span>
                </button>
                <button onclick=”selectPiece(0, 3)” id=”p1-s3″ class=”stash-btn w-full py-2 bg-neutral-700 rounded border border-neutral-600 flex justify-between px-4″>
                    <span>Large (3pt)</span> <span id=”p1-c3″ class=”font-bold”>5</span>
                </button>
            </div>
        </div>

        <!– Center Game Area –>
        <div class=”flex-[2] flex flex-col gap-4″>
            <div class=”flex flex-col gap-1″>
                <div id=”game-status” class=”text-center bg-orange-900/50 py-2 rounded border border-orange-800 text-orange-200 font-bold min-h-[42px] flex items-center justify-center text-lg”>
                    Waiting for setup…
                </div>
                <div id=”last-move-display” class=”text-center bg-black/40 py-1 rounded border border-neutral-800 text-cyan-400 text-xs font-mono min-h-[26px]”>
                </div>
            </div>

            <div id=”board” class=”board-grid relative”></div>

            <div class=”bg-black/30 p-2 rounded h-32 overflow-y-auto text-xs font-mono border border-neutral-800″ id=”game-log”>
                <div class=”opacity-50 italic”>Setup pending…</div>
            </div>

            <button onclick=”showSetup()” class=”self-center text-xs underline opacity-50 hover:opacity-100 mt-2″>New Game</button>
        </div>

        <!– Player 2 Panel –>
        <div id=”p2-panel” class=”turn-indicator flex-1 bg-neutral-800 p-4 rounded-lg border border-neutral-700 flex flex-col items-center”>
            <h2 class=”text-2xl mb-2 text-black bg-white px-2 rounded flex items-center gap-2″>
                Black <span id=”p2-type-badge” class=”text-xs bg-neutral-400 text-black px-2 py-1 rounded”>CPU</span>
            </h2>
            <div class=”text-5xl font-bold mb-4 text-yellow-500 transition-all” id=”p2-score”>5</div>
            <div class=”flex flex-col gap-2 w-full”>
                <button onclick=”selectPiece(1, 1)” id=”p2-s1″ class=”stash-btn w-full py-2 bg-neutral-700 rounded border border-neutral-600 flex justify-between px-4″>
                    <span>Small (1pt)</span> <span id=”p2-c1″ class=”font-bold”>5</span>
                </button>
                <button onclick=”selectPiece(1, 2)” id=”p2-s2″ class=”stash-btn w-full py-2 bg-neutral-700 rounded border border-neutral-600 flex justify-between px-4″>
                    <span>Medium (2pt)</span> <span id=”p2-c2″ class=”font-bold”>5</span>
                </button>
                <button onclick=”selectPiece(1, 3)” id=”p2-s3″ class=”stash-btn w-full py-2 bg-neutral-700 rounded border border-neutral-600 flex justify-between px-4″>
                    <span>Large (3pt)</span> <span id=”p2-c3″ class=”font-bold”>5</span>
                </button>
            </div>
        </div>
    </main>

    <!– SETUP MODAL –>
    <div id=”setup-modal” class=”modal-bg”>
        <div class=”bg-neutral-800 p-8 rounded-lg border border-orange-500 max-w-md w-full text-center shadow-[0_0_50px_rgba(255,165,0,0.2)]”>
            <h2 class=”text-3xl text-orange-400 font-bold mb-6″>New Game</h2>
            <div class=”space-y-6 mb-8″>
                <div class=”flex items-center justify-between bg-neutral-700 p-3 rounded”>
                    <span class=”font-bold text-white”>White (First)</span>
                    <div class=”flex gap-2″>
                        <button onclick=”setSetupType(0, ‘HUMAN’)” id=”btn-p1-human” class=”px-3 py-1 rounded bg-orange-600 text-white font-bold text-sm”>Human</button>
                        <button onclick=”setSetupType(0, ‘AI’)” id=”btn-p1-ai” class=”px-3 py-1 rounded bg-neutral-600 text-neutral-300 text-sm”>AI</button>
                    </div>
                </div>
                <div class=”flex items-center justify-between bg-neutral-700 p-3 rounded”>
                    <span class=”font-bold text-black bg-white px-2 rounded”>Black</span>
                    <div class=”flex gap-2″>
                        <button onclick=”setSetupType(1, ‘HUMAN’)” id=”btn-p2-human” class=”px-3 py-1 rounded bg-neutral-600 text-neutral-300 text-sm”>Human</button>
                        <button onclick=”setSetupType(1, ‘AI’)” id=”btn-p2-ai” class=”px-3 py-1 rounded bg-orange-600 text-white font-bold text-sm”>AI</button>
                    </div>
                </div>
            </div>
            <button onclick=”startGame()” class=”w-full py-3 bg-orange-500 hover:bg-orange-400 text-black font-bold text-lg rounded shadow-lg transform hover:scale-105 transition”>START</button>
        </div>
    </div>

    <!– VICTORY MODAL –>
    <div id=”victory-modal” class=”modal-bg hidden”>
        <div class=”bg-neutral-800 p-8 rounded-lg border border-yellow-500 max-w-md w-full text-center shadow-[0_0_50px_rgba(255,215,0,0.4)]”>
            <h2 class=”text-4xl text-yellow-400 font-bold mb-2″>GAME OVER</h2>
            <div id=”victory-message” class=”text-2xl text-white font-bold mb-6″></div>
            <div class=”grid grid-cols-2 gap-4 mb-8″>
                <div class=”bg-neutral-900 p-4 rounded border border-neutral-700″>
                    <div class=”text-sm text-gray-400 uppercase”>White</div>
                    <div id=”final-score-white” class=”text-3xl font-bold text-white”></div>
                </div>
                <div class=”bg-white p-4 rounded border border-neutral-700″>
                    <div class=”text-sm text-gray-600 uppercase”>Black</div>
                    <div id=”final-score-black” class=”text-3xl font-bold text-black”></div>
                </div>
            </div>
            <button onclick=”showSetup()” class=”w-full py-3 bg-yellow-600 hover:bg-yellow-500 text-white font-bold text-lg rounded shadow-lg”>PLAY AGAIN</button>
        </div>
    </div>

    <script>
        const ROWS = 4;
        const COLS = 8;
        const TOTAL_SQUARES = ROWS * COLS;
        const TYPE_NULL = 0;
        const TYPE_ROOT = 1;
        const TYPE_PYRAMID = 2;
        const PLAYER_1 = 0;
        const PLAYER_2 = 1;

        let board = [];
        let phase = ‘SETUP_NULL’;
        let turnPlayer = PLAYER_1;
        let stashes = [];
        let scores = [5, 5];
        let selectedSize = null;
        let lastMoveIndex = -1;
        let playerConfig = { 0: ‘HUMAN’, 1: ‘AI’ };
        let aiThinking = false;

        const elBoard = document.getElementById(‘board’);
        const elStatus = document.getElementById(‘game-status’);
        const elLastMove = document.getElementById(‘last-move-display’);
        const elLog = document.getElementById(‘game-log’);

        function showSetup() {
            document.getElementById(‘victory-modal’).classList.add(‘hidden’);
            document.getElementById(‘setup-modal’).classList.remove(‘hidden’);
        }

        function setSetupType(pid, type) {
            playerConfig[pid] = type;
            const btnHuman = document.getElementById(pid === 0 ? ‘btn-p1-human’ : ‘btn-p2-human’);
            const btnAi = document.getElementById(pid === 0 ? ‘btn-p1-ai’ : ‘btn-p2-ai’);
           
            if (type === ‘HUMAN’) {
                btnHuman.className = “px-3 py-1 rounded bg-orange-600 text-white font-bold text-sm”;
                btnAi.className = “px-3 py-1 rounded bg-neutral-600 text-neutral-300 text-sm”;
            } else {
                btnHuman.className = “px-3 py-1 rounded bg-neutral-600 text-neutral-300 text-sm”;
                btnAi.className = “px-3 py-1 rounded bg-orange-600 text-white font-bold text-sm”;
            }
        }

        function startGame() {
            document.getElementById(‘setup-modal’).classList.add(‘hidden’);
            document.getElementById(‘p1-type-badge’).innerText = playerConfig[0];
            document.getElementById(‘p2-type-badge’).innerText = playerConfig[1];
           
            board = Array(TOTAL_SQUARES).fill(null);
            stashes = [{ 1: 5, 2: 5, 3: 5 }, { 1: 5, 2: 5, 3: 5 }];
            scores = [5, 5];
            phase = ‘SETUP_NULL’;
            turnPlayer = PLAYER_1;
            selectedSize = null;
            lastMoveIndex = -1;
            aiThinking = false;
           
            elLog.innerHTML = ‘<div class=”opacity-50 italic”>Game Started.</div>’;
            elLastMove.innerText = “”;
           
            renderBoard();
            updateUI();
            checkAI();
        }

        function getCoord(index) { return { r: Math.floor(index / COLS), c: index % COLS }; }
        function getIndex(r, c) {
            if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return -1;
            return r * COLS + c;
        }

        function getNeighbors(index) {
            const { r, c } = getCoord(index);
            const dirs = [
                { r: -1, c: 0, label: ‘N’, rot: 0 },
                { r: 0, c: 1, label: ‘E’, rot: 90 },
                { r: 1, c: 0, label: ‘S’, rot: 180 },
                { r: 0, c: -1, label: ‘W’, rot: 270 }
            ];
            return dirs.map(d => ({ index: getIndex(r + d.r, c + d.c), label: d.label, rot: d.rot })).filter(n => n.index !== -1);
        }

        function isConnected(withoutIndex) {
            let start = -1, emptyCount = 0;
            for(let i=0; i<TOTAL_SQUARES; i++) {
                if (i !== withoutIndex) {
                    if (start === -1) start = i;
                    emptyCount++;
                }
            }
            if (start === -1) return true;

            let visited = new Set(), queue = [start], reached = 0;
            visited.add(start);

            while(queue.length > 0) {
                let curr = queue.shift();
                reached++;
                getNeighbors(curr).forEach(n => {
                    if (n.index !== withoutIndex && !visited.has(n.index)) {
                        visited.add(n.index);
                        queue.push(n.index);
                    }
                });
            }
            return reached === emptyCount;
        }

        function handleCellClick(index) {
            if (aiThinking || playerConfig[turnPlayer] === ‘AI’) return;

            if (phase === ‘SETUP_NULL’) {
                if (!isConnected(index)) {
                    log(“Invalid Null.”, “red”);
                    return;
                }
                placeSetupPiece(index, TYPE_NULL);
                return;
            }

            if (phase === ‘SETUP_ROOT’) {
                if (board[index]) return;
                placeSetupPiece(index, TYPE_ROOT);
                return;
            }

            if (phase === ‘PLAY’) {
                if (board[index]) return;
                if (!selectedSize) {
                    log(“Select piece size.”, “orange”);
                    return;
                }
               
                const validNeighbors = getValidNeighborsForPlay(index);
                if (validNeighbors.length === 0) {
                    log(“Invalid placement.”, “red”);
                    return;
                }

                if (validNeighbors.length === 1) {
                    placePiece(index, selectedSize, validNeighbors[0].rot, validNeighbors[0].index);
                } else {
                    showDirectionSelector(index, validNeighbors);
                }
            }
        }

        function placeSetupPiece(index, type) {
            board[index] = { type: type };
            lastMoveIndex = index;
            const pName = turnPlayer === 0 ? “White” : “Black”;
            const tName = type === TYPE_NULL ? “Null” : “Root”;
            updateLastMoveText(`${pName} placed ${tName}`);
            renderBoard();
            nextPhase();
        }

        function nextPhase() {
            if (phase === ‘SETUP_NULL’) {
                phase = ‘SETUP_ROOT’;
                turnPlayer = PLAYER_2;
                log(“Null placed.”);
            } else if (phase === ‘SETUP_ROOT’) {
                phase = ‘PLAY’;
                turnPlayer = PLAYER_1;
                log(“Root placed.”);
            } else if (phase === ‘PLAY’) {
                selectedSize = null;
                const empty = board.filter(x => x === null).length;
                if (empty === 0) {
                    phase = ‘GAME_OVER’;
                    showVictoryScreen();
                    return;
                }
                turnPlayer = 1 – turnPlayer;
            }
            updateUI();
            checkAI();
        }

        function showVictoryScreen() {
            const winner = scores[0] > scores[1] ? “White Wins!” : (scores[1] > scores[0] ? “Black Wins!” : “It’s a Tie!”);
            document.getElementById(‘victory-message’).innerText = winner;
            document.getElementById(‘final-score-white’).innerText = scores[0];
            document.getElementById(‘final-score-black’).innerText = scores[1];
            document.getElementById(‘victory-modal’).classList.remove(‘hidden’);
            log(`GAME OVER! ${winner}`, “gold”);
        }

        function getValidNeighborsForPlay(index) {
            return getNeighbors(index).filter(n => {
                const c = board[n.index];
                return c && (c.type === TYPE_ROOT || c.type === TYPE_PYRAMID);
            });
        }

        function placePiece(index, size, rotation, targetIndex) {
            const target = board[targetIndex];
            stashes[turnPlayer][size]–;
           
            board[index] = {
                type: TYPE_PYRAMID,
                owner: turnPlayer,
                size: size,
                rotation: rotation
            };
           
            lastMoveIndex = index;

            // Scoring Logic
            let shortMsg = “”;
            let prevScores = […scores];
           
            if (target.type === TYPE_ROOT || (target.type === TYPE_PYRAMID && target.owner === turnPlayer)) {
                // Safe
                shortMsg = “Safe”;
            } else {
                // Attack
                const pen = target.size;
                const bonus = size;
                scores[turnPlayer] -= pen;
                scores[1 – turnPlayer] += bonus;
                shortMsg = `Attack (-${pen})`;
            }

            // Normalization Rule (Bankruptcy)
            let refillMsg = “”;
            if (scores[0] < 0 || scores[1] < 0) {
                scores[0] += 5;
                scores[1] += 5;
                refillMsg = ” (Pot Refill: +5 Both)”;
            }
           
            const pName = turnPlayer === 0 ? “White” : “Black”;
            const sizeName = [“”, “Small”, “Medium”, “Large”][size];
           
            // Highlight score changes
            animateScoreChange(0, scores[0] – prevScores[0]);
            animateScoreChange(1, scores[1] – prevScores[1]);

            log(`${pName}: ${sizeName} (${shortMsg})${refillMsg}`);
            updateLastMoveText(`${pName} placed ${sizeName} (${shortMsg})`);
           
            renderBoard();
            nextPhase();
        }

        function animateScoreChange(pid, change) {
            const el = document.getElementById(pid === 0 ? ‘p1-score’ : ‘p2-score’);
            el.className = ‘text-5xl font-bold mb-4 transition-all’; // reset
            void el.offsetWidth; // trigger reflow
            if (change > 0) el.classList.add(‘score-gain’, ‘text-green-400’);
            else if (change < 0) el.classList.add(‘score-loss’, ‘text-red-400’);
            else el.classList.add(‘text-yellow-500’);
        }
       
        function updateLastMoveText(text) {
            elLastMove.innerText = `Last Move: ${text}`;
        }

        function checkAI() {
            if (phase === ‘GAME_OVER’) return;
            if (playerConfig[turnPlayer] === ‘AI’) {
                aiThinking = true;
                elStatus.innerHTML = `${turnPlayer === 0 ? “White” : “Black”} (AI) is thinking…`;
                elBoard.classList.add(‘board-disabled’);
                setTimeout(performAIMove, 800);
            } else {
                aiThinking = false;
                elBoard.classList.remove(‘board-disabled’);
            }
        }

        function performAIMove() {
            if (phase === ‘SETUP_NULL’) {
                let valid = [];
                for(let i=0; i<TOTAL_SQUARES; i++) if(isConnected(i)) valid.push(i);
                placeSetupPiece(valid[Math.floor(Math.random() * valid.length)], TYPE_NULL);
                return;
            }
            if (phase === ‘SETUP_ROOT’) {
                let valid = [];
                for(let i=0; i<TOTAL_SQUARES; i++) if(!board[i]) valid.push(i);
                placeSetupPiece(valid[Math.floor(Math.random() * valid.length)], TYPE_ROOT);
                return;
            }
            if (phase === ‘PLAY’) {
                const move = calculateBestMove();
                if (move) placePiece(move.index, move.size, move.rotation, move.targetIndex);
            }
        }

        function calculateBestMove() {
            let possibleMoves = [];
            const myStash = stashes[turnPlayer];
            const availableSizes = [1, 2, 3].filter(s => myStash[s] > 0);
            if (availableSizes.length === 0) return null;

            for(let i=0; i<TOTAL_SQUARES; i++) {
                if (board[i]) continue;
                const validNeighbors = getValidNeighborsForPlay(i);
                validNeighbors.forEach(vn => {
                    availableSizes.forEach(size => {
                        possibleMoves.push({
                            index: i, size: size, rotation: vn.rot, targetIndex: vn.index,
                            targetType: board[vn.index].type, targetOwner: board[vn.index].owner,
                            targetSize: board[vn.index].size || 0
                        });
                    });
                });
            }
           
            let bestScore = -Infinity;
            let bestMove = null;
            possibleMoves.forEach(m => {
                let score = 0;
                const isAttack = (m.targetType === TYPE_PYRAMID && m.targetOwner !== turnPlayer);
                if (!isAttack) { score += 50 + m.size * 2; }
                else { score -= (m.size + m.targetSize) * 10; }
               
                const neighbors = getNeighbors(m.index);
                neighbors.forEach(n => { if (!board[n.index]) score += 5; });
                score += Math.random() * 5;

                if (score > bestScore) { bestScore = score; bestMove = m; }
            });
            return bestMove;
        }

        function selectPiece(player, size) {
            if (phase !== ‘PLAY’ || player !== turnPlayer || playerConfig[turnPlayer] === ‘AI’) return;
            if (stashes[player][size] > 0) {
                selectedSize = size;
                updateUI();
            }
        }

        function showDirectionSelector(index, validNeighbors) {
            document.querySelectorAll(‘.direction-overlay’).forEach(e => e.remove());
            const cell = document.getElementById(`cell-${index}`);
            const overlay = document.createElement(‘div’);
            overlay.className = ‘direction-overlay’;
            validNeighbors.forEach(n => {
                const btn = document.createElement(‘div’);
                btn.className = `dir-btn`;
                btn.style.top = n.label === ‘N’ ? ‘2px’ : n.label === ‘S’ ? ‘auto’ : ‘50%’;
                btn.style.bottom = n.label === ‘S’ ? ‘2px’ : ‘auto’;
                btn.style.left = n.label === ‘W’ ? ‘2px’ : n.label === ‘E’ ? ‘auto’ : ‘50%’;
                btn.style.right = n.label === ‘E’ ? ‘2px’ : ‘auto’;
                if(n.label === ‘N’ || n.label === ‘S’) btn.style.transform = ‘translateX(-50%)’;
                if(n.label === ‘E’ || n.label === ‘W’) btn.style.transform = ‘translateY(-50%)’;
               
                const arrows = { ‘N’:’▲’, ‘E’:’▶’, ‘S’:’▼’, ‘W’:’◀’ };
                btn.innerText = arrows[n.label];
                btn.onclick = (e) => { e.stopPropagation(); placePiece(index, selectedSize, n.rot, n.index); };
                overlay.appendChild(btn);
            });
            const cancel = document.createElement(‘div’);
            cancel.style.position = ‘absolute’; cancel.style.inset = ‘0’; cancel.style.zIndex = ‘-1’;
            cancel.onclick = (e) => { e.stopPropagation(); renderBoard(); };
            overlay.appendChild(cancel);
            cell.appendChild(overlay);
        }

        function renderBoard() {
            elBoard.innerHTML = ”;
            board.forEach((cell, index) => {
                const el = document.createElement(‘div’);
                el.className = ‘cell’;
                el.id = `cell-${index}`;
                if (index === lastMoveIndex) el.classList.add(‘last-move’);
               
                if (cell) {
                    if (cell.type === TYPE_NULL) {
                        el.classList.add(‘null-square’);
                    } else if (cell.type === TYPE_ROOT) {
                        el.classList.add(‘root-square’);
                        el.innerHTML = ‘<div class=”w-4 h-4 bg-blue-200 rounded-full shadow-inner”></div>’;
                    } else if (cell.type === TYPE_PYRAMID) {
                        const p = document.createElement(‘div’);
                        p.className = `pyramid size-${cell.size} ${cell.owner === PLAYER_1 ? ‘p1-piece’ : ‘p2-piece’}`;
                        p.style.transform = `rotate(${cell.rotation}deg)`;
                        el.appendChild(p);
                    }
                } else {
                    el.onclick = () => handleCellClick(index);
                }
                elBoard.appendChild(el);
            });
        }

        function updateUI() {
            document.getElementById(‘p1-score’).innerText = scores[0];
            document.getElementById(‘p2-score’).innerText = scores[1];

            const p1Pan = document.getElementById(‘p1-panel’);
            const p2Pan = document.getElementById(‘p2-panel’);
           
            if (phase === ‘PLAY’ || phase.startsWith(‘SETUP’)) {
                const isP1 = turnPlayer === PLAYER_1;
                p1Pan.classList.toggle(‘active-turn’, isP1);
                p2Pan.classList.toggle(‘active-turn’, !isP1);
               
                const pName = isP1 ? “White” : “Black”;
                const pType = playerConfig[turnPlayer];
                elStatus.innerText = (phase === ‘PLAY’) ? `${pName} (${pType})’s Turn` : `${pName} setting up…`;
            } else {
                p1Pan.classList.remove(‘active-turn’);
                p2Pan.classList.remove(‘active-turn’);
                elStatus.innerText = “GAME OVER”;
            }

            [0, 1].forEach(pid => {
                [1, 2, 3].forEach(size => {
                    const btn = document.getElementById(`${pid === 0 ? ‘p1’ : ‘p2’}-s${size}`);
                    const count = document.getElementById(`${pid === 0 ? ‘p1’ : ‘p2’}-c${size}`);
                    count.innerText = stashes[pid][size];
                   
                    if (pid === turnPlayer && phase === ‘PLAY’ && stashes[pid][size] > 0 && playerConfig[pid] === ‘HUMAN’) {
                        btn.disabled = false;
                        if (selectedSize === size) {
                            btn.classList.add(‘active’);
                            btn.classList.remove(‘opacity-50’);
                        } else {
                            btn.classList.remove(‘active’);
                            btn.classList.add(‘opacity-50’);
                        }
                    } else {
                        btn.disabled = true;
                        btn.classList.remove(‘active’);
                        btn.classList.remove(‘opacity-50’);
                    }
                });
            });
        }

        function log(msg, color=’white’) {
            const line = document.createElement(‘div’);
            // Log Format: [White-Black] Message
            line.innerHTML = `<span class=”opacity-50 text-xs mr-2 font-mono”>[${scores[0]}-${scores[1]}]</span> ${msg}`;
            if (color !== ‘white’) line.style.color = color;
            elLog.prepend(line);
        }

        showSetup();
    </script>
</body>
</html>

Leave a Reply