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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user