Shop-Leiste im TFT-Stil: Cards mit Splash, Trait-Pills, Shine, Upgrade-Sterne, Lock, Gold/Streak-Pills

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
team3
2026-07-23 05:47:52 +02:00
parent d191af34ca
commit 50c3f2857e
6 changed files with 214 additions and 45 deletions

View File

@@ -59,3 +59,19 @@ def test_state_shape(client):
assert key in state
assert state["player"]["score"] == 0.0
assert all(o["board"] is not None for o in state["opponents"])
def test_upgrade_hint():
from tft.api.app import upgrade_hint
from tft.sim.game import Game
static = make_static()
static["meta"]["patch"] = "t"
game = Game(artifact_mod.build(static), load_constants(17), seed=1)
assert upgrade_hint(game, "U1_0") == 0
game.bench = [{"api_name": "U1_0", "stars": 1, "items": []} for _ in range(2)]
assert upgrade_hint(game, "U1_0") == 2
game.bench = [{"api_name": "U1_0", "stars": 2, "items": []} for _ in range(2)] + \
[{"api_name": "U1_0", "stars": 1, "items": []} for _ in range(2)]
assert upgrade_hint(game, "U1_0") == 3
game.bench = [{"api_name": "U1_0", "stars": 3, "items": []}]
assert upgrade_hint(game, "U1_0") == 0

View File

@@ -79,3 +79,11 @@ def test_invalid_actions_raise(art, cfg):
game.buy_xp()
with pytest.raises(InvalidAction):
game.sell("bench", 0)
def test_shop_lock_survives_step(art, cfg):
game = Game(art, cfg, seed=2)
game.toggle_lock()
before = list(game.shop)
game.step()
assert game.shop == before

View File

@@ -75,6 +75,8 @@ def action(game_id: str, req: ActionRequest) -> dict:
game.equip(req.item_idx, req.board_idx)
elif req.action == "pick_augment":
game.pick_augment(req.choice)
elif req.action == "lock":
game.toggle_lock()
elif req.action == "next":
game.step()
else:
@@ -86,6 +88,18 @@ def action(game_id: str, req: ActionRequest) -> dict:
return serialize(game)
def upgrade_hint(game: Game, api: str) -> int:
"""0 = kein Upgrade, 2/3 = Kauf ergibt einen 2-/3-Sterner."""
owned = [u for u in game.board + game.bench
if u["api_name"] == api and u["stars"] < 3]
copies = sum(3 ** (u["stars"] - 1) for u in owned)
if copies + 1 >= 9:
return 3
if sum(1 for u in owned if u["stars"] == 1) == 2:
return 2
return 0
def unit_view(game: Game, u: dict) -> dict:
info = game.artifact["static"]["units"][u["api_name"]]
return {
@@ -148,12 +162,17 @@ def serialize(game: Game) -> dict:
"api_name": api,
"name": static["units"][api].get("name", api),
"cost": static["units"][api]["cost"],
"traits": [static["traits"][t].get("name", t) for t in static["units"][api]["traits"]
if t in static["traits"]],
"traits": [
{"name": static["traits"][t].get("name", t),
"icon": static["traits"][t].get("icon")}
for t in static["units"][api]["traits"] if t in static["traits"]
],
"icon": static["units"][api].get("icon"),
"upgrade": upgrade_hint(game, api),
}
for api in game.shop
],
"shop_locked": game.shop_locked,
"traits": traits_panel,
"augment_offer": [
{"api_name": a, "name": static["augments"].get(a, {}).get("name", a),

View File

@@ -36,6 +36,7 @@ class Game:
self.augments: list[str] = []
self.streak = 0
self.shop: list[str | None] = []
self.shop_locked = False
self.augment_offer: list[str] = []
self.placement: int | None = None
self.log: list[str] = []
@@ -166,6 +167,9 @@ class Game:
raise InvalidAction("unit has 3 items")
self.board[board_idx]["items"].append(self.items.pop(item_idx))
def toggle_lock(self) -> None:
self.shop_locked = not self.shop_locked
def pick_augment(self, choice: int) -> None:
if not self.augment_offer:
raise InvalidAction("no augment offer")
@@ -210,7 +214,8 @@ class Game:
self.idx += 1
if self.round["stage"] != prev_stage:
self._refresh_bots()
self._reroll_shop()
if not self.shop_locked:
self._reroll_shop()
self._maybe_offer_augment()
def _resolve(self, rnd: dict) -> bool: