1
1
from axelrod import Player , Actions
2
- import itertools
2
+ from itertools import product
3
3
4
4
C , D = Actions .C , Actions .D
5
5
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.
12
12
13
13
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.
27
23
28
24
Some well-known strategies can be expressed as special cases; for example
29
25
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,
40
33
}
41
-
42
34
where m=1 and n=0.
43
35
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
46
38
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
+ ...
53
44
}
54
-
55
45
states that if self and opponent both cooperated on the previous turn, we
56
46
should cooperate this turn unless the opponent started by defecting, in
57
47
which case we should defect.
58
-
59
48
"""
60
-
61
49
62
50
name = 'LookerUp'
63
51
classifier = {
@@ -70,81 +58,81 @@ class LookerUp(Player):
70
58
71
59
def __init__ (self , lookup_table = None ):
72
60
"""
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.
74
62
"""
75
63
Player .__init__ (self )
76
64
77
65
if not lookup_table :
78
66
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 ,
83
71
}
84
72
85
73
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.
93
80
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
95
83
if self .opponent_start_plays == 0 :
96
84
self .classifier ['memory_depth' ] = self .plays
97
-
98
85
self .init_args = (lookup_table ,)
99
86
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" )
117
93
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
125
110
126
111
127
112
class EvolvedLookerUp (LookerUp ):
128
113
"""
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.
131
116
"""
132
117
118
+ name = "EvolvedLookerUp"
119
+
133
120
def __init__ (self , lookup_table = None ):
134
- self .name = "EvolvedLookerUp"
135
121
plays = 2
136
122
opponent_start_plays = 2
137
123
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.
145
135
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.
148
137
lookup_table = dict (zip (lookup_table_keys , pattern ))
149
-
150
138
LookerUp .__init__ (self , lookup_table = lookup_table )
0 commit comments