diff --git a/src/game.js b/src/game.js index 39ca918..eea6c07 100644 --- a/src/game.js +++ b/src/game.js @@ -401,7 +401,7 @@ function processEvent(internalGameState, event) { updateBoard(internalGameState, event, cards); } -export function computeState(gameData, gameMode) { +export function computeState(gameData, gameMode, newEvents = null) { if (!modes.hasOwnProperty(gameMode)) { throw new Error(`invalid gameMode: ${gameMode}`); } @@ -440,10 +440,21 @@ export function computeState(gameData, gameMode) { }; if (gameData.events) { - // Array.sort() is guaranteed to be stable in since around 2018 - const events = Object.values(gameData.events).sort( - (e1, e2) => e1.time - e2.time - ); + let events; + // Array.sort() is guaranteed to be stable in since around 2018. + if (newEvents?.size) { + // Always sort new events (with approximate times) after old events. + events = Object.entries(gameData.events) + .sort( + ([k1, e1], [k2, e2]) => + newEvents.has(k1) - newEvents.has(k2) || e1.time - e2.time + ) + .map(([k, e]) => e); + } else { + events = Object.values(gameData.events).sort( + (e1, e2) => e1.time - e2.time + ); + } for (const event of events) { processEvent(internalGameState, event); } diff --git a/src/pages/GamePage.js b/src/pages/GamePage.js index 8bfcd11..076a3b7 100644 --- a/src/pages/GamePage.js +++ b/src/pages/GamePage.js @@ -124,6 +124,7 @@ function GamePage({ match }) { const [game, loadingGame] = useFirebaseRef(`games/${gameId}`); const [gameData, loadingGameData] = useFirebaseRef(`gameData/${gameId}`); + const newEvents = useRef(new Set()); const [hasNextGame] = useFirebaseRef( game?.status === "done" && (!game.users || !(user.id in game.users)) ? `games/${nextGameId}/status` @@ -201,7 +202,11 @@ function GamePage({ match }) { lastKeptSet, } = useMemo(() => { if (!gameData) return {}; - const state = computeState({ ...gameData, random, deck }, gameMode); + const state = computeState( + { ...gameData, random, deck }, + gameMode, + newEvents.current + ); const { current, boardSize, findState, history } = state; const board = current.slice(0, boardSize); const answer = findSet(board, gameMode, findState); @@ -251,7 +256,7 @@ function GamePage({ match }) { if (numHints) { firebase.database().ref(`gameData/${gameId}/hints`).remove(); } - firebase + const eventRef = firebase .database() .ref(`gameData/${gameId}/events`) .push({ @@ -259,6 +264,21 @@ function GamePage({ match }) { user: user.id, time: firebase.database.ServerValue.TIMESTAMP, }); + // Track "new" events that have approximate times. An event is new since + // the time it was created until its time is updated from the server. + // This happens when our callback is called the second time (the first + // time it is called with the approximate time). + const eventKey = eventRef.key; + const timeRef = eventRef.child("time"); + newEvents.current.add(eventKey); + let updateCount = 0; + const timeUpdated = () => { + if (++updateCount === 2) { + newEvents.current.delete(eventKey); + timeRef.off("value", timeUpdated); + } + }; + timeRef.on("value", timeUpdated); } const hint = game.enableHint && answer ? answer.slice(0, numHints) : null;