Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
da1043a
added lookerup strategy
mojones Oct 27, 2015
d4e4dff
fixed typos
mojones Oct 27, 2015
5a6e063
added docstring to evolved version
mojones Oct 27, 2015
7b73b9f
added dynamic memory classifier and changed plys -> plays
mojones Oct 27, 2015
b7ef81d
added description of LookerUp to overview of strategies
mojones Oct 27, 2015
ef31c86
trying to add tests
Oct 28, 2015
12f9736
basic tests now working
Oct 28, 2015
a80b3b9
added tests for lookerup
Oct 28, 2015
c56722b
added nonempty start string tuple to docstring examples
mojones Oct 29, 2015
4ba047d
added start-move-dependent tests
mojones Oct 29, 2015
eb8a015
added a note about weird lambda in the test file
mojones Oct 29, 2015
68fe905
added lookerup strategy
mojones Oct 27, 2015
76e67b2
fixed typos
mojones Oct 27, 2015
2666ca2
added docstring to evolved version
mojones Oct 27, 2015
0c5b8af
added dynamic memory classifier and changed plys -> plays
mojones Oct 27, 2015
2972d9e
added description of LookerUp to overview of strategies
mojones Oct 27, 2015
bf04862
trying to add tests
Oct 28, 2015
406c99e
basic tests now working
Oct 28, 2015
1b0bdc2
added tests for lookerup
Oct 28, 2015
246b4c8
added nonempty start string tuple to docstring examples
mojones Oct 29, 2015
87b101d
added start-move-dependent tests
mojones Oct 29, 2015
4e883c1
added a note about weird lambda in the test file
mojones Oct 29, 2015
fb871dc
merged overview
Oct 30, 2015
5bf200b
added default second argument to evolved looker up (not sure why!)
Oct 30, 2015
6af3b3e
fixed keys() for python3
Oct 30, 2015
287abab
Some comments, tests, and style edits for the LookerUp strategies
marcharper Nov 2, 2015
22da99b
Merge pull request #1 from marcharper/lookerup
mojones Nov 3, 2015
5455523
Merge branch 'master' of https://github.com/mojones/Axelrod into mojo…
drvinceknight Nov 3, 2015
b8201e2
Basic Lookerup was not being tested.
drvinceknight Nov 3, 2015
2fe997a
Adding an extra test for default table
drvinceknight Nov 3, 2015
bac9758
Converting docstrings C and D to 'C' and 'D'
drvinceknight Nov 3, 2015
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
2 changes: 2 additions & 0 deletions axelrod/strategies/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
DefectorHunter, CooperatorHunter, CycleHunter, AlternatorHunter,
MathConstantHunter, RandomHunter, EventualCycleHunter)
from .inverse import Inverse
from .lookerup import LookerUp, EvolvedLookerUp
from .mathematicalconstants import Golden, Pi, e
from .memoryone import (
WinStayLoseShift, GTFT, StochasticCooperator, StochasticWSLS, ZDGTFT2,
Expand Down Expand Up @@ -105,6 +106,7 @@
LimitedRetaliate,
LimitedRetaliate2,
LimitedRetaliate3,
EvolvedLookerUp,
MathConstantHunter,
MindBender,
MindController,
Expand Down
153 changes: 153 additions & 0 deletions axelrod/strategies/lookerup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from axelrod import Player, Actions
from itertools import product

C, D = Actions.C, Actions.D

class LookerUp(Player):
"""
A strategy that uses a lookup table to decide what to do based on a
combination of the last m turns and the opponent's opening n actions. If
there isn't enough history to do this (i.e. for the first m turns) then
cooperate.

The lookup table is implemented as a dict. The keys are 3-tuples giving the
opponents first n actions, self's last m actions, and opponents last m
actions, all as strings. The values are the actions to play on this round.
For example, in the case of m=n=1, if
- the opponent started by playing C
- my last action was a C the opponents
- last action was a D
then the corresponding key would be
('C', 'C', 'D')
and the value would contain the action to play on this turn.

Some well-known strategies can be expressed as special cases; for example
Cooperator is given by the dict:
{('', '', '') : C}
where m and n are both zero. Tit-For-Tat is given by:

{
('', 'C', 'D') : D,
('', 'D', 'D') : D,
('', 'C', 'C') : C,
('', 'D', 'C') : C,
}

where m=1 and n=0.

Lookup tables where the action depends on the opponent's first actions (as
opposed to most recent actions) will have a non-empty first string in the
tuple. For example, this fragment of a dict:

{
...
('C', 'C', 'C') : C.
('D', 'C', 'C') : D,
...
}

states that if self and opponent both cooperated on the previous turn, we
should cooperate this turn unless the opponent started by defecting, in
which case we should defect.

To denote lookup tables where the action depends on sequences of actions
(so m or n are greater than 1), simply concatenate the strings together.
Below is an incomplete example where m=3 and n=2.

{
...
('CC', 'CDD', 'CCC') : C.
('CD', 'CCD', 'CCC') : D,
...
}
"""

name = 'LookerUp'
classifier = {
'memory_depth': float('inf'),
'stochastic': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def __init__(self, lookup_table=None):
"""
If no lookup table is provided to the constructor, then use the TFT one.
"""
Player.__init__(self)

if not lookup_table:
lookup_table = {
('', 'C', 'D') : D,
('', 'D', 'D') : D,
('', 'C', 'C') : C,
('', 'D', 'C') : C,
}

self.lookup_table = lookup_table
# Rather than pass the number of previous turns (m) to consider in as a
# separate variable, figure it out. The number of turns is the length
# of the second element of any given key in the dict.
self.plays = len(list(self.lookup_table.keys())[0][1])
# The number of opponent starting actions is the length of the first
# element of any given key in the dict.
self.opponent_start_plays = len(list(self.lookup_table.keys())[0][0])
# If the table dictates to ignore the opening actions of the opponent
# then the memory classification is adjusted
if self.opponent_start_plays == 0:
self.classifier['memory_depth'] = self.plays
self.init_args = (lookup_table,)

# Ensure that table is well-formed
for k, v in lookup_table.items():
if (len(k[1]) != self.plays) or (len(k[0]) != self.opponent_start_plays):
raise ValueError("All table elements must have the same size")
if len(v) > 1:
raise ValueError("Table values should be of length one, C or D")

def strategy(self, opponent):
# If there isn't enough history to lookup an action, cooperate.
if len(self.history) < max(self.plays, self.opponent_start_plays):
return C
# Count backward m turns to get my own recent history.
history_start = -1 * self.plays
my_history = ''.join(self.history[history_start:])
# Do the same for the opponent.
opponent_history = ''.join(opponent.history[history_start:])
# Get the opponents first n actions.
opponent_start = ''.join(opponent.history[:self.opponent_start_plays])
# Put these three strings together in a tuple.
key = (opponent_start, my_history, opponent_history)
# Look up the action associated with that tuple in the lookup table.
action = self.lookup_table[key]
return action


class EvolvedLookerUp(LookerUp):
"""
A LookerUp strategy that uses a lookup table generated using an evolutionary
algorithm.
"""

name = "EvolvedLookerUp"

def __init__(self, lookup_table=None):
plays = 2
opponent_start_plays = 2

# Generate the list of possible tuples, i.e. all possible combinations
# of m actions for me, m actions for opponent, and n starting actions
# for opponent.
self_histories = [''.join(x) for x in product('CD', repeat=plays)]
other_histories = [''.join(x) for x in product('CD', repeat=plays)]
opponent_starts = [''.join(x) for x in
product('CD', repeat=opponent_start_plays)]
lookup_table_keys = list(product(opponent_starts, self_histories,
other_histories))

# Pattern of values determed previously with an evolutionary algorithm.
pattern='CDCCDCCCDCDDDDDCCDCCCDDDCDDDDDDCDDDDCDDDDCCDDCDDCDDDCCCDCDCDDDDD'
# Zip together the keys and the action pattern to get the lookup table.
lookup_table = dict(zip(lookup_table_keys, pattern))
LookerUp.__init__(self, lookup_table=lookup_table)
148 changes: 148 additions & 0 deletions axelrod/tests/unit/test_lookerup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""Test for the Looker Up strategy."""

import axelrod
from .test_player import TestPlayer, TestHeadsUp

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


class TestLookerUp(TestPlayer):

name = "LookerUp"
player = axelrod.LookerUp

expected_classifier = {
'memory_depth': 1, # Default TFT table
'stochastic': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def test_init(self):
# Test empty table
player = self.player(dict())
opponent = axelrod.Cooperator()
self.assertEqual(player.strategy(opponent), C)
# Test default table
player = self.player()
expected_lookup_table = {
('', 'C', 'D') : D,
('', 'D', 'D') : D,
('', 'C', 'C') : C,
('', 'D', 'C') : C,
}
self.assertEqual(player.lookup_table, expected_lookup_table)
# Test malformed tables
table = {(C, C): C, ('DD', 'DD'): C}
with self.assertRaises(ValueError):
player = self.player(table)
table = {(C, C): C, (C, D): 'CD'}
with self.assertRaises(ValueError):
player = self.player(table)

def test_strategy(self):
self.markov_test([C, D, C, D]) # TFT
self.responses_test([C] * 4, [C, C, C, C], [C])
self.responses_test([C] * 5, [C, C, C, C, D], [D])

def test_defector_table(self):
"""
Testing a lookup table that always defects if there is enough history.
In order for the testing framework to be able to construct new player
objects for the test, self.player needs to be callable with no
arguments, thus we use a lambda expression which will call the
constructor with the lookup table we want.
"""
defector_table = {
('', C, D) : D,
('', D, D) : D,
('', C, C) : D,
('', D, C) : D,
}
self.player = lambda : axelrod.LookerUp(defector_table)
self.responses_test([C, C], [C, C], [D])
self.responses_test([C, D], [D, C], [D])
self.responses_test([D, D], [D, D], [D])

def test_starting_move(self):
"""A lookup table that always repeats the opponent's first move."""

first_move_table = {
# If oppponent starts by cooperating:
(C, C, D) : C,
(C, D, D) : C,
(C, C, C) : C,
(C, D, C) : C,
# If opponent starts by defecting:
(D, C, D) : D,
(D, D, D) : D,
(D, C, C) : D,
(D, D, C) : D,
}

self.player = lambda : axelrod.LookerUp(first_move_table)

# if the opponent started by cooperating, we should always cooperate
self.responses_test([C, C, C], [C, C, C], [C])
self.responses_test([D, D, D], [C, C, C], [C])
self.responses_test([C, C, C], [C, D, C], [C])
self.responses_test([C, C, D], [C, D, C], [C])

# if the opponent started by defecting, we should always defect
self.responses_test([C, C, C], [D, C, C], [D])
self.responses_test([D, D, D], [D, C, C], [D])
self.responses_test([C, C, C], [D, D, C], [D])
self.responses_test([C, C, D], [D, D, C], [D])


class TestEvolvedLookerUp(TestPlayer):

name = "EvolvedLookerUp"
player = axelrod.EvolvedLookerUp

expected_classifier = {
'memory_depth': float('inf'),
'stochastic': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def test_init(self):
# Check for a few known keys
known_pairs = {('DD', 'CC', 'CD'): 'D', ('DC', 'CD', 'CD'): 'D',
('DD', 'CD', 'CD'): 'C', ('DC', 'DC', 'DC'): 'C',
('DD', 'DD', 'CC'): 'D', ('CD', 'CC', 'DC'): 'C'}
player = self.player()
for k, v in known_pairs.items():
self.assertEqual(player.lookup_table[k], v)

def test_strategy(self):
"""Starts by cooperating."""
self.first_play_test(C)


# Some heads up tests for EvolvedLookerUp
class EvolvedLookerUpvsDefector(TestHeadsUp):
def test_vs(self):
outcomes = zip([C, C, D], [D, D, D])
self.versus_test(axelrod.EvolvedLookerUp, axelrod.Defector, outcomes)


class EvolvedLookerUpvsCooperator(TestHeadsUp):
def test_vs(self):
outcomes = zip([C] * 10, [C] * 10)
self.versus_test(axelrod.EvolvedLookerUp, axelrod.Cooperator, outcomes)


class EvolvedLookerUpvsTFT(TestHeadsUp):
def test_vs(self):
outcomes = zip([C] * 10, [C] * 10)
self.versus_test(axelrod.EvolvedLookerUp, axelrod.TitForTat, outcomes)


class EvolvedLookerUpvsAlternator(TestHeadsUp):
def test_vs(self):
outcomes = zip([C, C, D, D, D, D], [C, D, C, D, C, D])
self.versus_test(axelrod.EvolvedLookerUp, axelrod.Alternator, outcomes)
9 changes: 8 additions & 1 deletion docs/reference/overview_of_strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,6 @@ AntiCycler plays a sequence that contains no cycles::

C CD CCD CCCD CCCCD CCCCCD ...


APavlov2006
^^^^^^^^^^^

Expand All @@ -1090,3 +1089,11 @@ counters of how often the opponent changes actions. When the counter
exceeds a threshold, OmegaTFT defects for the rest of the rounds. OmegaTFT
also keeps a counter to break deadlocks (C D to D C cycles) against
strategies like SuspiciousTitForTat.
=======
=======

LookerUp
^^^^^^^^

LookerUp uses a lookup table to decide what to play based on the last few rounds plus
the first few plays made by the opponent at the start of the match.