Skip to content

Commit

Permalink
Merge pull request #953 from Chadys/802
Browse files Browse the repository at this point in the history
Automatically set strategy classes (and transformers) __repr__ to include parameters. #802
  • Loading branch information
drvinceknight authored Apr 2, 2017
2 parents 1d0e359 + 1151f2c commit d0c1636
Show file tree
Hide file tree
Showing 31 changed files with 197 additions and 202 deletions.
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,12 @@ ENV/
# Rope project settings
.ropeproject

# Sublime Text Settings
# Sublime Text settings
*.sublime-*

# Jetbrain editor settings
.idea/

# Docker files
Dockerfile
docker-compose.yml
docker-compose.yml
36 changes: 30 additions & 6 deletions axelrod/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from functools import wraps
import random
import copy
import inspect

from axelrod.actions import Actions, flip_action, Action
from .game import DefaultGame
Expand Down Expand Up @@ -73,7 +74,7 @@ class Player(object):
"""

name = "Player"
classifier = {} # type: Dict[str, Any]
classifier = {} # type: Dict[str, Any]
default_classifier = {
'stochastic': False,
'memory_depth': float('inf'),
Expand All @@ -87,10 +88,26 @@ class Player(object):
def __new__(cls, *args, **kwargs):
"""Caches arguments for Player cloning."""
obj = super().__new__(cls)
obj.init_args = args
obj.init_kwargs = kwargs
obj.init_kwargs = cls.init_params(*args, **kwargs)
return obj

@classmethod
def init_params(cls, *args, **kwargs):
"""
Return a dictionary containing the init parameters of a strategy (without 'self').
Use *args and *kwargs as value if specified
and complete the rest with the default values.
"""
sig = inspect.signature(cls.__init__)
# the 'self' parameter needs to be removed or the first *args will be assigned to it
self_param = sig.parameters.get('self')
new_params = list(sig.parameters.values())
new_params.remove(self_param)
sig = sig.replace(parameters=new_params)
boundargs = sig.bind_partial(*args, **kwargs)
boundargs.apply_defaults()
return boundargs.arguments

def __init__(self):
"""Initiates an empty history and 0 score for a player."""
self.history = []
Expand Down Expand Up @@ -122,8 +139,15 @@ def set_match_attributes(self, length=-1, game=None, noise=0):
self.receive_match_attributes()

def __repr__(self):
"""The string method for the strategy."""
return self.name
"""The string method for the strategy.
Appends the `__init__` parameters to the strategy's name."""
name = self.name
prefix = ': '
gen = (value for value in self.init_kwargs.values() if value is not None)
for value in gen:
name = ''.join([name, prefix, str(value)])
prefix = ', '
return name

@staticmethod
def _add_noise(noise, s1, s2):
Expand Down Expand Up @@ -158,7 +182,7 @@ def clone(self):
# be significant changes required throughout the library.
# Override in special cases only if absolutely necessary
cls = self.__class__
new_player = cls(*self.init_args, **self.init_kwargs)
new_player = cls(**self.init_kwargs)
new_player.match_attributes = copy.copy(self.match_attributes)
return new_player

Expand Down
3 changes: 0 additions & 3 deletions axelrod/strategies/axelrod_first.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,6 @@ def __init__(self, p: float = 0.9) -> None:
self.p = p
super().__init__(four_vector)

def __repr__(self) -> str:
return "%s: %s" % (self.name, round(self.p, 2))


class Nydegger(Player):
"""
Expand Down
25 changes: 12 additions & 13 deletions axelrod/strategies/cycler.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def __init__(self, cycle: str = "CCD") -> None:
super().__init__()
self.cycle_str = cycle
self.cycle = self.get_new_itertools_cycle()
self.name = "Cycler {}".format(cycle)
self.classifier['memory_depth'] = len(cycle) - 1

def get_new_itertools_cycle(self):
Expand All @@ -99,8 +98,8 @@ class CyclerDC(Cycler):
classifier = copy.copy(Cycler.classifier)
classifier['memory_depth'] = 1

def __init__(self, cycle="DC") -> None:
super().__init__(cycle=cycle)
def __init__(self) -> None:
super().__init__(cycle="DC")


class CyclerCCD(Cycler):
Expand All @@ -109,8 +108,8 @@ class CyclerCCD(Cycler):
classifier = copy.copy(Cycler.classifier)
classifier['memory_depth'] = 2

def __init__(self, cycle="CCD") -> None:
super().__init__(cycle=cycle)
def __init__(self) -> None:
super().__init__(cycle="CCD")


class CyclerDDC(Cycler):
Expand All @@ -119,8 +118,8 @@ class CyclerDDC(Cycler):
classifier = copy.copy(Cycler.classifier)
classifier['memory_depth'] = 2

def __init__(self, cycle="DDC") -> None:
super().__init__(cycle=cycle)
def __init__(self) -> None:
super().__init__(cycle="DDC")


class CyclerCCCD(Cycler):
Expand All @@ -129,8 +128,8 @@ class CyclerCCCD(Cycler):
classifier = copy.copy(Cycler.classifier)
classifier['memory_depth'] = 3

def __init__(self, cycle="CCCD") -> None:
super().__init__(cycle=cycle)
def __init__(self) -> None:
super().__init__(cycle="CCCD")


class CyclerCCCCCD(Cycler):
Expand All @@ -139,8 +138,8 @@ class CyclerCCCCCD(Cycler):
classifier = copy.copy(Cycler.classifier)
classifier['memory_depth'] = 5

def __init__(self, cycle="CCCCCD") -> None:
super().__init__(cycle=cycle)
def __init__(self) -> None:
super().__init__(cycle="CCCCCD")


class CyclerCCCDCD(Cycler):
Expand All @@ -149,5 +148,5 @@ class CyclerCCCDCD(Cycler):
classifier = copy.copy(Cycler.classifier)
classifier['memory_depth'] = 5

def __init__(self, cycle="CCCDCD") -> None:
super().__init__(cycle=cycle)
def __init__(self) -> None:
super().__init__(cycle="CCCDCD")
4 changes: 3 additions & 1 deletion axelrod/strategies/gobymajority.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ def __init__(self, memory_depth: int = float('inf'), soft: bool = True) -> None:
self.memory = self.classifier['memory_depth']
else:
self.memory = 0

self.name = (
'Go By Majority' + (self.memory > 0) * (": %i" % self.memory))
if self.soft:
self.name = "Soft " + self.name
else:
self.name = "Hard " + self.name

def __repr__(self):
return self.name

def strategy(self, opponent: Player) -> Action:
"""This is affected by the history of the opponent.
Expand Down
10 changes: 5 additions & 5 deletions axelrod/strategies/human.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Human(Player):
'manipulates_state': False
}

def __init__(self, name='Human', c_symbol='C', d_symbol='D'):
def __init__(self, name='human', c_symbol='C', d_symbol='D'):
"""
Parameters
----------
Expand All @@ -64,7 +64,7 @@ def __init__(self, name='Human', c_symbol='C', d_symbol='D'):
and prompt
"""
super().__init__()
self.name = name
self.human_name = name
self.symbols = {
C: c_symbol,
D: d_symbol
Expand All @@ -81,7 +81,7 @@ def _history_toolbar(self, cli):
self.symbols[action] for action in self.opponent_history]
history = list(zip(my_history, opponent_history))
if self.history:
content = 'History ({}, opponent): {}'.format(self.name, history)
content = 'History ({}, opponent): {}'.format(self.human_name, history)
else:
content = ''
return [(Token.Toolbar, content)]
Expand All @@ -105,7 +105,7 @@ def _status_messages(self):
toolbar = self._history_toolbar
print_statement = (
'{}Turn {}: {} played {}, opponent played {}'.format(
linesep, len(self.history), self.name,
linesep, len(self.history), self.human_name,
self.symbols[self.history[-1]],
self.symbols[self.opponent_history[-1]])
)
Expand All @@ -130,7 +130,7 @@ def _get_human_input(self) -> Action: # pragma: no cover
"""
action = prompt(
'Turn {} action [C or D] for {}: '.format(
len(self.history) + 1, self.name),
len(self.history) + 1, self.human_name),
validator=ActionValidator(),
get_bottom_toolbar_tokens=self.status_messages['toolbar'],
style=toolbar_style)
Expand Down
6 changes: 4 additions & 2 deletions axelrod/strategies/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def __init__(self, team=None):
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 '')

def strategy(self, opponent):
# Get the results of all our players.
results = []
Expand Down Expand Up @@ -148,7 +152,6 @@ def reset(self):


NiceMetaWinner = NiceTransformer()(MetaWinner)
NiceMetaWinner.name = "Nice Meta Winner"


class MetaWinnerEnsemble(MetaWinner):
Expand All @@ -175,7 +178,6 @@ def meta_strategy(self, results, opponent):


NiceMetaWinnerEnsemble = NiceTransformer()(MetaWinnerEnsemble)
NiceMetaWinnerEnsemble.name = "Nice Meta Winner Ensemble"


class MetaHunter(MetaPlayer):
Expand Down
2 changes: 1 addition & 1 deletion axelrod/strategies/negation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class Negation(Player):
"""
A player starts by cooperating or defecting randomly if it's their first move,
then simply doing the opposite of the opponents last move thereafter.
then simply doing the opposite of the opponents last move thereafter.
Names:
Expand Down
3 changes: 0 additions & 3 deletions axelrod/strategies/prober.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,6 @@ def strategy(self, opponent: Player) -> Action:
choice = random_choice(1 - self.p)
return choice

