dubov

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

pairing/dubov/dubov.go

Package dubov implements the Dubov Swiss pairing system (C.04.4.1).

The Dubov system is an ARO-equalization Swiss variant that splits score groups by colour preference (G1=White-seekers, G2=Black-seekers), sorts G1 by ascending ARO, and uses transposition-based matching. It has 10 criteria (C1-C10) and tracks MaxT upfloater limits.

Key differences from Dutch:

  • No S1/S2 half-split; instead G1/G2 by colour preference
  • S1 (=G1) sorted by ascending ARO, not descending score then TPN
  • Transposition-only matching (no exchanges)
  • MaxT upfloater limit = 2 + floor(Rnds/5)
  • 10 criteria (C1-C10), not 13 (C8-C21)
  • Distinct 5-rule colour allocation
  • Distinct bye selection (Art. 2.3)

Index

Constants

View Source
const (
	IdxC4  = 0 // upfloater count
	IdxC5  = 1 // upfloater score sum (negated: higher = better -> stored as deficit)
	IdxC6  = 2 // colour preference violations
	IdxC7  = 3 // MaxT upfloater violations
	IdxC8  = 4 // consecutive-round upfloaters
	IdxC9  = 5 // MaxT upfloater-opponent violations
	IdxC10 = 6 // consecutive-round MaxT violations
)

Violation index constants for DubovCandidateScore.Violations.

View Source
const NumDubovViolations = 7

NumDubovViolations is the number of quality criteria (C4-C10).

Variables

View Source
var ErrNoPairingPossible = errors.New("no valid pairing exists for the remaining players")

ErrNoPairingPossible is returned when no valid pairing can be found.

View Source
var ErrTooFewPlayers = errors.New("dubov pairing requires at least 1 active player")

ErrTooFewPlayers is returned when there aren't enough active players.

Functions

func AllocateColor

func AllocateColor(a, b *swisslib.PlayerState, boardNumber int, topSeedColor *swisslib.Color) (string, string)

AllocateColor decides which player gets White and which gets Black for a Dubov pairing. Returns (whiteID, blackID).

Per Art. 5, the 5 rules are equivalent to swisslib.AllocateColor with topScorerRules=false. Dubov does not have topscorer-specific colour rules.

func BalanceG1G2

func BalanceG1G2(g1, g2 []*swisslib.PlayerState, ratings map[string]int) ([]*swisslib.PlayerState, []*swisslib.PlayerState)

BalanceG1G2 ensures |G1| = floor(total/2) and |G2| = ceil(total/2) by shifting players between groups per Art. 3.2.4. Shifted players are chosen per Art. 4.3 (shifter sorting).

func BuildRatingMap

func BuildRatingMap(players []swisslib.PlayerState) map[string]int

BuildRatingMap creates a player ID -> rating lookup from a PlayerState slice.

func C1NoRematches

func C1NoRematches(a, b *swisslib.PlayerState) bool

C1NoRematches returns true if the two players have NOT already played each other.

func C3NoAbsoluteColorConflict

func C3NoAbsoluteColorConflict(a, b *swisslib.PlayerState) bool

C3NoAbsoluteColorConflict returns true if the two players do NOT both have the same absolute colour preference. If either player has no absolute preference, there is no conflict.

func ComputeARO

func ComputeARO(player *swisslib.PlayerState, ratings map[string]int) float64

ComputeARO returns the Average Rating of Opponents for a player. Per Dubov Art. 1.7: arithmetic mean of the ratings of all opponents the player has played. Returns 0 if the player has no opponents.

The opponents list comes from PlayerState.Opponents, which already excludes forfeits (see swisslib.BuildPlayerStates).

func CriterionC4

func CriterionC4(floaters []*swisslib.PlayerState) int

CriterionC4 returns the number of upfloaters in the candidate.

func CriterionC5

func CriterionC5(floaters []*swisslib.PlayerState) float64

CriterionC5 returns the sum of upfloater scores. Higher is better, so callers should compare reversed.

func CriterionC6

func CriterionC6(pairs []proposedPairing) int

CriterionC6 returns the number of players not receiving their colour preference.

func CriterionC7

func CriterionC7(floaters []*swisslib.PlayerState, maxT int) int

CriterionC7 returns the number of upfloaters who have reached or exceeded MaxT.

func CriterionC8

func CriterionC8(floaters []*swisslib.PlayerState) int

CriterionC8 returns the number of upfloaters who also upfloated in the previous round.

func CriterionC9

func CriterionC9(pairs []proposedPairing, floaterIDs map[string]bool, maxT int) int

CriterionC9 returns the number of upfloater opponents who are at/above MaxT.

func CriterionC10

func CriterionC10(floaters []*swisslib.PlayerState, maxT int) int

CriterionC10 returns the number of consecutive-round upfloaters at/above MaxT.

func MatchBracketDubov

func MatchBracketDubov(bracket swisslib.Bracket, ctx *matchContext) (*bracketResult, error)

