diff --git a/public/waiting.html b/public/waiting.html
index bcb678c..c9222a4 100644
--- a/public/waiting.html
+++ b/public/waiting.html
@@ -267,11 +267,26 @@
connectAndJoin();
- // Start button handler (host)
+ // Start button handler (host) — ensure custom deck is applied before starting
document.getElementById('startBtn').addEventListener('click', ()=>{
if(!socket) return;
+ // If custom draw is enabled and host selected cards, send the deck first
+ try{
+ if(customDrawToggleEl && customDrawToggleEl.checked && selectedCards.size && myPlayerId && roomHostId && myPlayerId === roomHostId){
+ socket.emit('room:deck:set', { roomId, selected: Array.from(selectedCards) }, (deckResp)=>{
+ if(deckResp && deckResp.error){ showToast(deckResp.message || deckResp.error, { background: 'rgba(200,60,60,0.95)' }); return; }
+ // now start the game
+ socket.emit('game:start', { roomId }, (resp)=>{
+ if(resp && resp.error){ log('start error', resp); showToast(resp.error || resp.message || 'Erreur', { background: 'rgba(200,60,60,0.95)' }); return; }
+ log('game:start acknowledged');
+ });
+ });
+ return;
+ }
+ }catch(_){ }
+ // fallback: start immediately
socket.emit('game:start', { roomId }, (resp)=>{
- if(resp && resp.error){ log('start error', resp); showToast(resp.error || resp.message || 'Erreur', { background: 'rgba(200,60,60,0.95)' }); return; }
+ if(resp && resp.error){ log('start error', resp); showToast(resp.error || resp.message || 'Erreur', { background: 'rgba(200,60,60,0.95)' }); return; }
log('game:start acknowledged');
});
});
@@ -311,6 +326,26 @@
const checked = !!customDrawToggleEl.checked;
log('custom-draw toggled (minimal)', { checked });
if(customCardsSection) customCardsSection.style.display = checked ? 'block' : 'none';
+ // When toggling the checkbox, inform server: if unchecked, clear custom deck on server so default deck is used.
+ try{
+ if(socket && socket.connected && myPlayerId && roomHostId && myPlayerId === roomHostId){
+ if(!checked){
+ // inform server to revert to default deck
+ socket.emit('room:deck:set', { roomId, selected: [] }, (resp)=>{
+ if(resp && resp.error) showToast(resp.message || resp.error, { background: 'rgba(200,60,60,0.95)' });
+ else showToast('Pioche personnalisée désactivée — pioche par défaut restaurée', { background: 'rgba(40,160,40,0.95)' });
+ });
+ } else {
+ // if checked and we already have a selection, push it to server
+ if(selectedCards.size){
+ socket.emit('room:deck:set', { roomId, selected: Array.from(selectedCards) }, (resp)=>{
+ if(resp && resp.error) showToast(resp.message || resp.error, { background: 'rgba(200,60,60,0.95)' });
+ else showToast('Pioche personnalisée activée', { background: 'rgba(40,160,40,0.95)' });
+ });
+ }
+ }
+ }
+ }catch(_){ }
if(checked){
// fetch card list and render simple names
if(customCardsContainer){
@@ -379,6 +414,15 @@
selectMark.style.display = 'flex';
}
saveSelection();
+ // if we're the host and connected AND custom-draw is enabled, update the server's custom deck selection
+ try{
+ if(socket && socket.connected && myPlayerId && roomHostId && myPlayerId === roomHostId && customDrawToggleEl && customDrawToggleEl.checked){
+ socket.emit('room:deck:set', { roomId, selected: Array.from(selectedCards) }, (resp)=>{
+ if(resp && resp.error){ showToast(resp.message || resp.error, { background: 'rgba(200,60,60,0.95)' }); }
+ else { showToast('Pioche personnalisée mise à jour', { background: 'rgba(40,160,40,0.95)' }); }
+ });
+ }
+ }catch(_){ }
});
grid.appendChild(cardEl);
@@ -400,6 +444,15 @@
else { selectedCards.delete(id); el.classList.remove('selected'); if(mark) mark.style.display = 'none'; }
});
saveSelection();
+ // send selection to server if host AND custom-draw enabled
+ try{
+ if(socket && socket.connected && myPlayerId && roomHostId && myPlayerId === roomHostId && customDrawToggleEl && customDrawToggleEl.checked){
+ socket.emit('room:deck:set', { roomId, selected: Array.from(selectedCards) }, (resp)=>{
+ if(resp && resp.error) showToast(resp.message || resp.error, { background: 'rgba(200,60,60,0.95)' });
+ else showToast('Pioche personnalisée mise à jour', { background: 'rgba(40,160,40,0.95)' });
+ });
+ }
+ }catch(_){ }
}
if(customSelectAllBtn) customSelectAllBtn.addEventListener('click', (e)=>{ e.preventDefault(); setAllVisible(true); });
if(customDeselectAllBtn) customDeselectAllBtn.addEventListener('click', (e)=>{ e.preventDefault(); setAllVisible(false); });
diff --git a/server.js b/server.js
index 7d10081..14717c6 100644
--- a/server.js
+++ b/server.js
@@ -303,7 +303,9 @@ function buildDefaultDeck(){
let ascii = normalized;
try{ ascii = ascii.normalize('NFD').replace(/\p{Diacritic}/gu, ''); }catch(e){ /* ignore if normalize unsupported */ }
const cardId = ascii.replace(/[^a-z0-9]+/gi,'_').toLowerCase();
- return { id: uuidv4().slice(0,8), cardId, title: cap(normalized), description: desc };
+ // Use deterministic id equal to the slug so clients and server agree on identifiers across requests
+ const id = cardId;
+ return { id, cardId, title: cap(normalized), description: desc };
});
}
@@ -1251,6 +1253,38 @@ io.on('connection', (socket) => {
cb && cb({ ok: true });
});
+ // Host can set a custom deck for the room (selected list of card ids). Only host may change it.
+ socket.on('room:deck:set', ({ roomId, selected }, cb) => {
+ const room = rooms.get(roomId);
+ if(!room) return cb && cb({ error: 'room not found', message: "Aucune salle trouvée." });
+ const sender = socket.data.playerId;
+ if(!sender) return cb && cb({ error: 'not joined', message: "Vous n'avez pas rejoint la salle." });
+ if(room.hostId !== sender) return cb && cb({ error: 'only host can set deck', message: "Seul l'hôte peut définir la pioche." });
+ try{
+ // selected is expected to be an array of card identifiers (either id or cardId)
+ const sel = Array.isArray(selected) ? selected : [];
+ // Build a lookup from the canonical default deck and current room deck
+ const master = buildDefaultDeck();
+ const pool = (room.deck && Array.isArray(room.deck) && room.deck.length) ? room.deck.concat(master) : master;
+ const byId = {};
+ pool.forEach(c => { if(c && c.id) byId[c.id] = c; if(c && c.cardId) byId[c.cardId] = c; });
+ // Map selected ids to card objects, preserving order. If a selected id is unknown, ignore it.
+ const newDeck = [];
+ sel.forEach(sid => { const c = byId[sid]; if(c) newDeck.push(Object.assign({}, c)); });
+ if(newDeck.length === 0){
+ // empty selection -> revert to default deck
+ room.deck = buildDefaultDeck();
+ } else {
+ room.deck = newDeck;
+ }
+ // reset discard and hands so the new deck applies for the upcoming game
+ room.discard = room.discard || [];
+ // inform players
+ sendRoomUpdate(room);
+ return cb && cb({ ok: true, deckCount: room.deck.length });
+ }catch(err){ console.error('room:deck:set error', err); return cb && cb({ error: 'server_error' }); }
+ });
+
socket.on('disconnect', () => {
const roomId = socket.data.roomId;
if (!roomId) return;