purification du code

This commit is contained in:
Didictateur 2025-11-29 22:33:12 +01:00
parent 5db522f7c2
commit ebbeecbe75
3 changed files with 302 additions and 619 deletions

View file

@ -44,7 +44,6 @@
<section class="card" style="margin-top:12px">
<div><strong>Outils / Logs</strong></div>
<div style="margin-top:8px">
<!-- movement controls removed as requested -->
</div>
<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>
@ -70,32 +69,24 @@
if(roomIdEl) roomIdEl.textContent = roomId || '-';
if(playerIdEl) playerIdEl.textContent = playerId || '-';
// optional UI elements: support removing the sidebar/sections without breaking JS
const logEl = document.getElementById('log');
const playersEl = document.getElementById('players');
const boardEl = document.getElementById('board');
const myColorEl = document.getElementById('myColor');
let socket = null;
let myColor = null;
// map playerId -> color (kept up-to-date from room:update)
let playerColors = {};
// selection state for this client
let selectedSquare = null; // algebraic name like 'e2'
let selectedSquare = null;
let selectedSquareEl = null;
let myPlayerId = playerId || null;
// track remote selections: { playerId: square }
const remoteSelections = {};
// whether this client stayed to spectate after game over
let isSpectating = false;
// track remote moves: { playerId: [moves] }
const remoteMoves = {};
// current boardState cached locally
let currentBoardState = null;
let myMines = [];
let currentVeiledSquares = [];
let pendingCard = null; // when set, next square click is used as card target
let pendingCard = null;
// safe logger: prefer on-page log if present, otherwise console
function log(...args){
if(logEl){
try{
@ -107,23 +98,18 @@
}
}
// append a user-facing activity message (visible to players). Keeps recent N entries.
function appendActivity(message){
try{
const feed = document.getElementById('activityFeed');
if(!feed) return;
const line = document.createElement('div');
// show only the message (no timestamp)
line.textContent = message;
// prepend newest at top
if(feed.textContent && feed.textContent.indexOf("Aucune activité") !== -1){ feed.textContent = ''; }
feed.insertBefore(line, feed.firstChild);
// limit to 30 entries
while(feed.children.length > 30) feed.removeChild(feed.lastChild);
}catch(_){ }
}
// small toast notification helper (non-blocking)
function showToast(message, opts){
try{
opts = opts || {};
@ -147,9 +133,6 @@
}catch(_){ }
}
// Robust card:play emitter with refresh fallback.
// Sometimes the client or network misses the room:update after playing a card —
// this helper emits card:play and requests a room refresh if no ack/update is received.
function emitCardPlay(payload, cb){
try{
if(!socket) return cb && cb({ error: 'no_socket' });
@ -161,65 +144,46 @@
}, 1500);
socket.emit('card:play', payload, (resp)=>{
acked = true; try{ clearTimeout(timer); }catch(_){ }
// ask server to push a fresh room:update to ensure UIs are in sync
try{ socket.emit('room:refresh', { roomId }, ()=>{}); }catch(_){ }
if(cb) cb(resp);
});
}catch(e){ if(cb) cb({ error: 'emit_failed' }); }
}
// play a short shuffle animation (shake + fade) when pieces are mixed
function playShuffleAnimation(){
if(!boardEl) return;
try{
boardEl.classList.add('shuffle-play');
// remove after animation duration
setTimeout(()=>{ try{ boardEl.classList.remove('shuffle-play'); }catch(_){ } }, 700);
}catch(_){ }
}
// helper: decide if a card requires a target selected after clicking 'Jouer'
function cardRequiresTarget(cardId){
if(!cardId) return null;
// normalize and strip diacritics so titles like "Téléportation" become "teleportation"
let id = String(cardId).toLowerCase();
try{
id = id.normalize('NFD').replace(/\p{Diacritic}/gu, '');
}catch(e){
// fallback: remove non-ascii characters
id = id.replace(/[^\x00-\x7F]/g, '');
}
if(id.indexOf('mine') !== -1) return 'empty';
// revolution is a global immediate effect: it should NOT require any piece selection
if(id.indexOf('revol') !== -1) return null;
// sniper: select one of your pieces to bind the sniper effect (capture later without moving)
if(id.indexOf('sniper') !== -1) return 'owned';
// doppelganger: select one of your pieces after playing the card (minimal flow)
if(id.indexOf('doppel') !== -1) return 'owned';
// 'toucher c'est jouer' : require selecting an enemy piece as the card target
if(id.indexOf('toucher') !== -1) return 'enemy';
// la parrure: select an enemy queen to downgrade to a pawn
if(id.indexOf('parrure') !== -1) return 'enemy_queen';
// tout ou rien: select a piece (any, except kings) to force it to only move when capturing
if(id.indexOf('tout') !== -1 && id.indexOf('rien') !== -1) return 'piece';
// kamikaz targets one of your pieces (play card, then choose a piece)
if(id.indexOf('tout') !== -1) return 'piece';
if(id.indexOf('kamikaz') !== -1) return 'owned';
if(id.indexOf('invis') !== -1 || id.indexOf('invisible') !== -1) return 'owned';
if(id.indexOf('invisible') !== -1) return 'owned';
if(id.indexOf('rebond') !== -1) return 'owned';
if(id.indexOf('adoub') !== -1) return 'owned';
if(id.indexOf('folie') !== -1 || id.indexOf('fou') !== -1) return 'owned';
if(id.indexOf('fortif') !== -1 || id.indexOf('fortification') !== -1) return 'owned';
// promotion targets one of your pawns (special handling client-side to show promotion choice)
if(id.indexOf('promotion') !== -1 || id.indexOf('promot') !== -1 || id.indexOf('promouvoir') !== -1) return 'owned_pawn';
// steal a card (target a player) if the card mentions 'carte'/'card' — otherwise a 'vol' often targets a piece
if(id.indexOf('vol') !== -1 || id.indexOf('vole') !== -1 || id.indexOf('steal') !== -1){
if(id.indexOf('carte') !== -1 || id.indexOf('card') !== -1) return 'player';
return 'enemy';
}
if(id.indexOf('coin') !== -1 || id === 'coin_coin') return 'owned';
// teleportation: require selecting one of your pieces first (like promotion)
if(id.indexOf('teleport') !== -1 || id.indexOf('tele') !== -1 || id.indexOf('t_l_portation') !== -1) return 'owned';
// inversion: first choose one of your pieces, then choose an enemy piece to swap places
if(id.indexOf('folie') !== -1) return 'owned';
if(id.indexOf('fortification') !== -1) return 'owned';
if(id.indexOf('promotion') !== -1) return 'owned_pawn_then_enemy';
if(id.indexOf('vol_carte') !== -1) return 'player';
if(id.indexOf('vole_piece') !== -1) return 'enemy';
if(id.indexOf('coincoin') !== -1) return 'owned';
if(id.indexOf('teleportation') !== -1) return 'owned';
if(id.indexOf('inversion') !== -1) return 'own_then_enemy';
return null;
}
@ -232,13 +196,10 @@
socket.on('room:update', (data)=>{
log('room:update', data);
// server now sends `boardState` (JSON). renderBoardFromState will handle it.
if(playersEl){
playersEl.innerHTML = '';
(data.players||[]).forEach(p=>{
// keep a mapping from playerId -> color for activity feed formatting
try{ playerColors[p.id] = p.color; }catch(_){ }
// if this is our player, store the color locally and update UI
try{ if(p.id === myPlayerId){ myColor = p.color; if(myColorEl) myColorEl.textContent = myColor; } }catch(_){ }
const li = document.createElement('li');
li.textContent = (p.color || 'Joueur') + (p.id === myPlayerId ? ' (vous)' : '');
@ -246,29 +207,20 @@
});
}
if(data.size){
// ensure grid layout matches requested size and build squares
buildGrid(data.size);
// adjust board width/height responsively (keep squares roughly proportional)
const side = Math.min(800, Math.max(320, data.size * 80));
boardEl.style.width = side + 'px';
boardEl.style.height = side + 'px';
}
// store veiled squares list for brouillard effect (if provided by server)
try{ currentVeiledSquares = Array.isArray(data.veiledSquares) ? data.veiledSquares : (data.boardState && data.boardState.veiledSquares) || []; }catch(_){ currentVeiledSquares = []; }
if(data.boardState) {
renderBoardFromState(data.boardState);
updateTurnBanner(data.boardState);
}
// if the room is no longer finished, remove spectator quit button
try{ if(data && data.status && data.status !== 'finished'){ removeSpectatorQuitButton(); } }catch(_){ }
// store mines owned by this client (private)
try{ myMines = data.minesOwn || []; }catch(e){ myMines = []; }
// after rendering the board, render mines for owner
try{ renderMines(); }catch(e){ }
// render hands: prefer new private `handsOwn` + `handCounts`.
// If server (or older code) mistakenly sends a full `hands` object, only show the current player's own hand and public counts — never reveal other players' cards.
if(data.hands){
// legacy: full hands object mapping playerId -> array
const own = (data.hands && data.hands[myPlayerId]) ? data.hands[myPlayerId] : [];
const counts = {};
Object.entries(data.hands || {}).forEach(([pid, arr])=>{ counts[pid] = (arr && arr.length) || 0; });
@ -276,7 +228,6 @@
} 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){
@ -284,7 +235,6 @@
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;
@ -298,42 +248,31 @@
try{
log('game:over', data);
if(data.boardState){ renderBoardFromState(data.boardState); updateTurnBanner(data.boardState); }
// show modal with friendly message
try{ showGameOver(data); }catch(_){ }
}catch(e){ console.warn('game:over handler error', e); }
});
socket.on('game:started', (data)=>{ log('game:started', data); if(data.boardState){ renderBoardFromState(data.boardState); updateTurnBanner(data.boardState); } });
// card drawn notification from server
socket.on('card:drawn', (data) => {
try{
log('recv card:drawn', data);
// show a small inline notification in the hands section
showCardDrawnNotification(data);
}catch(e){ console.warn('card:drawn handler error', e); }
});
// public notice that a player drew a card when autoDraw is OFF (server emits this only in that case)
socket.on('player:drew', (data) => {
try{
const pid = data && data.playerId;
if(!pid) return;
const actor = (pid === myPlayerId) ? 'Vous' : (playerColors[pid] ? playerColors[pid] : 'Adversaire');
// show owner message as 'Vous : a pioché une carte' so the player sees the activity line too
appendActivity(`${actor} : a pioché une carte`);
}catch(_){ }
});
// 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{
log('recv card:played', data);
// data.played or data depending on server shape
const played = data && data.played ? data.played : data;
const cardObj = played && (played.card || played.cardObj || null);
const owner = played && played.playerId;
// if we have the card object and it's our card, remove its DOM element
if(owner === myPlayerId){
// find the card element by uuid (data-card-uuid) or by cardId
const row = document.querySelector('.cards-row');
if(row){
const elems = Array.from(row.querySelectorAll('.pokemon-card'));
@ -346,12 +285,9 @@
});
}
}
// show friendly activity: who played which card (use title if available)
try{
const title = (cardObj && (cardObj.title || cardObj.cardId)) || (played && played.card && (played.card.title || played.card.cardId)) || 'Carte';
// show player's color instead of their id; fallback to generic label
const actor = (owner === myPlayerId) ? 'Vous' : (playerColors[owner] ? playerColors[owner] : 'Adversaire');
// If the played card is marked hidden, do not reveal the title to other players
const isHidden = (cardObj && cardObj.hidden) || (played && played.card && played.card.hidden);
if(isHidden && owner !== myPlayerId){
appendActivity(`${actor} : a joué une carte`);
@ -361,16 +297,13 @@
}catch(_){ }
}catch(e){ console.warn('card:played handler error', e); }
});
// show card effect notifications (both public and private) so owners get feedback on failed placements/promotions
socket.on('card:effect:applied', (data) => {
try{
const effect = data && data.effect ? data.effect : data;
if(!effect) return;
// play shuffle animation for melange
if(effect.type === 'melange' || effect.type === 'm\u00E9lange'){
if(effect.type === 'melange' || effect.type === 'melange'){
try{ playShuffleAnimation(); }catch(_){ }
}
// if this is a private or public effect relating to this player, show a small note
if(effect.playerId && effect.playerId === myPlayerId){
showEffectNotification(effect);
} else if(effect.type && (effect.type === 'promotion' || effect.type === 'steal' || effect.type === 'mine')){
@ -1164,7 +1097,6 @@
const top = document.createElement('div'); top.className = 'pokemon-card-top';
const art = document.createElement('div'); art.className = 'pokemon-card-art';
art.textContent = '';
// show card artwork if available in assets/img/cards matching the cardId or title
try{
const cid = (c.cardId || c.id || '').toString().toLowerCase();
const title = (c.title || '').toString().toLowerCase();
@ -1186,7 +1118,7 @@
if(cid === 'sniper' || title.indexOf('sniper') !== -1) imgSrc = '/assets/img/cards/sniper.png';
if(cid === 'totem' || title.indexOf('totem') !== -1) imgSrc = '/assets/img/cards/totem.png';
if(cid === 'toucher' || title.indexOf('toucher') !== -1) imgSrc = '/assets/img/cards/toucher.png';
if(cid === 'vole' || title.indexOf('vole') !== -1 || title.indexOf('vole') !== -1) imgSrc = '/assets/img/cards/vole_piece.png';
if(cid === 'vole_piece' || title.indexOf('vole_piece') !== -1) imgSrc = '/assets/img/cards/vole_piece.png';
if(imgSrc){
const img = document.createElement('img');
img.src = imgSrc;

View file

@ -38,12 +38,19 @@
<li><strong>Fin de partie :</strong> si un roi est capturé, la partie s'arrête sur une victoire, une défaite ou peut-être même une égalité. Les joueurs peuvent choisir de rester en spectateur ou de quitter la partie.</li>
</ul>
<p style="margin-top:8px;font-size:0.95em;color:#444">Remarque : ces règles couvrent uniquement les mécaniques des cartes — pour le jeu d'échecs standard, les mouvements habituels s'appliquent (pions, tours, cavaliers, fous, dames, rois).</p>
<h3>Modes de jeux</h3>
<p>Il existe plusieurs modes de jeux pour s'adapter à différents styles de jeu :</p>
<ul>
<li><strong>Pioche automatique :</strong> Si activé, les cartes sont piochées automatiquement à chaque tour. Sinon, les joueurs doivent décider s'ils piochent ou s'ils jouent. Attention ! Il n'est pas possible de piocher deux fois de suite !</li>
<li><strong>Sans remise :</strong> Les cartes jouées ne sont pas remises dans la pioche, ce qui les rend uniques.</li>
<li><strong>Pioche personnalisée :</strong> Choisissez les cartes disponibles dans la pioche.</li>
</ul>
</section>
<section class="card" id="availableCards" style="margin-top:16px">
<h3>Cartes disponibles</h3>
<div id="cardsGrid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:12px">
<!-- cards will be injected here -->
<!-- Affichage des cartes disponibles -->
</div>
</section>
</div>

776
server.js

File diff suppressed because it is too large Load diff