From 7d680269557772811ceb4b25d0962259dfb8d575 Mon Sep 17 00:00:00 2001 From: Marc Wieland Date: Fri, 23 Jan 2026 11:30:15 +0100 Subject: [PATCH] algorithmus improvements --- src/context/TournamentContext.tsx | 34 ++++++-- src/utils/tournamentUtils.ts | 138 +++++++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 6 deletions(-) diff --git a/src/context/TournamentContext.tsx b/src/context/TournamentContext.tsx index 32266c1..c68d548 100644 --- a/src/context/TournamentContext.tsx +++ b/src/context/TournamentContext.tsx @@ -1,6 +1,6 @@ import React, { createContext, useContext, useState, ReactNode, useMemo } from 'react'; import { Team, Round, TeamScore, Match, MatchResult } from '@/types/tournament'; -import { generateMatches } from '@/utils/tournamentUtils'; +import { generateMatches, createNextRoundMatches } from '@/utils/tournamentUtils'; interface TournamentContextType { teams: Team[]; @@ -53,7 +53,17 @@ export const TournamentProvider = ({ children }: { children: ReactNode }) => { const startNewRound = () => { if (currentRound && !currentRound.completed) return; - const state = generateMatches(teams, fieldCount); + let state; + + if (rounds.length === 0) { + // First round: initial random generation + state = generateMatches(teams, fieldCount); + } else { + // Subsequent rounds: rotation system + const lastRound = rounds[rounds.length - 1]; + state = createNextRoundMatches(lastRound, fieldCount); + } + const allMatches = [...state.bundesligaMatches, ...state.championsMatches]; const newRound: Round = { @@ -69,12 +79,26 @@ export const TournamentProvider = ({ children }: { children: ReactNode }) => { }; const updateMatchResult = (matchId: string, scoreA: number, scoreB: number) => { - const diff = scoreB - scoreA; + const diff = Math.abs(scoreB - scoreA); + + let pointsA: number; + let pointsB: number; + + if (scoreA > scoreB) { + // Team A gewinnt + pointsA = diff + 2; + pointsB = -diff; + } else { + // Team B gewinnt + pointsA = -diff; + pointsB = diff + 2; + } + const result: MatchResult = { scoreA, scoreB, - pointsA: -diff, - pointsB: diff, + pointsA, + pointsB, }; setRounds((prev) => diff --git a/src/utils/tournamentUtils.ts b/src/utils/tournamentUtils.ts index a49071a..7668995 100644 --- a/src/utils/tournamentUtils.ts +++ b/src/utils/tournamentUtils.ts @@ -1,4 +1,4 @@ -import { Team, Match, TournamentState, League } from '@/types/tournament'; +import { Team, Match, TournamentState, League, Round } from '@/types/tournament'; export const generateMatches = (teams: Team[], fieldCount: number): TournamentState => { const bundesligaTeams = teams.filter((t) => t.league === 'bundesliga'); @@ -71,3 +71,139 @@ export const generateMatches = (teams: Team[], fieldCount: number): TournamentSt championsFields, }; }; + +/** + * Rotation system for subsequent rounds + * Implements 3 cases: + * - Case 1: Even teams (no waiting) - all rotate using Circle Rotation algorithm + * - Case 2: Odd teams (1 waiting) - waiting joins, last playing goes to waiting + * - Case 3: Multiple waiting (3+) - all waiting play, last N playing go to waiting + */ +export const createNextRoundMatches = ( + previousRound: Round, + fieldCount: number +): TournamentState => { + const createRotatedMatches = ( + currentMatches: Match[], + waitingTeams: Team[], + availableFields: number, + league: League, + fieldOffset: number + ): { matches: Match[]; waiting: Team[] } => { + if (currentMatches.length === 0) { + return { matches: [], waiting: waitingTeams }; + } + + // Collect all teams from current matches and waiting + const playingTeams = currentMatches.flatMap((m) => [m.teamA, m.teamB]); + const allTeamsForLeague = [...playingTeams, ...waitingTeams]; + + if (allTeamsForLeague.length < 2) { + return { matches: [], waiting: allTeamsForLeague }; + } + + const numWaiting = waitingTeams.length; + let rotatedTeams: Team[] = []; + + if (numWaiting === 0) { + // Case 1: No waiting teams - use Circle Rotation algorithm + rotatedTeams = rotateTeamsInCircle(allTeamsForLeague); + } else if (numWaiting === 1) { + // Case 2: 1 waiting team - waiting joins, last playing goes to waiting + rotatedTeams = [waitingTeams[0], ...playingTeams.slice(0, -1), playingTeams[playingTeams.length - 1]]; + } else { + // Case 3: Multiple waiting - all waiting play, last N playing go to waiting + rotatedTeams = [ + ...waitingTeams, + ...playingTeams.slice(0, playingTeams.length - numWaiting), + ...playingTeams.slice(-numWaiting), + ]; + } + + // Now create matches based on available fields + const maxTeamsPlaying = availableFields * 2; + const teamsToPlay = rotatedTeams.slice(0, maxTeamsPlaying); + const newWaiting = rotatedTeams.slice(maxTeamsPlaying); + + // Create matches from playing teams + const matches: Match[] = []; + let teamIndex = 0; + let fieldNumber = fieldOffset + 1; + + while (teamIndex < teamsToPlay.length - 1 && matches.length < availableFields) { + matches.push({ + id: crypto.randomUUID(), + teamA: teamsToPlay[teamIndex], + teamB: teamsToPlay[teamIndex + 1], + fieldNumber, + league, + }); + teamIndex += 2; + fieldNumber++; + } + + return { matches, waiting: newWaiting }; + }; + + // Determine field allocation + const bundesligaMatches = previousRound.matches.filter((m) => m.league === 'bundesliga'); + const championsMatches = previousRound.matches.filter((m) => m.league === 'champions'); + + const bundesligaHasTeams = bundesligaMatches.length > 0 || previousRound.bundesligaWaiting.length > 0; + const championsHasTeams = championsMatches.length > 0 || previousRound.championsWaiting.length > 0; + + let bundesligaFields = 0; + let championsFields = 0; + + if (bundesligaHasTeams && championsHasTeams) { + bundesligaFields = Math.floor(fieldCount / 2); + championsFields = fieldCount - bundesligaFields; + } else if (bundesligaHasTeams) { + bundesligaFields = fieldCount; + } else if (championsHasTeams) { + championsFields = fieldCount; + } + + // Apply rotation for each league + const bundesliga = createRotatedMatches( + bundesligaMatches, + previousRound.bundesligaWaiting, + bundesligaFields, + 'bundesliga', + 0 + ); + + const champions = createRotatedMatches( + championsMatches, + previousRound.championsWaiting, + championsFields, + 'champions', + bundesligaFields + ); + + return { + bundesligaMatches: bundesliga.matches, + championsMatches: champions.matches, + bundesligaWaiting: bundesliga.waiting, + championsWaiting: champions.waiting, + bundesligaFields, + championsFields, + }; +}; + +/** + * Circle Rotation Algorithm (Roundrobin) + * Keeps first team fixed, rotates all others by 1 position + * Example: [A, B, C, D] → [A, D, B, C] → [A, C, D, B] → [A, B, C, D] + */ +function rotateTeamsInCircle(teams: Team[]): Team[] { + if (teams.length <= 2) return teams; + + const fixed = teams[0]; + const rotating = teams.slice(1); + + // Rotate: move last to first + const rotated = [rotating[rotating.length - 1], ...rotating.slice(0, -1)]; + + return [fixed, ...rotated]; +}