Skip to content

Commit 12efc34

Browse files
authored
Merge pull request #975 from Axelrod-Python/885
Implement an eq method for players
2 parents c4f685d + 239a511 commit 12efc34

File tree

12 files changed

+247
-84
lines changed

12 files changed

+247
-84
lines changed

axelrod/player.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import random
44
import copy
55
import inspect
6+
import types
7+
import numpy as np
8+
import itertools
69

710
from axelrod.actions import Actions, flip_action, Action
811
from .game import DefaultGame
@@ -122,6 +125,57 @@ def __init__(self):
122125
self.state_distribution = defaultdict(int)
123126
self.set_match_attributes()
124127

128+
129+
def __eq__(self, other):
130+
"""
131+
Test if two players are equal.
132+
"""
133+
if self.__repr__() != other.__repr__():
134+
return False
135+
for attribute in set(list(self.__dict__.keys()) +
136+
list(other.__dict__.keys())):
137+
138+
value = getattr(self, attribute, None)
139+
other_value = getattr(other, attribute, None)
140+
141+
if isinstance(value, np.ndarray):
142+
if not (np.array_equal(value, other_value)):
143+
return False
144+
145+
elif isinstance(value, types.GeneratorType) or \
146+
isinstance(value, itertools.cycle):
147+
148+
# Split the original generator so it is not touched
149+
generator, original_value = itertools.tee(value)
150+
other_generator, original_other_value = itertools.tee(other_value)
151+
152+
153+
if isinstance(value, types.GeneratorType):
154+
setattr(self, attribute,
155+
(ele for ele in original_value))
156+
setattr(other, attribute,
157+
(ele for ele in original_other_value))
158+
else:
159+
setattr(self, attribute,
160+
itertools.cycle(original_value))
161+
setattr(other, attribute,
162+
itertools.cycle(original_other_value))
163+
164+
if not (all(next(generator) == next(other_generator)
165+
for _ in range(200))):
166+
return False
167+
168+
# Code for a strange edge case where each strategy points at each
169+
# other
170+
elif (value is other and other_value is self):
171+
pass
172+
173+
else:
174+
if value != other_value:
175+
return False
176+
return True
177+
178+
125179
def receive_match_attributes(self):
126180
# Overwrite this function if your strategy needs
127181
# to make use of match_attributes such as

axelrod/strategies/qlearner.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ def __init__(self) -> None:
4848
# for any subclasses that do not override methods using random calls.
4949
self.classifier['stochastic'] = True
5050

51-
self.prev_action = random_choice()
52-
self.original_prev_action = self.prev_action
51+
self.prev_action = None # type: Action
52+
self.original_prev_action = None # type: Action
5353
self.history = [] # type: List[Action]
5454
self.score = 0
5555
self.Qs = OrderedDict({'': OrderedDict(zip([C, D], [0, 0]))})
@@ -62,6 +62,9 @@ def receive_match_attributes(self):
6262

6363
def strategy(self, opponent: Player) -> Action:
6464
"""Runs a qlearn algorithm while the tournament is running."""
65+
if len(self.history) == 0:
66+
self.prev_action = random_choice()
67+
self.original_prev_action = self.prev_action
6568
state = self.find_state(opponent)
6669
reward = self.find_reward(opponent)
6770
if state not in self.Qs:
@@ -118,7 +121,8 @@ def reset(self):
118121
self.Qs = {'': {C: 0, D: 0}}
119122
self.Vs = {'': 0}
120123
self.prev_state = ''
121-
self.prev_action = self.original_prev_action
124+
self.prev_action = None
125+
self.original_prev_action = None
122126

123127

124128
class ArrogantQLearner(RiskyQLearner):

axelrod/strategies/titfortat.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,11 @@ class ContriteTitForTat(Player):
425425
'manipulates_source': False,
426426
'manipulates_state': False
427427
}
428-
contrite = False
428+
429+
def __init__(self):
430+
super().__init__()
431+
self.contrite = False
432+
self._recorded_history = []
429433

