[Odin] Events, groups, teams, matches etc.[Odin] Events, groups, teams, matches etc.
🏆
FIFA World Cup 2026
Week 23, 2026
package main
import "core:encoding/json"
import "core:fmt"
import "core:slice"
INPUT_TYPE :: "fifa" // "fifa", "uefa"
INPUT_GROUPS_JSON := #load("groups_" + INPUT_TYPE + ".json")
INPUT_RANKINGS_JSON := #load("rankings_" + INPUT_TYPE + ".json")
INPUT_QUALIFIERS_JSON := #load("qualifiers.json") // The third place qualifiers table
INPUT_KNOCKOUT_32_JSON := #load("knockout_32.json") // The matches played in Knockout stage with 32 teams
when INPUT_TYPE == "fifa" do input_ruler := Input_Match_Ruler { 100, 250, 500 }
else when INPUT_TYPE == "uefa" do input_ruler := Input_Match_Ruler { 20, 40, 80 }
else do #panic("input_ruler is undefined for `" + INPUT_TYPE + "`")
main :: proc () {
input_groups: Input_Groups
json.unmarshal(INPUT_GROUPS_JSON, &input_groups)
input_rankings: Input_Rankings
json.unmarshal(INPUT_RANKINGS_JSON, &input_rankings)
input_qualifiers: Input_Qualifiers
json.unmarshal(INPUT_QUALIFIERS_JSON, &input_qualifiers)
input_knockout_32: Input_Knockout_32
json.unmarshal(INPUT_KNOCKOUT_32_JSON, &input_knockout_32)
fmt.ensuref(len(input_knockout_32) == 32, "Expecting 32 teams (16 pairs) in knockout_32, got %i", len(input_knockout_32))
fmt.ensuref(len(input_groups) == 12, "Expecting 12 groups, got %i", len(input_groups))
for group, i in input_groups {
fmt.ensuref(len(group) == 4, "Expecting 4 teams, got %i in group #%i", len(group), i)
for team in group do fmt.ensuref(team in input_rankings, "Missing ranking for team `%s`", team)
}
event: Event
event_init(&event, input_groups, input_rankings, input_ruler)
event_do_groups(&event)
event_do_third_place_qualifiers_and_eliminations(&event)
event_do_knockout_32(&event, input_qualifiers, input_knockout_32)
event_do_knockout_bracket(&event, from=event.knockout.teams_16[:], to=event.knockout.teams_8[:])
event_do_knockout_bracket(&event, from=event.knockout.teams_8[:], to=event.knockout.teams_4[:])
event_do_knockout_bracket(&event, from=event.knockout.teams_4[:], to=event.knockout.teams_2[:])
event_do_knockout_bracket(&event, from=event.knockout.teams_2[:], to=event.knockout.teams_1[:])
}
Input_Groups :: [] [] string
Input_Rankings :: map [string] f32
Input_Qualifiers :: map [string] [] string
Input_Knockout_32 :: [] string
Input_Match_Ruler :: [3] f32 // 0..<[0] draw/penalties, [0]..<[1] 1 goal, [1]..<[2] 2 goals, [2]+ 3 goals
Event :: struct {
teams : [48] Event_Team,
groups : [12] Event_Group,
ruler : Input_Match_Ruler,
knockout: struct {
teams_32: [32] ^Event_Team, // Round of 32: [0..23] 12 winners and 12 runnings, [24..31] top 8 3rd places
teams_16: [16] ^Event_Team, // Round of 16
teams_8 : [8] ^Event_Team, // Quarter Final
teams_4 : [4] ^Event_Team, // Semi Final
teams_2 : [2] ^Event_Team, // Final
teams_1 : [1] ^Event_Team, // Champion
},
}
Event_Group :: struct {
name : byte,
teams : [4] ^Event_Team,
}
Event_Team :: struct {
name : string,
group : ^Event_Group,
points : int,
goals : int,
ranking : f32,
}
event_init :: proc (e: ^Event, input_groups: Input_Groups, input_rankings: Input_Rankings, input_ruler: Input_Match_Ruler) {
e^ = {}
e.ruler = input_ruler
for group, i in input_groups {
e.groups[i].name = 'A' + byte(i)
for team, j in group {
fmt.ensuref(team in input_rankings, "Missing ranking for team `%s`", team)
e.teams[i*4+j] = {
name = team,
group = &e.groups[i],
ranking = input_rankings[team],
}
e.groups[i].teams[j] = &e.teams[i*4+j]
}
}
}
event_do_groups :: proc (e: ^Event) {
for &group in e.groups {
fmt.printfln("--- Group %c Matches ---", group.name)
for i:=0; i<4; i+=1 do for j:=i+1; j<4; j+=1 {
event_teams_match(group.teams[i], group.teams[j], e.ruler, draw_allowed=true)
}
fmt.println()
event_teams_sort(group.teams[:])
fmt.printfln("--- Group %c Table ---", group.name)
for t, i in group.teams {
fmt.printfln("%i. %s %i %i", i+1, t.name, t.points, t.goals)
}
fmt.println()
}
}
event_do_third_place_qualifiers_and_eliminations :: proc (e: ^Event) {
third_place_teams: [12] ^Event_Team
for group, i in e.groups {
e.knockout.teams_32[i*2+0] = group.teams[0] // group winner
e.knockout.teams_32[i*2+1] = group.teams[1] // group runner
third_place_teams[i] = group.teams[2] // third place candidate
}
event_teams_sort(third_place_teams[:])
for team, i in third_place_teams {
if i < 8 do e.knockout.teams_32[12*2+i] = team
if i == 0 do fmt.println("--- Third Place Qualifiers ---")
if i == 8 do fmt.println("--- Third Place Eliminations ---")
fmt.printfln("%i. %s %i %i", i+1, team.name, team.points, team.goals)
}
fmt.println()
}
event_do_knockout_32 :: proc (e: ^Event, input_qualifiers: Input_Qualifiers, input_knockout_32: Input_Knockout_32) {
fmt.println("--- Round of 32 ---")
q_row := event_qualifiers_row(e, input_qualifiers)
for &winner, i in e.knockout.teams_16 {
pair: [2] ^Event_Team
for &team, j in pair {
group_name: byte
team_pos: int
key := input_knockout_32[i*2+j]
if key == "?" {
assert(1 == j) // The "?" can only be the second in a pair
assert(1 == event_team_group_pos(pair[0])) // and 1st team must be a winner, e.g. 1E not 2E
group_name = q_row[pair[0].group.name-'A']
team_pos = 3
} else {
group_name = key[1]
team_pos = int(key[0]-'0')
}
team = event_team_in_group(e.knockout.teams_32[:], group_name, team_pos)
}
fmt.printf("%i. ", i+1)
winner = event_teams_match(pair[0], pair[1], e.ruler, draw_allowed=false)
}
fmt.println()
}
event_do_knockout_bracket :: proc (e: ^Event, from, to: [] ^Event_Team) {
assert(len(from) == 2 * len(to))
switch len(from) {
case 16 : fmt.println("--- Round of 16 ---")
case 8 : fmt.println("--- Quarter Final ---")
case 4 : fmt.println("--- Semi Final ---")
case 2 : fmt.println("--- Final ---")
case : panic("Unexpected bracket size")
}
for &winner, i in to {
fmt.printf("%i. ", i+1)
winner = event_teams_match(from[i*2+0], from[i*2+1], e.ruler, draw_allowed=false)
}
fmt.println()
}
event_qualifiers_row :: proc (e: ^Event, input_qualifiers: Input_Qualifiers) -> [12] byte {
q_key: [8] byte
for &b, i in q_key do b = e.knockout.teams_32[i+24].group.name
slice.sort(q_key[:])
q_key_str := string(q_key[:])
fmt.ensuref(q_key_str in input_qualifiers, "Missing qualifier key `%s`", q_key_str)
q: [8] byte
for s, i in input_qualifiers[q_key_str] do q[i] = s[0]
// 1A 1B 1C 1D 1E 1F 1G 1H 1I 1J 1K 1L
return { q[0], q[1], 0, q[2], q[3], 0, q[4], 0, q[5], 0, q[6], q[7] }
}
event_teams_match :: proc (team1, team2: ^Event_Team, ruler: Input_Match_Ruler, draw_allowed := true) -> (winner: ^Event_Team) {
ranking_diff := team1.ranking - team2.ranking
ranking_diff_abs := abs(ranking_diff)
if ranking_diff_abs < ruler[0] && draw_allowed {
fmt.printfln("%s drew with %s", team1.name, team2.name)
team1.points += 1
team2.points += 1
return
}
goals: int
beat_order: [2] ^Event_Team = ranking_diff > 0 ? { team1, team2 } : { team2, team1 }
beat_order[0].points += 3
switch {
case ranking_diff_abs < ruler[0]:
fmt.printfln("%s beat %s on penalties", beat_order[0].name, beat_order[1].name)
// ignore goals counter for penalties
case ranking_diff_abs < ruler[1]:
fmt.printfln("%s beat %s by 1 goal", beat_order[0].name, beat_order[1].name)
goals = 1
case ranking_diff_abs < ruler[2]:
fmt.printfln("%s beat %s by 2 goals", beat_order[0].name, beat_order[1].name)
goals = 2
case:
fmt.printfln("%s beat %s by 3 goals", beat_order[0].name, beat_order[1].name)
goals = 3
}
beat_order[0].goals += goals
beat_order[1].goals -= goals
return beat_order[0]
}
event_teams_sort :: proc (teams: [] ^Event_Team) {
slice.sort_by(teams[:], less = proc (t1, t2: ^Event_Team) -> bool {
switch {
case t1.points != t2.points : return t1.points > t2.points
case t1.goals != t2.goals : return t1.goals > t2.goals
case : return t1.ranking > t2.ranking
}
})
}
event_team_in_group :: proc (teams: [] ^Event_Team, group_name: byte, team_pos: int) -> ^Event_Team {
Args :: struct { group_name: byte, team_pos: int }
context.user_ptr = &Args { group_name, team_pos }
i, ok := slice.linear_search_proc(teams, proc (t: ^Event_Team) -> bool {
args := cast (^Args) context.user_ptr
return t.group.name == args.group_name && event_team_group_pos(t) == args.team_pos
})
assert(ok)
return teams[i]
}
event_team_group_pos :: proc (team: ^Event_Team) -> int {
i, ok := slice.linear_search(team.group.teams[:], team)
assert(ok)
return 1 + i
}