Skip to content

add freqAnalyzer strategy #1444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 10, 2025
Merged
2 changes: 2 additions & 0 deletions axelrod/strategies/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@
)
from .shortmem import ShortMem
from .stalker import Stalker
from .frequency_analyzer import FrequencyAnalyzer
from .titfortat import (
AdaptiveTitForTat,
Alexei,
Expand Down Expand Up @@ -366,6 +367,7 @@
ForgivingTitForTat,
Fortress3,
Fortress4,
FrequencyAnalyzer,
GTFT,
GeneralSoftGrudger,
GoByMajority,
Expand Down
109 changes: 109 additions & 0 deletions axelrod/strategies/frequency_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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 FrequencyAnalyzer(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:

- FrequencyAnalyzer (FREQ): Original by Ian Miller
"""

# These are various properties for the strategy
name = "FrequencyAnalyzer"
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.25
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)

# dataset generation phase
if (len(self.history) < 30) or (
self.current_sequence not in self.frequency_table
):
if not self.history:
return C
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():
results = self.frequency_table[self.last_sequence]
results.append(opponent.history[-1])
self.frequency_table[self.last_sequence] = results
else:
self.frequency_table[self.last_sequence] = [opponent.history[-1]]
159 changes: 159 additions & 0 deletions axelrod/tests/strategies/test_frequency_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""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,
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
)
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Count the number of available players::

>>> import axelrod as axl
>>> len(axl.strategies)
240
241

Create matches between two players::

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/strategy_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Here are the docstrings of all the strategies in the library.
:members:
.. automodule:: axelrod.strategies.forgiver
:members:
.. automodule:: axelrod.strategies.frequency_analyzer
:members:
.. automodule:: axelrod.strategies.gambler
:members:
.. automodule:: axelrod.strategies.gobymajority
Expand Down
1 change: 1 addition & 0 deletions run_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading