diff --git a/players.json b/players.json index 5bd14dc..5625211 100644 --- a/players.json +++ b/players.json @@ -1,14 +1,14 @@ { "expansions": { - "ShockColar1": {"players": ["Brosef"], "types": ["shock"]}, - "RobotBarman": {"players": ["Tango", "TRS_MML"], "types": ["drink"]}, - "ChallengeDB": {"players": ["TRS_MML"], "types": ["challenge"]}, - "SourCandy": {"players": [], "types": ["food"]} + "ShockColar1": {"players": ["Brosef"], "tags": ["shock"]}, + "RobotBarman": {"players": ["Tango", "TRS_MML"], "tags": ["drink"]}, + "ChallengeDB": {"players": ["TRS_MML"], "tags": ["challenge"]}, + "SourCandy": {"players": [], "tags": ["food"]} }, "players": { - "Brosef": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": 2}}, - "TRS_MML": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": 1}}, - "Tango": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": 0}} + "Brosef": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "gamesSave": {"gameID0": 2}}, + "TRS_MML": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "gamesSave": {"gameID0": 1}}, + "Tango": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "gamesSave": {"gameID0": 0}} } } \ No newline at end of file diff --git a/src/NoPELib/expansion.py b/src/NoPELib/expansion.py index 35d8af8..b3d9bb8 100644 --- a/src/NoPELib/expansion.py +++ b/src/NoPELib/expansion.py @@ -1,7 +1,20 @@ -class Expansion: - def __init__(self, parent, globalConfig): - setattr(parent, self.__class__.ID, self) +from player_settings import Expansion -class PlayerExpansion: - def __init__(self, player, localConfig): +class serialShockers(Expansion): + _api = None + + def __init__(self, ID, expansionsManager): + super().__init__(ID, expansionsManager) + if serialShockers._api is None: + pass + else: + pass + + def step(self, action): + pass + + def reset(self): + pass + + def close(self): pass diff --git a/src/NoPELib/player_settings.py b/src/NoPELib/player_settings.py index 5caea04..8838fe9 100644 --- a/src/NoPELib/player_settings.py +++ b/src/NoPELib/player_settings.py @@ -13,14 +13,21 @@ _log = logging.getLogger('NoPE-Lib') class Hook: + """ + A class that allows the creation of hooks. + + Methods: + connect: Connect a slot when the signal is launched. + """ def __init__(self): self._slot = [] - + def __call__(self, *args, **kwargs): for fct in self._slot: fct(*args, **kwargs) - + def connect(self, fct): + """ Connect a slot when the signal is launched. """ self._slot.append(fct) @@ -43,15 +50,21 @@ class PlayersManager: """ defaultPlayerConfig = {"flags": [], "expansions": {}, "games": {}} - def __init__(self, gameID: str=None, activePlayers: list[str]=None, playersPath: str='../players.json', loggerID: str='PlayersManager'): + def __init__( + self, gameID: str=None, activePlayers: list[str]=None, + playersPath: str='../players.json', loggerID: str='PlayersManager' + ): """ Initialises a list of players. Args: - gameID (str): The ID of the game used in the configuration file. - activePlayers (list of str): The names of the players that are playing. Defaults to none which includes all players. - playersPath (str, optional): The path of the players.json file. Defaults to '../players.json'. - loggerID (str, optional): The ID used for logging. Defaults to 'Players'. + gameID (str): The ID of the game used in the configuration file. + activePlayers (list of str): The names of the players that are playing. + Defaults to none which includes all players. + playersPath (str, optional): The path of the players.json file. + Defaults to '../players.json'. + loggerID (str, optional): The ID used for logging. Defaults to + 'Players'. """ # Store the arguments @@ -60,7 +73,7 @@ class PlayersManager: # Deal with the path self._playersPath = Path(playersPath) # Get the config file - with open(self._playersPath, 'r') as f: + with open(self._playersPath, "r", encoding="utf-8") as f: self._cfg = json.load(f) # Create the players activePlayers = activePlayers if activePlayers is not None else self._cfg["players"].keys() @@ -72,7 +85,7 @@ class PlayersManager: # Create the signals self.onMadePlayerActive = Hook() self.onMadePlayerInactive = Hook() - + def __getitem__(self, playerName: str): """ Get a player object. @@ -88,16 +101,12 @@ class PlayersManager: self._log.debug(f"Made {playerName} active") self.onMadePlayerActive(playerName) return self._player_data[playerName] - - def __setitem__(self, playerName: str, config: dict, makeNewPlayerObject=False): + + def __setitem__(self, playerName: str, config: dict): """ Replace a player's config with another one. - - Arguments: - playerName (str): - config (dict): - makeNewPlayerObject (bool): """ + makeNewPlayerObject = False if makeNewPlayerObject: previousInstance = self._player_data[playerName] self._player_data[playerName] = Player(playerName, self, **config) @@ -109,49 +118,58 @@ class PlayersManager: del self._player_data[playerName] self.onMadePlayerInactive(playerName) self._log.debug(f"Removed {playerName} from active players") - + def __len__(self): return len(self._player_data) - + def __iter__(self): return self._player_data.__iter__() - + def __repr__(self): return f"Manager is playing {self.gameID} with {len(self._player_data)} active players" - + @property def config(self): + """ Deepcopy of the dictionnary of the config. """ return copy.deepcopy(self._cfg) @property def gameID(self): + """ String of the current game ID. """ return self._currentGameID @gameID.setter def gameID(self, gameID: str): self._currentGameID = gameID - self._player_data = {name: Player(name, self, **cfg) for name, cfg in self._cfg["players"].items()} - + self._player_data = { + name: Player(name, self, **cfg) + for name, cfg in self._cfg["players"].items() + } + @gameID.deleter def gameID(self): self._currentGameID = None - self._player_data = {name: Player(name, self, **cfg) for name, cfg in self._cfg["players"].items()} + self._player_data = { + name: Player(name, self, **cfg) + for name, cfg in self._cfg["players"].items() + } def keys(self): """ Iterator of the active players' names """ return self._player_data.keys() - + def values(self): """ Iterator of the active players' objects """ return self._player_data.keys() - + def items(self): """ Two iterators of the activate players' names and object """ return self._player_data.items() - + def save(self): - with open(self._playersPath, 'w') as f: - self._cfg = json.dump(f, self._cfg) + """ Save the config to the disk. """ + with open(self._playersPath, "w", encoding="utf-8") as f: + self._cfg = f.write(json.dump(f, self._cfg)) class Player: @@ -165,58 +183,74 @@ class Player: gameState: Methods: - generateConfig: + exportConfig: punish: """ - def __init__(self, name: str, manager: PlayersManager, **cfg): - self._name = name + def __init__(self, playerName: str, manager: PlayersManager, **cfg): + self._name = playerName self._manager = manager + self._gamesSave = {} for key, val in cfg.items(): setattr(self, "_" + key, val) - + def __repr__(self): - return f"Player {self._name} has {len(self._flags)} flag(s), {len(self._expansions)} expansions and {len(self._games)} saved game" - + txt = f"Player {self._name} has " + txt += f"{len(self.flags)} flags, " + txt += f"{len(self._expansions)} expansions and " + txt += f"{len(self._gamesSave)} saved game" + return txt + @property def name(self): + """ String representing the name of the player. """ return self._name - + @property def flags(self): + """ List of the flags associated with the player. """ return self._flags @flags.setter def flags(self, newFlags): self._flags = newFlags # The container was changed and must be transmited to the manager - self._manager[self._name] = self.generateConfig() + self._manager[self._name] = self.exportConfig() @property - def expansionsConfig(self): - return self._expansionsConfig - - @expansionsConfig.setter + def expansions(self): + """ Dictionnary of the player's expansion settings. """ + return self._expansions + + @expansions.setter def expansions(self, newExpansions): - self._expansionsConfig = newExpansions + self._expansions = newExpansions # The container was changed and must be transmited to the manager - self._manager[self._name] = self.generateConfig() - + self._manager[self._name] = self.exportConfig() + @property def gameSave(self): - """The save for the current game. If no configuration was found None is returned.""" - return self._games.get(self._manager.gameID) - + """ + The save for the current game. If no configuration was found None + is returned. + """ + return self._gamesSave.get(self._manager.gameID) + @gameSave.setter def gameSave(self, newGameSave): - self._games[self._manager.gameID] = newGameSave + self._gamesSave[self._manager.gameID] = newGameSave + + def exportConfig(self): + """ Export the exact player's configuration. """ + return { + "flags": self._flags, + "expansions": self._expansions, + "games": self._gamesSave + } - def generateConfig(self): - return {"flags": self._flags, "expansions": self._expansions, "games": self._games} - def punish(self, value, preferedExpansion: str=None): - do_something = lambda value: value + do_something = lambda value, preferedExpansion: value additionalInfos = { - "expansionClass": "challengeDB", + "expansionID": "challengeDB", "balancedValue": 0.2, "showOnScreen": "Do 100 push-up", "error": None, @@ -224,59 +258,10 @@ class Player: } # NOTE Make a result class instead and an error class # TODO Implement a way to choose between the available expansions - additionalInfos = do_something(value) + additionalInfos = do_something(value, preferedExpansion) return additionalInfos -class Expansion: - """ - - Attributes: - specs: - - Methods: - step: - reset: - close: - - Signals: - onError: - """ - - def __init__(self): - """ - Intended behavior of the method: - Initialise the creation of an expansion - Should NEVER use any kind of argument. - Raise an error if not available. - """ - self.onError = Hook() - self._specs - pass - - @property - def specs(self): - return self._specs - - @abc.abstractmethod - def step(self, action): - """ - Call close if an error is thrown - """ - pass - - @abc.abstractmethod - def reset(self): - pass - - @abc.abstractmethod - def close(self): - pass - - def makeSpecs(self): - pass - - class ExpansionsManager: """ Manager of the availability of the expansions. @@ -286,75 +271,100 @@ class ExpansionsManager: Container of the expansions to try making available activeExpansions: dict of Expansion Container of the relevent expansions - - TODO Add an interface to allow the modification of the expansion settings """ defaultExpansionConfig = {"players": (), "types": ()} keysConvert2Tuple = ("players", "types") tags = ["shock", "spice", "sour", "drink", "challenge"] + # TODO Add an interface to allow the modification of the expansion settings + # (attributed players / types) + # TODO Allow to get a list of expansions with a specific tag/player def __init__(self, playersManager: PlayersManager, includedExpansions: tuple[str]=None): + """ + Arguments: + tryInclude: bool=False, tryActivate: bool=False + """ # Create the attributes self.playersManager = playersManager self._cfg = playersManager.config["expansions"] - self._includedExpansions = tuple(includedExpansions) if includedExpansions is not None else () + if includedExpansions is not None: + self._includedExpansions = tuple(includedExpansions) + else: + self._includedExpansions = () # Convert the required lists into tuples for expansionID in self._cfg["expansions"]: for key in self.keysConvert2Tuple: - self._cfg["expansions"][expansionID][key] = tuple(self._cfg["expansions"][expansionID][key]) + converted = tuple(self._cfg["expansions"][expansionID][key]) + self._cfg["expansions"][expansionID][key] = converted # Compute the active expansions self._activeExpansions = {} for expansionID in self._listPossiblyValidExpansions(): self._createExpansion(expansionID) - + # Connect the signals to the slots playersManager.onMadePlayerActive.connect(self._activePlayerAdded) playersManager.onMadePlayerInactive.connect(self._activePlayerRemoved) - + + def __getitem__(self, expansionID: str): + return self._activeExpansions[expansionID] + + def __len__(self): + return len(self._activeExpansions) + + def __iter__(self): + return self._activeExpansions.__iter__() + + def __repr__(self): + return f"ExpansionsManager has {len(self._activeExpansions)} active expansions" + @property def includedExpansions(self): + """ Dictionnary of the included expansions. """ return self._includedExpansions - + @includedExpansions.setter def includedExpansions(self, newIncluded: tuple[str]): self._includedExpansions = tuple(newIncluded) self._includeChanged() - + @includedExpansions.deleter def includedExpansions(self): self._includedExpansions = () - + @property - def activeExpansions(self): - return self._activeExpansions - + def config(self): + """ Dictionnary of the expansions' settings. """ + return self._cfg + def _getPlayersFillMissing(self, expansionID): return set(self._cfg["expansions"].get(expansionID, {}).get("players", [])) - + def _listPossiblyValidExpansions(self): """ - List expansions that are included, defined in PDOLib and are available to at least one active player. + List expansions that are included, defined in PDOLib and are available + to at least one active player. """ activePlayers = set(self.playersManager.keys()) possiblyValidExpansions = [ expansion for expansion in self._includedExpansions - if hasattr(PDOLib, expansion) and not self._getPlayersFillMissing(expansion).isdisjoint(activePlayers) + if hasattr(PDOLib, expansion) and + not self._getPlayersFillMissing(expansion).isdisjoint(activePlayers) ] return possiblyValidExpansions - + def _createExpansion(self, expansionID): try: - expansion = getattr(PDOLib, expansionID)() + expansion = getattr(PDOLib, expansionID)(expansionID, self) except: pass else: self._activeExpansions[expansionID] = expansion - + def _removeExpansion(self, expansionID): expansion = self._activeExpansions.pop(expansionID) expansion.close() del expansion - + def _includeChanged(self): """ Update the list of active Expansions @@ -374,10 +384,11 @@ class ExpansionsManager: def _activePlayerAdded(self, playerName): # Find the expansions that are to be created - possibleAddition = set(self._includedExpansions).difference(self.activeExpansions) + possibleAddition = set(self._includedExpansions).difference(self._activeExpansions) filteredAddition = [ expansionID for expansionID in possibleAddition - if hasattr(PDOLib, expansionID) and playerName in self._getPlayersFillMissing(expansionID) + if hasattr(PDOLib, expansionID) and + playerName in self._getPlayersFillMissing(expansionID) ] # Create the expansions for expansionID in filteredAddition: @@ -386,7 +397,7 @@ class ExpansionsManager: def _activePlayerRemoved(self, playerName): # Find the expansions that are to be removed filteredRemoval = [ - expansionID for expansionID in self.activeExpansions + expansionID for expansionID in self._activeExpansions if playerName in self._cfg["players"] and len(self._cfg["players"]) == 1 ] # Close the expansions @@ -396,7 +407,7 @@ class ExpansionsManager: def _errorOccured(self, expansionID): self._removeExpansion(expansionID) - def _expansionPlayersChanged(self, expansionID): + def expansionPlayersChanged(self, expansionID): expansionIsActive = expansionID in self._activeExpansions activePlayers = set(self.playersManager.keys()) expansionHasNoPlayers = self._getPlayersFillMissing(expansionID).isdisjoint(activePlayers) @@ -405,32 +416,109 @@ class ExpansionsManager: self._removeExpansion(expansionID) elif (not expansionIsActive) and (not expansionHasNoPlayers) and expansionDefined: self._createExpansion(expansionID) - + def initilizeExpansion(self, expansionID: str, suppressError: bool=False): """ - Initilise the expansion. + Initilise (possibly again) the expansion. + + Arguments: + expansionID (str): + suppressError (bool): """ if expansionID in self._activeExpansions: self._removeExpansion(expansionID) + # Make sure that the expansion is relevent if not suppressError: - # Make sure that the expansion is relevent # Verify that creating the expansion was successful pass # Try creating a new instance of the expansion # Update the list of relevent expansions - pass + # TODO Implement the rest + + +class Expansion: + """ + A meta class implementation meant to describe how to interact with any kind + of expansions. + + Attributes: + players (tuple of str): + tags (tuple of flag): + + Methods: + step: + reset: + close: + + Signals: + onError: + """ + + def __init__(self, ID: str, expansionsManager: ExpansionsManager): + """ + Initialise the creation of an expansion and raise an error if not + available. + """ + self._ID = ID + self._manager = expansionsManager + self.onError = Hook() + + @property + def players(self): + """ Players that have access to this expansion. """ + return self._manager.config[self._ID]["players"] + + @players.setter + def players(self, newPlayers): + """ Players that have access to this expansion. """ + self._manager.config[self._ID]["players"] = tuple(newPlayers) + self._manager.expansionPlayersChanged(self._ID) + + @players.deleter + def players(self): + self._manager.config[self._ID]["players"] = () + self._manager.expansionPlayersChanged(self._ID) + + @property + def tags(self): + """ Players that have access to this expansion. """ + return self._manager.config[self._ID]["tags"] + + @tags.setter + def tags(self, newPlayers): + """ Players that have access to this expansion. """ + self._manager.config[self._ID]["tags"] = tuple(newPlayers) + + @tags.deleter + def tags(self): + self._manager.config[self._ID]["tags"] = () + + @abc.abstractmethod + def step(self, action): + """ + Call close if an error is thrown + """ + raise NotImplementedError + + @abc.abstractmethod + def reset(self): + raise NotImplementedError + + @abc.abstractmethod + def close(self): + raise NotImplementedError if __name__ == "__main__": # Test run to make sure nothing is flagrantly flawed configPath = Path(__file__).parent / "players.json" - manager = PlayersManager(playersPath=configPath, gameID="gameID0") + pm = PlayersManager(playersPath=configPath, gameID="gameID0") # Iteration - for playerName in manager: - print(playerName) + for name in pm: + print(name) # Modification of a players data - brosef = manager["Brosef"] + brosef = pm["Brosef"] brosef.gameSave = [1] - print(manager["Brosef"].gameSave) + print(pm["Brosef"].gameSave) brosef.state = ["alive"] - # TODO Verify that changing the gameID works as intended \ No newline at end of file + # TODO Verify that changing the gameID works as intended