mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2024-12-26 05:42:22 +00:00
Draw dices
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
parent
eb8ad4e771
commit
5399a875c6
@ -1,4 +1,7 @@
|
|||||||
import json
|
# Copyright (C) 2023 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from random import randint
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||||
@ -24,14 +27,16 @@ def ensure_orga(f):
|
|||||||
class DrawConsumer(AsyncJsonWebsocketConsumer):
|
class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
tournament_id = self.scope['url_route']['kwargs']['tournament_id']
|
tournament_id = self.scope['url_route']['kwargs']['tournament_id']
|
||||||
self.tournament = await sync_to_async(Tournament.objects.get)(pk=tournament_id)
|
self.tournament = await Tournament.objects.filter(pk=tournament_id)\
|
||||||
|
.prefetch_related('draw__current_round__current_pool__current_team').aget()
|
||||||
|
|
||||||
self.participations = await sync_to_async(lambda: list(Participation.objects\
|
self.participations = []
|
||||||
.filter(tournament=self.tournament, valid=True)\
|
async for participation in Participation.objects.filter(tournament=self.tournament, valid=True)\
|
||||||
.prefetch_related('team').all()))()
|
.prefetch_related('team'):
|
||||||
|
self.participations.append(participation)
|
||||||
|
|
||||||
user = self.scope['user']
|
user = self.scope['user']
|
||||||
reg = await sync_to_async(Registration.objects.get)(user=user)
|
reg = await Registration.objects.aget(user=user)
|
||||||
self.registration = reg
|
self.registration = reg
|
||||||
if reg.is_volunteer and not reg.is_admin and self.tournament not in reg.interesting_tournaments \
|
if reg.is_volunteer and not reg.is_admin and self.tournament not in reg.interesting_tournaments \
|
||||||
or not reg.is_volunteer and reg.team.participation.tournament != self.tournament:
|
or not reg.is_volunteer and reg.team.participation.tournament != self.tournament:
|
||||||
@ -41,11 +46,19 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
|
|
||||||
await self.accept()
|
await self.accept()
|
||||||
await self.channel_layer.group_add(f"tournament-{self.tournament.id}", self.channel_name)
|
await self.channel_layer.group_add(f"tournament-{self.tournament.id}", self.channel_name)
|
||||||
|
if not self.registration.is_volunteer:
|
||||||
|
await self.channel_layer.group_add(f"team-{self.registration.team.trigram}", self.channel_name)
|
||||||
|
else:
|
||||||
|
await self.channel_layer.group_add(f"volunteer-{self.tournament.id}", self.channel_name)
|
||||||
|
|
||||||
async def disconnect(self, close_code):
|
async def disconnect(self, close_code):
|
||||||
await self.channel_layer.group_discard(f"tournament-{self.tournament.id}", self.channel_name)
|
await self.channel_layer.group_discard(f"tournament-{self.tournament.id}", self.channel_name)
|
||||||
|
if not self.registration.is_volunteer:
|
||||||
|
await self.channel_layer.group_discard(f"team-{self.registration.team.trigram}", self.channel_name)
|
||||||
|
else:
|
||||||
|
await self.channel_layer.group_discard(f"volunteer-{self.tournament.id}", self.channel_name)
|
||||||
|
|
||||||
async def alert(self, message: str, alert_type: str = 'info'):
|
async def alert(self, message: str, alert_type: str = 'info', **kwargs):
|
||||||
return await self.send_json({'type': 'alert', 'alert_type': alert_type, 'message': str(message)})
|
return await self.send_json({'type': 'alert', 'alert_type': alert_type, 'message': str(message)})
|
||||||
|
|
||||||
async def receive_json(self, content, **kwargs):
|
async def receive_json(self, content, **kwargs):
|
||||||
@ -54,6 +67,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
match content['type']:
|
match content['type']:
|
||||||
case 'start_draw':
|
case 'start_draw':
|
||||||
await self.start_draw(**content)
|
await self.start_draw(**content)
|
||||||
|
case 'dice':
|
||||||
|
await self.process_dice(**content)
|
||||||
|
|
||||||
@ensure_orga
|
@ensure_orga
|
||||||
async def start_draw(self, fmt, **kwargs):
|
async def start_draw(self, fmt, **kwargs):
|
||||||
@ -70,21 +85,204 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
_("The sum must be equal to the number of teams: expected {len}, got {sum}")\
|
_("The sum must be equal to the number of teams: expected {len}, got {sum}")\
|
||||||
.format(len=len(self.participations), sum=sum(fmt)), 'danger')
|
.format(len=len(self.participations), sum=sum(fmt)), 'danger')
|
||||||
|
|
||||||
draw = await sync_to_async(Draw.objects.create)(tournament=self.tournament)
|
draw = await Draw.objects.acreate(tournament=self.tournament)
|
||||||
|
r1 = None
|
||||||
for i in [1, 2]:
|
for i in [1, 2]:
|
||||||
r = await sync_to_async(Round.objects.create)(draw=draw, number=i)
|
r = await Round.objects.acreate(draw=draw, number=i)
|
||||||
|
if i == 1:
|
||||||
|
r1 = r
|
||||||
|
|
||||||
for j, f in enumerate(fmt):
|
for j, f in enumerate(fmt):
|
||||||
await sync_to_async(Pool.objects.create)(round=r, letter=j + 1, size=f)
|
await Pool.objects.acreate(round=r, letter=j + 1, size=f)
|
||||||
for participation in self.participations:
|
for participation in self.participations:
|
||||||
await sync_to_async(TeamDraw.objects.create)(participation=participation)
|
await TeamDraw.objects.acreate(participation=participation, round=r)
|
||||||
|
|
||||||
|
draw.current_round = r1
|
||||||
|
await sync_to_async(draw.save)()
|
||||||
|
|
||||||
await self.alert(_("Draw started!"), 'success')
|
await self.alert(_("Draw started!"), 'success')
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'type': 'draw.start', 'fmt': fmt, 'draw': draw})
|
{'type': 'draw.start', 'fmt': fmt, 'draw': draw})
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.set_info', 'draw': draw})
|
||||||
|
|
||||||
async def draw_start(self, content):
|
async def draw_start(self, content):
|
||||||
await self.alert(_("The draw for the tournament {tournament} will start.")\
|
await self.alert(_("The draw for the tournament {tournament} will start.")\
|
||||||
.format(tournament=self.tournament.name), 'warning')
|
.format(tournament=self.tournament.name), 'warning')
|
||||||
await self.send_json({'type': 'draw_start', 'fmt': content['fmt'],
|
await self.send_json({'type': 'draw_start', 'fmt': content['fmt'],
|
||||||
'trigrams': [p.team.trigram for p in self.participations]})
|
'trigrams': [p.team.trigram for p in self.participations]})
|
||||||
|
|
||||||
|
|
||||||
|
async def process_dice(self, trigram: str | None = None, **kwargs):
|
||||||
|
if self.registration.is_volunteer:
|
||||||
|
participation = await Participation.objects.filter(team__trigram=trigram).prefetch_related('team').aget()
|
||||||
|
else:
|
||||||
|
participation = await Participation.objects.filter(team__participants=self.registration)\
|
||||||
|
.prefetch_related('team').aget()
|
||||||
|
trigram = participation.team.trigram
|
||||||
|
|
||||||
|
team_draw = await TeamDraw.objects.filter(participation=participation,
|
||||||
|
round_id=self.tournament.draw.current_round_id).aget()
|
||||||
|
|
||||||
|
state = await sync_to_async(self.tournament.draw.get_state)()
|
||||||
|
match state:
|
||||||
|
case 'DICE_SELECT_POULES':
|
||||||
|
if team_draw.last_dice is not None:
|
||||||
|
return await self.alert(_("You've already launched the dice."), 'danger')
|
||||||
|
case 'DICE_ORDER_POULE':
|
||||||
|
if team_draw.last_dice is not None:
|
||||||
|
return await self.alert(_("You've already launched the dice."), 'danger')
|
||||||
|
if not await self.tournament.draw.current_round.current_pool.teamdraw_set\
|
||||||
|
.filter(participation=participation).aexists():
|
||||||
|
return await self.alert(_("It is not your turn."), 'danger')
|
||||||
|
case _:
|
||||||
|
return await self.alert(_("This is not the time for this."), 'danger')
|
||||||
|
|
||||||
|
res = randint(1, 100)
|
||||||
|
team_draw.last_dice = res
|
||||||
|
await sync_to_async(team_draw.save)()
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}", {'type': 'draw.dice', 'team': trigram, 'result': res})
|
||||||
|
|
||||||
|
if state == 'DICE_SELECT_POULES' and \
|
||||||
|
not await TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id,
|
||||||
|
last_dice__isnull=True).aexists():
|
||||||
|
tds = []
|
||||||
|
async for td in TeamDraw.objects.filter(round_id=self.tournament.draw.current_round_id)\
|
||||||
|
.prefetch_related('participation__team'):
|
||||||
|
tds.append(td)
|
||||||
|
|
||||||
|
dices = {td: td.last_dice for td in tds}
|
||||||
|
values = list(dices.values())
|
||||||
|
error = False
|
||||||
|
for v in set(values):
|
||||||
|
if values.count(v) > 1:
|
||||||
|
dups = [td for td in tds if td.last_dice == v]
|
||||||
|
|
||||||
|
for dup in dups:
|
||||||
|
dup.last_dice = None
|
||||||
|
await sync_to_async(dup.save)()
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.dice', 'team': dup.participation.team.trigram, 'result': None})
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.alert',
|
||||||
|
'message': _('Dices from teams {teams} are identical. Please relaunch your dices.').format(
|
||||||
|
teams=', '.join(td.participation.team.trigram for td in dups)),
|
||||||
|
'alert_type': 'warning'})
|
||||||
|
error = True
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return
|
||||||
|
|
||||||
|
tds.sort(key=lambda td: td.last_dice)
|
||||||
|
tds_copy = tds.copy()
|
||||||
|
|
||||||
|
async for p in Pool.objects.filter(round_id=self.tournament.draw.current_round_id).order_by('letter').all():
|
||||||
|
while (c := await TeamDraw.objects.filter(pool=p).acount()) < p.size:
|
||||||
|
td = tds_copy.pop(0)
|
||||||
|
td.pool = p
|
||||||
|
td.passage_index = c
|
||||||
|
await sync_to_async(td.save)()
|
||||||
|
|
||||||
|
if self.tournament.draw.current_round.number == 2 \
|
||||||
|
and await self.tournament.draw.current_round.pool_set.acount() >= 2:
|
||||||
|
# Check that we don't have a same pool as the first day
|
||||||
|
async for p1 in Pool.objects.filter(round__draw=self.tournament.draw, number=1).all():
|
||||||
|
async for p2 in Pool.objects.filter(round_id=self.tournament.draw.current_round_id).all():
|
||||||
|
if set(await p1.teamdraw_set.avalues('id')) \
|
||||||
|
== set(await p2.teamdraw_set.avalues('id')):
|
||||||
|
await TeamDraw.objects.filter(round=self.tournament.draw.current_round)\
|
||||||
|
.aupdate(last_dice=None, pool=None, passage_index=None)
|
||||||
|
for td in tds:
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.dice', 'team': td.participation.team.trigram, 'result': None})
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.alert',
|
||||||
|
'message': _('Two pools are identical. Please relaunch your dices.'),
|
||||||
|
'alert_type': 'warning'})
|
||||||
|
return
|
||||||
|
|
||||||
|
pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
|
||||||
|
self.tournament.draw.current_round.current_pool = pool
|
||||||
|
await sync_to_async(self.tournament.draw.current_round.save)()
|
||||||
|
|
||||||
|
await TeamDraw.objects.filter(round=self.tournament.draw.current_round).aupdate(last_dice=None)
|
||||||
|
for td in tds:
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.dice', 'team': td.participation.team.trigram, 'result': None})
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.dice_visibility', 'visible': False})
|
||||||
|
|
||||||
|
async for td in pool.teamdraw_set.prefetch_related('participation__team').all():
|
||||||
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
|
{'type': 'draw.dice_visibility', 'visible': True})
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
||||||
|
elif state == 'DICE_ORDER_POULE' and \
|
||||||
|
not await TeamDraw.objects.filter(pool=self.tournament.draw.current_round.current_pool,
|
||||||
|
last_dice__isnull=True).aexists():
|
||||||
|
pool = self.tournament.draw.current_round.current_pool
|
||||||
|
|
||||||
|
tds = []
|
||||||
|
async for td in TeamDraw.objects.filter(pool=pool)\
|
||||||
|
.prefetch_related('participation__team'):
|
||||||
|
tds.append(td)
|
||||||
|
|
||||||
|
dices = {td: td.last_dice for td in tds}
|
||||||
|
values = list(dices)
|
||||||
|
error = False
|
||||||
|
for v in set(values):
|
||||||
|
if values.count(v) > 1:
|
||||||
|
dups = [td for td in tds if td.last_dice == v]
|
||||||
|
|
||||||
|
for dup in dups:
|
||||||
|
dup.last_dice = None
|
||||||
|
await sync_to_async(dup.save)()
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.dice', 'team': dup.participation.team.trigram, 'result': None})
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.alert',
|
||||||
|
'message': _('Dices from teams {teams} are identical. Please relaunch your dices.').format(
|
||||||
|
teams=', '.join(td.participation.team.trigram for td in dups)),
|
||||||
|
'alert_type': 'warning'})
|
||||||
|
error = True
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return
|
||||||
|
|
||||||
|
tds.sort(key=lambda x: -x.last_dice)
|
||||||
|
for i, td in enumerate(tds):
|
||||||
|
td.choose_index = i
|
||||||
|
await sync_to_async(td.save)()
|
||||||
|
|
||||||
|
pool.current_team = tds[0]
|
||||||
|
await sync_to_async(pool.save)()
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
|
{'type': 'draw.set_info', 'draw': self.tournament.draw})
|
||||||
|
|
||||||
|
async def draw_alert(self, content):
|
||||||
|
return await self.alert(**content)
|
||||||
|
|
||||||
|
async def draw_notify(self, content):
|
||||||
|
await self.send_json({'type': 'notification', 'title': content['title'], 'body': content['body']})
|
||||||
|
|
||||||
|
async def draw_set_info(self, content):
|
||||||
|
await self.send_json({'type': 'set_info', 'information': await content['draw'].ainformation()})
|
||||||
|
|
||||||
|
async def draw_dice(self, content):
|
||||||
|
await self.send_json({'type': 'dice', 'team': content['team'], 'result': content['result']})
|
||||||
|
|
||||||
|
async def draw_dice_visibility(self, content):
|
||||||
|
await self.send_json({'type': 'dice_visibility', 'visible': content['visible']})
|
||||||
|
24
draw/migrations/0003_teamdraw_round.py
Normal file
24
draw/migrations/0003_teamdraw_round.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-03-22 21:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("draw", "0002_pool_size_alter_pool_letter_alter_round_number_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="teamdraw",
|
||||||
|
name="round",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
default=1,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="draw.round",
|
||||||
|
verbose_name="round",
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-03-22 23:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("draw", "0003_teamdraw_round"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="teamdraw",
|
||||||
|
name="index",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="teamdraw",
|
||||||
|
name="choose_index",
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
choices=[(1, 1), (2, 2), (3, 3), (4, 4)],
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
verbose_name="choose index",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="teamdraw",
|
||||||
|
name="passage_index",
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
choices=[(1, 1), (2, 2), (3, 3), (4, 4)],
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
verbose_name="passage index",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (C) 2023 by Animath
|
# Copyright (C) 2023 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.text import format_lazy
|
from django.utils.text import format_lazy
|
||||||
@ -24,6 +25,52 @@ class Draw(models.Model):
|
|||||||
verbose_name=_('current round'),
|
verbose_name=_('current round'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
if self.current_round.current_pool is None:
|
||||||
|
return 'DICE_SELECT_POULES'
|
||||||
|
elif self.current_round.current_pool.current_team is None:
|
||||||
|
return 'DICE_ORDER_POULE'
|
||||||
|
elif self.current_round.current_pool.current_team.purposed is None:
|
||||||
|
return 'WAITING_DRAW_PROBLEM'
|
||||||
|
elif self.current_round.current_pool.current_team.accepted is None:
|
||||||
|
return 'WAITING_CHOOSE_PROBLEM'
|
||||||
|
else:
|
||||||
|
return 'DRAW_ENDED'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def information(self):
|
||||||
|
s = ""
|
||||||
|
match self.get_state():
|
||||||
|
case 'DICE_SELECT_POULES':
|
||||||
|
if self.current_round.number == 1:
|
||||||
|
s += """Nous allons commencer le tirage des problèmes.<br>
|
||||||
|
Vous pouvez à tout moment poser toute question si quelque chose
|
||||||
|
n'est pas clair ou ne va pas.<br><br>
|
||||||
|
Nous allons d'abord tirer les poules et l'ordre de passage
|
||||||
|
pour le premier tour avec toutes les équipes puis pour chaque poule,
|
||||||
|
nous tirerons l'ordre de tirage pour le tour et les problèmes.<br><br>"""
|
||||||
|
s += """
|
||||||
|
Les capitaines, vous pouvez désormais toustes lancer un dé 100,
|
||||||
|
en cliquant sur le gros bouton. Les poules et l'ordre de passage
|
||||||
|
lors du premier tour sera l'ordre croissant des dés, c'est-à-dire
|
||||||
|
que le plus petit lancer sera le premier à passer dans la poule A."""
|
||||||
|
case 'DICE_ORDER_POULE':
|
||||||
|
s += f"""Nous passons au tirage des problèmes pour la poule
|
||||||
|
<strong>{self.current_round.current_pool}</strong>, entre les équipes
|
||||||
|
<strong>{', '.join(td.participation.team.trigram
|
||||||
|
for td in self.current_round.current_pool.teamdraw_set.all())}</strong>.
|
||||||
|
Les capitaines peuvent lancer un dé 100 en cliquant sur le gros bouton
|
||||||
|
pour déterminer l'ordre de tirage. L'équipe réalisant le plus gros score pourra
|
||||||
|
tirer en premier."""
|
||||||
|
|
||||||
|
s += """<br><br>Pour plus de détails sur le déroulement du tirage au sort,
|
||||||
|
le règlement est accessible sur
|
||||||
|
<a class="alert-link" href="https://tfjm.org/reglement">https://tfjm.org/reglement</a>."""
|
||||||
|
return s
|
||||||
|
|
||||||
|
async def ainformation(self):
|
||||||
|
return await sync_to_async(lambda: self.information)()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('draw')
|
verbose_name = _('draw')
|
||||||
verbose_name_plural = _('draws')
|
verbose_name_plural = _('draws')
|
||||||
@ -89,8 +136,12 @@ class Pool(models.Model):
|
|||||||
verbose_name=_('current team'),
|
verbose_name=_('current team'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def trigrams(self):
|
||||||
|
return set(td.participation.team.trigram for td in self.teamdraw_set.all())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.letter}{self.round}"
|
return f"{self.get_letter_display()}{self.round.number}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('pool')
|
verbose_name = _('pool')
|
||||||
@ -104,6 +155,12 @@ class TeamDraw(models.Model):
|
|||||||
verbose_name=_('participation'),
|
verbose_name=_('participation'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
round = models.ForeignKey(
|
||||||
|
Round,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_('round'),
|
||||||
|
)
|
||||||
|
|
||||||
pool = models.ForeignKey(
|
pool = models.ForeignKey(
|
||||||
Pool,
|
Pool,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -112,11 +169,18 @@ class TeamDraw(models.Model):
|
|||||||
verbose_name=_('pool'),
|
verbose_name=_('pool'),
|
||||||
)
|
)
|
||||||
|
|
||||||
index = models.PositiveSmallIntegerField(
|
passage_index = models.PositiveSmallIntegerField(
|
||||||
choices=zip(range(1, 6), range(1, 6)),
|
choices=zip(range(1, 5), range(1, 5)),
|
||||||
null=True,
|
null=True,
|
||||||
default=None,
|
default=None,
|
||||||
verbose_name=_('index'),
|
verbose_name=_('passage index'),
|
||||||
|
)
|
||||||
|
|
||||||
|
choose_index = models.PositiveSmallIntegerField(
|
||||||
|
choices=zip(range(1, 5), range(1, 5)),
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
verbose_name=_('choose index'),
|
||||||
)
|
)
|
||||||
|
|
||||||
accepted = models.PositiveSmallIntegerField(
|
accepted = models.PositiveSmallIntegerField(
|
||||||
@ -149,6 +213,9 @@ class TeamDraw(models.Model):
|
|||||||
verbose_name=_('rejected problems'),
|
verbose_name=_('rejected problems'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def current(self):
|
||||||
|
return TeamDraw.objects.get(participation=self.participation, round=self.round.draw.current_round)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('team draw')
|
verbose_name = _('team draw')
|
||||||
verbose_name_plural = _('team draws')
|
verbose_name_plural = _('team draws')
|
||||||
|
@ -1,8 +1,23 @@
|
|||||||
|
(async () => {
|
||||||
|
// check notification permission
|
||||||
|
await Notification.requestPermission()
|
||||||
|
})()
|
||||||
|
|
||||||
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
|
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
|
||||||
const sockets = {}
|
const sockets = {}
|
||||||
|
|
||||||
const messages = document.getElementById('messages')
|
const messages = document.getElementById('messages')
|
||||||
|
|
||||||
|
function drawDice(tid, trigram = null) {
|
||||||
|
sockets[tid].send(JSON.stringify({'type': 'dice', 'trigram': trigram}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNotification(title, body, timeout = 5000) {
|
||||||
|
let notif = new Notification(title, {'body': body, 'icon': "/static/tfjm.svg"})
|
||||||
|
if (timeout)
|
||||||
|
setTimeout(() => notif.close(), timeout)
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (document.location.hash) {
|
if (document.location.hash) {
|
||||||
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(elem => {
|
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(elem => {
|
||||||
@ -18,7 +33,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
for (let tournament of tournaments) {
|
for (let tournament of tournaments) {
|
||||||
let socket = new WebSocket(
|
let socket = new WebSocket(
|
||||||
'ws://' + window.location.host + '/ws/draw/' + tournament.id + '/'
|
(document.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host
|
||||||
|
+ '/ws/draw/' + tournament.id + '/'
|
||||||
)
|
)
|
||||||
sockets[tournament.id] = socket
|
sockets[tournament.id] = socket
|
||||||
|
|
||||||
@ -35,11 +51,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
setTimeout(() => wrapper.remove(), timeout)
|
setTimeout(() => wrapper.remove(), timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw_start(data) {
|
function setInfo(info) {
|
||||||
|
document.getElementById(`messages-${tournament.id}`).innerHTML = info
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawStart() {
|
||||||
document.getElementById(`banner-not-started-${tournament.id}`).classList.add('d-none')
|
document.getElementById(`banner-not-started-${tournament.id}`).classList.add('d-none')
|
||||||
document.getElementById(`draw-content-${tournament.id}`).classList.remove('d-none')
|
document.getElementById(`draw-content-${tournament.id}`).classList.remove('d-none')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDiceInfo(trigram, result) {
|
||||||
|
let elem = document.getElementById(`dice-${tournament.id}-${trigram}`)
|
||||||
|
if (result === null) {
|
||||||
|
elem.classList.remove('text-bg-success')
|
||||||
|
elem.classList.add('text-bg-warning')
|
||||||
|
elem.innerText = `${trigram} 🎲 ??`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elem.classList.remove('text-bg-warning')
|
||||||
|
elem.classList.add('text-bg-success')
|
||||||
|
elem.innerText = `${trigram} 🎲 ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDiceVisibility(visible) {
|
||||||
|
let div = document.getElementById(`launch-dice-${tournament.id}`)
|
||||||
|
if (visible)
|
||||||
|
div.classList.remove('d-none')
|
||||||
|
else
|
||||||
|
div.classList.add('d-none')
|
||||||
|
}
|
||||||
|
|
||||||
socket.addEventListener('message', e => {
|
socket.addEventListener('message', e => {
|
||||||
const data = JSON.parse(e.data)
|
const data = JSON.parse(e.data)
|
||||||
console.log(data)
|
console.log(data)
|
||||||
@ -48,8 +90,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
case 'alert':
|
case 'alert':
|
||||||
addMessage(data.message, data.alert_type)
|
addMessage(data.message, data.alert_type)
|
||||||
break
|
break
|
||||||
|
case 'notification':
|
||||||
|
showNotification(data.title, data.body)
|
||||||
|
case 'set_info':
|
||||||
|
setInfo(data.information)
|
||||||
|
break
|
||||||
case 'draw_start':
|
case 'draw_start':
|
||||||
draw_start(data)
|
drawStart()
|
||||||
|
break
|
||||||
|
case 'dice':
|
||||||
|
updateDiceInfo(data.team, data.result)
|
||||||
|
break
|
||||||
|
case 'dice_visibility':
|
||||||
|
updateDiceVisibility(data.visible)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -59,8 +113,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
socket.addEventListener('open', e => {})
|
socket.addEventListener('open', e => {})
|
||||||
|
|
||||||
document.getElementById('format-form-' + tournament.id)
|
let format_form = document.getElementById('format-form-' + tournament.id)
|
||||||
.addEventListener('submit', function (e) {
|
if (format_form !== null) {
|
||||||
|
format_form.addEventListener('submit', function (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
@ -69,4 +124,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
@ -31,7 +31,13 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
{% for participation in tournament.participations.all %}
|
{% for participation in tournament.participations.all %}
|
||||||
<div class="col-md-1">
|
<div class="col-md-1">
|
||||||
<div class="badge rounded-pill text-bg-warning">{{ participation.team.trigram }} 🎲 ??</div>
|
<div id="dice-{{ tournament.id }}-{{ participation.team.trigram }}"
|
||||||
|
class="badge rounded-pill text-bg-{% if participation.teamdraw_set.all.first.current.last_dice %}success{% else %}warning{% endif %}"
|
||||||
|
{% if request.user.registration.is_volunteer %}
|
||||||
|
onclick="drawDice({{ tournament.id }}, '{{ participation.team.trigram }}')"
|
||||||
|
{% endif %}>
|
||||||
|
{{ participation.team.trigram }} 🎲 {{ participation.teamdraw_set.all.first.current.last_dice|default:'??' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@ -149,12 +155,13 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="messages-{{ tournament.id }}" class="alert alert-info">
|
<div id="messages-{{ tournament.id }}" class="alert alert-info">
|
||||||
Information
|
{{ tournament.draw.information|safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="launch-dice-{{ tournament.id }}">
|
<div id="launch-dice-{{ tournament.id }}"
|
||||||
|
{% if tournament.draw.get_state != 'DICE_SELECT_POULES' %}{% if tournament.draw.get_state != 'DICE_ORDER_POULE' or user.registration.team.trigram not in tournament.draw.current_round.current_pool.trigrams %}class="d-none"{% endif %}{% endif %}>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button class="btn btn-lg disabled" style="font-size: 100pt">
|
<button class="btn btn-lg" style="font-size: 100pt" onclick="drawDice({{ tournament.id }})">
|
||||||
🎲
|
🎲
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user