From 05114896505cdad254e1616f4fdff8fdd1639cc2 Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Sat, 3 Feb 2018 20:08:46 +0800 Subject: [PATCH 1/7] Can customize phase skip permission Defaults to server owner Match phase skip method now takes nickname argument Follow 80 char line convention Fixed syntax error in match.js --- src/model/match.py | 29 ++++++++++++++++++++++++----- src/pages/api.py | 2 +- src/res/js/match/match.js | 1 + 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/model/match.py b/src/model/match.py index ebf238f..a7fca67 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) @@ -311,18 +319,29 @@ def user_can_skip_phase(self, nickname): bool: Whether the given nickname belongs to a user that can skip to the next phase """ - # Currently, only the owner can skip to the next phase - return self.get_owner_nick() == nickname + if self.skip_role == "picker": + for part in self.get_participants(False): + if part.picking and part.nickname == nickname: + return True + return False + elif self.skip_role == "anyone": + return True + else: + return self.get_owner_nick() == nickname - def skip_to_next_phase(self): + def skip_to_next_phase(self, nick): """Skips directly to the next phase + + Args: + nick (str): The nickname of the user who is skipping the phase. """ if int(self._timer - time()) > 1: self._timer = time() self._chat.append(("SYSTEM", - "" + self.get_owner_nick() + " skipped to next phase")) + "" + nick + " skipped to next phase")) else: - self._chat.append(("SYSTEM", "Can't skip phase with less than 1 second remaining")) + 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/pages/api.py b/src/pages/api.py index 710fd92..3aa7fc1 100644 --- a/src/pages/api.py +++ b/src/pages/api.py @@ -316,7 +316,7 @@ def api_skip(ctx: EndpointContext) -> None: 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() diff --git a/src/res/js/match/match.js b/src/res/js/match/match.js index 10f1555..873636a 100644 --- a/src/res/js/match/match.js +++ b/src/res/js/match/match.js @@ -424,6 +424,7 @@ method: "POST", url: "/api/skip" }) + } /* * Toggle hand visibility. From 9a979eb50276ab09652981dadc9490c1f1ad5fbe Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Sat, 3 Feb 2018 20:30:41 +0800 Subject: [PATCH 2/7] Implemented majority vote skipping --- src/model/match.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/model/match.py b/src/model/match.py index a7fca67..1919e1b 100644 --- a/src/model/match.py +++ b/src/model/match.py @@ -53,6 +53,7 @@ class Match: 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. + skip_count (int): Number of people who want to skip the phase. """ # The minimum amount of players for a match @@ -98,6 +99,7 @@ class Match: # Which users are allowed to skip the phase skip_role = "owner" + skip_count = 0 @classmethod @named_mutex("_pool_lock") @@ -326,6 +328,14 @@ def user_can_skip_phase(self, nickname): return False elif self.skip_role == "anyone": return True + elif self.skip_role == "majority": + self.skip_count += 1 + majority = int(len(list(self.get_participants(False))) / 2) + if self.skip_count > majority: + self.skip_count = 0 + return True + else: + return False else: return self.get_owner_nick() == nickname From 59eb1250feb07393bd91c2faa81a9c7f438bda3b Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Sat, 3 Feb 2018 20:33:16 +0800 Subject: [PATCH 3/7] Anyone can skip if picker is chosen and phase is not "choosing" --- src/model/match.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/model/match.py b/src/model/match.py index 1919e1b..e54f172 100644 --- a/src/model/match.py +++ b/src/model/match.py @@ -322,10 +322,13 @@ def user_can_skip_phase(self, nickname): can skip to the next phase """ if self.skip_role == "picker": - for part in self.get_participants(False): - if part.picking and part.nickname == nickname: - return True - return False + if self._state == "CHOOSING": + for part in self.get_participants(False): + if part.picking and part.nickname == nickname: + return True + return False + else: + return True elif self.skip_role == "anyone": return True elif self.skip_role == "majority": From 996150ab19995ca9dc226f3f129955dccb1cc92c Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Wed, 7 Feb 2018 09:14:14 +0800 Subject: [PATCH 4/7] Added chat message for majority skipping Indicates who wants to skip Indicates how many skips required to reach majority --- src/model/match.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/model/match.py b/src/model/match.py index e54f172..44c2e49 100644 --- a/src/model/match.py +++ b/src/model/match.py @@ -338,6 +338,11 @@ def user_can_skip_phase(self, nickname): self.skip_count = 0 return True else: + self._chat.append(("SYSTEM", + "" + nickname + + " wants to skip the phase. " + + str(majority - self.skip_count + 1) + + " request(s) left to reach majority.")) return False else: return self.get_owner_nick() == nickname From 87b6b5b9e107e3ea081ce526938cff4679348aa3 Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Wed, 7 Feb 2018 09:27:57 +0800 Subject: [PATCH 5/7] Moved skip property to participant class Checking for majority now O(n) time Prevent same user from making multiple skip requests user_can_skip_phase now takes participant argument --- src/model/match.py | 21 ++++++++++++--------- src/model/participant.py | 4 ++++ src/pages/api.py | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/model/match.py b/src/model/match.py index 44c2e49..2101262 100644 --- a/src/model/match.py +++ b/src/model/match.py @@ -53,7 +53,6 @@ class Match: 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. - skip_count (int): Number of people who want to skip the phase. """ # The minimum amount of players for a match @@ -99,7 +98,6 @@ class Match: # Which users are allowed to skip the phase skip_role = "owner" - skip_count = 0 @classmethod @named_mutex("_pool_lock") @@ -314,7 +312,7 @@ def get_seconds_to_next_phase(self): # Locking is not needed here as access is atomic. return int(self._timer - time()) - def user_can_skip_phase(self, nickname): + def user_can_skip_phase(self, participant): """Determine whether a user can skip to the next phase Returns: @@ -332,20 +330,25 @@ def user_can_skip_phase(self, nickname): elif self.skip_role == "anyone": return True elif self.skip_role == "majority": - self.skip_count += 1 + particpant.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 self.skip_count > majority: - self.skip_count = 0 + if skip_count > majority: + for part in self.get_participants(False): + part.wants_skip = False return True else: self._chat.append(("SYSTEM", - "" + nickname + + "" + participant.nickname + " wants to skip the phase. " + - str(majority - self.skip_count + 1) + + str(majority - skip_count + 1) + " request(s) left to reach majority.")) return False else: - return self.get_owner_nick() == nickname + return self.get_owner_nick() == participant.nickname def skip_to_next_phase(self, nick): """Skips directly to the next phase 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 3aa7fc1..44be9ef 100644 --- a/src/pages/api.py +++ b/src/pages/api.py @@ -312,7 +312,7 @@ def api_skip(ctx: EndpointContext) -> None: part = match.get_participant(ctx.session["id"]) # Check that the POST request was made by a user that can skip phases - if not match.user_can_skip_phase(part.nickname): + if not match.user_can_skip_phase(part): raise HTTPException.forbidden(True, "not authorized to skip phase") # Skip remaining time From 94afb18805e166abd4d7a4f5a2177194edfb1a62 Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Wed, 7 Feb 2018 09:32:00 +0800 Subject: [PATCH 6/7] Fixed typo --- src/model/match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/match.py b/src/model/match.py index 2101262..f49b595 100644 --- a/src/model/match.py +++ b/src/model/match.py @@ -330,7 +330,7 @@ def user_can_skip_phase(self, participant): elif self.skip_role == "anyone": return True elif self.skip_role == "majority": - particpant.wants_skip = True + participant.wants_skip = True skip_count = 0 for part in self.get_participants(False): if part.wants_skip: From d35d1c7f27865171a8a38984afebf8afa2862f0c Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Thu, 8 Feb 2018 08:24:41 +0800 Subject: [PATCH 7/7] Fixed user_can_skip_phase for 'picker' Fixed to use new function call Added argument to comment for user_can_skip_phase --- src/model/match.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/model/match.py b/src/model/match.py index f49b595..c0b3c1b 100644 --- a/src/model/match.py +++ b/src/model/match.py @@ -315,16 +315,17 @@ def get_seconds_to_next_phase(self): def user_can_skip_phase(self, participant): """Determine whether a user can skip to the next phase + Args: + participant: The participant that made the request + to skip the phase + Returns: bool: Whether the given nickname belongs to a user that can skip to the next phase """ if self.skip_role == "picker": if self._state == "CHOOSING": - for part in self.get_participants(False): - if part.picking and part.nickname == nickname: - return True - return False + return participant.picking else: return True elif self.skip_role == "anyone":