Regeln nach Recherche korrigiert: Schadensformel, Odds L8/L9, Start-Level 2, Streak ab 2, Verkaufsregel
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,8 @@ def test_shop_odds_shape(c):
|
||||
|
||||
|
||||
def test_xp_reaches_max_level(c):
|
||||
assert len(c["xp"]["to_next"]) == c["xp"]["max_level"] - 1
|
||||
start = c["xp"].get("start_level", 1)
|
||||
assert len(c["xp"]["to_next"]) == c["xp"]["max_level"] - start
|
||||
assert all(x > 0 for x in c["xp"]["to_next"])
|
||||
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ def test_streak_gold(cfg):
|
||||
|
||||
|
||||
def test_apply_xp_levels_up(cfg):
|
||||
level, xp = economy.apply_xp(1, 0, 4, cfg)
|
||||
assert level == 3 # 1->2 costs 2, 2->3 costs 2
|
||||
level, xp = economy.apply_xp(2, 0, 8, cfg)
|
||||
assert level == 4 # 2->3 kostet 2, 3->4 kostet 6
|
||||
assert xp == 0
|
||||
level, _ = economy.apply_xp(9, 0, 1000, cfg)
|
||||
assert level == cfg["xp"]["max_level"]
|
||||
|
||||
@@ -6,6 +6,7 @@ from pydantic import BaseModel
|
||||
|
||||
from tft import paths
|
||||
from tft.model.score import active_trait_tiers
|
||||
from tft.sim import economy
|
||||
from tft.sim.game import Game, InvalidAction
|
||||
|
||||
app = FastAPI(title="tft-sim")
|
||||
@@ -150,8 +151,7 @@ def serialize(game: Game) -> dict:
|
||||
],
|
||||
"player": {
|
||||
"hp": game.hp, "gold": game.gold, "level": game.level, "xp": game.xp,
|
||||
"xp_needed": game.cfg["xp"]["to_next"][game.level - 1]
|
||||
if game.level < game.cfg["xp"]["max_level"] else None,
|
||||
"xp_needed": economy.xp_to_next(game.level, game.cfg),
|
||||
"streak": game.streak,
|
||||
"score": round(game.player_score(), 1),
|
||||
},
|
||||
|
||||
@@ -26,9 +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"]
|
||||
if len(xp["to_next"]) != xp["max_level"] - 1:
|
||||
start = xp.get("start_level", 1)
|
||||
if len(xp["to_next"]) != xp["max_level"] - start:
|
||||
raise ValueError(
|
||||
f"xp.to_next needs {xp['max_level'] - 1} entries, has {len(xp['to_next'])}"
|
||||
f"xp.to_next needs {xp['max_level'] - start} entries, has {len(xp['to_next'])}"
|
||||
)
|
||||
if len(c["shop"]["odds"]) != xp["max_level"]:
|
||||
raise ValueError("shop odds must have one row per level")
|
||||
|
||||
@@ -6,7 +6,7 @@ set = 17
|
||||
|
||||
[shop]
|
||||
# Odds pro Level: [1-cost, 2-cost, 3-cost, 4-cost, 5-cost], Summe = 100.
|
||||
# TODO: Quellen widersprechen sich für Level 5-9 (Patch 17.1b änderte Odds).
|
||||
# Level 1-7 mehrquellig bestätigt; L8/L9 = Zwei-Quellen-Mehrheit (metabot, redeemertft).
|
||||
odds = [
|
||||
[100, 0, 0, 0, 0], # Level 1
|
||||
[100, 0, 0, 0, 0], # Level 2
|
||||
@@ -15,8 +15,8 @@ odds = [
|
||||
[45, 33, 20, 2, 0], # Level 5
|
||||
[30, 40, 25, 5, 0], # Level 6
|
||||
[19, 30, 40, 10, 1], # Level 7
|
||||
[17, 24, 32, 24, 3], # Level 8
|
||||
[15, 18, 25, 30, 12],# Level 9
|
||||
[15, 20, 32, 30, 3], # Level 8
|
||||
[10, 17, 25, 33, 15],# Level 9
|
||||
[5, 10, 20, 40, 25], # Level 10
|
||||
]
|
||||
reroll_cost = 2
|
||||
@@ -29,8 +29,9 @@ copies_for_2star = 3
|
||||
copies_for_3star = 9
|
||||
|
||||
[xp]
|
||||
# XP zum nächsten Level, Index 0 = Level 1 -> 2.
|
||||
to_next = [2, 2, 6, 10, 20, 36, 60, 68, 68]
|
||||
# Spiel startet auf Level 2. XP zum nächsten Level, Index 0 = Level 2 -> 3.
|
||||
start_level = 2
|
||||
to_next = [2, 6, 10, 20, 36, 60, 68, 68]
|
||||
passive_per_round = 2
|
||||
buy_amount = 4
|
||||
buy_cost = 4
|
||||
@@ -43,15 +44,15 @@ early_income = { "1-2" = 2, "1-3" = 2, "1-4" = 3, "2-1" = 4 }
|
||||
interest_per_10 = 1
|
||||
interest_cap = 5
|
||||
win_bonus = 1
|
||||
# Streak-Gold: [min_streak, gold]
|
||||
streak_gold = [[3, 1], [5, 2], [6, 3]]
|
||||
# Streak-Gold: [min_streak, gold] — ab 2er-Streak +1 (tft-lab, op.gg)
|
||||
streak_gold = [[2, 1], [5, 2], [6, 3]]
|
||||
starting_gold = 0
|
||||
|
||||
[damage]
|
||||
# Spielerschaden = stage_base[stage] + per_surviving_unit * überlebende Units.
|
||||
# TODO: gegen aktuelle Werte prüfen.
|
||||
stage_base = [0, 0, 1, 2, 8, 15, 30, 40] # Index 0 = Stage 1
|
||||
per_surviving_unit = 2
|
||||
# Spielerschaden = stage_base[stage] + 1 pro überlebender Unit (flach, seit Set 11).
|
||||
# Mehrquellig bestätigt: tft.ninja, op.gg, dotesports.
|
||||
stage_base = [0, 2, 5, 8, 10, 12, 17, 17] # Index 0 = Stage 1
|
||||
per_surviving_unit = 1
|
||||
player_hp = 100
|
||||
|
||||
[rounds]
|
||||
|
||||
@@ -24,10 +24,10 @@ def round_income(gold: int, streak: int, won: bool, round_label: str, cfg: dict)
|
||||
|
||||
|
||||
def xp_to_next(level: int, cfg: dict) -> int | None:
|
||||
to_next = cfg["xp"]["to_next"]
|
||||
if level >= cfg["xp"]["max_level"]:
|
||||
xp = cfg["xp"]
|
||||
if level >= xp["max_level"]:
|
||||
return None
|
||||
return to_next[level - 1]
|
||||
return xp["to_next"][level - xp.get("start_level", 1)]
|
||||
|
||||
|
||||
def apply_xp(level: int, xp: int, gained: int, cfg: dict) -> tuple[int, int]:
|
||||
|
||||
@@ -28,7 +28,7 @@ class Game:
|
||||
|
||||
self.hp = cfg["damage"]["player_hp"]
|
||||
self.gold = cfg["gold"]["starting_gold"]
|
||||
self.level = 1
|
||||
self.level = cfg["xp"].get("start_level", 1)
|
||||
self.xp = 0
|
||||
self.board: list[dict] = []
|
||||
self.bench: list[dict] = []
|
||||
@@ -133,7 +133,12 @@ class Game:
|
||||
u = units.pop(idx)
|
||||
copies = 3 ** (u["stars"] - 1)
|
||||
self.pool.put_back(u["api_name"], copies)
|
||||
self.gold += self.artifact["static"]["units"][u["api_name"]]["cost"] * copies
|
||||
cost = self.artifact["static"]["units"][u["api_name"]]["cost"]
|
||||
refund = cost * copies
|
||||
# Ab 2 Sternen und Kosten >= 2 gilt -1 Gold; 1-Coster immer voller Wert.
|
||||
if cost > 1 and u["stars"] >= 2:
|
||||
refund -= 1
|
||||
self.gold += refund
|
||||
self.items.extend(u["items"])
|
||||
|
||||
def reroll(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user