affichage du décompte pour le brouillard
This commit is contained in:
parent
01234391f1
commit
43a1c0287e
5 changed files with 153 additions and 20 deletions
BIN
assets/img/cards/medusa.png
Normal file
BIN
assets/img/cards/medusa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
149
public/game.html
149
public/game.html
|
|
@ -46,6 +46,20 @@
|
|||
#log {
|
||||
display: none;
|
||||
}
|
||||
/* effects feed for user-facing logs (card plays) */
|
||||
#effectsFeed {
|
||||
max-height: 140px;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
color: #222;
|
||||
}
|
||||
#log {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -139,10 +153,14 @@
|
|||
logs...</pre
|
||||
>
|
||||
</div>
|
||||
<div style="margin-top: 8px">
|
||||
<div><strong>Activité</strong></div>
|
||||
<div id="activityFeed" style="margin-top: 8px">
|
||||
Aucune activité pour l'instant.
|
||||
<div style="margin-top: 8px; display:flex; gap:8px; align-items:flex-start;">
|
||||
<div style="flex:1; min-width:0">
|
||||
<div><strong>Activité</strong></div>
|
||||
<div id="activityFeed" style="margin-top: 8px">Aucune activité pour l'instant.</div>
|
||||
</div>
|
||||
<div style="width:200px; min-width:160px">
|
||||
<div><strong>Effets actifs</strong></div>
|
||||
<div id="effectsFeed" style="margin-top: 8px">Aucun effet actif pour l'instant.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -183,6 +201,54 @@ logs...</pre
|
|||
|
||||
const logEl = document.getElementById("log");
|
||||
const playersEl = document.getElementById("players");
|
||||
const effectsFeedEl = document.getElementById("effectsFeed");
|
||||
// simple client-side registry of active effects we know about (keyed by effect.id)
|
||||
const activeEffects = {};
|
||||
|
||||
function refreshEffectsFeed() {
|
||||
try {
|
||||
const dest = document.getElementById("effectsFeed");
|
||||
if (!dest) return;
|
||||
const effects = Object.values(activeEffects).filter(
|
||||
(e) => e && e.type === "brouillard",
|
||||
);
|
||||
if (!effects || !effects.length) {
|
||||
dest.textContent = "Aucun effet actif pour l'instant.";
|
||||
return;
|
||||
}
|
||||
dest.innerHTML = "";
|
||||
effects.forEach((e) => {
|
||||
try {
|
||||
const row = document.createElement("div");
|
||||
row.style.padding = "4px 6px";
|
||||
row.style.borderBottom = "1px solid #f3f3f3";
|
||||
const rem = typeof e.remainingTurns !== "undefined" ? e.remainingTurns : "?";
|
||||
row.textContent = `${e.title} — ${rem+1} tour(s) restant(s)`;
|
||||
dest.appendChild(row);
|
||||
} catch (_) {}
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
// Apply a local visual tick when any player plays a card or moves.
|
||||
// This updates the displayed remainingTurns immediately for UX, but
|
||||
// does not replace server authority — incoming card:effect:updated or
|
||||
// room:update will resync the true values.
|
||||
function applyLocalPlayTick() {
|
||||
try {
|
||||
let changed = false;
|
||||
Object.keys(activeEffects).forEach((id) => {
|
||||
try {
|
||||
const e = activeEffects[id];
|
||||
if (e && typeof e.remainingTurns === "number") {
|
||||
// decrement visually by one (but never below zero)
|
||||
e.remainingTurns = Math.max(0, (e.remainingTurns || 0) - 1);
|
||||
changed = true;
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
if (changed) refreshEffectsFeed();
|
||||
} catch (_) {}
|
||||
}
|
||||
const boardEl = document.getElementById("board");
|
||||
const myColorEl = document.getElementById("myColor");
|
||||
let socket = null;
|
||||
|
|
@ -490,6 +556,29 @@ logs...</pre
|
|||
removeSpectatorQuitButton();
|
||||
}
|
||||
} catch (_) {}
|
||||
// sync activeEffects from server-provided activeCardEffects (update remainingTurns and remove expired)
|
||||
try {
|
||||
if (Array.isArray(data.activeCardEffects)) {
|
||||
const seen = new Set();
|
||||
data.activeCardEffects.forEach((eff) => {
|
||||
try {
|
||||
if (eff && eff.type === "brouillard" && eff.id) {
|
||||
activeEffects[eff.id] = eff;
|
||||
seen.add(eff.id);
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
// remove stale brouillard entries not present in server list
|
||||
Object.keys(activeEffects).forEach((id) => {
|
||||
try {
|
||||
if (!seen.has(id) && activeEffects[id] && activeEffects[id].type === "brouillard") {
|
||||
delete activeEffects[id];
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
refreshEffectsFeed();
|
||||
}
|
||||
} catch (_) {}
|
||||
try {
|
||||
myMines = data.minesOwn || [];
|
||||
} catch (e) {
|
||||
|
|
@ -564,10 +653,11 @@ logs...</pre
|
|||
const actor =
|
||||
pid === myPlayerId
|
||||
? "Vous"
|
||||
: playerColors[pid]
|
||||
? playerColors[pid]
|
||||
: "Adversaire";
|
||||
appendActivity(`${actor} : a pioché une carte`);
|
||||
try {
|
||||
applyLocalPlayTick();
|
||||
} catch (_) {}
|
||||
} catch (_) {}
|
||||
});
|
||||
socket.on("card:played", (data) => {
|
||||
|
|
@ -607,8 +697,6 @@ logs...</pre
|
|||
const actor =
|
||||
owner === myPlayerId
|
||||
? "Vous"
|
||||
: playerColors[owner]
|
||||
? playerColors[owner]
|
||||
: "Adversaire";
|
||||
const isHidden =
|
||||
(cardObj && cardObj.hidden) ||
|
||||
|
|
@ -618,6 +706,10 @@ logs...</pre
|
|||
} else {
|
||||
appendActivity(`${actor} : a joué la carte "${title}"`);
|
||||
}
|
||||
try {
|
||||
// Visual tick: decrement displayed counters immediately when any card is played
|
||||
applyLocalPlayTick();
|
||||
} catch (_) {}
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
console.warn("card:played handler error", e);
|
||||
|
|
@ -701,6 +793,13 @@ logs...</pre
|
|||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
// track brouillard effects for the sidebar feed
|
||||
try {
|
||||
if (effect.type === "brouillard") {
|
||||
activeEffects[effect.id] = effect;
|
||||
refreshEffectsFeed();
|
||||
}
|
||||
} catch (_) {}
|
||||
// If empathie was applied, explicitly request a room refresh as a reliable fallback
|
||||
try {
|
||||
if (effect.type === "empathie") {
|
||||
|
|
@ -722,6 +821,21 @@ logs...</pre
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("card:effect:updated", (data) => {
|
||||
try {
|
||||
const eff = data && (data.effect || data);
|
||||
if (!eff || !eff.id) return;
|
||||
try {
|
||||
activeEffects[eff.id] = eff;
|
||||
} catch (_) {}
|
||||
try {
|
||||
refreshEffectsFeed();
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
console.warn("card:effect:updated handler error", e);
|
||||
}
|
||||
});
|
||||
|
||||
// toucher / medusa removals
|
||||
socket.on("card:effect:removed", (data) => {
|
||||
try {
|
||||
|
|
@ -763,6 +877,17 @@ logs...</pre
|
|||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
// brouillard removal from sidebar feed
|
||||
try {
|
||||
if (type === "brouillard") {
|
||||
try {
|
||||
delete activeEffects[effId];
|
||||
} catch (_) {}
|
||||
try {
|
||||
refreshEffectsFeed();
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
if (type === "medusa") {
|
||||
try {
|
||||
const sq =
|
||||
|
|
@ -915,6 +1040,10 @@ logs...</pre
|
|||
fromBeforeEl.querySelector("img.piece")
|
||||
)
|
||||
hasPieceFromBefore = true;
|
||||
try {
|
||||
// moving a piece is an in-game action — apply visual tick immediately
|
||||
applyLocalPlayTick();
|
||||
} catch (_) {}
|
||||
} catch (_) {}
|
||||
try {
|
||||
const toBeforeEl =
|
||||
|
|
@ -2231,7 +2360,7 @@ logs...</pre
|
|||
imgSrc = "/assets/img/cards/inversion.png";
|
||||
if (cid === "invisible")
|
||||
imgSrc = "/assets/img/cards/invisible.png";
|
||||
if (cid === "kamikaze") imgSrc = "/assets/img/cards/kamikaz.png";
|
||||
if (cid === "kamikaze") imgSrc = "/assets/img/cards/kamikaze.png";
|
||||
if (cid === "melange") imgSrc = "/assets/img/cards/mélange.png";
|
||||
if (cid === "mine") imgSrc = "/assets/img/cards/mine.png";
|
||||
if (cid === "promotion")
|
||||
|
|
@ -2259,6 +2388,8 @@ logs...</pre
|
|||
if (cid === "tout") imgSrc = "/assets/img/cards/tout.png";
|
||||
if (cid === "vole_carte")
|
||||
imgSrc = "/assets/img/cards/vole_carte.png";
|
||||
if (cid === "medusa")
|
||||
imgSrc = "/assets/img/cards/medusa.png";
|
||||
|
||||
if (imgSrc) {
|
||||
const img = document.createElement("img");
|
||||
|
|
|
|||
|
|
@ -232,6 +232,8 @@
|
|||
if (cid === "tout") imgSrc = "/assets/img/cards/tout.png";
|
||||
if (cid === "vole_carte")
|
||||
imgSrc = "/assets/img/cards/vole_carte.png";
|
||||
if (cid === "medusa")
|
||||
imgSrc = "/assets/img/cards/medusa.png";
|
||||
|
||||
if (imgSrc) {
|
||||
const img = document.createElement("img");
|
||||
|
|
|
|||
|
|
@ -613,7 +613,7 @@
|
|||
if (cid === "invisible")
|
||||
imgSrc = "/assets/img/cards/invisible.png";
|
||||
if (cid === "kamikaze")
|
||||
imgSrc = "/assets/img/cards/kamikaz.png";
|
||||
imgSrc = "/assets/img/cards/kamikaze.png";
|
||||
if (cid === "melange")
|
||||
imgSrc = "/assets/img/cards/mélange.png";
|
||||
if (cid === "mine") imgSrc = "/assets/img/cards/mine.png";
|
||||
|
|
@ -647,6 +647,8 @@
|
|||
if (cid === "tout") imgSrc = "/assets/img/cards/tout.png";
|
||||
if (cid === "vole_carte")
|
||||
imgSrc = "/assets/img/cards/vole_carte.png";
|
||||
if (cid === "medusa")
|
||||
imgSrc = "/assets/img/cards/medusa.png";
|
||||
|
||||
if (imgSrc) {
|
||||
const img = document.createElement("img");
|
||||
|
|
|
|||
18
server.js
18
server.js
|
|
@ -52,7 +52,6 @@ function sendRoomUpdate(room) {
|
|||
previousDraws: room._playerDrewPrev || {},
|
||||
playerDrew: room._playerDrew || {},
|
||||
};
|
||||
|
||||
const handCounts = {};
|
||||
(room.players || []).forEach((p) => {
|
||||
handCounts[p.id] =
|
||||
|
|
@ -440,7 +439,7 @@ function checkAndHandleVictory(room) {
|
|||
function buildDefaultDeck() {
|
||||
const cards = [
|
||||
[
|
||||
'médusa',
|
||||
'Médusa',
|
||||
'La pièce désignée ne peut plus bouger pendant 4 tours',
|
||||
'medusa',
|
||||
],
|
||||
|
|
@ -1841,11 +1840,12 @@ io.on("connection", (socket) => {
|
|||
|
||||
// Update brouillard play counts
|
||||
try {
|
||||
room.activeCardEffects = room.activeCardEffects || [];
|
||||
for (let ei = room.activeCardEffects.length - 1; ei >= 0; ei--) {
|
||||
const ev = room.activeCardEffects[ei];
|
||||
if (!ev || ev.type !== "brouillard") continue;
|
||||
try {
|
||||
// only consider play-count based expiry if the effect explicitly enabled it
|
||||
if (!ev.countByPlay) continue;
|
||||
ev.playCounts = ev.playCounts || {};
|
||||
ev.playCounts[senderId] = (ev.playCounts[senderId] || 0) + 1;
|
||||
try {
|
||||
|
|
@ -4403,20 +4403,18 @@ io.on("connection", (socket) => {
|
|||
}
|
||||
}
|
||||
room.activeCardEffects = room.activeCardEffects || [];
|
||||
const playCounts = {};
|
||||
try {
|
||||
(room.players || []).forEach((pl) => {
|
||||
if (pl && pl.id) playCounts[pl.id] = 0;
|
||||
});
|
||||
} catch (_) {}
|
||||
// legacy: optionally expire brouillard by play-counts. Default: disabled.
|
||||
const countByPlay = !!(payload && payload.countByPlay);
|
||||
const effect = {
|
||||
id: played.id,
|
||||
title: "Brouillard de guerre",
|
||||
type: "brouillard",
|
||||
playerId: targetPlayer.id,
|
||||
ts: Date.now(),
|
||||
remainingTurns: (payload && payload.turns) || 6,
|
||||
decrementOn: "both",
|
||||
veiledSquares: all,
|
||||
playCounts,
|
||||
countByPlay,
|
||||
};
|
||||
room.activeCardEffects.push(effect);
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue