Compare commits

..

27 Commits

Author SHA1 Message Date
3d8bcc0efb Ocliam fixed this a typo or something 2025-07-16 12:06:06 +01:00
23aea1777f Improved the error handling 2025-07-08 16:36:58 -04:00
d16567ea18 Cleaned up some import settings 2025-07-08 16:19:05 -04:00
63f44aacec Fixed some bugs 2025-07-08 16:09:33 -04:00
bb2b953b04 Changed an attribute name to avoid confusion 2025-07-08 14:42:44 -04:00
919793df09 Allowed more flexibility in the creation of expansions 2025-07-08 14:41:14 -04:00
81f38a8044 Ensured that an error midstep would be catched by expansionsManager 2025-07-08 14:22:58 -04:00
ed7cdd21c1 Added a way to find all expansions available to a player 2025-07-08 13:57:48 -04:00
43ee91c853 Update the doc 2025-07-08 13:57:13 -04:00
bab32622ff Added a traceback for when an expansion initilisation goes wrong 2025-07-08 10:07:34 -04:00
587d93f7ea Removed the punish method 2025-07-08 09:41:26 -04:00
20a9aa5505 Added the return information 2025-07-08 09:40:27 -04:00
31e8d9fcb2 Modified a little bit of the documentation 2025-07-08 09:39:51 -04:00
b46d221173 Reinstated the typehint for the Expansion 2025-07-08 09:38:54 -04:00
8abb0acf59 Removed typehint 2025-07-04 20:17:36 -04:00
6117d8c2ed Moved the Hook class 2025-07-04 20:06:55 -04:00
53c86cb2dc Solved a missing dependancy 2025-07-04 20:00:32 -04:00
239f9573be Solved a circular import issue 2025-07-04 19:59:39 -04:00
d83c29e1ec Made some expansions and removed PDOLib 2025-07-03 18:42:30 -04:00
fca81916d5 Further defined the Expansion class 2025-06-26 18:46:05 -04:00
2f51162eef Added a bit more doc 2025-06-23 15:46:27 -04:00
075e9899ba Implemented alot of methods in ExpansionsManager 2025-06-22 15:09:12 -04:00
38f6142ed6 Cleaned up players.json 2025-06-22 12:29:22 -04:00
93c70ba576 Fix init 2025-06-22 10:31:48 -04:00
1abfec05da Restructure the library a bit 2025-06-22 09:34:26 -04:00
c39fd28d96 Connected the __init__ 2025-06-22 09:30:17 -04:00
fdf89ed0a0 Typo correction 2025-06-20 16:50:25 -04:00
10 changed files with 704 additions and 352 deletions

View File

@ -1,19 +0,0 @@
{
"exampleExpansion": {
"optionA": 1,
"optionB": 2
},
"availableExpansion":
{
"bracelet1": "Brosef",
"robotic_barman": "Tango",
"challenge": "TRS"
},
"players": {
"Brosef": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": []}},
"TRS_MML": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": []}},
"Tango": {"flags": [], "expansions": {"exampleExpansion": {"playerOption": 5}}, "games": {"gameID0": []}}
}
}

15
players.json Normal file
View File

@ -0,0 +1,15 @@
{
"expansions":
{
"ShockColar1": {"players": ["Brosef"], "tags": ["shock"], "class": "serialShocker", "config": {"COM_port": "COM3", "shocker_ID": 24770}},
"RobotBarman": {"players": ["Tango", "TRS_MML"], "tags": ["drink"], "class": "bar", "config": {}},
"ChallengeDB": {"players": ["TRS_MML"], "tags": ["challenge"], "class": "challenge", "config": {}},
"SourCandy": {"players": [], "tags": ["food"], "class": "candy", "config": {}},
"Simple": {"players": ["Brosef"], "tags": [], "class": "simplest", "config": {}}
},
"players": {
"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}}
}
}

1
src/NoPELib/__init__.py Normal file
View File

@ -0,0 +1 @@
from .player_settings import PlayersManager, ExpansionsManager

View File

