Skip to contents

This vignette models a 16-team World Cup: four groups of four teams, with the top two from each group advancing to a single-elimination knockout round.

It demonstrates:

  • round_robin("groups", groups = 4) for parallel group play,
  • top_per_group(2) for per-group advancement routing,
  • auto-advance behavior (no explicit advance() needed),
  • stage_status(), matches(), and standings() for live inspection.

1) Participant pool

teams <- c(
  "Argentina", "Australia", "Brazil",   "Croatia",
  "England",   "France",   "Japan",     "Morocco",
  "Netherlands", "Poland", "Portugal",  "Senegal",
  "South Korea", "Spain",  "Switzerland", "United States"
)

length(teams)

2) Define the tournament

round_robin("groups", groups = 4) creates four independent groups within a single stage node. Each group runs a full round-robin schedule.

top_per_group(2) selects the top two finishers from each group’s own standings — not the top 8 from a combined ranking. This is the standard World Cup advancement rule.

trn <- tournament(teams) |>
  round_robin("groups", groups = 4) |>
  single_elim("knockout", take = top_per_group(2))

from = previous_stage() is implicit — “knockout” reads from “groups” automatically.


3) Inspect the group schedule

stage_status(trn)

group_ms <- matches(trn, "groups")
nrow(group_ms)   # 4 groups × 6 matches = 24 matches
head(group_ms)

4) Enter group results

Auto-advance is on by default. Once the last group match is entered, the knockout stage materializes automatically with the top two from each group.

for (i in seq_len(nrow(group_ms))) {
  trn <- trn |> result("groups", match = group_ms$id[i], score = c(1, 0))
}

No explicit advance() call — the knockout stage materializes on its own.


5) Confirm advancement

stage_status(trn)
#   stage     status        complete  total  materialized
#   groups    complete            24     24          TRUE
#   knockout  active               0      8          TRUE

matches(trn, "knockout")  # 8 teams: 2 × 4 groups

6) Inspect group standings

standings(trn, "groups")

The standings include a group membership column when the source stage uses groups =.


7) Run the knockout round

knockout_ms <- matches(trn, "knockout")

for (i in seq_len(nrow(knockout_ms))) {
  trn <- trn |> result("knockout", match = knockout_ms$id[i], score = c(1, 0))
}

winner(trn)
rankings(trn)

8) Routing audit

routing_log() records the transition resolution: which participants were selected, from which source stage, by which selector.


9) Comparison with previous API

Task Old API New API
Define groups → knockout tournament_spec() \|> add_stage() \|> add_stage() \|> split_stage(from = ..., into = list(...)) \|> build_tournament(spec, teams) tournament(teams) \|> round_robin("groups", groups = 4) \|> single_elim("knockout", take = top_per_group(2))
Enter a result result(trn, "groups", match_id = 1, score1 = 1, score2 = 0) result(trn, "groups", match = 1, score = c(1, 0))
Advance stage if (is_stage_complete(trn, "groups")) advance(trn, "groups") automatic by default
See standings get_standings(trn$stage_state$groups$bracket) standings(trn, "groups")
Winner get_winner(trn$stage_state$knockout$bracket) winner(trn)