Skip to content

Commit a91985b

Browse files
committed
Implemented new scrimmaging rules
1 parent 496278b commit a91985b

File tree

2 files changed

+65
-51
lines changed

2 files changed

+65
-51
lines changed

backend/siarnaq/api/compete/views.py

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ class ScrimmageRateLimited(APIException):
8080
default_code = "scrimmages_rate_limited"
8181

8282

83+
class ScrimmageLowerRank(APIException):
84+
status_code = status.HTTP_409_CONFLICT
85+
default_detail = (
86+
"You cannot request a ranked scrimmage against a team ranked lower than you."
87+
)
88+
default_code = "scrimmage_lower_rank"
89+
90+
8391
class EpisodeTeamUserContextMixin:
8492
"""Add the current episode, team and user to the serializer context."""
8593

@@ -926,24 +934,54 @@ def create(self, request, *, episode_id):
926934
serializer = self.get_serializer(data=request.data)
927935
serializer.is_valid(raise_exception=True)
928936

929-
if serializer.validated_data["is_ranked"]:
930-
# Get the global settings (assuming only one instance exists)
931-
episode = Episode.objects.get(pk=self.kwargs["episode_id"])
932-
if episode and (not episode.is_allowed_ranked_scrimmage):
933-
raise RankedMatchesDisabed
937+
# if serializer.validated_data["is_ranked"]:
938+
# Get the global settings (assuming only one instance exists)
939+
episode = Episode.objects.get(pk=self.kwargs["episode_id"])
940+
is_ranked = serializer.validated_data["is_ranked"]
941+
if episode and (not episode.is_allowed_ranked_scrimmage) and is_ranked:
942+
raise RankedMatchesDisabed
934943

935-
active_statuses = {
936-
SaturnStatus.CREATED,
937-
SaturnStatus.QUEUED,
938-
SaturnStatus.RUNNING,
939-
SaturnStatus.RETRY,
940-
}
941-
existing_requests = ScrimmageRequest.objects.filter(
942-
requested_by=serializer.validated_data["requested_by_id"],
944+
past_hour = timezone.now() - timezone.timedelta(hours=1)
945+
946+
active_statuses = {
947+
SaturnStatus.CREATED,
948+
SaturnStatus.QUEUED,
949+
SaturnStatus.RUNNING,
950+
SaturnStatus.RETRY,
951+
}
952+
# requests initiated by requestor in past hour
953+
existing_requests_from_requestor = ScrimmageRequest.objects.filter(
954+
requested_by=serializer.validated_data["requested_by_id"],
955+
is_ranked=is_ranked,
956+
created__gte=past_hour,
957+
)
958+
959+
# matches involving requestor in past hour
960+
match_count = MatchParticipant.objects.filter(
961+
team_id=serializer.validated_data["requested_by_id"],
962+
match__episode=episode,
963+
match__tournament_round__isnull=True,
964+
match__is_ranked=is_ranked,
965+
match__created__gte=past_hour,
966+
).count()
967+
# max matches that can be requested by participant in current mode
968+
max_matches = (
969+
episode.ranked_scrimmage_hourly_limit
970+
if serializer.validated_data["is_ranked"]
971+
else episode.unranked_scrimmage_hourly_limit
972+
)
973+
974+
# check number of matches requested + run in past hour (rate limiting)
975+
# note this checks both unranked and ranked caps
976+
if existing_requests_from_requestor.count() + match_count >= max_matches:
977+
raise ScrimmageRateLimited
978+
# check if too many ranked scrimmages requested between two teams
979+
if is_ranked:
980+
# existing request between two teams
981+
existing_requests = existing_requests_from_requestor.filter(
943982
requested_to=serializer.validated_data["requested_to"],
944-
is_ranked=True,
945-
status=ScrimmageRequestStatus.PENDING,
946983
).count()
984+
# existing active matches between the two participants
947985
existing_matches = (
948986
Match.objects.annotate(
949987
has_requester=Exists(
@@ -963,20 +1001,27 @@ def create(self, request, *, episode_id):
9631001
has_requester=True,
9641002
has_requestee=True,
9651003
status__in=active_statuses,
966-
is_ranked=True,
1004+
is_ranked=is_ranked,
9671005
)
9681006
.count()
9691007
)
1008+
9701009
if (
9711010
existing_requests + existing_matches
9721011
>= settings.MAX_SCRIMMAGES_AGAINST_TEAM
9731012
):
9741013
raise TooManyScrimmages
9751014

976-
# Check if we should reject based on rate-limiting
977-
requestor = Team.objects.get(pk=serializer.validated_data["requested_by_id"])
978-
if not requestor.can_scrimmage(serializer.validated_data["is_ranked"]):
979-
raise ScrimmageRateLimited
1015+
# stop teams from scrimmaging lower ranked teams
1016+
if is_ranked:
1017+
# fetch ranks of the two teams
1018+
team1 = Team.objects.select_related("profile__rating").get(
1019+
pk=serializer.validated_data["requested_by_id"]
1020+
)
1021+
team2 = serializer.validated_data["requested_to"]
1022+
# compare ratings
1023+
if team1.profile.rating.value > team2.profile.rating.value:
1024+
raise ScrimmageLowerRank
9801025

9811026
serializer.save()
9821027
# Generate the Location header, as supplied by CreateModelMixin

backend/siarnaq/api/teams/models.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from django.conf import settings
66
from django.core.validators import RegexValidator
77
from django.db import models, transaction
8-
from django.utils import timezone
98

109
import siarnaq.api.refs as refs
1110
from siarnaq.api.teams.managers import TeamQuerySet
@@ -185,36 +184,6 @@ def get_active_submission(self):
185184
def get_non_staff_count(self):
186185
return self.members.filter(is_staff=False).count()
187186

188-
def can_scrimmage(self, is_ranked):
189-
"""
190-
Check whether this team is currently rate-limited from
191-
creating a given type of scrimmage request.
192-
is_ranked determines which type of scrimmage is being checked.
193-
"""
194-
from siarnaq.api.compete.models import MatchParticipant
195-
from siarnaq.api.episodes.models import Episode
196-
197-
past_hour = timezone.now() - timezone.timedelta(hours=1)
198-
199-
# Get this team's scrimmages created in the past hour
200-
match_count = MatchParticipant.objects.filter(
201-
team_id=self.id,
202-
match__episode=self.episode,
203-
match__tournament_round__isnull=True,
204-
match__is_ranked=is_ranked,
205-
match__created__gte=past_hour,
206-
).count()
207-
208-
# Get the maximum number of created matches allowed in the past hour
209-
episode = Episode.objects.get(pk=self.episode_id)
210-
max_matches = (
211-
episode.ranked_scrimmage_hourly_limit
212-
if is_ranked
213-
else episode.unranked_scrimmage_hourly_limit
214-
)
215-
216-
return match_count < max_matches
217-
218187

219188
class TeamProfile(models.Model):
220189
"""

0 commit comments

Comments
 (0)