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 logging
from pathlib import Path
import PDOLib
_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:
"""
Manager of players for a given game.
@ -22,20 +32,21 @@ class PlayersManager:
Attributes:
gameID (str): The gameID of the active game
config (dict): The in memory state of the config file
Methods:
save:
"""
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.
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'.
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'.
"""
@ -54,45 +65,45 @@ class PlayersManager:
for name, player_cfg in self._cfg["players"].items()
if name in activePlayers
}
# Create the signals
self.onMadePlayerActive = Hook()
self.onMadePlayerInactive = Hook()
def __getitem__(self, playerName: str):
"""
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 playerName not in self._cfg["players"]:
# 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:
if playerName not in self._player_data:
# Fetch the player's data and make them active
playerAdded = Player(playerName, self, **self._cfg["players"][playerName])
self._player_data[playerName] = playerAdded
self._log.debug(f"Fetched {playerName}'s data and made them active")
return playerAdded
else:
return self._player_data[playerName]
if playerName in self._cfg["players"]:
self[playerName] = self._cfg["players"][playerName]
else:
self[playerName] = copy.deepcopy(self.defaultPlayerConfig)
self._log.debug(f"Made {playerName} active")
self.onMadePlayerActive(playerName)
return self._player_data[playerName]
def __setitem__(self, playerName: str, config: dict, makeNewPlayerObject=False):
"""
Replace a player's config with another one. It possible to also automatically replace the
player object by specifying the corresponding argument.
Replace a player's config with another one.
Arguments:
playerName (str):
config (dict):
makeNewPlayerObject (bool):
"""
self._log.debug(f"Changed {playerName}'s config")
if makeNewPlayerObject:
previousInstance = self._player_data[playerName]
self._player_data[playerName] = Player(playerName, self, **config)
del previousInstance
self._cfg["players"][playerName] = config
self._log.debug(f"Changed {playerName}'s config")
def __delitem__(self, playerName):
self._log.debug(f"Removing {playerName} from active players")
del self._player_data[playerName]
self._log.debug(f"Removed {playerName} from active players")
def __len__(self):
return len(self._player_data)
@ -103,6 +114,10 @@ class PlayersManager:
def __repr__(self):
return f"Manager is playing {self.gameID} with {len(self._player_data)} active players"
@property
def config(self):
return copy.deepcopy(self._cfg)
@property
def gameID(self):
return self._currentGameID
@ -146,6 +161,7 @@ class Player:
Methods:
generateConfig:
punish:
"""
def __init__(self, name: str, manager: PlayersManager, **cfg):
self._name = name
@ -195,7 +211,7 @@ class Player:
def punish(self, value, preferedExpansion: str=None):
do_something = lambda value: value
additionalInfos = {
"expansionID": "challengeDB",
"expansionClass": "challengeDB",
"balancedValue": 0.2,
"showOnScreen": "Do 100 push-up",
"error": None,
@ -210,6 +226,7 @@ class Expansion:
"""
Attributes:
specs:
Methods:
step:
@ -217,13 +234,19 @@ class Expansion:
close:
"""
@abc.abstractmethod
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
@property
def specs(self):
return self._specs
@abc.abstractmethod
def step(self, action):
"""
@ -239,6 +262,9 @@ class Expansion:
def close(self):
pass
def makeSpecs(self):
pass
class ExpansionsManager:
"""
@ -247,15 +273,32 @@ class ExpansionsManager:
Attributes:
includedExpansions: tuple of str
Container of the expansions to try making available
releventExpansions: dict of Expansion
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"]
def __init__(self, playersManager: PlayersManager, includedExpansions: tuple[str]=None):
# Create the attributes
self.playersManager = playersManager
self._cfg = playersManager.config["expansions"]
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
def includedExpansions(self):
@ -264,40 +307,104 @@ class ExpansionsManager:
@includedExpansions.setter
def includedExpansions(self, newIncluded: tuple[str]):
self._includedExpansions = tuple(newIncluded)
self._includedChanged()
self._includeChanged()
@includedExpansions.deleter
def includedExpansions(self):
self._includedExpansions = ()
@property
def releventExpansions(self):
return self._releventExpansions
def activeExpansions(self):
return self._activeExpansions
def _includedChanged(self):
"""
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 _getPlayersFillMissing(self, expansionID):
return set(self._cfg["expansions"].get(expansionID, {}).get("players", []))
def _activePlayersChanged(self, activatePlayers):
pass
def _listPossiblyValidExpansions(self):
"""
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):
pass
self._removeExpansion(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.
In any case, try creating a new
Initilise the expansion.
"""
# 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
# Update the list of relevent expansions
pass
@ -315,3 +422,4 @@ if __name__ == "__main__":
brosef.gameSave = [1]
print(manager["Brosef"].gameSave)
brosef.state = ["alive"]
# TODO Verify that changing the gameID works as intended