Help us shape the future of Go! Take the 2026 Go Developer Survey

tournoi

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 9 Imported by: 4

README

tournoi — moteur de tournoi de backgammon (Go)

Bibliothèque Go sans dépendance, à intégrer dans un logiciel web dans le même processus. Journal d'événements, état reconstruit par Replay, propositions d'actions pour le directeur de tournoi, aucun départage. Exemple d'intégration dans doc.go. Les choix de conception viennent d'une étude par simulation des formats résumée dans docs/etude_formats.md ; le travail restant est dans docs/RESTE_A_FAIRE.md. Pour une présentation accessible du fonctionnement et des décisions de conception — utilisable telle quelle pour répondre aux questions des joueurs et des organisateurs — voir docs/comprendre_le_moteur.md. La spécification complète, assez détaillée pour reconstruire le moteur à partir d'elle seule, est dans docs/specification.md. Les deux existent aussi en PDF à côté.

Documentation en ligne, en neuf langues : https://pileofcells.github.io/backgammon-tournoi/

go get github.com/PileOfCells/backgammon-tournoi

Formats

Kind Description
swiss_lives Suisse à L vies, appariement dans le groupe de même nombre de défaites, continu (dès que deux joueurs sont libres) ou par rondes ; option batch_minutes : micro-rondes, les joueurs libres attendent l'échéance et partent par lots ; option target : arrêt quand la somme des vies vaut une puissance de 2 pour basculer sur un tableau
lives_bracket Tableau à élimination simple où un joueur à 2 vies est exempt du premier tour
gsl Blocs de groupes GSL de 4 (3, 2) pour les joueurs à 0 défaite, mini-tableaux pour ceux à 1 défaite ; option target
bracket Élimination simple ; consolation (consolante progressive), last_chance, reconciliation + recharge (double élimination vraie)
round_robin Poules (table de Berger), qualifiers par poule, égalités réglées par barrage à 2 vies

Les phases s'enchaînent (entry : survivors avec leurs vies, all, top:N).

Le tirage d'un tableau est intégralement aléatoire par défaut — c'est la conclusion de l'étude, pas un oubli. seeding: "rating" sur une phase de tableau active le placement classique par cote (1 contre 16, 2 contre 15…) pour l'organisateur qui le veut.

Longueurs de match : length partout, final_length pour le dernier tour, lengths pour une grille par tour lue du dernier tour vers le premier ([15,13,11,9]), length_late + late_threshold pour allonger la fin d'un suisse.

Fichiers

  • types.go, events.go, state.go, engine.go : modèle, journal, état, propositions.
  • codes.go, fr.go : les libellés, notes, avertissements et raisons sont des codes ; fr.go en donne le rendu français. Un hôte multilingue traduit les codes lui-même.
  • graph.go : graphes de matchs (tableaux, GSL, poules, consolantes) remplis par les résultats.
  • phase_swiss.go, phase_bracket.go, phase_gsl.go, phase_rr.go : les formats.
  • standings.go (rangs, prix partagés, CSV par section), prizes.go (droit d'entrée, retenue, barème par section), clock.go (durées, matchs lents), tables.go (tables indisponibles, réservées).
  • retardataire.go : places d'exemption libres (FreeSlots) et entrée d'un joueur arrivé après le tirage, sans jamais refaire le tirage.
  • reparation.go : après une correction, Propose propose l'annulation en série des matchs devenus incohérents (cancel_match) ; rien n'est appliqué d'office.
  • horaires.go : micro-rondes (batch_minutes, échéance dérivée du journal) et pauses programmées (Config.Breaks, avertissement sans blocage).
  • seeding.go : têtes de série en option (seeding: "rating"), éteintes par défaut.
  • reconfig.go : configuration modifiable en cours (ConfigChangedEvent, la configuration entière) et réouverture d'un tournoi clos (ReopenedEvent).
  • render/ : rendu traduisible (un Labeler injecté rend les codes ET les mots du rendu), feuille de style et crédit injectés. Arbres SVG de toutes les sections d'une phase côte à côte avec les descentes, grille des tables, tableau des vies (adversaires rencontrés, attente), matchs en cours, classement, feuille d'appariements imprimable, page autonome (un fichier, CSS embarqué, aucune ressource externe). Fichiers témoins dans render/testdata/.
  • sim/ : simulation (tests d'invariants, comparaison avec le simulateur de l'étude, prévision de fin).
  • players/ : import et export CSV d'une liste de joueurs (l'annuaire de l'hôte fait l'aller-retour).
  • cmd/tournoi-demo : démonstration (go run ./cmd/tournoi-demo -format suisse_tableau -joueurs 32 -etapes -sortie demo, -rejouer demo/journal.json).
  • cmd/tournoi-td : console interactive pour diriger un tournoi à la main (voir « Tester à la main »).
  • exemples/ : tournois simulés rendus à quatre stades pour sept formats ; ouvrir exemples/index.html. Régénérer avec scripts/exemples.sh.
  • site/ : le site publié (Sphinx + gettext, neuf langues). Voir site/README.md pour le construire et pour ce qui reste à traduire.

Tests

go test -short ./...             # la boucle de travail : matrice réduite, sous trois secondes
go test ./...                    # tout : invariants sur tous les formats × 8 effectifs × 8 graines, rejeu, corrections, parité
go test -run TestParity -v ./    # P(meilleur gagne), matchs et durées sur 64 joueurs (long)
go test -run '^$' -fuzz FuzzApply -fuzztime 60s ./   # fuzzing d'Apply (aussi en intégration continue)

Tester à la main

go run ./cmd/tournoi-td -journal montournoi.json -format suisse_tableau

Dans la console : ajoute Alice Paris 4.2 (ou import joueurs.csv), propose, ok tous, resultat M1 alice 7 3, corrige M1 bob, forfait chloe, configure nouvelle.json, rouvre, exporte joueurs.csv, matchs, vies, classement, simule (joue au hasard les matchs en cours), quitte. Le journal est écrit après chaque commande ; relancer la même commande reprend le tournoi. La page affichage.html à côté du journal est régénérée à chaque commande : ouvrez-la dans un navigateur.

Documentation

Overview

Package tournoi — moteur de tournoi de backgammon fondé sur un journal d'événements.

Intégration dans un logiciel hôte (Go, même processus)

L'hôte conserve le journal (tournoi.Journal, un tableau JSON) et l'état courant peut être reconstruit à tout moment par Replay. Boucle type côté serveur :

cfg := tournoi.Config{Name: "Open", Phases: []tournoi.PhaseConfig{
    {Kind: tournoi.KindSwissLives, Length: 7, Target: 16},          // suisse 2 vies continu, bascule à Σvies = 16
    {Kind: tournoi.KindLivesBracket, Length: 9, FinalLength: 11},    // tableau à exemptions
}}
st, created, _ := tournoi.New(cfg, seed, time.Now())
journal := tournoi.Journal{created}
// inscriptions
ev := tournoi.Event{Kind: tournoi.EvPlayerAdded, Time: time.Now(), Player: &p}
st.Apply(ev); journal = append(journal, ev)
// boucle du TD
for _, a := range st.Propose() {            // « lancer P3 contre P8 en 7 points, table 4 », etc.
    ev, _ := st.EventFromAction(a, time.Now()) // le TD confirme
    st.Apply(ev); journal = append(journal, ev)
}
// résultat saisi par le TD
ev = tournoi.ResultEvent("M12", "P3", 7, 4, time.Now())
st.Apply(ev); journal = append(journal, ev)

Corrections : ajouter un événement EvResultCorrected (jamais modifier le journal) ; l'état est recalculé et State.Warnings signale ce qui n'est plus cohérent (par exemple un match de tableau joué par le mauvais joueur). Forfaits : EvPlayerWithdrawn. Reprise après panne : Replay(journal).

Affichage : le paquet render fournit les composants HTML/SVG (arbres, tableau des vies, matchs en cours, classement, page complète). Prévision de fin : sim.Forecast(journal, now, K, …). Import de joueurs : players.FromCSV.

Règles pures : le paquet rules porte la dotation, la prévision de fin (temps de jeu, jour, fin après les pauses) et la feuille d'appariements en fonctions sans état ni E/S ; les méthodes de State, sim et render qui les appliquent en sont des enveloppes. Un hôte qui tient son propre état les appelle directement ; State.RulesMatches et Config.BreakIntervals en donnent les entrées depuis un State.

