Skip to content
10 changes: 9 additions & 1 deletion axelrod/_strategy_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,20 @@ def detect_cycle(history, min_size=1, max_size=12, offset=0):
return None


def inspect_strategy(inspector, opponent):
"""Simulate one round vs opponent unless opponent has an inspection countermeasure."""
if hasattr(opponent, 'foil_strategy_inspection'):
return opponent.foil_strategy_inspection()
else:
return opponent.strategy(inspector)


def limited_simulate_play(player_1, player_2, h1):
"""Here we want to replay player_1's history to player_2, allowing
player_2's strategy method to set any internal variables as needed. If you
need a more complete simulation, see `simulate_play` in player.py. This
function is specifically designed for the needs of MindReader."""
h2 = player_2.strategy(player_1)
h2 = inspect_strategy(player_1, player_2)
update_history(player_1, h1)
update_history(player_2, h2)

Expand Down
16 changes: 7 additions & 9 deletions axelrod/strategies/darwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
indicated by their classifier). We do not recommend putting a lot of time in to
optimising it.
"""
import inspect
from axelrod.actions import Action, Actions
from axelrod.player import Player

from typing import List

C, D = Actions.C, Actions.D


Expand Down Expand Up @@ -44,18 +41,19 @@ class Darwin(Player):
valid_callers = ["play"] # What functions may invoke our strategy.

def __init__(self) -> None:
super().__init__()
self.outcomes = None # type: dict
self.response = Darwin.genome[0]
super().__init__()

def receive_match_attributes(self):
self.outcomes = self.match_attributes["game"].scores

def strategy(self, opponent: Player) -> Action:
# Frustrate psychics and ensure that simulated rounds
# do not influence genome.
if inspect.stack()[1][3] not in Darwin.valid_callers:
return C
@staticmethod
def foil_strategy_inspection() -> Action:
Copy link
Member

Choose a reason for hiding this comment

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

Could you add a docstring here please.

"""Foils _strategy_utils.inspect_strategy and _strategy_utils.look_ahead"""
return C

def strategy(self, opponent: Player) -> Action:
trial = len(self.history)

if trial > 0:
Expand Down
29 changes: 18 additions & 11 deletions axelrod/strategies/geller.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
indicated by their classifier). We do not recommend putting a lot of time in to
optimising them.
"""
import inspect

from axelrod.actions import Actions, Action
from axelrod.player import Player
from axelrod.random_ import random_choice
from axelrod._strategy_utils import inspect_strategy

C, D = Actions.C, Actions.D

Expand Down Expand Up @@ -36,7 +36,6 @@ class Geller(Player):
"""

name = 'Geller'
default = lambda self: random_choice(0.5)
classifier = {
'memory_depth': -1,
'stochastic': True,
Expand All @@ -47,27 +46,26 @@ class Geller(Player):
'manipulates_state': False
}

@staticmethod
def foil_strategy_inspection() -> Action:
Copy link
Member

Choose a reason for hiding this comment

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

Docstring.

"""Foils _strategy_utils.inspect_strategy and _strategy_utils.look_ahead"""
return random_choice(0.5)

def strategy(self, opponent: Player) -> Action:
"""
Look at what the opponent will play in the next round and choose a strategy
that gives the least jail time, which is is equivalent to playing the same
strategy as that which the opponent will play.
"""
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
calname = calframe[1][3]
if calname == 'strategy':
return self.default()
else:
return opponent.strategy(self)

return inspect_strategy(self, opponent)


class GellerCooperator(Geller):
"""Observes what the payer will do (like :code:`Geller`) but if unable to
will cooperate.
"""
name = 'Geller Cooperator'
default = lambda self: C
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the IDE balked at making a variable a lambda and recommended changing to a function. so i did.

classifier = {
'memory_depth': -1,
'stochastic': False,
Expand All @@ -78,13 +76,17 @@ class GellerCooperator(Geller):
'manipulates_state': False
}

@staticmethod
def foil_strategy_inspection() -> Action:
"""Foils _strategy_utils.inspect_strategy and _strategy_utils.look_ahead"""
return C


class GellerDefector(Geller):
"""Observes what the payer will do (like :code:`Geller`) but if unable to
will defect.
"""
name = 'Geller Defector'
default = lambda self: D
classifier = {
'memory_depth': -1,
'stochastic': False,
Expand All @@ -94,3 +96,8 @@ class GellerDefector(Geller):
'manipulates_source': False,
'manipulates_state': False
}

@staticmethod
def foil_strategy_inspection() -> Action:
"""Foils _strategy_utils.inspect_strategy and _strategy_utils.look_ahead"""
return D
47 changes: 17 additions & 30 deletions axelrod/strategies/mindreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
indicated by their classifier). We do not recommend putting a lot of time in to
optimising them.
"""
import inspect

