Skip to content

Commit 4423875

Browse files
committed
add freqAnalyzer strat
1 parent 706f454 commit 4423875

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed

axelrod/strategies/_strategies.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,15 @@
238238
)
239239
from .shortmem import ShortMem
240240
from .stalker import Stalker
241+
from .frequency_analyzer import FreqAnalyzer
241242
from .titfortat import (
242243
AdaptiveTitForTat,
243244
Alexei,
244245
AntiTitForTat,
245246
Bully,
246247
BurnBothEnds,
248+
SofteningTitForTat,
249+
HardeningTitForTat,
247250
ContriteTitForTat,
248251
DynamicTwoTitsForTat,
249252
EugineNier,
@@ -366,6 +369,7 @@
366369
ForgivingTitForTat,
367370
Fortress3,
368371
Fortress4,
372+
FreqAnalyzer,
369373
GTFT,
370374
GeneralSoftGrudger,
371375
GoByMajority,
@@ -392,6 +396,8 @@
392396
Hopeless,
393397
Inverse,
394398
InversePunisher,
399+
SofteningTitForTat,
400+
HardeningTitForTat,
395401
KnowledgeableWorseAndWorse,
396402
LevelPunisher,
397403
LimitedRetaliate,
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
class FreqAnalyzer(Player):
11+
"""
12+
A player starts by playing TitForTat for the first 30 turns (dataset generation phase).
13+
14+
Take the matrix of last 2 moves by both Player and Opponent.
15+
16+
While in dataset generation phase, construct a dictionary d, where keys are each 4 move sequence
17+
and the corresponding value for each key is a list of the subsequent Opponent move. The 4 move sequence
18+
starts with the Opponent move.
19+
20+
For example, if a game at turn 5 looks like this:
21+
22+
Opp: C, C, D, C, D
23+
Player: C, C, C, D, C
24+
25+
d should look like this:
26+
27+
{ [CCCC]: [D],
28+
[CCDC]: [C],
29+
[DCCD]: [D] }
30+
31+
During dataset generation phase, Player will play TitForTat. After end of dataset generation phase,
32+
Player will switch strategies. Upon encountering a particular 4-move sequence in the game, Player will look up history
33+
of subsequent Opponent move. If ratio of defections to total moves exceeds p, Player will defect. Otherwise,
34+
Player will cooperate.
35+
36+
Could fall under "Hunter" class of strategies.
37+
More likely falls under LookerUp class of strategies.
38+
39+
Names:
40+
41+
- FreqAnalyzer (FREQ): Original by Ian Miller
42+
"""
43+
44+
# These are various properties for the strategy
45+
name = "FreqAnalyzer"
46+
classifier = {
47+
"memory_depth": float("inf"),
48+
"stochastic": False,
49+
"long_run_time": False,
50+
"inspects_source": False,
51+
"manipulates_source": False,
52+
"manipulates_state": False,
53+
}
54+
def __init__(self) -> None:
55+
"""
56+
Parameters
57+
----------
58+
p, float
59+
The probability to cooperate
60+
"""
61+
super().__init__()
62+
self.minimum_cooperation_ratio = 0.5
63+
self.frequency_table = dict()
64+
self.last_sequence = ''
65+
self.current_sequence = ''
66+
67+
def strategy(self, opponent: Player) -> Action:
68+
"""This is the actual strategy"""
69+
if len(self.history) > 5:
70+
self.last_sequence = str(opponent.history[-3]) + str(self.history[-3]) + str(opponent.history[-2]) + str(self.history[-2])
71+
self.current_sequence = str(opponent.history[-2]) + str(self.history[-2]) + str(opponent.history[-1]) + str(self.history[-1])
72+
73+
self.update_table(opponent)
74+
75+
if len(self.history) < 30:
76+
# Play TitForTat
77+
# First move
78+
if not self.history:
79+
return C
80+
# React to the opponent's last move
81+
if opponent.history[-1] == D:
82+
return D
83+
return C
84+
else:
85+
try:
86+
results = self.frequency_table[self.current_sequence]
87+
cooperates = results.count('C')
88+
if (cooperates / len(self.history)) > self.minimum_cooperation_ratio:
89+
return C
90+
return D
91+
except:
92+
if not self.history:
93+
return C
94+
# React to the opponent's last move
95+
if opponent.history[-1] == D:
96+
return D
97+
return C
98+
99+
def update_table(self, opponent: Player):
100+
print(self.frequency_table)
101+
print("___________________")
102+
print("current sequence is {}", self.last_sequence)
103+
if self.last_sequence in self.frequency_table.keys():
104+
print("seen this key before")
105+
print("freq table keys = {}", self.frequency_table.keys())
106+
results = self.frequency_table[self.last_sequence]
107+
results.append(opponent.history[-1])
108+
self.frequency_table[self.last_sequence] = results
109+
else:
110+
print("not seen this key ever")
111+
self.frequency_table[self.last_sequence] = [opponent.history[-1]]
112+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""Tests for the Stalker 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 TestStalker(TestPlayer):
11+
12+
name = "Stalker: (D,)"
13+
player = axl.Stalker
14+
expected_classifier = {
15+
"memory_depth": float("inf"),
16+
"stochastic": True,
17+
"makes_use_of": {"game", "length"},
18+
"long_run_time": False,
19+
"inspects_source": False,
20+
"manipulates_source": False,
21+
"manipulates_state": False,
22+
}
23+
24+
def test_strategy(self):
25+
actions = [(C, C)] * 3 + [(D, C)]
26+
self.versus_test(opponent=axl.Cooperator(), expected_actions=actions)
27+
28+
# wish_score < current_average_score < very_good_score
29+
actions = [(C, C)] * 7 + [(C, D), (C, D), (C, C), (C, C), (D, C)]
30+
self.versus_test(
31+
opponent=axl.MockPlayer(actions=[C] * 7 + [D] * 2),
32+
expected_actions=actions,
33+
)
34+
35+
actions = [(C, C)] * 7 + [(C, D), (C, C), (D, C)]
36+
self.versus_test(
37+
opponent=axl.MockPlayer(actions=[C] * 7 + [D]),
38+
expected_actions=actions,
39+
)
40+
41+
# current_average_score > 2
42+
actions = [(C, C)] * 9 + [(D, C)]
43+
self.versus_test(axl.Cooperator(), expected_actions=actions)
44+
45+
# 1 < current_average_score < 2
46+
actions = [(C, C)] * 7 + [(C, D)] * 4 + [(D, D)]
47+
self.versus_test(
48+
opponent=axl.MockPlayer(actions=[C] * 7 + [D] * 5),
49+
expected_actions=actions,
50+
)
51+
52+
def test_strategy2(self):
53+
# current_average_score < 1
54+
actions = (
55+
[(C, D)]
56+
+ [(D, D)] * 2
57+
+ [(C, D)] * 3
58+
+ [(D, D), (C, D), (D, D), (C, D), (D, D), (C, D), (D, D)]
59+
)
60+
self.versus_test(axl.Defector(), expected_actions=actions, seed=3222)
61+
62+
def test_strategy3(self):
63+
actions = [(C, D)] * 3 + [
64+
(D, D),
65+
(C, D),
66+
(D, D),
67+
(C, D),
68+
(C, D),
69+
(D, D),
70+
(C, D),
71+
(C, D),
72+
(C, D),
73+
(D, D),
74+
]
75+
self.versus_test(axl.Defector(), expected_actions=actions, seed=649)
76+
77+
def test_strategy4(self):
78+
# defect in last round
79+
actions = [(C, C)] * 199 + [(D, C)]
80+
self.versus_test(
81+
axl.Cooperator(),
82+
expected_actions=actions,
83+
match_attributes={"length": 200},
84+
)
85+
86+
# Force a defection before the end of the actual match which ensures
87+
# that current_average_score > very_good_score
88+
actions = [(C, C)] * 3 + [(D, C)] * 3
89+
self.versus_test(
90+
opponent=axl.Cooperator(),
91+
expected_actions=actions,
92+
match_attributes={"length": 4},
93+
)

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)