430434
def strategy(self, opponent: Player) -> Action:
431435

axelrod/tests/strategies/test_calculator.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,6 @@ def test_edge_case_calculator_ignores_cycles_gt_len_ten(self):
8181
axelrod.MockPlayer(actions=opponent_actions),
8282
expected_actions=uses_tit_for_tat_after_twenty_rounds, seed=seed)
8383

84-
def attribute_equality_test(self, player, clone):
85-
"""Overwrite the default test to check Joss instance"""
86-
self.assertIsInstance(player.joss_instance, axelrod.Joss)
87-
self.assertIsInstance(clone.joss_instance, axelrod.Joss)
88-
8984
def test_get_joss_strategy_actions(self):
9085
opponent = [C, D, D, C, C]
9186

axelrod/tests/strategies/test_memorytwo.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,3 @@ def test_strategy(self):
4141
# ALLD forever if all D twice
4242
self.responses_test([D] * 10, [C, D, D, D, D, D], [D, D, D, D, D, D])
4343
self.responses_test([D] * 9, [C] + [D] * 5 + [C] * 4, [D] * 6 + [C] * 4)
44-
45-
def attribute_equality_test(self, player, clone):
46-
"""Overwrite specific test to be able to test self.players"""
47-
for p in [player, clone]:
48-
self.assertEqual(p.play_as, "TFT")
49-
self.assertEqual(p.shift_counter, 3)
50-
self.assertEqual(p.alld_counter, 0)
51-
52-
for key, value in [("TFT", axelrod.TitForTat),
53-
("TFTT", axelrod.TitFor2Tats),
54-
("ALLD", axelrod.Defector)]:
55-
self.assertEqual(p.players[key].history, [])
56-
self.assertIsInstance(p.players[key], value)

axelrod/tests/strategies/test_meta.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,6 @@ def classifier_test(self, expected_class_classifier=None):
4646
msg="%s - Behaviour: %s != Expected Behaviour: %s" %
4747
(key, player.classifier[key], classifier[key]))
4848

49-
def attribute_equality_test(self, player, clone):
50-
"""Overwriting this specific method to check team."""
51-
for p1, p2 in zip(player.team, clone.team):
52-
self.assertEqual(len(p1.history), 0)
53-
self.assertEqual(len(p2.history), 0)
54-
55-
team_player_names = [p.__repr__() for p in player.team]
56-
team_clone_names = [p.__repr__() for p in clone.team]
57-
self.assertEqual(team_player_names, team_clone_names)
58-
5949
def test_repr(self):
6050
player = self.player()
6151
team_size = len(player.team)

axelrod/tests/strategies/test_player.py

Lines changed: 146 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from axelrod import DefaultGame, MockPlayer, Player, simulate_play
1010
from axelrod.player import get_state_distribution_from_history
1111

12+
from hypothesis import given
13+
from axelrod.tests.property import strategy_lists
1214

1315
C, D = axelrod.Actions.C, axelrod.Actions.D
1416

@@ -150,6 +152,148 @@ def test_clone(self):
150152
self.assertEqual(len(player1.history), turns)
151153
self.assertEqual(player1.history, player2.history)
152154

