Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N tit(s) for M tat(s) #1084

Merged
merged 12 commits into from
Jul 25, 2017
5 changes: 2 additions & 3 deletions axelrod/strategies/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@
from .titfortat import (
TitForTat, TitFor2Tats, TwoTitsForTat, Bully, SneakyTitForTat,
SuspiciousTitForTat, AntiTitForTat, HardTitForTat, HardTitFor2Tats,
OmegaTFT, Gradual, ContriteTitForTat, SlowTitForTwoTats, AdaptiveTitForTat,
SpitefulTitForTat, SlowTitForTwoTats2, Alexei, EugineNier, DynamicTwoTitsForTat)
OmegaTFT, Gradual, ContriteTitForTat, AdaptiveTitForTat,
SpitefulTitForTat, SlowTitForTwoTats2, Alexei, EugineNier, DynamicTwoTitsForTat, NTitsForMTats)
from .verybad import VeryBad
from .worse_and_worse import (WorseAndWorse, KnowledgeableWorseAndWorse,
WorseAndWorse2, WorseAndWorse3)
Expand Down Expand Up @@ -224,7 +224,6 @@
SelfSteem,
ShortMem,
Shubik,
SlowTitForTwoTats,
SlowTitForTwoTats2,
SneakyTitForTat,
SoftGrudger,
Expand Down
92 changes: 57 additions & 35 deletions axelrod/strategies/titfortat.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class TitFor2Tats(Player):
Names:

- Tit for two Tats: [Axelrod1984]_
- Slow tit for two tats: Original name by Ranjini Das
"""

name = "Tit For 2 Tats"
Expand Down Expand Up @@ -496,41 +497,6 @@ def reset(self):
self._recorded_history = []


class SlowTitForTwoTats(Player):
"""
A player plays C twice, then if the opponent plays the same move twice,
plays that move.

Names:

- Slow tit for two tats: Original name by Ranjini Das
"""

name = 'Slow Tit For Two Tats'
classifier = {
'memory_depth': 2,
'stochastic': False,
'makes_use_of': set(),
'long_run_time': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def strategy(self, opponent: Player) -> Action:

# Start with two cooperations
if len(self.history) < 2:
return C

# Mimic if opponent plays the same move twice
if opponent.history[-2] == opponent.history[-1]:
return opponent.history[-1]

# Otherwise cooperate
return C


class AdaptiveTitForTat(Player):
"""ATFT - Adaptive Tit For Tat (Basic Model)

Expand Down Expand Up @@ -750,3 +716,59 @@ def strategy(self, opponent: Player) -> Action:
def reset(self):
super().reset()
self.is_defector = False


class NTitsForMTats(Player):
"""
A parameterizable Tit-for-Tat,
The arguments are :
1) the number of defection before retaliation
2) the number of retaliations

Names:

- N Tit(s) For M Tat(s): Original name by Marc Harper
"""

