输入上一轮比分后赛事Bracket未自动更新晋级队伍
问题:赛事对阵Bracket无法自动晋级获胜队伍
我正在学习Svelte,想要实现一个能自动选出下一轮晋级队伍的赛事对阵Bracket。当前遇到的问题是:输入初始轮次(如temp8得2分)的比分后,获胜队伍并未自动进入下一轮(本该对阵temp1)。我尝试过在createMatches函数递归时传递bracket副本,以及在Svelte代码中传递副本,但都无效,我认为函数逻辑本身是正确的,恳请提供解决建议或方向。
问题截图:
Svelte组件代码
<script lang="ts"> import { createMatches } from "$lib/handlers/bracketupdate"; import type { Bracket } from "$lib/types/bracket"; export let bracket: Bracket; export let readonly = false; $: bracket = createMatches(bracket); </script> <div class="bracket"> {#each Object.keys(bracket.scores) .sort((a, b) => +a - +b) .map((k) => +k) as round} <div class="round"> {#each bracket.scores[round] as match} <div class="match"> <div class="team"> <p>{match.team1._id}</p> <input type="number" bind:value={match.team1_score} {readonly} /> </div> <div class="team"> <p>{match.team2._id}</p> <input type="number" bind:value={match.team2_score} {readonly} /> </div> </div> {/each} </div> {/each} </div>
Bracket生成函数代码
import { bye, type Bracket, type BracketMatch, pending } from "$lib/types/bracket"; import type { Team } from "$lib/types/teams"; export function createMatches(bracket: Bracket, round = 0): Bracket { // Base case if (bracket.teams.length < 2) return bracket; if (round === 0) { // Add byes to make the number of teams a power of 2 const numByes = 2 ** Math.ceil(Math.log2(bracket.teams.length)) - bracket.teams.length; if (numByes > 0) { for (let i = 0; i < numByes; i++) { bracket.teams.push(bye); } } // Shuffle the teams by seed bracket.teams = shuffle(bracket.teams); } // Create matches for this round const matches: BracketMatch[] = []; for (let i = 0; i < bracket.teams.length; i += 2) { const team1 = bracket.teams[i]; const team2 = bracket.teams[i + 1]; matches.push({ team1, team2, team1_score: 0, team2_score: 0 }); } // Add the matches to the bracket bracket.scores[round] = matches; // Create the next round const winners = getWinners(matches); bracket.teams = winners; createMatches(bracket, round + 1); return bracket; } function getWinners(matches: BracketMatch[]) { return matches.map((match) => { if (match.team1._id === bye._id) return match.team2; if (match.team2._id === bye._id) return match.team1; if (match.team1_score > match.team2_score) return match.team1; if (match.team2_score > match.team1_score) return match.team2; return pending; }); } function shuffle(teams: Team[]) { // Create a copy of the teams array let result = [...teams]; // Calculate and loop for the number of rounds const numRounds = Math.log2(teams.length / 2); for (let i = 0; i < numRounds; i++) { // Create a new round const round = []; // Calculate the number of teams to splice const splice = Math.pow(2, i); // Loop while there are still teams to splice while (result.length) { round.push(...result.splice(0, splice)); round.push(...result.splice(-splice)); } // Set the round to the result result = round; } return result; }
解决建议
1. 核心问题:每次比分变化都会重置整个Bracket
你在组件里用$: bracket = createMatches(bracket),意味着只要bracket有任何变化(比如输入比分),就会重新执行createMatches。但这个函数的逻辑是从头构建整个Bracket:
round===0时会重新洗牌队伍- 所有新生成的match都会被设置
team1_score:0, team2_score:0,直接覆盖你输入的比分 - 后续轮次的队伍会被重新计算,但因为比分被重置,结果自然不对
2. 修复方案:拆分初始化与更新逻辑
把原有createMatches拆成两个独立函数,分别负责初始化结构和更新晋级队伍:
(1)初始化函数:只执行一次
负责生成完整的Bracket结构,后续轮次默认填充pending队伍,不覆盖比分:
export function initializeBracket(initialTeams: Team[]): Bracket { const bracket: Bracket = { teams: [...initialTeams], scores: {} }; // 添加轮空队伍,凑成2的幂次 const numByes = 2 ** Math.ceil(Math.log2(bracket.teams.length)) - bracket.teams.length; if (numByes > 0) { for (let i = 0; i < numByes; i++) { bracket.teams.push(bye); } } // 洗牌队伍 bracket.teams = shuffle(bracket.teams); // 递归生成所有轮次结构 function buildRounds(currentBracket: Bracket, round: number) { if (currentBracket.teams.length < 2) return currentBracket; // 生成当前轮次的match const matches = []; for (let i = 0; i < currentBracket.teams.length; i += 2) { matches.push({ team1: currentBracket.teams[i], team2: currentBracket.teams[i + 1], team1_score: 0, team2_score: 0 }); } currentBracket.scores[round] = matches; // 下一轮默认填充pending,等待后续更新 currentBracket.teams = matches.map(() => pending); buildRounds(currentBracket, round + 1); return currentBracket; } return buildRounds(bracket, 0); }
(2)更新晋级函数:比分变化时执行
只根据当前已有的比分,从第一轮开始依次计算后续轮次的晋级队伍,保留已有比分:
export function updateBracketWinners(bracket: Bracket): Bracket { // 从第一轮开始,逐层计算胜者 let currentWinners = getWinners(bracket.scores[0]); // 更新后续每一轮的队伍 for (let round = 1; bracket.scores[round]; round++) { // 保留原有比分,只更新对阵队伍 bracket.scores[round] = currentWinners.reduce((acc, _, idx, arr) => { if (idx % 2 === 0) { const existingMatch = bracket.scores[round][idx / 2]; acc.push({ ...existingMatch, team1: arr[idx], team2: arr[idx + 1] }); } return acc; }, [] as BracketMatch[]); // 计算当前轮的胜者,用于下一轮 currentWinners = getWinners(bracket.scores[round]); } // 返回新对象触发Svelte响应式更新 return { ...bracket }; }
3. 组件中调整逻辑
初始化时调用一次initializeBracket,之后只在比分变化时触发更新:
<script lang="ts"> import { initializeBracket, updateBracketWinners } from "$lib/handlers/bracketupdate"; import type { Bracket } from "$lib/types/bracket"; import type { Team } from "$lib/types/teams"; export let initialTeams: Team[]; export let readonly = false; // 初始化Bracket结构 let bracket: Bracket = initializeBracket(initialTeams); // 比分变化时更新晋级队伍 $: { // 依赖bracket.scores触发更新,Svelte会自动监听内部属性变化 bracket = updateBracketWinners({ ...bracket }); } </script>
4. 额外注意:Svelte响应式触发
Svelte需要检测到引用变化才会更新DOM,所以更新函数必须返回新的对象/数组,而不是直接修改原对象。比如用{ ...bracket }创建副本,或者修改数组时使用[...arr]。
内容的提问来源于stack exchange,提问作者mhay10
相关产品推荐
相关产品推荐

