Source code for axelrod.strategies.meta

import random

from axelrod.action import Action
from axelrod.player import Player, obey_axelrod
from axelrod.strategies import TitForTat
from axelrod.strategy_transformers import NiceTransformer
from numpy.random import choice

from ._strategies import all_strategies
from .hunter import (
    AlternatorHunter,
    CooperatorHunter,
    CycleHunter,
    DefectorHunter,
    EventualCycleHunter,
    MathConstantHunter,
    RandomHunter,
)

# Needs to be computed manually to prevent circular dependency
ordinary_strategies = [s for s in all_strategies if obey_axelrod(s)]
C, D = Action.C, Action.D


[docs]class MetaPlayer(Player): """ A generic player that has its own team of players. Names: - Meta Player: Original name by Karol Langner """ name = "Meta Player" classifier = { "memory_depth": float("inf"), # Long memory "stochastic": True, "makes_use_of": {"game", "length"}, "long_run_time": True, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self, team=None): super().__init__() # The default is to use all strategies available, but we need to import # the list at runtime, since _strategies import also _this_ module # before defining the list. if team: self.team = team else: # Needs to be computed manually to prevent circular dependency self.team = ordinary_strategies # Make sure we don't use any meta players to avoid infinite recursion. self.team = [t for t in self.team if not issubclass(t, MetaPlayer)] self.nteam = len(self.team) # Initiate all the player in our team. self.team = [t() for t in self.team] # This player inherits the classifiers of its team. # Note that memory_depth is not simply the max memory_depth of the team. for key in [ "stochastic", "inspects_source", "manipulates_source", "manipulates_state", ]: self.classifier[key] = any(t.classifier[key] for t in self.team) for t in self.team: self.classifier["makes_use_of"].update(t.classifier["makes_use_of"]) def __repr__(self): team_size = len(self.team) return "{}: {} player{}".format( self.name, team_size, "s" if team_size > 1 else "" )
[docs] def strategy(self, opponent): # Get the results of all our players. results = [] for player in self.team: play = player.strategy(opponent) player.history.append(play) results.append(play) # A subclass should just define a way to choose the result based on # team results. return self.meta_strategy(results, opponent)
[docs] def meta_strategy(self, results, opponent): """Determine the meta result based on results of all players. Override this function in child classes.""" return C
[docs]class MetaMajority(MetaPlayer): """A player who goes by the majority vote of all other non-meta players. Names: - Meta Marjority: Original name by Karol Langner """ name = "Meta Majority" def __init__(self, team=None): super().__init__(team=team)
[docs] @staticmethod def meta_strategy(results, opponent): if results.count(D) > results.count(C): return D return C
[docs]class MetaMinority(MetaPlayer): """A player who goes by the minority vote of all other non-meta players. Names: - Meta Minority: Original name by Karol Langner """ name = "Meta Minority" def __init__(self, team=None): super().__init__(team=team)
[docs] @staticmethod def meta_strategy(results, opponent): if results.count(D) < results.count(C): return D return C
[docs]class MetaWinner(MetaPlayer): """A player who goes by the strategy of the current winner. Names: - Meta Winner: Original name by Karol Langner """ name = "Meta Winner" def __init__(self, team=None): super().__init__(team=team) # For each player, we will keep the history of proposed moves and # a running score since the beginning of the game. self.scores = [0] * len(self.team) self.classifier["long_run_time"] = True def _update_scores(self, opponent): # Update the running score for each player, before determining the # next move. game = self.match_attributes["game"] if len(self.history): for i, player in enumerate(self.team): last_round = (player.history[-1], opponent.history[-1]) s = game.scores[last_round][0] self.scores[i] += s
[docs] def meta_strategy(self, results, opponent): self._update_scores(opponent) # Choice an action based on the collection of scores bestscore = max(self.scores) beststrategies = [ i for (i, score) in enumerate(self.scores) if score == bestscore ] bestproposals = [results[i] for i in beststrategies] bestresult = C if C in bestproposals else D return bestresult
NiceMetaWinner = NiceTransformer()(MetaWinner)
[docs]class MetaWinnerEnsemble(MetaWinner): """A variant of MetaWinner that chooses one of the top scoring strategies at random against each opponent. Note this strategy is always stochastic regardless of the team. Names: - Meta Winner Ensemble: Original name by Marc Harper """ name = "Meta Winner Ensemble"
[docs] def meta_strategy(self, results, opponent): self._update_scores(opponent) # Sort by score scores = [(score, i) for (i, score) in enumerate(self.scores)] # Choose one of the best scorers at random scores.sort(reverse=True) prop = max(1, int(len(scores) * 0.08)) index = choice([i for (s, i) in scores[:prop]]) return results[index]
NiceMetaWinnerEnsemble = NiceTransformer()(MetaWinnerEnsemble)
[docs]class MetaHunter(MetaPlayer): """A player who uses a selection of hunters. Names - Meta Hunter: Original name by Karol Langner """ name = "Meta Hunter" 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): # Notice that we don't include the cooperator hunter, because it leads # to excessive defection and therefore bad performance against # unforgiving strategies. We will stick to hunters that use defections # as cues. However, a really tangible benefit comes from combining # Random Hunter and Math Constant Hunter, since together they catch # strategies that are lightly randomized but still quite constant # (the tricky/suspicious ones). team = [ DefectorHunter, AlternatorHunter, RandomHunter, MathConstantHunter, CycleHunter, EventualCycleHunter, ] super().__init__(team=team)
[docs] @staticmethod def meta_strategy(results, opponent): # If any of the hunters smells prey, then defect! if D in results: return D # Tit-for-tat might seem like a better default choice, but in many # cases it complicates the heuristics of hunting and creates # false-positives. So go ahead and use it, but only for longer # histories. if len(opponent.history) > 100: return D if opponent.history[-1:] == [D] else C else: return C
[docs]class MetaHunterAggressive(MetaPlayer): """A player who uses a selection of hunters. Names - Meta Hunter Aggressive: Original name by Marc Harper """ name = "Meta Hunter Aggressive" 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, team=None): # This version uses CooperatorHunter if team is None: team = [ DefectorHunter, AlternatorHunter, RandomHunter, MathConstantHunter, CycleHunter, EventualCycleHunter, CooperatorHunter, ] super().__init__(team=team)
[docs] @staticmethod def meta_strategy(results, opponent): # If any of the hunters smells prey, then defect! if D in results: return D # Tit-for-tat might seem like a better default choice, but in many # cases it complicates the heuristics of hunting and creates # false-positives. So go ahead and use it, but only for longer # histories. if len(opponent.history) > 100: return D if opponent.history[-1:] == [D] else C else: return C
[docs]class MetaMajorityMemoryOne(MetaMajority): """MetaMajority with the team of Memory One players Names - Meta Majority Memory One: Original name by Marc Harper """ name = "Meta Majority Memory One" def __init__(self): team = [s for s in ordinary_strategies if s().classifier["memory_depth"] <= 1] super().__init__(team=team) self.classifier["long_run_time"] = False
[docs]class MetaMajorityFiniteMemory(MetaMajority): """MetaMajority with the team of Finite Memory Players Names - Meta Majority Finite Memory: Original name by Marc Harper """ name = "Meta Majority Finite Memory" def __init__(self): team = [ s for s in ordinary_strategies if s().classifier["memory_depth"] < float("inf") ] super().__init__(team=team)
[docs]class MetaMajorityLongMemory(MetaMajority): """MetaMajority with the team of Long (infinite) Memory Players Names - Meta Majority Long Memory: Original name by Marc Harper """ name = "Meta Majority Long Memory" def __init__(self): team = [ s for s in ordinary_strategies if s().classifier["memory_depth"] == float("inf") ] super().__init__(team=team)
[docs]class MetaWinnerMemoryOne(MetaWinner): """MetaWinner with the team of Memory One players Names - Meta Winner Memory Memory One: Original name by Marc Harper """ name = "Meta Winner Memory One" def __init__(self): team = [s for s in ordinary_strategies if s().classifier["memory_depth"] <= 1] super().__init__(team=team) self.classifier["long_run_time"] = False
[docs]class MetaWinnerFiniteMemory(MetaWinner): """MetaWinner with the team of Finite Memory Players Names - Meta Winner Finite Memory: Original name by Marc Harper """ name = "Meta Winner Finite Memory" def __init__(self): team = [ s for s in ordinary_strategies if s().classifier["memory_depth"] < float("inf") ] super().__init__(team=team)
[docs]class MetaWinnerLongMemory(MetaWinner): """MetaWinner with the team of Long (infinite) Memory Players Names - Meta Winner Long Memory: Original name by Marc Harper """ name = "Meta Winner Long Memory" def __init__(self): team = [ s for s in ordinary_strategies if s().classifier["memory_depth"] == float("inf") ] super().__init__(team=team)
[docs]class MetaWinnerDeterministic(MetaWinner): """Meta Winner with the team of Deterministic Players. Names - Meta Winner Deterministic: Original name by Marc Harper """ name = "Meta Winner Deterministic" def __init__(self): team = [s for s in ordinary_strategies if not s().classifier["stochastic"]] super().__init__(team=team) self.classifier["stochastic"] = False
[docs]class MetaWinnerStochastic(MetaWinner): """Meta Winner with the team of Stochastic Players. Names - Meta Winner Stochastic: Original name by Marc Harper """ name = "Meta Winner Stochastic" def __init__(self): team = [s for s in ordinary_strategies if s().classifier["stochastic"]] super().__init__(team=team)
[docs]class MetaMixer(MetaPlayer): """A player who randomly switches between a team of players. If no distribution is passed then the player will uniformly choose between sub players. In essence this is creating a Mixed strategy. Parameters team : list of strategy classes, optional Team of strategies that are to be randomly played If none is passed will select the ordinary strategies. distribution : list representing a probability distribution, optional This gives the distribution from which to select the players. If none is passed will select uniformly. Names - Meta Mixer: Original name by Vince Knight """ name = "Meta Mixer" classifier = { "memory_depth": float("inf"), # Long memory "stochastic": True, "makes_use_of": set(), "long_run_time": True, "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__(self, team=None, distribution=None): self.distribution = distribution super().__init__(team=team)
[docs] def meta_strategy(self, results, opponent): """Using the numpy.random choice function to sample with weights""" return choice(results, p=self.distribution)
[docs]class NMWEDeterministic(NiceMetaWinnerEnsemble): """Nice Meta Winner Ensemble with the team of Deterministic Players. Names - Nice Meta Winner Ensemble Deterministic: Original name by Marc Harper """ name = "NMWE Deterministic" def __init__(self): team = [s for s in ordinary_strategies if not s().classifier["stochastic"]] super().__init__(team=team) self.classifier["stochastic"] = True
[docs]class NMWEStochastic(NiceMetaWinnerEnsemble): """Nice Meta Winner Ensemble with the team of Stochastic Players. Names - Nice Meta Winner Ensemble Stochastic: Original name by Marc Harper """ name = "NMWE Stochastic" def __init__(self): team = [s for s in ordinary_strategies if s().classifier["stochastic"]] super().__init__(team=team)
[docs]class NMWEFiniteMemory(NiceMetaWinnerEnsemble): """Nice Meta Winner Ensemble with the team of Finite Memory Players. Names - Nice Meta Winner Ensemble Finite Memory: Original name by Marc Harper """ name = "NMWE Finite Memory" def __init__(self): team = [ s for s in ordinary_strategies if s().classifier["memory_depth"] < float("inf") ] super().__init__(team=team)
[docs]class NMWELongMemory(NiceMetaWinnerEnsemble): """Nice Meta Winner Ensemble with the team of Long Memory Players. Names - Nice Meta Winner Ensemble Long Memory: Original name by Marc Harper """ name = "NMWE Long Memory" def __init__(self): team = [ s for s in ordinary_strategies if s().classifier["memory_depth"] == float("inf") ] super().__init__(team=team)
[docs]class NMWEMemoryOne(NiceMetaWinnerEnsemble): """Nice Meta Winner Ensemble with the team of Memory One Players. Names - Nice Meta Winner Ensemble Memory One: Original name by Marc Harper """ name = "NMWE Memory One" def __init__(self): team = [s for s in ordinary_strategies if s().classifier["memory_depth"] <= 1] super().__init__(team=team) self.classifier["long_run_time"] = False
[docs]class MemoryDecay(MetaPlayer): """ A player utilizes the (default) Tit for Tat strategy for the first (default) 15 turns, at the same time memorizing the opponent's decisions. After the 15 turns have passed, the player calculates a 'net cooperation score' (NCS) for their opponent, weighing decisions to Cooperate as (default) 1, and to Defect as (default) -2. If the opponent's NCS is below 0, the player defects; otherwise, they cooperate. The player's memories of the opponent's decisions have a random chance to be altered (i.e., a C decision becomes D or vice versa; default probability is 0.03) or deleted (default probability is 0.1). It is possible to pass a different axelrod player class to change the inital player behavior. Name: Memory Decay """ name = "Memory Decay" classifier = { "memory_depth": float("inf"), "long_run_time": False, "stochastic": True, "makes_use_of": set(), "inspects_source": False, "manipulates_source": False, "manipulates_state": False, } def __init__( self, p_memory_delete: float = 0.1, p_memory_alter: float = 0.03, loss_value: float = -2, gain_value: float = 1, memory: list = None, start_strategy: Player = TitForTat, start_strategy_duration: int = 15, ): super().__init__(team=[start_strategy]) self.classifier["stochastic"] = True self.p_memory_delete = p_memory_delete self.p_memory_alter = p_memory_alter self.loss_value = loss_value self.gain_value = gain_value self.memory = [] if memory == None else memory self.start_strategy_duration = start_strategy_duration def __repr__(self): return Player.__repr__(self)
[docs] def gain_loss_translate(self): """ Translates the actions (D and C) to numeric values (loss_value and gain_value). """ values = {C: self.gain_value, D: self.loss_value} self.gloss_values = [values[action] for action in self.memory]
[docs] def memory_alter(self): """ Alters memory entry, i.e. puts C if there's a D and vice versa. """ alter = choice(range(0, len(self.memory))) self.memory[alter] = self.memory[alter].flip()
[docs] def memory_delete(self): """ Deletes memory entry. """ self.memory.pop(choice(range(0, len(self.memory))))
[docs] def strategy(self, opponent): try: self.memory.append(opponent.history[-1]) except IndexError: pass if len(self.history) < self.start_strategy_duration: play = self.team[0].strategy(opponent) self.team[0].history.append(play) return play else: if random.random() <= self.p_memory_alter: self.memory_alter() if random.random() <= self.p_memory_delete: self.memory_delete() self.gain_loss_translate() if sum(self.gloss_values) < 0: return D else: return C