Compare commits

...

39 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
dd72d14ef0 Merge branch 'main' of https://git.personal.imadumbass.dog/Brosef/NoPELib 2025-06-20 16:49:10 -04:00
9bb383e03c Sync 2025-06-20 16:44:18 -04:00
6b3f94bd68 Made an outline of ExpansionManager and Expansion 2025-06-20 15:52:05 -04:00
f2e247caac Updated pyproject.toml to reflect who actually did the work 2025-06-19 10:13:37 +01:00
c4bc183462 Stopped tracking .vscode directory 2025-06-17 17:39:19 +01:00
1938ff4c90 Renamed the library code to be in src/ to follow conventions 2025-06-16 21:10:19 +01:00
8f7b524c45 Slight change in the organization 2025-06-16 16:04:47 -04:00
a1a77f0f95 Made some basic tests, fixed a few things and added more dict methods 2025-06-16 15:29:07 -04:00
64bb9f7eec Allowed not specifying the gameID 2025-06-16 13:48:39 -04:00
67d84edcf5 Adjusted a comment 2025-06-16 13:47:56 -04:00
4d2d9c2910 Added a security to avoid reusing the same player object 2025-06-16 13:46:58 -04:00
a22001f3ed Removed irrelevent comment 2025-06-16 13:45:49 -04:00
11 changed files with 708 additions and 240 deletions

4
.gitignore vendored
View File

@ -174,4 +174,6 @@ cython_debug/
# PyPI configuration file # PyPI configuration file
.pypirc .pypirc
/test.py # ---> Other
/test.py
/.vscode/

View File

@ -1 +0,0 @@
pass

View File

@ -1,18 +0,0 @@
class Expansion:
def __init__(self, parent, globalConfig):
setattr(parent, self.__class__.ID, self)
class PlayerExpansion:
def __init__(self, player, localConfig):
pass
import PDOLib
def receive_punisment(value):
expansion_available = self.expansion
getattr(PDFLib, expansion_available).punish(value)
player.receive_punishment(0.5)

View File

@ -1,200 +0,0 @@
"""
Needs to manage any number of players
Needs to work with mulitple rounds/turns
Needs to handle modifying number of players
"""
import copy
import json
import logging
_log = logging.getLogger('NoPE-Lib')
class PlayersManager:
"""
Attribute:
gameID (str): The gameID of the active game
Methods:
save:
"""
defaultPlayerConfig = {"flags": [], "expansions": {}, "games": {}}
def __init__(self, gameID: str, activePlayers: list[str]=None, playersPath: str='./players.json', loggerID: str='Players'):
"""
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
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'.
"""
"""
Need to allow:
getting a list of players
getting the number of players
modify the list of players
modify the current game
"""
# Store the arguments
self._log = _log.getChild(loggerID)
self._currentGameID = gameID
self._playersPath = playersPath
activePlayers = activePlayers if activePlayers is not None else []
# Get the config file
with open(playersPath, 'r') as f:
self._cfg = json.load(f)
# Create the players
self._player_data = {
Player(name, game=None, **cfg)
for name, 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:
# Create the brand new player
self._cfg[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:
self._player_data[playerName] = Player(playerName, self, **config)
self._cfg["players"][playerName] = config
def __delitem__(self, playerName):
self._log.debug(f"Removing {playerName} from active players")
del self._player_data[playerName]
@property
def keys(self):
"""
Iterator of the active players' names
"""
return self._player_data.keys()
@property
def items(self):
"""
Two iterators of the activate players' names and data
"""
return self._player_data.items()
@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 save(self):
with open(self._playersPath, 'w') as f:
self._cfg = json.dump(f, self._cfg)
def addExpansion(self, expansion):
"""
Adds an expansion, used by things like PDO-Lib.
Args:
expansion (Expansion): The expansion to add.
"""
expID = expansion.__class__.ID
_log.debug(f'Adding expansion {expID}...')
expansion = expansion(self._cfg.get(expID))
class Player:
"""
The game settings are not guaranteed to have data in it.
Attributes:
flags:
expansions:
gameSave:
Methods:
generateConfig:
"""
def __init__(self, name: str, manager: PlayersManager, flags: list[str], expansions: dict, games: dict):
self._name = name
self._manager = manager
self._flags = flags
self._expansions = expansions
self._gameSave = games[manager._currentGameID] if manager._currentGameID is not None else games
@property
def name(self):
return self._name
@property
def flags(self):
return self._flags
@flags.setter
def flags(self, newFlags):
self._flags = newFlags
self._manager[self._name] = self.generateConfig()
@property
def expansions(self):
return self._expansions
@expansions.setter
def expansions(self, newExpansions):
self._expansions = newExpansions
self._manager[self._name] = self.generateConfig()
@property
def gameSave(self):
return self._gameSave
@gameSave.setter
def gameSave(self, newGameSave):
self._gameSave = newGameSave
self._manager[self._name] = self.generateConfig()
def generateConfig(self):
# Get the config of all games
games = self._manager[self._name]
if self._game is not None:
games[self.manager._currentGameID] = self._gameSave
# Create the config
config = {"flags": self._flags, "expansions": self._expansions, "games": games}
return config

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

View File

@ -6,5 +6,5 @@ build-backend = "setuptools.build_meta"
name = "NoPELib" name = "NoPELib"
version = "0.0.1.dev0" version = "0.0.1.dev0"
description = "Norway Player Execution Library (A player library)" description = "Norway Player Execution Library (A player library)"
authors = [{ name = "Brosef" }] authors = [{ name = "Oclaim" }]
dependencies = [] dependencies = []

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"