Implemented alot of methods in ExpansionsManager

This commit is contained in:
2025-06-22 15:09:12 -04:00
parent 38f6142ed6
commit 075e9899ba

View File

@ -1,7 +1,4 @@
""" """
Needs to manage any number of players
Needs to work with mulitple rounds/turns
Needs to handle modifying number of players
""" """
@ -10,10 +7,23 @@ import copy
import json import json
import logging import logging
from pathlib import Path from pathlib import Path
import PDOLib
_log = logging.getLogger('NoPE-Lib') _log = logging.getLogger('NoPE-Lib')
class Hook:
def __init__(self):
self._slot = []
def __call__(self, *args, **kwargs):
for fct in self._slot:
fct(*args, **kwargs)
def connect(self, fct):
self._slot.append(fct)
class PlayersManager: class PlayersManager:
""" """
Manager of players for a given game. Manager of players for a given game.
@ -22,20 +32,21 @@ class PlayersManager:
Attributes: Attributes:
gameID (str): The gameID of the active game gameID (str): The gameID of the active game
config (dict): The in memory state of the config file
Methods: Methods:
save: save:
""" """
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', includedExpansions: tuple[str]=None): def __init__(self, gameID: str=None, activePlayers: list[str]=None, playersPath: str='../players.json', loggerID: str='PlayersManager', includedExpansions: tuple[str]=None):
""" """
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. Defaults to none which includes all players.
playersPath (str, optional): The path of the players.json file. Defaults to './players.json'. 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'. loggerID (str, optional): The ID used for logging. Defaults to 'Players'.
""" """
@ -54,45 +65,45 @@ class PlayersManager:
for name, player_cfg in self._cfg["players"].items() for name, player_cfg in self._cfg["players"].items()
if name in activePlayers if name in activePlayers
} }
# Create the signals
self.onMadePlayerActive = Hook()
self.onMadePlayerInactive = Hook()
def __getitem__(self, playerName: str): def __getitem__(self, playerName: str):
""" """
Get a player object. Get a player object.
If the player wasn't active, make them active. If the player is not active, make them active.
If the player didn't exist previously, create a default config and make them active. If the player didn't exist previously, create a default config and make them active.
""" """
if playerName not in self._cfg["players"]: if playerName not in self._player_data:
# Create the brand new player
self._cfg["players"][playerName] = copy.deepcopy(self.defaultPlayerConfig)
newPlayer = Player(playerName, self, **self._cfg["players"][playerName])
# Make the player active
self._player_data[playerName] = newPlayer
self._log.debug(f"Created a new player called {playerName}")
return newPlayer
elif playerName not in self._player_data:
# Fetch the player's data and make them active # Fetch the player's data and make them active
playerAdded = Player(playerName, self, **self._cfg["players"][playerName]) if playerName in self._cfg["players"]:
self._player_data[playerName] = playerAdded self[playerName] = self._cfg["players"][playerName]
self._log.debug(f"Fetched {playerName}'s data and made them active") else:
return playerAdded self[playerName] = copy.deepcopy(self.defaultPlayerConfig)
else: self._log.debug(f"Made {playerName} active")
return self._player_data[playerName] self.onMadePlayerActive(playerName)
return self._player_data[playerName]
def __setitem__(self, playerName: str, config: dict, makeNewPlayerObject=False): def __setitem__(self, playerName: str, config: dict, makeNewPlayerObject=False):
""" """
Replace a player's config with another one. It possible to also automatically replace the Replace a player's config with another one.
player object by specifying the corresponding argument.
Arguments:
playerName (str):
config (dict):
makeNewPlayerObject (bool):
""" """
self._log.debug(f"Changed {playerName}'s config")
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)
del previousInstance del previousInstance
self._cfg["players"][playerName] = config self._cfg["players"][playerName] = config
self._log.debug(f"Changed {playerName}'s config")
def __delitem__(self, playerName): def __delitem__(self, playerName):
self._log.debug(f"Removing {playerName} from active players")
del self._player_data[playerName] del self._player_data[playerName]
self._log.debug(f"Removed {playerName} from active players")
def __len__(self): def __len__(self):
return len(self._player_data) return len(self._player_data)
@ -103,6 +114,10 @@ class PlayersManager:
def __repr__(self): def __repr__(self):
return f"Manager is playing {self.gameID} with {len(self._player_data)} active players" return f"Manager is playing {self.gameID} with {len(self._player_data)} active players"
@property
def config(self):
return copy.deepcopy(self._cfg)
@property @property
def gameID(self): def gameID(self):
return self._currentGameID return self._currentGameID
@ -146,6 +161,7 @@ class Player:
Methods: Methods:
generateConfig: generateConfig:
punish:
""" """
def __init__(self, name: str, manager: PlayersManager, **cfg): def __init__(self, name: str, manager: PlayersManager, **cfg):
self._name = name self._name = name
@ -195,7 +211,7 @@ class Player:
def punish(self, value, preferedExpansion: str=None): def punish(self, value, preferedExpansion: str=None):
do_something = lambda value: value do_something = lambda value: value
additionalInfos = { additionalInfos = {
"expansionID": "challengeDB", "expansionClass": "challengeDB",
"balancedValue": 0.2, "balancedValue": 0.2,
"showOnScreen": "Do 100 push-up", "showOnScreen": "Do 100 push-up",
"error": None, "error": None,
@ -210,6 +226,7 @@ class Expansion:
""" """
Attributes: Attributes:
specs:
Methods: Methods:
step: step:
@ -217,13 +234,19 @@ class Expansion:
close: close:
""" """
@abc.abstractmethod
def __init__(self): def __init__(self):
""" """
Raise an error if not available Intended behavior of the method:
Initialise the creation of an expansion
Should NEVER use any kind of argument.
Raise an error if not available.
""" """
pass pass
@property
def specs(self):
return self._specs
@abc.abstractmethod @abc.abstractmethod
def step(self, action): def step(self, action):
""" """
@ -239,6 +262,9 @@ class Expansion:
def close(self): def close(self):
pass pass
def makeSpecs(self):
pass
class ExpansionsManager: class ExpansionsManager:
""" """
@ -247,15 +273,32 @@ class ExpansionsManager:
Attributes: Attributes:
includedExpansions: tuple of str includedExpansions: tuple of str
Container of the expansions to try making available Container of the expansions to try making available
releventExpansions: 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": ()}
keysConvert2Tuple = ("players", "types")
tags = ["shock", "spice", "sour", "drink", "challenge"] tags = ["shock", "spice", "sour", "drink", "challenge"]
def __init__(self, playersManager: PlayersManager, includedExpansions: tuple[str]=None): def __init__(self, playersManager: PlayersManager, includedExpansions: tuple[str]=None):
# Create the attributes
self.playersManager = playersManager self.playersManager = playersManager
self._cfg = playersManager.config["expansions"]
self._includedExpansions = tuple(includedExpansions) if includedExpansions is not None else () self._includedExpansions = tuple(includedExpansions) if includedExpansions is not None else ()
self._releventExpansions = {} # TODO Populate the dictionnary # 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])
# 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)
@property @property
def includedExpansions(self): def includedExpansions(self):
@ -264,40 +307,104 @@ class ExpansionsManager:
@includedExpansions.setter @includedExpansions.setter
def includedExpansions(self, newIncluded: tuple[str]): def includedExpansions(self, newIncluded: tuple[str]):
self._includedExpansions = tuple(newIncluded) self._includedExpansions = tuple(newIncluded)
self._includedChanged() self._includeChanged()
@includedExpansions.deleter @includedExpansions.deleter
def includedExpansions(self): def includedExpansions(self):
self._includedExpansions = () self._includedExpansions = ()
@property @property
def releventExpansions(self): def activeExpansions(self):
return self._releventExpansions return self._activeExpansions
def _includedChanged(self): def _getPlayersFillMissing(self, expansionID):
""" return set(self._cfg["expansions"].get(expansionID, {}).get("players", []))
Compute the list of only the relevant expansions.
The expansions that do not exists are excluded.
The expansions that have no players assigned to them are excluded.
The expansions that are not responding are excluded.
"""
pass
def _activePlayersChanged(self, activatePlayers): def _listPossiblyValidExpansions(self):
pass """
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)
]
return possiblyValidExpansions
def _createExpansion(self, expansionID):
try:
expansion = getattr(PDOLib, expansionID)()
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
"""
# Compute the expansions involved in the modification
possiblyValidExpansions = set(self._listPossiblyValidExpansions())
previousExpansions = set(self._includedExpansions)
# Remove irrelevant expansions
expansionsToRemove = previousExpansions.difference(possiblyValidExpansions)
for expansionID in expansionsToRemove:
self._activeExpansions.pop(expansionID).close()
# Add the new expansions
expansionsToAdd = possiblyValidExpansions.difference(previousExpansions)
for expansionID in expansionsToAdd:
self._createExpansion(expansionID)
def _activePlayerAdded(self, playerName):
# Find the expansions that are to be created
possibleAddition = set(self._includedExpansions).difference(self.activeExpansions)
filteredAddition = [
expansionID for expansionID in possibleAddition
if hasattr(PDOLib, expansionID) and playerName in self._getPlayersFillMissing(expansionID)
]
# Create the expansions
for expansionID in filteredAddition:
self._createExpansion(expansionID)
def _activePlayerRemoved(self, playerName):
# Find the expansions that are to be created
filteredRemoval = [
expansionID for expansionID in self.activeExpansions
if playerName in self._cfg["players"] and len(self._cfg["players"])
]
# Close the expansions
for expansionID in filteredRemoval:
self._removeExpansion(expansionID)
def _errorOccured(self, expansionID): def _errorOccured(self, expansionID):
pass self._removeExpansion(expansionID)
def _expansionPlayersChanged(self, expansionID): def _expansionPlayersChanged(self, expansionID):
pass expansionIsActive = expansionID in self._activeExpansions
activePlayers = set(self.playersManager.keys())
expansionHasNoPlayers = self._getPlayersFillMissing(expansionID).isdisjoint(activePlayers)
expansionDefined = hasattr(PDOLib, expansionID)
if expansionIsActive and expansionHasNoPlayers:
self._removeExpansion(expansionID)
elif (not expansionIsActive) and (not expansionHasNoPlayers) and expansionDefined:
self._createExpansion(expansionID)
def resetExpansion(self, expansion: Expansion, suppressError: bool=False): def initilizeExpansion(self, expansionID: str, suppressError: bool=False):
""" """
If the expansion was relevent, close it. Initilise the expansion.
In any case, try creating a new
""" """
# If the expansion was relevent, close it if expansionID in self._activeExpansions:
self._removeExpansion(expansionID)
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 # Try creating a new instance of the expansion
# Update the list of relevent expansions # Update the list of relevent expansions
pass pass
@ -315,3 +422,4 @@ if __name__ == "__main__":
brosef.gameSave = [1] brosef.gameSave = [1]
print(manager["Brosef"].gameSave) print(manager["Brosef"].gameSave)
brosef.state = ["alive"] brosef.state = ["alive"]
# TODO Verify that changing the gameID works as intended