Skip to content

Commit

Permalink
app engine: fix handling of ties
Browse files Browse the repository at this point in the history
When two players exactly tie (in milliseconds) on a buzz,
their clients can end up in a state where each client believes it has
won the buzz.

The getWinningBuzzer function iterates through the buzzes map to
find a winner. Since JavaScript Maps iterate in insertion order, each
local client can choose a different winner.

Sort the buzzes by userId prior to searching to make the winner
deterministic across clients.
  • Loading branch information
menonsamir authored and cmnord committed Aug 21, 2023
1 parent 9a9bbb8 commit a2c713f
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 13 deletions.
16 changes: 16 additions & 0 deletions app/engine/engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CANT_BUZZ_FLAG,
CLUE_TIMEOUT_MS,
gameEngine,
getWinningBuzzer,
} from "./engine";
import type { Player } from "./state";
import { GameState, State } from "./state";
Expand Down Expand Up @@ -1935,3 +1936,18 @@ describe("gameEngine", () => {
});
}
});

describe("getWinningBuzzer", () => {
it("returns the buzz of the lower user ID in case of a tie", () => {
// NB: Maps iterate over keys in insertion order
let buzzes = new Map();
buzzes.set(PLAYER1.userId, 100);
buzzes.set(PLAYER2.userId, 100);
expect(getWinningBuzzer(buzzes)?.userId).toBe(PLAYER1.userId);

buzzes = new Map();
buzzes.set(PLAYER2.userId, 100);
buzzes.set(PLAYER1.userId, 100);
expect(getWinningBuzzer(buzzes)?.userId).toBe(PLAYER1.userId);
});
});
29 changes: 16 additions & 13 deletions app/engine/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,22 @@ export function getWinningBuzzer(buzzes: Map<string, number>):
deltaMs: number;
}
| undefined {
const result = Array.from(buzzes.entries()).reduce(
(acc, [userId, deltaMs]) => {
if (
deltaMs !== CANT_BUZZ_FLAG &&
deltaMs < acc.deltaMs &&
deltaMs <= CLUE_TIMEOUT_MS
) {
return { userId, deltaMs };
}
return acc;
},
{ userId: "", deltaMs: Number.MAX_SAFE_INTEGER },
);
const result = Array.from(buzzes.entries())
// Sort buzzes by user ID for deterministic results in case of a tie.
.sort(([aUserId], [bUserId]) => (aUserId > bUserId ? 1 : -1))
.reduce(
(acc, [userId, deltaMs]) => {
if (
deltaMs !== CANT_BUZZ_FLAG &&
deltaMs < acc.deltaMs &&
deltaMs <= CLUE_TIMEOUT_MS
) {
return { userId, deltaMs };
}
return acc;
},
{ userId: "", deltaMs: Number.MAX_SAFE_INTEGER },
);

if (result.userId === "") {
return undefined;
Expand Down

1 comment on commit a2c713f

@vercel
Copy link

@vercel vercel bot commented on a2c713f Aug 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.