File size: 4,146 Bytes
e83ab43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6139d24
 
 
 
e83ab43
3339781
 
 
 
 
 
 
 
 
 
 
 
 
6139d24
e83ab43
3339781
e83ab43
 
 
 
 
 
 
 
a3f099e
e83ab43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
(() => {
  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();
})();