Skip to content

Commit

Permalink
Merge pull request #1052 from eric-s-s/enum_actions
Browse files Browse the repository at this point in the history
Enum actions #816
  • Loading branch information
meatballs authored Jul 1, 2017
2 parents 7adefca + 742ce8e commit 1947fb0
Show file tree
Hide file tree
Showing 43 changed files with 436 additions and 295 deletions.
70 changes: 52 additions & 18 deletions axelrod/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,65 @@
C, D = Actions.C, Actions.D
"""

# Type alias for actions.
Action = str
from enum import Enum
from typing import Iterable


class UnknownActionError(ValueError):
def __init__(self, *args):
super(UnknownActionError, self).__init__(*args)


class Actions(Enum):

C = 1
D = 0

def __bool__(self):
return bool(self.value)

def __repr__(self):
return '{}'.format(self.name)

class Actions(object):
C = 'C' # type: Action
D = 'D' # type: Action
def __str__(self):
return '{}'.format(self.name)

def flip(self):
"""Returns the opposite Action. """
if self == Actions.C:
return Actions.D
if self == Actions.D:
return Actions.C

@classmethod
def from_char(cls, character):
"""Converts a single character into an Action. `Action.from_char('C')`
returns `Action.C`. `Action.from_char('CC')` raises an error. Use
`str_to_actions` instead."""
if character == 'C':
return cls.C
elif character == 'D':
return cls.D
else:
raise UnknownActionError('Character must be "C" or "D".')

# Type alias for actions.
Action = Actions


def flip_action(action: Action) -> Action:
if action == Actions.C:
return Actions.D
elif action == Actions.D:
return Actions.C
else:
raise ValueError("Encountered a invalid action.")
if not isinstance(action, Action):
raise UnknownActionError('Not an Action')
return action.flip()


def str_to_actions(actions: str) -> tuple:
"""Takes a string like 'CCDD' and returns a tuple of the appropriate
actions."""
action_dict = {'C': Actions.C,
'D': Actions.D}
try:
return tuple(action_dict[action] for action in actions)
except KeyError:
raise ValueError(
'The characters of "actions" str may only be "C" or "D"')
return tuple(Actions.from_char(element) for element in actions)


def actions_to_str(actions: Iterable[Action]) -> str:
"""Takes any iterable of Action and returns a string of 'C's
and 'D's. ex: (D, D, C) -> 'DDC' """
return "".join(map(repr, actions))
2 changes: 1 addition & 1 deletion axelrod/deterministic_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class DeterministicCache(UserDict):
resulting interactions. e.g. for a 3 turn Match between Cooperator and
Alternator, the dictionary entry would be:
(axelrod.Cooperator, axelrod.Alternator): [('C', 'C'), ('C', 'D'), ('C', 'C')]
(axelrod.Cooperator, axelrod.Alternator): [(C, C), (C, D), (C, C)]
Most of the functionality is provided by the UserDict class (which uses an
instance of dict as the 'data' attribute to hold the dictionary entries).
Expand Down
12 changes: 7 additions & 5 deletions axelrod/interaction_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import csv
import tqdm

from axelrod.actions import Actions
from axelrod.actions import Actions, str_to_actions
from .game import Game


Expand Down Expand Up @@ -255,7 +255,9 @@ def read_interactions_from_file(filename, progress_bar=True,
with open(filename, 'r') as f:
for row in csv.reader(f):
index_pair = (int(row[0]), int(row[1]))
interaction = list(zip(row[4], row[5]))
p1_actions = str_to_actions(row[4])
p2_actions = str_to_actions(row[5])
interaction = list(zip(p1_actions, p2_actions))

try:
pairs_to_interactions[index_pair].append(interaction)
Expand All @@ -275,12 +277,12 @@ def string_to_interactions(string):
Converts a compact string representation of an interaction to an
interaction:
'CDCDDD' -> [('C', 'D'), ('C', 'D'), ('D', 'D')]
'CDCDDD' -> [(C, D), (C, D), (D, D)]
"""
interactions = []
interactions_list = list(string)
while interactions_list:
p1action = interactions_list.pop(0)
p2action = interactions_list.pop(0)
p1action = Actions.from_char(interactions_list.pop(0))
p2action = Actions.from_char(interactions_list.pop(0))
interactions.append((p1action, p2action))
return interactions
4 changes: 2 additions & 2 deletions axelrod/random_.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

