doppelganger

This commit is contained in:
Didictateur 2025-11-24 21:59:23 +01:00
parent ca013bbd8f
commit 98b6a1c976
2 changed files with 66 additions and 6 deletions

View file

@ -111,6 +111,8 @@
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

View file

@ -266,7 +266,7 @@ function buildDefaultDeck(){
// ['tous les mêmes','Au yeux de l ennemie, toutes les pièces se ressemblent pendant 2 tours.'],
// ['petit pion','Le joueur choisit un pion. À partir du prochain tour, il est promu en reine dès qu il capture un pièce non pion.'],
['révolution','Tous les pions sont aléatoirement changés en Cavalier, Fou ou Tour et les Cavaliers, Fous et Tours sont changés en pions.'],
// ['doppelganger','Choisis une pièce. À partir de maintenant, devient chacune des pièces qu elle capture.'],
["doppelganger","Choisis une pièce. À partir de maintenant, devient chacune des pièces qu'elle capture."],
// ['kurby','Choisis une pièce. À sa prochaine capture, récupère les mouvements de la pièce capturée.']
];
function cap(s){ if(!s) return s; s = String(s).trim(); return s.charAt(0).toUpperCase() + s.slice(1); }
@ -907,6 +907,8 @@ io.on('connection', (socket) => {
const targetIndex = pieces.findIndex(p => p.square === to);
// sniper: special-case capture without moving when an active sniper effect is bound to the moving piece
let sniperTriggered = false;
// remember captured piece for post-capture reactions (like doppelganger)
let capturedPieceForReactions = null;
if(targetIndex >= 0){
// check for sniper effect bound to the moving piece for this player
try{
@ -915,6 +917,7 @@ io.on('connection', (socket) => {
if(sniperIdx !== -1){
// perform sniper capture: remove target piece and record it for potential resurrection
const capturedPiece = pieces.splice(targetIndex, 1)[0];
capturedPieceForReactions = capturedPiece;
try{
room.captured = room.captured || [];
const originalOwner = (room.players || []).find(p => (p.color && p.color[0]) === capturedPiece.color);
@ -941,6 +944,7 @@ io.on('connection', (socket) => {
} else {
// no sniper: normal capture
const capturedPiece = pieces.splice(targetIndex, 1)[0];
capturedPieceForReactions = capturedPiece;
try{
room.captured = room.captured || [];
const originalOwner = (room.players || []).find(p => (p.color && p.color[0]) === capturedPiece.color);
@ -963,9 +967,30 @@ io.on('connection', (socket) => {
}catch(_){
// fallback: perform normal capture
const capturedPiece = pieces.splice(targetIndex, 1)[0];
capturedPieceForReactions = capturedPiece;
try{ room.captured = room.captured || []; const originalOwner = (room.players || []).find(p => (p.color && p.color[0]) === capturedPiece.color); room.captured.push({ id: uuidv4().slice(0,8), piece: capturedPiece, originalOwnerId: (originalOwner && originalOwner.id) || null, capturedBy: senderId, ts: Date.now() }); }catch(_){ }
}
}
// If a capture happened, and the moving piece has a doppelganger effect bound, adopt captured type
if(capturedPieceForReactions){
try{
room.activeCardEffects = room.activeCardEffects || [];
const doppel = room.activeCardEffects.find(e => e && e.type === 'doppelganger' && e.pieceId === moving.id);
if(doppel && capturedPieceForReactions && typeof capturedPieceForReactions.type !== 'undefined'){
// change the moving piece's type to the captured piece's type (preserve color)
moving.type = capturedPieceForReactions.type;
if(capturedPieceForReactions.promoted) {
moving.promoted = true;
} else if(moving.promoted){
try{ delete moving.promoted; }catch(_){ }
}
// update recorded square for the effect so it follows the piece
doppel.pieceSquare = to;
try{ io.to(room.id).emit('card:effect:updated', { roomId: room.id, effect: doppel }); }catch(_){ }
try{ io.to(room.id).emit('card:effect:applied', { roomId: room.id, effect: Object.assign({}, doppel, { appliedType: capturedPieceForReactions.type }) }); }catch(_){ }
}
}catch(_){ }
}
// move the piece only if sniper didn't trigger
if(!sniperTriggered){ moving.square = to; }
@ -1378,6 +1403,7 @@ io.on('connection', (socket) => {
room._cardPlayedThisTurn = room._cardPlayedThisTurn || {};
if(room._cardPlayedThisTurn[senderId]) return cb && cb({ error: 'card_already_played_this_turn' });
}
}catch(e){ console.error('card play pre-check error', e); }
// store played card and apply card effects when applicable
const played = { id: uuidv4().slice(0,8), playerId: senderId, cardId, payload, ts: Date.now() };
@ -1513,6 +1539,38 @@ io.on('connection', (socket) => {
played.payload = Object.assign({}, payload, { applied: 'adoubement', appliedTo: target });
}catch(e){ console.error('adoubement effect error', e); }
}
// doppelganger (minimal): require selecting one of your pieces when playing the card; record target but do not apply any effect
else if((typeof cardId === 'string' && cardId.indexOf('doppelganger') !== -1) || cardId === 'doppelganger'){
try{
const board = room.boardState;
let target = payload && payload.targetSquare;
if(!target){ try{ target = socket.data && socket.data.lastSelectedSquare; }catch(e){ target = null; } }
const roomPlayer = room.players.find(p => p.id === senderId);
const playerColorShort = (roomPlayer && roomPlayer.color && roomPlayer.color[0]) || null;
const targetPiece = (board && board.pieces || []).find(p => p.square === target);
// validate target exists and belongs to the player
if(!board || !target || !targetPiece || targetPiece.color !== playerColorShort){
// restore removed card to hand and abort
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' });
}
// create a persistent doppelganger effect bound to the selected piece
try{
room.activeCardEffects = room.activeCardEffects || [];
const effect = { id: played.id, type: 'doppelganger', pieceId: targetPiece.id, pieceSquare: target, playerId: senderId };
room.activeCardEffects.push(effect);
// inform clients that the effect was applied so they can show badges/UI
try{ io.to(room.id).emit('card:effect:applied', { roomId: room.id, effect }); }catch(_){ }
played.payload = Object.assign({}, payload, { applied: 'doppelganger', appliedTo: target });
}catch(e){ console.error('apply persistent doppelganger error', e); played.payload = Object.assign({}, payload, { applied: 'doppelganger', appliedTo: target }); }
}catch(e){ console.error('doppelganger minimal apply error', e); }
}
// folie: grant permanent bishop-move ability to the targeted piece
else if(cardId === 'folie' || (typeof cardId === 'string' && cardId.indexOf('folie') !== -1)){
try{