diff --git a/assets/img/cards/travesti.png b/assets/img/cards/travesti.png new file mode 100644 index 0000000..3c158b1 Binary files /dev/null and b/assets/img/cards/travesti.png differ diff --git a/public/game.html b/public/game.html index 5a064c4..61c0288 100644 --- a/public/game.html +++ b/public/game.html @@ -386,6 +386,7 @@ logs... e && e.type === "travesti"); + if (!travestiEffects.length) return; + + const possibleTypes = ["P", "N", "B", "R", "Q"]; + + travestiEffects.forEach((eff) => { + const piece = pieces.find((p) => p.id === eff.pieceId); + if (!piece) { + // Piece was captured, remove the effect + try { + room.activeCardEffects = effects.filter((e) => e !== eff); + io.to(room.id).emit("card:effect:removed", { + roomId: room.id, + effectId: eff.id, + type: "travesti", + playerId: eff.playerId, + }); + } catch (_) {} + return; + } + // Choose a random type different from current (if possible) + const currentType = piece.type; + const availableTypes = possibleTypes.filter((t) => t !== currentType); + const newType = availableTypes[Math.floor(Math.random() * availableTypes.length)]; + piece.type = newType; + + // Update the effect to track the current type + eff.currentType = newType; + + try { + io.to(room.id).emit("card:effect:updated", { + roomId: room.id, + effect: eff, + pieceChanged: { pieceId: piece.id, newType, square: piece.square }, + }); + } catch (_) {} + }); + + // Bump version so clients pick up the change + try { + board.version = (board.version || 0) + 1; + } catch (_) {} + } catch (e) { + console.error("applyTraverstiEffects error", e); + } +} + // 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. @@ -409,6 +464,10 @@ function endTurnAfterCard(room, senderId) { (p) => (p.color && p.color[0]) === nextColor, ); if (nextPlayer) { + // Apply travesti effect at the start of the new turn + try { + applyTraverstiEffects(room); + } catch (_) {} maybeDrawAtTurnStart(room, nextPlayer.id); } else { sendRoomUpdate(room); @@ -659,17 +718,21 @@ function buildDefaultDeck() { "cachotier" ], + [ + "Travesti", + "La pièce désignée change aléatoirement de type à chaque tour.", + "travesti" + ] + // facile et intéresssant // ["intrication quantique","Deux pièces sont intriquées. Quand l'une bouge, l'autre bouge de la même manière."], // ['kurby','Choisis une pièce. À sa prochaine capture, récupère tous les mouvements de la pièce capturée.'], - // ['défausse','Le joueur adverse défausse une carte de son choix'], // ['glue','Toutes les pièces autour de la pièce désignée ne peuvent pas bouger tant que cette dernière ne bouge pas'], // ['immunité à la capture','Désigne une pièce qui ne pourra pas être capturée au prochain tour'], // ['marécage','Pendant X tours, toutes les pièces ne peuvent se déplacer que comme un roi'], // ['tricherie','Choisis une carte de la pioche parmis trois'], // ['trêve','Aucun capture ne peut avoir lieu pendant 4 tours'], - // ['traversti','La pièce désignée change aléatoirement de type à chaque tour'] // ['pièce berserk','Une pièce choisie doit capturer dans les trois prochains tours, sinon elle est capturée'], // moyen facile mais intéressant @@ -2095,6 +2158,10 @@ io.on("connection", (socket) => { (p) => (p.color && p.color[0]) === nextColor, ); if (nextPlayer) { + // Apply travesti effect at the start of the new turn + try { + applyTraverstiEffects(room); + } catch (_) {} maybeDrawAtTurnStart(room, nextPlayer.id); } else { sendRoomUpdate(room); @@ -2552,6 +2619,10 @@ io.on("connection", (socket) => { ); if (nextPlayer) { try { + // Apply travesti effect at the start of the new turn + try { + applyTraverstiEffects(room); + } catch (_) {} maybeDrawAtTurnStart(room, nextPlayer.id); } catch (_) { sendRoomUpdate(room); @@ -2667,6 +2738,8 @@ io.on("connection", (socket) => { typeof cardId === "string" && cardId.indexOf("folie") !== -1; const isFort = typeof cardId === "string" && cardId.indexOf("fortification") !== -1; + const isTraversti = + typeof cardId === "string" && cardId.indexOf("travesti") !== -1; const isTargetCard = isRebond || isAdoub || isFolie || isFort; if (isTargetCard) { const board = room.boardState; @@ -2702,6 +2775,40 @@ io.on("connection", (socket) => { payload.targetSquare = targetCandidate; played.payload = Object.assign({}, payload); } + // travesti: any piece except king + if (isTraversti) { + const board = room.boardState; + let targetCandidate = payload && payload.targetSquare; + if (!targetCandidate) { + try { + targetCandidate = socket.data && socket.data.lastSelectedSquare; + } catch (e) { + targetCandidate = null; + } + } + const targetPiece = ((board && board.pieces) || []).find( + (p) => p.square === targetCandidate, + ); + if ( + !board || + !targetCandidate || + !targetPiece || + targetPiece.type === "K" + ) { + return ( + cb && + cb({ + error: "no valid target", + message: targetPiece && targetPiece.type === "K" + ? "Vous ne pouvez pas cibler un roi." + : "Sélectionnez une pièce à affecter.", + }) + ); + } + payload = payload || {}; + payload.targetSquare = targetCandidate; + played.payload = Object.assign({}, payload); + } } catch (e) { console.error("target card pre-check error", e); } @@ -3816,6 +3923,10 @@ io.on("connection", (socket) => { ); if (nextPlayer) { try { + // Apply travesti effect at the start of the new turn + try { + applyTraverstiEffects(room); + } catch (_) {} maybeDrawAtTurnStart(room, nextPlayer.id); } catch (_) {} } @@ -3995,6 +4106,10 @@ io.on("connection", (socket) => { ); if (nextPlayer) { try { + // Apply travesti effect at the start of the new turn + try { + applyTraverstiEffects(room); + } catch (_) {} maybeDrawAtTurnStart(room, nextPlayer.id); } catch (_) {} } @@ -4739,6 +4854,10 @@ io.on("connection", (socket) => { ); if (nextPlayer) { try { + // Apply travesti effect at the start of the new turn + try { + applyTraverstiEffects(room); + } catch (_) {} maybeDrawAtTurnStart(room, nextPlayer.id); } catch (_) {} } @@ -5113,6 +5232,86 @@ io.on("connection", (socket) => { console.error("cachotier effect error", e); } } + + // travesti + else if (cardId === "travesti") { + try { + const board = room.boardState; + let target = payload && payload.targetSquare; + if (!target) { + try { + target = socket.data && socket.data.lastSelectedSquare; + } catch (e) { + target = null; + } + } + const targetPiece = ((board && board.pieces) || []).find( + (p) => p.square === target, + ); + // Must select a piece that is not a king + if ( + !board || + !target || + !targetPiece || + targetPiece.type === "K" + ) { + try { + room.hands = room.hands || {}; + room.hands[senderId] = room.hands[senderId] || []; + if (removed) room.hands[senderId].push(removed); + room.discard = room.discard || []; + for (let i = room.discard.length - 1; i >= 0; i--) { + if ( + room.discard[i] && + room.discard[i].id === (removed && removed.id) + ) { + room.discard.splice(i, 1); + break; + } + } + } catch (e) { + console.error("restore removed card error", e); + } + return ( + cb && + cb({ + error: "no valid target", + message: + targetPiece && targetPiece.type === "K" + ? "Vous ne pouvez pas cibler un roi." + : "Aucune cible valide n'a été sélectionnée.", + }) + ); + } + + room.activeCardEffects = room.activeCardEffects || []; + const effect = { + id: played.id, + type: "travesti", + pieceId: targetPiece.id, + pieceSquare: target, + originalType: targetPiece.type, + currentType: targetPiece.type, + playerId: senderId, + targetColor: targetPiece.color, + ts: Date.now(), + }; + room.activeCardEffects.push(effect); + try { + io.to(room.id).emit("card:effect:applied", { + roomId: room.id, + effect, + }); + } catch (_) {} + played.payload = Object.assign({}, payload, { + applied: "travesti", + appliedTo: target, + pieceId: targetPiece.id, + }); + } catch (e) { + console.error("travesti effect error", e); + } + } } catch (e) { console.error("card:play effect error", e); } @@ -5269,6 +5468,10 @@ io.on("connection", (socket) => { (p) => (p.color && p.color[0]) === nextColor, ); if (nextPlayer) { + // Apply travesti effect at the start of the new turn + try { + applyTraverstiEffects(room); + } catch (_) {} maybeDrawAtTurnStart(room, nextPlayer.id); } else { sendRoomUpdate(room);