def test_next_event(): random.seed(250) # We're going to assume that the game goes on long enough for a few queries not to reach the # end of the game map_ = P.Map.load('maps/quad.json') agent0, agent1 = random_agent.Agent(), random_agent.Agent() game = P.Game.start(map_, [agent0, agent1]) assert next(game).method == 'place' assert game.next_event(method='reinforce').method == 'reinforce' nevents = len(game.world.event_log) assert game.next_event(method='act').method == 'act' assert len(game.world.event_log ) == nevents + 1, 'act should immediately follow reinforce' assert game.next_event(player_index=1).agent is agent1 for _ in range(2): # do this twice to check that multiple conditions combine with AND, because agent0.reinforce # would be separated by both agent1.reinforce and agent0.act event = game.next_event(agent=agent0, method='reinforce') assert event.state.player_index == 0 assert event.method == 'reinforce' with pytest.raises(StopIteration): game.next_event(predicate=lambda e: False)
def test_placement_phase_asymmetric(): random.seed(500) map_ = P.Map.load('maps/tiny4.json') game = P.Game.start( map_, [random_agent.Agent(), random_agent.Agent(), random_agent.Agent()]) placement_events = list(it.takewhile(lambda e: e.method == 'place', game)) # game should now be paused before evaluating the first reinforce() assert not game.world.has_neutral n_armies = map_.initial_armies[3] assert len( placement_events ) == 3 * n_armies - 4, 'initial armies minus assigned territories' assert len(set( id(e.agent) for e in placement_events[:3])) == 3, 'placement is round-robin' armies_by_owner = collections.defaultdict(int) territories_by_owner = collections.defaultdict(int) for owner, armies in zip(game.world.owners, game.world.armies): armies_by_owner[owner] += armies territories_by_owner[owner] += 1 assert all(n == n_armies for n in armies_by_owner.values()) assert list(sorted(territories_by_owner.values())) == [ 1, 1, 2 ], 'someone random gets an extra territory'
def test_play_game_fallback_agent_redeem_only(): random.seed(250) map_ = P.Map.load('maps/quad.json') with pytest.raises(ValueError): P.Game.play(map_, [RedeemWrongAgent(), random_agent.Agent()]) P.Game.play(map_, [P.FallbackAgent(RedeemWrongAgent()), random_agent.Agent()])
def test_play_game_fallback_agent(): random.seed(250) map_ = P.Map.load('maps/tiny4.json') with pytest.raises(ValueError): P.Game.play(map_, [EverythingWrongAgent(), random_agent.Agent()]) P.Game.play( map_, [P.FallbackAgent(EverythingWrongAgent()), random_agent.Agent()])
def test_start_game(): agents = [um.Mock(), um.Mock()] agents[0].place.side_effect = random_agent.Agent().place agents[1].place.side_effect = random_agent.Agent().place event = next(P.Game.start(P.Map.load('maps/tiny3.json'), agents)) agents[0].start_game.assert_called_once() agents[1].start_game.assert_called_once() assert all(owner is not None for owner in event.state.world.owners), \ 'start_game should be called after the initial allocation of territories' assert all(armies == 1 for armies in event.state.world.armies), \ 'start_game should be called for all agents before any place() calls'
def test_watch_game(): random.seed(987) map_ = P.Map.load('maps/tiny3.json') agents = [ ConsistencyCheckingAgent(random_agent.Agent()), ConsistencyCheckingAgent(random_agent.Agent()) ] name = 'test_watch_game_tmp.mp4' try: P.Game.watch(map_, agents, name) assert os.path.isfile(name) finally: if os.path.exists(name): os.remove(name)
def test_fuzz(map_name): SMALL_MAPS = {'mini', 'tiny3', 'tiny4'} random.seed(100) map_ = P.Map.load('maps/{}.json'.format(map_name)) rand_agent = ConsistencyCheckingAgent(random_agent.Agent()) for n_players in range(2, map_.max_players): agents = [rand_agent] * n_players ntrials = 1000 if map_name in SMALL_MAPS else 10 results = [P.Game.play(map_, agents) for _ in range(ntrials)] for result in results: assert set(result.winners) | set(result.eliminated) == set( range(n_players)) assert not (set(result.winners) & set(result.eliminated)) if map_name in SMALL_MAPS: winners = dict( collections.Counter([r.outright_winner for r in results])) # small maps, aggressive agents => there should normally be a winner n_draws = winners.pop(None, 0) assert n_draws < 0.1 * ntrials # fairness means the wins should be distributed evenly # - this test could fail - if so, try increasing ntrials p_win = 1 / n_players norm_approx_std = (ntrials * p_win * (1 - p_win))**0.5 assert max(winners.values()) - min( winners.values()) < 3 * norm_approx_std
def test_placement_phase(): map_ = P.Map.load('maps/tiny3.json') game = P.Game.start(map_, [random_agent.Agent(), random_agent.Agent()]) placement_events = list(it.takewhile(lambda e: e.method == 'place', game)) # game should now be paused before evaluating the first reinforce() assert game.world.has_neutral n_armies = map_.initial_armies[2] assert len( placement_events ) == 3 * n_armies - 3, 'initial armies minus assigned territories' assert len(set( id(e.agent) for e in placement_events[:3])) == 3, 'placement is round-robin' assert all(a == n_armies for a in game.world.armies) assert set(game.world.owners) == {0, 1, 2}
def test_placement_phase_too_many_players(): random.seed(400) map_ = P.Map.load('maps/tiny3.json') game = P.Game.start(map_, [random_agent.Agent()] * 4) with pytest.raises(ValueError) as e: list(game) assert '4' in str(e) assert '3' in str(e)
def test_play_game(): random.seed(345) map_ = P.Map.load('maps/tiny4.json') agents = [ ConsistencyCheckingAgent(random_agent.Agent()), ConsistencyCheckingAgent(random_agent.Agent()) ] game = P.Game.start(map_, agents) assert game.map is map_ for n, event in enumerate(game): # log should already contain this latest event assert game.world.event_log[-1] == event._replace( agent=repr(event.agent), state=event.state.player_index) assert len(game.world.event_log) == n + 1 assert game.world._repr_svg_() assert event._repr_svg_() # game should be over assert len(game.result.winners) == 1 or game.world.turn == map_.max_turns
def test_placement_phase_error(): random.seed(500) map_ = P.Map.load('maps/tiny3.json') bad_agent = um.Mock(__str__=um.Mock(return_value='BadMockAgent')) agents = [bad_agent, random_agent.Agent(), random_agent.Agent()] world, agents_and_states = _make_world(map_, agents) bad_agent.place = um.Mock(return_value=3) # out-of-bounds placement with pytest.raises(ValueError) as e: list(P._placement_phase(world, agents_and_states, random)) assert 'place' in str(e) and 'BadMockAgent' in str(e) assert '0..2' in str(e) and '3' in str(e) world, agents_and_states = _make_world(map_, agents) bad_agent.place = um.Mock(side_effect=lambda state: state.world.owners. index(1)) # enemy territory placement with pytest.raises(ValueError) as e: list(P._placement_phase(world, agents_and_states, random)) assert 'place' in str(e) and 'enemy' in str(e) and 'BadMockAgent' in str(e) assert '1' in str(e)
def test_map_world_state_game_repr(): map_ = P.Map.load('maps/tiny3.json') game = P.Game.start(map_, [random_agent.Agent()] * 2) assert 'territories=3' in str(game.world.map) assert 'continents=1' in str(game.world.map) assert game.world.map._repr_svg_() is not None assert str(game.world.map) in str(game.world) assert 'players=3' in str(game.world) assert game.world._repr_svg_() is not None first_placement = next(game) state = game.agents_and_states[0][1] state._add_cards([1, 2, 3, 4]) # don't actually need real cards here assert 'territories=1/3' in str(state) assert 'armies=1/3' in str(state) assert 'cards=4' in str(state) assert state._repr_svg_() is not None assert str(first_placement.state) in str(first_placement) assert first_placement._repr_svg_() is not None
def mini(path): if os.path.isdir(path): shutil.rmtree(path) os.makedirs(path) map_ = P.Map.load('maps/mini.json') agents = [random_agent.Agent(min_attack=1)] * 3 seed = 1000 random.seed(seed) game = P.Game.start(map_, agents) with open('{}/start.svg'.format(path), 'w') as f: f.write(next(game).state.world._repr_svg_()) with open('{}/end.svg'.format(path), 'w') as f: f.write(list(game)[-1].state.world._repr_svg_()) random.seed(seed) for n, event in enumerate(P.Game.start(map_, agents)): with open('{}/step_{}.svg'.format(path, n), 'w') as f: f.write(event._repr_svg_()) random.seed(seed) P.Game.watch(map_, agents, '{}/game.mp4'.format(path), dpi=144)
import random import os import sys import shutil import networkx as nx import preem as P from agents import random_agent map_classic = P.Map.load('maps/classic.json') map_classic.max_turns = 10 # otherwise it is far too long with just these silly random agents! classic_agents = [random_agent.Agent()] * 3 def eg_classic_svg(name): random.seed(42) game = P.Game.start(map_classic, classic_agents) next(game) with open(name, 'w') as f: f.write(game.world._repr_svg_()) def eg_classic_mp4(name): random.seed(42) P.Game.watch(map_classic, classic_agents, name, dpi=144) def agent_flow_svg(name): g = nx.DiGraph() g.graph.update(rankdir='LR', pad=.2) g.add_nodes_from(['place', 'redeem', 'reinforce', 'act']) g.add_edges_from([('place', 'place', dict(label='initial armies')),
def test_long_class_name(): assert P._long_class_name( random_agent.Agent()) == 'agents.random_agent.Agent'