155+
def test_equality(self):
156+
"""Test the equality method for some bespoke cases"""
157+
# Check repr
158+
p1 = axelrod.Cooperator()
159+
p2 = axelrod.Cooperator()
160+
self.assertEqual(p1, p2)
161+
p1.__repr__ = lambda: "John Nash"
162+
self.assertNotEqual(p1, p2)
163+
164+
# Check attributes
165+
p1 = axelrod.Cooperator()
166+
p2 = axelrod.Cooperator()
167+
p1.test = "29"
168+
self.assertNotEqual(p1, p2)
169+
170+
p1 = axelrod.Cooperator()
171+
p2 = axelrod.Cooperator()
172+
p2.test = "29"
173+
self.assertNotEqual(p1, p2)
174+
175+
p1.test = "29"
176+
self.assertEqual(p1, p2)
177+
178+
# Check that attributes of both players are tested.
179+
p1.another_attribute = [1, 2, 3]
180+
self.assertNotEqual(p1, p2)
181+
p2.another_attribute = [1, 2, 3]
182+
self.assertEqual(p1, p2)
183+
184+
p2.another_attribute_2 = {1: 2}
185+
self.assertNotEqual(p1, p2)
186+
p1.another_attribute_2 = {1: 2}
187+
self.assertEqual(p1, p2)
188+
189+
def test_equality_for_numpy_array(self):
190+
"""Check numpy array attribute (a special case)"""
191+
p1 = axelrod.Cooperator()
192+
p2 = axelrod.Cooperator()
193+
194+
p1.array = np.array([0, 1])
195+
p2.array = np.array([0, 1])
196+
self.assertEqual(p1, p2)
197+
198+
p2.array = np.array([1, 0])
199+
self.assertNotEqual(p1, p2)
200+
201+
def test_equality_for_generator(self):
202+
"""Test equality works with generator attribute and that the generator
203+
attribute is not altered during checking of equality"""
204+
p1 = axelrod.Cooperator()
205+
p2 = axelrod.Cooperator()
206+
207+
# Check that players are equal with generator
208+
p1.generator = (i for i in range(10))
209+
p2.generator = (i for i in range(10))
210+
self.assertEqual(p1, p2)
211+
212+
# Check state of one generator (ensure it hasn't changed)
213+
n = next(p2.generator)
214+
self.assertEqual(n, 0)
215+
216+
# Players are no longer equal (one generator has changed)
217+
self.assertNotEqual(p1, p2)
218+
219+
# Check that internal generator object has not been changed for either
220+
# player after latest equal check.
221+
self.assertEqual(list(p1.generator), list(range(10)))
222+
self.assertEqual(list(p2.generator), list(range(1, 10)))
223+
224+
# Check that type is generator
225+
self.assertIsInstance(p2.generator, types.GeneratorType)
226+
227+
def test_equality_for_cycle(self):
228+
"""Test equality works with cycle attribute and that the cycle attribute
229+
is not altered during checking of equality"""
230+
# Check cycle attribute (a special case)
231+
p1 = axelrod.Cooperator()
232+
p2 = axelrod.Cooperator()
233+
234+
# Check that players are equal with cycle
235+
p1.cycle = itertools.cycle(range(10))
236+
p2.cycle = itertools.cycle(range(10))
237+
self.assertEqual(p1, p2)
238+
239+
# Check state of one generator (ensure it hasn't changed)
240+
n = next(p2.cycle)
241+
self.assertEqual(n, 0)
242+
243+
# Players are no longer equal (one generator has changed)
244+
self.assertNotEqual(p1, p2)
245+
246+
# Check that internal cycle object has not been changed for either
247+
# player after latest not equal check.
248+
self.assertEqual(next(p1.cycle), 0)
249+
self.assertEqual(next(p2.cycle), 1)
250+
251+
# Check that type is cycle
252+
self.assertIsInstance(p2.cycle, itertools.cycle)
253+
254+
def test_equality_on_init(self):
255+
"""Test instances of all strategies are equal on init"""
256+
for s in axelrod.strategies:
257+
p1, p2 = s(), s()
258+
# Check three times (so testing equality doesn't change anything)
259+
self.assertEqual(p1, p2)
260+
self.assertEqual(p1, p2)
261+
self.assertEqual(p1, p2)
262+
263+
def test_equality_with_player_as_attributes(self):
264+
"""Test for a strange edge case where players have pointers to each
265+
other"""
266+
p1 = axelrod.Cooperator()
267+
p2 = axelrod.Cooperator()
268+
269+
# If pointing at each other
270+
p1.player = p2
271+
p2.player = p1
272+
self.assertEqual(p1, p2)
273+
274+
# Still checking other attributes.
275+
p1.test_attribute = "29"
276+
self.assertNotEqual(p1, p2)
277+
278+
# If pointing at same strategy instances
279+
p1.player = axelrod.Cooperator()
280+
p2.player = axelrod.Cooperator()
281+
p2.test_attribute = "29"
282+
self.assertEqual(p1, p2)
283+
284+
285+
# If pointing at different strategy instances
286+
p1.player = axelrod.Cooperator()
287+
p2.player = axelrod.Defector()
288+
self.assertNotEqual(p1, p2)
289+
290+
# If different strategies pointing at same strategy instances
291+
p3 = axelrod.Defector()
292+
p1.player = axelrod.Cooperator()
293+
p3.player = axelrod.Cooperator()
294+
self.assertNotEqual(p1, p3)
295+
296+
153297
def test_init_params(self):
154298
"""Tests player correct parameters signature detection."""
155299
self.assertEqual(self.player.init_params(), {})
@@ -285,41 +429,14 @@ def test_reset_history_and_attributes(self):
285429
player.play(opponent)
286430

