[2014, 2019], 'cadet' => [2011, 2013], 'junior' => [2008, 2010], 'senior' => [0, 2007], ]; foreach ($ageGroups as $group => [$startYear, $endYear]) { if ($startYear <= $birthYear && $birthYear <= $endYear) { return $group; } } return 'unknown'; } /** * Assign weight class based on age group, gender, and weight. * * @param string $ageGroup * @param string $gender Lowercase 'male' or 'female' * @param float $weight * @return string Weight class label or 'unknown' */ function assignWeightClass(string $ageGroup, string $gender, float $weight): string { $weightClasses = [ 'senior' => [ 'male' => [ ['min' => 0, 'max' => 54, 'label' => '-54kg'], ['min' => 54, 'max' => 58, 'label' => '-58kg'], ['min' => 58, 'max' => 63, 'label' => '-63kg'], ['min' => 63, 'max' => 68, 'label' => '-68kg'], ['min' => 68, 'max' => 74, 'label' => '-74kg'], ['min' => 74, 'max' => 80, 'label' => '-80kg'], ['min' => 80, 'max' => 87, 'label' => '-87kg'], ['min' => 87, 'max' => INF, 'label' => '+87kg'], ], 'female' => [ ['min' => 0, 'max' => 46, 'label' => '-46kg'], ['min' => 46, 'max' => 49, 'label' => '-49kg'], ['min' => 49, 'max' => 53, 'label' => '-53kg'], ['min' => 53, 'max' => 57, 'label' => '-57kg'], ['min' => 57, 'max' => 62, 'label' => '-62kg'], ['min' => 62, 'max' => 67, 'label' => '-67kg'], ['min' => 67, 'max' => 73, 'label' => '-73kg'], ['min' => 73, 'max' => INF, 'label' => '+73kg'], ], ], 'junior' => [ 'male' => [ ['min' => 0, 'max' => 45, 'label' => '-45kg'], ['min' => 45, 'max' => 48, 'label' => '-48kg'], ['min' => 48, 'max' => 51, 'label' => '-51kg'], ['min' => 51, 'max' => 55, 'label' => '-55kg'], ['min' => 55, 'max' => 59, 'label' => '-59kg'], ['min' => 59, 'max' => 63, 'label' => '-63kg'], ['min' => 63, 'max' => 68, 'label' => '-68kg'], ['min' => 68, 'max' => 73, 'label' => '-73kg'], ['min' => 73, 'max' => 78, 'label' => '-78kg'], ['min' => 78, 'max' => INF, 'label' => '+78kg'], ], 'female' => [ ['min' => 0, 'max' => 42, 'label' => '-42kg'], ['min' => 42, 'max' => 44, 'label' => '-44kg'], ['min' => 44, 'max' => 46, 'label' => '-46kg'], ['min' => 46, 'max' => 49, 'label' => '-49kg'], ['min' => 49, 'max' => 52, 'label' => '-52kg'], ['min' => 52, 'max' => 55, 'label' => '-55kg'], ['min' => 55, 'max' => 59, 'label' => '-59kg'], ['min' => 59, 'max' => 63, 'label' => '-63kg'], ['min' => 63, 'max' => 68, 'label' => '-68kg'], ['min' => 68, 'max' => INF, 'label' => '+68kg'], ], ], 'cadet' => [ 'male' => [ ['min' => 0, 'max' => 33, 'label' => '-33kg'], ['min' => 33, 'max' => 37, 'label' => '-37kg'], ['min' => 37, 'max' => 41, 'label' => '-41kg'], ['min' => 41, 'max' => 45, 'label' => '-45kg'], ['min' => 45, 'max' => 49, 'label' => '-49kg'], ['min' => 49, 'max' => 53, 'label' => '-53kg'], ['min' => 53, 'max' => 57, 'label' => '-57kg'], ['min' => 57, 'max' => 61, 'label' => '-61kg'], ['min' => 61, 'max' => 65, 'label' => '-65kg'], ['min' => 65, 'max' => INF, 'label' => '+65kg'], ], 'female' => [ ['min' => 0, 'max' => 29, 'label' => '-29kg'], ['min' => 29, 'max' => 33, 'label' => '-33kg'], ['min' => 33, 'max' => 37, 'label' => '-37kg'], ['min' => 37, 'max' => 41, 'label' => '-41kg'], ['min' => 41, 'max' => 44, 'label' => '-44kg'], ['min' => 44, 'max' => 47, 'label' => '-47kg'], ['min' => 47, 'max' => 51, 'label' => '-51kg'], ['min' => 51, 'max' => 55, 'label' => '-55kg'], ['min' => 55, 'max' => 59, 'label' => '-59kg'], ['min' => 59, 'max' => INF, 'label' => '+59kg'], ], ], 'child' => [ 'male' => [ ['min' => 0, 'max' => 24, 'label' => '-24kg'], ['min' => 24, 'max' => 28, 'label' => '-28kg'], ['min' => 28, 'max' => 32, 'label' => '-32kg'], ['min' => 32, 'max' => 36, 'label' => '-36kg'], ['min' => 36, 'max' => 40, 'label' => '-40kg'], ['min' => 40, 'max' => 44, 'label' => '-44kg'], ['min' => 44, 'max' => 48, 'label' => '-48kg'], ['min' => 48, 'max' => 52, 'label' => '-52kg'], ['min' => 52, 'max' => 56, 'label' => '-56kg'], ['min' => 56, 'max' => INF, 'label' => '+56kg'], ], 'female' => [ ['min' => 0, 'max' => 29, 'label' => '-29kg'], ['min' => 29, 'max' => 33, 'label' => '29-33kg'], ['min' => 33, 'max' => 37, 'label' => '33-37kg'], ['min' => 37, 'max' => 41, 'label' => '37-41kg'], ['min' => 41, 'max' => 44, 'label' => '41-44kg'], ['min' => 44, 'max' => 47, 'label' => '44-47kg'], ['min' => 47, 'max' => 51, 'label' => '47-51kg'], ['min' => 51, 'max' => 55, 'label' => '51-55kg'], ['min' => 55, 'max' => 59, 'label' => '55-59kg'], ['min' => 59, 'max' => INF, 'label' => '+59kg'], ], ], ]; if (!isset($weightClasses[$ageGroup][$gender])) { return 'unknown'; } foreach ($weightClasses[$ageGroup][$gender] as $class) { if ($weight > $class['min'] && $weight <= $class['max']) { return $class['label']; } } return 'unknown'; } /** * Classify a single fighter by age group, gender, birth year, and weight class. * * @param array $fighter Array with keys 'date_of_birth', 'gender', 'weight'. * @param int $competitionYear * @return array Augmented fighter data. */ function classifyFighter(array $fighter, int $competitionYear = 2025): array { $dob = Carbon::parse($fighter['date_of_birth']); $birthYear = $dob->year; $ageGroup = getAgeGroupByBirthYear($birthYear, $competitionYear); $gender = strtolower($fighter['gender']); $weight = floatval($fighter['weight']); $weightClass = assignWeightClass($ageGroup, $gender, $weight); return array_merge($fighter, [ 'age_group' => $ageGroup, 'birth_year' => $birthYear, 'gender' => $gender, 'weight_class' => $weightClass, ]); } /** * Group fighters by age group, gender, and weight class, * then generate a Taekwondo single-elimination draw with colors. * * @param array $fighters * @param int $competitionYear * @return array Grouped fighters with tournament draws. */ function groupFightersWithTaekwondoDraw(array $fighters, int $competitionYear = 2025): array { $classified = array_map(fn($f) => classifyFighter($f, $competitionYear), $fighters); $collection = collect($classified); $grouped = $collection->groupBy([ 'age_group', 'gender', 'weight_class', ]); $result = []; foreach ($grouped as $ageGroup => $genders) { foreach ($genders as $gender => $weightClasses) { foreach ($weightClasses as $weightClass => $fightersInGroup) { // Important: Reset array keys to prevent undefined index errors $fightersArray = array_values($fightersInGroup->toArray()); // Create Taekwondo-style draw $draw = createTaekwondoDraw($fightersArray); $result[] = [ 'age_group' => $ageGroup, 'gender' => $gender, 'weight_class' => $weightClass, 'fighters' => $fightersArray, 'tournament_draw' => $draw, ]; } } } return $result; } /** * Create a Taekwondo single-elimination tournament draw. * Assign colors (red/blue) properly to matches. * Fixes index issues by ensuring the fighters array is properly zero-based and extends to bracket size with byes. * * @param array $fighters Array of fighters in a single group * @return array Tournament draw data with rounds and matches. */ function createTaekwondoDraw(array $fighters): array { // Reset keys to zero-indexed array to avoid undefined index errors $fighters = array_values($fighters); $numFighters = count($fighters); $bracketSize = 1; while ($bracketSize < $numFighters) { $bracketSize <<= 1; } $numByes = $bracketSize - $numFighters; // Add byes as null if needed for ($i = 0; $i < $numByes; $i++) { $fighters[] = null; // represent bye by null } $numMatches = $bracketSize / 2; $matches = []; for ($i = 0; $i < $numMatches; $i++) { $p1Index = 2 * $i; $p2Index = 2 * $i + 1; $player1 = isset($fighters[$p1Index]) ? $fighters[$p1Index] : null; $player2 = isset($fighters[$p2Index]) ? $fighters[$p2Index] : null; $matches[] = [ 'match_number' => $i + 1, 'player1' => $player1 ? ['fighter_data' => $player1, 'color' => 'red'] : null, 'player2' => $player2 ? ['fighter_data' => $player2, 'color' => 'blue'] : null, 'winner' => null, ]; } $totalRounds = (int) log($bracketSize, 2); $rounds = [ [ 'round' => 1, 'matches' => $matches, ] ]; // Add empty rounds for future matches (could be filled dynamically) for ($r = 2; $r <= $totalRounds; $r++) { $rounds[] = [ 'round' => $r, 'matches' => [], ]; } return [ 'totalRounds' => $totalRounds, 'rounds' => $rounds, 'bracketSize' => $bracketSize, 'originalFightersCount' => $numFighters, ]; }