name = 'N Tit(s) For M Tat(s)'
classifier = {
'memory_depth': float('inf'),
'stochastic': False,
'makes_use_of': set(),
'long_run_time': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def __init__(self, N: int=1, M: int=1) -> None:
"""
Parameters
----------
N, int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the type hints let's drop the type in the docstrings (in case at some point they are changed and we forget to change them in the docstrings).

Number of retaliations
M, int
Number of defection before retaliation

Special Cases
-------------
NTitsForMTats(1,1) is equivalent to TitForTat
NTitsForMTats(1,2) is equivalent to TitFor2Tats
NTitsForMTats(2,1) is equivalent to TwoTitsForTat
NTitsForMTats(0,*) is equivalent to Cooperator
NTitsForMTats(*,0) is equivalent to Defector
"""
super().__init__()
self.N = N
self.M = M
self.classifier['memory_depth'] = max([M,N])
self.retaliate_count = 0

def strategy(self, opponent: Player) -> Action:
# if opponent defected consecutively M times, start the retaliation
if not self.M or opponent.history[-self.M:].count(D) == self.M:
self.retaliate_count = self.N
if self.retaliate_count:
self.retaliate_count-=1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP8: self.retaliate_count -= 1

return D
return C
98 changes: 75 additions & 23 deletions axelrod/tests/strategies/test_titfortat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Tests for the tit for tat strategies."""

import random

import copy
from hypothesis import given
from hypothesis.strategies import integers
import axelrod
Expand Down Expand Up @@ -79,10 +81,14 @@ class TestTitFor2Tats(TestPlayer):
}

def test_strategy(self):
# Will punish sequence of 2 defections but will forgive
# Will punish sequence of 2 defections but will forgive one
opponent = axelrod.MockPlayer(actions=[D, D, D, C, C])
actions = [(C, D), (C, D), (D, D), (D, C), (C, C), (C, D)]
self.versus_test(opponent, expected_actions=actions)
opponent = axelrod.MockPlayer(actions=[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 TestTwoTitsForTat(TestPlayer):
Expand Down Expand Up @@ -473,28 +479,6 @@ def test_strategy_with_noise(self):
self.assertFalse(ctft.contrite)


class TestSlowTitForTwoTats(TestPlayer):

name = "Slow Tit For Two Tats"
player = axelrod.SlowTitForTwoTats
expected_classifier = {
'memory_depth': 2,
'stochastic': False,
'makes_use_of': set(),
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def test_strategy(self):
# If opponent plays the same move twice, repeats last action of
# opponent history.
opponent = axelrod.MockPlayer(actions=[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):

name = "Adaptive Tit For Tat: 0.5"
Expand Down Expand Up @@ -642,3 +626,71 @@ def test_strategy(self):
actions = [(C, D), (D, C), (C, D), (D, D),
(D, D), (D, D), (D, C), (D, C)]
self.versus_test(opponent, expected_actions=actions)



class TestNTitsForMTats(TestPlayer):
"""
Tests for the N Tit(s) For M Tat(s) strategy
"""

name = 'N Tit(s) For M Tat(s): 1, 1'
player = axelrod.NTitsForMTats
expected_classifier = {
'memory_depth': 1,
'stochastic': False,
'makes_use_of': set(),
'long_run_time': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

expected_class_classifier = copy.copy(expected_classifier)
expected_class_classifier['memory_depth'] = float('inf')

def test_strategy(self):
TestTitForTat.test_strategy(self)

# TitFor2Tats test_strategy
opponent = axelrod.MockPlayer(actions=[D, D, D, C, C])
actions = [(C, D), (C, D), (D, D), (D, C), (C, C), (C, D)]
self.versus_test(opponent, expected_actions=actions, init_kwargs={'N': 1, 'M': 2})

# TwoTitsForTat test_strategy
opponent = axelrod.MockPlayer(actions=[D, C, C, D, C])
actions = [(C, D), (D, C), (D, C), (C, D), (D, C)]
self.versus_test(opponent, expected_actions=actions, init_kwargs={'N': 2, 'M': 1})
actions = [(C, C), (C, C)]
self.versus_test(opponent=axelrod.Cooperator(),
expected_actions=actions, init_kwargs={'N': 2, 'M': 1})
actions = [(C, D), (D, D), (D, D)]
self.versus_test(opponent=axelrod.Defector(),
expected_actions=actions, init_kwargs={'N': 2, 'M': 1})

# Cooperator test_strategy
actions = [(C, C)] + [(C, D), (C, C)] * 9
self.versus_test(opponent=axelrod.Alternator(),
expected_actions=actions, init_kwargs={'N': 0, 'M': 1})
self.versus_test(opponent=axelrod.Alternator(),
expected_actions=actions, init_kwargs={'N': 0, 'M': 5})
self.versus_test(opponent=axelrod.Alternator(),
expected_actions=actions, init_kwargs={'N': 0, 'M': 0})

# Defector test_strategy
actions = [(D, C)] + [(D, D), (D, C)] * 9
self.versus_test(opponent=axelrod.Alternator(),
expected_actions=actions, init_kwargs={'N': 1, 'M': 0})
self.versus_test(opponent=axelrod.Alternator(),
expected_actions=actions, init_kwargs={'N': 5, 'M': 0})

# Other init args
actions = [(C, C), (C, D), (C, D), (D, C), (D, C), (D, D), (C, C)]
opponent = axelrod.MockPlayer(actions=[acts[1] for acts in actions])
self.versus_test(opponent=opponent,
expected_actions=actions, init_kwargs={'N': 3, 'M': 2})

def test_varying_memory_depth(self):
self.assertEqual(self.player().classifier['memory_depth'], 1)
self.assertEqual(self.player(0, 3).classifier['memory_depth'], 3)
self.assertEqual(self.player(5, 3).classifier['memory_depth'], 5)
2 changes: 1 addition & 1 deletion docs/tutorials/advanced/classification_of_strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ range of memory_depth values, we can use the 'min_memory_depth' and
... }
>>> strategies = axl.filtered_strategies(filterset)
>>> len(strategies)
51
50

We can also identify strategies that make use of particular properties of the
tournament. For example, here is the number of strategies that make use of the
Expand Down