From 87344ee5e70a596d46da18d3b76d5a03c6f37e3a Mon Sep 17 00:00:00 2001 From: "T.J. Gaffney" Date: Sun, 28 Jan 2018 11:10:57 -0500 Subject: [PATCH 1/2] Implemented Yamachi, k64r from Axelrod's Second. --- axelrod/strategies/_strategies.py | 4 +- axelrod/strategies/axelrod_second.py | 93 +++++++++++++++ .../tests/strategies/test_axelrod_second.py | 106 ++++++++++++++++++ docs/reference/overview_of_strategies.rst | 2 +- 4 files changed, 203 insertions(+), 2 deletions(-) diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 79edb6e16..b3d7f5b62 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -10,7 +10,8 @@ from .axelrod_second import ( Champion, Eatherley, Tester, Gladstein, Tranquilizer, MoreGrofman, Kluepfel, Borufsen, Cave, WmAdams, GraaskampKatzen, Weiner, Harrington, - MoreTidemanAndChieruzzi, Getzler, Leyvraz, White, Black, RichardHufford) + MoreTidemanAndChieruzzi, Getzler, Leyvraz, White, Black, RichardHufford, + Yamachi) from .backstabber import BackStabber, DoubleCrosser from .better_and_better import BetterAndBetter from .bush_mosteller import BushMosteller @@ -288,6 +289,7 @@ WorseAndWorse, WorseAndWorse2, WorseAndWorse3, + Yamachi, ZDExtortion, ZDExtort2, ZDExtort3, diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 75fbde15e..b26100884 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -1709,3 +1709,96 @@ def strategy(self, opponent: Player) -> Action: elif proportion_agree >= 0.625 and last_four_num >= 2: return opponent.history[-1] return D + + +class Yamachi(Player): + """ + Strategy submitted to Axelrod's second tournament by Brian Yamachi (K64R) + and came in seventeenth in that tournament. + + The strategy keeps track of play history through a variable called + `count_them_us_them`, which is a dict indexed by (X, Y, Z), where X is an + opponent's move and Y and Z are the following moves by this player and the + opponent, respectively. Each turn, we look at our opponent's move two + turns ago, call X, and our move last turn, call Y. If (X, Y, C) has + occurred more often (or as often) as (X, Y, D), then Cooperate. Otherwise + Defect. [Note that this reflects likelihood of Cooperations or Defections + in opponent's previous move; we don't update `count_them_us_them` with + previous move until next turn.] + + Starting with the 41st turn, there's a possibility to override this + behavior. If `portion_defect` is between 45% and 55% (exclusive), then + Defect, where `portion_defect` equals number of opponent defects plus 0.5 + divided by the turn number (indexed by 1). When overriding this way, still + record `count_them_us_them` as though the strategy didn't override. + + Names: + + - Yamachi: [Axelrod1980b]_ + """ + + name = 'Yamachi' + 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) -> None: + super().__init__() + self.count_them_us_them = {(C, C, C): 0, + (C, C, D): 0, + (C, D, C): 0, + (C, D, D): 0, + (D, C, C): 0, + (D, C, D): 0, + (D, D, C): 0, + (D, D, D): 0} + self.mod_history = list() + + def try_return(self, to_return, opp_def): + """ + Return `to_return`, unless the turn is greater than 40 AND + `portion_defect` is between 45% and 55%. + + In this case, still record the history as `to_return` so that the + modified behavior doesn't affect the calculation of `count_us_them_us`. + """ + turn = len(self.history) + 1 + + self.mod_history.append(to_return) + + # In later turns, check if the opponent is close to 50/50 + # If so, then override + if turn > 40: + portion_defect = (opp_def + 0.5) / turn + if 0.45 < portion_defect and portion_defect < 0.55: + return D + + return to_return + + def strategy(self, opponent: Player) -> Action: + turn = len(self.history) + 1 + if turn == 1: + return self.try_return(C, 0) + + us_last = self.mod_history[-1] + them_two_ago, us_two_ago, them_three_ago = C, C, C + if turn >= 3: + them_two_ago = opponent.history[-2] + us_two_ago = self.mod_history[-2] + if turn >= 4: + them_three_ago = opponent.history[-3] + + # Update history + if turn >= 3: + self.count_them_us_them[(them_three_ago, us_two_ago, them_two_ago)] += 1 + + if self.count_them_us_them[(them_two_ago, us_last, C)] >= \ + self.count_them_us_them[(them_two_ago, us_last, D)]: + return self.try_return(C, opponent.defections) + return self.try_return(D, opponent.defections) diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index 52e99dee4..1a89e47ed 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -1055,3 +1055,109 @@ def test_strategy(self): actions += [(D, D)] # Three of last four are disagreements. actions += [(D, D)] # Now there are 5/9 disagreements, so Defect. self.versus_test(axelrod.WinShiftLoseStay(), expected_actions=actions, attrs={"num_agreements": 5}) + + +class TestYamachi(TestPlayer): + name = 'Yamachi' + player = axelrod.Yamachi + 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 + self.versus_test(axelrod.Cooperator(), expected_actions=actions) + + actions = [(C, D)] * 2 # Also Cooperate in first two moves (until we update `count_them_us_them`.) + actions += [(C, D)] # them_three_ago defaults to C, so that (C, C, *) gets updated, then (D, C, *) get checked. + # It's actually impossible to Defect on the third move. + actions += [(D, D)] # (D, C, *) gets updated, then checked. + actions += [(C, D)] # (D, C, *) gets updated, but (D, D, *) checked. + actions += [(D, D)] * 30 # (D, D, *) gets updated and checked from here on. + self.versus_test(axelrod.Defector(), expected_actions=actions) + + actions = [(C, C), (C, D)] + actions += [(C, C)] # Increment (C, C, C). Check (C, C, *). Cooperate. + # Reminder that first C is default value and last C is opponent's first move. + actions += [(C, D)] # Increment (C, C, D). Check (D, C, *) = 0. Cooperate. + actions += [(C, C)] # Increment (D, C, C). Check (C, C, *) = 0. Cooperate. + # There is one Defection and one Cooperation in this scenario, + # but the Cooperation was due to a default value only. We can see where this is going. + actions += [(C, D)] # Increment (C, C, D). Check (D, C, *) = 1. Cooperate. + actions += [(D, C)] # Increment (D, C, C). Check (C, C, *) = -1. Defect. + actions += [(C, D)] # Increment (C, C, D). Check (D, D, *) = 0 (New). Cooperate. + actions += [(D, C)] # Increment (D, D, C). Check (C, C, *) < 0. Defect. + actions += [(C, D)] # Increment (C, C, D). Check (D, D, *) > 0. Cooperate. + actions += [(D, C), (C, D)] * 15 # This pattern continues for a while. + actions += [(D, C), (D, D)] * 30 # Defect from turn 41 on, since near 50% Defections. + self.versus_test(axelrod.Alternator(), expected_actions=actions) + + # Rip-off is the most interesting interaction. + actions = [(C, D), + (C, C), + (C, D), + (D, C), + (C, C), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C)] + my_dict = {(C, C, C): 1, + (C, C, D): 18, + (C, D, C): 1, + (C, D, D): 0, + (D, C, C): 1, + (D, C, D): 0, + (D, D, C): 17, + (D, D, D): 0} + RipoffPlayer = axelrod.Ripoff() + self.versus_test(RipoffPlayer, expected_actions=actions, attrs={"count_them_us_them": my_dict}) + self.assertEqual(RipoffPlayer.defections, 19) # Next turn, `portion_defect` = 0.4756 + + # The pattern (C, D), (D, C) will continue indefintely unless overriden. + actions += [(D, D)] # Next turn, `portion_defect` = 0.4881 + actions += [(D, D)] # Next turn, `portion_defect` = 0.5 + actions += [(D, D)] # Next turn, `portion_defect` = 0.5114 + actions += [(D, D)] # Next turn, `portion_defect` = 0.5222 + actions += [(D, D)] # Next turn, `portion_defect` = 0.5326 + actions += [(D, D)] # Next turn, `portion_defect` = 0.5426 + actions += [(D, D)] # Next turn, `portion_defect` = 0.5521 + actions += [(D, D), (C, D), (D, C), (C, D)] # Takes a turn to fall back into the cycle. + self.versus_test(axelrod.Ripoff(), expected_actions=actions) diff --git a/docs/reference/overview_of_strategies.rst b/docs/reference/overview_of_strategies.rst index d7fe86d76..ad7100bfd 100644 --- a/docs/reference/overview_of_strategies.rst +++ b/docs/reference/overview_of_strategies.rst @@ -132,7 +132,7 @@ repository. "K61R_", "Danny C Champion", ":class:`Champion `" "K62R_", "Howard R Hollander", "Not Implemented" "K63R_", "George Duisman", "Not Implemented" - "K64R_", "Brian Yamachi", "Not Implemented" + "K64R_", "Brian Yamachi", ":class:`Yamachi `" "K65R_", "Mark F Batell", "Not Implemented" "K66R_", "Ray Mikkelson", "Not Implemented" "K67R_", "Craig Feathers", ":class:`Tranquilizer `" From c16f1964b6387507bc500865063655333e2cd839 Mon Sep 17 00:00:00 2001 From: "T.J. Gaffney" Date: Sun, 28 Jan 2018 15:46:58 -0500 Subject: [PATCH 2/2] Tried to make mypy happy. --- axelrod/strategies/axelrod_second.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index b26100884..ac4064ca2 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -4,6 +4,7 @@ import random import numpy as np +from typing import List from axelrod.action import Action from axelrod.player import Player @@ -1758,7 +1759,7 @@ def __init__(self) -> None: (D, C, D): 0, (D, D, C): 0, (D, D, D): 0} - self.mod_history = list() + self.mod_history = list() # type: List[Action] def try_return(self, to_return, opp_def): """