236 lines
9.1 KiB
HTML
236 lines
9.1 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>ChessNut - Partie</title>
|
|
<link rel="stylesheet" href="/styles/style.css">
|
|
</head>
|
|
<body>
|
|
<h2>ChessNut — Partie</h2>
|
|
<div>Room: <strong id="roomId">-</strong> • Vous jouez: <strong id="myColor">-</strong>
|
|
<div class="layout-row">
|
|
<div>
|
|
<div id="board"></div>
|
|
</div>
|
|
<aside style="min-width:320px;margin-left:18px">
|
|
|
|
<section class="card" style="margin-top:12px">
|
|
<div><strong>Outils / Logs</strong></div>
|
|
<div style="margin-top:8px">
|
|
<input id="from" placeholder="from (e2)" style="width:90px" />
|
|
<input id="to" placeholder="to (e4)" style="width:90px" />
|
|
<input id="promo" placeholder="promo (q,r)" style="width:80px" />
|
|
<button id="move">Envoyer</button>
|
|
</div>
|
|
<pre id="log" style="margin-top:8px;height:160px;overflow:auto">logs...</pre>
|
|
</section>
|
|
</aside>
|
|
</div>
|
|
|
|
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
|
|
<script>
|
|
function qs(name){ const url = new URL(window.location.href); return url.searchParams.get(name); }
|
|
const roomId = qs('roomId');
|
|
const playerId = qs('playerId') || undefined;
|
|
const roomIdEl = document.getElementById('roomId');
|
|
const playerIdEl = document.getElementById('playerId');
|
|
if(roomIdEl) roomIdEl.textContent = roomId || '-';
|
|
if(playerIdEl) playerIdEl.textContent = playerId || '-';
|
|
|
|
// optional UI elements: support removing the sidebar/sections without breaking JS
|
|
const logEl = document.getElementById('log');
|
|
const fenEl = document.getElementById('fen');
|
|
const playersEl = document.getElementById('players');
|
|
const boardEl = document.getElementById('board');
|
|
const myColorEl = document.getElementById('myColor');
|
|
let socket = null;
|
|
let myColor = null;
|
|
|
|
// safe logger: prefer on-page log if present, otherwise console
|
|
function log(...args){
|
|
if(logEl){
|
|
try{
|
|
logEl.textContent += '\n' + args.map(a=>typeof a==='object'?JSON.stringify(a):a).join(' ');
|
|
logEl.scrollTop = logEl.scrollHeight;
|
|
}catch(e){ console.log(...args); }
|
|
} else {
|
|
console.log(...args);
|
|
}
|
|
}
|
|
|
|
if(!roomId){ alert('roomId manquant dans l\'URL'); }
|
|
|
|
function connectAndJoin(){
|
|
socket = io();
|
|
socket.on('connect', ()=>{ log('socket connecté', socket.id); });
|
|
|
|
socket.on('room:update', (data)=>{
|
|
log('room:update', data);
|
|
if(fenEl) fenEl.textContent = data.fen || '-';
|
|
if(playersEl){
|
|
playersEl.innerHTML = '';
|
|
(data.players||[]).forEach(p=>{
|
|
const li = document.createElement('li');
|
|
li.textContent = p.id + ' (' + p.color + ')';
|
|
playersEl.appendChild(li);
|
|
});
|
|
}
|
|
if(data.size){
|
|
// ensure grid layout matches requested size and build squares
|
|
buildGrid(data.size);
|
|
// adjust board width/height responsively (keep squares roughly proportional)
|
|
const side = Math.min(800, Math.max(320, data.size * 80));
|
|
boardEl.style.width = side + 'px';
|
|
boardEl.style.height = side + 'px';
|
|
}
|
|
if(data.fen) renderFen(data.fen);
|
|
});
|
|
|
|
socket.on('move:moved', (data)=>{ log('move:moved', data); if(fenEl) fenEl.textContent = data.fen; renderFen(data.fen); });
|
|
socket.on('game:over', (data)=>{ log('game:over', data); if(fenEl) fenEl.textContent = data.fen; renderFen(data.fen); });
|
|
socket.on('game:started', (data)=>{ log('game:started', data); if(fenEl) fenEl.textContent = data.fen || '-'; renderFen(data.fen || ''); });
|
|
|
|
socket.emit('room:join', { roomId, playerId }, (resp)=>{
|
|
if(resp && resp.error){ log('join error', resp); alert(resp.error); return; }
|
|
log('Rejoint room', resp);
|
|
// remember assigned id/color from server
|
|
if(resp.playerId){
|
|
if(playerIdEl) playerIdEl.textContent = resp.playerId;
|
|
}
|
|
if(resp.color){
|
|
myColor = resp.color;
|
|
if(myColorEl) myColorEl.textContent = myColor;
|
|
// orient board for black
|
|
if(boardEl){
|
|
if(myColor === 'black') boardEl.classList.add('flipped');
|
|
else boardEl.classList.remove('flipped');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
connectAndJoin();
|
|
|
|
// show markers for legal moves when clicking any square (any piece color)
|
|
function clearLegalMarkers(){
|
|
const prev = boardEl.querySelectorAll('.move-marker');
|
|
prev.forEach(p=>p.remove());
|
|
}
|
|
|
|
function showLegalMoves(moves){
|
|
clearLegalMarkers();
|
|
(moves||[]).forEach(m => {
|
|
const to = m.to || (m.move && m.move.to);
|
|
if(!to) return;
|
|
const target = boardEl.querySelector(`.square[data-square="${to}"]`);
|
|
if(!target) return;
|
|
const marker = document.createElement('div');
|
|
marker.className = 'move-marker';
|
|
// store metadata if needed later
|
|
marker.dataset.from = m.from || '';
|
|
marker.dataset.to = to;
|
|
target.appendChild(marker);
|
|
});
|
|
}
|
|
|
|
if(boardEl){
|
|
boardEl.addEventListener('click', (ev)=>{
|
|
const sqEl = ev.target.closest && ev.target.closest('.square');
|
|
if(!sqEl) return;
|
|
const square = sqEl.getAttribute('data-square');
|
|
if(!square) return;
|
|
if(socket){
|
|
socket.emit('game:legalMoves', { roomId, square }, (resp)=>{
|
|
if(resp && resp.error){ log('legalMoves error', resp); clearLegalMarkers(); return; }
|
|
log('legalMoves for', square, resp.moves || resp);
|
|
showLegalMoves(resp.moves || []);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
const moveBtn = document.getElementById('move');
|
|
if(moveBtn){
|
|
moveBtn.addEventListener('click', ()=>{
|
|
if(!socket){ alert('non connecté'); return; }
|
|
const fromEl = document.getElementById('from');
|
|
const toEl = document.getElementById('to');
|
|
const promoEl = document.getElementById('promo');
|
|
const from = fromEl ? fromEl.value.trim() : '';
|
|
const to = toEl ? toEl.value.trim() : '';
|
|
const promotion = promoEl ? promoEl.value.trim() || undefined : undefined;
|
|
socket.emit('game:move', { roomId, from, to, promotion }, (resp)=>{
|
|
if(resp && resp.error) { log('move error', resp); alert(resp.error); return; }
|
|
log('move accepted', resp);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Board rendering using provided SVG assets. Accepts FEN.
|
|
const pieceSetPath = '/assets/chess-pieces/chess_maestro_bw';
|
|
// board visual defaults are handled by CSS (background, size, border, position)
|
|
|
|
function renderFen(fen){
|
|
if(!fen) return;
|
|
// only handle standard FEN for 8x8; for other sizes server should send a 'board' structure
|
|
const parts = fen.split(' ');
|
|
const rows = parts[0].split('/');
|
|
// clear existing pieces but keep squares if present
|
|
const squares = boardEl.querySelectorAll('.square');
|
|
squares.forEach(s=> s.innerHTML = '');
|
|
|
|
// place pieces according to FEN (assume 8x8)
|
|
for(let r=0;r<rows.length;r++){
|
|
const row = rows[r];
|
|
let file = 0;
|
|
for(let i=0;i<row.length;i++){
|
|
const ch = row[i];
|
|
if(/[1-8]/.test(ch)){
|
|
const empties = parseInt(ch,10);
|
|
file += empties;
|
|
} else {
|
|
const isWhite = (ch === ch.toUpperCase());
|
|
const letter = ch.toUpperCase();
|
|
const filename = (isWhite ? 'w' + letter : 'b' + letter) + '.svg';
|
|
const rank = 8 - r; // FEN rows are from rank 8..1
|
|
const fileLetter = String.fromCharCode('a'.charCodeAt(0) + file);
|
|
const squareName = `${fileLetter}${rank}`;
|
|
const sqEl = boardEl.querySelector(`.square[data-square="${squareName}"]`);
|
|
if(sqEl){
|
|
const img = document.createElement('img');
|
|
img.src = `${pieceSetPath}/${filename}`;
|
|
img.className = 'piece';
|
|
sqEl.appendChild(img);
|
|
}
|
|
file++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// build grid with independent square elements and data-square attributes
|
|
function buildGrid(size){
|
|
if(!size || size < 1) size = 8;
|
|
boardEl.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
|
|
boardEl.style.gridTemplateRows = `repeat(${size}, 1fr)`;
|
|
// if grid already has correct number of squares, keep them
|
|
const existing = boardEl.querySelectorAll('.square');
|
|
if(existing.length === size * size) return;
|
|
boardEl.innerHTML = '';
|
|
// create rows from rank size..1 (so that a1 is bottom-left if CSS doesn't flip)
|
|
for(let rank = size; rank >= 1; rank--){
|
|
for(let file = 0; file < size; file++){
|
|
const fileLetter = String.fromCharCode('a'.charCodeAt(0) + file);
|
|
const squareName = `${fileLetter}${rank}`;
|
|
const sq = document.createElement('div');
|
|
// color squares in checker pattern
|
|
const dark = ((file + rank) % 2) === 0;
|
|
sq.className = 'square ' + (dark ? 'dark' : 'light');
|
|
sq.setAttribute('data-square', squareName);
|
|
boardEl.appendChild(sq);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|