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:
team3
2026-07-23 06:25:23 +02:00
parent 76acd026ec
commit c0cd303eb0
7 changed files with 31 additions and 23 deletions

View File

@@ -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),
},

View File

@@ -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")

View File

@@ -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]

View File

@@ -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]:

View File

@@ -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: