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":
{
"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}}
}
}

View File

@ -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

View File

@ -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
# TODO Verify that changing the gameID works as intended