<!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>
