Further defined the Expansion class

This commit is contained in:
2025-06-26 18:46:05 -04:00
parent 2f51162eef
commit fca81916d5
3 changed files with 248 additions and 147 deletions

View File

@ -1,14 +1,14 @@
{ {
"expansions": "expansions":
{ {
"ShockColar1": {"players": ["Brosef"], "types": ["shock"]}, "ShockColar1": {"players": ["Brosef"], "tags": ["shock"]},
"RobotBarman": {"players": ["Tango", "TRS_MML"], "types": ["drink"]}, "RobotBarman": {"players": ["Tango", "TRS_MML"], "tags": ["drink"]},
"ChallengeDB": {"players": ["TRS_MML"], "types": ["challenge"]}, "ChallengeDB": {"players": ["TRS_MML"], "tags": ["challenge"]},
"SourCandy": {"players": [], "types": ["food"]} "SourCandy": {"players": [], "tags": ["food"]}
}, },
"players": { "players": {
"Brosef": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": 2}}, "Brosef": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "gamesSave": {"gameID0": 2}},
"TRS_MML": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": 1}}, "TRS_MML": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "gamesSave": {"gameID0": 1}},
"Tango": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": 0}} "Tango": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "gamesSave": {"gameID0": 0}}
} }
} }

View File

@ -1,7 +1,20 @@
class Expansion: from player_settings import Expansion
def __init__(self, parent, globalConfig):
setattr(parent, self.__class__.ID, self)
class PlayerExpansion: class serialShockers(Expansion):
def __init__(self, player, localConfig): _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 pass

View File

