dd-timer/algorithmes.md

9.8 KiB
Raw Permalink Blame History

Algorithmes de calcul — Minuteur Dragodinde

Ce document décrit tous les algorithmes utilisés dans l'application, expliqués simplement.


1. Système de tiers des jauges

Chaque jauge (baffeur, caresseur, foudroyeur, abreuvoir, dragofesse, mangeoire) a un niveau entre 0 et 100 000. Ce niveau détermine un tier qui fixe la vitesse de conversion :

Niveau de jauge Tier Taux (pts / 10 sec)
0 40 000 1 10
40 001 70 000 2 20
70 001 90 000 3 30
90 001 100 000 4 40

Principe : La jauge se vide en donnant des points à la DD. Plus la jauge est haute, plus elle se vide vite (tier élevé = plus de points par tick).

Un "tick" = 10 secondes de timer.


2. Calcul des points gagnés dans le temps — gainedIn(lvl, sec)

Question : "Si ma jauge démarre au niveau lvl et tourne pendant sec secondes, combien de points ma DD gagne-t-elle ?"

Algorithme :

  1. On part du haut (tier 4) et on descend
  2. Pour chaque palier, on calcule combien de ticks on peut consommer avant de passer au palier en-dessous
  3. On additionne les points de chaque palier traversé

Exemple : Jauge à 95 000, timer 60 sec (= 6 ticks)

  • Tier 4 (90k100k) : 5 000 pts dispo ÷ 40/tick = 125 ticks possibles → on en utilise 6 → gain = 6 × 40 = 240 pts
  • Jauge restante : 95 000 - 240 = 94 760

La jauge descend au fur et à mesure qu'elle donne des points. Quand elle passe sous un seuil de tier, le taux ralentit.


3. Temps pour gagner X points — timeToGain(lvl, pts)

Question : "Combien de temps faut-il pour gagner pts points depuis une jauge au niveau lvl ?"

C'est l'inverse de gainedIn. On parcourt les paliers du haut vers le bas :

  1. Pour chaque tier, combien de points peut-on donner avant de descendre au palier suivant ?
  2. On prend le minimum entre les points disponibles et les points restants à donner
  3. On calcule le temps correspondant : ceil(points / taux) × 10 sec

Si la jauge se vide complètement avant d'atteindre l'objectif → retourne Infinity (impossible).


4. Niveau de jauge après X secondes — gaugeAfter(lvl, sec)

Question : "Si ma jauge démarre à lvl et tourne sec secondes, à quel niveau sera-t-elle ?"

Même logique que gainedIn, mais au lieu de compter les points donnés, on soustrait directement du niveau de la jauge.


5. Temps écoulé — elapsed(enc)

Calcule les secondes écoulées depuis le démarrage du timer d'un enclos, en excluant les pauses :

Si en cours     : (maintenant - démarrage - temps_en_pause) / 1000
Si en pause     : (moment_pause - démarrage - temps_en_pause) / 1000
Si non démarré  : 0

6. Calcul unifié d'une jauge — computeGaugeLive(enc, dd, gid, el, started)

C'est le cœur du système. Pour chaque DD et chaque jauge active, il calcule en temps réel :

  • La stat estimée actuelle
  • Si la cible est atteinte
  • Le % de progression
  • Le countdown restant

Pour les jauges normales (sérénité, endurance, maturité, amour) :

  1. On récupère le snapshot de la jauge et de la stat au moment du démarrage du timer
  2. On calcule les points gagnés depuis le snapshot : gainedIn(snapshotJauge, tempsÉcoulé)
  3. On applique la direction (dir) :
    • dir = +1 (caresseur, foudroyeur, abreuvoir, dragofesse) : stat monte
    • dir = -1 (baffeur) : stat descend
  4. On clamp la stat dans ses bornes (ex: sérénité entre -5000 et +5000)
  5. On calcule le temps total et le countdown via timeToGain

Pour la mangeoire (XP) — modèle spécial :

L'XP utilise un modèle linéaire constant car le joueur est supposé recharger la jauge manuellement :

  1. On détermine le taux fixe = tierRate(snapshotJauge) au démarrage
  2. XP gagnée = nombre_de_ticks × taux (pas de dégression)
  3. XP nécessaire = xpForLevel(cible) - xpForLevel(départ)
  4. Countdown = ceil(xp_restante / taux) × 10 sec

Pourquoi un modèle différent ? Parce que pour les stats, la jauge se vide naturellement. Pour l'XP, le joueur remet des croquettes (recharge la mangeoire), donc le taux reste constant.


7. Table d'XP et niveaux

Table XP_RAW

Dictionnaire de 200 entrées : niveau → XP cumulatif total.

Exemples :

  • Niveau 1 → 0 XP
  • Niveau 10 → 809 XP
  • Niveau 50 → 34 365 XP
  • Niveau 100 → 172 668 XP
  • Niveau 200 → 867 582 XP

Les valeurs sont cumulatives (pas incrémentales).

xpForLevel(lvl) : Retourne l'XP cumulatif pour atteindre le niveau lvl.

levelFromXp(xp) : Retourne le plus haut niveau atteint avec xp points d'XP cumulatifs. Parcourt la table de 200 à 1 pour trouver le seuil.


8. ETA Sérénité — calcSerenEta / calcSerenEtaLive

