complete regen of hero classes, spells, races, etc
This commit is contained in:
73
app/game/generators/level_progression.py
Normal file
73
app/game/generators/level_progression.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from bisect import bisect_right
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LevelThreshold:
|
||||
level: int
|
||||
xp: int # cumulative XP required to *reach* this level
|
||||
rewards: Dict[str, Any] # optional; can be empty
|
||||
|
||||
class LevelProgression:
|
||||
"""
|
||||
Provides XP<->Level mapping. Can be built from a parametric curve or YAML-like dict.
|
||||
"""
|
||||
def __init__(self, thresholds: List[LevelThreshold]):
|
||||
# Normalize, unique-by-level, sorted
|
||||
uniq = {}
|
||||
for t in thresholds:
|
||||
uniq[t.level] = LevelThreshold(level=t.level, xp=int(t.xp), rewards=(t.rewards or {}))
|
||||
levels = sorted(uniq.values(), key=lambda x: x.level)
|
||||
if not levels or levels[0].level != 1 or levels[0].xp != 0:
|
||||
raise ValueError("Progression must start at level 1 with xp=0")
|
||||
object.__setattr__(self, "_levels", levels)
|
||||
object.__setattr__(self, "_xp_list", [t.xp for t in levels])
|
||||
object.__setattr__(self, "_level_list", [t.level for t in levels])
|
||||
|
||||
@classmethod
|
||||
def from_curve(cls, max_level: int = 100, base_xp: int = 100, growth: float = 1.45) -> "LevelProgression":
|
||||
"""
|
||||
Exponential-ish cumulative thresholds:
|
||||
XP(level L) = round(base_xp * ((growth^(L-1) - 1) / (growth - 1)))
|
||||
Ensures level 1 => 0 XP.
|
||||
"""
|
||||
thresholds: List[LevelThreshold] = []
|
||||
for L in range(1, max_level + 1):
|
||||
if L == 1:
|
||||
xp = 0
|
||||
else:
|
||||
# cumulative sum of geometric series
|
||||
xp = round(base_xp * ((growth ** (L - 1) - 1) / (growth - 1)))
|
||||
thresholds.append(LevelThreshold(level=L, xp=xp, rewards={}))
|
||||
return cls(thresholds)
|
||||
|
||||
# --------- Queries ----------
|
||||
@property
|
||||
def max_level(self) -> int:
|
||||
return self._level_list[-1]
|
||||
|
||||
def xp_for_level(self, level: int) -> int:
|
||||
if level < 1: return 0
|
||||
if level >= self.max_level: return self._xp_list[-1]
|
||||
# Levels are dense and 1-indexed
|
||||
idx = level - 1
|
||||
return self._xp_list[idx]
|
||||
|
||||
def level_for_xp(self, xp: int) -> int:
|
||||
"""
|
||||
Find the highest level where threshold.xp <= xp. Levels are dense.
|
||||
"""
|
||||
if xp < 0: return 1
|
||||
idx = bisect_right(self._xp_list, xp) - 1
|
||||
if idx < 0: return 1
|
||||
return self._level_list[idx]
|
||||
|
||||
def rewards_for_level(self, level: int) -> Dict[str, Any]:
|
||||
if level < 2: return {}
|
||||
if level > self.max_level: level = self.max_level
|
||||
return self._levels[level - 1].rewards or {}
|
||||
|
||||
LEVEL_CURVE = 1.25
|
||||
DEFAULT_LEVEL_PROGRESSION = LevelProgression.from_curve(growth=LEVEL_CURVE)
|
||||
Reference in New Issue
Block a user