From cb0ba5eae2a6060e650fb679b8ad4571fcec3ef4 Mon Sep 17 00:00:00 2001 From: Didictateur Date: Fri, 7 Nov 2025 23:45:36 +0100 Subject: [PATCH] in the same game --- public/game.html | 81 +++++++++++++++++++++++++++++++++-------- public/waiting.html | 89 +++++++++++++++++++++++++++++++++++++-------- server.js | 31 ++++++++++++---- 3 files changed, 163 insertions(+), 38 deletions(-) diff --git a/public/game.html b/public/game.html index 4a3a636..65cbd99 100644 --- a/public/game.html +++ b/public/game.html @@ -8,15 +8,16 @@

ChessNut — Partie

Room: - Player: -
-
Fen: -
-
Joueurs: - -
- -

Contrôles (dev)

-
- From: To: Promotion: - +
+
+
Fen: -
+
Joueurs: +
    +
    +
    +
    +
    +

    Événements

    @@ -31,10 +32,11 @@ document.getElementById('roomId').textContent = roomId || '-'; document.getElementById('playerId').textContent = playerId || '-'; - const logEl = document.getElementById('log'); - const fenEl = document.getElementById('fen'); - const playersEl = document.getElementById('players'); - let socket = null; + 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; } @@ -53,10 +55,12 @@ li.textContent = p.id + ' (' + p.color + ')'; playersEl.appendChild(li); }); + if(data.fen) renderFen(data.fen); }); - socket.on('move:moved', (data)=>{ log('move:moved', data); fenEl.textContent = data.fen; }); - socket.on('game:over', (data)=>{ log('game:over', data); fenEl.textContent = 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.emit('room:join', { roomId, playerId }, (resp)=>{ if(resp && resp.error){ log('join error', resp); alert(resp.error); return; } @@ -76,6 +80,53 @@ log('move accepted', resp); }); }); + + // Board rendering using provided SVG assets. Accepts FEN. + const pieceSetPath = '/assets/chess-pieces/chess_1Kbyte_gambit'; + // set board background image (if available) + boardEl.style.backgroundImage = "url('/assets/chess-pieces/boards/8x8_wood.svg')"; + boardEl.style.backgroundSize = 'cover'; + boardEl.style.position = 'relative'; + + function renderFen(fen){ + if(!fen) return; + const parts = fen.split(' '); + const rows = parts[0].split('/'); + boardEl.innerHTML = ''; + for(let r=0;r<8;r++){ + const row = rows[r]; + let file = 0; + for(let i=0;i diff --git a/public/waiting.html b/public/waiting.html index a4cc47d..aa32207 100644 --- a/public/waiting.html +++ b/public/waiting.html @@ -11,6 +11,7 @@
    Room: -
    Status: -
    Joueurs: 0/2
    +
    Hôte: -
    @@ -32,6 +33,9 @@ let socket = null; let myPlayerId = null; let myColor = null; + let roomHostId = null; + const hostLabelEl = document.getElementById('hostLabel'); + const youAreHostEl = document.getElementById('youAreHost'); function log(...args){ console.log(...args); } @@ -41,11 +45,53 @@ socket = io(); socket.on('connect', ()=>{ log('socket connecté', socket.id); }); - socket.on('room:update', (data)=>{ + socket.on('room:update', (data)=>{ console.log('room:update', data); statusEl.textContent = data.status || '-'; const count = (data.players||[]).length; playerCountEl.textContent = `${count}/2`; + // remember hostId from server updates; if missing, fall back to first player + if(data.hostId !== undefined && data.hostId !== null) { + roomHostId = data.hostId; + } else if ((data.players || []).length > 0) { + // fallback: first player is host + roomHostId = data.players[0].id; + } else { + roomHostId = null; + } + + // update host label UI + if(hostLabelEl){ + hostLabelEl.textContent = roomHostId || '-'; + if(myPlayerId && roomHostId && myPlayerId === roomHostId){ + youAreHostEl.style.display = 'inline'; + } else { + youAreHostEl.style.display = 'none'; + } + } + + // enable start button only when there are 2 players and current user is host + const startBtn = document.getElementById('startBtn'); + if(startBtn){ + // host determined by explicit hostId sent by server (or fallback) + if(myPlayerId && roomHostId && myPlayerId === roomHostId){ + startBtn.style.display = 'inline-block'; + startBtn.disabled = count !== 2; + } else { + // hide for non-hosts + startBtn.style.display = 'none'; + } + } + }); + + // register game started handler early so we don't miss the broadcast + socket.on('game:started', (data)=>{ + log('game:started', data); + if(myPlayerId){ + window.location.href = `/game.html?roomId=${encodeURIComponent(roomId)}&playerId=${encodeURIComponent(myPlayerId)}`; + } else { + window.location.href = `/game.html?roomId=${encodeURIComponent(roomId)}`; + } }); socket.emit('room:join', { roomId, playerId }, (resp)=>{ @@ -58,11 +104,32 @@ } myPlayerId = resp.playerId; myColor = resp.color; - log('Rejoint room', resp); - // show start button only to host (white) - if(myColor === 'white'){ - document.getElementById('startBtn').style.display = 'inline-block'; + // server may or may not include hostId in the response; fall back to color + if(resp.hostId !== undefined && resp.hostId !== null){ + roomHostId = resp.hostId; + } else if(resp.color === 'white'){ + roomHostId = resp.playerId; // assume creator (first join) is white } + log('Rejoint room', resp); + // update host UI immediately + if(hostLabelEl){ hostLabelEl.textContent = roomHostId || '-'; } + if(youAreHostEl){ youAreHostEl.style.display = (myPlayerId && roomHostId && myPlayerId === roomHostId) ? 'inline' : 'none'; } + if(myPlayerId && roomHostId && myPlayerId === roomHostId){ + const btn = document.getElementById('startBtn'); + btn.style.display = 'inline-block'; + btn.disabled = true; + } + // after join, fetch room state to avoid missing a recent 'playing' state + fetch(`/rooms/${encodeURIComponent(roomId)}`).then(r => r.json()).then(roomInfo => { + if(roomInfo && roomInfo.status === 'playing'){ + // server already marked room as playing; redirect immediately + if(myPlayerId){ + window.location.href = `/game.html?roomId=${encodeURIComponent(roomId)}&playerId=${encodeURIComponent(myPlayerId)}`; + } else { + window.location.href = `/game.html?roomId=${encodeURIComponent(roomId)}`; + } + } + }).catch(e => { /* ignore */ }); }); } @@ -89,16 +156,8 @@ } }); - // when server tells us game started, redirect both players to game page with playerId preserved - socket && socket.on && socket.on('game:started', (data)=>{ - log('game:started', data); - // redirect, include playerId so client can rejoin as same player - if(myPlayerId){ - window.location.href = `/game.html?roomId=${encodeURIComponent(roomId)}&playerId=${encodeURIComponent(myPlayerId)}`; - } else { - window.location.href = `/game.html?roomId=${encodeURIComponent(roomId)}`; - } - }); + // fallback: if socket already existed and server emits game:started earlier, ensure handler exists + // (handled after join) diff --git a/server.js b/server.js index 2a1e3a4..757cc54 100644 --- a/server.js +++ b/server.js @@ -5,11 +5,14 @@ const { Chess } = require('chess.js'); const { v4: uuidv4 } = require('uuid'); const app = express(); +const path = require('path'); const server = http.createServer(app); const io = new Server(server, { cors: { origin: '*' } }); app.use(express.json()); app.use(express.static('public')); +// Serve provided assets (piece sets, boards, etc.) under /assets +app.use('/assets', express.static(path.join(__dirname, 'assets'))); // In-memory rooms store. For production replace with persistent store. // room = { id, chess: Chess, players: [{id, socketId, color}], status } @@ -18,14 +21,15 @@ const rooms = new Map(); app.post('/rooms', (req, res) => { const id = uuidv4().slice(0, 8); const chess = new Chess(); - rooms.set(id, { id, chess, players: [], status: 'waiting' }); + // hostId will be set when the first player joins + rooms.set(id, { id, chess, players: [], status: 'waiting', hostId: null }); res.json({ roomId: id }); }); 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 }); + 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 }); }); io.on('connection', (socket) => { @@ -43,14 +47,18 @@ io.on('connection', (socket) => { socket.data.roomId = roomId; socket.data.playerId = assignedId; + // set hostId when first player joins + if (!room.hostId) room.hostId = assignedId; + io.to(roomId).emit('room:update', { fen: room.chess.fen(), players: room.players.map(p => ({ id: p.id, color: p.color })), - status: room.status + status: room.status, + hostId: room.hostId }); - cb && cb({ ok: true, color, roomId, playerId: assignedId }); + cb && cb({ ok: true, color, roomId, playerId: assignedId, hostId: room.hostId }); }); socket.on('game:move', ({ roomId, from, to, promotion }, cb) => { @@ -85,15 +93,16 @@ io.on('connection', (socket) => { const senderId = socket.data.playerId; if (!senderId) return cb && cb({ error: 'not joined' }); if (room.players.length < 2) return cb && cb({ error: 'need 2 players to start' }); - const host = room.players[0]; - if (!host || host.id !== senderId) return cb && cb({ error: 'only host can start' }); + // verify sender is the host (explicit hostId) + if (!room.hostId || room.hostId !== senderId) return cb && cb({ error: 'only host can start' }); room.status = 'playing'; io.to(roomId).emit('game:started', { roomId, fen: room.chess.fen() }); io.to(roomId).emit('room:update', { fen: room.chess.fen(), players: room.players.map(p => ({ id: p.id, color: p.color })), - status: room.status + status: room.status, + hostId: room.hostId }); cb && cb({ ok: true }); @@ -108,10 +117,16 @@ io.on('connection', (socket) => { room.players = room.players.filter(p => p.socketId !== socket.id); if (room.players.length < 2 && room.status === 'playing') room.status = 'waiting'; + // if the host left, promote the next player to host (or null) + if (room.hostId && !room.players.find(p => p.id === room.hostId)) { + room.hostId = room.players[0] ? room.players[0].id : null; + } + io.to(roomId).emit('room:update', { fen: room.chess.fen(), players: room.players.map(p => ({ id: p.id, color: p.color })), - status: room.status + status: room.status, + hostId: room.hostId }); }); });