From 95bef7ea5116b2fa54a139ff6ee3b58fdef8148f Mon Sep 17 00:00:00 2001 From: Didictateur Date: Sun, 23 Nov 2025 11:29:51 +0100 Subject: [PATCH] toucher c'est jouer --- public/game.html | 2 ++ server.js | 56 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/public/game.html b/public/game.html index 353c3e1..3d47d03 100644 --- a/public/game.html +++ b/public/game.html @@ -94,6 +94,8 @@ id = id.replace(/[^\x00-\x7F]/g, ''); } if(id.indexOf('mine') !== -1) return 'empty'; + // 'toucher c'est jouer' : require selecting an enemy piece as the card target + if(id.indexOf('toucher') !== -1) return 'enemy'; // kamikaz targets one of your pieces (play card, then choose a piece) if(id.indexOf('kamikaz') !== -1) return 'owned'; if(id.indexOf('invis') !== -1 || id.indexOf('invisible') !== -1) return 'owned'; diff --git a/server.js b/server.js index 9a3aff3..130524b 100644 --- a/server.js +++ b/server.js @@ -171,7 +171,7 @@ function buildDefaultDeck(){ ['changement de camp','On retourne le plateau'], // ['ça tangue','Toutes les pièces se décale du même côté'], // ['réinitialisation','Toutes les pièces reviennent à leur position initiale. S il y a des pièces supplémenaires, se rangent devant les pions'], - // ['toucher c est jouer','Toucher une pièce adverse qu il sera obligé de jouer'], + ["toucher c'est jouer","Toucher une pièce adverse qu'il sera obligé de jouer"], // ['marécage','Pendant X tours, toutes les pièces ne peuvent se déplacer que comme un roi'], // ['sniper','Capturer une pièce sans avoir à bouger la pièce capturante'], // ['inversion','Échange la position d une pièce avec une pièce adverse'], @@ -748,6 +748,16 @@ io.on('connection', (socket) => { if(!moving) return cb && cb({ error: 'no piece at source' }); if(moving.color !== playerColorShort) return cb && cb({ error: 'not your piece' }); + // Enforce 'toucher' restriction: if there is an active 'toucher' effect targeting this player, + // they may ONLY move the specified piece(s). + try{ + const effects = room.activeCardEffects || []; + const toucher = effects.find(e => e && e.type === 'toucher' && e.playerId === senderId); + if(toucher && toucher.pieceId && moving.id !== toucher.pieceId){ + return cb && cb({ error: 'must_move_restricted_piece' }); + } + }catch(_){ } + // validate that 'to' is among legal moves const legal = computeLegalMoves(room, from) || []; const ok = legal.some(m => m.to === to); @@ -1040,6 +1050,19 @@ io.on('connection', (socket) => { console.error('computeLegalMoves error', e); moves = []; } + // Enforce 'toucher' restriction: if the selecting player is affected by a 'toucher' effect, + // they may only move the targeted piece. If they select another piece, show no moves. + try{ + const effects = room.activeCardEffects || []; + const toucherEffects = effects.filter(e => e && e.type === 'toucher' && e.playerId === playerId); + if(toucherEffects && toucherEffects.length > 0){ + const selectedPiece = (room.boardState && room.boardState.pieces || []).find(p => p.square === square); + const allowedPieceIds = toucherEffects.map(e => e.pieceId).filter(Boolean); + if(!selectedPiece || allowedPieceIds.indexOf(selectedPiece.id) === -1){ + moves = []; + } + } + }catch(_){ /* ignore enforcement errors */ } // send selection to the selecting client WITH moves, but broadcast selection WITHOUT moves to other clients try{ // send to selecting socket (include moves) @@ -1334,6 +1357,37 @@ io.on('connection', (socket) => { played.payload = Object.assign({}, payload, { applied: 'fortification', appliedTo: target }); }catch(e){ console.error('fortification effect error', e); } } + // toucher c'est jouer: force the targeted player to move only the chosen piece on their next turn + else if((typeof cardId === 'string' && cardId.indexOf('toucher') !== -1) || cardId === 'toucher_cest_jouer'){ + 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 opponent + if(!board || !target || !targetPiece || targetPiece.color === playerColorShort){ + // invalid target: 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' }); + } + // find the owner (player) of the targeted piece + const targetOwner = (room.players || []).find(p => (p.color && p.color[0]) === targetPiece.color) || null; + // create a one-turn effect that forces the targetOwner to move only this piece on their next turn + room.activeCardEffects = room.activeCardEffects || []; + const effect = { id: played.id, type: 'toucher', playerId: (targetOwner && targetOwner.id) || null, pieceId: targetPiece.id, pieceSquare: targetPiece.square, remainingTurns: 1, decrementOn: 'owner', imposedBy: senderId, ts: Date.now() }; + room.activeCardEffects.push(effect); + played.payload = Object.assign({}, payload, { applied: 'toucher', appliedTo: target }); + try{ io.to(room.id).emit('card:effect:applied', { roomId: room.id, effect }); }catch(_){ } + }catch(e){ console.error('toucher effect error', e); } + } // teleportation: allow the selected piece to move to any empty square for one turn else if((typeof cardId === 'string' && (cardId.indexOf('teleport') !== -1 || cardId.indexOf('t_l_portation') !== -1 || cardId.indexOf('t_lportation') !== -1)) || cardId === 'teleport'){ try{