diff --git a/axelrod/mock_player.py b/axelrod/mock_player.py index 9396d591e..1745bbf9c 100644 --- a/axelrod/mock_player.py +++ b/axelrod/mock_player.py @@ -2,6 +2,7 @@ from axelrod.actions import Actions, Action from axelrod.player import Player, update_history, update_state_distribution from collections import defaultdict +from itertools import cycle from typing import List, Tuple @@ -28,16 +29,16 @@ def __init__(self, actions: List[Action] =None, history: List[Action] =None, sta if state_dist: self.state_distribution = dict(state_dist) if actions: - self.actions = list(actions) + self.actions = cycle(actions) else: self.actions = [] def strategy(self, opponent: Player) -> Action: # Return the next saved action, if present. try: - action = self.actions.pop(0) + action = self.actions.__next__() return action - except IndexError: + except AttributeError: return C diff --git a/axelrod/strategies/apavlov.py b/axelrod/strategies/apavlov.py index 1aa692f2c..9bbb5ed0e 100644 --- a/axelrod/strategies/apavlov.py +++ b/axelrod/strategies/apavlov.py @@ -29,7 +29,7 @@ class APavlov2006(Player): def __init__(self) -> None: super().__init__() - self.opponent_class = "" + self.opponent_class = None def strategy(self, opponent: Player) -> Action: # TFT for six rounds @@ -96,7 +96,7 @@ class APavlov2011(Player): def __init__(self) -> None: super().__init__() - self.opponent_class = "" + self.opponent_class = None def strategy(self, opponent: Player) -> Action: # TFT for six rounds diff --git a/axelrod/strategies/appeaser.py b/axelrod/strategies/appeaser.py index 3d273d7e4..29462ec6a 100644 --- a/axelrod/strategies/appeaser.py +++ b/axelrod/strategies/appeaser.py @@ -24,11 +24,11 @@ class Appeaser(Player): def strategy(self, opponent: Player) -> Action: if not len(opponent.history): - self.move = C + return C else: if opponent.history[-1] == D: - if self.move == C: - self.move = D + if self.history[-1] == C: + return D else: - self.move = C - return self.move + return C + return self.history[-1] diff --git a/axelrod/strategies/axelrod_first.py b/axelrod/strategies/axelrod_first.py index 2cd95d7d1..abe24a55c 100644 --- a/axelrod/strategies/axelrod_first.py +++ b/axelrod/strategies/axelrod_first.py @@ -98,12 +98,10 @@ def strategy(self, opponent: Player) -> Action: if self.revised: if round_number == 1: - self.move = C - return self.move + return C elif not self.revised: if round_number <= 2: - self.move = D - return self.move + return D # Update various counts if round_number > 2: @@ -121,12 +119,12 @@ def strategy(self, opponent: Player) -> Action: c = 6.0 * self.good - 8.0 * self.bad - 2 alt = 4.0 * self.good - 5.0 * self.bad - 1 if (c >= 0 and c >= alt): - self.move = C + move = C elif (c >= 0 and c < alt) or (alt >= 0): - self.move = flip_action(self.move) + move = flip_action(self.history[-1]) else: - self.move = D - return self.move + move = D + return move def reset(self): super().reset() diff --git a/axelrod/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index fc013d1b7..5c971235d 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -32,6 +32,13 @@ def move(self, opponent_action): self.state = next_state return next_action + def __eq__(self, other): + """Equality of two FSMs""" + check = True + for attr in ["state", "state_transitions"]: + check = check and getattr(self, attr) == getattr(other, attr) + return check + class FSMPlayer(Player): """Abstract base class for finite state machine players.""" @@ -57,6 +64,7 @@ def __init__(self, transitions=None, initial_state=None, initial_action = C super().__init__() self.initial_state = initial_state + self.state = initial_state self.initial_action = initial_action self.fsm = SimpleFSM(transitions, initial_state) @@ -73,6 +81,7 @@ def strategy(self, opponent): def reset(self): super().reset() self.fsm.state = self.initial_state + self.state = self.initial_state class Fortress3(FSMPlayer): diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index c35c17fd6..bb3c0f82f 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -61,6 +61,15 @@ def is_well_formed(self): return False return True + def __eq__(self, other): + """Equality of two HMMs""" + check = True + for attr in ["transitions_C", "transitions_D", + "emission_probabilities", "state"]: + check = check and getattr(self, attr) == getattr(other, attr) + return check + + def move(self, opponent_action): """Changes state and computes the response action. @@ -114,6 +123,7 @@ def __init__(self, transitions_C=None, transitions_D=None, self.hmm = SimpleHMM(transitions_C, transitions_D, emission_probabilities, initial_state) assert self.hmm.is_well_formed() + self.state = self.hmm.state self.classifier['stochastic'] = self.is_stochastic() def is_stochastic(self): @@ -141,6 +151,7 @@ def strategy(self, opponent): def reset(self): super().reset() self.hmm.state = self.initial_state + self.state = self.hmm.state class EvolvedHMM5(HMMPlayer): diff --git a/axelrod/strategies/punisher.py b/axelrod/strategies/punisher.py index 37d02fa97..f67439012 100644 --- a/axelrod/strategies/punisher.py +++ b/axelrod/strategies/punisher.py @@ -62,7 +62,7 @@ def reset(self): """ super().reset() self.grudged = False - self.grudge_memory = 0 + self.grudge_memory = 1 self.mem_length = 1 @@ -122,15 +122,15 @@ def reset(self): """Resets internal variables and history""" super().reset() self.grudged = False - self.grudge_memory = 0 + self.grudge_memory = 1 self.mem_length = 1 class LevelPunisher(Player): """ - A player starts by cooperating however, after 10 rounds - will defect if at any point the number of defections + A player starts by cooperating however, after 10 rounds + will defect if at any point the number of defections by an opponent is greater than 20%. - + Names: - Level Punisher: Name from CoopSim https://github.com/jecki/CoopSim diff --git a/axelrod/strategies/qlearner.py b/axelrod/strategies/qlearner.py index 578b0537d..47a9fd94c 100644 --- a/axelrod/strategies/qlearner.py +++ b/axelrod/strategies/qlearner.py @@ -49,6 +49,7 @@ def __init__(self) -> None: self.classifier['stochastic'] = True self.prev_action = random_choice() + self.original_prev_action = self.prev_action self.history = [] # type: List[Action] self.score = 0 self.Qs = OrderedDict({'': OrderedDict(zip([C, D], [0, 0]))}) @@ -117,7 +118,7 @@ def reset(self): self.Qs = {'': {C: 0, D: 0}} self.Vs = {'': 0} self.prev_state = '' - self.prev_action = random_choice() + self.prev_action = self.original_prev_action class ArrogantQLearner(RiskyQLearner): diff --git a/axelrod/strategies/stalker.py b/axelrod/strategies/stalker.py index 2e3b3294c..eb8584859 100644 --- a/axelrod/strategies/stalker.py +++ b/axelrod/strategies/stalker.py @@ -34,7 +34,7 @@ class Stalker(Player): classifier = { 'memory_depth': float('inf'), 'stochastic': True, - 'makes_use_of': set(), + 'makes_use_of': set(["game", "length"]), 'long_run_time': False, 'inspects_source': False, 'manipulates_source': False, @@ -46,7 +46,7 @@ def __init__(self) -> None: R, P, S, T = self.match_attributes["game"].RPST() self.very_good_score = R self.very_bad_score = P - self.wish_score = ( R + P ) / 2 + self.wish_score = (R + P) / 2 self.current_score = 0 def score_last_round(self, opponent: Player): diff --git a/axelrod/strategies/titfortat.py b/axelrod/strategies/titfortat.py index 61334cd12..977f9a7f7 100644 --- a/axelrod/strategies/titfortat.py +++ b/axelrod/strategies/titfortat.py @@ -304,7 +304,7 @@ def strategy(self, opponent: Player) -> Action: # Are we deadlocked? (in a CD -> DC loop) if (self.deadlock_counter >= self.deadlock_threshold): - self.move = C + move = C if self.deadlock_counter == self.deadlock_threshold: self.deadlock_counter = self.deadlock_threshold + 1 else: @@ -323,16 +323,16 @@ def strategy(self, opponent: Player) -> Action: # Compare counts to thresholds # If randomness_counter exceeds Y, Defect for the remainder if self.randomness_counter >= 8: - self.move = D + move = D else: # TFT - self.move = D if opponent.history[-1:] == [D] else C + move = D if opponent.history[-1:] == [D] else C # Check for deadlock if opponent.history[-2] != opponent.history[-1]: self.deadlock_counter += 1 else: self.deadlock_counter = 0 - return self.move + return move def reset(self): super().reset() diff --git a/axelrod/tests/unit/test_calculator.py b/axelrod/tests/unit/test_calculator.py index 92b548f12..bcbcda2f2 100644 --- a/axelrod/tests/unit/test_calculator.py +++ b/axelrod/tests/unit/test_calculator.py @@ -44,3 +44,7 @@ def test_strategy(self): D, C] self.responses_test([C], [C] * 22, history) + def attribute_equality_test(self, player, clone): + """Overwrite the default test to check Joss instance""" + self.assertIsInstance(player.joss_instance, axelrod.Joss) + self.assertIsInstance(clone.joss_instance, axelrod.Joss) diff --git a/axelrod/tests/unit/test_grumpy.py b/axelrod/tests/unit/test_grumpy.py index 1d85fb322..c8ce48c7a 100644 --- a/axelrod/tests/unit/test_grumpy.py +++ b/axelrod/tests/unit/test_grumpy.py @@ -45,7 +45,7 @@ def test_strategy(self): init_kwargs={"grumpy_threshold": 3, "nice_threshold": 0}) - def test_reset(self): + def test_reset_state_with_non_default_init(self): P1 = axelrod.Grumpy(starting_state='Grumpy') P1.state = 'Nice' P1.reset() diff --git a/axelrod/tests/unit/test_human.py b/axelrod/tests/unit/test_human.py index 9f5d946c0..ec0be8506 100644 --- a/axelrod/tests/unit/test_human.py +++ b/axelrod/tests/unit/test_human.py @@ -91,3 +91,7 @@ def test_strategy(self): expected_action = C actual_action = human.strategy(Player(), lambda: C) self.assertEqual(actual_action, expected_action) + + def test_reset_history_and_attributes(self): + """Overwrite the reset method for this strategy.""" + pass diff --git a/axelrod/tests/unit/test_memorytwo.py b/axelrod/tests/unit/test_memorytwo.py index fbda665fb..d245a97dc 100644 --- a/axelrod/tests/unit/test_memorytwo.py +++ b/axelrod/tests/unit/test_memorytwo.py @@ -41,3 +41,16 @@ def test_strategy(self): # ALLD forever if all D twice self.responses_test([D] * 10, [C, D, D, D, D, D], [D, D, D, D, D, D]) self.responses_test([D] * 9, [C] + [D] * 5 + [C] * 4, [D] * 6 + [C] * 4) + + def attribute_equality_test(self, player, clone): + """Overwrite specific test to be able to test self.players""" + for p in [player, clone]: + self.assertEqual(p.play_as, "TFT") + self.assertEqual(p.shift_counter, 3) + self.assertEqual(p.alld_counter, 0) + + for key, value in [("TFT", axelrod.TitForTat), + ("TFTT", axelrod.TitFor2Tats), + ("ALLD", axelrod.Defector)]: + self.assertEqual(p.players[key].history, []) + self.assertIsInstance(p.players[key], value) diff --git a/axelrod/tests/unit/test_meta.py b/axelrod/tests/unit/test_meta.py index e36fb107e..065c3e71c 100644 --- a/axelrod/tests/unit/test_meta.py +++ b/axelrod/tests/unit/test_meta.py @@ -46,16 +46,15 @@ def classifier_test(self, expected_class_classifier=None): msg="%s - Behaviour: %s != Expected Behaviour: %s" % (key, player.classifier[key], classifier[key])) - def test_reset(self): - p1 = self.player() - p2 = axelrod.Cooperator() - p1.play(p2) - p1.play(p2) - p1.play(p2) - p1.reset() - for player in p1.team: - self.assertEqual(len(player.history), 0) + def attribute_equality_test(self, player, clone): + """Overwriting this specific method to check team.""" + for p1, p2 in zip(player.team, clone.team): + self.assertEqual(len(p1.history), 0) + self.assertEqual(len(p2.history), 0) + team_player_names = [p.__repr__() for p in player.team] + team_clone_names = [p.__repr__() for p in clone.team] + self.assertEqual(team_player_names, team_clone_names) class TestMetaMajority(TestMetaPlayer): @@ -222,8 +221,6 @@ def test_strategy(self): self.responses_test([D], [C] * 4, [D] * 4) self.responses_test([D], [C] * 6, [C, D] * 3) self.responses_test([D], [C] * 8, [C, C, C, D, C, C, C, D]) - self.responses_test([D], [C] * 100, - [random.choice([C, D]) for i in range(100)]) # Test post 100 rounds responses self.responses_test([C], [C] * 101, [C] * 101) self.responses_test([D], [C] * 101, [C] * 100 + [D]) @@ -257,8 +254,6 @@ def test_strategy(self): self.responses_test([D], [C] * 4, [D] * 4) self.responses_test([D], [C] * 6, [C, D] * 3) self.responses_test([D], [C] * 8, [C, C, C, D, C, C, C, D]) - self.responses_test([D], [C] * 100, - [random.choice([C, D]) for i in range(100)]) # Test post 100 rounds responses self.responses_test([D], [C] * 101, [C] * 101) self.responses_test([D], [C] * 101, [C] * 100 + [D]) diff --git a/axelrod/tests/unit/test_mock_player.py b/axelrod/tests/unit/test_mock_player.py index 10b1896b8..689e66b5f 100644 --- a/axelrod/tests/unit/test_mock_player.py +++ b/axelrod/tests/unit/test_mock_player.py @@ -23,13 +23,14 @@ def test_strategy(self): def test_history(self): t = TestOpponent() m1 = MockPlayer([C], history=[C]*10) - self.assertEqual(m1.actions[0], C) + self.assertEqual(m1.actions.__next__(), C) self.assertEqual(m1.history, [C] * 10) self.assertEqual(m1.cooperations, 10) self.assertEqual(m1.defections, 0) self.assertEqual(m1.strategy(t), C) + m2 = MockPlayer([D], history=[D]*10) - self.assertEqual(m2.actions[0], D) + self.assertEqual(m2.actions.__next__(), D) self.assertEqual(m2.history, [D] * 10) self.assertEqual(m2.cooperations, 0) self.assertEqual(m2.defections, 10) diff --git a/axelrod/tests/unit/test_player.py b/axelrod/tests/unit/test_player.py index 43ee22e85..bacb868a2 100644 --- a/axelrod/tests/unit/test_player.py +++ b/axelrod/tests/unit/test_player.py @@ -1,6 +1,7 @@ import random import unittest import warnings +import types import numpy as np @@ -207,37 +208,52 @@ def test_match_attributes(self): t_attrs = player.match_attributes self.assertEqual(t_attrs['noise'], .5) - def test_reset_history(self): + def test_reset_history_and_attributes(self): """Make sure resetting works correctly.""" - if self.player.name == "Human": - return - p = self.player() - p_clone = p.clone() - player2 = axelrod.Random() - for _ in range(10): - p.play(player2) - p.reset() - self.assertEqual(len(p.history), 0) - self.assertEqual(self.player().cooperations, 0) - self.assertEqual(self.player().defections, 0) - self.assertEqual(self.player().state_distribution, dict()) - - for k, v in p_clone.__dict__.items(): - try: - self.assertEqual(v, getattr(p_clone, k)) - except ValueError: - self.assertTrue(np.array_equal(v, getattr(p_clone, k))) + player = self.player() + clone = player.clone() + opponent = axelrod.Random() + + for seed in range(10): + axelrod.seed(seed) + player.play(opponent) + + player.reset() + self.assertEqual(len(player.history), 0) + self.assertEqual(player.cooperations, 0) + self.assertEqual(player.defections, 0) + self.assertEqual(player.state_distribution, dict()) + + self.attribute_equality_test(player, clone) def test_reset_clone(self): """Make sure history resetting with cloning works correctly, regardless if self.test_reset() is overwritten.""" player = self.player() clone = player.clone() - for k, v in clone.__dict__.items(): - if isinstance(v, np.ndarray): - self.assertTrue(np.array_equal(v, getattr(clone, k))) + self.attribute_equality_test(player, clone) + + def attribute_equality_test(self, player, clone): + """A separate method to test equality of attributes. This method can be + overwritten in certain cases. + + This method checks that all the attributes of `player` and `clone` are + the same which is used in the test of the cloning and the resetting. + """ + + for attribute, reset_value in player.__dict__.items(): + original_value = getattr(clone, attribute) + + if isinstance(reset_value, np.ndarray): + self.assertTrue(np.array_equal(reset_value, original_value), + msg=attribute) + + elif isinstance(reset_value, types.GeneratorType): + for _ in range(10): + self.assertEqual(next(reset_value), + next(original_value), msg=attribute) else: - self.assertEqual(v, getattr(clone, k)) + self.assertEqual(reset_value, original_value, msg=attribute) def test_clone(self): # Test that the cloned player produces identical play @@ -288,8 +304,61 @@ def second_play_test(self, rCC, rCD, rDC, rDD, seed=None): test_responses(self, self.player(), axelrod.Defector(), rDD, D, D, seed=seed) + def versus_test(self, opponent, expected_actions, + noise=None, seed=None, turns=10, + match_attributes=None, attrs=None, + init_kwargs=None): + """ + Tests a sequence of outcomes for two given players. + + Parameters: + ----------- + + opponent: Player or list + An instance of a player OR a sequence of actions. If a sequence of + actions is passed, a Mock Player is created that cycles over that + sequence. + expected_actions: List + The expected outcomes of the match (list of tuples of actions). + noise: float + Any noise to be passed to a match + seed: int + The random seed to be used + length: int + The length of the game. If `opponent` is a sequence of actions then + the length is taken to be the length of the sequence. + match_attributes: dict + The match attributes to be passed to the players. For example, + `{length:-1}` implies that the players do not know the length of the + match. + attrs: dict + Dictionary of internal attributes to check at the end of all plays + in player + init_kwargs: dict + A dictionary of keyword arguments to instantiate player with + """ + + turns = len(expected_actions) + if init_kwargs is None: + init_kwargs = dict() + + if seed is not None: + axelrod.seed(seed) + + player = self.player(**init_kwargs) + + match = axelrod.Match((player, opponent), turns=turns, noise=noise, + match_attributes=match_attributes) + self.assertEqual(match.play(), expected_actions) + + if attrs: + player = match.players[0] + for attr, value in attrs.items(): + self.assertEqual(getattr(player, attr), value) + def responses_test(self, responses, player_history=None, - opponent_history=None, seed=None, length=200, attrs=None, + opponent_history=None, seed=None, length=200, + attrs=None, init_args=None, init_kwargs=None): """Test responses to arbitrary histories. A match is played where the histories are enforced and the sequence of plays in responses is diff --git a/axelrod/tests/unit/test_punisher.py b/axelrod/tests/unit/test_punisher.py index 9769c49e4..d8c19afaf 100644 --- a/axelrod/tests/unit/test_punisher.py +++ b/axelrod/tests/unit/test_punisher.py @@ -47,16 +47,6 @@ def test_strategy(self): attrs={"grudged": True, "grudge_memory": 0, "mem_length": 2}) - def test_reset_method(self): - """Tests the reset method.""" - P1 = axelrod.Punisher() - P1.history = [C, D, D, D] - P1.grudged = True - P1.grudge_memory = 4 - P1.reset() - self.assertEqual(P1.grudged, False) - self.assertEqual(P1.grudge_memory, 0) - class TestInversePunisher(TestPlayer): @@ -103,15 +93,6 @@ def test_strategy(self): attrs={"grudged": True, "grudge_memory": 0, "mem_length": 16}) - def test_reset_method(self): - P1 = axelrod.InversePunisher() - P1.history = [C, D, D, D] - P1.grudged = True - P1.grudge_memory = 4 - P1.reset() - self.assertEqual(P1.grudged, False) - self.assertEqual(P1.grudge_memory, 0) - class TestLevelPunisher(TestPlayer): name = "Level Punisher" @@ -129,11 +110,11 @@ class TestLevelPunisher(TestPlayer): def test_strategy(self): # Starts by Cooperating self.first_play_test(C) - + # Defects if the turns played are less than 10. self.responses_test([C], [C], [C]) self.responses_test([C], [C] * 4, [C, D, C, D]) - + # Check for the number of rounds greater than 10. self.responses_test([C], [C] * 10, [C, C, C, C, D, C, C, C, C, D]) #Check if number of defections by opponent is greater than 20% diff --git a/axelrod/tests/unit/test_rand.py b/axelrod/tests/unit/test_rand.py index 5d78da5a4..f6a4b3afa 100644 --- a/axelrod/tests/unit/test_rand.py +++ b/axelrod/tests/unit/test_rand.py @@ -24,7 +24,24 @@ def test_strategy(self): """Test that strategy is randomly picked (not affected by history).""" self.first_play_test(C, seed=1) self.first_play_test(D, seed=2) - self.responses_test([C], [C, D, C], [C, C, D], seed=1) + + opponent = axelrod.MockPlayer() + actions = [(C, C), (D, C), (D, C), (C, C)] + self.versus_test(opponent, expected_actions=actions, seed=1) + + opponent = axelrod.MockPlayer() + actions = [(D, C), (D, C), (C, C)] + self.versus_test(opponent, expected_actions=actions, seed=2) + + opponent = axelrod.MockPlayer() + actions = [(D, C), (D, C), (D, C)] + self.versus_test(opponent, expected_actions=actions, + init_kwargs={"p": 0}) + + opponent = axelrod.MockPlayer() + actions = [(C, C), (C, C), (C, C)] + self.versus_test(opponent, expected_actions=actions, + init_kwargs={"p": 1}) def test_deterministic_classification(self): """Test classification when p is 0 or 1""" diff --git a/axelrod/tests/unit/test_stalker.py b/axelrod/tests/unit/test_stalker.py index f319a8691..302daae5c 100644 --- a/axelrod/tests/unit/test_stalker.py +++ b/axelrod/tests/unit/test_stalker.py @@ -13,7 +13,7 @@ class TestStalker(TestPlayer): expected_classifier = { 'memory_depth': float('inf'), 'stochastic': True, - 'makes_use_of': set(), + 'makes_use_of': set(["game", "length"]), 'long_run_time': False, 'inspects_source': False, 'manipulates_source': False, @@ -44,10 +44,3 @@ def test_strategy(self): # defect in last round self.responses_test([C, D], [C] * 198, [C] * 198, length=200) - - def test_reset(self): - """Tests to see if the score is reset correctly """ - P1 = axelrod.Stalker() - P1.current_score = 14 - P1.reset() - self.assertEqual(P1.current_score, 0) diff --git a/axelrod/tests/unit/test_titfortat.py b/axelrod/tests/unit/test_titfortat.py index 92face35e..0c0b7e78c 100644 --- a/axelrod/tests/unit/test_titfortat.py +++ b/axelrod/tests/unit/test_titfortat.py @@ -30,12 +30,42 @@ class TestTitForTat(TestPlayer): } def test_strategy(self): - # Starts by cooperating. self.first_play_test(C) - # Repeats last action of opponent history. - self.second_play_test(C, D, C, D) - self.responses_test([C], [C] * 4, [C, C, C, C]) - self.responses_test([D], [C] * 5, [C, C, C, C, D]) + self.second_play_test(rCC=C, rCD=D, rDC=C, rDD=D) + + # Play against opponents + actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] + self.versus_test(axelrod.Alternator(), expected_actions=actions) + + actions = [(C, C), (C, C), (C, C), (C, C), (C, C)] + self.versus_test(axelrod.Cooperator(), expected_actions=actions) + + actions = [(C, D), (D, D), (D, D), (D, D), (D, D)] + self.versus_test(axelrod.Defector(), expected_actions=actions) + + # This behaviour is independent of knowledge of the Match length + actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] + self.versus_test(axelrod.Alternator(), expected_actions=actions, + match_attributes={"length": -1}) + + # We can also test against random strategies + actions = [(C, D), (D, D), (D, C), (C, C), (C, D)] + self.versus_test(axelrod.Random(), expected_actions=actions, + seed=0) + + actions = [(C, C), (C, D), (D, D), (D, C)] + self.versus_test(axelrod.Random(), expected_actions=actions, + seed=1) + + # If you would like to test against a sequence of moves you should use + # a MockPlayer + opponent = axelrod.MockPlayer([C, D]) + actions = [(C, C), (C, D), (D, C), (C, D)] + self.versus_test(opponent, expected_actions=actions) + + opponent = axelrod.MockPlayer([C, C, D, D, C, D]) + actions = [(C, C), (C, C), (C, D), (D, D), (D, C), (C, D)] + self.versus_test(opponent, expected_actions=actions) class TestTitFor2Tats(TestPlayer): @@ -52,11 +82,13 @@ class TestTitFor2Tats(TestPlayer): } def test_strategy(self): - # Starts by cooperating. self.first_play_test(C) - # Will defect only when last two turns of opponent were defections. - self.responses_test([D], [C, C, C], [D, D, D]) - self.responses_test([C], [C, C, D, D], [D, D, D, C]) + self.second_play_test(rCC=C, rCD=C, rDC=C, rDD=C) + + # Will punish sequence of 2 defections but will forgive + opponent = axelrod.MockPlayer([D, D, D, C, C]) + actions = [(C, D), (C, D), (D, D), (D, C), (C, C)] + self.versus_test(opponent, expected_actions=actions) class TestTwoTitsForTat(TestPlayer): @@ -73,13 +105,13 @@ class TestTwoTitsForTat(TestPlayer): } def test_strategy(self): - # Starts by cooperating. self.first_play_test(C) + self.second_play_test(rCC=C, rCD=D, rDC=C, rDD=D) + # Will defect twice when last turn of opponent was defection. - self.responses_test([D], [C], [D]) - self.responses_test([D], [C, C], [D, D]) - self.responses_test([D], [C, C, C], [D, D, C]) - self.responses_test([C], [C, C, D, D], [D, D, C, C]) + opponent = axelrod.MockPlayer([D, C, C, D, C]) + actions = [(C, D), (D, C), (D, C), (C, D), (D, C)] + self.versus_test(opponent, expected_actions=actions) class TestBully(TestPlayer): @@ -99,7 +131,16 @@ def test_strategy(self): # Starts by defecting. self.first_play_test(D) # Will do opposite of what opponent does. - self.second_play_test(D, C, D, C) + self.second_play_test(rCC=D, rCD=C, rDC=D, rDD=C) + + actions = [(D, C), (D, D), (C, C), (D, D), (C, C)] + self.versus_test(axelrod.Alternator(), expected_actions=actions) + + actions = [(D, C), (D, C), (D, C), (D, C), (D, C)] + self.versus_test(axelrod.Cooperator(), expected_actions=actions) + + actions = [(D, D), (C, D), (C, D), (C, D), (C, D)] + self.versus_test(axelrod.Defector(), expected_actions=actions) class TestSneakyTitForTat(TestPlayer): @@ -118,10 +159,14 @@ class TestSneakyTitForTat(TestPlayer): def test_strategy(self): # Starts by cooperating. self.first_play_test(C) - # Will try defecting after two turns of cooperation, but will stop - # if punished. - self.responses_test([D], [C, C], [C, C]) - self.responses_test([C], [C, C, D, D], [C, C, C, D]) + + opponent = axelrod.MockPlayer([C, C, C, D, C, C]) + actions = [(C, C), (C, C), (D, C), (D, D), (C, C), (C, C)] + self.versus_test(opponent, expected_actions=actions) + + # Repents if punished for a defection + actions = [(C, C), (C, D), (D, C), (C, D), (C, C)] + self.versus_test(axelrod.Alternator(), expected_actions=actions) class TestSuspiciousTitForTat(TestPlayer): @@ -142,7 +187,10 @@ def test_strategy(self): self.first_play_test(D) # Plays like TFT after the first move, repeating the opponents last # move. - self.second_play_test(C, D, C, D) + self.second_play_test(rCC=C, rCD=D, rDC=C, rDD=D) + + actions = [(D, C), (C, D)] * 8 + self.versus_test(axelrod.TitForTat(), expected_actions=actions) class TestAntiTitForTat(TestPlayer): @@ -164,13 +212,16 @@ def test_strategy(self): # Will do opposite of what opponent does. self.second_play_test(D, C, D, C) + actions = [(C, C), (D, C), (D, D), (C, D)] * 4 + self.versus_test(axelrod.TitForTat(), expected_actions=actions) + class TestHardTitForTat(TestPlayer): name = "Hard Tit For Tat" player = axelrod.HardTitForTat expected_classifier = { - 'memory_depth': 3, # Four-Vector = (1.,0.,1.,0.) + 'memory_depth': 3, 'stochastic': False, 'makes_use_of': set(), 'inspects_source': False, @@ -181,12 +232,10 @@ class TestHardTitForTat(TestPlayer): def test_strategy(self): # Starts by cooperating. self.first_play_test(C) - # Repeats last action of opponent history. - self.responses_test([C], [C, C, C], [C, C, C]) - self.responses_test([D], [C, C, C], [D, C, C]) - self.responses_test([D], [C, C, C], [C, D, C]) - self.responses_test([D], [C, C, C], [C, C, D]) - self.responses_test([C], [C, C, C, C], [D, C, C, C]) + + opponent = axelrod.MockPlayer([D, C, C, C, D, C]) + actions = [(C, D), (D, C), (D, C), (D, C), (C, D), (D, C)] + self.versus_test(opponent, expected_actions=actions) class TestHardTitFor2Tats(TestPlayer): @@ -205,22 +254,14 @@ class TestHardTitFor2Tats(TestPlayer): def test_strategy(self): # Starts by cooperating. self.first_play_test(C) - # Repeats last action of opponent history. - self.responses_test([C], [C, C, C], [C, C, C]) - self.responses_test([C], [C, C, C], [D, C, C]) - self.responses_test([C], [C, C, C], [C, D, C]) - self.responses_test([C], [C, C, C], [C, C, D]) - - self.responses_test([C], [C, C, C], [D, C, D]) - self.responses_test([D], [C, C, C], [D, D, C]) - self.responses_test([D], [C, C, C], [C, D, D]) - self.responses_test([C], [C, C, C, C], [D, C, C, C]) - self.responses_test([C], [C, C, C, C], [D, D, C, C]) - self.responses_test([D], [C, C, C, C], [C, D, D, C]) + # Uses memory 3 to punish 2 consecutive defections + opponent = axelrod.MockPlayer([D, C, C, D, D, D, C]) + actions = [(C, D), (C, C), (C, C), (C, D), (C, D), (D, D), (D, C)] + self.versus_test(opponent, expected_actions=actions) -class OmegaTFT(TestPlayer): +class TestOmegaTFT(TestPlayer): name = "Omega TFT" player = axelrod.OmegaTFT @@ -237,34 +278,17 @@ class OmegaTFT(TestPlayer): def test_strategy(self): # Starts by cooperating. self.first_play_test(C) - for i in range(10): - self.responses_test([C], [C] * i, [C] * i) - - def test_reset(self): - player = self.player() - opponent = axelrod.Defector() - [player.play(opponent) for _ in range(10)] - player.reset() - self.assertEqual(player.randomness_counter, 0) - self.assertEqual(player.deadlock_counter, 0) + player_history = [C, C, D, C, D, C, C, C, D, C, C, C, D, D, D, D, D, D] + opp_history = [C, D] * 9 + actions = list(zip(player_history, opp_history)) + self.versus_test(axelrod.Alternator(), expected_actions=actions) -class TestOmegaTFTvsSTFT(TestMatch): - def test_rounds(self): - self.versus_test( - axelrod.OmegaTFT(), axelrod.SuspiciousTitForTat(), - [C, D, C, D, C, C, C, C, C], - [D, C, D, C, D, C, C, C, C] - ) - - -class TestOmegaTFTvsAlternator(TestMatch): - def test_rounds(self): - self.versus_test( - axelrod.OmegaTFT(), axelrod.Alternator(), - [C, C, D, C, D, C, C, C, D, C, C, C, D, D, D, D, D, D], - [C, D, C, D, C, D, C, D, C, D, C, D, C, D, C, D, C, D] - ) + player_history = [C, D, C, D, C, C, C, C, C] + opp_history = [D, C, D, C, D, C, C, C, C] + actions = list(zip(player_history, opp_history)) + self.versus_test(axelrod.SuspiciousTitForTat(), + expected_actions=actions) class TestGradual(TestPlayer): @@ -285,82 +309,62 @@ def test_strategy(self): self.first_play_test(C) # Punishes defection with a growing number of defections and calms # the opponent with two cooperations in a row. - self.responses_test([C], [C], [C], attrs={ - "calming": False, - "punishing": False, - "punishment_count": 0, - "punishment_limit": 0 - }) - self.responses_test([D], [C], [D], attrs={ - "calming": False, - "punishing": True, - "punishment_count": 1, - "punishment_limit": 1 - }) - self.responses_test([C], [C, D], [D, C], attrs={ - "calming": True, - "punishing": False, - "punishment_count": 0, - "punishment_limit": 1 - }) - self.responses_test([C], [C, D, C], [D, C, D], attrs={ - "calming": False, - "punishing": False, - "punishment_count": 0, - "punishment_limit": 1 - }) - self.responses_test([C], [C, D, C, C], [D, C, D, C], attrs={ - "calming": False, - "punishing": False, - "punishment_count": 0, - "punishment_limit": 1 - }) - self.responses_test([C], [C, D, C, D, C], [D, C, D, C, C], attrs={ - "calming": False, - "punishing": False, - "punishment_count": 0, - "punishment_limit": 1 - }) - self.responses_test([D], [C, D, C, D, C, C], [D, C, D, C, C, D], attrs={ - "calming": False, - "punishing": True, - "punishment_count": 1, - "punishment_limit": 2 - }) - self.responses_test([D], [C, D, C, D, D, C, D], [D, C, D, C, C, D, C], - attrs={ - "calming": False, - "punishing": True, - "punishment_count": 2, - "punishment_limit": 2 - }) - self.responses_test([C], [C, D, C, D, D, C, D, D], - [D, C, D, C, C, D, C, C], attrs={ - "calming": True, - "punishing": False, - "punishment_count": 0, - "punishment_limit": 2 - }) - self.responses_test([C], [C, D, C, D, D, C, D, D, C], - [D, C, D, C, C, D, C, C, C], attrs={ - "calming": False, - "punishing": False, - "punishment_count": 0, - "punishment_limit": 2 - }) - - def test_reset_cleans_all(self): - p = axelrod.Gradual() - p.calming = True - p.punishing = True - p.punishment_count = 1 - p.punishment_limit = 1 - p.reset() + opponent = axelrod.MockPlayer([C]) + actions = [(C, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": False, + "punishment_count": 0, "punishment_limit": 0}) + + opponent = axelrod.MockPlayer([D]) + actions = [(C, D)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": False, + "punishment_count": 0, "punishment_limit": 0}) + + opponent = axelrod.MockPlayer([D, C]) + actions = [(C, D), (D, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": True, + "punishment_count": 1, "punishment_limit": 1}) + + opponent = axelrod.MockPlayer([D, C, C]) + actions = [(C, D), (D, C), (C, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": True, "punishing": False, + "punishment_count": 0, "punishment_limit": 1}) + + opponent = axelrod.MockPlayer([D, C, D, C]) + actions = [(C, D), (D, C), (C, D), (C, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": False, + "punishment_count": 0, "punishment_limit": 1}) + + opponent = axelrod.MockPlayer([D, C, D, C, C]) + actions = [(C, D), (D, C), (C, D), (C, C), (C, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": False, + "punishment_count": 0, "punishment_limit": 1}) + + opponent = axelrod.MockPlayer([D, C, D, C, C, C]) + actions = [(C, D), (D, C), (C, D), (C, C), (C, C), (C, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": False, + "punishment_count": 0, "punishment_limit": 1}) + + opponent = axelrod.MockPlayer([D, C, D, C, C, C, D, C]) + actions = [(C, D), (D, C), (C, D), (C, C), + (C, C), (C, C), (C, D), (D, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": True, + "punishment_count": 1, "punishment_limit": 2}) + + opponent = axelrod.MockPlayer([D, C, D, C, C, D, D, D]) + actions = [(C, D), (D, C), (C, D), (C, C), + (C, C), (C, D), (D, D), (D, D)] + self.versus_test(opponent, expected_actions=actions, + attrs={"calming": False, "punishing": True, + "punishment_count": 2, "punishment_limit": 2}) - self.assertFalse(p.calming) - self.assertFalse(p.punishing) - self.assertEqual(p.punishment_count, 0) - self.assertEqual(p.punishment_limit, 0) def test_output_from_literature(self): """ @@ -373,7 +377,7 @@ def test_output_from_literature(self): This test just ensures that the strategy is as was originally defined. """ - player = axelrod.Gradual() + player = self.player() opp1 = axelrod.Defector() match = axelrod.Match((player, opp1), 1000) @@ -448,7 +452,8 @@ def test_strategy_with_noise(self): self.assertEqual(opponent.history, [C, D, D, D]) self.assertFalse(ctft.contrite) - def test_reset_cleans_all(self): + def test_reset_history_and_attributes(self): + """Overwrite reset test because of decorator""" p = self.player() p.contrite = True p.reset() @@ -473,9 +478,10 @@ def test_strategy(self): self.first_play_test(C) # If opponent plays the same move twice, repeats last action of # opponent history. - self.responses_test([C], [C] * 2, [C, C]) - self.responses_test([C], [C] * 3, [C, D, C]) - self.responses_test([D], [C] * 3, [C, D, D]) + opponent = axelrod.MockPlayer([C, C, D, D, C, D, D, C, C, D, D]) + actions = [(C, C), (C, C), (C, D), (C, D), (D, C), (C, D), (C, D), + (D, C), (C, C), (C, D), (C, D)] + self.versus_test(opponent, expected_actions=actions) class TestAdaptiveTitForTat(TestPlayer): @@ -496,18 +502,9 @@ def test_strategy(self): self.first_play_test(C) self.second_play_test(C, D, C, D) - p1, p2 = self.player(), self.player() - p1.play(p2) - p1.play(p2) - self.assertEqual(p2.world, 0.75) - - def test_world_rate_reset(self): - p1, p2 = self.player(), self.player() - p1.play(p2) - p1.play(p2) - p2.reset() - self.assertEqual(p2.world, 0.5) - self.assertEqual(p2.rate, 0.5) + actions = [(C, C), (C, C)] + self.versus_test(self.player(), expected_actions=actions, + attrs={"world":0.75, "rate":0.5}) class TestSpitefulTitForTat(TestPlayer): @@ -528,15 +525,18 @@ def test_strategy(self): # Repeats last action of opponent history until 2 consecutive # defections, then always defects self.second_play_test(C, D, C, D) - self.responses_test([C], [C] * 4, [C, C, C, C], - attrs={"retaliating": False}) - self.responses_test([D], [C] * 5, [C, C, C, C, D], - attrs={"retaliating": False}) - self.responses_test([D], [C] * 5, [C, C, D, D, C], - attrs={"retaliating": True}) - - def test_reset_retaliating(self): - player = self.player() - player.retaliating = True - player.reset() - self.assertFalse(player.retaliating) + + opponent = axelrod.MockPlayer([C, C, C, C]) + actions = [(C, C)] * 5 + self.versus_test(opponent, expected_actions=actions, + attrs={"retaliating": False}) + + opponent = axelrod.MockPlayer([C, C, C, C, D, C]) + actions = [(C, C)] * 4 + [(C, D), (D, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"retaliating": False}) + + opponent = axelrod.MockPlayer([C, C, D, D, C]) + actions = [(C, C), (C, C), (C, D), (D, D), (D, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"retaliating": True}) diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index bc3e86389..c24be13c3 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -80,7 +80,7 @@ length of each match of the tournament:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 24 + 25 Note that in the filterset dictionary, the value for the 'makes_use_of' key must be a list. Here is how we might identify the number of strategies that use @@ -91,7 +91,7 @@ both the length of the tournament and the game being played:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 20 + 21 Some strategies have been classified as having a particularly long run time:: diff --git a/docs/tutorials/contributing/strategy/writing_test_for_the_new_strategy.rst b/docs/tutorials/contributing/strategy/writing_test_for_the_new_strategy.rst index 849f53bb8..d4c290c82 100644 --- a/docs/tutorials/contributing/strategy/writing_test_for_the_new_strategy.rst +++ b/docs/tutorials/contributing/strategy/writing_test_for_the_new_strategy.rst @@ -11,11 +11,6 @@ Typically we want to test the following: * That the strategy behaves as intended on the first move and subsequent moves, triggering any expected actions * That the strategy initializes correctly -* That the strategy resets and clones correctly - -If the strategy does not use any internal variables then there are generic tests -that are automatically invoked to verify proper initialization, resetting, and -cloning. A :code:`TestPlayer` class has been written that has a number of convenience methods to help write tests efficiently for how a strategy plays. It has three @@ -44,32 +39,47 @@ argument :code:`seed` (useful and necessary for stochastic strategies, :code:`D` following the last round of play and checking the player's subsequent action. -3. The member function :code:`responses_test` takes arbitrary histories for each - player and tests a list of expected next responses:: - - self.responses_test(responses=[D, C, C, C], player_history=[C], - opponent_history=[C], seed=None) +3. The member function :code:`versus_test` can be used to test how the player + plays against a given opponent:: - In this case each player has their history simulated to be :code:`[C]` and - the expected responses are D, C, C, C. Note that the histories will elongate - as the responses accumulated, with the opponent accruing cooperations. + self.versus_test(opponent=axelrod.MockPlayer([C, D]), + expected_actions=[(D, C), (C, D), (C, C)], seed=None) - If the given histories are not possible for the strategy then the test will - not be meaningful. For example, setting the history of Defector to have - cooperations is not a possible history of play since Defector always defects, - and so will not actually test the strategy correctly. The test suite will - warn you if it detects a mismatch in simulated history and actual history. + In this case the player is tested against an opponent that will cycle through + :code:`C, D`. The :code:`expected_actions` are the actions player by both + the tested player and the opponent in the match. In this case we see that the + player is expected to play :code:`D, C, C` against :code:`C, D, C`. - Note also that in general it is not a good idea to manually set the history - of any player. + Note that you can either user a :code:`MockPlayer` that will cycle through a + given sequence or you can use another strategy from the Axelrod library. - The function :code:`responses_test` also accepts a dictionary parameter of - attributes to check at the end of the checks. For example this test checks + The function :code:`versus_test` also accepts a dictionary parameter of + attributes to check at the end of the match. For example this test checks if the player's internal variable :code:`opponent_class` is set to :code:`"Cooperative"`:: - self.responses_test([C], [C] * 6, [C] * 6, - attrs={"opponent_class": "Cooperative"}) + actions = [(C, C)] * 6 + self.versus_test(axelrod.Cooperator(), expected_actions=actions + attrs={"opponent_class": "Cooperative"}) + + Note here that instead of passing a sequence of actions as an opponent we are + passing an actual player from the axelrod library. + + The function :code:`versus_test` also accepts a dictionary parameter of match + attributes that dictate the knowledge of the players. For example this test + assumes that players do not know the length of the match:: + + actions = [(C, C), (C, D), (D, C), (C, D)] + self.versus_test(axelrod.Alternator(), expected_actions=actions, + match_attributes={"length": -1}) + + The function :code:`versus_test` also accepts a dictionary parameter of + keyword arguments that dictate how the player is initiated. For example this + test how the player plays when initialised with :code:`p=1`:: + + actions = [(C, C), (C, D), (C, C), (C, D)] + self.versus_test(axelrod.Alternator(), expected_actions=actions, + init_kwargs={"p": 1}) As an example, the tests for Tit-For-Tat are as follows:: @@ -90,41 +100,53 @@ As an example, the tests for Tit-For-Tat are as follows:: expected_classifier = { 'memory_depth': 1, 'stochastic': False, + 'makes_use_of': set(), 'inspects_source': False, 'manipulates_source': False, 'manipulates_state': False } def test_strategy(self): - # Starts by cooperating. self.first_play_test(C) - # Repeats last action of opponent history. - self.second_play_test(C, D, C, D) - self.responses_test([C], [C, C, C, C], [C, C, C, C]) - self.responses_test([D], [C, C, C, C, C], [C, C, C, C, D]) + self.second_play_test(rCC=C, rCD=D, rDC=C, rDD=D) + + # Play against opponents + actions = [(C, C), (C, D), (D, C), (C, D)] + self.versus_test(axelrod.Alternator(), expected_actions=actions) -The :code:`test_strategy` method mainly checks that the -:code:`strategy` method in the :code:`TitForTat` class works as expected: + actions = [(C, C), (C, C), (C, C), (C, C)] + self.versus_test(axelrod.Cooperator(), expected_actions=actions) -1. If the opponent's last strategy was :code:`C`: then :code:`TitForTat` should - cooperate:: + actions = [(C, D), (D, D), (D, D), (D, D)] + self.versus_test(axelrod.Defector(), expected_actions=actions) - self.responses_test(responses=[C], player_history=[C], opponent_history=[C]) + # This behaviour is independent of knowledge of the Match length + actions = [(C, C), (C, D), (D, C), (C, D)] + self.versus_test(axelrod.Alternator(), expected_actions=actions, + match_attributes={"length": -1}) - Or simply:: + # We can also test against random strategies + actions = [(C, D), (D, D), (D, C), (C, C)] + self.versus_test(axelrod.Random(), expected_actions=actions, + seed=0) - self.responses_test([C], [C], [C]) + actions = [(C, C), (C, D), (D, D), (D, C)] + self.versus_test(axelrod.Random(), expected_actions=actions, + seed=1) -2. If the opponent's last strategy was :code:`D`: after four cooperates then - :code:`TitForTat` should defect. Note that we need to give the history for - :code:`TitForTat` as well:: + # If you would like to test against a sequence of moves you should use + # a MockPlayer + opponent = axelrod.MockPlayer([C, D]) + actions = [(C, C), (C, D), (D, C), (C, D)] + self.versus_test(opponent, expected_actions=actions) - self.responses_test(responses=[D], player_history=[C, C, C, C, C], - opponent_history=[C, C, C, C, D]) + opponent = axelrod.MockPlayer([C, C, D, D, C, D]) + actions = [(C, C), (C, C), (C, D), (D, D), (D, C), (C, D)] + self.versus_test(opponent, expected_actions=actions) - Or:: - self.responses_test([D], [C, C, C, C, C], [C, C, C, C, D]) +There are other examples of using this testing framework in +:code:`axelrod/tests/unit/test_titfortat.py`. The :code:`expected_classifier` dictionary tests that the classification of the strategy is as expected (the tests for this is inherited in the :code:`init` @@ -132,20 +154,3 @@ method). Please be sure to classify new strategies according to the already present dimensions but if you create a new dimension you do not **need** to re classify all the other strategies (but feel free to! :)), but please do add it to the :code:`default_classifier` in the :code:`axelrod/player.py` parent class. - -Finally, there is a :code:`TestMatch` class that streamlines the testing of -two strategies playing each other using a test function :code:`versus_test`. For -example, to test several rounds of play of :code:`TitForTwoTats` versus -:code:`Bully`:: - - class TestTF2TvsBully(TestMatch): - """Test Tit for Two Tats vs Bully""" - def test_rounds(self): - outcomes = [[C, D], [C, D], [D, D], [D, C], [C, C], [C, D], [C, D], [D, D]] - self.versus_test(axelrod.TitFor2Tats, axelrod.Bully, outcomes) - -Using :code:`TestMatch` is essentially equivalent to playing a short `Match` -between the players and checking the outcome. - -The function :code:`versus_test` also accepts a :code:`seed` keyword, and -like :code:`responses_test` the history is accumulated.