from __future__ import annotations import pytest from python.gems.ai import choose_ai_command from python.gems.domain.engine import RuleError, apply_command, bonuses, new_game, public_state, score from python.gems.domain.legal_actions import command, legal_commands from python.gems.domain.models import GameSettings, OwnedCard def test_setup_and_hidden_information(content_pack) -> None: state = new_game("ABCDEFGH", ["A", "B", "C", "D"], content_pack, GameSettings(), seed=9) assert state.supply["pearl"] == 7 assert all(len(market) == 4 for market in state.markets.values()) state.players[1].reserved.append("base_1_0") view = public_state(state, 0) assert view["players"][1]["reserved"] == [None] assert isinstance(view["decks"]["base:1"], int) def test_distinct_take_requires_three_when_available(content_pack) -> None: settings = GameSettings(first_player_mode="selected", first_player_seat=0) state = new_game("ABCDEFGH", ["A", "B"], content_pack, settings, seed=1) bad = command(state, "take_distinct", {"resources": ["pearl", "wave"]}) with pytest.raises(RuleError, match="exactly three"): apply_command(state, bad, content_pack, settings, actor_seat=0) good = command(state, "take_distinct", {"resources": ["pearl", "wave", "leaf"]}) state = apply_command(state, good, content_pack, settings, actor_seat=0) assert state.players[0].tokens["pearl"] == 1 assert state.current_seat == 1 def test_purchase_uses_discount_and_returns_payment(content_pack) -> None: settings = GameSettings(first_player_mode="selected", first_player_seat=0) state = new_game("ABCDEFGH", ["A", "B"], content_pack, settings, seed=2) card_id = state.markets["base:1"][0] card = content_pack.card(card_id) resource, cost = next(iter(card.cost.items())) state.players[0].tokens[resource] = cost state.supply[resource] -= cost buy = command(state, "purchase", {"card_id": card_id, "payment": {resource: cost}}) state = apply_command(state, buy, content_pack, settings, actor_seat=0) assert any(item.card_id == card_id for item in state.players[0].cards) assert state.supply[resource] >= cost assert bonuses(state.players[0], content_pack)[card.bonus_resource] == 1 def test_reserving_visible_card_refills_market(content_pack) -> None: settings = GameSettings(first_player_mode="selected", first_player_seat=0) state = new_game("ABCDEFGH", ["A", "B"], content_pack, settings, seed=12) card_id = state.markets["base:1"][0] deck_size = len(state.decks["base:1"]) state = apply_command(state, command(state, "reserve", {"card_id": card_id}), content_pack, settings, actor_seat=0) assert card_id in state.players[0].reserved assert card_id not in state.markets["base:1"] assert len(state.markets["base:1"]) == settings.base_market_size assert len(state.decks["base:1"]) == deck_size - 1 def test_score_counts_cards_patrons_and_scoring_outpost(content_pack) -> None: state = new_game("ABCDEFGH", ["A"], content_pack, GameSettings(), seed=3) player = state.players[0] player.cards.append(OwnedCard(card_id="base_3_0")) player.patrons.append("patron_one") player.outposts.append("post_points_per_outpost") assert score(player, content_pack) == 6 def test_each_ai_level_returns_a_legal_command(content_pack) -> None: settings = GameSettings(first_player_mode="selected", first_player_seat=0) state = new_game("ABCDEFGH", ["A", "B"], content_pack, settings, seed=4) legal = legal_commands(state, content_pack, settings, 0) legal_shapes = {(item.type, str(item.payload)) for item in legal} for difficulty in ("easy", "medium", "hard"): chosen = choose_ai_command(state, content_pack, settings, 0, difficulty) assert (chosen.type, str(chosen.payload)) in legal_shapes def test_first_player_can_be_selected_or_random(content_pack) -> None: selected = GameSettings(first_player_mode="selected", first_player_seat=2) selected_state = new_game("ABCDEFGH", ["A", "B", "C"], content_pack, selected, seed=8) assert selected_state.first_seat == 2 assert selected_state.current_seat == 2 random_settings = GameSettings(first_player_mode="random") first = new_game("ABCDEFGH", ["A", "B", "C"], content_pack, random_settings, seed=8) repeated = new_game("ABCDEFGH", ["A", "B", "C"], content_pack, random_settings, seed=8) assert first.first_seat == repeated.first_seat assert 0 <= first.first_seat < 3 def test_excess_tokens_can_be_discarded_and_returned_to_supply(content_pack) -> None: settings = GameSettings(token_limit=1, first_player_mode="selected", first_player_seat=0) state = new_game("ABCDEFGH", ["A", "B"], content_pack, settings, seed=6) colors = list(content_pack.resource_ids[:3]) state = apply_command( state, command(state, "take_distinct", {"resources": colors}), content_pack, settings, actor_seat=0, ) assert state.pending is not None assert state.pending.kind == "discard_tokens" assert state.pending.amount == 2 discard = legal_commands(state, content_pack, settings, 0)[0] returned = sum(discard.payload["tokens"].values()) state = apply_command(state, discard, content_pack, settings, actor_seat=0) assert returned == 2 assert sum(state.players[0].tokens.values()) == 1 assert state.pending is None assert state.current_seat == 1