Bots spielen echt: PlayerState + geteilte Policy, Pairing mit Ghost, Pool-Erhaltung, Streak-Fix, Meepsie-Filter

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
team3
2026-07-23 06:47:05 +02:00
parent c0cd303eb0
commit ca471d2b32
10 changed files with 547 additions and 399 deletions

View File

@@ -103,3 +103,84 @@ def test_carousel_grants_unit_and_loot(art, cfg):
game = Game(art, cfg, seed=5)
assert len(game.bench) == 1 # Unit vom 1-1-Carousel
assert len(game.items) == 1 # Item-Komponente vom Carousel
def _pool_total(game, api):
n = game.pool.available(api)
for p in game.players:
for u in p.board + p.bench:
if u["api_name"] == api:
n += 3 ** (u["stars"] - 1)
return n
def test_pool_conservation(art, cfg):
for seed in (1, 2, 3):
game = Game(art, cfg, seed=seed)
initial = {api: cfg["pool"]["sizes"][art["static"]["units"][api]["cost"] - 1]
for api in art["static"]["units"]}
while not game.over:
game.step()
for api, total in initial.items():
assert _pool_total(game, api) == total, f"pool leak: {api}"
def test_bots_start_with_one_unit(art, cfg):
game = Game(art, cfg, seed=7)
for b in game.bots:
assert len(b.board) + len(b.bench) == 1 # nur die Carousel-Unit
assert all(u["stars"] == 1 for u in b.board + b.bench)
def test_streak_resets_on_win(art, cfg):
game = Game(art, cfg, seed=1)
game.player.streak = -3
# direkter Formel-Test wie in step():
won = True
streak = max(game.player.streak, 0) + 1 if won else 0
assert streak == 1
def test_pairing():
import random
from tft.sim.game import pair_players
from tft.sim.player import PlayerState
def mk(n):
return [PlayerState(name=f"P{i}", archetype="fast8", hp=100, gold=0, level=2)
for i in range(n)]
rng = random.Random(1)
for n in (8, 5, 3, 2):
players = mk(n)
pairs, odd, ghost_src = pair_players(players, rng)
paired = {p.name for a, b in pairs for p in (a, b)}
if odd:
assert odd.name not in paired
assert ghost_src is not odd
assert len(paired) + 1 == n
else:
assert len(paired) == n
# kein Wiederholungsgegner bei >2
players = mk(8)
rng = random.Random(2)
for _ in range(10):
pairs, _, _ = pair_players(players, rng)
for a, b in pairs:
assert a.last_opponent == b.name
def test_ghost_source_takes_no_damage(art, cfg):
game = Game(art, cfg, seed=3)
game.bots[0].hp = 0
game.bots[0].placement = 8
game.bots[0].board, game.bots[0].bench = [], []
# 7 Lebende -> jede PvP-Runde hat einen Ghost-Kampf; HP-Summe der
# Ghost-Quelle darf nur durch ihren echten Kampf sinken (kein Doppelschaden).
hp_before = {p.name: p.hp for p in game.players}
while game.round["kind"] != "pvp":
game.step()
game.step()
drops = sum(1 for p in game.players if p.hp < hp_before[p.name])
assert drops <= 4 # max. 3 Paar-Verlierer + 1 Ghost-Verlierer