werewolftown_agent / tests /features /test_plot_value.py
gabiwzx's picture
refactor: 五层架构重构 M1-M8 完成
1c66cf8
Raw
History Blame Contribute Delete
5.07 kB
"""Tests for features/plot_value.py."""
from __future__ import annotations
from werewolftown.features.plot_value import (
find_best_buy_candidate,
plot_buyer_gain,
plot_opportunity_cost,
plot_value,
)
from werewolftown.state.game_state import PlayerView
class TestPlotValue:
def test_empty_plot_best_shop(self, make_state):
state = make_state(
plots=frozenset({4, 5, 6}),
built_on={4: "Happy", 5: "Happy"},
shops=("Happy",),
)
# Plot 6 adjacent to 5 (Happy) → gain = rev(3,4) - rev(2,4) = 50000-30000
val = plot_value(state, 6, viewer="me")
assert val == 20000
def test_built_plot_fixed_type(self, make_state):
state = make_state(
plots=frozenset({4, 5, 6}),
built_on={4: "Happy", 5: "Happy", 6: "猫天天"},
shops=(),
)
# Plot 6 is built with 猫天天 → gain from acquiring = 0 (already built, fixed type)
# Actually, gain is the marginal revenue from having this plot
# For a built plot, it's the cluster gain for that type
val = plot_value(state, 6, viewer="me")
# 6 is isolated 猫天天 (no adjacent 猫天天) → gain = 0
assert val == 0
def test_isolated_empty_plot(self, make_state):
state = make_state(
plots=frozenset({4, 5, 7}),
built_on={4: "Happy"},
shops=("Happy",),
)
# Plot 7 not adjacent to any Happy → can still build 1-cluster = 10000
val = plot_value(state, 7, viewer="me")
assert val == 10000
class TestPlotOpportunityCost:
def test_built_plot_loss(self, make_state, cfg):
state = make_state(
round=1,
plots=frozenset({4, 5}),
built_on={4: "Happy", 5: "Happy"},
shops=(),
)
# Selling plot 4: lose 2-cluster → 1-cluster
# rev(2,4)=30000, rev(1,4)=10000 → loss=20000/round * 4 rounds = 80000
# + extra_penalty 5000*4 = 20000 → total = 100000
cost = plot_opportunity_cost(state, 4, cfg)
assert cost == 100000
def test_empty_plot_adjacent(self, make_state, cfg):
state = make_state(
round=1,
plots=frozenset({4, 5, 6}),
built_on={4: "Happy", 5: "Happy"},
shops=("Happy",),
)
# Empty plot 6, adjacent to my plots → floor=10000 * 4 = 40000
cost = plot_opportunity_cost(state, 6, cfg)
assert cost == 40000
def test_empty_plot_isolated(self, make_state, cfg):
state = make_state(
round=1,
plots=frozenset({4, 5, 7}),
built_on={4: "Happy", 5: "Happy"},
shops=("Happy",),
)
# Empty plot 7, not adjacent → floor=5000 * 4 = 20000
cost = plot_opportunity_cost(state, 7, cfg)
assert cost == 20000
class TestPlotBuyerGain:
def test_buyer_gain_with_penalty(self, make_state, cfg):
state = make_state(
round=1,
player_id="子涵",
plots=frozenset({4, 5}),
built_on={4: "Happy", 5: "Happy"},
shops=("Happy",),
other_players={"浩宇": PlayerView(
name="浩宇", cash=50000,
plots=frozenset({6, 10}),
shops=("Happy",),
)},
)
# 浩宇 has Happy card, plot 6 → can build 1-cluster = 10000/round
# penalty ratio R1 = 0.4, remaining = 4 → 10000 * 4 * 0.4 = 16000
gain = plot_buyer_gain(state, 6, "浩宇", cfg)
assert gain == 16000
def test_zero_for_no_gain(self, make_state, cfg):
state = make_state(round=1)
gain = plot_buyer_gain(state, 4, "浩宇", cfg)
assert gain == 0
class TestFindBestBuyCandidate:
def test_finds_adjacent_opp_plot(self, make_state, cfg):
state = make_state(
round=1,
player_id="子涵",
plots=frozenset({4, 5}),
built_on={4: "Happy", 5: "Happy"},
shops=("Happy",),
other_players={"浩宇": PlayerView(
name="浩宇", cash=50000,
plots=frozenset({6}), # 6 adj 5
shops=(),
)},
)
cand = find_best_buy_candidate(state, cfg)
assert cand is not None
assert cand.plot_id == 6
assert cand.owner == "浩宇"
assert cand.is_adjacent is True
assert cand.gain > 0
def test_none_when_no_adjacent(self, make_state, cfg):
state = make_state(
plots=frozenset({4, 5}),
other_players={"浩宇": PlayerView(
name="浩宇", cash=50000,
plots=frozenset({10}), # 10 adj 9, not adj to 4/5
shops=(),
)},
)
assert find_best_buy_candidate(state, cfg) is None
def test_none_when_no_opponents(self, make_state, cfg):
state = make_state(plots=frozenset({4, 5}))
assert find_best_buy_candidate(state, cfg) is None