def __repr__(self) -> str:
return "%s: %s" % (self.name, round(self.p, 2))


class RemorsefulProber(NaiveProber):
"""
Expand Down
3 changes: 0 additions & 3 deletions axelrod/strategies/rand.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,3 @@ def __init__(self, p: float=0.5) -> None:

def strategy(self, opponent: Player) -> Action:
return random_choice(self.p)

def __repr__(self) -> str:
return "%s: %s" % (self.name, round(self.p, 2))
8 changes: 0 additions & 8 deletions axelrod/strategies/retaliate.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ def __init__(self, retaliation_threshold: float = 0.1) -> None:
"""
super().__init__()
self.retaliation_threshold = retaliation_threshold
self.name = (
'Retaliate (' +
str(self.retaliation_threshold) + ')')
self.play_counts = defaultdict(int) # type: defaultdict

def strategy(self, opponent: Player) -> Action:
Expand Down Expand Up @@ -130,11 +127,6 @@ def __init__(self, retaliation_threshold: float = 0.1, retaliation_limit: int =
self.retaliation_limit = retaliation_limit
self.play_counts = defaultdict(int) # type: defaultdict

self.name = (
'Limited Retaliate (' +
str(self.retaliation_threshold) +
'/' + str(self.retaliation_limit) + ')')

def strategy(self, opponent: Player) -> Action:
"""
If the opponent has played D to my C more often than x% of the time
Expand Down
4 changes: 0 additions & 4 deletions axelrod/strategies/titfortat.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,6 @@ def reset(self):
self.world = 0.5
self.rate = self.starting_rate

def __repr__(self) -> str:

return "%s: %s" % (self.name, round(self.rate, 2))


class SpitefulTitForTat(Player):
"""
Expand Down
28 changes: 23 additions & 5 deletions axelrod/strategy_transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,42 @@ def strategy(self, opponent):
return strategy_wrapper(self, opponent, proposed_action,
*args, **kwargs)

# Define a new class and wrap the strategy method
# Modify the PlayerClass name
new_class_name = PlayerClass.__name__
name = PlayerClass.name
name_prefix = self.name_prefix
if name_prefix:
# Modify the Player name (class variable inherited from Player)
new_class_name = name_prefix + PlayerClass.__name__
new_class_name = ''.join([name_prefix, PlayerClass.__name__])
# Modify the Player name (class variable inherited from Player)
name = name_prefix + ' ' + PlayerClass.name
name = ' '.join([name_prefix, PlayerClass.name])

# Define the new __repr__ method to add the wrapper arguments
# at the end of the name
def __repr__(self):
name = PlayerClass.__repr__(self)
# add eventual transformers' arguments in name
prefix = ': '
for arg in args:
try:
arg = [player.name for player in arg]
except TypeError:
pass
except AttributeError:
pass
name = ''.join([name, prefix, str(arg)])
prefix = ', '
return name

# Define a new class and wrap the strategy method
# Dynamically create the new class
new_class = type(
new_class_name, (PlayerClass,),
{
"name": name,
"original_class": PlayerClass,
"strategy": strategy,
"__repr__": __repr__,
"__module__": PlayerClass.__module__,
})
return new_class
Expand Down Expand Up @@ -193,8 +212,7 @@ def dual_wrapper(player, opponent, proposed_action):
action: an axelrod.Action, C or D
"""
if not player.history:
player.original_player = player.original_class(*player.init_args,
**player.init_kwargs)
player.original_player = player.original_class(**player.init_kwargs)

action = player.original_player.strategy(opponent)
player.original_player.history.append(action)
Expand Down
Loading

0 comments on commit d0c1636

Please sign in to comment.