@ -13,6 +13,12 @@ _log = logging.getLogger('NoPE-Lib')
class Hook: class Hook:
"""
A class that allows the creation of hooks.
Methods:
connect: Connect a slot when the signal is launched.
"""
def __init__(self): def __init__(self):
self._slot = [] self._slot = []
@ -21,6 +27,7 @@ class Hook:
fct(*args, **kwargs) fct(*args, **kwargs)
def connect(self, fct): def connect(self, fct):
""" Connect a slot when the signal is launched. """
self._slot.append(fct) self._slot.append(fct)
@ -43,15 +50,21 @@ class PlayersManager:
""" """
defaultPlayerConfig = {"flags": [], "expansions": {}, "games": {}} 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. Initialises a list of players.
Args: Args:
gameID (str): The ID of the game used in the configuration file. 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. activePlayers (list of str): The names of the players that are playing.
playersPath (str, optional): The path of the players.json file. Defaults to '../players.json'. Defaults to none which includes all players.
loggerID (str, optional): The ID used for logging. Defaults to '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 # Store the arguments
@ -60,7 +73,7 @@ class PlayersManager:
# Deal with the path # Deal with the path
self._playersPath = Path(playersPath) self._playersPath = Path(playersPath)
# Get the config file # 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) self._cfg = json.load(f)
# Create the players # Create the players
activePlayers = activePlayers if activePlayers is not None else self._cfg["players"].keys() activePlayers = activePlayers if activePlayers is not None else self._cfg["players"].keys()
@ -89,15 +102,11 @@ class PlayersManager:
self.onMadePlayerActive(playerName) self.onMadePlayerActive(playerName)
return self._player_data[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. Replace a player's config with another one.
Arguments:
playerName (str):
config (dict):
makeNewPlayerObject (bool):
""" """
makeNewPlayerObject = False
if makeNewPlayerObject: if makeNewPlayerObject:
previousInstance = self._player_data[playerName] previousInstance = self._player_data[playerName]
self._player_data[playerName] = Player(playerName, self, **config) self._player_data[playerName] = Player(playerName, self, **config)
@ -121,21 +130,29 @@ class PlayersManager:
@property @property
def config(self): def config(self):
""" Deepcopy of the dictionnary of the config. """
return copy.deepcopy(self._cfg) return copy.deepcopy(self._cfg)
@property @property
def gameID(self): def gameID(self):
""" String of the current game ID. """
return self._currentGameID return self._currentGameID
@gameID.setter @gameID.setter
def gameID(self, gameID: str): def gameID(self, gameID: str):
self._currentGameID = gameID 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 @gameID.deleter
def gameID(self): def gameID(self):
self._currentGameID = None 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): def keys(self):
""" Iterator of the active players' names """ """ Iterator of the active players' names """
@ -150,8 +167,9 @@ class PlayersManager:
return self._player_data.items() return self._player_data.items()
def save(self): def save(self):
with open(self._playersPath, 'w') as f: """ Save the config to the disk. """
self._cfg = json.dump(f, self._cfg) with open(self._playersPath, "w", encoding="utf-8") as f:
self._cfg = f.write(json.dump(f, self._cfg))
class Player: class Player:
@ -165,58 +183,74 @@ class Player:
gameState: gameState:
Methods: Methods:
generateConfig: exportConfig:
punish: punish:
""" """
def __init__(self, name: str, manager: PlayersManager, **cfg): def __init__(self, playerName: str, manager: PlayersManager, **cfg):
self._name = name self._name = playerName
self._manager = manager self._manager = manager
self._gamesSave = {}
for key, val in cfg.items(): for key, val in cfg.items():
setattr(self, "_" + key, val) setattr(self, "_" + key, val)
def __repr__(self): 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 @property
def name(self): def name(self):
""" String representing the name of the player. """
return self._name return self._name
@property @property
def flags(self): def flags(self):
""" List of the flags associated with the player. """
return self._flags return self._flags
@flags.setter @flags.setter
def flags(self, newFlags): def flags(self, newFlags):
self._flags = newFlags self._flags = newFlags
# The container was changed and must be transmited to the manager # The container was changed and must be transmited to the manager
self._manager[self._name] = self.generateConfig() self._manager[self._name] = self.exportConfig()
@property @property
def expansionsConfig(self): def expansions(self):
return self._expansionsConfig """ Dictionnary of the player's expansion settings. """
return self._expansions
@expansionsConfig.setter @expansions.setter
def expansions(self, newExpansions): def expansions(self, newExpansions):
self._expansionsConfig = newExpansions self._expansions = newExpansions
# The container was changed and must be transmited to the manager # The container was changed and must be transmited to the manager
self._manager[self._name] = self.generateConfig() self._manager[self._name] = self.exportConfig()
@property @property
def gameSave(self): 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 @gameSave.setter
def gameSave(self, newGameSave): def gameSave(self, newGameSave):
self._games[self._manager.gameID] = newGameSave self._gamesSave[self._manager.gameID] = newGameSave
def generateConfig(self): def exportConfig(self):
return {"flags": self._flags, "expansions": self._expansions, "games": self._games} """ Export the exact player's configuration. """
return {
"flags": self._flags,
"expansions": self._expansions,
"games": self._gamesSave
}
def punish(self, value, preferedExpansion: str=None): def punish(self, value, preferedExpansion: str=None):
do_something = lambda value: value do_something = lambda value, preferedExpansion: value
additionalInfos = { additionalInfos = {
"expansionClass": "challengeDB", "expansionID": "challengeDB",
"balancedValue": 0.2, "balancedValue": 0.2,
"showOnScreen": "Do 100 push-up", "showOnScreen": "Do 100 push-up",
"error": None, "error": None,
@ -224,59 +258,10 @@ class Player:
} }
# NOTE Make a result class instead and an error class # NOTE Make a result class instead and an error class
# TODO Implement a way to choose between the available expansions # TODO Implement a way to choose between the available expansions
additionalInfos = do_something(value) additionalInfos = do_something(value, preferedExpansion)
return additionalInfos 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: class ExpansionsManager:
""" """
Manager of the availability of the expansions. Manager of the availability of the expansions.
@ -286,22 +271,31 @@ class ExpansionsManager:
Container of the expansions to try making available Container of the expansions to try making available
activeExpansions: dict of Expansion activeExpansions: dict of Expansion
Container of the relevent expansions Container of the relevent expansions
TODO Add an interface to allow the modification of the expansion settings
""" """
defaultExpansionConfig = {"players": (), "types": ()} defaultExpansionConfig = {"players": (), "types": ()}
keysConvert2Tuple = ("players", "types") keysConvert2Tuple = ("players", "types")
tags = ["shock", "spice", "sour", "drink", "challenge"] 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): def __init__(self, playersManager: PlayersManager, includedExpansions: tuple[str]=None):
"""
Arguments:
tryInclude: bool=False, tryActivate: bool=False
"""
# Create the attributes # Create the attributes
self.playersManager = playersManager self.playersManager = playersManager
self._cfg = playersManager.config["expansions"] 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 # Convert the required lists into tuples
for expansionID in self._cfg["expansions"]: for expansionID in self._cfg["expansions"]:
for key in self.keysConvert2Tuple: 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 # Compute the active expansions
self._activeExpansions = {} self._activeExpansions = {}
for expansionID in self._listPossiblyValidExpansions(): for expansionID in self._listPossiblyValidExpansions():
@ -311,8 +305,21 @@ class ExpansionsManager:
playersManager.onMadePlayerActive.connect(self._activePlayerAdded) playersManager.onMadePlayerActive.connect(self._activePlayerAdded)
playersManager.onMadePlayerInactive.connect(self._activePlayerRemoved) 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 @property
def includedExpansions(self): def includedExpansions(self):
""" Dictionnary of the included expansions. """
return self._includedExpansions return self._includedExpansions
@includedExpansions.setter @includedExpansions.setter
@ -325,26 +332,29 @@ class ExpansionsManager:
self._includedExpansions = () self._includedExpansions = ()
@property @property
def activeExpansions(self): def config(self):
return self._activeExpansions """ Dictionnary of the expansions' settings. """
return self._cfg
def _getPlayersFillMissing(self, expansionID): def _getPlayersFillMissing(self, expansionID):
return set(self._cfg["expansions"].get(expansionID, {}).get("players", [])) return set(self._cfg["expansions"].get(expansionID, {}).get("players", []))
def _listPossiblyValidExpansions(self): 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()) activePlayers = set(self.playersManager.keys())
possiblyValidExpansions = [ possiblyValidExpansions = [
expansion for expansion in self._includedExpansions 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 return possiblyValidExpansions
def _createExpansion(self, expansionID): def _createExpansion(self, expansionID):
try: try:
expansion = getattr(PDOLib, expansionID)() expansion = getattr(PDOLib, expansionID)(expansionID, self)
except: except:
pass pass
else: else:
@ -374,10 +384,11 @@ class ExpansionsManager:
def _activePlayerAdded(self, playerName): def _activePlayerAdded(self, playerName):
# Find the expansions that are to be created # Find the expansions that are to be created
possibleAddition = set(self._includedExpansions).difference(self.activeExpansions) possibleAddition = set(self._includedExpansions).difference(self._activeExpansions)
filteredAddition = [ filteredAddition = [
expansionID for expansionID in possibleAddition 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 # Create the expansions
for expansionID in filteredAddition: for expansionID in filteredAddition:
@ -386,7 +397,7 @@ class ExpansionsManager:
def _activePlayerRemoved(self, playerName): def _activePlayerRemoved(self, playerName):
# Find the expansions that are to be removed # Find the expansions that are to be removed
filteredRemoval = [ 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 if playerName in self._cfg["players"] and len(self._cfg["players"]) == 1
] ]
# Close the expansions # Close the expansions
@ -396,7 +407,7 @@ class ExpansionsManager:
def _errorOccured(self, expansionID): def _errorOccured(self, expansionID):
self._removeExpansion(expansionID) self._removeExpansion(expansionID)
def _expansionPlayersChanged(self, expansionID): def expansionPlayersChanged(self, expansionID):
expansionIsActive = expansionID in self._activeExpansions expansionIsActive = expansionID in self._activeExpansions
activePlayers = set(self.playersManager.keys()) activePlayers = set(self.playersManager.keys())
expansionHasNoPlayers = self._getPlayersFillMissing(expansionID).isdisjoint(activePlayers) expansionHasNoPlayers = self._getPlayersFillMissing(expansionID).isdisjoint(activePlayers)
@ -408,29 +419,106 @@ class ExpansionsManager:
def initilizeExpansion(self, expansionID: str, suppressError: bool=False): 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: if expansionID in self._activeExpansions:
self._removeExpansion(expansionID) self._removeExpansion(expansionID)
if not suppressError:
# Make sure that the expansion is relevent # Make sure that the expansion is relevent
if not suppressError:
# Verify that creating the expansion was successful # Verify that creating the expansion was successful
pass pass
# Try creating a new instance of the expansion # Try creating a new instance of the expansion
# Update the list of relevent expansions # 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__": if __name__ == "__main__":
# Test run to make sure nothing is flagrantly flawed # Test run to make sure nothing is flagrantly flawed
configPath = Path(__file__).parent / "players.json" configPath = Path(__file__).parent / "players.json"
manager = PlayersManager(playersPath=configPath, gameID="gameID0") pm = PlayersManager(playersPath=configPath, gameID="gameID0")
# Iteration # Iteration
for playerName in manager: for name in pm:
print(playerName) print(name)
# Modification of a players data # Modification of a players data
brosef = manager["Brosef"] brosef = pm["Brosef"]
brosef.gameSave = [1] brosef.gameSave = [1]
print(manager["Brosef"].gameSave) print(pm["Brosef"].gameSave)
brosef.state = ["alive"] brosef.state = ["alive"]
# TODO Verify that changing the gameID works as intended # TODO Verify that changing the gameID works as intended