from axelrod.actions import Actions, Action
from axelrod.player import Player
from axelrod._strategy_utils import look_ahead
from axelrod._strategy_utils import look_ahead, inspect_strategy


C, D = Actions.C, Actions.D


class MindReader(Player):
"""A player that looks ahead at what the opponent will do and decides what
to do."""
Expand All @@ -27,23 +26,17 @@ class MindReader(Player):
'manipulates_state': False
}

@staticmethod
def foil_strategy_inspection() -> Action:
"""Foils _strategy_utils.inspect_strategy and _strategy_utils.look_ahead"""
return D

def strategy(self, opponent: Player) -> Action:
"""Pretends to play the opponent a number of times before each match.
"""
Pretends to play the opponent a number of times before each match.
The primary purpose is to look far enough ahead to see if a defect will
be punished by the opponent.

If the MindReader attempts to play itself (or another similar
strategy), then it will cause a recursion loop, so this is also handled
in this method, by defecting if the method is called by strategy
"""

curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
calname = calframe[1][3]

if calname in ('strategy', 'simulate_match'):
return D

game = self.match_attributes["game"]

best_strategy = look_ahead(self, opponent, game)
Expand Down Expand Up @@ -74,6 +67,7 @@ def __setattr__(self, name: str, val: str):
else:
self.__dict__[name] = val


class MirrorMindReader(ProtectedMindReader):
"""A player that will mirror whatever strategy it is playing against by
cheating and calling the opponent's strategy function instead of its own."""
Expand All @@ -90,18 +84,11 @@ class MirrorMindReader(ProtectedMindReader):
'manipulates_state': False
}

def strategy(self, opponent: Player) -> Action:
"""Will read the mind of the opponent and play the opponent's strategy.

Also avoid infinite recursion when called by itself or another mind
reader or bender by cooperating.
"""
@staticmethod
def foil_strategy_inspection() -> Action:
Copy link
Member

Choose a reason for hiding this comment

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

Docstring please.

"""Foils _strategy_utils.inspect_strategy and _strategy_utils.look_ahead"""
return C

curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
calname = calframe[1][3]

if calname in ('strategy', 'simulate_match'):
return C

return opponent.strategy(self)
def strategy(self, opponent: Player) -> Action:
"""Will read the mind of the opponent and play the opponent's strategy. """
return inspect_strategy(self, opponent)
21 changes: 4 additions & 17 deletions axelrod/tests/strategies/test_darwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ def test_setup(self):
self.assertEqual(player.genome, [C])
self.assertEqual(player.history, [])

def test_foil_strategy_inspection(self):
self.assertEqual(self.player().foil_strategy_inspection(), C)

def test_strategy(self):
p1 = self.player()
p1.reset()

self.versus_test(axelrod.Cooperator(), expected_actions=[(C, C)] * 5, attrs={'genome': [C] * 5})

expected_genome = [D] * 4 + [C]
Expand All @@ -47,23 +51,6 @@ def test_against_geller_and_mindreader(self):

self.versus_test(axelrod.MindReader(), expected_actions=[(C, D)] * 2, attrs={'genome': [D, C]})

def test_play(self):
Copy link
Contributor Author

@eric-s-s eric-s-s Apr 1, 2017

Choose a reason for hiding this comment

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

when i refactored test_darwin, i forgot to recommend removing test_play(self). Because versus_test calls "play", these are now redundant.

"""valid_callers must contain at least one entry..."""
self.assertTrue(len(self.player.valid_callers) > 0)
"""...and should allow round_robin.play to call"""
self.assertTrue("play" in self.player.valid_callers)
self.play()
self.play()

def play(self):
"""We need this to circumvent the agent's anti-inspection measure"""
p1 = self.player()
p2 = axelrod.Player()
p1.reset()
p1.strategy(p2)
# Genome contains only valid responses.
self.assertEqual(p1.genome.count(C) + p1.genome.count(D), len(p1.genome))

