Update draw with the new team repartition

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-03-24 21:37:37 +01:00
parent e9ae1fcb60
commit df036ba384
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
3 changed files with 52 additions and 24 deletions

View File

@ -152,7 +152,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
try: try:
# Parse format from string # Parse format from string
fmt: list[int] = sorted(map(int, fmt.split('+')), reverse=True) fmt: list[int] = sorted(map(int, fmt.split('+')))
except ValueError: except ValueError:
return await self.alert(_("Invalid format"), 'danger') return await self.alert(_("Invalid format"), 'danger')
@ -416,10 +416,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# For each pool of size N, put the N next teams into this pool # For each pool of size N, put the N next teams into this pool
async for p in Pool.objects.filter(round_id=self.tournament.draw.current_round_id).order_by('letter').all(): async for p in Pool.objects.filter(round_id=self.tournament.draw.current_round_id).order_by('letter').all():
# Fetch the N teams, then order them in a new order for the passages inside the pool # Fetch the N teams
# We multiply the dice scores by 27 mod 100 (which order is 20 mod 100) for this new order pool_tds = tds_copy[:p.size].copy()
# This simulates a deterministic shuffle
pool_tds = sorted(tds_copy[:p.size], key=lambda td: (td.passage_dice * 27) % 100)
# Remove the head # Remove the head
tds_copy = tds_copy[p.size:] tds_copy = tds_copy[p.size:]
for i, td in enumerate(pool_tds): for i, td in enumerate(pool_tds):
@ -428,34 +426,64 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
td.passage_index = i td.passage_index = i
await td.asave() await td.asave()
# The passages of the second round are determined from the scores of the dices # The passages of the second round are determined from the order of the passages of the first round.
# The team that has the lowest dice score goes to the first pool, then the team # We order teams by increasing passage index, and then by decreasing pool number.
# that has the second-lowest score goes to the second pool, etc. # We keep teams that were at the last position in a 5-teams pool apart, as "jokers".
# This also determines the passage order, in the natural order this time. # Then, we fill pools one team by one team.
# If there is a 5-teams pool, we force the last team to be in the first pool, # As we fill one pool for the second round, we check if we can place a joker in it.
# which is this specific pool since they are ordered by decreasing size. # We can add a joker team if there is not already a team in the pool that was in the same pool
# This is not true for the final tournament, which considers the scores of the # in the first round, and such that the number of such jokers is exactly the free space of the current pool.
# first round. # Exception: if there is one only pool with 5 teams, we exchange the first and the last teams of the pool.
if not self.tournament.final: if not self.tournament.final:
tds_copy = tds.copy() tds_copy = sorted(tds, key=lambda td: (td.passage_index, -td.pool.letter,))
jokers = [td for td in tds if td.passage_index == 4]
round2 = await self.tournament.draw.round_set.filter(number=2).aget() round2 = await self.tournament.draw.round_set.filter(number=2).aget()
round2_pools = [p async for p in Pool.objects.filter(round__draw__tournament=self.tournament, round=round2) round2_pools = [p async for p in Pool.objects.filter(round__draw__tournament=self.tournament, round=round2)
.order_by('letter').all()] .order_by('letter').all()]
current_pool_id, current_passage_index = 0, 0 current_pool_id, current_passage_index = 0, 0
for i, td in enumerate(tds_copy): for i, td in enumerate(tds_copy):
if i == len(tds) - 1 and round2_pools[0].size == 5:
current_pool_id = 0
current_passage_index = 4
td2 = await TeamDraw.objects.filter(participation=td.participation, round=round2).aget() td2 = await TeamDraw.objects.filter(participation=td.participation, round=round2).aget()
td2.pool = round2_pools[current_pool_id] td2.pool = round2_pools[current_pool_id]
td2.passage_index = current_passage_index td2.passage_index = current_passage_index
current_pool_id += 1 if len(round2_pools) == 1 and len(tds) == 5:
if current_pool_id == len(round2_pools): # Exchange teams 1 and 5 if there is only one pool with 5 teams
current_pool_id = 0 if i == 0:
current_passage_index += 1 td2.passage_index = 4
elif i == 4:
td2.passage_index = 0
current_passage_index += 1
await td2.asave() await td2.asave()
valid_jokers = []
# A joker is valid if it was not in the same pool in the first round
# as a team that is already in the current pool in the second round
for joker in jokers:
async for td2 in round2_pools[current_pool_id].teamdraw_set.all():
if await joker.pool.teamdraw_set.filter(participation_id=td2.participation_id).aexists():
break
else:
valid_jokers.append(joker)
# We can add a joker if there is exactly enough free space in the current pool
if valid_jokers and current_passage_index + len(valid_jokers) == td2.pool.size:
for joker in valid_jokers:
tds_copy.remove(joker)
jokers.remove(joker)
td2_joker = await TeamDraw.objects.filter(participation_id=joker.participation_id,
round=round2).aget()
td2_joker.pool = round2_pools[current_pool_id]
td2_joker.passage_index = current_passage_index
current_passage_index += 1
await td2_joker.asave()
jokers = []
current_passage_index = 0
current_pool_id += 1
if current_passage_index == round2_pools[current_pool_id].size:
current_passage_index = 0
current_pool_id += 1
# The current pool is the first pool of the current (first) round # The current pool is the first pool of the current (first) round
pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget() pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
self.tournament.draw.current_round.current_pool = pool self.tournament.draw.current_round.current_pool = pool

View File

@ -93,7 +93,7 @@ class TeamAdmin(admin.ModelAdmin):
class ParticipationAdmin(admin.ModelAdmin): class ParticipationAdmin(admin.ModelAdmin):
list_display = ('team', 'tournament', 'valid', 'final',) list_display = ('team', 'tournament', 'valid', 'final',)
search_fields = ('team__name', 'team__trigram',) search_fields = ('team__name', 'team__trigram',)
list_filter = ('valid',) list_filter = ('valid', 'tournament',)
autocomplete_fields = ('team', 'tournament',) autocomplete_fields = ('team', 'tournament',)
inlines = (SolutionInline, SynthesisInline,) inlines = (SolutionInline, SynthesisInline,)

View File

@ -407,7 +407,7 @@ class Tournament(models.Model):
def best_format(self): def best_format(self):
n = len(self.participations.filter(valid=True).all()) n = len(self.participations.filter(valid=True).all())
fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3] fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3]
return '+'.join(map(str, sorted(fmt, reverse=True))) return '+'.join(map(str, sorted(fmt)))
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy("participation:tournament_detail", args=(self.pk,)) return reverse_lazy("participation:tournament_detail", args=(self.pk,))