Joffrey Thomas commited on
Commit
e83ab43
·
1 Parent(s): bea46e9
chess_game/static/room.js ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (() => {
2
+ const { roomId, basePath } = window.__ROOM_CONFIG__ || {};
3
+ const boardEl = document.getElementById('boardUI');
4
+ let board, chess;
5
+
6
+ async function getJSON(path, options) {
7
+ const res = await fetch(path, options);
8
+ if (!res.ok) throw new Error(await res.text());
9
+ return res.json();
10
+ }
11
+
12
+ async function refreshSideInfo() {
13
+ const s = await getJSON(`${basePath}/room/${roomId}/status`);
14
+ document.getElementById('turn').textContent = s.turn + (s.is_check ? ' (check)' : '');
15
+ document.getElementById('result').textContent = s.is_game_over ? (s.result || 'game over') : '-';
16
+ const legal = await getJSON(`${basePath}/room/${roomId}/legal_moves`);
17
+ document.getElementById('legal').textContent = legal.join(', ');
18
+ }
19
+
20
+ async function syncFromServer() {
21
+ const st = await getJSON(`${basePath}/room/${roomId}/board`);
22
+ // chess.js v0.13 uses load(fen)
23
+ chess.load(st.fen);
24
+ board.position(chess.fen());
25
+ await refreshSideInfo();
26
+ }
27
+
28
+ function onDragStart(source, piece) {
29
+ // Disallow moves if game over or trying to move wrong color
30
+ if (chess.game_over()) return false;
31
+ const turn = chess.turn() === 'w' ? 'white' : 'black';
32
+ if ((turn === 'white' && piece.search(/^b/) !== -1) || (turn === 'black' && piece.search(/^w/) !== -1)) {
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+
38
+ async function onDrop(source, target) {
39
+ let moveObj = chess.move({ from: source, to: target, promotion: 'q' });
40
+ if (moveObj == null) return 'snapback';
41
+ // Send move to server in UCI
42
+ const uci = `${source}${target}${moveObj.promotion ? moveObj.promotion : ''}`;
43
+ try {
44
+ await getJSON(`${basePath}/room/${roomId}/player_move`, {
45
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ move: uci })
47
+ });
48
+ await syncFromServer();
49
+ } catch (e) {
50
+ console.error(e);
51
+ chess.undo();
52
+ return 'snapback';
53
+ }
54
+ }
55
+
56
+ function onSnapEnd() {
57
+ board.position(chess.fen());
58
+ }
59
+
60
+ async function init() {
61
+ chess = new Chess();
62
+ board = Chessboard(boardEl, {
63
+ position: 'start', draggable: true, dropOffBoard: 'snapback',
64
+ onDragStart, onDrop, onSnapEnd
65
+ });
66
+ await syncFromServer();
67
+ }
68
+
69
+ document.getElementById('startBtn').addEventListener('click', async () => {
70
+ const color = document.getElementById('color').value;
71
+ await getJSON(`${basePath}/room/${roomId}/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ player_color: color })});
72
+ await syncFromServer();
73
+ });
74
+
75
+ document.getElementById('aiBtn').addEventListener('click', async () => {
76
+ await getJSON(`${basePath}/room/${roomId}/ai_move`, { method: 'POST' });
77
+ await syncFromServer();
78
+ });
79
+
80
+ document.getElementById('copyPgnBtn').addEventListener('click', async () => {
81
+ const data = await getJSON(`${basePath}/room/${roomId}/pgn`);
82
+ await navigator.clipboard.writeText(data.pgn || '');
83
+ alert('PGN copied to clipboard');
84
+ });
85
+
86
+ init();
87
+ })();
88
+
89
+
chess_game/templates/room.html CHANGED
@@ -4,13 +4,22 @@
4
  <meta charset="utf-8">
5
  <title>Chess Room {{ room_id }}</title>
6
  <link rel="stylesheet" href="{{ base_path }}/static/style.css">
 
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
8
  <style>
9
- pre.board { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 14px; line-height: 1.2; }
 
10
  .controls { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
11
  .status { margin-top: 8px; }
 
12
  </style>
13
  <link rel="icon" href="data:,">
 
 
 
 
 
 
14
  </head>
15
  <body>
16
  <div class="container">
@@ -23,71 +32,30 @@
23
  <option value="black">Black</option>
24
  </select>
25
  <button id="startBtn">Start / Reset</button>
26
- <input id="moveInput" placeholder="Enter SAN or UCI (e4, Nf3, e7e5)" />
27
- <button id="moveBtn">Make Move</button>
28
  <button id="aiBtn">AI Move</button>
29
  <button id="copyPgnBtn">Copy PGN</button>
30
  </div>
31
 
32
- <pre id="board" class="board">{{ state.unicode }}</pre>
33
- <div class="status">
34
- <div><strong>Turn:</strong> <span id="turn"></span></div>
35
- <div><strong>Result:</strong> <span id="result"></span></div>
36
- <div><strong>Legal Moves:</strong> <span id="legal"></span></div>
 
 
 
 
 
 
 
 
 
37
  </div>
38
  </div>
39
 
40
- <script>
41
- const roomId = {{ room_id | tojson }};
42
- const basePath = {{ base_path | tojson }};
43
-
44
- async function getJSON(path, options) {
45
- const res = await fetch(path, options);
46
- if (!res.ok) {
47
- const msg = await res.text();
48
- throw new Error(msg || res.statusText);
49
- }
50
- return res.json();
51
- }
52
-
53
- async function refresh() {
54
- const state = await getJSON(`${basePath}/room/${roomId}/board`);
55
- document.getElementById('board').textContent = state.unicode;
56
- const s = await getJSON(`${basePath}/room/${roomId}/status`);
57
- document.getElementById('turn').textContent = s.turn + (s.is_check ? ' (check)' : '');
58
- document.getElementById('result').textContent = s.is_game_over ? (s.result || 'game over') : '-';
59
- const legal = await getJSON(`${basePath}/room/${roomId}/legal_moves`);
60
- document.getElementById('legal').textContent = legal.join(', ');
61
- }
62
-
63
- document.getElementById('startBtn').addEventListener('click', async () => {
64
- const color = document.getElementById('color').value;
65
- await getJSON(`${basePath}/room/${roomId}/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ player_color: color })});
66
- await refresh();
67
- });
68
-
69
- document.getElementById('moveBtn').addEventListener('click', async () => {
70
- const mv = document.getElementById('moveInput').value.trim();
71
- if (!mv) return;
72
- try {
73
- await getJSON(`${basePath}/room/${roomId}/player_move`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ move: mv })});
74
- } catch (e) { alert('Move error: ' + e.message); }
75
- await refresh();
76
- });
77
-
78
- document.getElementById('aiBtn').addEventListener('click', async () => {
79
- await getJSON(`${basePath}/room/${roomId}/ai_move`, { method: 'POST' });
80
- await refresh();
81
- });
82
-
83
- document.getElementById('copyPgnBtn').addEventListener('click', async () => {
84
- const data = await getJSON(`${basePath}/room/${roomId}/pgn`);
85
- await navigator.clipboard.writeText(data.pgn || '');
86
- alert('PGN copied to clipboard');
87
- });
88
-
89
- refresh();
90
- </script>
91
  </body>