def random_choice(p: float = 0.5) -> Action:
"""
Return 'C' with probability `p`, else return 'D'
Return C with probability `p`, else return D
No random sample is carried out if p is 0 or 1.
Parameters
----------
p : float
The probability of picking 'C'
The probability of picking C
Returns
-------
Expand Down
10 changes: 6 additions & 4 deletions axelrod/result_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from numpy import mean, nanmedian, std
import tqdm

from axelrod.actions import Actions
from axelrod.actions import Actions, str_to_actions
import axelrod.interaction_utils as iu
from . import eigen
from .game import Game
Expand Down Expand Up @@ -848,8 +848,8 @@ def summarise(self):
for player in self.normalised_state_to_action_distribution:
rates = []
for state in states:
counts = [counter[(state, 'C')] for counter in player
if counter[(state, 'C')] > 0]
counts = [counter[(state, C)] for counter in player
if counter[(state, C)] > 0]

if len(counts) > 0:
rate = mean(counts)
Expand Down Expand Up @@ -1075,7 +1075,9 @@ def read_match_chunks(self, progress_bar=False):
count = 0
for row in csv_reader:
index_and_names = row[:4]
interactions = list(zip(row[4], row[5]))
p1_actions = str_to_actions(row[4])
p2_actions = str_to_actions(row[5])
interactions = list(zip(p1_actions, p2_actions))
repetitions.append(index_and_names + interactions)
count += 1
if progress_bar:
Expand Down
4 changes: 2 additions & 2 deletions axelrod/strategies/appeaser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
class Appeaser(Player):
"""A player who tries to guess what the opponent wants.
Switch the classifier every time the opponent plays 'D'.
Start with 'C', switch between 'C' and 'D' when opponent plays 'D'.
Switch the classifier every time the opponent plays D.
Start with C, switch between C and D when opponent plays D.
Names:
Expand Down
2 changes: 1 addition & 1 deletion axelrod/strategies/human.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _get_human_input(self) -> Action: # pragma: no cover
get_bottom_toolbar_tokens=self.status_messages['toolbar'],
style=toolbar_style)

return action.upper()
return Actions.from_char(action.upper())

def strategy(self, opponent: Player, input_function=None):
"""
Expand Down
14 changes: 9 additions & 5 deletions axelrod/strategies/lookerup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections import namedtuple
from itertools import product

from axelrod.actions import Action, Actions, str_to_actions
from axelrod.actions import Action, Actions, str_to_actions, actions_to_str
from axelrod.player import Player

from typing import Any, TypeVar
Expand Down Expand Up @@ -139,7 +139,8 @@ def display(self,
:param sort_by: only_elements='self_plays', 'op_plays', 'op_openings'
"""
def sorter(plays):
return tuple(getattr(plays, field) for field in sort_by)
return tuple(actions_to_str(
getattr(plays, field) for field in sort_by))

col_width = 11
sorted_keys = sorted(self._dict, key=sorter)
Expand All @@ -148,9 +149,12 @@ def sorter(plays):
'{str_list[2]:^{width}}')
display_line = header_line.replace('|', ',') + ': {str_list[3]},'

line_elements = [(', '.join(getattr(key, sort_by[0])),
', '.join(getattr(key, sort_by[1])),
', '.join(getattr(key, sort_by[2])),
def make_commaed_str(action_tuple):
return ', '.join(str(action) for action in action_tuple)

line_elements = [(make_commaed_str(getattr(key, sort_by[0])),
make_commaed_str(getattr(key, sort_by[1])),
make_commaed_str(getattr(key, sort_by[2])),
self._dict[key])
for key in sorted_keys]
header = header_line.format(str_list=sort_by, width=col_width) + '\n'
Expand Down
2 changes: 1 addition & 1 deletion axelrod/strategies/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def strategy(self, opponent):
def meta_strategy(self, results, opponent):
"""Determine the meta result based on results of all players.
Override this function in child classes."""
return 'C'
return C

def reset(self):
super().reset()
Expand Down
6 changes: 4 additions & 2 deletions axelrod/strategies/qlearner.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections import OrderedDict
import random

from axelrod.actions import Actions, Action
from axelrod.actions import Actions, Action, actions_to_str
from axelrod.player import Player
from axelrod.random_ import random_choice

Expand Down Expand Up @@ -92,7 +92,9 @@ def find_state(self, opponent: Player) -> str:
its previous proportion of playing C) as a hashable state
"""
prob = '{:.1f}'.format(opponent.cooperations)
return ''.join(opponent.history[-self.memory_length:]) + prob
action_str = actions_to_str(
opponent.history[-self.memory_length:])
return action_str + prob

def perform_q_learning(self, prev_state: str, state: str, action: Action, reward):
"""
Expand Down
4 changes: 2 additions & 2 deletions axelrod/strategies/shortmem.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def strategy(opponent: Player) -> Action:
return C

array = opponent.history[-10:]
C_counts = array.count('C')
D_counts = array.count('D')
C_counts = array.count(C)
D_counts = array.count(D)

if C_counts - D_counts >= 3:
return C
Expand Down
2 changes: 1 addition & 1 deletion axelrod/strategies/stalker.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
C, D = Actions.C, Actions.D


@FinalTransformer((D), name_prefix=None) # End with defection
@FinalTransformer((D,), name_prefix=None) # End with defection
class Stalker(Player):
"""
Expand Down
8 changes: 4 additions & 4 deletions axelrod/strategies/titfortat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from axelrod.actions import Actions, Action
from axelrod.actions import Actions, Action, actions_to_str
from axelrod.player import Player
from axelrod.random_ import random_choice
from axelrod.strategy_transformers import (
Expand Down Expand Up @@ -182,11 +182,11 @@ class SneakyTitForTat(Player):

def strategy(self, opponent: Player) -> Action:
if len(self.history) < 2:
return "C"
return C
if D not in opponent.history:
return D
if opponent.history[-1] == D and self.history[-2] == D:
return "C"
return C
return opponent.history[-1]


Expand Down Expand Up @@ -297,7 +297,7 @@ def strategy(opponent: Player) -> Action:
if not opponent.history:
return C
# Defects if two consecutive D in the opponent's last three moves
history_string = "".join(opponent.history[-3:])
history_string = actions_to_str(opponent.history[-3:])
if 'DD' in history_string:
return D
# Otherwise cooperates
Expand Down
8 changes: 5 additions & 3 deletions axelrod/strategy_transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,13 @@ def __repr__(self):
prefix = ': '
for arg in args:
try:
arg = [player.name for player in arg]
except TypeError:
pass
# Action has .name but should not be made into a list
if not any(isinstance(el, Actions) for el in arg):
arg = [player.name for player in arg]
except AttributeError:
pass
except TypeError:
pass
name = ''.join([name, prefix, str(arg)])
prefix = ', '
return name
Expand Down
6 changes: 3 additions & 3 deletions axelrod/tests/integration/test_matches.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ def test_matches_with_det_player_for_stochastic_classes(self):
p3 = axelrod.MemoryOnePlayer(four_vector=(1, 1, 1, 0))

m = axelrod.Match((p1, p2), turns=3)
self.assertEqual(m.play(), [('C', 'C'), ('D', 'C'), ('D', 'D')])
self.assertEqual(m.play(), [(C, C), (D, C), (D, D)])

m = axelrod.Match((p2, p3), turns=3)
self.assertEqual(m.play(), [('C', 'C'), ('C', 'C'), ('C', 'C')])
self.assertEqual(m.play(), [(C, C), (C, C), (C, C)])

m = axelrod.Match((p1, p3), turns=3)
self.assertEqual(m.play(), [('C', 'C'), ('D', 'C'), ('D', 'C')])
self.assertEqual(m.play(), [(C, C), (D, C), (D, C)])
2 changes: 1 addition & 1 deletion axelrod/tests/strategies/test_axelrod_first.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def test_strategy(self):

class TestSteinAndRapoport(TestPlayer):

name = "Stein and Rapoport: 0.05: ('D', 'D')"
name = "Stein and Rapoport: 0.05: (D, D)"
player = axelrod.SteinAndRapoport
expected_classifier = {
'memory_depth': 15,
Expand Down
4 changes: 2 additions & 2 deletions axelrod/tests/strategies/test_backstabber.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class TestBackStabber(TestPlayer):

name = "BackStabber: ('D', 'D')"
name = "BackStabber: (D, D)"
player = axelrod.BackStabber
expected_classifier = {
'memory_depth': float('inf'),
Expand Down Expand Up @@ -56,7 +56,7 @@ class TestDoubleCrosser(TestBackStabber):
The alternate strategy is triggered when opponent did not defect in the
first 7 rounds, and 8 <= the current round <= 180.
"""
name = "DoubleCrosser: ('D', 'D')"
name = "DoubleCrosser: (D, D)"
player = axelrod.DoubleCrosser
expected_classifier = {
'memory_depth': float('inf'),
Expand Down
3 changes: 2 additions & 1 deletion axelrod/tests/strategies/test_cycler.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ def test_memory_depth_is_len_cycle_minus_one(self):

def test_cycler_works_as_expected(self):
expected = [(C, D), (D, D), (D, D), (C, D)] * 2
self.versus_test(axelrod.Defector(), expected_actions=expected, init_kwargs={'cycle': 'CDDC'})
self.versus_test(axelrod.Defector(), expected_actions=expected,
init_kwargs={'cycle': 'CDDC'})

def test_cycle_raises_value_error_on_bad_cycle_str(self):
self.assertRaises(ValueError, Cycler, cycle='CdDC')
Expand Down
4 changes: 2 additions & 2 deletions axelrod/tests/strategies/test_finite_state_machines.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class TestSampleFSMPlayer(TestPlayer):
"""Test a few sample tables to make sure that the finite state machines are
working as intended."""

name = "FSM Player: ((1, 'C', 1, 'C'), (1, 'D', 1, 'D')), 1, C"
name = "FSM Player: ((1, C, 1, C), (1, D, 1, D)), 1, C"
player = axelrod.FSMPlayer

expected_classifier = {
Expand Down Expand Up @@ -137,7 +137,7 @@ def test_wsls(self):


class TestFSMPlayer(TestPlayer):
name = "FSM Player: ((1, 'C', 1, 'C'), (1, 'D', 1, 'D')), 1, C"
name = "FSM Player: ((1, C, 1, C), (1, D, 1, D)), 1, C"
player = axelrod.FSMPlayer

expected_classifier = {
Expand Down
Loading

0 comments on commit 1947fb0

Please sign in to comment.