Version statique (calcSerenEta)

  1. Calcule diff = cible - sérénité actuelle
  2. Si diff > 0 → besoin du caresseur (monte la sérénité)
  3. Si diff < 0 → besoin du baffeur (baisse la sérénité)
  4. Temps = timeToGain(niveauJauge, |diff|)

Version live (calcSerenEtaLive)

  1. Utilise computeGaugeLive pour obtenir la sérénité estimée en temps réel
  2. Recalcule le diff depuis cette estimation
  3. Calcule le temps restant avec les points encore à parcourir

9. ETA Niveau — calcLevelEta / calcLevelEtaLive

Version statique (calcLevelEta)

  1. XP nécessaire = xpForLevel(cible) - xpForLevel(niveauActuel)
  2. Taux fixe = tierRate(niveauMangeoire)
  3. Temps = ceil(xpNécessaire / taux) × 10 sec

Version live (calcLevelEtaLive)

  1. Utilise computeGaugeLive pour obtenir le niveau estimé actuel
  2. XP restante = xpForLevel(cible) - xpForLevel(niveauEstimé)
  3. Taux fixe = tierRate(snapshotMangeoire)
  4. Countdown = ceil(xpRestante / taux) × 10 sec

10. Countdown global d'un enclos — enclosGlobalState(enc)

Question : "Dans combien de temps TOUTES les DD de cet enclos auront atteint TOUTES leurs cibles ?"

  1. Pour chaque DD × chaque jauge active → appeler computeGaugeLive
  2. Prendre le maximum de tous les countdowns = le plus long à terminer
  3. Compter combien de DD ont TOUTES leurs cibles atteintes (ddDone)
  4. allDone = true si toutes les DD sont terminées

11. Arbre de réapprovisionnement — calcAppro()

Question : "Pour produire Q exemplaires de la race X, de quelles races et en quelles quantités ai-je besoin ?"

Principe : décomposition récursive par génération

Chaque race de génération ≥ 2 est produite par le croisement de 2 races parentes (table BREEDING_RECIPES).

Algorithme :

  1. On part de la race cible et de la quantité voulue
  2. Pour chaque génération (de la plus haute à gen 2) :
    • Pour chaque race nécessaire à cette génération :
      • Calculer le nombre de couples nécessaires (voir §11.1)
      • Ajouter les parents nécessaires (chacun × nombre de couples) dans le pool
  3. Les races gen 1 restantes = matières premières

11.1 Mécanisme du reproducteur

Un reproducteur est une DD réutilisable : elle peut faire plusieurs bébés.

Si 2×R ≥ Q  →  couples = ceil(Q / 2)
Sinon       →  couples = Q - R

R = nombre de reproducteurs, Q = quantité nécessaire.

Explication :

  • Si on a assez de reproducteurs (≥ moitié de Q), chaque reproducteur fait 2 bébés → on divise par 2
  • Sinon, chaque reproducteur fait sa part et les autres sont des "one-shot"

12. Calcul d'inventaire avec contraintes ♂/♀ — calcInventaire()

Question : "Avec mon stock actuel de DD (mâles et femelles), quels croisements puis-je réaliser ?"

Modèle de données

Chaque race dans l'inventaire : { m: mâles, f: femelles, n: neutres }

  • m et f = stock réel renseigné par le joueur
  • n = bébés produits par le calcul (sexe inconnu)

Contrainte fondamentale

Un croisement nécessite 1 mâle + 1 femelle. On ne peut pas croiser ♂+♂ ou ♀+♀.

Algorithme : round-robin par génération

Pour chaque génération (2 → 10) :

  1. Lister tous les croisements possibles à cette génération
  2. Boucle round-robin : tant qu'au moins un croisement est possible :
    • Pour chaque croisement [Parent A + Parent B → Bébé] :
      • Vérifier qu'on a 1 mâle-capable chez A ET 1 femelle-capable chez B (ou l'inverse)
      • Si même race (A = B) : vérifier qu'on a ≥ 2 individus dont au moins 1 ♂ et 1 ♀
      • Consommer les parents (priorité au stock réel m/f, puis les neutres n)
      • Ajouter 1 neutre (n++) à la race du bébé produit
  3. Les bébés produits (neutres) sont disponibles pour les générations suivantes

Priorité de consommation

takeMale   : si m > 0 → m--, sinon n--
takeFemale : si f > 0 → f--, sinon n--

Les neutres (n) peuvent jouer le rôle de ♂ ou ♀ selon le besoin.

Pourquoi round-robin ?

Pour répartir équitablement les ressources entre les croisements possibles, plutôt que de tout donner au premier croisement trouvé.


13. Constantes des stats

Stat Min Max Jauge(s) associée(s)
Sérénité -5 000 5 000 Baffeur (↓), Caresseur (↑)
Endurance 0 20 000 Foudroyeur (↑)
Maturité 0 20 000 Abreuvoir (↑)
Amour 0 20 000 Dragofesse (↑)
Niveau/XP 1 200 Mangeoire (↑)

14. Système de snapshots

Quand le timer démarre, on prend un snapshot de l'état :

  • snapGauges : niveaux de toutes les jauges au moment du démarrage
  • snapStats[dd.id] : stats de chaque DD au moment du démarrage

Tous les calculs utilisent ces snapshots comme point de départ, pas l'état en temps réel. Cela évite les dérives de calcul si les valeurs sont modifiées pendant que le timer tourne.