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/",
|
"App\\": "app/",
|
||||||
"Database\\Factories\\": "database/factories/",
|
"Database\\Factories\\": "database/factories/",
|
||||||
"Database\\Seeders\\": "database/seeders/"
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
}
|
},
|
||||||
|
"files": [
|
||||||
|
"app/Helpers/FighterClassification.php"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
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 () {
|
Route::get('/', function () {
|
||||||
return view('welcome');
|
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