in the same game
This commit is contained in:
parent
c87c9bb763
commit
cb0ba5eae2
3 changed files with 163 additions and 38 deletions
|
|
@ -8,15 +8,16 @@
|
|||
<body>
|
||||
<h2>ChessNut — Partie</h2>
|
||||
<div>Room: <strong id="roomId">-</strong> Player: <strong id="playerId">-</strong></div>
|
||||
<div style="display:flex;gap:20px;align-items:flex-start">
|
||||
<div>
|
||||
<div>Fen: <span id="fen">-</span></div>
|
||||
<div>Joueurs:
|
||||
<ul id="players"></ul>
|
||||
</div>
|
||||
|
||||
<h3>Contrôles (dev)</h3>
|
||||
</div>
|
||||
<div>
|
||||
From: <input id="from" size="5" /> To: <input id="to" size="5" /> Promotion: <input id="promo" size="3" />
|
||||
<button id="move">Envoyer coup</button>
|
||||
<div id="board" style="width:360px;height:360px;display:grid;grid-template-columns:repeat(8,1fr);border:1px solid #333"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Événements</h3>
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
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<row.length;i++){
|
||||
const ch = row[i];
|
||||
if(/[1-8]/.test(ch)){
|
||||
const empties = parseInt(ch,10);
|
||||
for(let e=0;e<empties;e++){
|
||||
const sq = document.createElement('div');
|
||||
sq.style.width = '100%'; sq.style.height = '100%';
|
||||
sq.style.display = 'flex'; sq.style.alignItems='center'; sq.style.justifyContent='center';
|
||||
boardEl.appendChild(sq);
|
||||
file++;
|
||||
}
|
||||
} else {
|
||||
const sq = document.createElement('div');
|
||||
sq.style.width = '100%'; sq.style.height = '100%';
|
||||
sq.style.display = 'flex'; sq.style.alignItems='center'; sq.style.justifyContent='center';
|
||||
// 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.style.maxWidth = '78%';
|
||||
img.style.maxHeight = '78%';
|
||||
img.style.userSelect = 'none';
|
||||
sq.appendChild(img);
|
||||
boardEl.appendChild(sq);
|
||||
file++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<div>Room: <strong id="roomId">-</strong></div>
|
||||
<div>Status: <strong id="status">-</strong></div>
|
||||
<div>Joueurs: <strong id="playerCount">0/2</strong></div>
|
||||
<div>Hôte: <strong id="hostLabel">-</strong> <span id="youAreHost" style="display:none;color:green;font-weight:bold;margin-left:8px">(Vous êtes l'hôte)</span></div>
|
||||
</div>
|
||||
<div style="margin-top:8px">
|
||||
<button id="startBtn" style="display:none">Commencer la partie</button>
|
||||
|
|
@ -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); }
|
||||
|
||||
|
|
@ -46,6 +50,48 @@
|
|||
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)
|
||||
|
||||
|
||||
</script>
|
||||
|
|
|
|||
31
server.js
31
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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue