Documentation
¶
Overview ¶
Package keizer implements Keizer point scoring for chess tournaments.
In Keizer scoring, each player is assigned a value number based on their current rank. When you win against an opponent, you receive points equal to their value number. Draws award a fraction of the opponent's value.
This creates a self-reinforcing system: beating strong players (high value numbers) earns more points, which raises your rank, which increases your own value number. Absent players receive a penalty fraction of their own value number.
Scores are computed using ×2 integer arithmetic internally. Each game or absence contributes an integer "doubled" score. The final output divides by 2, giving scores rounded to the nearest 0.5. This eliminates float precision drift while preserving half-point granularity.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Options ¶
type Options struct {
// ValueNumberBase is the top-ranked player's value number.
// Default: player count (N).
ValueNumberBase *int `json:"valueNumberBase,omitempty"`
// ValueNumberStep is the decrement per rank position.
// Player at rank r gets: ValueNumberBase - (r-1) * ValueNumberStep.
// Default: 1.
ValueNumberStep *int `json:"valueNumberStep,omitempty"`
// WinFraction is the multiplier applied to the opponent's value number
// for a win. Points for win = opponent_value × WinFraction.
// Default: 1.0.
WinFraction *float64 `json:"winFraction,omitempty"`
// DrawFraction is the multiplier applied to the opponent's value number
// for a draw. Points for draw = opponent_value × DrawFraction.
// Default: 0.5.
DrawFraction *float64 `json:"drawFraction,omitempty"`
// LossFraction is the multiplier applied to the opponent's value number
// for a loss. Points for loss = opponent_value × LossFraction.
// Default: 0.0 (KeizerForClubs). Set to 1/6 for FreeKeizer toughness bonus.
LossFraction *float64 `json:"lossFraction,omitempty"`
// ForfeitWinFraction is the multiplier applied to the opponent's value number
// when winning by forfeit. Default: 1.0.
ForfeitWinFraction *float64 `json:"forfeitWinFraction,omitempty"`
// ForfeitLossFraction is the multiplier applied to the opponent's value number
// when losing by forfeit. Default: 0.0.
ForfeitLossFraction *float64 `json:"forfeitLossFraction,omitempty"`
// DoubleForfeitFraction is the multiplier applied to the opponent's value number
// when both players forfeit. Applied to each player. Default: 0.0.
DoubleForfeitFraction *float64 `json:"doubleForfeitFraction,omitempty"`
// ByeValueFraction is the fraction of own value number awarded for a
// pairing-allocated bye (PAB). Default: 0.50 (KeizerForClubs).
ByeValueFraction *float64 `json:"byeValueFraction,omitempty"`
// HalfByeFraction is the fraction of own value number awarded for a
// half-point bye. Default: 0.50.
HalfByeFraction *float64 `json:"halfByeFraction,omitempty"`
// ZeroByeFraction is the fraction of own value number awarded for a
// zero-point bye. Default: 0.0.
ZeroByeFraction *float64 `json:"zeroByeFraction,omitempty"`
// AbsentPenaltyFraction is the fraction of own value number awarded
// when a player is absent (unexcused). Default: 0.35 (KeizerForClubs).
AbsentPenaltyFraction *float64 `json:"absentPenaltyFraction,omitempty"`
// ExcusedAbsentFraction is the fraction of own value number awarded
// when a player has an excused absence. Default: 0.35.
ExcusedAbsentFraction *float64 `json:"excusedAbsentFraction,omitempty"`
// ClubCommitmentFraction is the fraction of own value number awarded
// when a player is absent for interclub team duty.
// Default: 0.70 (KeizerForClubs). Club commitments are exempt from
// absence limits and decay.
ClubCommitmentFraction *float64 `json:"clubCommitmentFraction,omitempty"`
// ByeFixedValue overrides ByeValueFraction with a fixed PAB bye score.
ByeFixedValue *int `json:"byeFixedValue,omitempty"`
// HalfByeFixedValue overrides HalfByeFraction with a fixed half-bye score.
HalfByeFixedValue *int `json:"halfByeFixedValue,omitempty"`
// ZeroByeFixedValue overrides ZeroByeFraction with a fixed zero-bye score.
ZeroByeFixedValue *int `json:"zeroByeFixedValue,omitempty"`
// AbsentFixedValue overrides AbsentPenaltyFraction with a fixed absence score.
AbsentFixedValue *int `json:"absentFixedValue,omitempty"`
// ExcusedAbsentFixedValue overrides ExcusedAbsentFraction with a fixed score.
ExcusedAbsentFixedValue *int `json:"excusedAbsentFixedValue,omitempty"`
// ClubCommitmentFixedValue overrides ClubCommitmentFraction with a fixed score.
ClubCommitmentFixedValue *int `json:"clubCommitmentFixedValue,omitempty"`
// SelfVictory controls whether each player's own Keizer value is added
// to their total (once, not per round). This is standard in every known
// Keizer implementation. Default: true.
SelfVictory *bool `json:"selfVictory,omitempty"`
// AbsenceLimit is the maximum number of absences that score points.
// Absences beyond this limit score 0. Club commitments are exempt.
// 0 means unlimited. Default: 5.
AbsenceLimit *int `json:"absenceLimit,omitempty"`
// AbsenceDecay halves the absence bonus for each successive absence:
// 1st absence = full fraction, 2nd = fraction/2, 3rd = fraction/4, etc.
// Club commitments are exempt. Default: false.
AbsenceDecay *bool `json:"absenceDecay,omitempty"`
// Frozen disables the iterative convergence loop. Instead of rescoring
// all rounds with the final ranking's value numbers, each round is scored
// once using the ranking as it stood before that round. Points from
// earlier rounds are never retroactively recalculated.
// Default: false (standard iterative Keizer).
Frozen *bool `json:"frozen,omitempty"`
// LateJoinHandicap is the fixed score awarded per round missed before
// a player joined the tournament. Unlike absences (which use a fraction
// of the player's own value or AbsentFixedValue), late-join rounds use
// this value directly. Late-join rounds do not count toward AbsenceLimit
// or AbsenceDecay.
// Requires PlayerEntry.JoinedRound to be set (0 or 1 = original player).
// Default: 0 (late-join rounds score nothing).
LateJoinHandicap *float64 `json:"lateJoinHandicap,omitempty"`
}
Options holds configurable settings for Keizer point scoring. All fields are pointers to distinguish "not set" (nil = use default) from "explicitly set to zero."
Defaults follow KeizerForClubs conventions (most widely used software).
Variant presets ¶
KeizerForClubs (default): all nil — use defaults.
Classic KNSB sixths: WinFraction=1, DrawFraction=0.5, LossFraction=0, ByeValueFraction=4/6, AbsentPenaltyFraction=2/6, ClubCommitmentFraction=2/3, ExcusedAbsentFraction=2/6, AbsenceLimit=5.
FreeKeizer: LossFraction=1/6, ByeValueFraction=4/6, AbsentPenaltyFraction=2/6, AbsenceLimit=5.
No self-victory: SelfVictory=false.
Fixed absences: AbsentFixedValue=15, ExcusedAbsentFixedValue=15, ClubCommitmentFixedValue=25.
Decaying absences: AbsenceDecay=true, AbsenceLimit=0.
func ParseOptions ¶
ParseOptions converts a map[string]any (from Firestore/JSON) into typed Options. Unrecognized keys are ignored. Type mismatches use defaults.
func (Options) ValueNumber ¶
ValueNumber calculates the value number for a player at the given rank. Rank is 1-based (rank 1 = strongest player).
func (Options) WithDefaults ¶
WithDefaults returns a copy of Options with all nil fields filled in with system defaults. playerCount is the number of active players in the tournament.
type Scorer ¶
type Scorer struct {
// contains filtered or unexported fields
}
Scorer implements the chesspairing.Scorer interface for Keizer scoring.
func New ¶
New creates a new Keizer scorer with the given options. Pass nil or empty Options to use all defaults.
func NewFromMap ¶
NewFromMap creates a new Keizer scorer from a map[string]any config.
func (*Scorer) PointsForResult ¶
func (s *Scorer) PointsForResult(result chesspairing.GameResult, rctx chesspairing.ResultContext) float64
PointsForResult returns the points awarded for a specific game result in Keizer scoring. This uses the ResultContext to access opponent/player value numbers. The result is rounded to 0.5 precision via ×2 arithmetic.
func (*Scorer) Score ¶
func (s *Scorer) Score(_ context.Context, state *chesspairing.TournamentState) ([]chesspairing.PlayerScore, error)
Score calculates Keizer scores for all active players.
The algorithm: 1. Build initial ranking from ratings (or previous scores if rounds exist). 2. For each round, calculate points earned by each player. 3. Re-rank players by total Keizer points after each round. 4. Value numbers update each round based on current rankings.
This iterative approach is important: value numbers change as rankings change, and all rounds use the final ranking's value numbers to compute the final scores. (Some Keizer variants recalculate retroactively; this implementation uses the standard approach where all rounds are scored using the final ranking.)
When SelfVictory is enabled (default), each player's own Keizer value is added to their total once (not per round). This is standard in every known Keizer implementation.