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

ModalTFT, ModalDefector strategies - Ben McGuffog #1433

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The simplest way to install is::

To install from source::

$ git clone https://github.com/Axelrod-Python/Axelrod.git
$ git clone https://github.com/benjjo/Axelrod.git
Copy link
Member

Choose a reason for hiding this comment

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

We'll want to keep this as is for others.

$ cd Axelrod
$ python setup.py install

Expand Down
5 changes: 4 additions & 1 deletion axelrod/strategies/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
from .cycler import Cycler, EvolvableCycler # pylint: disable=unused-import
from .darwin import Darwin
from .dbs import DBS
from .defector import Defector, TrickyDefector
from .defector import Defector, TrickyDefector, ModalDefector
from .doubler import Doubler
from .finite_state_machines import (
TF1,
Expand Down Expand Up @@ -260,6 +260,7 @@
HardTitFor2Tats,
HardTitForTat,
Michaelos,
ModalTFT,
NTitsForMTats,
OmegaTFT,
OriginalGradual,
Expand Down Expand Up @@ -409,6 +410,8 @@
MEM2,
MathConstantHunter,
Michaelos,
ModalDefector,
ModalTFT,
NTitsForMTats,
NaiveProber,
Negation,
Expand Down
33 changes: 33 additions & 0 deletions axelrod/strategies/defector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from axelrod.action import Action
from axelrod.player import Player
import statistics

C, D = Action.C, Action.D

Expand Down Expand Up @@ -61,3 +62,35 @@ def strategy(self, opponent: Player) -> Action:
):
return C
return D


class ModalDefector(Player):
"""
A player starts by Defecting and then analyses the history of the opponent. If the opponent Cooperated in the
last round, they are returned with a Defection. If the opponent chose to Defect in the previous round,
then this strategy will return with the mode of the previous opponent responses.
"""

# These are various properties for the strategy
Copy link
Member

Choose a reason for hiding this comment

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

I believe this comment/line is from our contributing guidelines. You can remove it.

name = "Modal Defector"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}

def strategy(self, opponent: Player) -> Action:
"""This is the actual strategy"""
# First move
if not self.history:
return D
# React to the opponent's historical moves
if opponent.history[-1] == C:
return D
else:
# returns with the mode of the opponent's history.
return statistics.mode(opponent.history)
Copy link
Member

Choose a reason for hiding this comment

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

Rather than use the statistics library, the history class already keeps a running count of C and D, so you can do something like if opponent.history.cooperations > opponent.history.defections return C else return D. That saves a library import and is more efficient (mode is going to be O(n^2) over the entire game, this way is linear).


37 changes: 35 additions & 2 deletions axelrod/strategies/titfortat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from axelrod.action import Action, actions_to_str
from axelrod.player import Player
import statistics
from axelrod.strategy_transformers import (
FinalTransformer,
TrackHistoryTransformer,
Expand Down Expand Up @@ -872,7 +873,7 @@ def strategy(self, opponent: Player) -> Action:
class RandomTitForTat(Player):
"""
A player starts by cooperating and then follows by copying its
opponent (tit for tat style). From then on the player
opponent (tit-for-tat style). From then on the player
will switch between copying its opponent and randomly
responding every other iteration.

Expand Down Expand Up @@ -945,9 +946,41 @@ def strategy(self, opponent):
if not opponent.history:
# Make sure we cooperate first turn
return C
# BBE modification
# BBE modification
if opponent.history[-1] == C:
# Cooperate with 0.9
return self._random.random_choice(0.9)
# Else TFT. Opponent played D, so play D in return.
return D


class ModalTFT(Player):
"""
A player starts by cooperating and then analyses the history of the opponent. If the opponent Cooperated in the
last round, they are returned with a Cooperation. If the opponent chose to Defect in the previous round,
then this strategy will return with the mode of the previous opponent responses.
"""

# These are various properties for the strategy
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

name = "Modal TFT"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}

def strategy(self, opponent: Player) -> Action:
"""This is the actual strategy"""
# First move
if not self.history:
return C
# React to the opponent's historical moves
if opponent.history[-1] == C:
return C
else:
# returns with the mode of the opponent's history.
return statistics.mode(opponent.history)
Copy link
Member

Choose a reason for hiding this comment

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

Same comment re: statistics.mode


18 changes: 18 additions & 0 deletions axelrod/tests/strategies/test_defector.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,21 @@ def test_defects_if_opponent_last_three_are_not_D(self):
self.versus_test(
axl.MockPlayer(actions=opponent_actions), expected_actions=actions
)


class TestModalDefector(TestPlayer):
name = "Modal Defector"
player = axl.ModalDefector
expected_classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"makes_use_of": set(),
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}

def test_strategy(self):
Copy link
Member

Choose a reason for hiding this comment

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

Try to add a few more tests that single out the specific behaviors like the first move and that the mode works for both C and D. If possible try to use a real strategy like Alternator rather than mock players, but mocks are ok if it's hard to target a particular case.

opponent = axl.MockPlayer(actions=[C, C, D, D, D, D, C, D])
actions = [(D, C), (D, C), (D, D), (C, D), (C, D), (D, D), (D, C), (D, D), (D, C)]
self.versus_test(opponent, expected_actions=actions)
20 changes: 20 additions & 0 deletions axelrod/tests/strategies/test_titfortat.py
Original file line number Diff line number Diff line change
Expand Up @@ -1341,3 +1341,23 @@ def test_vs_cooperator2(self):
def test_vs_defector(self):
actions = [(C, D), (D, D), (D, D), (D, D), (D, D)]
self.versus_test(axl.Defector(), expected_actions=actions)


class TestModalTFT(TestPlayer):
name = "Modal TFT"
player = axl.ModalTFT
expected_classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"makes_use_of": set(),
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}

def test_strategy(self):
opponent = axl.MockPlayer(actions=[C, C, D, D, D, D, C, D])
actions = [(C, C), (C, C), (C, D), (C, D), (C, D), (D, D), (D, C), (C, D), (D, C)]
self.versus_test(opponent, expected_actions=actions)