1
+ """
2
+ Particle Swarm strategy training code.
3
+
4
+ Original version by Georgios Koutsovoulos @GDKO :
5
+ https://gist.github.com/GDKO/60c3d0fd423598f3c4e4
6
+ Based on Martin Jones @mojones original LookerUp code
7
+ """
8
+
9
+ import axelrod
10
+
11
+ from pyswarm import pso
12
+
13
+
14
+ class Gambler (Player ):
15
+
16
+ name = 'Gambler'
17
+ classifier = {
18
+ 'memory_depth' : float ('inf' ),
19
+ 'stochastic' : True ,
20
+ 'makes_use_of' : set (),
21
+ 'inspects_source' : False ,
22
+ 'manipulates_source' : False ,
23
+ 'manipulates_state' : False
24
+ }
25
+
26
+ @init_args
27
+ def __init__ (self , lookup_table = None ):
28
+ """
29
+ If no lookup table is provided to the constructor, then use the TFT one.
30
+ """
31
+ Player .__init__ (self )
32
+
33
+ if not lookup_table :
34
+ lookup_table = {
35
+ ('' , 'C' , 'D' ) : 0 ,
36
+ ('' , 'D' , 'D' ) : 0 ,
37
+ ('' , 'C' , 'C' ) : 1 ,
38
+ ('' , 'D' , 'C' ) : 1 ,
39
+ }
40
+
41
+ self .lookup_table = lookup_table
42
+ # Rather than pass the number of previous turns (m) to consider in as a
43
+ # separate variable, figure it out. The number of turns is the length
44
+ # of the second element of any given key in the dict.
45
+ self .plays = len (list (self .lookup_table .keys ())[0 ][1 ])
46
+ # The number of opponent starting actions is the length of the first
47
+ # element of any given key in the dict.
48
+ self .opponent_start_plays = len (list (self .lookup_table .keys ())[0 ][0 ])
49
+ # If the table dictates to ignore the opening actions of the opponent
50
+ # then the memory classification is adjusted
51
+ if self .opponent_start_plays == 0 :
52
+ self .classifier ['memory_depth' ] = self .plays
53
+
54
+ # Ensure that table is well-formed
55
+ for k , v in lookup_table .items ():
56
+ if (len (k [1 ]) != self .plays ) or (len (k [0 ]) != self .opponent_start_plays ):
57
+ raise ValueError ("All table elements must have the same size" )
58
+
59
+
60
+ def strategy (self , opponent ):
61
+ # If there isn't enough history to lookup an action, cooperate.
62
+ if len (self .history ) < max (self .plays , self .opponent_start_plays ):
63
+ return C
64
+ # Count backward m turns to get my own recent history.
65
+ history_start = - 1 * self .plays
66
+ my_history = '' .join (self .history [history_start :])
67
+ # Do the same for the opponent.
68
+ opponent_history = '' .join (opponent .history [history_start :])
69
+ # Get the opponents first n actions.
70
+ opponent_start = '' .join (opponent .history [:self .opponent_start_plays ])
71
+ # Put these three strings together in a tuple.
72
+ key = (opponent_start , my_history , opponent_history )
73
+ # Look up the action associated with that tuple in the lookup table.
74
+ action = float (self .lookup_table [key ])
75
+ return random_choice (action )
76
+
77
+
78
+
79
+ class TestGambler (Gambler ):
80
+ """
81
+ A LookerUp strategy that uses pattern supplied when initialised.
82
+ """
83
+
84
+ name = "TestGambler"
85
+
86
+ def __init__ (self ,pattern ):
87
+ plays = 2
88
+ opponent_start_plays = 2
89
+
90
+ # Generate the list of possible tuples, i.e. all possible combinations
91
+ # of m actions for me, m actions for opponent, and n starting actions
92
+ # for opponent.
93
+ self_histories = ['' .join (x ) for x in product ('CD' , repeat = plays )]
94
+ other_histories = ['' .join (x ) for x in product ('CD' , repeat = plays )]
95
+ opponent_starts = ['' .join (x ) for x in
96
+ product ('CD' , repeat = opponent_start_plays )]
97
+ lookup_table_keys = list (product (opponent_starts , self_histories ,
98
+ other_histories ))
99
+
100
+ # Zip together the keys and the action pattern to get the lookup table.
101
+ lookup_table = dict (zip (lookup_table_keys , pattern ))
102
+ Gambler .__init__ (self , lookup_table = lookup_table )
103
+
104
+
105
+ def score_for_pattern (my_strategy_factory ,pattern , iterations = 200 ):
106
+ """
107
+ Given a function that will return a strategy,
108
+ calculate the average score per turn
109
+ against all ordinary strategies. If the
110
+ opponent is classified as stochastic, then
111
+ run 100 repetitions and take the average to get
112
+ a good estimate.
113
+ """
114
+ scores_for_all_opponents = []
115
+ for opponent in axelrod .ordinary_strategies :
116
+
117
+ # decide whether we need to sample or not
118
+ if opponent .classifier ['stochastic' ]:
119
+ repetitions = 100
120
+ else :
121
+ repetitions = 1
122
+ scores_for_this_opponent = []
123
+
124
+ # calculate an average for this opponent
125
+ for _ in range (repetitions ):
126
+ me = my_strategy_factory (pattern )
127
+ other = opponent ()
128
+ # make sure that both players know what length the match will be
129
+ me .set_tournament_attributes (length = iterations )
130
+ other .set_tournament_attributes (length = iterations )
131
+
132
+ scores_for_this_opponent .append (score_single (me , other , iterations ))
133
+
134
+ mean_vs_opponent = sum (scores_for_this_opponent ) / len (scores_for_this_opponent )
135
+ scores_for_all_opponents .append (mean_vs_opponent )
136
+
137
+ # calculate the average for all opponents
138
+ overall_average_score = sum (scores_for_all_opponents ) / len (scores_for_all_opponents )
139
+ return overall_average_score
140
+
141
+
142
+ def optimizepso (x ):
143
+ return - score_for_pattern (TestGambler , x )
144
+
145
+ if __name__ == "__main__" :
146
+ lb = [0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
147
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
148
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
149
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]
150
+ ub = [1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
151
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
152
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
153
+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ]
154
+
155
+ # The parameters of phip, phig and omega will lead to slower conversion
156
+ xopt , fopt = pso (optimizepso , lb , ub , swarmsize = 100 , maxiter = 20 , processes = 60 ,
157
+ debug = True ,
158
+ phip = 0.8 , phig = 0.8 , omega = 0.8 )
0 commit comments