From 19aff04e602ba28623dfea90f474947d539de9f5 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Wed, 15 Apr 2026 17:02:41 -0400 Subject: [PATCH] fix: ties on final match of bracket --- connect4-ui/app/spectate/page.tsx | 66 +++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/connect4-ui/app/spectate/page.tsx b/connect4-ui/app/spectate/page.tsx index 6daea9b..0720ce9 100644 --- a/connect4-ui/app/spectate/page.tsx +++ b/connect4-ui/app/spectate/page.tsx @@ -53,6 +53,14 @@ interface KnockoutRoundView { projected?: boolean; } +interface BracketMatchEntry { + id: number; + player1: string; + player2: string; + currentTurnColor: 1 | 2 | null; + result: LiveGame["result"]; +} + export default function SpectatePage() { const router = useRouter(); const { @@ -305,6 +313,10 @@ export default function SpectatePage() { if (tournamentType === "KnockoutBracket" && !knockoutRawData) { setSelectedGame((prev) => (prev === msg.matchId ? null : prev)); } + setTimeout(() => { + send(cmd.gameList()); + send(cmd.playerList()); + }, 750); break; case "GAME_TERMINATED": @@ -386,8 +398,14 @@ export default function SpectatePage() { [players], ); const knockoutBracket = useMemo( - () => buildKnockoutBracket(knockoutRounds, liveGames, tournamentWinner), - [knockoutRounds, liveGames, tournamentWinner], + () => + buildKnockoutBracket( + knockoutRounds, + gameList, + liveGames, + tournamentWinner, + ), + [gameList, knockoutRounds, liveGames, tournamentWinner], ); const seedingMatches = useMemo(() => { const seen = new Set(); @@ -813,14 +831,37 @@ function parseKnockoutRounds(raw: string) { function buildKnockoutBracket( rounds: string[][], + gameList: GameEntry[], liveGames: Map, tournamentWinner: string | null, ) { if (rounds.length === 0) return [] as KnockoutRoundView[]; - const liveGameEntries = Array.from(liveGames.values()).sort( - (a, b) => b.id - a.id, - ); + const activeMatchIds = new Set(gameList.map((game) => game.id)); + const bracketMatchEntries: BracketMatchEntry[] = [ + ...[...gameList] + .sort((a, b) => b.id - a.id) + .map((game) => { + const live = liveGames.get(game.id); + return { + id: game.id, + player1: game.player1, + player2: game.player2, + currentTurnColor: live?.currentTurnColor ?? null, + result: null, + } satisfies BracketMatchEntry; + }), + ...Array.from(liveGames.values()) + .sort((a, b) => b.id - a.id) + .filter((game) => !activeMatchIds.has(game.id)) + .map((game) => ({ + id: game.id, + player1: game.player1, + player2: game.player2, + currentTurnColor: game.currentTurnColor, + result: game.result, + })), + ]; const displayRounds: Array<{ label: string; players: Array; @@ -837,7 +878,7 @@ function buildKnockoutBracket( if (index % 2 !== 0) return acc; const player1 = source[index] ?? null; const player2 = source[index + 1] ?? null; - const liveMatch = findLiveMatch(liveGameEntries, player1, player2); + const liveMatch = findBracketMatch(bracketMatchEntries, player1, player2); acc.push( resolveLiveWinner(liveMatch, tournamentWinner, player1, player2), ); @@ -860,7 +901,7 @@ function buildKnockoutBracket( ([player1, player2], matchIndex) => { const nextRound = displayRounds[roundIndex + 1]; const nextRoundPlayer = nextRound?.players[matchIndex] ?? null; - const liveMatch = findLiveMatch(liveGameEntries, player1, player2); + const liveMatch = findBracketMatch(bracketMatchEntries, player1, player2); const displayPlayer1 = liveMatch?.player1 ?? player1; const displayPlayer2 = liveMatch?.player2 ?? player2; const hasAdvancedPastRound = @@ -906,22 +947,21 @@ function pairPlayers(players: Array) { return pairs; } -function findLiveMatch( - liveGames: LiveGame[], +function findBracketMatch( + matches: BracketMatchEntry[], player1: string | null, player2: string | null, ) { if (!player1 || !player2) return null; return ( - liveGames.find((game) => - samePlayers(game.player1, game.player2, player1, player2), - ) ?? null + matches.find((game) => samePlayers(game.player1, game.player2, player1, player2)) ?? + null ); } function resolveLiveWinner( - liveMatch: LiveGame | null, + liveMatch: BracketMatchEntry | null, tournamentWinner: string | null, player1: string | null, player2: string | null,