first prototype
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import json
|
||||
import random
|
||||
|
||||
import pytest
|
||||
|
||||
from wizard_game import (
|
||||
Bid,
|
||||
ChooseTrump,
|
||||
GameFinished,
|
||||
GameOptions,
|
||||
IllegalActionError,
|
||||
Observation,
|
||||
Phase,
|
||||
PlayCard,
|
||||
RoundCompleted,
|
||||
Suit,
|
||||
WizardGame,
|
||||
action_from_dict,
|
||||
card_by_id,
|
||||
)
|
||||
from wizard_game.cards import WIZARD_ID_OFFSET
|
||||
|
||||
|
||||
def play_random_game(num_players: int, seed: int, options: GameOptions | None = None) -> WizardGame:
|
||||
game = WizardGame(num_players, options, seed=seed)
|
||||
rng = random.Random(seed)
|
||||
rounds_completed = 0
|
||||
while not game.is_finished:
|
||||
player = game.current_player
|
||||
actions = game.legal_actions()
|
||||
assert actions, "current player must always have a legal action"
|
||||
events = game.apply(player, rng.choice(actions))
|
||||
rounds_completed += sum(isinstance(e, RoundCompleted) for e in events)
|
||||
assert rounds_completed == game.total_rounds
|
||||
return game
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_players", [3, 4, 5, 6])
|
||||
@pytest.mark.parametrize("seed", range(5))
|
||||
def test_random_games_complete(num_players, seed):
|
||||
options = GameOptions(not_equal=seed % 2 == 0, hidden_bids=seed % 3 == 0)
|
||||
game = play_random_game(num_players, seed, options)
|
||||
assert game.phase is Phase.FINISHED
|
||||
assert len(game.round_history) == game.total_rounds
|
||||
assert game.scores == list(game.round_history[-1].scores)
|
||||
for result in game.round_history:
|
||||
assert sum(result.tricks) == result.round_number
|
||||
if options.not_equal:
|
||||
assert sum(result.bids) != result.round_number
|
||||
|
||||
|
||||
def test_invalid_player_count():
|
||||
with pytest.raises(ValueError):
|
||||
WizardGame(2)
|
||||
with pytest.raises(ValueError):
|
||||
WizardGame(7)
|
||||
|
||||
|
||||
def test_deal_sizes_and_starter():
|
||||
game = WizardGame(4, seed=3, first_dealer=2)
|
||||
assert game.round_number == 1
|
||||
assert all(len(game.hand(p)) == 1 for p in range(4))
|
||||
if game.phase is Phase.BIDDING:
|
||||
assert game.current_player == 3
|
||||
|
||||
|
||||
def test_wrong_player_rejected():
|
||||
game = WizardGame(3, seed=1, first_dealer=0)
|
||||
other = (game.current_player + 1) % 3
|
||||
with pytest.raises(IllegalActionError):
|
||||
game.apply(other, game.legal_actions()[0])
|
||||
|
||||
|
||||
def test_illegal_bid_rejected():
|
||||
game = WizardGame(3, seed=1, first_dealer=0)
|
||||
if game.phase is Phase.TRUMP_SELECTION:
|
||||
game.apply(game.current_player, ChooseTrump(Suit.RED))
|
||||
with pytest.raises(IllegalActionError):
|
||||
game.apply(game.current_player, Bid(5))
|
||||
|
||||
|
||||
def test_wizard_trump_card_lets_dealer_choose():
|
||||
for seed in range(200):
|
||||
game = WizardGame(4, seed=seed, first_dealer=1)
|
||||
if game.round.trump_card and game.round.trump_card.is_wizard:
|
||||
break
|
||||
else:
|
||||
pytest.fail("no seed produced a wizard as trump card")
|
||||
assert game.phase is Phase.TRUMP_SELECTION
|
||||
assert game.current_player == 1
|
||||
game.apply(1, ChooseTrump(Suit.GREEN))
|
||||
assert game.round.trump_suit is Suit.GREEN
|
||||
assert game.phase is Phase.BIDDING
|
||||
assert game.current_player == 2
|
||||
|
||||
|
||||
def test_not_equal_restricts_last_bidder():
|
||||
game = WizardGame(3, GameOptions(not_equal=True), seed=11, first_dealer=0)
|
||||
if game.phase is Phase.TRUMP_SELECTION:
|
||||
game.apply(game.current_player, ChooseTrump(Suit.RED))
|
||||
game.apply(1, Bid(0))
|
||||
game.apply(2, Bid(0))
|
||||
assert game.legal_actions(0) == [Bid(0)] # bidding 1 would make the sum equal 1
|
||||
|
||||
|
||||
def test_hidden_bids():
|
||||
game = WizardGame(3, GameOptions(hidden_bids=True), seed=5, first_dealer=0)
|
||||
if game.phase is Phase.TRUMP_SELECTION:
|
||||
game.apply(game.current_player, ChooseTrump(Suit.RED))
|
||||
game.apply(1, Bid(1))
|
||||
assert game.observation(1).bids[1] == 1
|
||||
assert game.observation(2).bids[1] is None
|
||||
assert game.observation(2).has_bid[1] is True
|
||||
game.apply(2, Bid(0))
|
||||
game.apply(0, Bid(0))
|
||||
assert game.observation(2).bids == (0, 1, 0)
|
||||
|
||||
|
||||
def test_last_round_has_no_trump():
|
||||
game = play_random_game(3, seed=7)
|
||||
assert game.round.round_number == 20
|
||||
assert game.round.trump_card is None
|
||||
|
||||
|
||||
def test_observation_hides_other_hands_and_round_trips():
|
||||
game = WizardGame(5, seed=9)
|
||||
obs = game.observation(0)
|
||||
assert obs.hand == tuple(sorted(game.hand(0), key=lambda c: c.id))
|
||||
data = json.loads(json.dumps(obs.to_dict()))
|
||||
assert Observation.from_dict(data) == obs
|
||||
|
||||
|
||||
def test_action_round_trip():
|
||||
for action in (Bid(3), PlayCard(card_by_id(WIZARD_ID_OFFSET)), ChooseTrump(Suit.BLUE)):
|
||||
assert action_from_dict(action.to_dict()) == action
|
||||
with pytest.raises(ValueError):
|
||||
action_from_dict({"type": "bid", "value": True})
|
||||
with pytest.raises(ValueError):
|
||||
action_from_dict({"type": "play", "card": 99})
|
||||
|
||||
|
||||
def test_game_finished_event_and_winners():
|
||||
game = WizardGame(3, seed=2)
|
||||
rng = random.Random(2)
|
||||
last_events = []
|
||||
while not game.is_finished:
|
||||
last_events = game.apply(game.current_player, rng.choice(game.legal_actions()))
|
||||
finished = [e for e in last_events if isinstance(e, GameFinished)]
|
||||
assert finished and list(finished[0].winners) == game.winners()
|
||||
assert game.observation(0).winners == tuple(game.winners())
|
||||
assert game.legal_actions() == []
|
||||
|
||||
|
||||
def test_clone_is_independent():
|
||||
game = WizardGame(4, seed=4)
|
||||
clone = game.clone()
|
||||
clone.apply(clone.current_player, clone.legal_actions()[0])
|
||||
assert game.observation(0) != clone.observation(0) or game.current_player != clone.current_player
|
||||
Reference in New Issue
Block a user