diff --git a/src/model/match.py b/src/model/match.py index e884725..046a4ea 100644 --- a/src/model/match.py +++ b/src/model/match.py @@ -37,6 +37,7 @@ from threading import RLock from time import time +from nussschale.nussschale import nconfig from model.multideck import MultiDeck from nussschale.util.locks import mutex, named_mutex @@ -51,6 +52,7 @@ class Match: Class Attributes: frozen (bool): Whether matches are currently frozen, i.e. whether their state transitions are disabled. + skip_role (str): Which users are allowed to skip the current phase. """ # The minimum amount of players for a match @@ -94,6 +96,9 @@ class Match: # Whether matches are currently frozen frozen = False + # Which users are allowed to skip the phase + skip_role = "owner" + @classmethod @named_mutex("_pool_lock") def get_by_id(cls, id): @@ -228,6 +233,9 @@ def __init__(self): # The chat of this match, tuples with type/message self._chat = [("SYSTEM", "Match was created.")] + # Which users are allowed to skip the phase + self.skip_role = nconfig().get("skip-role", "owner") + def put_in_pool(self): """Puts this match into the match pool.""" Match.add_match(self.id, self) @@ -304,12 +312,12 @@ def get_seconds_to_next_phase(self): # Locking is not needed here as access is atomic. return int(self._timer - time()) - @mutex - def user_can_skip_phase(self, part): - """Determine whether a user can skip to the next phase. + def user_can_skip_phase(self, participant): + """Determine whether a user can skip to the next phase Args: - obj: The participant in question. + participant: The participant that made the request + to skip the phase Returns: bool: Whether the given participant can skip to the next phase @@ -321,24 +329,50 @@ def user_can_skip_phase(self, part): # The minimum amount of players has to be present if len(self._participants) < Match._MINIMUM_PLAYERS: return False + + if self.skip_role == "picker": + if self._state == "CHOOSING": + return participant.picking + else: + return True + elif self.skip_role == "anyone": + return True + elif self.skip_role == "majority": + participant.wants_skip = True + skip_count = 0 + for part in self.get_participants(False): + if part.wants_skip: + skip_count += 1 + majority = int(len(list(self.get_participants(False))) / 2) + if skip_count > majority: + for part in self.get_participants(False): + part.wants_skip = False + return True + else: + self._chat.append(("SYSTEM", + "" + participant.nickname + + " wants to skip the phase. " + + str(majority - skip_count + 1) + + " request(s) left to reach majority.")) + return False + else: + return self.get_owner_nick() == participant.nickname - # Currently, only the owner can skip to the next phase - return self.get_owner_nick() == part.nickname - - @mutex - def skip_to_next_phase(self): - """Skips directly to the next phase. + def skip_to_next_phase(self, nick): + """Skips directly to the next phase - Contract: - This method locks the match's instance lock. + Args: + nick (str): The nickname of the user who is skipping the phase. """ # One second difference to prevent edge cases of timer change close to # game state transitions. if self._timer - time() > 1: self._timer = time() self._chat.append(("SYSTEM", - "" + self.get_owner_nick() - + " skipped to the next phase.")) + "" + nick + " skipped to next phase")) + else: + self._chat.append(("SYSTEM", + "Can't skip phase with less than 1 second remaining")) def _set_state(self, state): """Updates the state for this match. diff --git a/src/model/participant.py b/src/model/participant.py index 0346a34..e29b3d2 100644 --- a/src/model/participant.py +++ b/src/model/participant.py @@ -52,6 +52,7 @@ class Participant: occurring. order: The order key of the particpant, used for shuffling. spectator: Whether the participant is a spectator. + wants_skip: Whether the participant wants to skip the phase. """ # The number of hand cards per type @@ -94,6 +95,9 @@ def __init__(self, id: str, nickname: str) -> None: # participant is part of a match. self.spectator = False + # Whether the particpant wants to skip the phase. + self.wants_skip = False + # The hand of this participant self._hand = OrderedDict() # type: Dict[int, HandCard] self._hand_counter = 1 diff --git a/src/pages/api.py b/src/pages/api.py index c4678a4..380da46 100644 --- a/src/pages/api.py +++ b/src/pages/api.py @@ -311,12 +311,12 @@ def api_skip(ctx: EndpointContext) -> None: part = match.get_participant(ctx.session["id"]) - # Check that the GET request was made by a user that can skip phases + # Check that the POST request was made by a user that can skip phases if not match.user_can_skip_phase(part): raise HTTPException.forbidden(True, "not authorized to skip phase") # Skip remaining time - match.skip_to_next_phase() + match.skip_to_next_phase(part.nickname) ctx.json_ok()