diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 471c7a227..b32399caf 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -7,7 +7,7 @@ from .axelrod_first import ( Davis, RevisedDowning, Feld, Grofman, Nydegger, Joss, Shubik, Tullock, UnnamedStrategy, SteinAndRapoport, TidemanAndChieruzzi) -from .axelrod_second import Champion, Eatherley, Tester, Gladstein +from .axelrod_second import Champion, Eatherley, Tester, Gladstein, Tranquilizer from .backstabber import BackStabber, DoubleCrosser from .better_and_better import BetterAndBetter from .calculator import Calculator @@ -253,6 +253,7 @@ TidemanAndChieruzzi, TitForTat, TitFor2Tats, + Tranquilizer, TrickyCooperator, TrickyDefector, Tullock, diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 31a00da2c..37dd94a6f 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -8,6 +8,9 @@ from axelrod.player import Player from axelrod.random_ import random_choice +from axelrod.interaction_utils import compute_final_score + + C, D = Action.C, Action.D @@ -199,3 +202,211 @@ def strategy(self, opponent: Player) -> Action: else: # Play TFT return opponent.history[-1] + +class Tranquilizer(Player): + + """ + Submitted to Axelrod's second tournament by Craig Feathers + + Description given in Axelrod's "More Effective Choice in the + Prisoner's Dilemma" paper: The rule normally cooperates but + is ready to defect if the other player defects too often. + Thus the rule tends to cooperate for the first dozen or two moves + if the other player is cooperating, but then it throws in a + defection. If the other player continues to cooperate, then defections + become more frequent. But as long as Tranquilizer is maintaining an + average payoff of at least 2.25 points per move, it will never defect + twice in succession and it will not defect more than + one-quarter of the time. + + This implementation is based on the reverse engineering of the + Fortran strategy K67R from Axelrod's second tournament. + Reversed engineered by: Owen Campbell, Will Guo and Mansour Hakem. + + The strategy starts by cooperating and has 3 states. + + At the start of the strategy it updates its states: + + - It counts the number of consecutive defections by the opponent. + - If it was in state 2 it moves to state 0 and calculates the + following quantities two_turns_after_good_defection_ratio and + two_turns_after_good_defection_ratio_count. + + Formula for: + + two_turns_after_good_defection_ratio: + + self.two_turns_after_good_defection_ratio = ( + ((self.two_turns_after_good_defection_ratio + * self.two_turns_after_good_defection_ratio_count) + + (3 - (3 * self.dict[opponent.history[-1]])) + + (2 * self.dict[self.history[-1]]) + - ((self.dict[opponent.history[-1]] + * self.dict[self.history[-1]]))) + / (self.two_turns_after_good_defection_ratio_count + 1) + ) + + two_turns_after_good_defection_ratio_count = + two_turns_after_good_defection_ratio + 1 + + - If it was in state 1 it moves to state 2 and calculates the + following quantities one_turn_after_good_defection_ratio and + one_turn_after_good_defection_ratio_count. + + Formula for: + + one_turn_after_good_defection_ratio: + + self.one_turn_after_good_defection_ratio = ( + ((self.one_turn_after_good_defection_ratio + * self.one_turn_after_good_defection_ratio_count) + + (3 - (3 * self.dict[opponent.history[-1]])) + + (2 * self.dict[self.history[-1]]) + - (self.dict[opponent.history[-1]] + * self.dict[self.history[-1]])) + / (self.one_turn_after_good_defection_ratio_count + 1) + ) + + one_turn_after_good_defection_ratio_count: + + one_turn_after_good_defection_ratio_count = + one_turn_after_good_defection_ratio + 1 + + If after this it is in state 1 or 2 then it cooperates. + + If it is in state 0 it will potentially perform 1 of the 2 + following stochastic tests: + + 1. If average score per turn is greater than 2.25 then it calculates a + value of probability: + + probability = ( + (.95 - (((self.one_turn_after_good_defection_ratio) + + (self.two_turns_after_good_defection_ratio) - 5) / 15)) + + (1 / (((len(self.history))+1) ** 2)) + - (self.dict[opponent.history[-1]] / 4) + ) + + and will cooperate if a random sampled number is less than that value of + probability. If it does not cooperate then the strategy moves to state 1 + and defects. + + 2. If average score per turn is greater than 1.75 but less than 2.25 + then it calculates a value of probability: + + probability = ( + (.25 + ((opponent.cooperations + 1) / ((len(self.history)) + 1))) + - (self.opponent_consecutive_defections * .25) + + ((current_score[0] + - current_score[1]) / 100) + + (4 / ((len(self.history)) + 1)) + ) + + and will cooperate if a random sampled number is less than that value of + probability. If not, it defects. + + If none of the above holds the player simply plays tit for tat. + + Tranquilizer came in 27th place in Axelrod's second torunament. + + + Names: + + - Tranquilizer: [Axelrod1980]_ + """ + + name = 'Tranquilizer' + classifier = { + 'memory_depth': float('inf'), + 'stochastic': True, + 'makes_use_of': {"game"}, + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def __init__(self): + super().__init__() + self.num_turns_after_good_defection = 0 # equal to FD variable + self.opponent_consecutive_defections = 0 # equal to S variable + self.one_turn_after_good_defection_ratio= 5 # equal to AD variable + self.two_turns_after_good_defection_ratio= 0 # equal to NO variable + self.one_turn_after_good_defection_ratio_count = 1 # equal to AK variable + self.two_turns_after_good_defection_ratio_count = 1 # equal to NK variable + # All above variables correspond to those in original Fotran Code + self.dict = {C: 0, D: 1} + + + def update_state(self, opponent): + + """ + Calculates the ratio values for the one_turn_after_good_defection_ratio, + two_turns_after_good_defection_ratio and the probability values, + and sets the value of num_turns_after_good_defection. + """ + if opponent.history[-1] == D: + self.opponent_consecutive_defections += 1 + else: + self.opponent_consecutive_defections = 0 + + if self.num_turns_after_good_defection == 2: + self.num_turns_after_good_defection = 0 + self.two_turns_after_good_defection_ratio = ( + ((self.two_turns_after_good_defection_ratio + * self.two_turns_after_good_defection_ratio_count) + + (3 - (3 * self.dict[opponent.history[-1]])) + + (2 * self.dict[self.history[-1]]) + - ((self.dict[opponent.history[-1]] + * self.dict[self.history[-1]]))) + / (self.two_turns_after_good_defection_ratio_count + 1) + ) + self.two_turns_after_good_defection_ratio_count += 1 + elif self.num_turns_after_good_defection == 1: + self.num_turns_after_good_defection = 2 + self.one_turn_after_good_defection_ratio = ( + ((self.one_turn_after_good_defection_ratio + * self.one_turn_after_good_defection_ratio_count) + + (3 - (3 * self.dict[opponent.history[-1]])) + + (2 * self.dict[self.history[-1]]) + - (self.dict[opponent.history[-1]] + * self.dict[self.history[-1]])) + / (self.one_turn_after_good_defection_ratio_count + 1) + ) + self.one_turn_after_good_defection_ratio_count += 1 + + def strategy(self, opponent: Player) -> Action: + + if not self.history: + return C + + + self.update_state(opponent) + if self.num_turns_after_good_defection in [1, 2]: + return C + + current_score = compute_final_score(zip(self.history, opponent.history)) + + if (current_score[0] / ((len(self.history)) + 1)) >= 2.25: + probability = ( + (.95 - (((self.one_turn_after_good_defection_ratio) + + (self.two_turns_after_good_defection_ratio) - 5) / 15)) + + (1 / (((len(self.history))+1) ** 2)) + - (self.dict[opponent.history[-1]] / 4) + ) + if random.random() <= probability: + return C + self.num_turns_after_good_defection = 1 + return D + if (current_score[0] / ((len(self.history)) + 1)) >= 1.75: + probability = ( + (.25 + ((opponent.cooperations + 1) / ((len(self.history)) + 1))) + - (self.opponent_consecutive_defections * .25) + + ((current_score[0] + - current_score[1]) / 100) + + (4 / ((len(self.history)) + 1)) + ) + if random.random() <= probability: + return C + return D + return opponent.history[-1] diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index 3e065b1b9..302c2678e 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -5,6 +5,7 @@ import axelrod from .test_player import TestPlayer + C, D = axelrod.Action.C, axelrod.Action.D @@ -158,3 +159,174 @@ def test_strategy(self): actions = [(D, C), (C, C), (C, D), (C, D), (D, D)] self.versus_test(opponent, expected_actions=actions, attrs={'patsy': False}) + + +class TestTranquilizer(TestPlayer): + + name = "Tranquilizer" + player = axelrod.Tranquilizer + expected_classifier = { + 'memory_depth': float('inf'), + 'stochastic': True, + 'makes_use_of': {"game"}, + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + + # test for initalised variables + + def test_init(self): + + player = axelrod.Tranquilizer() + + self.assertEqual(player.num_turns_after_good_defection, 0) + self.assertEqual(player.opponent_consecutive_defections, 0) + self.assertEqual(player.one_turn_after_good_defection_ratio, 5) + self.assertEqual(player.two_turns_after_good_defection_ratio, 0) + self.assertEqual(player.one_turn_after_good_defection_ratio_count, 1) + self.assertEqual(player.two_turns_after_good_defection_ratio_count, 1) + + def test_strategy(self): + + opponent = axelrod.Bully() + actions = [(C, D), (D, D), (D, C), (C, C), (C, D), (D, D), (D, C), (C, C)] + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, + attrs=expected_attrs) + + # Tests whether TitForTat is played given score is below 1.75 + + opponent = axelrod.Defector() + actions = [(C, D)] + [(D, D)] * 20 + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, + attrs=expected_attrs) + + opponent = axelrod.MockPlayer([C] * 2 + [D] * 8 + [C] * 4 ) + actions = [(C, C), (C, C)] + [(C, D)] + [(D, D)] * 7 + [(D, C)] + [(C, C)] * 3 + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, + attrs=expected_attrs) + + # If score is between 1.75 and 2.25, may cooperate or defect + + opponent = axelrod.MockPlayer(actions=[D] * 3 + [C] * 4 + [D] * 2) + actions = [(C, D)] + [(D, D)] * 2 + [(D, C)] + [(C, C)] * 3 + [(C, D)] + actions += ([(C, D)]) # <-- Random + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, seed=0, + attrs=expected_attrs) + + opponent = axelrod.MockPlayer(actions=[D] * 3 + [C] * 4 + [D] * 2) + actions = [(C, D)] + [(D, D)] * 2 + [(D, C)] + [(C, C)] * 3 + [(C, D)] + actions += ([(D, D)]) # <-- Random + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, seed=17, + attrs=expected_attrs) + + """If score is greater than 2.25 either cooperate or defect, + if turn number <= 5; cooperate""" + + opponent = axelrod.MockPlayer(actions=[C] * 5) + actions = [(C, C)] * 5 + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, seed=1, + attrs=expected_attrs) + + opponent = axelrod.MockPlayer(actions=[C] * 5) + actions = [(C, C)] * 4 + [(D, C)] + expected_attrs={"num_turns_after_good_defection": 1, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, seed=89, + attrs=expected_attrs) + + """ Given score per turn is greater than 2.25, + Tranquilizer will never defect twice in a row""" + + opponent = axelrod.MockPlayer(actions = [C] * 6) + actions = [(C, C)] * 4 + [(D, C), (C, C)] + expected_attrs={"num_turns_after_good_defection": 2, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 2, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, seed=89, + attrs=expected_attrs) + + # Tests cooperation after update_state + + opponent = axelrod.MockPlayer(actions=[C] * 5) + actions = [(C, C)] * 4 + [(D, C)] + [(C, C)] + expected_attrs={"num_turns_after_good_defection": 2, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 2, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, seed=89, + attrs=expected_attrs) + + # Ensures FD1 values are calculated + + opponent = axelrod.MockPlayer(actions=[C] * 6) + actions = [(C, C)] * 4 + [(D, C), (C, C)] + expected_attrs={"num_turns_after_good_defection": 2, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 2, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, seed=89, + attrs=expected_attrs) + + # Ensures FD2 values are calculated + + opponent = axelrod.MockPlayer(actions=[C] * 6) + actions = [(C, C)] * 4 + [(D, C)] + [(C, C)] * 2 + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 1.5, + "one_turn_after_good_defection_ratio_count": 2, + "two_turns_after_good_defection_ratio_count": 2} + self.versus_test(opponent, expected_actions=actions, seed=89, + attrs=expected_attrs) + + # Ensures scores are being counted + + opponent = axelrod.Defector() + actions = [(C, D)] + [(D, D)] * 19 + expected_attrs={"num_turns_after_good_defection": 0, + "one_turn_after_good_defection_ratio": 5, + "two_turns_after_good_defection_ratio": 0, + "one_turn_after_good_defection_ratio_count": 1, + "two_turns_after_good_defection_ratio_count": 1} + self.versus_test(opponent, expected_actions=actions, + attrs=expected_attrs) diff --git a/docs/reference/overview_of_strategies.rst b/docs/reference/overview_of_strategies.rst index 5aece3062..84b4241bb 100644 --- a/docs/reference/overview_of_strategies.rst +++ b/docs/reference/overview_of_strategies.rst @@ -137,7 +137,7 @@ repository. "K64R_", "Brian Yamachi", "Not Implemented" "K65R_", "Mark F Batell", "Not Implemented" "K66R_", "Ray Mikkelson", "Not Implemented" - "K67R_", "Craig Feathers", "Not Implemented" + "K67R_", "Craig Feathers", ":class:`Tranquilizer `" "K68R_", "Fransois Leyvraz", "Not Implemented" "K69R_", "Johann Joss", "Not Implemented" "K70R_", "Robert Pebly", "Not Implemented" diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index c6203e89c..d769593a8 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) - 72 + 73 Or, to find out how many strategies only use 1 turn worth of memory to make a decision:: diff --git a/training_data.csv b/training_data.csv new file mode 100644 index 000000000..e69de29bb