affichage des pièces
This commit is contained in:
parent
16807472ab
commit
af6bcf60c5
4 changed files with 193 additions and 50 deletions
164
public/game.html
164
public/game.html
|
|
@ -12,6 +12,19 @@
|
|||
<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>
|
||||
|
|
@ -19,16 +32,29 @@
|
|||
function qs(name){ const url = new URL(window.location.href); return url.searchParams.get(name); }
|
||||
const roomId = qs('roomId');
|
||||
const playerId = qs('playerId') || undefined;
|
||||
document.getElementById('roomId').textContent = roomId || '-';
|
||||
document.getElementById('playerId').textContent = playerId || '-';
|
||||
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');
|
||||
let socket = null;
|
||||
|
||||
function log(...args){ logEl.textContent += '\n' + args.map(a=>typeof a==='object'?JSON.stringify(a):a).join(' '); logEl.scrollTop = logEl.scrollHeight; }
|
||||
// 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'); }
|
||||
|
||||
|
|
@ -38,19 +64,29 @@
|
|||
|
||||
socket.on('room:update', (data)=>{
|
||||
log('room:update', data);
|
||||
fenEl.textContent = data.fen || '-';
|
||||
playersEl.innerHTML = '';
|
||||
(data.players||[]).forEach(p=>{
|
||||
const li = document.createElement('li');
|
||||
li.textContent = p.id + ' (' + p.color + ')';
|
||||
playersEl.appendChild(li);
|
||||
});
|
||||
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); fenEl.textContent = data.fen; renderFen(data.fen); });
|
||||
socket.on('game:over', (data)=>{ log('game:over', data); fenEl.textContent = data.fen; renderFen(data.fen); });
|
||||
socket.on('game:started', (data)=>{ log('game:started', data); fenEl.textContent = 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; }
|
||||
|
|
@ -60,56 +96,104 @@
|
|||
|
||||
connectAndJoin();
|
||||
|
||||
document.getElementById('move').addEventListener('click', ()=>{
|
||||
if(!socket){ alert('non connecté'); return; }
|
||||
const from = document.getElementById('from').value.trim();
|
||||
const to = document.getElementById('to').value.trim();
|
||||
const promotion = document.getElementById('promo').value.trim() || 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);
|
||||
// allow clicking on the board to request legal moves for a square
|
||||
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); return; }
|
||||
log('legalMoves for', square, resp.moves || resp);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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_1Kbyte_gambit';
|
||||
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('/');
|
||||
boardEl.innerHTML = '';
|
||||
for(let r=0;r<8;r++){
|
||||
// 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)){
|
||||
if(/[1-8]/.test(ch)){
|
||||
const empties = parseInt(ch,10);
|
||||
for(let e=0;e<empties;e++){
|
||||
const sq = document.createElement('div');
|
||||
sq.className = 'square';
|
||||
boardEl.appendChild(sq);
|
||||
file++;
|
||||
}
|
||||
file += empties;
|
||||
} else {
|
||||
const sq = document.createElement('div');
|
||||
sq.className = 'square';
|
||||
// determine filename from piece char
|
||||
const isWhite = (ch === ch.toUpperCase());
|
||||
const letter = ch.toUpperCase();
|
||||
const filename = (isWhite ? 'w' + letter : 'b' + letter) + '.svg';
|
||||
const img = document.createElement('img');
|
||||
img.src = `${pieceSetPath}/${filename}`;
|
||||
img.className = 'piece';
|
||||
sq.appendChild(img);
|
||||
boardEl.appendChild(sq);
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ a:hover{text-decoration:underline}
|
|||
.square{width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
||||
.square img, .piece{max-width:78%;max-height:78%;user-select:none}
|
||||
|
||||
/* basic checkerboard coloring */
|
||||
.square.light{background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));}
|
||||
.square.dark{background: linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.12));}
|
||||
.square{border:1px solid rgba(0,0,0,0.06)}
|
||||
|
||||
/* Lobby / hero styles - decorative and fun */
|
||||
.hero{
|
||||
display:flex;align-items:center;justify-content:space-between;padding:28px;border-radius:12px;margin-bottom:18px;overflow:hidden;position:relative;border:1px solid rgba(255,255,255,0.04);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ a:hover{text-decoration:underline}
|
|||
.square{width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
||||
.square img, .piece{max-width:78%;max-height:78%;user-select:none}
|
||||
|
||||
/* basic checkerboard coloring */
|
||||
.square.light{background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));}
|
||||
.square.dark{background: linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.12));}
|
||||
.square{border:1px solid rgba(0,0,0,0.06)}
|
||||
|
||||
/* Lobby / hero styles - decorative and fun */
|
||||
.hero{
|
||||
display:flex;align-items:center;justify-content:space-between;padding:28px;border-radius:12px;margin-bottom:18px;overflow:hidden;position:relative;border:1px solid rgba(255,255,255,0.04);
|
||||
|
|
|
|||
69
server.js
69
server.js
|
|
@ -20,16 +20,20 @@ const rooms = new Map();
|
|||
|
||||
app.post('/rooms', (req, res) => {
|
||||
const id = uuidv4().slice(0, 8);
|
||||
const chess = new Chess();
|
||||
// allow optional board size in request body (default 8)
|
||||
const size = (req.body && parseInt(req.body.size, 10)) || 8;
|
||||
// For standard 8x8 chess we use chess.js. For other sizes we keep a placeholder state
|
||||
const chess = size === 8 ? new Chess() : null;
|
||||
// hostId will be set when the first player joins
|
||||
rooms.set(id, { id, chess, players: [], status: 'waiting', hostId: null });
|
||||
res.json({ roomId: id });
|
||||
rooms.set(id, { id, chess, players: [], status: 'waiting', hostId: null, size, cards: {}, playedCards: [] });
|
||||
res.json({ roomId: id, size });
|
||||
});
|
||||
|
||||
app.get('/rooms/:id', (req, res) => {
|
||||
const room = rooms.get(req.params.id);
|
||||
if (!room) return res.status(404).json({ error: 'room not found' });
|
||||
res.json({ id: room.id, fen: room.chess.fen(), players: room.players.map(p => ({ id: p.id, color: p.color })), status: room.status, hostId: room.hostId });
|
||||
const fen = room.chess ? room.chess.fen() : null;
|
||||
res.json({ id: room.id, fen, size: room.size, players: room.players.map(p => ({ id: p.id, color: p.color })), status: room.status, hostId: room.hostId, cards: Object.keys(room.cards || {}) });
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
|
|
@ -52,10 +56,12 @@ io.on('connection', (socket) => {
|
|||
|
||||
|
||||
io.to(roomId).emit('room:update', {
|
||||
fen: room.chess.fen(),
|
||||
fen: room.chess ? room.chess.fen() : null,
|
||||
players: room.players.map(p => ({ id: p.id, color: p.color })),
|
||||
status: room.status,
|
||||
hostId: room.hostId
|
||||
hostId: room.hostId,
|
||||
size: room.size,
|
||||
cards: Object.keys(room.cards || {})
|
||||
});
|
||||
|
||||
cb && cb({ ok: true, color, roomId, playerId: assignedId, hostId: room.hostId });
|
||||
|
|
@ -67,6 +73,9 @@ io.on('connection', (socket) => {
|
|||
|
||||
const chess = room.chess;
|
||||
|
||||
// If we have a chess.js instance (standard 8x8) use it for validation
|
||||
if (!chess) return cb && cb({ error: 'custom board sizes not yet supported for moves' });
|
||||
|
||||
// Validate move
|
||||
const move = chess.move({ from, to, promotion });
|
||||
if (!move) return cb && cb({ error: 'invalid move' });
|
||||
|
|
@ -99,10 +108,12 @@ io.on('connection', (socket) => {
|
|||
room.status = 'playing';
|
||||
io.to(roomId).emit('game:started', { roomId, fen: room.chess.fen() });
|
||||
io.to(roomId).emit('room:update', {
|
||||
fen: room.chess.fen(),
|
||||
fen: room.chess ? room.chess.fen() : null,
|
||||
players: room.players.map(p => ({ id: p.id, color: p.color })),
|
||||
status: room.status,
|
||||
hostId: room.hostId
|
||||
hostId: room.hostId,
|
||||
size: room.size,
|
||||
cards: Object.keys(room.cards || {})
|
||||
});
|
||||
|
||||
cb && cb({ ok: true });
|
||||
|
|
@ -123,12 +134,50 @@ io.on('connection', (socket) => {
|
|||
}
|
||||
|
||||
io.to(roomId).emit('room:update', {
|
||||
fen: room.chess.fen(),
|
||||
fen: room.chess ? room.chess.fen() : null,
|
||||
players: room.players.map(p => ({ id: p.id, color: p.color })),
|
||||
status: room.status,
|
||||
hostId: room.hostId
|
||||
hostId: room.hostId,
|
||||
size: room.size,
|
||||
cards: Object.keys(room.cards || {})
|
||||
});
|
||||
});
|
||||
|
||||
// Provide legal moves for a given square (for standard 8x8 via chess.js)
|
||||
socket.on('game:legalMoves', ({ roomId, square }, cb) => {
|
||||
const room = rooms.get(roomId);
|
||||
if (!room) return cb && cb({ error: 'room not found' });
|
||||
if (!room.chess) return cb && cb({ error: 'legal moves not supported for custom board sizes yet', moves: [] });
|
||||
try{
|
||||
const moves = room.chess.moves({ square, verbose: true }) || [];
|
||||
return cb && cb({ ok: true, moves });
|
||||
}catch(e){
|
||||
return cb && cb({ error: 'invalid square', moves: [] });
|
||||
}
|
||||
});
|
||||
|
||||
// Simple cards API via sockets: list/play
|
||||
socket.on('card:list', ({ roomId }, cb) => {
|
||||
const room = rooms.get(roomId);
|
||||
if(!room) return cb && cb({ error: 'room not found' });
|
||||
// return available card types (placeholder)
|
||||
const available = [
|
||||
{ id: 'invert', name: 'Invert Turn', description: 'Swap movement directions for one move' },
|
||||
{ id: 'teleport', name: 'Teleport', description: 'Move one piece to any empty square' }
|
||||
];
|
||||
cb && cb({ ok: true, cards: available });
|
||||
});
|
||||
|
||||
socket.on('card:play', ({ roomId, playerId, cardId, payload }, cb) => {
|
||||
const room = rooms.get(roomId);
|
||||
if(!room) return cb && cb({ error: 'room not found' });
|
||||
// store played card (placeholder behaviour) and broadcast
|
||||
const played = { id: uuidv4().slice(0,8), playerId, cardId, payload, ts: Date.now() };
|
||||
room.playedCards = room.playedCards || [];
|
||||
room.playedCards.push(played);
|
||||
io.to(roomId).emit('card:played', played);
|
||||
cb && cb({ ok: true, played });
|
||||
});
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
|
|
|||
Loading…
Reference in a new issue