XP-System korrigiert: Level 2 gratis nach erster Welle, 1-1-Carousel-XP banken
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,8 +19,8 @@ def test_shop_odds_shape(c):
|
||||
|
||||
|
||||
def test_xp_reaches_max_level(c):
|
||||
start = c["xp"].get("start_level", 1)
|
||||
assert len(c["xp"]["to_next"]) == c["xp"]["max_level"] - start
|
||||
# Level 1 -> 2 ist gratis; to_next deckt 2->3 bis 9->10.
|
||||
assert len(c["xp"]["to_next"]) == c["xp"]["max_level"] - 2
|
||||
assert all(x > 0 for x in c["xp"]["to_next"])
|
||||
|
||||
|
||||
|
||||
@@ -192,3 +192,17 @@ def test_pve_freezes_streak_and_pays_no_win_bonus(art, cfg):
|
||||
while game.round["stage"] == 1 and not game.over:
|
||||
game.step()
|
||||
assert game.player.streak == 0
|
||||
|
||||
|
||||
def test_natural_level_progression(art, cfg):
|
||||
"""Ohne XP-Kauf: Level 1 bei 1-2, Level 4 bei 2-1, Level 5 mit 6/20 bei 3-2."""
|
||||
game = Game(art, cfg, seed=6)
|
||||
assert game.player.level == 1 and game.player.xp == 2 # 1-1-XP gebankt
|
||||
checks = {}
|
||||
while not game.over and game.round["label"] != "3-3":
|
||||
checks[game.round["label"]] = (game.player.level, game.player.xp)
|
||||
game.step()
|
||||
assert checks["1-2"][0] == 1
|
||||
assert checks["1-3"][0] == 3 # gratis Level 2 + kaskadierte XP
|
||||
assert checks["2-1"] == (4, 0)
|
||||
assert checks["3-2"] == (5, 6)
|
||||
|
||||
@@ -26,10 +26,10 @@ def validate(c: dict) -> None:
|
||||
if len(c["pool"]["sizes"]) != 5:
|
||||
raise ValueError("pool sizes must have 5 cost tiers")
|
||||
xp = c["xp"]
|
||||
start = xp.get("start_level", 1)
|
||||
if len(xp["to_next"]) != xp["max_level"] - start:
|
||||
# Level 1 -> 2 ist gratis (erste Welle); to_next deckt 2->3 bis (max-1)->max.
|
||||
if len(xp["to_next"]) != xp["max_level"] - 2:
|
||||
raise ValueError(
|
||||
f"xp.to_next needs {xp['max_level'] - start} entries, has {len(xp['to_next'])}"
|
||||
f"xp.to_next needs {xp['max_level'] - 2} entries, has {len(xp['to_next'])}"
|
||||
)
|
||||
if len(c["shop"]["odds"]) != xp["max_level"]:
|
||||
raise ValueError("shop odds must have one row per level")
|
||||
|
||||
@@ -29,10 +29,10 @@ copies_for_2star = 3
|
||||
copies_for_3star = 9
|
||||
|
||||
[xp]
|
||||
# Start auf Level 1; die 2 Passiv-XP nach 1-2 heben auf Level 2.
|
||||
# to_next: Index 0 = Level 1 -> 2.
|
||||
# Start auf Level 1. Level 2 kommt GRATIS nach der ersten PvE-Welle (1-2),
|
||||
# XP banken solange. to_next: Index 0 = Level 2 -> 3.
|
||||
start_level = 1
|
||||
to_next = [2, 2, 6, 10, 20, 36, 60, 68, 68]
|
||||
to_next = [2, 6, 10, 20, 36, 60, 68, 68]
|
||||
passive_per_round = 2
|
||||
buy_amount = 4
|
||||
buy_cost = 4
|
||||
|
||||
@@ -27,7 +27,9 @@ def xp_to_next(level: int, cfg: dict) -> int | None:
|
||||
xp = cfg["xp"]
|
||||
if level >= xp["max_level"]:
|
||||
return None
|
||||
return xp["to_next"][level - xp.get("start_level", 1)]
|
||||
if level < 2:
|
||||
return None # Level 2 kommt gratis nach der ersten PvE-Welle
|
||||
return xp["to_next"][level - 2]
|
||||
|
||||
|
||||
def apply_xp(level: int, xp: int, gained: int, cfg: dict) -> tuple[int, int]:
|
||||
|
||||
@@ -44,9 +44,11 @@ class Game:
|
||||
]
|
||||
|
||||
# 1-1-Carousel läuft automatisch für alle ab, dann startet 1-2.
|
||||
# Die 2 Passiv-XP aus 1-1 banken (Level 2 kommt erst nach 1-2 gratis).
|
||||
if self.round["kind"] == "carousel":
|
||||
for p in self.players:
|
||||
self._carousel(p)
|
||||
p.xp += cfg["xp"]["passive_per_round"]
|
||||
self.idx += 1
|
||||
# Bei 1-2 gibt es noch keinen Shop — niemand hat Gold.
|
||||
for p in self.players:
|
||||
@@ -154,6 +156,10 @@ class Game:
|
||||
p.level, p.xp = economy.apply_xp(
|
||||
p.level, p.xp, self.cfg["xp"]["passive_per_round"], self.cfg
|
||||
)
|
||||
# Nach der ersten PvE-Welle gibt es Level 2 gratis; gebankte XP kaskadieren.
|
||||
if rnd["label"] == "1-2" and p.level == 1:
|
||||
p.level = 2
|
||||
p.level, p.xp = economy.apply_xp(p.level, p.xp, 0, self.cfg)
|
||||
|
||||
self._advance()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user