Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 48 additions & 14 deletions src/model/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -228,6 +233,9 @@ def __init__(self):
# The chat of this match, tuples with type/message
self._chat = [("SYSTEM", "<b>Match was created.</b>")]

# 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)
Expand Down Expand Up @@ -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
Expand All @@ -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",
"<b>" + participant.nickname +
" wants to skip the phase. " +
str(majority - skip_count + 1) +
" request(s) left to reach majority.</b>"))
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",
"<b>" + self.get_owner_nick()
+ " skipped to the next phase.</b>"))
"<b>" + nick + " skipped to next phase</b>"))
else:
self._chat.append(("SYSTEM",
"<b>Can't skip phase with less than 1 second remaining</b>"))

def _set_state(self, state):
"""Updates the state for this match.
Expand Down
4 changes: 4 additions & 0 deletions src/model/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/pages/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand Down