Spaces:
Sleeping
Sleeping
| (() => { | |
| 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 = `<?xml version="1.0" encoding="UTF-8"?> | |
| <svg xmlns='http://www.w3.org/2000/svg' width='64' height='64'> | |
| <rect x='0' y='0' width='64' height='64' fill='transparent'/> | |
| <text x='50%' y='62%' font-size='48' text-anchor='middle' fill='${fill}' stroke='${stroke}' stroke-width='1.5' font-family='Segoe UI Symbol, DejaVu Sans, Noto Sans Symbols, Arial, sans-serif'>${glyph}</text> | |
| </svg>`; | |
| 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(); | |
| })(); | |