I am working on a tournament bracket with double elimination in Svelte. I keep having the issue of when two matchups have four teams when I update the score to select the winners, the matchup where the winners would face gets messed up. The team that was waiting for the other team gets set back to pending and the matchup that I just updated, gets put in the other team spot.
Visual of what is happening: https://imgur.com/a/Bzy2b5A
I believe it has something to do in updateBracket()
but I'm not sure besides that. How do I stop this from happening when I input new scores?
Here are the types:
export type Bracket = {
id: number;
name: string;
size: number;
teams: BracketTeam[];
};
export type BracketTeam = {
id: string;
name: string;
seed: number;
bracket_id?: number;
};
export type BracketMatchup = {
team1: { id: string; name: string; seed: number; score: number };
team2: { id: string; name: string; seed: number; score: number };
};
export type BracketScores = { [round: string]: { matchups: BracketMatchup[] } };
Here is the code that I use for the page:
<script lang="ts">
import { _showError } from '../../../+page';
import type {
Bracket,
BracketMatchup,
BracketScores,
BracketTeam
} from '../../../../stores/brackets';
export let selectedBracket: Bracket;
export let allMatchups: BracketScores = {};
export let existingMatchups: BracketScores = {};
$: createMatchups([...selectedBracket.teams]);
// $: console.log(allMatchups);
function createMatchups(teams: BracketTeam[], round = '0', losersBracket = false) {
// Stop if less than 2 teams
if (teams.length < 2) return;
// Create bye team
const bye = { id: 'bye', name: 'Bye', seed: 0 };
if (round === '0') {
// Make first round power of 2
const needByes = Math.log2(teams.length) % 1 !== 0;
if (needByes) {
while (Math.log2(teams.length) % 1 !== 0) teams.push(bye);
}
// Shuffle teams by seed
const numRounds = Math.log2(teams.length / 2);
for (let i = 0; i < numRounds; i++) {
let out = [];
const splice = 2 ** i;
while (teams.length > 0) {
out.push(...teams.splice(0, splice));
out.push(...teams.splice(-splice));
}
teams = out;
}
}
// Create matchups
const bracketKey = losersBracket ? `losers_${round}` : round;
allMatchups[bracketKey] = { matchups: [] };
// Add bye to teams if odd
if (teams.length % 2 !== 0) teams.splice(1, 0, bye);
// Pair teams
for (let i = 0; i < teams.length; i += 2) {
const existingMatchup = existingMatchups[bracketKey]?.matchups.find(
(matchup) => matchup.team1.id === teams[i].id && matchup.team2.id === teams[i + 1].id
);
const team1 = { ...teams[i], score: existingMatchup ? existingMatchup.team1.score : 0 };
const team2 = { ...teams[i + 1], score: existingMatchup ? existingMatchup.team2.score : 0 };
allMatchups[bracketKey].matchups.push({ team1, team2 });
}
// Select winners for next round
const pending = { id: 'pending', name: 'Pending', seed: 0 };
let winners = allMatchups[bracketKey].matchups.map((matchup) => {
const { team1, team2 } = matchup;
if (team1.id === 'bye') return team2;
if (team2.id === 'bye') return team1;
if (team1.score > team2.score) return team1;
if (team2.score > team1.score) return team2;
return pending;
});
// Create next round
if (round === '0') {
// Select losers for losers bracket
const losers = allMatchups[bracketKey].matchups.map((matchup) => {
const { team1, team2 } = matchup;
if (team1.id === 'bye') return bye;
if (team2.id === 'bye') return bye;
if (team1.score < team2.score) return team1;
if (team2.score < team1.score) return team2;
return pending;
});
// Create losers bracket
createMatchups(losers, +round + 1 + '', true);
} else if (losersBracket) createMatchups(winners, +round + 1 + '', true);
createMatchups(winners, +round + 1 + '');
}
function updateBracket(round: string, matchup: BracketMatchup) {
console.log(matchup);
// Make sure scores are not negative
if (matchup.team1.score < 0) matchup.team1.score = 0;
if (matchup.team2.score < 0) matchup.team2.score = 0;
// Ignore if pending or bye
if (matchup.team1.id === 'pending' || matchup.team2.id === 'pending') return;
if (matchup.team1.id === 'bye' || matchup.team2.id === 'bye') return;
// Make sure round exists
if (!existingMatchups[round]) existingMatchups[round] = { matchups: [] };
// Make sure matchup exists add if not
const matchupIdx = existingMatchups[round].matchups.findIndex(
(matchup) => matchup.team1.id === matchup.team1.id && matchup.team2.id === matchup.team2.id
);
if (matchupIdx === -1) existingMatchups[round].matchups.push(matchup);
else existingMatchups[round].matchups[matchupIdx] = matchup;
createMatchups([...selectedBracket.teams], '0', round.includes('losers'));
}
</script>
<div id="bracket">
{#each Object.keys(allMatchups).sort((a, b) => {
if (a.includes('losers') && !b.includes('losers')) return -1;
else if (!a.includes('losers') && b.includes('losers')) return 1;
else if (a.includes('losers') && b.includes('losers')) return b.localeCompare(a);
else return a.localeCompare(b);
}) as round}
<div class="round">
{#each allMatchups[round].matchups as matchup, i}
<div class="matchup">
<div class="team">
<span>{matchup.team1.seed || ''}</span>
<p>{matchup.team1.name}</p>
<input
type="number"
min="0"
bind:value={matchup.team1.score}
on:input={() => updateBracket(round, matchup)}
/>
</div>
<div class="team">
<span>{matchup.team2.seed || ''}</span>
<p>{matchup.team2.name}</p>
<input
type="number"
min="0"
bind:value={matchup.team2.score}
on:input={() => updateBracket(round, matchup)}
/>
</div>
</div>
{/each}
</div>
{/each}
</div>
<style>
#bracket {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100%;
margin-top: 2rem;
}
.round {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
height: 100%;
}
.matchup {
margin: 10px;
border: 1px solid var(--color-theme-3);
border-radius: 0.3rem;
width: 200px;
}
.team {
display: flex;
flex-direction: row;
justify-content: center;
align-items: normal;
background-color: var(--color-theme-1);
margin: 0.5rem;
padding: 0.3rem;
border-radius: 0.5rem;
font-size: 0.9rem;
}
.team p {
margin: 0.5rem;
padding-top: 0.3rem;
}
.team span {
font-size: 0.7rem;
}
</style>