def test_reset_only_resets_first_move_of_genome(self):
self.versus_test(axelrod.Defector(), expected_actions=[(C, D)] + [(D, D)] * 4)

Expand Down
16 changes: 16 additions & 0 deletions axelrod/tests/strategies/test_geller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import axelrod
from .test_player import TestPlayer
from axelrod.random_ import seed

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

Expand All @@ -20,6 +21,13 @@ class TestGeller(TestPlayer):
'manipulates_source': False
}

def test_foil_strategy_inspection(self):
seed(2)
player = self.player()
self.assertEqual(player.foil_strategy_inspection(), D)
self.assertEqual(player.foil_strategy_inspection(), D)
self.assertEqual(player.foil_strategy_inspection(), C)

def test_strategy(self):
"""Should cooperate against cooperators and defect against defectors."""
P1 = self.player()
Expand All @@ -45,6 +53,10 @@ class TestGellerCooperator(TestGeller):
'manipulates_state': False
}

def test_foil_strategy_inspection(self):
player = self.player()
self.assertEqual(player.foil_strategy_inspection(), C)

def test_against_self(self):
P1 = self.player()
P2 = self.player()
Expand All @@ -65,6 +77,10 @@ class TestGellerDefector(TestGeller):
'manipulates_state': False
}

def test_foil_strategy_inspection(self):
player = self.player()
self.assertEqual(player.foil_strategy_inspection(), D)

def test_against_self(self):
P1 = self.player()
P2 = self.player()
Expand Down
12 changes: 12 additions & 0 deletions axelrod/tests/strategies/test_mindreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class TestMindReader(TestPlayer):
'manipulates_state': False
}

def test_foil_inspection_strategy(self):
player = self.player()
self.assertEqual(player.foil_strategy_inspection(), D)

def test_strategy(self):
"""
Will defect against nice strategies
Expand Down Expand Up @@ -109,6 +113,10 @@ class TestProtectedMindReader(TestPlayer):
'manipulates_state': False
}

def test_foil_inspection_strategy(self):
player = self.player()
self.assertEqual(player.foil_strategy_inspection(), D)

def test_strategy(self):
"""
Will defect against nice strategies
Expand Down Expand Up @@ -149,6 +157,10 @@ class TestMirrorMindReader(TestPlayer):
'manipulates_state': False
}

def test_foil_inspection_strategy(self):
player = self.player()
self.assertEqual(player.foil_strategy_inspection(), C)

def test_strategy(self):
P1 = axelrod.MirrorMindReader()
P2 = axelrod.Cooperator()
Expand Down
27 changes: 26 additions & 1 deletion axelrod/tests/unit/test_strategy_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import unittest

import axelrod

from hypothesis import given
from hypothesis.strategies import sampled_from, lists, integers

from axelrod import Actions
from axelrod._strategy_utils import detect_cycle
from axelrod._strategy_utils import detect_cycle, inspect_strategy

C, D = Actions.C, Actions.D

Expand Down Expand Up @@ -48,3 +50,26 @@ def test_min_size_greater_than_two_times_max_size_has_no_effect(self):
def test_cycle_greater_than_max_size_returns_none(self):
self.assertEqual(detect_cycle([C, C, D] * 2, min_size=1, max_size=3), (C, C, D))
self.assertIsNone(detect_cycle([C, C, D] * 2, min_size=1, max_size=2))


class TestInspectStrategy(unittest.TestCase):

def test_strategies_without_countermeasures_return_their_strategy(self):
tft = axelrod.TitForTat()
inspector = axelrod.Alternator()

tft.play(inspector)
self.assertEqual(tft.history, [C])
self.assertEqual(inspect_strategy(inspector=inspector, opponent=tft), C)
tft.play(inspector)
self.assertEqual(tft.history, [C, C])
self.assertEqual(inspect_strategy(inspector=inspector, opponent=tft), D)
self.assertEqual(tft.strategy(inspector), D)

def test_strategies_with_countermeasures_return_their_countermeasures(self):
d_geller = axelrod.GellerDefector()
inspector = axelrod.Cooperator()
d_geller.play(inspector)

self.assertEqual(inspect_strategy(inspector=inspector, opponent=d_geller), D)
self.assertEqual(d_geller.strategy(inspector), C)