created a classifier and draw generator
This commit is contained in:
parent
fec7cb655b
commit
3fe9417af4
@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Determine the age group based on birth year.
|
||||
*
|
||||
* @param int $birthYear
|
||||
* @param int $competitionYear
|
||||
* @return string
|
||||
*/
|
||||
function getAgeGroupByBirthYear(int $birthYear, int $competitionYear = 2025): string
|
||||
{
|
||||
// Age group birth year ranges (adjust as per your competition)
|
||||
$ageGroups = [
|
||||
'child' => [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,
|
||||
];
|
||||
}
|
||||
16
app/Http/Controllers/FighterController.php
Normal file
16
app/Http/Controllers/FighterController.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Member;
|
||||
|
||||
class FighterController extends Controller
|
||||
{
|
||||
public function grouped()
|
||||
{
|
||||
$fighters = Member::all()->toArray();
|
||||
$groupedJson = groupFighters($fighters, 2025);
|
||||
return response($groupedJson, 200)->header('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,7 +25,10 @@
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"app/Helpers/FighterClassification.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use app\Helpers\FighterClassification;
|
||||
use App\Models\Member;
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@ -16,3 +19,20 @@ use Illuminate\Support\Facades\Route;
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
|
||||
|
||||
Route::get('/find', function () {
|
||||
$fighters = \App\Models\Member::all()->toArray();
|
||||
|
||||
// Call global function with leading backslash to avoid namespace issue
|
||||
$groupedJson = \groupFighters($fighters, 2025);
|
||||
|
||||
return response($groupedJson, 200)->header('Content-Type', 'application/json');
|
||||
});
|
||||
|
||||
Route::get('/taekwondo-draw', function () {
|
||||
$fighters = Member::all()->toArray();
|
||||
$draws = groupFightersWithTaekwondoDraw($fighters, 2025); // Change year if you want
|
||||
return response()->json($draws, 200);
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user