diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 8ddd0476a..ceb0394ce 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -20,6 +20,7 @@ UnnamedStrategy, ) from .axelrod_second import ( + Appold, Black, Borufsen, Cave, @@ -248,6 +249,7 @@ APavlov2006, APavlov2011, Appeaser, + Appold, ArrogantQLearner, AverageCopier, BackStabber, diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 6722fe0d6..fb4bb07de 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -536,15 +536,15 @@ def strategy(self, opponent: Player) -> Action: self.dd_counts += 1 else: if opponent.history[-1] == C: - self.dc_counts += 1 - else: self.cc_counts += 1 + else: + self.dc_counts += 1 # Check for randomness if len(self.history) > 26: if self.cd_counts >= (self.cd_counts + self.dd_counts) / 2 - 0.75 * np.sqrt( self.cd_counts + self.dd_counts - ) and self.cc_counts >= ( + ) and self.dc_counts >= ( self.dc_counts + self.cc_counts ) / 2 - 0.75 * np.sqrt( self.dc_counts + self.cc_counts @@ -2059,3 +2059,72 @@ def strategy(self, opponent: Player) -> Action: self.mode = "Coop Def Cycle 1" return D + +class Appold(Player): + """ + Strategy submitted to Axelrod's second tournament by Scott Appold (K88R) and + came in 22nd in that tournament. + + Cooperates for first four turns. + + After four turns, will cooperate immediately following the first time the + opponent cooperates (starting with the opponent's fourth move). Otherwise + will cooperate with probability equal to: + + - If this strategy defected two turns ago, the portion of the time + (historically) that the opponent followed a defection with a cooperation. + - If this strategy cooperated two turns ago, the portion of the time + (historically) that the opponent followed a cooperation with a cooperation. + The opponent's first move is counted as a response to a cooperation. + + + Names: + + - Appold: [Axelrod1980b]_ + """ + + name = "Appold" + classifier = { + "memory_depth": float("inf"), + "stochastic": True, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def __init__(self) -> None: + super().__init__() + + # Probability of a cooperation after an x is: + # opp_c_after_x / total_num_of_x. + self.opp_c_after_x = {C: 0, D: 1} + # This is the total counted, so it doesn't include the most recent. + self.total_num_of_x = {C: 0, D: 1} + + self.first_opp_def = False + + def strategy(self, opponent: Player) -> Action: + turn = len(self.history) + 1 + + us_two_turns_ago = C if turn <= 2 else self.history[-2] + + # Update trackers + if turn > 1: + self.total_num_of_x[us_two_turns_ago] += 1 + if turn > 1 and opponent.history[-1] == C: + self.opp_c_after_x[us_two_turns_ago] += 1 + + if turn <= 4: + return C + + if opponent.history[-1] == D and not self.first_opp_def: + self.first_opp_def = True + return C + + # Calculate the probability that the opponent cooperated last turn given + # what we know two turns ago. + prob_coop = self.opp_c_after_x[us_two_turns_ago] / self.total_num_of_x[ + us_two_turns_ago] + return random_choice(prob_coop) diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index ce8a85370..53e9994ba 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -1908,3 +1908,125 @@ def test_strategy(self): self.versus_test(custom_opponent, expected_actions=actions, attrs={ "distrust_points": 2}) # But no more + +class TestAppold(TestPlayer): + name = "Appold" + player = axelrod.Appold + expected_classifier = { + "memory_depth": float("inf"), + "stochastic": True, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def test_strategy(self): + # Should cooperate 100% of the time with the cooperator + actions = [(C, C)] * 100 + self.versus_test(axelrod.Cooperator(), expected_actions=actions) + + opponent = axelrod.Defector() + # Cooperate always the first 4 turns + actions = [(C, D)] * 4 + # Should cooperate because we forgive the first_opp_def after the fourth + # turn. + actions += [(C, D)] + # Own move two turns ago is C, so D. + actions += [(D, D)] + # Then defect most of the time, depending on the random number. We + # don't defect 100% of the time, because of the way that initialize + # opp_c_after_x. + actions += [(D, D), + (C, D), + (D, D), + (D, D), # C can never be two moves after a C. + (D, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, D), + (C, D), + (C, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, D), + (C, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, D), + (C, D), + (C, D), + (D, D), + (D, D)] + self.versus_test(opponent, expected_actions=actions, seed=1, + attrs={"first_opp_def": True}) + + # An opponent who defects for a long time, then tries cooperating + opponent_actions = [C] * 30 + [D] + [C] * 10 + MostlyCooperates = axelrod.MockPlayer(actions=opponent_actions) + # Cooperate always at first + actions = [(C, C)] * 30 + # The opponent defects once + actions += [(C, D)] + # But we forgive it. + actions += [(C, C)] * 10 + self.versus_test(MostlyCooperates, expected_actions=actions) + + opponent = axelrod.CyclerDC() + # First three opponent actions get counted as reactions to C. Fourth + # action will get counted on next turn. + actions = [(C, D), (C, C), (C, D), (C, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"opp_c_after_x": {C: 1, D: 1}, + "total_num_of_x": {C: 3, D: 1}}) + # Will cooperate 50% of the time + actions += [(C, D)] + self.versus_test(opponent, expected_actions=actions, + attrs={"opp_c_after_x": {C: 2, D: 1}, + "total_num_of_x": {C: 4, D: 1}, + "first_opp_def": False}, seed=100) + # Always cooperate, because we forgive the first defect + actions += [(C, C)] + self.versus_test(opponent, expected_actions=actions, + attrs={"first_opp_def": True}, seed=100) + + # Against a random opponent, will respond mostly randomly too. + actions = [(C, C), + (C, C), + (C, D), + (C, C), + (C, C), + (C, D), + (C, C), + (C, C), + (C, C), + (D, C), + (C, D), + (D, D), + (C, D), + (C, D), + (C, C), + (C, C), + (D, C), + (C, D), + (D, D), + (C, C), + (C, D), + (C, C), + (C, C), + (C, D), + (D, C), + (C, D), + (D, D), + (C, D), + (C, C), + (D, C)] + self.versus_test(axelrod.Random(0.5), expected_actions=actions, seed=7) diff --git a/docs/reference/overview_of_strategies.rst b/docs/reference/overview_of_strategies.rst index fc3b3f953..7f5d9c7ba 100644 --- a/docs/reference/overview_of_strategies.rst +++ b/docs/reference/overview_of_strategies.rst @@ -115,7 +115,7 @@ repository. "K85R_", "Robert B Falk and James M Langsted", "Not Implemented" "K86R_", "Bernard Grofman", "Not Implemented" "K87R_", "E E H Schurmann", "Not Implemented" - "K88R_", "Scott Appold", "Not Implemented" + "K88R_", "Scott Appold", ":class:`Appold `" "K89R_", "Gene Snodgrass", "Not Implemented" "K90R_", "John Maynard Smith", "Not Implemented" "K91R_", "Jonathan Pinkley", "Not Implemented" diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index 108656337..66133fbb2 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -47,7 +47,7 @@ strategies:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 86 + 87 Or, to find out how many strategies only use 1 turn worth of memory to make a decision::