diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 5c618d875..6f3b94d4e 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -234,6 +234,7 @@ ZDGen2, ZDMischief, ZDSet2, + AdaptiveZeroDet, ) # Note: Meta* strategies are handled in .__init__.py @@ -242,6 +243,7 @@ all_strategies = [ Adaptive, AdaptiveTitForTat, + AdaptiveZeroDet, AdaptorBrief, AdaptorLong, Aggravater, diff --git a/axelrod/strategies/grudger.py b/axelrod/strategies/grudger.py index 61215bb9a..af844bc3e 100644 --- a/axelrod/strategies/grudger.py +++ b/axelrod/strategies/grudger.py @@ -64,8 +64,8 @@ class ForgetfulGrudger(Player): def __init__(self) -> None: """Initialised the player.""" - super().__init__() self.mem_length = 10 + super().__init__() self.grudged = False self.grudge_memory = 0 diff --git a/axelrod/strategies/zero_determinant.py b/axelrod/strategies/zero_determinant.py index ea5a16c80..11e8ed9c3 100644 --- a/axelrod/strategies/zero_determinant.py +++ b/axelrod/strategies/zero_determinant.py @@ -1,4 +1,6 @@ +import random from axelrod.action import Action +from axelrod.player import Player from .memoryone import MemoryOnePlayer @@ -225,3 +227,77 @@ class ZDSet2(LRPlayer): def __init__(self, phi: float = 1 / 4, s: float = 0.0, l: float = 2) -> None: super().__init__(phi, s, l) + + +class AdaptiveZeroDet(LRPlayer): + """A strategy that uses a zero determinant structure that updates + its parameters after each round of play. + + Names: + - AdaptiveZeroDet by Emmanuel Estrada and Dashiell Fryer + """ + name = 'AdaptiveZeroDet' + classifier = { + 'memory_depth': float('inf'), # Long memory + 'stochastic': True, + 'makes_use_of': set(["game"]), + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def __init__(self, phi: float = 0.125, s: float = 0.5, l: float = 3, + initial: Action = C) -> None: + # This Keeps track of the parameter values (phi,s,l) as well as the + # four vector which makes final decisions. + super().__init__(phi=phi, s=s, l=l) + self._scores = {C: 0, D: 0} + self._initial = initial + + def score_last_round(self, opponent: Player): + """This gives the strategy the game attributes and allows the strategy + to score itself properly.""" + game = self.match_attributes["game"] + if len(self.history): + last_round = (self.history[-1], opponent.history[-1]) + scores = game.score(last_round) + self._scores[last_round[0]] += scores[0] + + def _adjust_parameters(self): + d = random.randint(0, 9) / 1000 # Selects random value to adjust s and l + R, P, S, T = self.match_attributes["game"].RPST() + l = self.l + s = self.s + if self._scores[C] > self._scores[D]: + # This checks scores to determine how to adjust s and l either + # up or down by d, making sure not to exceed bounds. + if l + d > R: + l = (l + R) / 2 + else: + l += d + s_min = - min((T - l) / (l - S), (l - S) / (T - l)) + if s - d < s_min: + s = (s + s_min) / 2 + else: + s = s - d + else: + # This adjusts s and l in the opposite direction, also checking distance + if l - d < P: + l = (l + P) / 2 + else: + l -= d + if s + d > 1: + s = (s + 1) / 2 + else: + s += d + # Update the four vector for the new l and s values + self.l = l + self.s = s + self.receive_match_attributes() + + def strategy(self, opponent: Player) -> Action: + if len(self.history) > 0: + self.score_last_round(opponent) + self._adjust_parameters() + return super().strategy(opponent) diff --git a/axelrod/tests/strategies/test_zero_determinant.py b/axelrod/tests/strategies/test_zero_determinant.py index a6f479124..7a7f2a39e 100644 --- a/axelrod/tests/strategies/test_zero_determinant.py +++ b/axelrod/tests/strategies/test_zero_determinant.py @@ -317,3 +317,46 @@ def test_strategy(self): actions = [(C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] self.versus_test(opponent=axelrod.CyclerDC(), expected_actions=actions, seed=5) + + +class TestAdaptiveZeroDet(TestPlayer): + """ + Test the AdaptiveZeroDet strategy. + """ + + name = "AdaptiveZeroDet: 0.125, 0.5, 3, C" + player = axelrod.AdaptiveZeroDet + expected_classifier = { + "memory_depth": float('inf'), + "stochastic": True, + "makes_use_of": set(['game']), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def test_strategy(self): + R, P, S, T = axelrod.Game().RPST() + + player = self.player(l=R) + axelrod.seed(0) + match = axelrod.Match((player, axelrod.Alternator()), turns=200) + match.play() + + player = self.player(l=P) + axelrod.seed(0) + match = axelrod.Match((player, axelrod.Alternator()), turns=200) + match.play() + + player = self.player(s=1) + axelrod.seed(0) + match = axelrod.Match((player, axelrod.Alternator()), turns=200) + match.play() + + l = 2 + s_min = - min((T - l) / (l - S), (l - S) / (T - l)) + player = self.player(s=s_min, l=2) + axelrod.seed(0) + match = axelrod.Match((player, axelrod.Alternator()), turns=200) + match.play() diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index 9e7db52b4..36eae01a1 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -47,7 +47,7 @@ strategies:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 88 + 89 Or, to find out how many strategies only use 1 turn worth of memory to make a decision::