Last active
July 4, 2025 05:06
-
-
Save joefaron/acb7f2abd96b42e692c0fb11ef2fb879 to your computer and use it in GitHub Desktop.
wsl odds calculator 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
ini_set('display_errors', 1); | |
ini_set('display_startup_errors', 1); | |
error_reporting(E_ERROR); | |
ini_set('max_execution_time', 0); | |
?> | |
View code at: <a href="https://gist.github.com/joefaron/acb7f2abd96b42e692c0fb11ef2fb879">https://gist.github.com/joefaron/acb7f2abd96b42e692c0fb11ef2fb879</a> | |
<?php | |
// 2025 WSL Championship Tour point structure | |
$original_place_to_points = array( | |
1 => 10000, 2 => 7800, 3 => 6085, 5 => 4745, 9 => 3320, | |
17 => 1330, 33 => 265 | |
); | |
// Current standings after Event 9 (VIVO Rio Pro) - Men's Championship Tour | |
// Top 24 surfers competing in events 10-12 | |
$surfers_text = <<<EOM | |
Rank Name Country 1 2 3 4 5 6 7 8 9 Total Points | |
1 "Jordy Smith" "South Africa" 1330 3320 3320 10000 4745 4745 10000 3320 4745 44195 | |
2 "Yago Dora" "Brazil" 265 4745 10000 4745 3320 4745 1330 10000 4745 43630 | |
3 "Kanoa Igarashi" "Japan" 3320 4745 3320 1330 7800 6085 3320 7800 3320 39710 | |
4 "Italo Ferreira" "Brazil" 6085 10000 7800 4745 1330 1330 1330 3320 4745 39355 | |
5 "Ethan Ewing" "Australia" 3320 6085 6085 4745 4745 1330 1330 4745 6085 37140 | |
6 "Griffin Colapinto" "United States" 1330 3320 1330 1330 6085 1330 7800 6085 7800 35080 | |
7 "Jack Robinson" "Australia" 1330 6085 4745 1330 10000 265 1330 6085 3320 34225 | |
8 "Barron Mamiya" "Hawaii" 10000 1330 6085 1330 1330 3320 6085 3320 1330 32800 | |
9 "Filipe Toledo" "Brazil" 3320 3320 4745 1330 3320 10000 1330 3320 3320 32675 | |
10 "Miguel Pupo" "Brazil" 4745 4745 1330 4745 1330 4745 3320 1330 6085 31045 | |
11 "Leonardo Fioravanti" "Italy" 7800 1330 3320 3320 3320 1330 4745 3320 3320 30475 | |
12 "Cole Houshmand" "United States" 1330 1330 3320 6085 1330 1330 1330 4745 10000 29470 | |
13 "Jake Marshall" "United States" 4745 1330 1330 1330 4745 3320 3320 3320 4745 26855 | |
14 "Joel Vaughan" "Australia" 3320 3320 3320 1330 3320 265 1330 4745 3320 24005 | |
15 "Connor O'Leary" "Japan" 1330 3320 1330 3320 3320 1330 4745 4745 1330 23440 | |
16 "Crosby Colapinto" "United States" 265 265 1330 6085 1330 1330 6085 3320 3320 23065 | |
17 "Alan Cleland" "Mexico" 3320 265 1330 3320 1330 3320 3320 1330 3320 20590 | |
18 "Joao Chianca" "Brazil" 3320 1330 265 3320 1330 3320 3320 3320 1330 20590 | |
19 "Rio Waida" "Indonesia" 1330 7800 4745 1330 1330 1330 1330 1330 1330 20525 | |
20 "Marco Mignot" "France" 1330 265 4745 3320 1330 3320 3320 1330 1330 20025 | |
21 "Seth Moniz" "Hawaii" 3320 265 3320 1330 3320 3320 265 1330 3320 19525 | |
22 "Alejo Muniz" "Brazil" 1330 1330 1330 3320 1330 6085 265 1330 1330 17385 | |
23 "Matthew McGillivray" "South Africa" 1330 1330 1330 7800 1330 265 1330 0 0 14450 | |
24 "Liam O'Brien" "Australia" 1330 3320 3320 265 1330 3320 1330 0 0 13950 | |
EOM; | |
function parse_surfers_text($text) { | |
$lines = array_filter(array_map('trim', explode("\n", $text))); | |
$surfers = array(); | |
$is_header = true; | |
foreach($lines as $line) { | |
if($is_header) { | |
$is_header = false; | |
continue; | |
} | |
$parts = explode("\t", $line); | |
if(count($parts) >= 13) { | |
$name = trim(str_replace('"', '', $parts[1])); | |
$country = trim(str_replace('"', '', $parts[2])); | |
$total_points = intval($parts[12]); // Column 12 now has the total points | |
$surfers[] = array('name' => $name, 'country' => $country, 'points' => $total_points); | |
} | |
} | |
return $surfers; | |
} | |
function ordinal($number) { | |
$ends = array('th','st','nd','rd','th','th','th','th','th','th'); | |
if ((($number % 100) >= 11) && (($number % 100) <= 13)) { | |
return $number . 'th'; | |
} else { | |
return $number . $ends[$number % 10]; | |
} | |
} | |
function get_country_flag($country) { | |
$flags = array( | |
'South Africa' => '🇿🇦', | |
'Brazil' => '🇧🇷', | |
'Japan' => '🇯🇵', | |
'Australia' => '🇦🇺', | |
'United States' => '🇺🇸', | |
'Hawaii' => '🏄♂️', | |
'Indonesia' => '🇮🇩', | |
'Morocco' => '🇲🇦', | |
'Italy' => '🇮🇹', | |
'France' => '🇫🇷', | |
'Peru' => '🇵🇪', | |
'Mexico' => '🇲🇽' | |
); | |
return isset($flags[$country]) ? $flags[$country] : '🏄♂️'; | |
} | |
function simulation($surfers, $place_to_points, $num_simulations = 10000, $scenarios = array(), $debug_surfer = null, $debug_position = null, $seed = null) { | |
if($seed !== null) { | |
mt_srand($seed); | |
} | |
$champion_count = array(); | |
$top5_count = array(); | |
$position_count = array(); | |
$debug_info = null; | |
$cutoffs = array(); // Collect 5th place scores after Event 11 | |
foreach($surfers as $surfer) { | |
$champion_count[$surfer['name']] = 0; | |
$top5_count[$surfer['name']] = 0; | |
$position_count[$surfer['name']] = array(1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0); | |
} | |
// WSL tournament structure for 24 surfers | |
function simulate_wsl_event($num_surfers) { | |
$results = array(); | |
// Create proper WSL tournament bracket results for 24 surfers | |
$places = array(); | |
$places[] = 1; // 1 winner | |
$places[] = 2; // 1 finalist | |
$places = array_merge($places, array_fill(0, 2, 3)); // 2 semifinal losers (tied 3rd) | |
$places = array_merge($places, array_fill(0, 4, 5)); // 4 quarterfinal losers (tied 5th) | |
$places = array_merge($places, array_fill(0, 8, 9)); // 8 round of 16 losers (tied 9th) | |
$places = array_merge($places, array_fill(0, 8, 17)); // 8 first round losers (tied 17th) | |
// Shuffle the places using mt_rand for reproducible results | |
for($i = count($places) - 1; $i > 0; $i--) { | |
$j = mt_rand(0, $i); | |
$temp = $places[$i]; | |
$places[$i] = $places[$j]; | |
$places[$j] = $temp; | |
} | |
return $places; | |
} | |
// Apply scenarios to event results | |
function apply_scenarios_to_event($event_results, $surfer_names, $scenarios, $event_number) { | |
// Apply any scenarios for this event | |
foreach($scenarios as $scenario) { | |
if($scenario['event'] == $event_number) { | |
$surfer_index = array_search($scenario['surfer'], $surfer_names); | |
if($surfer_index !== false) { | |
$event_results[$surfer_index] = $scenario['position']; | |
} | |
} | |
} | |
return $event_results; | |
} | |
// Simulate WSL Finals bracket format | |
function simulate_wsl_finals_bracket($finalists, $scenarios, $event_number) { | |
// Finalists are already sorted by points (1st seed = highest points) | |
$bracket_positions = array(1, 2, 3, 4, 5); // Final positions | |
// Check for scenarios that override the bracket | |
$scenario_overrides = array(); | |
foreach($scenarios as $scenario) { | |
if($scenario['event'] == $event_number) { | |
for($i = 0; $i < count($finalists); $i++) { | |
if($finalists[$i]['name'] == $scenario['surfer']) { | |
$scenario_overrides[$i] = $scenario['position']; | |
break; | |
} | |
} | |
} | |
} | |
// If we have scenario overrides, use them and fill in the rest | |
if(!empty($scenario_overrides)) { | |
$used_positions = array_values($scenario_overrides); | |
$available_positions = array_values(array_diff($bracket_positions, $used_positions)); | |
$result = array(); | |
$available_index = 0; | |
for($i = 0; $i < count($finalists); $i++) { | |
if(isset($scenario_overrides[$i])) { | |
$result[$i] = $scenario_overrides[$i]; | |
} else { | |
$result[$i] = $available_positions[$available_index]; | |
$available_index++; | |
} | |
} | |
// No detailed tracking for scenario overrides | |
return array('positions' => $result, 'bracket_details' => null); | |
} | |
// Initialize bracket details tracking | |
$bracket_details = array(); | |
// Simulate the actual bracket progression | |
// Match 1: 5th seed (index 4) vs 4th seed (index 3) | |
$match1_winner = mt_rand(0, 1) == 0 ? 4 : 3; // 4th seed has priority but still random | |
$match1_loser = $match1_winner == 4 ? 3 : 4; | |
$bracket_details['match1'] = array( | |
'description' => 'Match 1: Seed #5 vs Seed #4', | |
'surfer1' => $finalists[4]['name'], | |
'surfer2' => $finalists[3]['name'], | |
'winner' => $finalists[$match1_winner]['name'], | |
'loser' => $finalists[$match1_loser]['name'], | |
'loser_position' => '5th place' | |
); | |
// Match 2: Match 1 winner vs 3rd seed (index 2) | |
$match2_winner = mt_rand(0, 1) == 0 ? $match1_winner : 2; | |
$match2_loser = $match2_winner == $match1_winner ? 2 : $match1_winner; | |
$bracket_details['match2'] = array( | |
'description' => 'Match 2: Match 1 winner vs Seed #3', | |
'surfer1' => $finalists[$match1_winner]['name'], | |
'surfer2' => $finalists[2]['name'], | |
'winner' => $finalists[$match2_winner]['name'], | |
'loser' => $finalists[$match2_loser]['name'], | |
'loser_position' => '4th place' | |
); | |
// Match 3: Match 2 winner vs 2nd seed (index 1) | |
$match3_winner = mt_rand(0, 1) == 0 ? $match2_winner : 1; | |
$match3_loser = $match3_winner == $match2_winner ? 1 : $match2_winner; | |
$bracket_details['match3'] = array( | |
'description' => 'Match 3: Match 2 winner vs Seed #2', | |
'surfer1' => $finalists[$match2_winner]['name'], | |
'surfer2' => $finalists[1]['name'], | |
'winner' => $finalists[$match3_winner]['name'], | |
'loser' => $finalists[$match3_loser]['name'], | |
'loser_position' => '3rd place' | |
); | |
// Title Match: Match 3 winner vs 1st seed (index 0) | |
// Best of 3 heats - 1st seed needs to be beaten twice | |
$title_match_result = simulate_best_of_three($match3_winner, 0); | |
$title_match_winner = $title_match_result['winner']; | |
$title_match_loser = $title_match_winner == $match3_winner ? 0 : $match3_winner; | |
$bracket_details['title_match'] = array( | |
'description' => 'Title Match: Match 3 winner vs Seed #1 (Best of 3)', | |
'surfer1' => $finalists[$match3_winner]['name'], | |
'surfer2' => $finalists[0]['name'], | |
'winner' => $finalists[$title_match_winner]['name'], | |
'loser' => $finalists[$title_match_loser]['name'], | |
'heat_results' => $title_match_result['heat_details'], | |
'final_score' => $title_match_result['final_score'] | |
); | |
// Assign final positions based on bracket results | |
$result = array(); | |
$result[$title_match_winner] = 1; // Champion | |
$result[$title_match_loser] = 2; // Runner-up | |
$result[$match3_loser] = 3; // 3rd place | |
$result[$match2_loser] = 4; // 4th place | |
$result[$match1_loser] = 5; // 5th place | |
return array('positions' => $result, 'bracket_details' => $bracket_details); | |
} | |
// Simulate best of 3 heats (for title match) | |
function simulate_best_of_three($challenger_index, $champion_index) { | |
$challenger_wins = 0; | |
$champion_wins = 0; | |
$heat_details = array(); | |
for($heat = 0; $heat < 3; $heat++) { | |
$heat_winner = mt_rand(0, 1) == 0 ? $challenger_index : $champion_index; | |
$heat_number = $heat + 1; | |
if($heat_winner == $challenger_index) { | |
$challenger_wins++; | |
$heat_details[] = "Heat {$heat_number}: Challenger wins"; | |
} else { | |
$champion_wins++; | |
$heat_details[] = "Heat {$heat_number}: Champion wins"; | |
} | |
// Check if someone has won 2 heats | |
if($challenger_wins == 2) { | |
$final_score = "Challenger wins {$challenger_wins}-{$champion_wins}"; | |
return array( | |
'winner' => $challenger_index, | |
'heat_details' => $heat_details, | |
'final_score' => $final_score | |
); | |
} | |
if($champion_wins == 2) { | |
$final_score = "Champion wins {$champion_wins}-{$challenger_wins}"; | |
return array( | |
'winner' => $champion_index, | |
'heat_details' => $heat_details, | |
'final_score' => $final_score | |
); | |
} | |
} | |
// This shouldn't happen, but just in case | |
$final_score = "Champion wins {$champion_wins}-{$challenger_wins}"; | |
return array( | |
'winner' => $champion_index, | |
'heat_details' => $heat_details, | |
'final_score' => $final_score | |
); | |
} | |
for($sim = 0; $sim < $num_simulations; $sim++) { | |
// Copy current standings | |
$sim_surfers = array(); | |
foreach($surfers as $surfer) { | |
$sim_surfers[] = array('name' => $surfer['name'], 'points' => $surfer['points']); | |
} | |
// Simulate Event 10 (J-Bay) and Event 11 (Tahiti) for all 24 surfers | |
for($event = 0; $event < 2; $event++) { | |
$event_number = $event + 10; // Event 10 or 11 | |
$event_results = simulate_wsl_event(24); | |
// Create array of surfer names for scenario matching | |
$surfer_names = array(); | |
foreach($sim_surfers as $surfer) { | |
$surfer_names[] = $surfer['name']; | |
} | |
// Apply scenarios for this event | |
$event_results = apply_scenarios_to_event($event_results, $surfer_names, $scenarios, $event_number); | |
for($i = 0; $i < count($sim_surfers); $i++) { | |
$place = $event_results[$i]; | |
$points_earned = $place_to_points[$place]; | |
$sim_surfers[$i]['points'] += $points_earned; | |
// Track event details for debug | |
$sim_surfers[$i]['event' . $event_number . '_place'] = $place; | |
$sim_surfers[$i]['event' . $event_number . '_points'] = $points_earned; | |
} | |
} | |
// Sort by points to determine final top 5 | |
usort($sim_surfers, function($a, $b) { | |
return $b['points'] - $a['points']; | |
}); | |
// Collect cutoff score to make top 5 after Event 11 (6th place + 1) | |
if(count($sim_surfers) >= 6) { | |
$cutoffs[] = $sim_surfers[5]['points'] + 1; // 6th place (0-indexed) + 1 | |
} | |
// Only top 5 make it to WSL Finals (Event 12) | |
$finalists = array_slice($sim_surfers, 0, 5); | |
// Track who made top 5 | |
foreach($finalists as $finalist) { | |
$top5_count[$finalist['name']]++; | |
} | |
// Simulate WSL Finals bracket format | |
$finals_result = simulate_wsl_finals_bracket($finalists, $scenarios, 12); | |
$finals_places = $finals_result['positions']; | |
$bracket_details = isset($finals_result['bracket_details']) ? $finals_result['bracket_details'] : null; | |
// In WSL Finals, championship positions are determined by Finals placement, not cumulative points | |
for($i = 0; $i < count($finalists); $i++) { | |
$finals_position = $finals_places[$i]; | |
$finalists[$i]['points'] += $place_to_points[$finals_position]; | |
$finalists[$i]['finals_position'] = $finals_position; | |
} | |
// Sort finalists by their Finals event position (not cumulative points) | |
usort($finalists, function($a, $b) { | |
return $a['finals_position'] - $b['finals_position']; | |
}); | |
// Count final championship positions based on Finals placement | |
for($i = 0; $i < count($finalists); $i++) { | |
$championship_position = $i + 1; | |
$position_count[$finalists[$i]['name']][$championship_position]++; | |
if($championship_position == 1) { | |
$champion_count[$finalists[$i]['name']]++; | |
} | |
// Capture debug info if this matches what we're looking for | |
if($debug_surfer && $debug_position && | |
$finalists[$i]['name'] == $debug_surfer && | |
$championship_position == $debug_position && | |
$debug_info === null) { | |
// Find starting points | |
$starting_points = 0; | |
foreach($surfers as $orig_surfer) { | |
if($orig_surfer['name'] == $debug_surfer) { | |
$starting_points = $orig_surfer['points']; | |
break; | |
} | |
} | |
$debug_info = array( | |
'simulation' => $sim + 1, | |
'surfer' => $debug_surfer, | |
'final_position' => $championship_position, | |
'starting_points' => $starting_points, | |
'event10_place' => $finalists[$i]['event10_place'], | |
'event10_points' => $finalists[$i]['event10_points'], | |
'event11_place' => $finalists[$i]['event11_place'], | |
'event11_points' => $finalists[$i]['event11_points'], | |
'points_after_event11' => $finalists[$i]['points'] - $place_to_points[$finalists[$i]['finals_position']], | |
'made_finals' => true, | |
'finals_place' => $finalists[$i]['finals_position'], | |
'finals_points' => $place_to_points[$finalists[$i]['finals_position']], | |
'total_points' => $finalists[$i]['points'], | |
'finals_field' => array(), | |
'bracket_details' => $bracket_details | |
); | |
// Store the finals field with their points going into finals (before finals event) | |
$finals_qualifiers = array(); | |
foreach($finalists as $finalist) { | |
$points_before_finals = $finalist['points'] - $place_to_points[$finalist['finals_position']]; | |
$finals_qualifiers[] = array( | |
'name' => $finalist['name'], | |
'points_before_finals' => $points_before_finals | |
); | |
} | |
// Sort by points going into finals (highest to lowest) | |
usort($finals_qualifiers, function($a, $b) { | |
return $b['points_before_finals'] - $a['points_before_finals']; | |
}); | |
$debug_info['finals_field'] = $finals_qualifiers; | |
} | |
} | |
} | |
// Calculate cutoff statistics | |
sort($cutoffs); | |
$cutoff_count = count($cutoffs); | |
$cutoff_min = $cutoff_count > 0 ? $cutoffs[0] : 0; | |
$cutoff_max = $cutoff_count > 0 ? $cutoffs[$cutoff_count - 1] : 0; | |
$cutoff_median = $cutoff_count > 0 ? $cutoffs[floor($cutoff_count * 0.5)] : 0; | |
$cutoff_p90 = $cutoff_count > 0 ? $cutoffs[floor($cutoff_count * 0.9)] : 0; | |
return array( | |
'champion' => $champion_count, | |
'top5' => $top5_count, | |
'positions' => $position_count, | |
'total_simulations' => $num_simulations, | |
'debug_info' => $debug_info, | |
'cutoffs' => $cutoffs, | |
'cutoff' => array( | |
'min' => $cutoff_min, | |
'median' => $cutoff_median, | |
'p90' => $cutoff_p90, | |
'max' => $cutoff_max | |
) | |
); | |
} | |
// Parse surfers and run simulation | |
$surfers = parse_surfers_text($surfers_text); | |
$num_simulations = isset($_GET['simulations']) ? min(100000, max(1000, intval($_GET['simulations']))) : 25000; | |
// Parse scenarios from GET | |
$scenarios = array(); | |
if(isset($_GET['s']) && isset($_GET['e']) && isset($_GET['p'])) { | |
$surfer_names = explode(',', $_GET['s']); | |
$events = explode(',', $_GET['e']); | |
$positions = explode(',', $_GET['p']); | |
for($i = 0; $i < count($surfer_names); $i++) { | |
if(!empty($surfer_names[$i]) && !empty($events[$i]) && !empty($positions[$i])) { | |
$scenarios[] = array( | |
'surfer' => $surfer_names[$i], | |
'event' => intval($events[$i]), | |
'position' => intval($positions[$i]) | |
); | |
} | |
} | |
} | |
// Parse debug parameters and seed | |
$debug_surfer = isset($_GET['debug_surfer']) && !empty($_GET['debug_surfer']) ? $_GET['debug_surfer'] : null; | |
$debug_position = isset($_GET['debug_position']) && !empty($_GET['debug_position']) ? intval($_GET['debug_position']) : null; | |
$seed = isset($_GET['seed']) ? intval($_GET['seed']) : time(); | |
$results = simulation($surfers, $original_place_to_points, $num_simulations, $scenarios, $debug_surfer, $debug_position, $seed); | |
// Add mean calculation for cutoffs | |
if (!empty($results['cutoffs'])) { | |
$mean = array_sum($results['cutoffs']) / count($results['cutoffs']); | |
$results['cutoff']['mean'] = round($mean); | |
} | |
// Helper function for formatting numbers | |
function fmt($n) { | |
return number_format($n); | |
} | |
?> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>2025 WSL Championship Tour Simulation</title> | |
<!-- Social Media Meta Tags --> | |
<meta property="og:title" content="2025 WSL Championship Tour Simulation"> | |
<meta property="og:description" content="Simulate the 2025 WSL Championship Tour with real point projections, Finals qualification odds, and 'What If' scenarios. Track championship odds for all 24 surfers."> | |
<meta property="og:image" content="https://joefaron.com/wsl.webp"> | |
<meta property="og:url" content="https://joefaron.com/wsl-odds-calculator"> | |
<meta property="og:type" content="website"> | |
<meta property="og:site_name" content="WSL 2025 Simulation"> | |
<meta name="twitter:card" content="summary_large_image"> | |
<meta name="twitter:title" content="2025 WSL Championship Tour Simulation"> | |
<meta name="twitter:description" content="Simulate the 2025 WSL Championship Tour with real point projections, Finals qualification odds, and 'What If' scenarios. Track championship odds for all 24 surfers."> | |
<meta name="twitter:image" content="https://joefaron.com/wsl.webp"> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.css"> | |
<script type="text/javascript" charset="utf8" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | |
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script> | |
<style> | |
body { font-family: 'Inter', sans-serif; margin: 20px; background-color: #1a1a1a; color: #e5e5e5; } | |
.container { max-width: 1200px; margin: 0 auto; } | |
h1 { color: #60a5fa; text-align: center; margin-bottom: 30px; font-weight: 600; } | |
h3 a { font-family: 'Inter', sans-serif; font-weight: 500; color: #60a5fa; text-decoration: none; } | |
h3 a:hover { text-decoration: underline; } | |
.info { background-color: #2d3748; padding: 20px; border-radius: 8px; margin-bottom: 20px; } | |
.collapsible { cursor: pointer; user-select: none; transition: color 0.2s; } | |
.collapsible:hover { color: #93c5fd; } | |
.collapsible::after { content: ' ▼'; font-size: 0.8em; transition: transform 0.2s; } | |
.collapsible.collapsed::after { transform: rotate(-90deg); } | |
.collapsible-content { overflow: hidden; transition: max-height 0.3s ease; } | |
.collapsible-content.hidden { max-height: 0; padding: 0 20px; margin: 0; } | |
.current-standings { margin-bottom: 30px; } | |
.simulation-results { margin-top: 30px; } | |
table { width: 100%; border-collapse: collapse; margin: 20px 0; background-color: #2d3748; } | |
th, td { border: 1px solid #4a5568; padding: 8px; text-align: left; } | |
th { background-color: #1a202c; font-weight: bold; color: #e5e5e5; } | |
td {background-color: #2d3748;color:#e5e5e5} | |
/* have odd td's be diff color */ | |
.even td {background-color:rgb(76, 92, 123);} | |
.champion-odds { background-color:rgba(151, 106, 16, 0.72)!important; } | |
.top5-odds { background-color:rgba(11, 117, 25, 0.82)!important; } | |
.form-section { background-color: #2d3748; padding: 20px; border-radius: 8px; margin: 20px 0; } | |
textarea { width: 100%; height: 300px; font-family: monospace; background-color: #1a202c; color: #e5e5e5; border: 1px solid #4a5568; } | |
input[type="number"] { width: 100px; background-color: #1a202c; color: #e5e5e5; border: 1px solid #4a5568; } | |
select { background-color: #1a202c; color: #e5e5e5; border: 1px solid #4a5568; padding: 5px; } | |
button { background-color: #3182ce; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; } | |
button:hover { background-color: #2c5282; } | |
.explanation { font-style: italic; color: #a0aec0; margin-top: 10px; } | |
.scenario-row { margin: 10px 0; padding: 10px; background-color: #1a202c; border-radius: 4px; border: 1px solid #4a5568; } | |
.scenario-row select { margin-right: 10px; padding: 5px; } | |
h4 { color: #60a5fa; margin-top: 20px; } | |
a { color: #60a5fa; } | |
.dataTables_wrapper { background-color: #2d3748; } | |
.dataTables_filter input { background-color: #1a202c; color: #e5e5e5; border: 1px solid #4a5568; } | |
.dataTables_length select { background-color: #1a202c; color: #e5e5e5; border: 1px solid #4a5568; } | |
.dataTables_info, .dataTables_paginate { color: #e5e5e5; } | |
</style> | |
<script> | |
function addScenario() { | |
const container = document.getElementById('scenarios-container'); | |
const newRow = document.createElement('div'); | |
newRow.className = 'scenario-row'; | |
newRow.innerHTML = ` | |
<select class="scenario-surfer" onchange="updateScenarioFields()"> | |
<option value="">Select Surfer</option> | |
<?php foreach($surfers as $surfer): ?> | |
<?php $flag = get_country_flag($surfer['country']); ?> | |
<option value="<?php echo htmlspecialchars($surfer['name']); ?>"><?php echo htmlspecialchars($surfer['name']); ?> <?php echo $flag; ?> </option> | |
<?php endforeach; ?> | |
</select> | |
<select class="scenario-event" onchange="updateScenarioFields()"> | |
<option value="">Select Event</option> | |
<option value="10">Event 10 (J-Bay)</option> | |
<option value="11">Event 11 (Tahiti)</option> | |
<option value="12">Event 12 (Finals)</option> | |
</select> | |
<select class="scenario-position" onchange="updateScenarioFields()"> | |
<option value="">Select Position</option> | |
<option value="1">1st (10,000 pts)</option> | |
<option value="2">2nd (7,800 pts)</option> | |
<option value="3">3rd (6,085 pts)</option> | |
<option value="5">5th (4,745 pts)</option> | |
<option value="9">9th (3,320 pts)</option> | |
<option value="17">17th (1,330 pts)</option> | |
</select> | |
<button type="button" onclick="removeScenario(this)" style="background-color: #dc2626;">Remove</button> | |
`; | |
container.appendChild(newRow); | |
} | |
function removeScenario(button) { | |
const row = button.parentElement; | |
row.parentElement.removeChild(row); | |
updateScenarioFields(); | |
} | |
function updateScenarioFields() { | |
const surfers = [], events = [], positions = []; | |
document.querySelectorAll('.scenario-surfer').forEach(select => { | |
if(select.value) surfers.push(select.value); | |
}); | |
document.querySelectorAll('.scenario-event').forEach(select => { | |
if(select.value) events.push(select.value); | |
}); | |
document.querySelectorAll('.scenario-position').forEach(select => { | |
if(select.value) positions.push(select.value); | |
}); | |
document.querySelector('input[name="s"]').value = surfers.join(','); | |
document.querySelector('input[name="e"]').value = events.join(','); | |
document.querySelector('input[name="p"]').value = positions.join(','); | |
} | |
function copyCurrentURL() { | |
navigator.clipboard.writeText(window.location.href).then(function() { | |
alert('Link copied to clipboard! ' + window.location.href); | |
}).catch(function() { | |
prompt('Copy this URL:', window.location.href); | |
}); | |
} | |
// Update scenario fields when selects change | |
document.addEventListener('DOMContentLoaded', function() { | |
document.querySelectorAll('.scenario-surfer, .scenario-event, .scenario-position').forEach(select => { | |
select.addEventListener('change', updateScenarioFields); | |
}); | |
}); | |
</script> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>2025 WSL Championship Tour Simulation</h1> | |
<div class="info"> | |
<h3 class="collapsible" onclick="toggleSection('format-section')">🏄♂️ 2025 WSL Format</h3> | |
<div id="format-section" class="collapsible-content"> | |
<p><strong>Current Status:</strong> After Event 9 (VIVO Rio Pro), these 24 surfers will compete in the remaining events.</p> | |
<p><strong>Events 10-11:</strong> J-Bay (July 11-20) and Tahiti (August 7-16) - All 24 surfers compete</p> | |
<p><strong>Event 12:</strong> WSL Finals in Fiji (August 27-Sept 4) - Only the top 5 surfers after Event 11 qualify</p> | |
<p><strong>Point Structure:</strong> 1st=10,000, 2nd=7,800, 3rd=6,085, 5th=4,745, 9th=3,320, 17th=1,330, 33rd=265</p> | |
<p><em>Simulation run <?php echo number_format($num_simulations); ?> times</em></p> | |
</div> | |
<?php if(!empty($scenarios)): ?> | |
<div style="background-color: #fef3c7; padding: 15px; border-radius: 4px; margin-top: 15px; color:#333"> | |
<h4 style="margin-top: 0;">🎯 Active "What If" Scenarios:</h4> | |
<ul> | |
<?php foreach($scenarios as $scenario): ?> | |
<li><strong><?php echo htmlspecialchars($scenario['surfer']); ?></strong> gets <strong><?php echo ordinal($scenario['position']); ?> place</strong> in <strong>Event <?php echo $scenario['event']; ?></strong></li> | |
<?php endforeach; ?> | |
</ul> | |
</div> | |
<?php endif; ?> | |
</div> | |
<div class="info" style="background-color: #f0f9ff;color:#333"> | |
<h3 class="collapsible collapsed" onclick="toggleSection('how-section')">🎲 How This Simulation Works</h3> | |
<div id="how-section" class="collapsible-content hidden"> | |
<p><strong>Step 1:</strong> Start with each surfer's current points after Event 9</p> | |
<p><strong>Step 2:</strong> Simulate Events 10 & 11 (J-Bay & Tahiti) with all 24 surfers:</p> | |
<ul style="margin-left: 20px;"> | |
<li>Use realistic WSL tournament bracket: 1 winner, 1 finalist, 2 semifinal losers (tied 3rd), 4 quarterfinal losers (tied 5th), 8 round-of-16 losers (tied 9th), 8 first-round losers (tied 17th)</li> | |
<li>Add corresponding points: 1st=10,000, 2nd=7,800, 3rd=6,085, 5th=4,745, 9th=3,320, 17th=1,330</li> | |
<li>Apply any "What If" scenarios for these events</li> | |
</ul> | |
<p><strong>Step 3:</strong> Rank all surfers by total points - top 5 qualify for Finals</p> | |
<p><strong>Step 4:</strong> Simulate WSL Finals (Event 12) with only the top 5:</p> | |
<ul style="margin-left: 20px;"> | |
<li>Finals bracket: 1 winner, 1 finalist, 2 semifinal losers (tied 3rd), 1 quarterfinalist (5th)</li> | |
<li>Apply any "What If" scenarios for Finals</li> | |
<li><strong>Championship determined by Finals placement only</strong> (not cumulative points)</li> | |
<li>Winner of Finals = WSL Champion, regardless of points going into Finals</li> | |
</ul> | |
<p><strong>Step 5:</strong> Count how often each surfer wins championship, makes top 5, etc.</p> | |
<p><strong>Step 6:</strong> Repeat <?php echo number_format($num_simulations); ?> times to calculate percentage odds</p> | |
<p style="margin-top: 15px;"><strong>🎯 "What If" Scenarios:</strong> Override random results for specific surfers in specific events. For example, if you set "Griffin Colapinto wins Event 10", then in every simulation run, Griffin gets 1st place and 10,000 points in Event 10.</p> | |
</div> | |
</div> | |
<div class="current-standings"> | |
<h3>Current Rankings (After Event 9) <a href='https://www.worldsurfleague.com/athletes/tour/mct?year=2025' target='_blank'>🔗 WSL rankings </a></h3> | |
<div class="info" style="background-color: #4a5568; margin-bottom: 20px; padding: 15px;"> | |
<h4>📊 Projected Cut-off (Min Points to Make Top 5)</h4> | |
<p><strong>Min:</strong> <?php echo fmt($results['cutoff']['min']); ?> | | |
<strong>Mean:</strong> <?php echo fmt($results['cutoff']['mean']); ?> | | |
<strong>P50:</strong> <?php echo fmt($results['cutoff']['median']); ?> | | |
<strong>P90:</strong> <?php echo fmt($results['cutoff']['p90']); ?> | | |
<strong>Max:</strong> <?php echo fmt($results['cutoff']['max']); ?></p> | |
<p><em>These are the minimum points needed to beat 6th place and make top 5. P50 = typical cutoff (50% of simulations). P90 = almost-safe cutoff (90% of simulations).</em></p> | |
</div> | |
<div class="info" style="background-color: #2d3748; margin-bottom: 20px; padding: 15px;"> | |
<h4>Distribution: Minimum Points to Make Top-5 After Tahiti</h4> | |
<p><em>This shows how often each cutoff score occurs across <?php echo number_format($num_simulations); ?> simulations. Each bar represents the number of simulations where you needed that many points to beat 6th place.</em></p> | |
<p><em><strong>Why the distribution?</strong> Different event outcomes create different 6th place scores. When top surfers perform poorly, 6th place has fewer points (lower cutoff). When they perform well, 6th place has more points (higher cutoff).</em></p> | |
<canvas id="cutoffCurve" height="260"></canvas> | |
</div> | |
<table id="currentTable"> | |
<thead> | |
<tr> | |
<th>Rank</th> | |
<th>Surfer</th> | |
<th>Points</th> | |
<th>Gap to P50 cutoff</th> | |
<th>🏆 Odds</th> | |
<th>Top 5 Odds</th> | |
<th>🥇 </th> | |
<th>🥈 </th> | |
<th>🥉 </th> | |
<th>4th</th> | |
<th>5th</th> | |
</tr> | |
</thead> | |
<tbody> | |
<?php | |
// Sort surfers by current points for display | |
usort($surfers, function($a, $b) { | |
return $b['points'] - $a['points']; | |
}); | |
$rank = 1; | |
foreach($surfers as $surfer) { | |
$name = $surfer['name']; | |
$champion_pct = round(($results['champion'][$name] / $results['total_simulations']) * 100, 2); | |
$top5_pct = round(($results['top5'][$name] / $results['total_simulations']) * 100, 2); | |
// Calculate gap to P50 cutoff (points needed to beat 6th place) | |
$gap = max(0, $results['cutoff']['median'] - $surfer['points']); | |
$gap_style = ''; | |
if($gap == 0) { | |
$gap_style = 'background-color: rgba(34, 197, 94, 0.6);'; // Green | |
} elseif($gap <= 5000) { | |
$gap_style = 'background-color: rgba(251, 191, 36, 0.6);'; // Amber | |
} else { | |
$gap_style = 'background-color: rgba(239, 68, 68, 0.6);'; // Red | |
} | |
$flag = get_country_flag($surfer['country']); | |
echo "<tr>"; | |
echo "<td>$rank</td>"; | |
echo "<td>$name $flag</td>"; | |
echo "<td>" . number_format($surfer['points']) . "</td>"; | |
echo "<td style='$gap_style'>" . ($gap == 0 ? 'Safe' : fmt($gap)) . "</td>"; | |
echo "<td class='champion-odds'>{$champion_pct}%</td>"; | |
echo "<td class='top5-odds'>{$top5_pct}%</td>"; | |
// Display position percentages for all finals positions | |
foreach([1, 2, 3, 4, 5] as $pos) { | |
$pos_pct = round(($results['positions'][$name][$pos] / $results['total_simulations']) * 100, 2); | |
echo "<td>{$pos_pct}%</td>"; | |
} | |
echo "</tr>"; | |
$rank++; | |
} | |
?> | |
</tbody> | |
</table> | |
</div> | |
<?php if($debug_surfer && $debug_position): ?> | |
<div class="info" style="background-color: #fef3c7; border: 2px solid #f59e0b;color:#333"> | |
<h3>🔍 Debug: Simulation Details</h3> | |
<?php if($results['debug_info']): ?> | |
<p><strong>Showing details from simulation #<?php echo $results['debug_info']['simulation']; ?> where <?php echo htmlspecialchars($results['debug_info']['surfer']); ?> finished <?php echo ordinal($results['debug_info']['final_position']); ?> place</strong></p> | |
<?php else: ?> | |
<p><strong>❌ No simulation found where <?php echo htmlspecialchars($debug_surfer); ?> finished <?php echo ordinal($debug_position); ?> place</strong></p> | |
<p><em>This scenario never occurred in any of the <?php echo number_format($num_simulations); ?> simulation runs. Try different parameters or increase the number of simulations.</em></p> | |
<?php endif; ?> | |
<?php if($results['debug_info']): ?> | |
<table style="width: 100%; border: 1px solid #ccc; margin: 10px 0;"> | |
<tr><th style="background-color: #f59e0b; color: white;">Stage</th><th style="background-color: #f59e0b; color: white;">Details</th></tr> | |
<tr><td><strong>Starting Points (After Event 9)</strong></td><td><?php echo number_format($results['debug_info']['starting_points']); ?> points</td></tr> | |
<tr><td><strong>Event 10 (J-Bay)</strong></td><td>Place: <?php echo ordinal($results['debug_info']['event10_place']); ?>, Points: +<?php echo number_format($results['debug_info']['event10_points']); ?></td></tr> | |
<tr><td><strong>Event 11 (Tahiti)</strong></td><td>Place: <?php echo ordinal($results['debug_info']['event11_place']); ?>, Points: +<?php echo number_format($results['debug_info']['event11_points']); ?></td></tr> | |
<tr><td><strong>Points After Event 11</strong></td><td><?php echo number_format($results['debug_info']['points_after_event11']); ?> points</td></tr> | |
<tr><td><strong>Made Finals?</strong></td><td><?php echo $results['debug_info']['made_finals'] ? 'Yes - Top 5 qualification' : 'No'; ?></td></tr> | |
<?php if($results['debug_info']['made_finals']): ?> | |
<tr><td><strong>Finals Field</strong></td><td><?php | |
$finals_display = array(); | |
foreach($results['debug_info']['finals_field'] as $index => $qualifier) { | |
$position = $index + 1; | |
$name = $qualifier['name']; | |
$points = number_format($qualifier['points_before_finals']); | |
$finals_display[] = "{$position}. {$name} ({$points} pts)"; | |
} | |
echo implode(', ', $finals_display); | |
?></td></tr> | |
<tr><td><strong>Finals Place</strong></td><td><?php echo ordinal($results['debug_info']['finals_place']); ?> place, Points: +<?php echo number_format($results['debug_info']['finals_points']); ?></td></tr> | |
<tr><td><strong>Final Total Points</strong></td><td><?php echo number_format($results['debug_info']['total_points']); ?> points</td></tr> | |
<tr><td><strong>Championship Position</strong></td><td><strong style="color: #f59e0b;"><?php echo ordinal($results['debug_info']['final_position']); ?> Place</strong></td></tr> | |
<?php endif; ?> | |
</table> | |
<?php if($results['debug_info']['bracket_details']): ?> | |
<h4 style="color: #f59e0b; margin-top: 20px;">🏆 Finals Bracket Progression</h4> | |
<table style="width: 100%; border: 1px solid #ccc; margin: 10px 0;"> | |
<tr><th style="background-color: #f59e0b; color: white;">Match</th><th style="background-color: #f59e0b; color: white;">Details</th></tr> | |
<?php foreach($results['debug_info']['bracket_details'] as $match_key => $match): ?> | |
<tr> | |
<td><strong><?php echo $match['description']; ?></strong></td> | |
<td> | |
<?php echo $match['surfer1']; ?> vs <?php echo $match['surfer2']; ?> | |
<br><strong>Winner:</strong> <?php echo $match['winner']; ?> | |
<?php if($match_key == 'title_match'): ?> | |
<br><strong>Score:</strong> <?php echo $match['final_score']; ?> | |
<br><strong>Heat Details:</strong> <?php echo implode(', ', $match['heat_results']); ?> | |
<?php endif; ?> | |
<?php if(isset($match['loser_position'])): ?> | |
<br><em><?php echo $match['loser']; ?> eliminated (<?php echo $match['loser_position']; ?>)</em> | |
<?php endif; ?> | |
</td> | |
</tr> | |
<?php endforeach; ?> | |
</table> | |
<?php endif; ?> | |
<p><em>Note: Championship position is determined by Finals placement only, not total points.</em></p> | |
<?php endif; ?> | |
</div> | |
<?php endif; ?> | |
<div class="form-section"> | |
<h3>Modify Simulation </h3> | |
<form method="get"> | |
<label for="simulations"># of Simulations:</label> | |
<input type="number" name="simulations" value="<?php echo $num_simulations; ?>" min="1000" max="100000" step="1000"> | |
<br> | |
<h4>🎯 "What If" Scenarios | |
<span class="explanation">Set specific results for surfers in upcoming events:</span></h4> | |
<?php | |
$scenario_surfers = array(); | |
$scenario_events = array(); | |
$scenario_positions = array(); | |
foreach($scenarios as $scenario) { | |
$scenario_surfers[] = $scenario['surfer']; | |
$scenario_events[] = $scenario['event']; | |
$scenario_positions[] = $scenario['position']; | |
} | |
?> | |
<input type="hidden" name="s" value="<?php echo htmlspecialchars(implode(',', $scenario_surfers)); ?>"> | |
<input type="hidden" name="e" value="<?php echo htmlspecialchars(implode(',', $scenario_events)); ?>"> | |
<input type="hidden" name="p" value="<?php echo htmlspecialchars(implode(',', $scenario_positions)); ?>"> | |
<div id="scenarios-container"> | |
<?php if(!empty($scenarios)): ?> | |
<?php foreach($scenarios as $scenario): ?> | |
<div class="scenario-row"> | |
<select class="scenario-surfer"> | |
<option value="">Select Surfer</option> | |
<?php foreach($surfers as $surfer): ?> | |
<?php $flag = get_country_flag($surfer['country']); ?> | |
<option value="<?php echo htmlspecialchars($surfer['name']); ?>"<?php echo ($scenario['surfer'] == $surfer['name']) ? ' selected' : ''; ?>><?php echo htmlspecialchars($surfer['name']); ?> <?php echo $flag; ?> </option> | |
<?php endforeach; ?> | |
</select> | |
<select class="scenario-event"> | |
<option value="">Select Event</option> | |
<option value="10"<?php echo ($scenario['event'] == 10) ? ' selected' : ''; ?>>Event 10 (J-Bay)</option> | |
<option value="11"<?php echo ($scenario['event'] == 11) ? ' selected' : ''; ?>>Event 11 (Tahiti)</option> | |
<option value="12"<?php echo ($scenario['event'] == 12) ? ' selected' : ''; ?>>Event 12 (Finals)</option> | |
</select> | |
<select class="scenario-position"> | |
<option value="">Select Position</option> | |
<option value="1"<?php echo ($scenario['position'] == 1) ? ' selected' : ''; ?>>1st (10,000 pts)</option> | |
<option value="2"<?php echo ($scenario['position'] == 2) ? ' selected' : ''; ?>>2nd (7,800 pts)</option> | |
<option value="3"<?php echo ($scenario['position'] == 3) ? ' selected' : ''; ?>>3rd (6,085 pts)</option> | |
<option value="5"<?php echo ($scenario['position'] == 5) ? ' selected' : ''; ?>>5th (4,745 pts)</option> | |
<option value="9"<?php echo ($scenario['position'] == 9) ? ' selected' : ''; ?>>9th (3,320 pts)</option> | |
<option value="17"<?php echo ($scenario['position'] == 17) ? ' selected' : ''; ?>>17th (1,330 pts)</option> | |
</select> | |
<button type="button" onclick="removeScenario(this)" style="background-color: #dc2626;">Remove</button> | |
</div> | |
<?php endforeach; ?> | |
<?php else: ?> | |
<div class="scenario-row"> | |
<select class="scenario-surfer"> | |
<option value="">Select Surfer</option> | |
<?php foreach($surfers as $surfer): ?> | |
<?php $flag = get_country_flag($surfer['country']); ?> | |
<option value="<?php echo htmlspecialchars($surfer['name']); ?>"><?php echo htmlspecialchars($surfer['name']); ?> <?php echo $flag; ?> </option> | |
<?php endforeach; ?> | |
</select> | |
<select class="scenario-event"> | |
<option value="">Select Event</option> | |
<option value="10">Event 10 (J-Bay)</option> | |
<option value="11">Event 11 (Tahiti)</option> | |
<option value="12">Event 12 (Finals)</option> | |
</select> | |
<select class="scenario-position"> | |
<option value="">Select Position</option> | |
<option value="1">1st (10,000 pts)</option> | |
<option value="2">2nd (7,800 pts)</option> | |
<option value="3">3rd (6,085 pts)</option> | |
<option value="5">5th (4,745 pts)</option> | |
<option value="9">9th (3,320 pts)</option> | |
<option value="17">17th (1,330 pts)</option> | |
</select> | |
<button type="button" onclick="removeScenario(this)" style="background-color: #dc2626;">Remove</button> | |
</div> | |
<?php endif; ?> | |
</div> | |
<button type="button" onclick="addScenario()" style="background-color: #059669; margin-top: 10px;">Add Another Scenario</button> | |
<div class="explanation">Note: Event 12 (Finals) only allows positions 1st-5th since only 5 surfers compete.</div> | |
<br> | |
<h4>🔍 Debug Simulation Details | |
<span class="explanation">Show detailed breakdown of a specific simulation run:</span></h4> | |
<div class="scenario-row"> | |
<select name="debug_surfer"> | |
<option value="">Select Surfer to Debug</option> | |
<?php foreach($surfers as $surfer): ?> | |
<?php $flag = get_country_flag($surfer['country']); ?> | |
<option value="<?php echo htmlspecialchars($surfer['name']); ?>"<?php echo ($debug_surfer == $surfer['name']) ? ' selected' : ''; ?>><?php echo htmlspecialchars($surfer['name']); ?> <?php echo $flag; ?></option> | |
<?php endforeach; ?> | |
</select> | |
<select name="debug_position"> | |
<option value="">Select Final Position</option> | |
<option value="1"<?php echo ($debug_position == 1) ? ' selected' : ''; ?>>1st Place Champion</option> | |
<option value="2"<?php echo ($debug_position == 2) ? ' selected' : ''; ?>>2nd Place</option> | |
<option value="3"<?php echo ($debug_position == 3) ? ' selected' : ''; ?>>3rd Place</option> | |
<option value="5"<?php echo ($debug_position == 5) ? ' selected' : ''; ?>>5th Place</option> | |
</select> | |
</div> | |
<div class="explanation">This will show details of one simulation where the selected surfer finished in the selected position.</div> | |
<br> | |
Random Seed: <input type="number" name="seed" value="<?php echo $seed; ?>"> | |
<span class="explanation">Used to seed the simulation. Ensures that the simulation is reproducible.</span> | |
<BR><BR> | |
<button type="submit">Run Simulation</button> | |
<button type="button" onclick="copyCurrentURL()" style="background-color: #059669; margin-left: 10px;">📋 Copy Link</button> | |
</form> | |
</div> | |
</div> | |
<script> | |
// Expose data to JavaScript | |
const CUTOFFS = <?php echo json_encode($results['cutoffs']); ?>; | |
const CUT = <?php echo json_encode($results['cutoff']); ?>; | |
const S_NOW = <?php echo json_encode(array_column($surfers,'points','name')); ?>; | |
<?php if($results['debug_info']): ?> | |
const DBG = <?php echo json_encode($results['debug_info']); ?>; | |
<?php endif; ?> | |
// Toggle collapsible sections | |
function toggleSection(sectionId) { | |
const section = document.getElementById(sectionId); | |
const header = document.querySelector('[onclick="toggleSection(\'' + sectionId + '\')"]'); | |
if (section.classList.contains('hidden')) { | |
section.classList.remove('hidden'); | |
header.classList.remove('collapsed'); | |
section.style.maxHeight = section.scrollHeight + 'px'; | |
} else { | |
section.classList.add('hidden'); | |
header.classList.add('collapsed'); | |
section.style.maxHeight = '0px'; | |
} | |
} | |
$(document).ready(function() { | |
$('#currentTable').DataTable({ | |
"order": [[ 4, "desc" ]], // Adjusted for new Gap column | |
"pageLength": 25, | |
"columnDefs": [ | |
{ "type": "num", "targets": [0, 2, 3, 4, 5, 6, 7, 8, 9] } | |
] | |
}); | |
// Initialize collapsible sections | |
const formatSection = document.getElementById('format-section'); | |
const howSection = document.getElementById('how-section'); | |
if (formatSection) { | |
formatSection.style.maxHeight = formatSection.scrollHeight + 'px'; | |
} | |
if (howSection) { | |
howSection.style.maxHeight = '0px'; | |
} | |
// Create charts | |
createCharts(); | |
}); | |
function createCharts() { | |
// 1. Bell Curve of Cut-off Distribution | |
const bins = 10; | |
const [min, max] = [Math.min(...CUTOFFS), Math.max(...CUTOFFS)]; | |
// Round bin edges to nice numbers (multiples of 250) | |
<?php $mult=250; ?> | |
const roundedMin = Math.floor(min / <?php echo $mult; ?>) * <?php echo $mult; ?>; | |
const roundedMax = Math.ceil(max / <?php echo $mult; ?>) * <?php echo $mult; ?>; | |
const step = (roundedMax - roundedMin) / bins; | |
const roundedStep = Math.round(step / <?php echo $mult; ?>) * <?php echo $mult; ?>; | |
const freq = Array(bins).fill(0); | |
CUTOFFS.forEach(x => { | |
const binIndex = Math.min(bins - 1, Math.floor((x - roundedMin) / roundedStep)); | |
freq[binIndex]++; | |
}); | |
new Chart('cutoffCurve', { | |
type: 'bar', | |
data: { | |
labels: freq.map((_, i) => { | |
const binStart = roundedMin + i * roundedStep; | |
const binEnd = binStart + roundedStep; | |
return binStart.toLocaleString() + '-' + binEnd.toLocaleString(); | |
}), | |
datasets: [{ | |
data: freq, | |
label: 'Number of simulations', | |
backgroundColor: 'rgba(96, 165, 250, 0.8)', | |
borderColor: 'rgba(96, 165, 250, 1)', | |
borderWidth: 1 | |
}] | |
}, | |
options: { | |
responsive: true, | |
plugins: { | |
legend: { | |
labels: { | |
color: '#e5e5e5' | |
} | |
} | |
}, | |
scales: { | |
x: { | |
title: { | |
display: true, | |
text: 'Points Needed to Beat 6th Place', | |
color: '#e5e5e5' | |
}, | |
ticks: { | |
color: '#e5e5e5', | |
maxRotation: 45, | |
minRotation: 45 | |
}, | |
grid: { | |
color: '#4a5568' | |
} | |
}, | |
y: { | |
title: { | |
display: true, | |
text: 'Frequency', | |
color: '#e5e5e5' | |
}, | |
ticks: { | |
color: '#e5e5e5' | |
}, | |
grid: { | |
color: '#4a5568' | |
} | |
} | |
} | |
} | |
}); | |
// 2. Surfer-vs-Cut-off Gap Bars | |
const gaps = {}; | |
const surferNames = Object.keys(S_NOW); | |
surferNames.forEach(name => { | |
gaps[name] = Math.max(0, CUT.median - S_NOW[name]); | |
}); | |
// 3. Debug Run: Event-by-Event Bar Chart | |
<?php if($results['debug_info']): ?> | |
if (typeof DBG !== 'undefined' && DBG.finals_field) { | |
const names = DBG.finals_field.map(x => x.name); | |
const pts10 = names.map(n => { | |
// Find the surfer's event 10 points from debug info | |
if (n === DBG.surfer) { | |
return DBG.event10_points; | |
} | |
return 0; // We don't have this data for other surfers in debug | |
}); | |
const pts11 = names.map(n => { | |
// Find the surfer's event 11 points from debug info | |
if (n === DBG.surfer) { | |
return DBG.event11_points; | |
} | |
return 0; // We don't have this data for other surfers in debug | |
}); | |
} | |
<?php endif; ?> | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment