From 4423875e44a0a36c9252cae13ec75eb456a6f406 Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Fri, 5 Jul 2024 18:48:33 -0400 Subject: [PATCH 01/13] add freqAnalyzer strat --- axelrod/strategies/_strategies.py | 6 + axelrod/strategies/frequency_analyzer.py | 112 ++++++++++++++++++ axelrod/tests/strategies/test_freqanalyzer.py | 93 +++++++++++++++ run_mypy.py | 1 + 4 files changed, 212 insertions(+) create mode 100644 axelrod/strategies/frequency_analyzer.py create mode 100644 axelrod/tests/strategies/test_freqanalyzer.py diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index b1f147993..b0e0ab34b 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -238,12 +238,15 @@ ) from .shortmem import ShortMem from .stalker import Stalker +from .frequency_analyzer import FreqAnalyzer from .titfortat import ( AdaptiveTitForTat, Alexei, AntiTitForTat, Bully, BurnBothEnds, + SofteningTitForTat, + HardeningTitForTat, ContriteTitForTat, DynamicTwoTitsForTat, EugineNier, @@ -366,6 +369,7 @@ ForgivingTitForTat, Fortress3, Fortress4, + FreqAnalyzer, GTFT, GeneralSoftGrudger, GoByMajority, @@ -392,6 +396,8 @@ Hopeless, Inverse, InversePunisher, + SofteningTitForTat, + HardeningTitForTat, KnowledgeableWorseAndWorse, LevelPunisher, LimitedRetaliate, diff --git a/axelrod/strategies/frequency_analyzer.py b/axelrod/strategies/frequency_analyzer.py new file mode 100644 index 000000000..a362792fd --- /dev/null +++ b/axelrod/strategies/frequency_analyzer.py @@ -0,0 +1,112 @@ +from axelrod.action import Action, actions_to_str +from axelrod.player import Player +from axelrod.strategy_transformers import ( + FinalTransformer, + TrackHistoryTransformer, +) + +C, D = Action.C, Action.D + +class FreqAnalyzer(Player): + """ + A player starts by playing TitForTat for the first 30 turns (dataset generation phase). + + Take the matrix of last 2 moves by both Player and Opponent. + + While in dataset generation phase, construct a dictionary d, where keys are each 4 move sequence + and the corresponding value for each key is a list of the subsequent Opponent move. The 4 move sequence + starts with the Opponent move. + + For example, if a game at turn 5 looks like this: + + Opp: C, C, D, C, D + Player: C, C, C, D, C + + d should look like this: + + { [CCCC]: [D], + [CCDC]: [C], + [DCCD]: [D] } + + During dataset generation phase, Player will play TitForTat. After end of dataset generation phase, + Player will switch strategies. Upon encountering a particular 4-move sequence in the game, Player will look up history + of subsequent Opponent move. If ratio of defections to total moves exceeds p, Player will defect. Otherwise, + Player will cooperate. + + Could fall under "Hunter" class of strategies. + More likely falls under LookerUp class of strategies. + + Names: + + - FreqAnalyzer (FREQ): Original by Ian Miller + """ + + # These are various properties for the strategy + name = "FreqAnalyzer" + classifier = { + "memory_depth": float("inf"), + "stochastic": False, + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + def __init__(self) -> None: + """ + Parameters + ---------- + p, float + The probability to cooperate + """ + super().__init__() + self.minimum_cooperation_ratio = 0.5 + self.frequency_table = dict() + self.last_sequence = '' + self.current_sequence = '' + + def strategy(self, opponent: Player) -> Action: + """This is the actual strategy""" + if len(self.history) > 5: + self.last_sequence = str(opponent.history[-3]) + str(self.history[-3]) + str(opponent.history[-2]) + str(self.history[-2]) + self.current_sequence = str(opponent.history[-2]) + str(self.history[-2]) + str(opponent.history[-1]) + str(self.history[-1]) + + self.update_table(opponent) + + if len(self.history) < 30: + # Play TitForTat + # First move + if not self.history: + return C + # React to the opponent's last move + if opponent.history[-1] == D: + return D + return C + else: + try: + results = self.frequency_table[self.current_sequence] + cooperates = results.count('C') + if (cooperates / len(self.history)) > self.minimum_cooperation_ratio: + return C + return D + except: + if not self.history: + return C + # React to the opponent's last move + if opponent.history[-1] == D: + return D + return C + + def update_table(self, opponent: Player): + print(self.frequency_table) + print("___________________") + print("current sequence is {}", self.last_sequence) + if self.last_sequence in self.frequency_table.keys(): + print("seen this key before") + print("freq table keys = {}", self.frequency_table.keys()) + results = self.frequency_table[self.last_sequence] + results.append(opponent.history[-1]) + self.frequency_table[self.last_sequence] = results + else: + print("not seen this key ever") + self.frequency_table[self.last_sequence] = [opponent.history[-1]] + \ No newline at end of file diff --git a/axelrod/tests/strategies/test_freqanalyzer.py b/axelrod/tests/strategies/test_freqanalyzer.py new file mode 100644 index 000000000..224d66ef3 --- /dev/null +++ b/axelrod/tests/strategies/test_freqanalyzer.py @@ -0,0 +1,93 @@ +"""Tests for the Stalker strategy.""" + +import axelrod as axl + +from .test_player import TestPlayer + +C, D = axl.Action.C, axl.Action.D + + +class TestStalker(TestPlayer): + + name = "Stalker: (D,)" + player = axl.Stalker + expected_classifier = { + "memory_depth": float("inf"), + "stochastic": True, + "makes_use_of": {"game", "length"}, + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def test_strategy(self): + actions = [(C, C)] * 3 + [(D, C)] + self.versus_test(opponent=axl.Cooperator(), expected_actions=actions) + + # wish_score < current_average_score < very_good_score + actions = [(C, C)] * 7 + [(C, D), (C, D), (C, C), (C, C), (D, C)] + self.versus_test( + opponent=axl.MockPlayer(actions=[C] * 7 + [D] * 2), + expected_actions=actions, + ) + + actions = [(C, C)] * 7 + [(C, D), (C, C), (D, C)] + self.versus_test( + opponent=axl.MockPlayer(actions=[C] * 7 + [D]), + expected_actions=actions, + ) + + # current_average_score > 2 + actions = [(C, C)] * 9 + [(D, C)] + self.versus_test(axl.Cooperator(), expected_actions=actions) + + # 1 < current_average_score < 2 + actions = [(C, C)] * 7 + [(C, D)] * 4 + [(D, D)] + self.versus_test( + opponent=axl.MockPlayer(actions=[C] * 7 + [D] * 5), + expected_actions=actions, + ) + + def test_strategy2(self): + # current_average_score < 1 + actions = ( + [(C, D)] + + [(D, D)] * 2 + + [(C, D)] * 3 + + [(D, D), (C, D), (D, D), (C, D), (D, D), (C, D), (D, D)] + ) + self.versus_test(axl.Defector(), expected_actions=actions, seed=3222) + + def test_strategy3(self): + actions = [(C, D)] * 3 + [ + (D, D), + (C, D), + (D, D), + (C, D), + (C, D), + (D, D), + (C, D), + (C, D), + (C, D), + (D, D), + ] + self.versus_test(axl.Defector(), expected_actions=actions, seed=649) + + def test_strategy4(self): + # defect in last round + actions = [(C, C)] * 199 + [(D, C)] + self.versus_test( + axl.Cooperator(), + expected_actions=actions, + match_attributes={"length": 200}, + ) + + # Force a defection before the end of the actual match which ensures + # that current_average_score > very_good_score + actions = [(C, C)] * 3 + [(D, C)] * 3 + self.versus_test( + opponent=axl.Cooperator(), + expected_actions=actions, + match_attributes={"length": 4}, + ) diff --git a/run_mypy.py b/run_mypy.py index 94e3d669e..6e3ab6775 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -31,6 +31,7 @@ "axelrod/strategies/darwin.py", "axelrod/strategies/defector.py", "axelrod/strategies/forgiver.py", + "axelrod/strategies/frequency_analyzer.py", "axelrod/strategies/gradualkiller.py", "axelrod/strategies/grudger.py", "axelrod/strategies/grumpy.py", From 5ca735cc08245d876ad37b8131a9e36a9de27367 Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Fri, 5 Jul 2024 18:53:43 -0400 Subject: [PATCH 02/13] cleanup + wip --- axelrod/strategies/_strategies.py | 4 - axelrod/tests/strategies/test_freqanalyzer.py | 81 ++----------------- 2 files changed, 6 insertions(+), 79 deletions(-) diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index b0e0ab34b..14632e05a 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -245,8 +245,6 @@ AntiTitForTat, Bully, BurnBothEnds, - SofteningTitForTat, - HardeningTitForTat, ContriteTitForTat, DynamicTwoTitsForTat, EugineNier, @@ -396,8 +394,6 @@ Hopeless, Inverse, InversePunisher, - SofteningTitForTat, - HardeningTitForTat, KnowledgeableWorseAndWorse, LevelPunisher, LimitedRetaliate, diff --git a/axelrod/tests/strategies/test_freqanalyzer.py b/axelrod/tests/strategies/test_freqanalyzer.py index 224d66ef3..35906f062 100644 --- a/axelrod/tests/strategies/test_freqanalyzer.py +++ b/axelrod/tests/strategies/test_freqanalyzer.py @@ -1,4 +1,4 @@ -"""Tests for the Stalker strategy.""" +"""Tests for the FrequencyAnalyzer strategy.""" import axelrod as axl @@ -7,14 +7,13 @@ C, D = axl.Action.C, axl.Action.D -class TestStalker(TestPlayer): +class Test(TestPlayer): - name = "Stalker: (D,)" - player = axl.Stalker + name = "FrequencyAnalyzer" + player = axl.FreqAnalyzer expected_classifier = { "memory_depth": float("inf"), - "stochastic": True, - "makes_use_of": {"game", "length"}, + "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, @@ -22,72 +21,4 @@ class TestStalker(TestPlayer): } def test_strategy(self): - actions = [(C, C)] * 3 + [(D, C)] - self.versus_test(opponent=axl.Cooperator(), expected_actions=actions) - - # wish_score < current_average_score < very_good_score - actions = [(C, C)] * 7 + [(C, D), (C, D), (C, C), (C, C), (D, C)] - self.versus_test( - opponent=axl.MockPlayer(actions=[C] * 7 + [D] * 2), - expected_actions=actions, - ) - - actions = [(C, C)] * 7 + [(C, D), (C, C), (D, C)] - self.versus_test( - opponent=axl.MockPlayer(actions=[C] * 7 + [D]), - expected_actions=actions, - ) - - # current_average_score > 2 - actions = [(C, C)] * 9 + [(D, C)] - self.versus_test(axl.Cooperator(), expected_actions=actions) - - # 1 < current_average_score < 2 - actions = [(C, C)] * 7 + [(C, D)] * 4 + [(D, D)] - self.versus_test( - opponent=axl.MockPlayer(actions=[C] * 7 + [D] * 5), - expected_actions=actions, - ) - - def test_strategy2(self): - # current_average_score < 1 - actions = ( - [(C, D)] - + [(D, D)] * 2 - + [(C, D)] * 3 - + [(D, D), (C, D), (D, D), (C, D), (D, D), (C, D), (D, D)] - ) - self.versus_test(axl.Defector(), expected_actions=actions, seed=3222) - - def test_strategy3(self): - actions = [(C, D)] * 3 + [ - (D, D), - (C, D), - (D, D), - (C, D), - (C, D), - (D, D), - (C, D), - (C, D), - (C, D), - (D, D), - ] - self.versus_test(axl.Defector(), expected_actions=actions, seed=649) - - def test_strategy4(self): - # defect in last round - actions = [(C, C)] * 199 + [(D, C)] - self.versus_test( - axl.Cooperator(), - expected_actions=actions, - match_attributes={"length": 200}, - ) - - # Force a defection before the end of the actual match which ensures - # that current_average_score > very_good_score - actions = [(C, C)] * 3 + [(D, C)] * 3 - self.versus_test( - opponent=axl.Cooperator(), - expected_actions=actions, - match_attributes={"length": 4}, - ) + pass \ No newline at end of file From 3b0ac2c8a86b4a71b108912a9dc9b171fb420059 Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Thu, 11 Jul 2024 10:23:12 -0400 Subject: [PATCH 03/13] remove spurious changes --- axelrod/strategies/titfortat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/axelrod/strategies/titfortat.py b/axelrod/strategies/titfortat.py index dbeba4b9e..fcf62e1df 100644 --- a/axelrod/strategies/titfortat.py +++ b/axelrod/strategies/titfortat.py @@ -951,3 +951,4 @@ def strategy(self, opponent): return self._random.random_choice(0.9) # Else TFT. Opponent played D, so play D in return. return D + \ No newline at end of file From d706204eb329c76601ff71b7798909fbe11f987f Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Tue, 7 Jan 2025 21:31:44 -0500 Subject: [PATCH 04/13] fixed freq analyzer test --- axelrod/strategies/_strategies.py | 4 +- axelrod/strategies/frequency_analyzer.py | 15 ++---- axelrod/strategies/titfortat.py | 3 +- .../strategies/test_frequency_analyzer.py | 54 +++++++++++++++++++ docs/reference/strategy_index.rst | 2 + 5 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 axelrod/tests/strategies/test_frequency_analyzer.py diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 14632e05a..284cca1ee 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -238,7 +238,7 @@ ) from .shortmem import ShortMem from .stalker import Stalker -from .frequency_analyzer import FreqAnalyzer +from .frequency_analyzer import FrequencyAnalyzer from .titfortat import ( AdaptiveTitForTat, Alexei, @@ -367,7 +367,7 @@ ForgivingTitForTat, Fortress3, Fortress4, - FreqAnalyzer, + FrequencyAnalyzer, GTFT, GeneralSoftGrudger, GoByMajority, diff --git a/axelrod/strategies/frequency_analyzer.py b/axelrod/strategies/frequency_analyzer.py index a362792fd..1003e9e88 100644 --- a/axelrod/strategies/frequency_analyzer.py +++ b/axelrod/strategies/frequency_analyzer.py @@ -7,7 +7,7 @@ C, D = Action.C, Action.D -class FreqAnalyzer(Player): +class FrequencyAnalyzer(Player): """ A player starts by playing TitForTat for the first 30 turns (dataset generation phase). @@ -38,11 +38,11 @@ class FreqAnalyzer(Player): Names: - - FreqAnalyzer (FREQ): Original by Ian Miller + - FrequencyAnalyzer (FREQ): Original by Ian Miller """ # These are various properties for the strategy - name = "FreqAnalyzer" + name = "FrequencyAnalyzer" classifier = { "memory_depth": float("inf"), "stochastic": False, @@ -59,7 +59,7 @@ def __init__(self) -> None: The probability to cooperate """ super().__init__() - self.minimum_cooperation_ratio = 0.5 + self.minimum_cooperation_ratio = 0.8 self.frequency_table = dict() self.last_sequence = '' self.current_sequence = '' @@ -69,7 +69,6 @@ def strategy(self, opponent: Player) -> Action: if len(self.history) > 5: self.last_sequence = str(opponent.history[-3]) + str(self.history[-3]) + str(opponent.history[-2]) + str(self.history[-2]) self.current_sequence = str(opponent.history[-2]) + str(self.history[-2]) + str(opponent.history[-1]) + str(self.history[-1]) - self.update_table(opponent) if len(self.history) < 30: @@ -97,16 +96,10 @@ def strategy(self, opponent: Player) -> Action: return C def update_table(self, opponent: Player): - print(self.frequency_table) - print("___________________") - print("current sequence is {}", self.last_sequence) if self.last_sequence in self.frequency_table.keys(): - print("seen this key before") - print("freq table keys = {}", self.frequency_table.keys()) results = self.frequency_table[self.last_sequence] results.append(opponent.history[-1]) self.frequency_table[self.last_sequence] = results else: - print("not seen this key ever") self.frequency_table[self.last_sequence] = [opponent.history[-1]] \ No newline at end of file diff --git a/axelrod/strategies/titfortat.py b/axelrod/strategies/titfortat.py index fcf62e1df..11e53dccf 100644 --- a/axelrod/strategies/titfortat.py +++ b/axelrod/strategies/titfortat.py @@ -950,5 +950,4 @@ def strategy(self, opponent): # Cooperate with 0.9 return self._random.random_choice(0.9) # Else TFT. Opponent played D, so play D in return. - return D - \ No newline at end of file + return D \ No newline at end of file diff --git a/axelrod/tests/strategies/test_frequency_analyzer.py b/axelrod/tests/strategies/test_frequency_analyzer.py new file mode 100644 index 000000000..a88dd0935 --- /dev/null +++ b/axelrod/tests/strategies/test_frequency_analyzer.py @@ -0,0 +1,54 @@ +"""Tests for the FrequencyAnalyzer strategy.""" + +import axelrod as axl + +from .test_player import TestPlayer + +C, D = axl.Action.C, axl.Action.D + + +class Test(TestPlayer): + + name = "FrequencyAnalyzer" + player = axl.FrequencyAnalyzer + expected_classifier = { + "memory_depth": float("inf"), + "stochastic": False, + "long_run_time": False, + "makes_use_of": set(), + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def test_strategy_early(self): + # Test games that end while still in dataset generation phase (<30 turns) + opponent_actions = [C, C, D, C, D] + expected = [(C, C), (C, C), (C, D), (D, C), (C, D)] + self.versus_test( + axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 + ) + + def test_strategy_defector(self): + # Test against all defections + opponent_actions = [D] * 30 + expected = [(C, D)] + [(D, D)] * 29 + self.versus_test( + axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 + ) + + def test_strategy_cooperator(self): + # Test games that end while still in dataset generation phase (<30 turns) + opponent_actions = [C] * 30 + expected = [(C, C)] * 30 + self.versus_test( + axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 + ) + + def test_strategy_random(self): + # Test of 50 turns against random strategy + opponent_actions = [C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, D, C, C, C, C, D, D, C, C, C, D, D, D, C, C, D, D, D, D] + expected = [(C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (D, C), (C, D), (D, C), (C, C), (C, C), (D, C), (D, D), (D, D), (D, C), (C, C), (D, C), (D, D), (D, D), (D, D), (D, C), (D, C), (C, D), (D, D), (D, D), (D, D)] + self.versus_test( + axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 + ) \ No newline at end of file diff --git a/docs/reference/strategy_index.rst b/docs/reference/strategy_index.rst index 6732be88c..baecbb29d 100644 --- a/docs/reference/strategy_index.rst +++ b/docs/reference/strategy_index.rst @@ -48,6 +48,8 @@ Here are the docstrings of all the strategies in the library. :members: .. automodule:: axelrod.strategies.forgiver :members: +.. automodule:: axelrod.strategies.frequencyanalyzer + :members: .. automodule:: axelrod.strategies.gambler :members: .. automodule:: axelrod.strategies.gobymajority From a4fa0a54bbaa7a9aa09c00f7d76bbe331e5f44cb Mon Sep 17 00:00:00 2001 From: Ian Miller <46457417+miller-ian@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:42:30 -0500 Subject: [PATCH 05/13] Delete axelrod/tests/strategies/test_freqanalyzer.py --- axelrod/tests/strategies/test_freqanalyzer.py | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 axelrod/tests/strategies/test_freqanalyzer.py diff --git a/axelrod/tests/strategies/test_freqanalyzer.py b/axelrod/tests/strategies/test_freqanalyzer.py deleted file mode 100644 index 35906f062..000000000 --- a/axelrod/tests/strategies/test_freqanalyzer.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Tests for the FrequencyAnalyzer strategy.""" - -import axelrod as axl - -from .test_player import TestPlayer - -C, D = axl.Action.C, axl.Action.D - - -class Test(TestPlayer): - - name = "FrequencyAnalyzer" - player = axl.FreqAnalyzer - expected_classifier = { - "memory_depth": float("inf"), - "stochastic": False, - "long_run_time": False, - "inspects_source": False, - "manipulates_source": False, - "manipulates_state": False, - } - - def test_strategy(self): - pass \ No newline at end of file From b8ced55bc7fd76d414cd81564337853584c0a3e2 Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Tue, 7 Jan 2025 23:07:10 -0500 Subject: [PATCH 06/13] bump num strats --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index f7fe5e0b0..b8a865f4c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,7 +53,7 @@ Count the number of available players:: >>> import axelrod as axl >>> len(axl.strategies) - 240 + 241 Create matches between two players:: From 563a6bfb20ed5e3068cddee7f2c311ad340534fb Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Wed, 8 Jan 2025 00:19:28 -0500 Subject: [PATCH 07/13] remove unnecessary lines --- axelrod/strategies/frequency_analyzer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/axelrod/strategies/frequency_analyzer.py b/axelrod/strategies/frequency_analyzer.py index 1003e9e88..57b77b2f1 100644 --- a/axelrod/strategies/frequency_analyzer.py +++ b/axelrod/strategies/frequency_analyzer.py @@ -59,7 +59,7 @@ def __init__(self) -> None: The probability to cooperate """ super().__init__() - self.minimum_cooperation_ratio = 0.8 + self.minimum_cooperation_ratio = 0.15 self.frequency_table = dict() self.last_sequence = '' self.current_sequence = '' @@ -88,8 +88,6 @@ def strategy(self, opponent: Player) -> Action: return C return D except: - if not self.history: - return C # React to the opponent's last move if opponent.history[-1] == D: return D From 96eef3b25beb837a1b5533f07f6cc5b6afc57d87 Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Wed, 8 Jan 2025 00:58:08 -0500 Subject: [PATCH 08/13] fix type error --- axelrod/strategies/frequency_analyzer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/axelrod/strategies/frequency_analyzer.py b/axelrod/strategies/frequency_analyzer.py index 57b77b2f1..845311ded 100644 --- a/axelrod/strategies/frequency_analyzer.py +++ b/axelrod/strategies/frequency_analyzer.py @@ -59,7 +59,7 @@ def __init__(self) -> None: The probability to cooperate """ super().__init__() - self.minimum_cooperation_ratio = 0.15 + self.minimum_cooperation_ratio = 0.25 self.frequency_table = dict() self.last_sequence = '' self.current_sequence = '' @@ -83,7 +83,7 @@ def strategy(self, opponent: Player) -> Action: else: try: results = self.frequency_table[self.current_sequence] - cooperates = results.count('C') + cooperates = results.count(C) if (cooperates / len(self.history)) > self.minimum_cooperation_ratio: return C return D From 40e867e41d4396bd6ae153c2ebdda6d43aed54c4 Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Wed, 8 Jan 2025 11:14:05 -0500 Subject: [PATCH 09/13] fix test --- .../strategies/test_frequency_analyzer.py | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/strategies/test_frequency_analyzer.py b/axelrod/tests/strategies/test_frequency_analyzer.py index a88dd0935..e58cf9058 100644 --- a/axelrod/tests/strategies/test_frequency_analyzer.py +++ b/axelrod/tests/strategies/test_frequency_analyzer.py @@ -47,8 +47,57 @@ def test_strategy_cooperator(self): def test_strategy_random(self): # Test of 50 turns against random strategy - opponent_actions = [C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, D, C, C, C, C, D, D, C, C, C, D, D, D, C, C, D, D, D, D] - expected = [(C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (C, C), (D, C), (C, D), (D, C), (C, C), (C, C), (D, C), (D, D), (D, D), (D, C), (C, C), (D, C), (D, D), (D, D), (D, D), (D, C), (D, C), (C, D), (D, D), (D, D), (D, D)] + opponent_actions = [C, D, D, D, D, D, D, C, D, C, D, C, D, C, D, D, C, D, C, D, D, C, D, D, D, D, D, C, C, D, D, C, C, C, D, D, C, D, C, C, C, D, D, C, C, C, D, C, D, D] + expected = [(C, C), + (C, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, D), + (D, C), + (C, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, C), + (C, C), + (C, D), #rd 30 (end of dataset generation phase) + (D, D), + (D, C), + (D, C), #example of non TFT (by this point, FrequencyAnalyzer is generally distrustful of opponent) + (C, C), + (D, D), + (D, D), + (D, C), + (D, D), + (D, C), + (D, C), + (D, C), + (D, D), + (D, D), + (D, C), + (D, C), + (D, C), + (D, D), + (D, C), + (D, D), + (D, D)] self.versus_test( axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 ) \ No newline at end of file From 8384c367a589bf161a352799834f0369ae8d29c4 Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Wed, 8 Jan 2025 11:41:37 -0500 Subject: [PATCH 10/13] formatting --- axelrod/strategies/frequency_analyzer.py | 25 ++- axelrod/strategies/titfortat.py | 2 +- .../strategies/test_frequency_analyzer.py | 162 ++++++++++++------ 3 files changed, 128 insertions(+), 61 deletions(-) diff --git a/axelrod/strategies/frequency_analyzer.py b/axelrod/strategies/frequency_analyzer.py index 845311ded..32144f8e2 100644 --- a/axelrod/strategies/frequency_analyzer.py +++ b/axelrod/strategies/frequency_analyzer.py @@ -7,9 +7,10 @@ C, D = Action.C, Action.D + class FrequencyAnalyzer(Player): """ - A player starts by playing TitForTat for the first 30 turns (dataset generation phase). + A player starts by playing TitForTat for the first 30 turns (dataset generation phase). Take the matrix of last 2 moves by both Player and Opponent. @@ -30,7 +31,7 @@ class FrequencyAnalyzer(Player): During dataset generation phase, Player will play TitForTat. After end of dataset generation phase, Player will switch strategies. Upon encountering a particular 4-move sequence in the game, Player will look up history - of subsequent Opponent move. If ratio of defections to total moves exceeds p, Player will defect. Otherwise, + of subsequent Opponent move. If ratio of defections to total moves exceeds p, Player will defect. Otherwise, Player will cooperate. Could fall under "Hunter" class of strategies. @@ -51,6 +52,7 @@ class FrequencyAnalyzer(Player): "manipulates_source": False, "manipulates_state": False, } + def __init__(self) -> None: """ Parameters @@ -61,14 +63,24 @@ def __init__(self) -> None: super().__init__() self.minimum_cooperation_ratio = 0.25 self.frequency_table = dict() - self.last_sequence = '' - self.current_sequence = '' + self.last_sequence = "" + self.current_sequence = "" def strategy(self, opponent: Player) -> Action: """This is the actual strategy""" if len(self.history) > 5: - self.last_sequence = str(opponent.history[-3]) + str(self.history[-3]) + str(opponent.history[-2]) + str(self.history[-2]) - self.current_sequence = str(opponent.history[-2]) + str(self.history[-2]) + str(opponent.history[-1]) + str(self.history[-1]) + self.last_sequence = ( + str(opponent.history[-3]) + + str(self.history[-3]) + + str(opponent.history[-2]) + + str(self.history[-2]) + ) + self.current_sequence = ( + str(opponent.history[-2]) + + str(self.history[-2]) + + str(opponent.history[-1]) + + str(self.history[-1]) + ) self.update_table(opponent) if len(self.history) < 30: @@ -100,4 +112,3 @@ def update_table(self, opponent: Player): self.frequency_table[self.last_sequence] = results else: self.frequency_table[self.last_sequence] = [opponent.history[-1]] - \ No newline at end of file diff --git a/axelrod/strategies/titfortat.py b/axelrod/strategies/titfortat.py index 11e53dccf..dbeba4b9e 100644 --- a/axelrod/strategies/titfortat.py +++ b/axelrod/strategies/titfortat.py @@ -950,4 +950,4 @@ def strategy(self, opponent): # Cooperate with 0.9 return self._random.random_choice(0.9) # Else TFT. Opponent played D, so play D in return. - return D \ No newline at end of file + return D diff --git a/axelrod/tests/strategies/test_frequency_analyzer.py b/axelrod/tests/strategies/test_frequency_analyzer.py index e58cf9058..51e04fb33 100644 --- a/axelrod/tests/strategies/test_frequency_analyzer.py +++ b/axelrod/tests/strategies/test_frequency_analyzer.py @@ -44,60 +44,116 @@ def test_strategy_cooperator(self): self.versus_test( axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 ) - + def test_strategy_random(self): # Test of 50 turns against random strategy - opponent_actions = [C, D, D, D, D, D, D, C, D, C, D, C, D, C, D, D, C, D, C, D, D, C, D, D, D, D, D, C, C, D, D, C, C, C, D, D, C, D, C, C, C, D, D, C, C, C, D, C, D, D] - expected = [(C, C), - (C, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, D), - (D, C), - (C, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, C), - (C, C), - (C, D), #rd 30 (end of dataset generation phase) - (D, D), - (D, C), - (D, C), #example of non TFT (by this point, FrequencyAnalyzer is generally distrustful of opponent) - (C, C), - (D, D), - (D, D), - (D, C), - (D, D), - (D, C), - (D, C), - (D, C), - (D, D), - (D, D), - (D, C), - (D, C), - (D, C), - (D, D), - (D, C), - (D, D), - (D, D)] + opponent_actions = [ + C, + D, + D, + D, + D, + D, + D, + C, + D, + C, + D, + C, + D, + C, + D, + D, + C, + D, + C, + D, + D, + C, + D, + D, + D, + D, + D, + C, + C, + D, + D, + C, + C, + C, + D, + D, + C, + D, + C, + C, + C, + D, + D, + C, + C, + C, + D, + C, + D, + D, + ] + expected = [ + (C, C), + (C, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, D), + (D, C), + (C, D), + (D, C), + (C, D), + (D, D), + (D, C), + (C, D), + (D, D), + (D, D), + (D, D), + (D, D), + (D, C), + (C, C), + (C, D), # rd 30 (end of dataset generation phase) + (D, D), + (D, C), + ( + D, + C, + ), # example of non TFT (by this point, FrequencyAnalyzer is generally distrustful of opponent) + (C, C), + (D, D), + (D, D), + (D, C), + (D, D), + (D, C), + (D, C), + (D, C), + (D, D), + (D, D), + (D, C), + (D, C), + (D, C), + (D, D), + (D, C), + (D, D), + (D, D), + ] self.versus_test( axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 - ) \ No newline at end of file + ) From c2e38a3578088458fea29914225beb92e8b4306b Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Wed, 8 Jan 2025 12:03:46 -0500 Subject: [PATCH 11/13] python black with correct options --- axelrod/strategies/frequency_analyzer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/axelrod/strategies/frequency_analyzer.py b/axelrod/strategies/frequency_analyzer.py index 32144f8e2..be74b09e3 100644 --- a/axelrod/strategies/frequency_analyzer.py +++ b/axelrod/strategies/frequency_analyzer.py @@ -96,7 +96,9 @@ def strategy(self, opponent: Player) -> Action: try: results = self.frequency_table[self.current_sequence] cooperates = results.count(C) - if (cooperates / len(self.history)) > self.minimum_cooperation_ratio: + if ( + cooperates / len(self.history) + ) > self.minimum_cooperation_ratio: return C return D except: From 16170feac84cbcca200ae5b17d6087be8c7afbbe Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Wed, 8 Jan 2025 12:26:14 -0500 Subject: [PATCH 12/13] add strat to index --- docs/reference/strategy_index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/strategy_index.rst b/docs/reference/strategy_index.rst index baecbb29d..2bb232506 100644 --- a/docs/reference/strategy_index.rst +++ b/docs/reference/strategy_index.rst @@ -48,7 +48,7 @@ Here are the docstrings of all the strategies in the library. :members: .. automodule:: axelrod.strategies.forgiver :members: -.. automodule:: axelrod.strategies.frequencyanalyzer +.. automodule:: axelrod.strategies.frequency_analyzer :members: .. automodule:: axelrod.strategies.gambler :members: From 5cb6c32fe30297e3725e45ceef8e2b2289cc5aaf Mon Sep 17 00:00:00 2001 From: Ian Miller Date: Thu, 9 Jan 2025 10:31:19 -0500 Subject: [PATCH 13/13] code cleanup --- axelrod/strategies/frequency_analyzer.py | 29 +++++++++--------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/axelrod/strategies/frequency_analyzer.py b/axelrod/strategies/frequency_analyzer.py index be74b09e3..9545bdc83 100644 --- a/axelrod/strategies/frequency_analyzer.py +++ b/axelrod/strategies/frequency_analyzer.py @@ -83,29 +83,22 @@ def strategy(self, opponent: Player) -> Action: ) self.update_table(opponent) - if len(self.history) < 30: - # Play TitForTat - # First move + # dataset generation phase + if (len(self.history) < 30) or ( + self.current_sequence not in self.frequency_table + ): if not self.history: return C - # React to the opponent's last move if opponent.history[-1] == D: return D return C - else: - try: - results = self.frequency_table[self.current_sequence] - cooperates = results.count(C) - if ( - cooperates / len(self.history) - ) > self.minimum_cooperation_ratio: - return C - return D - except: - # React to the opponent's last move - if opponent.history[-1] == D: - return D - return C + + # post-dataset generation phase + results = self.frequency_table[self.current_sequence] + cooperates = results.count(C) + if (cooperates / len(self.history)) > self.minimum_cooperation_ratio: + return C + return D def update_table(self, opponent: Player): if self.last_sequence in self.frequency_table.keys():