diff --git a/axelrod/strategies/backstabber.py b/axelrod/strategies/backstabber.py index 2a87f969f..c1c350a2b 100644 --- a/axelrod/strategies/backstabber.py +++ b/axelrod/strategies/backstabber.py @@ -4,7 +4,7 @@ C, D = Actions.C, Actions.D -@FinalTransformer([D, D]) # End with two defections +@FinalTransformer((D, D)) # End with two defections class BackStabber(Player): """ Forgives the first 3 defections but on the fourth diff --git a/axelrod/strategies/mindreader.py b/axelrod/strategies/mindreader.py index 7310390fe..03cdfbe33 100644 --- a/axelrod/strategies/mindreader.py +++ b/axelrod/strategies/mindreader.py @@ -2,25 +2,23 @@ import inspect from axelrod import Actions, Player, RoundRobin, update_history +from .cycler import Cycler C, D = Actions.C, Actions.D +def limited_simulate_play(player_1, player_2, h1): + """Here we want to replay player_1's history to player_2, allowing + player_2's strategy method to set any internal variables as needed. If you + need a more complete simulation, see `simulate_play` in player.py. This + function is specifically designed for the needs of MindReader.""" + h2 = player_2.strategy(player_1) + update_history(player_1, h1) + update_history(player_2, h2) + def simulate_match(player_1, player_2, strategy, rounds=10): """Simulates a number of matches.""" for match in range(rounds): - play_1, play_2 = strategy, player_2.strategy(player_1) - # Update histories and counts - update_history(player_1, play_1) - update_history(player_2, play_2) - -def roll_back_history(player, rounds): - """Undo the last `rounds` rounds as sufficiently as possible.""" - for i in range(rounds): - play = player.history.pop(-1) - if play == C: - player.cooperations -= 1 - elif play == D: - player.defections -= 1 + limited_simulate_play(player_1, player_2, strategy) def look_ahead(player_1, player_2, game, rounds=10): """Looks ahead for `rounds` and selects the next strategy appropriately.""" @@ -29,14 +27,16 @@ def look_ahead(player_1, player_2, game, rounds=10): # Simulate plays for `rounds` rounds strategies = [C, D] for strategy in strategies: - opponent_ = copy.deepcopy(player_2) # need deepcopy here - round_robin = RoundRobin(players=[player_1, opponent_], game=game, - turns=rounds) - simulate_match(player_1, opponent_, strategy, rounds) - results.append(round_robin._calculate_scores(player_1, opponent_)[0]) + # Instead of a deepcopy, create a new opponent and play out the history + opponent_ = player_2.clone() + player_ = Cycler(strategy) # Either cooperator or defector + for h1 in player_1.history: + limited_simulate_play(player_, opponent_, h1) - # Restore histories and counts - roll_back_history(player_1, rounds) + round_robin = RoundRobin(players=[player_, opponent_], game=game, + turns=rounds) + simulate_match(player_, opponent_, strategy, rounds) + results.append(round_robin._calculate_scores(player_, opponent_)[0]) return strategies[results.index(max(results))] diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index 600c89825..3a5a24486 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -187,6 +187,12 @@ def final_sequence(player, opponent, action, seq): return action index = length - len(player.history) + # If for some reason we've overrun the expected game length, just pass + # the intended action through + if len(player.history) >= length: + return action + # Check if we're near the end and need to start passing the actions + # from seq for the final few rounds. if index <= len(seq): return seq[-index] return action diff --git a/axelrod/tests/unit/test_backstabber.py b/axelrod/tests/unit/test_backstabber.py index fbe6803d8..f2a600162 100644 --- a/axelrod/tests/unit/test_backstabber.py +++ b/axelrod/tests/unit/test_backstabber.py @@ -35,7 +35,8 @@ def test_strategy(self): # Defects on rounds 199, and 200 no matter what self.responses_test([C] * 197 , [C] * 197, [C, D, D], tournament_length=200) - self.responses_test([C] * 198 , [C] * 198, [D, D, D], + # Test that exceeds tournament length + self.responses_test([C] * 198 , [C] * 198, [D, D, C], tournament_length=200) # But only if the tournament is known self.responses_test([C] * 198 , [C] * 198, [C, C, C], diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index d1bf3aea2..674823675 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -106,10 +106,7 @@ def test_serial_play(self): {'cooperation': [], 'payoff': []}) self.assertFalse(tournament._run_parallel_repetitions.called) - #@given(s=lists(sampled_from(axelrod.strategies), - # Removing this as Hypothesis seems to have found a py2 bug. - # This is a temporary fix. For some reason mind reader seems to fail a test. - @given(s=lists(sampled_from([s for s in axelrod.strategies if s not in axelrod.cheating_strategies]), + @given(s=lists(sampled_from(axelrod.strategies), min_size=2, # Errors are returned if less than 2 strategies max_size=5, unique=True), turns=integers(min_value=2, max_value=50), @@ -118,9 +115,18 @@ def test_serial_play(self): @settings(max_examples=50, timeout=0) @example(s=test_strategies, turns=test_turns, repetitions=test_repetitions, rm=random.seed(0)) + + # These two examples are to make sure #465 is fixed. + # As explained there: https://github.com/Axelrod-Python/Axelrod/issues/465, + # these two examples were identified by hypothesis. + @example(s=[axelrod.BackStabber, axelrod.MindReader], turns=2, repetitions=1, + rm=random.seed(0)) + @example(s=[axelrod.ThueMorse, axelrod.MindReader], turns=2, repetitions=1, + rm=random.seed(0)) def test_property_serial_play(self, s, turns, repetitions, rm): """Test serial play using hypothesis""" # Test that we get an instance of ResultSet + players = [strat() for strat in s] tournament = axelrod.Tournament(