File size: 3,997 Bytes
9fca766
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Director tests: dominance assessment, bounded interventions, mercy."""

import random

import pytest

from scrypt.data import load_content
from scrypt.engine.cards import CardInstance, make_card
from scrypt.engine.combat import CombatState, IllegalMove
from scrypt.inference.backend import ScriptedBackend
from scrypt.warden.director import CRUSHED, DOMINATING, EVEN, Director, assess

TITAN = make_card("titan", power=6, health=6)
GRUNT = make_card("grunt", power=2, health=2)


def fight(turn: int = 3, scale: int = 0) -> CombatState:
    state = CombatState(main_deck=[GRUNT] * 4, side_deck=[], script=[[]], seed=1)
    state.turn = turn
    state.scale = scale
    return state


def make_director(backend=None, seed: int = 0) -> Director:
    return Director(content=load_content(), rng=random.Random(seed), backend=backend)


# ----------------------------------------------------------------- assess

def test_assess_reads_dominance_and_collapse():
    state = fight(scale=3)
    state.player_row[0] = CardInstance(spec=TITAN)
    assert assess(state) is DOMINATING

    crushed = fight(scale=-3)
    crushed.foe_row[0] = CardInstance(spec=TITAN)
    crushed.hand.clear()
    assert assess(crushed) is CRUSHED

    assert assess(fight(scale=0)) is EVEN


# ----------------------------------------------------------- intervention

async def test_no_intervention_in_opening_turns():
    state = fight(turn=0, scale=5)
    state.player_row[0] = CardInstance(spec=TITAN)
    assert await make_director().consider(state) is None


async def test_dominating_triggers_one_bounded_intervention():
    director = make_director()
    state = fight(scale=3)
    state.player_row[0] = CardInstance(spec=TITAN)

    first = await director.consider(state)
    assert first is not None and first.action in ("throttle", "reinforce")
    kinds = {e.kind for e in state.events}
    assert kinds & {"throttled", "reinforced"}

    # The per-fight budget refuses a second act of cruelty.
    assert await director.consider(state) is None

    director.new_fight()
    assert await director.consider(state) is not None


async def test_crushed_gets_secret_mercy():
    director = make_director()
    state = fight(scale=-4)
    state.foe_row[0] = CardInstance(spec=TITAN)
    state.foe_queue[1] = CardInstance(spec=TITAN)
    state.hand.clear()

    mercy = await director.consider(state)
    assert mercy is not None and mercy.action == "withdraw"
    assert state.foe_queue[1] is None
    assert any(e.kind == "withdrawn" for e in state.events)


async def test_backend_picks_from_menu():
    backend = ScriptedBackend(
        playbook={"Choose one intervention": '{"tool": "intervene", "args": {"action": "reinforce"}}'}
    )
    director = make_director(backend=backend)
    state = fight(scale=3)
    state.player_row[0] = CardInstance(spec=TITAN)
    result = await director.consider(state)
    assert result is not None and result.action == "reinforce"
    assert any(e.kind == "reinforced" for e in state.events)


async def test_nonsense_backend_falls_back_to_rng():
    backend = ScriptedBackend(default="I refuse to use tools.")
    director = make_director(backend=backend, seed=4)
    state = fight(scale=4)
    state.player_row[0] = CardInstance(spec=TITAN)
    result = await director.consider(state)
    assert result is not None and result.action in ("throttle", "reinforce")


# ------------------------------------------------------------ engine hooks

def test_throttle_floors_at_zero_power():
    state = fight()
    weak = CardInstance(spec=make_card("weak", power=1, health=1))
    state.player_row[0] = weak
    state.throttle_player_card(0)
    state.throttle_player_card(0)
    assert weak.power == 0  # never negative


def test_reinforce_requires_empty_lane():
    state = fight()
    state.foe_queue[0] = CardInstance(spec=GRUNT)
    with pytest.raises(IllegalMove):
        state.reinforce_queue(0, TITAN)
    with pytest.raises(IllegalMove):
        state.withdraw_queue(2)