+ Room:
- • Vous jouez:
+
-
+
+
-
-
-
Activité
-
Aucune activité pour l'instant.
-
-
-
-
+
-
-
-
-
+
-
+ const header = document.createElement("div");
+ header.className = "pokemon-hand-header";
+ const title = document.createElement("div");
+ title.textContent = "Vos cartes";
+ title.className = "pokemon-hand-title";
+ header.appendChild(title);
+ wrap.appendChild(header);
+
+ const row = document.createElement("div");
+ row.className = "cards-row";
+
+ (handsOwn || []).slice(0, 10).forEach((c) => {
+ const cardEl = document.createElement("div");
+ cardEl.className = "pokemon-card";
+ cardEl.dataset.cardId = c.cardId || c.id || "";
+ cardEl.dataset.cardUuid = c.id || "";
+
+ const top = document.createElement("div");
+ top.className = "pokemon-card-top";
+ const art = document.createElement("div");
+ art.className = "pokemon-card-art";
+ art.textContent = "";
+ try {
+ const cid = (c.cardId || c.id || "").toString().toLowerCase();
+ const title = (c.title || "").toString().toLowerCase();
+ let imgSrc = null;
+ if (cid === "adoubement")
+ imgSrc = "/assets/img/cards/adoubement.png";
+ if (cid === "folie") imgSrc = "/assets/img/cards/folie.png";
+ if (cid === "fortification")
+ imgSrc = "/assets/img/cards/fortification.png";
+ if (cid === "rebond") imgSrc = "/assets/img/cards/rebond.png";
+ if (cid === "anneau") imgSrc = "/assets/img/cards/anneau.png";
+ if (cid === "brouillard")
+ imgSrc = "/assets/img/cards/brouillard.png";
+ if (cid === "coincoin") imgSrc = "/assets/img/cards/coincoin.png";
+ if (cid === "inversion")
+ 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 === "melange") imgSrc = "/assets/img/cards/mélange.png";
+ if (cid === "mine") imgSrc = "/assets/img/cards/mine.png";
+ if (cid === "promotion")
+ imgSrc = "/assets/img/cards/promotion.png";
+ if (cid === "resurection")
+ imgSrc = "/assets/img/cards/resurection.png";
+ if (cid === "sniper") imgSrc = "/assets/img/cards/sniper.png";
+ if (cid === "totem") imgSrc = "/assets/img/cards/totem.png";
+ if (cid === "toucher") imgSrc = "/assets/img/cards/toucher.png";
+ if (cid === "vole_piece")
+ imgSrc = "/assets/img/cards/vole_piece.png";
+ if (cid === "double") imgSrc = "/assets/img/cards/double.png";
+ if (cid === "empathie") imgSrc = "/assets/img/cards/empathie.png";
+ if (cid === "parrure") imgSrc = "/assets/img/cards/parrure.png";
+ if (cid === "revolution")
+ imgSrc = "/assets/img/cards/révolution.png";
+ if (cid === "doppelganger")
+ imgSrc = "/assets/img/cards/doppelganger.png";
+ if (cid === "pareil") imgSrc = "/assets/img/cards/pareil.png";
+ if (cid === "sans_effet")
+ imgSrc = "/assets/img/cards/sans_effet.png";
+ if (cid === "teleportation")
+ imgSrc = "/assets/img/cards/teleportation.png";
+ if (cid === "tout") imgSrc = "/assets/img/cards/tout.png";
+ if (cid === "vole_carte")
+ imgSrc = "/assets/img/cards/vole_carte.png";
+
+ if (imgSrc) {
+ const img = document.createElement("img");
+ img.src = imgSrc;
+ img.alt = c.title || cid || "card";
+ img.className = "card-art-img";
+ img.style.width = "100%";
+ img.style.height = "100%";
+ img.style.objectFit = "cover";
+ art.appendChild(img);
+ }
+ } catch (e) {
+ /* ignore artwork errors */
+ }
+ top.appendChild(art);
+
+ const mid = document.createElement("div");
+ mid.className = "pokemon-card-mid";
+ const h = document.createElement("div");
+ h.className = "pokemon-card-title";
+ h.textContent = c.title || c.cardId || "Carte";
+ const p = document.createElement("div");
+ p.className = "pokemon-card-desc";
+ p.textContent = c.description || "";
+ mid.appendChild(h);
+ mid.appendChild(p);
+
+ const footer = document.createElement("div");
+ footer.className = "pokemon-card-footer";
+ const playBtn = document.createElement("button");
+ playBtn.className = "card-play-btn";
+ playBtn.textContent = "Jouer";
+ footer.appendChild(playBtn);
+
+ cardEl.appendChild(top);
+ cardEl.appendChild(mid);
+ cardEl.appendChild(footer);
+
+ // clicking the whole card or the play button emits card:play
+ function emitPlay() {
+ if (!socket) return;
+ const cid = cardEl.dataset.cardId;
+ // try cardId first, then fall back to the human title (helps with accented ids like "Téléportation")
+ const requireType =
+ cardRequiresTarget(cid) || cardRequiresTarget(c.title || "");
+ if (requireType) {
+ if (requireType === "player") {
+ // If there's only one opponent, auto-select them (no modal needed). Otherwise show modal.
+ const opponents = Object.keys(handCounts || {}).filter(
+ (pid) => pid !== myPlayerId,
+ );
+ if (opponents.length === 1) {
+ const targetPlayerId = opponents[0];
+ emitCardPlay(
+ {
+ roomId,
+ playerId: myPlayerId,
+ cardId: cid,
+ payload: { targetPlayerId },
+ },
+ (resp) => {
+ if (resp && resp.error) {
+ log("card:play error", resp);
+ showToast(resp.message || resp.error, {
+ background: "rgba(200,60,60,0.95)",
+ });
+ } else {
+ log("card played (steal card)", resp);
+ }
+ },
+ );
+ return;
+ }
+ // show modal to pick which player to steal from
+ showStealModal(handCounts, (targetPlayerId) => {
+ if (!targetPlayerId) return; // canceled
+ emitCardPlay(
+ {
+ roomId,
+ playerId: myPlayerId,
+ cardId: cid,
+ payload: { targetPlayerId },
+ },
+ (resp) => {
+ if (resp && resp.error) {
+ log("card:play error", resp);
+ showToast(resp.message || resp.error, {
+ background: "rgba(200,60,60,0.95)",
+ });
+ } else {
+ log("card played (steal card)", resp);
+ }
+ },
+ );
+ });
+ return;
+ }
+ // enter pending target mode: next square click will send the card with payload.targetSquare
+ pendingCard = {
+ cardId: cid,
+ cardUuid: cardEl.dataset.cardUuid || "",
+ requireType,
+ };
+ highlightTargets(requireType);
+ log("Pending card selection for", cid, "-> select target");
+ return;
+ }
+ // immediate-play card (no target required)
+ const payload = {};
+ emitCardPlay(
+ { roomId, playerId: myPlayerId, cardId: cid, payload },
+ (resp) => {
+ if (resp && resp.error) {
+ log("card:play error", resp);
+ showToast(resp.message || resp.error, {
+ background: "rgba(200,60,60,0.95)",
+ });
+ } else {
+ log("card played", resp);
+ }
+ },
+ );
+ }
+ cardEl.addEventListener("click", (ev) => {
+ if (ev.target === playBtn) return;
+ emitPlay();
+ });
+ playBtn.addEventListener("click", (ev) => {
+ ev.stopPropagation();
+ emitPlay();
+ });
+
+ 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);
+ showToast(resp.message || resp.error, {
+ background: "rgba(200,60,60,0.95)",
+ });
+ 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);
+ }
+
+ function showCardDrawnNotification(evt) {
+ try {
+ const handsEl = document.getElementById("hands");
+ if (!handsEl) return;
+ const note = document.createElement("div");
+ note.className = "card-draw-note";
+ note.textContent =
+ evt && evt.card
+ ? `Pioché: ${evt.card.title || evt.card.cardId}`
+ : "Carte piochée";
+ note.style.padding = "6px 8px";
+ note.style.border = "1px solid #ccc";
+ note.style.background = "#fff8e1";
+ note.style.marginTop = "8px";
+ handsEl.prepend(note);
+ setTimeout(() => note.remove(), 5000);
+ } catch (e) {
+ console.warn("showCardDrawnNotification error", e);
+ }
+ }
+
+ function showEffectNotification(effect) {
+ try {
+ const handsEl = document.getElementById("hands");
+ if (!handsEl) return;
+ const note = document.createElement("div");
+ note.className = "card-draw-note";
+ if (effect.type === "promotion_failed")
+ note.textContent = "Promotion échouée — la carte est consommée";
+ else if (effect.type === "promotion")
+ note.textContent = "Promotion réussie !";
+ else if (effect.type === "mine_failed")
+ note.textContent = "Placement de mine échoué — carte consommée";
+ else if (effect.type === "steal_failed")
+ note.textContent = "Vol échoué — carte consommée";
+ else if (effect.type === "card_lost")
+ note.textContent = "Une de vos cartes a été volée";
+ else note.textContent = `Effet: ${effect.type}`;
+ note.style.padding = "6px 8px";
+ note.style.border = "1px solid #ccc";
+ note.style.background = "#fff8e1";
+ note.style.marginTop = "8px";
+ handsEl.prepend(note);
+ setTimeout(() => note.remove(), 6000);
+ } catch (e) {
+ console.warn("showEffectNotification error", e);
+ }
+ }
+
+ // show a simple promotion choice modal. callback receives one of 'q','r','b','n' or null if cancelled
+ function showPromotionModal(square, callback) {
+ try {
+ if (document.getElementById("promotionModal")) return;
+ const modal = document.createElement("div");
+ modal.id = "promotionModal";
+ modal.style.position = "fixed";
+ modal.style.left = "0";
+ modal.style.top = "0";
+ modal.style.right = "0";
+ modal.style.bottom = "0";
+ modal.style.display = "flex";
+ modal.style.alignItems = "center";
+ modal.style.justifyContent = "center";
+ modal.style.background = "rgba(0,0,0,0.45)";
+ modal.style.zIndex = "9999";
+
+ const box = document.createElement("div");
+ box.style.background = "#fff";
+ box.style.padding = "16px";
+ box.style.borderRadius = "8px";
+ box.style.minWidth = "320px";
+ box.style.textAlign = "center";
+ const title = document.createElement("div");
+ title.textContent = "Choisir la promotion";
+ title.style.fontWeight = "700";
+ title.style.marginBottom = "8px";
+ box.appendChild(title);
+
+ const desc = document.createElement("div");
+ desc.textContent = `Promouvoir le pion en:`;
+ desc.style.marginBottom = "12px";
+ box.appendChild(desc);
+
+ const btnRow = document.createElement("div");
+ btnRow.style.display = "flex";
+ btnRow.style.gap = "12px";
+ btnRow.style.justifyContent = "center";
+ btnRow.style.marginBottom = "8px";
+
+ // determine the piece color at the square (should be a pawn)
+ const piece =
+ currentBoardState &&
+ Array.isArray(currentBoardState.pieces) &&
+ currentBoardState.pieces.find((p) => p.square === square);
+ const colorPrefix =
+ piece && piece.color
+ ? piece.color === "w"
+ ? "w"
+ : "b"
+ : (myColor && myColor[0]) || "w";
+ const promoTypes = [
+ ["q", "Dame"],
+ ["r", "Tour"],
+ ["b", "Fou"],
+ ["n", "Cavalier"],
+ ];
+ promoTypes.forEach(([code, label]) => {
+ const btn = document.createElement("button");
+ btn.type = "button";
+ btn.style.padding = "6px";
+ btn.style.border = "1px solid #ccc";
+ btn.style.background = "#fff";
+ btn.style.cursor = "pointer";
+ btn.style.display = "flex";
+ btn.style.flexDirection = "column";
+ btn.style.alignItems = "center";
+ btn.style.gap = "6px";
+ // image
+ const letter = code.toUpperCase();
+ const img = document.createElement("img");
+ img.src = `${pieceSetPath}/${colorPrefix}${letter}.svg`;
+ img.alt = label;
+ img.style.width = "48px";
+ img.style.height = "48px";
+ img.style.display = "block";
+ // if image fails, show text label
+ img.addEventListener("error", () => {
+ img.style.display = "none";
+ });
+ const lbl = document.createElement("div");
+ lbl.textContent = label;
+ lbl.style.fontSize = "12px";
+ btn.appendChild(img);
+ btn.appendChild(lbl);
+ btn.addEventListener("click", (ev) => {
+ ev.stopPropagation();
+ try {
+ modal.remove();
+ } catch (_) {}
+ callback(code);
+ });
+ btnRow.appendChild(btn);
+ });
+ box.appendChild(btnRow);
+
+ const cancel = document.createElement("button");
+ cancel.type = "button";
+ cancel.textContent = "Annuler";
+ cancel.style.marginTop = "6px";
+ cancel.style.padding = "8px 12px";
+ cancel.addEventListener("click", (ev) => {
+ ev.stopPropagation();
+ try {
+ modal.remove();
+ } catch (_) {}
+ callback(null);
+ });
+ box.appendChild(cancel);
+
+ modal.appendChild(box);
+ document.body.appendChild(modal);
+ } catch (e) {
+ console.warn("showPromotionModal error", e);
+ callback(null);
+ }
+ }
+
+ // show a modal to pick a player to steal from. handCounts is an object mapping playerId -> count
+ function showStealModal(handCounts, callback) {
+ try {
+ if (document.getElementById("stealModal")) return;
+ const modal = document.createElement("div");
+ modal.id = "stealModal";
+ modal.style.position = "fixed";
+ modal.style.left = "0";
+ modal.style.top = "0";
+ modal.style.right = "0";
+ modal.style.bottom = "0";
+ modal.style.display = "flex";
+ modal.style.alignItems = "center";
+ modal.style.justifyContent = "center";
+ modal.style.background = "rgba(0,0,0,0.45)";
+ modal.style.zIndex = "9999";
+ const box = document.createElement("div");
+ box.style.background = "#fff";
+ box.style.padding = "12px";
+ box.style.borderRadius = "8px";
+ box.style.minWidth = "320px";
+ const title = document.createElement("div");
+ title.textContent = "Choisir le joueur à voler";
+ title.style.fontWeight = "700";
+ title.style.marginBottom = "8px";
+ box.appendChild(title);
+ const list = document.createElement("div");
+ list.style.display = "flex";
+ list.style.flexDirection = "column";
+ list.style.gap = "8px";
+ list.style.marginBottom = "8px";
+ Object.entries(handCounts || {}).forEach(([pid, count]) => {
+ if (pid === myPlayerId) return; // skip self
+ const row = document.createElement("div");
+ row.style.display = "flex";
+ row.style.justifyContent = "space-between";
+ row.style.alignItems = "center";
+ const label = document.createElement("div");
+ label.textContent = `${pid} — ${count} carte(s)`;
+ label.style.fontWeight = "600";
+ const btn = document.createElement("button");
+ btn.type = "button";
+ btn.textContent = "Voler";
+ btn.style.padding = "6px 10px";
+ btn.addEventListener("click", (ev) => {
+ ev.stopPropagation();
+ try {
+ modal.remove();
+ } catch (_) {}
+ callback(pid);
+ });
+ row.appendChild(label);
+ row.appendChild(btn);
+ list.appendChild(row);
+ });
+ box.appendChild(list);
+ const cancel = document.createElement("button");
+ cancel.type = "button";
+ cancel.textContent = "Annuler";
+ cancel.style.padding = "8px 10px";
+ cancel.addEventListener("click", (ev) => {
+ ev.stopPropagation();
+ try {
+ modal.remove();
+ } catch (_) {}
+ callback(null);
+ });
+ box.appendChild(cancel);
+ modal.appendChild(box);
+ document.body.appendChild(modal);
+ } catch (e) {
+ console.warn("showStealModal error", e);
+ callback(null);
+ }
+ }
+
+
+
diff --git a/public/index.html b/public/index.html
index 05ec048..d7b1487 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,174 +1,289 @@
-
-