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

Adding contrite TfT #639

Merged
merged 8 commits into from
Jun 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion axelrod/strategies/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from .titfortat import (
TitForTat, TitFor2Tats, TwoTitsForTat, Bully, SneakyTitForTat,
SuspiciousTitForTat, AntiTitForTat, HardTitForTat, HardTitFor2Tats,
OmegaTFT, Gradual)
OmegaTFT, Gradual, ContriteTitForTat)


# Note: Meta* strategies are handled in .__init__.py
Expand All @@ -79,6 +79,7 @@
Calculator,
CautiousQLearner,
Champion,
ContriteTitForTat,
Cooperator,
CooperatorHunter,
CycleHunter,
Expand Down
5 changes: 5 additions & 0 deletions axelrod/strategies/finite_state_machines.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(self, transitions=None, initial_state=None, initial_action=None):
initial_state = 1
initial_action = C
Player.__init__(self)
self.initial_state = initial_state
self.initial_action = initial_action
self.fsm = SimpleFSM(transitions, initial_state)

Expand All @@ -67,6 +68,10 @@ def strategy(self, opponent):
self.state = self.fsm.state
return action

def reset(self):
Player.reset(self)
self.fsm.state = self.initial_state


class Fortress3(FSMPlayer):
"""Finite state machine player specified in DOI:10.1109/CEC.2006.1688322.
Expand Down
55 changes: 55 additions & 0 deletions axelrod/strategies/titfortat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from axelrod import Actions, Player, init_args, flip_action
from axelrod.strategy_transformers import (StrategyTransformerFactory,
history_track_wrapper)

C, D = Actions.C, Actions.D

Expand Down Expand Up @@ -331,3 +333,56 @@ def reset(self):
self.punishing = False
self.punishment_count = 0
self.punishment_limit = 0


Transformer = StrategyTransformerFactory(
history_track_wrapper, name_prefix=None)()


@Transformer
class ContriteTitForTat(Player):
"""
A player that corresponds to Tit For Tat if there is no noise. In the case
of a noisy match: if the opponent defects as a result of a noisy defection
then ContriteTitForTat will become 'contrite' until it successfully
cooperates..

Reference: "How to Cope with Noise In the Iterated Prisoner's Dilemma" by
Wu and Axelrod. Published in Journal of Conflict Resolution, 39 (March
1995), pp. 183-189.

http://www-personal.umich.edu/~axe/research/How_to_Cope.pdf
"""

name = "Contrite Tit For Tat"
classifier = {
'memory_depth': 3,
'stochastic': False,
'makes_use_of': set(),
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}
contrite = False

def strategy(self, opponent):

if not opponent.history:
return C

# If contrite but managed to cooperate: apologise.
if self.contrite and self.history[-1] == C:
self.contrite = False
return C

# Check if noise provoked opponent
if self._recorded_history[-1] != self.history[-1]: # Check if noise
if self.history[-1] == D and opponent.history[-1] == C:
self.contrite = True

return opponent.history[-1]

def reset(self):
Player.reset(self)
self.contrite = False
self._recorded_history = []
25 changes: 25 additions & 0 deletions axelrod/tests/integration/test_matches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tests for some expected match behaviours"""
import unittest
import axelrod

from hypothesis import given
from hypothesis.strategies import integers
from axelrod.tests.property import strategy_lists

C, D = axelrod.Actions.C, axelrod.Actions.D

deterministic_strategies = [s for s in axelrod.ordinary_strategies
if not s().classifier['stochastic']] # Well behaved strategies

class TestMatchOutcomes(unittest.TestCase):

@given(strategies=strategy_lists(strategies=deterministic_strategies,
min_size=2, max_size=2),
turns=integers(min_value=1, max_value=20))
def test_outcome_repeats(self, strategies, turns):
"""A test that if we repeat 3 matches with deterministic and well
behaved strategies then we get the same result"""
players = [s() for s in strategies]
matches = [axelrod.Match(players, turns) for _ in range(3)]
self.assertEqual(matches[0].play(), matches[1].play())
self.assertEqual(matches[1].play(), matches[2].play())
6 changes: 6 additions & 0 deletions axelrod/tests/unit/test_finite_state_machines.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ def test_transitions(self):
fsm = player.fsm
self.assertTrue(check_state_transitions(fsm.state_transitions))

def test_reset_initial_state(self):
player = self.player()
player.fsm.state = -1
player.reset()
self.assertFalse(player.fsm.state == -1)


class TestFortress3(TestFSMPlayer):

Expand Down
75 changes: 75 additions & 0 deletions axelrod/tests/unit/test_titfortat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
import axelrod
from .test_player import TestHeadsUp, TestPlayer

from hypothesis import given
from hypothesis.strategies import integers
from axelrod.tests.property import strategy_lists

import random

C, D = axelrod.Actions.C, axelrod.Actions.D


Expand Down Expand Up @@ -362,3 +368,72 @@ def test_output_from_literature(self):
match = axelrod.Match((player, opp2), 1000)
match.play()
self.assertEqual(match.final_score(), (3472, 767))


class TestContriteTitForTat(TestPlayer):

name = "Contrite Tit For Tat"
player = axelrod.ContriteTitForTat
expected_classifier = {
'memory_depth': 3,
'stochastic': False,
'makes_use_of': set(),
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

deterministic_strategies = [s for s in axelrod.ordinary_strategies
if not s().classifier['stochastic']]

@given(strategies=strategy_lists(strategies=deterministic_strategies,
max_size=1),
turns=integers(min_value=1, max_value=20))
def test_is_tit_for_tat_with_no_noise(self, strategies, turns):
tft = axelrod.TitForTat()
ctft = self.player()
opponent = strategies[0]()
m1 = axelrod.Match((tft, opponent), turns)
m2 = axelrod.Match((ctft, opponent), turns)
self.assertEqual(m1.play(), m2.play())

def test_strategy_with_noise(self):
ctft = self.player()
opponent = axelrod.Defector()
self.assertEqual(ctft.strategy(opponent), C)
self.assertEqual(ctft._recorded_history, [C])
ctft.reset() # Clear the recorded history
self.assertEqual(ctft._recorded_history, [])

random.seed(0)
ctft.play(opponent, noise=.9)
self.assertEqual(ctft.history, [D])
self.assertEqual(ctft._recorded_history, [C])
self.assertEqual(opponent.history, [C])

# After noise: is contrite
ctft.play(opponent)
self.assertEqual(ctft.history, [D, C])
self.assertEqual(ctft._recorded_history, [C, C])
self.assertEqual(opponent.history, [C, D])
self.assertTrue(ctft.contrite)

# Cooperates and no longer contrite
ctft.play(opponent)
self.assertEqual(ctft.history, [D, C, C])
self.assertEqual(ctft._recorded_history, [C, C, C])
self.assertEqual(opponent.history, [C, D, D])
self.assertFalse(ctft.contrite)

# Goes back to playing tft
ctft.play(opponent)
self.assertEqual(ctft.history, [D, C, C, D])
self.assertEqual(ctft._recorded_history, [C, C, C, D])
self.assertEqual(opponent.history, [C, D, D, D])
self.assertFalse(ctft.contrite)

def test_reset_cleans_all(self):
p = self.player()
p.contrite = True
p.reset()
self.assertFalse(p.contrite)