const pieces = []; for (let y = 0; y < appState.boardSize; y++) { for (let x = 0; x < appState.boardSize; x++) { const p = appState.board[y][x]; if (p && p.color === color) { const moves = getValidMoves(x, y, p); if (moves.length > 0) pieces.push({ x, y, moves }); } } } if (pieces.length === 0) return; const choice = pieces[Math.floor(Math.random() * pieces.length)]; const move = choice.moves[Math.floor(Math.random() * choice.moves.length)]; // Check if piece is already moving (not in board) if (appState.board[choice.y][choice.x]) { startHyperMove(choice.x, choice.y, move.x, move.y); } } function renderHyperBoard() { renderBoard(); // Overlay moving pieces const boardEl = document.getElementById('board'); const size = appState.boardSize; appState.movingPieces.forEach(mp => { const pieceEl = document.createElement('div'); pieceEl.className = `piece ${mp.piece.color} absolute`; pieceEl.style.width = `${100 / size}%`; pieceEl.style.height = `${100 / size}%`; pieceEl.style.left = `${(mp.x / size) * 100}%`; pieceEl.style.top = `${(mp.y / size) * 100}%`; pieceEl.innerHTML = getPieceSpriteHTML(mp.piece.type, mp.piece.color); boardEl.appendChild(pieceEl); }); lucide.createIcons(); } function executeMove(fromX, fromY, toX, toY) { if (!appState.board[fromY] || !appState.board[toY]) return; const piece = appState.board[fromY][fromX]; const target = appState.board[toY][toX]; if (!piece) return; // Sound const soundId = target ? 'sfx-capture' : 'sfx-move'; const audioEl = document.getElementById(soundId); if (audioEl && audioEl.src) audioEl.play().catch(() => {}); // Checkers transformation let finalPieceType = piece.type; if (piece.type === PIECE_TYPES.CHECKERS && target) { finalPieceType = target.type; logAction(`Checkers piece transformed into ${target.type}!`); } // Pawn Promotion & Checkers King Logic if (finalPieceType === PIECE_TYPES.PAWN || finalPieceType === PIECE_TYPES.CHECKERS) { if ((piece.color === 'white' && toY === 0) || (piece.color === 'black' && toY === (appState.boardSize - 1))) { if (finalPieceType === PIECE_TYPES.CHECKERS) { finalPieceType = PIECE_TYPES.CHECKER_KING; logAction(`${piece.color.toUpperCase()} Checker promoted to KING!`); } else { finalPieceType = PIECE_TYPES.QUEEN; logAction(`${piece.color.toUpperCase()} Pawn promoted to QUEEN!`); } } } // Engineer turret logic if (piece.type === PIECE_TYPES.ENGINEER) { appState.board[fromY][fromX] = { type: PIECE_TYPES.TURRET, color: piece.color, id: `turret_${Date.now()}`, spawnX: fromX, spawnY: fromY }; logAction(`Engineer deployed a Turret.`); } else { appState.board[fromY][fromX] = null; } // Update board appState.board[toY][toX] = { ...piece, type: finalPieceType }; // Reset selection appState.selected = null; appState.validMoves = []; // Apply special piece abilities (Anchor / Sentry) const updatedPiece = appState.board[toY][toX]; applyAuras(toX, toY, updatedPiece); // Handle turn change endTurn(); } function endTurn() { // Decay status effects appState.statusEffects = appState.statusEffects.map(se => ({ ...se, turns: se.turns - 1 })).filter(se => se.turns > 0); appState.turn = appState.turn === 'white' ? 'black' : 'white'; document.getElementById('current-turn-label').textContent = `${appState.turn.toUpperCase()}'S TURN`; updateCounts(); checkWinConditions(); renderBoard(); if (!appState.isGameOver) { checkTriggerAI(); } } function changeGameMode(val) { appState.gameMode = val; logAction(`Mode changed to ${val.toUpperCase()}`); checkTriggerAI(); } function checkTriggerAI() { const isWhiteCpu = appState.gameMode === 'cvp' || appState.gameMode === 'cvc'; const isBlackCpu = appState.gameMode === 'pvc' || appState.gameMode === 'cvc'; if ((appState.turn === 'white' && isWhiteCpu) || (appState.turn === 'black' && isBlackCpu)) { setTimeout(runAI, appState.aiDelay); } } function runAI() { if (appState.isGameOver || !appState.board) return; const cpuColor = appState.turn; const allPieces = []; const size = Math.min(appState.boardSize, appState.board.length); // Find all pieces of current turn color for (let y = 0; y < size; y++) { const row = appState.board[y]; if (!row) continue; for (let x = 0; x < row.length; x++) { const piece = row[x]; if (piece && piece.color === cpuColor) { // Check if immobilized const isImmobilized = appState.statusEffects.some(se => se.x === x && se.y === y && se.type === 'immobilized'); if (!isImmobilized) { const moves = getValidMoves(x, y, piece); if (moves.length > 0) { allPieces.push({ x, y, piece, moves }); } } } } } if (allPieces.length === 0) { // No moves possible, might be stalemate or attrition win for other side checkWinConditions(); return; } // Simple "Greedy" AI: Prefer captures, otherwise random let selectedPiece = null; let selectedMove = null; // Try to find a capture const captureMoves = []; allPieces.forEach(p => { p.moves.forEach(m => { const row = appState.board[m.y]; if (row && row[m.x]) { captureMoves.push({ from: p, to: m }); } }); }); if (captureMoves.length > 0) { const choice = captureMoves[Math.floor(Math.random() * captureMoves.length)]; selectedPiece = choice.from; selectedMove = choice.to; } else { // Move random piece selectedPiece = allPieces[Math.floor(Math.random() * allPieces.length)]; selectedMove = selectedPiece.moves[Math.floor(Math.random() * selectedPiece.moves.length)]; } // Visually select the piece first appState.selected = { x: selectedPiece.x, y: selectedPiece.y, piece: selectedPiece.piece }; appState.validMoves = selectedPiece.moves; renderBoard(); // Execute move after small delay for visual feedback setTimeout(() => { executeMove(selectedPiece.x, selectedPiece.y, selectedMove.x, selectedMove.y); }, 400); } function applyStatus(x, y, type, turns) { const existing = appState.statusEffects.find(se => se.x === x && se.y === y && se.type === type); if (existing) { existing.turns = Math.max(existing.turns, turns); } else { appState.statusEffects.push({ x, y, type, turns }); } } function applyAuras(x, y, piece) { if (piece.type === PIECE_TYPES.ANCHOR) { for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { if (dx === 0 && dy === 0) continue; const nx = x + dx, ny = y + dy; if (isInside(nx, ny)) { const p = appState.board[ny][nx]; if (p && p.color !== piece.color) applyStatus(nx, ny, 'immobilized', 3); } } } } if (piece.type === PIECE_TYPES.SENTRY) { for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { if (dx === 0 && dy === 0) continue; const nx = x + dx, ny = y + dy; if (isInside(nx, ny)) { const p = appState.board[ny][nx]; if (p && p.color === piece.color) applyStatus(nx, ny, 'invincible', 3); } } } } } // --- Move Validation Logic --- function getValidMoves(x, y, piece) { let moves = []; const type = piece.type; const color = piece.color; const isInvincible = (tx, ty) => appState.statusEffects.some(se => se.x === tx && se.y === ty && se.type === 'invincible'); const addMove = (nx, ny, path = []) => { if (!isInside(nx, ny) || !appState.board[ny]) return false; const target = appState.board[ny][nx]; if (!target) { moves.push({ x: nx, y: ny, path }); return true; } else if (target.color !== color) { if (!isInvincible(nx, ny)) moves.push({ x: nx, y: ny, path }); return false; } return false; }; const addSlider = (dx, dy) => { let nx = x + dx, ny = y + dy; while (addMove(nx, ny)) { nx += dx; ny += dy; } }; switch (type) { case PIECE_TYPES.PAWN: const dir = color === 'white' ? -1 : 1; const startRank = color === 'white' ? 9 : 2; // Forward (with safe row access) if (isInside(x, y + dir) && appState.board[y + dir] && !appState.board[y + dir][x]) { moves.push({ x, y: y + dir }); if (y === startRank && isInside(x, y + 2 * dir) && appState.board[y + 2 * dir] && !appState.board[y + 2 * dir][x]) { moves.push({ x, y: y + 2 * dir }); } } // Capture [1, -1].forEach(dx => { const nx = x + dx, ny = y + dir; if (isInside(nx, ny)) { const t = appState.board[ny][nx]; if (t && t.color !== color && !isInvincible(nx, ny)) moves.push({ x: nx, y: ny }); } }); break; case PIECE_TYPES.KNIGHT: [[2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1, 2], [-1, -2]].forEach(m => { const tx = x + m[0], ty = y + m[1]; addMove(tx, ty, getLeaperPath(x, y, tx, ty)); }); break; case PIECE_TYPES.CRAB: // Diagonal L: 3x1 and 1x3 leaper with diagonal pathing [[3, 1], [3, -1], [-3, 1], [-3, -1], [1, 3], [1, -3], [-1, 3], [-1, -3]].forEach(m => { const tx = x + m[0], ty = y + m[1]; addMove(tx, ty, getLeaperPath(x, y, tx, ty, true)); }); break; case PIECE_TYPES.ANGEL: // Knight (Straight L) + Crab (Diagonal L) [[2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1, 2], [-1, -2]].forEach(m => { const tx = x + m[0], ty = y + m[1]; addMove(tx, ty, getLeaperPath(x, y, tx, ty, false)); }); [[3, 1], [3, -1], [-3, 1], [-3, -1], [1, 3], [1, -3], [-1, 3], [-1, -3]].forEach(m => { const tx = x + m[0], ty = y + m[1]; addMove(tx, ty, getLeaperPath(x, y, tx, ty, true)); }); break; case PIECE_TYPES.BISHOP: [[1, 1], [1, -1], [-1, 1], [-1, -1]].forEach(d => addSlider(d[0], d[1])); break; case PIECE_TYPES.ROOK: [[1, 0], [-1, 0], [0, 1], [0, -1]].forEach(d => addSlider(d[0], d[1])); break; case PIECE_TYPES.QUEEN: [[1, 1], [1, -1], [-1, 1], [-1, -1], [1, 0], [-1, 0], [0, 1], [0, -1]].forEach(d => addSlider(d[0], d[1])); break; case PIECE_TYPES.KING: for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { if (dx !== 0 || dy !== 0) { const nx = x + dx, ny = y + dy; if (appState.gameType === 'vs_checkers' && color === 'white' && (nx < 2 || nx >= 10 || ny < 2 || ny >= 10)) continue; addMove(nx, ny); } } } break; case PIECE_TYPES.CHECKER_KING: // Traditional Checkers King: Any diagonal direction [1, -1].forEach(dx => { [1, -1].forEach(dy => { const nx = x + dx, ny = y + dy; if (isInside(nx, ny)) { const t = appState.board[ny][nx]; if (!t) moves.push({ x: nx, y: ny }); else if (t.color !== color && !isInvincible(nx, ny)) { const jx = x + 2 * dx, jy = y + 2 * dy; if (isInside(jx, jy) && !appState.board[jy][jx]) moves.push({ x: jx, y: jy }); } } }); }); break; case PIECE_TYPES.CHECKERS: const cDir = color === 'white' ? -1 : 1; if (appState.gameType === 'hyper' || appState.gameType === 'hyper_plus') { if (isInside(x, y + cDir)) addMove(x, y + cDir); } else if (appState.gameType === 'vs_checkers') { // Traditional Checkers movement: Forward diagonals only [1, -1].forEach(dx => { const nx = x + dx, ny = y + cDir; if (isInside(nx, ny)) { const t = appState.board[ny][nx]; if (!t) moves.push({ x: nx, y: ny }); else if (t.color !== color && !isInvincible(nx, ny)) { // Jump logic const jx = x + 2 * dx, jy = y + 2 * cDir; if (isInside(jx, jy) && !appState.board[jy][jx]) { moves.push({ x: jx, y: jy }); } } } }); } else { // Chess+ Mimic Logic: 1 step any direction (Forward default logic) [1, -1, 0].forEach(dx => { [1, -1, 0].forEach(dy => { if (dx === 0 && dy === 0) return; const nx = x + dx, ny = y + dy; addMove(nx, ny); }); }); } break; case PIECE_TYPES.ANCHOR: [[1, 0], [-1, 0], [0, 1], [0, -1]].forEach(d => addSlider(d[0], d[1])); break; case PIECE_TYPES.ENGINEER: // Jump exactly 2 squares in any direction (orthogonal and diagonal) for (let dy = -2; dy <= 2; dy += 2) { for (let dx = -2; dx <= 2; dx += 2) { if (dx !== 0 || dy !== 0) addMove(x + dx, y + dy); } } break; case PIECE_TYPES.SENTRY: // Jump exactly 2 squares diagonally [[2, 2], [2, -2], [-2, 2], [-2, -2]].forEach(m => addMove(x + m[0], y + m[1])); break; case PIECE_TYPES.TURRET: for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { const nx = x + dx, ny = y + dy; const isNearSpawn = piece.spawnX !== undefined ? (Math.abs(nx - piece.spawnX) <= 1 && Math.abs(ny - piece.spawnY) <= 1) : true; if ((dx !== 0 || dy !== 0) && isNearSpawn) addMove(nx, ny); } } break; case PIECE_TYPES.FROG: // 5x5 ring (not in 3x3) for (let dy = -2; dy <= 2; dy++) { for (let dx = -2; dx <= 2; dx++) { if (Math.abs(dx) === 2 || Math.abs(dy) === 2) addMove(x + dx, y + dy); } } break; } return moves; } function isInside(x, y) { const size = appState.boardSize; if (x < 0 || x >= size || y < 0 || y >= size) return false; // Robust check: ensure the board and the specific row exist return !!(appState.board && appState.board[y]); } // --- Utility Functions --- function updateCounts() { let white = 0, black = 0; appState.board.flat().forEach(p => { if (p) { if (p.color === 'white') white++; else if (p.color === 'black') black++; } }); appState.counts = { white, black }; document.getElementById('white-count').textContent = white; document.getElementById('black-count').textContent = black; } function checkWinConditions() { let whiteKing = false, blackKing = false; let checkersRemaining = 0; appState.board.flat().forEach(p => { if (!p) return; if (p.type === PIECE_TYPES.KING) { if (p.color === 'white') whiteKing = true; else blackKing = true; } if (p.color === 'black' && (p.type === PIECE_TYPES.CHECKERS || p.type === PIECE_TYPES.CHECKER_KING)) { checkersRemaining++; } }); if (appState.gameType === 'vs_checkers') { if (checkersRemaining === 0) gameOver('white', 'Attrition'); else if (!whiteKing) gameOver('black', 'Regicide'); return; } if (!blackKing || appState.counts.black === 0) gameOver('white', !blackKing ? 'Checkmate' : 'Attrition'); else if (!whiteKing || appState.counts.white === 0) gameOver('black', !whiteKing ? 'Checkmate' : 'Attrition'); } function gameOver(winner, reason) { appState.isGameOver = true; const modal = document.getElementById('victory-modal'); document.getElementById('victory-title').textContent = `${winner.toUpperCase()} VICTORIOUS`; document.getElementById('victory-reason').textContent = `Result: ${reason}`; modal.classList.add('visible'); if (appState.vibrateEnabled) sendMessage('gen_vibration', { duration: 500 }); } function resetGame() { appState.isGameOver = false; appState.turn = 'white'; appState.selected = null; appState.validMoves = []; appState.statusEffects = []; setupBoard(); renderBoard(); document.getElementById('victory-modal').classList.remove('visible'); document.getElementById('current-turn-label').textContent = "WHITE'S TURN"; logAction("Game reset. White to move."); } function logAction(msg) { const logEl = document.getElementById('game-log'); logEl.textContent = msg; } function toggleModal(id) { const modal = document.getElementById(id); modal.classList.toggle('visible'); } async function handleShare() { const shareBtn = document.getElementById('shareBtn'); if (shareBtn.dataset.loading === 'true') return; shareBtn.dataset.loading = 'true'; shareBtn.disabled = true; try { const element = document.getElementById('share-zone'); const area = element.offsetWidth * element.offsetHeight; const isLargeContent = area > 500000; const targetScale = isLargeContent ? 1.5 : 2; const quality = isLargeContent ? 0.6 : 0.7; const jpgImg = await window.snapdom.toJpg(element, { quality: quality, width: element.offsetWidth * targetScale, height: element.offsetHeight * targetScale, }); sendMessage('invoke_share', { title: `Check my 12x12 Chess! ${appState.turn.toUpperCase()} turn.`, coverImageBase64: jpgImg.src }); } catch (e) { console.error('Share failed:', e); } finally { shareBtn.dataset.loading = 'false'; shareBtn.disabled = false; } } // --- Required System Helpers --- function applyAllEditableValues() { window.sekaiEditable.tune?.forEach(t => { if (t.path) setNestedValue(window, t.path, t.value); }); window.sekaiEditable.colors?.forEach(c => { if (c.cssVar) document.documentElement.style.setProperty(c.cssVar, c.value); }); window.sekaiEditable.images?.forEach(img => { if (img.selector) { const el = document.querySelector(img.selector); if (el) { if (img.property === 'src') el.src = img.value; else if (img.property === 'backgroundImage') el.style.backgroundImage = `url('${img.value}')`; } } }); window.sekaiEditable.videos?.forEach(vid => { if (vid.path) setNestedValue(window, vid.path, vid.value); }); window.sekaiEditable.music?.forEach(m => { if (m.selector) { const el = document.querySelector(m.selector); if (el && el.src !== m.value) { el.src = m.value; el.load(); } } }); window.sekaiEditable.sfx?.forEach(s => { if (s.selector) { const el = document.querySelector(s.selector); if (el && el.src !== s.value) { el.src = s.value; el.load(); } } }); window.sekaiEditable.text?.forEach(t => { if (t.selector) { const el = document.querySelector(t.selector); if (el) { if (t.property === 'placeholder') el.placeholder = t.value; else el.textContent = t.value; } } }); } function setNestedValue(obj, path, value) { const parts = path.split('.'); let current = obj; for (let i = 0; i < parts.length - 1; i++) { if (!current[parts[i]]) current[parts[i]] = {}; current = current[parts[i]]; } current[parts[parts.length - 1]] = value; } function initBGM() { const el = document.getElementById('music-bgm'); if (!el?.src) return; el.loop = true; el.volume = 0.4; const playBgm = () => { el.play().catch(() => { document.addEventListener('touchstart', playBgm, { once: true }); document.addEventListener('click', playBgm, { once: true }); }); }; playBgm(); } (function setupPreviewEditingAPI() { window.addEventListener('message', (event) => { const msg = event.data; if (msg?.origin !== 'sekai_gaming_iframe_api') return; switch (msg.type) { case 'get_editable_metadata': window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'receive_editable_metadata', taskId: msg.taskId, data: window.sekaiEditable || {} }, '*'); break; case 'apply_change': const success = applySingleChange(msg.data.id, msg.data.value); window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'receive_apply_change', taskId: msg.taskId, data: { success } }, '*'); break; case 'get_current_html': window.parent.postMessage({ origin: 'sekai_gaming_iframe_api', type: 'receive_current_html', taskId: msg.taskId, data: { html: document.documentElement.outerHTML } }, '*'); break; case 'reset_changes': location.reload(); break; } }); function applySingleChange(id, value) { const categories = ['tune', 'images', 'videos', 'music', 'sfx', 'colors', 'text', 'prompts', 'voices']; for (const cat of categories) { const item = window.sekaiEditable[cat]?.find(i => i.id === id); if (!item) continue; item.value = value; if (item.cssVar) { document.documentElement.style.setProperty(item.cssVar, value); } else if (item.path) { setNestedValue(window, item.path, value); } else if (item.selector) { const el = document.querySelector(item.selector); if (!el) return false; if (el.tagName === 'AUDIO') { const wasPlaying = !el.paused; el.src = value; el.load(); if (wasPlaying) el.play().catch(() => {}); } else if (el.tagName === 'VIDEO') { el.src = value; el.load(); } else if (item.property === 'src') { el.src = value; } else if (item.property === 'backgroundImage') { el.style.backgroundImage = `url('${value}')`; } else if (item.property === 'placeholder') { el.placeholder = value; } else { el.textContent = value; } } return true; } return false; } })(); // Start document.addEventListener('DOMContentLoaded', initApp); ]]> ```