bouton de pioche
This commit is contained in:
parent
a73e07f510
commit
18403495eb
3 changed files with 147 additions and 10 deletions
|
|
@ -20,9 +20,13 @@
|
||||||
<div style="margin-top:8px">
|
<div style="margin-top:8px">
|
||||||
<!-- movement controls removed as requested -->
|
<!-- movement controls removed as requested -->
|
||||||
</div>
|
</div>
|
||||||
<pre id="log" style="margin-top:8px;height:160px;overflow:auto">logs...</pre>
|
<div style="margin-top:8px;display:flex;gap:8px;align-items:center">
|
||||||
|
<pre id="log" style="margin-top:0px;height:140px;overflow:auto;flex:1">logs...</pre>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div id="hands" style="margin-top:8px;display:flex;flex-direction:column;gap:8px"></div>
|
<div style="margin-top:10px;display:flex;align-items:center;gap:12px">
|
||||||
|
<div id="hands" style="display:flex;flex-direction:column;gap:8px;flex:1;margin-left:6px"></div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -154,6 +158,23 @@
|
||||||
} else if(data.handsOwn || data.handCounts){
|
} else if(data.handsOwn || data.handCounts){
|
||||||
renderHands(data.handsOwn || [], data.handCounts || {});
|
renderHands(data.handsOwn || [], data.handCounts || {});
|
||||||
}
|
}
|
||||||
|
// show/hide draw button: only when autoDraw is OFF and it's this player's turn
|
||||||
|
try{
|
||||||
|
const drawBtn = document.getElementById('drawBtn');
|
||||||
|
if(drawBtn){
|
||||||
|
const auto = !!data.autoDraw;
|
||||||
|
const board = data.boardState || null;
|
||||||
|
const myShort = myColor && myColor[0];
|
||||||
|
if(!auto && board && myShort && board.turn === myShort){
|
||||||
|
// enable draw if hand not full
|
||||||
|
const myHandLen = (data.handsOwn && data.handsOwn.length) || 0;
|
||||||
|
drawBtn.style.display = 'inline-block';
|
||||||
|
drawBtn.disabled = myHandLen >= 5;
|
||||||
|
} else {
|
||||||
|
drawBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(_){ }
|
||||||
});
|
});
|
||||||
socket.on('game:over', (data)=>{ log('game:over', data); if(data.boardState){ renderBoardFromState(data.boardState); updateTurnBanner(data.boardState); } });
|
socket.on('game:over', (data)=>{ log('game:over', data); if(data.boardState){ renderBoardFromState(data.boardState); updateTurnBanner(data.boardState); } });
|
||||||
socket.on('game:started', (data)=>{ log('game:started', data); if(data.boardState){ renderBoardFromState(data.boardState); updateTurnBanner(data.boardState); } });
|
socket.on('game:started', (data)=>{ log('game:started', data); if(data.boardState){ renderBoardFromState(data.boardState); updateTurnBanner(data.boardState); } });
|
||||||
|
|
@ -165,6 +186,8 @@
|
||||||
showCardDrawnNotification(data);
|
showCardDrawnNotification(data);
|
||||||
}catch(e){ console.warn('card:drawn handler error', e); }
|
}catch(e){ console.warn('card:drawn handler error', e); }
|
||||||
});
|
});
|
||||||
|
// when server broadcasts generic room updates, it includes autoDraw flag
|
||||||
|
// ensure draw button visibility is updated inside the room:update handler below
|
||||||
// when a card is played by any player, ensure the UI removes it from the owner's hand
|
// when a card is played by any player, ensure the UI removes it from the owner's hand
|
||||||
socket.on('card:played', (data) => {
|
socket.on('card:played', (data) => {
|
||||||
try{
|
try{
|
||||||
|
|
@ -848,6 +871,28 @@
|
||||||
row.appendChild(cardEl);
|
row.appendChild(cardEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// draw button inside the 'Vos cartes' header (visible only when autoDraw is off and it's your turn)
|
||||||
|
try{
|
||||||
|
const drawBtn = document.createElement('button');
|
||||||
|
drawBtn.id = 'drawBtn';
|
||||||
|
drawBtn.type = 'button';
|
||||||
|
drawBtn.className = 'btn-draw';
|
||||||
|
drawBtn.style.display = 'none';
|
||||||
|
drawBtn.textContent = 'Piocher';
|
||||||
|
// attach handler that emits player:draw and temporarily disables the button
|
||||||
|
drawBtn.addEventListener('click', (ev)=>{
|
||||||
|
ev.stopPropagation();
|
||||||
|
if(!socket) return;
|
||||||
|
drawBtn.disabled = true;
|
||||||
|
socket.emit('player:draw', { roomId }, (resp)=>{
|
||||||
|
if(resp && resp.error){ log('player:draw error', resp); alert(resp.error); drawBtn.disabled = false; return; }
|
||||||
|
log('player:draw ok', resp);
|
||||||
|
// server will send room:update which will hide/disable the button as needed
|
||||||
|
});
|
||||||
|
});
|
||||||
|
header.appendChild(drawBtn);
|
||||||
|
}catch(_){ }
|
||||||
|
|
||||||
wrap.appendChild(row);
|
wrap.appendChild(row);
|
||||||
handsEl.appendChild(wrap);
|
handsEl.appendChild(wrap);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,14 @@ a:hover{text-decoration:underline}
|
||||||
.btn-ghost{background:transparent;border:1px solid rgba(11,18,32,0.06);padding:8px 10px;border-radius:8px;color:inherit}
|
.btn-ghost{background:transparent;border:1px solid rgba(11,18,32,0.06);padding:8px 10px;border-radius:8px;color:inherit}
|
||||||
.btn-cta{background:linear-gradient(90deg,#3b82f6,#60a5fa);box-shadow:0 8px 20px rgba(59,130,246,0.18)}
|
.btn-cta{background:linear-gradient(90deg,#3b82f6,#60a5fa);box-shadow:0 8px 20px rgba(59,130,246,0.18)}
|
||||||
|
|
||||||
|
/* draw button: compact rectangular CTA used in the cards area */
|
||||||
|
.btn-draw{
|
||||||
|
background:linear-gradient(90deg,#10b981,#059669); /* green gradient */
|
||||||
|
color:white;padding:10px 14px;border-radius:8px;border:none;cursor:pointer;font-weight:800;font-size:14px;
|
||||||
|
min-width:140px;height:44px;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 8px 18px rgba(6,95,70,0.12);
|
||||||
|
}
|
||||||
|
.btn-draw[disabled]{opacity:0.6;cursor:not-allowed}
|
||||||
|
|
||||||
/* cancel button shown during pending card selection: white background, red text for clear contrast */
|
/* cancel button shown during pending card selection: white background, red text for clear contrast */
|
||||||
.btn-cancel{
|
.btn-cancel{
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|
|
||||||
100
server.js
100
server.js
|
|
@ -86,6 +86,21 @@ function sendRoomUpdate(room){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: at the start of a player's turn, either perform an automatic draw
|
||||||
|
// if room.autoDraw is enabled, or simply broadcast the room state so clients
|
||||||
|
// can update UI. This centralizes the auto-draw toggle behavior.
|
||||||
|
function maybeDrawAtTurnStart(room, playerId){
|
||||||
|
try{
|
||||||
|
if(!room) return;
|
||||||
|
if(room.autoDraw){
|
||||||
|
drawCardForPlayer(room, playerId);
|
||||||
|
} else {
|
||||||
|
// ensure clients get the updated room state even if no draw happens
|
||||||
|
sendRoomUpdate(room);
|
||||||
|
}
|
||||||
|
}catch(e){ console.error('maybeDrawAtTurnStart error', e); sendRoomUpdate(room); }
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the set of squares visible to a given player (adjacent to any of their pieces).
|
// Compute the set of squares visible to a given player (adjacent to any of their pieces).
|
||||||
// Returns a Set of square strings (e.g., 'e4').
|
// Returns a Set of square strings (e.g., 'e4').
|
||||||
function visibleSquaresForPlayer(room, playerId){
|
function visibleSquaresForPlayer(room, playerId){
|
||||||
|
|
@ -681,12 +696,12 @@ io.on('connection', (socket) => {
|
||||||
// If the game is already playing and it's this player's turn, attempt to draw (useful on reconnect)
|
// If the game is already playing and it's this player's turn, attempt to draw (useful on reconnect)
|
||||||
// Only draw if the player's hand is currently empty to avoid double-drawing (e.g. initial granted cards)
|
// Only draw if the player's hand is currently empty to avoid double-drawing (e.g. initial granted cards)
|
||||||
try{
|
try{
|
||||||
if(room.status === 'playing' && room.boardState && room.boardState.turn){
|
if(room.status === 'playing' && room.boardState && room.boardState.turn){
|
||||||
const myPlayer = room.players.find(p => p.id === assignedId);
|
const myPlayer = room.players.find(p => p.id === assignedId);
|
||||||
const myShort = (myPlayer && myPlayer.color || '')[0];
|
const myShort = (myPlayer && myPlayer.color || '')[0];
|
||||||
if(myShort === room.boardState.turn){
|
if(myShort === room.boardState.turn){
|
||||||
const hasHand = room.hands && room.hands[assignedId] && room.hands[assignedId].length > 0;
|
const hasHand = room.hands && room.hands[assignedId] && room.hands[assignedId].length > 0;
|
||||||
if(!hasHand) drawCardForPlayer(room, assignedId);
|
if(!hasHand) maybeDrawAtTurnStart(room, assignedId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch(e){ console.error('post-join draw error', e); }
|
}catch(e){ console.error('post-join draw error', e); }
|
||||||
|
|
@ -902,12 +917,12 @@ io.on('connection', (socket) => {
|
||||||
// determine next player and perform their draw BEFORE broadcasting the move, so the draw happens at the start of their turn
|
// determine next player and perform their draw BEFORE broadcasting the move, so the draw happens at the start of their turn
|
||||||
const moved = { playerId: senderId, from, to };
|
const moved = { playerId: senderId, from, to };
|
||||||
try{
|
try{
|
||||||
if(!consumedDoubleMove){
|
if(!consumedDoubleMove){
|
||||||
const nextColor = board.turn; // 'w' or 'b'
|
const nextColor = board.turn; // 'w' or 'b'
|
||||||
const nextPlayer = room.players.find(p => (p.color && p.color[0]) === nextColor);
|
const nextPlayer = room.players.find(p => (p.color && p.color[0]) === nextColor);
|
||||||
if(nextPlayer){
|
if(nextPlayer){
|
||||||
// draw for next player (this will emit card:drawn privately and send personalized room:update)
|
// draw for next player (respect room.autoDraw)
|
||||||
drawCardForPlayer(room, nextPlayer.id);
|
maybeDrawAtTurnStart(room, nextPlayer.id);
|
||||||
} else {
|
} else {
|
||||||
// ensure room state is broadcast
|
// ensure room state is broadcast
|
||||||
sendRoomUpdate(room);
|
sendRoomUpdate(room);
|
||||||
|
|
@ -955,7 +970,7 @@ io.on('connection', (socket) => {
|
||||||
const firstPlayer = room.players.find(p => (p.color && p.color[0]) === firstColor);
|
const firstPlayer = room.players.find(p => (p.color && p.color[0]) === firstColor);
|
||||||
if(firstPlayer){
|
if(firstPlayer){
|
||||||
const hasHand = room.hands && room.hands[firstPlayer.id] && room.hands[firstPlayer.id].length > 0;
|
const hasHand = room.hands && room.hands[firstPlayer.id] && room.hands[firstPlayer.id].length > 0;
|
||||||
if(!hasHand) drawCardForPlayer(room, firstPlayer.id);
|
if(!hasHand) maybeDrawAtTurnStart(room, firstPlayer.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
@ -1039,6 +1054,75 @@ io.on('connection', (socket) => {
|
||||||
cb && cb({ ok: true });
|
cb && cb({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Manual draw by player (consumes the player's turn). Only valid when autoDraw is disabled.
|
||||||
|
socket.on('player:draw', ({ roomId }, cb) => {
|
||||||
|
const room = rooms.get(roomId);
|
||||||
|
if(!room) return cb && cb({ error: 'room not found' });
|
||||||
|
const senderId = socket.data.playerId;
|
||||||
|
if(!senderId) return cb && cb({ error: 'not joined' });
|
||||||
|
const board = room.boardState;
|
||||||
|
if(!board) return cb && cb({ error: 'no board state' });
|
||||||
|
const roomPlayer = room.players.find(p => p.id === senderId);
|
||||||
|
if(!roomPlayer) return cb && cb({ error: 'player not in room' });
|
||||||
|
const playerColorShort = (roomPlayer.color && roomPlayer.color[0]) || null;
|
||||||
|
// must be player's turn
|
||||||
|
if(board.turn !== playerColorShort) return cb && cb({ error: 'not your turn' });
|
||||||
|
// manual draw only allowed when autoDraw is disabled
|
||||||
|
if(room.autoDraw) return cb && cb({ error: 'auto_draw_enabled' });
|
||||||
|
// do not allow drawing if player already played a card this turn
|
||||||
|
room._cardPlayedThisTurn = room._cardPlayedThisTurn || {};
|
||||||
|
if(room._cardPlayedThisTurn[senderId]) return cb && cb({ error: 'card_already_played_this_turn' });
|
||||||
|
|
||||||
|
try{
|
||||||
|
// perform the draw (this will emit card:drawn privately and send personalized room:update)
|
||||||
|
const drawn = drawCardForPlayer(room, senderId);
|
||||||
|
if(!drawn){
|
||||||
|
// nothing drawn (hand full or deck empty)
|
||||||
|
return cb && cb({ error: 'no_card_drawn' });
|
||||||
|
}
|
||||||
|
// drawing consumes the player's turn: flip turn and decrement per-turn effects
|
||||||
|
// advance board version
|
||||||
|
board.version = (board.version || 0) + 1;
|
||||||
|
// flip turn
|
||||||
|
board.turn = (board.turn === 'w') ? 'b' : 'w';
|
||||||
|
// decrement remainingTurns for time-limited effects that belong to the player who just finished their turn
|
||||||
|
try{
|
||||||
|
room.activeCardEffects = room.activeCardEffects || [];
|
||||||
|
for(let i = room.activeCardEffects.length - 1; i >= 0; i--){
|
||||||
|
const e = room.activeCardEffects[i];
|
||||||
|
if(typeof e.remainingTurns === 'number'){
|
||||||
|
let shouldDecrement = false;
|
||||||
|
if(e.decrementOn === 'opponent'){
|
||||||
|
shouldDecrement = (e.playerId !== senderId);
|
||||||
|
} else if(e.decrementOn === 'owner'){
|
||||||
|
shouldDecrement = (e.playerId === senderId);
|
||||||
|
} else {
|
||||||
|
shouldDecrement = (e.playerId === senderId);
|
||||||
|
}
|
||||||
|
if(shouldDecrement){
|
||||||
|
e.remainingTurns = e.remainingTurns - 1;
|
||||||
|
try{ io.to(room.id).emit('card:effect:updated', { roomId: room.id, effect: e }); }catch(_){ }
|
||||||
|
if(e.remainingTurns <= 0){ room.activeCardEffects.splice(i,1); try{ io.to(room.id).emit('card:effect:removed', { roomId: room.id, effectId: e.id, type: e.type, playerId: e.playerId }); }catch(_){ } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(e){ console.error('decrement-after-draw error', e); }
|
||||||
|
|
||||||
|
// reset per-turn card-play flags
|
||||||
|
try{ room._cardPlayedThisTurn = {}; }catch(_){ }
|
||||||
|
|
||||||
|
// at this point it's the next player's turn; perform start-of-turn draw if autoDraw enabled
|
||||||
|
const nextColor = board.turn;
|
||||||
|
const nextPlayer = (room.players || []).find(p => (p.color && p.color[0]) === nextColor);
|
||||||
|
if(nextPlayer){ try{ maybeDrawAtTurnStart(room, nextPlayer.id); }catch(_){ sendRoomUpdate(room); } }
|
||||||
|
else { sendRoomUpdate(room); }
|
||||||
|
|
||||||
|
// notify room that the player drew and ended their turn
|
||||||
|
try{ io.to(room.id).emit('player:drew', { roomId: room.id, playerId: senderId, card: drawn }); }catch(_){ }
|
||||||
|
return cb && cb({ ok: true, card: drawn });
|
||||||
|
}catch(err){ console.error('player:draw error', err); return cb && cb({ error: 'server_error' }); }
|
||||||
|
});
|
||||||
|
|
||||||
// Simple cards API via sockets: list/play
|
// Simple cards API via sockets: list/play
|
||||||
socket.on('card:list', ({ roomId }, cb) => {
|
socket.on('card:list', ({ roomId }, cb) => {
|
||||||
const room = rooms.get(roomId);
|
const room = rooms.get(roomId);
|
||||||
|
|
@ -1440,7 +1524,7 @@ io.on('connection', (socket) => {
|
||||||
// draw for the next player at the start of their turn
|
// draw for the next player at the start of their turn
|
||||||
const nextColor = board.turn;
|
const nextColor = board.turn;
|
||||||
const nextPlayer = (room.players || []).find(p => (p.color && p.color[0]) === nextColor);
|
const nextPlayer = (room.players || []).find(p => (p.color && p.color[0]) === nextColor);
|
||||||
if(nextPlayer){ try{ drawCardForPlayer(room, nextPlayer.id); }catch(_){ } }
|
if(nextPlayer){ try{ maybeDrawAtTurnStart(room, nextPlayer.id); }catch(_){ } }
|
||||||
}
|
}
|
||||||
}catch(_){ }
|
}catch(_){ }
|
||||||
}catch(e){ console.error('kamikaz effect error', e); }
|
}catch(e){ console.error('kamikaz effect error', e); }
|
||||||
|
|
@ -1713,7 +1797,7 @@ io.on('connection', (socket) => {
|
||||||
// draw for the next player at the start of their turn
|
// draw for the next player at the start of their turn
|
||||||
const nextColor = board.turn;
|
const nextColor = board.turn;
|
||||||
const nextPlayer = (room.players || []).find(p => (p.color && p.color[0]) === nextColor);
|
const nextPlayer = (room.players || []).find(p => (p.color && p.color[0]) === nextColor);
|
||||||
if(nextPlayer){ try{ drawCardForPlayer(room, nextPlayer.id); }catch(_){ } }
|
if(nextPlayer){ try{ maybeDrawAtTurnStart(room, nextPlayer.id); }catch(_){ } }
|
||||||
}
|
}
|
||||||
}catch(_){ }
|
}catch(_){ }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue