(() => { const { roomId, basePath } = window.__ROOM_CONFIG__ || {}; const boardEl = document.getElementById('boardUI'); let board, chess; async function getJSON(path, options) { const res = await fetch(path, options); if (!res.ok) throw new Error(await res.text()); return res.json(); } async function refreshSideInfo() { const s = await getJSON(`${basePath}/room/${roomId}/status`); document.getElementById('turn').textContent = s.turn + (s.is_check ? ' (check)' : ''); document.getElementById('result').textContent = s.is_game_over ? (s.result || 'game over') : '-'; const legal = await getJSON(`${basePath}/room/${roomId}/legal_moves`); document.getElementById('legal').textContent = legal.join(', '); } async function syncFromServer() { const st = await getJSON(`${basePath}/room/${roomId}/board`); // chess.js v0.13 uses load(fen) chess.load(st.fen); board.position(chess.fen()); await refreshSideInfo(); } function onDragStart(source, piece) { // Disallow moves if game over or trying to move wrong color if (chess.game_over()) return false; const turn = chess.turn() === 'w' ? 'white' : 'black'; if ((turn === 'white' && piece.search(/^b/) !== -1) || (turn === 'black' && piece.search(/^w/) !== -1)) { return false; } return true; } async function onDrop(source, target) { let moveObj = chess.move({ from: source, to: target, promotion: 'q' }); if (moveObj == null) return 'snapback'; // Send move to server in UCI const uci = `${source}${target}${moveObj.promotion ? moveObj.promotion : ''}`; try { await getJSON(`${basePath}/room/${roomId}/player_move`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ move: uci }) }); await syncFromServer(); } catch (e) { console.error(e); chess.undo(); return 'snapback'; } } function onSnapEnd() { board.position(chess.fen()); } async function init() { if (typeof Chess === 'undefined' || typeof Chessboard === 'undefined') { console.error('Required libraries not loaded: Chess and/or Chessboard'); return; } chess = new Chess(); const pieceSvg = (piece) => { const glyphs = { wK:'♔', wQ:'♕', wR:'♖', wB:'♗', wN:'♘', wP:'♙', bK:'♚', bQ:'♛', bR:'♜', bB:'♝', bN:'♞', bP:'♟' }; const isWhite = piece && piece[0] === 'w'; const glyph = glyphs[piece] || '?'; const fill = isWhite ? '#ffffff' : '#111827'; const stroke = isWhite ? '#e5e7eb' : '#0b0f19'; const svg = ` ${glyph} `; return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg); }; board = Chessboard('boardUI', { position: 'start', draggable: true, dropOffBoard: 'snapback', pieceTheme: (p) => pieceSvg(p), onDragStart, onDrop, onSnapEnd }); await syncFromServer(); } document.getElementById('startBtn').addEventListener('click', async () => { const color = document.getElementById('color').value; await getJSON(`${basePath}/room/${roomId}/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ player_color: color })}); board.orientation(color); await syncFromServer(); }); document.getElementById('aiBtn').addEventListener('click', async () => { await getJSON(`${basePath}/room/${roomId}/ai_move`, { method: 'POST' }); await syncFromServer(); }); document.getElementById('copyPgnBtn').addEventListener('click', async () => { const data = await getJSON(`${basePath}/room/${roomId}/pgn`); await navigator.clipboard.writeText(data.pgn || ''); alert('PGN copied to clipboard'); }); init(); })();