Source code for axelrod.strategies.axelrod_second

"""
Strategies from Axelrod's second tournament. All strategies in this module are
prefixed by `SecondBy` to indicate that they were submitted in Axelrod's Second
tournament by the given author.
"""

from typing import List

import numpy as np
from axelrod.action import Action
from axelrod.interaction_utils import compute_final_score
from axelrod.player import Player
from axelrod.strategies.finite_state_machines import FSMPlayer

C, D = Action.C, Action.D


[docs] class SecondByChampion(Player): """ Strategy submitted to Axelrod's second tournament by Danny Champion. This player cooperates on the first 10 moves and plays Tit for Tat for the next 15 more moves. After 25 moves, the program cooperates unless all the following are true: the other player defected on the previous move, the other player cooperated less than 60% and the random number between 0 and 1 is greater that the other player's cooperation rate. Names: - Champion: [Axelrod1980b]_ """ name = "Second by Champion" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, }
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" current_round = len(self.history) # Cooperate for the first 10 turns if current_round == 0: return C if current_round < 10: return C # Mirror partner for the next phase if current_round < 25: return opponent.history[-1] # Now cooperate unless all of the necessary conditions are true defection_prop = opponent.defections / len(opponent.history) if opponent.history[-1] == D: r = self._random.random() if defection_prop >= max(0.4, r): return D return C
[docs] class SecondByEatherley(Player): """ Strategy submitted to Axelrod's second tournament by Graham Eatherley. A player that keeps track of how many times in the game the other player defected. After the other player defects, it defects with a probability equal to the ratio of the other's total defections to the total moves to that point. Names: - Eatherley: [Axelrod1980b]_ """ name = "Second by Eatherley" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, }
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" # Cooperate on the first move if not len(opponent.history): return C # Reciprocate cooperation if opponent.history[-1] == C: return C # Respond to defections with probability equal to opponent's total # proportion of defections defection_prop = opponent.defections / len(opponent.history) return self._random.random_choice(1 - defection_prop)
[docs] class SecondByTester(Player): """ Submitted to Axelrod's second tournament by David Gladstein. This strategy is a TFT variant that attempts to exploit certain strategies. It defects on the first move. If the opponent ever defects, TESTER 'apologies' by cooperating and then plays TFT for the rest of the game. Otherwise TESTER alternates cooperation and defection. This strategy came 46th in Axelrod's second tournament. Names: - Tester: [Axelrod1980b]_ """ name = "Second by Tester" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.is_TFT = False
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" # Defect on the first move if not opponent.history: return D # Am I TFT? if self.is_TFT: return D if opponent.history[-1:] == [D] else C else: # Did opponent defect? if opponent.history[-1] == D: self.is_TFT = True return C if len(self.history) in [1, 2]: return C # Alternate C and D return self.history[-1].flip()
[docs] class SecondByGladstein(Player): """ Submitted to Axelrod's second tournament by David Gladstein. This strategy is also known as Tester and is based on the reverse engineering of the Fortran strategies from Axelrod's second tournament. This strategy is a TFT variant that defects on the first round in order to test the opponent's response. If the opponent ever defects, the strategy 'apologizes' by cooperating and then plays TFT for the rest of the game. Otherwise, it defects as much as possible subject to the constraint that the ratio of its defections to moves remains under 0.5, not counting the first defection. Names: - Gladstein: [Axelrod1980b]_ - Tester: [Axelrod1980b]_ """ name = "Second by Gladstein" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() # This strategy assumes the opponent is a patsy self.patsy = True
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" # Defect on the first move if not self.history: return D # Is the opponent a patsy? if self.patsy: # If the opponent defects, apologize and play TFT. if opponent.history[-1] == D: self.patsy = False return C # Cooperate as long as the cooperation ratio is below 0.5 cooperation_ratio = self.cooperations / len(self.history) if cooperation_ratio > 0.5: return D return C else: # Play TFT return opponent.history[-1]
[docs] class SecondByTranquilizer(Player): """ Submitted to Axelrod's second tournament by Craig Feathers Description given in Axelrod's "More Effective Choice in the Prisoner's Dilemma" paper: The rule normally cooperates but is ready to defect if the other player defects too often. Thus the rule tends to cooperate for the first dozen or two moves if the other player is cooperating, but then it throws in a defection. If the other player continues to cooperate, then defections become more frequent. But as long as Tranquilizer is maintaining an average payoff of at least 2.25 points per move, it will never defect twice in succession and it will not defect more than one-quarter of the time. This implementation is based on the reverse engineering of the Fortran strategy K67R from Axelrod's second tournament. Reversed engineered by: Owen Campbell, Will Guo and Mansour Hakem. The strategy starts by cooperating and has 3 states. At the start of the strategy it updates its states: - It counts the number of consecutive defections by the opponent. - If it was in state 2 it moves to state 0 and calculates the following quantities two_turns_after_good_defection_ratio and two_turns_after_good_defection_ratio_count. Formula for: two_turns_after_good_defection_ratio: self.two_turns_after_good_defection_ratio = ( ((self.two_turns_after_good_defection_ratio * self.two_turns_after_good_defection_ratio_count) + (3 - (3 * self.dict[opponent.history[-1]])) + (2 * self.dict[self.history[-1]]) - ((self.dict[opponent.history[-1]] * self.dict[self.history[-1]]))) / (self.two_turns_after_good_defection_ratio_count + 1) ) two_turns_after_good_defection_ratio_count = two_turns_after_good_defection_ratio + 1 - If it was in state 1 it moves to state 2 and calculates the following quantities one_turn_after_good_defection_ratio and one_turn_after_good_defection_ratio_count. Formula for: one_turn_after_good_defection_ratio: self.one_turn_after_good_defection_ratio = ( ((self.one_turn_after_good_defection_ratio * self.one_turn_after_good_defection_ratio_count) + (3 - (3 * self.dict[opponent.history[-1]])) + (2 * self.dict[self.history[-1]]) - (self.dict[opponent.history[-1]] * self.dict[self.history[-1]])) / (self.one_turn_after_good_defection_ratio_count + 1) ) one_turn_after_good_defection_ratio_count: one_turn_after_good_defection_ratio_count = one_turn_after_good_defection_ratio + 1 If after this it is in state 1 or 2 then it cooperates. If it is in state 0 it will potentially perform 1 of the 2 following stochastic tests: 1. If average score per turn is greater than 2.25 then it calculates a value of probability: probability = ( (.95 - (((self.one_turn_after_good_defection_ratio) + (self.two_turns_after_good_defection_ratio) - 5) / 15)) + (1 / (((len(self.history))+1) ** 2)) - (self.dict[opponent.history[-1]] / 4) ) and will cooperate if a random sampled number is less than that value of probability. If it does not cooperate then the strategy moves to state 1 and defects. 2. If average score per turn is greater than 1.75 but less than 2.25 then it calculates a value of probability: probability = ( (.25 + ((opponent.cooperations + 1) / ((len(self.history)) + 1))) - (self.opponent_consecutive_defections * .25) + ((current_score[0] - current_score[1]) / 100) + (4 / ((len(self.history)) + 1)) ) and will cooperate if a random sampled number is less than that value of probability. If not, it defects. If none of the above holds the player simply plays tit for tat. Tranquilizer came in 27th place in Axelrod's second torunament. Names: - Tranquilizer: [Axelrod1980]_ """ name = "Second by Tranquilizer" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self): super().__init__() self.num_turns_after_good_defection = 0 # equal to FD variable self.opponent_consecutive_defections = 0 # equal to S variable self.one_turn_after_good_defection_ratio = 5 # equal to AD variable self.two_turns_after_good_defection_ratio = 0 # equal to NO variable self.one_turn_after_good_defection_ratio_count = ( 1 # equal to AK variable ) self.two_turns_after_good_defection_ratio_count = ( 1 # equal to NK variable ) # All above variables correspond to those in original Fotran Code self.dict = {C: 0, D: 1}
[docs] def update_state(self, opponent): """ Calculates the ratio values for the one_turn_after_good_defection_ratio, two_turns_after_good_defection_ratio and the probability values, and sets the value of num_turns_after_good_defection. """ if opponent.history[-1] == D: self.opponent_consecutive_defections += 1 else: self.opponent_consecutive_defections = 0 if self.num_turns_after_good_defection == 2: self.num_turns_after_good_defection = 0 self.two_turns_after_good_defection_ratio = ( ( self.two_turns_after_good_defection_ratio * self.two_turns_after_good_defection_ratio_count ) + (3 - (3 * self.dict[opponent.history[-1]])) + (2 * self.dict[self.history[-1]]) - ( ( self.dict[opponent.history[-1]] * self.dict[self.history[-1]] ) ) ) / (self.two_turns_after_good_defection_ratio_count + 1) self.two_turns_after_good_defection_ratio_count += 1 elif self.num_turns_after_good_defection == 1: self.num_turns_after_good_defection = 2 self.one_turn_after_good_defection_ratio = ( ( self.one_turn_after_good_defection_ratio * self.one_turn_after_good_defection_ratio_count ) + (3 - (3 * self.dict[opponent.history[-1]])) + (2 * self.dict[self.history[-1]]) - ( self.dict[opponent.history[-1]] * self.dict[self.history[-1]] ) ) / (self.one_turn_after_good_defection_ratio_count + 1) self.one_turn_after_good_defection_ratio_count += 1
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" if not self.history: return C self.update_state(opponent) if self.num_turns_after_good_defection in [1, 2]: return C current_score = compute_final_score(zip(self.history, opponent.history)) if (current_score[0] / ((len(self.history)) + 1)) >= 2.25: probability = ( ( 0.95 - ( ( (self.one_turn_after_good_defection_ratio) + (self.two_turns_after_good_defection_ratio) - 5 ) / 15 ) ) + (1 / (((len(self.history)) + 1) ** 2)) - (self.dict[opponent.history[-1]] / 4) ) if self._random.random() <= probability: return C self.num_turns_after_good_defection = 1 return D if (current_score[0] / ((len(self.history)) + 1)) >= 1.75: probability = ( ( 0.25 + ((opponent.cooperations + 1) / ((len(self.history)) + 1)) ) - (self.opponent_consecutive_defections * 0.25) + ((current_score[0] - current_score[1]) / 100) + (4 / ((len(self.history)) + 1)) ) if self._random.random() <= probability: return C return D return opponent.history[-1]
[docs] class SecondByGrofman(Player): """ Submitted to Axelrod's second tournament by Bernard Grofman. This strategy has 3 phases: 1. First it cooperates on the first two rounds 2. For rounds 3-7 inclusive, it plays the same as the opponent's last move 3. Thereafter, it applies the following logic, looking at its memory of the last 8\* rounds (ignoring the most recent round). - If its own previous move was C and the opponent has defected less than 3 times in the last 8\* rounds, cooperate - If its own previous move was C and the opponent has defected 3 or more times in the last 8\* rounds, defect - If its own previous move was D and the opponent has defected only once or not at all in the last 8\* rounds, cooperate - If its own previous move was D and the opponent has defected more than once in the last 8\* rounds, defect The code looks at the first 7 of the last 8 rounds, ignoring the most recent round. Names: - Grofman's strategy: [Axelrod1980b]_ - K86R: [Axelrod1980b]_ """ name = "Second by Grofman" classifier = { "memory_depth": 8, "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, }
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" # Cooperate on the first two moves if len(self.history) < 2: return C # For rounds 3-7, play the opponent's last move elif 2 <= len(self.history) <= 6: return opponent.history[-1] else: # Note: the Fortran code behavior ignores the opponent behavior # in the last round and instead looks at the first 7 of the last # 8 rounds. opponent_defections_last_8_rounds = opponent.history[-8:-1].count(D) if self.history[-1] == C and opponent_defections_last_8_rounds <= 2: return C if self.history[-1] == D and opponent_defections_last_8_rounds <= 1: return C return D
[docs] class SecondByKluepfel(Player): """ Strategy submitted to Axelrod's second tournament by Charles Kluepfel (K32R). This player keeps track of the the opponent's responses to own behavior: - `cd_count` counts: Opponent cooperates as response to player defecting. - `dd_count` counts: Opponent defects as response to player defecting. - `cc_count` counts: Opponent cooperates as response to player cooperating. - `dc_count` counts: Opponent defects as response to player cooperating. After 26 turns, the player then tries to detect a random player. The player decides that the opponent is random if cd_counts >= (cd_counts+dd_counts)/2 - 0.75*sqrt(cd_counts+dd_counts) AND cc_counts >= (dc_counts+cc_counts)/2 - 0.75*sqrt(dc_counts+cc_counts). If the player decides that they are playing against a random player, then they will always defect. Otherwise respond to recent history using the following set of rules: - If opponent's last three choices are the same, then respond in kind. - If opponent's last two choices are the same, then respond in kind with probability 90%. - Otherwise if opponent's last action was to cooperate, then cooperate with probability 70%. - Otherwise if opponent's last action was to defect, then defect with probability 60%. Names: - Kluepfel: [Axelrod1980b]_ """ name = "Second by Kluepfel" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self): super().__init__() self.cd_counts, self.dd_counts, self.dc_counts, self.cc_counts = ( 0, 0, 0, 0, )
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" # First update the response matrix. if len(self.history) >= 2: if self.history[-2] == D: if opponent.history[-1] == C: self.cd_counts += 1 else: self.dd_counts += 1 else: if opponent.history[-1] == C: self.cc_counts += 1 else: self.dc_counts += 1 # Check for randomness if len(self.history) > 26: if self.cd_counts >= ( self.cd_counts + self.dd_counts ) / 2 - 0.75 * np.sqrt( self.cd_counts + self.dd_counts ) and self.dc_counts >= ( self.dc_counts + self.cc_counts ) / 2 - 0.75 * np.sqrt( self.dc_counts + self.cc_counts ): return D # Otherwise respond to recent history one_move_ago, two_moves_ago, three_moves_ago = C, C, C if len(opponent.history) >= 1: one_move_ago = opponent.history[-1] if len(opponent.history) >= 2: two_moves_ago = opponent.history[-2] if len(opponent.history) >= 3: three_moves_ago = opponent.history[-3] if one_move_ago == two_moves_ago and two_moves_ago == three_moves_ago: return one_move_ago r = self._random.random() # Everything following is stochastic if one_move_ago == two_moves_ago: if r < 0.9: return one_move_ago else: return one_move_ago.flip() if one_move_ago == C: if r < 0.7: return one_move_ago else: return one_move_ago.flip() if one_move_ago == D: if r < 0.6: return one_move_ago else: return one_move_ago.flip()
[docs] class SecondByBorufsen(Player): """ Strategy submitted to Axelrod's second tournament by Otto Borufsen (K32R), and came in third in that tournament. This player keeps track of the the opponent's responses to own behavior: - `cd_count` counts: Opponent cooperates as response to player defecting. - `cc_count` counts: Opponent cooperates as response to player cooperating. The player has a defect mode and a normal mode. In defect mode, the player will always defect. In normal mode, the player obeys the following ranked rules: 1. If in the last three turns, both the player/opponent defected, then cooperate for a single turn. 2. If in the last three turns, the player/opponent acted differently from each other and they're alternating, then change next defect to cooperate. (Doesn't block third rule.) 3. Otherwise, do tit-for-tat. Start in normal mode, but every 25 turns starting with the 27th turn, re-evaluate the mode. Enter defect mode if any of the following conditions hold: - Detected random: Opponent cooperated 7-18 times since last mode evaluation (or start) AND less than 70% of opponent cooperation was in response to player's cooperation, i.e. cc_count / (cc_count+cd_count) < 0.7 - Detect defective: Opponent cooperated fewer than 3 times since last mode evaluation. When switching to defect mode, defect immediately. The first two rules for normal mode require that last three turns were in normal mode. When starting normal mode from defect mode, defect on first move. Names: - Borufsen: [Axelrod1980b]_ """ name = "Second by Borufsen" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self): super().__init__() self.cd_counts, self.cc_counts = 0, 0 self.mutual_defect_streak = 0 self.echo_streak = 0 self.flip_next_defect = False self.mode = "Normal"
[docs] def try_return(self, to_return): """ We put the logic here to check for the `flip_next_defect` bit here, and proceed like normal otherwise. """ if to_return == C: return C # Otherwise look for flip bit. if self.flip_next_defect: self.flip_next_defect = False return C return D
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" turn = len(self.history) + 1 if turn == 1: return C # Update the response history. if turn >= 3: if opponent.history[-1] == C: if self.history[-2] == C: self.cc_counts += 1 else: self.cd_counts += 1 # Check if it's time for a mode change. if turn > 2 and turn % 25 == 2: coming_from_defect = False if self.mode == "Defect": coming_from_defect = True self.mode = "Normal" coops = self.cd_counts + self.cc_counts # Check for a defective strategy if coops < 3: self.mode = "Defect" # Check for a random strategy if (8 <= coops <= 17) and self.cc_counts / coops < 0.7: self.mode = "Defect" self.cd_counts, self.cc_counts = 0, 0 # If defect mode, clear flags if self.mode == "Defect": self.mutual_defect_streak = 0 self.echo_streak = 0 self.flip_next_defect = False # Check this special case if self.mode == "Normal" and coming_from_defect: return D # Proceed if self.mode == "Defect": return D else: assert self.mode == "Normal" # Look for mutual defects if self.history[-1] == D and opponent.history[-1] == D: self.mutual_defect_streak += 1 else: self.mutual_defect_streak = 0 if self.mutual_defect_streak >= 3: self.mutual_defect_streak = 0 self.echo_streak = 0 # Reset both streaks. return self.try_return(C) # Look for echoes # Fortran code defaults two turns back to C if only second turn my_two_back, opp_two_back = C, C if turn >= 3: my_two_back = self.history[-2] opp_two_back = opponent.history[-2] if ( self.history[-1] != opponent.history[-1] and self.history[-1] == opp_two_back and opponent.history[-1] == my_two_back ): self.echo_streak += 1 else: self.echo_streak = 0 if self.echo_streak >= 3: self.mutual_defect_streak = 0 # Reset both streaks. self.echo_streak = 0 self.flip_next_defect = True # Tit-for-tat return self.try_return(opponent.history[-1])
[docs] class SecondByCave(Player): """ Strategy submitted to Axelrod's second tournament by Rob Cave (K49R), and came in fourth in that tournament. First look for overly-defective or apparently random opponents, and defect if found. That is any opponent meeting one of: - turn > 39 and percent defects > 0.39 - turn > 29 and percent defects > 0.65 - turn > 19 and percent defects > 0.79 Otherwise, respond to cooperation with cooperation. And respond to defections with either a defection (if opponent has defected at least 18 times) or with a random (50/50) choice. [Cooperate on first.] Names: - Cave: [Axelrod1980b]_ """ name = "Second by Cave" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, }
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" turn = len(self.history) + 1 if turn == 1: return C number_defects = opponent.defections perc_defects = number_defects / turn # Defect if the opponent has defected often or appears random. if turn > 39 and perc_defects > 0.39: return D if turn > 29 and perc_defects > 0.65: return D if turn > 19 and perc_defects > 0.79: return D if opponent.history[-1] == D: if number_defects > 17: return D else: return self._random.random_choice(0.5) else: return C
[docs] class SecondByWmAdams(Player): """ Strategy submitted to Axelrod's second tournament by William Adams (K44R), and came in fifth in that tournament. Count the number of opponent defections after their first move, call `c_defect`. Defect if c_defect equals 4, 7, or 9. If c_defect > 9, then defect immediately after opponent defects with probability = (0.5)^(c_defect-1). Otherwise cooperate. Names: - WmAdams: [Axelrod1980b]_ """ name = "Second by WmAdams" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, }
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" if len(self.history) <= 1: return C number_defects = opponent.defections if opponent.history[0] == D: number_defects -= 1 if number_defects in [4, 7, 9]: return D if number_defects > 9 and opponent.history[-1] == D: return self._random.random_choice((0.5) ** (number_defects - 9)) return C
[docs] class SecondByGraaskampKatzen(Player): """ Strategy submitted to Axelrod's second tournament by Jim Graaskamp and Ken Katzen (K60R), and came in sixth in that tournament. Play Tit-for-Tat at first, and track own score. At select checkpoints, check for a high score. Switch to Default Mode if: - On move 11, score < 23 - On move 21, score < 53 - On move 31, score < 83 - On move 41, score < 113 - On move 51, score < 143 - On move 101, score < 293 Once in Defect Mode, defect forever. Names: - GraaskampKatzen: [Axelrod1980b]_ """ name = "Second by GraaskampKatzen" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self): super().__init__() self.own_score = 0 self.mode = "Normal" def update_score(self, opponent: Player): game = self.match_attributes["game"] last_round = (self.history[-1], opponent.history[-1]) self.own_score += game.score(last_round)[0]
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" if self.mode == "Defect": return D turn = len(self.history) + 1 if turn == 1: return C self.update_score(opponent) if ( turn == 11 and self.own_score < 23 or turn == 21 and self.own_score < 53 or turn == 31 and self.own_score < 83 or turn == 41 and self.own_score < 113 or turn == 51 and self.own_score < 143 or turn == 101 and self.own_score < 293 ): self.mode = "Defect" return D return opponent.history[-1] # Tit-for-Tat
[docs] class SecondByWeiner(Player): """ Strategy submitted to Axelrod's second tournament by Herb Weiner (K41R), and came in seventh in that tournament. Play Tit-for-Tat with a chance for forgiveness and a defective override. The chance for forgiveness happens only if `forgive_flag` is raised (flag discussed below). If raised and `turn` is greater than `grudge`, then override Tit-for-Tat with Cooperation. `grudge` is a variable that starts at 0 and increments 20 with each forgiven Defect (a Defect that is overriden through the forgiveness logic). `forgive_flag` is lower whether logic is overriden or not. The variable `defect_padding` increments with each opponent Defect, but resets to zero with each opponent Cooperate (or `forgive_flag` lowering) so that it roughly counts Defects between Cooperates. Whenever the opponent Cooperates, if `defect_padding` (before reseting) is odd, then we raise `forgive_flag` for next turn. Finally a defective override is assessed after forgiveness. If five or more of the opponent's last twelve actions are Defects, then Defect. This will overrule a forgiveness, but doesn't undo the lowering of `forgiveness_flag`. Note that "last twelve actions" doesn't count the most recent action. Actually the original code updates history after checking for defect override. Names: - Weiner: [Axelrod1980b]_ """ name = "Second by Weiner" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self): super().__init__() self.forgive_flag = False self.grudge = 0 self.defect_padding = 0 self.last_twelve = [0] * 12 self.lt_index = 0 # Circles around last_twelve
[docs] def try_return(self, to_return): """ We put the logic here to check for the defective override. """ if np.sum(self.last_twelve) >= 5: return D return to_return
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" if len(opponent.history) == 0: return C # Update history, lag 1. if len(opponent.history) >= 2: self.last_twelve[self.lt_index] = 0 if opponent.history[-2] == D: self.last_twelve[self.lt_index] = 1 self.lt_index = (self.lt_index + 1) % 12 if self.forgive_flag: self.forgive_flag = False self.defect_padding = 0 if ( self.grudge < len(self.history) + 1 and opponent.history[-1] == D ): # Then override self.grudge += 20 return self.try_return(C) else: return self.try_return(opponent.history[-1]) else: # See if forgive_flag should be raised if opponent.history[-1] == D: self.defect_padding += 1 else: if self.defect_padding % 2 == 1: self.forgive_flag = True self.defect_padding = 0 return self.try_return(opponent.history[-1])
[docs] class SecondByHarrington(Player): """ Strategy submitted to Axelrod's second tournament by Paul Harrington (K75R) and came in eighth in that tournament. This strategy has three modes: Normal, Fair-weather, and Defect. These mode names were not present in Harrington's submission. In Normal and Fair-weather modes, the strategy begins by: - Update history - Try to detect random opponent if turn is multiple of 15 and >=30. - Check if `burned` flag should be raised. - Check for Fair-weather opponent if turn is 38. Updating history means to increment the correct cell of the `move_history`. `move_history` is a matrix where the columns are the opponent's previous move and the rows are indexed by the combo of this player's and the opponent's moves two turns ago. [The upper-left cell must be all Cooperations, but otherwise order doesn't matter.] After we enter Defect mode, `move_history` won't be used again. If the turn is a multiple of 15 and >=30, then attempt to detect random. If random is detected, enter Defect mode and defect immediately. If the player was previously in Defect mode, then do not re-enter. The random detection logic is a modified Pearson's Chi Squared test, with some additional checks. [More details in `detect_random` docstrings.] Some of this player's moves are marked as "generous." If this player made a generous move two turns ago and the opponent replied with a Defect, then raise the `burned` flag. This will stop certain generous moves later. The player mostly plays Tit-for-Tat for the first 36 moves, then defects on the 37th move. If the opponent cooperates on the first 36 moves, and defects on the 37th move also, then enter Fair-weather mode and cooperate this turn. Entering Fair-weather mode is extremely rare, since this can only happen if the opponent cooperates for the first 36 then defects unprovoked on the 37th. (That is, this player's first 36 moves are also Cooperations, so there's nothing really to trigger an opponent Defection.) Next in Normal Mode: 1. Check for defect and parity streaks. 2. Check if cooperations are scheduled. 3. Otherwise, - If turn < 37, Tit-for-Tat. - If turn = 37, defect, mark this move as generous, and schedule two more cooperations**. - If turn > 37, then if `burned` flag is raised, then Tit-for-Tat. Otherwise, Tit-for-Tat with probability 1 - `prob`. And with probability `prob`, defect, schedule two cooperations, mark this move as generous, and increase `prob` by 5%. ** Scheduling two cooperations means to set `more_coop` flag to two. If in Normal mode and no streaks are detected, then the player will cooperate and lower this flag, until hitting zero. It's possible that the flag can be overwritten. Notable on the 37th turn defect, this is set to two, but the 38th turn Fair-weather check will set this. If the opponent's last twenty moves were defections, then defect this turn. Then check for a parity streak, by flipping the parity bit (there are two streaks that get tracked which are something like odd and even turns, but this flip bit logic doesn't get run every turn), then incrementing the parity streak that we're pointing to. If the parity streak that we're pointing to is then greater than `parity_limit` then reset the streak and cooperate immediately. `parity_limit` is initially set to five, but after it has been hit eight times, it decreases to three. The parity streak that we're pointing to also gets incremented if in normal mode and we defect but not on turn 38, unless we are defecting as the result of a defect streak. Note that the parity streaks resets but the defect streak doesn't. If `more_coop` >= 1, then we cooperate and lower that flag here, in Normal mode after checking streaks. Still lower this flag if cooperating as the result of a parity streak or in Fair-weather mode. Then use the logic based on turn from above. In Fair-Weather mode after running the code from above, check if opponent defected last turn. If so, exit Fair-Weather mode, and proceed THIS TURN with Normal mode. Otherwise cooperate. In Defect mode, update the `exit_defect_meter` (originally zero) by incrementing if opponent defected last turn and decreasing by three otherwise. If `exit_defect_meter` is then 11, then set mode to Normal (for future turns), cooperate and schedule two more cooperations. [Note that this move is not marked generous.] Names: - Harrington: [Axelrod1980b]_ """ name = "Second by Harrington" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self): super().__init__() self.mode = "Normal" self.recorded_defects = 0 # Count opponent defects after turn 1 self.exit_defect_meter = 0 # When >= 11, then exit defect mode. self.coops_in_first_36 = ( None # On turn 37, count cooperations in first 36 ) self.was_defective = False # Previously in Defect mode self.prob = 0.25 # After turn 37, probability that we'll defect self.move_history = np.zeros([4, 2]) self.more_coop = 0 # This schedules cooperation for future turns # Initial last_generous_n_turns_ago to 3 because this counts up and # triggers a strategy change at 2. self.last_generous_n_turns_ago = ( 3 # How many tuns ago was a "generous" move ) self.burned = False self.defect_streak = 0 self.parity_streak = [ 0, 0, ] # Counters that get (almost) alternatively incremented. self.parity_bit = 0 # Which parity_streak to increment self.parity_limit = ( 5 # When a parity streak hits this limit, alter strategy. ) self.parity_hits = 0 # Counts how many times a parity_limit was hit. # After hitting parity_hits 8 times, lower parity_limit to 3.
[docs] def try_return(self, to_return, lower_flags=True, inc_parity=False): """ This will return to_return, with some end-of-turn logic. """ if lower_flags and to_return == C: # In most cases when Cooperating, we want to reduce the number that # are scheduled. self.more_coop -= 1 self.last_generous_n_turns_ago += 1 if inc_parity and to_return == D: # In some cases we increment the `parity_streak` that we're on when # we return a Defection. In detect_parity_streak, `parity_streak` # counts opponent's Defections. self.parity_streak[self.parity_bit] += 1 return to_return
[docs] def calculate_chi_squared(self, turn): """ Pearson's Chi Squared statistic = sum[ (E_i-O_i)^2 / E_i ], where O_i are the observed matrix values, and E_i is calculated as number (of defects) in the row times the number in the column over (total number in the matrix minus 1). Equivalently, we expect we expect (for an independent distribution) the total number of recorded turns times the portion in that row times the portion in that column. In this function, the statistic is non-standard in that it excludes summands where E_i <= 1. """ denom = turn - 2 expected_matrix = ( np.outer( self.move_history.sum(axis=1), self.move_history.sum(axis=0) ) / denom ) chi_squared = 0.0 for i in range(4): for j in range(2): expect = expected_matrix[i, j] if expect > 1.0: chi_squared += ( expect - self.move_history[i, j] ) ** 2 / expect return chi_squared
[docs] def detect_random(self, turn): """ We check if the top-left cell of the matrix (corresponding to all Cooperations) has over 80% of the turns. In which case, we label non-random. Then we check if over 75% or under 25% of the opponent's turns are Defections. If so, then we label as non-random. Otherwise we calculates a modified Pearson's Chi Squared statistic on self.history, and returns True (is random) if and only if the statistic is less than or equal to 3. """ denom = turn - 2 if self.move_history[0, 0] / denom >= 0.8: return False if ( self.recorded_defects / denom < 0.25 or self.recorded_defects / denom > 0.75 ): return False if self.calculate_chi_squared(turn) > 3: return False return True
[docs] def detect_streak(self, last_move): """ Return true if and only if the opponent's last twenty moves are defects. """ if last_move == D: self.defect_streak += 1 else: self.defect_streak = 0 if self.defect_streak >= 20: return True return False
[docs] def detect_parity_streak(self, last_move): """ Switch which `parity_streak` we're pointing to and incerement if the opponent's last move was a Defection. Otherwise reset the flag. Then return true if and only if the `parity_streak` is at least `parity_limit`. This is similar to detect_streak with alternating streaks, except that these streaks get incremented elsewhere as well. """ self.parity_bit = 1 - self.parity_bit # Flip bit if last_move == D: self.parity_streak[self.parity_bit] += 1 else: self.parity_streak[self.parity_bit] = 0 if self.parity_streak[self.parity_bit] >= self.parity_limit: return True
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" turn = len(self.history) + 1 if turn == 1: return C if self.mode == "Defect": # There's a chance to exit Defect mode. if opponent.history[-1] == D: self.exit_defect_meter += 1 else: self.exit_defect_meter -= 3 # If opponent has been mostly defecting. if self.exit_defect_meter >= 11: self.mode = "Normal" self.was_defective = True self.more_coop = 2 return self.try_return(to_return=C, lower_flags=False) return self.try_return(D) # If not Defect mode, proceed to update history and check for random, # check if burned, and check if opponent's fairweather. # If we haven't yet entered Defect mode if not self.was_defective: if turn > 2: if opponent.history[-1] == D: self.recorded_defects += 1 # Column decided by opponent's last turn history_col = 1 if opponent.history[-1] == D else 0 # Row is decided by opponent's move two turns ago and our move # two turns ago. history_row = 1 if opponent.history[-2] == D else 0 if self.history[-2] == D: history_row += 2 self.move_history[history_row, history_col] += 1 # Try to detect random opponent if turn % 15 == 0 and turn > 15: if self.detect_random(turn): self.mode = "Defect" return self.try_return( D, lower_flags=False ) # Lower_flags not used here. # If generous 2 turns ago and opponent defected last turn if self.last_generous_n_turns_ago == 2 and opponent.history[-1] == D: self.burned = True # Only enter Fair-weather mode if the opponent Cooperated the first 37 # turns then Defected on the 38th. if ( turn == 38 and opponent.history[-1] == D and opponent.cooperations == 36 ): self.mode = "Fair-weather" return self.try_return(to_return=C, lower_flags=False) if self.mode == "Fair-weather": if opponent.history[-1] == D: self.mode = "Normal" # Post-Defect is not possible # Proceed with Normal mode this turn. else: # Never defect against a fair-weather opponent return self.try_return(C) # Continue with Normal mode # Check for streaks if self.detect_streak(opponent.history[-1]): return self.try_return(D, inc_parity=True) if self.detect_parity_streak(opponent.history[-1]): self.parity_streak[ self.parity_bit ] = 0 # Reset `parity_streak` when we hit the limit. self.parity_hits += ( 1 # Keep track of how many times we hit the limit. ) if self.parity_hits >= 8: # After 8 times, lower the limit. self.parity_limit = 3 return self.try_return( C, inc_parity=True ) # Inc parity won't get used here. # If we have Cooperations scheduled, then Cooperate here. if self.more_coop >= 1: return self.try_return(C, lower_flags=True, inc_parity=True) if turn < 37: # Tit-for-Tat return self.try_return(opponent.history[-1], inc_parity=True) if turn == 37: # Defect once on turn 37 (if no streaks) self.more_coop, self.last_generous_n_turns_ago = 2, 1 return self.try_return(D, lower_flags=False) if self.burned or self._random.random() > self.prob: # Tit-for-Tat with probability 1-`prob` return self.try_return(opponent.history[-1], inc_parity=True) # Otherwise Defect, Cooperate, Cooperate, and increase `prob` self.prob += 0.05 self.more_coop, self.last_generous_n_turns_ago = 2, 1 return self.try_return(D, lower_flags=False)
[docs] class SecondByTidemanAndChieruzzi(Player): """ Strategy submitted to Axelrod's second tournament by T. Nicolaus Tideman and Paula Chieruzzi (K84R) and came in ninth in that tournament. This strategy Cooperates if this player's score exceeds the opponent's score by at least `score_to_beat`. `score_to_beat` starts at zero and increases by `score_to_beat_inc` every time the opponent's last two moves are a Cooperation and Defection in that order. `score_to_beat_inc` itself increase by 5 every time the opponent's last two moves are a Cooperation and Defection in that order. Additionally, the strategy executes a "fresh start" if the following hold: - The strategy would Defect by score (difference less than `score_to_beat`) - The opponent did not Cooperate and Defect (in order) in the last two turns. - It's been at least 10 turns since the last fresh start. Or since the match started if there hasn't been a fresh start yet. A "fresh start" entails two Cooperations and resetting scores, `scores_to_beat` and `scores_to_beat_inc`. Names: - TidemanAndChieruzzi: [Axelrod1980b]_ """ name = "Second by Tideman and Chieruzzi" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.current_score = 0 self.opponent_score = 0 self.last_fresh_start = 0 self.fresh_start = False self.score_to_beat = 0 self.score_to_beat_inc = 0 def _fresh_start(self): """Give the opponent a fresh start by forgetting the past""" self.current_score = 0 self.opponent_score = 0 self.score_to_beat = 0 self.score_to_beat_inc = 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: """Actual strategy definition that determines player's action.""" current_round = len(self.history) + 1 if current_round == 1: 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() self.last_fresh_start = current_round self.fresh_start = False return C # Second cooperation opponent_CDd = False opponent_two_turns_ago = C # Default value for second turn. if len(opponent.history) >= 2: opponent_two_turns_ago = opponent.history[-2] # If opponent's last two turns are C and D in that order. if opponent_two_turns_ago == C and opponent.history[-1] == D: opponent_CDd = True self.score_to_beat += self.score_to_beat_inc self.score_to_beat_inc += 5 # Cooperate if we're beating opponent by at least `score_to_beat` if self.current_score - self.opponent_score >= self.score_to_beat: return C # Wait at least ten turns for another fresh start. if (not opponent_CDd) and current_round - self.last_fresh_start >= 10: # 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.fresh_start = True return C # First cooperation return D
[docs] class SecondByGetzler(Player): """ Strategy submitted to Axelrod's second tournament by Abraham Getzler (K35R) and came in eleventh in that tournament. Strategy Defects with probability `flack`, where `flack` is calculated as the sum over opponent Defections of 0.5 ^ (turns ago Defection happened). Names: - Getzler: [Axelrod1980b]_ """ name = "Second by Getzler" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.flack = 0.0 # The relative untrustworthiness of opponent
[docs] def strategy(self, opponent: Player) -> Action: """Actual strategy definition that determines player's action.""" if not opponent.history: return C self.flack += 1 if opponent.history[-1] == D else 0 self.flack *= 0.5 # Defections have half-life of one round return self._random.random_choice(1.0 - self.flack)
[docs] class SecondByLeyvraz(Player): """ Strategy submitted to Axelrod's second tournament by Fransois Leyvraz (K68R) and came in twelfth in that tournament. The strategy uses the opponent's last three moves to decide on an action based on the following ordered rules. 1. If opponent Defected last two turns, then Defect with prob 75%. 2. If opponent Defected three turns ago, then Cooperate. 3. If opponent Defected two turns ago, then Defect. 4. If opponent Defected last turn, then Defect with prob 50%. 5. Otherwise (all Cooperations), then Cooperate. Names: - Leyvraz: [Axelrod1980b]_ """ name = "Second by Leyvraz" classifier = { "memory_depth": 3, "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.prob_coop = { (C, C, C): 1.0, (C, C, D): 0.5, # Rule 4 (C, D, C): 0.0, # Rule 3 (C, D, D): 0.25, # Rule 1 (D, C, C): 1.0, # Rule 2 (D, C, D): 1.0, # Rule 2 (D, D, C): 1.0, # Rule 2 (D, D, D): 0.25, # Rule 1 }
[docs] def strategy(self, opponent: Player) -> Action: recent_history = [C, C, C] # Default to C. for go_back in range(1, 4): if len(opponent.history) >= go_back: recent_history[-go_back] = opponent.history[-go_back] return self._random.random_choice( self.prob_coop[ (recent_history[-3], recent_history[-2], recent_history[-1]) ] )
[docs] class SecondByWhite(Player): """ Strategy submitted to Axelrod's second tournament by Edward C White (K72R) and came in thirteenth in that tournament. * Cooperate in the first ten turns. * If the opponent Cooperated last turn then Cooperate. * Otherwise Defect if and only if: floor(log(turn)) * opponent Defections >= turn Names: - White: [Axelrod1980b]_ """ name = "Second by White" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, }
[docs] def strategy(self, opponent: Player) -> Action: turn = len(self.history) + 1 if turn <= 10 or opponent.history[-1] == C: return C if np.floor(np.log(turn)) * opponent.defections >= turn: return D return C
[docs] class SecondByBlack(Player): """ Strategy submitted to Axelrod's second tournament by Paul E Black (K83R) and came in fifteenth in that tournament. The strategy Cooperates for the first five turns. Then it calculates the number of opponent defects in the last five moves and Cooperates with probability `prob_coop`[`number_defects`], where: prob_coop[number_defects] = 1 - (number_defects^ 2 - 1) / 25 Names: - Black: [Axelrod1980b]_ """ name = "Second by Black" classifier = { "memory_depth": 5, "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() # Maps number of opponent defects from last five moves to own # Cooperation probability self.prob_coop = {0: 1.0, 1: 1.0, 2: 0.88, 3: 0.68, 4: 0.4, 5: 0.04}
[docs] def strategy(self, opponent: Player) -> Action: if len(opponent.history) < 5: return C recent_history = opponent.history[-5:] did_d = np.vectorize(lambda action: int(action == D)) number_defects = sum(did_d(recent_history)) return self._random.random_choice(self.prob_coop[number_defects])
[docs] class SecondByRichardHufford(Player): """ Strategy submitted to Axelrod's second tournament by Richard Hufford (K47R) and came in sixteenth in that tournament. The strategy tracks opponent "agreements", that is whenever the opponent's previous move is the some as this player's move two turns ago. If the opponent's first move is a Defection, this is counted as a disagreement, and otherwise an agreement. From the agreement counts, two measures are calculated: - `proportion_agree`: This is the number of agreements (through opponent's last turn) + 2 divided by the current turn number. - `last_four_num`: The number of agreements in the last four turns. If there have been fewer than four previous turns, then this is number of agreement + (4 - number of past turns). We then use these measures to decide how to play, using these rules: 1. If `proportion_agree` > 0.9 and `last_four_num` >= 4, then Cooperate. 2. Otherwise if `proportion_agree` >= 0.625 and `last_four_num` >= 2, then Tit-for-Tat. 3. Otherwise, Defect. However, if the opponent has Cooperated the last `streak_needed` turns, then the strategy deviates from the usual strategy, and instead Defects. (We call such deviation an "aberration".) In the turn immediately after an aberration, the strategy doesn't override, even if there's a streak of Cooperations. Two turns after an aberration, the strategy: Restarts the Cooperation streak (never looking before this turn); Cooperates; and changes `streak_needed` to: floor(20.0 * `num_abb_def` / `num_abb_coop`) + 1 Here `num_abb_def` is 2 + the number of times that the opponent Defected in the turn after an aberration, and `num_abb_coop` is 2 + the number of times that the opponent Cooperated in response to an aberration. Names: - RichardHufford: [Axelrod1980b]_ """ name = "Second by RichardHufford" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.num_agreements = 2 self.last_four_agreements = [1] * 4 self.last_four_index = 0 self.streak_needed = 21 self.current_streak = 2 self.last_aberration = float("inf") self.coop_after_ab_count = 2 self.def_after_ab_count = 2
[docs] def strategy(self, opponent: Player) -> Action: turn = len(self.history) + 1 if turn == 1: return C # Check if opponent agreed with us. self.last_four_index = (self.last_four_index + 1) % 4 me_two_moves_ago = C if turn > 2: me_two_moves_ago = self.history[-2] if me_two_moves_ago == opponent.history[-1]: self.num_agreements += 1 self.last_four_agreements[self.last_four_index] = 1 else: self.last_four_agreements[self.last_four_index] = 0 # Check if last_aberration is infinite. # i.e Not an aberration in last two turns. if turn < self.last_aberration: if opponent.history[-1] == C: self.current_streak += 1 else: self.current_streak = 0 if self.current_streak >= self.streak_needed: self.last_aberration = turn if self.current_streak == self.streak_needed: return D elif turn == self.last_aberration + 2: self.last_aberration = float("inf") if opponent.history[-1] == C: self.coop_after_ab_count += 1 else: self.def_after_ab_count += 1 self.streak_needed = ( np.floor( 20.0 * self.def_after_ab_count / self.coop_after_ab_count ) + 1 ) self.current_streak = 0 return C proportion_agree = self.num_agreements / turn last_four_num = sum(self.last_four_agreements) if proportion_agree > 0.9 and last_four_num >= 4: return C elif proportion_agree >= 0.625 and last_four_num >= 2: return opponent.history[-1] return D
[docs] class SecondByYamachi(Player): """ Strategy submitted to Axelrod's second tournament by Brian Yamachi (K64R) and came in seventeenth in that tournament. The strategy keeps track of play history through a variable called `count_them_us_them`, which is a dict indexed by (X, Y, Z), where X is an opponent's move and Y and Z are the following moves by this player and the opponent, respectively. Each turn, we look at our opponent's move two turns ago, call X, and our move last turn, call Y. If (X, Y, C) has occurred more often (or as often) as (X, Y, D), then Cooperate. Otherwise Defect. [Note that this reflects likelihood of Cooperations or Defections in opponent's previous move; we don't update `count_them_us_them` with previous move until next turn.] Starting with the 41st turn, there's a possibility to override this behavior. If `portion_defect` is between 45% and 55% (exclusive), then Defect, where `portion_defect` equals number of opponent defects plus 0.5 divided by the turn number (indexed by 1). When overriding this way, still record `count_them_us_them` as though the strategy didn't override. Names: - Yamachi: [Axelrod1980b]_ """ name = "Second by Yamachi" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.count_them_us_them = { (C, C, C): 0, (C, C, D): 0, (C, D, C): 0, (C, D, D): 0, (D, C, C): 0, (D, C, D): 0, (D, D, C): 0, (D, D, D): 0, } self.mod_history = list() # type: List[Action]
[docs] def try_return(self, to_return, opp_def): """ Return `to_return`, unless the turn is greater than 40 AND `portion_defect` is between 45% and 55%. In this case, still record the history as `to_return` so that the modified behavior doesn't affect the calculation of `count_us_them_us`. """ turn = len(self.history) + 1 self.mod_history.append(to_return) # In later turns, check if the opponent is close to 50/50 # If so, then override if turn > 40: portion_defect = (opp_def + 0.5) / turn if 0.45 < portion_defect < 0.55: return D return to_return
[docs] def strategy(self, opponent: Player) -> Action: turn = len(self.history) + 1 if turn == 1: return self.try_return(C, 0) us_last = self.mod_history[-1] them_two_ago, us_two_ago, them_three_ago = C, C, C if turn >= 3: them_two_ago = opponent.history[-2] us_two_ago = self.mod_history[-2] if turn >= 4: them_three_ago = opponent.history[-3] # Update history if turn >= 3: self.count_them_us_them[ (them_three_ago, us_two_ago, them_two_ago) ] += 1 if ( self.count_them_us_them[(them_two_ago, us_last, C)] >= self.count_them_us_them[(them_two_ago, us_last, D)] ): return self.try_return(C, opponent.defections) return self.try_return(D, opponent.defections)
[docs] class SecondByColbert(FSMPlayer): """ Strategy submitted to Axelrod's second tournament by William Colbert (K51R) and came in eighteenth in that tournament. In the first eight turns, this strategy Coopearates on all but the sixth turn, in which it Defects. After that, the strategy responds to an opponent Cooperation with a single Cooperation, and responds to a Defection with a chain of responses: Defect, Defect, Cooperate, Cooperate. During this chain, the strategy ignores opponent's moves. Names: - Colbert: [Axelrod1980b]_ """ name = "Second by Colbert" classifier = { "memory_depth": 4, "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: transitions = ( (0, C, 1, C), (0, D, 1, C), # First 8 turns are special (1, C, 2, C), (1, D, 2, C), (2, C, 3, C), (2, D, 3, C), (3, C, 4, C), (3, D, 4, C), (4, C, 5, D), (4, D, 5, D), # Defect on 6th turn. (5, C, 6, C), (5, D, 6, C), (6, C, 7, C), (6, D, 7, C), (7, C, 7, C), (7, D, 8, D), (8, C, 9, D), (8, D, 9, D), (9, C, 10, C), (9, D, 10, C), (10, C, 7, C), (10, D, 7, C), ) super().__init__( transitions=transitions, initial_state=0, initial_action=C )
[docs] class SecondByMikkelson(FSMPlayer): """ Strategy submitted to Axelrod's second tournament by Ray Mikkelson (K66R) and came in twentieth in that tournament. The strategy keeps track of a variable called `credit`, which determines if the strategy will Cooperate, in the sense that if `credit` is positive, then the strategy Cooperates. `credit` is initialized to 7. After the first turn, `credit` increments if the opponent Cooperated last turn, and decreases by two otherwise. `credit` is capped above by 8 and below by -7. [`credit` is assessed as postive or negative, after increasing based on opponent's last turn.] If `credit` is non-positive within the first ten turns, then the strategy Defects and `credit` is set to 4. If `credit` is non-positive later, then the strategy Defects if and only if (total # opponent Defections) / (turn#) is at least 15%. [Turn # starts at 1.] Names: - Mikkelson: [Axelrod1980b]_ """ name = "Second by Mikkelson" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.credit = 7
[docs] def strategy(self, opponent: Player) -> Action: turn = len(self.history) + 1 if turn == 1: return C if opponent.history[-1] == C: self.credit += 1 if self.credit > 8: self.credit = 8 else: self.credit -= 2 if self.credit < -7: self.credit = -7 if turn == 2: return C if self.credit > 0: return C if turn <= 10: self.credit = 4 return D if opponent.defections / turn >= 0.15: return D return C
[docs] class SecondByRowsam(Player): """ Strategy submitted to Axelrod's second tournament by Glen Rowsam (K58R) and came in 21st in that tournament. The strategy starts in Normal mode, where it cooperates every turn. Every six turns it checks the score per turn. [Rather the score of all previous turns divided by the turn number, which will be one more than the number of turns scored.] If this measure is less than 2.5 (the strategy is doing badly) and it increases `distrust_points`. `distrust_points` is a variable that starts at 0; if it ever exceeds 6 points, the strategy will enter Defect mode and defect from then on. It will increase `distrust_points` depending on the precise score per turn according to: - 5 points if score per turn is less than 1.0 - 3 points if score per turn is less than 1.5, but at least 1.0 - 2 points if score per turn is less than 2.0, but at least 1.5 - 1 points if score per turn is less than 2.5, but at least 2.0 If `distrust_points` are increased, then the strategy defects on that turn, then cooperates and defects on the next two turns. [Unless `distrust_points` exceeds 6 points, then it will enter Defect mode immediately.] Every 18 turns in Normal mode, the strategy will decrement `distrust_score` if it's more than 3. This represents a wearing off effect of distrust. Names: - Rowsam: [Axelrod1980b]_ """ name = "Second by Rowsam" classifier = { "memory_depth": float("inf"), "stochastic": False, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() self.mode = "Normal" self.distrust_points = 0 self.current_score = 0 self.opponent_score = 0 def _score_last_round(self, opponent: Player): """Updates the scores for each player.""" 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: turn = len(self.history) + 1 if turn > 1: self._score_last_round(opponent) if self.mode == "Defect": return D if self.mode == "Coop Def Cycle 1": self.mode = "Coop Def Cycle 2" return C if self.mode == "Coop Def Cycle 2": self.mode = "Normal" return D # Opportunity for distrust to cool off. if turn % 18 == 0: if self.distrust_points >= 3: self.distrust_points -= 1 # In normal mode, only check for strategy updates every sixth turn. if turn % 6 != 0: return C points_per_turn = self.current_score / turn # Off by one if points_per_turn < 1.0: self.distrust_points += 5 elif points_per_turn < 1.5: self.distrust_points += 3 elif points_per_turn < 2.0: self.distrust_points += 2 elif points_per_turn < 2.5: self.distrust_points += 1 else: # Continue Cooperating return C if self.distrust_points >= 7: self.mode = "Defect" else: # Def this time, then coop, then def. self.mode = "Coop Def Cycle 1" return D
[docs] class SecondByAppold(Player): """ Strategy submitted to Axelrod's second tournament by Scott Appold (K88R) and came in 22nd in that tournament. Cooperates for first four turns. After four turns, will cooperate immediately following the first time the opponent cooperates (starting with the opponent's fourth move). Otherwise will cooperate with probability equal to: - If this strategy defected two turns ago, the portion of the time (historically) that the opponent followed a defection with a cooperation. - If this strategy cooperated two turns ago, the portion of the time (historically) that the opponent followed a cooperation with a cooperation. The opponent's first move is counted as a response to a cooperation. Names: - Appold: [Axelrod1980b]_ """ name = "Second by Appold" classifier = { "memory_depth": float("inf"), "stochastic": True, "long_run_time": False, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self) -> None: super().__init__() # Probability of a cooperation after an x is: # opp_c_after_x / total_num_of_x. self.opp_c_after_x = {C: 0, D: 1} # This is the total counted, so it doesn't include the most recent. self.total_num_of_x = {C: 0, D: 1} self.first_opp_def = False
[docs] def strategy(self, opponent: Player) -> Action: turn = len(self.history) + 1 us_two_turns_ago = C if turn <= 2 else self.history[-2] # Update trackers if turn > 1: self.total_num_of_x[us_two_turns_ago] += 1 if turn > 1 and opponent.history[-1] == C: self.opp_c_after_x[us_two_turns_ago] += 1 if turn <= 4: return C if opponent.history[-1] == D and not self.first_opp_def: self.first_opp_def = True return C # Calculate the probability that the opponent cooperated last turn given # what we know two turns ago. prob_coop = ( self.opp_c_after_x[us_two_turns_ago] / self.total_num_of_x[us_two_turns_ago] ) return self._random.random_choice(prob_coop)