@ -0,0 +1,203 @@
"""
"""
import abc
import copy
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .player_settings import ExpansionsManager
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)
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):
config: The config of the expansion. Can't be modified during execution.
Methods:
__call__: Execute a given action with the expansion
Subclass methods:
step:
reset:
close:
Signals:
midStepError:
"""
def __init__(self, ID: str, expansionsManager: "ExpansionsManager"):
self._ID = ID
self._closed = False
self._manager = expansionsManager
self.midStepError = Hook()
self.midStepError.connect(expansionsManager._midStepError)
def __call__(self, action):
try:
assert not self._closed, "Can't use an expansion that has been closed."
return self.step(action)
except Exception as e:
step_info = {
"expansionID": self._ID,
"showOnScreen": None,
"error": e,
"done": False
}
self.midStepError(self._ID)
return step_info
@property
def config(self):
"""
The config of the expansion.
Can't be modified during execution.
"""
return copy.deepcopy(self._manager.config[self._ID]["config"])
@property
def players(self):
""" Players that have access to the expansion. """
return self._manager.config[self._ID]["players"]
@players.setter
def players(self, newPlayers):
self._manager.expansionPlayersChange(self._ID, newPlayers)
@players.deleter
def players(self):
self._manager.expansionPlayersChange(self._ID, ())
@property
def tags(self):
""" Tags of the expansion. """
return self._manager.config[self._ID]["tags"]
@tags.setter
def tags(self, newPlayers):
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):
raise NotImplementedError
@abc.abstractmethod
def reset(self):
raise NotImplementedError
@abc.abstractmethod
def close(self):
raise NotImplementedError
class serialShocker(Expansion):
"""
Shockers
TODO Describe the config file
"""
_api = {}
def __init__(self, ID, expansionsManager):
super().__init__(ID, expansionsManager)
if self.config["COM_port"] not in serialShocker._api:
self._assignApi()
self.shocker = serialShocker._api[self.config["COM_port"]].shocker(self.config["shocker_ID"])
def _assignApi(self):
from pishock import SerialAPI
serialShocker._api[self.config["COM_port"]] = SerialAPI(self.config["COM_port"])
def step(self, action):
"""
Arguments:
action (tuple): Tuple containing:
a bool deciding if vibrate should be used instead of shock
a positive float that defines the duration
a float within the range [0.0, 1.0] that defines the intensity
"""
# Execute the step
vibrateInstead, duration, intensity = action
callFunc = self.shocker.vibrate if vibrateInstead else self.shocker.shock
callFunc(duration=duration, intensity=intensity)
# Return additionnal info
step_info = {
"expansionID": self._ID,
"showOnScreen": None,
"error": None,
"done": True
}
return step_info
def close(self):
pass
def reset(self):
if self.config["COM_port"] not in serialShocker._api:
serialShocker._api[self.config["COM_port"]].restart()
else:
self._assignApi()
class simplest(Expansion):
""" A very simple expansion that only prints on screen. """
def __init__(self, ID, expansionsManager):
super().__init__(ID, expansionsManager)
print(f"Initialising with config {self.config}")
def step(self, action):
"""
Arguments:
action (tuple): Tuple containing:
a bool deciding if vibrate should be used instead of shock
a positive float that defines the duration
a float within the range [0.0, 1.0] that defines the intensity
"""
# Execute the step
vibrateInstead, duration, intensity = action
values = f"duration={duration}, intensity={intensity}"
interact_type = "Vibrate" if vibrateInstead else "Shock"
message = f"{interact_type} with {values}"
print(message)
# Return additionnal info
step_info = {
"expansionID": self._ID,
"showOnScreen": message,
"error": None,
"done": True
}
return step_info
def close(self):
print("Closing")
def reset(self):
print("Reseting")

View File

@ -0,0 +1,485 @@
"""
"""
import copy
import json
import logging
from pathlib import Path
from . import expansionsLib
_log = logging.getLogger('NoPE-Lib')
class PlayersManager:
"""
Manager of players for a given game.
Since this class implements most methods available to classic dict,
you can think of this class as a python dict.
Attributes:
gameID (str): The gameID of the active game
config (dict): The in memory state of the config file
Methods:
save:
Signals:
onMadePlayerActive:
onMadePlayerInactive:
"""
defaultPlayerConfig = {"flags": [], "expansions": {}, "games": {}}
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'.
"""
# Store the arguments
self._log = _log.getChild(loggerID)
self._currentGameID = gameID
# Deal with the path
self._playersPath = Path(playersPath)
# Get the config file
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()
self._player_data = {
name: Player(name, self, **player_cfg)
for name, player_cfg in self._cfg["players"].items()
if name in activePlayers
}
# Create the signals
self.onMadePlayerActive = expansionsLib.Hook()
self.onMadePlayerInactive = expansionsLib.Hook()
def __getitem__(self, playerName: str):
"""
Get a player object.
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._player_data:
# Fetch the player's data and make them active
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):
"""
Replace a player's config with another one.
"""
makeNewPlayerObject = False
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):
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()
}
@gameID.deleter
def gameID(self):
self._currentGameID = None
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):
""" 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:
"""
Attributes:
flags:
availableExpansions:
expansionsConfig:
gameSave: The game settings are not guaranteed to have data in it.
gameState:
Methods:
exportConfig:
punish:
"""
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):
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.exportConfig()
@property
def expansions(self):
""" Dictionnary of the player's expansion settings. """
return self._expansions
@expansions.setter
def expansions(self, newExpansions):
self._expansions = newExpansions
# The container was changed and must be transmited to the manager
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._gamesSave.get(self._manager.gameID)
@gameSave.setter
def gameSave(self, 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
}
class ExpansionsManager:
"""
Manager of the availability of the expansions.
Attributes:
includeExpansions: tuple of str
Container of the expansions to try making available
activeExpansions: dict of Expansion
Container of the relevent expansions
Methods:
expansionPlayersChange: Change the assigned players of an expansion
lookupPlayer: Provide a list of expansion available to a player
"""
defaultExpansionConfig = {"players": (), "types": ()}
keysConvert2Tuple = ("players", "tags")
tags = ["shock", "spice", "sour", "drink", "challenge"]
def __init__(self, playersManager: PlayersManager, includeExpansions: tuple[str]=None):
"""
Arguments:
tryInclude: bool=False, tryActivate: bool=False
"""
# Create the attributes
self.playersManager = playersManager
self._playersLookUp = {}
self._cfg = playersManager.config["expansions"]
if includeExpansions is not None:
self._includeExpansions = tuple(includeExpansions)
else:
self._includeExpansions = ()
# Convert the required lists into tuples
for expansionID in self._cfg:
for key in self.keysConvert2Tuple:
converted = tuple(self._cfg[expansionID][key])
self._cfg[expansionID][key] = converted
# Compute the active expansions
self._activeExpansions = {}
for expansionID in self._listPossiblyValidExpansions():
creation_out = self._createExpansion(expansionID)
if isinstance(creation_out, expansionsLib.Expansion):
self._activeExpansions[expansionID] = creation_out
else:
raise creation_out
# 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 includeExpansions(self):
""" Dictionnary of the included expansions. """
return self._includeExpansions
@includeExpansions.setter
def includeExpansions(self, newIncluded: tuple[str]):
self._includeExpansions = tuple(newIncluded)
self._includeChanged()
@includeExpansions.deleter
def includeExpansions(self):
self._includeExpansions = ()
@property
def config(self):
""" Dictionnary of the expansions' settings. """
return self._cfg
def _getPlayersFillMissing(self, expansionID):
return set(self._cfg.get(expansionID, {}).get("players", []))
def _getClassFillMissing(self, expansionID):
return self._cfg.get(expansionID, {}).get("class", "")
def _listPossiblyValidExpansions(self):
"""
List expansions that are: included, defined in expansionsLib and available
to at least one active player.
"""
activePlayers = set(self.playersManager.keys())
possiblyValidExpansions = [
expansionID for expansionID in self._includeExpansions
if hasattr(expansionsLib, self._getClassFillMissing(expansionID)) and
not self._getPlayersFillMissing(expansionID).isdisjoint(activePlayers)
]
return possiblyValidExpansions
def _createExpansion(self, expansionID):
terminalErrors = SystemExit, KeyboardInterrupt, GeneratorExit
try:
# Create the expansion
expansionClass = self._getClassFillMissing(expansionID)
class_to_create = getattr(expansionsLib, expansionClass)
expansion = class_to_create(expansionID, self)
# Update the lookup
for player in self._getPlayersFillMissing(expansionID):
if player in self._playersLookUp:
self._playersLookUp[player].append(expansionID)
else:
self._playersLookUp[player] = [expansionID]
return expansion
except terminalErrors as e:
raise e
except Exception as e:
return e
def _removeExpansion(self, expansionID):
# Update the lookup
for player in self._getPlayersFillMissing(expansionID):
self._playersLookUp[player].pop(expansionID)
# Remove the expansion
expansion = self._activeExpansions.pop(expansionID)
expansion.close()
def _includeChanged(self):
"""
Update the list of active Expansions
"""
# Compute the expansions involved in the modification
possiblyValidExpansions = set(self._listPossiblyValidExpansions())
previousExpansions = set(self._includeExpansions)
# 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:
creation_out = self._createExpansion(expansionID)
if isinstance(creation_out, expansionsLib.Expansion):
self._activeExpansions[expansionID] = creation_out
else:
raise creation_out
def _activePlayerAdded(self, playerName):
# Find the expansions that are to be created
possibleAddition = set(self._includeExpansions).difference(self._activeExpansions)
filteredAddition = [
expansionID for expansionID in possibleAddition
if hasattr(expansionsLib, self._getClassFillMissing(expansionID)) and
playerName in self._getPlayersFillMissing(expansionID)
]
# Create the expansions
for expansionID in filteredAddition:
creation_out = self._createExpansion(expansionID)
if isinstance(creation_out, expansionsLib.Expansion):
self._activeExpansions[expansionID] = creation_out
else:
raise creation_out
def _activePlayerRemoved(self, playerName):
# Find the expansions that are to be removed
filteredRemoval = [
expansionID for expansionID in self._activeExpansions
if playerName in self._cfg["players"] and len(self._cfg["players"]) == 1
]
# Close the expansions
for expansionID in filteredRemoval:
self._removeExpansion(expansionID)
def _midStepError(self, expansionID):
self._removeExpansion(expansionID)
def expansionPlayersChange(self, expansionID, newPlayers):
"""
Method that allows changing the assigned players of an expansion.
Arguments:
expansionID (str):
newPlayers (tuple of str):
"""
# Save the change
previousPlayers = self._cfg[expansionID]["players"]
self._cfg[expansionID]["players"] = tuple(newPlayers)
# Check the state of the expansion
expansionIsActive = expansionID in self._activeExpansions
activePlayers = set(self.playersManager.keys())
expansionHasNoPlayers = self._getPlayersFillMissing(expansionID).isdisjoint(activePlayers)
expansionDefined = hasattr(expansionsLib, self._getClassFillMissing(expansionID))
# Update the activation status
activeChanged = False
if expansionIsActive and expansionHasNoPlayers:
self._removeExpansion(expansionID)
activeChanged = True
elif (not expansionIsActive) and (not expansionHasNoPlayers) and expansionDefined:
creation_out = self._createExpansion(expansionID)
if isinstance(creation_out, expansionsLib.Expansion):
self._activeExpansions[expansionID] = creation_out
activeChanged = True
else:
raise creation_out
# Update the inverse lookup
if not activeChanged:
removedPlayers = set(previousPlayers).difference(newPlayers)
addedPlayers = set(newPlayers).difference(previousPlayers)
for playerName in removedPlayers:
self._playersLookUp[playerName].pop(expansionID)
for playerName in addedPlayers:
self._playersLookUp[playerName].append(expansionID)
def lookupPlayer(self, playerName):
"""
Get a tuple of all expansions available to a player.
Arguments:
playerName (str): Name of the player
Returns:
expansionsID (tuple of str): ID of the expansions available to the
player
"""
return tuple(self._playersLookUp.get(playerName, []))
if __name__ == "__main__":
# Test run to make sure nothing is flagrantly flawed
configPath = Path(__file__).parent.parent.parent / "players.json"
pm = PlayersManager(gameID="gameID0", activePlayers=["Brosef"], playersPath=configPath)
# Iteration
for name in pm:
print(name)
# Modification of a players data
brosef = pm["Brosef"]
brosef.gameSave = [1]
brosef.state = ["alive"]
# Test run for the expansionsManager
# Create the managers
configPath = Path(__file__).parent.parent.parent / "players.json"
pm = PlayersManager(gameID="gameID0", activePlayers=["Brosef"], playersPath=configPath)
em = ExpansionsManager(pm, includeExpansions=["Simple", "SourCandy"])
# Use the expansion manager to interact with a player using the expansion "Simple"
brosefAvailableExpansionsID = em.lookupPlayer("Brosef")
if "Simple" in brosefAvailableExpansionsID:
# Define some random action
vibrateInstead, duration, intensity = False, 1.0, 100
action = vibrateInstead, duration, intensity
# Perform the action
expansionObject = em["Simple"]
return_infos = expansionObject(action)
assert return_infos["done"], "The step was not successfully completed"

