diff --git a/public/game.html b/public/game.html
index 0b1b3c7..353c3e1 100644
--- a/public/game.html
+++ b/public/game.html
@@ -20,9 +20,13 @@
- logs...
+
-
+
@@ -154,6 +158,23 @@
} else if(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:started', (data)=>{ log('game:started', data); if(data.boardState){ renderBoardFromState(data.boardState); updateTurnBanner(data.boardState); } });
@@ -165,6 +186,8 @@
showCardDrawnNotification(data);
}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
socket.on('card:played', (data) => {
try{
@@ -848,6 +871,28 @@
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);
handsEl.appendChild(wrap);
}
diff --git a/public/styles/style.css b/public/styles/style.css
index ef60c81..6280620 100644
--- a/public/styles/style.css
+++ b/public/styles/style.css
@@ -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-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 */
.btn-cancel{
background: #ffffff;
diff --git a/server.js b/server.js
index d2cc33b..9a3aff3 100644
--- a/server.js
+++ b/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).
// Returns a Set of square strings (e.g., 'e4').
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)
// Only draw if the player's hand is currently empty to avoid double-drawing (e.g. initial granted cards)
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 myShort = (myPlayer && myPlayer.color || '')[0];
if(myShort === room.boardState.turn){
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); }
@@ -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
const moved = { playerId: senderId, from, to };
try{
- if(!consumedDoubleMove){
+ if(!consumedDoubleMove){
const nextColor = board.turn; // 'w' or 'b'
const nextPlayer = room.players.find(p => (p.color && p.color[0]) === nextColor);
if(nextPlayer){
- // draw for next player (this will emit card:drawn privately and send personalized room:update)
- drawCardForPlayer(room, nextPlayer.id);
+ // draw for next player (respect room.autoDraw)
+ maybeDrawAtTurnStart(room, nextPlayer.id);
} else {
// ensure room state is broadcast
sendRoomUpdate(room);
@@ -955,7 +970,7 @@ io.on('connection', (socket) => {
const firstPlayer = room.players.find(p => (p.color && p.color[0]) === firstColor);
if(firstPlayer){
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){
@@ -1039,6 +1054,75 @@ io.on('connection', (socket) => {
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
socket.on('card:list', ({ roomId }, cb) => {
const room = rooms.get(roomId);
@@ -1440,7 +1524,7 @@ io.on('connection', (socket) => {
// draw for the next player at the start of their turn
const nextColor = board.turn;
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(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
const nextColor = board.turn;
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(_){ }
}