287431
player.reset()
288-
self.assertEqual(len(player.history), 0)
289-
self.assertEqual(player.cooperations, 0)
290-
self.assertEqual(player.defections, 0)
291-
self.assertEqual(player.state_distribution, dict())
292-
293-
self.attribute_equality_test(player, clone)
432+
self.assertEqual(player, clone)
294433

295434
def test_reset_clone(self):
296435
"""Make sure history resetting with cloning works correctly, regardless
297436
if self.test_reset() is overwritten."""
298437
player = self.player()
299438
clone = player.clone()
300-
self.attribute_equality_test(player, clone)
301-
302-
def attribute_equality_test(self, player, clone):
303-
"""A separate method to test equality of attributes. This method can be
304-
overwritten in certain cases.
305-
306-
This method checks that all the attributes of `player` and `clone` are
307-
the same which is used in the test of the cloning and the resetting.
308-
"""
309-
310-
for attribute, reset_value in player.__dict__.items():
311-
original_value = getattr(clone, attribute)
312-
313-
if isinstance(reset_value, np.ndarray):
314-
self.assertTrue(np.array_equal(reset_value, original_value),
315-
msg=attribute)
316-
317-
elif isinstance(reset_value, types.GeneratorType) or isinstance(reset_value, itertools.cycle):
318-
for _ in range(10):
319-
self.assertEqual(next(reset_value),
320-
next(original_value), msg=attribute)
321-
else:
322-
self.assertEqual(reset_value, original_value, msg=attribute)
439+
self.assertEqual(player, clone)
323440

324441
def test_clone(self):
325442
# Test that the cloned player produces identical play

axelrod/tests/strategies/test_qlearner.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,6 @@ def test_strategy(self):
7070
p2 = axelrod.Cooperator()
7171
test_responses(self, p1, p2, [C, D, C, C, D, C, C])
7272

73-
def test_reset_method(self):
74-
"""Test the reset method."""
75-
P1 = axelrod.RiskyQLearner()
76-
P1.Qs = {'': {C: 0, D: -0.9}, '0.0': {C: 0, D: 0}}
77-
P1.Vs = {'': 0, '0.0': 0}
78-
P1.history = [C, D, D, D]
79-
P1.prev_state = C
80-
P1.reset()
81-
self.assertEqual(P1.prev_state, '')
82-
self.assertEqual(P1.Vs, {'': 0})
83-
self.assertEqual(P1.Qs, {'': {C: 0, D: 0}})
84-
8573

8674
class TestArrogantQLearner(TestPlayer):
8775

0 commit comments

Comments
 (0)