# Source code for axelrod.strategies.axelrod_first

```
"""
Additional strategies from Axelrod's first tournament.
"""
import random
from typing import Dict, List, Tuple
from axelrod.action import Action
from axelrod.player import Player
from axelrod.random_ import random_choice
from axelrod.strategy_transformers import FinalTransformer
from scipy.stats import chisquare
from .memoryone import MemoryOnePlayer
C, D = Action.C, Action.D
[docs]class Davis(Player):
"""
Submitted to Axelrod's first tournament by Morton Davis.
A player starts by cooperating for 10 rounds then plays Grudger,
defecting if at any point the opponent has defected.
This strategy came 8th in Axelrod's original tournament.
Names:
- Davis: [Axelrod1980]_
"""
name = "Davis"
classifier = {
"memory_depth": float("inf"), # Long memory
"stochastic": False,
"makes_use_of": set(),
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self, rounds_to_cooperate: int = 10) -> None:
"""
Parameters
----------
rounds_to_cooperate: int, 10
The number of rounds to cooperate initially
"""
super().__init__()
self._rounds_to_cooperate = rounds_to_cooperate
[docs] def strategy(self, opponent: Player) -> Action:
"""Begins by playing C, then plays D for the remaining rounds if the
opponent ever plays D."""
if len(self.history) < self._rounds_to_cooperate:
return C
if opponent.defections:
return D
return C
[docs]class RevisedDowning(Player):
"""This strategy attempts to estimate the next move of the opponent by estimating
the probability of cooperating given that they defected (:math:`p(C|D)`) or
cooperated on the previous round (:math:`p(C|C)`). These probabilities are
continuously updated during play and the strategy attempts to maximise the long
term play. Note that the initial values are :math:`p(C|C)=p(C|D)=.5`.
Downing is implemented as `RevisedDowning`. Apparently in the first tournament
the strategy was implemented incorrectly and defected on the first two rounds.
This can be controlled by setting `revised=True` to prevent the initial defections.
This strategy came 10th in Axelrod's original tournament but would have won
if it had been implemented correctly.
Names:
- Revised Downing: [Axelrod1980]_
"""
name = "Revised Downing"
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 __init__(self, revised: bool = True) -> None:
super().__init__()
self.revised = revised
self.good = 1.0
self.bad = 0.0
self.nice1 = 0
self.nice2 = 0
self.total_C = 0 # note the same as self.cooperations
self.total_D = 0 # note the same as self.defections
[docs] def strategy(self, opponent: Player) -> Action:
round_number = len(self.history) + 1
# According to internet sources, the original implementation defected
# on the first two moves. Otherwise it wins (if this code is removed
# and the comment restored.
# http://www.sci.brooklyn.cuny.edu/~sklar/teaching/f05/alife/notes/azhar-ipd-Oct19th.pdf
if self.revised:
if round_number == 1:
return C
elif not self.revised:
if round_number <= 2:
return D
# Update various counts
if round_number > 2:
if self.history[-1] == D:
if opponent.history[-1] == C:
self.nice2 += 1
self.total_D += 1
self.bad = self.nice2 / self.total_D
else:
if opponent.history[-1] == C:
self.nice1 += 1
self.total_C += 1
self.good = self.nice1 / self.total_C
# Make a decision based on the accrued counts
c = 6.0 * self.good - 8.0 * self.bad - 2
alt = 4.0 * self.good - 5.0 * self.bad - 1
if c >= 0 and c >= alt:
move = C
elif (c >= 0 and c < alt) or (alt >= 0):
move = self.history[-1].flip()
else:
move = D
return move
[docs]class Feld(Player):
"""
Submitted to Axelrod's first tournament by Scott Feld.
This strategy plays Tit For Tat, always defecting if the opponent defects but
cooperating when the opponent cooperates with a gradually decreasing probability
until it is only .5.
This strategy came 11th in Axelrod's original tournament.
Names:
- Feld: [Axelrod1980]_
"""
name = "Feld"
classifier = {
"memory_depth": 200, # Varies actually, eventually becomes depth 1
"stochastic": True,
"makes_use_of": set(),
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(
self,
start_coop_prob: float = 1.0,
end_coop_prob: float = 0.5,
rounds_of_decay: int = 200,
) -> None:
"""
Parameters
----------
start_coop_prob, float
The initial probability to cooperate
end_coop_prob, float
The final probability to cooperate
rounds_of_decay, int
The number of rounds to linearly decrease from start_coop_prob
to end_coop_prob
"""
super().__init__()
self._start_coop_prob = start_coop_prob
self._end_coop_prob = end_coop_prob
self._rounds_of_decay = rounds_of_decay
def _cooperation_probability(self) -> float:
"""It's not clear what the interpolating function is, so we'll do
something simple that decreases monotonically from 1.0 to 0.5 over
200 rounds."""
diff = self._end_coop_prob - self._start_coop_prob
slope = diff / self._rounds_of_decay
rounds = len(self.history)
return max(self._start_coop_prob + slope * rounds, self._end_coop_prob)
[docs] def strategy(self, opponent: Player) -> Action:
if not opponent.history:
return C
if opponent.history[-1] == D:
return D
p = self._cooperation_probability()
return random_choice(p)
[docs]class Grofman(Player):
"""
Submitted to Axelrod's first tournament by Bernard Grofman.
Cooperate on the first two rounds and
returns the opponent's last action for the next 5. For the rest of the game
Grofman cooperates if both players selected the same action in the previous
round, and otherwise cooperates randomly with probability 2/7.
This strategy came 4th in Axelrod's original tournament.
Names:
- Grofman: [Axelrod1980]_
"""
name = "Grofman"
classifier = {
"memory_depth": float("inf"),
"stochastic": True,
"makes_use_of": set(),
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
[docs] def strategy(self, opponent: Player) -> Action:
round_number = len(self.history) + 1
if round_number < 3:
return C
if round_number < 8:
return opponent.history[-1]
if self.history[-1] == opponent.history[-1]:
return C
return random_choice(2 / 7)
[docs]class Joss(MemoryOnePlayer):
"""
Submitted to Axelrod's first tournament by Johann Joss.
Cooperates with probability 0.9 when the opponent cooperates, otherwise
emulates Tit-For-Tat.
This strategy came 12th in Axelrod's original tournament.
Names:
- Joss: [Axelrod1980]_
- Hard Joss: [Stewart2012]_
"""
name = "Joss"
def __init__(self, p: float = 0.9) -> None:
"""
Parameters
----------
p, float
The probability of cooperating when the previous round was (C, C)
or (D, C), i.e. the opponent cooperated.
"""
four_vector = (p, 0, p, 0)
self.p = p
super().__init__(four_vector)
[docs]class Nydegger(Player):
"""
Submitted to Axelrod's first tournament by Rudy Nydegger.
The program begins with tit for tat for the first three moves, except
that if it was the only one to cooperate on the first move and the only one
to defect on the second move, it defects on the third move. After the
third move, its choice is determined from the 3 preceding outcomes in the
following manner.
.. math::
A = 16 a_1 + 4 a_2 + a_3
Where :math:`a_i` is dependent on the outcome of the previous :math:`i` th
round. If both strategies defect, :math:`a_i=3`, if the opponent only defects:
:math:`a_i=2` and finally if it is only this strategy that defects then
:math:`a_i=1`.
Finally this strategy defects if and only if:
.. math::
A \in \{1, 6, 7, 17, 22, 23, 26, 29, 30, 31, 33, 38, 39, 45, 49, 54, 55, 58, 61\}
Thus if all three preceding moves are mutual defection, A = 63 and the rule
cooperates. This rule was designed for use in laboratory experiments as a
stooge which had a memory and appeared to be trustworthy, potentially
cooperative, but not gullible.
This strategy came 3rd in Axelrod's original tournament.
Names:
- Nydegger: [Axelrod1980]_
"""
name = "Nydegger"
classifier = {
"memory_depth": 3,
"stochastic": False,
"makes_use_of": set(),
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self) -> None:
self.As = [1, 6, 7, 17, 22, 23, 26, 29, 30, 31, 33, 38, 39, 45, 54, 55, 58, 61]
self.score_map = {(C, C): 0, (C, D): 2, (D, C): 1, (D, D): 3}
super().__init__()
[docs] @staticmethod
def score_history(
my_history: List[Action],
opponent_history: List[Action],
score_map: Dict[Tuple[Action, Action], int],
) -> int:
"""Implements the Nydegger formula A = 16 a_1 + 4 a_2 + a_3"""
a = 0
for i, weight in [(-1, 16), (-2, 4), (-3, 1)]:
plays = (my_history[i], opponent_history[i])
a += weight * score_map[plays]
return a
[docs] def strategy(self, opponent: Player) -> Action:
if len(self.history) == 0:
return C
if len(self.history) == 1:
# TFT
return D if opponent.history[-1] == D else C
if len(self.history) == 2:
if opponent.history[0:2] == [D, C]:
return D
else:
# TFT
return D if opponent.history[-1] == D else C
A = self.score_history(self.history[-3:], opponent.history[-3:], self.score_map)
if A in self.As:
return D
return C
[docs]class Shubik(Player):
"""
Submitted to Axelrod's first tournament by Martin Shubik.
Plays like Tit-For-Tat with the following modification. After each
retaliation, the number of rounds that Shubik retaliates increases by 1.
This strategy came 5th in Axelrod's original tournament.
Names:
- Shubik: [Axelrod1980]_
"""
name = "Shubik"
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 __init__(self) -> None:
super().__init__()
self.is_retaliating = False
self.retaliation_length = 0
self.retaliation_remaining = 0
def _decrease_retaliation_counter(self):
"""Lower the remaining owed retaliation count and flip to non-retaliate
if the count drops to zero."""
if self.is_retaliating:
self.retaliation_remaining -= 1
if self.retaliation_remaining == 0:
self.is_retaliating = False
[docs] def strategy(self, opponent: Player) -> Action:
if not opponent.history:
return C
if opponent.history[-1] == D:
# Retaliate against defections
if self.history[-1] == C: # it's on now!
# Lengthen the retaliation period
self.is_retaliating = True
self.retaliation_length += 1
self.retaliation_remaining = self.retaliation_length
self._decrease_retaliation_counter()
return D
else:
# Just retaliate
if self.is_retaliating:
self._decrease_retaliation_counter()
return D
if self.is_retaliating:
# Are we retaliating still?
self._decrease_retaliation_counter()
return D
return C
[docs]class Tullock(Player):
"""
Submitted to Axelrod's first tournament by Gordon Tullock.
Cooperates for the first 11 rounds then randomly cooperates 10% less often
than the opponent has in previous rounds.
This strategy came 13th in Axelrod's original tournament.
Names:
- Tullock: [Axelrod1980]_
"""
name = "Tullock"
classifier = {
"memory_depth": 11, # long memory, modified by init
"stochastic": True,
"makes_use_of": set(),
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self, rounds_to_cooperate: int = 11) -> None:
"""
Parameters
----------
rounds_to_cooperate: int, 10
The number of rounds to cooperate initially
"""
super().__init__()
self._rounds_to_cooperate = rounds_to_cooperate
self.memory_depth = rounds_to_cooperate
[docs] def strategy(self, opponent: Player) -> Action:
rounds = self._rounds_to_cooperate
if len(self.history) < rounds:
return C
cooperate_count = opponent.history[-rounds:].count(C)
prop_cooperate = cooperate_count / rounds
prob_cooperate = max(0, prop_cooperate - 0.10)
return random_choice(prob_cooperate)
[docs]class UnnamedStrategy(Player):
"""Apparently written by a grad student in political science whose name was
withheld, this strategy cooperates with a given probability P. This
probability (which has initial value .3) is updated every 10 rounds based on
whether the opponent seems to be random, very cooperative or very
uncooperative. Furthermore, if after round 130 the strategy is losing then P
is also adjusted.
Fourteenth Place with 282.2 points is a 77-line program by a graduate
student of political science whose dissertation is in game theory. This rule
has a probability of cooperating, P, which is initially 30% and is updated
every 10 moves. P is adjusted if the other player seems random, very
cooperative, or very uncooperative. P is also adjusted after move 130 if the
rule has a lower score than the other player. Unfortunately, the complex
process of adjustment frequently left the probability of cooperation in the
30% to 70% range, and therefore the rule appeared random to many other players.
Names:
- Unnamed Strategy: [Axelrod1980]_
Warning: This strategy is not identical to the original strategy (source
unavailable) and was written based on published descriptions.
"""
name = "Unnamed Strategy"
classifier = {
"memory_depth": 0,
"stochastic": True,
"makes_use_of": set(),
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
[docs] @staticmethod
def strategy(opponent: Player) -> Action:
r = random.uniform(3, 7) / 10
return random_choice(r)
[docs]@FinalTransformer((D, D), name_prefix=None)
class SteinAndRapoport(Player):
"""This strategy plays a modification of Tit For Tat.
1. It cooperates for the first 4 moves.
2. It defects on the last 2 moves.
3. Every 15 moves it makes use of a `chi-squared
test <http://en.wikipedia.org/wiki/Chi-squared_test>`_ to check if the
opponent is playing randomly.
This strategy came 6th in Axelrod's original tournament.
Names:
- SteinAndRapoport: [Axelrod1980]_
"""
name = "Stein and Rapoport"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"makes_use_of": {"length"},
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self, alpha: float = 0.05) -> None:
"""
Parameters
----------
alpha: float
The significant level of p-value from chi-squared test with
alpha == 0.05 by default.
"""
super().__init__()
self.alpha = alpha
self.opponent_is_random = False
def strategy(self, opponent: Player) -> Action:
round_number = len(self.history) + 1
# First 4 moves
if round_number < 5:
return C
# For first 15 rounds tit for tat as we do not know opponents strategy
elif round_number < 15:
return opponent.history[-1]
if round_number % 15 == 0:
p_value = chisquare([opponent.cooperations, opponent.defections]).pvalue
self.opponent_is_random = p_value >= self.alpha
if self.opponent_is_random:
# Defect if opponent plays randomly
return D
else: # TitForTat if opponent plays not randomly
return opponent.history[-1]
[docs]class TidemanAndChieruzzi(Player):
"""
This strategy begins by playing Tit For Tat and then follows the following
rules:
1. Every run of defections played by the opponent increases the number of
defections that this strategy retaliates with by 1.
2. The opponent is given a ‘fresh start’ if:
- it is 10 points behind this strategy
- and it has not just started a run of defections
- and it has been at least 20 rounds since the last ‘fresh start’
- and there are more than 10 rounds remaining in the match
- and the total number of defections differs from a 50-50 random sample
by at least 3.0 standard deviations.
A ‘fresh start’ is a sequence of two cooperations followed by an assumption
that the game has just started (everything is forgotten).
This strategy came 2nd in Axelrod’s original tournament.
Names:
- TidemanAndChieruzzi: [Axelrod1980]_
"""
name = "Tideman and Chieruzzi"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"makes_use_of": {"game", "length"},
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self) -> None:
super().__init__()
self.is_retaliating = False
self.retaliation_length = 0
self.retaliation_remaining = 0
self.current_score = 0
self.opponent_score = 0
self.last_fresh_start = 0
self.fresh_start = False
def _decrease_retaliation_counter(self):
"""Lower the remaining owed retaliation count and flip to non-retaliate
if the count drops to zero."""
if self.is_retaliating:
self.retaliation_remaining -= 1
if self.retaliation_remaining == 0:
self.is_retaliating = False
def _fresh_start(self):
"""Give the opponent a fresh start by forgetting the past"""
self.is_retaliating = False
self.retaliation_length = 0
self.retaliation_remaining = 0
def _score_last_round(self, opponent: Player):
"""Updates the scores for each player."""
# Load the default game if not supplied by a tournament.
game = self.match_attributes["game"]
last_round = (self.history[-1], opponent.history[-1])
scores = game.score(last_round)
self.current_score += scores[0]
self.opponent_score += scores[1]
[docs] def strategy(self, opponent: Player) -> Action:
if not opponent.history:
return C
# Calculate the scores.
self._score_last_round(opponent)
# Check if we have recently given the strategy a fresh start.
if self.fresh_start:
self.fresh_start = False
return C # Second cooperation
# Check conditions to give opponent a fresh start.
current_round = len(self.history) + 1
if self.last_fresh_start == 0:
valid_fresh_start = True
# There needs to be at least 20 rounds before the next fresh start
else:
valid_fresh_start = current_round - self.last_fresh_start >= 20
if valid_fresh_start:
valid_points = self.current_score - self.opponent_score >= 10
valid_rounds = self.match_attributes["length"] - current_round >= 10
opponent_is_cooperating = opponent.history[-1] == C
if valid_points and valid_rounds and opponent_is_cooperating:
# 50-50 split is based off the binomial distribution.
N = opponent.cooperations + opponent.defections
# std_dev = sqrt(N*p*(1-p)) where p is 1 / 2.
std_deviation = (N ** (1 / 2)) / 2
lower = N / 2 - 3 * std_deviation
upper = N / 2 + 3 * std_deviation
if opponent.defections <= lower or opponent.defections >= upper:
# Opponent deserves a fresh start
self.last_fresh_start = current_round
self._fresh_start()
self.fresh_start = True
return C # First cooperation
if self.is_retaliating:
# Are we retaliating still?
self._decrease_retaliation_counter()
return D
if opponent.history[-1] == D:
self.is_retaliating = True
self.retaliation_length += 1
self.retaliation_remaining = self.retaliation_length
self._decrease_retaliation_counter()
return D
return C
```