From 255a2f20d4586bd59f5d4cc5c75126fb4afbc739 Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 16:05:40 +0200 Subject: [PATCH 01/12] Create 4_duality_optimizer --- axelrod/strategies/4duality_optimizer | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 axelrod/strategies/4duality_optimizer diff --git a/axelrod/strategies/4duality_optimizer b/axelrod/strategies/4duality_optimizer new file mode 100644 index 000000000..e78facef2 --- /dev/null +++ b/axelrod/strategies/4duality_optimizer @@ -0,0 +1,63 @@ +from axelrod.action import Action +from axelrod.player import Player + +C, D = Action.C, Action.D + +class FourDualityOptimizer1(Player): + """ + A strategy that operates as follows: + cooperate by default; + ignore betrayals of length 1; + when the other agent performs a series of betrayals of length greater than 1, + respond with a series of betrayals whose number is equal to the number + of series of betrayals performed by the other agent since the beginning. + + Names: + 4-Duality Optimizer 1 original by Paul Franceschi + """ + name = "4-Duality Optimizer 1" + classifier = { + 'memory_depth': float('inf'), + 'stochastic': False, + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def strategy(self, opponent: Player) -> Action: + # Calculate the number of opponent betrayal streaks of length > 1 + waves_greater_than_one = 0 + current_wave_length = 0 + for action in opponent.history: + if action == axl.Action.D: + current_wave_length += 1 + else: + if current_wave_length > 1: + waves_greater_than_one += 1 + current_wave_length = 0 + + # Calculate the length of our current betrayal streak + consecutive_our_defections = 0 + for i in range(len(self.history) - 1, -1, -1): + if self.history[i] == axl.Action.D: + consecutive_our_defections += 1 + else: + break + + # Calculate the length of the opponent's current betrayal streak + consecutive_opp_defections = 0 + for i in range(len(opponent.history) - 1, -1, -1): + if opponent.history[i] == axl.Action.D: + consecutive_opp_defections += 1 + else: + break + + # Decision rules + if consecutive_opp_defections >= 2: + return D + + if consecutive_our_defections > 0 and consecutive_our_defections < waves_greater_than_one: + return D + + return C From 5dd6ce9c56d17d2b53eaa244d8946a056283739f Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 16:06:28 +0200 Subject: [PATCH 02/12] Rename 4duality_optimizer to four_duality_optimizer --- axelrod/strategies/{4duality_optimizer => four_duality_optimizer} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename axelrod/strategies/{4duality_optimizer => four_duality_optimizer} (100%) diff --git a/axelrod/strategies/4duality_optimizer b/axelrod/strategies/four_duality_optimizer similarity index 100% rename from axelrod/strategies/4duality_optimizer rename to axelrod/strategies/four_duality_optimizer From 26db910caf663e02a7d4ea0b7397563f0029cc07 Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 17:22:14 +0200 Subject: [PATCH 03/12] Update _strategies.py --- axelrod/strategies/_strategies.py | 1 + 1 file changed, 1 insertion(+) diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index bc80eeccc..85112f585 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -116,6 +116,7 @@ FSMPlayer, ) from .forgiver import Forgiver, ForgivingTitForTat +from .four_duality_optimizer import FourDualityOptimizer1 from .gambler import ( PSOGambler1_1_1, PSOGambler2_2_2, From 2d1039be4824133f231614e5d53defa134bcf587 Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 17:27:19 +0200 Subject: [PATCH 04/12] Update strategy_index.rst --- docs/reference/strategy_index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/strategy_index.rst b/docs/reference/strategy_index.rst index 9764d3082..02c3670d0 100644 --- a/docs/reference/strategy_index.rst +++ b/docs/reference/strategy_index.rst @@ -50,6 +50,8 @@ Here are the docstrings of all the strategies in the library. :members: .. automodule:: axelrod.strategies.forgiver :members: +.. automodule:: axelrod.strategies.four_duality_optimizer + :members: .. automodule:: axelrod.strategies.frequency_analyzer :members: .. automodule:: axelrod.strategies.gambler From 3e5b96c3f8c9ff68c6d257db1fbfa096bd476e3d Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 17:50:15 +0200 Subject: [PATCH 05/12] Create test_four_duality_optimizer.py --- .../strategies/test_four_duality_optimizer.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 axelrod/tests/strategies/test_four_duality_optimizer.py diff --git a/axelrod/tests/strategies/test_four_duality_optimizer.py b/axelrod/tests/strategies/test_four_duality_optimizer.py new file mode 100644 index 000000000..20bac0597 --- /dev/null +++ b/axelrod/tests/strategies/test_four_duality_optimizer.py @@ -0,0 +1,36 @@ +"""Tests for the FourDualityOptimizer strategy.""" + +import axelrod as axl + +from .test_four_duality_optimizer import TestFourDualityOptimizer1 + +C, D = axl.Action.C, axl.Action.D + + +class TestFourDualityOptimizer1(TestPlayer): + + name = "4-Duality Optimizer 1" + player = axl.Handshake + 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), (D, D)] + [(C, C), (C, D)] * 10 + self.versus_test(axl.Alternator(), expected_actions=actions) + + actions = [(C, C), (D, C)] + [(D, C)] * 20 + self.versus_test(axl.Cooperator(), expected_actions=actions) + + opponent = axl.MockPlayer([D, C]) + actions = [(C, D), (D, C)] + [(D, D), (D, C)] * 10 + self.versus_test(opponent, expected_actions=actions) + + actions = [(C, D), (D, D)] + [(D, D)] * 20 + self.versus_test(axl.Defector(), expected_actions=actions) From 9169d4568fcdce881aa252fdd4b85a5ce654103c Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 17:50:52 +0200 Subject: [PATCH 06/12] Rename four_duality_optimizer to four_duality_optimizer.py --- .../{four_duality_optimizer => four_duality_optimizer.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename axelrod/strategies/{four_duality_optimizer => four_duality_optimizer.py} (100%) diff --git a/axelrod/strategies/four_duality_optimizer b/axelrod/strategies/four_duality_optimizer.py similarity index 100% rename from axelrod/strategies/four_duality_optimizer rename to axelrod/strategies/four_duality_optimizer.py From 65643dbf2f60b12b79b74d0e5061ab85a36b8c8d Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 19:48:54 +0200 Subject: [PATCH 07/12] Update test_four_duality_optimizer.py --- axelrod/tests/strategies/test_four_duality_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_four_duality_optimizer.py b/axelrod/tests/strategies/test_four_duality_optimizer.py index 20bac0597..2384a500d 100644 --- a/axelrod/tests/strategies/test_four_duality_optimizer.py +++ b/axelrod/tests/strategies/test_four_duality_optimizer.py @@ -2,7 +2,7 @@ import axelrod as axl -from .test_four_duality_optimizer import TestFourDualityOptimizer1 +from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D From 018fdaa3b4d645d663e1fdb2d08cc107e222ec4a Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 19:50:59 +0200 Subject: [PATCH 08/12] Update test_four_duality_optimizer.py --- axelrod/tests/strategies/test_four_duality_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_four_duality_optimizer.py b/axelrod/tests/strategies/test_four_duality_optimizer.py index 2384a500d..860d39de3 100644 --- a/axelrod/tests/strategies/test_four_duality_optimizer.py +++ b/axelrod/tests/strategies/test_four_duality_optimizer.py @@ -10,7 +10,7 @@ class TestFourDualityOptimizer1(TestPlayer): name = "4-Duality Optimizer 1" - player = axl.Handshake + player = axl.FourDualityOptimizer1 expected_classifier = { "memory_depth": float("inf"), "stochastic": False, From 9ff570fe2d7c91d5f6a8cae4ee0b78c690afaa55 Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 20:04:02 +0200 Subject: [PATCH 09/12] Refactor test actions in four duality optimizer tests Updated test actions for strategy tests with new expected outcomes. --- axelrod/tests/strategies/test_four_duality_optimizer.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/axelrod/tests/strategies/test_four_duality_optimizer.py b/axelrod/tests/strategies/test_four_duality_optimizer.py index 860d39de3..b34dd0b82 100644 --- a/axelrod/tests/strategies/test_four_duality_optimizer.py +++ b/axelrod/tests/strategies/test_four_duality_optimizer.py @@ -22,15 +22,8 @@ class TestFourDualityOptimizer1(TestPlayer): } def test_strategy(self): - actions = [(C, C), (D, D)] + [(C, C), (C, D)] * 10 - self.versus_test(axl.Alternator(), expected_actions=actions) - actions = [(C, C), (D, C)] + [(D, C)] * 20 + actions = [(C, C), (C, C)] + [(C, C)] * 20 self.versus_test(axl.Cooperator(), expected_actions=actions) - opponent = axl.MockPlayer([D, C]) - actions = [(C, D), (D, C)] + [(D, D), (D, C)] * 10 - self.versus_test(opponent, expected_actions=actions) - actions = [(C, D), (D, D)] + [(D, D)] * 20 - self.versus_test(axl.Defector(), expected_actions=actions) From f30f18de9c92429c0cf7ae43ba8ccaca08046b3e Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 20:05:25 +0200 Subject: [PATCH 10/12] Update test_four_duality_optimizer.py --- axelrod/tests/strategies/test_four_duality_optimizer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/axelrod/tests/strategies/test_four_duality_optimizer.py b/axelrod/tests/strategies/test_four_duality_optimizer.py index b34dd0b82..7134738b6 100644 --- a/axelrod/tests/strategies/test_four_duality_optimizer.py +++ b/axelrod/tests/strategies/test_four_duality_optimizer.py @@ -26,4 +26,6 @@ def test_strategy(self): actions = [(C, C), (C, C)] + [(C, C)] * 20 self.versus_test(axl.Cooperator(), expected_actions=actions) + actions = [(C, C), (C, C)] + [(C, C)] * 20 + self.versus_test(axl.TitForTat(), expected_actions=actions) From d4e392c5da1b4f46fd9a7d0c5ec91398fde35ccf Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Fri, 15 May 2026 20:07:24 +0200 Subject: [PATCH 11/12] Add test for Retaliate strategy in four_duality_optimizer --- axelrod/tests/strategies/test_four_duality_optimizer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/axelrod/tests/strategies/test_four_duality_optimizer.py b/axelrod/tests/strategies/test_four_duality_optimizer.py index 7134738b6..daf8d9444 100644 --- a/axelrod/tests/strategies/test_four_duality_optimizer.py +++ b/axelrod/tests/strategies/test_four_duality_optimizer.py @@ -29,3 +29,6 @@ def test_strategy(self): actions = [(C, C), (C, C)] + [(C, C)] * 20 self.versus_test(axl.TitForTat(), expected_actions=actions) + actions = [(C, C), (C, C)] + [(C, C)] * 20 + self.versus_test(axl.Retaliate(), expected_actions=actions) + From 91bf017f70d4d8a52cdefe5eb4b5f856bfcb28ed Mon Sep 17 00:00:00 2001 From: Paul Franceschi <5023585+paulfranceschi@users.noreply.github.com> Date: Sat, 16 May 2026 13:53:45 +0200 Subject: [PATCH 12/12] Update four_duality_optimizer.py --- axelrod/strategies/four_duality_optimizer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/axelrod/strategies/four_duality_optimizer.py b/axelrod/strategies/four_duality_optimizer.py index e78facef2..ec4271547 100644 --- a/axelrod/strategies/four_duality_optimizer.py +++ b/axelrod/strategies/four_duality_optimizer.py @@ -11,6 +11,7 @@ class FourDualityOptimizer1(Player): when the other agent performs a series of betrayals of length greater than 1, respond with a series of betrayals whose number is equal to the number of series of betrayals performed by the other agent since the beginning. + Based on the IPD analysis presented in the paper: https://philpapers.org/rec/FRACCP Names: 4-Duality Optimizer 1 original by Paul Franceschi