"""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