Skip to content

Commit 579f188

Browse files
authored
Merge pull request #639 from Axelrod-Python/636
Adding contrite TfT
2 parents c5d1aeb + 5fefc81 commit 579f188

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

axelrod/strategies/_strategies.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
from .titfortat import (
5757
TitForTat, TitFor2Tats, TwoTitsForTat, Bully, SneakyTitForTat,
5858
SuspiciousTitForTat, AntiTitForTat, HardTitForTat, HardTitFor2Tats,
59-
OmegaTFT, Gradual)
59+
OmegaTFT, Gradual, ContriteTitForTat)
6060

6161

6262
# Note: Meta* strategies are handled in .__init__.py
@@ -79,6 +79,7 @@
7979
Calculator,
8080
CautiousQLearner,
8181
Champion,
82+
ContriteTitForTat,
8283
Cooperator,
8384
CooperatorHunter,
8485
CycleHunter,

axelrod/strategies/titfortat.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from axelrod import Actions, Player, init_args, flip_action
2+
from axelrod.strategy_transformers import (StrategyTransformerFactory,
3+
history_track_wrapper)
24

35
C, D = Actions.C, Actions.D
46

@@ -331,3 +333,56 @@ def reset(self):
331333
self.punishing = False
332334
self.punishment_count = 0
333335
self.punishment_limit = 0
336+
337+
338+
Transformer = StrategyTransformerFactory(
339+
history_track_wrapper, name_prefix=None)()
340+
341+
342+
@Transformer
343+
class ContriteTitForTat(Player):
344+
"""
345+
A player that corresponds to Tit For Tat if there is no noise. In the case
346+
of a noisy match: if the opponent defects as a result of a noisy defection
347+
then ContriteTitForTat will become 'contrite' until it successfully
348+
cooperates..
349+
350+
Reference: "How to Cope with Noise In the Iterated Prisoner's Dilemma" by
351+
Wu and Axelrod. Published in Journal of Conflict Resolution, 39 (March
352+
1995), pp. 183-189.
353+
354+
http://www-personal.umich.edu/~axe/research/How_to_Cope.pdf
355+
"""
356+
357+
name = "Contrite Tit For Tat"
358+
classifier = {
359+
'memory_depth': 3,
360+
'stochastic': False,
361+
'makes_use_of': set(),
362+
'inspects_source': False,
363+
'manipulates_source': False,
364+
'manipulates_state': False
365+
}
366+
contrite = False
367+
368+
def strategy(self, opponent):
369+
370+
if not opponent.history:
371+
return C
372+
373+
# If contrite but managed to cooperate: apologise.
374+
if self.contrite and self.history[-1] == C:
375+
self.contrite = False
376+
return C
377+
378+
# Check if noise provoked opponent
379+
if self._recorded_history[-1] != self.history[-1]: # Check if noise
380+
if self.history[-1] == D and opponent.history[-1] == C:
381+
self.contrite = True
382+
383+
return opponent.history[-1]
384+
385+
def reset(self):
386+
Player.reset(self)
387+
self.contrite = False
388+
self._recorded_history = []

axelrod/tests/unit/test_titfortat.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
import axelrod
44
from .test_player import TestHeadsUp, TestPlayer
55

6+
from hypothesis import given
7+
from hypothesis.strategies import integers
8+
from axelrod.tests.property import strategy_lists
9+
10+
import random
11+
612
C, D = axelrod.Actions.C, axelrod.Actions.D
713

814

@@ -362,3 +368,72 @@ def test_output_from_literature(self):
362368
match = axelrod.Match((player, opp2), 1000)
363369
match.play()
364370
self.assertEqual(match.final_score(), (3472, 767))
371+
372+
373+
class TestContriteTitForTat(TestPlayer):
374+
375+
name = "Contrite Tit For Tat"
376+
player = axelrod.ContriteTitForTat
377+
expected_classifier = {
378+
'memory_depth': 3,
379+
'stochastic': False,
380+
'makes_use_of': set(),
381+
'inspects_source': False,
382+
'manipulates_source': False,
383+
'manipulates_state': False
384+
}
385+
386+
deterministic_strategies = [s for s in axelrod.ordinary_strategies
387+
if not s().classifier['stochastic']]
388+
389+
@given(strategies=strategy_lists(strategies=deterministic_strategies,
390+
max_size=1),
391+
turns=integers(min_value=1, max_value=20))
392+
def test_is_tit_for_tat_with_no_noise(self, strategies, turns):
393+
tft = axelrod.TitForTat()
394+
ctft = self.player()
395+
opponent = strategies[0]()
396+
m1 = axelrod.Match((tft, opponent), turns)
397+
m2 = axelrod.Match((ctft, opponent), turns)
398+
self.assertEqual(m1.play(), m2.play())
399+
400+
def test_strategy_with_noise(self):
401+
ctft = self.player()
402+
opponent = axelrod.Defector()
403+
self.assertEqual(ctft.strategy(opponent), C)
404+
self.assertEqual(ctft._recorded_history, [C])
405+
ctft.reset() # Clear the recorded history
406+
self.assertEqual(ctft._recorded_history, [])
407+
408+
random.seed(0)
409+
ctft.play(opponent, noise=.9)
410+
self.assertEqual(ctft.history, [D])
411+
self.assertEqual(ctft._recorded_history, [C])
412+
self.assertEqual(opponent.history, [C])
413+
414+
# After noise: is contrite
415+
ctft.play(opponent)
416+
self.assertEqual(ctft.history, [D, C])
417+
self.assertEqual(ctft._recorded_history, [C, C])
418+
self.assertEqual(opponent.history, [C, D])
419+
self.assertTrue(ctft.contrite)
420+
421+
# Cooperates and no longer contrite
422+
ctft.play(opponent)
423+
self.assertEqual(ctft.history, [D, C, C])
424+
self.assertEqual(ctft._recorded_history, [C, C, C])
425+
self.assertEqual(opponent.history, [C, D, D])
426+
self.assertFalse(ctft.contrite)
427+
428+
# Goes back to playing tft
429+
ctft.play(opponent)
430+
self.assertEqual(ctft.history, [D, C, C, D])
431+
self.assertEqual(ctft._recorded_history, [C, C, C, D])
432+
self.assertEqual(opponent.history, [C, D, D, D])
433+
self.assertFalse(ctft.contrite)
434+
435+
def test_reset_cleans_all(self):
436+
p = self.player()
437+
p.contrite = True
438+
p.reset()
439+
self.assertFalse(p.contrite)

0 commit comments

Comments
 (0)