golfgame/client/state-differ.js

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;
}