Skip to content

Commit 22da99b

Browse files
committed
Merge pull request #1 from marcharper/lookerup
Some comments, tests, and style edits for the LookerUp strategies
2 parents 6af3b3e + 287abab commit 22da99b

File tree

2 files changed

+182
-136
lines changed

2 files changed

+182
-136
lines changed

axelrod/strategies/lookerup.py

Lines changed: 81 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,51 @@
11
from axelrod import Player, Actions
2-
import itertools
2+
from itertools import product
33

44
C, D = Actions.C, Actions.D
55

6-
class LookerUp(Player):
7-
"""
8-
A strategy that uses a lookup table to
9-
decide what to do based on a combination of the last m rounds and the opponent's
10-
opening n moves. If there isn't enough history to do this (i.e. for the first m
11-
rounds) then cooperate.
6+
class LookerUp(Player):
7+
"""
8+
A strategy that uses a lookup table to decide what to do based on a
9+
combination of the last m turns and the opponent's opening n actions. If
10+
there isn't enough history to do this (i.e. for the first m turns) then
11+
cooperate.
1212
1313
The lookup table is implemented as a dict. The keys are 3-tuples giving the
14-
opponents first n moves, self's last m moves, and opponents last m moves,
15-
all as strings. The values are the moves to play on this round. For
16-
example, if
17-
18-
- the opponent started by playing C
19-
- my last move was a C the opponents
20-
- last move was a D
21-
22-
the corresponding key would be
23-
24-
('C', 'C', 'D')
25-
26-
and the value would contain the move to play on this turn.
14+
opponents first n actions, self's last m actions, and opponents last m
15+
actions, all as strings. The values are the actions to play on this round.
16+
For example, in the case of m=n=1, if
17+
- the opponent started by playing C
18+
- my last action was a C the opponents
19+
- last action was a D
20+
then the corresponding key would be
21+
(C, C, D)
22+
and the value would contain the action to play on this turn.
2723
2824
Some well-known strategies can be expressed as special cases; for example
2925
Cooperator is given by the dict:
30-
31-
{('', '', '') : 'C'}
32-
33-
where m and n are both zero. Tit For Tat is given by:
34-
35-
{
36-
('', 'C', 'D') : 'D',
37-
('', 'D', 'D') : 'D',
38-
('', 'C', 'C') : 'C',
39-
('', 'D', 'C') : 'C',
26+
{('', '', '') : C}
27+
where m and n are both zero. Tit-For-Tat is given by:
28+
{
29+
('', C, D) : D,
30+
('', D, D) : D,
31+
('', C, C) : C,
32+
('', D, C) : C,
4033
}
41-
4234
where m=1 and n=0.
4335
44-
Lookup tables where the move depends on the opponent's first moves (as
45-
opposed to most recent moves) will have a non-empty first string in the
36+
Lookup tables where the action depends on the opponent's first actions (as
37+
opposed to most recent actions) will have a non-empty first string in the
4638
tuple. For example, this fragment of a dict:
47-
48-
{
49-
...
50-
('C', 'C', 'C') : 'C'.
51-
('D', 'C', 'C') : 'D',
52-
...
39+
{
40+
...
41+
(C, C, C) : C.
42+
(D, C, C) : D,
43+
...
5344
}
54-
5545
states that if self and opponent both cooperated on the previous turn, we
5646
should cooperate this turn unless the opponent started by defecting, in
5747
which case we should defect.
58-
5948
"""
60-
6149

6250
name = 'LookerUp'
6351
classifier = {
@@ -70,81 +58,81 @@ class LookerUp(Player):
7058

7159
def __init__(self, lookup_table=None):
7260
"""
73-
If no lookup table is provided to the constructor, then use the TFT one
61+
If no lookup table is provided to the constructor, then use the TFT one.
7462
"""
7563
Player.__init__(self)
7664

7765
if not lookup_table:
7866
lookup_table = {
79-
('', 'C', 'D') : 'D',
80-
('', 'D', 'D') : 'D',
81-
('', 'C', 'C') : 'C',
82-
('', 'D', 'C') : 'C',
67+
('', C, D) : D,
68+
('', D, D) : D,
69+
('', C, C) : C,
70+
('', D, C) : C,
8371
}
8472

8573
self.lookup_table = lookup_table
86-
87-
88-
# rather than pass number of previous rounds to consider in as a separate variable, figure it out
89-
# the number of rounds is the length of the second element of any given key in the dict
90-
self.plays = len(list(self.lookup_table.keys())[0][1])
91-
92-
# the number of opponent starting moves is the lgnth of the first element of any given key in the dict
74+
# Rather than pass the number of previous turns (m) to consider in as a
75+
# separate variable, figure it out. The number of turns is the length
76+
# of the second element of any given key in the dict.
77+
self.plays = len(list(self.lookup_table.keys())[0][1])
78+
# The number of opponent starting actions is the length of the first
79+
# element of any given key in the dict.
9380
self.opponent_start_plays = len(list(self.lookup_table.keys())[0][0])
94-
81+
# If the table dictates to ignore the opening actions of the opponent
82+
# then the memory classification is adjusted
9583
if self.opponent_start_plays == 0:
9684
self.classifier['memory_depth'] = self.plays
97-
9885
self.init_args = (lookup_table,)
9986

100-
def strategy(self, opponent):
101-
"""
102-
If there isn't enough history to lookup a move from the table, cooperate
103-
"""
104-
if len(self.history) < max([self.plays,self.opponent_start_plays]) :
105-
return 'C'
106-
107-
else:
108-
# count back m moves to get my own recent history
109-
history_start = -1 * self.plays
110-
my_history = ''.join(self.history[history_start:])
111-
112-
# do the same for the opponent
113-
opponent_history = ''.join(opponent.history[history_start:])
114-
115-
# get the opponents first n moves
116-
opponent_start = ''.join(opponent.history[0:self.opponent_start_plays])
87+
# Ensure that table is well-formed
88+
for k, v in lookup_table.items():
89+
if (len(k[1]) != self.plays) or (len(k[0]) != self.opponent_start_plays):
90+
raise ValueError("All table elements must have the same size")
91+
if len(v) > 1:
92+
raise ValueError("Table values should be of length one, C or D")
11793

118-
# put these three strings together in a tuple
119-
key = (opponent_start, my_history, opponent_history)
120-
121-
# look up the move associated with that tuple in the lookup table
122-
move = self.lookup_table[key]
123-
124-
return move
94+
def strategy(self, opponent):
95+
# If there isn't enough history to lookup an action, cooperate.
96+
if len(self.history) < max(self.plays, self.opponent_start_plays):
97+
return C
98+
# Count backward m turns to get my own recent history.
99+
history_start = -1 * self.plays
100+
my_history = ''.join(self.history[history_start:])
101+
# Do the same for the opponent.
102+
opponent_history = ''.join(opponent.history[history_start:])
103+
# Get the opponents first n actions.
104+
opponent_start = ''.join(opponent.history[:self.opponent_start_plays])
105+
# Put these three strings together in a tuple.
106+
key = (opponent_start, my_history, opponent_history)
107+
# Look up the action associated with that tuple in the lookup table.
108+
action = self.lookup_table[key]
109+
return action
125110

126111

127112
class EvolvedLookerUp(LookerUp):
128113
"""
129-
A LookerUp strategy that uses a lookup table generated using an evolutionary algorithm.
130-
//TODO: update this docstring with a link to a blog post once I've written about it :-)
114+
A LookerUp strategy that uses a lookup table generated using an evolutionary
115+
algorithm.
131116
"""
132117

118+
name = "EvolvedLookerUp"
119+
133120
def __init__(self, lookup_table=None):
134-
self.name = "EvolvedLookerUp"
135121
plays = 2
136122
opponent_start_plays = 2
137123

138-
# functionaly generate the list of possible tuples (i.e. all possible combinations of m moves for me, m moves for opponent, and n starting moves for opponent)
139-
self_histories = [''.join(x) for x in itertools.product('CD', repeat=plays)]
140-
other_histories = [''.join(x) for x in itertools.product('CD', repeat=plays)]
141-
opponent_starts = [''.join(x) for x in itertools.product('CD', repeat=opponent_start_plays)]
142-
lookup_table_keys = list(itertools.product(opponent_starts,self_histories, other_histories))
143-
144-
# pattern of values determed with an evolutionary algorithm (blog post to follow)
124+
# Generate the list of possible tuples, i.e. all possible combinations
125+
# of m actions for me, m actions for opponent, and n starting actions
126+
# for opponent.
127+
self_histories = [''.join(x) for x in product('CD', repeat=plays)]
128+
other_histories = [''.join(x) for x in product('CD', repeat=plays)]
129+
opponent_starts = [''.join(x) for x in
130+
product('CD', repeat=opponent_start_plays)]
131+
lookup_table_keys = list(product(opponent_starts, self_histories,
132+
other_histories))
133+
134+
# Pattern of values determed previously with an evolutionary algorithm.
145135
pattern='CDCCDCCCDCDDDDDCCDCCCDDDCDDDDDDCDDDDCDDDDCCDDCDDCDDDCCCDCDCDDDDD'
146-
147-
# zip together the keys and the move pattern to get the lookup table
136+
# Zip together the keys and the action pattern to get the lookup table.
148137
lookup_table = dict(zip(lookup_table_keys, pattern))
149-
150138
LookerUp.__init__(self, lookup_table=lookup_table)

0 commit comments

Comments
 (0)