diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index c0870dfdb..92c31470e 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -7,7 +7,9 @@ from .axelrod_first import ( Davis, RevisedDowning, Feld, Grofman, Nydegger, Joss, Shubik, Tullock, UnnamedStrategy, SteinAndRapoport, TidemanAndChieruzzi) -from .axelrod_second import Champion, Eatherley, Tester, Gladstein, Tranquilizer, MoreGrofman, Kluepfel +from .axelrod_second import ( + Champion, Eatherley, Tester, Gladstein, Tranquilizer, MoreGrofman, + Kluepfel, Borufsen) from .backstabber import BackStabber, DoubleCrosser from .better_and_better import BetterAndBetter from .bush_mosteller import BushMosteller @@ -105,6 +107,7 @@ AverageCopier, BetterAndBetter, BackStabber, + Borufsen, Bully, BushMosteller, Calculator, diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 90112262c..396af290a 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -566,3 +566,155 @@ def strategy(self, opponent: Player) -> Action: return one_move_ago else: return one_move_ago.flip() + +class Borufsen(Player): + """ + Strategy submitted to Axelrod's second tournament by Otto Borufsen + (K32R), and came in third in that tournament. + + This player keeps track of the the opponent's responses to own behavior: + + - `cd_count` counts: Opponent cooperates as response to player defecting. + - `cc_count` counts: Opponent cooperates as response to player cooperating. + + The player has a defect mode and a normal mode. In defect mode, the + player will always defect. In normal mode, the player obeys the following + ranked rules: + + 1. If in the last three turns, both the player/opponent defected, then + cooperate for a single turn. + 2. If in the last three turns, the player/opponent acted differently from + each other and they're alternating, then change next defect to + cooperate. (Doesn't block third rule.) + 3. Otherwise, do tit-for-tat. + + Start in normal mode, but every 25 turns starting with the 27th turn, + re-evaluate the mode. Enter defect mode if any of the following + conditions hold: + + - Detected random: Opponent cooperated 7-18 times since last mode + evaluation (or start) AND less than 70% of opponent cooperation was in + response to player's cooperation, i.e. + cc_count / (cc_count+cd_count) < 0.7 + - Detect defective: Opponent cooperated fewer than 3 times since last mode + evaluation. + + When switching to defect mode, defect immediately. The first two rules for + normal mode require that last three turns were in normal mode. When starting + normal mode from defect mode, defect on first move. + + Names: + + - Borufsen: [Axelrod1980b]_ + """ + + name = "Borufsen" + classifier = { + 'memory_depth': float('inf'), + 'stochastic': False, + 'makes_use_of': set(), + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def __init__(self): + super().__init__() + self.cd_counts, self.cc_counts = 0, 0 + self.mutual_defect_streak = 0 + self.echo_streak = 0 + self.flip_next_defect = False + self.mode = "Normal" + + def try_return(self, to_return): + """ + We put the logic here to check for the `flip_next_defect` bit here, + and proceed like normal otherwise. + """ + + if to_return == C: + return C + # Otherwise look for flip bit. + if self.flip_next_defect: + self.flip_next_defect = False + return C + return D + + def strategy(self, opponent: Player) -> Action: + turn = len(self.history) + 1 + + if turn == 1: + return C + + # Update the response history. + if turn >= 3: + if opponent.history[-1] == C: + if self.history[-2] == C: + self.cc_counts += 1 + else: + self.cd_counts += 1 + + # Check if it's time for a mode change. + if turn > 2 and turn % 25 == 2: + coming_from_defect = False + if self.mode == "Defect": + coming_from_defect = True + + self.mode = "Normal" + coops = self.cd_counts + self.cc_counts + + # Check for a defective strategy + if coops < 3: + self.mode = "Defect" + + # Check for a random strategy + if (coops >= 8 and coops <= 17) and self.cc_counts/coops < 0.7: + self.mode = "Defect" + + self.cd_counts, self.cc_counts = 0, 0 + + # If defect mode, clear flags + if self.mode == "Defect": + self.mutual_defect_streak = 0 + self.echo_streak = 0 + self.flip_next_defect = False + + # Check this special case + if self.mode == "Normal" and coming_from_defect: + return D + + # Proceed + if self.mode == "Defect": + return D + else: + assert self.mode == "Normal" + + # Look for mutual defects + if self.history[-1] == D and opponent.history[-1] == D: + self.mutual_defect_streak += 1 + else: + self.mutual_defect_streak = 0 + if self.mutual_defect_streak >= 3: + self.mutual_defect_streak = 0 + self.echo_streak = 0 # Reset both streaks. + return self.try_return(C) + + # Look for echoes + # Fortran code defaults two turns back to C if only second turn + my_two_back, opp_two_back = C, C + if turn >= 3: + my_two_back = self.history[-2] + opp_two_back = opponent.history[-2] + if self.history[-1] != opponent.history[-1] and \ + self.history[-1] == opp_two_back and opponent.history[-1] == my_two_back: + self.echo_streak += 1 + else: + self.echo_streak = 0 + if self.echo_streak >= 3: + self.mutual_defect_streak = 0 # Reset both streaks. + self.echo_streak = 0 + self.flip_next_defect = True + + # Tit-for-tat + return self.try_return(opponent.history[-1]) diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index b32b5fbb5..10dc0287e 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -451,4 +451,42 @@ def test_strategy(self): (D, C),(D, C),(D, D),(D, D),(D, C),(D, C),(D, C),(D, C),(D, D), (D, C),(D, C),(D, C),(D, C),(D, D)] self.versus_test(axelrod.Random(0.5), expected_actions=actions, seed=10) - + +class TestBorufsen(TestPlayer): + name = "Borufsen" + player = axelrod.Borufsen + expected_classifier = { + 'memory_depth': float('inf'), + 'stochastic': False, + 'makes_use_of': set(), + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def test_strategy(self): + actions = [(C, C)] * 100 # Cooperate forever + self.versus_test(axelrod.Cooperator(), expected_actions=actions) + + # Tries to cooperate every third time until detecting defective + actions = [(C, D), (D, D), (D, D), (D, D)] * 6 + \ + [(C, D), (D, D)] + [(D, D)] * 100 + self.versus_test(axelrod.Defector(), expected_actions=actions) + + # Alternates with additional coop, every sixth turn + # Won't be labeled as random, since 2/3 of opponent's C follow player's C + # `flip_next_defect` will get set on the sixth turn, which changes the seventh action + # Note that the first two turns of each period of six aren't + # marked as echoes, and the third isn't marked that way until the fourth turn. + actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D)] * 20 + self.versus_test(axelrod.Alternator(), expected_actions=actions) + + # Basically does tit-for-tat against Win-Shift, Lose-Stay D + # After 26 turns, will detect random since half of opponent's C follow Cs + # Coming out of it, there will be new pattern. Then random is detected again. + actions = [(C, D), (D, C), (C, C)] * 8 + \ + [(C, D), (D, C)] + [(D, C)] * 25 + \ + [(D, C)] + [(C, C), (C, D), (D, C)] * 8 + \ + [(D, C)] * 25 + self.versus_test(axelrod.WinShiftLoseStay(D), expected_actions=actions) diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 80e1dcc89..cb9cf8b5d 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -335,7 +335,7 @@ def test_strategy(self): self.versus_test(opponent=axelrod.Alternator(), expected_actions=actions, seed=0) - actions = [(C, C), (C, D), (C, C), (C, D), (D, C)] + actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test(opponent=axelrod.Alternator(), expected_actions=actions, seed=1) diff --git a/docs/reference/overview_of_strategies.rst b/docs/reference/overview_of_strategies.rst index 1c7eda8b2..78e2e1287 100644 --- a/docs/reference/overview_of_strategies.rst +++ b/docs/reference/overview_of_strategies.rst @@ -112,7 +112,7 @@ repository. "K39R_", "Tom Almy", "Not Implemented" "K40R_", "Robert Adams", "Not Implemented" "K41R_", "Herb Weiner", "Not Implemented" - "K42R_", "Otto Borufsen", "Not Implemented" + "K42R_", "Otto Borufsen", ":class:`Borufsen `" "K43R_", "R D Anderson", "Not Implemented" "K44R_", "William Adams", "Not Implemented" "K45R_", "Michael F McGurrin", "Not Implemented"