Spaces:
Sleeping
Sleeping
Joffrey Thomas
commited on
Commit
·
e83ab43
1
Parent(s):
bea46e9
ches-UI
Browse files- chess_game/static/room.js +89 -0
- chess_game/templates/room.html +27 -59
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 |
-
|
|
|
|
| 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 |
-
<
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
</div>
|
| 38 |
</div>
|
| 39 |
|
| 40 |
-
<script>
|
| 41 |
-
|
| 42 |
-
|
| 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 |
|