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...
+
+
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(_){ } }