165 lines
6.4 KiB
JavaScript
165 lines
6.4 KiB
JavaScript
// StateDiffer - Detects what changed between game states
|
|
// Generates movement instructions for the animation queue
|
|
|
|
class StateDiffer {
|
|
constructor() {
|
|
this.previousState = null;
|
|
}
|
|
|
|
// Compare old and new state, return array of movements
|
|
diff(oldState, newState) {
|
|
const movements = [];
|
|
|
|
if (!oldState || !newState) {
|
|
return movements;
|
|
}
|
|
|
|
// Check for initial flip phase - still animate initial flips
|
|
if (oldState.waiting_for_initial_flip && !newState.waiting_for_initial_flip) {
|
|
// Initial flip just completed - detect which cards were flipped
|
|
for (const newPlayer of newState.players) {
|
|
const oldPlayer = oldState.players.find(p => p.id === newPlayer.id);
|
|
if (oldPlayer) {
|
|
for (let i = 0; i < 6; i++) {
|
|
if (!oldPlayer.cards[i].face_up && newPlayer.cards[i].face_up) {
|
|
movements.push({
|
|
type: 'flip',
|
|
playerId: newPlayer.id,
|
|
position: i,
|
|
faceUp: true,
|
|
card: newPlayer.cards[i]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return movements;
|
|
}
|
|
|
|
// Still in initial flip selection - no animations
|
|
if (newState.waiting_for_initial_flip) {
|
|
return movements;
|
|
}
|
|
|
|
// Check for turn change - the previous player just acted
|
|
const previousPlayerId = oldState.current_player_id;
|
|
const currentPlayerId = newState.current_player_id;
|
|
const turnChanged = previousPlayerId !== currentPlayerId;
|
|
|
|
// Detect if a swap happened (discard changed AND a hand position changed)
|
|
const newTop = newState.discard_top;
|
|
const oldTop = oldState.discard_top;
|
|
const discardChanged = newTop && (!oldTop ||
|
|
oldTop.rank !== newTop.rank ||
|
|
oldTop.suit !== newTop.suit);
|
|
|
|
// Find hand changes for the player who just played
|
|
if (turnChanged && previousPlayerId) {
|
|
const oldPlayer = oldState.players.find(p => p.id === previousPlayerId);
|
|
const newPlayer = newState.players.find(p => p.id === previousPlayerId);
|
|
|
|
if (oldPlayer && newPlayer) {
|
|
// First pass: detect swaps (card identity changed)
|
|
const swappedPositions = new Set();
|
|
for (let i = 0; i < 6; i++) {
|
|
const oldCard = oldPlayer.cards[i];
|
|
const newCard = newPlayer.cards[i];
|
|
|
|
// Card identity changed = swap happened at this position
|
|
if (this.cardIdentityChanged(oldCard, newCard)) {
|
|
swappedPositions.add(i);
|
|
|
|
// Use discard_top for the revealed card (more reliable for opponents)
|
|
const revealedCard = newState.discard_top || { ...oldCard, face_up: true };
|
|
|
|
movements.push({
|
|
type: 'swap',
|
|
playerId: previousPlayerId,
|
|
position: i,
|
|
oldCard: revealedCard,
|
|
newCard: newCard
|
|
});
|
|
break; // Only one swap per turn
|
|
}
|
|
}
|
|
|
|
// Second pass: detect flips (card went from face_down to face_up, not a swap)
|
|
for (let i = 0; i < 6; i++) {
|
|
if (swappedPositions.has(i)) continue; // Skip if already detected as swap
|
|
|
|
const oldCard = oldPlayer.cards[i];
|
|
const newCard = newPlayer.cards[i];
|
|
|
|
if (this.cardWasFlipped(oldCard, newCard)) {
|
|
movements.push({
|
|
type: 'flip',
|
|
playerId: previousPlayerId,
|
|
position: i,
|
|
faceUp: true,
|
|
card: newCard
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detect drawing (current player just drew)
|
|
if (newState.has_drawn_card && !oldState.has_drawn_card) {
|
|
// Discard pile decreased = drew from discard
|
|
const drewFromDiscard = !newState.discard_top ||
|
|
(oldState.discard_top &&
|
|
(!newState.discard_top ||
|
|
oldState.discard_top.rank !== newState.discard_top.rank ||
|
|
oldState.discard_top.suit !== newState.discard_top.suit));
|
|
|
|
movements.push({
|
|
type: drewFromDiscard ? 'draw-discard' : 'draw-deck',
|
|
playerId: currentPlayerId
|
|
});
|
|
}
|
|
|
|
return movements;
|
|
}
|
|
|
|
// Check if the card identity (rank+suit) changed between old and new
|
|
// Returns true if definitely different cards, false if same or unknown
|
|
cardIdentityChanged(oldCard, newCard) {
|
|
// If both have rank/suit data, compare directly
|
|
if (oldCard.rank && newCard.rank) {
|
|
return oldCard.rank !== newCard.rank || oldCard.suit !== newCard.suit;
|
|
}
|
|
// Can't determine - assume same card (flip, not swap)
|
|
return false;
|
|
}
|
|
|
|
// Check if a card was just flipped (same card, now face up)
|
|
cardWasFlipped(oldCard, newCard) {
|
|
return !oldCard.face_up && newCard.face_up;
|
|
}
|
|
|
|
// Get a summary of movements for debugging
|
|
summarize(movements) {
|
|
return movements.map(m => {
|
|
switch (m.type) {
|
|
case 'flip':
|
|
return `Flip: Player ${m.playerId} position ${m.position}`;
|
|
case 'swap':
|
|
return `Swap: Player ${m.playerId} position ${m.position}`;
|
|
case 'discard':
|
|
return `Discard: ${m.card.rank}${m.card.suit} from player ${m.fromPlayerId}`;
|
|
case 'draw-deck':
|
|
return `Draw from deck: Player ${m.playerId}`;
|
|
case 'draw-discard':
|
|
return `Draw from discard: Player ${m.playerId}`;
|
|
default:
|
|
return `Unknown: ${m.type}`;
|
|
}
|
|
}).join('\n');
|
|
}
|
|
}
|
|
|
|
// Export for use in app.js
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = StateDiffer;
|
|
}
|