File size: 5,008 Bytes
bdafe83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import copy
import json

from .utils import AttributedDict


class Config(AttributedDict):
    """
    Config class to manage the configuration of the games.

    The class has a few useful methods to load and save the config.
    """

    # convert dict to Config recursively
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for key, value in self.items():

            # Try to convert the value (the "metadata" field) to dict if applicable
            try:
                value = dict(eval(value))
            except Exception:
                pass

            if isinstance(value, dict):
                self[key] = init_config(value)  # convert dict to Config recursively
            # convert list of dict to list of Config recursively
            elif isinstance(value, list) and len(value) > 0:
                self[key] = [
                    init_config(item) if isinstance(item, dict) else item
                    for item in value
                ]

    def save(self, path: str):
        # save config to file
        with open(path, "w") as f:
            json.dump(self, f, indent=4)

    @classmethod
    def load(cls, path: str):
        # load config from file
        with open(path) as f:
            config = json.load(f)
        return cls(config)

    def deepcopy(self):
        # get the config class so that subclasses can be copied in the correct class
        config_class = self.__class__
        # make a deep copy of the config
        return config_class(copy.deepcopy(self))


class Configurable:
    """Configurable is an interface for classes that can be initialized with a config."""

    def __init__(self, **kwargs):
        self._config_dict = kwargs

    @classmethod
    def from_config(cls, config: Config):
        return cls(**config)

    def to_config(self) -> Config:
        # Convert the _config_dict to Config
        return Config(**self._config_dict)

    def save_config(self, path: str):
        self.to_config().save(path)


class EnvironmentConfig(Config):
    """EnvironmentConfig contains a env_type field to indicate the name of the environment."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # check if the env_type field is specified
        if "env_type" not in self:
            raise ValueError("The env_type field is not specified")


class BackendConfig(Config):
    """BackendConfig contains a backend_type field to indicate the name of the backend."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # check if the backend_type field is specified
        if "backend_type" not in self:
            raise ValueError("The backend_type field is not specified")


class AgentConfig(Config):
    """AgentConfig contains role_desc and backend fields."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # check if the role_desc field is specified
        if "role_desc" not in self:
            raise ValueError("The role_desc field is not specified")
        # check if the backend field is specified
        if "backend" not in self:
            raise ValueError("The backend field is not specified")
        # Make sure the backend field is a BackendConfig
        if not isinstance(self["backend"], BackendConfig):
            raise ValueError("The backend field must be a BackendConfig")


class ArenaConfig(Config):
    """ArenaConfig contains a list of AgentConfig."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # check if the players field is specified and it is List[AgentConfig]
        if "players" not in self:
            raise ValueError("The players field is not specified")
        if not isinstance(self["players"], list):
            raise ValueError("The players field must be a list")
        for player in self["players"]:
            if not isinstance(player, AgentConfig):
                raise ValueError("The players field must be a list of AgentConfig")

        # check if environment field is specified and it is EnvironmentConfig
        if "environment" not in self:
            raise ValueError("The environment field is not specified")
        if not isinstance(self["environment"], EnvironmentConfig):
            raise ValueError("The environment field must be an EnvironmentConfig")


# Initialize with different config class depending on whether the config is for environment or backend
def init_config(config: dict):
    if not isinstance(config, dict):
        raise ValueError("The config must be a dict")

    # check if the config is for environment or backend
    if "env_type" in config:
        return EnvironmentConfig(config)
    elif "backend_type" in config:
        return BackendConfig(config)
    elif "role_desc" in config:
        return AgentConfig(config)
    elif "players" in config:
        return ArenaConfig(config)
    else:
        return Config(config)