diff --git a/axelrod/player.py b/axelrod/player.py index 0eb60cf1c..96d32816e 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -199,6 +199,11 @@ def __repr__(self): prefix = ': ' gen = (value for value in self.init_kwargs.values() if value is not None) for value in gen: + try: + if issubclass(value, Player): + value = value.name + except TypeError: + pass name = ''.join([name, prefix, str(value)]) prefix = ', ' return name diff --git a/axelrod/strategies/__init__.py b/axelrod/strategies/__init__.py index 843cebb47..811e4d116 100644 --- a/axelrod/strategies/__init__.py +++ b/axelrod/strategies/__init__.py @@ -7,7 +7,7 @@ # because it creates circular dependencies from .meta import ( - MetaHunter, MetaHunterAggressive, MetaPlayer, MetaMajority, + MemoryDecay, MetaHunter, MetaHunterAggressive, MetaPlayer, MetaMajority, MetaMajorityMemoryOne, MetaMajorityFiniteMemory, MetaMajorityLongMemory, MetaMinority, MetaMixer, MetaWinner, MetaWinnerDeterministic, MetaWinnerEnsemble, MetaWinnerMemoryOne, MetaWinnerFiniteMemory, diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 59f32870e..30abf0daf 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -1,5 +1,7 @@ from numpy.random import choice +import random +from axelrod.strategies import TitForTat from axelrod.action import Action from axelrod.player import Player, obey_axelrod from axelrod.strategy_transformers import NiceTransformer @@ -533,3 +535,92 @@ def __init__(self): <= 1] super().__init__(team=team) self.classifier["long_run_time"] = False + +class MemoryDecay(MetaPlayer): + """ + A player utilizes the (default) Tit for Tat strategy for the first (default) 15 turns, + at the same time memorizing the opponent's decisions. After the 15 turns have + passed, the player calculates a 'net cooperation score' (NCS) for their opponent, + weighing decisions to Cooperate as (default) 1, and to Defect as (default) + -2. If the opponent's NCS is below 0, the player defects; otherwise, + they cooperate. + + The player's memories of the opponent's decisions have a random chance to be + altered (i.e., a C decision becomes D or vice versa; default probability + is 0.03) or deleted (default probability is 0.1). + + It is possible to pass a different axelrod player class to change the inital + player behavior. + + Name: Memory Decay + """ + name = 'Memory Decay' + classifier = { + 'memory_depth' : float('inf'), + 'long_run_time' : False, + 'stochastic' : True, + 'makes_use_of' : set(), + 'inspects_source' : False, + 'manipulates_source' : False, + 'manipulates_state' : False + } + + def __init__(self, p_memory_delete: float = 0.1, p_memory_alter: float = 0.03, + loss_value: float = -2, gain_value: float = 1, + memory: list = None, start_strategy: Player = TitForTat, + start_strategy_duration: int = 15): + super().__init__(team = [start_strategy]) + self.classifier["stochastic"] = True + self.p_memory_delete = p_memory_delete + self.p_memory_alter = p_memory_alter + self.loss_value = loss_value + self.gain_value = gain_value + self.memory = [] if memory == None else memory + self.start_strategy_duration = start_strategy_duration + + def __repr__(self): + return Player.__repr__(self) + + def gain_loss_translate(self): + """ + Translates the actions (D and C) to numeric values (loss_value and + gain_value). + """ + values = { + C: self.gain_value, + D: self.loss_value + } + self.gloss_values = [values[action] for action in self.memory] + + def memory_alter(self): + """ + Alters memory entry, i.e. puts C if there's a D and vice versa. + """ + alter = choice(range(0, len(self.memory))) + self.memory[alter] = self.memory[alter].flip() + + def memory_delete(self): + """ + Deletes memory entry. + """ + self.memory.pop(choice(range(0, len(self.memory)))) + + def strategy(self, opponent): + try: + self.memory.append(opponent.history[-1]) + except IndexError: + pass + if len(self.history) < self.start_strategy_duration: + play = self.team[0].strategy(opponent) + self.team[0].history.append(play) + return play + else: + if random.random() <= self.p_memory_alter: + self.memory_alter() + if random.random() <= self.p_memory_delete: + self.memory_delete() + self.gain_loss_translate() + if sum(self.gloss_values) < 0: + return D + else: + return C diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 44d90df80..22b5ed60b 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -585,3 +585,77 @@ def test_strategy(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C)] self.versus_test(opponent=axelrod.Alternator(), expected_actions=actions) + +"""Tests for the Memory Decay strategy""" + +class TestMemoryDecay(TestPlayer): + + name = "Memory Decay: 0.1, 0.03, -2, 1, Tit For Tat, 15" + player = axelrod.MemoryDecay + expected_classifier = { + 'memory_depth': float('inf'), + 'long_run_time': False, + 'stochastic': True, + 'makes_use_of': set(), + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def test_strategy(self): + #Test TitForTat behavior in first 15 turns + opponent = axelrod.Cooperator() + actions = list([(C, C)]) * 15 + self.versus_test(opponent, expected_actions=actions) + + opponent = axelrod.Defector() + actions = [(C, D)] + list([(D, D)]) * 14 + self.versus_test(opponent, expected_actions=actions) + + opponent = axelrod.Alternator() + actions = [(C, C)] + [(C, D), (D, C)] * 7 + self.versus_test(opponent, expected_actions=actions) + + opponent_actions = [C, D, D, C, D, C, C, D, C, D, D, C, C, D, D] + opponent = axelrod.MockPlayer(actions =opponent_actions) + mem_actions = [C, C, D, D, C, D, C, C, D, C, D, D, C, C, D] + actions = list(zip(mem_actions, opponent_actions)) + self.versus_test(opponent, expected_actions=actions) + + opponent = axelrod.Random() + actions = [(C, D), (D, D), (D, C), (C, C), (C, D), (D, C)] + self.versus_test(opponent, expected_actions=actions, seed=0) + + #Test net-cooperation-score (NCS) based decisions in subsequent turns + opponent = axelrod.Cooperator() + actions = [(C, C)] * 15 + [(C, C)] + self.versus_test(opponent, expected_actions=actions, seed=1, + init_kwargs = {'memory': [D] * 5 + [C] * 10}) + + opponent = axelrod.Cooperator() + actions = [(C, C)] * 15 + [(C, C)] + self.versus_test(opponent, expected_actions=actions, seed=1, + init_kwargs = {'memory': [D] * 4 + [C] * 11}) + + #Test alternative starting strategies + opponent = axelrod.Cooperator() + actions = list([(D, C)]) * 15 + self.versus_test(opponent, expected_actions=actions, + init_kwargs = {'start_strategy': axelrod.Defector}) + + opponent = axelrod.Cooperator() + actions = list([(C, C)]) * 15 + self.versus_test(opponent, expected_actions=actions, + init_kwargs = {'start_strategy': axelrod.Cooperator}) + + opponent = axelrod.Cooperator() + actions = [(C, C)] + list([(D, C), (C, C)]) * 7 + self.versus_test(opponent, expected_actions=actions, + init_kwargs = {'start_strategy': axelrod.Alternator}) + + opponent = axelrod.Defector() + actions = [(C, D)] * 7 + [((D, D))] + self.versus_test(opponent, expected_actions=actions, seed=4, + init_kwargs = {'memory': [C] * 12, + 'start_strategy': axelrod.Defector, + 'start_strategy_duration': 0}) 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 10af266b3..b72c48e23 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 @@ -23,7 +23,7 @@ argument :code:`seed` (useful and necessary for stochastic strategies, expected_actions=[(D, C), (C, D), (C, C)], seed=None) 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 +:code:`C, D`. The :code:`expected_actions` are the actions played 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`. @@ -52,7 +52,7 @@ assumes that players do not know the length of the match:: 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`:: +tests 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,