Déterminisme : Propose dépend seulement du journal (graine + nombre d'événements) ; le contenu des tirages est écrit dans les événements EvDraw, donc rejouer un journal ne dépend pas de l'algorithme d'appariement.

Package tournoi est un moteur de tournoi de backgammon fondé sur un journal d'événements.

L'hôte (le logiciel web) conserve la liste ordonnée des événements ; Replay reconstruit l'état ; Propose indique au directeur de tournoi (TD) quoi faire maintenant ; chaque décision du TD devient un événement. Aucun départage : les formats reposent sur les vies, les tableaux et les poules, avec barrages si nécessaire.

Index

Constants

View Source
const (
	KindSwissLives   = "swiss_lives"   // suisse à vies (continu ou par rondes)
	KindLivesBracket = "lives_bracket" // tableau à exemptions : 2 vies = exempt du 1er tour
	KindGSL          = "gsl"           // blocs de groupes GSL (2 vies)
	KindBracket      = "bracket"       // élimination simple, consolante, dernière chance, double élimination
	KindRoundRobin   = "round_robin"   // poules toutes rondes puis qualification (barrage si égalité)
)

Kinds de phases.

View Source
const (
	RefusalRemoved  = "removed"  // la configuration retire une phase déjà ouverte
	RefusalFinished = "finished" // la phase est terminée
	RefusalDrawn    = "drawn"    // le tirage de la phase est fait
	RefusalStarted  = "started"  // des matchs de la phase sont lancés
)

Raisons d'un ConfigRefusal.

View Source
const JournalVersion = 2

JournalVersion est la version du format du journal écrite dans chaque nouvel événement. 0 (champ absent) désigne les journaux antérieurs aux codes structurés, toujours rejouables. 2 : config_changed ne peut plus changer les options de construction d'un tableau déjà tiré (consolante, dernière chance, réconciliation, recharge, têtes de série) ; un événement de version 1 qui le faisait reste accepté, sans effet, comme il l'a été.

View Source
const PrizeSectionAll = "all"

PrizeSectionAll est la clé de la dotation du classement GÉNÉRAL, celui que Ranking renvoie. Ce n'est pas un nom de section — aucune section ne s'appelle ainsi — mais la place réservée, dans la même carte, à la dotation d'un tournoi qui n'a pas de tableau (un suisse, par exemple).

View Source
const SeedingRating = rules.SeedingRating

SeedingRating : placement classique par cote d'entrée (1 contre 16, 2 contre 15…).

Variables

This section is empty.

Functions

func New

func New(cfg Config, seed int64, now time.Time) (*State, Event, error)

New crée l'état initial d'un tournoi et l'événement de création correspondant.

func PhaseName

func PhaseName(p PhaseConfig) string

PhaseName rend le nom d'une phase : celui que le TD a saisi, sinon le défaut du format. Validate ne remplit plus Name, pour qu'un hôte multilingue puisse fabriquer le sien.

func Prizes

func Prizes(ranking []Rank, prizes []float64) map[PlayerID]float64

Prizes répartit une dotation par place sur un classement avec ex æquo : les joueurs classés à la même place se partagent les prix des places qu'ils occupent (rules.SharePrizes).

func SectionName

func SectionName(name string) string

SectionName rend le nom interne d'une section en français. Exportée parce que le paquet render en a besoin : un nom de section est un identifiant, et le traduire est le travail du Labeler de l'hôte — French() délègue ici.

Types

type Absence

type Absence struct {
	Until time.Time `json:"until,omitempty"` // heure de retour ; zéro = pas d'échéance horaire
	Round int       `json:"round,omitempty"` // suisse par rondes : ronde de retour
	Phase int       `json:"phase"`           // phase où elle a été déclarée (Round n'y vaut que là)
}

Absence est une indisponibilité déclarée.

type Action

type Action struct {
	Kind    ActionKind `json:"kind"`
	Phase   int        `json:"phase"`
	Section string     `json:"section,omitempty"`
	Label   Label      `json:"label,omitempty"`
	Round   int        `json:"round,omitempty"` // ronde suisse (0 = sans objet) ; remplace la relecture du libellé
	Key     string     `json:"key,omitempty"`
	Match   MatchID    `json:"match,omitempty"` // cancel_match : le match à annuler
	A       PlayerID   `json:"a,omitempty"`
	B       PlayerID   `json:"b,omitempty"`
	Length  int        `json:"length,omitempty"`
	Table   int        `json:"table,omitempty"`
	Draw    *Draw      `json:"draw,omitempty"`
	Reason  ReasonCode `json:"reason,omitempty"`
	Until   time.Time  `json:"until,omitempty"` // waiting_batch : échéance du prochain lot
	// Warn signale ce que le moteur a remarqué sur CETTE proposition sans rien bloquer — la
	// fin attendue tombe pendant une pause, par exemple. Le TD décide (voir horaires.go).
	Warn WarningCode `json:"warn,omitempty"`
}

Action est une proposition du moteur ; le TD la confirme en ajoutant l'événement correspondant (voir Action.Event).

func (Action) String

func (a Action) String() string

String décrit une action pour le TD.

type ActionKind

type ActionKind string

ActionKind est le type d'une action proposée au TD.

const (
	ActStartMatch ActionKind = "start_match" // lancer un match
	ActBye        ActionKind = "bye"         // donner un bye (ronde synchrone)
	ActDraw       ActionKind = "draw"        // tirer un tableau / des groupes (le tirage est joint)
	ActNextPhase  ActionKind = "next_phase"  // passer à la phase suivante
	ActFinish     ActionKind = "finish"      // clore le tournoi
	ActWait       ActionKind = "wait"        // rien à faire : attendre la fin de matchs en cours
	// ActCancelMatch : annuler un match devenu incohérent après une correction (reparation.go).
	// Comme toute action, elle est PROPOSÉE : rien n'est annulé d'office.
	ActCancelMatch ActionKind = "cancel_match"
	// ActRepechage : un qualifié de poule s'est retiré avant le tirage de la phase suivante ;
	// le suivant de sa poule (B) peut prendre sa place (A). Proposée, jamais imposée : sans
	// confirmation, la place devient une exemption (repechage.go).
	ActRepechage ActionKind = "repechage"
)

type Clock

type Clock struct {
	Played   int           `json:"played"`
	Running  int           `json:"running"`
	Elapsed  time.Duration `json:"elapsed"`
	AvgMatch time.Duration `json:"avg_match"`  // durée moyenne observée des matchs terminés
	AvgPerPt time.Duration `json:"avg_per_pt"` // durée moyenne observée par point
}

Clock résume l'avancement : matchs joués, en cours, temps écoulé.

type Config

type Config struct {
	Name        string        `json:"name"`
	Phases      []PhaseConfig `json:"phases"`
	MinPerPoint float64       `json:"min_per_point,omitempty"` // durée moyenne d'un point (minutes), défaut 8
	Tables      Tables        `json:"tables,omitempty"`        // les tables de la salle (tables.go)
	// Prizes : droit d'entrée, retenue d'organisation et barème par section (prizes.go). La
	// forme ancienne — une simple liste de montants — reste lue et devient le barème du
	// classement général.
	Prizes PrizePool `json:"prizes,omitempty"`
	// Breaks : les pauses programmées de la journée (repas, remise des prix). Rien n'est
	// bloqué : une proposition dont le match rencontrerait une pause porte l'avertissement
	// WarnEndsInBreak, et le directeur décide (horaires.go).
	Breaks []TimeRange `json:"breaks,omitempty"`
}

Config décrit un tournoi : une suite de phases.

func ParseConfig

func ParseConfig(b []byte) (*Config, error)

ParseConfig lit une configuration JSON et la valide.

func (Config) BreakIntervals added in v0.6.0

func (c Config) BreakIntervals() []rules.Interval

BreakIntervals : les pauses déclarées, telles que rules.AfterBreaks et rules.EstimatedEnd les lisent.

func (*Config) Validate

func (c *Config) Validate() error

Validate vérifie la cohérence d'une configuration et remplit les défauts.

type ConfigRefusal

type ConfigRefusal struct {
	Phase  int    `json:"phase"`
	Field  string `json:"field"`            // champ JSON de PhaseConfig ("kind", "consolation"…) ; "phases" pour un retrait
	Reason string `json:"reason"`           // RefusalRemoved, RefusalFinished, RefusalDrawn, RefusalStarted
	From   string `json:"from,omitempty"`   // kind : l'ancien format
	To     string `json:"to,omitempty"`     // kind : le nouveau format
	Opened int    `json:"opened,omitempty"` // removed : le nombre de phases déjà ouvertes
}

ConfigRefusal est le refus d'une configuration : la phase, le champ, et la raison — un CODE, comme tout ce qui sort du moteur. Error() en donne le rendu français, pour la console.

func (*ConfigRefusal) Error

func (r *ConfigRefusal) Error() string

type Draw

type Draw struct {
	Slots  []PlayerID       `json:"slots,omitempty"`  // tableau : places du premier tour (BYE = exemption)
	Groups [][]PlayerID     `json:"groups,omitempty"` // groupes (GSL, poules)
	Lives  map[PlayerID]int `json:"lives,omitempty"`  // vies à l'entrée (information)
}

Draw est le résultat d'un tirage (placement dans un tableau ou composition de groupes), matérialisé dans le journal pour que le rejeu ne dépende pas de l'algorithme.

type Event

type Event struct {
	Seq          int       `json:"seq"`
	Version      int       `json:"version,omitempty"` // format du journal ; 0 = avant les codes structurés
	Kind         EventKind `json:"kind"`
	Time         time.Time `json:"time"`
	Config       *Config   `json:"config,omitempty"`
	Seed         int64     `json:"seed,omitempty"`
	Player       *Player   `json:"player,omitempty"`
	ID           PlayerID  `json:"player_id,omitempty"`
	MatchID      MatchID   `json:"match_id,omitempty"`
	Phase        int       `json:"phase,omitempty"`
	Section      string    `json:"section,omitempty"`
	Label        Label     `json:"label,omitempty"`
	Round        int       `json:"round,omitempty"`
	Key          string    `json:"key,omitempty"`
	A            PlayerID  `json:"a,omitempty"`
	B            PlayerID  `json:"b,omitempty"`
	Length       int       `json:"length,omitempty"`
	Table        int       `json:"table,omitempty"`
	Winner       PlayerID  `json:"winner,omitempty"`
	ScoreA       int       `json:"score_a,omitempty"`
	ScoreB       int       `json:"score_b,omitempty"`
	Forfeit      bool      `json:"forfeit,omitempty"`
	AfterCurrent bool      `json:"after_current,omitempty"` // retrait différé : le joueur finit son match
	Draw         *Draw     `json:"draw,omitempty"`
	Text         string    `json:"text,omitempty"`
	// Slot : sur player_added, la clé de la place d'exemption qu'un retardataire vient prendre
	// dans un tableau déjà tiré (voir retardataire.go). Vide pour une inscription ordinaire.
	Slot string `json:"slot,omitempty"`
	// Until : sur player_unavailable, l'heure à laquelle le joueur redevient disponible.
	Until *time.Time `json:"until,omitempty"`
}

Event est une entrée du journal. Les champs inutiles pour un type restent vides.

func CancelEvent

func CancelEvent(id MatchID, now time.Time) Event

CancelEvent : annulation d'un match lancé par erreur.

func ConfigChangedEvent

func ConfigChangedEvent(cfg Config, now time.Time) Event

ConfigChangedEvent : nouvelle configuration du tournoi, ENTIÈRE. Le moteur la valide et refuse ce qui changerait le format d'une phase commencée ou terminée, ou retirerait une phase ouverte (reconfig.go). Tout le reste est admis : la bascule, les longueurs à venir, les tables, les pauses, la dotation, une phase ajoutée après la phase courante.

EvLengthChanged reste lu pour les journaux existants ; il n'est plus le seul moyen de changer une longueur.

func CorrectionEvent

func CorrectionEvent(id MatchID, winner PlayerID, scoreA, scoreB int, now time.Time) Event

CorrectionEvent : correction du résultat d'un match déjà terminé. Le journal n'est jamais modifié : la correction est un événement de plus, et l'état est recalculé.

func ForfeitEvent

func ForfeitEvent(id MatchID, winner PlayerID, now time.Time) Event

ForfeitEvent : un joueur ne se présente pas pour CE match, sans quitter le tournoi. Il suit ensuite le chemin d'un perdant ordinaire (la consolante, par exemple).

func LengthChangedEvent

func LengthChangedEvent(phase, length int, now time.Time) Event

LengthChangedEvent : nouvelle longueur pour les matchs à venir d'une phase.

func NoteEvent

func NoteEvent(text string, now time.Time) Event

NoteEvent : annotation libre du directeur de tournoi, horodatée.

func PlayerAddedAtSlotEvent

func PlayerAddedAtSlotEvent(p Player, slot Slot, now time.Time) Event

PlayerAddedAtSlotEvent : inscription d'un retardataire sur une place d'exemption libre d'un tableau déjà tiré (State.FreeSlots les énumère). Le tirage n'est pas refait : la place est occupée là où elle est. Une place déjà jouée est refusée par Apply.

func PlayerAddedEvent

func PlayerAddedEvent(p Player, now time.Time) Event

PlayerAddedEvent : inscription d'un joueur.

func PlayerAvailableEvent

func PlayerAvailableEvent(id PlayerID, now time.Time) Event

PlayerAvailableEvent : l'indisponibilité du joueur est levée, quelle qu'elle soit.

func PlayerUnavailableEvent

func PlayerUnavailableEvent(id PlayerID, until time.Time, now time.Time) Event

PlayerUnavailableEvent : le joueur est indisponible jusqu'à until — il joue dans une autre épreuve, il est parti dîner, il n'arrive que demain. Il n'est plus apparié, et ne perd rien : ni vie, ni rang, ni place. until nul = jusqu'à PlayerAvailableEvent.

func PlayerUnavailableUntilRoundEvent

func PlayerUnavailableUntilRoundEvent(id PlayerID, round int, now time.Time) Event

PlayerUnavailableUntilRoundEvent : suisse par rondes, le joueur est absent jusqu'à la ronde round, où il est de nouveau apparié. L'indisponibilité tombe aussi au passage de phase.

func PlayerUpdatedEvent

func PlayerUpdatedEvent(p Player, now time.Time) Event

PlayerUpdatedEvent : correction de la fiche d'un joueur déjà inscrit — nom, club, cote. La fiche est ENTIÈRE, comme la configuration de config_changed : ce qui est écrit est le résultat. Rien d'autre ne change : un retiré reste retiré, ses vies, ses matchs et sa place dans les phases sont intacts. Un identifiant inconnu est refusé : inscrire, c'est PlayerAddedEvent.

player_added sur un identifiant connu reste lu comme avant (mise à jour de la fiche ET réinscription d'un retiré) : c'était le seul chemin des journaux antérieurs, et c'est toujours celui du retour d'un joueur retiré.

func PlayerWithdrawnAfterCurrentEvent

func PlayerWithdrawnAfterCurrentEvent(id PlayerID, now time.Time) Event

PlayerWithdrawnAfterCurrentEvent : retrait différé. Le joueur n'est plus apparié, mais le match qu'il joue va à son terme — le cas de qui doit partir à 18 h.

func PlayerWithdrawnEvent

func PlayerWithdrawnEvent(id PlayerID, now time.Time) Event

PlayerWithdrawnEvent : retrait immédiat d'un joueur. Ses matchs en cours sont perdus par forfait, et ses matchs de graphe non lancés aussi.

func ReopenedEvent

func ReopenedEvent(now time.Time) Event

ReopenedEvent : un tournoi clos est rouvert, parce qu'un résultat était faux. Le classement final est effacé et sera recalculé à la clôture suivante — le journal, lui, garde tout.

func RepechageEvent

func RepechageEvent(phase int, section string, withdrawn, replacement PlayerID, now time.Time) Event

RepechageEvent : dans la poule section de la phase phase, replacement prend la place du qualifié withdrawn, retiré avant le tirage de la phase suivante. C'est l'événement que produit EventFromAction sur une ActRepechage ; le construire à la main sert au TD qui choisit un autre joueur de la poule que celui proposé (entre ex æquo, par exemple).

func ResultEvent

func ResultEvent(id MatchID, winner PlayerID, scoreA, scoreB int, now time.Time) Event

ResultEvent construit l'événement de résultat d'un match.

func TableChangedEvent

func TableChangedEvent(id MatchID, table int, now time.Time) Event

TableChangedEvent : un match en cours change de table (bruit, lumière, retransmission).

func (Event) WithNote

func (e Event) WithNote(text string) Event

WithNote attache une remarque à un événement de résultat (« tombé au temps », « abandon : … »). Rare, et irremplaçable quand elle sert.

type EventKind

type EventKind string

EventKind est le type d'un événement du journal.

const (
	EvCreated         EventKind = "created"          // Config, Seed
	EvPlayerAdded     EventKind = "player_added"     // Player
	EvPlayerWithdrawn EventKind = "player_withdrawn" // Player.ID ; AfterCurrent = finit son match
	EvPlayerUpdated   EventKind = "player_updated"   // Player : la fiche ENTIÈRE corrigée, l'état du joueur intact
	// EvPlayerUnavailable : ID ; Until (heure de retour) ou Round (ronde de retour), ou aucun des
	// deux (jusqu'à player_available). Le joueur n'est plus apparié, sans rien perdre.
	EvPlayerUnavailable EventKind = "player_unavailable"
	EvPlayerAvailable   EventKind = "player_available" // ID : l'indisponibilité est levée
	EvMatchStarted      EventKind = "match_started"    // MatchID, Phase, Section, Label, Key, A, B, Length, Table
	EvResult            EventKind = "result"           // MatchID, Winner, ScoreA, ScoreB, Forfeit
	EvResultCorrected   EventKind = "result_corrected" // idem, remplace le résultat précédent
	EvMatchCancelled    EventKind = "match_cancelled"  // MatchID (match lancé par erreur)
	EvBye               EventKind = "bye"              // Phase, Player, Label
	EvDraw              EventKind = "draw"             // Phase, Section, Draw
	EvNextPhase         EventKind = "next_phase"       // passage à la phase suivante
	EvRepechage         EventKind = "repechage"        // Phase, Section (poule), A (qualifié retiré), B (repêché)
	EvLengthChanged     EventKind = "length_changed"   // Phase, Length (matchs futurs de la phase)
	EvConfigChanged     EventKind = "config_changed"   // Config (la configuration ENTIÈRE, voir reconfig.go)
	EvReopened          EventKind = "reopened"         // le tournoi clos est rouvert ; Final sera recalculé
	EvTableChanged      EventKind = "table_changed"    // MatchID, Table (match en cours déplacé)
	EvFinished          EventKind = "finished"
	EvNote              EventKind = "note" // Text (annotation libre du TD)
)

type External

type External struct {
	// BusyTables : tables occupées hors de ce tournoi. L'attribution les saute ; une proposition
	// qui ne trouve plus de table attend (ReasonWaitingTable) au lieu de collisionner.
	BusyTables []int `json:"busy_tables,omitempty"`
	// BusyPlayers : joueurs de CE tournoi (ses PlayerID ; l'hôte traduit ceux de l'autre
	// épreuve) qui jouent en ce moment ailleurs. Au suisse et en barrage ils ne sont pas
	// appariés, et la file porte une attente ReasonPlayerBusy par joueur ; dans un graphe, où la
	// place est fixée, leur match reste proposé, sans table, avec cette raison. Un identifiant
	// inconnu est ignoré.
	BusyPlayers []PlayerID `json:"busy_players,omitempty"`
}

External décrit ce que l'hôte sait de la salle et que le journal de CE tournoi ignore : les tables où joue, en ce moment, une autre épreuve dirigée à côté, et les joueurs de ce tournoi qui y jouent. Ce n'est pas un événement : l'occupation par l'extérieur change à chaque match de l'autre épreuve, elle n'appartient pas à l'histoire de celle-ci, et le rejeu n'en a pas besoin — le match_started confirmé porte, lui, la table et les joueurs effectivement pris.

type GMatch

type GMatch struct {
	Key      string      `json:"key"`
	Label    Label       `json:"label,omitempty"`
	Length   int         `json:"length"`
	Src      [2]Src      `json:"src"`
	Players  [2]PlayerID `json:"players"`
	MatchID  MatchID     `json:"match_id,omitempty"`
	Winner   PlayerID    `json:"winner,omitempty"`
	Loser    PlayerID    `json:"loser,omitempty"`
	Done     bool        `json:"done"`
	Walkover bool        `json:"walkover,omitempty"`
	Skipped  bool        `json:"skipped,omitempty"`   // match conditionnel non joué (recharge)
	CondFrom int         `json:"cond_from,omitempty"` // index du match dont dépend l'existence (recharge)
	CondSide int         `json:"cond_side,omitempty"` // le match existe si le vainqueur de CondFrom est son joueur CondSide
	Cond     bool        `json:"cond,omitempty"`
}

GMatch est un match d'un graphe : ses deux places viennent de joueurs fixés ou d'autres matchs.

type Info

type Info struct {
	Code    InfoCode `json:"code"`
	Player  PlayerID `json:"player,omitempty"`
	Phase   int      `json:"phase,omitempty"`
	Section string   `json:"section,omitempty"`
	Label   Label    `json:"label,omitempty"`
}

Info est une information destinée au TD : un code et de quoi la situer.

func (Info) String

func (i Info) String() string

String rend une information en français.

type InfoCode

type InfoCode string

InfoCode est le code d'une information : ni une incohérence à corriger, ni une action à confirmer, mais un fait que le TD doit voir — le sort d'un joueur inscrit qui ne joue encore nulle part.

const (
	// InfoEntersAt : le joueur entrera là. Phase et Section (avec le libellé) disent où.
	InfoEntersAt InfoCode = "enters_at"
	// InfoNoEntry : le joueur est inscrit mais rien ne l'admet — aucune place d'exemption
	// libre, aucune phase à venir ouverte à tous. Le dire vaut mieux que le laisser disparaître
	// de l'affichage.
	InfoNoEntry InfoCode = "no_entry"
)

type Journal

type Journal []Event

Journal est la liste ordonnée des événements d'un tournoi.

func ParseJournal

func ParseJournal(b []byte) (Journal, error)

ParseJournal lit un journal JSON.

func (Journal) Bytes

func (j Journal) Bytes() ([]byte, error)

MarshalJSON / UnmarshalJSON : le journal est un simple tableau JSON.

type Label

type Label struct {
	Kind    LabelKind `json:"kind,omitempty"`
	N       int       `json:"n,omitempty"`       // ronde, tour, bloc, nombre de places, de poules
	Losses  int       `json:"losses,omitempty"`  // suisse continu : groupe de défaites
	Match   int       `json:"match,omitempty"`   // suisse continu : n-ième match du joueur
	Section string    `json:"section,omitempty"` // nom interne de la section concernée
	Text    string    `json:"text,omitempty"`    // seul texte admis : ce que le TD a lui-même saisi
	Players int       `json:"players,omitempty"` // barrage : joueurs à départager
	Spots   int       `json:"spots,omitempty"`   // barrage : places à attribuer
	Sub     *Label    `json:"sub,omitempty"`
}

Label est un libellé structuré. Les champs inutiles pour un code restent vides ; Sub compose un libellé dans un autre (« Bloc 2, groupe B : match décisif »).

func (Label) Empty

func (l Label) Empty() bool

Empty indique un libellé absent.

func (Label) String

func (l Label) String() string

String rend un libellé en français.

func (*Label) UnmarshalJSON

func (l *Label) UnmarshalJSON(b []byte) error

UnmarshalJSON accepte les deux formes d'un libellé : la structure (journaux à partir de la version 1) et la chaîne des journaux antérieurs, conservée telle quelle dans Text pour que rien ne soit perdu. upgrade (events.go) la convertit en code quand elle est reconnaissable.

type LabelKind

type LabelKind string

LabelKind est le code d'un libellé de match, de tirage ou de phase.

const (
	LabelNone LabelKind = ""

	// Suisse
	LabelSwissGroup LabelKind = LabelKind(rules.LabelSwissGroup) // Losses, Match : « 1 défaite, match 3 »
	LabelRound      LabelKind = LabelKind(rules.LabelRound)      // N : « Ronde 3 »
	LabelRematch    LabelKind = LabelKind(rules.LabelRematch)
	LabelCrossed    LabelKind = LabelKind(rules.LabelCrossed) // match croisé de secours

	// Tableaux
	LabelBracketRound       LabelKind = LabelKind(rules.LabelBracketRound) // N : « Tour 2 »
	LabelFinal              LabelKind = LabelKind(rules.LabelFinal)
	LabelSemiFinal          LabelKind = LabelKind(rules.LabelSemiFinal)
	LabelQuarterFinal       LabelKind = LabelKind(rules.LabelQuarterFinal)
	LabelMainDraw           LabelKind = LabelKind(rules.LabelMainDraw) // Sub : « Principal, quart de finale »
	LabelConsolationRound   LabelKind = LabelKind(rules.LabelConsolationRound)
	LabelConsolationFinal   LabelKind = LabelKind(rules.LabelConsolationFinal)
	LabelLastChance         LabelKind = LabelKind(rules.LabelLastChance) // Sub facultatif
	LabelGrandFinal         LabelKind = LabelKind(rules.LabelGrandFinal)
	LabelGrandFinalRecharge LabelKind = LabelKind(rules.LabelGrandFinalRecharge)

	// GSL
	LabelOpening      LabelKind = LabelKind(rules.LabelOpening)
	LabelWinnersMatch LabelKind = LabelKind(rules.LabelWinnersMatch)
	LabelLosersMatch  LabelKind = LabelKind(rules.LabelLosersMatch)
	LabelDecider      LabelKind = LabelKind(rules.LabelDecider)
	LabelSingleMatch  LabelKind = LabelKind(rules.LabelSingleMatch)
	LabelSingleElim   LabelKind = LabelKind(rules.LabelSingleElim)
	LabelInBlock      LabelKind = LabelKind(rules.LabelInBlock) // N = bloc, Section, Sub

	// Poules
	LabelPoolRound    LabelKind = LabelKind(rules.LabelPoolRound) // N
	LabelInSection    LabelKind = LabelKind(rules.LabelInSection) // Section, Sub
	LabelBarrage      LabelKind = LabelKind(rules.LabelBarrage)   // Section
	LabelBarrageCross LabelKind = LabelKind(rules.LabelBarrageCross)
	LabelDrawPools    LabelKind = LabelKind(rules.LabelDrawPools)   // N = nombre de poules
	LabelDrawBarrage  LabelKind = LabelKind(rules.LabelDrawBarrage) // Section, Players, Spots
	// LabelRepechage : repêchage proposé dans la poule Section ; Players = nombre de candidats
	// ex æquo (1 : un seul suivant ; davantage : le TD choisit, voir repechage.go).
	LabelRepechage     LabelKind = "repechage"
	LabelDrawBracket   LabelKind = LabelKind(rules.LabelDrawBracket) // N = places
	LabelDrawBlock     LabelKind = LabelKind(rules.LabelDrawBlock)   // N = bloc, Groups
	LabelPhase         LabelKind = LabelKind(rules.LabelPhase)       // Phase : le nom vient de la configuration
	LabelPoolName      LabelKind = "pool_name"                       // Text : la lettre de la poule
	LabelBarrageName   LabelKind = "barrage_name"                    // Sub = LabelPoolName
	LabelSectionMain   LabelKind = "section_main"
	LabelSectionConso  LabelKind = "section_conso"
	LabelSectionLast   LabelKind = "section_last"
	LabelSectionFinals LabelKind = "section_finals"
)

type Match

type Match struct {
	ID      MatchID     `json:"id"`
	Phase   int         `json:"phase"`
	Section string      `json:"section,omitempty"` // identifiant : "main", "conso", "poule:A"…
	Label   Label       `json:"label,omitempty"`   // libellé structuré (codes.go)
	Key     string      `json:"key,omitempty"`     // clé interne (position dans un graphe de matchs)
	Round   int         `json:"round,omitempty"`   // ronde suisse (0 = sans objet)
	A       PlayerID    `json:"a"`
	B       PlayerID    `json:"b"`
	Length  int         `json:"length"`
	Table   int         `json:"table,omitempty"`
	Status  MatchStatus `json:"status"`
	Start   time.Time   `json:"start"`
	End     time.Time   `json:"end,omitempty"`
	Winner  PlayerID    `json:"winner,omitempty"`
	ScoreA  int         `json:"score_a,omitempty"`
	ScoreB  int         `json:"score_b,omitempty"`
	Forfeit bool        `json:"forfeit,omitempty"`
}

Match est un match lancé (ou terminé) entre deux joueurs.

func (*Match) Has

func (m *Match) Has(p PlayerID) bool

Has indique si p joue ce match.

func (*Match) Loser

func (m *Match) Loser() PlayerID

Loser renvoie le perdant d'un match terminé.

type MatchID

type MatchID string

MatchID identifie un match dans le tournoi.

type MatchStatus

type MatchStatus string

MatchStatus est l'état d'un match.

const (
	Running   MatchStatus = "running"
	Finished  MatchStatus = "finished"
	Cancelled MatchStatus = "cancelled"
)

type Note

type Note struct {
	Kind      NoteKind `json:"kind,omitempty"`
	Wins      int      `json:"wins,omitempty"`
	Losses    int      `json:"losses,omitempty"`
	Lives     int      `json:"lives,omitempty"`
	Section   string   `json:"section,omitempty"`
	Qualified bool     `json:"qualified,omitempty"`
	Sub       *Label   `json:"sub,omitempty"`
}

Note est la note de classement d'un joueur : un code et ses paramètres, jamais une phrase.

func (Note) String

func (n Note) String() string

String rend une note de classement en français.

type NoteKind

type NoteKind string

NoteKind est le code d'une note de classement.

const (
	NoteNone          NoteKind = ""
	NoteWinner        NoteKind = NoteKind(rules.NoteWinner)
	NoteFinalist      NoteKind = NoteKind(rules.NoteFinalist)
	NoteAlive         NoteKind = NoteKind(rules.NoteAlive)         // Lives
	NoteRecord        NoteKind = NoteKind(rules.NoteRecord)        // Wins, Losses
	NoteForfeit       NoteKind = NoteKind(rules.NoteForfeit)       // match perdu par forfait (classement d'une section) ; journaux antérieurs : retiré
	NoteWithdrawn     NoteKind = NoteKind(rules.NoteWithdrawn)     // retiré, classé sur son parcours : Wins, Losses, Lives ; tableau : Section + Sub ; poule : Section + Wins
	NoteRunning       NoteKind = NoteKind(rules.NoteRunning)       // encore en course, tableau non terminé
	NoteAwaitingDraw  NoteKind = NoteKind(rules.NoteAwaitingDraw)  // tableau pas encore tiré
	NoteUnranked      NoteKind = NoteKind(rules.NoteUnranked)      // aucune sortie enregistrée
	NoteSectionExit   NoteKind = NoteKind(rules.NoteSectionExit)   // Section + Sub : sorti de cette section là — pas forcément éliminé (State.Statuses)
	NoteSectionWinner NoteKind = NoteKind(rules.NoteSectionWinner) // Section
	NotePoolRecord    NoteKind = NoteKind(rules.NotePoolRecord)    // Section, Wins, Qualified
)

type PhaseConfig

type PhaseConfig struct {
	Kind        string `json:"kind"`
	Name        string `json:"name,omitempty"`
	Lives       int    `json:"lives,omitempty"`        // swiss_lives : nombre de vies (défaut 2)
	Length      int    `json:"length"`                 // longueur des matchs (points)
	FinalLength int    `json:"final_length,omitempty"` // longueur de la finale (tableaux), 0 = Length
	// Lengths donne la longueur tour par tour d'un tableau, DU DERNIER TOUR VERS LE PREMIER
	// (« 15, 13, 11, 9 » = finale en 15, demies en 13, quarts en 11, reste en 9). L'ordre est
	// celui dans lequel un organisateur annonce son tournoi, et il ne dépend pas de la taille
	// du tableau : la même liste sert un tableau de 16 et un tableau de 64, où elle allonge les
	// quatre derniers tours. Une liste plus courte que le tableau retombe sur Length.
	Lengths []int `json:"lengths,omitempty"`
	// LengthLate allonge les matchs de la fin d'un suisse, quand il ne reste plus que
	// LateThreshold joueurs en vie ou moins. Les deux vont ensemble : l'un sans l'autre ne fait
	// rien. Un changement de longueur décidé à la main par le TD (EvLengthChanged) l'emporte.
	LengthLate    int `json:"length_late,omitempty"`
	LateThreshold int `json:"late_threshold,omitempty"`
	// BatchMinutes : micro-rondes d'un suisse continu. Les joueurs libres attendent l'échéance,
	// puis tous ceux d'un même groupe de défaites sont appariés d'un coup. 0 = appariement au
	// fil de l'eau. L'échéance est dérivée du journal (horaires.go), jamais stockée à part.
	BatchMinutes   int    `json:"batch_minutes,omitempty"`
	Mode           string `json:"mode,omitempty"`        // swiss_lives : "continuous" (défaut) ou "rounds"
	Pairing        string `json:"pairing,omitempty"`     // swiss_lives : "random" (défaut) ou "wins"
	AvoidClubs     bool   `json:"avoid_clubs,omitempty"` // éviter les joueurs du même club quand c'est possible
	AllowRematch   bool   `json:"allow_rematch,omitempty"`
	Target         int    `json:"target,omitempty"`         // swiss_lives / gsl : figer quand la somme des vies vaut Target (puissance de 2)
	Consolation    bool   `json:"consolation,omitempty"`    // bracket : consolante progressive (perdants du tableau principal)
	LastChance     bool   `json:"last_chance,omitempty"`    // bracket : dernière chance (perdants de la consolante)
	Reconciliation bool   `json:"reconciliation,omitempty"` // bracket : le vainqueur de la consolante joue le vainqueur du principal (double élimination)
	Recharge       bool   `json:"recharge,omitempty"`       // bracket : en double élimination, le vainqueur du principal doit être battu deux fois
	GroupSize      int    `json:"group_size,omitempty"`     // round_robin : taille des poules (défaut 4)
	Qualifiers     int    `json:"qualifiers,omitempty"`     // round_robin : qualifiés par poule (défaut 2)
	Entry          string `json:"entry,omitempty"`          // "survivors" (défaut : joueurs encore en vie de la phase précédente), "all", "top:N"
	// Seeding place les joueurs d'un tableau par cote au lieu de les tirer au hasard :
	// "" (défaut) ou SeedingRating. Le défaut VIDE est un choix de conception et non un oubli —
	// l'étude conclut « pas de têtes de série protégées », c'est la culture actuelle du
	// backgammon. Voir seeding.go.
	Seeding string `json:"seeding,omitempty"`
}

PhaseConfig paramètre une phase. Les champs inutiles pour un type sont ignorés.

type PhaseState

type PhaseState struct {
	Index     int                     `json:"index"`
	Cfg       PhaseConfig             `json:"cfg"`
	Entrants  []PlayerID              `json:"entrants"`
	Lives     map[PlayerID]int        `json:"lives"` // vies à l'entrée de la phase
	Losses    map[PlayerID]int        `json:"losses"`
	Wins      map[PlayerID]int        `json:"wins"`
	Byes      map[PlayerID]int        `json:"byes"`
	Opponents map[PlayerID][]PlayerID `json:"opponents"`
	ElimOrder []PlayerID              `json:"elim_order"` // ordre d'élimination
	Round     int                     `json:"round"`      // rondes synchrones / blocs
	// Roster : suisse par rondes, les joueurs appelés à la ronde Round, relevés au premier
	// événement de la ronde (match ou bye). La ronde reste ouverte tant que l'un d'eux n'y est
	// pas engagé : ses appariements restent proposés (phase_swiss.go).
	Roster []PlayerID `json:"roster,omitempty"`
	// ByeRounds : les rondes où le joueur a reçu un bye (suisse par rondes).
	ByeRounds map[PlayerID][]int `json:"bye_rounds,omitempty"`
	Drawn     bool               `json:"drawn"`
	Done      bool               `json:"done"`
	Sections  []*Section         `json:"sections,omitempty"`
	Length    int                `json:"length"` // longueur courante des matchs
	Started   bool               `json:"started"`
	// Repechages : poules, qualifié retiré → joueur repêché à sa place (repechage.go).
	Repechages map[PlayerID]PlayerID `json:"repechages,omitempty"`
}

PhaseState est l'état d'une phase.

type Player

type Player struct {
	ID     PlayerID `json:"id"`
	Name   string   `json:"name"`
	Club   string   `json:"club,omitempty"`
	Rating float64  `json:"rating,omitempty"` // PR (plus bas = meilleur) ; 0 = inconnu
}

Player décrit un joueur inscrit.

type PlayerID

type PlayerID string

PlayerID identifie un joueur ; fourni par l'hôte.

const BYE PlayerID = "BYE"

BYE est l'identifiant réservé d'une place vide dans un tableau (exemption).

type PlayerStatus

type PlayerStatus struct {
	Player PlayerID   `json:"player"`
	Kind   StatusKind `json:"kind"`
	// Phase : la phase dont parle le statut — en cours (en jeu, exempté), à venir (qualifié,
	// indécis), dernière jouée (éliminé, retiré, vainqueur).
	Phase   int    `json:"phase,omitempty"`
	Section string `json:"section,omitempty"`
	Round   int    `json:"round,omitempty"`
	Label   Label  `json:"label,omitempty"`
}

PlayerStatus est le statut d'un joueur : un code et de quoi le situer, jamais une phrase.

func (PlayerStatus) String

func (p PlayerStatus) String() string

String rend le statut d'un joueur en français (« est-ce que je joue ? »), sans son nom. Les phases sont numérotées à partir de 1 : leur nom est dans la configuration, que l'hôte a.

type PrizePool

type PrizePool struct {
	EntryFee  float64               `json:"entry_fee,omitempty"` // droit d'entrée par inscrit
	Retention Retention             `json:"retention,omitempty"`
	Sections  map[string]PrizeScale `json:"sections,omitempty"` // clé : PrizeSectionAll, "main", "conso"…
}

PrizePool décrit la dotation entière d'un tournoi.

func (PrizePool) Empty

func (p PrizePool) Empty() bool

Empty : aucune dotation configurée.

func (PrizePool) SectionNames

func (p PrizePool) SectionNames() []string

SectionNames : les sections dotées, dans un ordre stable (le classement général d'abord).

func (*PrizePool) UnmarshalJSON

func (p *PrizePool) UnmarshalJSON(b []byte) error

UnmarshalJSON accepte les deux formes : la liste de montants des configurations d'avant les sections (`"prizes": [100, 60, 40]`), qui devient la dotation du classement général, et la structure. Un journal existant se relit donc sans être réécrit.

type PrizeScale

type PrizeScale struct {
	Percents []float64 `json:"percents,omitempty"` // % du pool distribuable, place par place
	Amounts  []float64 `json:"amounts,omitempty"`  // montants fixes, place par place
}

PrizeScale est la dotation d'une section : des pourcentages du pool distribuable, ou des montants fixes. Les deux ensemble n'ont pas de sens ; Amounts l'emporte, et Validate le dit.

type Rank

type Rank struct {
	Player PlayerID `json:"player"`
	Rank   int      `json:"rank"`
	Note   Note     `json:"note,omitempty"` // note structurée (codes.go)
}

Rank est une place au classement ; les ex æquo partagent le même Rank.

type ReasonCode

type ReasonCode string

ReasonCode dit pourquoi le moteur ne propose rien à faire.

const (
	ReasonNone           ReasonCode = ""
	ReasonMatchesRunning ReasonCode = "matches_running"
	ReasonNoPairing      ReasonCode = "no_pairing"
	ReasonWaitingBatch   ReasonCode = "waiting_batch"
	ReasonWaitingTable   ReasonCode = "waiting_table"
	// ReasonPlayerUnavailable : un joueur est indisponible (player_unavailable). Sur une attente
	// (A = le joueur, Until ou Round = son retour) dans un suisse ; sur la proposition de match
	// d'un graphe, qui reste dans la file sans table.
	ReasonPlayerUnavailable ReasonCode = "player_unavailable"
	// ReasonPlayerBusy : un joueur joue en ce moment dans une autre épreuve de la salle
	// (External.BusyPlayers). Sur une attente (A = le joueur) dans un suisse ; sur la proposition
	// de match d'un graphe, qui reste dans la file sans table. Jamais écrit au journal.
	ReasonPlayerBusy ReasonCode = "player_busy"
)

func (ReasonCode) String

func (r ReasonCode) String() string

String rend une raison d'attente en français.

type Retention

type Retention struct {
	Amount  float64 `json:"amount,omitempty"`
	Percent float64 `json:"percent,omitempty"`
}

Retention est ce que l'organisation garde avant de distribuer : un montant, un pourcentage du pool, ou les deux (le pourcentage s'applique au pool brut, puis le montant s'en retranche).

type Section

type Section struct {
	Name    string     `json:"name"`
	Kind    string     `json:"kind"` // secMain, secConso, secLast, secGrandFinal, secKindGSL, secKindSE, secKindPool, secKindBarrage
	Group   int        `json:"group,omitempty"`
	Block   int        `json:"block,omitempty"`
	Matches []GMatch   `json:"matches"`
	Rounds  [][]int    `json:"rounds,omitempty"`  // indices de matchs par tour (affichage)
	Players []PlayerID `json:"players,omitempty"` // barrage : joueurs concernés (appariement dynamique)
	Spots   int        `json:"spots,omitempty"`   // barrage : nombre de places à attribuer
}

Section est un graphe de matchs dont les places se remplissent par les résultats (tableau, groupe GSL, poule, barrage).

type Slot

type Slot struct {
	Phase   int    `json:"phase"`
	Section string `json:"section"`
	Key     string `json:"key"`
	Label   Label  `json:"label,omitempty"`
}

Slot est une place d'exemption libre : un côté d'un match de graphe dont la source est un BYE fixe, dans un tour que personne n'a commencé à jouer. C'est ce qu'un hôte propose au TD quand un joueur se présente en retard.

type Src

type Src struct {
	Player  PlayerID `json:"player,omitempty"`
	From    int      `json:"from"`
	Section string   `json:"section,omitempty"`
	Loser   bool     `json:"loser,omitempty"`
}

Src est l'origine d'une place : un joueur, ou le vainqueur/perdant d'un match (index dans la section Section, ou la même section si vide).

type State

type State struct {
	Config    Config               `json:"config"`
	Seed      int64                `json:"seed"`
	Players   map[PlayerID]*Player `json:"players"`
	Order     []PlayerID           `json:"order"` // ordre d'inscription
	Withdrawn map[PlayerID]bool    `json:"withdrawn,omitempty"`
	// WithdrawAfter : retraits différés. Le joueur a demandé à partir mais finit le match qu'il
	// joue ; il n'est plus apparié (il est occupé), et le retrait devient effectif dès que ce
	// match est terminé. Un retrait différé sans match en cours est un retrait immédiat.
	WithdrawAfter map[PlayerID]bool  `json:"withdraw_after,omitempty"`
	Matches       map[MatchID]*Match `json:"matches"`
	MatchOrder    []MatchID          `json:"match_order"`
	Phases        []*PhaseState      `json:"phases"`
	Current       int                `json:"current"` // index de la phase en cours (-1 avant création)
	Finished      bool               `json:"finished"`
	Final         []Rank             `json:"final,omitempty"`
	NEvents       int                `json:"n_events"`
	Last          time.Time          `json:"last"`
	Warnings      []Warning          `json:"warnings,omitempty"`
	// Infos : les inscrits qui ne jouent encore nulle part et où ils entreront (retardataire.go).
	// Dérivées de l'état à chaque événement, jamais accumulées.
	Infos []Info `json:"infos,omitempty"`
	// Revived : joueurs qu'une correction a rendus à la vie dans un suisse, jusqu'à ce qu'ils
	// rejouent (WarnCorrectionRevives).
	Revived map[PlayerID]bool `json:"revived,omitempty"`
	// Unavailable : les indisponibilités déclarées (player_unavailable), levées par
	// player_available, par l'heure ou par la ronde de retour (absence.go).
	Unavailable map[PlayerID]Absence `json:"unavailable,omitempty"`
	// contains filtered or unexported fields
}

State est l'état complet d'un tournoi, reconstruit à partir du journal.

func Replay

func Replay(j Journal) (*State, error)

Replay reconstruit l'état à partir du journal.

func (*State) Apply

func (s *State) Apply(ev Event) error

Apply applique un événement à l'état.

func (*State) CheckConfig

func (s *State) CheckConfig(next Config) error

CheckConfig dit si next pourrait remplacer la configuration courante, sans rien appliquer : la prévisualisation d'un formulaire de réglages. Le refus est un *ConfigRefusal (ou l'erreur de Validate). Il applique les règles d'aujourd'hui, celles d'un config_changed de la version courante du journal.

func (*State) ClockAt

func (s *State) ClockAt(now time.Time, start time.Time) Clock

ClockAt calcule les indicateurs à l'instant now (rules.PaceAt).

func (*State) Distributable

func (s *State) Distributable() float64

Distributable : ce qui reste à distribuer après la retenue d'organisation.

func (*State) EventFromAction

func (s *State) EventFromAction(a Action, now time.Time) (Event, error)

EventFromAction construit l'événement qui confirme une action proposée. Pour ActStartMatch, l'identifiant de match est attribué par le moteur (prochain numéro).

func (*State) Expected

func (s *State) Expected(n int) time.Duration

Expected : durée attendue d'un match de n points.

func (*State) FreeSlots

func (s *State) FreeSlots() []Slot

FreeSlots énumère les places d'exemption qu'un retardataire peut encore prendre, dans l'ordre du tableau. L'hôte les propose au TD ; la place choisie voyage dans l'événement d'inscription (PlayerAddedAtSlotEvent).

func (*State) Pool

func (s *State) Pool() float64

Pool : le pool brut, droit d'entrée fois nombre d'inscrits.

Tous les inscrits comptent, y compris ceux qui se sont retirés : ils ont payé. Un forfait n'est pas un remboursement, et le moteur n'a pas à en décider.

func (*State) PrizeAmounts

func (s *State) PrizeAmounts(section string) []float64

PrizeAmounts : les montants place par place d'une section, arrondis à l'unité (rules.PlaceAmounts).

Les pourcentages d'une affiche ne tombent presque jamais juste : 50/30/20 % de 432 € font 216, 129,60 et 86,40. Chaque place est payée à l'unité — personne ne paie en centimes à une table de tournoi — par la méthode du plus fort reste : 216, 129 et 86 tronqués, puis l'euro qui manque à la plus grande partie décimale, 0,60, d'où 216/130/86. La somme distribuée égale exactement le pool après retenue, aucun prix n'est négatif, et un barème décroissant donne des prix décroissants.

Des montants fixes (Amounts) ne sont ni arrondis ni ajustés : ils sont ce que l'organisateur a écrit. Une section sans dotation : nil.

func (*State) Propose

func (s *State) Propose() []Action

Propose renvoie les actions à effectuer maintenant. Le TD en confirme tout ou partie ; chaque confirmation devient un événement (voir State.EventFromAction). Déterministe pour un journal donné.

« Maintenant » est ici l'horodatage du dernier événement du journal : le moteur n'a pas d'horloge, et ne va pas s'en inventer une. Un hôte qui affiche un compte à rebours ou qui veut les avertissements de pause à sa propre heure appelle ProposeAt.

func (*State) ProposeAt

func (s *State) ProposeAt(now time.Time) []Action

ProposeAt est Propose à une heure donnée. Elle ne change que ce qui dépend du temps : les micro-rondes (l'échéance du prochain lot) et les pauses (la fin attendue d'un match proposé). Le reste — appariements, tirages, passage de phase — ne dépend que du journal.

Le rejeu n'en est pas affecté : ce qui est rejoué, ce sont les événements, pas les propositions. Deux hôtes dont les horloges diffèrent proposent les mêmes matchs, à des instants différents.

func (*State) ProposeWith

func (s *State) ProposeWith(now time.Time, ext External) []Action

ProposeWith est ProposeAt avec ce que l'hôte sait de l'extérieur (External). L'attribution des tables et la disponibilité des joueurs en dépendent ; les tirages et passages de phase restent fonction du seul journal.

func (*State) Ranking

func (s *State) Ranking() []Rank

Ranking renvoie le classement général courant : la phase en cours d'abord, puis les phases précédentes (joueurs éliminés plus tôt classés derrière), ex æquo conservés (rules.MergeRankings sur les rules.Rank de chaque phase).

func (*State) RulesMatches added in v0.6.0

func (s *State) RulesMatches() []rules.Match

RulesMatches : les matchs de l'état, dans l'ordre de lancement, tels que les règles pures les lisent. C'est l'entrée de rules.PlayingTime, rules.PaceAt, rules.Batches et rules.PairingSheet pour un hôte qui tient un State ; chaque ligne porte son libellé (Label), celui qu'une feuille imprime.

func (*State) RulesPhase added in v0.7.0

func (s *State) RulesPhase(i int) *rules.Phase

RulesPhase : l'instantané de la phase i tel que rules le lit (rules.Pair, rules.Rank, rules.RemainingMatches), pour un hôte qui tient un State et veut appeler les règles lui-même. nil si la phase n'existe pas. Les joueurs appariables d'un suisse (Free) sont ceux de l'instant du dernier événement.

func (*State) Running

func (s *State) Running() []*Match

Running renvoie les matchs en cours.

func (*State) SectionPrizes

func (s *State) SectionPrizes(section string) map[PlayerID]float64

SectionPrizes : ce que chaque joueur d'une section touche, ex æquo partagés.

func (*State) SectionRanking

func (s *State) SectionRanking(name string) []Rank

SectionRanking : le classement propre d'une section (main, conso, last, gf, un groupe GSL, une poule), et non le classement général du tournoi.

Une consolante a ses propres prix, donc son propre classement : celui de ses joueurs, par tour atteint DANS CETTE SECTION. Le classement général mélange les sections par priorité (un vainqueur de consolante passe derrière un demi-finaliste du principal) ; ici on ne regarde que la section, et le vainqueur de la consolante est premier de la consolante.

Enveloppe de rules.RankSection. La section est cherchée de la dernière phase vers la première : c'est la plus récente qui porte ce nom qui compte, un tournoi pouvant enchaîner deux tableaux.

func (*State) SlowMatches

func (s *State) SlowMatches(now time.Time, facteur float64) []*Match

SlowMatches : matchs en cours qui dépassent `facteur` fois leur durée attendue.

func (*State) StandingsCSV

func (s *State) StandingsCSV() []byte

StandingsCSV exporte le classement, une section par bloc.

La première colonne dit à quel classement la ligne appartient : « all » pour le classement général, puis l'identifiant de chaque section dotée (« main », « conso »…). Les blocs sont contigus et se lisent dans un tableur comme des tableaux séparés ; un hôte qui n'en veut qu'un filtre sur la colonne.

Les sections apparaissent parce qu'elles ont une DOTATION : sortir le classement de chaque groupe GSL d'un tournoi de cent joueurs noierait la feuille que le TD affiche au mur.

func (*State) StatusOf

func (s *State) StatusOf(p PlayerID) PlayerStatus

StatusOf donne le statut d'un joueur. Un identifiant inconnu est « non engagé ».

func (*State) Statuses

func (s *State) Statuses() []PlayerStatus

Statuses donne le statut de chaque inscrit, dans l'ordre d'inscription. Rien avant la création du tournoi.

func (*State) Step

func (s *State) Step(evs ...Event) ([]Action, error)

Apply-and-propose helpers for hosts: Step applique une liste d'événements puis propose.

type StatusKind

type StatusKind string

StatusKind est le code du statut d'un joueur.

const (
	// StatusPlaying : encore en course dans la phase en cours. Dans un tableau, Section, Round
	// et Label situent son prochain match.
	StatusPlaying StatusKind = "playing"
	// StatusBye : exempté — ne joue pas maintenant et entre plus tard. Tableau : il n'a encore
	// joué aucun match de la phase et le tirage (ou un forfait) l'a avancé jusqu'au match situé
	// par Section, Round (tour de la section, à partir de 1) et Label. Suisse par rondes : bye de
	// la ronde en cours ; il rejoue à la ronde Round (Label = LabelRound).
	StatusBye StatusKind = "bye"
	// StatusQualified : entre dans la phase Phase — la phase en cours est terminée et il fait
	// partie de ceux qui passent, ou le passage est fait et la phase n'est ni tirée ni commencée.
	// Un repêché (N26) est qualifié ; le retiré qu'il remplace ne l'est pas.
	StatusQualified StatusKind = "qualified"
	// StatusUndecided : son sort dans la phase Phase n'est pas encore fixé. Soit son parcours
	// dans la phase en cours est terminé mais Phase prend les N premiers (entrée top:N), connus à
	// la fin seulement ; soit il est candidat à un repêchage proposé que le TD n'a pas tranché.
	StatusUndecided StatusKind = "undecided"
	// StatusEliminated : plus aucun match à jouer dans le tournoi. Phase : la dernière phase
	// où il est entré.
	StatusEliminated StatusKind = "eliminated"
	// StatusWinner : la dernière phase est terminée (ou le tournoi clos) et il est premier.
	StatusWinner StatusKind = "winner"
	// StatusWithdrawn : retiré. Phase : la dernière phase où il est entré. Prime sur tout le
	// reste : un qualifié retiré n'est pas qualifié.
	StatusWithdrawn StatusKind = "withdrawn"
	// StatusNotEntered : inscrit, engagé dans aucune phase (un retardataire) ; State.Infos dit
	// où il entrera.
	StatusNotEntered StatusKind = "not_entered"
)

type TableRule

type TableRule struct {
	Table     int    `json:"table"`
	Section   string `json:"section,omitempty"` // identifiant de section (main, conso, poule:A…)
	Phase     int    `json:"phase,omitempty"`   // index de phase ; 0 = toutes
	AllPhases bool   `json:"all_phases,omitempty"`
}

TableRule réserve une table à une section ou à une phase. Une réservation vide des deux côtés ne réserve rien.

type Tables

type Tables struct {
	Count       int         `json:"count,omitempty"`
	Unavailable []int       `json:"unavailable,omitempty"` // plateau cassé, table retirée
	Reserved    []TableRule `json:"reserved,omitempty"`    // retransmission, table d'honneur
}

Tables décrit les tables de la salle. Count = 0 signifie « illimité » : le moteur numérote alors sans borne, ce qui reste utile pour une simulation.

func (Tables) AvailableFor

func (t Tables) AvailableFor(n int, section string, phase int) bool

AvailableFor : la table n peut accueillir un match de cette section et de cette phase. Une table réservée n'accueille que ce pour quoi elle est réservée ; les autres tables sont libres.

func (*Tables) UnmarshalJSON

func (t *Tables) UnmarshalJSON(b []byte) error

UnmarshalJSON accepte les deux formes : le nombre des configurations d'avant les exceptions, et la structure. Une configuration écrite `"tables": 12` reste donc lisible.

type TimeRange

type TimeRange struct {
	Start time.Time `json:"start"`
	End   time.Time `json:"end"`
}

TimeRange est un créneau de la journée : le repas, la remise des prix, la fermeture de la salle. Start incluse, End exclue.

type Warning

type Warning struct {
	Code      WarningCode `json:"code"`
	Match     MatchID     `json:"match,omitempty"`
	Section   string      `json:"section,omitempty"`
	Label     Label       `json:"label,omitempty"`
	A         PlayerID    `json:"a,omitempty"`
	B         PlayerID    `json:"b,omitempty"`
	ExpectedA PlayerID    `json:"expected_a,omitempty"`
	ExpectedB PlayerID    `json:"expected_b,omitempty"`
	Length    int         `json:"length,omitempty"`
	ScoreA    int         `json:"score_a,omitempty"`
	ScoreB    int         `json:"score_b,omitempty"`
	Player    PlayerID    `json:"player,omitempty"` // correction_* : le joueur concerné
	Table     int         `json:"table,omitempty"`  // table_shared : la table disputée
	Other     MatchID     `json:"other,omitempty"`  // table_shared : l'autre match sur la table
}

Warning est une incohérence : un code et de quoi la situer. Rien n'est bloqué par un avertissement — le TD décide (voir README, « le moteur propose, le TD décide »).

func (Warning) String

func (w Warning) String() string

String rend un avertissement en français.

type WarningCode

type WarningCode string

WarningCode est le code d'une incohérence signalée par le moteur.

const (
	// WarnBracketWrongPlayers : un match de graphe a été joué par d'autres joueurs que ceux
	// que la place attendait (typiquement après la correction d'un résultat antérieur).
	WarnBracketWrongPlayers WarningCode = "bracket_wrong_players"
	// WarnScoreOverLength : un score dépasse la longueur annoncée du match.
	WarnScoreOverLength WarningCode = "score_over_length"
	// WarnEndsInBreak : la fin attendue d'un match proposé tombe dans une pause.
	WarnEndsInBreak WarningCode = "ends_in_break"
	// WarnSlowMatch : un match en cours dépasse la durée attendue.
	WarnSlowMatch WarningCode = "slow_match"
	// WarnTableShared : deux matchs en cours sur la même table (Table), Match et Other. Le
	// déplacement qui l'a produit a été accepté : le moteur avertit, il ne bloque pas.
	// L'avertissement tombe dès que l'un des deux matchs se termine ou change de table.
	WarnTableShared WarningCode = "table_shared"
	// WarnCorrectionEliminatesRunning : dans un suisse, une correction (ou une annulation) a fait
	// tomber à 0 vie un joueur (Player) qui joue en ce moment le match Match. L'annulation de ce
	// match est proposée (cancel_match). Dérivé : tombe quand le match finit ou est annulé.
	WarnCorrectionEliminatesRunning WarningCode = "correction_eliminates_running"
	// WarnCorrectionRevives : dans un suisse, une correction (ou une annulation) a rendu une vie à
	// un joueur (Player) qu'elle avait éliminé. Il redevient appariable ; l'avertissement reste
	// jusqu'à ce qu'il rejoue, ou qu'il soit de nouveau éliminé.
	WarnCorrectionRevives WarningCode = "correction_revives"
)

Directories

Path Synopsis
cmd
tournoi-demo command
tournoi-demo : joue un tournoi fictif avec le moteur et écrit le journal, la page d'affichage et les SVG dans un dossier ; ou rejoue un journal existant et affiche ce que le TD doit faire.
tournoi-demo : joue un tournoi fictif avec le moteur et écrit le journal, la page d'affichage et les SVG dans un dossier ; ou rejoue un journal existant et affiche ce que le TD doit faire.
tournoi-td command
tournoi-td : console interactive de direction de tournoi pour tester le moteur à la main.
tournoi-td : console interactive de direction de tournoi pour tester le moteur à la main.
internal
dores
Package dores décrit les fichiers dorés des règles pures (rules/testdata) : leur forme JSON, leur lecture et leur réécriture.
Package dores décrit les fichiers dorés des règles pures (rules/testdata) : leur forme JSON, leur lecture et leur réécriture.
Package players : import léger de listes de joueurs (CSV HelloAsso, FFBG, tableur).
Package players : import léger de listes de joueurs (CSV HelloAsso, FFBG, tableur).
Package render produit des composants d'affichage (SVG, HTML) à partir d'un état de tournoi : arbres de tableaux, tableau des vies, appariements en cours, grille des tables, feuille d'appariements imprimable, classement, et une page autonome qui les rassemble.
Package render produit des composants d'affichage (SVG, HTML) à partir d'un état de tournoi : arbres de tableaux, tableau des vies, appariements en cours, grille des tables, feuille d'appariements imprimable, classement, et une page autonome qui les rassemble.
Package rules porte les règles de tournoi en fonctions pures : sans état, sans entrée ni sortie, sans horloge — l'instant et la graine sont des arguments — et sans autre dépendance que la bibliothèque standard.
Package rules porte les règles de tournoi en fonctions pures : sans état, sans entrée ni sortie, sans horloge — l'instant et la graine sont des arguments — et sans autre dépendance que la bibliothèque standard.
Package sim joue des tournois fictifs avec le moteur tournoi : résultats tirés au sort selon les PR, durées de match aléatoires.
Package sim joue des tournois fictifs avec le moteur tournoi : résultats tirés au sort selon les PR, durées de match aléatoires.

Jump to

Keyboard shortcuts

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