View File

@ -1 +0,0 @@
pass

View File

@ -1,7 +0,0 @@
class Expansion:
def __init__(self, parent, globalConfig):
setattr(parent, self.__class__.ID, self)
class PlayerExpansion:
def __init__(self, player, localConfig):
pass

View File

@ -1,311 +0,0 @@
"""
Needs to manage any number of players
Needs to work with mulitple rounds/turns
Needs to handle modifying number of players
"""
import abc
import copy
import json
import logging
from pathlib import Path
_log = logging.getLogger('NoPE-Lib')
class PlayersManager:
"""
Manager of players for a given game.
Since this class implements most methods available to classic dict,
you can think of this class as a python dict.
Attributes:
gameID (str): The gameID of the active game
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):
"""
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'.
"""
# Store the arguments
self._log = _log.getChild(loggerID)
self._currentGameID = gameID
# Deal with the path
self._playersPath = Path(playersPath)
# Get the config file
with open(self._playersPath, 'r') as f:
self._cfg = json.load(f)
# Create the players
activePlayers = activePlayers if activePlayers is not None else self._cfg["players"].keys()
self._player_data = {
name: Player(name, self, **player_cfg)
for name, player_cfg in self._cfg["players"].items()
if name in activePlayers
}
def __getitem__(self, playerName: str):
"""
Get a player object.
If the player wasn't 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:
# 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]
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.
"""
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
def __delitem__(self, playerName):
self._log.debug(f"Removing {playerName} from active players")
del self._player_data[playerName]
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 gameID(self):
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()}
@gameID.deleter
def gameID(self):
self._currentGameID = None
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)
class Player:
"""
Attributes:
flags:
availableExpansions:
expansionsConfig:
gameSave: The game settings are not guaranteed to have data in it.
gameState:
Methods:
generateConfig:
"""
def __init__(self, name: str, manager: PlayersManager, **cfg):
self._name = name
self._manager = manager
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"
@property
def name(self):
return self._name
@property
def flags(self):
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()
@property
def expansionsConfig(self):
return self._expansionsConfig
@expansionsConfig.setter
def expansions(self, newExpansions):
self._expansionsConfig = newExpansions
# The container was changed and must be transmited to the manager
self._manager[self._name] = self.generateConfig()
@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)
@gameSave.setter
def gameSave(self, newGameSave):
self._games[self._manager.gameID] = newGameSave
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
additionalInfos = {"expansionID": "challengeDB", "balancedValue": 0.2, "showOnScreen": "Do 100 push-up", "error": None, "done": False}
# NOTE Make a result class instead and an error class
additionalInfos = do_something(value)
return additionalInfos
class Expansion:
"""
Attributes:
Methods:
step:
reset:
close:
"""
@abc.abstractmethod
def __init__(self):
"""
Raise an error if not available
"""
pass
@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
class ExpansionManager:
"""
Manager of the availability of the expansions.
Attributes:
includedExpansions: tuple of str
Container of the expansions to try making available
releventExpansions: dict of Expansion
Container of the relevent expansions
"""
tags = ["shock", "spice", "sour", "drink", "challenge"]
def __init__(self, playersManager: PlayersManager, includedExpansions: tuple[str]=None):
self.playersManager = playersManager
self._includedExpansions = tuple(includedExpansions) if includedExpansions is not None else ()
self._releventExpansions = {} # TODO Populate the dictionnary
@property
def includedExpansions(self):
return self._includedExpansions
@includedExpansions.setter
def includedExpansions(self, newIncluded: tuple[str]):
self._includedExpansions = tuple(newIncluded)
self._includedChanged()
@includedExpansions.deleter
def includedExpansions(self):
self._includedExpansions = ()
@property
def releventExpansions(self):
return self._releventExpansions
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 _activePlayersChanged(self, activatePlayers):
pass
def _errorOccured(self, expansionID):
pass
def _expansionPlayersChanged(self, expansionID):
pass
def resetExpansion(self, expansion: Expansion, suppressError: bool=False):
"""
If the expansion was relevent, close it.
In any case, try creating a new
"""
# If the expansion was relevent, close it
# Try creating a new instance of the expansion
# Update the list of relevent expansions
pass
if __name__ == "__main__":
# Test run to make sure nothing is flagrantly flawed
configPath = Path(__file__).parent / "players.json"
manager = PlayersManager(playersPath=configPath, gameID="gameID0")
# Iteration
for playerName in manager:
print(playerName)
# Modification of a players data
brosef = manager["Brosef"]
brosef.gameSave = [1]
print(manager["Brosef"].gameSave)
brosef.state = ["alive"]

View File

@ -1,14 +0,0 @@
{
"expansions":
{
"shockColar1": {"players": ["Brosef"], "class": "pishock", "type": ["shock"]},
"robotBarman": {"players": ["Tango", "TRS_MML"], "class": "roboticBarman", "type": ["drink"]},
"challengeDB": {"players": ["TRS_MML"], "class": "challengeDataBase", "type": ["challenge"]},
"sourCandy": {"players": [], "class": "sourCandy", "type": ["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}}
}
}