inversion
This commit is contained in:
parent
72d096f0e5
commit
1624e4caf8
2 changed files with 127 additions and 6 deletions
|
|
@ -115,6 +115,8 @@
|
|||
if(id.indexOf('coin') !== -1 || id === 'coin_coin') return 'owned';
|
||||
// teleportation: require selecting one of your pieces first (like promotion)
|
||||
if(id.indexOf('teleport') !== -1 || id.indexOf('tele') !== -1 || id.indexOf('t_l_portation') !== -1) return 'owned';
|
||||
// inversion: first choose one of your pieces, then choose an enemy piece to swap places
|
||||
if(id.indexOf('inversion') !== -1) return 'own_then_enemy';
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -483,8 +485,35 @@
|
|||
|
||||
// If we're in a pending card-targeting mode, handle target selection and do NOT attempt moves
|
||||
if(pendingCard){
|
||||
const req = pendingCard.requireType; // 'empty' | 'owned' | 'owned_pawn' | 'enemy'
|
||||
const req = pendingCard.requireType; // 'empty' | 'owned' | 'owned_pawn' | 'enemy' | 'own_then_enemy' | 'enemy_queen'
|
||||
const myShort = (myColor && myColor[0]) || '';
|
||||
// handle two-step selection for cards like 'inversion'
|
||||
if(req === 'own_then_enemy'){
|
||||
// first select one of your own pieces
|
||||
if(!pendingCard.firstTarget){
|
||||
const piece = currentBoardState && Array.isArray(currentBoardState.pieces) && currentBoardState.pieces.find(p => p.square === square);
|
||||
if(!piece || piece.color !== myShort){ log('select one of your pieces (first)'); return; }
|
||||
if(piece.type && piece.type.toLowerCase() === 'k'){ log('cannot select king'); return; }
|
||||
// store first selection and highlight enemy pieces
|
||||
pendingCard.firstTarget = square;
|
||||
highlightTargets('enemy');
|
||||
// mark visually the chosen source (temporary)
|
||||
try{ const el = boardEl.querySelector(`.square[data-square="${square}"]`); if(el) el.classList.add('target-chosen'); }catch(_){ }
|
||||
log('First piece selected for inversion:', square);
|
||||
return;
|
||||
}
|
||||
// second selection: enemy piece to swap with
|
||||
const occ = currentBoardState && Array.isArray(currentBoardState.pieces) && currentBoardState.pieces.find(p => p.square === square);
|
||||
if(!occ || occ.color === myShort){ log('select an enemy piece (second)'); return; }
|
||||
// emit swap payload: sourceSquare + targetSquare
|
||||
socket.emit('card:play', { roomId, playerId: myPlayerId, cardId: pendingCard.cardId, payload: { sourceSquare: pendingCard.firstTarget, targetSquare: square } }, (resp)=>{
|
||||
if(resp && resp.error){ log('card:play error', resp); alert(resp.error); }
|
||||
else { log('inversion played', resp); }
|
||||
});
|
||||
// cleanup visuals
|
||||
try{ const el = boardEl.querySelector(`.square[data-square="${pendingCard.firstTarget}"]`); if(el) el.classList.remove('target-chosen'); }catch(_){ }
|
||||
pendingCard = null; clearTargetHighlights(); return;
|
||||
}
|
||||
if(req === 'empty'){
|
||||
const occupied = currentBoardState && Array.isArray(currentBoardState.pieces) && currentBoardState.pieces.find(p => p.square === square);
|
||||
if(occupied){ log('square not empty for mine placement'); return; }
|
||||
|
|
@ -497,6 +526,7 @@
|
|||
} else if(req === 'owned'){
|
||||
const piece = currentBoardState && Array.isArray(currentBoardState.pieces) && currentBoardState.pieces.find(p => p.square === square);
|
||||
if(!piece || piece.color !== myShort){ log('select one of your pieces'); return; }
|
||||
if(piece.type && piece.type.toLowerCase() === 'k'){ log('cannot select king'); return; }
|
||||
socket.emit('card:play', { roomId, playerId: myPlayerId, cardId: pendingCard.cardId, payload: { targetSquare: square } }, (resp)=>{
|
||||
if(resp && resp.error){ log('card:play error', resp); alert(resp.error); }
|
||||
else { log('card played with target', resp); }
|
||||
|
|
@ -524,6 +554,7 @@
|
|||
} else if(req === 'enemy'){
|
||||
const occ = currentBoardState && Array.isArray(currentBoardState.pieces) && currentBoardState.pieces.find(p => p.square === square);
|
||||
if(!occ || occ.color === myShort){ log('select an enemy piece'); return; }
|
||||
if(occ.type && occ.type.toLowerCase() === 'k'){ log('cannot select king'); return; }
|
||||
socket.emit('card:play', { roomId, playerId: myPlayerId, cardId: pendingCard.cardId, payload: { targetSquare: square } }, (resp)=>{
|
||||
if(resp && resp.error){ log('card:play error', resp); alert(resp.error); }
|
||||
else { log('card played with target', resp);
|
||||
|
|
@ -724,16 +755,20 @@
|
|||
if(!occ) sqEl.classList.add('targetable');
|
||||
} else if(type === 'owned'){
|
||||
const occ = (currentBoardState.pieces||[]).find(p=>p.square===sq);
|
||||
if(occ && occ.color === myShort) sqEl.classList.add('targetable');
|
||||
if(occ && occ.color === myShort && occ.type && occ.type.toLowerCase() !== 'k') sqEl.classList.add('targetable');
|
||||
} else if(type === 'owned_pawn'){
|
||||
const occ = (currentBoardState.pieces||[]).find(p=>p.square===sq);
|
||||
if(occ && occ.color === myShort && occ.type && occ.type.toLowerCase() === 'p') sqEl.classList.add('targetable');
|
||||
} else if(type === 'own_then_enemy'){
|
||||
// initial phase: highlight only own pieces (client will switch to enemy after first selection)
|
||||
const occ = (currentBoardState.pieces||[]).find(p=>p.square===sq);
|
||||
if(occ && occ.color === myShort && occ.type && occ.type.toLowerCase() !== 'k') sqEl.classList.add('targetable');
|
||||
} else if(type === 'enemy_queen'){
|
||||
const occ = (currentBoardState.pieces||[]).find(p=>p.square===sq);
|
||||
if(occ && occ.color && occ.color !== myShort && occ.type && occ.type.toLowerCase() === 'q') sqEl.classList.add('targetable');
|
||||
} else if(type === 'enemy'){
|
||||
const occ = (currentBoardState.pieces||[]).find(p=>p.square===sq);
|
||||
if(occ && occ.color && occ.color !== myShort) sqEl.classList.add('targetable');
|
||||
if(occ && occ.color && occ.color !== myShort && occ.type && occ.type.toLowerCase() !== 'k') sqEl.classList.add('targetable');
|
||||
}
|
||||
});
|
||||
// add a small note near hands
|
||||
|
|
|
|||
92
server.js
92
server.js
|
|
@ -136,6 +136,48 @@ function visibleSquaresForPlayer(room, playerId){
|
|||
return out;
|
||||
}
|
||||
|
||||
// Helper to end a player's turn after playing a card: flip turn, decrement per-turn effects and trigger next player's draw
|
||||
function endTurnAfterCard(room, senderId){
|
||||
try{
|
||||
if(!room || !room.boardState) return;
|
||||
const board = room.boardState;
|
||||
// flip turn
|
||||
if(board.turn) board.turn = (board.turn === 'w') ? 'b' : 'w';
|
||||
// decrement remainingTurns for any time-limited effects that belong to the player who just finished their turn
|
||||
try{
|
||||
room.activeCardEffects = room.activeCardEffects || [];
|
||||
for(let i = room.activeCardEffects.length - 1; i >= 0; i--){
|
||||
const e = room.activeCardEffects[i];
|
||||
if(typeof e.remainingTurns === 'number'){
|
||||
let shouldDecrement = false;
|
||||
if(e.decrementOn === 'opponent'){
|
||||
shouldDecrement = (e.playerId !== senderId);
|
||||
} else if(e.decrementOn === 'owner'){
|
||||
shouldDecrement = (e.playerId === senderId);
|
||||
} else {
|
||||
shouldDecrement = (e.playerId === senderId);
|
||||
}
|
||||
if(shouldDecrement){
|
||||
e.remainingTurns = e.remainingTurns - 1;
|
||||
try{ io.to(room.id).emit('card:effect:updated', { roomId: room.id, effect: e }); }catch(_){ }
|
||||
if(e.remainingTurns <= 0){ room.activeCardEffects.splice(i,1); try{ io.to(room.id).emit('card:effect:removed', { roomId: room.id, effectId: e.id, type: e.type, playerId: e.playerId }); }catch(_){ } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(e){ console.error('updating temporary effects error', e); }
|
||||
|
||||
// reset per-turn card-play flags
|
||||
try{ room._cardPlayedThisTurn = {}; }catch(_){ }
|
||||
|
||||
// perform next player's draw (if autoDraw) or at least send room update
|
||||
try{
|
||||
const nextColor = board.turn;
|
||||
const nextPlayer = room.players.find(p => (p.color && p.color[0]) === nextColor);
|
||||
if(nextPlayer){ maybeDrawAtTurnStart(room, nextPlayer.id); } else { sendRoomUpdate(room); }
|
||||
}catch(_){ sendRoomUpdate(room); }
|
||||
}catch(e){ console.error('endTurnAfterCard error', e); }
|
||||
}
|
||||
|
||||
// Build a default deck from README list. Each card has a unique id, cardId (slug), title and description.
|
||||
function buildDefaultDeck(){
|
||||
const cards = [
|
||||
|
|
@ -174,7 +216,7 @@ function buildDefaultDeck(){
|
|||
["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'],
|
||||
['inversion','Échange la position d une pièce avec une pièce adverse'],
|
||||
// ['jeu des 7 différences','Déplace une pièce du plateau pendant que le joueur adverse à les yeux fermés. S il la retrouve, elle est capturée, laissée sinon'],
|
||||
// ['punching ball','Replace le roi dans sa position initiale, et place un nouveau pion à l ancienne position du roi'],
|
||||
// ['reversi','Si deux pions encadrent parfaitement une pièce adverse, cette dernière change de camp'],
|
||||
|
|
@ -1412,6 +1454,50 @@ io.on('connection', (socket) => {
|
|||
try{ io.to(room.id).emit('card:effect:applied', { roomId: room.id, effect: { id: played.id, type: 'parrure', pieceId: targetPiece.id, pieceSquare: targetPiece.square, playerId: senderId } }); }catch(_){ }
|
||||
}catch(e){ console.error('parrure apply error', e); }
|
||||
}catch(e){ console.error('parrure effect error', e); }
|
||||
}
|
||||
// inversion: pick one of your pieces, then pick an enemy piece — swap their squares
|
||||
else if((typeof cardId === 'string' && cardId.indexOf('inversion') !== -1) || cardId === 'inversion'){
|
||||
try{
|
||||
const board = room.boardState;
|
||||
const payloadSrc = payload && payload.sourceSquare;
|
||||
const payloadTgt = payload && payload.targetSquare;
|
||||
// fallback to lastSelectedSquare for source if client didn't provide both
|
||||
let source = payloadSrc || null;
|
||||
let target = payloadTgt || null;
|
||||
if(!source){ try{ source = socket.data && socket.data.lastSelectedSquare; }catch(_){ source = null; } }
|
||||
if(!target){ /* nothing */ }
|
||||
const roomPlayer = room.players.find(p => p.id === senderId);
|
||||
const playerColorShort = (roomPlayer && roomPlayer.color && roomPlayer.color[0]) || null;
|
||||
const srcPiece = (board && board.pieces || []).find(p => p.square === source);
|
||||
const tgtPiece = (board && board.pieces || []).find(p => p.square === target);
|
||||
// validate both pieces exist and belong respectively to player and opponent
|
||||
if(!board || !source || !target || !srcPiece || !tgtPiece || srcPiece.color !== playerColorShort || tgtPiece.color === playerColorShort){
|
||||
// invalid target(s): 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 targets' });
|
||||
}
|
||||
// swap their squares
|
||||
try{
|
||||
const sSquare = srcPiece.square;
|
||||
const tSquare = tgtPiece.square;
|
||||
srcPiece.square = tSquare;
|
||||
tgtPiece.square = sSquare;
|
||||
// update any activeCardEffects that reference squares
|
||||
try{
|
||||
room.activeCardEffects = room.activeCardEffects || [];
|
||||
room.activeCardEffects.forEach(e => {
|
||||
try{
|
||||
if(e && e.pieceSquare && e.pieceSquare === sSquare) e.pieceSquare = tSquare;
|
||||
else if(e && e.pieceSquare && e.pieceSquare === tSquare) e.pieceSquare = sSquare;
|
||||
}catch(_){ }
|
||||
});
|
||||
}catch(_){ }
|
||||
// bump board version
|
||||
try{ board.version = (board.version || 0) + 1; }catch(_){ }
|
||||
played.payload = Object.assign({}, payload, { applied: 'inversion', from: source, to: target });
|
||||
try{ io.to(room.id).emit('card:effect:applied', { roomId: room.id, effect: { id: played.id, type: 'inversion', swapped: [srcPiece.id, tgtPiece.id], playerId: senderId } }); }catch(_){ }
|
||||
}catch(e){ console.error('inversion apply error', e); }
|
||||
}catch(e){ console.error('inversion 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'){
|
||||
|
|
@ -2029,8 +2115,8 @@ io.on('connection', (socket) => {
|
|||
}catch(e){ console.error('mark card played error', e); }
|
||||
// emit card played to entire room (informational)
|
||||
io.to(roomId).emit('card:played', played);
|
||||
// send personalized room updates (will include updated hands and discardCount)
|
||||
sendRoomUpdate(room);
|
||||
// After playing a card, the player's turn is consumed: perform end-of-turn bookkeeping then send updates
|
||||
try{ endTurnAfterCard(room, played.playerId); }catch(_){ /* fallback to simple update */ sendRoomUpdate(room); }
|
||||
|
||||
cb && cb({ ok: true, played });
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue