Skip to content

Commit

Permalink
Add data classes and YAML serialization of test matches
Browse files Browse the repository at this point in the history
  • Loading branch information
marcharper committed Jul 3, 2020
1 parent ca05063 commit 5195f31
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 3 deletions.
2 changes: 2 additions & 0 deletions axelrod/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from axelrod.plot import Plot
from axelrod.game import DefaultGame, Game
from axelrod.history import History, LimitedHistory
from axelrod.data_classes import PlayerConfig
from axelrod.yaml import log_kwargs, load_matches
from axelrod.player import Player
from axelrod.classifier import Classifiers
from axelrod.evolvable_player import EvolvablePlayer
Expand Down
4 changes: 2 additions & 2 deletions axelrod/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from enum import Enum
from functools import total_ordering
from typing import Iterable
from typing import Iterable, Tuple


class UnknownActionError(ValueError):
Expand Down Expand Up @@ -70,7 +70,7 @@ def from_char(cls, character):
raise UnknownActionError('Character must be "C" or "D".')


def str_to_actions(actions: str) -> tuple:
def str_to_actions(actions: str) -> Tuple[Action, ...]:
"""Converts a string to a tuple of actions.
Parameters
Expand Down
61 changes: 61 additions & 0 deletions axelrod/data_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional, Tuple

import axelrod
from axelrod.action import Action


@dataclass
class PlayerConfig:
name: str
init_kwargs: dict = None

def __call__(self):
# Look up player by name
player_class = getattr(axelrod, self.name)
if self.init_kwargs:
return player_class(**self.init_kwargs)
return player_class()


@dataclass
class MatchParameters:
turns: Optional[int] = None
noise: Optional[float] = None
prob_end: Optional[float] = None
seed: Optional[int] = None
game: Optional[axelrod.Game] = None


@dataclass
class ExpectedMatchOutcome:
player_actions: Tuple[Action, ...]
coplayer_actions: Tuple[Action, ...]
player_attributes: Optional[dict] = None


@dataclass
class MatchConfig:
player: PlayerConfig
coplayer: PlayerConfig
match_parameters: MatchParameters
expected_outcome: Optional[ExpectedMatchOutcome] = None

def __call__(self):
"""Generate the match."""
player = self.player()
coplayer = self.coplayer()
noise = self.match_parameters.noise
prob_end = self.match_parameters.prob_end
turns = len(self.expected_outcome.player_actions)
match = axelrod.Match(
(player, coplayer),
turns=turns,
noise=noise,
prob_end=prob_end
)
return match

def test_match(self):
pass
5 changes: 4 additions & 1 deletion axelrod/tests/strategies/test_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import axelrod as axl
import numpy as np
from axelrod.player import simultaneous_play
from axelrod.tests.property import strategy_lists
from axelrod.tests.property import
from axelrod.yaml import log_kwargs

from hypothesis import given, settings
from hypothesis.strategies import integers, sampled_from

Expand Down Expand Up @@ -633,6 +635,7 @@ class TestMatch(unittest.TestCase):
"""Test class for heads up play between two given players. Plays an
axelrod match between the two players."""

@log_kwargs
def versus_test(
self,
player1,
Expand Down
13 changes: 13 additions & 0 deletions axelrod/tests/unit/test_data_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import unittest
import axelrod as axl


class TestPlayerConfig(unittest.TestCase):

def test_call(self):
pc = axl.PlayerConfig("Cooperator")
player = pc()
print(player)
self.assertTrue(issubclass(type(player), axl.Player))
self.assertTrue(isinstance(player, axl.Cooperator))

59 changes: 59 additions & 0 deletions axelrod/yaml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from dataclasses import asdict
from typing import Tuple

from dacite import from_dict, Config
import yaml

import axelrod
from axelrod.action import Action, actions_to_str, str_to_actions
from axelrod.data_classes import ExpectedMatchOutcome, PlayerConfig, MatchConfig, MatchParameters

filename = "test_matches.yaml"


def build_player_spec(name, init_kwargs=None):
return PlayerConfig(name=name, init_kwargs=dict(init_kwargs))


def build_expected_spec(player_actions, coplayer_actions, attr=None):
return ExpectedMatchOutcome(
player_actions=player_actions,
coplayer_actions=coplayer_actions,
player_attributes=attr)


def build_match_parameters_spec(noise=None, seed=None):
return MatchParameters(noise=noise, seed=seed)


def build_match_spec(player_name, coplayer_name, player_actions, coplayer_actions, noise=None, seed=None,
player_init_kwargs=None, coplayer_init_kwargs=None, attr=None):
return MatchConfig(
player=build_player_spec(player_name, init_kwargs=player_init_kwargs.copy()),
coplayer=build_player_spec(coplayer_name,init_kwargs=coplayer_init_kwargs.copy()),
match_parameters=build_match_parameters_spec(noise=noise, seed=seed),
expected_outcome=build_expected_spec(player_actions, coplayer_actions, attr=attr)
)


def log_kwargs(func):
def wrapper(*args, **kwargs):
stream = open('test_matches.yaml', 'a')
spec = build_match_spec(str(args[1].__class__.__name__), str(args[2].__class__.__name__),
actions_to_str(args[-2]), actions_to_str(args[-1]),
noise=kwargs["noise"], seed=kwargs["seed"],
player_init_kwargs=args[1].init_kwargs,
coplayer_init_kwargs=args[2].init_kwargs)
stream.write("---\n")
yaml.dump(asdict(spec), stream)
return func(*args, **kwargs)
return wrapper


def load_matches():
stream = open(filename, 'r')
matches = yaml.load_all(stream, Loader=yaml.Loader)
return [from_dict(data_class=MatchConfig, data=match, config=Config(
type_hooks={Tuple[Action, ...]: str_to_actions})) for match in matches]


0 comments on commit 5195f31

Please sign in to comment.