MatchBracketDubov attempts to pair all players in a bracket using the Dubov algorithm.

Algorithm:

  1. Split into G1 (white-seekers) and G2 (black-seekers/neutral)
  2. Balance G1/G2 so |G1| = floor(n/2)
  3. Sort G1 by ascending ARO (Art. 3.2.5)
  4. Try sequential pairing with each G2 transposition
  5. Pick the best candidate per criteria scoring
  6. Unmatched players become floaters

func MaxT

func MaxT(completedRounds int) int

MaxT returns the maximum number of times a player may be upfloated. Per Dubov Art. 1.8: MaxT = 2 + floor(Rnds/5).

func SatisfiesAbsoluteCriteria

func SatisfiesAbsoluteCriteria(a, b *swisslib.PlayerState, forbidden map[[2]string]bool) bool

SatisfiesAbsoluteCriteria checks C1, C3, and forbidden pairs for a proposed pair.

func SortByAROAscending

func SortByAROAscending(players []*swisslib.PlayerState, ratings map[string]int)

SortByAROAscending sorts players by ascending ARO, breaking ties by ascending TPN (Art. 3.2.5). This is the G1/S1 sorting rule.

func SplitG1G2

func SplitG1G2(players []*swisslib.PlayerState, isRound1 bool) (g1, g2 []*swisslib.PlayerState)

SplitG1G2 splits a score group into G1 (White-preference) and G2 (Black-preference/neutral).

Round 1 (isRound1=true): G1 = first floor(n/2) players by TPN, G2 = rest. Later rounds: G1 = players preferring White, G2 = players preferring Black or no preference.

func UpfloatCount

func UpfloatCount(p *swisslib.PlayerState) int

UpfloatCount returns how many times a player has been upfloated across all completed rounds.

Types

type DubovByeSelector

type DubovByeSelector struct{}

DubovByeSelector selects the bye player per Dubov system rules (Art. 2.3): lowest score -> most games played -> highest TPN (lowest-ranked).

func (DubovByeSelector) SelectBye

func (s DubovByeSelector) SelectBye(players []*swisslib.PlayerState) *swisslib.PlayerState

SelectBye returns the player to receive the PAB, or nil if all have already received one.

type DubovCandidateScore

type DubovCandidateScore struct {
	UpfloaterCount    int                     // C4: number of upfloaters (lower = better)
	UpfloaterScoreSum float64                 // C5: sum of upfloater scores (higher = better)
	Violations        [NumDubovViolations]int // C4-C10 violation counts
	TranspositionIdx  int                     // Transposition sequence number (lower = better)
}

DubovCandidateScore holds the quality evaluation of a complete bracket pairing.

func (*DubovCandidateScore) Compare

func (s *DubovCandidateScore) Compare(other *DubovCandidateScore) int

Compare returns -1 if s is better than other, +1 if worse, 0 if equal. Comparison order per FIDE C.04.4.1:

  1. C4: fewer upfloaters is better
  2. C5: higher upfloater score sum is better
  3. C6-C10: fewer violations is better (lexicographic)
  4. Lower transposition index is better

func (*DubovCandidateScore) IsPerfect

func (s *DubovCandidateScore) IsPerfect() bool

IsPerfect returns true if there are no upfloaters and no violations.

type Options

type Options struct {
	// TopSeedColor forces the top seed's color in round 1.
	// Values: "auto" (default), "white", "black".
	TopSeedColor *string `json:"topSeedColor,omitempty"`

	// ForbiddenPairs lists player ID pairs that must not be paired together.
	ForbiddenPairs [][]string `json:"forbiddenPairs,omitempty"`

	// TotalRounds is the planned number of rounds in the tournament.
	// If nil, derived from state.
	TotalRounds *int `json:"totalRounds,omitempty"`
}

Options holds Dubov-specific pairing configuration. All pointer fields use nil = use default.

func ParseOptions

func ParseOptions(m map[string]any) Options

ParseOptions converts a generic map[string]any into typed Options.

func (Options) WithDefaults

func (o Options) WithDefaults() Options

WithDefaults returns a copy of options with defaults applied for nil fields.

type Pairer

type Pairer struct {
	// contains filtered or unexported fields
}

Pairer implements the chesspairing.Pairer interface for the Dubov Swiss system.

func New

func New(opts Options) *Pairer

New creates a new Dubov pairer with the given options.

func NewFromMap

func NewFromMap(m map[string]any) *Pairer

NewFromMap creates a new Dubov pairer from a generic options map.

func (*Pairer) Pair

Pair generates pairings for the next round using the Dubov system (C.04.4.1).

Algorithm:

  1. Build PlayerState for all active players
  2. Handle single-player edge case (bye only)
  3. PAB bye selection via DubovByeSelector if odd count
  4. Build score groups and brackets
  5. Match each bracket using Dubov G1/G2 algorithm
  6. Allocate colours per Art. 5
  7. Board ordering and final assembly

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL