1
1
from typing import Any , Callable , Generic , List , Optional , Set , Text , Type , \
2
2
TypeVar , Union
3
3
4
+ import os
4
5
import yaml
5
6
6
7
from axelrod .player import Player
11
12
12
13
13
14
class Classifier (Generic [T ]):
15
+ """Describes a Player (strategy).
16
+
17
+ User sets a name and function, f, at initialization. Through
18
+ calc_for_player, looks for the classifier to be set in the passed Player
19
+ class. If not set, then passes to f for calculation.
20
+
21
+ f must operate on the class, and not an instance. If necessary, f may
22
+ initialize an instance, but this shouldn't depend on runtime states, because
23
+ the result gets stored in a file. If a strategy's classifier depends on
24
+ runtime states, such as those created by transformers, then it can set the
25
+ field in its classifier dict, and that will take precedent over saved
26
+ values.
27
+
28
+ Attributes
29
+ ----------
30
+ name: An identifier for the classifier, used as a dict key in storage and in
31
+ 'classifier' dicts of Player classes.
32
+ f: A function that takes in a Player class (not an instance) and returns a
33
+ value.
34
+ """
35
+
14
36
def __init__ (self , name : Text , f : Callable [[Type [Player ]], T ]):
15
37
self .name = name
16
38
self .f = f
17
39
18
40
def calc_for_player (self , player : Type [Player ]) -> T :
41
+ """Look for this classifier in the passed player's 'classifier' dict,
42
+ otherwise pass to the player to f."""
19
43
if self .name in player .classifier :
20
44
return player .classifier [self .name ]
21
45
@@ -33,6 +57,7 @@ def calc_for_player(self, player: Type[Player]) -> T:
33
57
manipulates_state = Classifier [Optional [bool ]]("manipulates_state" ,
34
58
lambda _ : None )
35
59
60
+ # Should list all known classifiers.
36
61
all_classifiers = [
37
62
stochastic ,
38
63
memory_depth ,
@@ -47,37 +72,88 @@ def calc_for_player(self, player: Type[Player]) -> T:
47
72
def rebuild_classifier_table (classifiers : List [Classifier ],
48
73
players : List [Type [Player ]],
49
74
path : Text = ALL_CLASSIFIERS_PATH ) -> None :
75
+ """Builds the classifier table in data.
76
+
77
+ Parameters
78
+ ----------
79
+ classifiers: A list of classifiers to calculate on the strategies
80
+ players: A list of strategies (classes, not instances) to compute the
81
+ classifiers for.
82
+ path: Where to save the resulting yaml file.
83
+ """
84
+ # Get absolute path
85
+ dirname = os .path .dirname (__file__ )
86
+ filename = os .path .join (dirname , path )
87
+
50
88
all_player_dicts = dict ()
51
89
for p in players :
52
90
new_player_dict = dict ()
53
91
for c in classifiers :
54
92
new_player_dict [c .name ] = c .calc_for_player (p )
55
93
all_player_dicts [p .name ] = new_player_dict
56
94
57
- with open (path , 'w' ) as f :
95
+ with open (filename , 'w' ) as f :
58
96
yaml .dump (all_player_dicts , f )
59
97
60
98
61
99
class Classifiers (object ):
100
+ """A singleton used to calculate any known classifier.
101
+
102
+ Attributes
103
+ ----------
104
+ all_player_dicts: A local copy of the dict saved in the classifier table.
105
+ The keys are player names, and the values are 'classifier' dicts (keyed
106
+ by classifier name).
107
+ """
62
108
_instance = None
63
109
all_player_dicts = dict ()
64
110
65
111
# Make this a singleton
66
112
def __new__ (cls ):
67
113
if cls ._instance is None :
68
114
cls ._instance = super (Classifiers , cls ).__new__ (cls )
69
- with open (ALL_CLASSIFIERS_PATH , 'r' ) as f :
115
+ # When this is first created, read from the classifier table file.
116
+ # Get absolute path
117
+ dirname = os .path .dirname (__file__ )
118
+ filename = os .path .join (dirname , ALL_CLASSIFIERS_PATH )
119
+ with open (filename , 'r' ) as f :
70
120
cls .all_player_dicts = yaml .load (f , Loader = yaml .FullLoader )
71
121
72
122
return cls ._instance
73
123
124
+ @classmethod
125
+ def known_classifier (cls , classifier_name : Text ) -> bool :
126
+ """Returns True if the passed classifier_name is known."""
127
+ global all_classifiers
128
+ return classifier_name in (c .name for c in all_classifiers )
129
+
74
130
@classmethod
75
131
def get (cls , classifier : Union [Classifier , Text ],
76
132
player : Player ) -> Any :
133
+ """Looks up the classifier for the player.
134
+
135
+ If the classifier is found in the 'classifier' dict on the player, then
136
+ return that. Otherwise look for the classifier for the player in the
137
+ all_player_dicts. Returns None if the classifier is not found in either
138
+ of those.
139
+
140
+ Parameters
141
+ ----------
142
+ classifier: A classifier or classifier name that we want to calculate
143
+ for the player.
144
+ player: The player (instance) for which we compute the classifier.
145
+
146
+ Returns
147
+ -------
148
+ The classifier value for the player, or None if unknown.
149
+ """
77
150
# Classifier may be the name or an instance. Convert to name.
78
151
if not isinstance (classifier , str ):
79
152
classifier = classifier .name
80
153
154
+ if not cls .known_classifier (classifier ):
155
+ raise KeyError ("Unknown classifier" )
156
+
81
157
# Factory-generated players won't exist in the table. As well, some
82
158
# players, like Random, may change classifiers at construction time;
83
159
# this get() function takes a player instance, while the saved-values
@@ -102,3 +178,34 @@ def return_missing() -> None:
102
178
if classifier not in player_classifiers :
103
179
return return_missing ()
104
180
return player_classifiers [classifier ]
181
+
182
+
183
+ # Strategy classifiers
184
+
185
+ def is_basic (s ):
186
+ """
187
+ Defines criteria for a strategy to be considered 'basic'
188
+ """
189
+ stochastic = Classifiers ().get ("stochastic" , s )
190
+ depth = Classifiers ().get ("memory_depth" , s )
191
+ inspects_source = Classifiers ().get ("inspects_source" , s )
192
+ manipulates_source = Classifiers ().get ("manipulates_source" , s )
193
+ manipulates_state = Classifiers ().get ("manipulates_state" , s )
194
+ return (
195
+ not stochastic
196
+ and not inspects_source
197
+ and not manipulates_source
198
+ and not manipulates_state
199
+ and depth in (0 , 1 )
200
+ )
201
+
202
+
203
+ def obey_axelrod (s ):
204
+ """
205
+ A function to check if a strategy obeys Axelrod's original tournament
206
+ rules.
207
+ """
208
+ for c in ["inspects_source" , "manipulates_source" , "manipulates_state" ]:
209
+ if Classifiers ().get (c , s ):
210
+ return False
211
+ return True
0 commit comments