Skip to content

Commit 684b354

Browse files
authored
add freqAnalyzer strategy (#1444)
* add freqAnalyzer strat * cleanup + wip * remove spurious changes * fixed freq analyzer test * Delete axelrod/tests/strategies/test_freqanalyzer.py * bump num strats * remove unnecessary lines * fix type error * fix test * formatting * python black with correct options * add strat to index * code cleanup
1 parent cfa5889 commit 684b354

File tree

6 files changed

+274
-1
lines changed

6 files changed

+274
-1
lines changed

axelrod/strategies/_strategies.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@
238238
)
239239
from .shortmem import ShortMem
240240
from .stalker import Stalker
241+
from .frequency_analyzer import FrequencyAnalyzer
241242
from .titfortat import (
242243
AdaptiveTitForTat,
243244
Alexei,
@@ -366,6 +367,7 @@
366367
ForgivingTitForTat,
367368
Fortress3,
368369
Fortress4,
370+
FrequencyAnalyzer,
369371
GTFT,
370372
GeneralSoftGrudger,
371373
GoByMajority,
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from axelrod.action import Action, actions_to_str
2+
from axelrod.player import Player
3+
from axelrod.strategy_transformers import (
4+
FinalTransformer,
5+
TrackHistoryTransformer,
6+
)
7+
8+
C, D = Action.C, Action.D
9+
10+
11+
class FrequencyAnalyzer(Player):
12+
"""
13+
A player starts by playing TitForTat for the first 30 turns (dataset generation phase).
14+
15+
Take the matrix of last 2 moves by both Player and Opponent.
16+
17+
While in dataset generation phase, construct a dictionary d, where keys are each 4 move sequence
18+
and the corresponding value for each key is a list of the subsequent Opponent move. The 4 move sequence
19+
starts with the Opponent move.
20+
21+
For example, if a game at turn 5 looks like this:
22+
23+
Opp: C, C, D, C, D
24+
Player: C, C, C, D, C
25+
26+
d should look like this:
27+
28+
{ [CCCC]: [D],
29+
[CCDC]: [C],
30+
[DCCD]: [D] }
31+
32+
During dataset generation phase, Player will play TitForTat. After end of dataset generation phase,
33+
Player will switch strategies. Upon encountering a particular 4-move sequence in the game, Player will look up history
34+
of subsequent Opponent move. If ratio of defections to total moves exceeds p, Player will defect. Otherwise,
35+
Player will cooperate.
36+
37+
Could fall under "Hunter" class of strategies.
38+
More likely falls under LookerUp class of strategies.
39+
40+
Names:
41+
42+
- FrequencyAnalyzer (FREQ): Original by Ian Miller
43+
"""
44+
45+
# These are various properties for the strategy
46+
name = "FrequencyAnalyzer"
47+
classifier = {
48+
"memory_depth": float("inf"),
49+
"stochastic": False,
50+
"long_run_time": False,
51+
"inspects_source": False,
52+
"manipulates_source": False,
53+
"manipulates_state": False,
54+
}
55+
56+
def __init__(self) -> None:
57+
"""
58+
Parameters
59+
----------
60+
p, float
61+
The probability to cooperate
62+
"""
63+
super().__init__()
64+
self.minimum_cooperation_ratio = 0.25
65+
self.frequency_table = dict()
66+
self.last_sequence = ""
67+
self.current_sequence = ""
68+
69+
def strategy(self, opponent: Player) -> Action:
70+
"""This is the actual strategy"""
71+
if len(self.history) > 5:
72+
self.last_sequence = (
73+
str(opponent.history[-3])
74+
+ str(self.history[-3])
75+
+ str(opponent.history[-2])
76+
+ str(self.history[-2])
77+
)
78+
self.current_sequence = (
79+
str(opponent.history[-2])
80+
+ str(self.history[-2])
81+
+ str(opponent.history[-1])
82+
+ str(self.history[-1])
83+
)
84+
self.update_table(opponent)
85+
86+
# dataset generation phase
87+
if (len(self.history) < 30) or (
88+
self.current_sequence not in self.frequency_table
89+
):
90+
if not self.history:
91+
return C
92+
if opponent.history[-1] == D:
93+
return D
94+
return C
95+
96+
# post-dataset generation phase
97+
results = self.frequency_table[self.current_sequence]
98+
cooperates = results.count(C)
99+
if (cooperates / len(self.history)) > self.minimum_cooperation_ratio:
100+
return C
101+
return D
102+
103+
def update_table(self, opponent: Player):
104+
if self.last_sequence in self.frequency_table.keys():
105+
results = self.frequency_table[self.last_sequence]
106+
results.append(opponent.history[-1])
107+
self.frequency_table[self.last_sequence] = results
108+
else:
109+
self.frequency_table[self.last_sequence] = [opponent.history[-1]]
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""Tests for the FrequencyAnalyzer strategy."""
2+
3+
import axelrod as axl
4+
5+
from .test_player import TestPlayer
6+
7+
C, D = axl.Action.C, axl.Action.D
8+
9+
10+
class Test(TestPlayer):
11+
12+
name = "FrequencyAnalyzer"
13+
player = axl.FrequencyAnalyzer
14+
expected_classifier = {
15+
"memory_depth": float("inf"),
16+
"stochastic": False,
17+
"long_run_time": False,
18+
"makes_use_of": set(),
19+
"inspects_source": False,
20+
"manipulates_source": False,
21+
"manipulates_state": False,
22+
}
23+
24+
def test_strategy_early(self):
25+
# Test games that end while still in dataset generation phase (<30 turns)
26+
opponent_actions = [C, C, D, C, D]
27+
expected = [(C, C), (C, C), (C, D), (D, C), (C, D)]
28+
self.versus_test(
29+
axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4
30+
)
31+
32+
def test_strategy_defector(self):
33+
# Test against all defections
34+
opponent_actions = [D] * 30
35+
expected = [(C, D)] + [(D, D)] * 29
36+
self.versus_test(
37+
axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4
38+
)
39+
40+
def test_strategy_cooperator(self):
41+
# Test games that end while still in dataset generation phase (<30 turns)
42+
opponent_actions = [C] * 30
43+
expected = [(C, C)] * 30
44+
self.versus_test(
45+
axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4
46+
)
47+
48+
def test_strategy_random(self):
49+
# Test of 50 turns against random strategy
50+
opponent_actions = [
51+
C,
52+
D,
53+
D,
54+
D,
55+
D,
56+
D,
57+
D,
58+
C,
59+
D,
60+
C,
61+
D,
62+
C,
63+
D,
64+
C,
65+
D,
66+
D,
67+
C,
68+
D,
69+
C,
70+
D,
71+
D,
72+
C,
73+
D,
74+
D,
75+
D,
76+
D,
77+
D,
78+
C,
79+
C,
80+
D,
81+
D,
82+
C,
83+
C,
84+
C,
85+
D,
86+
D,
87+
C,
88+
D,
89+
C,
90+
C,
91+
C,
92+
D,
93+
D,
94+
C,
95+
C,
96+
C,
97+
D,
98+
C,
99+
D,
100+
D,
101+
]
102+
expected = [
103+
(C, C),
104+
(C, D),
105+
(D, D),
106+
(D, D),
107+
(D, D),
108+
(D, D),
109+
(D, D),
110+
(D, C),
111+
(C, D),
112+
(D, C),
113+
(C, D),
114+
(D, C),
115+
(C, D),
116+
(D, C),
117+
(C, D),
118+
(D, D),
119+
(D, C),
120+
(C, D),
121+
(D, C),
122+
(C, D),
123+
(D, D),
124+
(D, C),
125+
(C, D),
126+
(D, D),
127+
(D, D),
128+
(D, D),
129+
(D, D),
130+
(D, C),
131+
(C, C),
132+
(C, D), # rd 30 (end of dataset generation phase)
133+
(D, D),
134+
(D, C),
135+
(
136+
D,
137+
C,
138+
), # example of non TFT (by this point, FrequencyAnalyzer is generally distrustful of opponent)
139+
(C, C),
140+
(D, D),
141+
(D, D),
142+
(D, C),
143+
(D, D),
144+
(D, C),
145+
(D, C),
146+
(D, C),
147+
(D, D),
148+
(D, D),
149+
(D, C),
150+
(D, C),
151+
(D, C),
152+
(D, D),
153+
(D, C),
154+
(D, D),
155+
(D, D),
156+
]
157+
self.versus_test(
158+
axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4
159+
)

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Count the number of available players::
5353

5454
>>> import axelrod as axl
5555
>>> len(axl.strategies)
56-
240
56+
241
5757

5858
Create matches between two players::
5959

docs/reference/strategy_index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Here are the docstrings of all the strategies in the library.
4848
:members:
4949
.. automodule:: axelrod.strategies.forgiver
5050
:members:
51+
.. automodule:: axelrod.strategies.frequency_analyzer
52+
:members:
5153
.. automodule:: axelrod.strategies.gambler
5254
:members:
5355
.. automodule:: axelrod.strategies.gobymajority

run_mypy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"axelrod/strategies/darwin.py",
3232
"axelrod/strategies/defector.py",
3333
"axelrod/strategies/forgiver.py",
34+
"axelrod/strategies/frequency_analyzer.py",
3435
"axelrod/strategies/gradualkiller.py",
3536
"axelrod/strategies/grudger.py",
3637
"axelrod/strategies/grumpy.py",

0 commit comments

Comments
 (0)