werewolftown_agent / tests /features /test_standing.py
gabiwzx's picture
feat: 策略优化 — R4终局收益+追赶模式+ROI gate+EV定价+废卡路径可行性
7d0326b
Raw
History Blame Contribute Delete
4.42 kB
"""Tests for standing analysis — trailing mode detection.
来源:STRATEGY_IMPROVEMENT_REPORT_20260714.md §5.7
"""
from __future__ import annotations
import pytest
from werewolftown.config import StrategyConfig
from werewolftown.features.standing import is_trailing, projected_gap_to_leader
from werewolftown.state.game_state import GameState, PendingLedger, PlayerView
@pytest.fixture
def make_state():
def _make(
player_id: str = "me",
round: int = 2,
cash: int = 50000,
plots: frozenset[int] | None = None,
shops: tuple[str, ...] | None = None,
built_on: dict[int, str] | None = None,
other_players: dict[str, PlayerView] | None = None,
) -> GameState:
return GameState(
player_id=player_id,
round=round,
cash=cash,
initial_cash=50000,
plots=plots or frozenset(),
shops=shops or (),
built_on=built_on or {},
other_players=other_players or {},
pending=PendingLedger(),
)
return _make
@pytest.fixture
def cfg() -> StrategyConfig:
return StrategyConfig({
"standing": {
"trailing_gap_threshold": 50000,
"trailing_rank_threshold": 2,
}
})
class TestProjectedGapToLeader:
def test_leading_negative_gap(self, make_state):
"""When leading, gap should be negative."""
state = make_state(
cash=300000,
other_players={
"opp": PlayerView(name="opp", cash=100000, plots=frozenset(), shops=()),
},
)
gap = projected_gap_to_leader(state)
assert gap < 0
def test_trailing_positive_gap(self, make_state):
"""When trailing, gap should be positive."""
state = make_state(
cash=100000,
other_players={
"opp": PlayerView(name="opp", cash=300000, plots=frozenset(), shops=()),
},
)
gap = projected_gap_to_leader(state)
assert gap > 0
assert gap == 200000
def test_no_opponents_zero_gap(self, make_state):
"""No opponents → gap is negative (we're the only player)."""
state = make_state(cash=100000)
gap = projected_gap_to_leader(state)
assert gap < 0 # No opponents = effectively leading
class TestIsTrailing:
def test_trailing_when_far_behind(self, make_state, cfg):
"""Trailing when gap > threshold and rank >= 2."""
state = make_state(
cash=100000,
other_players={
"opp1": PlayerView(name="opp1", cash=300000, plots=frozenset(), shops=()),
"opp2": PlayerView(name="opp2", cash=200000, plots=frozenset(), shops=()),
},
)
assert is_trailing(state, cfg) is True
def test_not_trailing_when_leading(self, make_state, cfg):
"""Not trailing when leading."""
state = make_state(
cash=500000,
other_players={
"opp": PlayerView(name="opp", cash=100000, plots=frozenset(), shops=()),
},
)
assert is_trailing(state, cfg) is False
def test_not_trailing_when_gap_small(self, make_state, cfg):
"""Not trailing when gap < threshold."""
state = make_state(
cash=190000,
other_players={
"opp": PlayerView(name="opp", cash=200000, plots=frozenset(), shops=()),
},
)
# Gap = 10k < 50k threshold
assert is_trailing(state, cfg) is False
def test_not_trailing_no_opponents(self, make_state, cfg):
"""Not trailing with no opponents."""
state = make_state(cash=100000)
assert is_trailing(state, cfg) is False
def test_trailing_r4_uses_projection(self, make_state, cfg):
"""R4 uses projected ranking."""
state = make_state(
round=4,
cash=100000,
plots=frozenset({1}),
shops=("猫天天",),
built_on={},
other_players={
"opp": PlayerView(
name="opp",
cash=400000,
plots=frozenset({2}),
shops=(),
),
},
)
# In R4, opponent has 300k more cash → definitely trailing
assert is_trailing(state, cfg) is True