Documentation
¶
Overview ¶
Package brackets generates and runs tournament formats: single and double elimination brackets, Swiss, round robin, group stages, free-for-all lobby leagues and multi-stage flows. It uses only the Go standard library.
Give a Format participants and Options; it returns Stage values holding a deterministic schedule of Match values. When a match finishes, record the result on the stage and call Format.Advance (or use Report, which validates the result, advances and applies the outcome in one call). Formats are stateless: the stage is plain data you can persist between calls, and every call recomputes what it needs from the stage, so corrected results are picked up on the next call. Callers that keep matches in their own storage can feed them to a Tally one at a time instead of building Stage.Matches.
Formats are registered by key in a Registry; DefaultRegistry holds every built-in format.
Example ¶
A six-player single-elimination bracket: the two top seeds get byes, the higher seed wins every match.
package main
import (
"fmt"
"github.com/Aquilabot/brackets"
)
func main() {
players := []brackets.Participant{
{ID: "ana", Seed: 1}, {ID: "ben", Seed: 2}, {ID: "cleo", Seed: 3},
{ID: "dan", Seed: 4}, {ID: "eve", Seed: 5}, {ID: "fay", Seed: 6},
}
f := brackets.SingleElimination{}
stages, err := f.Plan(players, brackets.Options{})
if err != nil {
panic(err)
}
st := &stages[0]
for _, m := range st.Matches {
switch {
case m.Bye:
fmt.Printf("%s: bye for %s\n", m.ID, m.Winner)
case m.Round == 1:
fmt.Printf("%s: %s vs %s\n", m.ID, m.Home(), m.Away())
}
}
for !st.Finished {
for i := range st.Matches {
m := &st.Matches[i]
if m.Status != brackets.Pending || m.Home() == "" || m.Away() == "" {
continue
}
if _, err := brackets.ReportAt(f, st, i, brackets.Result{Winner: m.Home(), Scores: []int{2, 0}}); err != nil {
panic(err)
}
}
}
fmt.Println("champion:", st.Winner)
}
Output: r1-m1: bye for ana r1-m2: dan vs eve r1-m3: bye for ben r1-m4: cleo vs fay champion: ana
Index ¶
- Constants
- Variables
- func DefaultSwissRounds(n int) int
- func FillPlaceholders(st *Stage, source string, qualifiers []string) int
- func GroupName(i int) string
- func GroupStandings(st *Stage) [][]Standing
- func IsPlaceholder(ref string) bool
- func NextPowerOfTwo(n int) int
- func Placeholder(stage string, rank int) string
- func Qualifiers(st *Stage, n int) []string
- func SeedOrder(size int) []int
- type Advance
- type Bracket
- type Bye
- type CountError
- type CountRule
- type DoubleElimination
- func (f DoubleElimination) Advance(st *Stage, finished *Match) (*Advance, error)
- func (DoubleElimination) Key() string
- func (DoubleElimination) Moves(finished *Match) Moves
- func (DoubleElimination) Plan(participants []Participant, opts Options) ([]Stage, error)
- func (DoubleElimination) Validate(Options) error
- type FFALeague
- type Format
- type Group
- type GroupStage
- type Link
- type LinkError
- type Match
- type Moves
- type MultiStage
- type OptionError
- type Options
- type Pairing
- type PartialPolicy
- type Participant
- type ParticipantError
- type Registry
- type Result
- type RoundRobin
- type Seeding
- type SingleElimination
- func (SingleElimination) Advance(st *Stage, finished *Match) (*Advance, error)
- func (SingleElimination) Key() string
- func (SingleElimination) Parent(round, number int) (nextRound, nextNumber, slot int)
- func (SingleElimination) Plan(participants []Participant, opts Options) ([]Stage, error)
- func (SingleElimination) Validate(Options) error
- type Slot
- type SlotUpdate
- type Stage
- type StageError
- type StageSpec
- type Standing
- type Status
- type Swiss
- type Tally
Examples ¶
Constants ¶
const ( KeySingleElimination = "single_elimination" KeyDoubleElimination = "double_elimination" KeySwiss = "swiss" KeyRoundRobin = "round_robin" KeyGroupStage = "group_stage" KeyFFALeague = "ffa_league" KeyMultiStage = "multi_stage" )
Built-in format keys.
const PlaceholderPrefix = "slot:"
PlaceholderPrefix marks slot references that will be filled by the qualifiers of an earlier stage (see Placeholder). Participant IDs must not start with it.
Variables ¶
var ( // ErrUnknownFormat is returned by [Registry.Get] for unregistered keys. ErrUnknownFormat = errors.New("brackets: unknown format") // ErrInvalidOptions wraps every [*OptionError]. ErrInvalidOptions = errors.New("brackets: invalid options") // ErrParticipantCount wraps every [*CountError]. ErrParticipantCount = errors.New("brackets: unsupported number of participants") // ErrInvalidParticipant wraps every [*ParticipantError]. ErrInvalidParticipant = errors.New("brackets: invalid participant") // ErrMatchNotFound is returned when a match (or the target of a // winner/loser link) does not exist in the stage. ErrMatchNotFound = errors.New("brackets: match not found") // ErrInvalidStage is returned when a stage lacks the data a format needs // to advance it. ErrInvalidStage = errors.New("brackets: invalid stage") // ErrInvalidResult is returned by [Report] for results that cannot be // applied to the match. ErrInvalidResult = errors.New("brackets: invalid result") )
Sentinel errors. Every error returned by this package wraps one of them, so callers can branch with errors.Is and inspect details with errors.As.
Functions ¶
func DefaultSwissRounds ¶
DefaultSwissRounds returns the default number of Swiss rounds for n participants: ceil(log2(n)), and 1 for n <= 2.
func FillPlaceholders ¶
FillPlaceholders replaces the placeholders of st that reference source with the given qualifiers (qualifiers[0] fills "slot:<source>:1"), in participants, groups and match slots. It returns the number of slots filled.
func GroupName ¶
GroupName returns the conventional name of the i-th group (0-based): "A".."Z", then "G27", "G28", ...
func GroupStandings ¶
GroupStandings returns one standings table per group of the stage, in group order, ranked within the group.
func IsPlaceholder ¶
IsPlaceholder reports whether ref is a placeholder produced by Placeholder rather than a participant ID.
func NextPowerOfTwo ¶
NextPowerOfTwo returns the smallest power of two >= n (1 for n <= 1).
func Placeholder ¶
Placeholder returns the reference used for the rank-th qualifier (1-based) of the stage with the given key, for example "slot:groups:3".
func Qualifiers ¶
Qualifiers returns the IDs of the best n participants of a finished stage by its standings (see Standing); for matchpoint stages the champion comes first. n <= 0 returns every ranked participant.
func SeedOrder ¶
SeedOrder returns the seed expected in each first-round slot of a standard elimination bracket of the given power-of-two size. Adjacent slots meet in round 1, and the top seeds cannot meet before the rounds their seeding earns them: for 8 slots it returns [1 8 4 5 2 7 3 6] (quarterfinals 1v8, 4v5, 2v7, 3v6).
Types ¶
type Advance ¶
type Advance struct {
// Updates fill slots of existing matches (bracket progression).
Updates []SlotUpdate
// NewMatches are matches to append to the stage (next Swiss round, next
// matchpoint game).
NewMatches []Match
// Standings are the recomputed standings, for formats that keep them.
Standings []Standing
// NewByes are byes granted while scheduling NewMatches.
NewByes []Bye
// CurrentRound is the round the stage moved to (0 = unchanged).
CurrentRound int
// Scoreboard is the updated matchpoint scoreboard (nil = unchanged).
Scoreboard map[string]int
// Winner is set when the stage produced a winner.
Winner string
// Finished reports that every match of the stage is resolved (or, for
// formats that end on a specific match, that the deciding match ended).
Finished bool
}
Advance is the outcome of a finished match.
type Bracket ¶
type Bracket uint8
Bracket identifies the side of a double-elimination bracket a match belongs to. Formats without sides leave it as NoBracket.
type CountError ¶
CountError reports a participant count the format cannot schedule.
func (*CountError) Error ¶
func (e *CountError) Error() string
func (*CountError) Unwrap ¶
func (e *CountError) Unwrap() error
type CountRule ¶
type CountRule uint8
CountRule is the participant-count constraint a CountError violated.
const ( // AtLeast: fewer than Need participants. AtLeast CountRule = iota // PowerOfTwo: the format needs a power-of-two participant count. PowerOfTwo // MultipleOf: the count must be a multiple of Need (strict groups). MultipleOf // NoPlayableMatches: the configuration produced no playable match. NoPlayableMatches )
type DoubleElimination ¶
type DoubleElimination struct{}
DoubleElimination plans an upper/lower bracket in the style used by many esports main events: the top half of the seeds starts in the upper bracket, the bottom half starts in the lower bracket, and a participant is eliminated after a loss in the lower bracket.
The participant count must be a power of two and at least 4. With n participants the upper bracket has n/2 participants and log2(n/2) rounds; the lower bracket has 2*log2(n/2) rounds that alternate between "lower winners meet upper losers" (even rounds) and "lower winners meet each other" (odd rounds). Upper-bracket losers of upper round r drop into lower round 2r. The grand final has no game advantage for the upper bracket winner.
Series hints: upper matches and lower rounds 2+ are best-of-3, lower round 1 is best-of-1, the grand final is best-of-5.
Match IDs are "upper-r<round>-m<n>", "lower-r<round>-m<n>" and "grand-final". Match.Round is a schedule column: upper round r is column 2r-1, lower round r is column r, the grand final is column 2*upperRounds+1. The stage finishes when the grand final is reported.
func (DoubleElimination) Advance ¶
func (f DoubleElimination) Advance(st *Stage, finished *Match) (*Advance, error)
Advance sends the winner along WinnerTo and the loser along LoserTo. Targets are resolved by match ID, so the links stored on the matches are the source of truth.
func (DoubleElimination) Key ¶
func (DoubleElimination) Key() string
func (DoubleElimination) Moves ¶
func (DoubleElimination) Moves(finished *Match) Moves
Moves returns the outcome of a finished double-elimination match from the match alone: the winner follows WinnerTo, the loser (if any) follows LoserTo, and a decided grand final ends the stage. A match without a winner moves nobody. Callers that store matches themselves can resolve the target IDs in their own storage instead of building a Stage.
func (DoubleElimination) Plan ¶
func (DoubleElimination) Plan(participants []Participant, opts Options) ([]Stage, error)
func (DoubleElimination) Validate ¶
func (DoubleElimination) Validate(Options) error
type FFALeague ¶
type FFALeague struct{}
FFALeague plans a free-for-all lobby league: participants are split into groups (default 4) that play LobbyRounds (default 4) lobbies of MatchSize (default 4) participants, as in battle-royale or arena-shooter leagues. Results are reported per slot (Placement, Points or Score). The stage keeps standings and finishes, with the top of the standings as winner, once every lobby is resolved.
type Format ¶
type Format interface {
// Key is the stable identifier the format is registered under.
Key() string
// Validate checks options without planning anything.
Validate(opts Options) error
// Plan seeds the participants and returns the initial stages with their
// matches. Matches whose participants are not known yet (later bracket
// rounds, later Swiss rounds, stages fed by earlier stages) have empty
// or placeholder slots. Byes and phantom matches are resolved up front.
// The output depends only on the inputs: same participants and options,
// same stages, byte for byte.
Plan(participants []Participant, opts Options) ([]Stage, error)
// Advance is called after a match of stage finished (its Status, Winner
// and slot results already recorded in stage.Matches, and passed again
// as finished). It does not modify stage; it returns the effects: slots
// to fill, new matches to schedule, updated standings and whether the
// stage is complete. Use [Stage.Apply] to apply them, or [Report] to do
// all of it in one call.
Advance(stage *Stage, finished *Match) (*Advance, error)
}
Format is the strategy every competitive structure implements.
Formats are stateless and safe for concurrent use: all state lives in the Stage values they return, which the caller stores and hands back.
type GroupStage ¶
type GroupStage struct{}
GroupStage splits participants into groups and schedules them.
With MatchSize 2 (default) every group plays a round robin (circle method, Legs times, home/away swapped on even legs); match IDs are "g<group>-r<round>-m<number>". With MatchSize > 2 every group plays LobbyRounds free-for-all lobbies of MatchSize participants, rotating the group order by one position each lobby round; match IDs are "g<group>-r<round>-lobby<number>". Match numbers count across groups within a round.
Groups are filled with Options.Seeding (Snake by default) using ceil(n/GroupSize) groups, so the last groups may be smaller. With Options.Strict the participant count must be a multiple of GroupSize. Lobbies smaller than MatchSize are scheduled (and flagged Partial) unless partial lobbies are disabled; lobbies of fewer than 2 are never scheduled.
The stage finishes when every match is resolved. Advance also returns the overall standings; use GroupStandings for per-group tables.
func (GroupStage) Key ¶
func (GroupStage) Key() string
func (GroupStage) Plan ¶
func (GroupStage) Plan(participants []Participant, opts Options) ([]Stage, error)
func (GroupStage) Validate ¶
func (GroupStage) Validate(opts Options) error
type Link ¶
type Link struct {
Match string // target match ID
Slot int // 0 = home, 1 = away
// Index is a hint: the position of the target in Stage.Matches when the
// link was created. It is verified against Match before use, so a stale
// or zero hint only costs a lookup by ID.
Index int
}
Link points at a slot of another match: where the winner (or the loser) of a match goes next.
type LinkError ¶
type LinkError struct {
From string // finished match ID
Target string // target match ID, when known
Round int // target round, for formats that address matches by round/number
Number int
}
LinkError reports that the match a result should flow into is missing.
type Match ¶
type Match struct {
ID string
Round int // schedule column; 1-based
Number int // 1-based position within the round
Slots []Slot
// Winner is the winning participant ID. An empty winner on a finished
// head-to-head match is a draw.
Winner string
Status Status
// Bracket structure (elimination formats).
Bracket Bracket
Elimination bool // losing this match eliminates the loser
// Bye is set on matches resolved at planning time because one side was
// empty; Phantom on matches cancelled because both sides were empty.
Bye bool
Phantom bool
Partial bool // lobby with fewer participants than the configured size
BracketRound int // round inside Bracket (upper/lower are counted separately)
BestOf int // series length hint (1, 3, 5); 0 = unspecified
WinnerTo *Link
LoserTo *Link
// From lists the IDs of the matches whose results fill this match.
From []string
// Group structure (group stages, round robin, lobby leagues).
Group string // group name ("A", "B", ...); "" when not grouped
GroupIndex int
Leg int // 1-based leg for multi-leg round robins
LobbyRound int // 1-based lobby round for free-for-all lobbies
Size int // configured participants per match (lobby size)
}
Match is a single game or series between the participants in Slots.
Match IDs are deterministic and unique within a stage: the same input always yields the same IDs, so callers can use them as stable keys.
type Moves ¶
type Moves struct {
Winner, Loser string
LoserSlot int // index of the loser in the match slots, or -1
WinnerTo, LoserTo *Link // nil when that participant goes nowhere
Final bool // the match decided the stage
}
Moves describes where the participants of a finished match go next.
type MultiStage ¶
type MultiStage struct{}
MultiStage chains stages described by Options.Stages. Each stage either takes every participant or, when StageSpec.Source names an earlier stage, StageSpec.Take placeholders ("slot:<source>:<rank>", see Placeholder) to be replaced by that stage's qualifiers once it finishes (see FillPlaceholders and Qualifiers).
Stage types:
- "group_stage": a GroupStage (round robin groups or lobbies).
- "matchpoint": a single lobby of up to MatchSize participants (default all) that replays games until someone has won TargetPoints games (default 1). Each finished game without a champion schedules the next one ("<key>-matchpoint-<round>"); the first is "<key>-matchpoint".
Advance dispatches on Stage.Type; group stages finish when every match is resolved, matchpoint stages when the champion is decided.
func (MultiStage) Key ¶
func (MultiStage) Key() string
func (MultiStage) Plan ¶
func (MultiStage) Plan(participants []Participant, opts Options) ([]Stage, error)
func (MultiStage) Validate ¶
func (MultiStage) Validate(opts Options) error
type OptionError ¶
type OptionError struct {
Format string // format key
Field string // Options field (or "Stages[i].Field")
Reason string
}
OptionError reports an invalid Options field.
func (*OptionError) Error ¶
func (e *OptionError) Error() string
func (*OptionError) Unwrap ¶
func (e *OptionError) Unwrap() error
type Options ¶
type Options struct {
// Name labels the planned stage. Defaults to a per-format name.
Name string
// Rounds is the number of Swiss rounds. Default: ceil(log2(n)), 1 for
// two participants.
Rounds int
// Pairing selects the Swiss pairing algorithm. Default AvoidRematches.
Pairing Pairing
// Shuffle randomizes the order of Swiss participants whose records are
// identical before each round is paired (including round 1, which is
// otherwise paired 1v2, 3v4, ... by seed). Randomness comes from Rand,
// or from a PCG generator seeded with (Seed, round) when Rand is nil,
// so a persisted Seed reproduces every round exactly.
Shuffle bool
Seed uint64
Rand rand.Source
// Legs is how many times each pair meets in round robin and group
// stages (home and away swap on even legs). Default 1.
Legs int
// GroupSize is the number of participants per group. Default 4.
GroupSize int
// MatchSize is the number of participants per match: 2 for head-to-head
// round robin groups, more for free-for-all lobbies. Default 2 for
// group stages, 4 for FFA leagues, every participant for matchpoint.
MatchSize int
// LobbyRounds is the number of lobby rounds when MatchSize > 2.
// Default 1 for group stages, 4 for FFA leagues.
LobbyRounds int
// Seeding distributes participants into groups. Default Snake.
Seeding Seeding
// Strict requires the participant count to be a multiple of GroupSize.
Strict bool
// Partial controls partial lobbies. Default PartialAuto.
Partial PartialPolicy
// Advances and AdvancesPerGroup are informational qualification counts
// carried on the stage for callers that chain stages.
Advances int
AdvancesPerGroup int
// TargetPoints is the number of game wins a participant needs to take a
// matchpoint stage. Default 1.
TargetPoints int
// Stages describes the stages of a multi-stage format, in order.
Stages []StageSpec
}
Options configures a format. Every field is optional; zero values select the documented defaults and fields a format does not use are ignored.
type Pairing ¶
type Pairing uint8
Pairing selects the Swiss pairing algorithm.
const ( // AvoidRematches (default) pairs participants in standings order, each // with the highest-ranked opponent they have not met, and backtracks // when that greedy choice would force a rematch later in the round. // A rematch is only produced when no rematch-free pairing exists for the // round (or the search budget is exhausted), in which case the result is // the Greedy pairing. AvoidRematches Pairing = iota // Greedy pairs the best remaining participant with the best remaining // opponent they have not met yet, or with the next participant in the // standings if they have met everyone left. Never backtracks, so it can // produce avoidable rematches near the bottom of the standings. Greedy )
type PartialPolicy ¶
type PartialPolicy uint8
PartialPolicy controls whether lobbies with fewer participants than Options.MatchSize are scheduled.
const ( // PartialAuto allows partial lobbies unless Options.Strict is set. PartialAuto PartialPolicy = iota PartialAllow PartialDeny )
type Participant ¶
Participant is an entrant of a tournament: a player, a team, a roster.
Seed ranks the participant before play starts. Seeds greater than zero are ordered ascending (1 is the strongest); a Seed of zero or less means "unseeded" and those participants are placed after every seeded one, in the order they were given. Ties keep input order.
func SortParticipants ¶
func SortParticipants(ps []Participant) []Participant
SortParticipants returns a copy of ps in seed order: seeds > 0 ascending, then unseeded participants (Seed <= 0). The sort is stable, so equal seeds and unseeded participants keep their input order.
type ParticipantError ¶
type ParticipantError struct {
ID string
Reason string // "empty id", "duplicate id", "reserved placeholder prefix"
}
ParticipantError reports an invalid participant ID.
func (*ParticipantError) Error ¶
func (e *ParticipantError) Error() string
func (*ParticipantError) Unwrap ¶
func (e *ParticipantError) Unwrap() error
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps format keys to formats. The zero value is not usable; use NewRegistry or DefaultRegistry.
Example ¶
package main
import (
"fmt"
"github.com/Aquilabot/brackets"
)
func main() {
reg := brackets.DefaultRegistry()
f, err := reg.Get("double_elimination")
if err != nil {
panic(err)
}
stages, _ := f.Plan([]brackets.Participant{{ID: "a"}, {ID: "b"}, {ID: "c"}, {ID: "d"}}, brackets.Options{})
for _, m := range stages[0].Matches {
fmt.Println(m.ID, m.Bracket, "best of", m.BestOf)
}
fmt.Println(reg.Keys())
}
Output: upper-r1-m1 upper best of 3 lower-r1-m1 lower best of 1 lower-r2-m1 lower best of 3 grand-final grand_final best of 5 [double_elimination ffa_league group_stage multi_stage round_robin single_elimination swiss]
func DefaultRegistry ¶
func DefaultRegistry() *Registry
DefaultRegistry returns a new registry with every built-in format.
func NewRegistry ¶
NewRegistry returns a registry with the given formats. Later formats replace earlier ones with the same key.
type Result ¶
type Result struct {
// Winner is the winning participant ID. Leave it empty for a draw
// (head-to-head formats without elimination) or when lobby placements
// decide the result.
Winner string
// Scores, Points and Placements are per slot, in slot order. Any of
// them may be nil. A non-nil Points marks every slot as having points.
Scores []int
Points []int
Placements []int
}
Result is the outcome of a match, reported through Report.
type RoundRobin ¶
type RoundRobin struct{}
RoundRobin plans a single group in which every participant meets every other participant Legs times (home and away swap on even legs). With an odd count one participant rests each round. Match IDs are "gA-r<round>-m<n>". Standings are kept on the stage; when every match is resolved the stage finishes and the top of the standings is the winner.
func (RoundRobin) Key ¶
func (RoundRobin) Key() string
func (RoundRobin) Plan ¶
func (f RoundRobin) Plan(participants []Participant, opts Options) ([]Stage, error)
func (RoundRobin) Validate ¶
func (RoundRobin) Validate(opts Options) error
type SingleElimination ¶
type SingleElimination struct{}
SingleElimination plans one knockout bracket.
The bracket size is the next power of two. Participants are placed with the standard seed order (SeedOrder), so the empty slots (byes) always face the top seeds: with n participants and size S, seeds 1..S-n advance to round 2 without playing. Bye matches are Finished with Bye set at planning time and their winners are already placed in round 2.
Match IDs are "r<round>-m<number>". The stage finishes when every match is resolved; the winner of the final is reported as the stage winner.
func (SingleElimination) Advance ¶
func (SingleElimination) Advance(st *Stage, finished *Match) (*Advance, error)
func (SingleElimination) Key ¶
func (SingleElimination) Key() string
func (SingleElimination) Parent ¶
func (SingleElimination) Parent(round, number int) (nextRound, nextNumber, slot int)
Parent returns where the winner of match (round, number) plays next: the first match at (round+1, ceil(number/2)), home slot for odd numbers and away slot for even ones. Callers that store matches themselves can use it to advance a bracket without building a Stage.
func (SingleElimination) Plan ¶
func (SingleElimination) Plan(participants []Participant, opts Options) ([]Stage, error)
func (SingleElimination) Validate ¶
func (SingleElimination) Validate(Options) error
type Slot ¶
type Slot struct {
// ID is the participant occupying the slot, or "" while unresolved.
ID string
// Placeholder is set instead of ID when the seat will be filled by a
// qualifier from an earlier stage.
Placeholder string
// Score is the raw score of the slot: maps/goals/rounds in head-to-head
// matches, an arbitrary score in lobbies.
Score int
// Points are ranking points earned in a lobby. When HasPoints is set,
// standings use Points instead of Score as "points for".
Points int
HasPoints bool
// Placement is the 1-based finishing position in a lobby (0 = not
// reported). Placement 1 counts as a win, any other placement as a loss.
Placement int
}
Slot is one seat in a match. Head-to-head matches have exactly two slots (index 0 is "home", index 1 is "away"); free-for-all lobbies have one slot per participant.
type SlotUpdate ¶
type SlotUpdate struct {
Index int // index of the match in Stage.Matches
Match string // ID of the match
Slot int // 0 = home, 1 = away
Participant string
}
SlotUpdate places a participant into a slot of an existing match.
type Stage ¶
type Stage struct {
Key string // stage key inside a multi-stage plan; "" otherwise
Name string
Format string // key of the format that planned the stage
Type string // structural kind: "bracket", "double_elimination", "swiss", "round_robin", "group", "ffa_league", "matchpoint"
Order int // 0-based position inside the plan
Options Options
Participants []string // participant IDs (or placeholders) in seed order
Groups []Group
Matches []Match
Rounds int // total rounds when known up front (0 = derive from matches)
CurrentRound int
Size int // bracket size (elimination formats)
SeedOrder []int // seed expected in each first-round slot (elimination formats)
UpperSize int // double elimination: participants starting in the upper bracket
UpperRounds int
LowerRounds int
// Source and SourceCount are set when the stage is fed by the
// SourceCount best participants of an earlier stage.
Source string
SourceCount int
Byes []Bye
Standings []Standing
Scoreboard map[string]int // matchpoint: games won per participant
Winner string
Finished bool
}
Stage is one phase of a tournament: its configuration, its participants and its matches. A Stage is plain data; persist it however you like and pass it back to Format.Advance when a match finishes.
func (*Stage) Apply ¶
Apply applies the outcome of Format.Advance to the stage: fills slots, appends new matches, records byes, standings, scoreboard and winner.
type StageError ¶
StageError wraps an error raised while planning one stage of a multi-stage format.
func (*StageError) Error ¶
func (e *StageError) Error() string
func (*StageError) Unwrap ¶
func (e *StageError) Unwrap() error
type StageSpec ¶
type StageSpec struct {
// Key identifies the stage; later stages reference it in Source.
Key string
// Name labels the stage. Defaults to Options.Name, then Key.
Name string
// Type is "group_stage" or "matchpoint".
Type string
// Source is the key of an earlier stage whose top Take participants
// fill this stage through placeholders. Empty means "every participant".
Source string
Take int
// Options configures the stage (group sizes, lobby rounds, target
// points, ...).
Options Options
}
StageSpec describes one stage of a MultiStage format.
type Standing ¶
type Standing struct {
Rank int
ID string
Played int
Wins int
Losses int
Draws int
Byes int
PointsFor int
PointsAgainst int
Order int
}
Standing is one row of a standings table.
Ranking (tie-breakers in order):
- Wins, descending. A bye counts as a win.
- Losses, ascending.
- Score difference (PointsFor - PointsAgainst), descending.
- PointsFor, descending.
- Order, ascending: the participant's position in the stage's seed order (participants first seen in match results come after, in order of appearance). This makes the table total and deterministic.
Only Finished matches count. In head-to-head matches (two slots) a finished match with an empty Winner is a draw for both sides, PointsFor is the slot's own Score and PointsAgainst the opponent's Score. In lobbies (any other slot count) PointsFor adds the slot's Points (or Score when no points were reported), Placement 1 is a win and any other reported placement a loss; without a placement the match Winner decides.
type Status ¶
type Status uint8
Status is the lifecycle state of a match.
const ( // Pending matches have not started. Unresolved slots may still be empty. Pending Status = iota // Live matches are being played. Live // Finished matches have a result (a winner, or a draw when Winner is ""). Finished // Cancelled matches will never be played (for example a phantom bracket // match whose two feeders were both byes). Cancelled )
type Swiss ¶
type Swiss struct{}
Swiss plans a Swiss-system stage: a fixed number of rounds in which participants with similar records meet, without eliminations.
Round 1 pairs adjacent seeds (1v2, 3v4, ...) unless Options.Shuffle is set. Every later round is generated when all matches of the current round are resolved: participants are ordered by the current standings (see Standing) and paired with Options.Pairing (AvoidRematches by default).
Byes: with an odd number of participants the lowest-ranked participant that has not had a bye yet sits out and is credited a win (if everyone already had one, the lowest-ranked participant gets another).
Match IDs are "swiss-r<round>-m<number>". The stage finishes when the last round is resolved, or earlier if no pairing is possible; the winner is the top of the standings.
Example ¶
Swiss rounds are generated one at a time, as soon as the previous round is complete.
package main
import (
"fmt"
"github.com/Aquilabot/brackets"
)
func main() {
f := brackets.Swiss{}
stages, err := f.Plan([]brackets.Participant{
{ID: "ana", Seed: 1}, {ID: "ben", Seed: 2}, {ID: "cleo", Seed: 3},
{ID: "dan", Seed: 4}, {ID: "eve", Seed: 5},
}, brackets.Options{Rounds: 3})
if err != nil {
panic(err)
}
st := &stages[0]
for i := 0; i < len(st.Matches); i++ { // new rounds are appended
m := &st.Matches[i]
if _, err := brackets.ReportAt(f, st, i, brackets.Result{Winner: m.Away()}); err != nil {
panic(err)
}
}
for _, b := range st.Byes {
fmt.Printf("round %d bye: %s\n", b.Round, b.Participant)
}
for _, s := range st.Standings[:3] {
fmt.Printf("%d. %s %d-%d\n", s.Rank, s.ID, s.Wins, s.Losses)
}
}
Output: round 1 bye: eve round 2 bye: cleo round 3 bye: ben 1. ana 2-1 2. ben 2-1 3. dan 2-1
func (Swiss) AdvanceTally ¶
AdvanceTally is Swiss.Advance for callers that keep matches in their own storage: the stage's matches are read from t (fed with every match of the stage) instead of st.Matches. st supplies the configuration: Participants, Rounds, CurrentRound and Options. AdvanceTally finalizes t.
type Tally ¶
type Tally struct {
// contains filtered or unexported fields
}
Tally accumulates a stage's matches one at a time: standings, the pairs that already met, and round completion. It lets callers that keep matches in their own storage advance a stage without first copying every match into Stage.Matches: build the tally, feed each stored match through a reused Match value with Add, then read Standings or call Swiss.AdvanceTally.
The result is identical to the stage-based functions over the same matches in the same order (Standings, Swiss.Advance).
func NewTally ¶
NewTally starts a tally over the stage's participants (in seed order) and the byes granted so far.
func (*Tally) Add ¶
Add records one match. The tally keeps no reference to m or its slots, so callers can reuse the same Match value for every call.
func (*Tally) AllResolved ¶
AllResolved reports whether no added match is pending or live.
func (*Tally) Grow ¶
Grow reserves room for n more matches. It is optional and only saves reallocations when the number of matches is known up front.
func (*Tally) RoundResolved ¶
RoundResolved reports whether round r has matches and all are resolved.