92
  </html>
93
 
 
4
  <meta charset="utf-8">
5
  <title>Chess Room {{ room_id }}</title>
6
  <link rel="stylesheet" href="{{ base_path }}/static/style.css">
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/chessboard.js/1.0.0/chessboard-1.0.0.min.css" integrity="sha512-NE1T0qj4Y8p4129f7m6C8b6oym3s+3X7B2+bXhYzIY8w14JzO3Qb5rG3wI+XHX5cS8l88sC5d6z7+q5U3aY4nA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
8
  <meta name="viewport" content="width=device-width, initial-scale=1">
9
  <style>
10
+ .layout { display: grid; grid-template-columns: 520px 1fr; gap: 16px; align-items: start; }
11
+ #boardUI { width: 520px; max-width: 90vw; }
12
  .controls { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
13
  .status { margin-top: 8px; }
14
+ .pill { background: #0f172a; border: 1px solid #334155; padding: 8px 10px; border-radius: 8px; }
15
  </style>
16
  <link rel="icon" href="data:,">
17
+ <script>
18
+ window.__ROOM_CONFIG__ = {
19
+ roomId: {{ room_id | tojson }},
20
+ basePath: {{ base_path | tojson }}
21
+ };
22
+ </script>
23
  </head>
24
  <body>
25
  <div class="container">
 
32
  <option value="black">Black</option>
33
  </select>
34
  <button id="startBtn">Start / Reset</button>
 
 
35
  <button id="aiBtn">AI Move</button>
36
  <button id="copyPgnBtn">Copy PGN</button>
37
  </div>
38
 
39
+ <div class="layout">
40
+ <div class="pill">
41
+ <div id="boardUI"></div>
42
+ </div>
43
+ <div>
44
+ <div class="pill">
45
+ <div><strong>Turn:</strong> <span id="turn"></span></div>
46
+ <div><strong>Result:</strong> <span id="result"></span></div>
47
+ </div>
48
+ <div class="pill" style="margin-top:8px;">
49
+ <div><strong>Legal Moves:</strong></div>
50
+ <div id="legal" style="margin-top:4px;"></div>
51
+ </div>
52
+ </div>
53
  </div>
54
  </div>
55
 
56
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.13.4/chess.min.js" integrity="sha512-QI3VgD1x5bY2Hf7Kx4Ew6U3bN1a90R0l3tq0m7pFx5tW5Lgn8gZyQmVh0Wk5xk/fXUeV3I8w6L6S3m0q8lFv8Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
57
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/chessboard.js/1.0.0/chessboard-1.0.0.min.js" integrity="sha512-YOsk0kM2w3Jr0o5Q2E7jv0o3s8fCqfVqUjEJ7sU2iQzQ9Q2G4fMBxQ8f2cN7z7m2fA2q5S5n0l1C1zjY00kHqA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
58
+ <script src="{{ base_path }}/static/room.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </body>
60
  </html>
61