vole d'une carte
This commit is contained in:
parent
1504d2bc81
commit
c9b8588c4f
2 changed files with 108 additions and 6 deletions
|
|
@ -79,8 +79,11 @@
|
||||||
if(id.indexOf('fortif') !== -1 || id.indexOf('fortification') !== -1) return 'owned';
|
if(id.indexOf('fortif') !== -1 || id.indexOf('fortification') !== -1) return 'owned';
|
||||||
// promotion targets one of your pawns (special handling client-side to show promotion choice)
|
// promotion targets one of your pawns (special handling client-side to show promotion choice)
|
||||||
if(id.indexOf('promotion') !== -1 || id.indexOf('promot') !== -1 || id.indexOf('promouvoir') !== -1) return 'owned_pawn';
|
if(id.indexOf('promotion') !== -1 || id.indexOf('promot') !== -1 || id.indexOf('promouvoir') !== -1) return 'owned_pawn';
|
||||||
// steal/vol card targets an enemy piece
|
// steal a card (target a player) if the card mentions 'carte'/'card' — otherwise a 'vol' often targets a piece
|
||||||
if(id.indexOf('vol') !== -1 || id.indexOf('vole') !== -1 || id.indexOf('steal') !== -1) return 'enemy';
|
if(id.indexOf('vol') !== -1 || id.indexOf('vole') !== -1 || id.indexOf('steal') !== -1){
|
||||||
|
if(id.indexOf('carte') !== -1 || id.indexOf('card') !== -1) return 'player';
|
||||||
|
return 'enemy';
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,6 +181,22 @@
|
||||||
}
|
}
|
||||||
}catch(e){ console.warn('card:effect:applied handler error', e); }
|
}catch(e){ console.warn('card:effect:applied handler error', e); }
|
||||||
});
|
});
|
||||||
|
// when the server sends the stolen card to the stealer (private)
|
||||||
|
socket.on('card:stolen', (data) => {
|
||||||
|
try{
|
||||||
|
log('recv card:stolen', data);
|
||||||
|
// show notification and a small highlight in hands
|
||||||
|
const handsEl = document.getElementById('hands');
|
||||||
|
if(handsEl){
|
||||||
|
const note = document.createElement('div'); note.className = 'card-draw-note';
|
||||||
|
note.textContent = data && data.card ? `Vous avez volé : ${data.card.title || data.card.cardId || 'une carte'}` : 'Vous avez volé une carte';
|
||||||
|
handsEl.prepend(note); setTimeout(()=> note.remove(), 6000);
|
||||||
|
}
|
||||||
|
}catch(e){ console.warn('card:stolen handler error', e); }
|
||||||
|
});
|
||||||
|
socket.on('card:lost', (data) => {
|
||||||
|
try{ log('recv card:lost', data); showEffectNotification({ type: 'card_lost' }); }catch(e){ }
|
||||||
|
});
|
||||||
// selection events (include moves) broadcasted by server
|
// selection events (include moves) broadcasted by server
|
||||||
socket.on('game:select', (data) => {
|
socket.on('game:select', (data) => {
|
||||||
try{
|
try{
|
||||||
|
|
@ -756,6 +775,27 @@
|
||||||
const cid = cardEl.dataset.cardId;
|
const cid = cardEl.dataset.cardId;
|
||||||
const requireType = cardRequiresTarget(cid);
|
const requireType = cardRequiresTarget(cid);
|
||||||
if(requireType){
|
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];
|
||||||
|
socket.emit('card:play', { roomId, playerId: myPlayerId, cardId: cid, payload: { targetPlayerId } }, (resp)=>{
|
||||||
|
if(resp && resp.error){ log('card:play error', resp); alert(resp.error); }
|
||||||
|
else { log('card played (steal card)', resp); }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// show modal to pick which player to steal from
|
||||||
|
showStealModal(handCounts, (targetPlayerId)=>{
|
||||||
|
if(!targetPlayerId) return; // canceled
|
||||||
|
socket.emit('card:play', { roomId, playerId: myPlayerId, cardId: cid, payload: { targetPlayerId } }, (resp)=>{
|
||||||
|
if(resp && resp.error){ log('card:play error', resp); alert(resp.error); }
|
||||||
|
else { log('card played (steal card)', resp); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
// enter pending target mode: next square click will send the card with payload.targetSquare
|
// enter pending target mode: next square click will send the card with payload.targetSquare
|
||||||
pendingCard = { cardId: cid, cardUuid: cardEl.dataset.cardUuid || '', requireType };
|
pendingCard = { cardId: cid, cardUuid: cardEl.dataset.cardUuid || '', requireType };
|
||||||
highlightTargets(requireType);
|
highlightTargets(requireType);
|
||||||
|
|
@ -805,6 +845,7 @@
|
||||||
else if(effect.type === 'promotion') note.textContent = 'Promotion réussie !';
|
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 === '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 === '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}`;
|
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';
|
note.style.padding = '6px 8px'; note.style.border = '1px solid #ccc'; note.style.background = '#fff8e1'; note.style.marginTop = '8px';
|
||||||
handsEl.prepend(note);
|
handsEl.prepend(note);
|
||||||
|
|
@ -861,6 +902,31 @@
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
}catch(e){ console.warn('showPromotionModal error', e); callback(null); }
|
}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); }
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
40
server.js
40
server.js
|
|
@ -108,7 +108,8 @@ function buildDefaultDeck(){
|
||||||
// ['annulation d une carte','Annule l effet d une carte qui est jouée par l adversaire'],
|
// ['annulation d une carte','Annule l effet d une carte qui est jouée par l adversaire'],
|
||||||
['placement de mines','Le joueur place une mine sur une case vide sans la révéler au joueur adverse. Une pièce qui se pose dessus explose et est capturée par le joueur ayant placé la mine'],
|
['placement de mines','Le joueur place une mine sur une case vide sans la révéler au joueur adverse. Une pièce qui se pose dessus explose et est capturée par le joueur ayant placé la mine'],
|
||||||
['vole d une pièce','Désigne une pièce non roi qui change de camp'],
|
['vole d une pièce','Désigne une pièce non roi qui change de camp'],
|
||||||
['promotion','Un pion au choix est promu reine'],
|
['promotion','Un pion au choix est promu'],
|
||||||
|
['vole d une carte','Vole une carte aléatoirement au joueur adverse'],
|
||||||
// ['vole d une carte','Vole une carte aléatoirement au joueur adverse'],
|
// ['vole d une carte','Vole une carte aléatoirement au joueur adverse'],
|
||||||
// ['resurection','Choisie une pièce capturée pour la ressuciter dans son camp'],
|
// ['resurection','Choisie une pièce capturée pour la ressuciter dans son camp'],
|
||||||
// ['carte sans effet','N a aucun effet'],
|
// ['carte sans effet','N a aucun effet'],
|
||||||
|
|
@ -1235,7 +1236,8 @@ io.on('connection', (socket) => {
|
||||||
}catch(e){ console.error('double_move effect error', e); }
|
}catch(e){ console.error('double_move effect error', e); }
|
||||||
}
|
}
|
||||||
// vol de pièce: transfer ownership of a targeted enemy piece to the playing player
|
// vol de pièce: transfer ownership of a targeted enemy piece to the playing player
|
||||||
else if((typeof cardId === 'string' && (cardId.indexOf('vol') !== -1 || cardId.indexOf('vole') !== -1 || cardId.indexOf('steal') !== -1))){
|
// Note: do NOT handle "vole ... carte" here (steal-a-card) — that is handled by a separate branch below.
|
||||||
|
else if((typeof cardId === 'string' && (cardId.indexOf('vol') !== -1 || cardId.indexOf('vole') !== -1 || cardId.indexOf('steal') !== -1) && !(cardId.indexOf('carte') !== -1 || cardId.indexOf('card') !== -1))){
|
||||||
try{
|
try{
|
||||||
const board = room.boardState;
|
const board = room.boardState;
|
||||||
let target = payload && payload.targetSquare;
|
let target = payload && payload.targetSquare;
|
||||||
|
|
@ -1266,6 +1268,40 @@ io.on('connection', (socket) => {
|
||||||
}
|
}
|
||||||
}catch(e){ console.error('steal effect error', e); }
|
}catch(e){ console.error('steal effect error', e); }
|
||||||
}
|
}
|
||||||
|
// vole d'une carte: steal one random card from the target player's hand
|
||||||
|
else if((typeof cardId === 'string' && cardId.indexOf('vole') !== -1 && (cardId.indexOf('carte') !== -1 || cardId.indexOf('card') !== -1))){
|
||||||
|
try{
|
||||||
|
// determine target player id: payload.targetPlayerId or pick the opponent
|
||||||
|
let targetPlayerId = payload && payload.targetPlayerId;
|
||||||
|
if(!targetPlayerId){ const opp = (room.players || []).find(p => p.id !== senderId); targetPlayerId = opp && opp.id; }
|
||||||
|
const targetPlayer = (room.players || []).find(p => p.id === targetPlayerId);
|
||||||
|
if(!targetPlayer || targetPlayer.id === senderId){
|
||||||
|
// invalid target: consume card and notify owner
|
||||||
|
played.payload = Object.assign({}, payload, { applied: 'steal_card_failed', attemptedTo: targetPlayerId });
|
||||||
|
try{ const owner = (room.players || []).find(p => p.id === senderId); if(owner && owner.socketId) io.to(owner.socketId).emit('card:effect:applied', { roomId: room.id, effect: { id: played.id, type: 'steal_card_failed', playerId: senderId, targetPlayerId, ts: Date.now() } }); }catch(_){ }
|
||||||
|
} else {
|
||||||
|
room.hands = room.hands || {};
|
||||||
|
const victimHand = room.hands[targetPlayerId] || [];
|
||||||
|
if(!victimHand || victimHand.length === 0){
|
||||||
|
// nothing to steal
|
||||||
|
played.payload = Object.assign({}, payload, { applied: 'steal_card_failed_empty', attemptedTo: targetPlayerId });
|
||||||
|
try{ const owner = (room.players || []).find(p => p.id === senderId); if(owner && owner.socketId) io.to(owner.socketId).emit('card:effect:applied', { roomId: room.id, effect: { id: played.id, type: 'steal_card_failed_empty', playerId: senderId, targetPlayerId, ts: Date.now() } }); }catch(_){ }
|
||||||
|
} else {
|
||||||
|
// pick random card from victim
|
||||||
|
const idx = Math.floor(Math.random() * victimHand.length);
|
||||||
|
const stolen = victimHand.splice(idx,1)[0];
|
||||||
|
// give to stealer
|
||||||
|
room.hands[senderId] = room.hands[senderId] || [];
|
||||||
|
room.hands[senderId].push(stolen);
|
||||||
|
played.payload = Object.assign({}, payload, { applied: 'steal_card', stolenFrom: targetPlayerId, stolenCardId: stolen.cardId || stolen.id });
|
||||||
|
// inform the stealer privately about the stolen card details
|
||||||
|
try{ const stealer = (room.players || []).find(p => p.id === senderId); if(stealer && stealer.socketId) io.to(stealer.socketId).emit('card:stolen', { roomId: room.id, from: targetPlayerId, card: stolen }); }catch(_){ }
|
||||||
|
// inform the victim privately that they lost a card (do not reveal which)
|
||||||
|
try{ const victim = (room.players || []).find(p => p.id === targetPlayerId); if(victim && victim.socketId) io.to(victim.socketId).emit('card:lost', { roomId: room.id, lostCount: 1 }); }catch(_){ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(e){ console.error('steal-card effect error', e); }
|
||||||
|
}
|
||||||
// rebondir: grant a one-time bounce ability to a specific piece (targetSquare required in payload)
|
// rebondir: grant a one-time bounce ability to a specific piece (targetSquare required in payload)
|
||||||
else if(cardId === 'rebondir_sur_les_bords' || cardId === 'rebondir' || (typeof cardId === 'string' && cardId.indexOf('rebondir') !== -1)){
|
else if(cardId === 'rebondir_sur_les_bords' || cardId === 'rebondir' || (typeof cardId === 'string' && cardId.indexOf('rebondir') !== -1)){
|
||||||
try{
|
try{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue