Aller au contenu

Résultats et Scoring

Vue d'ensemble

Le système de scoring de Primatch permet la saisie, la validation croisée et l'enregistrement des résultats des parties de padel. Il gère le calcul automatique des niveaux des joueurs après validation.

Cycle de vie du score

stateDiagram-v2
    [*] --> EnCours: Partie démarre
    EnCours --> ScoreSoumis: Un joueur saisit le score
    ScoreSoumis --> EnValidation: Notification aux participants
    EnValidation --> ScoreValidé: Un joueur de chaque équipe confirme
    EnValidation --> Contesté: Un joueur conteste
    Contesté --> ScoreSoumis: Nouveau score proposé
    EnValidation --> ScoreValidé: Délai 24h dépassé (auto-validation)
    ScoreValidé --> [*]: Niveau mis à jour

Saisie du score

Qui peut saisir ?

N'importe quel joueur participant à la partie peut saisir le score une fois la partie terminée (statut finished).

Format du score

Le score est saisi par set :

Champ Type Description
team_a_score integer Score de l'équipe A pour le set
team_b_score integer Score de l'équipe B pour le set
set_number integer Numéro du set (1, 2 ou 3)

Le nombre de sets correspond à la configuration choisie lors de la création de la partie (sets_count).

Règles de validation

  • Les scores doivent être des entiers positifs
  • Le nombre de sets soumis doit correspondre au sets_count de la partie
  • L'équipe gagnante est déterminée automatiquement (majorité des sets gagnés)

Validation croisée

Processus

  1. Soumission : Un joueur soumet le score → notification envoyée aux 3 autres joueurs
  2. Validation : Chaque joueur peut :
  3. Confirmer le score proposé
  4. Contester en proposant un score différent
  5. Résolution :
  6. Si un joueur de chaque équipe confirme → score validé
  7. Si un joueur conteste → nouveau cycle avec le score alternatif
  8. Si pas de réponse sous 24 heures → auto-validation

Endpoint API

POST   /api/v1/games/{game}/score          # Soumettre un score
GET    /api/v1/games/{game}/score           # Voir le score
POST   /api/v1/games/{game}/score/validate  # Valider le score
POST   /api/v1/games/{game}/score/contest   # Contester le score

Impact sur le niveau

Parties compétitives uniquement

Seules les parties de type competitive impactent le niveau des joueurs. Les parties friendly n'ont aucun effet sur le niveau mais contribuent quand même au score de fiabilité.


Algorithme de progression des niveaux

Phase 1 — Évaluation initiale (quiz d'inscription)

À l'inscription, un quiz de 3 questions détermine le niveau de départ du joueur :

Question Options Points attribués
Expérience (années de padel) Jamais / < 6 mois / 6m–2 ans / 2–5 ans / 5 ans+ 0, 1, 3, 5, 7
Fréquence (par mois) < 1 / 1–3 / 4–8 / 8+ 0, 1, 2, 3
Compétition Loisir / P100 / P250 / P500+ 0, 1, 3, 5

Le score total (0 à 15) est mappé vers l'un des 14 niveaux disponibles via un tableau de correspondance fixe :

Score 0–1   → Niveau 1.0 (Débutant)
Score 2     → Niveau 2.0 (Perfectionnement)
Score 3     → Niveau 2.5 (Perfectionnement+)
Score 4     → Niveau 3.0 (Élémentaire)
Score 5     → Niveau 3.5 (Élémentaire+)
Score 6     → Niveau 4.0 (Intermédiaire)
Score 7     → Niveau 4.5 (Intermédiaire+)
Score 8     → Niveau 5.0 (Confirmé)
Score 9     → Niveau 5.5 (Confirmé+)
Score 10    → Niveau 6.0 (Avancé)
Score 11    → Niveau 6.5 (Avancé+)
Score 12    → Niveau 7.0 (Expert)
Score 13    → Niveau 7.5 (Expert+)
Score 14-15 → Niveau 8.0 (Élite)

Fiabilité initiale

Après le quiz, la fiabilité démarre à 20% — c'est une auto-déclaration, pas des données réelles de jeu. Elle augmente au fil des parties.


Phase 2 — Formule de progression (parties compétitives)

Le changement de niveau après une partie compétitive est calculé par la méthode calculateLevelChange() dans ScoreService :

$$\Delta_{\text{niveau}} = \text{baseFactor} \times \text{surpriseFactor} \times \text{scoreAmplifier}$$

$$\text{nouveauNiveau} = \max(1.0,\ \min(10.0,\ \text{ancienNiveau} + \Delta_{\text{niveau}}))$$

Facteur 1 — Base (baseFactor)

Détermine le sens du changement selon le résultat :

$$\text{baseFactor} = \begin{cases} +0.15 & \text{victoire} \ -0.10 & \text{défaite} \end{cases}$$

Asymétrie volontaire

Gagner rapporte plus (+0.15) que perdre ne coûte (-0.10). Cette asymétrie encourage les joueurs à affronter des équipes plus fortes sans craindre une pénalité disproportionnée.

Facteur 2 — Surprise (surpriseFactor)

Amplifie le gain quand on bat une équipe plus forte, et réduit le gain quand on bat une équipe plus faible :

$$\text{levelDiff} = \text{moyNiveau}{adversaires} - \text{moyNiveau}$$

$$\text{surpriseFactor} = 1.0 + (\text{levelDiff} \times 0.1)$$

Situation levelDiff surpriseFactor Effet
Adversaires +3 niveaux +3.0 ×1.30 +30%
Adversaires +2 niveaux +2.0 ×1.20 +20%
Adversaires +1 niveau +1.0 ×1.10 +10%
Niveaux égaux 0.0 ×1.00 neutre
Adversaires −1 niveau −1.0 ×0.90 −10%
Adversaires −2 niveaux −2.0 ×0.80 −20%
Adversaires −3 niveaux −3.0 ×0.70 −30%

Comportement lors d'une défaite

Contrairement à l'ELO (où perdre contre plus fort coûte peu), perdre contre une équipe beaucoup plus forte coûte plus cher dans Primatch. Si le système détecte régulièrement cette situation, c'est un signal que le niveau initial du joueur était surévalué.

Facteur 3 — Amplificateur de score (scoreAmplifier)

Amplifie le changement selon la marge de victoire ou de défaite :

$$\text{scoreDiff} = \frac{|\text{score}_A - \text{score}_B|}{\text{score}_A + \text{score}_B}$$

$$\text{scoreAmplifier} = 1.0 + (\text{scoreDiff} \times 0.3)$$

Résultat Ratio scoreAmplifier Effet
6–0 (domination) 1.00 ×1.30 +30%
6–3 0.33 ×1.10 +10%
7–5 0.09 ×1.03 quasi neutre
6–5 (très serré) 0.09 ×1.03 quasi neutre

Le score de la partie amplifie le changement — une victoire écrasante accélère la montée (ou la descente), un match très serré a peu d'impact quel que soit le résultat.


Exemples concrets

Scénario baseFactor surpriseFactor scoreAmplifier Δ niveau
Victoire, niveaux égaux, 6–0 +0.15 ×1.00 ×1.30 +0.20
Victoire, adversaires +2 niveaux, 7–5 +0.15 ×1.20 ×1.03 +0.19
Victoire, adversaires −2 niveaux, 6–3 +0.15 ×0.80 ×1.10 +0.13
Défaite, niveaux égaux, 3–6 −0.10 ×1.00 ×1.30 −0.13
Défaite, adversaires +3 niveaux, 5–6 −0.10 ×1.30 ×1.03 −0.13
Défaite, adversaires −2 niveaux, 0–6 −0.10 ×0.80 ×1.30 −0.10

Bornes du niveau

Le niveau est toujours borné entre 1.0 et 10.0 :

$newLevel = max(1.0, min(10.0, $currentLevel + $delta));

Un joueur ne peut pas descendre en dessous de 1.0 ni monter au-dessus de 10.0 (même si l'échelle des niveaux va jusqu'à 8.0 dans le seeder, la plage technique est 1–10).


Score de fiabilité

Le score de fiabilité mesure la confiance qu'on peut accorder au niveau affiché.

Règle Effet
Partie validée (compétitive ou amicale) +2% de fiabilité
Plafond 100%
Initial (après quiz) 20%
Initial (autres parcours) 50%

Un joueur avec 40 parties validées atteint 100% de fiabilité (20% + 40 × 2%). La fiabilité est destinée à être affichée publiquement pour indiquer si le niveau est "certifié terrain" ou basé sur une auto-déclaration.

Lecture de la fiabilité

"Niveau 4.5 — fiabilité 35%" → le niveau vient principalement du quiz + quelques parties.
"Niveau 4.5 — fiabilité 90%" → niveau consolidé sur ~35+ parties réelles.

Notifications liées au scoring

Événement Canal Description
Score soumis In-app Notification aux 3 autres joueurs
Score validé par un joueur In-app Confirmation
Score contesté In-app Nouveau cycle de validation
Score finalisé In-app Résultat définitif + nouveau niveau
Rappel validation In-app Rappel avant expiration des 24h